Commit f7557579 authored by Egor Pasko's avatar Egor Pasko Committed by Chromium LUCI CQ

base/android: ModernLinker: Test CreateSharedRelroFd()

The test creates a fake RELRO region in private memory and checks that
it is spawned into a sealed read-only shared memory region.

Since the normal way of using the ModernLinker involves a separate DSO
(=shared library), this would add more moving parts to base_unittests.
We prefer less advanced ways with GN, so another approach is chosen: all
the necessary code is linked with base_unittests.

Testing the way ModernLinker loads libraries would be difficult without
extra libraries pushed to the device, but RELRO region creation, sharing
and consumption can be tested using this approach.

Bug: 1154224
Change-Id: Ia78cce98f566084f6f799a7a35ab3cc45beb9346
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2571758Reviewed-by: default avatarBenoit L <lizeb@chromium.org>
Commit-Queue: Egor Pasko <pasko@chromium.org>
Cr-Commit-Position: refs/heads/master@{#833793}
parent 63d53142
...@@ -3222,6 +3222,11 @@ if (build_base_unittests) { ...@@ -3222,6 +3222,11 @@ if (build_base_unittests) {
"android/jni_array_unittest.cc", "android/jni_array_unittest.cc",
"android/jni_string_unittest.cc", "android/jni_string_unittest.cc",
"android/library_loader/library_prefetcher_unittest.cc", "android/library_loader/library_prefetcher_unittest.cc",
"android/linker/linker_jni.cc",
"android/linker/linker_jni.h",
"android/linker/modern_linker_jni.cc",
"android/linker/modern_linker_jni.h",
"android/linker/modern_linker_unittest.cc",
"android/path_utils_unittest.cc", "android/path_utils_unittest.cc",
"android/reached_addresses_bitset_unittest.cc", "android/reached_addresses_bitset_unittest.cc",
"android/scoped_java_ref_unittest.cc", "android/scoped_java_ref_unittest.cc",
......
...@@ -13,6 +13,7 @@ shared_library("chromium_android_linker") { ...@@ -13,6 +13,7 @@ shared_library("chromium_android_linker") {
"legacy_linker_jni.h", "legacy_linker_jni.h",
"linker_jni.cc", "linker_jni.cc",
"linker_jni.h", "linker_jni.h",
"linker_jni_onload.cc",
"modern_linker_jni.cc", "modern_linker_jni.cc",
"modern_linker_jni.h", "modern_linker_jni.h",
] ]
......
...@@ -19,9 +19,6 @@ ...@@ -19,9 +19,6 @@
#include <string.h> #include <string.h>
#include <sys/mman.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 { namespace chromium_android_linker {
// Variable containing LibInfo for the loaded library. // Variable containing LibInfo for the loaded library.
...@@ -107,44 +104,4 @@ Java_org_chromium_base_library_1loader_Linker_nativeGetRandomBaseLoadAddress( ...@@ -107,44 +104,4 @@ Java_org_chromium_base_library_1loader_Linker_nativeGetRandomBaseLoadAddress(
return static_cast<jlong>(reinterpret_cast<uintptr_t>(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 } // namespace chromium_android_linker
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
return chromium_android_linker::JNI_OnLoad(vm, reserved);
}
...@@ -207,9 +207,11 @@ struct LibInfo_class { ...@@ -207,9 +207,11 @@ struct LibInfo_class {
} }
}; };
// Variable containing LibInfo for the loaded library. // Variable containing LibInfo accessors for the loaded library.
extern LibInfo_class s_lib_info_fields; extern LibInfo_class s_lib_info_fields;
extern jint JNI_OnLoad(JavaVM* vm, void* reserved);
} // namespace chromium_android_linker } // namespace chromium_android_linker
#endif // BASE_ANDROID_LINKER_LINKER_JNI_H_ #endif // BASE_ANDROID_LINKER_LINKER_JNI_H_
// Copyright 2020 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.
// The JNI_OnLoad() definition for the linker library is moved here to avoid a
// conflict with JNI_Onload() defined by the test library. The linker tests
// together with the linker internals are smashed into (=linked with) the test
// library.
//
// This file also helps avoiding LegacyLinkerJNIInit() and its dependencies in
// base_unittests. There are no plans to unittest LegacyLinker.
#include <jni.h>
#include "base/android/linker/legacy_linker_jni.h"
#include "base/android/linker/linker_jni.h"
#include "base/android/linker/modern_linker_jni.h"
namespace chromium_android_linker {
namespace {
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;
}
} // namespace
// JNI_OnLoad() is called when the linker library is loaded through the regular
// System.LoadLibrary) API. This shall save the Java VM handle and initialize
// LibInfo field accessors.
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
LOG_INFO("Entering");
JNIEnv* env;
if (JNI_OK != vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_4)) {
LOG_ERROR("Could not create JNIEnv");
return -1;
}
if (!LinkerJNIInit(vm, env) || !LegacyLinkerJNIInit(vm, env) ||
!ModernLinkerJNIInit(vm, env)) {
return -1;
}
LOG_INFO("Done");
return JNI_VERSION_1_4;
}
} // namespace chromium_android_linker
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
return chromium_android_linker::JNI_OnLoad(vm, reserved);
}
...@@ -159,122 +159,6 @@ struct SharedMemoryFunctions { ...@@ -159,122 +159,6 @@ struct SharedMemoryFunctions {
void* library_handle; void* library_handle;
}; };
// Holds address ranges of the loaded native library, its RELRO region, along
// with the RELRO FD identifying the shared memory region. Carries the same
// members as the Java-side LibInfo, allowing to internally import/export the
// member values from/to the Java-side counterpart.
//
// Does *not* own the RELRO FD as soon as the latter gets exported to Java
// (as a result of 'spawning' the RELRO region as shared memory.
//
// *Not* threadsafe.
class NativeLibInfo {
public:
// Constructs an (almost) empty instance. To be later populated from native
// code. The |env| and |java_object| will be useful later for exporting the
// values to the Java counterpart.
NativeLibInfo(size_t address, JNIEnv* env, jobject java_object)
: load_address_(address), env_(env), java_object_(java_object) {}
// Constructs and imports fields from the Java LibInfo. As above, the |env|
// and |java_object| are used for exporting.
NativeLibInfo(JNIEnv* env, jobject java_object)
: env_(env), java_object_(java_object) {
s_lib_info_fields.GetLoadInfo(env, java_object, &load_address_,
&load_size_);
s_lib_info_fields.GetRelroInfo(env, java_object, &relro_start_,
&relro_size_, &relro_fd_);
}
size_t load_address() const { return load_address_; }
// Loads the native library using android_dlopen_ext and invokes JNI_OnLoad().
//
// On a successful load exports the address range of the library to the
// Java-side LibInfo.
//
// Iff |spawn_relro_region| is true, also finds the RELRO region in the
// library (PT_GNU_RELRO), converts it to be backed by a shared memory region
// (here referred as "RELRO FD") and exports the RELRO information to Java
// (the address range and the RELRO FD).
//
// When spawned, the shared memory region is exported only after sealing as
// read-only and without writable memory mappings. This allows any process to
// provide RELRO FD before it starts processing arbitrary input. For example,
// an App Zygote can create a RELRO FD in a sufficiently trustworthy way to
// make the Browser/Privileged processes share the region with it.
//
// TODO(pasko): Add an option to use an existing memory reservation.
bool LoadLibrary(const String& library_path, bool spawn_relro_region);
// Finds the RELRO region in the native library identified by
// |this->load_address()| and replaces it with the shared memory region
// identified by |other_lib_info|.
//
// The external NativeLibInfo can arrive from a different process.
//
// Note on security: The RELRO region is treated as *trusted*, no untrusted
// user/website/network input can be processed in an isolated process before
// it sends the RELRO FD. This is because there is no way to check whether the
// process has a writable mapping of the region remaining.
bool CompareRelroAndReplaceItBy(const NativeLibInfo& other_lib_info);
private:
friend int VisitLibraryPhdrs(dl_phdr_info*, size_t, void*);
NativeLibInfo() = delete;
// Not copyable or movable.
NativeLibInfo(const NativeLibInfo&) = delete;
NativeLibInfo& operator=(const NativeLibInfo&) = delete;
// Exports the address range of the library described by |this| to the
// Java-side LibInfo.
void ExportLoadInfoToJava() const {
s_lib_info_fields.SetLoadInfo(env_, java_object_, load_address_,
load_size_);
}
// Initializes |relro_fd_| with a newly created read-only shared memory region
// sized as the library's RELRO and with identical data.
bool CreateSharedRelroFd();
// Exports the address range of the RELRO region and RELRO FD described by
// |this| to the Java-side LibInfo.
void ExportRelroInfoToJava() const {
s_lib_info_fields.SetRelroInfo(env_, java_object_, relro_start_,
relro_size_, relro_fd_);
}
void CloseRelroFd() {
if (relro_fd_ == kInvalidFd)
return;
close(relro_fd_);
relro_fd_ = kInvalidFd;
}
// Loads and initializes the load address ranges: |load_address_|,
// |load_size_|.
bool LoadWithDlopenExt(const String& path, void** handle);
// Assuming that RELRO-related information is populated, memory-maps the RELRO
// FD on top of the library's RELRO.
bool ReplaceRelroWithSharedOne() const;
// Returns true iff the RELRO address and size, along with the contents are
// equal among the two.
bool RelroIsIdentical(const NativeLibInfo& external_lib_info) const;
static constexpr int kInvalidFd = -1;
size_t load_address_ = 0;
size_t load_size_ = 0;
size_t relro_start_ = 0;
size_t relro_size_ = 0;
int relro_fd_ = kInvalidFd;
JNIEnv* env_;
jobject java_object_;
};
// android_dlopen_ext() wrapper. // android_dlopen_ext() wrapper.
// Returns false if no android_dlopen_ext() is available, otherwise true with // Returns false if no android_dlopen_ext() is available, otherwise true with
// the return value from android_dlopen_ext() in |status|. // the return value from android_dlopen_ext() in |status|.
...@@ -297,11 +181,95 @@ bool AndroidDlopenExt(const char* filename, ...@@ -297,11 +181,95 @@ bool AndroidDlopenExt(const char* filename,
return true; return true;
} }
// Callback for dl_iterate_phdr(). From program headers (phdr(s)) of a loaded // Creates an android_dlextinfo struct so that a library is loaded inside the
// library determines its load address, and in case it is equal to // space referenced by |mapping|.
// |lib_info.load_address()|, extracts the RELRO and size information from std::unique_ptr<android_dlextinfo> MakeAndroidDlextinfo(
// corresponding phdr(s). const ScopedAnonymousMmap& mapping) {
int VisitLibraryPhdrs(dl_phdr_info* info, size_t size UNUSED, void* lib_info) { 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;
}
// Resizes the address space reservation to the required size. Failure here is
// only a warning, since at worst this wastes virtual address space, not
// physical memory.
void ResizeMapping(const ScopedAnonymousMmap& mapping, size_t load_size) {
// 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() <= load_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.
void* unmap = reinterpret_cast<void*>(uintptr_addr + load_size);
const size_t length = mapping.size() - load_size;
munmap(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;
}
}
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
void NativeLibInfo::ExportLoadInfoToJava() const {
s_lib_info_fields.SetLoadInfo(env_, java_object_, load_address_, load_size_);
}
void NativeLibInfo::ExportRelroInfoToJava() const {
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;
}
// static
int NativeLibInfo::VisitLibraryPhdrs(dl_phdr_info* info,
size_t size UNUSED,
void* lib_info) {
auto* out_lib_info = reinterpret_cast<NativeLibInfo*>(lib_info); auto* out_lib_info = reinterpret_cast<NativeLibInfo*>(lib_info);
ElfW(Addr) lookup_address = ElfW(Addr) lookup_address =
static_cast<ElfW(Addr)>(out_lib_info->load_address()); static_cast<ElfW(Addr)>(out_lib_info->load_address());
...@@ -368,75 +336,20 @@ int VisitLibraryPhdrs(dl_phdr_info* info, size_t size UNUSED, void* lib_info) { ...@@ -368,75 +336,20 @@ int VisitLibraryPhdrs(dl_phdr_info* info, size_t size UNUSED, void* lib_info) {
return false; return false;
} }
bool FindRelroAndLibraryRangesInElf(void* load_address, bool NativeLibInfo::FindRelroAndLibraryRangesInElf() {
NativeLibInfo* lib_info) { LOG_INFO("Called for %zx", load_address_);
LOG_INFO("Called for %p", load_address);
if (!dl_iterate_phdr) { if (!dl_iterate_phdr) {
LOG_ERROR("No dl_iterate_phdr() found"); LOG_ERROR("No dl_iterate_phdr() found");
return false; return false;
} }
int status = dl_iterate_phdr(&VisitLibraryPhdrs, lib_info); int status = dl_iterate_phdr(&VisitLibraryPhdrs, this);
if (!status) { if (!status) {
LOG_ERROR("Failed to find library at address %p", load_address); LOG_ERROR("Failed to find library at address %zx", load_address_);
return false; return false;
} }
return true; return true;
} }
// 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;
}
// Resizes the address space reservation to the required size. Failure here is
// only a warning, since at worst this wastes virtual address space, not
// physical memory.
void ResizeMapping(const ScopedAnonymousMmap& mapping, size_t load_size) {
// 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() <= load_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.
void* unmap = reinterpret_cast<void*>(uintptr_addr + load_size);
const size_t length = mapping.size() - load_size;
munmap(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;
}
}
return true;
}
bool NativeLibInfo::LoadWithDlopenExt(const String& path, void** handle) { bool NativeLibInfo::LoadWithDlopenExt(const String& path, void** handle) {
LOG_INFO("Entering"); LOG_INFO("Entering");
...@@ -464,7 +377,7 @@ bool NativeLibInfo::LoadWithDlopenExt(const String& path, void** handle) { ...@@ -464,7 +377,7 @@ bool NativeLibInfo::LoadWithDlopenExt(const String& path, void** handle) {
mapping.Release(); mapping.Release();
// Find RELRO and trim the unused parts of the memory mapping. // Find RELRO and trim the unused parts of the memory mapping.
if (!FindRelroAndLibraryRangesInElf(address, this)) { if (!FindRelroAndLibraryRangesInElf()) {
// Fail early if PT_GNU_RELRO is not found. It likely indicates a // Fail early if PT_GNU_RELRO is not found. It likely indicates a
// build misconfiguration. // build misconfiguration.
LOG_ERROR("Could not find RELRO in the loaded library: %s", path.c_str()); LOG_ERROR("Could not find RELRO in the loaded library: %s", path.c_str());
...@@ -550,17 +463,14 @@ bool NativeLibInfo::ReplaceRelroWithSharedOne() const { ...@@ -550,17 +463,14 @@ bool NativeLibInfo::ReplaceRelroWithSharedOne() const {
return true; return true;
} }
bool LoadNoSharedRelocations(const String& path) { NativeLibInfo::NativeLibInfo(size_t address, JNIEnv* env, jobject java_object)
void* handle = dlopen(path.c_str(), RTLD_NOW); : load_address_(address), env_(env), java_object_(java_object) {}
if (!handle) {
LOG_ERROR("dlopen: %s", dlerror());
return false;
}
if (!CallJniOnLoad(handle))
return false;
return true; NativeLibInfo::NativeLibInfo(JNIEnv* env, jobject java_object)
: env_(env), java_object_(java_object) {
s_lib_info_fields.GetLoadInfo(env, java_object, &load_address_, &load_size_);
s_lib_info_fields.GetRelroInfo(env, java_object, &relro_start_, &relro_size_,
&relro_fd_);
} }
bool NativeLibInfo::LoadLibrary(const String& library_path, bool NativeLibInfo::LoadLibrary(const String& library_path,
...@@ -636,10 +546,9 @@ bool NativeLibInfo::CompareRelroAndReplaceItBy( ...@@ -636,10 +546,9 @@ bool NativeLibInfo::CompareRelroAndReplaceItBy(
return false; return false;
} }
void* library_address = reinterpret_cast<void*>(other_lib_info.load_address_); if (!FindRelroAndLibraryRangesInElf()) {
if (!FindRelroAndLibraryRangesInElf(library_address, this)) {
LOG_ERROR("Could not find RELRO from externally provided address: 0x%p", LOG_ERROR("Could not find RELRO from externally provided address: 0x%p",
library_address); reinterpret_cast<void*>(other_lib_info.load_address_));
return false; return false;
} }
...@@ -668,7 +577,11 @@ bool NativeLibInfo::CompareRelroAndReplaceItBy( ...@@ -668,7 +577,11 @@ bool NativeLibInfo::CompareRelroAndReplaceItBy(
return true; return true;
} }
} // namespace // static
bool NativeLibInfo::SharedMemoryFunctionsSupportedForTesting() {
SharedMemoryFunctions functions;
return functions.IsWorking();
}
JNI_GENERATOR_EXPORT jboolean JNI_GENERATOR_EXPORT jboolean
Java_org_chromium_base_library_1loader_ModernLinker_nativeLoadLibrary( Java_org_chromium_base_library_1loader_ModernLinker_nativeLoadLibrary(
......
...@@ -6,9 +6,12 @@ ...@@ -6,9 +6,12 @@
#define BASE_ANDROID_LINKER_MODERN_LINKER_JNI_H_ #define BASE_ANDROID_LINKER_MODERN_LINKER_JNI_H_
#include <jni.h> #include <jni.h>
#include <link.h>
namespace chromium_android_linker { namespace chromium_android_linker {
class String;
// Used to find out whether RELRO sharing is often rejected due to mismatch of // Used to find out whether RELRO sharing is often rejected due to mismatch of
// the contents. // the contents.
// //
...@@ -23,6 +26,123 @@ enum class RelroSharingStatus { ...@@ -23,6 +26,123 @@ enum class RelroSharingStatus {
COUNT = 3, COUNT = 3,
}; };
// Holds address ranges of the loaded native library, its RELRO region, along
// with the RELRO FD identifying the shared memory region. Carries the same
// members as the Java-side LibInfo, allowing to internally import/export the
// member values from/to the Java-side counterpart.
//
// Does *not* own the RELRO FD as soon as the latter gets exported to Java
// (as a result of 'spawning' the RELRO region as shared memory.
//
// *Not* threadsafe.
class NativeLibInfo {
public:
// Constructs an (almost) empty instance. To be later populated from native
// code. The |env| and |java_object| will be useful later for exporting the
// values to the Java counterpart.
NativeLibInfo(size_t address, JNIEnv* env, jobject java_object);
// Constructs and imports fields from the Java LibInfo. As above, the |env|
// and |java_object| are used for exporting.
NativeLibInfo(JNIEnv* env, jobject java_object);
size_t load_address() const { return load_address_; }
// Loads the native library using android_dlopen_ext and invokes JNI_OnLoad().
//
// On a successful load exports the address range of the library to the
// Java-side LibInfo.
//
// Iff |spawn_relro_region| is true, also finds the RELRO region in the
// library (PT_GNU_RELRO), converts it to be backed by a shared memory region
// (here referred as "RELRO FD") and exports the RELRO information to Java
// (the address range and the RELRO FD).
//
// When spawned, the shared memory region is exported only after sealing as
// read-only and without writable memory mappings. This allows any process to
// provide RELRO FD before it starts processing arbitrary input. For example,
// an App Zygote can create a RELRO FD in a sufficiently trustworthy way to
// make the Browser/Privileged processes share the region with it.
//
// TODO(pasko): Add an option to use an existing memory reservation.
bool LoadLibrary(const String& library_path, bool spawn_relro_region);
// Finds the RELRO region in the native library identified by
// |this->load_address()| and replaces it with the shared memory region
// identified by |other_lib_info|.
//
// The external NativeLibInfo can arrive from a different process.
//
// Note on security: The RELRO region is treated as *trusted*, no untrusted
// user/website/network input can be processed in an isolated process before
// it sends the RELRO FD. This is because there is no way to check whether the
// process has a writable mapping of the region remaining.
bool CompareRelroAndReplaceItBy(const NativeLibInfo& other_lib_info);
void set_relro_info_for_testing(size_t start, size_t size) {
relro_start_ = start;
relro_size_ = size;
}
bool CreateSharedRelroFdForTesting() { return CreateSharedRelroFd(); }
int get_relro_fd_for_testing() { return relro_fd_; }
static bool SharedMemoryFunctionsSupportedForTesting();
private:
NativeLibInfo() = delete;
// Not copyable or movable.
NativeLibInfo(const NativeLibInfo&) = delete;
NativeLibInfo& operator=(const NativeLibInfo&) = delete;
// Exports the address range of the library described by |this| to the
// Java-side LibInfo.
void ExportLoadInfoToJava() const;
// Exports the address range of the RELRO region and RELRO FD described by
// |this| to the Java-side LibInfo.
void ExportRelroInfoToJava() const;
void CloseRelroFd();
// Callback for dl_iterate_phdr(). From program headers (phdr(s)) of a loaded
// library determines its load address, and in case it is equal to
// |lib_info.load_address()|, extracts the RELRO and size information from
// corresponding phdr(s).
static int VisitLibraryPhdrs(dl_phdr_info* info, size_t size, void* lib_info);
// Invokes dl_iterate_phdr() for the current load address, with
// VisitLibraryPhdrs() as a callback.
bool FindRelroAndLibraryRangesInElf();
// Loads and initializes the load address ranges: |load_address_|,
// |load_size_|.
bool LoadWithDlopenExt(const String& path, void** handle);
// Initializes |relro_fd_| with a newly created read-only shared memory region
// sized as the library's RELRO and with identical data.
bool CreateSharedRelroFd();
// Assuming that RELRO-related information is populated, memory-maps the RELRO
// FD on top of the library's RELRO.
bool ReplaceRelroWithSharedOne() const;
// Returns true iff the RELRO address and size, along with the contents are
// equal among the two.
bool RelroIsIdentical(const NativeLibInfo& external_lib_info) const;
static constexpr int kInvalidFd = -1;
size_t load_address_ = 0;
size_t load_size_ = 0;
size_t relro_start_ = 0;
size_t relro_size_ = 0;
int relro_fd_ = kInvalidFd;
JNIEnv* env_;
jobject java_object_;
};
// JNI_OnLoad() initialization hook for the modern linker. // JNI_OnLoad() initialization hook for the modern linker.
// Sets up JNI and other initializations for native linker code. // Sets up JNI and other initializations for native linker code.
// |vm| is the Java VM handle passed to JNI_OnLoad(). // |vm| is the Java VM handle passed to JNI_OnLoad().
......
// Copyright 2020 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.
#include <sys/mman.h>
#include "base/android/linker/modern_linker_jni.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace chromium_android_linker {
// These tests get linked with base_unittests and leave JNI uninitialized. The
// tests must not execute any parts relying on initialization with JNI_Onload().
class ModernLinkerTest : public testing::Test {
public:
ModernLinkerTest() = default;
~ModernLinkerTest() override = default;
};
// Checks that NativeLibInfo::CreateSharedRelroFd() creates a shared memory
// region that is 'sealed' as read-only.
TEST_F(ModernLinkerTest, CreatedRegionIsSealed) {
constexpr size_t kRelroSize = 1 << 21; // 2 MiB.
void* relro_address = mmap(nullptr, kRelroSize, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
ASSERT_NE(MAP_FAILED, relro_address);
NativeLibInfo lib_info = {0, 0, 0};
lib_info.set_relro_info_for_testing(reinterpret_cast<size_t>(relro_address),
kRelroSize);
memset(relro_address, 0xEE, kRelroSize);
if (!NativeLibInfo::SharedMemoryFunctionsSupportedForTesting()) {
// The ModernLinker uses functions from libandroid.so that are not available
// on old Android releases. TODO(pasko): Add a fallback to ashmem for L-M,
// as it is done in crazylinker and enable the testing below for these
// devices.
return;
}
ASSERT_EQ(true, lib_info.CreateSharedRelroFdForTesting());
int relro_fd = lib_info.get_relro_fd_for_testing();
ASSERT_NE(-1, relro_fd);
// Check that a read-only mapping contains the original data.
void* ro_address =
mmap(nullptr, kRelroSize, PROT_READ, MAP_SHARED, relro_fd, 0);
ASSERT_NE(MAP_FAILED, ro_address);
int not_equal = memcmp(relro_address, ro_address, kRelroSize);
EXPECT_EQ(false, not_equal);
munmap(ro_address, kRelroSize);
// Check that attempts to mmap with PROT_WRITE fail.
void* rw_address = mmap(nullptr, kRelroSize, PROT_READ | PROT_WRITE,
MAP_SHARED, relro_fd, 0);
EXPECT_EQ(MAP_FAILED, rw_address);
munmap(rw_address, kRelroSize);
void* w_address =
mmap(nullptr, kRelroSize, PROT_WRITE, MAP_SHARED, relro_fd, 0);
EXPECT_EQ(MAP_FAILED, w_address);
munmap(w_address, kRelroSize);
}
} // namespace chromium_android_linker
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment