Commit fdb31883 authored by Egor Pasko's avatar Egor Pasko Committed by Commit Bot

Use the LegacyLinker instead of the ModernLinker

Remove the ModernLinker since it becomes unused. Also remove the possibility to
set the linker differently in tests.

Public document explaining the motivation: https://goo.gl/kWnHYr

BUG=719977 (google-internal)

Change-Id: I5b9b85817093b36127fa4a607bced57b4350a5b8
Reviewed-on: https://chromium-review.googlesource.com/538582
Commit-Queue: Egor Pasko <pasko@chromium.org>
Reviewed-by: default avatarYaron Friedman <yfriedman@chromium.org>
Reviewed-by: default avatarRichard Coles <torne@chromium.org>
Cr-Commit-Position: refs/heads/master@{#517838}
parent 70f8551d
...@@ -2654,7 +2654,6 @@ if (is_android) { ...@@ -2654,7 +2654,6 @@ if (is_android) {
"android/java/src/org/chromium/base/library_loader/LibraryLoader.java", "android/java/src/org/chromium/base/library_loader/LibraryLoader.java",
"android/java/src/org/chromium/base/library_loader/Linker.java", "android/java/src/org/chromium/base/library_loader/Linker.java",
"android/java/src/org/chromium/base/library_loader/LoaderErrors.java", "android/java/src/org/chromium/base/library_loader/LoaderErrors.java",
"android/java/src/org/chromium/base/library_loader/ModernLinker.java",
"android/java/src/org/chromium/base/library_loader/NativeLibraryPreloader.java", "android/java/src/org/chromium/base/library_loader/NativeLibraryPreloader.java",
"android/java/src/org/chromium/base/library_loader/ProcessInitException.java", "android/java/src/org/chromium/base/library_loader/ProcessInitException.java",
"android/java/src/org/chromium/base/metrics/CachedMetrics.java", "android/java/src/org/chromium/base/metrics/CachedMetrics.java",
......
...@@ -4,13 +4,11 @@ ...@@ -4,13 +4,11 @@
package org.chromium.base.library_loader; package org.chromium.base.library_loader;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Parcel; import android.os.Parcel;
import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor;
import android.os.Parcelable; import android.os.Parcelable;
import org.chromium.base.ContextUtils;
import org.chromium.base.Log; import org.chromium.base.Log;
import org.chromium.base.annotations.AccessedByNative; import org.chromium.base.annotations.AccessedByNative;
...@@ -153,7 +151,7 @@ public abstract class Linker { ...@@ -153,7 +151,7 @@ public abstract class Linker {
private static final String LINKER_JNI_LIBRARY = "chromium_android_linker"; private static final String LINKER_JNI_LIBRARY = "chromium_android_linker";
// Constants used to control the behaviour of the browser process with // Constants used to control the behaviour of the browser process with
// regards to the shared RELRO section. Not applicable to ModernLinker. // regards to the shared RELRO section.
// NEVER -> The browser never uses it itself. // NEVER -> The browser never uses it itself.
// LOW_RAM_ONLY -> It is only used on devices with low RAM. // LOW_RAM_ONLY -> It is only used on devices with low RAM.
// ALWAYS -> It is always used. // ALWAYS -> It is always used.
...@@ -164,13 +162,12 @@ public abstract class Linker { ...@@ -164,13 +162,12 @@ public abstract class Linker {
// Configuration variable used to control how the browser process uses the // Configuration variable used to control how the browser process uses the
// shared RELRO. Only change this while debugging linker-related issues. // shared RELRO. Only change this while debugging linker-related issues.
// Not used by ModernLinker.
// NOTE: This variable's name is known and expected by the Linker test scripts. // NOTE: This variable's name is known and expected by the Linker test scripts.
public static final int BROWSER_SHARED_RELRO_CONFIG = public static final int BROWSER_SHARED_RELRO_CONFIG =
BROWSER_SHARED_RELRO_CONFIG_LOW_RAM_ONLY; BROWSER_SHARED_RELRO_CONFIG_LOW_RAM_ONLY;
// Constants used to control the memory device config. Can be set explicitly // Constants used to control the memory device config. Can be set explicitly
// by setMemoryDeviceConfigForTesting(). Not applicable to ModernLinker. // by setMemoryDeviceConfigForTesting().
// INIT -> Value is undetermined (will check at runtime). // INIT -> Value is undetermined (will check at runtime).
// LOW -> This is a low-memory device. // LOW -> This is a low-memory device.
// NORMAL -> This is not a low-memory device. // NORMAL -> This is not a low-memory device.
...@@ -181,7 +178,6 @@ public abstract class Linker { ...@@ -181,7 +178,6 @@ public abstract class Linker {
// Indicates if this is a low-memory device or not. The default is to // Indicates if this is a low-memory device or not. The default is to
// determine this by probing the system at runtime, but this can be forced // determine this by probing the system at runtime, but this can be forced
// for testing by calling setMemoryDeviceConfigForTesting(). // for testing by calling setMemoryDeviceConfigForTesting().
// Not used by ModernLinker.
protected int mMemoryDeviceConfig = MEMORY_DEVICE_CONFIG_INIT; protected int mMemoryDeviceConfig = MEMORY_DEVICE_CONFIG_INIT;
// Set to true to enable debug logs. // Set to true to enable debug logs.
...@@ -208,13 +204,6 @@ public abstract class Linker { ...@@ -208,13 +204,6 @@ public abstract class Linker {
// ensure that we don't try to load outside the area originally requested. // ensure that we don't try to load outside the area originally requested.
protected static final int ADDRESS_SPACE_RESERVATION = 192 * 1024 * 1024; protected static final int ADDRESS_SPACE_RESERVATION = 192 * 1024 * 1024;
// Constants used to indicate a given Linker implementation, for testing.
// LEGACY -> Always uses the LegacyLinker implementation.
// MODERN -> Always uses the ModernLinker implementation.
// NOTE: These names are known and expected by the Linker test scripts.
public static final int LINKER_IMPLEMENTATION_LEGACY = 1;
public static final int LINKER_IMPLEMENTATION_MODERN = 2;
// Singleton. // Singleton.
private static Linker sSingleton; private static Linker sSingleton;
private static Object sSingletonLock = new Object(); private static Object sSingletonLock = new Object();
...@@ -223,36 +212,19 @@ public abstract class Linker { ...@@ -223,36 +212,19 @@ public abstract class Linker {
protected Linker() { } protected Linker() { }
/** /**
* Get singleton instance. Returns either a LegacyLinker or a ModernLinker. * Get singleton instance. Returns a LegacyLinker.
*
* Returns a ModernLinker if running on Android M or later, otherwise returns
* a LegacyLinker.
*
* ModernLinker requires OS features from Android M and later: a system linker
* that handles packed relocations and load from APK, and android_dlopen_ext()
* for shared RELRO support. It cannot run on Android releases earlier than M.
*
* LegacyLinker runs on all Android releases but it is slower and more complex
* than ModernLinker, so ModernLinker is preferred for Android M and later.
* *
* @return the Linker implementation instance. * @return the Linker implementation instance.
*/ */
public static final Linker getInstance() { public static final Linker getInstance() {
// TODO(pasko): The linker is created dynamically for historical
// reasons. Formerly there was a runtime choice between LegacyLinker and
// ModernLinker. To simplify, move the LegacyLinker logic into the
// Linker.
synchronized (sSingletonLock) { synchronized (sSingletonLock) {
if (sSingleton == null) { if (sSingleton == null) {
// With incremental install, it's important to fall back to the "normal"
// library loading path in order for the libraries to be found.
String appClass =
ContextUtils.getApplicationContext().getApplicationInfo().className;
boolean isIncrementalInstall =
appClass != null && appClass.contains("incrementalinstall");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !isIncrementalInstall) {
sSingleton = ModernLinker.create();
} else {
sSingleton = LegacyLinker.create(); sSingleton = LegacyLinker.create();
} }
Log.i(TAG, "Using linker: " + sSingleton.getClass().getName());
}
return sSingleton; return sSingleton;
} }
} }
...@@ -292,56 +264,6 @@ public abstract class Linker { ...@@ -292,56 +264,6 @@ public abstract class Linker {
} }
} }
/**
* Set Linker implementation type.
* For testing. Sets either a LegacyLinker or a ModernLinker. Must be called
* before getInstance().
*
* @param type LINKER_IMPLEMENTATION_LEGACY or LINKER_IMPLEMENTATION_MODERN
*/
public static final void setImplementationForTesting(int type) {
// Sanity check. This method may only be called during tests.
assertLinkerTestsAreEnabled();
assertForTesting(type == LINKER_IMPLEMENTATION_LEGACY
|| type == LINKER_IMPLEMENTATION_MODERN);
synchronized (sSingletonLock) {
assertForTesting(sSingleton == null);
if (type == LINKER_IMPLEMENTATION_MODERN) {
sSingleton = ModernLinker.create();
} else if (type == LINKER_IMPLEMENTATION_LEGACY) {
sSingleton = LegacyLinker.create();
}
Log.i(TAG, "Forced linker: " + sSingleton.getClass().getName());
}
}
/**
* Get Linker implementation type.
* For testing.
*
* @return LINKER_IMPLEMENTATION_LEGACY or LINKER_IMPLEMENTATION_MODERN
*/
public final int getImplementationForTesting() {
// Sanity check. This method may only be called during tests.
assertLinkerTestsAreEnabled();
synchronized (sSingletonLock) {
assertForTesting(sSingleton == this);
if (sSingleton instanceof ModernLinker) {
return LINKER_IMPLEMENTATION_MODERN;
} else if (sSingleton instanceof LegacyLinker) {
return LINKER_IMPLEMENTATION_LEGACY;
} else {
Log.wtf(TAG, "Invalid linker: " + sSingleton.getClass().getName());
assertForTesting(false);
}
return 0;
}
}
/** /**
* A public interface used to run runtime linker tests after loading * A public interface used to run runtime linker tests after loading
* libraries. Should only be used to implement the linker unit tests, * libraries. Should only be used to implement the linker unit tests,
...@@ -397,32 +319,30 @@ public abstract class Linker { ...@@ -397,32 +319,30 @@ public abstract class Linker {
} }
/** /**
* Set up the Linker for a test. * Sets the test class name.
* Convenience function that calls setImplementationForTesting() to force an
* implementation, and then setTestRunnerClassNameForTesting() to set the test
* class name.
* *
* On first call, instantiates a Linker of the requested type and sets its test * On first call, instantiates a Linker of the requested type and sets its test
* runner class name. On subsequent calls, checks that the singleton produced by * runner class name. On subsequent calls, checks that the singleton produced by
* the first call matches the requested type and test runner class name. * the first call matches the test runner class name.
*/ */
public static final void setupForTesting(int type, String testRunnerClassName) { public static final void setupForTesting(String testRunnerClassName) {
if (DEBUG) { if (DEBUG) {
Log.i(TAG, "setupForTesting(" + type + ", " + testRunnerClassName + ") called"); Log.i(TAG, "setupForTesting(" + testRunnerClassName + ") called");
} }
// Sanity check. This method may only be called during tests. // Sanity check. This method may only be called during tests.
assertLinkerTestsAreEnabled(); assertLinkerTestsAreEnabled();
synchronized (sSingletonLock) { synchronized (sSingletonLock) {
// If this is the first call, configure the Linker to the given type and test class. // If this is the first call, instantiate the Linker and the test class.
if (sSingleton == null) { if (sSingleton == null) {
setImplementationForTesting(type); assertLinkerTestsAreEnabled();
assertForTesting(sSingleton == null);
sSingleton = LegacyLinker.create();
sSingleton.setTestRunnerClassNameForTesting(testRunnerClassName); sSingleton.setTestRunnerClassNameForTesting(testRunnerClassName);
return; return;
} }
// If not the first call, check that the Linker configuration matches this request. // If not the first call, check that the Linker configuration matches this request.
assertForTesting(sSingleton.getImplementationForTesting() == type);
String ourTestRunnerClassName = sSingleton.getTestRunnerClassNameForTesting(); String ourTestRunnerClassName = sSingleton.getTestRunnerClassNameForTesting();
if (testRunnerClassName == null) { if (testRunnerClassName == null) {
assertForTesting(ourTestRunnerClassName == null); assertForTesting(ourTestRunnerClassName == null);
......
// 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.
package org.chromium.base.library_loader;
import android.os.Bundle;
import android.os.SystemClock;
import org.chromium.base.Log;
import org.chromium.base.PathUtils;
import java.util.HashMap;
import java.util.Locale;
import javax.annotation.Nullable;
/*
* For more, see Technical note, Security considerations, and the explanation
* of how this class is supposed to be used in Linker.java.
*/
/**
* Provides a concrete implementation of the Chromium Linker.
*
* This Linker implementation uses the Android M and later system linker to map and then
* run Chrome for Android.
*
* For more on the operations performed by the Linker, see {@link Linker}.
*/
class ModernLinker extends Linker {
// Log tag for this class.
private static final String TAG = "LibraryLoader";
// Becomes true after linker initialization.
private boolean mInitialized;
// Becomes true to indicate this process needs to wait for a shared RELRO in LibraryLoad().
private boolean mWaitForSharedRelros;
// The map of all RELRO sections either created or used in this process.
private HashMap<String, LibInfo> mSharedRelros;
// Cached Bundle representation of the RELRO sections map for transfer across processes.
private Bundle mSharedRelrosBundle;
// Set to true if this runs in the browser process. Disabled by initServiceProcess().
private boolean mInBrowserProcess = true;
// Current common random base load address. A value of -1 indicates not yet initialized.
private long mBaseLoadAddress = -1;
// Current fixed-location load address for the next library called by loadLibrary().
// Initialized to mBaseLoadAddress in prepareLibraryLoad(), and then adjusted as each
// library is loaded by loadLibrary().
private long mCurrentLoadAddress = -1;
// Becomes true once prepareLibraryLoad() has been called.
private boolean mPrepareLibraryLoadCalled;
// The map of libraries that are currently loaded in this process.
private HashMap<String, LibInfo> mLoadedLibraries;
// Private singleton constructor, and singleton factory method.
private ModernLinker() { }
static Linker create() {
return new ModernLinker();
}
// Used internally to initialize the linker's data. Assumes lock is held.
private void ensureInitializedLocked() {
assert Thread.holdsLock(mLock);
assert NativeLibraries.sUseLinker;
// On first call, load libchromium_android_linker.so. Cannot be done in the
// constructor because the instance is constructed on the UI thread.
if (!mInitialized) {
loadLinkerJniLibrary();
mInitialized = true;
}
}
/**
* Call this method to determine if the linker will try to use shared RELROs
* for the browser process.
*/
@Override
public boolean isUsingBrowserSharedRelros() {
// This Linker does not attempt to share RELROS between the browser and
// the renderers, but only between renderers.
return false;
}
/**
* Call this method just before loading any native shared libraries in this process.
* Loads the Linker's JNI library, and initializes the variables involved in the
* implementation of shared RELROs.
*/
@Override
public void prepareLibraryLoad() {
if (DEBUG) {
Log.i(TAG, "prepareLibraryLoad() called");
}
assert NativeLibraries.sUseLinker;
synchronized (mLock) {
assert !mPrepareLibraryLoadCalled;
ensureInitializedLocked();
// If in the browser, generate a random base load address for service processes
// and create an empty shared RELROs map. For service processes, the shared
// RELROs map must remain null until set by useSharedRelros().
if (mInBrowserProcess) {
setupBaseLoadAddressLocked();
mSharedRelros = new HashMap<String, LibInfo>();
}
// Create an empty loaded libraries map.
mLoadedLibraries = new HashMap<String, LibInfo>();
// Start the current load address at the base load address.
mCurrentLoadAddress = mBaseLoadAddress;
mPrepareLibraryLoadCalled = true;
}
}
/**
* Call this method just after loading all native shared libraries in this process.
* If not in the browser, closes the LibInfo entries used for RELRO sharing.
*/
@Override
public void finishLibraryLoad() {
if (DEBUG) {
Log.i(TAG, "finishLibraryLoad() called");
}
synchronized (mLock) {
assert mPrepareLibraryLoadCalled;
// Close shared RELRO file descriptors if not in the browser.
if (!mInBrowserProcess && mSharedRelros != null) {
closeLibInfoMap(mSharedRelros);
mSharedRelros = null;
}
// If testing, run tests now that all libraries are loaded and initialized.
if (NativeLibraries.sEnableLinkerTests) {
runTestRunnerClassForTesting(0, mInBrowserProcess);
}
}
}
// Used internally to wait for shared RELROs. Returns once useSharedRelros() has been
// called to supply a valid shared RELROs bundle.
private void waitForSharedRelrosLocked() {
if (DEBUG) {
Log.i(TAG, "waitForSharedRelros called");
}
assert Thread.holdsLock(mLock);
// Return immediately if shared RELROs are already available.
if (mSharedRelros != null) {
return;
}
// Wait until notified by useSharedRelros() that shared RELROs have arrived.
long startTime = DEBUG ? SystemClock.uptimeMillis() : 0;
while (mSharedRelros == null) {
try {
mLock.wait();
} catch (InterruptedException e) {
// Restore the thread's interrupt status.
Thread.currentThread().interrupt();
}
}
if (DEBUG) {
Log.i(TAG, String.format(
Locale.US, "Time to wait for shared RELRO: %d ms",
SystemClock.uptimeMillis() - startTime));
}
}
/**
* Call this to send a Bundle containing the shared RELRO sections to be
* used in this process. If initServiceProcess() was previously called,
* libraryLoad() will wait until this method is called in another
* thread with a non-null value.
*
* @param bundle The Bundle instance containing a map of shared RELRO sections
* to use in this process.
*/
@Override
public void useSharedRelros(Bundle bundle) {
if (DEBUG) {
Log.i(TAG, "useSharedRelros() called with " + bundle);
}
synchronized (mLock) {
mSharedRelros = createLibInfoMapFromBundle(bundle);
mLock.notifyAll();
}
}
/**
* Call this to retrieve the shared RELRO sections created in this process,
* after loading all libraries.
*
* @return a new Bundle instance, or null if RELRO sharing is disabled on
* this system, or if initServiceProcess() was called previously.
*/
@Override
public Bundle getSharedRelros() {
if (DEBUG) {
Log.i(TAG, "getSharedRelros() called");
}
synchronized (mLock) {
if (!mInBrowserProcess) {
if (DEBUG) {
Log.i(TAG, "Not in browser, so returning null Bundle");
}
return null;
}
// Create a new Bundle containing RELRO section information for all the shared
// RELROs created while loading libraries.
if (mSharedRelrosBundle == null && mSharedRelros != null) {
mSharedRelrosBundle = createBundleFromLibInfoMap(mSharedRelros);
if (DEBUG) {
Log.i(TAG, "Shared RELRO bundle created from map: " + mSharedRelrosBundle);
}
}
if (DEBUG) {
Log.i(TAG, "Returning " + mSharedRelrosBundle);
}
return mSharedRelrosBundle;
}
}
/**
* Call this method before loading any libraries to indicate that this
* process shall neither create or reuse shared RELRO sections.
*/
@Override
public void disableSharedRelros() {
if (DEBUG) {
Log.i(TAG, "disableSharedRelros() called");
}
synchronized (mLock) {
// Mark this as a service process, and disable wait for shared RELRO.
mInBrowserProcess = false;
mWaitForSharedRelros = false;
}
}
/**
* Call this method before loading any libraries to indicate that this
* process is ready to reuse shared RELRO sections from another one.
* Typically used when starting service processes.
*
* @param baseLoadAddress the base library load address to use.
*/
@Override
public void initServiceProcess(long baseLoadAddress) {
if (DEBUG) {
Log.i(TAG, String.format(
Locale.US, "initServiceProcess(0x%x) called",
baseLoadAddress));
}
synchronized (mLock) {
assert !mPrepareLibraryLoadCalled;
// Mark this as a service process, and flag wait for shared RELRO.
// Save the base load address passed in.
mInBrowserProcess = false;
mWaitForSharedRelros = true;
mBaseLoadAddress = baseLoadAddress;
}
}
/**
* Retrieve the base load address for libraries that share RELROs.
*
* @return a common, random base load address, or 0 if RELRO sharing is
* disabled.
*/
@Override
public long getBaseLoadAddress() {
synchronized (mLock) {
ensureInitializedLocked();
setupBaseLoadAddressLocked();
if (DEBUG) {
Log.i(TAG, String.format(
Locale.US, "getBaseLoadAddress() returns 0x%x",
mBaseLoadAddress));
}
return mBaseLoadAddress;
}
}
// Used internally to lazily setup the common random base load address.
private void setupBaseLoadAddressLocked() {
assert Thread.holdsLock(mLock);
// No-op if the base load address is already set up.
if (mBaseLoadAddress == -1) {
mBaseLoadAddress = getRandomBaseLoadAddress();
}
if (mBaseLoadAddress == 0) {
// If the random address is 0 there are issues with finding enough
// free address space, so disable RELRO shared / fixed load addresses.
Log.w(TAG, "Disabling shared RELROs due address space pressure");
mWaitForSharedRelros = false;
}
}
/**
* Load a native shared library with the Chromium linker. If the zip file
* is not null, the shared library must be uncompressed and page aligned
* inside the zipfile. The library must not be the Chromium linker library.
*
* If asked to wait for shared RELROs, this function will block library loads
* until the shared RELROs bundle is received by useSharedRelros().
*
* @param zipFilePath The path of the zip file containing the library (or null).
* @param libFilePath The path of the library (possibly in the zip file).
* @param isFixedAddressPermitted If true, uses a fixed load address if one was
* supplied, otherwise ignores the fixed address and loads wherever available.
*/
@Override
void loadLibraryImpl(@Nullable String zipFilePath,
String libFilePath,
boolean isFixedAddressPermitted) {
if (DEBUG) {
Log.i(TAG, "loadLibraryImpl: "
+ zipFilePath + ", " + libFilePath + ", " + isFixedAddressPermitted);
}
synchronized (mLock) {
assert mPrepareLibraryLoadCalled;
String dlopenExtPath;
if (zipFilePath != null) {
// The android_dlopen_ext() function understands strings with the format
// <zip_path>!/<file_path> to represent the file_path element within the zip
// file at zip_path. This enables directly loading from APK. We add the
// "crazy." prefix to the path in the zip file to prevent the Android package
// manager from seeing this as a library and so extracting it from the APK.
String cpuAbi = nativeGetCpuAbi();
dlopenExtPath = zipFilePath + "!/lib/" + cpuAbi + "/crazy." + libFilePath;
} else {
// Not loading from APK directly, so simply pass the library name to
// android_dlopen_ext().
dlopenExtPath = libFilePath;
}
if (mLoadedLibraries.containsKey(dlopenExtPath)) {
if (DEBUG) {
Log.i(TAG, "Not loading " + libFilePath + " twice");
}
return;
}
// If not in the browser, shared RELROs are not disabled, and fixed addresses
// have not been disallowed, load the library at a fixed address. Otherwise,
// load anywhere.
long loadAddress = 0;
if (!mInBrowserProcess && mWaitForSharedRelros && isFixedAddressPermitted) {
loadAddress = mCurrentLoadAddress;
// For multiple libraries, ensure we stay within reservation range.
if (loadAddress > mBaseLoadAddress + ADDRESS_SPACE_RESERVATION) {
String errorMessage = "Load address outside reservation, for: " + libFilePath;
Log.e(TAG, errorMessage);
throw new UnsatisfiedLinkError(errorMessage);
}
}
LibInfo libInfo = new LibInfo();
if (mInBrowserProcess && mCurrentLoadAddress != 0) {
// We are in the browser, and with a current load address that indicates that
// there is enough address space for shared RELRO to operate. Create the
// shared RELRO, and store it in the map.
String relroPath = PathUtils.getDataDirectory() + "/RELRO:" + libFilePath;
if (nativeCreateSharedRelro(dlopenExtPath,
mCurrentLoadAddress, relroPath, libInfo)) {
mSharedRelros.put(dlopenExtPath, libInfo);
} else {
String errorMessage = "Unable to create shared relro: " + relroPath;
Log.w(TAG, errorMessage);
}
} else if (!mInBrowserProcess && mCurrentLoadAddress != 0 && mWaitForSharedRelros) {
// We are in a service process, again with a current load address that is
// suitable for shared RELRO, and we are to wait for shared RELROs. So
// do that, then use the map we receive to provide libinfo for library load.
waitForSharedRelrosLocked();
if (mSharedRelros.containsKey(dlopenExtPath)) {
libInfo = mSharedRelros.get(dlopenExtPath);
}
}
// Load the library. In the browser, loadAddress is 0, so nativeLoadLibrary()
// will load without shared RELRO. Otherwise, it uses shared RELRO if the attached
// libInfo is usable.
if (!nativeLoadLibrary(dlopenExtPath, loadAddress, libInfo)) {
String errorMessage = "Unable to load library: " + dlopenExtPath;
Log.e(TAG, errorMessage);
throw new UnsatisfiedLinkError(errorMessage);
}
// Print the load address to the logcat when testing the linker. The format
// of the string is expected by the Python test_runner script as one of:
// BROWSER_LIBRARY_ADDRESS: <library-name> <address>
// RENDERER_LIBRARY_ADDRESS: <library-name> <address>
// Where <library-name> is the library name, and <address> is the hexadecimal load
// address.
if (NativeLibraries.sEnableLinkerTests) {
String tag = mInBrowserProcess ? "BROWSER_LIBRARY_ADDRESS"
: "RENDERER_LIBRARY_ADDRESS";
Log.i(TAG, String.format(
Locale.US, "%s: %s %x", tag, libFilePath, libInfo.mLoadAddress));
}
if (loadAddress != 0 && mCurrentLoadAddress != 0) {
// Compute the next current load address. If mCurrentLoadAddress
// is not 0, this is an explicit library load address.
mCurrentLoadAddress = libInfo.mLoadAddress + libInfo.mLoadSize
+ BREAKPAD_GUARD_REGION_BYTES;
}
mLoadedLibraries.put(dlopenExtPath, libInfo);
if (DEBUG) {
Log.i(TAG, "Library details " + libInfo.toString());
}
}
}
/**
* Native method to return the CPU ABI.
* Obtaining it from the linker's native code means that we always correctly
* match the loaded library's ABI to the linker's ABI.
*
* @return CPU ABI string.
*/
private static native String nativeGetCpuAbi();
/**
* Native method used to load a library.
*
* @param dlopenExtPath For load from APK, the path to the enclosing
* zipfile concatenated with "!/" and the path to the library within the zipfile;
* otherwise the platform specific library name (e.g. libfoo.so).
* @param loadAddress Explicit load address, or 0 for randomized one.
* @param libInfo If not null, the mLoadAddress and mLoadSize fields
* of this LibInfo instance will set on success.
* @return true for success, false otherwise.
*/
private static native boolean nativeLoadLibrary(String dlopenExtPath,
long loadAddress,
LibInfo libInfo);
/**
* Native method used to create a shared RELRO section.
* Creates a shared RELRO file for the given library. Done by loading a
* a new temporary library at the specified address, saving the RELRO section
* from it, then unloading.
*
* @param dlopenExtPath For load from APK, the path to the enclosing
* zipfile concatenated with "!/" and the path to the library within the zipfile;
* otherwise the platform specific library name (e.g. libfoo.so).
* @param loadAddress load address, which can be different from the one
* used to load the library in the current process!
* @param relroPath Path to the shared RELRO file for this library.
* @param libInfo libInfo instance. On success, the mRelroStart, mRelroSize
* and mRelroFd will be set.
* @return true on success, false otherwise.
*/
private static native boolean nativeCreateSharedRelro(String dlopenExtPath,
long loadAddress,
String relroPath,
LibInfo libInfo);
}
...@@ -13,8 +13,6 @@ shared_library("chromium_android_linker") { ...@@ -13,8 +13,6 @@ 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",
"modern_linker_jni.cc",
"modern_linker_jni.h",
] ]
# The NDK contains the crazy_linker here: # The NDK contains the crazy_linker here:
......
...@@ -20,7 +20,6 @@ ...@@ -20,7 +20,6 @@
#include <string.h> #include <string.h>
#include "legacy_linker_jni.h" #include "legacy_linker_jni.h"
#include "modern_linker_jni.h"
namespace chromium_android_linker { namespace chromium_android_linker {
...@@ -224,8 +223,7 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) { ...@@ -224,8 +223,7 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
} }
// Initialize linker base and implementations. // Initialize linker base and implementations.
if (!LinkerJNIInit(vm, env) if (!LinkerJNIInit(vm, env) || !LegacyLinkerJNIInit(vm, env)) {
|| !LegacyLinkerJNIInit(vm, env) || !ModernLinkerJNIInit(vm, env)) {
return -1; return -1;
} }
......
// 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 the version of the Android-specific Chromium linker that uses
// the Android M and later system linker to load libraries.
// This source code *cannot* depend on anything from base/ or the C++
// STL, to keep the final library small, and avoid ugly dependency issues.
#include "modern_linker_jni.h"
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <dlfcn.h>
#include <errno.h>
#include <fcntl.h>
#include <jni.h>
#include <limits.h>
#include <link.h>
#include <stddef.h>
#include <string.h>
#include "android_dlext.h"
#include "linker_jni.h"
#define PAGE_START(x) ((x) & PAGE_MASK)
#define PAGE_END(x) PAGE_START((x) + (PAGE_SIZE - 1))
namespace chromium_android_linker {
namespace {
// Record of the Java VM passed to JNI_OnLoad().
static JavaVM* s_java_vm = nullptr;
// Get the CPU ABI string for which the linker is running.
//
// The returned string is used to construct the path to libchrome.so when
// loading directly from APK.
//
// |env| is the current JNI environment handle.
// |clazz| is the static class handle for org.chromium.base.Linker,
// and is ignored here.
// Returns the CPU ABI string for which the linker is running.
jstring GetCpuAbi(JNIEnv* env, jclass clazz) {
#if defined(__arm__) && defined(__ARM_ARCH_7A__)
static const char* kCurrentAbi = "armeabi-v7a";
#elif defined(__arm__)
static const char* kCurrentAbi = "armeabi";
#elif defined(__i386__)
static const char* kCurrentAbi = "x86";
#elif defined(__mips__)
static const char* kCurrentAbi = "mips";
#elif defined(__x86_64__)
static const char* kCurrentAbi = "x86_64";
#elif defined(__aarch64__)
static const char* kCurrentAbi = "arm64-v8a";
#else
#error "Unsupported target abi"
#endif
return env->NewStringUTF(kCurrentAbi);
}
// Convenience wrapper around dlsym() on the main executable. Returns
// the address of the requested symbol, or nullptr if not found. Status
// is available from dlerror().
void* Dlsym(const char* symbol_name) {
static void* handle = nullptr;
if (!handle)
handle = dlopen(nullptr, RTLD_NOW);
void* result = dlsym(handle, symbol_name);
return result;
}
// dl_iterate_phdr() wrapper, accessed via dlsym lookup. Done this way.
// so that this code compiles for Android versions that are too early to
// offer it. Checks in LibraryLoader.java should ensure that we
// never reach here at runtime on Android versions that are too old to
// supply dl_iterate_phdr; that is, earlier than Android M. Returns
// false if no dl_iterate_phdr() is available, otherwise true with the
// return value from dl_iterate_phdr() in |status|.
bool DlIteratePhdr(int (*callback)(dl_phdr_info*, size_t, void*),
void* data,
int* status) {
using DlIteratePhdrCallback = int (*)(dl_phdr_info*, size_t, void*);
using DlIteratePhdrFunctionPtr = int (*)(DlIteratePhdrCallback, void*);
static DlIteratePhdrFunctionPtr function_ptr = nullptr;
if (!function_ptr) {
function_ptr =
reinterpret_cast<DlIteratePhdrFunctionPtr>(Dlsym("dl_iterate_phdr"));
if (!function_ptr) {
LOG_ERROR("dlsym: dl_iterate_phdr: %s", dlerror());
return false;
}
}
*status = (*function_ptr)(callback, data);
return true;
}
// Convenience struct wrapper round android_dlextinfo.
struct AndroidDlextinfo {
AndroidDlextinfo(int flags,
void* reserved_addr, size_t reserved_size, int relro_fd) {
memset(&extinfo, 0, sizeof(extinfo));
extinfo.flags = flags;
extinfo.reserved_addr = reserved_addr;
extinfo.reserved_size = reserved_size;
extinfo.relro_fd = relro_fd;
}
android_dlextinfo extinfo;
};
// android_dlopen_ext() wrapper, accessed via dlsym lookup. Returns false
// if no android_dlopen_ext() is available, otherwise true with the return
// value from android_dlopen_ext() in |status|.
bool AndroidDlopenExt(const char* filename,
int flag,
const AndroidDlextinfo* dlextinfo,
void** status) {
using DlopenExtFunctionPtr = void* (*)(const char*,
int, const android_dlextinfo*);
static DlopenExtFunctionPtr function_ptr = nullptr;
if (!function_ptr) {
function_ptr =
reinterpret_cast<DlopenExtFunctionPtr>(Dlsym("android_dlopen_ext"));
if (!function_ptr) {
LOG_ERROR("dlsym: android_dlopen_ext: %s", dlerror());
return false;
}
}
const android_dlextinfo* extinfo = &dlextinfo->extinfo;
LOG_INFO("android_dlopen_ext:"
" flags=0x%llx, reserved_addr=%p, reserved_size=%d, relro_fd=%d",
static_cast<long long>(extinfo->flags),
extinfo->reserved_addr,
static_cast<int>(extinfo->reserved_size),
extinfo->relro_fd);
*status = (*function_ptr)(filename, flag, extinfo);
return true;
}
// Callback data for FindLoadedLibrarySize().
struct CallbackData {
explicit CallbackData(void* address)
: load_address(address), load_size(0), min_vaddr(0) { }
const void* load_address;
size_t load_size;
size_t min_vaddr;
};
// Callback for dl_iterate_phdr(). Read phdrs to identify whether or not
// this library's load address matches the |load_address| passed in
// |data|. If yes, pass back load size and min vaddr via |data|. A non-zero
// return value terminates iteration.
int FindLoadedLibrarySize(dl_phdr_info* info, size_t size UNUSED, void* data) {
CallbackData* callback_data = reinterpret_cast<CallbackData*>(data);
// Use max and min vaddr to compute the library's load size.
ElfW(Addr) min_vaddr = ~0;
ElfW(Addr) max_vaddr = 0;
bool is_matching = false;
for (size_t i = 0; i < info->dlpi_phnum; ++i) {
const ElfW(Phdr)* phdr = &info->dlpi_phdr[i];
if (phdr->p_type != PT_LOAD)
continue;
// See if this segment's load address matches what we passed to
// android_dlopen_ext as extinfo.reserved_addr.
void* load_addr = reinterpret_cast<void*>(info->dlpi_addr + phdr->p_vaddr);
if (load_addr == callback_data->load_address)
is_matching = true;
if (phdr->p_vaddr < min_vaddr)
min_vaddr = phdr->p_vaddr;
if (phdr->p_vaddr + phdr->p_memsz > max_vaddr)
max_vaddr = phdr->p_vaddr + phdr->p_memsz;
}
// If this library matches what we seek, return its load size.
if (is_matching) {
callback_data->load_size = PAGE_END(max_vaddr) - PAGE_START(min_vaddr);
callback_data->min_vaddr = min_vaddr;
return true;
}
return false;
}
// Helper class for anonymous memory mapping.
class ScopedAnonymousMmap {
public:
ScopedAnonymousMmap(void* addr, size_t size);
~ScopedAnonymousMmap() { munmap(addr_, size_); }
void* GetAddr() const { return effective_addr_; }
void Release() { addr_ = nullptr; size_ = 0; effective_addr_ = nullptr; }
private:
void* addr_;
size_t size_;
// The effective_addr_ is the address seen by client code. It may or may
// not be the same as addr_, the real start of the anonymous mapping.
void* effective_addr_;
};
// ScopedAnonymousMmap constructor. |addr| is a requested mapping address, or
// zero if any address will do, and |size| is the size of mapping required.
ScopedAnonymousMmap::ScopedAnonymousMmap(void* addr, size_t size) {
#if RESERVE_BREAKPAD_GUARD_REGION
// Increase size to extend the address reservation mapping so that it will
// also include a guard region from load_bias_ to start_addr. If loading
// at a fixed address, move our requested address back by the guard region
// size.
size += kBreakpadGuardRegionBytes;
if (addr) {
if (addr < reinterpret_cast<void*>(kBreakpadGuardRegionBytes)) {
LOG_ERROR("Fixed address %p is too low to accommodate Breakpad guard",
addr);
addr_ = MAP_FAILED;
size_ = 0;
return;
}
addr = reinterpret_cast<void*>(
reinterpret_cast<uintptr_t>(addr) - kBreakpadGuardRegionBytes);
}
LOG_INFO("Added %d to size, for Breakpad guard",
static_cast<int>(kBreakpadGuardRegionBytes));
#endif
addr_ = mmap(addr, size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (addr_ != MAP_FAILED) {
size_ = size;
} else {
LOG_INFO("mmap failed: %s", strerror(errno));
size_ = 0;
}
effective_addr_ = addr_;
#if RESERVE_BREAKPAD_GUARD_REGION
// If we increased size to accommodate a Breakpad guard region, move
// the effective address, if valid, upwards by the size of the guard region.
if (addr_ == MAP_FAILED)
return;
if (addr_ < reinterpret_cast<void*>(kBreakpadGuardRegionBytes)) {
LOG_ERROR("Map address %p is too low to accommodate Breakpad guard",
addr_);
effective_addr_ = MAP_FAILED;
} else {
effective_addr_ = reinterpret_cast<void*>(
reinterpret_cast<uintptr_t>(addr_) + kBreakpadGuardRegionBytes);
}
#endif
}
// Helper for LoadLibrary(). Return the actual size of the library loaded
// at |addr| in |load_size|, and the min vaddr in |min_vaddr|. Returns false
// if the library appears not to be loaded.
bool GetLibraryLoadSize(void* addr, size_t* load_size, size_t* min_vaddr) {
LOG_INFO("Called for %p", addr);
// Find the real load size and min vaddr for the library loaded at |addr|.
CallbackData callback_data(addr);
int status = 0;
if (!DlIteratePhdr(&FindLoadedLibrarySize, &callback_data, &status)) {
LOG_ERROR("No dl_iterate_phdr function found");
return false;
}
if (!status) {
LOG_ERROR("Failed to find library at address %p", addr);
return false;
}
*load_size = callback_data.load_size;
*min_vaddr = callback_data.min_vaddr;
return true;
}
// Helper for LoadLibrary(). We reserve an address space larger than
// needed. After library loading we want to trim that reservation to only
// what is needed. Failure to trim should not occur, but if it does then
// everything will still run, so we treat it as a warning rather than
// an error.
void ResizeReservedAddressSpace(void* addr,
size_t reserved_size,
size_t load_size,
size_t min_vaddr) {
LOG_INFO("Called for %p, reserved %d, loaded %d, min_vaddr %d",
addr, static_cast<int>(reserved_size),
static_cast<int>(load_size), static_cast<int>(min_vaddr));
const uintptr_t uintptr_addr = reinterpret_cast<uintptr_t>(addr);
if (reserved_size > load_size) {
// 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 = reserved_size - load_size;
if (munmap(unmap, length) == -1) {
LOG_ERROR("WARNING: unmap of %d bytes at %p failed: %s",
static_cast<int>(length), unmap, strerror(errno));
}
} else {
LOG_ERROR("WARNING: library reservation was too small");
}
#if RESERVE_BREAKPAD_GUARD_REGION
if (kBreakpadGuardRegionBytes > min_vaddr) {
// Unmap the part of the reserved address space that is ahead of where we
// actually need the guard region to start. Resizes the guard region to
// min_vaddr bytes.
void* unmap =
reinterpret_cast<void*>(uintptr_addr - kBreakpadGuardRegionBytes);
const size_t length = kBreakpadGuardRegionBytes - min_vaddr;
if (munmap(unmap, length) == -1) {
LOG_ERROR("WARNING: unmap of %d bytes at %p failed: %s",
static_cast<int>(length), unmap, strerror(errno));
}
} else {
LOG_ERROR("WARNING: breakpad guard region reservation was too small");
}
#endif
}
// Load a library with the chromium linker, using android_dlopen_ext().
//
// android_dlopen_ext() understands how to directly load from a zipfile,
// based on the format of |dlopen_ext_path|. If it contains a "!/" separator
// then the string indicates <zip_path>!/<file_path> and indicates the
// file_path element within the zip file at zip_path. A library in a
// zipfile must be uncompressed and page aligned. The library is expected
// to be lib/<abi_tag>/crazy.<basename>. The <abi_tag> used will be the
// same as the abi for this linker. The "crazy." prefix is included
// so that the Android Package Manager doesn't extract the library into
// /data/app-lib.
//
// If |dlopen_ext_path| contains no "!/" separator then android_dlopen_ext()
// assumes that it is a normal path to a standalone library file.
//
// Loading the library will also call its JNI_OnLoad() method, which
// shall register its methods. Note that lazy native method resolution
// will _not_ work after this, because Dalvik uses the system's dlsym()
// which won't see the new library, so explicit registration is mandatory.
//
// |env| is the current JNI environment handle.
// |clazz| is the static class handle for org.chromium.base.Linker,
// and is ignored here.
// |dlopen_ext_path| is the library identifier (e.g. libfoo.so).
// |load_address| is an explicit load address.
// |relro_path| is the path to the file into which RELRO data is held.
// |lib_info_obj| is a LibInfo handle used to communicate information
// with the Java side.
// Return true on success.
jboolean LoadLibrary(JNIEnv* env,
jclass clazz,
jstring dlopen_ext_path,
jlong load_address,
jobject lib_info_obj) {
String dlopen_library_path(env, dlopen_ext_path);
LOG_INFO("Called for %s, at address 0x%llx",
dlopen_library_path.c_str(), load_address);
if (!IsValidAddress(load_address)) {
LOG_ERROR("Invalid address 0x%llx", load_address);
return false;
}
const size_t size = kAddressSpaceReservationSize;
void* wanted_addr = reinterpret_cast<void*>(load_address);
// Reserve the address space into which we load the library.
ScopedAnonymousMmap mapping(wanted_addr, size);
void* addr = mapping.GetAddr();
if (addr == MAP_FAILED) {
LOG_ERROR("Failed to reserve space for load");
return false;
}
if (wanted_addr && addr != wanted_addr) {
LOG_ERROR("Failed to obtain fixed address for load");
return false;
}
// Build dlextinfo to load the library into the reserved space, using
// the shared RELRO if supplied and if its start address matches addr.
int relro_fd = -1;
int flags = ANDROID_DLEXT_RESERVED_ADDRESS;
if (wanted_addr && lib_info_obj) {
void* relro_start;
s_lib_info_fields.GetRelroInfo(env, lib_info_obj,
reinterpret_cast<size_t*>(&relro_start),
nullptr, &relro_fd);
if (relro_fd != -1 && relro_start == addr) {
flags |= ANDROID_DLEXT_USE_RELRO;
}
}
AndroidDlextinfo dlextinfo(flags, addr, size, relro_fd);
// Load the library into the reserved space.
const char* path = dlopen_library_path.c_str();
void* handle = nullptr;
if (!AndroidDlopenExt(path, RTLD_NOW, &dlextinfo, &handle)) {
LOG_ERROR("No android_dlopen_ext function found");
return false;
}
if (handle == nullptr) {
LOG_ERROR("android_dlopen_ext: %s", dlerror());
return false;
}
// For https://crbug.com/568880.
//
// Release the scoped mapping. Now that the library has loaded we can no
// longer assume we have control of all of this area. libdl knows addr and
// has loaded the library into some portion of the reservation. It will
// not expect that portion of memory to be arbitrarily unmapped.
mapping.Release();
// After loading we can find the actual size of the library. It should
// be less than the space we reserved for it.
size_t load_size = 0;
size_t min_vaddr = 0;
if (!GetLibraryLoadSize(addr, &load_size, &min_vaddr)) {
LOG_ERROR("Unable to find size for load at %p", addr);
return false;
}
// 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.
ResizeReservedAddressSpace(addr, size, load_size, min_vaddr);
// 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;
}
}
// Note the load address and load size in the supplied libinfo object.
const size_t cast_addr = reinterpret_cast<size_t>(addr);
s_lib_info_fields.SetLoadInfo(env, lib_info_obj, cast_addr, load_size);
LOG_INFO("Success loading library %s", dlopen_library_path.c_str());
return true;
}
// Create a shared RELRO file for a library, using android_dlopen_ext().
//
// Loads the library similarly to LoadLibrary() above, by reserving address
// space and then using android_dlopen_ext() to load into the reserved
// area. Adds flags to android_dlopen_ext() to saved the library's RELRO
// memory into the given file path, then unload the library and returns.
//
// Does not call JNI_OnLoad() or otherwise execute any code from the library.
//
// |env| is the current JNI environment handle.
// |clazz| is the static class handle for org.chromium.base.Linker,
// and is ignored here.
// |dlopen_ext_path| is the library identifier (e.g. libfoo.so).
// |load_address| is an explicit load address.
// |relro_path| is the path to the file into which RELRO data is written.
// |lib_info_obj| is a LibInfo handle used to communicate information
// with the Java side.
// Return true on success.
jboolean CreateSharedRelro(JNIEnv* env,
jclass clazz,
jstring dlopen_ext_path,
jlong load_address,
jstring relro_path,
jobject lib_info_obj) {
String dlopen_library_path(env, dlopen_ext_path);
LOG_INFO("Called for %s, at address 0x%llx",
dlopen_library_path.c_str(), load_address);
if (!IsValidAddress(load_address) || load_address == 0) {
LOG_ERROR("Invalid address 0x%llx", load_address);
return false;
}
const size_t size = kAddressSpaceReservationSize;
void* wanted_addr = reinterpret_cast<void*>(load_address);
// Reserve the address space into which we load the library.
ScopedAnonymousMmap mapping(wanted_addr, size);
void* addr = mapping.GetAddr();
if (addr == MAP_FAILED) {
LOG_ERROR("Failed to reserve space for load");
return false;
}
if (addr != wanted_addr) {
LOG_ERROR("Failed to obtain fixed address for load");
return false;
}
// Open the shared RELRO file for write. Overwrites any prior content.
String shared_relro_path(env, relro_path);
const char* filepath = shared_relro_path.c_str();
unlink(filepath);
int relro_fd = open(filepath, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
if (relro_fd == -1) {
LOG_ERROR("open: %s: %s", filepath, strerror(errno));
return false;
}
// Use android_dlopen_ext() to create the shared RELRO.
const int flags = ANDROID_DLEXT_RESERVED_ADDRESS
| ANDROID_DLEXT_WRITE_RELRO;
AndroidDlextinfo dlextinfo(flags, addr, size, relro_fd);
const char* path = dlopen_library_path.c_str();
void* handle = nullptr;
if (!AndroidDlopenExt(path, RTLD_NOW, &dlextinfo, &handle)) {
LOG_ERROR("No android_dlopen_ext function found");
close(relro_fd);
return false;
}
if (handle == nullptr) {
LOG_ERROR("android_dlopen_ext: %s", dlerror());
close(relro_fd);
return false;
}
// For https://crbug.com/568880.
//
// Release the scoped mapping. See comment in LoadLibrary() above for more.
mapping.Release();
// For https://crbug.com/568880.
//
// Unload the library from this address. Calling dlclose() will unmap the
// part of the reservation occupied by the libary, but will leave the
// remainder of the reservation mapped, and we have no effective way of
// unmapping the leftover portions because we don't know where dlclose's
// unmap ended.
//
// For now we live with this. It is a loss of some virtual address space
// (but not actual memory), and because it occurs only once and only in
// the browser process, and never in renderer processes, it is not a
// significant issue.
//
// TODO(simonb): Between mapping.Release() and here, consider calling the
// functions that trim the reservation down to the size of the loaded
// library. This may help recover some or all of the virtual address space
// that is otherwise lost.
dlclose(handle);
// Reopen the shared RELRO fd in read-only mode. This ensures that nothing
// can write to it through the RELRO fd that we return in libinfo.
close(relro_fd);
relro_fd = open(filepath, O_RDONLY);
if (relro_fd == -1) {
LOG_ERROR("open: %s: %s", filepath, strerror(errno));
return false;
}
// Delete the directory entry for the RELRO file. The fd we hold ensures
// that its data remains intact.
if (unlink(filepath) == -1) {
LOG_ERROR("unlink: %s: %s", filepath, strerror(errno));
return false;
}
// Note the shared RELRO fd in the supplied libinfo object. In this
// implementation the RELRO start is set to the library's load address,
// and the RELRO size is unused.
const size_t cast_addr = reinterpret_cast<size_t>(addr);
s_lib_info_fields.SetRelroInfo(env, lib_info_obj, cast_addr, 0, relro_fd);
LOG_INFO("Success creating shared RELRO %s", shared_relro_path.c_str());
return true;
}
const JNINativeMethod kNativeMethods[] = {
{"nativeGetCpuAbi",
"("
")"
"Ljava/lang/String;",
reinterpret_cast<void*>(&GetCpuAbi)},
{"nativeLoadLibrary",
"("
"Ljava/lang/String;"
"J"
"Lorg/chromium/base/library_loader/Linker$LibInfo;"
")"
"Z",
reinterpret_cast<void*>(&LoadLibrary)},
{"nativeCreateSharedRelro",
"("
"Ljava/lang/String;"
"J"
"Ljava/lang/String;"
"Lorg/chromium/base/library_loader/Linker$LibInfo;"
")"
"Z",
reinterpret_cast<void*>(&CreateSharedRelro)},
};
const size_t kNumNativeMethods =
sizeof(kNativeMethods) / sizeof(kNativeMethods[0]);
} // namespace
bool ModernLinkerJNIInit(JavaVM* vm, JNIEnv* env) {
LOG_INFO("Entering");
// Register native methods.
jclass linker_class;
if (!InitClassReference(env,
"org/chromium/base/library_loader/ModernLinker",
&linker_class))
return false;
LOG_INFO("Registering native methods");
if (env->RegisterNatives(linker_class, kNativeMethods, kNumNativeMethods) < 0)
return false;
// Record the Java VM handle.
s_java_vm = vm;
return true;
}
} // namespace chromium_android_linker
// 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.
#ifndef BASE_ANDROID_LINKER_MODERN_LINKER_JNI_H_
#define BASE_ANDROID_LINKER_MODERN_LINKER_JNI_H_
#include <jni.h>
namespace chromium_android_linker {
// 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().
// |env| is the current JNI environment handle.
// On success, returns true.
extern bool ModernLinkerJNIInit(JavaVM* vm, JNIEnv* env);
} // namespace chromium_android_linker
#endif // BASE_ANDROID_LINKER_MODERN_LINKER_JNI_H_
...@@ -9,7 +9,6 @@ from pylib.linker import test_case ...@@ -9,7 +9,6 @@ from pylib.linker import test_case
with host_paths.SysPath(host_paths.BUILD_COMMON_PATH): with host_paths.SysPath(host_paths.BUILD_COMMON_PATH):
import unittest_util import unittest_util
_MODERN_LINKER_MINIMUM_SDK_INT = 23
class LinkerTestInstance(test_instance.TestInstance): class LinkerTestInstance(test_instance.TestInstance):
...@@ -26,15 +25,11 @@ class LinkerTestInstance(test_instance.TestInstance): ...@@ -26,15 +25,11 @@ class LinkerTestInstance(test_instance.TestInstance):
def test_filter(self): def test_filter(self):
return self._test_filter return self._test_filter
def GetTests(self, min_device_sdk): def GetTests(self):
tests = [ tests = [
test_case.LinkerSharedRelroTest(is_modern_linker=False, test_case.LinkerSharedRelroTest(is_low_memory=False),
is_low_memory=False), test_case.LinkerSharedRelroTest(is_low_memory=True)
test_case.LinkerSharedRelroTest(is_modern_linker=False,
is_low_memory=True)
] ]
if min_device_sdk >= _MODERN_LINKER_MINIMUM_SDK_INT:
tests.append(test_case.LinkerSharedRelroTest(is_modern_linker=True))
if self._test_filter: if self._test_filter:
filtered_names = unittest_util.FilterTestNames( filtered_names = unittest_util.FilterTestNames(
......
...@@ -124,16 +124,11 @@ class AddressList(list): ...@@ -124,16 +124,11 @@ class AddressList(list):
class LinkerTestCaseBase(object): class LinkerTestCaseBase(object):
"""Base class for linker test cases.""" """Base class for linker test cases."""
def __init__(self, is_modern_linker=False, is_low_memory=False): def __init__(self, is_low_memory=False):
"""Create a test case. """Create a test case.
Args: Args:
is_modern_linker: True to test ModernLinker, False to test LegacyLinker.
is_low_memory: True to simulate a low-memory device, False otherwise. is_low_memory: True to simulate a low-memory device, False otherwise.
""" """
self.is_modern_linker = is_modern_linker
if is_modern_linker:
test_suffix = 'ForModernLinker'
else:
test_suffix = 'ForLegacyLinker' test_suffix = 'ForLegacyLinker'
self.is_low_memory = is_low_memory self.is_low_memory = is_low_memory
if is_low_memory: if is_low_memory:
...@@ -167,10 +162,7 @@ class LinkerTestCaseBase(object): ...@@ -167,10 +162,7 @@ class LinkerTestCaseBase(object):
logging.info('Running linker test: %s', self.tagged_name) logging.info('Running linker test: %s', self.tagged_name)
# Create command-line file on device. # Create command-line file on device.
if self.is_modern_linker: command_line_flags = ''
command_line_flags = '--use-linker=modern'
else:
command_line_flags = '--use-linker=legacy'
if self.is_low_memory: if self.is_low_memory:
command_line_flags += ' --low-memory-device' command_line_flags += ' --low-memory-device'
device.WriteFile(_COMMAND_LINE_FILE, command_line_flags) device.WriteFile(_COMMAND_LINE_FILE, command_line_flags)
......
...@@ -27,10 +27,6 @@ public class ChromiumLinkerParams { ...@@ -27,10 +27,6 @@ public class ChromiumLinkerParams {
// registered in the service process. // registered in the service process.
public final String mTestRunnerClassNameForTesting; public final String mTestRunnerClassNameForTesting;
// If mTestRunnerClassNameForTesting is not empty, the Linker implementation
// to force for testing.
public final int mLinkerImplementationForTesting;
private static final String EXTRA_LINKER_PARAMS_BASE_LOAD_ADDRESS = private static final String EXTRA_LINKER_PARAMS_BASE_LOAD_ADDRESS =
"org.chromium.content.common.linker_params.base_load_address"; "org.chromium.content.common.linker_params.base_load_address";
...@@ -40,27 +36,20 @@ public class ChromiumLinkerParams { ...@@ -40,27 +36,20 @@ public class ChromiumLinkerParams {
private static final String EXTRA_LINKER_PARAMS_TEST_RUNNER_CLASS_NAME = private static final String EXTRA_LINKER_PARAMS_TEST_RUNNER_CLASS_NAME =
"org.chromium.content.common.linker_params.test_runner_class_name"; "org.chromium.content.common.linker_params.test_runner_class_name";
private static final String EXTRA_LINKER_PARAMS_LINKER_IMPLEMENTATION =
"org.chromium.content.common.linker_params.linker_implementation";
public ChromiumLinkerParams(long baseLoadAddress, boolean waitForSharedRelro) { public ChromiumLinkerParams(long baseLoadAddress, boolean waitForSharedRelro) {
mBaseLoadAddress = baseLoadAddress; mBaseLoadAddress = baseLoadAddress;
mWaitForSharedRelro = waitForSharedRelro; mWaitForSharedRelro = waitForSharedRelro;
mTestRunnerClassNameForTesting = null; mTestRunnerClassNameForTesting = null;
mLinkerImplementationForTesting = 0;
} }
/** /**
* Use this constructor to create a LinkerParams instance for testing. * Use this constructor to create a LinkerParams instance for testing.
*/ */
public ChromiumLinkerParams(long baseLoadAddress, public ChromiumLinkerParams(
boolean waitForSharedRelro, long baseLoadAddress, boolean waitForSharedRelro, String testRunnerClassName) {
String testRunnerClassName,
int linkerImplementation) {
mBaseLoadAddress = baseLoadAddress; mBaseLoadAddress = baseLoadAddress;
mWaitForSharedRelro = waitForSharedRelro; mWaitForSharedRelro = waitForSharedRelro;
mTestRunnerClassNameForTesting = testRunnerClassName; mTestRunnerClassNameForTesting = testRunnerClassName;
mLinkerImplementationForTesting = linkerImplementation;
} }
/** /**
...@@ -73,8 +62,7 @@ public class ChromiumLinkerParams { ...@@ -73,8 +62,7 @@ public class ChromiumLinkerParams {
public static ChromiumLinkerParams create(Bundle bundle) { public static ChromiumLinkerParams create(Bundle bundle) {
if (!bundle.containsKey(EXTRA_LINKER_PARAMS_BASE_LOAD_ADDRESS) if (!bundle.containsKey(EXTRA_LINKER_PARAMS_BASE_LOAD_ADDRESS)
|| !bundle.containsKey(EXTRA_LINKER_PARAMS_WAIT_FOR_SHARED_RELRO) || !bundle.containsKey(EXTRA_LINKER_PARAMS_WAIT_FOR_SHARED_RELRO)
|| !bundle.containsKey(EXTRA_LINKER_PARAMS_TEST_RUNNER_CLASS_NAME) || !bundle.containsKey(EXTRA_LINKER_PARAMS_TEST_RUNNER_CLASS_NAME)) {
|| !bundle.containsKey(EXTRA_LINKER_PARAMS_LINKER_IMPLEMENTATION)) {
return null; return null;
} }
return new ChromiumLinkerParams(bundle); return new ChromiumLinkerParams(bundle);
...@@ -85,8 +73,6 @@ public class ChromiumLinkerParams { ...@@ -85,8 +73,6 @@ public class ChromiumLinkerParams {
mWaitForSharedRelro = bundle.getBoolean(EXTRA_LINKER_PARAMS_WAIT_FOR_SHARED_RELRO, false); mWaitForSharedRelro = bundle.getBoolean(EXTRA_LINKER_PARAMS_WAIT_FOR_SHARED_RELRO, false);
mTestRunnerClassNameForTesting = mTestRunnerClassNameForTesting =
bundle.getString(EXTRA_LINKER_PARAMS_TEST_RUNNER_CLASS_NAME); bundle.getString(EXTRA_LINKER_PARAMS_TEST_RUNNER_CLASS_NAME);
mLinkerImplementationForTesting =
bundle.getInt(EXTRA_LINKER_PARAMS_LINKER_IMPLEMENTATION, 0);
} }
/** /**
...@@ -99,7 +85,6 @@ public class ChromiumLinkerParams { ...@@ -99,7 +85,6 @@ public class ChromiumLinkerParams {
bundle.putBoolean(EXTRA_LINKER_PARAMS_WAIT_FOR_SHARED_RELRO, mWaitForSharedRelro); bundle.putBoolean(EXTRA_LINKER_PARAMS_WAIT_FOR_SHARED_RELRO, mWaitForSharedRelro);
bundle.putString( bundle.putString(
EXTRA_LINKER_PARAMS_TEST_RUNNER_CLASS_NAME, mTestRunnerClassNameForTesting); EXTRA_LINKER_PARAMS_TEST_RUNNER_CLASS_NAME, mTestRunnerClassNameForTesting);
bundle.putInt(EXTRA_LINKER_PARAMS_LINKER_IMPLEMENTATION, mLinkerImplementationForTesting);
} }
// For debugging traces only. // For debugging traces only.
...@@ -107,8 +92,8 @@ public class ChromiumLinkerParams { ...@@ -107,8 +92,8 @@ public class ChromiumLinkerParams {
public String toString() { public String toString() {
return String.format(Locale.US, return String.format(Locale.US,
"LinkerParams(baseLoadAddress:0x%x, waitForSharedRelro:%s, " "LinkerParams(baseLoadAddress:0x%x, waitForSharedRelro:%s, "
+ "testRunnerClassName:%s, linkerImplementation:%d", + "testRunnerClassName:%s",
mBaseLoadAddress, Boolean.toString(mWaitForSharedRelro), mBaseLoadAddress, Boolean.toString(mWaitForSharedRelro),
mTestRunnerClassNameForTesting, mLinkerImplementationForTesting); mTestRunnerClassNameForTesting);
} }
} }
...@@ -196,8 +196,7 @@ public class ContentChildProcessServiceDelegate implements ChildProcessServiceDe ...@@ -196,8 +196,7 @@ public class ContentChildProcessServiceDelegate implements ChildProcessServiceDe
// For testing, set the Linker implementation and the test runner // For testing, set the Linker implementation and the test runner
// class name to match those used by the parent. // class name to match those used by the parent.
assert mLinkerParams != null; assert mLinkerParams != null;
Linker.setupForTesting(mLinkerParams.mLinkerImplementationForTesting, Linker.setupForTesting(mLinkerParams.mTestRunnerClassNameForTesting);
mLinkerParams.mTestRunnerClassNameForTesting);
} }
return Linker.getInstance(); return Linker.getInstance();
} }
......
...@@ -577,8 +577,7 @@ public class ChildProcessLauncherHelper { ...@@ -577,8 +577,7 @@ public class ChildProcessLauncherHelper {
if (Linker.areTestsEnabled()) { if (Linker.areTestsEnabled()) {
Linker linker = Linker.getInstance(); Linker linker = Linker.getInstance();
return new ChromiumLinkerParams(sLinkerLoadAddress, waitForSharedRelros, return new ChromiumLinkerParams(sLinkerLoadAddress, waitForSharedRelros,
linker.getTestRunnerClassNameForTesting(), linker.getTestRunnerClassNameForTesting());
linker.getImplementationForTesting());
} else { } else {
return new ChromiumLinkerParams(sLinkerLoadAddress, waitForSharedRelros); return new ChromiumLinkerParams(sLinkerLoadAddress, waitForSharedRelros);
} }
......
...@@ -46,11 +46,6 @@ jboolean RunChecks(bool in_browser_process, bool need_relros) { ...@@ -46,11 +46,6 @@ jboolean RunChecks(bool in_browser_process, bool need_relros) {
// //
// "/dev/ashmem/RELRO:<libname> (deleted)" // "/dev/ashmem/RELRO:<libname> (deleted)"
// //
// and for the ModernLinker, something like:
//
// "/data/data/org.chromium.chromium_linker_test_apk/
// app_chromium_linker_test/RELRO:<libname> (deleted)"
//
// Where <libname> is the library name and '(deleted)' is actually // Where <libname> is the library name and '(deleted)' is actually
// added by the kernel to indicate there is no corresponding file // added by the kernel to indicate there is no corresponding file
// on the filesystem. // on the filesystem.
...@@ -59,7 +54,6 @@ jboolean RunChecks(bool in_browser_process, bool need_relros) { ...@@ -59,7 +54,6 @@ jboolean RunChecks(bool in_browser_process, bool need_relros) {
// section, but for the component build, there are several libraries, // section, but for the component build, there are several libraries,
// each one with its own RELRO. // each one with its own RELRO.
static const char kLegacyRelroSectionPattern[] = "/dev/ashmem/RELRO:.*"; static const char kLegacyRelroSectionPattern[] = "/dev/ashmem/RELRO:.*";
static const char kModernRelroSectionPattern[] = "/data/.*/RELRO:.*";
// Parse /proc/self/maps and builds a list of region mappings in this // Parse /proc/self/maps and builds a list of region mappings in this
// process. // process.
...@@ -78,7 +72,6 @@ jboolean RunChecks(bool in_browser_process, bool need_relros) { ...@@ -78,7 +72,6 @@ jboolean RunChecks(bool in_browser_process, bool need_relros) {
} }
const RE2 legacy_linker_re(kLegacyRelroSectionPattern); const RE2 legacy_linker_re(kLegacyRelroSectionPattern);
const RE2 modern_linker_re(kModernRelroSectionPattern);
int num_shared_relros = 0; int num_shared_relros = 0;
int num_bad_shared_relros = 0; int num_bad_shared_relros = 0;
...@@ -88,15 +81,8 @@ jboolean RunChecks(bool in_browser_process, bool need_relros) { ...@@ -88,15 +81,8 @@ jboolean RunChecks(bool in_browser_process, bool need_relros) {
const std::string path = region.path; const std::string path = region.path;
const bool is_legacy_relro = re2::RE2::FullMatch(path, legacy_linker_re); const bool is_legacy_relro = re2::RE2::FullMatch(path, legacy_linker_re);
const bool is_modern_relro = re2::RE2::FullMatch(path, modern_linker_re);
if (is_legacy_relro && is_modern_relro) { if (!is_legacy_relro) {
LOG(ERROR) << prefix
<< "FAIL RELRO cannot be both Legacy and Modern (test error)";
return false;
}
if (!is_legacy_relro && !is_modern_relro) {
// Ignore any mapping that isn't a shared RELRO. // Ignore any mapping that isn't a shared RELRO.
continue; continue;
} }
...@@ -127,16 +113,6 @@ jboolean RunChecks(bool in_browser_process, bool need_relros) { ...@@ -127,16 +113,6 @@ jboolean RunChecks(bool in_browser_process, bool need_relros) {
continue; continue;
} }
// Shared RELROs implemented by the Android M+ system linker are not in
// ashmem. The Android M+ system linker maps everything with MAP_PRIVATE
// rather than MAP_SHARED. Remapping such a RELRO section read-write will
// therefore succeed, but it is not a problem. The memory copy-on-writes,
// and updates are not visible to either the mapped file or other processes
// mapping the same file. So... we skip the remap test for ModernLinker.
if (is_modern_relro) {
continue;
}
// Check that trying to remap it read-write fails with EACCES // Check that trying to remap it read-write fails with EACCES
size_t region_size = region.end - region.start; size_t region_size = region.end - region.start;
int ret = ::mprotect(region_start, region_size, PROT_READ | PROT_WRITE); int ret = ::mprotect(region_start, region_size, PROT_READ | PROT_WRITE);
......
...@@ -39,12 +39,6 @@ public class ChromiumLinkerTestActivity extends Activity { ...@@ -39,12 +39,6 @@ public class ChromiumLinkerTestActivity extends Activity {
// target device running the test really is. // target device running the test really is.
private static final String LOW_MEMORY_DEVICE = "--low-memory-device"; private static final String LOW_MEMORY_DEVICE = "--low-memory-device";
// Use one of these on the command-line to force a specific Linker
// implementation. Passed from the main process to sub-processes so that
// everything that participates in a test uses a consistent implementation.
private static final String USE_MODERN_LINKER = "--use-linker=modern";
private static final String USE_LEGACY_LINKER = "--use-linker=legacy";
private ShellManager mShellManager; private ShellManager mShellManager;
private ActivityWindowAndroid mWindowAndroid; private ActivityWindowAndroid mWindowAndroid;
...@@ -64,8 +58,6 @@ public class ChromiumLinkerTestActivity extends Activity { ...@@ -64,8 +58,6 @@ public class ChromiumLinkerTestActivity extends Activity {
// CommandLine.getInstance().hasSwitch() doesn't work here for some funky // CommandLine.getInstance().hasSwitch() doesn't work here for some funky
// reason, so parse the command-line differently here: // reason, so parse the command-line differently here:
boolean hasLowMemoryDeviceSwitch = false; boolean hasLowMemoryDeviceSwitch = false;
boolean hasModernLinkerSwitch = false;
boolean hasLegacyLinkerSwitch = false;
String[] commandLine = CommandLine.getJavaSwitchesOrNull(); String[] commandLine = CommandLine.getJavaSwitchesOrNull();
if (commandLine == null) { if (commandLine == null) {
Log.i(TAG, "Command line is null"); Log.i(TAG, "Command line is null");
...@@ -76,31 +68,11 @@ public class ChromiumLinkerTestActivity extends Activity { ...@@ -76,31 +68,11 @@ public class ChromiumLinkerTestActivity extends Activity {
Log.i(TAG, " '" + option + "'"); Log.i(TAG, " '" + option + "'");
if (option.equals(LOW_MEMORY_DEVICE)) { if (option.equals(LOW_MEMORY_DEVICE)) {
hasLowMemoryDeviceSwitch = true; hasLowMemoryDeviceSwitch = true;
} else if (option.equals(USE_MODERN_LINKER)) {
hasModernLinkerSwitch = true;
} else if (option.equals(USE_LEGACY_LINKER)) {
hasLegacyLinkerSwitch = true;
} }
} }
} }
if (!(hasModernLinkerSwitch || hasLegacyLinkerSwitch)) { Linker.setupForTesting(LinkerTests.class.getName());
Log.e(TAG, "Missing --use-linker command line argument.");
finish();
} else if (hasModernLinkerSwitch && hasLegacyLinkerSwitch) {
Log.e(TAG, "Conflicting --use-linker command line arguments.");
finish();
}
// Set the requested Linker implementation from the command-line, and
// register the test runner class by name.
if (hasModernLinkerSwitch) {
Linker.setupForTesting(Linker.LINKER_IMPLEMENTATION_MODERN,
LinkerTests.class.getName());
} else {
Linker.setupForTesting(Linker.LINKER_IMPLEMENTATION_LEGACY,
LinkerTests.class.getName());
}
// Determine which kind of device to simulate from the command-line. // Determine which kind of device to simulate from the command-line.
int memoryDeviceConfig = Linker.MEMORY_DEVICE_CONFIG_NORMAL; int memoryDeviceConfig = Linker.MEMORY_DEVICE_CONFIG_NORMAL;
......
...@@ -23,38 +23,24 @@ public class LinkerTests implements Linker.TestRunner { ...@@ -23,38 +23,24 @@ public class LinkerTests implements Linker.TestRunner {
boolean isBrowserProcess) { boolean isBrowserProcess) {
boolean checkSharedRelro; boolean checkSharedRelro;
if (isBrowserProcess) { if (isBrowserProcess) {
Linker linker = Linker.getInstance();
int linkerImplementation = linker.getImplementationForTesting();
if (linkerImplementation == Linker.LINKER_IMPLEMENTATION_LEGACY) {
// LegacyLinker may share RELROs in the browser.
switch (Linker.BROWSER_SHARED_RELRO_CONFIG) { switch (Linker.BROWSER_SHARED_RELRO_CONFIG) {
case Linker.BROWSER_SHARED_RELRO_CONFIG_NEVER: case Linker.BROWSER_SHARED_RELRO_CONFIG_NEVER:
checkSharedRelro = false; checkSharedRelro = false;
break; break;
case Linker.BROWSER_SHARED_RELRO_CONFIG_LOW_RAM_ONLY: case Linker.BROWSER_SHARED_RELRO_CONFIG_LOW_RAM_ONLY:
// A shared RELRO should only be used on low-end devices. // A shared RELRO should only be used on low-end devices.
checkSharedRelro = checkSharedRelro = (memoryDeviceConfig == Linker.MEMORY_DEVICE_CONFIG_LOW);
(memoryDeviceConfig == Linker.MEMORY_DEVICE_CONFIG_LOW);
break; break;
case Linker.BROWSER_SHARED_RELRO_CONFIG_ALWAYS: case Linker.BROWSER_SHARED_RELRO_CONFIG_ALWAYS:
// Always check for a shared RELRO. // Always check for a shared RELRO.
checkSharedRelro = true; checkSharedRelro = true;
break; break;
default: default:
Log.e(TAG, "Invalid shared RELRO linker configuration: " Log.e(TAG,
"Invalid shared RELRO linker configuration: "
+ Linker.BROWSER_SHARED_RELRO_CONFIG); + Linker.BROWSER_SHARED_RELRO_CONFIG);
return false; return false;
} }
} else if (linkerImplementation == Linker.LINKER_IMPLEMENTATION_MODERN) {
// ModernLinker never shares RELROs in the browser.
checkSharedRelro = false;
} else {
Log.e(TAG, "Unknown linker: " + linker.getClass().getName());
return false;
}
} else { } else {
// Service processes should always use a shared RELRO section. // Service processes should always use a shared RELRO section.
checkSharedRelro = true; checkSharedRelro = true;
......
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