Skip to content

Commit 75867f8

Browse files
authored
[scudo] Fix realloc hooks behavior (#74149)
`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, we always report the old pointer is released and report the new allocation afterward (even it's the same pointer). This also fixes that we didn't report the new size when it doesn't need to allocate a new space.
1 parent a09b881 commit 75867f8

File tree

4 files changed

+39
-12
lines changed

4 files changed

+39
-12
lines changed

compiler-rt/lib/scudo/standalone/combined.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -827,10 +827,15 @@ class Allocator {
827827
// for it, which then forces realloc to copy the usable size of a chunk as
828828
// opposed to its actual size.
829829
uptr getUsableSize(const void *Ptr) {
830-
initThreadMaybe();
831830
if (UNLIKELY(!Ptr))
832831
return 0;
833832

833+
return getAllocSize(Ptr);
834+
}
835+
836+
uptr getAllocSize(const void *Ptr) {
837+
initThreadMaybe();
838+
834839
#ifdef GWP_ASAN_HOOKS
835840
if (UNLIKELY(GuardedAlloc.pointerIsMine(Ptr)))
836841
return GuardedAlloc.getSize(Ptr);
@@ -839,9 +844,11 @@ class Allocator {
839844
Ptr = getHeaderTaggedPointer(const_cast<void *>(Ptr));
840845
Chunk::UnpackedHeader Header;
841846
Chunk::loadHeader(Cookie, Ptr, &Header);
842-
// Getting the usable size of a chunk only makes sense if it's allocated.
847+
848+
// Getting the alloc size of a chunk only makes sense if it's allocated.
843849
if (UNLIKELY(Header.State != Chunk::State::Allocated))
844850
reportInvalidChunkState(AllocatorAction::Sizing, const_cast<void *>(Ptr));
851+
845852
return getSize(Ptr, &Header);
846853
}
847854

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ extern "C" {
1717
__attribute__((weak)) const char *__scudo_default_options(void);
1818

1919
// Post-allocation & pre-deallocation hooks.
20-
// They must be thread-safe and not use heap related functions.
2120
__attribute__((weak)) void __scudo_allocate_hook(void *ptr, size_t size);
2221
__attribute__((weak)) void __scudo_deallocate_hook(void *ptr);
2322

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

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,12 @@ class ScudoWrappersCTest : public Test {
8383
printf("Hooks are enabled but hooks tests are disabled.\n");
8484
}
8585

86-
void invalidateAllocHookPtrAs(UNUSED void *Ptr) {
87-
if (SCUDO_ENABLE_HOOKS_TESTS)
88-
AC.Ptr = Ptr;
86+
void invalidateHookPtrs() {
87+
if (SCUDO_ENABLE_HOOKS_TESTS) {
88+
void *InvalidPtr = reinterpret_cast<void *>(0xdeadbeef);
89+
AC.Ptr = InvalidPtr;
90+
DC.Ptr = InvalidPtr;
91+
}
8992
}
9093
void verifyAllocHookPtr(UNUSED void *Ptr) {
9194
if (SCUDO_ENABLE_HOOKS_TESTS)
@@ -258,6 +261,7 @@ TEST_F(ScudoWrappersCTest, AlignedAlloc) {
258261
}
259262

260263
TEST_F(ScudoWrappersCDeathTest, Realloc) {
264+
invalidateHookPtrs();
261265
// realloc(nullptr, N) is malloc(N)
262266
void *P = realloc(nullptr, Size);
263267
EXPECT_NE(P, nullptr);
@@ -266,6 +270,7 @@ TEST_F(ScudoWrappersCDeathTest, Realloc) {
266270
free(P);
267271
verifyDeallocHookPtr(P);
268272

273+
invalidateHookPtrs();
269274
P = malloc(Size);
270275
EXPECT_NE(P, nullptr);
271276
// realloc(P, 0U) is free(P) and returns nullptr
@@ -277,30 +282,32 @@ TEST_F(ScudoWrappersCDeathTest, Realloc) {
277282
EXPECT_LE(Size, malloc_usable_size(P));
278283
memset(P, 0x42, Size);
279284

280-
invalidateAllocHookPtrAs(reinterpret_cast<void *>(0xdeadbeef));
285+
invalidateHookPtrs();
281286
void *OldP = P;
282287
P = realloc(P, Size * 2U);
283288
EXPECT_NE(P, nullptr);
284289
EXPECT_LE(Size * 2U, malloc_usable_size(P));
285290
for (size_t I = 0; I < Size; I++)
286291
EXPECT_EQ(0x42, (reinterpret_cast<uint8_t *>(P))[I]);
287292
if (OldP == P) {
288-
verifyAllocHookPtr(reinterpret_cast<void *>(0xdeadbeef));
293+
verifyDeallocHookPtr(OldP);
294+
verifyAllocHookPtr(OldP);
289295
} else {
290296
verifyAllocHookPtr(P);
291297
verifyAllocHookSize(Size * 2U);
292298
verifyDeallocHookPtr(OldP);
293299
}
294300

295-
invalidateAllocHookPtrAs(reinterpret_cast<void *>(0xdeadbeef));
301+
invalidateHookPtrs();
296302
OldP = P;
297303
P = realloc(P, Size / 2U);
298304
EXPECT_NE(P, nullptr);
299305
EXPECT_LE(Size / 2U, malloc_usable_size(P));
300306
for (size_t I = 0; I < Size / 2U; I++)
301307
EXPECT_EQ(0x42, (reinterpret_cast<uint8_t *>(P))[I]);
302308
if (OldP == P) {
303-
verifyAllocHookPtr(reinterpret_cast<void *>(0xdeadbeef));
309+
verifyDeallocHookPtr(OldP);
310+
verifyAllocHookPtr(OldP);
304311
} else {
305312
verifyAllocHookPtr(P);
306313
verifyAllocHookSize(Size / 2U);

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

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,10 +175,24 @@ INTERFACE WEAK void *SCUDO_PREFIX(realloc)(void *ptr, size_t size) {
175175
return nullptr;
176176
}
177177

178+
// Given that the reporting of deallocation and allocation are not atomic, we
179+
// always pretend the old pointer will be released so that the user doesn't
180+
// need to worry about the false double-use case from the view of hooks.
181+
//
182+
// For example, assume that `realloc` releases the old pointer and allocates a
183+
// new pointer. Before the reporting of both operations has been done, another
184+
// thread may get the old pointer from `malloc`. It may be misinterpreted as
185+
// double-use if it's not handled properly on the hook side.
186+
reportDeallocation(ptr);
178187
void *NewPtr = SCUDO_ALLOCATOR.reallocate(ptr, size, SCUDO_MALLOC_ALIGNMENT);
179-
if (NewPtr != ptr) {
188+
if (NewPtr != nullptr) {
189+
// Note that even if NewPtr == ptr, the size has changed. We still need to
190+
// report the new size.
180191
reportAllocation(NewPtr, size);
181-
reportDeallocation(ptr);
192+
} else {
193+
// If `realloc` fails, the old pointer is not released. Report the old
194+
// pointer as allocated back.
195+
reportAllocation(ptr, SCUDO_ALLOCATOR.getAllocSize(ptr));
182196
}
183197

184198
return scudo::setErrnoOnNull(NewPtr);

0 commit comments

Comments
 (0)