Skip to content

[scudo] Add hooks to mark the range of realloc #74353

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 6 commits into from
Dec 7, 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
13 changes: 13 additions & 0 deletions compiler-rt/lib/scudo/standalone/include/scudo/interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,19 @@ __attribute__((weak)) const char *__scudo_default_options(void);
__attribute__((weak)) void __scudo_allocate_hook(void *ptr, size_t size);
__attribute__((weak)) void __scudo_deallocate_hook(void *ptr);

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

void __scudo_print_stats(void);

typedef void (*iterate_callback)(uintptr_t base, size_t size, void *arg);
Expand Down
38 changes: 38 additions & 0 deletions compiler-rt/lib/scudo/standalone/tests/wrappers_c_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,14 @@ struct AllocContext {
struct DeallocContext {
void *Ptr;
};
struct ReallocContext {
void *AllocPtr;
void *DeallocPtr;
size_t Size;
};
static AllocContext AC;
static DeallocContext DC;
static ReallocContext RC;

#if (SCUDO_ENABLE_HOOKS_TESTS == 1)
__attribute__((visibility("default"))) void __scudo_allocate_hook(void *Ptr,
Expand All @@ -73,6 +79,28 @@ __attribute__((visibility("default"))) void __scudo_allocate_hook(void *Ptr,
__attribute__((visibility("default"))) void __scudo_deallocate_hook(void *Ptr) {
DC.Ptr = Ptr;
}
__attribute__((visibility("default"))) void
__scudo_realloc_allocate_hook(void *OldPtr, void *NewPtr, size_t Size) {
// Verify that __scudo_realloc_deallocate_hook is called first and set the
// right pointer.
EXPECT_EQ(OldPtr, RC.DeallocPtr);
RC.AllocPtr = NewPtr;
RC.Size = Size;

// Note that this is only used for testing. In general, only one pair of hooks
// will be invoked in `realloc`. if __scudo_realloc_*_hook are not defined,
// it'll call the general hooks only. To make the test easier, we call the
// general one here so that either case (whether __scudo_realloc_*_hook are
// defined) will be verified without separating them into different tests.
__scudo_allocate_hook(NewPtr, Size);
}
__attribute__((visibility("default"))) void
__scudo_realloc_deallocate_hook(void *Ptr) {
RC.DeallocPtr = Ptr;

// See the comment in the __scudo_realloc_allocate_hook above.
__scudo_deallocate_hook(Ptr);
}
#endif // (SCUDO_ENABLE_HOOKS_TESTS == 1)
}

Expand All @@ -88,6 +116,7 @@ class ScudoWrappersCTest : public Test {
void *InvalidPtr = reinterpret_cast<void *>(0xdeadbeef);
AC.Ptr = InvalidPtr;
DC.Ptr = InvalidPtr;
RC.AllocPtr = RC.DeallocPtr = InvalidPtr;
}
}
void verifyAllocHookPtr(UNUSED void *Ptr) {
Expand All @@ -102,6 +131,13 @@ class ScudoWrappersCTest : public Test {
if (SCUDO_ENABLE_HOOKS_TESTS)
EXPECT_EQ(Ptr, DC.Ptr);
}
void verifyReallocHookPtrs(UNUSED void *OldPtr, void *NewPtr, size_t Size) {
if (SCUDO_ENABLE_HOOKS_TESTS) {
EXPECT_EQ(OldPtr, RC.DeallocPtr);
EXPECT_EQ(NewPtr, RC.AllocPtr);
EXPECT_EQ(Size, RC.Size);
}
}
};
using ScudoWrappersCDeathTest = ScudoWrappersCTest;

Expand Down Expand Up @@ -297,6 +333,7 @@ TEST_F(ScudoWrappersCDeathTest, Realloc) {
verifyAllocHookSize(Size * 2U);
verifyDeallocHookPtr(OldP);
}
verifyReallocHookPtrs(OldP, P, Size * 2U);

invalidateHookPtrs();
OldP = P;
Expand All @@ -312,6 +349,7 @@ TEST_F(ScudoWrappersCDeathTest, Realloc) {
verifyAllocHookPtr(P);
verifyAllocHookSize(Size / 2U);
}
verifyReallocHookPtrs(OldP, P, Size / 2U);
free(P);

EXPECT_DEATH(P = realloc(P, Size), "");
Expand Down
27 changes: 23 additions & 4 deletions compiler-rt/lib/scudo/standalone/wrappers_c.inc
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,24 @@ static void reportDeallocation(void *ptr) {
if (__scudo_deallocate_hook)
__scudo_deallocate_hook(ptr);
}
static void reportReallocAllocation(void *old_ptr, void *new_ptr, size_t size) {
DCHECK_NE(new_ptr, nullptr);

if (SCUDO_ENABLE_HOOKS) {
if (__scudo_realloc_allocate_hook)
__scudo_realloc_allocate_hook(old_ptr, new_ptr, size);
else if (__scudo_allocate_hook)
__scudo_allocate_hook(new_ptr, size);
}
}
static void reportReallocDeallocation(void *old_ptr) {
if (SCUDO_ENABLE_HOOKS) {
if (__scudo_realloc_deallocate_hook)
__scudo_realloc_deallocate_hook(old_ptr);
else if (__scudo_deallocate_hook)
__scudo_deallocate_hook(old_ptr);
}
}

extern "C" {

Expand Down Expand Up @@ -183,16 +201,17 @@ INTERFACE WEAK void *SCUDO_PREFIX(realloc)(void *ptr, size_t size) {
// 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);
reportReallocDeallocation(ptr);
void *NewPtr = SCUDO_ALLOCATOR.reallocate(ptr, size, SCUDO_MALLOC_ALIGNMENT);
if (NewPtr != nullptr) {
// Note that even if NewPtr == ptr, the size has changed. We still need to
// report the new size.
reportAllocation(NewPtr, size);
reportReallocAllocation(/*OldPtr=*/ptr, NewPtr, size);
} else {
// If `realloc` fails, the old pointer is not released. Report the old
// pointer as allocated back.
reportAllocation(ptr, SCUDO_ALLOCATOR.getAllocSize(ptr));
// pointer as allocated again.
reportReallocAllocation(/*OldPtr=*/ptr, /*NewPtr=*/ptr,
SCUDO_ALLOCATOR.getAllocSize(ptr));
}

return scudo::setErrnoOnNull(NewPtr);
Expand Down