ART: Randomize mem_map start address for linear scan search
When using linear scan for mem_map, randomize the start of the
search with getauxval(AT_RANDOM).
Change-Id: Id1e4c86b928147d74b9b0b73ff704de5d87b4500
diff --git a/runtime/mem_map.cc b/runtime/mem_map.cc
index 8a555fb..49e0b54 100644
--- a/runtime/mem_map.cc
+++ b/runtime/mem_map.cc
@@ -20,6 +20,11 @@
#include <backtrace/BacktraceMap.h>
#include <memory>
+// See CreateStartPos below.
+#ifdef __BIONIC__
+#include <sys/auxv.h>
+#endif
+
#include "base/stringprintf.h"
#include "ScopedFd.h"
#include "utils.h"
@@ -47,10 +52,61 @@
}
#if defined(__LP64__) && !defined(__x86_64__)
-// Where to start with low memory allocation. The first 64KB is protected by SELinux.
+// Handling mem_map in 32b address range for 64b architectures that do not support MAP_32BIT.
+
+// The regular start of memory allocations. The first 64KB is protected by SELinux.
static constexpr uintptr_t LOW_MEM_START = 64 * KB;
-uintptr_t MemMap::next_mem_pos_ = LOW_MEM_START; // first page to check for low-mem extent
+// Generate random starting position.
+// To not interfere with image position, take the image's address and only place it below. Current
+// formula (sketch):
+//
+// ART_BASE_ADDR = 0001XXXXXXXXXXXXXXX
+// ----------------------------------------
+// = 0000111111111111111
+// & ~(kPageSize - 1) =~0000000000000001111
+// ----------------------------------------
+// mask = 0000111111111110000
+// & random data = YYYYYYYYYYYYYYYYYYY
+// -----------------------------------
+// tmp = 0000YYYYYYYYYYY0000
+// + LOW_MEM_START = 0000000000001000000
+// --------------------------------------
+// start
+//
+// getauxval as an entropy source is exposed in Bionic, but not in glibc before 2.16. When we
+// do not have Bionic, simply start with LOW_MEM_START.
+
+// Function is standalone so it can be tested somewhat in mem_map_test.cc.
+#ifdef __BIONIC__
+uintptr_t CreateStartPos(uint64_t input) {
+ CHECK_NE(0, ART_BASE_ADDRESS);
+
+ // Start with all bits below highest bit in ART_BASE_ADDRESS.
+ constexpr size_t leading_zeros = CLZ(static_cast<uint32_t>(ART_BASE_ADDRESS));
+ constexpr uintptr_t mask_ones = (1 << (31 - leading_zeros)) - 1;
+
+ // Lowest (usually 12) bits are not used, as aligned by page size.
+ constexpr uintptr_t mask = mask_ones & ~(kPageSize - 1);
+
+ // Mask input data.
+ return (input & mask) + LOW_MEM_START;
+}
+#endif
+
+static uintptr_t GenerateNextMemPos() {
+#ifdef __BIONIC__
+ uint8_t* random_data = reinterpret_cast<uint8_t*>(getauxval(AT_RANDOM));
+ // The lower 8B are taken for the stack guard. Use the upper 8B (with mask).
+ return CreateStartPos(*reinterpret_cast<uintptr_t*>(random_data + 8));
+#else
+ // No auxv on host, see above.
+ return LOW_MEM_START;
+#endif
+}
+
+// Initialize linear scan to random position.
+uintptr_t MemMap::next_mem_pos_ = GenerateNextMemPos();
#endif
static bool CheckMapRequest(byte* expected_ptr, void* actual_ptr, size_t byte_count,
diff --git a/runtime/mem_map.h b/runtime/mem_map.h
index 4255d17..1411856 100644
--- a/runtime/mem_map.h
+++ b/runtime/mem_map.h
@@ -28,6 +28,12 @@
namespace art {
// Used to keep track of mmap segments.
+//
+// On 64b systems not supporting MAP_32BIT, the implementation of MemMap will do a linear scan
+// for free pages. For security, the start of this scan should be randomized. This requires a
+// dynamic initializer.
+// For this to work, it is paramount that there are no other static initializers that access MemMap.
+// Otherwise, calls might see uninitialized values.
class MemMap {
public:
// Request an anonymous region of length 'byte_count' and a requested base address.
diff --git a/runtime/mem_map_test.cc b/runtime/mem_map_test.cc
index c07a9a3..c108a5f 100644
--- a/runtime/mem_map_test.cc
+++ b/runtime/mem_map_test.cc
@@ -84,8 +84,41 @@
}
delete m1;
}
+
+#if defined(__LP64__) && !defined(__x86_64__)
+ static uintptr_t GetLinearScanPos() {
+ return MemMap::next_mem_pos_;
+ }
+#endif
};
+#if defined(__LP64__) && !defined(__x86_64__)
+
+#ifdef __BIONIC__
+extern uintptr_t CreateStartPos(uint64_t input);
+#endif
+
+TEST_F(MemMapTest, Start) {
+ uintptr_t start = GetLinearScanPos();
+ EXPECT_LE(64 * KB, start);
+ EXPECT_LT(start, static_cast<uintptr_t>(ART_BASE_ADDRESS));
+
+#ifdef __BIONIC__
+ // Test a couple of values. Make sure they are different.
+ uintptr_t last = 0;
+ for (size_t i = 0; i < 100; ++i) {
+ uintptr_t random_start = CreateStartPos(i * kPageSize);
+ EXPECT_NE(last, random_start);
+ last = random_start;
+ }
+
+ // Even on max, should be below ART_BASE_ADDRESS.
+ EXPECT_LT(CreateStartPos(~0), static_cast<uintptr_t>(ART_BASE_ADDRESS));
+#endif
+ // End of test.
+}
+#endif
+
TEST_F(MemMapTest, MapAnonymousEmpty) {
std::string error_msg;
std::unique_ptr<MemMap> map(MemMap::MapAnonymous("MapAnonymousEmpty",