Skip to content

Commit 58c2a4e

Browse files
authored
[scudo] Add hooks to mark the range of realloc (#74353)
`realloc` may involve both allocation and deallocation. Given that the reporting the events is not atomic and which may lead the hook user to a false case that the double-use pattern happens. In general, this can be resolved on the hook side. To alleviate the task of handling it, we add two new hooks to mark the range so that the hook user can combine those calls together.
1 parent 4de7d4e commit 58c2a4e

File tree

3 files changed

+74
-4
lines changed

3 files changed

+74
-4
lines changed

compiler-rt/lib/scudo/standalone/include/scudo/interface.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,19 @@ __attribute__((weak)) const char *__scudo_default_options(void);
2020
__attribute__((weak)) void __scudo_allocate_hook(void *ptr, size_t size);
2121
__attribute__((weak)) void __scudo_deallocate_hook(void *ptr);
2222

23+
// `realloc` involves both deallocation and allocation but they are not reported
24+
// atomically. In one specific case which may keep taking a snapshot right in
25+
// the middle of `realloc` reporting the deallocation and allocation, it may
26+
// confuse the user by missing memory from `realloc`. To alleviate that case,
27+
// define the two `realloc` hooks to get the knowledge of the bundled hook
28+
// calls. These hooks are optional and should only be used when a hooks user
29+
// wants to track reallocs more closely.
30+
//
31+
// See more details in the comment of `realloc` in wrapper_c.inc.
32+
__attribute__((weak)) void
33+
__scudo_realloc_allocate_hook(void *old_ptr, void *new_ptr, size_t size);
34+
__attribute__((weak)) void __scudo_realloc_deallocate_hook(void *old_ptr);
35+
2336
void __scudo_print_stats(void);
2437

2538
typedef void (*iterate_callback)(uintptr_t base, size_t size, void *arg);

compiler-rt/lib/scudo/standalone/tests/wrappers_c_test.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,14 @@ struct AllocContext {
6161
struct DeallocContext {
6262
void *Ptr;
6363
};
64+
struct ReallocContext {
65+
void *AllocPtr;
66+
void *DeallocPtr;
67+
size_t Size;
68+
};
6469
static AllocContext AC;
6570
static DeallocContext DC;
71+
static ReallocContext RC;
6672

6773
#if (SCUDO_ENABLE_HOOKS_TESTS == 1)
6874
__attribute__((visibility("default"))) void __scudo_allocate_hook(void *Ptr,
@@ -73,6 +79,28 @@ __attribute__((visibility("default"))) void __scudo_allocate_hook(void *Ptr,
7379
__attribute__((visibility("default"))) void __scudo_deallocate_hook(void *Ptr) {
7480
DC.Ptr = Ptr;
7581
}
82+
__attribute__((visibility("default"))) void
83+
__scudo_realloc_allocate_hook(void *OldPtr, void *NewPtr, size_t Size) {
84+
// Verify that __scudo_realloc_deallocate_hook is called first and set the
85+
// right pointer.
86+
EXPECT_EQ(OldPtr, RC.DeallocPtr);
87+
RC.AllocPtr = NewPtr;
88+
RC.Size = Size;
89+
90+
// Note that this is only used for testing. In general, only one pair of hooks
91+
// will be invoked in `realloc`. if __scudo_realloc_*_hook are not defined,
92+
// it'll call the general hooks only. To make the test easier, we call the
93+
// general one here so that either case (whether __scudo_realloc_*_hook are
94+
// defined) will be verified without separating them into different tests.
95+
__scudo_allocate_hook(NewPtr, Size);
96+
}
97+
__attribute__((visibility("default"))) void
98+
__scudo_realloc_deallocate_hook(void *Ptr) {
99+
RC.DeallocPtr = Ptr;
100+
101+
// See the comment in the __scudo_realloc_allocate_hook above.
102+
__scudo_deallocate_hook(Ptr);
103+
}
76104
#endif // (SCUDO_ENABLE_HOOKS_TESTS == 1)
77105
}
78106

@@ -88,6 +116,7 @@ class ScudoWrappersCTest : public Test {
88116
void *InvalidPtr = reinterpret_cast<void *>(0xdeadbeef);
89117
AC.Ptr = InvalidPtr;
90118
DC.Ptr = InvalidPtr;
119+
RC.AllocPtr = RC.DeallocPtr = InvalidPtr;
91120
}
92121
}
93122
void verifyAllocHookPtr(UNUSED void *Ptr) {
@@ -102,6 +131,13 @@ class ScudoWrappersCTest : public Test {
102131
if (SCUDO_ENABLE_HOOKS_TESTS)
103132
EXPECT_EQ(Ptr, DC.Ptr);
104133
}
134+
void verifyReallocHookPtrs(UNUSED void *OldPtr, void *NewPtr, size_t Size) {
135+
if (SCUDO_ENABLE_HOOKS_TESTS) {
136+
EXPECT_EQ(OldPtr, RC.DeallocPtr);
137+
EXPECT_EQ(NewPtr, RC.AllocPtr);
138+
EXPECT_EQ(Size, RC.Size);
139+
}
140+
}
105141
};
106142
using ScudoWrappersCDeathTest = ScudoWrappersCTest;
107143

@@ -297,6 +333,7 @@ TEST_F(ScudoWrappersCDeathTest, Realloc) {
297333
verifyAllocHookSize(Size * 2U);
298334
verifyDeallocHookPtr(OldP);
299335
}
336+
verifyReallocHookPtrs(OldP, P, Size * 2U);
300337

301338
invalidateHookPtrs();
302339
OldP = P;
@@ -312,6 +349,7 @@ TEST_F(ScudoWrappersCDeathTest, Realloc) {
312349
verifyAllocHookPtr(P);
313350
verifyAllocHookSize(Size / 2U);
314351
}
352+
verifyReallocHookPtrs(OldP, P, Size / 2U);
315353
free(P);
316354

317355
EXPECT_DEATH(P = realloc(P, Size), "");

compiler-rt/lib/scudo/standalone/wrappers_c.inc

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,24 @@ static void reportDeallocation(void *ptr) {
2727
if (__scudo_deallocate_hook)
2828
__scudo_deallocate_hook(ptr);
2929
}
30+
static void reportReallocAllocation(void *old_ptr, void *new_ptr, size_t size) {
31+
DCHECK_NE(new_ptr, nullptr);
32+
33+
if (SCUDO_ENABLE_HOOKS) {
34+
if (__scudo_realloc_allocate_hook)
35+
__scudo_realloc_allocate_hook(old_ptr, new_ptr, size);
36+
else if (__scudo_allocate_hook)
37+
__scudo_allocate_hook(new_ptr, size);
38+
}
39+
}
40+
static void reportReallocDeallocation(void *old_ptr) {
41+
if (SCUDO_ENABLE_HOOKS) {
42+
if (__scudo_realloc_deallocate_hook)
43+
__scudo_realloc_deallocate_hook(old_ptr);
44+
else if (__scudo_deallocate_hook)
45+
__scudo_deallocate_hook(old_ptr);
46+
}
47+
}
3048

3149
extern "C" {
3250

@@ -183,16 +201,17 @@ INTERFACE WEAK void *SCUDO_PREFIX(realloc)(void *ptr, size_t size) {
183201
// new pointer. Before the reporting of both operations has been done, another
184202
// thread may get the old pointer from `malloc`. It may be misinterpreted as
185203
// double-use if it's not handled properly on the hook side.
186-
reportDeallocation(ptr);
204+
reportReallocDeallocation(ptr);
187205
void *NewPtr = SCUDO_ALLOCATOR.reallocate(ptr, size, SCUDO_MALLOC_ALIGNMENT);
188206
if (NewPtr != nullptr) {
189207
// Note that even if NewPtr == ptr, the size has changed. We still need to
190208
// report the new size.
191-
reportAllocation(NewPtr, size);
209+
reportReallocAllocation(/*OldPtr=*/ptr, NewPtr, size);
192210
} else {
193211
// If `realloc` fails, the old pointer is not released. Report the old
194-
// pointer as allocated back.
195-
reportAllocation(ptr, SCUDO_ALLOCATOR.getAllocSize(ptr));
212+
// pointer as allocated again.
213+
reportReallocAllocation(/*OldPtr=*/ptr, /*NewPtr=*/ptr,
214+
SCUDO_ALLOCATOR.getAllocSize(ptr));
196215
}
197216

198217
return scudo::setErrnoOnNull(NewPtr);

0 commit comments

Comments
 (0)