diff --git a/compiler-rt/lib/gwp_asan/optional/segv_handler_posix.cpp b/compiler-rt/lib/gwp_asan/optional/segv_handler_posix.cpp --- a/compiler-rt/lib/gwp_asan/optional/segv_handler_posix.cpp +++ b/compiler-rt/lib/gwp_asan/optional/segv_handler_posix.cpp @@ -99,6 +99,12 @@ ThreadBuffer); } +static bool HasReportedBadPoolAccess = false; +static const char *kUnknownCrashText = + "GWP-ASan cannot provide any more information about this error. This may " + "occur due to a wild memory access into the GWP-ASan pool, or an " + "overflow/underflow that is > 512B in length.\n"; + void dumpReport(uintptr_t ErrorPtr, const gwp_asan::AllocatorState *State, const gwp_asan::AllocationMetadata *Metadata, SegvBacktrace_t SegvBacktrace, Printf_t Printf, @@ -117,6 +123,15 @@ const gwp_asan::AllocationMetadata *AllocMeta = __gwp_asan_get_metadata(State, Metadata, ErrorPtr); + if (AllocMeta == nullptr) { + if (HasReportedBadPoolAccess) return; + HasReportedBadPoolAccess = true; + Printf("*** GWP-ASan detected a memory error ***\n"); + ScopedEndOfReportDecorator Decorator(Printf); + Printf(kUnknownCrashText); + return; + } + // It's unusual for a signal handler to be invoked multiple times for the same // allocation, but it's possible in various scenarios, like: // 1. A double-free or invalid-free was invoked in one thread at the same @@ -132,9 +147,7 @@ Error E = __gwp_asan_diagnose_error(State, Metadata, ErrorPtr); if (E == Error::UNKNOWN) { - Printf("GWP-ASan cannot provide any more information about this error. " - "This may occur due to a wild memory access into the GWP-ASan pool, " - "or an overflow/underflow that is > 512B in length.\n"); + Printf(kUnknownCrashText); return; } @@ -149,9 +162,6 @@ PrintBacktrace(Trace, TraceLength, Printf); - if (AllocMeta == nullptr) - return; - // Maybe print the deallocation trace. if (__gwp_asan_is_deallocated(AllocMeta)) { uint64_t ThreadID = __gwp_asan_get_deallocation_thread_id(AllocMeta); 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 @@ -27,7 +27,8 @@ enable_disable.cpp late_init.cpp options.cpp - recoverable.cpp) + recoverable.cpp + never_allocated.cpp) set(GWP_ASAN_UNIT_TEST_HEADERS ${GWP_ASAN_HEADERS} diff --git a/compiler-rt/lib/gwp_asan/tests/backtrace.cpp b/compiler-rt/lib/gwp_asan/tests/backtrace.cpp --- a/compiler-rt/lib/gwp_asan/tests/backtrace.cpp +++ b/compiler-rt/lib/gwp_asan/tests/backtrace.cpp @@ -68,10 +68,6 @@ ; } -INSTANTIATE_TEST_SUITE_P(RecoverableSignalDeathTest, - BacktraceGuardedPoolAllocatorDeathTest, - /* Recoverable */ testing::Bool()); - TEST(Backtrace, Short) { gwp_asan::AllocationMetadata Meta; Meta.AllocationTrace.RecordBacktrace( 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 @@ -46,6 +46,8 @@ void DeallocateMemory2(gwp_asan::GuardedPoolAllocator &GPA, void *Ptr); void TouchMemory(void *Ptr); +void CheckOnlyOneGwpAsanCrash(const std::string &OutputBuffer); + class DefaultGuardedPoolAllocator : public Test { public: void SetUp() override { diff --git a/compiler-rt/lib/gwp_asan/tests/harness.cpp b/compiler-rt/lib/gwp_asan/tests/harness.cpp --- a/compiler-rt/lib/gwp_asan/tests/harness.cpp +++ b/compiler-rt/lib/gwp_asan/tests/harness.cpp @@ -8,6 +8,8 @@ #include "gwp_asan/tests/harness.h" +#include + namespace gwp_asan { namespace test { bool OnlyOnce() { @@ -34,3 +36,19 @@ __attribute__((optnone)) void TouchMemory(void *Ptr) { *(reinterpret_cast(Ptr)) = 7; } + +void CheckOnlyOneGwpAsanCrash(const std::string &OutputBuffer) { + const char *kGwpAsanErrorString = "GWP-ASan detected a memory error"; + size_t FirstIndex = OutputBuffer.find(kGwpAsanErrorString); + ASSERT_NE(FirstIndex, std::string::npos) << "Didn't detect a GWP-ASan crash"; + ASSERT_EQ(OutputBuffer.find(kGwpAsanErrorString, FirstIndex + 1), + std::string::npos) + << "Detected more than one GWP-ASan crash:\n" + << OutputBuffer; +} + +INSTANTIATE_TEST_SUITE_P(RecoverableTests, BacktraceGuardedPoolAllocator, + /* Recoverable */ testing::Values(true)); +INSTANTIATE_TEST_SUITE_P(RecoverableAndNonRecoverableTests, + BacktraceGuardedPoolAllocatorDeathTest, + /* Recoverable */ testing::Bool()); diff --git a/compiler-rt/lib/gwp_asan/tests/never_allocated.cpp b/compiler-rt/lib/gwp_asan/tests/never_allocated.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/lib/gwp_asan/tests/never_allocated.cpp @@ -0,0 +1,55 @@ +//===-- never_allocated.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 + +#include "gwp_asan/common.h" +#include "gwp_asan/crash_handler.h" +#include "gwp_asan/tests/harness.h" + +TEST_P(BacktraceGuardedPoolAllocatorDeathTest, NeverAllocated) { + SCOPED_TRACE(""); + void *Ptr = GPA.allocate(0x1000); + GPA.deallocate(Ptr); + + std::string DeathNeedle = + "GWP-ASan cannot provide any more information about this error"; + + // Trigger a guard page in a completely different slot that's never allocated. + // Previously, there was a bug that this would result in nullptr-dereference + // in the posix crash handler. + char *volatile NeverAllocatedPtr = static_cast(Ptr) + 0x3000; + if (!Recoverable) { + ASSERT_DEATH(*NeverAllocatedPtr = 0, DeathNeedle); + return; + } + + *NeverAllocatedPtr = 0; + CheckOnlyOneGwpAsanCrash(GetOutputBuffer()); + ASSERT_NE(std::string::npos, GetOutputBuffer().find(DeathNeedle)); + + // Check that subsequent invalid touches of the pool don't print a report. + GetOutputBuffer().clear(); + for (size_t i = 0; i < 100; ++i) { + *NeverAllocatedPtr = 0; + *(NeverAllocatedPtr + 0x2000) = 0; + *(NeverAllocatedPtr + 0x3000) = 0; + ASSERT_TRUE(GetOutputBuffer().empty()); + } + + // Check that reports on the other slots still report a double-free, but only + // once. + GetOutputBuffer().clear(); + GPA.deallocate(Ptr); + ASSERT_NE(std::string::npos, GetOutputBuffer().find("Double Free")); + GetOutputBuffer().clear(); + for (size_t i = 0; i < 100; ++i) { + DeallocateMemory(GPA, Ptr); + ASSERT_TRUE(GetOutputBuffer().empty()); + } +} diff --git a/compiler-rt/lib/gwp_asan/tests/recoverable.cpp b/compiler-rt/lib/gwp_asan/tests/recoverable.cpp --- a/compiler-rt/lib/gwp_asan/tests/recoverable.cpp +++ b/compiler-rt/lib/gwp_asan/tests/recoverable.cpp @@ -17,16 +17,6 @@ #include "gwp_asan/crash_handler.h" #include "gwp_asan/tests/harness.h" -void CheckOnlyOneGwpAsanCrash(const std::string &OutputBuffer) { - const char *kGwpAsanErrorString = "GWP-ASan detected a memory error"; - size_t FirstIndex = OutputBuffer.find(kGwpAsanErrorString); - ASSERT_NE(FirstIndex, std::string::npos) << "Didn't detect a GWP-ASan crash"; - ASSERT_EQ(OutputBuffer.find(kGwpAsanErrorString, FirstIndex + 1), - std::string::npos) - << "Detected more than one GWP-ASan crash:\n" - << OutputBuffer; -} - TEST_P(BacktraceGuardedPoolAllocator, MultipleDoubleFreeOnlyOneOutput) { SCOPED_TRACE(""); void *Ptr = AllocateMemory(GPA); @@ -202,6 +192,3 @@ runInterThreadThrashingSingleAlloc(kNumIterations, &GPA); CheckOnlyOneGwpAsanCrash(GetOutputBuffer()); } - -INSTANTIATE_TEST_SUITE_P(RecoverableTests, BacktraceGuardedPoolAllocator, - /* Recoverable */ testing::Values(true));