tcmalloc: support userland ASLR on Linux and Chrome OS
Original CLs:
- https://chromiumcodereview.appspot.com/12093035
TCMalloc: support userland ASLR on Linux and Chrome OS
On Linux and Chrome OS, we implement user-land ASLR in TCMalloc
on 64 bits Intel architecture.
In this configuration, we are not constrained by the address space
and we don't mind fragmentation.
But to be on the safe side, we only ever fragment half of the
address space.
BUG=170133
NOTRY=true
Committed:
https://src.chromium.org/viewvc/chrome?view=rev&revision=179776
- https://chromiumcodereview.appspot.com/12192024
Linux: grow a unique random mapping in ASLR
We loosen ASLR by only growing one random mapping. The previous
version
had security benefits but had a negative performance impact.
This change aims to be performance neutral in respect to the pre-ASLR
era.
At a later date, we may try to strike a good balance between
performance and
security.
This is a re-land of https://chromiumcodereview.appspot.com/12090112/
BUG=170133, 173371
NOTRY=true
TBR=jar
Committed:
https://src.chromium.org/viewvc/chrome?view=rev&revision=180556
- https://codereview.chromium.org/237673002
remove redundant ifdefs
OS_CHROMEOS implies OS_LINUX, so OS_LINUX || OS_CHROMEOS can be
simplified to OS_LINUX
BUG=none
Committed:
https://src.chromium.org/viewvc/chrome?view=rev&revision=263993
BUG=724399,b:70905156
Change-Id: Ic9da3524439312252f0f14d4c55ad882d67ebfed
Reviewed-on: https://chromium-review.googlesource.com/1130791
Reviewed-by: Will Harris <wfh@chromium.org>
Reviewed-by: Chris Palmer <palmer@chromium.org>
Commit-Queue: Gabriel Marin <gmx@chromium.org>
Cr-Original-Commit-Position: refs/heads/master@{#585993}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: 27ebd00bcd3c6095c3626526cec2b7f438965757
diff --git a/src/system-alloc.cc b/src/system-alloc.cc
old mode 100755
new mode 100644
index 292e482..235ccb1
--- a/src/system-alloc.cc
+++ b/src/system-alloc.cc
@@ -48,11 +48,12 @@
#ifdef HAVE_UNISTD_H
#include <unistd.h> // for sbrk, getpagesize, off_t
#endif
-#include <new> // for operator new
#include <gperftools/malloc_extension.h>
+#include <new> // for operator new
#include "base/basictypes.h"
#include "base/commandlineflags.h"
-#include "base/spinlock.h" // for SpinLockHolder, SpinLock, etc
+#include "base/spinlock.h" // for SpinLockHolder, SpinLock, etc
+#include "build/build_config.h"
#include "common.h"
#include "internal_logging.h"
@@ -105,6 +106,144 @@
return always_ok || ((ptr >> shift_bits) == 0);
}
+namespace {
+
+#if defined(OS_LINUX) && defined(__x86_64__)
+#define ASLR_IS_SUPPORTED
+#endif
+
+#if defined(ASLR_IS_SUPPORTED)
+// From libdieharder, public domain library by Bob Jenkins (rngav.c).
+// Described at http://burtleburtle.net/bob/rand/smallprng.html.
+// Not cryptographically secure, but good enough for what we need.
+typedef uint32_t u4;
+struct ranctx {
+ u4 a;
+ u4 b;
+ u4 c;
+ u4 d;
+};
+
+#define rot(x, k) (((x) << (k)) | ((x) >> (32 - (k))))
+
+u4 ranval(ranctx* x) {
+ /* xxx: the generator being tested */
+ u4 e = x->a - rot(x->b, 27);
+ x->a = x->b ^ rot(x->c, 17);
+ x->b = x->c + x->d;
+ x->c = x->d + e;
+ x->d = e + x->a;
+ return x->d;
+}
+
+void raninit(ranctx* x, u4 seed) {
+ u4 i;
+ x->a = 0xf1ea5eed;
+ x->b = x->c = x->d = seed;
+ for (i = 0; i < 20; ++i) {
+ (void)ranval(x);
+ }
+}
+
+// If the kernel cannot honor the hint in arch_get_unmapped_area_topdown, it
+// will simply ignore it. So we give a hint that has a good chance of
+// working.
+// The mmap top-down allocator will normally allocate below TASK_SIZE - gap,
+// with a gap that depends on the max stack size. See x86/mm/mmap.c. We
+// should make allocations that are below this area, which would be
+// 0x7ffbf8000000.
+// We use 0x3ffffffff000 as the mask so that we only "pollute" half of the
+// address space. In the unlikely case where fragmentation would become an
+// issue, the kernel will still have another half to use.
+const uint64_t kRandomAddressMask = 0x3ffffffff000ULL;
+
+#endif // defined(ASLR_IS_SUPPORTED)
+
+// Give a random "hint" that is suitable for use with mmap(). This cannot make
+// mmap fail, as the kernel will simply not follow the hint if it can't.
+// However, this will create address space fragmentation. Currently, we only
+// implement it on x86_64, where we have a 47 bits userland address space and
+// fragmentation is not an issue.
+void* GetRandomAddrHint() {
+#if !defined(ASLR_IS_SUPPORTED)
+ return NULL;
+#else
+ // Note: we are protected by the general TCMalloc_SystemAlloc spinlock. Given
+ // the nature of what we're doing, it wouldn't be critical if we weren't for
+ // ctx, but it is for the "initialized" variable.
+ // It's nice to share the state between threads, because scheduling will add
+ // some randomness to the succession of ranval() calls.
+ static ranctx ctx;
+ static bool initialized = false;
+ if (!initialized) {
+ initialized = true;
+ // We really want this to be a stack variable and don't want any compiler
+ // optimization. We're using its address as a poor-man source of
+ // randomness.
+ volatile char c;
+ // Pre-initialize our seed with a "random" address in case /dev/urandom is
+ // not available.
+ uint32_t seed =
+ (reinterpret_cast<uint64_t>(&c) >> 32) ^ reinterpret_cast<uint64_t>(&c);
+ int urandom_fd = open("/dev/urandom", O_RDONLY);
+ if (urandom_fd >= 0) {
+ const ssize_t length = read(urandom_fd, &seed, sizeof(seed));
+ ASSERT(length == sizeof(seed));
+ int ret = close(urandom_fd);
+ ASSERT(ret == 0);
+ }
+ raninit(&ctx, seed);
+ }
+ uint64_t random_address =
+ (static_cast<uint64_t>(ranval(&ctx)) << 32) | ranval(&ctx);
+ // A bit-wise "and" won't bias our random distribution because of all the 0xfs
+ // in the high-order bits.
+ random_address &= kRandomAddressMask;
+ return reinterpret_cast<void*>(random_address);
+#endif // ASLR_IS_SUPPORTED
+}
+
+// Allocate |length| bytes of memory using mmap(). The memory will be
+// readable and writeable, but not executable.
+// Like mmap(), we will return MAP_FAILED on failure.
+// |is_aslr_enabled| controls address space layout randomization. When true, we
+// will put the first mapping at a random address and will then try to grow it.
+// If it's not possible to grow an existing mapping, a new one will be created.
+void* AllocWithMmap(size_t length, bool is_aslr_enabled) {
+ // Note: we are protected by the general TCMalloc_SystemAlloc spinlock.
+ static void* address_hint = NULL;
+#if defined(ASLR_IS_SUPPORTED)
+ if (is_aslr_enabled &&
+ (!address_hint ||
+ reinterpret_cast<uint64_t>(address_hint) & ~kRandomAddressMask)) {
+ address_hint = GetRandomAddrHint();
+ }
+#endif // ASLR_IS_SUPPORTED
+
+ // address_hint is likely to make us grow an existing mapping.
+ void* result = mmap(address_hint, length, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+#if defined(ASLR_IS_SUPPORTED)
+ if (result == address_hint) {
+ // If mmap() succeeded at a address_hint, our next mmap() will try to grow
+ // the current mapping as long as it's compatible with our ASLR mask.
+ // This has been done for performance reasons, see https://crbug.com/173371.
+ // It should be possible to strike a better balance between performance
+ // and security but will be done at a later date.
+ // If this overflows, it could only set address_hint to NULL, which is
+ // what we want (and can't happen on the currently supported architecture).
+ address_hint = static_cast<char*>(result) + length;
+ } else {
+ // mmap failed or a collision prevented the kernel from honoring the hint,
+ // reset the hint.
+ address_hint = NULL;
+ }
+#endif // ASLR_IS_SUPPORTED
+ return result;
+}
+
+} // Anonymous namespace.
+
COMPILE_ASSERT(kAddressBits <= 8 * sizeof(void*),
address_bits_larger_than_pointer_size);
@@ -141,6 +280,14 @@
"Whether MADV_FREE/MADV_DONTNEED should be used"
" to return unused memory to the system.");
+DEFINE_bool(malloc_random_allocator,
+#if defined(ASLR_IS_SUPPORTED)
+ EnvToBool("TCMALLOC_ASLR", true),
+#else
+ EnvToBool("TCMALLOC_ASLR", false),
+#endif
+ "Whether to randomize the address space via mmap().");
+
// static allocators
class SbrkSysAllocator : public SysAllocator {
public:
@@ -315,10 +462,7 @@
// size + alignment < (1<<NBITS).
// and extra <= alignment
// therefore size + extra < (1<<NBITS)
- void* result = mmap(NULL, size + extra,
- PROT_READ|PROT_WRITE,
- MAP_PRIVATE|MAP_ANONYMOUS,
- -1, 0);
+ void* result = AllocWithMmap(size + extra, FLAGS_malloc_random_allocator);
if (result == reinterpret_cast<void*>(MAP_FAILED)) {
return NULL;
}
@@ -470,6 +614,12 @@
// the heap-checker is less likely to misinterpret a number as a
// pointer).
DefaultSysAllocator *sdef = new (default_space.buf) DefaultSysAllocator();
+// Unfortunately, this code runs before flags are initialized. So
+// we can't use FLAGS_malloc_random_allocator.
+#if defined(ASLR_IS_SUPPORTED)
+ // Our only random allocator is mmap.
+ sdef->SetChildAllocator(mmap, 0, mmap_name);
+#else
if (kDebugMode && sizeof(void*) > 4) {
sdef->SetChildAllocator(mmap, 0, mmap_name);
sdef->SetChildAllocator(sbrk, 1, sbrk_name);
@@ -477,7 +627,7 @@
sdef->SetChildAllocator(sbrk, 0, sbrk_name);
sdef->SetChildAllocator(mmap, 1, mmap_name);
}
-
+#endif // ASLR_IS_SUPPORTED
tcmalloc_sys_alloc = tc_get_sysalloc_override(sdef);
}