Commit 1bb00105 authored by simonb@chromium.org's avatar simonb@chromium.org

Switch from local random address generation to kernel ASLR

The current random base address generation in the Android chromium linker is prone
to error.  It selects an address at random between 0x20000000 and 0x40000000 and
expects that this will be clear.  This is occasionally untrue for ARM, but very
often untrue for MIPS.  As a consequence, RELRO sharing is being turned off more
frequently than it could be.

This change removes the local random address generation code and instead replaces
it with code that speculatively maps a large region, captures the address returned
by mmap, then unmaps and returns the address.  The expectation is that this region
will remain free for use when the time comes for the crazy linker to map the browser
into it.  This generally holds because the time between these two actions is short
and little, if anything, loads or mmaps between them.  Worst case is that RELRO
sharing turns off as at present, but the probability of this happening should now
be much lower.

Note that capturing the address from mmap relies on Android ASLR being active for
mmap.  This is the default device state since ICS.  The revised random browser
load address is only as entropic as Android's ASLR.

BUG=397634

Review URL: https://codereview.chromium.org/470053003

Cr-Commit-Position: refs/heads/master@{#291111}
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@291111 0039d316-1c4b-4281-b951-d872f2087c98
parent 4c07a715
...@@ -13,8 +13,6 @@ import android.util.Log; ...@@ -13,8 +13,6 @@ import android.util.Log;
import org.chromium.base.SysUtils; import org.chromium.base.SysUtils;
import org.chromium.base.ThreadUtils; import org.chromium.base.ThreadUtils;
import java.io.File;
import java.io.FileInputStream;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
...@@ -595,9 +593,9 @@ public class Linker { ...@@ -595,9 +593,9 @@ public class Linker {
sBaseLoadAddress = address; sBaseLoadAddress = address;
sCurrentLoadAddress = address; sCurrentLoadAddress = address;
if (address == 0) { if (address == 0) {
// If the computed address is 0, there are issues with the // If the computed address is 0, there are issues with finding enough
// entropy source, so disable RELRO shared / fixed load addresses. // free address space, so disable RELRO shared / fixed load addresses.
Log.w(TAG, "Disabling shared RELROs due to bad entropy sources"); Log.w(TAG, "Disabling shared RELROs due address space pressure");
sBrowserUsesSharedRelro = false; sBrowserUsesSharedRelro = false;
sWaitForSharedRelros = false; sWaitForSharedRelros = false;
} }
...@@ -606,101 +604,35 @@ public class Linker { ...@@ -606,101 +604,35 @@ public class Linker {
/** /**
* Compute a random base load address where to place loaded libraries. * Compute a random base load address at which to place loaded libraries.
* @return new base load address, or 0 if the system does not support * @return new base load address, or 0 if the system does not support
* RELRO sharing. * RELRO sharing.
*/ */
private static long computeRandomBaseLoadAddress() { private static long computeRandomBaseLoadAddress() {
// The kernel ASLR feature will place randomized mappings starting // nativeGetRandomBaseLoadAddress() returns an address at which it has previously
// from this address. Never try to load anything above this // successfully mapped an area of the given size, on the basis that we will be
// explicitly to avoid random conflicts. // able, with high probability, to map our library into it.
final long baseAddressLimit = 0x40000000; //
// One issue with this is that we do not yet know the size of the library that
// Start loading libraries from this base address. // we will load is. So here we pass a value that we expect will always be larger
final long baseAddress = 0x20000000; // than that needed. If it is smaller the library mapping may still succeed. The
// other issue is that although highly unlikely, there is no guarantee that
// Maximum randomized base address value. Used to ensure a margin // something else does not map into the area we are going to use between here and
// of 192 MB below baseAddressLimit. // when we try to map into it.
final long baseAddressMax = baseAddressLimit - 192 * 1024 * 1024; //
// The above notes mean that all of this is probablistic. It is however okay to do
// The maximum limit of the desired random offset. // because if, worst case and unlikely, we get unlucky in our choice of address,
final long pageSize = nativeGetPageSize(); // the back-out and retry without the shared RELRO in the ChildProcessService will
final int offsetLimit = (int) ((baseAddressMax - baseAddress) / pageSize); // keep things running.
final long maxExpectedBytes = 192 * 1024 * 1024;
// Get the greatest power of 2 that is smaller or equal to offsetLimit. final long address = nativeGetRandomBaseLoadAddress(maxExpectedBytes);
int numBits = 30;
for (; numBits > 1; numBits--) {
if ((1 << numBits) <= offsetLimit)
break;
}
if (DEBUG) {
final int maxValue = (1 << numBits) - 1;
Log.i(TAG, String.format(Locale.US, "offsetLimit=%d numBits=%d maxValue=%d (0x%x)",
offsetLimit, numBits, maxValue, maxValue));
}
// Find a random offset between 0 and (2^numBits - 1), included.
int offset = getRandomBits(numBits);
long address = 0;
if (offset >= 0)
address = baseAddress + offset * pageSize;
if (DEBUG) { if (DEBUG) {
Log.i(TAG, Log.i(TAG,
String.format(Locale.US, "Linker.computeRandomBaseLoadAddress() return 0x%x", String.format(Locale.US, "Random native base load address: 0x%x", address));
address));
} }
return address; return address;
} }
/**
* Return a cryptographically-strong random number of numBits bits.
* @param numBits The number of bits in the result. Must be in 1..31 range.
* @return A random integer between 0 and (2^numBits - 1), inclusive, or -1
* in case of error (e.g. if /dev/urandom can't be opened or read).
*/
private static int getRandomBits(int numBits) {
// Sanity check.
assert numBits > 0;
assert numBits < 32;
FileInputStream input;
try {
// A naive implementation would read a 32-bit integer then use modulo, but
// this introduces a slight bias. Instead, read 32-bit integers from the
// entropy source until the value is positive but smaller than maxLimit.
input = new FileInputStream(new File("/dev/urandom"));
} catch (Exception e) {
Log.e(TAG, "Could not open /dev/urandom", e);
return -1;
}
int result = 0;
try {
for (int n = 0; n < 4; n++) {
result = (result << 8) | (input.read() & 255);
}
} catch (Exception e) {
Log.e(TAG, "Could not read /dev/urandom", e);
return -1;
} finally {
try {
input.close();
} catch (Exception e) {
// Can't really do anything here.
}
}
result &= (1 << numBits) - 1;
if (DEBUG) {
Log.i(TAG, String.format(
Locale.US, "getRandomBits(%d) returned %d", numBits, result));
}
return result;
}
// Used for debugging only. // Used for debugging only.
private static void dumpBundle(Bundle bundle) { private static void dumpBundle(Bundle bundle) {
if (DEBUG) Log.i(TAG, "Bundle has " + bundle.size() + " items: " + bundle); if (DEBUG) Log.i(TAG, "Bundle has " + bundle.size() + " items: " + bundle);
...@@ -976,8 +908,16 @@ public class Linker { ...@@ -976,8 +908,16 @@ public class Linker {
*/ */
private static native boolean nativeCanUseSharedRelro(); private static native boolean nativeCanUseSharedRelro();
// Returns the native page size in bytes. /**
private static native long nativeGetPageSize(); * Return a random address that should be free to be mapped with the given size.
* Maps an area of size bytes, and if successful then unmaps it and returns
* the address of the area allocated by the system (with ASLR). The idea is
* that this area should remain free of other mappings until we map our library
* into it.
* @param sizeBytes Size of area in bytes to search for.
* @return address to pass to future mmap, or 0 on error.
*/
private static native long nativeGetRandomBaseLoadAddress(long sizeBytes);
/** /**
* Record information for a given library. * Record information for a given library.
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
#include <crazy_linker.h> #include <crazy_linker.h>
#include <jni.h> #include <jni.h>
#include <stdlib.h> #include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h> #include <unistd.h>
// Set this to 1 to enable debug traces to the Android log. // Set this to 1 to enable debug traces to the Android log.
...@@ -560,10 +561,16 @@ jboolean CanUseSharedRelro(JNIEnv* env, jclass clazz) { ...@@ -560,10 +561,16 @@ jboolean CanUseSharedRelro(JNIEnv* env, jclass clazz) {
return crazy_system_can_share_relro(); return crazy_system_can_share_relro();
} }
jlong GetPageSize(JNIEnv* env, jclass clazz) { jlong GetRandomBaseLoadAddress(JNIEnv* env, jclass clazz, jlong bytes) {
jlong result = static_cast<jlong>(sysconf(_SC_PAGESIZE)); void* address =
LOG_INFO("%s: System page size is %lld bytes\n", __FUNCTION__, result); mmap(NULL, bytes, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
return result; if (address == MAP_FAILED) {
LOG_INFO("%s: Random base load address not determinable\n", __FUNCTION__);
return 0;
}
munmap(address, bytes);
LOG_INFO("%s: Random base load address is %p\n", __FUNCTION__, address);
return static_cast<jlong>(reinterpret_cast<intptr_t>(address));
} }
const JNINativeMethod kNativeMethods[] = { const JNINativeMethod kNativeMethods[] = {
...@@ -610,11 +617,12 @@ const JNINativeMethod kNativeMethods[] = { ...@@ -610,11 +617,12 @@ const JNINativeMethod kNativeMethods[] = {
")" ")"
"Z", "Z",
reinterpret_cast<void*>(&CanUseSharedRelro)}, reinterpret_cast<void*>(&CanUseSharedRelro)},
{"nativeGetPageSize", {"nativeGetRandomBaseLoadAddress",
"(" "("
"J"
")" ")"
"J", "J",
reinterpret_cast<void*>(&GetPageSize)}, }; reinterpret_cast<void*>(&GetRandomBaseLoadAddress)}, };
} // namespace } // namespace
......
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