[ASan] [HWASan] Add __sanitizer_ignore_free_hook() (#96749)
This change adds a new weak API function which makes the sanitizer ignore the call to free(), and implements the functionality in ASan and HWAsan. The runtime that implements this hook can then call free() at a later point again on the same pointer (and making sure the hook returns zero so that the memory will actually be freed) when it's actually ready for the memory to be cleaned up. This is needed in order to implement an sanitizer-compatible version of Chrome's BackupRefPtr algorithm, since process-wide double-shimming of malloc/free does not work on some platforms. Requested and designed by @c01db33f (Mark) from Project Zero. --------- Co-authored-by: Mark Brand <markbrand@google.com>
This commit is contained in:
@@ -62,13 +62,20 @@ size_t SANITIZER_CDECL __sanitizer_get_free_bytes(void);
|
||||
size_t SANITIZER_CDECL __sanitizer_get_unmapped_bytes(void);
|
||||
|
||||
/* Malloc hooks that may be optionally provided by user.
|
||||
__sanitizer_malloc_hook(ptr, size) is called immediately after
|
||||
allocation of "size" bytes, which returned "ptr".
|
||||
__sanitizer_free_hook(ptr) is called immediately before
|
||||
deallocation of "ptr". */
|
||||
- __sanitizer_malloc_hook(ptr, size) is called immediately after allocation
|
||||
of "size" bytes, which returned "ptr".
|
||||
- __sanitizer_free_hook(ptr) is called immediately before deallocation of
|
||||
"ptr".
|
||||
- __sanitizer_ignore_free_hook(ptr) is called immediately before deallocation
|
||||
of "ptr", and if it returns a non-zero value, the deallocation of "ptr"
|
||||
will not take place. This allows software to make free a no-op until it
|
||||
calls free() again in the same pointer at a later time. Hint: read this as
|
||||
"ignore the free" rather than "ignore the hook".
|
||||
*/
|
||||
void SANITIZER_CDECL __sanitizer_malloc_hook(const volatile void *ptr,
|
||||
size_t size);
|
||||
void SANITIZER_CDECL __sanitizer_free_hook(const volatile void *ptr);
|
||||
int SANITIZER_CDECL __sanitizer_ignore_free_hook(const volatile void *ptr);
|
||||
|
||||
/* Installs a pair of hooks for malloc/free.
|
||||
Several (currently, 5) hook pairs may be installed, they are executed
|
||||
|
||||
@@ -717,7 +717,15 @@ struct Allocator {
|
||||
return;
|
||||
}
|
||||
|
||||
RunFreeHooks(ptr);
|
||||
if (RunFreeHooks(ptr)) {
|
||||
// Someone used __sanitizer_ignore_free_hook() and decided that they
|
||||
// didn't want the memory to __sanitizer_ignore_free_hook freed right now.
|
||||
// When they call free() on this pointer again at a later time, we should
|
||||
// ignore the alloc-type mismatch and allow them to deallocate the pointer
|
||||
// through free(), rather than the initial alloc type.
|
||||
m->alloc_type = FROM_MALLOC;
|
||||
return;
|
||||
}
|
||||
|
||||
// Must mark the chunk as quarantined before any changes to its metadata.
|
||||
// Do not quarantine given chunk if we failed to set CHUNK_QUARANTINE flag.
|
||||
|
||||
@@ -289,6 +289,9 @@ static void HwasanDeallocate(StackTrace *stack, void *tagged_ptr) {
|
||||
CHECK(tagged_ptr);
|
||||
void *untagged_ptr = UntagPtr(tagged_ptr);
|
||||
|
||||
if (RunFreeHooks(tagged_ptr))
|
||||
return;
|
||||
|
||||
if (CheckInvalidFree(stack, untagged_ptr, tagged_ptr))
|
||||
return;
|
||||
|
||||
@@ -302,8 +305,6 @@ static void HwasanDeallocate(StackTrace *stack, void *tagged_ptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
RunFreeHooks(tagged_ptr);
|
||||
|
||||
uptr orig_size = meta->GetRequestedSize();
|
||||
u32 free_context_id = StackDepotPut(*stack);
|
||||
u32 alloc_context_id = meta->GetAllocStackId();
|
||||
|
||||
@@ -40,6 +40,8 @@ SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE
|
||||
void __sanitizer_malloc_hook(void *ptr, uptr size);
|
||||
SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE
|
||||
void __sanitizer_free_hook(void *ptr);
|
||||
SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE int
|
||||
__sanitizer_ignore_free_hook(void *ptr);
|
||||
|
||||
SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE void
|
||||
__sanitizer_purge_allocator();
|
||||
|
||||
@@ -347,7 +347,13 @@ void RunMallocHooks(void *ptr, uptr size) {
|
||||
}
|
||||
}
|
||||
|
||||
void RunFreeHooks(void *ptr) {
|
||||
// Returns '1' if the call to free() should be ignored (based on
|
||||
// __sanitizer_ignore_free_hook), or '0' otherwise.
|
||||
int RunFreeHooks(void *ptr) {
|
||||
if (__sanitizer_ignore_free_hook(ptr)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
__sanitizer_free_hook(ptr);
|
||||
for (int i = 0; i < kMaxMallocFreeHooks; i++) {
|
||||
auto hook = MFHooks[i].free_hook;
|
||||
@@ -355,6 +361,8 @@ void RunFreeHooks(void *ptr) {
|
||||
break;
|
||||
hook(ptr);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int InstallMallocFreeHooks(void (*malloc_hook)(const void *, uptr),
|
||||
@@ -419,4 +427,9 @@ SANITIZER_INTERFACE_WEAK_DEF(void, __sanitizer_free_hook, void *ptr) {
|
||||
(void)ptr;
|
||||
}
|
||||
|
||||
SANITIZER_INTERFACE_WEAK_DEF(int, __sanitizer_ignore_free_hook, void *ptr) {
|
||||
(void)ptr;
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
@@ -177,7 +177,7 @@ bool DontDumpShadowMemory(uptr addr, uptr length);
|
||||
// Check if the built VMA size matches the runtime one.
|
||||
void CheckVMASize();
|
||||
void RunMallocHooks(void *ptr, uptr size);
|
||||
void RunFreeHooks(void *ptr);
|
||||
int RunFreeHooks(void *ptr);
|
||||
|
||||
class ReservedAddressRange {
|
||||
public:
|
||||
|
||||
@@ -46,6 +46,7 @@ INTERFACE_FUNCTION(__sanitizer_purge_allocator)
|
||||
INTERFACE_FUNCTION(__sanitizer_print_memory_profile)
|
||||
INTERFACE_WEAK_FUNCTION(__sanitizer_free_hook)
|
||||
INTERFACE_WEAK_FUNCTION(__sanitizer_malloc_hook)
|
||||
INTERFACE_WEAK_FUNCTION(__sanitizer_ignore_free_hook)
|
||||
// Memintrinsic functions.
|
||||
INTERFACE_FUNCTION(__sanitizer_internal_memcpy)
|
||||
INTERFACE_FUNCTION(__sanitizer_internal_memmove)
|
||||
|
||||
118
compiler-rt/test/asan/TestCases/Posix/ignore_free_hook.cpp
Normal file
118
compiler-rt/test/asan/TestCases/Posix/ignore_free_hook.cpp
Normal file
@@ -0,0 +1,118 @@
|
||||
// RUN: %clangxx_asan -O2 %s -o %t -DTEST=basic_hook_works && not %run %t \
|
||||
// RUN: |& FileCheck %s -check-prefix=CHECK-BASIC
|
||||
// RUN: %clangxx_asan -O2 %s -o %t -DTEST=ignore && %run %t \
|
||||
// RUN: |& FileCheck %s -check-prefix=CHECK-IGNORE
|
||||
// RUN: %clangxx_asan -O2 %s -o %t -DTEST=ignore_twice && not %run %t \
|
||||
// RUN: |& FileCheck %s -check-prefix=CHECK-IGNORE-2
|
||||
// RUN: %clangxx_asan -O2 %s -o %t -DTEST=mismatch && not %run %t \
|
||||
// RUN: |& FileCheck %s -check-prefix=CHECK-MISMATCH
|
||||
// RUN: %clangxx_asan -O2 %s -o %t -DTEST=ignore_mismatch && %run %t \
|
||||
// RUN: |& FileCheck %s -check-prefix=CHECK-IGNORE-MISMATCH
|
||||
// RUN: %clangxx_asan -O2 %s -o %t -DTEST=double_delete && not %run %t \
|
||||
// RUN: |& FileCheck %s -check-prefix=CHECK-DOUBLE-DELETE
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static char *volatile glob_ptr;
|
||||
bool ignore_free = false;
|
||||
|
||||
#if (__APPLE__)
|
||||
// Required for dyld macOS 12.0+
|
||||
# define WEAK_ON_APPLE __attribute__((weak))
|
||||
#else // !(__APPLE__)
|
||||
# define WEAK_ON_APPLE
|
||||
#endif // (__APPLE__)
|
||||
|
||||
extern "C" {
|
||||
WEAK_ON_APPLE void __sanitizer_free_hook(const volatile void *ptr) {
|
||||
if (ptr == glob_ptr)
|
||||
fprintf(stderr, "Free Hook\n");
|
||||
}
|
||||
|
||||
WEAK_ON_APPLE int __sanitizer_ignore_free_hook(const volatile void *ptr) {
|
||||
if (ptr != glob_ptr)
|
||||
return 0;
|
||||
fprintf(stderr, ignore_free ? "Free Ignored\n" : "Free Respected\n");
|
||||
return ignore_free;
|
||||
}
|
||||
} // extern "C"
|
||||
|
||||
void allocate() { glob_ptr = reinterpret_cast<char *volatile>(malloc(100)); }
|
||||
void deallocate() { free(reinterpret_cast<void *>(glob_ptr)); }
|
||||
|
||||
void basic_hook_works() {
|
||||
allocate();
|
||||
deallocate(); // CHECK-BASIC-NOT: Free Ignored
|
||||
// CHECK-BASIC: Free Respected
|
||||
// CHECK-BASIC: Free Hook
|
||||
*glob_ptr = 0; // CHECK-BASIC: AddressSanitizer: heap-use-after-free
|
||||
}
|
||||
|
||||
void ignore() {
|
||||
allocate();
|
||||
ignore_free = true;
|
||||
deallocate();
|
||||
// CHECK-IGNORE: Free Ignored
|
||||
// CHECK-IGNORE-NOT: Free Respected
|
||||
// CHECK-IGNORE-NOT: Free Hook
|
||||
// CHECK-IGNORE-NOT: AddressSanitizer
|
||||
*glob_ptr = 0;
|
||||
}
|
||||
|
||||
void ignore_twice() {
|
||||
allocate();
|
||||
ignore_free = true;
|
||||
deallocate(); // CHECK-IGNORE-2: Free Ignored
|
||||
*glob_ptr = 0;
|
||||
ignore_free = false;
|
||||
deallocate(); // CHECK-IGNORE-2-NOT: Free Ignored
|
||||
// CHECK-IGNORE-2: Free Respected
|
||||
// CHECK-IGNORE-2: Free Hook
|
||||
*glob_ptr = 0; // CHECK-IGNORE-2: AddressSanitizer: heap-use-after-free
|
||||
}
|
||||
|
||||
void ignore_a_lot() {
|
||||
allocate();
|
||||
ignore_free = true;
|
||||
for (int i = 0; i < 10000; ++i) {
|
||||
deallocate(); // CHECK-IGNORE-3: Free Ignored
|
||||
*glob_ptr = 0;
|
||||
}
|
||||
ignore_free = false;
|
||||
deallocate(); // CHECK-IGNORE-3: Free Respected
|
||||
// CHECK-IGNORE-3: Free Hook
|
||||
*glob_ptr = 0; // CHECK-IGNORE-3: AddressSanitizer: heap-use-after-free
|
||||
}
|
||||
|
||||
void mismatch() {
|
||||
glob_ptr = new char;
|
||||
deallocate(); // CHECK-MISMATCH: AddressSanitizer: alloc-dealloc-mismatch
|
||||
}
|
||||
|
||||
void ignore_mismatch() {
|
||||
glob_ptr = new char;
|
||||
ignore_free = true;
|
||||
// Mismatch isn't detected when the free() is ignored.
|
||||
deallocate();
|
||||
deallocate();
|
||||
ignore_free = false;
|
||||
// And also isn't detected when the memory is free()-d for real.
|
||||
deallocate(); // CHECK-IGNORE-MISMATCH-NOT: AddressSanitizer: alloc-dealloc-mismatch
|
||||
}
|
||||
|
||||
void double_delete() {
|
||||
allocate();
|
||||
ignore_free = true;
|
||||
deallocate(); // CHECK-DOUBLE-DELETE: Free Ignored
|
||||
deallocate(); // CHECK-DOUBLE-DELETE: Free Ignored
|
||||
ignore_free = false;
|
||||
deallocate(); // CHECK-DOUBLE-DELETE: Free Respected
|
||||
// CHECK-DOUBLE-DELETE: Free Hook
|
||||
deallocate(); // CHECK-DOUBLE-DELETE: AddressSanitizer: attempting double-free
|
||||
}
|
||||
|
||||
int main() {
|
||||
TEST();
|
||||
return 0;
|
||||
}
|
||||
100
compiler-rt/test/hwasan/TestCases/Posix/ignore_free_hook.cpp
Normal file
100
compiler-rt/test/hwasan/TestCases/Posix/ignore_free_hook.cpp
Normal file
@@ -0,0 +1,100 @@
|
||||
// RUN: %clangxx_hwasan -O2 %s -o %t -DTEST=basic_hook_works && not %run %t \
|
||||
// RUN: |& FileCheck %s -check-prefix=CHECK-BASIC
|
||||
// RUN: %clangxx_hwasan -O2 %s -o %t -DTEST=ignore && %run %t \
|
||||
// RUN: |& FileCheck %s -check-prefix=CHECK-IGNORE
|
||||
// RUN: %clangxx_hwasan -O2 %s -o %t -DTEST=ignore_twice && not %run %t \
|
||||
// RUN: |& FileCheck %s -check-prefix=CHECK-IGNORE-2
|
||||
// RUN: %clangxx_hwasan -O2 %s -o %t -DTEST=double_delete && not %run %t \
|
||||
// RUN: |& FileCheck %s -check-prefix=CHECK-DOUBLE-DELETE
|
||||
|
||||
#include <sanitizer/hwasan_interface.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static char *volatile glob_ptr;
|
||||
bool ignore_free = false;
|
||||
|
||||
#if (__APPLE__)
|
||||
// Required for dyld macOS 12.0+
|
||||
# define WEAK_ON_APPLE __attribute__((weak))
|
||||
#else // !(__APPLE__)
|
||||
# define WEAK_ON_APPLE
|
||||
#endif // (__APPLE__)
|
||||
|
||||
extern "C" {
|
||||
WEAK_ON_APPLE void __sanitizer_free_hook(const volatile void *ptr) {
|
||||
if (ptr == glob_ptr)
|
||||
fprintf(stderr, "Free Hook\n");
|
||||
}
|
||||
|
||||
WEAK_ON_APPLE int __sanitizer_ignore_free_hook(const volatile void *ptr) {
|
||||
if (ptr != glob_ptr)
|
||||
return 0;
|
||||
fprintf(stderr, ignore_free ? "Free Ignored\n" : "Free Respected\n");
|
||||
return ignore_free;
|
||||
}
|
||||
} // extern "C"
|
||||
|
||||
void allocate() { glob_ptr = reinterpret_cast<char *volatile>(malloc(100)); }
|
||||
void deallocate() { free(reinterpret_cast<void *>(glob_ptr)); }
|
||||
|
||||
void basic_hook_works() {
|
||||
allocate();
|
||||
deallocate(); // CHECK-BASIC-NOT: Free Ignored
|
||||
// CHECK-BASIC: Free Respected
|
||||
// CHECK-BASIC: Free Hook
|
||||
*glob_ptr = 0; // CHECK-BASIC: HWAddressSanitizer: tag-mismatch
|
||||
}
|
||||
|
||||
void ignore() {
|
||||
allocate();
|
||||
ignore_free = true;
|
||||
deallocate();
|
||||
// CHECK-IGNORE: Free Ignored
|
||||
// CHECK-IGNORE-NOT: Free Respected
|
||||
// CHECK-IGNORE-NOT: Free Hook
|
||||
// CHECK-IGNORE-NOT: HWAddressSanitizer
|
||||
*glob_ptr = 0;
|
||||
}
|
||||
|
||||
void ignore_twice() {
|
||||
allocate();
|
||||
ignore_free = true;
|
||||
deallocate(); // CHECK-IGNORE-2: Free Ignored
|
||||
*glob_ptr = 0;
|
||||
ignore_free = false;
|
||||
deallocate(); // CHECK-IGNORE-2-NOT: Free Ignored
|
||||
// CHECK-IGNORE-2: Free Respected
|
||||
// CHECK-IGNORE-2: Free Hook
|
||||
*glob_ptr = 0; // CHECK-IGNORE-2: HWAddressSanitizer: tag-mismatch
|
||||
}
|
||||
|
||||
void ignore_a_lot() {
|
||||
allocate();
|
||||
ignore_free = true;
|
||||
for (int i = 0; i < 10000; ++i) {
|
||||
deallocate(); // CHECK-IGNORE-3: Free Ignored
|
||||
*glob_ptr = 0;
|
||||
}
|
||||
ignore_free = false;
|
||||
deallocate(); // CHECK-IGNORE-3: Free Respected
|
||||
// CHECK-IGNORE-3: Free Hook
|
||||
*glob_ptr = 0; // CHECK-IGNORE-3: HWAddressSanitizer: tag-mismatch
|
||||
}
|
||||
|
||||
void double_delete() {
|
||||
allocate();
|
||||
ignore_free = true;
|
||||
deallocate(); // CHECK-DOUBLE-DELETE: Free Ignored
|
||||
deallocate(); // CHECK-DOUBLE-DELETE: Free Ignored
|
||||
ignore_free = false;
|
||||
deallocate(); // CHECK-DOUBLE-DELETE: Free Respected
|
||||
// CHECK-DOUBLE-DELETE: Free Hook
|
||||
deallocate(); // CHECK-DOUBLE-DELETE: HWAddressSanitizer: invalid-free
|
||||
}
|
||||
|
||||
int main() {
|
||||
__hwasan_enable_allocator_tagging();
|
||||
TEST();
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user