Double free for large allocation not detected, aborts due to invalid write
- Dominant language
- C
- Stars
- 12.5k
- Forks
- 1.1k
- PR merge metrics
- No merged PRs in 30d
Description
I originally noticed this issue in a program with (unintentional) behavior equivalent to the following:
```c
void *p, *q;
p = malloc(18);
p = realloc(p, 2147483658UL);
q = realloc(p, 0);
if (NULL == q)
p = realloc(p, 0);
```
(I’m aware that `realloc(p, 0)` for non-`NULL` `p` is expected to become undefined behavior in C23.)
The allocator_frees_and_returns_null_on_realloc_zero flag being enabled by default means this example causes a double free; but ASan does not detect it, instead aborting due to an invalid write:
```
AddressSanitizer:DEADLYSIGNAL
=================================================================
==29282==ERROR: AddressSanitizer: SEGV on unknown address 0xa4cff7f0 (pc 0xb79cfc7b bp 0xa4cff7f0 sp 0xbfd5af20 T0)
==29282==The signal is caused by a WRITE memory access.
#0 0xb79cfc7b in bool __sanitizer::atomic_compare_exchange_strong<__sanitizer::atomic_uint8_t>(__sanitizer::atomic_uint8_t volatile*, __sanitizer::atomic_uint8_t::Type*, __sanitizer::atomic_uint8_t::Type, __sanitizer::memory_order) /usr/src/debug/gcc/libsanitizer/sanitizer_common/sanitizer_atomic_clang.h:80
#1 0xb79cfc7b in __asan::Allocator::AtomicallySetQuarantineFlagIfAllocated(__asan::AsanChunk*, void*, __sanitizer::BufferedStackTrace*) /usr/src/debug/gcc/libsanitizer/asan/asan_allocator.cpp:621
#2 0xb79cfc7b in __asan::Allocator::Deallocate(void*, unsigned long, unsigned long, __sanitizer::BufferedStackTrace*, __asan::AllocType) /usr/src/debug/gcc/libsanitizer/asan/asan_allocator.cpp:697
#3 0xb79cfc7b in __asan::asan_realloc(void*, unsigned long, __sanitizer::BufferedStackTrace*) /usr/src/debug/gcc/libsanitizer/asan/asan_allocator.cpp:1003
#4 0xb7a69e24 in __interceptor_realloc /usr/src/debug/gcc/libsanitizer/asan/asan_malloc_linux.cpp:165
#5 0x4b9295 in main (/home/user/a.out+0x1295)
#6 0xb77dc258 in __libc_start_call_main (/usr/lib/libc.so.6+0x25258)
#7 0xb77dc333 in __libc_start_main_impl (/usr/lib/libc.so.6+0x25333)
#8 0x4b90ea in _start (/home/user/a.out+0x10ea)
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV /usr/src/debug/gcc/libsanitizer/sanitizer_common/sanitizer_atomic_clang.h:80 in bool __sanitizer::atomic_compare_exchange_strong<__sanitizer::atomic_uint8_t>(__sanitizer::atomic_uint8_t volatile*, __sanitizer::atomic_uint8_t::Type*, __sanitizer::atomic_uint8_t::Type, __sanitizer::memory_order)
==29282==ABORTING
```
The issue more generally seems to be that when doing `void *p = malloc(s); free(p); free(p);` for sufficiently large `s` (I’m not aware what determines the cutoff), ASan does not detect the double free, and an invalid write in ASan occurs instead. I observed this with both LLVM Clang 13.0.1 (still present in 17.0.1) on macOS and GCC 11.2.0 on Linux.
Contributor guide
Research direction
Reproduce the large-allocation double-free with the C snippet, then trace the realloc and deallocation paths named in the report, including asan_malloc_linux.cpp and asan_allocator.cpp. Compare behavior for small and large allocations and add regression coverage showing that the second free is diagnosed as a double free rather than causing an invalid write.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- c
- Domain
- testing-qa, tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100