Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-22 14:04:28 +01:00
parent 81b91f4139
commit f8c34fa5ee
22732 changed files with 4815320 additions and 2 deletions

View file

@ -0,0 +1,234 @@
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This is the version of the Android-specific Chromium linker that uses
// the crazy linker to load libraries.
// This source code *cannot* depend on anything from base/ or the C++
// STL, to keep the final library small, and avoid ugly dependency issues.
#include "base/android/linker/legacy_linker_jni.h"
#include <crazy_linker.h>
#include <fcntl.h>
#include <jni.h>
#include <limits.h>
#include <stddef.h>
#include <stdlib.h>
#include <unistd.h>
#include "base/android/linker/linker_jni.h"
namespace chromium_android_linker {
namespace {
// The linker uses a single crazy_context_t object created on demand.
// There is no need to protect this against concurrent access, locking
// is already handled on the Java side.
crazy_context_t* GetCrazyContext() {
static crazy_context_t* s_crazy_context = nullptr;
if (!s_crazy_context) {
// Create new context.
s_crazy_context = crazy_context_create();
// Ensure libraries located in the same directory as the linker
// can be loaded before system ones.
crazy_add_search_path_for_address(
reinterpret_cast<void*>(&GetCrazyContext));
}
return s_crazy_context;
}
// A scoped crazy_library_t that automatically closes the handle
// on scope exit, unless Release() has been called.
class ScopedLibrary {
public:
ScopedLibrary() : lib_(nullptr) {}
~ScopedLibrary() {
if (lib_)
crazy_library_close_with_context(lib_, GetCrazyContext());
}
crazy_library_t* Get() { return lib_; }
crazy_library_t** GetPtr() { return &lib_; }
crazy_library_t* Release() {
crazy_library_t* ret = lib_;
lib_ = nullptr;
return ret;
}
private:
crazy_library_t* lib_;
};
// Add a zip archive file path to the context's current search path
// list. Making it possible to load libraries directly from it.
JNI_GENERATOR_EXPORT bool
Java_org_chromium_base_library_1loader_LegacyLinker_nativeAddZipArchivePath(
JNIEnv* env,
jclass clazz,
jstring apk_path_obj) {
String apk_path(env, apk_path_obj);
char search_path[512];
snprintf(search_path, sizeof(search_path), "%s!lib/" CURRENT_ABI "/",
apk_path.c_str());
crazy_add_search_path(search_path);
return true;
}
// Load a library with the chromium linker. This will also call its
// JNI_OnLoad() method, which shall register its methods. Note that
// lazy native method resolution will _not_ work after this, because
// Dalvik uses the system's dlsym() which won't see the new library,
// so explicit registration is mandatory.
//
// |env| is the current JNI environment handle.
// |clazz| is the static class handle for org.chromium.base.Linker,
// and is ignored here.
// |library_name| is the library name (e.g. libfoo.so).
// |load_address| is an explicit load address.
// |lib_info_obj| is a LibInfo handle used to communicate information
// with the Java side.
// Return true on success.
JNI_GENERATOR_EXPORT bool
Java_org_chromium_base_library_1loader_LegacyLinker_nativeLoadLibrary(
JNIEnv* env,
jclass clazz,
jstring lib_name_obj,
jlong load_address,
jobject lib_info_obj) {
String library_name(env, lib_name_obj);
LOG_INFO("Called for %s, at address 0x%llx", library_name.c_str(),
static_cast<unsigned long long>(load_address));
crazy_context_t* context = GetCrazyContext();
if (!IsValidAddress(load_address)) {
LOG_ERROR("Invalid address 0x%llx",
static_cast<unsigned long long>(load_address));
return false;
}
// Set the desired load address (0 means randomize it).
crazy_context_set_load_address(context, static_cast<size_t>(load_address));
ScopedLibrary library;
if (!crazy_library_open(library.GetPtr(), library_name.c_str(), context)) {
return false;
}
crazy_library_info_t info;
if (!crazy_library_get_info(library.Get(), context, &info)) {
LOG_ERROR("Could not get library information for %s: %s",
library_name.c_str(), crazy_context_get_error(context));
return false;
}
// Release library object to keep it alive after the function returns.
library.Release();
s_lib_info_fields.SetLoadInfo(env, lib_info_obj, info.load_address,
info.load_size);
LOG_INFO("Success loading library %s", library_name.c_str());
return true;
}
JNI_GENERATOR_EXPORT jboolean
Java_org_chromium_base_library_1loader_LegacyLinker_nativeCreateSharedRelro(
JNIEnv* env,
jclass clazz,
jstring library_name,
jlong load_address,
jobject lib_info_obj) {
String lib_name(env, library_name);
LOG_INFO("Called for %s", lib_name.c_str());
if (!IsValidAddress(load_address)) {
LOG_ERROR("Invalid address 0x%llx",
static_cast<unsigned long long>(load_address));
return false;
}
ScopedLibrary library;
if (!crazy_library_find_by_name(lib_name.c_str(), library.GetPtr())) {
LOG_ERROR("Could not find %s", lib_name.c_str());
return false;
}
crazy_context_t* context = GetCrazyContext();
size_t relro_start = 0;
size_t relro_size = 0;
int relro_fd = -1;
if (!crazy_library_create_shared_relro(
library.Get(), context, static_cast<size_t>(load_address),
&relro_start, &relro_size, &relro_fd)) {
LOG_ERROR("Could not create shared RELRO sharing for %s: %s\n",
lib_name.c_str(), crazy_context_get_error(context));
return false;
}
s_lib_info_fields.SetRelroInfo(env, lib_info_obj, relro_start, relro_size,
relro_fd);
return true;
}
JNI_GENERATOR_EXPORT jboolean
Java_org_chromium_base_library_1loader_LegacyLinker_nativeUseSharedRelro(
JNIEnv* env,
jclass clazz,
jstring library_name,
jobject lib_info_obj) {
String lib_name(env, library_name);
LOG_INFO("Called for %s, lib_info_ref=%p", lib_name.c_str(), lib_info_obj);
ScopedLibrary library;
if (!crazy_library_find_by_name(lib_name.c_str(), library.GetPtr())) {
LOG_ERROR("Could not find %s", lib_name.c_str());
return false;
}
crazy_context_t* context = GetCrazyContext();
size_t relro_start = 0;
size_t relro_size = 0;
int relro_fd = -1;
s_lib_info_fields.GetRelroInfo(env, lib_info_obj, &relro_start, &relro_size,
&relro_fd);
LOG_INFO("library=%s relro start=%p size=%p fd=%d", lib_name.c_str(),
(void*)relro_start, (void*)relro_size, relro_fd);
if (!crazy_library_use_shared_relro(library.Get(), context, relro_start,
relro_size, relro_fd)) {
LOG_ERROR("Could not use shared RELRO for %s: %s", lib_name.c_str(),
crazy_context_get_error(context));
return false;
}
LOG_INFO("Library %s using shared RELRO section!", lib_name.c_str());
return true;
}
} // namespace
bool LegacyLinkerJNIInit(JavaVM* vm, JNIEnv* env) {
LOG_INFO("Entering");
// Save JavaVM* handle into linker, so that it can call JNI_OnLoad()
// automatically when loading libraries containing JNI entry points.
crazy_set_java_vm(vm, JNI_VERSION_1_4);
return true;
}
} // namespace chromium_android_linker

