866 lines
28 KiB
C++
866 lines
28 KiB
C++
// Copyright 2015 The Chromium Authors
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "base/android/linker/linker_jni.h"
|
|
|
|
#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 <time.h>
|
|
#include <unistd.h>
|
|
|
|
#include <memory>
|
|
|
|
namespace chromium_android_linker {
|
|
|
|
namespace {
|
|
|
|
// Variable containing LibInfo for the loaded library.
|
|
LibInfo_class s_lib_info_fields;
|
|
|
|
// Guarded by |mLock| in Linker.java.
|
|
RelroSharingStatus s_relro_sharing_status = RelroSharingStatus::NOT_ATTEMPTED;
|
|
|
|
// Saved JavaVM passed to JNI_OnLoad().
|
|
JavaVM* s_java_vm = nullptr;
|
|
|
|
// With mmap(2) reserves a range of virtual addresses.
|
|
//
|
|
// The range must start with |hint| and be of size |size|. The |hint==0|
|
|
// indicates that the address of the mapping should be chosen at random,
|
|
// utilizing ASLR built into mmap(2).
|
|
//
|
|
// The start of the resulting region is returned in |address|.
|
|
//
|
|
// The value 0 returned iff the attempt failed (a part of the address range is
|
|
// already reserved by some other subsystem).
|
|
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)) {
|
|
// Something grabbed the address range before the early phase of the
|
|
// linker had a chance, this should be uncommon.
|
|
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;
|
|
}
|
|
}
|
|
|
|
// Extract the region start and end. The failures below should not happen as
|
|
// long as the reservation is made the same way in
|
|
// frameworks/base/native/webview/loader/loader.cpp.
|
|
uintptr_t vma_start, vma_end;
|
|
char permissions[5] = {'\0'}; // Ensure a null-terminated string.
|
|
// Example line from proc(5):
|
|
// address perms offset dev inode pathname
|
|
// 00400000-00452000 r-xp 00000000 08:02 173521 /usr/bin/dbus-daemon
|
|
if (sscanf(line_start, "%" SCNxPTR "-%" SCNxPTR " %4c", &vma_start, &vma_end,
|
|
permissions) < 3) {
|
|
return false;
|
|
}
|
|
|
|
if (strcmp(permissions, "---p"))
|
|
return false;
|
|
|
|
if (vma_start % PAGE_SIZE || vma_end % PAGE_SIZE)
|
|
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;
|
|
constexpr size_t kReadSize = PAGE_SIZE;
|
|
|
|
// Loop until no bytes left to scan. On every iteration except the last, fill
|
|
// the buffer till the end. On every iteration except the first, the buffer
|
|
// begins with kMaxLineLength bytes from the end of the previous fill.
|
|
char buf[kReadSize + kMaxLineLength + 1];
|
|
buf[kReadSize + kMaxLineLength] = '\0'; // Stop strstr().
|
|
size_t pos = 0;
|
|
size_t bytes_requested = kReadSize + kMaxLineLength;
|
|
bool reached_end = false;
|
|
while (true) {
|
|
// Fill the |buf| to the maximum and determine whether reading reached the
|
|
// end.
|
|
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));
|
|
|
|
// Return results if the buffer contains the pattern.
|
|
if (ScanRegionInBuffer(buf, pos + bytes_read, out_address, out_size))
|
|
return true;
|
|
|
|
// Did not find the pattern.
|
|
if (reached_end)
|
|
return false;
|
|
|
|
// The buffer is filled to the end. Copy the end bytes to the beginning,
|
|
// allowing to scan these bytes on the next iteration.
|
|
memcpy(buf, buf + kReadSize, kMaxLineLength);
|
|
pos = kMaxLineLength;
|
|
bytes_requested = kReadSize;
|
|
}
|
|
}
|
|
|
|
// Calls the Linker Java methods to record the time intervals in UMA. The calls
|
|
// are made only once pre process, hence there is no need to cache the values
|
|
// obtained from JNIEnv. The class must *not* be reused across different
|
|
// Java->native calls because the |jclass| reference may become invalid in this
|
|
// case.
|
|
class LoadTimeReporterJni : public LoadTimeReporter {
|
|
public:
|
|
LoadTimeReporterJni(JNIEnv* env, jclass linker_jni_class)
|
|
: env_(env), class_(linker_jni_class) {}
|
|
|
|
void reportDlopenExtTime(int64_t millis) const override {
|
|
env_->CallStaticVoidMethod(
|
|
class_, env_->GetStaticMethodID(class_, "reportDlopenExtTime", "(J)V"),
|
|
millis);
|
|
}
|
|
|
|
void reportIteratePhdrTime(int64_t millis) const override {
|
|
env_->CallStaticVoidMethod(
|
|
class_,
|
|
env_->GetStaticMethodID(class_, "reportIteratePhdrTime", "(J)V"),
|
|
millis);
|
|
}
|
|
|
|
private:
|
|
// Not copyable or movable.
|
|
LoadTimeReporterJni(const LoadTimeReporterJni&) = delete;
|
|
LoadTimeReporterJni& operator=(const LoadTimeReporterJni&) = delete;
|
|
|
|
JNIEnv* env_;
|
|
jclass class_;
|
|
};
|
|
|
|
constexpr int64_t kMillisecondsPerSecond = 1'000;
|
|
constexpr int64_t kNanosecondsPerMillisecond = 1'000'000;
|
|
|
|
int64_t GetMillisNow() {
|
|
struct timespec ts;
|
|
if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) {
|
|
PLOG_ERROR("clock_gettime");
|
|
return 0;
|
|
}
|
|
|
|
int64_t result = ts.tv_sec;
|
|
result *= kMillisecondsPerSecond;
|
|
result += (ts.tv_nsec / kNanosecondsPerMillisecond);
|
|
return result;
|
|
}
|
|
|
|
// Invokes android_dlopen_ext() to load the library into a given address range.
|
|
// Assumes that the address range is already reserved with mmap(2). On success,
|
|
// the |handle| of the loaded library is returned.
|
|
//
|
|
// Returns true iff this operation succeeds.
|
|
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;
|
|
}
|
|
|
|
// With munmap(2) unmaps the tail of the given contiguous range of virtual
|
|
// memory. Ignores errors.
|
|
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 {
|
|
// Unmap the part of the reserved address space that is beyond the end of
|
|
// the loaded library data.
|
|
const uintptr_t unmap = address + new_size;
|
|
const size_t length = old_size - new_size;
|
|
munmap(reinterpret_cast<void*>(unmap), length);
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
LOG_INFO("Done");
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
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);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// Finds 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;
|
|
}
|
|
|
|
// Initializes 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;
|
|
}
|
|
|
|
bool FindWebViewReservation(uintptr_t* out_address, size_t* out_size) {
|
|
// Note: reading /proc/PID/maps or /proc/PID/smaps is inherently racy. Among
|
|
// other things, the kernel provides these guarantees:
|
|
// * Each region record (line) is well formed
|
|
// * If there is something at a given vaddr during the entirety of the life of
|
|
// the smaps/maps walk, there will be some output for it.
|
|
//
|
|
// In order for the address/size extraction to be safe, these precausions are
|
|
// made in base/android/linker:
|
|
// * Modification of the range is done only after this function exits
|
|
// * The use of the range is avoided if it is not sufficient in size, which
|
|
// might happen if it gets split
|
|
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;
|
|
}
|
|
|
|
// Starting with API level 26 (Android O) the following functions from
|
|
// libandroid.so should be used to create shared memory regions to ensure
|
|
// compatibility with the future versions:
|
|
// * ASharedMemory_create()
|
|
// * ASharedMemory_setProt()
|
|
//
|
|
// This is inspired by //third_party/ashmem/ashmem-dev.c, which cannot be
|
|
// referenced from the linker library to avoid increasing binary size.
|
|
//
|
|
// *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"));
|
|
}
|
|
|
|
bool IsWorking() const {
|
|
if (!create || !set_protection) {
|
|
LOG_ERROR("Cannot get the shared memory functions from libandroid");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
~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 = nullptr;
|
|
};
|
|
|
|
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_);
|
|
|
|
// Check that an ELF library starts at the |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;
|
|
}
|
|
|
|
// Sanitycheck PAGE_SIZE before use.
|
|
int page_size = sysconf(_SC_PAGESIZE);
|
|
if (page_size != PAGE_SIZE)
|
|
abort();
|
|
|
|
// Compute the ranges of PT_LOAD segments and the PT_GNU_RELRO. It is possible
|
|
// to reach for the same information by iterating over all loaded libraries
|
|
// and their program headers using dl_iterate_phdr(3). Instead here the
|
|
// iteration goes through the array |e_phoff[e_phnum]| to avoid acquisition of
|
|
// the global lock in Bionic (dlfcn.cpp).
|
|
//
|
|
// The code relies on (1) having RELRO in the PT_GNU_RELRO segment, and (2)
|
|
// the fact that the address *range* occupied by the library is the minimal
|
|
// address range containing all of the PT_LOAD and PT_GNU_RELRO segments.
|
|
// This is a contract between the static linker and the dynamic linker which
|
|
// seems unlikely to get broken. It might break though as a result of
|
|
// post-processing the DSO, which has historically happened for a few
|
|
// occasions (eliminating the unwind tables and splitting the library into
|
|
// DFMs).
|
|
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);
|
|
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 = PAGE_START(phdr->p_vaddr);
|
|
max_relro_vaddr = phdr->p_vaddr + phdr->p_memsz;
|
|
|
|
// As of 2020-11 in libmonochrome.so RELRO is covered by a LOAD segment.
|
|
// It is not clear whether this property is going to be guaranteed in
|
|
// the future. Include the RELRO segment as part of the 'load size'.
|
|
// This way a potential future change in layout of LOAD segments would
|
|
// not open address space for racy mmap(MAP_FIXED).
|
|
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;
|
|
}
|
|
}
|
|
|
|
// Fill out size and RELRO information.
|
|
load_size_ = PAGE_END(max_vaddr) - PAGE_START(min_vaddr);
|
|
relro_size_ = PAGE_END(max_relro_vaddr) - PAGE_START(min_relro_vaddr);
|
|
relro_start_ = load_address_ + PAGE_START(min_relro_vaddr);
|
|
return true;
|
|
}
|
|
|
|
bool NativeLibInfo::LoadWithDlopenExt(const String& path,
|
|
const LoadTimeReporter& reporter,
|
|
void** handle) {
|
|
LOG_INFO("Entering");
|
|
|
|
// The address range must be reserved during initialization in Linker.java.
|
|
if (!load_address_) {
|
|
// TODO(pasko): measure how often this happens.
|
|
return false;
|
|
}
|
|
|
|
// Remember the memory reservation size. Starting from this point load_size_
|
|
// changes the meaning to reflect the size of the loaded library.
|
|
size_t reservation_size = load_size_;
|
|
auto* address = reinterpret_cast<void*>(load_address_);
|
|
|
|
// Invoke android_dlopen_ext.
|
|
int64_t ticks_initial = GetMillisNow();
|
|
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;
|
|
}
|
|
int64_t ticks_after_dlopen_ext = GetMillisNow();
|
|
reporter.reportDlopenExtTime(ticks_after_dlopen_ext - ticks_initial);
|
|
|
|
// Determine the library address ranges and the RELRO region.
|
|
if (!FindRelroAndLibraryRangesInElf()) {
|
|
// Fail early if PT_GNU_RELRO is not found. It likely indicates a
|
|
// build misconfiguration.
|
|
LOG_ERROR("Could not find RELRO in the loaded library: %s", path.c_str());
|
|
abort();
|
|
}
|
|
reporter.reportIteratePhdrTime(GetMillisNow() - ticks_after_dlopen_ext);
|
|
|
|
// Release the unused parts of the memory reservation.
|
|
TrimMapping(load_address_, reservation_size, load_size_);
|
|
|
|
*handle = local_handle;
|
|
return true;
|
|
}
|
|
|
|
bool NativeLibInfo::CreateSharedRelroFd(
|
|
const SharedMemoryFunctions& functions) {
|
|
LOG_INFO("Entering");
|
|
if (!relro_start_ || !relro_size_) {
|
|
LOG_ERROR("RELRO region is not populated");
|
|
return false;
|
|
}
|
|
|
|
// Create a writable shared memory region.
|
|
int shared_mem_fd = functions.create("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;
|
|
functions.set_protection(shared_mem_fd, rw_flags);
|
|
|
|
// Map the region as writable.
|
|
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;
|
|
}
|
|
|
|
// Populate the shared memory region with the contents of RELRO.
|
|
void* relro_addr = reinterpret_cast<void*>(relro_start_);
|
|
memcpy(relro_copy_addr, relro_addr, relro_size_);
|
|
|
|
// Protect the underlying physical pages from further modifications from all
|
|
// processes including the forked ones.
|
|
//
|
|
// Setting protection flags on the region to read-only guarantees that the
|
|
// memory can no longer get mapped as writable, for any FD pointing to the
|
|
// region, for any process. It is necessary to also munmap(2) the existing
|
|
// writable memory mappings, since they are not directly affected by the
|
|
// change of region's protection flags.
|
|
munmap(relro_copy_addr, relro_size_);
|
|
if (functions.set_protection(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 SharedMemoryFunctions& functions) const {
|
|
LOG_INFO("Entering");
|
|
if (relro_fd_ == -1 || !relro_start_ || !relro_size_) {
|
|
LOG_ERROR("Replacement RELRO not ready");
|
|
return false;
|
|
}
|
|
|
|
// Map as read-only to *atomically* replace the RELRO region provided by the
|
|
// dynamic linker. To avoid memory corruption it is important that the
|
|
// contents of both memory regions is identical.
|
|
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,
|
|
const LoadTimeReporter& reporter) {
|
|
// Load the library.
|
|
void* handle = nullptr;
|
|
if (!LoadWithDlopenExt(library_path, reporter, &handle)) {
|
|
LOG_ERROR("Failed to load native library: %s", library_path.c_str());
|
|
return false;
|
|
}
|
|
if (!CallJniOnLoad(handle))
|
|
return false;
|
|
|
|
// Publish the library size and load address back to LibInfo in Java.
|
|
ExportLoadInfoToJava();
|
|
|
|
if (!spawn_relro_region)
|
|
return true;
|
|
|
|
// Spawn RELRO to a shared memory region by copying and remapping on top of
|
|
// itself.
|
|
SharedMemoryFunctions functions;
|
|
if (!functions.IsWorking())
|
|
return false;
|
|
if (!CreateSharedRelroFd(functions)) {
|
|
LOG_ERROR("Failed to create shared RELRO");
|
|
return false;
|
|
}
|
|
if (!ReplaceRelroWithSharedOne(functions)) {
|
|
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 SharedMemoryFunctions& functions) const {
|
|
// Abandon sharing if contents of the incoming RELRO region does not match the
|
|
// current one. This can be useful for debugging, but should never happen in
|
|
// the field.
|
|
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;
|
|
}
|
|
|
|
SharedMemoryFunctions functions;
|
|
if (!functions.IsWorking()) {
|
|
s_relro_sharing_status = RelroSharingStatus::NO_SHMEM_FUNCTIONS;
|
|
return false;
|
|
}
|
|
if (!RelroIsIdentical(other_lib_info, functions)) {
|
|
LOG_ERROR("RELRO is not identical");
|
|
s_relro_sharing_status = RelroSharingStatus::NOT_IDENTICAL;
|
|
return false;
|
|
}
|
|
|
|
// Make it shared.
|
|
//
|
|
// The alternative approach to invoke mprotect+mremap is probably faster than
|
|
// munmap+mmap here. The advantage of the latter is that it removes all
|
|
// formerly writable mappings, so:
|
|
// * It does not rely on disallowing mprotect(PROT_WRITE)
|
|
// * This way |ReplaceRelroWithSharedOne()| is reused across spawning RELRO
|
|
// and receiving it
|
|
if (!other_lib_info.ReplaceRelroWithSharedOne(functions)) {
|
|
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() {
|
|
// The library providing these functions will be dlclose()-ed after returning
|
|
// from this context. The extra overhead of dlopen() is OK for testing.
|
|
SharedMemoryFunctions functions;
|
|
if (!functions.IsWorking())
|
|
abort();
|
|
return CreateSharedRelroFd(functions);
|
|
}
|
|
|
|
// static
|
|
bool NativeLibInfo::SharedMemoryFunctionsSupportedForTesting() {
|
|
SharedMemoryFunctions functions;
|
|
return functions.IsWorking();
|
|
}
|
|
|
|
JNI_GENERATOR_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_GENERATOR_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_GENERATOR_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_GENERATOR_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");
|
|
|
|
// Copy the contents from the Java-side LibInfo object.
|
|
NativeLibInfo lib_info = {env, lib_info_obj};
|
|
if (!lib_info.CopyFromJavaObject())
|
|
return false;
|
|
|
|
String library_path(env, jdlopen_ext_path);
|
|
LoadTimeReporterJni reporter = {env, clazz};
|
|
if (!lib_info.LoadLibrary(library_path, spawn_relro_region, reporter)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
JNI_GENERATOR_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");
|
|
// Copy the contents from the Java-side LibInfo object.
|
|
NativeLibInfo incoming_lib_info = {env, remote_lib_info_obj};
|
|
if (!incoming_lib_info.CopyFromJavaObject()) {
|
|
s_relro_sharing_status = RelroSharingStatus::CORRUPTED_IN_JAVA;
|
|
return false;
|
|
}
|
|
|
|
// Create an empty NativeLibInfo to extract the current information about the
|
|
// loaded library and later compare with the contents of the
|
|
// |incoming_lib_info|.
|
|
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_GENERATOR_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) {
|
|
// Find LibInfo field ids.
|
|
if (!s_lib_info_fields.Init(env)) {
|
|
return false;
|
|
}
|
|
|
|
s_java_vm = vm;
|
|
return true;
|
|
}
|
|
|
|
} // namespace chromium_android_linker
|