From 1bf93991ffe7bfe8adbf4f9fc92f2a1f287fc6ed Mon Sep 17 00:00:00 2001 From: wfh Date: Sun, 20 Sep 2015 19:21:14 -0700 Subject: Add support for base::UncheckedMalloc on Windows. Based on CL 944603002 by anthonyvd. BUG=447186,532981 TEST=base_unittests Review URL: https://codereview.chromium.org/1333393002 Cr-Commit-Position: refs/heads/master@{#349891} --- base/allocator/allocator_shim_win.cc | 3 + base/process/memory_unittest.cc | 78 +++++++++++++- base/process/memory_win.cc | 30 ++++-- base/security_unittest.cc | 194 +---------------------------------- 4 files changed, 98 insertions(+), 207 deletions(-) diff --git a/base/allocator/allocator_shim_win.cc b/base/allocator/allocator_shim_win.cc index a1473e5..2a933ee 100644 --- a/base/allocator/allocator_shim_win.cc +++ b/base/allocator/allocator_shim_win.cc @@ -172,6 +172,9 @@ void* malloc(size_t size) { return ptr; } +// Symbol to allow weak linkage to win_heap_malloc from memory_win.cc. +void* (*malloc_unchecked)(size_t) = &win_heap_malloc; + // free.c void free(void* p) { win_heap_free(p); diff --git a/base/process/memory_unittest.cc b/base/process/memory_unittest.cc index 0276b49..98f049a 100644 --- a/base/process/memory_unittest.cc +++ b/base/process/memory_unittest.cc @@ -30,6 +30,16 @@ #endif #if defined(OS_WIN) + +#if defined(_MSC_VER) +// ssize_t needed for OutOfMemoryTest. +#if defined(_WIN64) +typedef __int64 ssize_t; +#else +typedef long ssize_t; +#endif +#endif + // HeapQueryInformation function pointer. typedef BOOL (WINAPI* HeapQueryFn) \ (HANDLE, HEAP_INFORMATION_CLASS, PVOID, SIZE_T, PSIZE_T); @@ -130,8 +140,9 @@ TEST(ProcessMemoryTest, MacTerminateOnHeapCorruption) { // OutOfMemoryTest cases. OpenBSD does not support these tests either. // Don't test these on ASan/TSan/MSan configurations: only test the real // allocator. -// TODO(vandebo) make this work on Windows too. -#if !defined(OS_ANDROID) && !defined(OS_OPENBSD) && !defined(OS_WIN) && \ +// Windows only supports these tests with the allocator shim in place. +#if !defined(OS_ANDROID) && !defined(OS_OPENBSD) && \ + !(defined(OS_WIN) && !defined(ALLOCATOR_SHIM)) && \ !defined(MEMORY_TOOL_REPLACES_ALLOCATOR) #if defined(USE_TCMALLOC) @@ -151,6 +162,9 @@ class OutOfMemoryTest : public testing::Test { // Make test size as large as possible minus a few pages so // that alignment or other rounding doesn't make it wrap. test_size_(std::numeric_limits::max() - 12 * 1024), + // A test size that is > 2Gb and will cause the allocators to reject + // the allocation due to security restrictions. See crbug.com/169327. + insecure_test_size_(std::numeric_limits::max()), signed_test_size_(std::numeric_limits::max()) { } @@ -163,6 +177,7 @@ class OutOfMemoryTest : public testing::Test { protected: void* value_; size_t test_size_; + size_t insecure_test_size_; ssize_t signed_test_size_; }; @@ -213,6 +228,47 @@ TEST_F(OutOfMemoryDeathTest, Calloc) { }, kOomRegex); } +// OS X has no 2Gb allocation limit. +// See https://crbug.com/169327. +#if !defined(OS_MACOSX) +TEST_F(OutOfMemoryDeathTest, SecurityNew) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = operator new(insecure_test_size_); + }, kOomRegex); +} + +TEST_F(OutOfMemoryDeathTest, SecurityNewArray) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = new char[insecure_test_size_]; + }, kOomRegex); +} + +TEST_F(OutOfMemoryDeathTest, SecurityMalloc) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = malloc(insecure_test_size_); + }, kOomRegex); +} + +TEST_F(OutOfMemoryDeathTest, SecurityRealloc) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = realloc(NULL, insecure_test_size_); + }, kOomRegex); +} + +TEST_F(OutOfMemoryDeathTest, SecurityCalloc) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = calloc(1024, insecure_test_size_ / 1024L); + }, kOomRegex); +} +#endif // !defined(OS_MACOSX) + +#if defined(OS_LINUX) + TEST_F(OutOfMemoryDeathTest, Valloc) { ASSERT_DEATH({ SetUpInDeathAssert(); @@ -220,7 +276,12 @@ TEST_F(OutOfMemoryDeathTest, Valloc) { }, kOomRegex); } -#if defined(OS_LINUX) +TEST_F(OutOfMemoryDeathTest, SecurityValloc) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = valloc(insecure_test_size_); + }, kOomRegex); +} #if PVALLOC_AVAILABLE == 1 TEST_F(OutOfMemoryDeathTest, Pvalloc) { @@ -229,6 +290,13 @@ TEST_F(OutOfMemoryDeathTest, Pvalloc) { value_ = pvalloc(test_size_); }, kOomRegex); } + +TEST_F(OutOfMemoryDeathTest, SecurityPvalloc) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = pvalloc(insecure_test_size_); + }, kOomRegex); +} #endif // PVALLOC_AVAILABLE == 1 TEST_F(OutOfMemoryDeathTest, Memalign) { @@ -415,5 +483,5 @@ TEST_F(OutOfMemoryHandledTest, UncheckedCalloc) { EXPECT_TRUE(value_ == NULL); } #endif // !defined(MEMORY_TOOL_REPLACES_ALLOCATOR) -#endif // !defined(OS_ANDROID) && !defined(OS_OPENBSD) && !defined(OS_WIN) && - // !defined(ADDRESS_SANITIZER) +#endif // !defined(OS_ANDROID) && !defined(OS_OPENBSD) && !(defined(OS_WIN) && + // !defined(ALLOCATOR_SHIM)) && !defined(MEMORY_TOOL_REPLACES_ALLOCATOR) diff --git a/base/process/memory_win.cc b/base/process/memory_win.cc index fc57b48..b949b5d 100644 --- a/base/process/memory_win.cc +++ b/base/process/memory_win.cc @@ -10,6 +10,21 @@ #include "base/logging.h" #include "base/memory/scoped_ptr.h" +// malloc_unchecked is required to implement UncheckedMalloc properly. +// It's provided by allocator_shim_win.cc but since that's not always present, +// we provide a default that falls back to regular malloc. +typedef void* (*MallocFn)(size_t); +extern "C" void* (*const malloc_unchecked)(size_t); +extern "C" void* (*const malloc_default)(size_t) = &malloc; + +#if defined(_M_IX86) +#pragma comment(linker, "/alternatename:_malloc_unchecked=_malloc_default") +#elif defined(_M_X64) || defined(_M_ARM) +#pragma comment(linker, "/alternatename:malloc_unchecked=malloc_default") +#else +#error Unsupported platform +#endif + namespace base { namespace { @@ -17,10 +32,12 @@ namespace { #pragma warning(push) #pragma warning(disable: 4702) -int OnNoMemory(size_t) { +int OnNoMemory(size_t size) { // Kill the process. This is important for security since most of code // does not check the result of memory allocation. - __debugbreak(); + LOG(FATAL) << "Out of memory, size = " << size; + + // Safety check, make sure process exits here. _exit(1); return 0; } @@ -88,14 +105,9 @@ HMODULE GetModuleFromAddress(void* address) { return instance; } -// TODO(b.kelemen): implement it with the required semantics. On Linux this is -// implemented with a weak symbol that is overridden by tcmalloc. This is -// neccessary because base cannot have a direct dependency on tcmalloc. Since -// weak symbols are not supported on Windows this will involve some build time -// magic, much like what is done for libcrt in order to override the allocation -// functions. +// Implemented using a weak symbol. bool UncheckedMalloc(size_t size, void** result) { - *result = malloc(size); + *result = malloc_unchecked(size); return *result != NULL; } diff --git a/base/security_unittest.cc b/base/security_unittest.cc index 07ba6f5..a9c73f7 100644 --- a/base/security_unittest.cc +++ b/base/security_unittest.cc @@ -23,47 +23,11 @@ #include #endif -#if defined(OS_WIN) -#include -#endif - using std::nothrow; using std::numeric_limits; namespace { -#if defined(OS_WIN) -// This is a permitted size but exhausts memory pretty quickly. -const size_t kLargePermittedAllocation = 0x7FFFE000; - -int OnNoMemory(size_t) { - _exit(1); -} - -void ExhaustMemoryWithMalloc() { - for (;;) { - // Without the |volatile|, clang optimizes away the allocation. - void* volatile buf = malloc(kLargePermittedAllocation); - if (!buf) - break; - } -} - -void ExhaustMemoryWithRealloc() { - size_t size = kLargePermittedAllocation; - void* buf = malloc(size); - if (!buf) - return; - for (;;) { - size += kLargePermittedAllocation; - void* new_buf = realloc(buf, size); - if (!buf) - break; - buf = new_buf; - } -} -#endif - // This function acts as a compiler optimization barrier. We use it to // prevent the compiler from making an expression a compile-time constant. // We also use it so that the compiler doesn't discard certain return values @@ -92,144 +56,16 @@ NOINLINE Type HideValueFromCompiler(volatile Type value) { #define MALLOC_OVERFLOW_TEST(function) DISABLED_##function #endif -// TODO(jln): switch to std::numeric_limits::max() when we switch to -// C++11. -const size_t kTooBigAllocSize = INT_MAX; - +#if defined(OS_LINUX) && defined(__x86_64__) // Detect runtime TCMalloc bypasses. bool IsTcMallocBypassed() { -#if defined(OS_LINUX) // This should detect a TCMalloc bypass from Valgrind. char* g_slice = getenv("G_SLICE"); if (g_slice && !strcmp(g_slice, "always-malloc")) return true; -#endif return false; } - -bool CallocDiesOnOOM() { -// The sanitizers' calloc dies on OOM instead of returning NULL. -// The wrapper function in base/process_util_linux.cc that is used when we -// compile without TCMalloc will just die on OOM instead of returning NULL. -#if defined(ADDRESS_SANITIZER) || \ - defined(MEMORY_SANITIZER) || \ - defined(THREAD_SANITIZER) || \ - (defined(OS_LINUX) && defined(NO_TCMALLOC)) - return true; -#else - return false; #endif -} - -// Fake test that allow to know the state of TCMalloc by looking at bots. -TEST(SecurityTest, MALLOC_OVERFLOW_TEST(IsTCMallocDynamicallyBypassed)) { - printf("Malloc is dynamically bypassed: %s\n", - IsTcMallocBypassed() ? "yes." : "no."); -} - -// The MemoryAllocationRestrictions* tests test that we can not allocate a -// memory range that cannot be indexed via an int. This is used to mitigate -// vulnerabilities in libraries that use int instead of size_t. See -// crbug.com/169327. - -TEST(SecurityTest, MALLOC_OVERFLOW_TEST(MemoryAllocationRestrictionsMalloc)) { - if (!IsTcMallocBypassed()) { - scoped_ptr ptr(static_cast( - HideValueFromCompiler(malloc(kTooBigAllocSize)))); - ASSERT_TRUE(!ptr); - } -} - -#if defined(GTEST_HAS_DEATH_TEST) && defined(OS_WIN) -TEST(SecurityTest, MALLOC_OVERFLOW_TEST(MemoryAllocationMallocDeathTest)) { - _set_new_handler(&OnNoMemory); - _set_new_mode(1); - { - scoped_ptr ptr; - EXPECT_DEATH(ptr.reset(static_cast( - HideValueFromCompiler(malloc(kTooBigAllocSize)))), - ""); - ASSERT_TRUE(!ptr); - } - _set_new_handler(NULL); - _set_new_mode(0); -} - -TEST(SecurityTest, MALLOC_OVERFLOW_TEST(MemoryAllocationExhaustDeathTest)) { - _set_new_handler(&OnNoMemory); - _set_new_mode(1); - { - ASSERT_DEATH(ExhaustMemoryWithMalloc(), ""); - } - _set_new_handler(NULL); - _set_new_mode(0); -} - -TEST(SecurityTest, MALLOC_OVERFLOW_TEST(MemoryReallocationExhaustDeathTest)) { - _set_new_handler(&OnNoMemory); - _set_new_mode(1); - { - ASSERT_DEATH(ExhaustMemoryWithRealloc(), ""); - } - _set_new_handler(NULL); - _set_new_mode(0); -} -#endif - -TEST(SecurityTest, MALLOC_OVERFLOW_TEST(MemoryAllocationRestrictionsCalloc)) { - if (!IsTcMallocBypassed()) { - scoped_ptr ptr(static_cast( - HideValueFromCompiler(calloc(kTooBigAllocSize, 1)))); - ASSERT_TRUE(!ptr); - } -} - -TEST(SecurityTest, MALLOC_OVERFLOW_TEST(MemoryAllocationRestrictionsRealloc)) { - if (!IsTcMallocBypassed()) { - char* orig_ptr = static_cast(malloc(1)); - ASSERT_TRUE(orig_ptr); - scoped_ptr ptr(static_cast( - HideValueFromCompiler(realloc(orig_ptr, kTooBigAllocSize)))); - ASSERT_TRUE(!ptr); - // If realloc() did not succeed, we need to free orig_ptr. - free(orig_ptr); - } -} - -typedef struct { - char large_array[kTooBigAllocSize]; -} VeryLargeStruct; - -TEST(SecurityTest, MALLOC_OVERFLOW_TEST(MemoryAllocationRestrictionsNew)) { - if (!IsTcMallocBypassed()) { - scoped_ptr ptr( - HideValueFromCompiler(new (nothrow) VeryLargeStruct)); - ASSERT_TRUE(!ptr); - } -} - -#if defined(GTEST_HAS_DEATH_TEST) && defined(OS_WIN) -TEST(SecurityTest, MALLOC_OVERFLOW_TEST(MemoryAllocationNewDeathTest)) { - _set_new_handler(&OnNoMemory); - { - scoped_ptr ptr; - EXPECT_DEATH( - ptr.reset(HideValueFromCompiler(new (nothrow) VeryLargeStruct)), ""); - ASSERT_TRUE(!ptr); - } - _set_new_handler(NULL); -} -#endif - -TEST(SecurityTest, MALLOC_OVERFLOW_TEST(MemoryAllocationRestrictionsNewArray)) { - if (!IsTcMallocBypassed()) { - scoped_ptr ptr( - HideValueFromCompiler(new (nothrow) char[kTooBigAllocSize])); - ASSERT_TRUE(!ptr); - } -} - -// The tests bellow check for overflows in new[] and calloc(). // There are platforms where these tests are known to fail. We would like to // be able to easily check the status on the bots, but marking tests as @@ -287,34 +123,6 @@ TEST(SecurityTest, MAYBE_NewOverflow) { #endif // !defined(OS_WIN) || !defined(ARCH_CPU_64_BITS) } -// Call calloc(), eventually free the memory and return whether or not -// calloc() did succeed. -bool CallocReturnsNull(size_t nmemb, size_t size) { - scoped_ptr array_pointer( - static_cast(calloc(nmemb, size))); - // We need the call to HideValueFromCompiler(): we have seen LLVM - // optimize away the call to calloc() entirely and assume the pointer to not - // be NULL. - return HideValueFromCompiler(array_pointer.get()) == NULL; -} - -// Test if calloc() can overflow. -TEST(SecurityTest, CallocOverflow) { - const size_t kArraySize = 4096; - const size_t kMaxSizeT = numeric_limits::max(); - const size_t kArraySize2 = kMaxSizeT / kArraySize + 10; - if (!CallocDiesOnOOM()) { - EXPECT_TRUE(CallocReturnsNull(kArraySize, kArraySize2)); - EXPECT_TRUE(CallocReturnsNull(kArraySize2, kArraySize)); - } else { - // It's also ok for calloc to just terminate the process. -#if defined(GTEST_HAS_DEATH_TEST) - EXPECT_DEATH(CallocReturnsNull(kArraySize, kArraySize2), ""); - EXPECT_DEATH(CallocReturnsNull(kArraySize2, kArraySize), ""); -#endif // GTEST_HAS_DEATH_TEST - } -} - #if defined(OS_LINUX) && defined(__x86_64__) // Check if ptr1 and ptr2 are separated by less than size chars. bool ArePointersToSameArea(void* ptr1, void* ptr2, size_t size) { -- cgit v1.1