#ifdef UNSAFE_BUFFERS_BUILD
#pragma allow_unsafe_buffers
#endif
#include <android/dlext.h>
#include <dlfcn.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <jni.h>
#include <link.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <memory>
#include <android/log.h>
#include <sys/system_properties.h>
#define LOG_E(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "linker-jni", __VA_ARGS__))
#include "base/android/linker/ashmem.h"
#include "base/android/linker/linker_jni.h"
namespace chromium_android_linker {
namespace {
LibInfo_class s_lib_info_fields;
RelroSharingStatus s_relro_sharing_status = RelroSharingStatus::NOT_ATTEMPTED;
JavaVM* s_java_vm = nullptr;
size_t GetPageSize() {
return sysconf(_SC_PAGESIZE);
}
void ReserveAddressWithHint(uintptr_t hint, uintptr_t* address, size_t* size) {
void* ptr = reinterpret_cast<void*>(hint);
void* new_ptr = mmap(ptr, kAddressSpaceReservationSize, PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (new_ptr == MAP_FAILED) {
PLOG_ERROR("mmap");
*address = 0;
} else if ((hint != 0) && (new_ptr != ptr)) {
LOG_ERROR("Address range starting at 0x%" PRIxPTR " was not free to use",
hint);
munmap(new_ptr, kAddressSpaceReservationSize);
*address = 0;
} else {
*address = reinterpret_cast<uintptr_t>(new_ptr);
*size = kAddressSpaceReservationSize;
LOG_INFO("Reserved region at address: 0x%" PRIxPTR ", size: 0x%zu",
*address, *size);
}
}
bool ScanRegionInBuffer(const char* buf,
size_t length,
uintptr_t* out_address,
size_t* out_size) {
const char* position = strstr(buf, "[anon:libwebview reservation]");
if (!position) {
return false;
}
const char* line_start = position;
while (line_start > buf) {
line_start--;
if (*line_start == '\n') {
line_start++;
break;
}
}
uintptr_t vma_start, vma_end;
char permissions[5] = {'\0'};
if (sscanf(line_start, "%" SCNxPTR "-%" SCNxPTR " %4c", &vma_start, &vma_end,
permissions) < 3) {
return false;
}
if (strcmp(permissions, "---p")) {
return false;
}
const size_t kPageSize = GetPageSize();
if (vma_start % kPageSize || vma_end % kPageSize) {
return false;
}
*out_address = static_cast<uintptr_t>(vma_start);
*out_size = vma_end - vma_start;
return true;
}
bool FindRegionInOpenFile(int fd, uintptr_t* out_address, size_t* out_size) {
constexpr size_t kMaxLineLength = 256;
const size_t kPageSize = GetPageSize();
const size_t kReadSize = kPageSize;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wvla-extension"
char buf[kReadSize + kMaxLineLength + 1];
#pragma clang diagnostic pop
buf[kReadSize + kMaxLineLength] = '\0';
size_t pos = 0;
size_t bytes_requested = kReadSize + kMaxLineLength;
bool reached_end = false;
while (true) {
size_t bytes_read = 0;
do {
ssize_t rv = HANDLE_EINTR(
read(fd, buf + pos + bytes_read, bytes_requested - bytes_read));
if (rv == 0) {
reached_end = true;
} else if (rv < 0) {
PLOG_ERROR("read to find webview reservation");
return false;
}
bytes_read += rv;
} while (!reached_end && (bytes_read < bytes_requested));
if (ScanRegionInBuffer(buf, pos + bytes_read, out_address, out_size)) {
return true;
}
if (reached_end) {
return false;
}
memcpy(buf, buf + kReadSize, kMaxLineLength);
pos = kMaxLineLength;
bytes_requested = kReadSize;
}
}
bool AndroidDlopenExt(void* mapping_start,
size_t mapping_size,
const char* filename,
void** handle) {
android_dlextinfo dlextinfo = {};
dlextinfo.flags = ANDROID_DLEXT_RESERVED_ADDRESS;
dlextinfo.reserved_addr = mapping_start;
dlextinfo.reserved_size = mapping_size;
LOG_INFO(
"android_dlopen_ext:"
" flags=0x%" PRIx64 ", reserved_addr=%p, reserved_size=%zu",
dlextinfo.flags, dlextinfo.reserved_addr, dlextinfo.reserved_size);
void* rv = android_dlopen_ext(filename, RTLD_NOW, &dlextinfo);
if (rv == nullptr) {
LOG_ERROR("android_dlopen_ext: %s", dlerror());
return false;
}
*handle = rv;
return true;
}
void TrimMapping(uintptr_t address, size_t old_size, size_t new_size) {
if (old_size <= new_size) {
LOG_ERROR("WARNING: library reservation was too small");
} else {
const uintptr_t unmap = address + new_size;
const size_t length = old_size - new_size;
munmap(reinterpret_cast<void*>(unmap), length);
}
}
bool CallJniOnLoad(void* handle) {
LOG_INFO("Entering");
using JNI_OnLoadFunctionPtr = int (*)(void* vm, void* reserved);
auto jni_onload =
reinterpret_cast<JNI_OnLoadFunctionPtr>(dlsym(handle, "JNI_OnLoad"));
if (jni_onload != nullptr) {
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;
}
}
LOG_INFO("Done");
return true;
}
}
String::String(JNIEnv* env, jstring str) {
size_ = env->GetStringUTFLength(str);
ptr_ = static_cast<char*>(::malloc(size_ + 1));
const char* bytes = env->GetStringUTFChars(str, nullptr);
::memcpy(ptr_, bytes, size_);
ptr_[size_] = '\0';
env->ReleaseStringUTFChars(str, bytes);
}
bool IsValidAddress(jlong address) {
bool result = static_cast<jlong>(static_cast<uintptr_t>(address)) == address;
if (!result) {
LOG_ERROR("Invalid address 0x%" PRIx64, static_cast<uint64_t>(address));
}
return result;
}
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;
}
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;
}
bool FindWebViewReservation(uintptr_t* out_address, size_t* out_size) {
const char kFileName[] = "/proc/self/maps";
int fd = HANDLE_EINTR(open(kFileName, O_RDONLY));
if (fd == -1) {
PLOG_ERROR("open %s", kFileName);
return false;
}
bool result = FindRegionInOpenFile(fd, out_address, out_size);
close(fd);
return result;
}
void NativeLibInfo::ExportLoadInfoToJava() const {
if (!env_) {
return;
}
s_lib_info_fields.SetLoadInfo(env_, java_object_, load_address_, load_size_);
}
void NativeLibInfo::ExportRelroInfoToJava() const {
if (!env_) {
return;
}
s_lib_info_fields.SetRelroInfo(env_, java_object_, relro_start_, relro_size_,
relro_fd_);
}
void NativeLibInfo::CloseRelroFd() {
if (relro_fd_ == kInvalidFd) {
return;
}
close(relro_fd_);
relro_fd_ = kInvalidFd;
}
bool NativeLibInfo::FindRelroAndLibraryRangesInElf() {
LOG_INFO("Called for 0x%" PRIxPTR, load_address_);
if (memcmp(reinterpret_cast<void*>(load_address_), ELFMAG, SELFMAG) != 0) {
LOG_ERROR("Wrong magic number");
return false;
}
auto class_type = *reinterpret_cast<uint8_t*>(load_address_ + EI_CLASS);
if (class_type == ELFCLASS32) {
LOG_INFO("ELFCLASS32");
} else if (class_type == ELFCLASS64) {
LOG_INFO("ELFCLASS64");
} else {
LOG_ERROR("Could not determine ELF class");
return false;
}
auto min_vaddr = std::numeric_limits<ElfW(Addr)>::max();
auto min_relro_vaddr = min_vaddr;
ElfW(Addr) max_vaddr = 0;
ElfW(Addr) max_relro_vaddr = 0;
const auto* ehdr = reinterpret_cast<const ElfW(Ehdr)*>(load_address_);
const auto* phdrs =
reinterpret_cast<const ElfW(Phdr)*>(load_address_ + ehdr->e_phoff);
const size_t kPageSize = GetPageSize();
for (int i = 0; i < ehdr->e_phnum; i++) {
const ElfW(Phdr)* phdr = &phdrs[i];
switch (phdr->p_type) {
case PT_LOAD:
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 = PageStart(kPageSize, phdr->p_vaddr);
max_relro_vaddr = phdr->p_vaddr + phdr->p_memsz;
if (min_relro_vaddr < min_vaddr) {
min_vaddr = min_relro_vaddr;
}
if (max_vaddr < max_relro_vaddr) {
max_vaddr = max_relro_vaddr;
}
break;
default:
break;
}
}
load_size_ = PageEnd(kPageSize, max_vaddr) - PageStart(kPageSize, min_vaddr);
relro_size_ = PageEnd(kPageSize, max_relro_vaddr) -
PageStart(kPageSize, min_relro_vaddr);
relro_start_ = load_address_ + PageStart(kPageSize, min_relro_vaddr);
return true;
}
bool NativeLibInfo::LoadWithDlopenExt(const String& path, void** handle) {
LOG_INFO("Entering");
if (!load_address_) {
return false;
}
size_t reservation_size = load_size_;
auto* address = reinterpret_cast<void*>(load_address_);
void* local_handle = nullptr;
if (!AndroidDlopenExt(address, reservation_size, path.c_str(),
&local_handle)) {
LOG_ERROR("android_dlopen_ext() error");
munmap(address, load_size_);
return false;
}
if (!FindRelroAndLibraryRangesInElf()) {
LOG_ERROR("Could not find RELRO in the loaded library: %s", path.c_str());
abort();
}
TrimMapping(load_address_, reservation_size, load_size_);
*handle = local_handle;
return true;
}
bool NativeLibInfo::CreateSharedRelroFd() {
LOG_INFO("Entering");
if (!relro_start_ || !relro_size_) {
LOG_ERROR("RELRO region is not populated");
return false;
}
int shared_mem_fd = SharedMemoryRegionCreate("cr_relro", relro_size_);
if (shared_mem_fd == -1) {
LOG_ERROR("Cannot create the shared memory file");
return false;
}
int rw_flags = PROT_READ | PROT_WRITE;
SharedMemoryRegionSetProtectionFlags(shared_mem_fd, rw_flags);
void* relro_copy_addr =
mmap(nullptr, relro_size_, rw_flags, MAP_SHARED, shared_mem_fd, 0);
if (relro_copy_addr == MAP_FAILED) {
PLOG_ERROR("failed to allocate space for copying RELRO");
close(shared_mem_fd);
return false;
}
void* relro_addr = reinterpret_cast<void*>(relro_start_);
memcpy(relro_copy_addr, relro_addr, relro_size_);
munmap(relro_copy_addr, relro_size_);
if (SharedMemoryRegionSetProtectionFlags(shared_mem_fd, PROT_READ) == -1) {
LOG_ERROR("Failed to set the RELRO FD as read-only.");
close(shared_mem_fd);
return false;
}
relro_fd_ = shared_mem_fd;
return true;
}
bool NativeLibInfo::ReplaceRelroWithSharedOne() const {
LOG_INFO("Entering");
if (relro_fd_ == -1 || !relro_start_ || !relro_size_) {
LOG_ERROR("Replacement RELRO not ready");
return false;
}
void* new_addr = mmap(reinterpret_cast<void*>(relro_start_), relro_size_,
PROT_READ, MAP_FIXED | MAP_SHARED, relro_fd_, 0);
if (new_addr == MAP_FAILED) {
PLOG_ERROR("mmap: replace RELRO");
return false;
}
LOG_INFO("Replaced RELRO at 0x%" PRIxPTR, relro_start_);
return true;
}
NativeLibInfo::NativeLibInfo(JNIEnv* env, jobject java_object)
: env_(env), java_object_(java_object) {}
bool NativeLibInfo::CopyFromJavaObject() {
if (!env_) {
return false;
}
if (!s_lib_info_fields.GetLoadInfo(env_, java_object_, &load_address_,
&load_size_)) {
return false;
}
s_lib_info_fields.GetRelroInfo(env_, java_object_, &relro_start_,
&relro_size_, &relro_fd_);
return true;
}
bool NativeLibInfo::LoadLibrary(const String& library_path,
bool spawn_relro_region) {
void* handle = nullptr;
if (!LoadWithDlopenExt(library_path, &handle)) {
LOG_ERROR("Failed to load native library: %s", library_path.c_str());
return false;
}
if (!CallJniOnLoad(handle)) {
return false;
}
ExportLoadInfoToJava();
if (!spawn_relro_region) {
return true;
}
if (!CreateSharedRelroFd()) {
LOG_ERROR("Failed to create shared RELRO");
return false;
}
if (!ReplaceRelroWithSharedOne()) {
LOG_ERROR("Failed to convert RELRO to shared memory");
CloseRelroFd();
return false;
}
LOG_INFO(
"Created and converted RELRO to shared memory: relro_fd=%d, "
"relro_start=0x%" PRIxPTR,
relro_fd_, relro_start_);
ExportRelroInfoToJava();
return true;
}
bool NativeLibInfo::RelroIsIdentical(
const NativeLibInfo& other_lib_info) const {
if (other_lib_info.relro_start_ != relro_start_ ||
other_lib_info.relro_size_ != relro_size_ ||
other_lib_info.load_size_ != load_size_) {
LOG_ERROR("Incoming RELRO size does not match RELRO of the loaded library");
return false;
}
void* shared_relro_address =
mmap(nullptr, other_lib_info.relro_size_, PROT_READ, MAP_SHARED,
other_lib_info.relro_fd_, 0);
if (shared_relro_address == MAP_FAILED) {
PLOG_ERROR("mmap: check RELRO is identical");
return false;
}
void* current_relro_address = reinterpret_cast<void*>(relro_start_);
int not_equal =
memcmp(shared_relro_address, current_relro_address, relro_size_);
munmap(shared_relro_address, relro_size_);
if (not_equal) {
LOG_ERROR("Relocations are not identical, giving up.");
return false;
}
return true;
}
bool NativeLibInfo::CompareRelroAndReplaceItBy(
const NativeLibInfo& other_lib_info) {
if (other_lib_info.relro_fd_ == -1) {
LOG_ERROR("No shared region to use");
s_relro_sharing_status = RelroSharingStatus::EXTERNAL_RELRO_FD_NOT_PROVIDED;
return false;
}
if (load_address_ == 0) {
LOG_ERROR("Load address reset. Second attempt to load the library?");
s_relro_sharing_status = RelroSharingStatus::EXTERNAL_LOAD_ADDRESS_RESET;
return false;
}
if (!FindRelroAndLibraryRangesInElf()) {
LOG_ERROR("Could not find RELRO from externally provided address: 0x%p",
reinterpret_cast<void*>(other_lib_info.load_address_));
s_relro_sharing_status = RelroSharingStatus::EXTERNAL_RELRO_NOT_FOUND;
return false;
}
if (!RelroIsIdentical(other_lib_info)) {
LOG_ERROR("RELRO is not identical");
s_relro_sharing_status = RelroSharingStatus::NOT_IDENTICAL;
return false;
}
if (!other_lib_info.ReplaceRelroWithSharedOne()) {
LOG_ERROR("Failed to use relro_fd");
s_relro_sharing_status = RelroSharingStatus::REMAP_FAILED;
return false;
}
s_relro_sharing_status = RelroSharingStatus::SHARED;
return true;
}
bool NativeLibInfo::CreateSharedRelroFdForTesting() {
return CreateSharedRelroFd();
}
JNI_ZERO_BOUNDARY_EXPORT void
Java_org_chromium_base_library_1loader_LinkerJni_nativeFindMemoryRegionAtRandomAddress(
JNIEnv* env,
jclass clazz,
jobject lib_info_obj) {
LOG_INFO("Entering");
uintptr_t address;
size_t size;
ReserveAddressWithHint(0, &address, &size);
s_lib_info_fields.SetLoadInfo(env, lib_info_obj, address, size);
}
JNI_ZERO_BOUNDARY_EXPORT void
Java_org_chromium_base_library_1loader_LinkerJni_nativeReserveMemoryForLibrary(
JNIEnv* env,
jclass clazz,
jobject lib_info_obj) {
LOG_INFO("Entering");
uintptr_t address;
size_t size;
s_lib_info_fields.GetLoadInfo(env, lib_info_obj, &address, &size);
ReserveAddressWithHint(address, &address, &size);
s_lib_info_fields.SetLoadInfo(env, lib_info_obj, address, size);
}
JNI_ZERO_BOUNDARY_EXPORT jboolean
Java_org_chromium_base_library_1loader_LinkerJni_nativeFindRegionReservedByWebViewZygote(
JNIEnv* env,
jclass clazz,
jobject lib_info_obj) {
LOG_INFO("Entering");
uintptr_t address;
size_t size;
if (!FindWebViewReservation(&address, &size)) {
return false;
}
s_lib_info_fields.SetLoadInfo(env, lib_info_obj, address, size);
return true;
}
JNI_ZERO_BOUNDARY_EXPORT jboolean
Java_org_chromium_base_library_1loader_LinkerJni_nativeLoadLibrary(
JNIEnv* env,
jclass clazz,
jstring jdlopen_ext_path,
jobject lib_info_obj,
jboolean spawn_relro_region) {
LOG_INFO("Entering");
NativeLibInfo lib_info = {env, lib_info_obj};
if (!lib_info.CopyFromJavaObject()) {
return false;
}
String library_path(env, jdlopen_ext_path);
if (!lib_info.LoadLibrary(library_path, spawn_relro_region)) {
return false;
}
return true;
}
JNI_ZERO_BOUNDARY_EXPORT jboolean
Java_org_chromium_base_library_1loader_LinkerJni_nativeUseRelros(
JNIEnv* env,
jclass clazz,
jlong local_load_address,
jobject remote_lib_info_obj) {
LOG_INFO("Entering");
NativeLibInfo incoming_lib_info = {env, remote_lib_info_obj};
if (!incoming_lib_info.CopyFromJavaObject()) {
s_relro_sharing_status = RelroSharingStatus::CORRUPTED_IN_JAVA;
return false;
}
NativeLibInfo lib_info = {nullptr, nullptr};
lib_info.set_load_address(static_cast<uintptr_t>(local_load_address));
if (!lib_info.CompareRelroAndReplaceItBy(incoming_lib_info)) {
return false;
}
return true;
}
JNI_ZERO_BOUNDARY_EXPORT jint
Java_org_chromium_base_library_1loader_LinkerJni_nativeGetRelroSharingResult(
JNIEnv* env,
jclass clazz) {
return static_cast<jint>(s_relro_sharing_status);
}
bool LinkerJNIInit(JavaVM* vm, JNIEnv* env) {
if (!s_lib_info_fields.Init(env)) {
return false;
}
s_java_vm = vm;
return true;
}
}