Skip to content

[scudo] Fix realloc hooks behavior #74149

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

Merged
merged 1 commit into from
Dec 1, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
11 changes: 9 additions & 2 deletions compiler-rt/lib/scudo/standalone/combined.h
Original file line number Diff line number Diff line change
Expand Up @@ -827,10 +827,15 @@ class Allocator {
// for it, which then forces realloc to copy the usable size of a chunk as
// opposed to its actual size.
uptr getUsableSize(const void *Ptr) {
initThreadMaybe();
if (UNLIKELY(!Ptr))
return 0;

return getAllocSize(Ptr);
}

uptr getAllocSize(const void *Ptr) {
initThreadMaybe();

#ifdef GWP_ASAN_HOOKS
if (UNLIKELY(GuardedAlloc.pointerIsMine(Ptr)))
return GuardedAlloc.getSize(Ptr);
Expand All @@ -839,9 +844,11 @@ class Allocator {
Ptr = getHeaderTaggedPointer(const_cast<void *>(Ptr));
Chunk::UnpackedHeader Header;
Chunk::loadHeader(Cookie, Ptr, &Header);
// Getting the usable size of a chunk only makes sense if it's allocated.

// Getting the alloc size of a chunk only makes sense if it's allocated.
if (UNLIKELY(Header.State != Chunk::State::Allocated))
reportInvalidChunkState(AllocatorAction::Sizing, const_cast<void *>(Ptr));

return getSize(Ptr, &Header);
}

Expand Down
1 change: 0 additions & 1 deletion compiler-rt/lib/scudo/standalone/include/scudo/interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ extern "C" {
__attribute__((weak)) const char *__scudo_default_options(void);

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

Expand Down
21 changes: 14 additions & 7 deletions compiler-rt/lib/scudo/standalone/tests/wrappers_c_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,12 @@ class ScudoWrappersCTest : public Test {
printf("Hooks are enabled but hooks tests are disabled.\n");
}

void invalidateAllocHookPtrAs(UNUSED void *Ptr) {
if (SCUDO_ENABLE_HOOKS_TESTS)
AC.Ptr = Ptr;
void invalidateHookPtrs() {
if (SCUDO_ENABLE_HOOKS_TESTS) {
void *InvalidPtr = reinterpret_cast<void *>(0xdeadbeef);
AC.Ptr = InvalidPtr;
DC.Ptr = InvalidPtr;
}
}
void verifyAllocHookPtr(UNUSED void *Ptr) {
if (SCUDO_ENABLE_HOOKS_TESTS)
Expand Down Expand Up @@ -258,6 +261,7 @@ TEST_F(ScudoWrappersCTest, AlignedAlloc) {
}

TEST_F(ScudoWrappersCDeathTest, Realloc) {
invalidateHookPtrs();
// realloc(nullptr, N) is malloc(N)
void *P = realloc(nullptr, Size);
EXPECT_NE(P, nullptr);
Expand All @@ -266,6 +270,7 @@ TEST_F(ScudoWrappersCDeathTest, Realloc) {
free(P);
verifyDeallocHookPtr(P);

invalidateHookPtrs();
P = malloc(Size);
EXPECT_NE(P, nullptr);
// realloc(P, 0U) is free(P) and returns nullptr
Expand All @@ -277,30 +282,32 @@ TEST_F(ScudoWrappersCDeathTest, Realloc) {
EXPECT_LE(Size, malloc_usable_size(P));
memset(P, 0x42, Size);

invalidateAllocHookPtrAs(reinterpret_cast<void *>(0xdeadbeef));
invalidateHookPtrs();
void *OldP = P;
P = realloc(P, Size * 2U);
EXPECT_NE(P, nullptr);
EXPECT_LE(Size * 2U, malloc_usable_size(P));
for (size_t I = 0; I < Size; I++)
EXPECT_EQ(0x42, (reinterpret_cast<uint8_t *>(P))[I]);
if (OldP == P) {
verifyAllocHookPtr(reinterpret_cast<void *>(0xdeadbeef));
verifyDeallocHookPtr(OldP);
verifyAllocHookPtr(OldP);
} else {
verifyAllocHookPtr(P);
verifyAllocHookSize(Size * 2U);
verifyDeallocHookPtr(OldP);
}

invalidateAllocHookPtrAs(reinterpret_cast<void *>(0xdeadbeef));
invalidateHookPtrs();
OldP = P;
P = realloc(P, Size / 2U);
EXPECT_NE(P, nullptr);
EXPECT_LE(Size / 2U, malloc_usable_size(P));
for (size_t I = 0; I < Size / 2U; I++)
EXPECT_EQ(0x42, (reinterpret_cast<uint8_t *>(P))[I]);
if (OldP == P) {
verifyAllocHookPtr(reinterpret_cast<void *>(0xdeadbeef));
verifyDeallocHookPtr(OldP);
verifyAllocHookPtr(OldP);
} else {
verifyAllocHookPtr(P);
verifyAllocHookSize(Size / 2U);
Expand Down
18 changes: 16 additions & 2 deletions compiler-rt/lib/scudo/standalone/wrappers_c.inc
Original file line number Diff line number Diff line change
Expand Up @@ -175,10 +175,24 @@ INTERFACE WEAK void *SCUDO_PREFIX(realloc)(void *ptr, size_t size) {
return nullptr;
}

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

return scudo::setErrnoOnNull(NewPtr);
Expand Down