View file

@ -0,0 +1,21 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_ANDROID_LINKER_LEGACY_LINKER_JNI_H_
#define BASE_ANDROID_LINKER_LEGACY_LINKER_JNI_H_
#include <jni.h>
namespace chromium_android_linker {
// JNI_OnLoad() initialization hook for the legacy linker.
// Sets up JNI and other initializations for native linker code.
// |vm| is the Java VM handle passed to JNI_OnLoad().
// |env| is the current JNI environment handle.
// On success, returns true.
extern bool LegacyLinkerJNIInit(JavaVM* vm, JNIEnv* env);
} // namespace chromium_android_linker
#endif // BASE_ANDROID_LINKER_LEGACY_LINKER_JNI_H_

View file

@ -0,0 +1,150 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This is the Android-specific Chromium linker, a tiny shared library
// implementing a custom dynamic linker that can be used to load the
// real Chromium libraries.
// The main point of this linker is to be able to share the RELRO
// section of libchrome.so (or equivalent) between renderer processes.
// This source code *cannot* depend on anything from base/ or the C++
// STL, to keep the final library small, and avoid ugly dependency issues.
#include "base/android/linker/linker_jni.h"
#include <jni.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include "base/android/linker/legacy_linker_jni.h"
#include "base/android/linker/modern_linker_jni.h"
namespace chromium_android_linker {
// Variable containing LibInfo for the loaded library.
LibInfo_class s_lib_info_fields;
// Simple scoped UTF String class constructor.
String::String(JNIEnv* env, jstring str) {
size_ = env->GetStringUTFLength(str);
ptr_ = static_cast<char*>(::malloc(size_ + 1));
// Note: This runs before browser native code is loaded, and so cannot
// rely on anything from base/. This means that we must use
// GetStringUTFChars() and not base::android::ConvertJavaStringToUTF8().
//
// GetStringUTFChars() suffices because the only strings used here are
// paths to APK files or names of shared libraries, all of which are
// plain ASCII, defined and hard-coded by the Chromium Android build.
//
// For more: see
// https://crbug.com/508876
//
// Note: GetStringUTFChars() returns Java UTF-8 bytes. This is good
// enough for the linker though.
const char* bytes = env->GetStringUTFChars(str, nullptr);
::memcpy(ptr_, bytes, size_);
ptr_[size_] = '\0';
env->ReleaseStringUTFChars(str, bytes);
}
// Find the jclass JNI reference corresponding to a given |class_name|.
// |env| is the current JNI environment handle.
// On success, return true and set |*clazz|.
bool InitClassReference(JNIEnv* env, const char* class_name, jclass* clazz) {
*clazz = env->FindClass(class_name);
if (!*clazz) {
LOG_ERROR("Could not find class for %s", class_name);
return false;
}
return true;
}
// Initialize a jfieldID corresponding to the field of a given |clazz|,
// with name |field_name| and signature |field_sig|.
// |env| is the current JNI environment handle.
// On success, return true and set |*field_id|.
bool InitFieldId(JNIEnv* env,
jclass clazz,
const char* field_name,
const char* field_sig,
jfieldID* field_id) {
*field_id = env->GetFieldID(clazz, field_name, field_sig);
if (!*field_id) {
LOG_ERROR("Could not find ID for field '%s'", field_name);
return false;
}
LOG_INFO("Found ID %p for field '%s'", *field_id, field_name);
return true;
}
// Use Android ASLR to create a random address into which we expect to be
// able to load libraries. Note that this is probabilistic; we unmap the
// address we get from mmap and assume we can re-map into it later. This
// works the majority of the time. If it doesn't, client code backs out and
// then loads the library normally at any available address.
// |env| is the current JNI environment handle, and |clazz| a class.
// Returns the address selected by ASLR, or 0 on error.
JNI_GENERATOR_EXPORT jlong
Java_org_chromium_base_library_1loader_Linker_nativeGetRandomBaseLoadAddress(
JNIEnv* env,
jclass clazz) {
size_t bytes = kAddressSpaceReservationSize;
void* address =
mmap(nullptr, bytes, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (address == MAP_FAILED) {
LOG_INFO("Random base load address not determinable");
return 0;
}
munmap(address, bytes);
LOG_INFO("Random base load address is %p", address);
return static_cast<jlong>(reinterpret_cast<uintptr_t>(address));
}
namespace {
// JNI_OnLoad() initialization hook.
bool LinkerJNIInit(JavaVM* vm, JNIEnv* env) {
// Find LibInfo field ids.
LOG_INFO("Caching field IDs");
if (!s_lib_info_fields.Init(env)) {
return false;
}
return true;
}
// JNI_OnLoad() hook called when the linker library is loaded through
// the regular System.LoadLibrary) API. This shall save the Java VM
// handle and initialize LibInfo fields.
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
LOG_INFO("Entering");
// Get new JNIEnv
JNIEnv* env;
if (JNI_OK != vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_4)) {
LOG_ERROR("Could not create JNIEnv");
return -1;
}
// Initialize linker base and implementations.
if (!LinkerJNIInit(vm, env) || !LegacyLinkerJNIInit(vm, env) ||
!ModernLinkerJNIInit(vm, env)) {
return -1;
}
LOG_INFO("Done");
return JNI_VERSION_1_4;
}
} // namespace
} // namespace chromium_android_linker
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
return chromium_android_linker::JNI_OnLoad(vm, reserved);
}

