diff --git a/compiler-rt/lib/gwp_asan/guarded_pool_allocator.h b/compiler-rt/lib/gwp_asan/guarded_pool_allocator.h --- a/compiler-rt/lib/gwp_asan/guarded_pool_allocator.h +++ b/compiler-rt/lib/gwp_asan/guarded_pool_allocator.h @@ -98,6 +98,12 @@ // pool using the provided options. See options.inc for runtime configuration // options. void init(const options::Options &Opts); + // Initialize parts of GWP-ASan that require functional underlying malloc(). + static void setupForFork(); + void uninitTestOnly(); + + static void disable(); + static void enable(); // Return whether the allocation should be randomly chosen for sampling. GWP_ASAN_ALWAYS_INLINE bool shouldSample() { @@ -156,6 +162,7 @@ // mappings, call mapMemory() followed by markReadWrite() on the returned // pointer. void *mapMemory(size_t Size) const; + void unmapMemory(void *Addr, size_t Size) const; void markReadWrite(void *Ptr, size_t Size) const; void markInaccessible(void *Ptr, size_t Size) const; @@ -169,6 +176,7 @@ // signal(), we have to use platform-specific signal handlers to obtain the // address that caused the SIGSEGV exception. static void installSignalHandlers(); + static void uninstallSignalHandlers(); // Returns the index of the slot that this pointer resides in. If the pointer // is not owned by this pool, the result is undefined. diff --git a/compiler-rt/lib/gwp_asan/guarded_pool_allocator.cpp b/compiler-rt/lib/gwp_asan/guarded_pool_allocator.cpp --- a/compiler-rt/lib/gwp_asan/guarded_pool_allocator.cpp +++ b/compiler-rt/lib/gwp_asan/guarded_pool_allocator.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -171,6 +172,38 @@ installSignalHandlers(); } +void GuardedPoolAllocator::disable() { + if (SingletonPtr) + SingletonPtr->PoolMutex.lock(); +} + +void GuardedPoolAllocator::enable() { + if (SingletonPtr) + SingletonPtr->PoolMutex.unlock(); +} + +void GuardedPoolAllocator::setupForFork() { + pthread_atfork(disable, enable, enable); +} + +void GuardedPoolAllocator::uninitTestOnly() { + if (GuardedPagePool != UINTPTR_MAX) { + unmapMemory(reinterpret_cast(GuardedPagePool), + GuardedPagePoolEnd - GuardedPagePool); + GuardedPagePool = UINTPTR_MAX; + GuardedPagePoolEnd = 0; + } + if (Metadata) { + unmapMemory(Metadata, MaxSimultaneousAllocations * sizeof(*Metadata)); + Metadata = nullptr; + } + if (FreeSlots) { + unmapMemory(FreeSlots, MaxSimultaneousAllocations * sizeof(*FreeSlots)); + FreeSlots = nullptr; + } + uninstallSignalHandlers(); +} + void *GuardedPoolAllocator::allocate(size_t Size) { // GuardedPagePoolEnd == 0 when GWP-ASan is disabled. If we are disabled, fall // back to the supporting allocator. diff --git a/compiler-rt/lib/gwp_asan/platform_specific/guarded_pool_allocator_posix.cpp b/compiler-rt/lib/gwp_asan/platform_specific/guarded_pool_allocator_posix.cpp --- a/compiler-rt/lib/gwp_asan/platform_specific/guarded_pool_allocator_posix.cpp +++ b/compiler-rt/lib/gwp_asan/platform_specific/guarded_pool_allocator_posix.cpp @@ -9,6 +9,7 @@ #include "gwp_asan/guarded_pool_allocator.h" #include +#include #include #include #include @@ -30,6 +31,16 @@ return Ptr; } +void GuardedPoolAllocator::unmapMemory(void *Addr, size_t Size) const { + int Res = munmap(Addr, Size); + + if (Res != 0) { + Printf("Failed to unmap guarded pool allocator memory, errno: %d\n", errno); + Printf(" unmmap(%p, %zu, ...) failed.\n", Addr, Size); + exit(EXIT_FAILURE); + } +} + void GuardedPoolAllocator::markReadWrite(void *Ptr, size_t Size) const { if (mprotect(Ptr, Size, PROT_READ | PROT_WRITE) != 0) { Printf("Failed to set guarded pool allocator memory at as RW, errno: %d\n", @@ -58,6 +69,7 @@ } struct sigaction PreviousHandler; +bool SignalHandlerInstalled; static void sigSegvHandler(int sig, siginfo_t *info, void *ucontext) { gwp_asan::GuardedPoolAllocator::reportError( @@ -83,6 +95,14 @@ Action.sa_sigaction = sigSegvHandler; Action.sa_flags = SA_SIGINFO; sigaction(SIGSEGV, &Action, &PreviousHandler); + SignalHandlerInstalled = true; +} + +void GuardedPoolAllocator::uninstallSignalHandlers() { + if (SignalHandlerInstalled) { + sigaction(SIGSEGV, &PreviousHandler, nullptr); + SignalHandlerInstalled = false; + } } uint64_t GuardedPoolAllocator::getThreadID() { diff --git a/compiler-rt/lib/gwp_asan/tests/CMakeLists.txt b/compiler-rt/lib/gwp_asan/tests/CMakeLists.txt --- a/compiler-rt/lib/gwp_asan/tests/CMakeLists.txt +++ b/compiler-rt/lib/gwp_asan/tests/CMakeLists.txt @@ -17,7 +17,9 @@ driver.cpp mutex_test.cpp slot_reuse.cpp - thread_contention.cpp) + thread_contention.cpp + harness.cpp + enable_disable.cpp) set(GWP_ASAN_UNIT_TEST_HEADERS ${GWP_ASAN_HEADERS} diff --git a/compiler-rt/lib/gwp_asan/tests/enable_disable.cpp b/compiler-rt/lib/gwp_asan/tests/enable_disable.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/lib/gwp_asan/tests/enable_disable.cpp @@ -0,0 +1,86 @@ +//===-- enable_disable.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 "gwp_asan/tests/harness.h" + +constexpr size_t Size = 100; + +TEST_F(DefaultGuardedPoolAllocator, Fork) { + void *P; + pid_t Pid = fork(); + EXPECT_GE(Pid, 0); + if (Pid == 0) { + P = GPA.allocate(Size); + EXPECT_NE(P, nullptr); + memset(P, 0x42, Size); + GPA.deallocate(P); + _exit(0); + } + waitpid(Pid, nullptr, 0); + P = GPA.allocate(Size); + EXPECT_NE(P, nullptr); + memset(P, 0x42, Size); + GPA.deallocate(P); + + // fork should stall if the allocator has been disabled. + EXPECT_DEATH( + { + GPA.disable(); + alarm(1); + Pid = fork(); + EXPECT_GE(Pid, 0); + }, + ""); +} + +static pthread_mutex_t Mutex; +static pthread_cond_t Conditional = PTHREAD_COND_INITIALIZER; + +static void *enableMalloc(void *arg) { + auto &GPA = *(gwp_asan::GuardedPoolAllocator *)arg; + // Initialize the allocator for this thread. + void *P = GPA.allocate(Size); + EXPECT_NE(P, nullptr); + memset(P, 0x42, Size); + GPA.deallocate(P); + + // Signal the main thread we are ready. + pthread_mutex_lock(&Mutex); + pthread_cond_signal(&Conditional); + pthread_mutex_unlock(&Mutex); + + // Wait for the malloc_disable & fork, then enable the allocator again. + sleep(1); + GPA.enable(); + + return nullptr; +} + +TEST_F(DefaultGuardedPoolAllocator, DisableForkEnable) { + pthread_t ThreadId; + EXPECT_EQ(pthread_create(&ThreadId, nullptr, &enableMalloc, &GPA), 0); + + // Wait for the thread to be warmed up. + pthread_mutex_lock(&Mutex); + pthread_cond_wait(&Conditional, &Mutex); + pthread_mutex_unlock(&Mutex); + + // Disable the allocator and fork. fork should succeed after malloc_enable. + GPA.disable(); + pid_t Pid = fork(); + EXPECT_GE(Pid, 0); + if (Pid == 0) { + void *P = GPA.allocate(Size); + EXPECT_NE(P, nullptr); + memset(P, 0x42, Size); + GPA.deallocate(P); + _exit(0); + } + waitpid(Pid, nullptr, 0); + EXPECT_EQ(pthread_join(ThreadId, 0), 0); +} diff --git a/compiler-rt/lib/gwp_asan/tests/harness.h b/compiler-rt/lib/gwp_asan/tests/harness.h --- a/compiler-rt/lib/gwp_asan/tests/harness.h +++ b/compiler-rt/lib/gwp_asan/tests/harness.h @@ -24,22 +24,36 @@ // `optional/printf_sanitizer_common.cpp` which supplies the __sanitizer::Printf // for this purpose. options::Printf_t getPrintfFunction(); + +static void setupForFork() { + static struct Guard { + Guard() { gwp_asan::GuardedPoolAllocator::setupForFork(); } + } guard; +} + +extern gwp_asan::GuardedPoolAllocator GPA; + }; // namespace test }; // namespace gwp_asan + class DefaultGuardedPoolAllocator : public ::testing::Test { public: - DefaultGuardedPoolAllocator() { + void SetUp() override { gwp_asan::options::Options Opts; Opts.setDefaults(); MaxSimultaneousAllocations = Opts.MaxSimultaneousAllocations; Opts.Printf = gwp_asan::test::getPrintfFunction(); GPA.init(Opts); + gwp_asan::test::setupForFork(); } + void TearDown() override { + GPA.uninitTestOnly(); + } protected: - gwp_asan::GuardedPoolAllocator GPA; + gwp_asan::GuardedPoolAllocator &GPA = gwp_asan::test::GPA; decltype(gwp_asan::options::Options::MaxSimultaneousAllocations) MaxSimultaneousAllocations; }; @@ -57,17 +71,22 @@ Opts.Printf = gwp_asan::test::getPrintfFunction(); GPA.init(Opts); + gwp_asan::test::setupForFork(); } -protected: - gwp_asan::GuardedPoolAllocator GPA; + void TearDown() override { + GPA.uninitTestOnly(); + } + + protected: + gwp_asan::GuardedPoolAllocator &GPA = gwp_asan::test::GPA; decltype(gwp_asan::options::Options::MaxSimultaneousAllocations) MaxSimultaneousAllocations; }; class BacktraceGuardedPoolAllocator : public ::testing::Test { public: - BacktraceGuardedPoolAllocator() { + void SetUp() override { gwp_asan::options::Options Opts; Opts.setDefaults(); @@ -75,10 +94,14 @@ Opts.Backtrace = gwp_asan::options::getBacktraceFunction(); Opts.PrintBacktrace = gwp_asan::options::getPrintBacktraceFunction(); GPA.init(Opts); + gwp_asan::test::setupForFork(); } + void TearDown() override { + GPA.uninitTestOnly(); + } protected: - gwp_asan::GuardedPoolAllocator GPA; + gwp_asan::GuardedPoolAllocator &GPA = gwp_asan::test::GPA; }; #endif // GWP_ASAN_TESTS_HARNESS_H_ diff --git a/compiler-rt/lib/gwp_asan/tests/harness.cpp b/compiler-rt/lib/gwp_asan/tests/harness.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/lib/gwp_asan/tests/harness.cpp @@ -0,0 +1,7 @@ +#include "harness.h" + +namespace gwp_asan { +namespace test { +gwp_asan::GuardedPoolAllocator GPA; +} +} diff --git a/compiler-rt/lib/scudo/standalone/combined.h b/compiler-rt/lib/scudo/standalone/combined.h --- a/compiler-rt/lib/scudo/standalone/combined.h +++ b/compiler-rt/lib/scudo/standalone/combined.h @@ -175,6 +175,9 @@ void unmapTestOnly() { TSDRegistry.unmapTestOnly(); Primary.unmapTestOnly(); +#ifdef GWP_ASAN_HOOKS + GuardedAlloc.uninitTestOnly(); +#endif // GWP_ASAN_HOOKS } TSDRegistryT *getTSDRegistry() { return &TSDRegistry; } @@ -427,6 +430,9 @@ // this function finishes. We will revisit that later. NOINLINE void disable() { initThreadMaybe(); +#ifdef GWP_ASAN_HOOKS + GuardedAlloc.disable(); +#endif TSDRegistry.disable(); Stats.disable(); Quarantine.disable(); @@ -441,6 +447,9 @@ Quarantine.enable(); Stats.enable(); TSDRegistry.enable(); +#ifdef GWP_ASAN_HOOKS + GuardedAlloc.enable(); +#endif } // The function returns the amount of bytes required to store the statistics, diff --git a/compiler-rt/lib/scudo/standalone/tests/combined_test.cpp b/compiler-rt/lib/scudo/standalone/tests/combined_test.cpp --- a/compiler-rt/lib/scudo/standalone/tests/combined_test.cpp +++ b/compiler-rt/lib/scudo/standalone/tests/combined_test.cpp @@ -164,6 +164,17 @@ EXPECT_NE(Stats.find("Stats: Quarantine"), std::string::npos); } +// Test that multiple instantiations of the allocator have not messed up the +// process's signal handlers (GWP-ASan used to do this). +void testSEGV() { + const scudo::uptr Size = 4 * scudo::getPageSizeCached(); + scudo::MapPlatformData Data = {}; + void *P = scudo::map(nullptr, Size, "testSEGV", MAP_NOACCESS, &Data); + EXPECT_NE(P, nullptr); + EXPECT_DEATH(memset(P, 0xaa, Size), ""); + scudo::unmap(P, Size, UNMAP_ALL, &Data); +} + TEST(ScudoCombinedTest, BasicCombined) { UseQuarantine = false; testAllocator(); @@ -173,6 +184,7 @@ testAllocator(); UseQuarantine = true; testAllocator(); + testSEGV(); #endif }