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

base/android: Split Loading and RELRO Sharing in ModernLinker

=== API

The current ModernLinker loading primitives look like:
    LoadLibraryCreateRelros(path, loadAddress, libInfo);
    LoadLibraryUseRelros(path, loadAddress, fd);

This change replaces them with:
    LoadLibrary(path, loadAddress, libInfo, boolean spawnRelroRegion);
    UseRelros(libInfo);

The UseRelros() can be called *while* the library code is running,
atomically replacing the RELRO region with its version backed by shared
memory, if all stars align.

=== Motivation

The main motivation is to allow the App Zygote to provide the RELRO
region on systems with App Zygote enabled, and use the usual mechanism
when App Zygote is not available.

=== Implementation highlights

With this change the ModernLinker still waits for mLibInfo to arrive
from the browser process, as usual, but RELRO sharing already happens
after the library is loaded. I believe, there is no code path to replace
concurrently with execution.

Semantically there are a few notable changes:

1. When spawned, the RELRO FD is sealed as PROT_READ, making it
   impossible to make it writable ever again even by privileged
   processes. This way we should not worry where the code runs, in the
   Browser process or in the App Zygote.

2. Library load ranges are explicitly including the range of
   PT_GNU_RELRO segment. It should not affect anything now, but can
   potentially eliminate hard to debug crashes in distant future.

The main changes are in modern_linker_jni.cc where 'class NativeLibInfo'
is introduced to hold basically the same information as the Java-side
LibInfo, with the ability to perform the primitives described above, and
import/export internally between Java-LibInfo and NativeLibInfo.

Retrospectively the move into the class does *not* provide much useful
encapsulation at the moment, but seems like a good direction if we later
decide to use it for LegacyLinker, mock out parts for testing. The most
part I dislike most is that the class behaves like two independent parts
(LoadInfo and RelroInfo), some operations affect one, but not the other.
I did not attempt to make the split, as things are still a bit coupled
sometimes, maybe later.

OK, this was long.

