blob: 70b176b8ffd2161001ec623e6c5b3c26c3f757b9 [file] [log] [blame]
// 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.
package org.chromium.base.library_loader;
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import org.jni_zero.AccessedByNative;
import org.chromium.base.Log;
import org.chromium.base.StreamUtil;
import org.chromium.base.metrics.RecordHistogram;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import javax.annotation.concurrent.GuardedBy;
/*
* This class provides a way to load the native library as an alternative to System.loadLibrary().
* It has the ability to save RAM by placing the PT_GNU_RELRO segments in a shared memory region and
* memory-mapping this region from different processes. This approach saves a few MiB RAM compared
* to the normal placement of the segment in private dirty memory.
*
* In the main library only one PT_GNU_RELRO segment is present, and it maps only one section
* (.data.rel.ro), so here and below it will be referred as a "RELRO section".
*
* When two processes load the same native library at the _same_ memory address, the content of
* their RELRO section (which includes C++ vtables or any constants that contain pointers) will be
* largely identical. The exceptions are pointers to external, randomized, symbols, like those from
* some system libraries, but these are very rare in practice.
*
* In order to establish usage of the same shared region in different processes, the Linker can
* serialize/deserialize the relevant information to/from a Bundle. Providing the RELRO shared
* memory region is done by loading the library normally, then replacing the virtual address mapping
* behind the RELRO section with the one backed by the shared memory, with identical contents.
*
* Security considerations:
*
* - The shared RELRO memory region is always forced read-only after creation, which means it is
* impossible for a compromised process to map it read-write (e.g. by calling mmap() or
* mprotect()) and modify its content, altering values seen in other processes.
*
* - The common library load addresses are randomized for each instance of the program on the
* device.
*
* Usage:
*
* - The native shared library must be loaded with Linker.loadLibrary(), instead of
* System.loadLibrary(). The two functions should behave the same (at a high level).
*
* - Early on, before the attempt to load the library, the linker needs to be initialized either as
* a producer or a consumer of the RELRO region by invoking ensureInitialized(). Since various
* Chromium projects have vastly different initialization paths, for convenience the
* initialization runs implicitly as part of loading the library. In this case the behaviour is of
* a producer.
*
* - After loading the native library as a RELRO producer, the putSharedRelrosToBundle() becomes
* available to then send the Bundle to Linkers in other processes, consumed
* by takeSharedRelrosFromBundle().
*/
class Linker {
private static final String TAG = "Linker";
// Name of the library that contains the JNI code.
private static final String LINKER_JNI_LIBRARY = "chromium_android_linker";
// Constant guarding debug logging.
private static final boolean DEBUG = LibraryLoader.DEBUG;
// Constants used to pass the shared RELRO Bundle through Binder.
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
static final String SHARED_RELROS = "org.chromium.base.android.linker.shared_relros";
private static final String BASE_LOAD_ADDRESS =
"org.chromium.base.android.linker.base_load_address";
private final Object mLock = new Object();
// Holds the address and the size of the reserved address range until the library is loaded.
// After that its |mLoadAddress| and |mLoadSize| will reflect the state of the loaded library.
// Further, when the RELRO region is extracted into shared memory, the |mRelroFd| is initialized
// along with |mRelro{Start,Size}|. This object is serialized for use in other processes if the
// process is a "RELRO producer".
@GuardedBy("mLock")
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
protected LibInfo mLocalLibInfo;
// The library info that was transferred from another process. Only useful if it contains RELRO
// FD.
@GuardedBy("mLock")
private LibInfo mRemoteLibInfo;
// Whether this Linker instance should potentially create the RELRO region. Even if true, the
// library loading can fall back to the system linker without producing the region. The default
// value is used in tests, it is set to true so that the Linker does not have to wait for RELRO
// to arrive from another process.
@GuardedBy("mLock")
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
boolean mRelroProducer = true;
/**
* The state machine of library loading.
*
* The states are:
* - UNINITIALIZED: Initial state.
* - INITIALIZED: After linker initialization. Required for using the linker.
*
* When loading a library, there are two possibilities:
*
* - RELRO is not shared.
*
* - RELRO is shared: the producer process loads the library, consumers load the
* native library without waiting, they use the RELRO bundle later when it arrives, or
* immediately if it arrived before load
*
* Once the library has been loaded, in the producer process the state is DONE_PROVIDE_RELRO,
* and in consumer processes it is DONE.
*
* Transitions are:
* All processes: UNINITIALIZED -> INITIALIZED
* Producer: INITIALIZED -> DONE_PROVIDE_RELRO
* Consumer: INITIALIZED -> DONE
*
* When RELRO sharing failed for one reason or another, the state transitions remain the same,
* despite DONE_PROVIDE_RELRO being not appropriate as a name for this case.
*/
@IntDef({State.UNINITIALIZED, State.INITIALIZED, State.DONE_PROVIDE_RELRO, State.DONE})
@Retention(RetentionPolicy.SOURCE)
private @interface State {
int UNINITIALIZED = 0;
int INITIALIZED = 1;
int DONE_PROVIDE_RELRO = 2;
int DONE = 3;
}
@GuardedBy("mLock")
@State
private int mState = State.UNINITIALIZED;
void pretendLibraryIsLoadedForTesting() {
synchronized (mLock) {
mState = State.DONE;
}
}
private static Linker sLinkerForAssert;
Linker() {
// Only one instance is allowed in a given process because effects of loading a library are
// global, and the list of loaded libraries is not maintained at this level.
assert sLinkerForAssert == null;
sLinkerForAssert = this;
}
@IntDef({PreferAddress.FIND_RESERVED, PreferAddress.RESERVE_HINT, PreferAddress.RESERVE_RANDOM})
@Retention(RetentionPolicy.SOURCE)
public @interface PreferAddress {
int FIND_RESERVED = 0;
int RESERVE_HINT = 1;
int RESERVE_RANDOM = 2;
}
private String preferAddressToString(@PreferAddress int a) {
switch (a) {
case PreferAddress.FIND_RESERVED:
return "FIND_RESERVED";
case PreferAddress.RESERVE_HINT:
return "RESERVE_HINT";
case PreferAddress.RESERVE_RANDOM:
return "RESERVE_RANDOM";
default:
return String.valueOf(a);
}
}
// Exposed to be able to mock out an assertion.
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
boolean isNonZeroLoadAddress(LibInfo libInfo) {
return libInfo != null && libInfo.mLoadAddress != 0;
}
/**
* Initializes the Linker. This is the first method to be called on the instance.
*
* The Linker abstracts away from knowing process types and what the role of each process is.
* The LibraryLoader and the layers above tell the singleton linker whether it needs to produce
* the RELRO region, consume it, whether to use the address hint or to synthesize according to a
* strategy.
*
* In many cases finding the library load address is on the critical path, and needs to be
* transferred to other processes as soon as possible. For this purpose initialization is
* invoked separately from loading.
*
* The caller should provide the |preference| for obtaining the address at which to load the
* native library. The value is one of these:
*
* FIND_RESERVED, RESERVE_HINT, RESERVE_RANDOM.
*
* In the vast majority of cases the chosen preference will be fulfilled and the address (with
* the size) will be saved for use during {@link Linker#loadLibrary(String)}. In case the
* preferred way fails (due to address fragmentation, for example), a fallback attempt will be
* made with |preference| to the right of the current one in the list above. There is no
* fallback for RESERVE_RANDOM.
*
* FIND_RESERVED: Finds the (named) reserved address range for library loading. The caller needs
* to make sure that this is only called on platform versions supporting this memory
* reservation (Android Q+).
*
* RESERVE_HINT: Reserves enough of address range for loading a library, starting at the
* |addressHint| provided. The latter is expected to arrive from another process (randomized
* there), hence sometimes the address range may not be available.
*
* RESERVE_RANDOM: Finds a free random address range and reserves it.
*
* @param asRelroProducer whether the Linker instance will need to produce the shared memory
* region as part of work in {@link Linker#loadLibrary(String)}.
* @param preference the preference for obtaining the address, with fallback to a less memory
* efficient method
* @param addressHint the hint to be used when RESERVE_HINT is provided as |preference|
*
*/
final void ensureInitialized(
boolean asRelroProducer, @PreferAddress int preference, long addressHint) {
if (DEBUG) {
Log.i(
TAG,
"ensureInitialized(asRelroProducer=%b, preference=%s, "
+ "loadAddressHint=0x%x)",
asRelroProducer,
preferAddressToString(preference),
addressHint);
}
assert !asRelroProducer || preference != PreferAddress.RESERVE_HINT
: "Producer does not accept hints from outside";
synchronized (mLock) {
if (mState != State.UNINITIALIZED) return;
chooseAndReserveMemoryRange(asRelroProducer, preference, addressHint);
if (DEBUG) {
Log.i(TAG, "ensureInitialized: chose address=0x%x", mLocalLibInfo.mLoadAddress);
}
mState = State.INITIALIZED;
}
}
// Initializes the |mLocalLibInfo| and reserves the address range chosen.
@GuardedBy("mLock")
private void chooseAndReserveMemoryRange(
boolean asRelroProducer, @PreferAddress int preference, long addressHint) {
mLocalLibInfo = new LibInfo();
mRelroProducer = asRelroProducer;
loadLinkerJniLibraryLocked();
switch (preference) {
case PreferAddress.FIND_RESERVED:
boolean reservationFound =
getLinkerJni().findRegionReservedByWebViewZygote(mLocalLibInfo);
if (reservationFound) {
assert isNonZeroLoadAddress(mLocalLibInfo);
if (addressHint == 0 || addressHint == mLocalLibInfo.mLoadAddress) {
// Subtle: Both the producer and the consumer are expected to find the same
// address reservation. When |addressHint != 0| the producer was quick
// enough to provide the address before the consumer started initialization.
// Choosing the hint sounds like the right thing to do, and faster than
// looking up the named address range again. However, there is not enough
// information on how the hint was obtained by the producer. If it was found
// by a fallback (less likely) then the region must be reserved in this
// process. On systems where FIND_RESERVED is the preference, the most
// likely variant is that it is already reserved, hence check for it first.
return;
}
}
// Intentional fallthrough.
case PreferAddress.RESERVE_HINT:
mLocalLibInfo.mLoadAddress = addressHint;
if (addressHint != 0) {
getLinkerJni().reserveMemoryForLibrary(mLocalLibInfo);
if (mLocalLibInfo.mLoadAddress != 0) return;
}
// Intentional fallthrough.
case PreferAddress.RESERVE_RANDOM:
getLinkerJni().findMemoryRegionAtRandomAddress(mLocalLibInfo);
}
}
/**
* Extracts the native library start address as serialized by
* {@link #putLoadAddressToBundle(Bundle)} in a Linker instance from another process.
*/
static long extractLoadAddressFromBundle(Bundle bundle) {
return bundle.getLong(BASE_LOAD_ADDRESS, 0);
}
/**
* Serializes the native library start address. If not asked to be initialized previously,
* initializes the Linker as a RELRO producer.
* @param bundle Bundle to put the address to.
*/
void putLoadAddressToBundle(Bundle bundle) {
if (DEBUG) Log.i(TAG, "putLoadAddressToBundle");
synchronized (mLock) {
assert mState != State.UNINITIALIZED;
if (mLocalLibInfo != null && mLocalLibInfo.mLoadAddress != 0) {
bundle.putLong(BASE_LOAD_ADDRESS, mLocalLibInfo.mLoadAddress);
}
}
}
/**
* Tells whether atomic replacement of RELRO after library load should be performed. The linker
* should give up with RELRO on the retry that uses the RelroSharingMode.NO_SHARING. This method
* should be called after loading the library.
*/
@GuardedBy("mLock")
private boolean shouldAtomicallyReplaceRelroAfterLoad() {
if (mRemoteLibInfo != null && mState == State.DONE) {
// In the State.DONE while mRemoteLibInfo is not nullified yet. With an invalid load
// address it is impossible to locate the RELRO region in the current process. This
// could happen when the library loaded successfully only after the fallback to no
// sharing.
//
// TODO(pasko): There is no need to check for |mLoadAddress| here because in the worst
// case the zero address will be ignored on the native side of the
// atomicReplaceRelroLocked(). The takeSharedRelrosFromBundle() relies on zero addresses
// being ignored in native anyway. It seems the only effect of removing this check here
// will be extra added samples to the RelroSharingStatus2 histogram. This will be a tiny
// bit smoother to do after M99.
return mLocalLibInfo.mLoadAddress != 0;
}
return false;
}
/**
* Loads the native library using a given mode.
*
* @param library The library name to load.
* @param relroMode Tells whether and how to share relocations.
*/
@GuardedBy("mLock")
private void attemptLoadLibraryLocked(String library, @RelroSharingMode int relroMode) {
if (DEBUG) Log.i(TAG, "attemptLoadLibraryLocked: %s", library);
assert !library.equals(LINKER_JNI_LIBRARY);
loadLibraryImplLocked(library, relroMode);
if (DEBUG) {
Log.i(
TAG,
"Attempt to replace RELRO: remotenonnull=%b, state=%d",
mRemoteLibInfo != null,
mState);
}
if (shouldAtomicallyReplaceRelroAfterLoad()) {
atomicReplaceRelroLocked(/* relroAvailableImmediately= */ true);
}
}
/**
* Loads the native shared library.
*
* The library must not be the Chromium linker library.
*
* @param library The library name to load.
*/
final void loadLibrary(String library) {
synchronized (mLock) {
try {
// Normally Chrome/WebView processes initialize when they choose whether to produce
// or consume the shared relocations. Initialization here is the last resort to
// choose the load address in tests that forget to decide whether they are a
// producer or a consumer.
ensureInitializedImplicitlyAsLastResort();
// Load the library. During initialization Linker subclass reserves the address
// range where the library will be loaded and keeps it in |mLocalLibInfo|.
attemptLoadLibraryLocked(
library,
mRelroProducer ? RelroSharingMode.PRODUCE : RelroSharingMode.CONSUME);
} catch (UnsatisfiedLinkError e) {
Log.w(TAG, "Failed to load native library with shared RELRO, retrying without");
try {
// Retry without relocation sharing.
mLocalLibInfo.mLoadAddress = 0;
attemptLoadLibraryLocked(library, RelroSharingMode.NO_SHARING);
} catch (UnsatisfiedLinkError e2) {
Log.w(TAG, "Failed to load native library without RELRO sharing");
throw e2;
}
}
}
}
/**
* Serializes information about the RELRO region to be passed to a Linker in another process.
* @param bundle The Bundle to serialize to.
*/
void putSharedRelrosToBundle(Bundle bundle) {
Bundle relros = null;
synchronized (mLock) {
if (DEBUG) Log.i(TAG, "putSharedRelrosToBundle: state=%d", mState);
if (mState == State.DONE_PROVIDE_RELRO) {
assert mRelroProducer;
relros = mLocalLibInfo.toBundle();
}
bundle.putBundle(SHARED_RELROS, relros);
if (DEBUG && relros != null) {
Log.i(
TAG,
"putSharedRelrosToBundle() puts mLoadAddress=0x%x, mLoadSize=%d, "
+ "mRelroFd=%d",
mLocalLibInfo.mLoadAddress,
mLocalLibInfo.mLoadSize,
mLocalLibInfo.mRelroFd);
}
}
}
/**
* Deserializes the RELRO region information that was marshalled by
* {@link #putLoadAddressToBundle(Bundle)} and wakes up the threads waiting for it to replace
* the RELRO section in this process with shared memory.
* @param bundle The Bundle to extract the information from.
*/
void takeSharedRelrosFromBundle(Bundle bundle) {
if (DEBUG) Log.i(TAG, "called takeSharedRelrosFromBundle(%s)", bundle);
Bundle relros = bundle.getBundle(SHARED_RELROS);
if (relros == null) return;
LibInfo newRemote = LibInfo.fromBundle(relros);
if (newRemote == null) return;
synchronized (mLock) {
if (mRemoteLibInfo != null && mRemoteLibInfo.mRelroFd != -1) {
if (DEBUG) {
Log.i(
TAG,
"Attempt to replace RELRO a second time "
+ "library addr=0x%x, with new library addr=0x%x",
mRemoteLibInfo.mLoadAddress,
newRemote.mLoadAddress);
}
return;
}
mRemoteLibInfo = newRemote;
if (mState == State.DONE) {
atomicReplaceRelroLocked(/* relroAvailableImmediately= */ false);
}
}
}
@IntDef({
Linker.RelroSharingMode.NO_SHARING,
Linker.RelroSharingMode.PRODUCE,
Linker.RelroSharingMode.CONSUME
})
@Retention(RetentionPolicy.SOURCE)
private @interface RelroSharingMode {
// Do not attempt to create or use a RELRO region.
int NO_SHARING = 0;
// Produce a shared memory region with relocations.
int PRODUCE = 1;
// Load the library and (potentially later) use the externally provided region.
int CONSUME = 2;
}
// Loads the library via Linker for later consumption of the RELRO region, throws on
// failure to allow a safe retry.
@GuardedBy("mLock")
private void loadWithoutProducingRelro(String libFilePath) {
assert mRemoteLibInfo == null || libFilePath.equals(mRemoteLibInfo.mLibFilePath);
if (!getLinkerJni()
.loadLibrary(libFilePath, mLocalLibInfo, /* spawnRelroRegion= */ false)) {
resetAndThrow(String.format("Unable to load library: %s", libFilePath), null);
}
assert mLocalLibInfo.mRelroFd == -1;
}
// Loads the library via Linker. Does not throw on failure because in both cases
// System.loadLibrary() is useful. Records a histogram to count failures.
@GuardedBy("mLock")
private void loadAndProduceSharedRelro(String libFilePath) {
mLocalLibInfo.mLibFilePath = libFilePath;
if (getLinkerJni().loadLibrary(libFilePath, mLocalLibInfo, /* spawnRelroRegion= */ true)) {
if (DEBUG) {
Log.i(
TAG,
"Successfully spawned RELRO: mLoadAddress=0x%x, mLoadSize=%d",
mLocalLibInfo.mLoadAddress,
mLocalLibInfo.mLoadSize);
}
} else {
Log.e(TAG, "Unable to load with Linker, using the system linker instead");
// System.loadLibrary() below implements the fallback.
mLocalLibInfo.mRelroFd = -1;
}
RecordHistogram.recordBooleanHistogram(
"ChromiumAndroidLinker.RelroProvidedSuccessfully", mLocalLibInfo.mRelroFd != -1);
}
/**
* Linker-specific entry point for library loading. Loads the library into the address range
* provided by mLocalLibInfo. Assumes that the range is reserved with mmap(2).
*
* If the library is within a zip file, it must be uncompressed and page aligned in this file.
*
* The {@link #atomicReplaceRelroLocked(boolean)} must be implemented to *atomically* replace
* the RELRO region. Atomicity is required because the library code can be running concurrently
* on another thread.
*
* @param library The name of the library to load.
* @param relroMode Tells whether to use RELRO sharing and whether to produce or consume the
* RELRO region.
*/
@GuardedBy("mLock")
private void loadLibraryImplLocked(String library, @RelroSharingMode int relroMode) {
// Only loading monochrome is supported.
if (!"monochrome".equals(library) || DEBUG) {
Log.i(TAG, "loadLibraryImplLocked: %s, relroMode=%d", library, relroMode);
}
assert mState == State.INITIALIZED; // Only one successful call.
// Load or declare fallback to System.loadLibrary.
String libFilePath = System.mapLibraryName(library);
if (relroMode == RelroSharingMode.NO_SHARING) {
// System.loadLibrary() below implements the fallback.
mState = State.DONE;
} else if (relroMode == RelroSharingMode.PRODUCE) {
loadAndProduceSharedRelro(libFilePath); // Throws on a failed load.
// Next state is still to "provide relro", even if there is none, to indicate that
// consuming RELRO is not expected with this Linker instance.
mState = State.DONE_PROVIDE_RELRO;
} else {
assert relroMode == RelroSharingMode.CONSUME;
loadWithoutProducingRelro(libFilePath); // Does not throw.
// Done loading the library, but using an externally provided RELRO may happen later.
mState = State.DONE;
}
// Load the library a second time, in order to keep using lazy JNI registration. When
// loading the library with the Chromium linker, ART doesn't know about our library, so
// cannot resolve JNI methods lazily. Loading the library a second time makes sure it
// knows about us.
//
// This is not wasteful though, as libraries are reference-counted, and as a consequence the
// library is not really loaded a second time, and we keep relocation sharing.
try {
System.loadLibrary(library);
} catch (UnsatisfiedLinkError e) {
resetAndThrow("Failed at System.loadLibrary()", e);
}
}
/**
* Atomically replaces the RELRO with the shared memory region described in the
* |mRemoteLibInfo|. In order to perform the replacement verifies that the replacement is safe
* by inspecting |mLocalLibInfo| for equality of the library address range and the contents of
* the RELRO region.
*
* @param relroAvailableImmediately Whether the RELRO bundle arrived before
* {@link #loadLibraryImplLocked(String, int)} was called.
*/
@GuardedBy("mLock")
private void atomicReplaceRelroLocked(boolean relroAvailableImmediately) {
assert mRemoteLibInfo != null;
assert mState == State.DONE;
if (mRemoteLibInfo.mRelroFd == -1) return;
if (DEBUG) {
Log.i(
TAG,
"Received mRemoteLibInfo: mLoadAddress=0x%x, mLoadSize=%d",
mRemoteLibInfo.mLoadAddress,
mRemoteLibInfo.mLoadSize);
}
if (mLocalLibInfo == null) return;
getLinkerJni().useRelros(mLocalLibInfo.mLoadAddress, mRemoteLibInfo);
// *Not* closing the RELRO FD after using it because the FD may need to be transferred to
// another process after this point.
if (DEBUG) Log.i(TAG, "Immediate RELRO availability: %b", relroAvailableImmediately);
RecordHistogram.recordBooleanHistogram(
"ChromiumAndroidLinker.RelroAvailableImmediately", relroAvailableImmediately);
int status = getLinkerJni().getRelroSharingResult();
assert status != RelroSharingStatus.NOT_ATTEMPTED;
RecordHistogram.recordEnumeratedHistogram(
"ChromiumAndroidLinker.RelroSharingStatus2", status, RelroSharingStatus.COUNT);
}
/** Loads the Linker JNI library. Throws UnsatisfiedLinkError on error. */
@SuppressLint({"UnsafeDynamicallyLoadedCode"})
@GuardedBy("mLock")
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
void loadLinkerJniLibraryLocked() {
assert mState == State.UNINITIALIZED;
LibraryLoader.setEnvForNative();
if (DEBUG) Log.i(TAG, "Loading lib%s.so", LINKER_JNI_LIBRARY);
// May throw UnsatisfiedLinkError, we do not catch it as we cannot continue if we cannot
// load the linker. Technically we could try to load the library with the system linker on
// Android M+, but this should never happen, better to catch it in crash reports.
System.loadLibrary(LINKER_JNI_LIBRARY);
}
/**
* Initializes the auxiliary native library unless it was initialized before.
*
* Initializes as a RELRO producer without knowledge about preferred placement of the RELRO
* region. Should only be used as the last resort: when the simplicity of avoiding the explicit
* initialization is preferred over memory savings, such as in tests.
*/
private void ensureInitializedImplicitlyAsLastResort() {
ensureInitialized(
/* asRelroProducer= */ true, PreferAddress.RESERVE_RANDOM, /* addressHint= */ 0);
}
@GuardedBy("mLock")
private void resetAndThrow(String message, UnsatisfiedLinkError cause) {
mState = State.INITIALIZED;
Log.e(TAG, message);
var e = new UnsatisfiedLinkError(message);
if (cause != null) {
e.initCause(cause);
}
throw e;
}
/**
* Holds the information for a given native library or the address range for the future library
* load. Owns the shared RELRO file descriptor.
*
* Native code accesses the fields of this class by name. Renaming should be done on C++ size as
* well.
*/
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
static class LibInfo implements Parcelable {
private static final String EXTRA_LINKER_LIB_INFO = "libinfo";
LibInfo() {}
// from Parcelable
LibInfo(Parcel in) {
// See below in writeToParcel() for the serialization protocol.
mLibFilePath = in.readString();
mLoadAddress = in.readLong();
mLoadSize = in.readLong();
mRelroStart = in.readLong();
mRelroSize = in.readLong();
boolean hasRelroFd = in.readInt() != 0;
if (hasRelroFd) {
ParcelFileDescriptor fd = ParcelFileDescriptor.CREATOR.createFromParcel(in);
// If CreateSharedRelro fails, the OS file descriptor will be -1 and |fd| will be
// null.
if (fd != null) {
mRelroFd = fd.detachFd();
}
} else {
mRelroFd = -1;
}
}
public void close() {
if (mRelroFd >= 0) {
StreamUtil.closeQuietly(ParcelFileDescriptor.adoptFd(mRelroFd));
mRelroFd = -1;
}
}
public static LibInfo fromBundle(Bundle bundle) {
bundle.setClassLoader(Linker.class.getClassLoader());
return bundle.getParcelable(EXTRA_LINKER_LIB_INFO);
}
public Bundle toBundle() {
Bundle bundle = new Bundle();
bundle.putParcelable(EXTRA_LINKER_LIB_INFO, this);
return bundle;
}
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeString(mLibFilePath);
out.writeLong(mLoadAddress);
out.writeLong(mLoadSize);
out.writeLong(mRelroStart);
out.writeLong(mRelroSize);
// Parcel#writeBoolean() is API level 29, so use an int instead.
// We use this as a flag as we cannot serialize an invalid fd.
out.writeInt(mRelroFd >= 0 ? 1 : 0);
if (mRelroFd >= 0) {
try {
ParcelFileDescriptor fd = ParcelFileDescriptor.fromFd(mRelroFd);
fd.writeToParcel(out, 0);
fd.close();
} catch (java.io.IOException e) {
Log.e(TAG, "Can't write LibInfo file descriptor to parcel", e);
}
}
}
@Override
public int describeContents() {
return Parcelable.CONTENTS_FILE_DESCRIPTOR;
}
// From Parcelable
public static final Parcelable.Creator<LibInfo> CREATOR =
new Parcelable.Creator<LibInfo>() {
@Override
public LibInfo createFromParcel(Parcel in) {
return new LibInfo(in);
}
@Override
public LibInfo[] newArray(int size) {
return new LibInfo[size];
}
};
public String mLibFilePath;
// IMPORTANT: Don't change these fields without modifying the
// native code that accesses them directly!
@AccessedByNative public long mLoadAddress; // page-aligned library load address.
@AccessedByNative public long mLoadSize; // page-aligned library load size.
@AccessedByNative public long mRelroStart; // page-aligned address in memory, or 0 if none.
@AccessedByNative public long mRelroSize; // page-aligned size in memory, or 0.
@AccessedByNative public int mRelroFd = -1; // shared RELRO file descriptor, or -1
}
interface Natives {
/**
* Reserves a memory region (=mapping) of sufficient size to hold the loaded library before
* the real size is known. The mmap(2) being used here provides built in randomization.
*
* On failure |libInfo.mLoadAddress| should be set to 0 and the LibraryLoader will fall back
* to loading using the system linker.
*
* @param libInfo holds the output values: |mLoadAddress| and |mLoadSize|. On failure sets
* the |libInfo.mLoadAddress| to 0.
*/
void findMemoryRegionAtRandomAddress(@NonNull LibInfo libInfo);
/**
* Reserves the fixed address range starting at |libInfo.mLoadAddress| big enough to load
* the main native library. The size of the range is an internal detail of the native
* implementation.
*
* @param libInfo holds the output values: |mLoadAddress| and |mLoadSize|. On success
* returns the size in |libInfo.mLoadSize|. On failure sets the
* |libInfo.mLoadAddress| to 0.
*/
void reserveMemoryForLibrary(@NonNull LibInfo libInfo);
/**
* Finds the (named) address range reservation made by the system zygote and dedicated for
* loading the native library. Reads /proc/self/maps, which is a slow operation (up to a few
* ms).
*
* @param libInfo holds the output values: |mLoadAddress| and |mLoadSize|. On success saves
* the start address and the size of the webview memory reservation to them.
* @return whether the region was found.
*/
boolean findRegionReservedByWebViewZygote(@NonNull LibInfo libInfo);
/**
* Load the native library.
*
* @param libFilePath library file name.
* @param libInfo holds the information about the loaded library and the associated RELRO
* region if the latter was created.
* @param spawnRelroRegion whether to spawn a new RELRO region.
* @return false on failure.
*/
boolean loadLibrary(String libFilePath, LibInfo libInfo, boolean spawnRelroRegion);
/**
* Replace the current RELRO data in memory with the incoming RELRO region.
*
* @param localLoadAddress the address at which this Linker loaded the native library.
* @param remoteLibInfo contains the RELRO region for replacement, and the start address
* required for the library to be able to use this region.
* @return whether the operation was a success.
*/
boolean useRelros(long localLoadAddress, LibInfo remoteLibInfo);
/**
* Reveals the result of RELRO sharing after the library has been loaded.
*
* @return RelroSharingStatus.
*/
int getRelroSharingResult();
}
private static Linker.Natives sNativesInstance;
static void setLinkerNativesForTesting(Natives instance) {
sNativesInstance = instance;
sLinkerForAssert = null; // Also allow to create Linker multiple times in tests.
}
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
static Linker.Natives getLinkerJni() {
if (sNativesInstance != null) return sNativesInstance;
return new LinkerJni(); // R8 optimizes away all construction except the initial one.
}
}