View file

@ -0,0 +1,202 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This is the Android-specific Chromium linker, a tiny shared library
// implementing a custom dynamic linker that can be used to load the
// real Chromium libraries.
// The main point of this linker is to be able to share the RELRO
// section of libcontentshell.so (or equivalent) between the browser and
// renderer process.
// This source code *cannot* depend on anything from base/ or the C++
// STL, to keep the final library small, and avoid ugly dependency issues.
#ifndef BASE_ANDROID_LINKER_LINKER_JNI_H_
#define BASE_ANDROID_LINKER_LINKER_JNI_H_
#include <android/log.h>
#include <jni.h>
#include <stddef.h>
#include <stdlib.h>
#include "build/build_config.h"
// Set this to 1 to enable debug traces to the Android log.
// Note that LOG() from "base/logging.h" cannot be used, since it is
// in base/ which hasn't been loaded yet.
#define DEBUG 0
#define TAG "cr_ChromiumAndroidLinker"
#if DEBUG
#define LOG_INFO(FORMAT, ...) \
__android_log_print(ANDROID_LOG_INFO, TAG, "%s: " FORMAT, __FUNCTION__, \
##__VA_ARGS__)
#else
#define LOG_INFO(FORMAT, ...) ((void)0)
#endif
#define LOG_ERROR(FORMAT, ...) \
__android_log_print(ANDROID_LOG_ERROR, TAG, "%s: " FORMAT, __FUNCTION__, \
##__VA_ARGS__)
#define UNUSED __attribute__((unused))
#if defined(ARCH_CPU_X86)
// Dalvik JIT generated code doesn't guarantee 16-byte stack alignment on
// x86 - use force_align_arg_pointer to realign the stack at the JNI
// boundary. https://crbug.com/655248
#define JNI_GENERATOR_EXPORT \
extern "C" __attribute__((visibility("default"), force_align_arg_pointer))
#else
#define JNI_GENERATOR_EXPORT extern "C" __attribute__((visibility("default")))
#endif
#if defined(__arm__) && defined(__ARM_ARCH_7A__)
#define CURRENT_ABI "armeabi-v7a"
#elif defined(__arm__)
#define CURRENT_ABI "armeabi"
#elif defined(__i386__)
#define CURRENT_ABI "x86"
#elif defined(__mips__)
#define CURRENT_ABI "mips"
#elif defined(__x86_64__)
#define CURRENT_ABI "x86_64"
#elif defined(__aarch64__)
#define CURRENT_ABI "arm64-v8a"
#else
#error "Unsupported target abi"
#endif
namespace chromium_android_linker {
// Larger than the largest library we might attempt to load.
static const size_t kAddressSpaceReservationSize = 192 * 1024 * 1024;
// A simple scoped UTF String class that can be initialized from
// a Java jstring handle. Modeled like std::string, which cannot
// be used here.
class String {
public:
String(JNIEnv* env, jstring str);
inline ~String() { ::free(ptr_); }
inline const char* c_str() const { return ptr_ ? ptr_ : ""; }
inline size_t size() const { return size_; }
private:
char* ptr_;
size_t size_;
};
// Return true iff |address| is a valid address for the target CPU.
inline bool IsValidAddress(jlong address) {
return static_cast<jlong>(static_cast<size_t>(address)) == address;
}
// Find the jclass JNI reference corresponding to a given |class_name|.
// |env| is the current JNI environment handle.
// On success, return true and set |*clazz|.
extern bool InitClassReference(JNIEnv* env,
const char* class_name,
jclass* clazz);
// Initialize a jfieldID corresponding to the field of a given |clazz|,
// with name |field_name| and signature |field_sig|.
// |env| is the current JNI environment handle.
// On success, return true and set |*field_id|.
extern bool InitFieldId(JNIEnv* env,
jclass clazz,
const char* field_name,
const char* field_sig,
jfieldID* field_id);
// Initialize a jfieldID corresponding to the static field of a given |clazz|,
// with name |field_name| and signature |field_sig|.
// |env| is the current JNI environment handle.
// On success, return true and set |*field_id|.
extern bool InitStaticFieldId(JNIEnv* env,
jclass clazz,
const char* field_name,
const char* field_sig,
jfieldID* field_id);
// Use Android ASLR to create a random library load address.
// |env| is the current JNI environment handle, and |clazz| a class.
// Returns the address selected by ASLR.
extern jlong GetRandomBaseLoadAddress(JNIEnv* env, jclass clazz);
// A class used to model the field IDs of the org.chromium.base.Linker
// LibInfo inner class, used to communicate data with the Java side
// of the linker.
struct LibInfo_class {
jfieldID load_address_id;
jfieldID load_size_id;
jfieldID relro_start_id;
jfieldID relro_size_id;
jfieldID relro_fd_id;
// Initialize an instance.
bool Init(JNIEnv* env) {
jclass clazz;
if (!InitClassReference(
env, "org/chromium/base/library_loader/Linker$LibInfo", &clazz)) {
return false;
}
return InitFieldId(env, clazz, "mLoadAddress", "J", &load_address_id) &&
InitFieldId(env, clazz, "mLoadSize", "J", &load_size_id) &&
InitFieldId(env, clazz, "mRelroStart", "J", &relro_start_id) &&
InitFieldId(env, clazz, "mRelroSize", "J", &relro_size_id) &&
InitFieldId(env, clazz, "mRelroFd", "I", &relro_fd_id);
}
void SetLoadInfo(JNIEnv* env,
jobject library_info_obj,
size_t load_address,
size_t load_size) {
env->SetLongField(library_info_obj, load_address_id, load_address);
env->SetLongField(library_info_obj, load_size_id, load_size);
}
void SetRelroInfo(JNIEnv* env,
jobject library_info_obj,
size_t relro_start,
size_t relro_size,
int relro_fd) {
env->SetLongField(library_info_obj, relro_start_id, relro_start);
env->SetLongField(library_info_obj, relro_size_id, relro_size);
env->SetIntField(library_info_obj, relro_fd_id, relro_fd);
}
// Use this instance to convert a RelroInfo reference into
// a crazy_library_info_t.
void GetRelroInfo(JNIEnv* env,
jobject library_info_obj,
size_t* relro_start,
size_t* relro_size,
int* relro_fd) {
if (relro_start) {
*relro_start = static_cast<size_t>(
env->GetLongField(library_info_obj, relro_start_id));
}
if (relro_size) {
*relro_size = static_cast<size_t>(
env->GetLongField(library_info_obj, relro_size_id));
}
if (relro_fd) {
*relro_fd = env->GetIntField(library_info_obj, relro_fd_id);
}
}
};
// Variable containing LibInfo for the loaded library.
extern LibInfo_class s_lib_info_fields;
} // namespace chromium_android_linker
#endif // BASE_ANDROID_LINKER_LINKER_JNI_H_

View file

@ -0,0 +1,582 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Uses android_dlopen_ext() to share relocations.
// This source code *cannot* depend on anything from base/ or the C++
// STL, to keep the final library small, and avoid ugly dependency issues.
#include "base/android/linker/modern_linker_jni.h"
#include <dlfcn.h>
#include <errno.h>
#include <fcntl.h>
#include <jni.h>
#include <limits.h>
#include <link.h>
#include <stddef.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <limits>
#include <memory>
#include <android/dlext.h>
#include "base/android/linker/linker_jni.h"
// From //base/posix/eintr_wrapper.h, but we don't want to depend on //base.
#define HANDLE_EINTR(x) \
({ \
decltype(x) eintr_wrapper_result; \
do { \
eintr_wrapper_result = (x); \
} while (eintr_wrapper_result == -1 && errno == EINTR); \
eintr_wrapper_result; \
})
// Not defined on all platforms. As this linker is only supported on ARM32/64,
// x86/x86_64 and MIPS, page size is always 4k.
#if !defined(PAGE_SIZE)
#define PAGE_SIZE (1 << 12)
#define PAGE_MASK (~(PAGE_SIZE - 1))
#endif
#define PAGE_START(x) ((x)&PAGE_MASK)
#define PAGE_END(x) PAGE_START((x) + (PAGE_SIZE - 1))
extern "C" {
// <android/dlext.h> does not declare android_dlopen_ext() if __ANDROID_API__
// is smaller than 21, so declare it here as a weak function. This will allow
// detecting its availability at runtime. For API level 21 or higher, the
// attribute is ignored due to the previous declaration.
void* android_dlopen_ext(const char*, int, const android_dlextinfo*)
__attribute__((weak_import));
// This function is exported by the dynamic linker but never declared in any
// official header for some architecture/version combinations.
int dl_iterate_phdr(int (*cb)(dl_phdr_info* info, size_t size, void* data),
void* data) __attribute__((weak_import));
} // extern "C"
namespace chromium_android_linker {
namespace {
// Record of the Java VM passed to JNI_OnLoad().
static JavaVM* s_java_vm = nullptr;
// Helper class for anonymous memory mapping.
class ScopedAnonymousMmap {
public:
static ScopedAnonymousMmap ReserveAtAddress(void* address, size_t size);
~ScopedAnonymousMmap() {
if (addr_ && owned_)
munmap(addr_, size_);
}
ScopedAnonymousMmap(ScopedAnonymousMmap&& o) {
addr_ = o.addr_;
size_ = o.size_;
owned_ = o.owned_;
o.Release();
}
void* address() const { return addr_; }
size_t size() const { return size_; }
void Release() { owned_ = false; }
private:
ScopedAnonymousMmap() = default;
ScopedAnonymousMmap(void* addr, size_t size) : addr_(addr), size_(size) {}
private:
bool owned_ = true;
void* addr_ = nullptr;
size_t size_ = 0;
// Move only.
ScopedAnonymousMmap(const ScopedAnonymousMmap&) = delete;
ScopedAnonymousMmap& operator=(const ScopedAnonymousMmap&) = delete;
};
// Reserves an address space range, starting at |address|.
// If successful, returns a valid mapping, otherwise returns an empty one.
ScopedAnonymousMmap ScopedAnonymousMmap::ReserveAtAddress(void* address,
size_t size) {
void* actual_address =
mmap(address, size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (actual_address == MAP_FAILED) {
LOG_INFO("mmap failed: %s", strerror(errno));
return {};
}
if (actual_address && actual_address != address) {
LOG_ERROR("Failed to obtain fixed address for load");
return {};
}
return {actual_address, size};
}
// Starting with API level 26, the following functions from
// libandroid.so should be used to create shared memory regions.
//
// This ensures compatibility with post-Q versions of Android that may not rely
// on ashmem for shared memory.
//
// This is heavily inspired from //third_party/ashmem/ashmem-dev.c, which we
// cannot reference directly to avoid increasing binary size. Also, we don't
// need to support API level <26.
//
// *Not* threadsafe.
struct SharedMemoryFunctions {
SharedMemoryFunctions() {
library_handle = dlopen("libandroid.so", RTLD_NOW);
create = reinterpret_cast<CreateFunction>(
dlsym(library_handle, "ASharedMemory_create"));
set_protection = reinterpret_cast<SetProtectionFunction>(
dlsym(library_handle, "ASharedMemory_setProt"));
if (!create || !set_protection)
LOG_ERROR("Cannot get the shared memory functions from libandroid");
}
~SharedMemoryFunctions() {
if (library_handle)
dlclose(library_handle);
}
typedef int (*CreateFunction)(const char*, size_t);
typedef int (*SetProtectionFunction)(int fd, int prot);
CreateFunction create;
SetProtectionFunction set_protection;
void* library_handle;
};
// Metadata about a library loaded at a given |address|.
struct LoadedLibraryMetadata {
explicit LoadedLibraryMetadata(void* address)
: load_address(address),
load_size(0),
min_vaddr(0),
relro_start(0),
relro_size(0) {}
const void* load_address;
size_t load_size;
size_t min_vaddr;
size_t relro_start;
size_t relro_size;
};
// android_dlopen_ext() wrapper.
// Returns false if no android_dlopen_ext() is available, otherwise true with
// the return value from android_dlopen_ext() in |status|.
bool AndroidDlopenExt(const char* filename,
int flag,
const android_dlextinfo& extinfo,
void** status) {
if (!android_dlopen_ext) {
LOG_ERROR("android_dlopen_ext is not found");
return false;
}
LOG_INFO(
"android_dlopen_ext:"
" flags=0x%llx, reserved_addr=%p, reserved_size=%d",
static_cast<long long>(extinfo.flags), extinfo.reserved_addr,
static_cast<int>(extinfo.reserved_size));
*status = android_dlopen_ext(filename, flag, &extinfo);
return true;
}
// Callback for dl_iterate_phdr(). Read phdrs to identify whether or not
// this library's load address matches the |load_address| passed in
// |data|. If yes, fills the metadata we care about in |data|.
//
// A non-zero return value terminates iteration.
int FindLoadedLibraryMetadata(dl_phdr_info* info,
size_t size UNUSED,
void* data) {
auto* metadata = reinterpret_cast<LoadedLibraryMetadata*>(data);
// Use max and min vaddr to compute the library's load size.
auto min_vaddr = std::numeric_limits<ElfW(Addr)>::max();
ElfW(Addr) max_vaddr = 0;
ElfW(Addr) min_relro_vaddr = ~0;
ElfW(Addr) max_relro_vaddr = 0;
bool is_matching = false;
for (int i = 0; i < info->dlpi_phnum; ++i) {
const ElfW(Phdr)* phdr = &info->dlpi_phdr[i];
switch (phdr->p_type) {
case PT_LOAD: {
// See if this segment's load address matches what we passed to
// android_dlopen_ext as extinfo.reserved_addr.
//
// Here and below, the virtual address in memory is computed by
// address == info->dlpi_addr + program_header->p_vaddr
// that is, the p_vaddr fields is relative to the object base address.
// See dl_iterate_phdr(3) for details.
void* load_addr =
reinterpret_cast<void*>(info->dlpi_addr + phdr->p_vaddr);
// Matching is based on the load address, since we have no idea
// where the relro segment is.
if (load_addr == metadata->load_address)
is_matching = true;
if (phdr->p_vaddr < min_vaddr)
min_vaddr = phdr->p_vaddr;
if (phdr->p_vaddr + phdr->p_memsz > max_vaddr)
max_vaddr = phdr->p_vaddr + phdr->p_memsz;
} break;
case PT_GNU_RELRO:
min_relro_vaddr = PAGE_START(phdr->p_vaddr);
max_relro_vaddr = phdr->p_vaddr + phdr->p_memsz;
break;
default:
break;
}
}
// If this library matches what we seek, return its load size.
if (is_matching) {
int page_size = sysconf(_SC_PAGESIZE);
if (page_size != PAGE_SIZE)
abort();
metadata->load_size = PAGE_END(max_vaddr) - PAGE_START(min_vaddr);
metadata->min_vaddr = min_vaddr;
metadata->relro_size =
PAGE_END(max_relro_vaddr) - PAGE_START(min_relro_vaddr);
metadata->relro_start = info->dlpi_addr + PAGE_START(min_relro_vaddr);
return true;
}
return false;
}
// Creates an android_dlextinfo struct so that a library is loaded inside the
// space referenced by |mapping|.
std::unique_ptr<android_dlextinfo> MakeAndroidDlextinfo(
const ScopedAnonymousMmap& mapping) {
auto info = std::make_unique<android_dlextinfo>();
memset(info.get(), 0, sizeof(*info));
info->flags = ANDROID_DLEXT_RESERVED_ADDRESS;
info->reserved_addr = mapping.address();
info->reserved_size = mapping.size();
return info;
}
// Copies the current relocations into a shared-memory file, and uses this file
// as the relocations.
//
// Returns true for success, and populate |fd| with the relocations's fd in this
// case.
bool CopyAndRemapRelocations(const LoadedLibraryMetadata& metadata, int* fd) {
LOG_INFO("Entering");
void* relro_addr = reinterpret_cast<void*>(metadata.relro_start);
SharedMemoryFunctions fns;
if (!fns.create)
return false;
int shared_mem_fd = fns.create("cr_relro", metadata.relro_size);
if (shared_mem_fd == -1) {
LOG_ERROR("Cannot create the shared memory file");
return false;
}
int rw_flags = PROT_READ | PROT_WRITE;
fns.set_protection(shared_mem_fd, rw_flags);
void* relro_copy_addr = mmap(nullptr, metadata.relro_size, rw_flags,
MAP_SHARED, shared_mem_fd, 0);
if (relro_copy_addr == MAP_FAILED) {
LOG_ERROR("Cannot mmap() space for the copy");
close(shared_mem_fd);
return false;
}
memcpy(relro_copy_addr, relro_addr, metadata.relro_size);
int retval = mprotect(relro_copy_addr, metadata.relro_size, PROT_READ);
if (retval) {
LOG_ERROR("Cannot call mprotect()");
close(shared_mem_fd);
munmap(relro_copy_addr, metadata.relro_size);
return false;
}
void* new_addr =
mremap(relro_copy_addr, metadata.relro_size, metadata.relro_size,
MREMAP_MAYMOVE | MREMAP_FIXED, relro_addr);
if (new_addr != relro_addr) {
LOG_ERROR("mremap() error");
close(shared_mem_fd);
munmap(relro_copy_addr, metadata.relro_size);
return false;
}
*fd = shared_mem_fd;
return true;
}
// Gathers metadata about the library loaded at |addr|.
//
// Returns true for success.
bool GetLoadedLibraryMetadata(LoadedLibraryMetadata* metadata) {
LOG_INFO("Called for %p", metadata->load_address);
if (!dl_iterate_phdr) {
LOG_ERROR("No dl_iterate_phdr() found");
return false;
}
int status = dl_iterate_phdr(&FindLoadedLibraryMetadata, metadata);
if (!status) {
LOG_ERROR("Failed to find library at address %p", metadata->load_address);
return false;
}
LOG_INFO("Relro start address = %p, size = %d",
reinterpret_cast<void*>(metadata->relro_start),
static_cast<int>(metadata->relro_size));
return true;
}
// Resizes the address space reservation to the actual required size.
// Failure here is only a warning, as at worst this wastes virtual address
// space, not actual memory.
void ResizeMapping(const ScopedAnonymousMmap& mapping,
const LoadedLibraryMetadata& metadata) {
// Trim the reservation mapping to match the library's actual size. Failure
// to resize is not a fatal error. At worst we lose a portion of virtual
// address space that we might otherwise have recovered. Note that trimming
// the mapping here requires that we have already released the scoped
// mapping.
const uintptr_t uintptr_addr = reinterpret_cast<uintptr_t>(mapping.address());
if (mapping.size() > metadata.load_size) {
// Unmap the part of the reserved address space that is beyond the end of
// the loaded library data.
void* unmap = reinterpret_cast<void*>(uintptr_addr + metadata.load_size);
const size_t length = mapping.size() - metadata.load_size;
if (munmap(unmap, length) == -1) {
LOG_ERROR("WARNING: unmap of %d bytes at %p failed: %s",
static_cast<int>(length), unmap, strerror(errno));
}
} else {
LOG_ERROR("WARNING: library reservation was too small");
}
}
// Calls JNI_OnLoad() in the library referenced by |handle|.
// Returns true for success.
bool CallJniOnLoad(void* handle) {
LOG_INFO("Entering");
// Locate and if found then call the loaded library's JNI_OnLoad() function.
using JNI_OnLoadFunctionPtr = int (*)(void* vm, void* reserved);
auto jni_onload =
reinterpret_cast<JNI_OnLoadFunctionPtr>(dlsym(handle, "JNI_OnLoad"));
if (jni_onload != nullptr) {
// Check that JNI_OnLoad returns a usable JNI version.
int jni_version = (*jni_onload)(s_java_vm, nullptr);
if (jni_version < JNI_VERSION_1_4) {
LOG_ERROR("JNI version is invalid: %d", jni_version);
return false;
}
}
return true;
}
// Load the library at |path| at address |wanted_address| if possible, and
// creates a file with relro at |relocations_path|.
//
// In case of success, returns a readonly file descriptor to the relocations,
// otherwise returns -1.
int LoadCreateSharedRelocations(const String& path, void* wanted_address) {
LOG_INFO("Entering");
ScopedAnonymousMmap mapping = ScopedAnonymousMmap::ReserveAtAddress(
wanted_address, kAddressSpaceReservationSize);
if (!mapping.address())
return -1;
std::unique_ptr<android_dlextinfo> dlextinfo = MakeAndroidDlextinfo(mapping);
void* handle = nullptr;
if (!AndroidDlopenExt(path.c_str(), RTLD_NOW, *dlextinfo, &handle)) {
LOG_ERROR("android_dlopen_ext() error");
return -1;
}
if (handle == nullptr) {
LOG_ERROR("android_dlopen_ext: %s", dlerror());
return -1;
}
mapping.Release();
LoadedLibraryMetadata metadata{mapping.address()};
bool ok = GetLoadedLibraryMetadata(&metadata);
int relro_fd = -1;
if (ok) {
ResizeMapping(mapping, metadata);
CopyAndRemapRelocations(metadata, &relro_fd);
}
if (!CallJniOnLoad(handle))
return false;
return relro_fd;
}
// Load the library at |path| at address |wanted_address| if possible, and
// uses the relocations in |relocations_fd| if possible.
bool LoadUseSharedRelocations(const String& path,
void* wanted_address,
int relocations_fd) {
LOG_INFO("Entering");
ScopedAnonymousMmap mapping = ScopedAnonymousMmap::ReserveAtAddress(
wanted_address, kAddressSpaceReservationSize);
if (!mapping.address())
return false;
std::unique_ptr<android_dlextinfo> dlextinfo = MakeAndroidDlextinfo(mapping);
void* handle = nullptr;
if (!AndroidDlopenExt(path.c_str(), RTLD_NOW, *dlextinfo, &handle)) {
LOG_ERROR("No android_dlopen_ext function found");
return false;
}
if (handle == nullptr) {
LOG_ERROR("android_dlopen_ext: %s", dlerror());
return false;
}
mapping.Release();
LoadedLibraryMetadata metadata{mapping.address()};
bool ok = GetLoadedLibraryMetadata(&metadata);
if (!ok) {
LOG_ERROR("Cannot get library's metadata");
return false;
}
ResizeMapping(mapping, metadata);
void* shared_relro_mapping_address = mmap(
nullptr, metadata.relro_size, PROT_READ, MAP_SHARED, relocations_fd, 0);
if (shared_relro_mapping_address == MAP_FAILED) {
LOG_ERROR("Cannot map the relocations");
return false;
}
void* current_relro_address = reinterpret_cast<void*>(metadata.relro_start);
int retval = memcmp(shared_relro_mapping_address, current_relro_address,
metadata.relro_size);
if (!retval) {
void* new_addr = mremap(shared_relro_mapping_address, metadata.relro_size,
metadata.relro_size, MREMAP_MAYMOVE | MREMAP_FIXED,
current_relro_address);
if (new_addr != current_relro_address) {
LOG_ERROR("Cannot call mremap()");
munmap(shared_relro_mapping_address, metadata.relro_size);
return false;
}
} else {
munmap(shared_relro_mapping_address, metadata.relro_size);
LOG_ERROR("Relocations are not identical, giving up.");
}
if (!CallJniOnLoad(handle))
return false;
return true;
}
bool LoadNoSharedRelocations(const String& path) {
void* handle = dlopen(path.c_str(), RTLD_NOW);
if (!handle) {
LOG_ERROR("dlopen: %s", dlerror());
return false;
}
if (!CallJniOnLoad(handle))
return false;
return true;
}
} // namespace
JNI_GENERATOR_EXPORT jboolean
Java_org_chromium_base_library_1loader_ModernLinker_nativeLoadLibraryCreateRelros(
JNIEnv* env,
jclass clazz,
jstring jdlopen_ext_path,
jlong load_address,
jobject lib_info_obj) {
LOG_INFO("Entering");
String library_path(env, jdlopen_ext_path);
if (!IsValidAddress(load_address)) {
LOG_ERROR("Invalid address 0x%llx", static_cast<long long>(load_address));
return false;
}
void* address = reinterpret_cast<void*>(load_address);
int fd = LoadCreateSharedRelocations(library_path, address);
if (fd == -1)
return false;
// Note the shared RELRO fd in the supplied libinfo object. In this
// implementation the RELRO start is set to the library's load address,
// and the RELRO size is unused.
const size_t cast_addr = reinterpret_cast<size_t>(address);
s_lib_info_fields.SetRelroInfo(env, lib_info_obj, cast_addr, 0, fd);
return true;
}
JNI_GENERATOR_EXPORT jboolean
Java_org_chromium_base_library_1loader_ModernLinker_nativeLoadLibraryUseRelros(
JNIEnv* env,
jclass clazz,
jstring jdlopen_ext_path,
jlong load_address,
jint relro_fd) {
LOG_INFO("Entering");
String library_path(env, jdlopen_ext_path);
if (!IsValidAddress(load_address)) {
LOG_ERROR("Invalid address 0x%llx", static_cast<long long>(load_address));
return false;
}
void* address = reinterpret_cast<void*>(load_address);
return LoadUseSharedRelocations(library_path, address, relro_fd);
}
JNI_GENERATOR_EXPORT jboolean
Java_org_chromium_base_library_1loader_ModernLinker_nativeLoadLibraryNoRelros(
JNIEnv* env,
jclass clazz,
jstring jdlopen_ext_path) {
String library_path(env, jdlopen_ext_path);
return LoadNoSharedRelocations(library_path);
}
bool ModernLinkerJNIInit(JavaVM* vm, JNIEnv* env) {
s_java_vm = vm;
return true;
}
} // namespace chromium_android_linker

View file

@ -0,0 +1,21 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_ANDROID_LINKER_MODERN_LINKER_JNI_H_
#define BASE_ANDROID_LINKER_MODERN_LINKER_JNI_H_
#include <jni.h>
namespace chromium_android_linker {
// JNI_OnLoad() initialization hook for the modern linker.
// Sets up JNI and other initializations for native linker code.
// |vm| is the Java VM handle passed to JNI_OnLoad().
// |env| is the current JNI environment handle.
// On success, returns true.
extern bool ModernLinkerJNIInit(JavaVM* vm, JNIEnv* env);
} // namespace chromium_android_linker
#endif // BASE_ANDROID_LINKER_MODERN_LINKER_JNI_H_