Bug: 1154224
Change-Id: I88af048a298310b56b3205a2b2dd765e2c69759a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2560308Reviewed-by: default avatarBenoit L <lizeb@chromium.org>
Commit-Queue: Egor Pasko <pasko@chromium.org>
Cr-Commit-Position: refs/heads/master@{#833245}
parent 34ffdbca
...@@ -48,29 +48,41 @@ class ModernLinker extends Linker { ...@@ -48,29 +48,41 @@ class ModernLinker extends Linker {
if (!ok) resetAndThrow("Cannot load without relro sharing"); if (!ok) resetAndThrow("Cannot load without relro sharing");
mState = State.DONE; mState = State.DONE;
} else if (provideRelro) { } else if (provideRelro) {
// We are in the browser, and with a current load address that indicates that there // Running in the browser process, with a fixed load address, hence having
// is enough address space for shared RELRO to operate. Create the shared RELRO, and // enough address space for shared RELRO to operate. Create the shared RELRO, and
// store it in the map. // store it.
LibInfo libInfo = new LibInfo(); LibInfo libInfo = new LibInfo();
libInfo.mLibFilePath = libFilePath; libInfo.mLibFilePath = libFilePath;
if (!nativeLoadLibraryCreateRelros(libFilePath, loadAddress, libInfo)) { if (!nativeLoadLibrary(
Log.e(TAG, "Unable to create relro, retrying without"); libFilePath, loadAddress, libInfo, true /* spawnRelroRegion */)) {
Log.e(TAG, "Unable to load with ModernLinker, using the system linker instead");
nativeLoadLibraryNoRelros(libFilePath); nativeLoadLibraryNoRelros(libFilePath);
libInfo.mRelroFd = -1; libInfo.mRelroFd = -1;
} }
mLibInfo = libInfo; mLibInfo = libInfo;
Log.d(TAG, "Successfully spawned RELRO: mLoadAddress=%d, mLoadSize=%d",
mLibInfo.mLoadAddress, mLibInfo.mLoadSize);
// Next state is still to provide relro (even if we don't have any), as child processes // Next state is still to provide relro (even if we don't have any), as child processes
// would wait for them. // would wait for them.
mState = State.DONE_PROVIDE_RELRO; mState = State.DONE_PROVIDE_RELRO;
} else { } else {
// We are in a service process, again with a current load address that is suitable for // Running in a child process, also with a fixed load address that is suitable for
// shared RELRO, and we are to wait for shared RELROs. So do that, then use the LibInfo // shared RELRO.
// we received.
waitForSharedRelrosLocked(); waitForSharedRelrosLocked();
Log.d(TAG, "Received mLibInfo: mLoadAddress=%d, mLoadSize=%d", mLibInfo.mLoadAddress,
mLibInfo.mLoadSize);
// Two LibInfo objects are used: |mLibInfo| that brings the RELRO FD, and a temporary
// LibInfo to load the library. Before replacing the library's RELRO with the one from
// |mLibInfo|, the two objects are compared to make sure the memory ranges and the
// contents match.
LibInfo libInfoForLoad = new LibInfo();
assert libFilePath.equals(mLibInfo.mLibFilePath); assert libFilePath.equals(mLibInfo.mLibFilePath);
if (!nativeLoadLibraryUseRelros(libFilePath, loadAddress, mLibInfo.mRelroFd)) { if (!nativeLoadLibrary(
libFilePath, loadAddress, libInfoForLoad, false /* spawnRelroRegion */)) {
resetAndThrow(String.format("Unable to load library: %s", libFilePath)); resetAndThrow(String.format("Unable to load library: %s", libFilePath));
} }
assert libInfoForLoad.mRelroFd == -1;
nativeUseRelros(mLibInfo);
mLibInfo.close(); mLibInfo.close();
mLibInfo = null; mLibInfo = null;
...@@ -107,10 +119,9 @@ class ModernLinker extends Linker { ...@@ -107,10 +119,9 @@ class ModernLinker extends Linker {
throw new UnsatisfiedLinkError(message); throw new UnsatisfiedLinkError(message);
} }
private static native boolean nativeLoadLibraryCreateRelros(
String dlopenExtPath, long loadAddress, LibInfo libInfo);
private static native boolean nativeLoadLibraryUseRelros(
String dlopenExtPath, long loadAddress, int fd);
private static native boolean nativeLoadLibraryNoRelros(String dlopenExtPath); private static native boolean nativeLoadLibraryNoRelros(String dlopenExtPath);
private static native boolean nativeLoadLibrary(
String dlopenExtPath, long loadAddress, LibInfo libInfo, boolean spawnRelroRegion);
private static native boolean nativeUseRelros(LibInfo libInfo);
private static native int nativeGetRelroSharingResult(); private static native int nativeGetRelroSharingResult();
} }
...@@ -40,6 +40,7 @@ ...@@ -40,6 +40,7 @@
#define LOG_ERROR(FORMAT, ...) \ #define LOG_ERROR(FORMAT, ...) \
__android_log_print(ANDROID_LOG_ERROR, TAG, "%s: " FORMAT, __FUNCTION__, \ __android_log_print(ANDROID_LOG_ERROR, TAG, "%s: " FORMAT, __FUNCTION__, \
##__VA_ARGS__) ##__VA_ARGS__)
#define PLOG_ERROR(MESSAGE) LOG_ERROR(MESSAGE ": %s", strerror(errno))
#define UNUSED __attribute__((unused)) #define UNUSED __attribute__((unused))
...@@ -171,8 +172,20 @@ struct LibInfo_class { ...@@ -171,8 +172,20 @@ struct LibInfo_class {
env->SetIntField(library_info_obj, relro_fd_id, relro_fd); env->SetIntField(library_info_obj, relro_fd_id, relro_fd);
} }
// Use this instance to convert a RelroInfo reference into void GetLoadInfo(JNIEnv* env,
// a crazy_library_info_t. jobject library_info_obj,
size_t* load_address,
size_t* load_size) {
if (load_address) {
*load_address = static_cast<size_t>(
env->GetLongField(library_info_obj, load_address_id));
}
if (load_size) {
*load_size = static_cast<size_t>(
env->GetLongField(library_info_obj, load_size_id));
}
}
void GetRelroInfo(JNIEnv* env, void GetRelroInfo(JNIEnv* env,
jobject library_info_obj, jobject library_info_obj,
size_t* relro_start, size_t* relro_start,
......
This diff is collapsed.
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