summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--base/process_util_mac.mm62
-rw-r--r--base/process_util_unittest.cc34
2 files changed, 89 insertions, 7 deletions
diff --git a/base/process_util_mac.mm b/base/process_util_mac.mm
index e6ccd32..24f93ee 100644
--- a/base/process_util_mac.mm
+++ b/base/process_util_mac.mm
@@ -547,8 +547,34 @@ malloc_error_break_t LookUpMallocErrorBreak() {
return NULL;
}
+// Simple scoper that saves the current value of errno, resets it to 0, and on
+// destruction puts the old value back. This is so that CrMallocErrorBreak can
+// safely test errno free from the effects of other routines.
+class ScopedClearErrno {
+ public:
+ ScopedClearErrno() : old_errno_(errno) {
+ errno = 0;
+ }
+ ~ScopedClearErrno() {
+ if (errno == 0)
+ errno = old_errno_;
+ }
+
+ private:
+ int old_errno_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedClearErrno);
+};
+
void CrMallocErrorBreak() {
g_original_malloc_error_break();
+
+ // Out of memory is certainly not heap corruption, and not necessarily
+ // something for which the process should be terminated. Leave that decision
+ // to the OOM killer.
+ if (errno == ENOMEM)
+ return;
+
// A unit test checks this error message, so it needs to be in release builds.
LOG(ERROR) <<
"Terminating process due to a potential for future heap corruption";
@@ -594,6 +620,8 @@ typedef void* (*calloc_type)(struct _malloc_zone_t* zone,
size_t size);
typedef void* (*valloc_type)(struct _malloc_zone_t* zone,
size_t size);
+typedef void (*free_type)(struct _malloc_zone_t* zone,
+ void* ptr);
typedef void* (*realloc_type)(struct _malloc_zone_t* zone,
void* ptr,
size_t size);
@@ -604,17 +632,20 @@ typedef void* (*memalign_type)(struct _malloc_zone_t* zone,
malloc_type g_old_malloc;
calloc_type g_old_calloc;
valloc_type g_old_valloc;
+free_type g_old_free;
realloc_type g_old_realloc;
memalign_type g_old_memalign;
malloc_type g_old_malloc_purgeable;
calloc_type g_old_calloc_purgeable;
valloc_type g_old_valloc_purgeable;
+free_type g_old_free_purgeable;
realloc_type g_old_realloc_purgeable;
memalign_type g_old_memalign_purgeable;
void* oom_killer_malloc(struct _malloc_zone_t* zone,
size_t size) {
+ ScopedClearErrno clear_errno;
void* result = g_old_malloc(zone, size);
if (!result && size)
debug::BreakDebugger();
@@ -624,6 +655,7 @@ void* oom_killer_malloc(struct _malloc_zone_t* zone,
void* oom_killer_calloc(struct _malloc_zone_t* zone,
size_t num_items,
size_t size) {
+ ScopedClearErrno clear_errno;
void* result = g_old_calloc(zone, num_items, size);
if (!result && num_items && size)
debug::BreakDebugger();
@@ -632,15 +664,23 @@ void* oom_killer_calloc(struct _malloc_zone_t* zone,
void* oom_killer_valloc(struct _malloc_zone_t* zone,
size_t size) {
+ ScopedClearErrno clear_errno;
void* result = g_old_valloc(zone, size);
if (!result && size)
debug::BreakDebugger();
return result;
}
+void oom_killer_free(struct _malloc_zone_t* zone,
+ void* ptr) {
+ ScopedClearErrno clear_errno;
+ g_old_free(zone, ptr);
+}
+
void* oom_killer_realloc(struct _malloc_zone_t* zone,
void* ptr,
size_t size) {
+ ScopedClearErrno clear_errno;
void* result = g_old_realloc(zone, ptr, size);
if (!result && size)
debug::BreakDebugger();
@@ -650,6 +690,7 @@ void* oom_killer_realloc(struct _malloc_zone_t* zone,
void* oom_killer_memalign(struct _malloc_zone_t* zone,
size_t alignment,
size_t size) {
+ ScopedClearErrno clear_errno;
void* result = g_old_memalign(zone, alignment, size);
// Only die if posix_memalign would have returned ENOMEM, since there are
// other reasons why NULL might be returned (see
@@ -663,6 +704,7 @@ void* oom_killer_memalign(struct _malloc_zone_t* zone,
void* oom_killer_malloc_purgeable(struct _malloc_zone_t* zone,
size_t size) {
+ ScopedClearErrno clear_errno;
void* result = g_old_malloc_purgeable(zone, size);
if (!result && size)
debug::BreakDebugger();
@@ -672,6 +714,7 @@ void* oom_killer_malloc_purgeable(struct _malloc_zone_t* zone,
void* oom_killer_calloc_purgeable(struct _malloc_zone_t* zone,
size_t num_items,
size_t size) {
+ ScopedClearErrno clear_errno;
void* result = g_old_calloc_purgeable(zone, num_items, size);
if (!result && num_items && size)
debug::BreakDebugger();
@@ -680,15 +723,23 @@ void* oom_killer_calloc_purgeable(struct _malloc_zone_t* zone,
void* oom_killer_valloc_purgeable(struct _malloc_zone_t* zone,
size_t size) {
+ ScopedClearErrno clear_errno;
void* result = g_old_valloc_purgeable(zone, size);
if (!result && size)
debug::BreakDebugger();
return result;
}
+void oom_killer_free_purgeable(struct _malloc_zone_t* zone,
+ void* ptr) {
+ ScopedClearErrno clear_errno;
+ g_old_free_purgeable(zone, ptr);
+}
+
void* oom_killer_realloc_purgeable(struct _malloc_zone_t* zone,
void* ptr,
size_t size) {
+ ScopedClearErrno clear_errno;
void* result = g_old_realloc_purgeable(zone, ptr, size);
if (!result && size)
debug::BreakDebugger();
@@ -698,6 +749,7 @@ void* oom_killer_realloc_purgeable(struct _malloc_zone_t* zone,
void* oom_killer_memalign_purgeable(struct _malloc_zone_t* zone,
size_t alignment,
size_t size) {
+ ScopedClearErrno clear_errno;
void* result = g_old_memalign_purgeable(zone, alignment, size);
// Only die if posix_memalign would have returned ENOMEM, since there are
// other reasons why NULL might be returned (see
@@ -864,13 +916,16 @@ void EnableTerminationOnOutOfMemory() {
g_old_malloc = default_zone->malloc;
g_old_calloc = default_zone->calloc;
g_old_valloc = default_zone->valloc;
+ g_old_free = default_zone->free;
g_old_realloc = default_zone->realloc;
- CHECK(g_old_malloc && g_old_calloc && g_old_valloc && g_old_realloc)
+ CHECK(g_old_malloc && g_old_calloc && g_old_valloc && g_old_free &&
+ g_old_realloc)
<< "Failed to get system allocation functions.";
default_zone->malloc = oom_killer_malloc;
default_zone->calloc = oom_killer_calloc;
default_zone->valloc = oom_killer_valloc;
+ default_zone->free = oom_killer_free;
default_zone->realloc = oom_killer_realloc;
if (default_zone->version >= 5) {
@@ -885,14 +940,17 @@ void EnableTerminationOnOutOfMemory() {
g_old_malloc_purgeable = purgeable_zone->malloc;
g_old_calloc_purgeable = purgeable_zone->calloc;
g_old_valloc_purgeable = purgeable_zone->valloc;
+ g_old_free_purgeable = purgeable_zone->free;
g_old_realloc_purgeable = purgeable_zone->realloc;
CHECK(g_old_malloc_purgeable && g_old_calloc_purgeable &&
- g_old_valloc_purgeable && g_old_realloc_purgeable)
+ g_old_valloc_purgeable && g_old_free_purgeable &&
+ g_old_realloc_purgeable)
<< "Failed to get system allocation functions.";
purgeable_zone->malloc = oom_killer_malloc_purgeable;
purgeable_zone->calloc = oom_killer_calloc_purgeable;
purgeable_zone->valloc = oom_killer_valloc_purgeable;
+ purgeable_zone->free = oom_killer_free_purgeable;
purgeable_zone->realloc = oom_killer_realloc_purgeable;
if (purgeable_zone->version >= 5) {
diff --git a/base/process_util_unittest.cc b/base/process_util_unittest.cc
index 8d26375f..0b6c135 100644
--- a/base/process_util_unittest.cc
+++ b/base/process_util_unittest.cc
@@ -7,6 +7,7 @@
#include <limits>
#include "base/command_line.h"
+#include "base/debug/alias.h"
#include "base/eintr_wrapper.h"
#include "base/file_path.h"
#include "base/logging.h"
@@ -22,12 +23,12 @@
#include "testing/multiprocess_func_list.h"
#if defined(OS_LINUX)
-#include <errno.h>
#include <malloc.h>
#include <glib.h>
#include <sched.h>
#endif
#if defined(OS_POSIX)
+#include <errno.h>
#include <dlfcn.h>
#include <fcntl.h>
#include <signal.h>
@@ -39,6 +40,7 @@
#include <windows.h>
#endif
#if defined(OS_MACOSX)
+#include <mach/vm_param.h>
#include <malloc/malloc.h>
#include "base/process_util_unittest_mac.h"
#endif
@@ -448,11 +450,33 @@ TEST_F(ProcessUtilTest, LaunchAsUser) {
#if defined(OS_MACOSX)
-TEST_F(ProcessUtilTest, MacTerminateOnHeapCorruption) {
- // Note that base::EnableTerminationOnHeapCorruption() is called as part of
- // test suite setup and does not need to be done again, else mach_override
- // will fail.
+// For the following Mac tests:
+// Note that base::EnableTerminationOnHeapCorruption() is called as part of
+// test suite setup and does not need to be done again, else mach_override
+// will fail.
+
+TEST_F(ProcessUtilTest, MacMallocFailureDoesNotTerminate) {
+ // Install the OOM killer.
+ base::EnableTerminationOnOutOfMemory();
+
+ // Test that ENOMEM doesn't crash via CrMallocErrorBreak two ways: the exit
+ // code and lack of the error string. The number of bytes is one less than
+ // MALLOC_ABSOLUTE_MAX_SIZE, more than which the system early-returns NULL and
+ // does not call through malloc_error_break(). See the comment at
+ // EnableTerminationOnOutOfMemory() for more information.
+ void* buf = NULL;
+ ASSERT_EXIT(
+ buf = malloc(std::numeric_limits<size_t>::max() - (2 * PAGE_SIZE) - 1),
+ testing::KilledBySignal(SIGTRAP),
+ "\\*\\*\\* error: can't allocate region.*"
+ "(Terminating process due to a potential for future heap "
+ "corruption){0}");
+
+ base::debug::Alias(buf);
+}
+TEST_F(ProcessUtilTest, MacTerminateOnHeapCorruption) {
+ // Assert that freeing an unallocated pointer will crash the process.
char buf[3];
#ifndef ADDRESS_SANITIZER
ASSERT_DEATH(free(buf), "being freed.*"