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) {
"android/jni_array_unittest.cc",
"android/jni_string_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/reached_addresses_bitset_unittest.cc",
"android/scoped_java_ref_unittest.cc",
......
......@@ -13,6 +13,7 @@ shared_library("chromium_android_linker") {
"legacy_linker_jni.h",
"linker_jni.cc",
"linker_jni.h",
"linker_jni_onload.cc",
"modern_linker_jni.cc",
"modern_linker_jni.h",
]
......
......@@ -19,9 +19,6 @@
#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.
......@@ -107,44 +104,4 @@ Java_org_chromium_base_library_1loader_Linker_nativeGetRandomBaseLoadAddress(
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);
}
......@@ -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 jint JNI_OnLoad(JavaVM* vm, void* reserved);
} // namespace chromium_android_linker
#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);
}
This diff is collapsed.
......@@ -6,9 +6,12 @@
#define BASE_ANDROID_LINKER_MODERN_LINKER_JNI_H_
#include <jni.h>
#include <link.h>
namespace chromium_android_linker {
class String;
// Used to find out whether RELRO sharing is often rejected due to mismatch of
// the contents.
//
......@@ -23,6 +26,123 @@ enum class RelroSharingStatus {
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.
// Sets up JNI and other initializations for native linker code.
// |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