Skip to content

Commit 89b7f84

Browse files
hctimc01db33f
authored andcommitted
[ASan] [HWASan] Add __sanitizer_ignore_free_hook() (llvm#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 <[email protected]>
1 parent e2714ab commit 89b7f84

File tree

9 files changed

+259
-9
lines changed

9 files changed

+259
-9
lines changed

compiler-rt/include/sanitizer/allocator_interface.h

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,20 @@ size_t SANITIZER_CDECL __sanitizer_get_free_bytes(void);
6262
size_t SANITIZER_CDECL __sanitizer_get_unmapped_bytes(void);
6363

6464
/* Malloc hooks that may be optionally provided by user.
65-
__sanitizer_malloc_hook(ptr, size) is called immediately after
66-
allocation of "size" bytes, which returned "ptr".
67-
__sanitizer_free_hook(ptr) is called immediately before
68-
deallocation of "ptr". */
65+
- __sanitizer_malloc_hook(ptr, size) is called immediately after allocation
66+
of "size" bytes, which returned "ptr".
67+
- __sanitizer_free_hook(ptr) is called immediately before deallocation of
68+
"ptr".
69+
- __sanitizer_ignore_free_hook(ptr) is called immediately before deallocation
70+
of "ptr", and if it returns a non-zero value, the deallocation of "ptr"
71+
will not take place. This allows software to make free a no-op until it
72+
calls free() again in the same pointer at a later time. Hint: read this as
73+
"ignore the free" rather than "ignore the hook".
74+
*/
6975
void SANITIZER_CDECL __sanitizer_malloc_hook(const volatile void *ptr,
7076
size_t size);
7177
void SANITIZER_CDECL __sanitizer_free_hook(const volatile void *ptr);
78+
int SANITIZER_CDECL __sanitizer_ignore_free_hook(const volatile void *ptr);
7279

7380
/* Installs a pair of hooks for malloc/free.
7481
Several (currently, 5) hook pairs may be installed, they are executed

compiler-rt/lib/asan/asan_allocator.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -717,7 +717,15 @@ struct Allocator {
717717
return;
718718
}
719719

720-
RunFreeHooks(ptr);
720+
if (RunFreeHooks(ptr)) {
721+
// Someone used __sanitizer_ignore_free_hook() and decided that they
722+
// didn't want the memory to __sanitizer_ignore_free_hook freed right now.
723+
// When they call free() on this pointer again at a later time, we should
724+
// ignore the alloc-type mismatch and allow them to deallocate the pointer
725+
// through free(), rather than the initial alloc type.
726+
m->alloc_type = FROM_MALLOC;
727+
return;
728+
}
721729

722730
// Must mark the chunk as quarantined before any changes to its metadata.
723731
// Do not quarantine given chunk if we failed to set CHUNK_QUARANTINE flag.

compiler-rt/lib/hwasan/hwasan_allocator.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,9 @@ static void HwasanDeallocate(StackTrace *stack, void *tagged_ptr) {
289289
CHECK(tagged_ptr);
290290
void *untagged_ptr = UntagPtr(tagged_ptr);
291291

292+
if (RunFreeHooks(tagged_ptr))
293+
return;
294+
292295
if (CheckInvalidFree(stack, untagged_ptr, tagged_ptr))
293296
return;
294297

@@ -302,8 +305,6 @@ static void HwasanDeallocate(StackTrace *stack, void *tagged_ptr) {
302305
return;
303306
}
304307

305-
RunFreeHooks(tagged_ptr);
306-
307308
uptr orig_size = meta->GetRequestedSize();
308309
u32 free_context_id = StackDepotPut(*stack);
309310
u32 alloc_context_id = meta->GetAllocStackId();

compiler-rt/lib/sanitizer_common/sanitizer_allocator_interface.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE
4040
void __sanitizer_malloc_hook(void *ptr, uptr size);
4141
SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE
4242
void __sanitizer_free_hook(void *ptr);
43+
SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE int
44+
__sanitizer_ignore_free_hook(void *ptr);
4345

4446
SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE void
4547
__sanitizer_purge_allocator();

compiler-rt/lib/sanitizer_common/sanitizer_common.cpp

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,14 +347,22 @@ void RunMallocHooks(void *ptr, uptr size) {
347347
}
348348
}
349349

350-
void RunFreeHooks(void *ptr) {
350+
// Returns '1' if the call to free() should be ignored (based on
351+
// __sanitizer_ignore_free_hook), or '0' otherwise.
352+
int RunFreeHooks(void *ptr) {
353+
if (__sanitizer_ignore_free_hook(ptr)) {
354+
return 1;
355+
}
356+
351357
__sanitizer_free_hook(ptr);
352358
for (int i = 0; i < kMaxMallocFreeHooks; i++) {
353359
auto hook = MFHooks[i].free_hook;
354360
if (!hook)
355361
break;
356362
hook(ptr);
357363
}
364+
365+
return 0;
358366
}
359367

360368
static int InstallMallocFreeHooks(void (*malloc_hook)(const void *, uptr),
@@ -419,4 +427,9 @@ SANITIZER_INTERFACE_WEAK_DEF(void, __sanitizer_free_hook, void *ptr) {
419427
(void)ptr;
420428
}
421429

430+
SANITIZER_INTERFACE_WEAK_DEF(int, __sanitizer_ignore_free_hook, void *ptr) {
431+
(void)ptr;
432+
return 0;
433+
}
434+
422435
} // extern "C"

compiler-rt/lib/sanitizer_common/sanitizer_common.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ bool DontDumpShadowMemory(uptr addr, uptr length);
177177
// Check if the built VMA size matches the runtime one.
178178
void CheckVMASize();
179179
void RunMallocHooks(void *ptr, uptr size);
180-
void RunFreeHooks(void *ptr);
180+
int RunFreeHooks(void *ptr);
181181

182182
class ReservedAddressRange {
183183
public:

compiler-rt/lib/sanitizer_common/sanitizer_common_interface.inc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ INTERFACE_FUNCTION(__sanitizer_purge_allocator)
4646
INTERFACE_FUNCTION(__sanitizer_print_memory_profile)
4747
INTERFACE_WEAK_FUNCTION(__sanitizer_free_hook)
4848
INTERFACE_WEAK_FUNCTION(__sanitizer_malloc_hook)
49+
INTERFACE_WEAK_FUNCTION(__sanitizer_ignore_free_hook)
4950
// Memintrinsic functions.
5051
INTERFACE_FUNCTION(__sanitizer_internal_memcpy)
5152
INTERFACE_FUNCTION(__sanitizer_internal_memmove)
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// RUN: %clangxx_asan -O2 %s -o %t -DTEST=basic_hook_works && not %run %t \
2+
// RUN: |& FileCheck %s -check-prefix=CHECK-BASIC
3+
// RUN: %clangxx_asan -O2 %s -o %t -DTEST=ignore && %run %t \
4+
// RUN: |& FileCheck %s -check-prefix=CHECK-IGNORE
5+
// RUN: %clangxx_asan -O2 %s -o %t -DTEST=ignore_twice && not %run %t \
6+
// RUN: |& FileCheck %s -check-prefix=CHECK-IGNORE-2
7+
// RUN: %clangxx_asan -O2 %s -o %t -DTEST=mismatch && not %run %t \
8+
// RUN: |& FileCheck %s -check-prefix=CHECK-MISMATCH
9+
// RUN: %clangxx_asan -O2 %s -o %t -DTEST=ignore_mismatch && %run %t \
10+
// RUN: |& FileCheck %s -check-prefix=CHECK-IGNORE-MISMATCH
11+
// RUN: %clangxx_asan -O2 %s -o %t -DTEST=double_delete && not %run %t \
12+
// RUN: |& FileCheck %s -check-prefix=CHECK-DOUBLE-DELETE
13+
14+
#include <stdio.h>
15+
#include <stdlib.h>
16+
17+
static char *volatile glob_ptr;
18+
bool ignore_free = false;
19+
20+
#if (__APPLE__)
21+
// Required for dyld macOS 12.0+
22+
# define WEAK_ON_APPLE __attribute__((weak))
23+
#else // !(__APPLE__)
24+
# define WEAK_ON_APPLE
25+
#endif // (__APPLE__)
26+
27+
extern "C" {
28+
WEAK_ON_APPLE void __sanitizer_free_hook(const volatile void *ptr) {
29+
if (ptr == glob_ptr)
30+
fprintf(stderr, "Free Hook\n");
31+
}
32+
33+
WEAK_ON_APPLE int __sanitizer_ignore_free_hook(const volatile void *ptr) {
34+
if (ptr != glob_ptr)
35+
return 0;
36+
fprintf(stderr, ignore_free ? "Free Ignored\n" : "Free Respected\n");
37+
return ignore_free;
38+
}
39+
} // extern "C"
40+
41+
void allocate() { glob_ptr = reinterpret_cast<char *volatile>(malloc(100)); }
42+
void deallocate() { free(reinterpret_cast<void *>(glob_ptr)); }
43+
44+
void basic_hook_works() {
45+
allocate();
46+
deallocate(); // CHECK-BASIC-NOT: Free Ignored
47+
// CHECK-BASIC: Free Respected
48+
// CHECK-BASIC: Free Hook
49+
*glob_ptr = 0; // CHECK-BASIC: AddressSanitizer: heap-use-after-free
50+
}
51+
52+
void ignore() {
53+
allocate();
54+
ignore_free = true;
55+
deallocate();
56+
// CHECK-IGNORE: Free Ignored
57+
// CHECK-IGNORE-NOT: Free Respected
58+
// CHECK-IGNORE-NOT: Free Hook
59+
// CHECK-IGNORE-NOT: AddressSanitizer
60+
*glob_ptr = 0;
61+
}
62+
63+
void ignore_twice() {
64+
allocate();
65+
ignore_free = true;
66+
deallocate(); // CHECK-IGNORE-2: Free Ignored
67+
*glob_ptr = 0;
68+
ignore_free = false;
69+
deallocate(); // CHECK-IGNORE-2-NOT: Free Ignored
70+
// CHECK-IGNORE-2: Free Respected
71+
// CHECK-IGNORE-2: Free Hook
72+
*glob_ptr = 0; // CHECK-IGNORE-2: AddressSanitizer: heap-use-after-free
73+
}
74+
75+
void ignore_a_lot() {
76+
allocate();
77+
ignore_free = true;
78+
for (int i = 0; i < 10000; ++i) {
79+
deallocate(); // CHECK-IGNORE-3: Free Ignored
80+
*glob_ptr = 0;
81+
}
82+
ignore_free = false;
83+
deallocate(); // CHECK-IGNORE-3: Free Respected
84+
// CHECK-IGNORE-3: Free Hook
85+
*glob_ptr = 0; // CHECK-IGNORE-3: AddressSanitizer: heap-use-after-free
86+
}
87+
88+
void mismatch() {
89+
glob_ptr = new char;
90+
deallocate(); // CHECK-MISMATCH: AddressSanitizer: alloc-dealloc-mismatch
91+
}
92+
93+
void ignore_mismatch() {
94+
glob_ptr = new char;
95+
ignore_free = true;
96+
// Mismatch isn't detected when the free() is ignored.
97+
deallocate();
98+
deallocate();
99+
ignore_free = false;
100+
// And also isn't detected when the memory is free()-d for real.
101+
deallocate(); // CHECK-IGNORE-MISMATCH-NOT: AddressSanitizer: alloc-dealloc-mismatch
102+
}
103+
104+
void double_delete() {
105+
allocate();
106+
ignore_free = true;
107+
deallocate(); // CHECK-DOUBLE-DELETE: Free Ignored
108+
deallocate(); // CHECK-DOUBLE-DELETE: Free Ignored
109+
ignore_free = false;
110+
deallocate(); // CHECK-DOUBLE-DELETE: Free Respected
111+
// CHECK-DOUBLE-DELETE: Free Hook
112+
deallocate(); // CHECK-DOUBLE-DELETE: AddressSanitizer: attempting double-free
113+
}
114+
115+
int main() {
116+
TEST();
117+
return 0;
118+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// RUN: %clangxx_hwasan -O2 %s -o %t -DTEST=basic_hook_works && not %run %t \
2+
// RUN: |& FileCheck %s -check-prefix=CHECK-BASIC
3+
// RUN: %clangxx_hwasan -O2 %s -o %t -DTEST=ignore && %run %t \
4+
// RUN: |& FileCheck %s -check-prefix=CHECK-IGNORE
5+
// RUN: %clangxx_hwasan -O2 %s -o %t -DTEST=ignore_twice && not %run %t \
6+
// RUN: |& FileCheck %s -check-prefix=CHECK-IGNORE-2
7+
// RUN: %clangxx_hwasan -O2 %s -o %t -DTEST=double_delete && not %run %t \
8+
// RUN: |& FileCheck %s -check-prefix=CHECK-DOUBLE-DELETE
9+
10+
#include <sanitizer/hwasan_interface.h>
11+
#include <stdio.h>
12+
#include <stdlib.h>
13+
14+
static char *volatile glob_ptr;
15+
bool ignore_free = false;
16+
17+
#if (__APPLE__)
18+
// Required for dyld macOS 12.0+
19+
# define WEAK_ON_APPLE __attribute__((weak))
20+
#else // !(__APPLE__)
21+
# define WEAK_ON_APPLE
22+
#endif // (__APPLE__)
23+
24+
extern "C" {
25+
WEAK_ON_APPLE void __sanitizer_free_hook(const volatile void *ptr) {
26+
if (ptr == glob_ptr)
27+
fprintf(stderr, "Free Hook\n");
28+
}
29+
30+
WEAK_ON_APPLE int __sanitizer_ignore_free_hook(const volatile void *ptr) {
31+
if (ptr != glob_ptr)
32+
return 0;
33+
fprintf(stderr, ignore_free ? "Free Ignored\n" : "Free Respected\n");
34+
return ignore_free;
35+
}
36+
} // extern "C"
37+
38+
void allocate() { glob_ptr = reinterpret_cast<char *volatile>(malloc(100)); }
39+
void deallocate() { free(reinterpret_cast<void *>(glob_ptr)); }
40+
41+
void basic_hook_works() {
42+
allocate();
43+
deallocate(); // CHECK-BASIC-NOT: Free Ignored
44+
// CHECK-BASIC: Free Respected
45+
// CHECK-BASIC: Free Hook
46+
*glob_ptr = 0; // CHECK-BASIC: HWAddressSanitizer: tag-mismatch
47+
}
48+
49+
void ignore() {
50+
allocate();
51+
ignore_free = true;
52+
deallocate();
53+
// CHECK-IGNORE: Free Ignored
54+
// CHECK-IGNORE-NOT: Free Respected
55+
// CHECK-IGNORE-NOT: Free Hook
56+
// CHECK-IGNORE-NOT: HWAddressSanitizer
57+
*glob_ptr = 0;
58+
}
59+
60+
void ignore_twice() {
61+
allocate();
62+
ignore_free = true;
63+
deallocate(); // CHECK-IGNORE-2: Free Ignored
64+
*glob_ptr = 0;
65+
ignore_free = false;
66+
deallocate(); // CHECK-IGNORE-2-NOT: Free Ignored
67+
// CHECK-IGNORE-2: Free Respected
68+
// CHECK-IGNORE-2: Free Hook
69+
*glob_ptr = 0; // CHECK-IGNORE-2: HWAddressSanitizer: tag-mismatch
70+
}
71+
72+
void ignore_a_lot() {
73+
allocate();
74+
ignore_free = true;
75+
for (int i = 0; i < 10000; ++i) {
76+
deallocate(); // CHECK-IGNORE-3: Free Ignored
77+
*glob_ptr = 0;
78+
}
79+
ignore_free = false;
80+
deallocate(); // CHECK-IGNORE-3: Free Respected
81+
// CHECK-IGNORE-3: Free Hook
82+
*glob_ptr = 0; // CHECK-IGNORE-3: HWAddressSanitizer: tag-mismatch
83+
}
84+
85+
void double_delete() {
86+
allocate();
87+
ignore_free = true;
88+
deallocate(); // CHECK-DOUBLE-DELETE: Free Ignored
89+
deallocate(); // CHECK-DOUBLE-DELETE: Free Ignored
90+
ignore_free = false;
91+
deallocate(); // CHECK-DOUBLE-DELETE: Free Respected
92+
// CHECK-DOUBLE-DELETE: Free Hook
93+
deallocate(); // CHECK-DOUBLE-DELETE: HWAddressSanitizer: invalid-free
94+
}
95+
96+
int main() {
97+
__hwasan_enable_allocator_tagging();
98+
TEST();
99+
return 0;
100+
}

0 commit comments

Comments
 (0)