diff --git a/compiler-rt/lib/scudo/standalone/include/scudo/interface.h b/compiler-rt/lib/scudo/standalone/include/scudo/interface.h --- a/compiler-rt/lib/scudo/standalone/include/scudo/interface.h +++ b/compiler-rt/lib/scudo/standalone/include/scudo/interface.h @@ -17,7 +17,10 @@ __attribute__((weak)) const char *__scudo_default_options(void); // Post-allocation & pre-deallocation hooks. -// They must be thread-safe and not use heap related functions. +// They must be thread-safe. +// +// Note: Care must be taken, when using heap related functions +// (e.g. allocating on the heap), to avoid infinite recursions. __attribute__((weak)) void __scudo_allocate_hook(void *ptr, size_t size); __attribute__((weak)) void __scudo_deallocate_hook(void *ptr); diff --git a/compiler-rt/lib/scudo/standalone/tests/CMakeLists.txt b/compiler-rt/lib/scudo/standalone/tests/CMakeLists.txt --- a/compiler-rt/lib/scudo/standalone/tests/CMakeLists.txt +++ b/compiler-rt/lib/scudo/standalone/tests/CMakeLists.txt @@ -139,3 +139,11 @@ add_scudo_unittest(ScudoHooksUnitTest SOURCES ${SCUDO_HOOKS_UNIT_TEST_SOURCES}) + +set(SCUDO_HOOKS_REENTRANCY_TEST_SOURCES + scudo_hooks_reentrancy_test.cpp + scudo_unit_test_main.cpp + ) + +add_scudo_unittest(ScudoHooksReentrancyUnitTest + SOURCES ${SCUDO_HOOKS_REENTRANCY_TEST_SOURCES}) diff --git a/compiler-rt/lib/scudo/standalone/tests/scudo_hooks_reentrancy_test.cpp b/compiler-rt/lib/scudo/standalone/tests/scudo_hooks_reentrancy_test.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/lib/scudo/standalone/tests/scudo_hooks_reentrancy_test.cpp @@ -0,0 +1,69 @@ +//===-- scudo_hooks_reentrancy_test.cpp -------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "tests/scudo_unit_test.h" + +#include "allocator_config.h" +#include "combined.h" + +#include + +namespace { +scudo::Allocator Allocator; +constexpr scudo::uptr DefaultSize = 16U; +constexpr scudo::Chunk::Origin Origin = scudo::Chunk::Origin::Malloc; + +// A struct and accompanying thread local variable serving as guards +// against re-entrancy. This is used to allocate from the hooks while +// preventing an infinite recursion. +thread_local bool executing = false; +struct ReentrancyGuard { + ReentrancyGuard() { executing = true; } + ~ReentrancyGuard() { executing = false; } + + ReentrancyGuard(const ReentrancyGuard &) = delete; + ReentrancyGuard &operator=(const ReentrancyGuard &) = delete; + + static bool IsReentrant() { return executing; } +}; +} // namespace + +extern "C" { +// Post-allocation hook. Allocates, but uses a reentrancy guard to +// avoid infinite recursions. +__attribute__((visibility("default"))) void __scudo_allocate_hook(void *, + size_t) { + if (ReentrancyGuard::IsReentrant()) + return; + ReentrancyGuard guard; + EXPECT_TRUE(ReentrancyGuard::IsReentrant()); + void *Ptr = Allocator.allocate(DefaultSize, Origin); + EXPECT_NE(Ptr, nullptr); + Allocator.deallocate(Ptr, Origin); +} + +// Pre-deallocation hook. Allocates, but uses a reentrancy guard to +// avoid infinite recursions. +__attribute__((visibility("default"))) void __scudo_deallocate_hook(void *) { + if (ReentrancyGuard::IsReentrant()) + return; + ReentrancyGuard guard; + EXPECT_TRUE(ReentrancyGuard::IsReentrant()); + void *Ptr = Allocator.allocate(DefaultSize, Origin); + EXPECT_NE(Ptr, nullptr); + Allocator.deallocate(Ptr, Origin); +} +} + +// Allocates, to cause the allocation hooks to be called. +TEST(ScudoHooksReentrancyTest, HooksCanAllocate) { + EXPECT_FALSE(ReentrancyGuard::IsReentrant()); + void *Ptr = Allocator.allocate(DefaultSize, Origin); + EXPECT_NE(Ptr, nullptr); + Allocator.deallocate(Ptr, Origin); +}