google / google/sanitizers

Double free for large allocation not detected, aborts due to invalid write

Open
#1,502 1 comment 0 reactions 0 assignees View on GitHub
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.