diff --git a/compiler-rt/lib/gwp_asan/common.cpp b/compiler-rt/lib/gwp_asan/common.cpp index 790a331aa66ca..b0f6c58bf4967 100644 --- a/compiler-rt/lib/gwp_asan/common.cpp +++ b/compiler-rt/lib/gwp_asan/common.cpp @@ -105,8 +105,4 @@ size_t AllocatorState::getNearestSlot(uintptr_t Ptr) const { return addrToSlot(this, Ptr + PageSize); // Round up. } -uintptr_t AllocatorState::internallyDetectedErrorFaultAddress() const { - return GuardedPagePoolEnd - 0x10; -} - } // namespace gwp_asan diff --git a/compiler-rt/lib/gwp_asan/common.h b/compiler-rt/lib/gwp_asan/common.h index df451021d3412..6b238ad9ecbdc 100644 --- a/compiler-rt/lib/gwp_asan/common.h +++ b/compiler-rt/lib/gwp_asan/common.h @@ -35,7 +35,7 @@ struct AllocatorVersionMagic { uint8_t Magic[4] = {}; // Update the version number when the AllocatorState or AllocationMetadata // change. - static constexpr uint16_t kAllocatorVersion = 2; + static constexpr uint16_t kAllocatorVersion = 1; uint16_t Version = 0; uint16_t Reserved = 0; }; @@ -98,12 +98,6 @@ struct AllocationMetadata { // Whether this allocation has been deallocated yet. bool IsDeallocated = false; - - // In recoverable mode, whether this allocation has had a crash associated - // with it. This has certain side effects, like meaning this allocation will - // permanently occupy a slot, and won't ever have another crash reported from - // it. - bool HasCrashed = false; }; // This holds the state that's shared between the GWP-ASan allocator and the @@ -133,11 +127,6 @@ struct AllocatorState { // must be within memory owned by this pool, else the result is undefined. bool isGuardPage(uintptr_t Ptr) const; - // Returns the address that's used by __gwp_asan_get_internal_crash_address() - // and GPA::raiseInternallyDetectedError() to communicate that the SEGV in - // question comes from an internally-detected error. - uintptr_t internallyDetectedErrorFaultAddress() const; - // The number of guarded slots that this pool holds. size_t MaxSimultaneousAllocations = 0; diff --git a/compiler-rt/lib/gwp_asan/crash_handler.cpp b/compiler-rt/lib/gwp_asan/crash_handler.cpp index 555365c6e6f4d..48f54e2e912e9 100644 --- a/compiler-rt/lib/gwp_asan/crash_handler.cpp +++ b/compiler-rt/lib/gwp_asan/crash_handler.cpp @@ -31,15 +31,7 @@ bool __gwp_asan_error_is_mine(const gwp_asan::AllocatorState *State, } uintptr_t -__gwp_asan_get_internal_crash_address(const gwp_asan::AllocatorState *State, - uintptr_t ErrorPtr) { - // There can be a race between internally- and externally-raised faults. The - // fault address from the signal handler is used to discriminate whether it's - // internally- or externally-raised, and the pool maintains a special page at - // the end of the GuardedPagePool specifically for the internally-raised - // faults. - if (ErrorPtr != State->internallyDetectedErrorFaultAddress()) - return 0u; +__gwp_asan_get_internal_crash_address(const gwp_asan::AllocatorState *State) { return State->FailureAddress; } diff --git a/compiler-rt/lib/gwp_asan/crash_handler.h b/compiler-rt/lib/gwp_asan/crash_handler.h index 1ff60edea47df..4a95069dac585 100644 --- a/compiler-rt/lib/gwp_asan/crash_handler.h +++ b/compiler-rt/lib/gwp_asan/crash_handler.h @@ -46,18 +46,12 @@ __gwp_asan_diagnose_error(const gwp_asan::AllocatorState *State, const gwp_asan::AllocationMetadata *Metadata, uintptr_t ErrorPtr); -// This function, provided the fault address from the signal handler, returns -// the following values: -// 1. If the crash was caused by an internally-detected error (invalid free, -// double free), this function returns the pointer that was used for the -// internally-detected bad operation (i.e. the pointer given to free()). -// 2. For externally-detected crashes (use-after-free, buffer-overflow), this -// function returns zero. -// 3. If GWP-ASan wasn't responsible for the crash at all, this function also -// returns zero. +// For internally-detected errors (double free, invalid free), this function +// returns the pointer that the error occurred at. If the error is unrelated to +// GWP-ASan, or if the error was caused by a non-internally detected failure, +// this function returns zero. uintptr_t -__gwp_asan_get_internal_crash_address(const gwp_asan::AllocatorState *State, - uintptr_t ErrorPtr); +__gwp_asan_get_internal_crash_address(const gwp_asan::AllocatorState *State); // Returns a pointer to the metadata for the allocation that's responsible for // the crash. This metadata should not be dereferenced directly due to API diff --git a/compiler-rt/lib/gwp_asan/guarded_pool_allocator.cpp b/compiler-rt/lib/gwp_asan/guarded_pool_allocator.cpp index 9017ab7cf7ac0..7096b428764cc 100644 --- a/compiler-rt/lib/gwp_asan/guarded_pool_allocator.cpp +++ b/compiler-rt/lib/gwp_asan/guarded_pool_allocator.cpp @@ -8,7 +8,6 @@ #include "gwp_asan/guarded_pool_allocator.h" -#include "gwp_asan/crash_handler.h" #include "gwp_asan/options.h" #include "gwp_asan/utilities.h" @@ -74,15 +73,8 @@ void GuardedPoolAllocator::init(const options::Options &Opts) { assert((PageSize & (PageSize - 1)) == 0); State.PageSize = PageSize; - // Number of pages required = - // + MaxSimultaneousAllocations * maximumAllocationSize (N pages per slot) - // + MaxSimultaneousAllocations (one guard on the left side of each slot) - // + 1 (an extra guard page at the end of the pool, on the right side) - // + 1 (an extra page that's used for reporting internally-detected crashes, - // like double free and invalid free, to the signal handler; see - // raiseInternallyDetectedError() for more info) size_t PoolBytesRequired = - PageSize * (2 + State.MaxSimultaneousAllocations) + + PageSize * (1 + State.MaxSimultaneousAllocations) + State.MaxSimultaneousAllocations * State.maximumAllocationSize(); assert(PoolBytesRequired % PageSize == 0); void *GuardedPoolMemory = reserveGuardedPool(PoolBytesRequired); @@ -266,60 +258,22 @@ void *GuardedPoolAllocator::allocate(size_t Size, size_t Alignment) { return reinterpret_cast(UserPtr); } -void GuardedPoolAllocator::raiseInternallyDetectedError(uintptr_t Address, - Error E) { - // Disable the allocator before setting the internal failure state. In - // non-recoverable mode, the allocator will be permanently disabled, and so - // things will be accessed without locks. - disable(); - - // Races between internally- and externally-raised faults can happen. Right - // now, in this thread we've locked the allocator in order to raise an - // internally-detected fault, and another thread could SIGSEGV to raise an - // externally-detected fault. What will happen is that the other thread will - // wait in the signal handler, as we hold the allocator's locks from the - // disable() above. We'll trigger the signal handler by touching the - // internal-signal-raising address below, and the signal handler from our - // thread will get to run first as we will continue to hold the allocator - // locks until the enable() at the end of this function. Be careful though, if - // this thread receives another SIGSEGV after the disable() above, but before - // touching the internal-signal-raising address below, then this thread will - // get an "externally-raised" SIGSEGV while *also* holding the allocator - // locks, which means this thread's signal handler will deadlock. This could - // be resolved with a re-entrant lock, but asking platforms to implement this - // seems unnecessary given the only way to get a SIGSEGV in this critical - // section is either a memory safety bug in the couple lines of code below (be - // careful!), or someone outside uses `kill(this_thread, SIGSEGV)`, which - // really shouldn't happen. - +void GuardedPoolAllocator::trapOnAddress(uintptr_t Address, Error E) { State.FailureType = E; State.FailureAddress = Address; - // Raise a SEGV by touching a specific address that identifies to the crash - // handler that this is an internally-raised fault. Changing this address? - // Don't forget to update __gwp_asan_get_internal_crash_address. - volatile char *p = - reinterpret_cast(State.internallyDetectedErrorFaultAddress()); + // Raise a SEGV by touching first guard page. + volatile char *p = reinterpret_cast(State.GuardedPagePool); *p = 0; + // Normally, would be __builtin_unreachable(), but because of + // https://bugs.llvm.org/show_bug.cgi?id=47480, unreachable will DCE the + // volatile store above, even though it has side effects. + __builtin_trap(); +} - // This should never be reached in non-recoverable mode. Ensure that the - // signal handler called handleRecoverablePostCrashReport(), which was - // responsible for re-setting these fields. - assert(State.FailureType == Error::UNKNOWN); - assert(State.FailureAddress == 0u); - - // In recoverable mode, the signal handler (after dumping the crash) marked - // the page containing the InternalFaultSegvAddress as read/writeable, to - // allow the second touch to succeed after returning from the signal handler. - // Now, we need to mark the page as non-read/write-able again, so future - // internal faults can be raised. - deallocateInGuardedPool( - reinterpret_cast(getPageAddr( - State.internallyDetectedErrorFaultAddress(), State.PageSize)), - State.PageSize); - - // And now we're done with patching ourselves back up, enable the allocator. - enable(); +void GuardedPoolAllocator::stop() { + getThreadLocals()->RecursiveGuard = true; + PoolMutex.tryLock(); } void GuardedPoolAllocator::deallocate(void *Ptr) { @@ -328,25 +282,19 @@ void GuardedPoolAllocator::deallocate(void *Ptr) { size_t Slot = State.getNearestSlot(UPtr); uintptr_t SlotStart = State.slotToAddr(Slot); AllocationMetadata *Meta = addrToMetadata(UPtr); - - // If this allocation is responsible for crash, never recycle it. Turn the - // deallocate() call into a no-op. - if (Meta->HasCrashed) - return; - if (Meta->Addr != UPtr) { - raiseInternallyDetectedError(UPtr, Error::INVALID_FREE); - return; - } - if (Meta->IsDeallocated) { - raiseInternallyDetectedError(UPtr, Error::DOUBLE_FREE); - return; + // If multiple errors occur at the same time, use the first one. + ScopedLock L(PoolMutex); + trapOnAddress(UPtr, Error::INVALID_FREE); } // Intentionally scope the mutex here, so that other threads can access the // pool during the expensive markInaccessible() call. { ScopedLock L(PoolMutex); + if (Meta->IsDeallocated) { + trapOnAddress(UPtr, Error::DOUBLE_FREE); + } // Ensure that the deallocation is recorded before marking the page as // inaccessible. Otherwise, a racy use-after-free will have inconsistent @@ -370,62 +318,6 @@ void GuardedPoolAllocator::deallocate(void *Ptr) { freeSlot(Slot); } -// Thread-compatible, protected by PoolMutex. -static bool PreviousRecursiveGuard; - -void GuardedPoolAllocator::preCrashReport(void *Ptr) { - assert(pointerIsMine(Ptr) && "Pointer is not mine!"); - uintptr_t InternalCrashAddr = __gwp_asan_get_internal_crash_address( - &State, reinterpret_cast(Ptr)); - if (!InternalCrashAddr) - disable(); - - // If something in the signal handler calls malloc() while dumping the - // GWP-ASan report (e.g. backtrace_symbols()), make sure that GWP-ASan doesn't - // service that allocation. `PreviousRecursiveGuard` is protected by the - // allocator locks taken in disable(), either explicitly above for - // externally-raised errors, or implicitly in raiseInternallyDetectedError() - // for internally-detected errors. - PreviousRecursiveGuard = getThreadLocals()->RecursiveGuard; - getThreadLocals()->RecursiveGuard = true; -} - -void GuardedPoolAllocator::postCrashReportRecoverableOnly(void *SignalPtr) { - uintptr_t SignalUPtr = reinterpret_cast(SignalPtr); - uintptr_t InternalCrashAddr = - __gwp_asan_get_internal_crash_address(&State, SignalUPtr); - uintptr_t ErrorUptr = InternalCrashAddr ?: SignalUPtr; - - AllocationMetadata *Metadata = addrToMetadata(ErrorUptr); - Metadata->HasCrashed = true; - - allocateInGuardedPool( - reinterpret_cast(getPageAddr(SignalUPtr, State.PageSize)), - State.PageSize); - - // Clear the internal state in order to not confuse the crash handler if a - // use-after-free or buffer-overflow comes from a different allocation in the - // future. - if (InternalCrashAddr) { - State.FailureType = Error::UNKNOWN; - State.FailureAddress = 0; - } - - size_t Slot = State.getNearestSlot(ErrorUptr); - // If the slot is available, remove it permanently. - for (size_t i = 0; i < FreeSlotsLength; ++i) { - if (FreeSlots[i] == Slot) { - FreeSlots[i] = FreeSlots[FreeSlotsLength - 1]; - FreeSlotsLength -= 1; - break; - } - } - - getThreadLocals()->RecursiveGuard = PreviousRecursiveGuard; - if (!InternalCrashAddr) - enable(); -} - size_t GuardedPoolAllocator::getSize(const void *Ptr) { assert(pointerIsMine(Ptr)); ScopedLock L(PoolMutex); diff --git a/compiler-rt/lib/gwp_asan/guarded_pool_allocator.h b/compiler-rt/lib/gwp_asan/guarded_pool_allocator.h index de07b6798c19c..6d2ce2576c136 100644 --- a/compiler-rt/lib/gwp_asan/guarded_pool_allocator.h +++ b/compiler-rt/lib/gwp_asan/guarded_pool_allocator.h @@ -67,6 +67,11 @@ class GuardedPoolAllocator { // allocate. void iterate(void *Base, size_t Size, iterate_callback Cb, void *Arg); + // This function is used to signal the allocator to indefinitely stop + // functioning, as a crash has occurred. This stops the allocator from + // servicing any further allocations permanently. + void stop(); + // Return whether the allocation should be randomly chosen for sampling. GWP_ASAN_ALWAYS_INLINE bool shouldSample() { // NextSampleCounter == 0 means we "should regenerate the counter". @@ -110,12 +115,6 @@ class GuardedPoolAllocator { // Returns a pointer to the AllocatorState region. const AllocatorState *getAllocatorState() const { return &State; } - // Functions that the signal handler is responsible for calling, while - // providing the SEGV pointer, prior to dumping the crash, and after dumping - // the crash (in recoverable mode only). - void preCrashReport(void *Ptr); - void postCrashReportRecoverableOnly(void *Ptr); - // Exposed as protected for testing. protected: // Returns the actual allocation size required to service an allocation with @@ -186,7 +185,7 @@ class GuardedPoolAllocator { // Raise a SEGV and set the corresponding fields in the Allocator's State in // order to tell the crash handler what happened. Used when errors are // detected internally (Double Free, Invalid Free). - void raiseInternallyDetectedError(uintptr_t Address, Error E); + void trapOnAddress(uintptr_t Address, Error E); static GuardedPoolAllocator *getSingleton(); diff --git a/compiler-rt/lib/gwp_asan/optional/segv_handler.h b/compiler-rt/lib/gwp_asan/optional/segv_handler.h index 72105ded7d55a..87d9fe1dff17c 100644 --- a/compiler-rt/lib/gwp_asan/optional/segv_handler.h +++ b/compiler-rt/lib/gwp_asan/optional/segv_handler.h @@ -23,8 +23,7 @@ namespace segv_handler { // before this function. void installSignalHandlers(gwp_asan::GuardedPoolAllocator *GPA, Printf_t Printf, gwp_asan::backtrace::PrintBacktrace_t PrintBacktrace, - gwp_asan::backtrace::SegvBacktrace_t SegvBacktrace, - bool Recoverable = false); + gwp_asan::backtrace::SegvBacktrace_t SegvBacktrace); // Uninistall the signal handlers, test-only. void uninstallSignalHandlers(); diff --git a/compiler-rt/lib/gwp_asan/optional/segv_handler_fuchsia.cpp b/compiler-rt/lib/gwp_asan/optional/segv_handler_fuchsia.cpp index f5ff35e27ac20..966d7d0bd9962 100644 --- a/compiler-rt/lib/gwp_asan/optional/segv_handler_fuchsia.cpp +++ b/compiler-rt/lib/gwp_asan/optional/segv_handler_fuchsia.cpp @@ -15,8 +15,7 @@ namespace segv_handler { void installSignalHandlers(gwp_asan::GuardedPoolAllocator * /* GPA */, Printf_t /* Printf */, backtrace::PrintBacktrace_t /* PrintBacktrace */, - backtrace::SegvBacktrace_t /* SegvBacktrace */, - bool /* Recoverable */) {} + backtrace::SegvBacktrace_t /* SegvBacktrace */) {} void uninstallSignalHandlers() {} } // namespace segv_handler diff --git a/compiler-rt/lib/gwp_asan/optional/segv_handler_posix.cpp b/compiler-rt/lib/gwp_asan/optional/segv_handler_posix.cpp index e012963bffd89..b3e72c9640001 100644 --- a/compiler-rt/lib/gwp_asan/optional/segv_handler_posix.cpp +++ b/compiler-rt/lib/gwp_asan/optional/segv_handler_posix.cpp @@ -106,31 +106,19 @@ void dumpReport(uintptr_t ErrorPtr, const gwp_asan::AllocatorState *State, assert(State && "dumpReport missing Allocator State."); assert(Metadata && "dumpReport missing Metadata."); assert(Printf && "dumpReport missing Printf."); - assert(__gwp_asan_error_is_mine(State, ErrorPtr) && - "dumpReport() called on a non-GWP-ASan error."); - uintptr_t InternalErrorPtr = - __gwp_asan_get_internal_crash_address(State, ErrorPtr); - if (InternalErrorPtr) - ErrorPtr = InternalErrorPtr; - - const gwp_asan::AllocationMetadata *AllocMeta = - __gwp_asan_get_metadata(State, Metadata, ErrorPtr); - - // 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 - // time as a buffer-overflow or use-after-free in another thread, or - // 2. Two threads do a use-after-free or buffer-overflow at the same time. - // In these instances, we've already dumped a report for this allocation, so - // skip dumping this issue as well. - if (AllocMeta->HasCrashed) + if (!__gwp_asan_error_is_mine(State, ErrorPtr)) return; Printf("*** GWP-ASan detected a memory error ***\n"); ScopedEndOfReportDecorator Decorator(Printf); + uintptr_t InternalErrorPtr = __gwp_asan_get_internal_crash_address(State); + if (InternalErrorPtr != 0u) + ErrorPtr = InternalErrorPtr; + 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, " @@ -138,6 +126,9 @@ void dumpReport(uintptr_t ErrorPtr, const gwp_asan::AllocatorState *State, return; } + const gwp_asan::AllocationMetadata *AllocMeta = + __gwp_asan_get_metadata(State, Metadata, ErrorPtr); + // Print the error header. printHeader(E, ErrorPtr, AllocMeta, Printf); @@ -177,33 +168,23 @@ void dumpReport(uintptr_t ErrorPtr, const gwp_asan::AllocatorState *State, struct sigaction PreviousHandler; bool SignalHandlerInstalled; -bool RecoverableSignal; gwp_asan::GuardedPoolAllocator *GPAForSignalHandler; Printf_t PrintfForSignalHandler; PrintBacktrace_t PrintBacktraceForSignalHandler; SegvBacktrace_t BacktraceForSignalHandler; static void sigSegvHandler(int sig, siginfo_t *info, void *ucontext) { - const gwp_asan::AllocatorState *State = - GPAForSignalHandler->getAllocatorState(); - void *FaultAddr = info->si_addr; - uintptr_t FaultAddrUPtr = reinterpret_cast(FaultAddr); + if (GPAForSignalHandler) { + GPAForSignalHandler->stop(); - if (__gwp_asan_error_is_mine(State, FaultAddrUPtr)) { - GPAForSignalHandler->preCrashReport(FaultAddr); - - dumpReport(FaultAddrUPtr, State, GPAForSignalHandler->getMetadataRegion(), + dumpReport(reinterpret_cast(info->si_addr), + GPAForSignalHandler->getAllocatorState(), + GPAForSignalHandler->getMetadataRegion(), BacktraceForSignalHandler, PrintfForSignalHandler, PrintBacktraceForSignalHandler, ucontext); - - if (RecoverableSignal) { - GPAForSignalHandler->postCrashReportRecoverableOnly(FaultAddr); - return; - } } - // Process any previous handlers as long as the crash wasn't a GWP-ASan crash - // in recoverable mode. + // Process any previous handlers. if (PreviousHandler.sa_flags & SA_SIGINFO) { PreviousHandler.sa_sigaction(sig, info, ucontext); } else if (PreviousHandler.sa_handler == SIG_DFL) { @@ -229,7 +210,7 @@ namespace segv_handler { void installSignalHandlers(gwp_asan::GuardedPoolAllocator *GPA, Printf_t Printf, PrintBacktrace_t PrintBacktrace, - SegvBacktrace_t SegvBacktrace, bool Recoverable) { + SegvBacktrace_t SegvBacktrace) { assert(GPA && "GPA wasn't provided to installSignalHandlers."); assert(Printf && "Printf wasn't provided to installSignalHandlers."); assert(PrintBacktrace && @@ -240,7 +221,6 @@ void installSignalHandlers(gwp_asan::GuardedPoolAllocator *GPA, Printf_t Printf, PrintfForSignalHandler = Printf; PrintBacktraceForSignalHandler = PrintBacktrace; BacktraceForSignalHandler = SegvBacktrace; - RecoverableSignal = Recoverable; struct sigaction Action = {}; Action.sa_sigaction = sigSegvHandler; diff --git a/compiler-rt/lib/gwp_asan/options.inc b/compiler-rt/lib/gwp_asan/options.inc index 3a593216e8dfc..9900a2ac40df1 100644 --- a/compiler-rt/lib/gwp_asan/options.inc +++ b/compiler-rt/lib/gwp_asan/options.inc @@ -49,16 +49,6 @@ GWP_ASAN_OPTION( "the same. Note, if the previously installed SIGSEGV handler is SIG_IGN, " "we terminate the process after dumping the error report.") -GWP_ASAN_OPTION( - bool, Recoverable, false, - "Install GWP-ASan's signal handler in recoverable mode. This means that " - "upon GWP-ASan detecting an error, it'll print the error report, but *not* " - "crash. Only one crash per sampled allocation will ever be recorded, and " - "if a sampled allocation does actually cause a crash, it'll permanently " - "occupy a slot in the pool. The recoverable mode also means that " - "previously-installed signal handlers will only be triggered for " - "non-GWP-ASan errors, as all GWP-ASan errors won't be forwarded.") - GWP_ASAN_OPTION(bool, InstallForkHandlers, true, "Install GWP-ASan atfork handlers to acquire internal locks " "before fork and release them after.") diff --git a/compiler-rt/lib/gwp_asan/tests/CMakeLists.txt b/compiler-rt/lib/gwp_asan/tests/CMakeLists.txt index 046ca7ce6799f..ef7ea28b39837 100644 --- a/compiler-rt/lib/gwp_asan/tests/CMakeLists.txt +++ b/compiler-rt/lib/gwp_asan/tests/CMakeLists.txt @@ -26,8 +26,7 @@ set(GWP_ASAN_UNITTESTS harness.cpp enable_disable.cpp late_init.cpp - options.cpp - recoverable.cpp) + options.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 index 4dccda815e8df..a4eb8eb9b214d 100644 --- a/compiler-rt/lib/gwp_asan/tests/backtrace.cpp +++ b/compiler-rt/lib/gwp_asan/tests/backtrace.cpp @@ -6,7 +6,6 @@ // //===----------------------------------------------------------------------===// -#include #include #include "gwp_asan/common.h" @@ -15,47 +14,38 @@ // Optnone to ensure that the calls to these functions are not optimized away, // as we're looking for them in the backtraces. -__attribute__((optnone)) static void * +__attribute((optnone)) void * AllocateMemory(gwp_asan::GuardedPoolAllocator &GPA) { return GPA.allocate(1); } -__attribute__((optnone)) static void +__attribute((optnone)) void DeallocateMemory(gwp_asan::GuardedPoolAllocator &GPA, void *Ptr) { GPA.deallocate(Ptr); } -__attribute__((optnone)) static void +__attribute((optnone)) void DeallocateMemory2(gwp_asan::GuardedPoolAllocator &GPA, void *Ptr) { GPA.deallocate(Ptr); } -__attribute__((optnone)) static void TouchMemory(void *Ptr) { +__attribute__((optnone)) void TouchMemory(void *Ptr) { *(reinterpret_cast(Ptr)) = 7; } -TEST_P(BacktraceGuardedPoolAllocatorDeathTest, DoubleFree) { +TEST_F(BacktraceGuardedPoolAllocatorDeathTest, DoubleFree) { void *Ptr = AllocateMemory(GPA); DeallocateMemory(GPA, Ptr); - std::string DeathRegex = "Double Free.*DeallocateMemory2.*"; - DeathRegex.append("was deallocated.*DeallocateMemory[^2].*"); - DeathRegex.append("was allocated.*AllocateMemory"); - if (!Recoverable) { - ASSERT_DEATH(DeallocateMemory2(GPA, Ptr), DeathRegex); - return; - } - - // For recoverable, assert that DeallocateMemory2() doesn't crash. - DeallocateMemory2(GPA, Ptr); - // Fuchsia's zxtest doesn't have an EXPECT_THAT(testing::MatchesRegex(), ...), - // so check the regex manually. - EXPECT_TRUE(std::regex_search( - GetOutputBuffer(), - std::basic_regex(DeathRegex, std::regex_constants::extended))) - << "Regex \"" << DeathRegex - << "\" was not found in input:\n============\n" - << GetOutputBuffer() << "\n============"; + std::string DeathRegex = "Double Free.*"; + DeathRegex.append("DeallocateMemory2.*"); + + DeathRegex.append("was deallocated.*"); + DeathRegex.append("DeallocateMemory.*"); + + DeathRegex.append("was allocated.*"); + DeathRegex.append("AllocateMemory.*"); + ASSERT_DEATH(DeallocateMemory2(GPA, Ptr), DeathRegex); } -TEST_P(BacktraceGuardedPoolAllocatorDeathTest, UseAfterFree) { +TEST_F(BacktraceGuardedPoolAllocatorDeathTest, UseAfterFree) { #if defined(__linux__) && __ARM_ARCH == 7 // Incomplete backtrace on Armv7 Linux GTEST_SKIP(); @@ -64,31 +54,16 @@ TEST_P(BacktraceGuardedPoolAllocatorDeathTest, UseAfterFree) { void *Ptr = AllocateMemory(GPA); DeallocateMemory(GPA, Ptr); - std::string DeathRegex = "Use After Free.*TouchMemory.*"; - DeathRegex.append("was deallocated.*DeallocateMemory[^2].*"); - DeathRegex.append("was allocated.*AllocateMemory"); - - if (!Recoverable) { - ASSERT_DEATH(TouchMemory(Ptr), DeathRegex); - return; - } - - // For recoverable, assert that TouchMemory() doesn't crash. - TouchMemory(Ptr); - // Fuchsia's zxtest doesn't have an EXPECT_THAT(testing::MatchesRegex(), ...), - // so check the regex manually. - EXPECT_TRUE(std::regex_search( - GetOutputBuffer(), - std::basic_regex(DeathRegex, std::regex_constants::extended))) - << "Regex \"" << DeathRegex - << "\" was not found in input:\n============\n" - << GetOutputBuffer() << "\n============"; - ; -} + std::string DeathRegex = "Use After Free.*"; + DeathRegex.append("TouchMemory.*"); -INSTANTIATE_TEST_SUITE_P(RecoverableSignalDeathTest, - BacktraceGuardedPoolAllocatorDeathTest, - /* Recoverable */ testing::Bool()); + DeathRegex.append("was deallocated.*"); + DeathRegex.append("DeallocateMemory.*"); + + DeathRegex.append("was allocated.*"); + DeathRegex.append("AllocateMemory.*"); + ASSERT_DEATH(TouchMemory(Ptr), DeathRegex); +} TEST(Backtrace, Short) { gwp_asan::AllocationMetadata Meta; diff --git a/compiler-rt/lib/gwp_asan/tests/crash_handler_api.cpp b/compiler-rt/lib/gwp_asan/tests/crash_handler_api.cpp index 598b7b8789295..4cdb5694842f9 100644 --- a/compiler-rt/lib/gwp_asan/tests/crash_handler_api.cpp +++ b/compiler-rt/lib/gwp_asan/tests/crash_handler_api.cpp @@ -40,8 +40,7 @@ class CrashHandlerAPITest : public Test { void setupState() { State.GuardedPagePool = 0x2000; - State.GuardedPagePoolEnd = 0xc000; - InternalFaultAddr = State.GuardedPagePoolEnd - 0x10; + State.GuardedPagePoolEnd = 0xb000; State.MaxSimultaneousAllocations = 4; // 0x3000, 0x5000, 0x7000, 0x9000. State.PageSize = 0x1000; } @@ -101,7 +100,6 @@ class CrashHandlerAPITest : public Test { static uintptr_t BacktraceConstants[kNumBacktraceConstants]; AllocatorState State = {}; AllocationMetadata Metadata[4] = {}; - uintptr_t InternalFaultAddr; }; uintptr_t CrashHandlerAPITest::BacktraceConstants[kNumBacktraceConstants] = { @@ -127,7 +125,7 @@ TEST_F(CrashHandlerAPITest, PointerNotAllocated) { EXPECT_TRUE(__gwp_asan_error_is_mine(&State, FailureAddress)); EXPECT_EQ(Error::UNKNOWN, __gwp_asan_diagnose_error(&State, Metadata, FailureAddress)); - EXPECT_EQ(0u, __gwp_asan_get_internal_crash_address(&State, FailureAddress)); + EXPECT_EQ(0u, __gwp_asan_get_internal_crash_address(&State)); EXPECT_EQ(nullptr, __gwp_asan_get_metadata(&State, Metadata, FailureAddress)); } @@ -142,8 +140,7 @@ TEST_F(CrashHandlerAPITest, DoubleFree) { EXPECT_TRUE(__gwp_asan_error_is_mine(&State)); EXPECT_EQ(Error::DOUBLE_FREE, __gwp_asan_diagnose_error(&State, Metadata, 0x0)); - EXPECT_EQ(FailureAddress, - __gwp_asan_get_internal_crash_address(&State, InternalFaultAddr)); + EXPECT_EQ(FailureAddress, __gwp_asan_get_internal_crash_address(&State)); checkMetadata(Index, FailureAddress); } @@ -158,8 +155,7 @@ TEST_F(CrashHandlerAPITest, InvalidFree) { EXPECT_TRUE(__gwp_asan_error_is_mine(&State)); EXPECT_EQ(Error::INVALID_FREE, __gwp_asan_diagnose_error(&State, Metadata, 0x0)); - EXPECT_EQ(FailureAddress, - __gwp_asan_get_internal_crash_address(&State, InternalFaultAddr)); + EXPECT_EQ(FailureAddress, __gwp_asan_get_internal_crash_address(&State)); checkMetadata(Index, FailureAddress); } @@ -172,8 +168,7 @@ TEST_F(CrashHandlerAPITest, InvalidFreeNoMetadata) { EXPECT_TRUE(__gwp_asan_error_is_mine(&State)); EXPECT_EQ(Error::INVALID_FREE, __gwp_asan_diagnose_error(&State, Metadata, 0x0)); - EXPECT_EQ(FailureAddress, - __gwp_asan_get_internal_crash_address(&State, InternalFaultAddr)); + EXPECT_EQ(FailureAddress, __gwp_asan_get_internal_crash_address(&State)); EXPECT_EQ(nullptr, __gwp_asan_get_metadata(&State, Metadata, FailureAddress)); } @@ -185,7 +180,7 @@ TEST_F(CrashHandlerAPITest, UseAfterFree) { EXPECT_TRUE(__gwp_asan_error_is_mine(&State, FailureAddress)); EXPECT_EQ(Error::USE_AFTER_FREE, __gwp_asan_diagnose_error(&State, Metadata, FailureAddress)); - EXPECT_EQ(0u, __gwp_asan_get_internal_crash_address(&State, FailureAddress)); + EXPECT_EQ(0u, __gwp_asan_get_internal_crash_address(&State)); checkMetadata(Index, FailureAddress); } @@ -197,7 +192,7 @@ TEST_F(CrashHandlerAPITest, BufferOverflow) { EXPECT_TRUE(__gwp_asan_error_is_mine(&State, FailureAddress)); EXPECT_EQ(Error::BUFFER_OVERFLOW, __gwp_asan_diagnose_error(&State, Metadata, FailureAddress)); - EXPECT_EQ(0u, __gwp_asan_get_internal_crash_address(&State, FailureAddress)); + EXPECT_EQ(0u, __gwp_asan_get_internal_crash_address(&State)); checkMetadata(Index, FailureAddress); } @@ -209,6 +204,6 @@ TEST_F(CrashHandlerAPITest, BufferUnderflow) { EXPECT_TRUE(__gwp_asan_error_is_mine(&State, FailureAddress)); EXPECT_EQ(Error::BUFFER_UNDERFLOW, __gwp_asan_diagnose_error(&State, Metadata, FailureAddress)); - EXPECT_EQ(0u, __gwp_asan_get_internal_crash_address(&State, FailureAddress)); + EXPECT_EQ(0u, __gwp_asan_get_internal_crash_address(&State)); checkMetadata(Index, FailureAddress); } diff --git a/compiler-rt/lib/gwp_asan/tests/harness.h b/compiler-rt/lib/gwp_asan/tests/harness.h index 2c8187ca873f8..ed91e642de70e 100644 --- a/compiler-rt/lib/gwp_asan/tests/harness.h +++ b/compiler-rt/lib/gwp_asan/tests/harness.h @@ -81,8 +81,7 @@ class CustomGuardedPoolAllocator : public Test { MaxSimultaneousAllocations; }; -class BacktraceGuardedPoolAllocator - : public testing::TestWithParam { +class BacktraceGuardedPoolAllocator : public Test { public: void SetUp() override { gwp_asan::options::Options Opts; @@ -92,19 +91,10 @@ class BacktraceGuardedPoolAllocator Opts.InstallForkHandlers = gwp_asan::test::OnlyOnce(); GPA.init(Opts); - // In recoverable mode, capture GWP-ASan logs to an internal buffer so that - // we can search it in unit tests. For non-recoverable tests, the default - // buffer is fine, as any tests should be EXPECT_DEATH()'d. - Recoverable = GetParam(); - gwp_asan::Printf_t PrintfFunction = PrintfToBuffer; - GetOutputBuffer().clear(); - if (!Recoverable) - PrintfFunction = gwp_asan::test::getPrintfFunction(); - gwp_asan::segv_handler::installSignalHandlers( - &GPA, PrintfFunction, gwp_asan::backtrace::getPrintBacktraceFunction(), - gwp_asan::backtrace::getSegvBacktraceFunction(), - /* Recoverable */ Recoverable); + &GPA, gwp_asan::test::getPrintfFunction(), + gwp_asan::backtrace::getPrintBacktraceFunction(), + gwp_asan::backtrace::getSegvBacktraceFunction()); } void TearDown() override { @@ -113,23 +103,7 @@ class BacktraceGuardedPoolAllocator } protected: - static std::string &GetOutputBuffer() { - static std::string Buffer; - return Buffer; - } - - __attribute__((format(printf, 1, 2))) static void - PrintfToBuffer(const char *Format, ...) { - va_list AP; - va_start(AP, Format); - char Buffer[8192]; - vsnprintf(Buffer, sizeof(Buffer), Format, AP); - GetOutputBuffer() += Buffer; - va_end(AP); - } - gwp_asan::GuardedPoolAllocator GPA; - bool Recoverable; }; // https://github.com/google/googletest/blob/master/docs/advanced.md#death-tests-and-threads diff --git a/compiler-rt/lib/gwp_asan/tests/recoverable.cpp b/compiler-rt/lib/gwp_asan/tests/recoverable.cpp deleted file mode 100644 index 4ca7eb5c8cf67..0000000000000 --- a/compiler-rt/lib/gwp_asan/tests/recoverable.cpp +++ /dev/null @@ -1,222 +0,0 @@ -//===-- recoverable.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 -#include -#include -#include -#include - -#include "gwp_asan/common.h" -#include "gwp_asan/crash_handler.h" -#include "gwp_asan/tests/harness.h" - -// Optnone to ensure that the calls to these functions are not optimized away, -// as we're looking for them in the backtraces. -__attribute__((optnone)) static char * -AllocateMemory(gwp_asan::GuardedPoolAllocator &GPA) { - return static_cast(GPA.allocate(1)); -} -__attribute__((optnone)) static void -DeallocateMemory(gwp_asan::GuardedPoolAllocator &GPA, void *Ptr) { - GPA.deallocate(Ptr); -} -__attribute__((optnone)) static void -DeallocateMemory2(gwp_asan::GuardedPoolAllocator &GPA, void *Ptr) { - GPA.deallocate(Ptr); -} -__attribute__((optnone)) static 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; -} - -TEST_P(BacktraceGuardedPoolAllocator, MultipleDoubleFreeOnlyOneOutput) { - SCOPED_TRACE(""); - void *Ptr = AllocateMemory(GPA); - DeallocateMemory(GPA, Ptr); - // First time should generate a crash report. - DeallocateMemory(GPA, Ptr); - CheckOnlyOneGwpAsanCrash(GetOutputBuffer()); - ASSERT_NE(std::string::npos, GetOutputBuffer().find("Double Free")); - - // Ensure the crash is only reported once. - GetOutputBuffer().clear(); - for (size_t i = 0; i < 100; ++i) { - DeallocateMemory(GPA, Ptr); - ASSERT_TRUE(GetOutputBuffer().empty()); - } -} - -TEST_P(BacktraceGuardedPoolAllocator, MultipleInvalidFreeOnlyOneOutput) { - SCOPED_TRACE(""); - char *Ptr = static_cast(AllocateMemory(GPA)); - // First time should generate a crash report. - DeallocateMemory(GPA, Ptr + 1); - CheckOnlyOneGwpAsanCrash(GetOutputBuffer()); - ASSERT_NE(std::string::npos, GetOutputBuffer().find("Invalid (Wild) Free")); - - // Ensure the crash is only reported once. - GetOutputBuffer().clear(); - for (size_t i = 0; i < 100; ++i) { - DeallocateMemory(GPA, Ptr + 1); - ASSERT_TRUE(GetOutputBuffer().empty()); - } -} - -TEST_P(BacktraceGuardedPoolAllocator, MultipleUseAfterFreeOnlyOneOutput) { - SCOPED_TRACE(""); - void *Ptr = AllocateMemory(GPA); - DeallocateMemory(GPA, Ptr); - // First time should generate a crash report. - TouchMemory(Ptr); - ASSERT_NE(std::string::npos, GetOutputBuffer().find("Use After Free")); - - // Ensure the crash is only reported once. - GetOutputBuffer().clear(); - for (size_t i = 0; i < 100; ++i) { - TouchMemory(Ptr); - ASSERT_TRUE(GetOutputBuffer().empty()); - } -} - -TEST_P(BacktraceGuardedPoolAllocator, MultipleBufferOverflowOnlyOneOutput) { - SCOPED_TRACE(""); - char *Ptr = static_cast(AllocateMemory(GPA)); - // First time should generate a crash report. - TouchMemory(Ptr - 16); - TouchMemory(Ptr + 16); - CheckOnlyOneGwpAsanCrash(GetOutputBuffer()); - if (GetOutputBuffer().find("Buffer Overflow") == std::string::npos && - GetOutputBuffer().find("Buffer Underflow") == std::string::npos) - FAIL() << "Failed to detect buffer underflow/overflow:\n" - << GetOutputBuffer(); - - // Ensure the crash is only reported once. - GetOutputBuffer().clear(); - for (size_t i = 0; i < 100; ++i) { - TouchMemory(Ptr - 16); - TouchMemory(Ptr + 16); - ASSERT_TRUE(GetOutputBuffer().empty()) << GetOutputBuffer(); - } -} - -TEST_P(BacktraceGuardedPoolAllocator, OneDoubleFreeOneUseAfterFree) { - SCOPED_TRACE(""); - void *Ptr = AllocateMemory(GPA); - DeallocateMemory(GPA, Ptr); - // First time should generate a crash report. - DeallocateMemory(GPA, Ptr); - CheckOnlyOneGwpAsanCrash(GetOutputBuffer()); - ASSERT_NE(std::string::npos, GetOutputBuffer().find("Double Free")); - - // Ensure the crash is only reported once. - GetOutputBuffer().clear(); - for (size_t i = 0; i < 100; ++i) { - DeallocateMemory(GPA, Ptr); - ASSERT_TRUE(GetOutputBuffer().empty()); - } -} - -// We use double-free to detect that each slot can generate as single error. -// Use-after-free would also be acceptable, but buffer-overflow wouldn't be, as -// the random left/right alignment means that one right-overflow can disable -// page protections, and a subsequent left-overflow of a slot that's on the -// right hand side may not trap. -TEST_P(BacktraceGuardedPoolAllocator, OneErrorReportPerSlot) { - SCOPED_TRACE(""); - for (size_t i = 0; i < GPA.getAllocatorState()->MaxSimultaneousAllocations; - ++i) { - void *Ptr = AllocateMemory(GPA); - DeallocateMemory(GPA, Ptr); - DeallocateMemory(GPA, Ptr); - CheckOnlyOneGwpAsanCrash(GetOutputBuffer()); - ASSERT_NE(std::string::npos, GetOutputBuffer().find("Double Free")); - // Ensure the crash from this slot is only reported once. - GetOutputBuffer().clear(); - DeallocateMemory(GPA, Ptr); - ASSERT_TRUE(GetOutputBuffer().empty()); - // Reset the buffer, as we're gonna move to the next allocation. - GetOutputBuffer().clear(); - } - - // All slots should have been used. No further errors should occur. - void *Ptr = AllocateMemory(GPA); - DeallocateMemory(GPA, Ptr); - DeallocateMemory(GPA, Ptr); - ASSERT_TRUE(GetOutputBuffer().empty()); -} - -void singleAllocThrashTask(gwp_asan::GuardedPoolAllocator *GPA, - std::atomic *StartingGun, - unsigned NumIterations, unsigned Job, char *Ptr) { - while (!*StartingGun) { - // Wait for starting gun. - } - - for (unsigned i = 0; i < NumIterations; ++i) { - switch (Job) { - case 0: - DeallocateMemory(*GPA, Ptr); - break; - case 1: - DeallocateMemory(*GPA, Ptr + 1); - break; - case 2: - TouchMemory(Ptr); - break; - case 3: - TouchMemory(Ptr - 16); - TouchMemory(Ptr + 16); - break; - default: - __builtin_trap(); - } - } -} - -void runInterThreadThrashingSingleAlloc(unsigned NumIterations, - gwp_asan::GuardedPoolAllocator *GPA) { - std::atomic StartingGun{false}; - std::vector Threads; - constexpr unsigned kNumThreads = 4; - if (std::thread::hardware_concurrency() < kNumThreads) { - GTEST_SKIP() << "Not enough threads to run this test"; - } - - char *Ptr = static_cast(AllocateMemory(*GPA)); - - for (unsigned i = 0; i < kNumThreads; ++i) { - Threads.emplace_back(singleAllocThrashTask, GPA, &StartingGun, - NumIterations, i, Ptr); - } - - StartingGun = true; - - for (auto &T : Threads) - T.join(); -} - -TEST_P(BacktraceGuardedPoolAllocator, InterThreadThrashingSingleAlloc) { - SCOPED_TRACE(""); - constexpr unsigned kNumIterations = 100000; - runInterThreadThrashingSingleAlloc(kNumIterations, &GPA); - CheckOnlyOneGwpAsanCrash(GetOutputBuffer()); -} - -INSTANTIATE_TEST_SUITE_P(RecoverableTests, BacktraceGuardedPoolAllocator, - /* Recoverable */ testing::Values(true)); diff --git a/compiler-rt/lib/scudo/standalone/combined.h b/compiler-rt/lib/scudo/standalone/combined.h index 6a38761ce8b2c..37d94e39eeb76 100644 --- a/compiler-rt/lib/scudo/standalone/combined.h +++ b/compiler-rt/lib/scudo/standalone/combined.h @@ -191,7 +191,6 @@ class Allocator { getFlags()->GWP_ASAN_MaxSimultaneousAllocations; Opt.SampleRate = getFlags()->GWP_ASAN_SampleRate; Opt.InstallSignalHandlers = getFlags()->GWP_ASAN_InstallSignalHandlers; - Opt.Recoverable = getFlags()->GWP_ASAN_Recoverable; // Embedded GWP-ASan is locked through the Scudo atfork handler (via // Allocator::disable calling GWPASan.disable). Disable GWP-ASan's atfork // handler. @@ -203,8 +202,7 @@ class Allocator { gwp_asan::segv_handler::installSignalHandlers( &GuardedAlloc, Printf, gwp_asan::backtrace::getPrintBacktraceFunction(), - gwp_asan::backtrace::getSegvBacktraceFunction(), - Opt.Recoverable); + gwp_asan::backtrace::getSegvBacktraceFunction()); GuardedAllocSlotSize = GuardedAlloc.getAllocatorState()->maximumAllocationSize();