Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ASan] [HWASan] Add __sanitizer_ignore_free_hook() #96749

Merged
merged 3 commits into from
Jul 12, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions compiler-rt/include/sanitizer/allocator_interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 9 additions & 1 deletion compiler-rt/lib/asan/asan_allocator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
5 changes: 3 additions & 2 deletions compiler-rt/lib/hwasan/hwasan_allocator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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))
hctim marked this conversation as resolved.
Show resolved Hide resolved
return;

if (CheckInvalidFree(stack, untagged_ptr, tagged_ptr))
return;

Expand All @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
15 changes: 14 additions & 1 deletion compiler-rt/lib/sanitizer_common/sanitizer_common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -347,14 +347,22 @@ 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;
if (!hook)
break;
hook(ptr);
}

return 0;
}

static int InstallMallocFreeHooks(void (*malloc_hook)(const void *, uptr),
Expand Down Expand Up @@ -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"
2 changes: 1 addition & 1 deletion compiler-rt/lib/sanitizer_common/sanitizer_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
119 changes: 119 additions & 0 deletions compiler-rt/test/asan/TestCases/Posix/ignore_free_hook.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Check that free hook doesn't conflict with Realloc.
// 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 \
hctim marked this conversation as resolved.
Show resolved Hide resolved
// 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;
}
101 changes: 101 additions & 0 deletions compiler-rt/test/hwasan/TestCases/Posix/ignore_free_hook.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Check that free hook doesn't conflict with Realloc.
// 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;
}
Loading