summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwfh <wfh@chromium.org>2015-09-20 19:21:14 -0700
committerCommit bot <commit-bot@chromium.org>2015-09-21 02:21:54 +0000
commit1bf93991ffe7bfe8adbf4f9fc92f2a1f287fc6ed (patch)
tree6210f54347b10e5ff8b14d7b306ef708bef14cd6
parent451c919c691d80e78fa0e32e1fb829bd67aea9d0 (diff)
downloadchromium_src-1bf93991ffe7bfe8adbf4f9fc92f2a1f287fc6ed.zip
chromium_src-1bf93991ffe7bfe8adbf4f9fc92f2a1f287fc6ed.tar.gz
chromium_src-1bf93991ffe7bfe8adbf4f9fc92f2a1f287fc6ed.tar.bz2
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}
-rw-r--r--base/allocator/allocator_shim_win.cc3
-rw-r--r--base/process/memory_unittest.cc78
-rw-r--r--base/process/memory_win.cc30
-rw-r--r--base/security_unittest.cc194
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<std::size_t>::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<int>::max()),
signed_test_size_(std::numeric_limits<ssize_t>::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 <unistd.h>
#endif
-#if defined(OS_WIN)
-#include <new.h>
-#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<int>::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<char, base::FreeDeleter> ptr(static_cast<char*>(
- 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<char, base::FreeDeleter> ptr;
- EXPECT_DEATH(ptr.reset(static_cast<char*>(
- 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<char, base::FreeDeleter> ptr(static_cast<char*>(
- HideValueFromCompiler(calloc(kTooBigAllocSize, 1))));
- ASSERT_TRUE(!ptr);
- }
-}
-
-TEST(SecurityTest, MALLOC_OVERFLOW_TEST(MemoryAllocationRestrictionsRealloc)) {
- if (!IsTcMallocBypassed()) {
- char* orig_ptr = static_cast<char*>(malloc(1));
- ASSERT_TRUE(orig_ptr);
- scoped_ptr<char, base::FreeDeleter> ptr(static_cast<char*>(
- 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<VeryLargeStruct> 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<VeryLargeStruct> 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<char[]> 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<char, base::FreeDeleter> array_pointer(
- static_cast<char*>(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<size_t>::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) {