diff options
author | Christopher Ferris <cferris@google.com> | 2016-03-15 22:39:39 -0700 |
---|---|---|
committer | Christopher Ferris <cferris@google.com> | 2016-03-16 17:38:08 -0700 |
commit | d0919623a2ef56107590eca9a9522a250fb8bd4a (patch) | |
tree | b0e66ab45ca64f3fb90e03205ebd814f5c55c4f0 /libc/malloc_debug/malloc_debug.cpp | |
parent | 8d0af0bf8004c65f13b985643004a915d7e382eb (diff) |
Fix race in malloc debug option free_track.
The free track mechanism could fail if, at the same time a free occurs,
another thread is trying to free and verify the same allocation. This
doesn't work if the freed allocation is added to the list and we still
do work on it. The fix is to only add to the free list when we are done
with the allocation.
Also fix a problem where the usable size is computed incorrectly because
two of the arguments where reversed.
In addition, add a check that the allocation being verified has the correct
tag before trying to check the body of the allocation.
Add a test to catch the original failure.
Add a test for the tag being different.
Bug: 27601650
Change-Id: Ie9200677d066255b8e668a48422f23f909f4ddee
Diffstat (limited to 'libc/malloc_debug/malloc_debug.cpp')
-rw-r--r-- | libc/malloc_debug/malloc_debug.cpp | 24 |
1 files changed, 14 insertions, 10 deletions
diff --git a/libc/malloc_debug/malloc_debug.cpp b/libc/malloc_debug/malloc_debug.cpp index 568192d69..1ee76897d 100644 --- a/libc/malloc_debug/malloc_debug.cpp +++ b/libc/malloc_debug/malloc_debug.cpp @@ -112,6 +112,7 @@ static void InitAtfork() { ); }); } + static void LogTagError(const Header* header, const void* pointer, const char* name) { ScopedDisableDebugCalls disable; @@ -145,7 +146,7 @@ static void* InitHeader(Header* header, void* orig_pointer, size_t size) { return nullptr; } header->usable_size -= g_debug->pointer_offset() + - reinterpret_cast<uintptr_t>(orig_pointer) - reinterpret_cast<uintptr_t>(header); + reinterpret_cast<uintptr_t>(header) - reinterpret_cast<uintptr_t>(orig_pointer); if (g_debug->config().options & FRONT_GUARD) { uint8_t* guard = g_debug->GetFrontGuard(header); @@ -326,8 +327,9 @@ void debug_free(void* pointer) { void* free_pointer = pointer; size_t bytes; + Header* header; if (g_debug->need_header()) { - Header* header = g_debug->GetHeader(pointer); + header = g_debug->GetHeader(pointer); if (header->tag != DEBUG_TAG) { LogTagError(header, pointer, "free"); return; @@ -353,13 +355,6 @@ void debug_free(void* pointer) { } g_debug->track->Remove(header, backtrace_found); } - - if (g_debug->config().options & FREE_TRACK) { - g_debug->free_track->Add(*g_debug, header); - - // Do not free this pointer just yet. - free_pointer = nullptr; - } header->tag = DEBUG_FREE_TAG; bytes = header->usable_size; @@ -373,7 +368,16 @@ void debug_free(void* pointer) { memset(pointer, g_debug->config().fill_free_value, bytes); } - g_dispatch->free(free_pointer); + if (g_debug->config().options & FREE_TRACK) { + // Do not add the allocation until we are done modifying the pointer + // itself. This avoids a race if a lot of threads are all doing + // frees at the same time and we wind up trying to really free this + // pointer from another thread, while still trying to free it in + // this function. + g_debug->free_track->Add(*g_debug, header); + } else { + g_dispatch->free(free_pointer); + } } void* debug_memalign(size_t alignment, size_t bytes) { |