blob: b1e9b8ddf4260aa3986e7145e30cbaea3a889d39 [file] [log] [blame]
// 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 a part of the Android-specific Chromium dynamic linker.
//
// See linker_jni.h for more details and the dependency rules.
#include "base/android/linker/linker_jni.h"
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <jni.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include <memory>
namespace chromium_android_linker {
// Variable containing LibInfo for the loaded library.
LibInfo_class s_lib_info_fields;
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;
}
namespace {
// 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;
}
}
} // namespace
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;
}
// Performs as described in Linker.java.
JNI_GENERATOR_EXPORT void
Java_org_chromium_base_library_1loader_LinkerJni_nativeFindMemoryRegionAtRandomAddress(
JNIEnv* env,
jclass clazz,
jobject lib_info_obj,
jboolean keep_reserved) {
LOG_INFO("Entering");
uintptr_t address;
size_t size;
ReserveAddressWithHint(0, &address, &size);
if (!keep_reserved && address != 0) {
munmap(reinterpret_cast<void*>(address), size);
}
s_lib_info_fields.SetLoadInfo(env, lib_info_obj, address, size);
}
// Performs as described in Linker.java.
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);
}
// Performs as described in Linker.java.
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;
}
} // namespace chromium_android_linker