diff --git a/compiler-rt/lib/gwp_asan/CMakeLists.txt b/compiler-rt/lib/gwp_asan/CMakeLists.txt --- a/compiler-rt/lib/gwp_asan/CMakeLists.txt +++ b/compiler-rt/lib/gwp_asan/CMakeLists.txt @@ -5,6 +5,7 @@ set(GWP_ASAN_SOURCES platform_specific/guarded_pool_allocator_posix.cpp platform_specific/mutex_posix.cpp + platform_specific/utilities_posix.cpp guarded_pool_allocator.cpp random.cpp stack_trace_compressor.cpp @@ -18,6 +19,7 @@ options.inc random.h stack_trace_compressor.h + utilities.h ) # Ensure that GWP-ASan meets the delegated requirements of some supporting @@ -50,6 +52,9 @@ options.h options.inc ) +set(GWP_ASAN_CRASH_HANDLER_HEADERS + optional/crash_handler.h + options.h) set(GWP_ASAN_OPTIONS_PARSER_CFLAGS ${GWP_ASAN_CFLAGS} @@ -94,6 +99,11 @@ SOURCES optional/backtrace_linux_libc.cpp ADDITIONAL_HEADERS ${GWP_ASAN_BACKTRACE_HEADERS} CFLAGS ${GWP_ASAN_CFLAGS}) + add_compiler_rt_object_libraries(RTGwpAsanCrashHandler + ARCHS ${GWP_ASAN_SUPPORTED_ARCH} + SOURCES optional/crash_handler_posix.cpp + ADDITIONAL_HEADERS ${GWP_ASAN_CRASH_HANDLER_HEADERS} + CFLAGS ${GWP_ASAN_CFLAGS}) add_compiler_rt_object_libraries(RTGwpAsanBacktraceSanitizerCommon ARCHS ${GWP_ASAN_SUPPORTED_ARCH} SOURCES optional/backtrace_sanitizer_common.cpp diff --git a/compiler-rt/lib/gwp_asan/crash_handler_interface.h b/compiler-rt/lib/gwp_asan/crash_handler_interface.h new file mode 100644 --- /dev/null +++ b/compiler-rt/lib/gwp_asan/crash_handler_interface.h @@ -0,0 +1,135 @@ +//===-- crash_handler_interface.h -------------------------------*- 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 +// +//===----------------------------------------------------------------------===// + +// This file contains interface functions that can be called by an in-process or +// out-of-process crash handler after the process has terminated. Functions in +// this interface are never thread safe. For an in-process crash handler, the +// handler should call GuardedPoolAllocator::disable() to stop any other threads +// from retrieving new GWP-ASan allocations, which may corrupt the metadata. +#ifndef GWP_ASAN_INTERFACE_H_ +#define GWP_ASAN_INTERFACE_H_ + +#include "gwp_asan/guarded_pool_allocator.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// When a process crashes, there are three possible outcomes: +// 1. The crash is unrelated to GWP-ASan - in which case this function returns +// false. +// 2. The crash is internally detected within GWP-ASan itself (e.g. a +// double-free bug is caught in GuardedPoolAllocator::deallocate(), and +// GWP-ASan will terminate the process). In this case - this function +// returns true. +// 3. The crash is caused by a memory error at `AccessPtr` that's caught by the +// system, but GWP-ASan is responsible for the allocation. In this case - +// the function also returns true. +// This function takes an optional `AccessPtr` parameter. If the pointer that +// was attempted to be accessed is available, you should provide it here. In the +// case of some internally-detected errors, the crash may manifest as an abort +// or trap that doesn't have an associated pointer. In these cases, the pointer +// can be obtained by a call to __gwp_asan_get_internal_crash_address. +bool __gwp_asan_error_is_mine(const gwp_asan::AllocatorState *State, + uintptr_t AccessPtr = 0u); + +// Diagnose and return the type of error that occurred at `ErrorPtr`. If +// `ErrorPtr` is unrelated to GWP-ASan, or if the error type cannot be deduced, +// this function returns Error::UNKNOWN. +gwp_asan::Error +__gwp_asan_diagnose_error(const gwp_asan::AllocatorState *State, + const gwp_asan::AllocationMetadata *Metadata, + uintptr_t ErrorPtr); + +// 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); + +// +---------------------------------------------------------------------------+ +// | Error Information Functions | +// +---------------------------------------------------------------------------+ +// Functions below return information about the type of error that was caught by +// GWP-ASan, or information about the allocation that caused the error. These +// functions generally take an `ErrorPtr` argument. This pointer can be either +// the address of the access that caused the fault, or the internally-detected +// crash address returned by __gwp_asan_get_internal_crash_address(). + +// Returns the pointer to the start of the allocation that caused the error at +// `ErrorPtr`. This function may not be called if +// `__gwp_asan_diagnose_error(ErrorPtr) == Error::UNKNOWN`. +uintptr_t +__gwp_asan_get_allocation_address(const gwp_asan::AllocatorState *State, + const gwp_asan::AllocationMetadata *Metadata, + uintptr_t ErrorPtr); + +// Returns the size of the allocation (in bytes) that caused the error at +// `ErrorPtr`. This function may not be called if +// `__gwp_asan_diagnose_error(ErrorPtr) == Error::UNKNOWN`. +size_t +__gwp_asan_get_allocation_size(const gwp_asan::AllocatorState *State, + const gwp_asan::AllocationMetadata *Metadata, + uintptr_t ErrorPtr); + +// Returns the Thread ID that allocated the memory that caused the error at +// `ErrorPtr`. This function may not be called if +// `__gwp_asan_diagnose_error(ErrorPtr) == Error::UNKNOWN`. +uint64_t __gwp_asan_get_allocation_thread_id( + const gwp_asan::AllocatorState *State, + const gwp_asan::AllocationMetadata *Metadata, uintptr_t ErrorPtr); + +// Retrieve the allocation trace for the allocation that caused the error at +// `ErrorPtr`, and place it into the provided `Buffer` that has at least +// `BufferLen` elements. This function returns the number of frames that would +// have been written into `Buffer` if the space was available (i.e. however many +// frames were stored by GWP-ASan). A return value greater than `BufferLen` +// indicates that the trace was truncated when storing to `Buffer`. This +// function may not be called if `__gwp_asan_diagnose_error(ErrorPtr) == +// Error::UNKNOWN`. +size_t +__gwp_asan_get_allocation_trace(const gwp_asan::AllocatorState *State, + const gwp_asan::AllocationMetadata *Metadata, + uintptr_t ErrorPtr, uintptr_t *Buffer, + size_t BufferLen); + +// Returns whether the allocation that caused the error at `ErrorPtr` has been +// deallocated. This function may not be called if +// `__gwp_asan_diagnose_error(ErrorPtr) == Error::UNKNOWN`. +bool __gwp_asan_is_deallocated(const gwp_asan::AllocatorState *State, + const gwp_asan::AllocationMetadata *Metadata, + uintptr_t ErrorPtr); + +// Returns the Thread ID that deallocated the memory that caused the error at +// `ErrorPtr`. This function may not be called if +// `__gwp_asan_diagnose_error(ErrorPtr) == Error::UNKNOWN` or +// __gwp_asan_is_deallocated(ErrorPtr) returns false. +uint64_t __gwp_asan_get_deallocation_thread_id( + const gwp_asan::AllocatorState *State, + const gwp_asan::AllocationMetadata *Metadata, uintptr_t ErrorPtr); + +// Retrieve the deallocation trace for the allocation that caused the error at +// `ErrorPtr`, and place it into the provided `Buffer` that has at least +// `BufferLen` elements. This function returns the number of frames that would +// have been written into `Buffer` if the space was available (i.e. however many +// frames were stored by GWP-ASan). A return value greater than `BufferLen` +// indicates that the trace was truncated when storing to `Buffer`. This +// function may not be called if `__gwp_asan_diagnose_error(ErrorPtr) == +// Error::UNKNOWN` or __gwp_asan_is_deallocated(ErrorPtr) returns false. +size_t +__gwp_asan_get_deallocation_trace(const gwp_asan::AllocatorState *State, + const gwp_asan::AllocationMetadata *Metadata, + uintptr_t ErrorPtr, uintptr_t *Buffer, + size_t BufferLen); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // GWP_ASAN_INTERFACE_H_ 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 @@ -19,6 +19,105 @@ #include namespace gwp_asan { +enum class Error { + UNKNOWN, + USE_AFTER_FREE, + DOUBLE_FREE, + INVALID_FREE, + BUFFER_OVERFLOW, + BUFFER_UNDERFLOW +}; + +const char *ErrorToString(const Error &E); + +// This struct contains all the metadata recorded about a single allocation made +// by GWP-ASan. If `AllocationMetadata.Addr` is zero, the metadata is non-valid. +struct AllocationMetadata { + static constexpr uint64_t kInvalidThreadID = UINT64_MAX; + + // The number of bytes used to store a compressed stack frame. On 64-bit + // platforms, assuming a compression ratio of 50%, this should allow us to + // store ~64 frames per trace. + static constexpr size_t kStackFrameStorageBytes = 256; + + // Maximum number of stack frames to collect on allocation/deallocation. The + // actual number of collected frames may be less than this as the stack + // frames are compressed into a fixed memory range. + static constexpr size_t kMaxTraceLengthToCollect = 128; + + // Records the given allocation metadata into this struct. + void RecordAllocation(uintptr_t Addr, size_t Size); + // Record that this allocation is now deallocated. + void RecordDeallocation(); + + struct CallSiteInfo { + // Record the current backtrace to this callsite. + void RecordBacktrace(options::Backtrace_t Backtrace); + + // The compressed backtrace to the allocation/deallocation. + uint8_t CompressedTrace[kStackFrameStorageBytes]; + // The thread ID for this trace, or kInvalidThreadID if not available. + uint64_t ThreadID = kInvalidThreadID; + // The size of the compressed trace (in bytes). Zero indicates that no + // trace was collected. + size_t TraceSize = 0; + }; + + // The address of this allocation. If zero, the rest of this struct isn't + // valid, as the allocation has never occurred. + uintptr_t Addr = 0; + // Represents the actual size of the allocation. + size_t Size = 0; + + CallSiteInfo AllocationTrace; + CallSiteInfo DeallocationTrace; + + // Whether this allocation has been deallocated yet. + bool IsDeallocated = false; +}; + +// This holds the state that's shared between the GWP-ASan allocator and the +// crash handler. This, in conjunction with the Metadata array, forms the entire +// set of information required for understanding a GWP-ASan crash. +struct AllocatorState { + // Returns whether the provided pointer is a current sampled allocation that + // is owned by this pool. + GWP_ASAN_ALWAYS_INLINE bool pointerIsMine(const void *Ptr) const { + uintptr_t P = reinterpret_cast(Ptr); + return P < GuardedPagePoolEnd && GuardedPagePool <= P; + } + + // Returns the address of the N-th guarded slot. + uintptr_t slotToAddr(size_t N) const; + + // Returns the largest allocation that is supported by this pool. + size_t maximumAllocationSize() const; + + // Gets the nearest slot to the provided address. + size_t getNearestSlot(uintptr_t Ptr) const; + + // Returns whether the provided pointer is a guard page or not. The pointer + // must be within memory owned by this pool, else the result is undefined. + bool isGuardPage(uintptr_t Ptr) const; + + // The number of guarded slots that this pool holds. + size_t MaxSimultaneousAllocations = 0; + + // Pointer to the pool of guarded slots. Note that this points to the start of + // the pool (which is a guard page), not a pointer to the first guarded page. + uintptr_t GuardedPagePool = 0; + uintptr_t GuardedPagePoolEnd = 0; + + // Cached page size for this system in bytes. + size_t PageSize = 0; + + // The type and address of an internally-detected failure. For INVALID_FREE + // and DOUBLE_FREE, these errors are detected in GWP-ASan, which will set + // these values and terminate the process. + Error FailureType = Error::UNKNOWN; + uintptr_t FailureAddress = 0; +}; + // This class is the primary implementation of the allocator portion of GWP- // ASan. It is the sole owner of the pool of sequentially allocated guarded // slots. It should always be treated as a singleton. @@ -31,57 +130,6 @@ // Name of the GWP-ASan mapping that for `Metadata`. static constexpr const char *kGwpAsanMetadataName = "GWP-ASan Metadata"; - static constexpr uint64_t kInvalidThreadID = UINT64_MAX; - - enum class Error { - UNKNOWN, - USE_AFTER_FREE, - DOUBLE_FREE, - INVALID_FREE, - BUFFER_OVERFLOW, - BUFFER_UNDERFLOW - }; - - struct AllocationMetadata { - // The number of bytes used to store a compressed stack frame. On 64-bit - // platforms, assuming a compression ratio of 50%, this should allow us to - // store ~64 frames per trace. - static constexpr size_t kStackFrameStorageBytes = 256; - - // Maximum number of stack frames to collect on allocation/deallocation. The - // actual number of collected frames may be less than this as the stack - // frames are compressed into a fixed memory range. - static constexpr size_t kMaxTraceLengthToCollect = 128; - - // Records the given allocation metadata into this struct. - void RecordAllocation(uintptr_t Addr, size_t Size, - options::Backtrace_t Backtrace); - - // Record that this allocation is now deallocated. - void RecordDeallocation(options::Backtrace_t Backtrace); - - struct CallSiteInfo { - // The compressed backtrace to the allocation/deallocation. - uint8_t CompressedTrace[kStackFrameStorageBytes]; - // The thread ID for this trace, or kInvalidThreadID if not available. - uint64_t ThreadID = kInvalidThreadID; - // The size of the compressed trace (in bytes). Zero indicates that no - // trace was collected. - size_t TraceSize = 0; - }; - - // The address of this allocation. - uintptr_t Addr = 0; - // Represents the actual size of the allocation. - size_t Size = 0; - - CallSiteInfo AllocationTrace; - CallSiteInfo DeallocationTrace; - - // Whether this allocation has been deallocated yet. - bool IsDeallocated = false; - }; - // During program startup, we must ensure that memory allocations do not land // in this allocation pool if the allocator decides to runtime-disable // GWP-ASan. The constructor value-initialises the class such that if no @@ -103,15 +151,23 @@ void init(const options::Options &Opts); void uninitTestOnly(); + // Functions exported for libmemunreachable's use on Android. disable() + // installs a lock in the allocator that prevents any thread from being able + // to allocate memory, until enable() is called. void disable(); void enable(); typedef void (*iterate_callback)(uintptr_t base, size_t size, void *arg); - // Execute the callback Cb for every allocation the lies in [Base, Base + Size). - // Must be called while the allocator is disabled. The callback can not + // Execute the callback Cb for every allocation the lies in [Base, Base + + // Size). Must be called while the allocator is disabled. The callback can not // 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". @@ -130,8 +186,7 @@ // Returns whether the provided pointer is a current sampled allocation that // is owned by this pool. GWP_ASAN_ALWAYS_INLINE bool pointerIsMine(const void *Ptr) const { - uintptr_t P = reinterpret_cast(Ptr); - return P < GuardedPagePoolEnd && GuardedPagePool <= P; + return State.pointerIsMine(Ptr); } // Allocate memory in a guarded slot, and return a pointer to the new @@ -146,22 +201,16 @@ // Returns the size of the allocation at Ptr. size_t getSize(const void *Ptr); - // Returns the largest allocation that is supported by this pool. Any - // allocations larger than this should go to the regular system allocator. - size_t maximumAllocationSize() const; - - // Dumps an error report (including allocation and deallocation stack traces). - // An optional error may be provided if the caller knows what the error is - // ahead of time. This is primarily a helper function to locate the static - // singleton pointer and call the internal version of this function. This - // method is never thread safe, and should only be called when fatal errors - // occur. - static void reportError(uintptr_t AccessPtr, Error E = Error::UNKNOWN); - // Get the current thread ID, or kInvalidThreadID if failure. Note: This // implementation is platform-specific. static uint64_t getThreadID(); + // Returns a pointer to the Metadata region, or nullptr if it doesn't exist. + const AllocationMetadata *getMetadataRegion() const { return Metadata; } + + // Returns a pointer to the AllocatorState region. + const AllocatorState *getAllocatorState() const { return &State; } + private: // Name of actively-occupied slot mappings. static constexpr const char *kGwpAsanAliveSlotName = "GWP-ASan Alive Slot"; @@ -191,35 +240,10 @@ // be called once, and the result should be cached in PageSize in this class. static size_t getPlatformPageSize(); - // Install the SIGSEGV crash handler for printing use-after-free and heap- - // buffer-{under|over}flow exceptions. This is platform specific as even - // though POSIX and Windows both support registering handlers through - // 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. - size_t addrToSlot(uintptr_t Ptr) const; - - // Returns the address of the N-th guarded slot. - uintptr_t slotToAddr(size_t N) const; - // Returns a pointer to the metadata for the owned pointer. If the pointer is // not owned by this pool, the result is undefined. AllocationMetadata *addrToMetadata(uintptr_t Ptr) const; - // Returns the address of the page that this pointer resides in. - uintptr_t getPageAddr(uintptr_t Ptr) const; - - // Gets the nearest slot to the provided address. - size_t getNearestSlot(uintptr_t Ptr) const; - - // Returns whether the provided pointer is a guard page or not. The pointer - // must be within memory owned by this pool, else the result is undefined. - bool isGuardPage(uintptr_t Ptr) const; - // Reserve a slot for a new guarded allocation. Returns kInvalidSlotID if no // slot is available to be reserved. size_t reserveSlot(); @@ -232,33 +256,24 @@ // the allocation and the options provided at init-time. uintptr_t allocationSlotOffset(size_t AllocationSize) const; - // Returns the diagnosis for an unknown error. If the diagnosis is not - // Error::INVALID_FREE or Error::UNKNOWN, the metadata for the slot - // responsible for the error is placed in *Meta. - Error diagnoseUnknownError(uintptr_t AccessPtr, AllocationMetadata **Meta); - - void reportErrorInternal(uintptr_t AccessPtr, Error E); + // 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 trapOnAddress(uintptr_t Address, Error E); static GuardedPoolAllocator *getSingleton(); // Install a pthread_atfork handler. void installAtFork(); - // Cached page size for this system in bytes. - size_t PageSize = 0; + gwp_asan::AllocatorState State; // A mutex to protect the guarded slot and metadata pool for this class. Mutex PoolMutex; - // The number of guarded slots that this pool holds. - size_t MaxSimultaneousAllocations = 0; // Record the number allocations that we've sampled. We store this amount so // that we don't randomly choose to recycle a slot that previously had an // allocation before all the slots have been utilised. size_t NumSampledAllocations = 0; - // Pointer to the pool of guarded slots. Note that this points to the start of - // the pool (which is a guard page), not a pointer to the first guarded page. - uintptr_t GuardedPagePool = 0; - uintptr_t GuardedPagePoolEnd = 0; // Pointer to the allocation metadata (allocation/deallocation stack traces), // if any. AllocationMetadata *Metadata = nullptr; @@ -271,12 +286,9 @@ // See options.{h, inc} for more information. bool PerfectlyRightAlign = false; - // Printf function supplied by the implementing allocator. We can't (in - // general) use printf() from the cstdlib as it may malloc(), causing infinite - // recursion. - options::Printf_t Printf = nullptr; + // Backtrace function provided by the supporting allocator. See `options.h` + // for more information. options::Backtrace_t Backtrace = nullptr; - options::PrintBacktrace_t PrintBacktrace = nullptr; // The adjusted sample rate for allocation sampling. Default *must* be // nonzero, as dynamic initialisation may call malloc (e.g. from libstdc++) 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 @@ -9,6 +9,8 @@ #include "gwp_asan/guarded_pool_allocator.h" #include "gwp_asan/options.h" +#include "gwp_asan/utilities.h" +#include "optional/crash_handler.h" // RHEL creates the PRIu64 format macro (for printing uint64_t's) only when this // macro is defined before including . @@ -18,13 +20,14 @@ #include #include +#include #include #include #include #include -using AllocationMetadata = gwp_asan::GuardedPoolAllocator::AllocationMetadata; -using Error = gwp_asan::GuardedPoolAllocator::Error; +using AllocationMetadata = gwp_asan::AllocationMetadata; +using Error = gwp_asan::Error; namespace gwp_asan { namespace { @@ -43,18 +46,30 @@ private: bool &Bool; }; +} // anonymous namespace -void defaultPrintStackTrace(uintptr_t *Trace, size_t TraceLength, - options::Printf_t Printf) { - if (TraceLength == 0) - Printf(" \n"); +// Weak forward-define the uninstallSignalHandlers() function. This allows us to +// unconditionally uninstall signal handlers here, even if the optional crash +// handler isn't linked. +__attribute__((weak)) void crash_handler::uninstallSignalHandlers() {} - for (size_t i = 0; i < TraceLength; ++i) { - Printf(" #%zu 0x%zx in \n", i, Trace[i]); +const char *ErrorToString(const Error &E) { + switch (E) { + case Error::UNKNOWN: + return "Unknown"; + case Error::USE_AFTER_FREE: + return "Use After Free"; + case Error::DOUBLE_FREE: + return "Double Free"; + case Error::INVALID_FREE: + return "Invalid (Wild) Free"; + case Error::BUFFER_OVERFLOW: + return "Buffer Overflow"; + case Error::BUFFER_UNDERFLOW: + return "Buffer Underflow"; } - Printf("\n"); + __builtin_trap(); } -} // anonymous namespace // Gets the singleton implementation of this class. Thread-compatible until // init() is called, thread-safe afterwards. @@ -62,46 +77,34 @@ return SingletonPtr; } -void GuardedPoolAllocator::AllocationMetadata::RecordAllocation( - uintptr_t AllocAddr, size_t AllocSize, options::Backtrace_t Backtrace) { +void AllocationMetadata::RecordAllocation(uintptr_t AllocAddr, + size_t AllocSize) { Addr = AllocAddr; Size = AllocSize; IsDeallocated = false; - // TODO(hctim): Ask the caller to provide the thread ID, so we don't waste - // other thread's time getting the thread ID under lock. - AllocationTrace.ThreadID = getThreadID(); - AllocationTrace.TraceSize = 0; + AllocationTrace.ThreadID = GuardedPoolAllocator::getThreadID(); DeallocationTrace.TraceSize = 0; DeallocationTrace.ThreadID = kInvalidThreadID; +} - if (Backtrace) { - uintptr_t UncompressedBuffer[kMaxTraceLengthToCollect]; - size_t BacktraceLength = - Backtrace(UncompressedBuffer, kMaxTraceLengthToCollect); - AllocationTrace.TraceSize = compression::pack( - UncompressedBuffer, BacktraceLength, AllocationTrace.CompressedTrace, - kStackFrameStorageBytes); - } +void AllocationMetadata::RecordDeallocation() { + IsDeallocated = true; + DeallocationTrace.ThreadID = GuardedPoolAllocator::getThreadID(); } -void GuardedPoolAllocator::AllocationMetadata::RecordDeallocation( +void AllocationMetadata::CallSiteInfo::RecordBacktrace( options::Backtrace_t Backtrace) { - IsDeallocated = true; - // Ensure that the unwinder is not called if the recursive flag is set, - // otherwise non-reentrant unwinders may deadlock. - DeallocationTrace.TraceSize = 0; - if (Backtrace && !ThreadLocals.RecursiveGuard) { - ScopedBoolean B(ThreadLocals.RecursiveGuard); - - uintptr_t UncompressedBuffer[kMaxTraceLengthToCollect]; - size_t BacktraceLength = - Backtrace(UncompressedBuffer, kMaxTraceLengthToCollect); - DeallocationTrace.TraceSize = compression::pack( - UncompressedBuffer, BacktraceLength, DeallocationTrace.CompressedTrace, - kStackFrameStorageBytes); - } - DeallocationTrace.ThreadID = getThreadID(); + TraceSize = 0; + if (!Backtrace) + return; + + uintptr_t UncompressedBuffer[kMaxTraceLengthToCollect]; + size_t BacktraceLength = + Backtrace(UncompressedBuffer, kMaxTraceLengthToCollect); + TraceSize = + compression::pack(UncompressedBuffer, BacktraceLength, CompressedTrace, + AllocationMetadata::kStackFrameStorageBytes); } void GuardedPoolAllocator::init(const options::Options &Opts) { @@ -112,47 +115,32 @@ Opts.MaxSimultaneousAllocations == 0) return; - if (Opts.SampleRate < 0) { - Opts.Printf("GWP-ASan Error: SampleRate is < 0.\n"); - exit(EXIT_FAILURE); - } - - if (Opts.SampleRate > INT32_MAX) { - Opts.Printf("GWP-ASan Error: SampleRate is > 2^31.\n"); - exit(EXIT_FAILURE); - } - - if (Opts.MaxSimultaneousAllocations < 0) { - Opts.Printf("GWP-ASan Error: MaxSimultaneousAllocations is < 0.\n"); - exit(EXIT_FAILURE); - } + Check(Opts.SampleRate >= 0, "GWP-ASan Error: SampleRate is < 0."); + Check(Opts.SampleRate <= INT32_MAX, "GWP-ASan Error: SampleRate is > 2^31."); + Check(Opts.MaxSimultaneousAllocations >= 0, + "GWP-ASan Error: MaxSimultaneousAllocations is < 0."); SingletonPtr = this; + Backtrace = Opts.Backtrace; - MaxSimultaneousAllocations = Opts.MaxSimultaneousAllocations; + State.MaxSimultaneousAllocations = Opts.MaxSimultaneousAllocations; - PageSize = getPlatformPageSize(); + State.PageSize = getPlatformPageSize(); PerfectlyRightAlign = Opts.PerfectlyRightAlign; - Printf = Opts.Printf; - Backtrace = Opts.Backtrace; - if (Opts.PrintBacktrace) - PrintBacktrace = Opts.PrintBacktrace; - else - PrintBacktrace = defaultPrintStackTrace; size_t PoolBytesRequired = - PageSize * (1 + MaxSimultaneousAllocations) + - MaxSimultaneousAllocations * maximumAllocationSize(); + State.PageSize * (1 + State.MaxSimultaneousAllocations) + + State.MaxSimultaneousAllocations * State.maximumAllocationSize(); void *GuardedPoolMemory = mapMemory(PoolBytesRequired, kGwpAsanGuardPageName); - size_t BytesRequired = MaxSimultaneousAllocations * sizeof(*Metadata); + size_t BytesRequired = State.MaxSimultaneousAllocations * sizeof(*Metadata); Metadata = reinterpret_cast( mapMemory(BytesRequired, kGwpAsanMetadataName)); markReadWrite(Metadata, BytesRequired, kGwpAsanMetadataName); // Allocate memory and set up the free pages queue. - BytesRequired = MaxSimultaneousAllocations * sizeof(*FreeSlots); + BytesRequired = State.MaxSimultaneousAllocations * sizeof(*FreeSlots); FreeSlots = reinterpret_cast( mapMemory(BytesRequired, kGwpAsanFreeSlotsName)); markReadWrite(FreeSlots, BytesRequired, kGwpAsanFreeSlotsName); @@ -164,16 +152,10 @@ else AdjustedSampleRatePlusOne = 2; - GuardedPagePool = reinterpret_cast(GuardedPoolMemory); - GuardedPagePoolEnd = + State.GuardedPagePool = reinterpret_cast(GuardedPoolMemory); + State.GuardedPagePoolEnd = reinterpret_cast(GuardedPoolMemory) + PoolBytesRequired; - // Ensure that signal handlers are installed as late as possible, as the class - // is not thread-safe until init() is finished, and thus a SIGSEGV may cause a - // race to members if received during init(). - if (Opts.InstallSignalHandlers) - installSignalHandlers(); - if (Opts.InstallForkHandlers) installAtFork(); } @@ -185,7 +167,7 @@ void GuardedPoolAllocator::iterate(void *Base, size_t Size, iterate_callback Cb, void *Arg) { uintptr_t Start = reinterpret_cast(Base); - for (size_t i = 0; i < MaxSimultaneousAllocations; ++i) { + for (size_t i = 0; i < State.MaxSimultaneousAllocations; ++i) { const AllocationMetadata &Meta = Metadata[i]; if (Meta.Addr && !Meta.IsDeallocated && Meta.Addr >= Start && Meta.Addr < Start + Size) @@ -194,29 +176,35 @@ } void GuardedPoolAllocator::uninitTestOnly() { - if (GuardedPagePool) { - unmapMemory(reinterpret_cast(GuardedPagePool), - GuardedPagePoolEnd - GuardedPagePool, kGwpAsanGuardPageName); - GuardedPagePool = 0; - GuardedPagePoolEnd = 0; + if (State.GuardedPagePool) { + unmapMemory(reinterpret_cast(State.GuardedPagePool), + State.GuardedPagePoolEnd - State.GuardedPagePool, + kGwpAsanGuardPageName); + State.GuardedPagePool = 0; + State.GuardedPagePoolEnd = 0; } if (Metadata) { - unmapMemory(Metadata, MaxSimultaneousAllocations * sizeof(*Metadata), + unmapMemory(Metadata, State.MaxSimultaneousAllocations * sizeof(*Metadata), kGwpAsanMetadataName); Metadata = nullptr; } if (FreeSlots) { - unmapMemory(FreeSlots, MaxSimultaneousAllocations * sizeof(*FreeSlots), + unmapMemory(FreeSlots, + State.MaxSimultaneousAllocations * sizeof(*FreeSlots), kGwpAsanFreeSlotsName); FreeSlots = nullptr; } - uninstallSignalHandlers(); + crash_handler::uninstallSignalHandlers(); +} + +static uintptr_t getPageAddr(uintptr_t Ptr, uintptr_t PageSize) { + return Ptr & ~(PageSize - 1); } void *GuardedPoolAllocator::allocate(size_t Size) { // GuardedPagePoolEnd == 0 when GWP-ASan is disabled. If we are disabled, fall // back to the supporting allocator. - if (GuardedPagePoolEnd == 0) + if (State.GuardedPagePoolEnd == 0) return nullptr; // Protect against recursivity. @@ -224,7 +212,7 @@ return nullptr; ScopedBoolean SB(ThreadLocals.RecursiveGuard); - if (Size == 0 || Size > maximumAllocationSize()) + if (Size == 0 || Size > State.maximumAllocationSize()) return nullptr; size_t Index; @@ -236,29 +224,47 @@ if (Index == kInvalidSlotID) return nullptr; - uintptr_t Ptr = slotToAddr(Index); + uintptr_t Ptr = State.slotToAddr(Index); Ptr += allocationSlotOffset(Size); AllocationMetadata *Meta = addrToMetadata(Ptr); // If a slot is multiple pages in size, and the allocation takes up a single // page, we can improve overflow detection by leaving the unused pages as // unmapped. - markReadWrite(reinterpret_cast(getPageAddr(Ptr)), Size, - kGwpAsanAliveSlotName); + markReadWrite(reinterpret_cast(getPageAddr(Ptr, State.PageSize)), + Size, kGwpAsanAliveSlotName); - Meta->RecordAllocation(Ptr, Size, Backtrace); + Meta->RecordAllocation(Ptr, Size); + Meta->AllocationTrace.RecordBacktrace(Backtrace); return reinterpret_cast(Ptr); } +void GuardedPoolAllocator::trapOnAddress(uintptr_t Address, Error E) { + State.FailureType = E; + State.FailureAddress = Address; + + // Raise a SEGV, so that the internal crash handler (that only sees SEGV) will + // be able to catch our error. + raise(SIGSEGV); + __builtin_unreachable(); +} + +void GuardedPoolAllocator::stop() { + ThreadLocals.RecursiveGuard = true; + PoolMutex.tryLock(); +} + void GuardedPoolAllocator::deallocate(void *Ptr) { assert(pointerIsMine(Ptr) && "Pointer is not mine!"); uintptr_t UPtr = reinterpret_cast(Ptr); - uintptr_t SlotStart = slotToAddr(addrToSlot(UPtr)); + size_t Slot = State.getNearestSlot(UPtr); + uintptr_t SlotStart = State.slotToAddr(Slot); AllocationMetadata *Meta = addrToMetadata(UPtr); if (Meta->Addr != UPtr) { - reportError(UPtr, Error::INVALID_FREE); - exit(EXIT_FAILURE); + // 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 @@ -266,22 +272,28 @@ { ScopedLock L(PoolMutex); if (Meta->IsDeallocated) { - reportError(UPtr, Error::DOUBLE_FREE); - exit(EXIT_FAILURE); + 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 // metadata. - Meta->RecordDeallocation(Backtrace); + Meta->RecordDeallocation(); + + // Ensure that the unwinder is not called if the recursive flag is set, + // otherwise non-reentrant unwinders may deadlock. + if (!ThreadLocals.RecursiveGuard) { + ScopedBoolean B(ThreadLocals.RecursiveGuard); + Meta->DeallocationTrace.RecordBacktrace(Backtrace); + } } - markInaccessible(reinterpret_cast(SlotStart), maximumAllocationSize(), - kGwpAsanGuardPageName); + markInaccessible(reinterpret_cast(SlotStart), + State.maximumAllocationSize(), kGwpAsanGuardPageName); // And finally, lock again to release the slot back into the pool. ScopedLock L(PoolMutex); - freeSlot(addrToSlot(UPtr)); + freeSlot(Slot); } size_t GuardedPoolAllocator::getSize(const void *Ptr) { @@ -292,28 +304,23 @@ return Meta->Size; } -size_t GuardedPoolAllocator::maximumAllocationSize() const { return PageSize; } +static AllocationMetadata *addrToMetadata(const AllocatorState *State, + AllocationMetadata *Metadata, + uintptr_t Ptr) { + return &Metadata[State->getNearestSlot(Ptr)]; +} AllocationMetadata *GuardedPoolAllocator::addrToMetadata(uintptr_t Ptr) const { - return &Metadata[addrToSlot(Ptr)]; + return gwp_asan::addrToMetadata(&State, Metadata, Ptr); } -size_t GuardedPoolAllocator::addrToSlot(uintptr_t Ptr) const { - assert(pointerIsMine(reinterpret_cast(Ptr))); - size_t ByteOffsetFromPoolStart = Ptr - GuardedPagePool; - return ByteOffsetFromPoolStart / (maximumAllocationSize() + PageSize); -} +size_t AllocatorState::maximumAllocationSize() const { return PageSize; } -uintptr_t GuardedPoolAllocator::slotToAddr(size_t N) const { +uintptr_t AllocatorState::slotToAddr(size_t N) const { return GuardedPagePool + (PageSize * (1 + N)) + (maximumAllocationSize() * N); } -uintptr_t GuardedPoolAllocator::getPageAddr(uintptr_t Ptr) const { - assert(pointerIsMine(reinterpret_cast(Ptr))); - return Ptr & ~(static_cast(PageSize) - 1); -} - -bool GuardedPoolAllocator::isGuardPage(uintptr_t Ptr) const { +bool AllocatorState::isGuardPage(uintptr_t Ptr) const { assert(pointerIsMine(reinterpret_cast(Ptr))); size_t PageOffsetFromPoolStart = (Ptr - GuardedPagePool) / PageSize; size_t PagesPerSlot = maximumAllocationSize() / PageSize; @@ -323,7 +330,7 @@ size_t GuardedPoolAllocator::reserveSlot() { // Avoid potential reuse of a slot before we have made at least a single // allocation in each slot. Helps with our use-after-free detection. - if (NumSampledAllocations < MaxSimultaneousAllocations) + if (NumSampledAllocations < State.MaxSimultaneousAllocations) return NumSampledAllocations++; if (FreeSlotsLength == 0) @@ -336,7 +343,7 @@ } void GuardedPoolAllocator::freeSlot(size_t SlotIndex) { - assert(FreeSlotsLength < MaxSimultaneousAllocations); + assert(FreeSlotsLength < State.MaxSimultaneousAllocations); FreeSlots[FreeSlotsLength++] = SlotIndex; } @@ -347,7 +354,7 @@ if (!ShouldRightAlign) return 0; - uintptr_t Offset = maximumAllocationSize(); + uintptr_t Offset = State.maximumAllocationSize(); if (!PerfectlyRightAlign) { if (Size == 3) Size = 4; @@ -360,210 +367,146 @@ return Offset; } -void GuardedPoolAllocator::reportError(uintptr_t AccessPtr, Error E) { - if (SingletonPtr) - SingletonPtr->reportErrorInternal(AccessPtr, E); +static size_t addrToSlot(const AllocatorState *State, uintptr_t Ptr) { + size_t ByteOffsetFromPoolStart = Ptr - State->GuardedPagePool; + return ByteOffsetFromPoolStart / + (State->maximumAllocationSize() + State->PageSize); } -size_t GuardedPoolAllocator::getNearestSlot(uintptr_t Ptr) const { +size_t AllocatorState::getNearestSlot(uintptr_t Ptr) const { if (Ptr <= GuardedPagePool + PageSize) return 0; if (Ptr > GuardedPagePoolEnd - PageSize) return MaxSimultaneousAllocations - 1; if (!isGuardPage(Ptr)) - return addrToSlot(Ptr); + return addrToSlot(this, Ptr); if (Ptr % PageSize <= PageSize / 2) - return addrToSlot(Ptr - PageSize); // Round down. - return addrToSlot(Ptr + PageSize); // Round up. + return addrToSlot(this, Ptr - PageSize); // Round down. + return addrToSlot(this, Ptr + PageSize); // Round up. +} + +GWP_ASAN_TLS_INITIAL_EXEC +GuardedPoolAllocator::ThreadLocalPackedVariables + GuardedPoolAllocator::ThreadLocals; +} // namespace gwp_asan + +// ============================================================================ +// Implementation of crash handler API. +// ============================================================================ +#ifdef __cplusplus +extern "C" { +#endif + +bool __gwp_asan_error_is_mine(gwp_asan::AllocatorState *State, + uintptr_t AccessPtr) { + assert(State && "State should not be nullptr."); + if (State->FailureType != Error::UNKNOWN && State->FailureAddress != 0) + return true; + + return AccessPtr < State->GuardedPagePoolEnd && + State->GuardedPagePool <= AccessPtr; } -Error GuardedPoolAllocator::diagnoseUnknownError(uintptr_t AccessPtr, - AllocationMetadata **Meta) { +uintptr_t +__gwp_asan_get_internal_crash_address(gwp_asan::AllocatorState *State) { + return State->FailureAddress; +} + +gwp_asan::Error +__gwp_asan_diagnose_error(gwp_asan::AllocatorState *State, + gwp_asan::AllocationMetadata *Metadata, + uintptr_t ErrorPtr) { + if (!__gwp_asan_error_is_mine(State, ErrorPtr)) + return Error::UNKNOWN; + + if (State->FailureType != Error::UNKNOWN) + return State->FailureType; + // Let's try and figure out what the source of this error is. - if (isGuardPage(AccessPtr)) { - size_t Slot = getNearestSlot(AccessPtr); - AllocationMetadata *SlotMeta = addrToMetadata(slotToAddr(Slot)); + if (State->isGuardPage(ErrorPtr)) { + size_t Slot = State->getNearestSlot(ErrorPtr); + AllocationMetadata *SlotMeta = + addrToMetadata(State, Metadata, State->slotToAddr(Slot)); // Ensure that this slot was allocated once upon a time. if (!SlotMeta->Addr) return Error::UNKNOWN; - *Meta = SlotMeta; - if (SlotMeta->Addr < AccessPtr) + if (SlotMeta->Addr < ErrorPtr) return Error::BUFFER_OVERFLOW; return Error::BUFFER_UNDERFLOW; } // Access wasn't a guard page, check for use-after-free. - AllocationMetadata *SlotMeta = addrToMetadata(AccessPtr); + AllocationMetadata *SlotMeta = addrToMetadata(State, Metadata, ErrorPtr); if (SlotMeta->IsDeallocated) { - *Meta = SlotMeta; return Error::USE_AFTER_FREE; } - // If we have reached here, the error is still unknown. There is no metadata - // available. - *Meta = nullptr; + // If we have reached here, the error is still unknown. return Error::UNKNOWN; } -namespace { -// Prints the provided error and metadata information. -void printErrorType(Error E, uintptr_t AccessPtr, AllocationMetadata *Meta, - options::Printf_t Printf, uint64_t ThreadID) { - // Print using intermediate strings. Platforms like Android don't like when - // you print multiple times to the same line, as there may be a newline - // appended to a log file automatically per Printf() call. - const char *ErrorString; - switch (E) { - case Error::UNKNOWN: - ErrorString = "GWP-ASan couldn't automatically determine the source of " - "the memory error. It was likely caused by a wild memory " - "access into the GWP-ASan pool. The error occurred"; - break; - case Error::USE_AFTER_FREE: - ErrorString = "Use after free"; - break; - case Error::DOUBLE_FREE: - ErrorString = "Double free"; - break; - case Error::INVALID_FREE: - ErrorString = "Invalid (wild) free"; - break; - case Error::BUFFER_OVERFLOW: - ErrorString = "Buffer overflow"; - break; - case Error::BUFFER_UNDERFLOW: - ErrorString = "Buffer underflow"; - break; - } - - constexpr size_t kDescriptionBufferLen = 128; - char DescriptionBuffer[kDescriptionBufferLen]; - if (Meta) { - if (E == Error::USE_AFTER_FREE) { - snprintf(DescriptionBuffer, kDescriptionBufferLen, - "(%zu byte%s into a %zu-byte allocation at 0x%zx)", - AccessPtr - Meta->Addr, (AccessPtr - Meta->Addr == 1) ? "" : "s", - Meta->Size, Meta->Addr); - } else if (AccessPtr < Meta->Addr) { - snprintf(DescriptionBuffer, kDescriptionBufferLen, - "(%zu byte%s to the left of a %zu-byte allocation at 0x%zx)", - Meta->Addr - AccessPtr, (Meta->Addr - AccessPtr == 1) ? "" : "s", - Meta->Size, Meta->Addr); - } else if (AccessPtr > Meta->Addr) { - snprintf(DescriptionBuffer, kDescriptionBufferLen, - "(%zu byte%s to the right of a %zu-byte allocation at 0x%zx)", - AccessPtr - Meta->Addr, (AccessPtr - Meta->Addr == 1) ? "" : "s", - Meta->Size, Meta->Addr); - } else { - snprintf(DescriptionBuffer, kDescriptionBufferLen, - "(a %zu-byte allocation)", Meta->Size); - } - } - - // Possible number of digits of a 64-bit number: ceil(log10(2^64)) == 20. Add - // a null terminator, and round to the nearest 8-byte boundary. - constexpr size_t kThreadBufferLen = 24; - char ThreadBuffer[kThreadBufferLen]; - if (ThreadID == GuardedPoolAllocator::kInvalidThreadID) - snprintf(ThreadBuffer, kThreadBufferLen, ""); - else - snprintf(ThreadBuffer, kThreadBufferLen, "%" PRIu64, ThreadID); - - Printf("%s at 0x%zx %s by thread %s here:\n", ErrorString, AccessPtr, - DescriptionBuffer, ThreadBuffer); +uintptr_t +__gwp_asan_get_allocation_address(gwp_asan::AllocatorState *State, + gwp_asan::AllocationMetadata *Metadata, + uintptr_t ErrorPtr) { + AllocationMetadata *Meta = addrToMetadata(State, Metadata, ErrorPtr); + return Meta->Addr; } -void printAllocDeallocTraces(uintptr_t AccessPtr, AllocationMetadata *Meta, - options::Printf_t Printf, - options::PrintBacktrace_t PrintBacktrace) { - assert(Meta != nullptr && "Metadata is non-null for printAllocDeallocTraces"); - - if (Meta->IsDeallocated) { - if (Meta->DeallocationTrace.ThreadID == - GuardedPoolAllocator::kInvalidThreadID) - Printf("0x%zx was deallocated by thread here:\n", AccessPtr); - else - Printf("0x%zx was deallocated by thread %zu here:\n", AccessPtr, - Meta->DeallocationTrace.ThreadID); - - uintptr_t UncompressedTrace[AllocationMetadata::kMaxTraceLengthToCollect]; - size_t UncompressedLength = compression::unpack( - Meta->DeallocationTrace.CompressedTrace, - Meta->DeallocationTrace.TraceSize, UncompressedTrace, - AllocationMetadata::kMaxTraceLengthToCollect); - - PrintBacktrace(UncompressedTrace, UncompressedLength, Printf); - } - - if (Meta->AllocationTrace.ThreadID == GuardedPoolAllocator::kInvalidThreadID) - Printf("0x%zx was allocated by thread here:\n", Meta->Addr); - else - Printf("0x%zx was allocated by thread %zu here:\n", Meta->Addr, - Meta->AllocationTrace.ThreadID); - - uintptr_t UncompressedTrace[AllocationMetadata::kMaxTraceLengthToCollect]; - size_t UncompressedLength = compression::unpack( - Meta->AllocationTrace.CompressedTrace, Meta->AllocationTrace.TraceSize, - UncompressedTrace, AllocationMetadata::kMaxTraceLengthToCollect); - - PrintBacktrace(UncompressedTrace, UncompressedLength, Printf); +size_t __gwp_asan_get_allocation_size(gwp_asan::AllocatorState *State, + gwp_asan::AllocationMetadata *Metadata, + uintptr_t ErrorPtr) { + AllocationMetadata *Meta = addrToMetadata(State, Metadata, ErrorPtr); + return Meta->Size; } -struct ScopedEndOfReportDecorator { - ScopedEndOfReportDecorator(options::Printf_t Printf) : Printf(Printf) {} - ~ScopedEndOfReportDecorator() { Printf("*** End GWP-ASan report ***\n"); } - options::Printf_t Printf; -}; -} // anonymous namespace - -void GuardedPoolAllocator::reportErrorInternal(uintptr_t AccessPtr, Error E) { - if (!pointerIsMine(reinterpret_cast(AccessPtr))) { - return; - } - - // Attempt to prevent races to re-use the same slot that triggered this error. - // This does not guarantee that there are no races, because another thread can - // take the locks during the time that the signal handler is being called. - PoolMutex.tryLock(); - ThreadLocals.RecursiveGuard = true; - - Printf("*** GWP-ASan detected a memory error ***\n"); - ScopedEndOfReportDecorator Decorator(Printf); +uint64_t +__gwp_asan_get_allocation_thread_id(gwp_asan::AllocatorState *State, + gwp_asan::AllocationMetadata *Metadata, + uintptr_t ErrorPtr) { + AllocationMetadata *Meta = addrToMetadata(State, Metadata, ErrorPtr); + return Meta->AllocationTrace.ThreadID; +} - AllocationMetadata *Meta = nullptr; +size_t __gwp_asan_get_allocation_trace(gwp_asan::AllocatorState *State, + gwp_asan::AllocationMetadata *Metadata, + uintptr_t ErrorPtr, uintptr_t *Buffer, + size_t BufferLen) { + AllocationMetadata *Meta = addrToMetadata(State, Metadata, ErrorPtr); + return gwp_asan::compression::unpack(Meta->AllocationTrace.CompressedTrace, + Meta->AllocationTrace.TraceSize, Buffer, + BufferLen); +} - if (E == Error::UNKNOWN) { - E = diagnoseUnknownError(AccessPtr, &Meta); - } else { - size_t Slot = getNearestSlot(AccessPtr); - Meta = addrToMetadata(slotToAddr(Slot)); - // Ensure that this slot has been previously allocated. - if (!Meta->Addr) - Meta = nullptr; - } +bool __gwp_asan_is_deallocated(gwp_asan::AllocatorState *State, + gwp_asan::AllocationMetadata *Metadata, + uintptr_t ErrorPtr) { + AllocationMetadata *Meta = addrToMetadata(State, Metadata, ErrorPtr); + return Meta->IsDeallocated; +} - // Print the error information. - uint64_t ThreadID = getThreadID(); - printErrorType(E, AccessPtr, Meta, Printf, ThreadID); - if (Backtrace) { - static constexpr unsigned kMaximumStackFramesForCrashTrace = 512; - uintptr_t Trace[kMaximumStackFramesForCrashTrace]; - size_t TraceLength = Backtrace(Trace, kMaximumStackFramesForCrashTrace); - - PrintBacktrace(Trace, TraceLength, Printf); - } else { - Printf(" \n\n"); - } +uint64_t +__gwp_asan_get_deallocation_thread_id(gwp_asan::AllocatorState *State, + gwp_asan::AllocationMetadata *Metadata, + uintptr_t ErrorPtr) { + AllocationMetadata *Meta = addrToMetadata(State, Metadata, ErrorPtr); + return Meta->DeallocationTrace.ThreadID; +} - if (Meta) - printAllocDeallocTraces(AccessPtr, Meta, Printf, PrintBacktrace); +size_t __gwp_asan_get_deallocation_trace(gwp_asan::AllocatorState *State, + gwp_asan::AllocationMetadata *Metadata, + uintptr_t ErrorPtr, uintptr_t *Buffer, + size_t BufferLen) { + AllocationMetadata *Meta = addrToMetadata(State, Metadata, ErrorPtr); + return gwp_asan::compression::unpack(Meta->DeallocationTrace.CompressedTrace, + Meta->DeallocationTrace.TraceSize, + Buffer, BufferLen); } -GWP_ASAN_TLS_INITIAL_EXEC -GuardedPoolAllocator::ThreadLocalPackedVariables - GuardedPoolAllocator::ThreadLocals; -} // namespace gwp_asan +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/compiler-rt/lib/gwp_asan/optional/backtrace.h b/compiler-rt/lib/gwp_asan/optional/backtrace.h --- a/compiler-rt/lib/gwp_asan/optional/backtrace.h +++ b/compiler-rt/lib/gwp_asan/optional/backtrace.h @@ -10,6 +10,7 @@ #define GWP_ASAN_OPTIONAL_BACKTRACE_H_ #include "gwp_asan/options.h" +#include "gwp_asan/optional/crash_handler.h" namespace gwp_asan { namespace options { @@ -21,7 +22,7 @@ // note any thread-safety descriptions for the implementation of these functions // that you use. Backtrace_t getBacktraceFunction(); -PrintBacktrace_t getPrintBacktraceFunction(); +crash_handler::PrintBacktrace_t getPrintBacktraceFunction(); } // namespace options } // namespace gwp_asan diff --git a/compiler-rt/lib/gwp_asan/optional/backtrace_linux_libc.cpp b/compiler-rt/lib/gwp_asan/optional/backtrace_linux_libc.cpp --- a/compiler-rt/lib/gwp_asan/optional/backtrace_linux_libc.cpp +++ b/compiler-rt/lib/gwp_asan/optional/backtrace_linux_libc.cpp @@ -24,7 +24,7 @@ } static void PrintBacktrace(uintptr_t *Trace, size_t TraceLength, - gwp_asan::options::Printf_t Printf) { + gwp_asan::crash_handler::Printf_t Printf) { if (TraceLength == 0) { Printf(" \n\n"); return; @@ -49,6 +49,8 @@ namespace gwp_asan { namespace options { Backtrace_t getBacktraceFunction() { return Backtrace; } -PrintBacktrace_t getPrintBacktraceFunction() { return PrintBacktrace; } +crash_handler::PrintBacktrace_t getPrintBacktraceFunction() { + return PrintBacktrace; +} } // namespace options } // namespace gwp_asan diff --git a/compiler-rt/lib/gwp_asan/optional/backtrace_sanitizer_common.cpp b/compiler-rt/lib/gwp_asan/optional/backtrace_sanitizer_common.cpp --- a/compiler-rt/lib/gwp_asan/optional/backtrace_sanitizer_common.cpp +++ b/compiler-rt/lib/gwp_asan/optional/backtrace_sanitizer_common.cpp @@ -45,7 +45,7 @@ } static void PrintBacktrace(uintptr_t *Trace, size_t TraceLength, - gwp_asan::options::Printf_t Printf) { + gwp_asan::crash_handler::Printf_t Printf) { __sanitizer::StackTrace StackTrace; StackTrace.trace = reinterpret_cast<__sanitizer::uptr *>(Trace); StackTrace.size = TraceLength; @@ -73,6 +73,8 @@ __sanitizer::InitializeCommonFlags(); return Backtrace; } -PrintBacktrace_t getPrintBacktraceFunction() { return PrintBacktrace; } +crash_handler::PrintBacktrace_t getPrintBacktraceFunction() { + return PrintBacktrace; +} } // namespace options } // namespace gwp_asan diff --git a/compiler-rt/lib/gwp_asan/optional/crash_handler.h b/compiler-rt/lib/gwp_asan/optional/crash_handler.h new file mode 100644 --- /dev/null +++ b/compiler-rt/lib/gwp_asan/optional/crash_handler.h @@ -0,0 +1,81 @@ +//===-- crash_handler.h -----------------------------------------*- 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 +// +//===----------------------------------------------------------------------===// + +#ifndef GWP_ASAN_OPTIONAL_CRASH_HANDLER_H_ +#define GWP_ASAN_OPTIONAL_CRASH_HANDLER_H_ + +#include "gwp_asan/guarded_pool_allocator.h" +#include "gwp_asan/options.h" + +namespace gwp_asan { +namespace crash_handler { +// ================================ Requirements =============================== +// This function must be provided by the supporting allocator only when this +// provided crash handler is used to dump the generic report. +// sanitizer::Printf() function can be simply used here. +// ================================ Description ================================ +// This function shall produce output according to a strict subset of the C +// standard library's printf() family. This function must support printing the +// following formats: +// 1. integers: "%([0-9]*)?(z|ll)?{d,u,x,X}" +// 2. pointers: "%p" +// 3. strings: "%[-]([0-9]*)?(\\.\\*)?s" +// 4. chars: "%c" +// This function must be implemented in a signal-safe manner, and thus must not +// malloc(). +// =================================== Notes =================================== +// This function has a slightly different signature than the C standard +// library's printf(). Notably, it returns 'void' rather than 'int'. +typedef void (*Printf_t)(const char *Format, ...); + +// ================================ Requirements =============================== +// This function is required for the supporting allocator, but one of the three +// provided implementations may be used (RTGwpAsanBacktraceLibc, +// RTGwpAsanBacktraceSanitizerCommon, or BasicPrintBacktraceFunction). +// ================================ Description ================================ +// This function shall take the backtrace provided in `TraceBuffer`, and print +// it in a human-readable format using `Print`. Generally, this function shall +// resolve raw pointers to section offsets and print them with the following +// sanitizer-common format: +// " #{frame_number} {pointer} in {function name} ({binary name}+{offset}" +// e.g. " #5 0x420459 in _start (/tmp/uaf+0x420459)" +// This format allows the backtrace to be symbolized offline successfully using +// llvm-symbolizer. +// =================================== Notes =================================== +// This function may directly or indirectly call malloc(), as the +// GuardedPoolAllocator contains a reentrancy barrier to prevent infinite +// recursion. Any allocation made inside this function will be served by the +// supporting allocator, and will not have GWP-ASan protections. +typedef void (*PrintBacktrace_t)(uintptr_t *TraceBuffer, size_t TraceLength, + Printf_t Print); + +// Returns a function pointer to a basic PrintBacktrace implementation. This +// implementation simply prints the stack trace in a human readable fashion +// without any symbolization. +PrintBacktrace_t getBasicPrintBacktraceFunction(); + +// Install the SIGSEGV crash handler for printing use-after-free and heap- +// buffer-{under|over}flow exceptions if the user asked for it. This is platform +// specific as even though POSIX and Windows both support registering handlers +// through signal(), we have to use platform-specific signal handlers to obtain +// the address that caused the SIGSEGV exception. GPA->init() must be called +// before this function. +void installSignalHandlers(gwp_asan::GuardedPoolAllocator *GPA, Printf_t Printf, + PrintBacktrace_t PrintBacktrace, + options::Backtrace_t Backtrace); + +void uninstallSignalHandlers(); + +void dumpReport(uintptr_t ErrorPtr, const gwp_asan::AllocatorState *State, + const gwp_asan::AllocationMetadata *Metadata, + options::Backtrace_t Backtrace, Printf_t Printf, + PrintBacktrace_t PrintBacktrace); +} // namespace crash_handler +} // namespace gwp_asan + +#endif // GWP_ASAN_OPTIONAL_CRASH_HANDLER_H_ diff --git a/compiler-rt/lib/gwp_asan/optional/crash_handler_posix.cpp b/compiler-rt/lib/gwp_asan/optional/crash_handler_posix.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/lib/gwp_asan/optional/crash_handler_posix.cpp @@ -0,0 +1,219 @@ +//===-- crash_handler_posix.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/crash_handler_interface.h" +#include "gwp_asan/guarded_pool_allocator.h" +#include "gwp_asan/optional/crash_handler.h" +#include "gwp_asan/options.h" + +#include +#include +#include +#include + +namespace { +using gwp_asan::AllocationMetadata; +using gwp_asan::Error; +using gwp_asan::GuardedPoolAllocator; +using gwp_asan::crash_handler::PrintBacktrace_t; +using gwp_asan::crash_handler::Printf_t; +using gwp_asan::options::Backtrace_t; + +struct sigaction PreviousHandler; +bool SignalHandlerInstalled; +gwp_asan::GuardedPoolAllocator *GPAForSignalHandler; +Printf_t PrintfForSignalHandler; +PrintBacktrace_t PrintBacktraceForSignalHandler; +Backtrace_t BacktraceForSignalHandler; + +static void sigSegvHandler(int sig, siginfo_t *info, void *ucontext) { + if (GPAForSignalHandler) { + GPAForSignalHandler->stop(); + + gwp_asan::crash_handler::dumpReport( + reinterpret_cast(info->si_addr), + GPAForSignalHandler->getAllocatorState(), + GPAForSignalHandler->getMetadataRegion(), BacktraceForSignalHandler, + PrintfForSignalHandler, PrintBacktraceForSignalHandler); + } + + // Process any previous handlers. + if (PreviousHandler.sa_flags & SA_SIGINFO) { + PreviousHandler.sa_sigaction(sig, info, ucontext); + } else if (PreviousHandler.sa_handler == SIG_IGN || + PreviousHandler.sa_handler == SIG_DFL) { + // If the previous handler was the default handler, or was ignoring this + // signal, install the default handler and re-raise the signal in order to + // get a core dump and terminate this process. + signal(SIGSEGV, SIG_DFL); + raise(SIGSEGV); + } else { + PreviousHandler.sa_handler(sig); + } +} + +struct ScopedEndOfReportDecorator { + ScopedEndOfReportDecorator(gwp_asan::crash_handler::Printf_t Printf) + : Printf(Printf) {} + ~ScopedEndOfReportDecorator() { Printf("*** End GWP-ASan report ***\n"); } + gwp_asan::crash_handler::Printf_t Printf; +}; + +// Prints the provided error and metadata information. +void printHeader(Error E, uintptr_t AccessPtr, + const gwp_asan::AllocatorState *State, + const gwp_asan::AllocationMetadata *Metadata, + Printf_t Printf) { + // Print using intermediate strings. Platforms like Android don't like when + // you print multiple times to the same line, as there may be a newline + // appended to a log file automatically per Printf() call. + constexpr size_t kDescriptionBufferLen = 128; + char DescriptionBuffer[kDescriptionBufferLen]; + if (E != Error::UNKNOWN) { + uintptr_t Address = + __gwp_asan_get_allocation_address(State, Metadata, AccessPtr); + size_t Size = __gwp_asan_get_allocation_size(State, Metadata, AccessPtr); + if (E == Error::USE_AFTER_FREE) { + snprintf(DescriptionBuffer, kDescriptionBufferLen, + "(%zu byte%s into a %zu-byte allocation at 0x%zx) ", + AccessPtr - Address, (AccessPtr - Address == 1) ? "" : "s", Size, + Address); + } else if (AccessPtr < Address) { + snprintf(DescriptionBuffer, kDescriptionBufferLen, + "(%zu byte%s to the left of a %zu-byte allocation at 0x%zx) ", + Address - AccessPtr, (Address - AccessPtr == 1) ? "" : "s", Size, + Address); + } else if (AccessPtr > Address) { + snprintf(DescriptionBuffer, kDescriptionBufferLen, + "(%zu byte%s to the right of a %zu-byte allocation at 0x%zx) ", + AccessPtr - Address, (AccessPtr - Address == 1) ? "" : "s", Size, + Address); + } else { + snprintf(DescriptionBuffer, kDescriptionBufferLen, + "(a %zu-byte allocation) ", Size); + } + } + + // Possible number of digits of a 64-bit number: ceil(log10(2^64)) == 20. Add + // a null terminator, and round to the nearest 8-byte boundary. + uint64_t ThreadID = GuardedPoolAllocator::getThreadID(); + constexpr size_t kThreadBufferLen = 24; + char ThreadBuffer[kThreadBufferLen]; + if (ThreadID == AllocationMetadata::kInvalidThreadID) + snprintf(ThreadBuffer, kThreadBufferLen, ""); + else + snprintf(ThreadBuffer, kThreadBufferLen, "%" PRIu64, ThreadID); + + Printf("%s at 0x%zx %sby thread %s here:\n", gwp_asan::ErrorToString(E), + AccessPtr, DescriptionBuffer, ThreadBuffer); +} + +void defaultPrintStackTrace(uintptr_t *Trace, size_t TraceLength, + gwp_asan::crash_handler::Printf_t Printf) { + if (TraceLength == 0) + Printf(" \n"); + + for (size_t i = 0; i < TraceLength; ++i) { + Printf(" #%zu 0x%zx in \n", i, Trace[i]); + } + Printf("\n"); +} + +} // anonymous namespace + +namespace gwp_asan { +namespace crash_handler { +PrintBacktrace_t getBasicPrintBacktraceFunction() { + return defaultPrintStackTrace; +} + +void installSignalHandlers(gwp_asan::GuardedPoolAllocator *GPA, Printf_t Printf, + PrintBacktrace_t PrintBacktrace, + options::Backtrace_t Backtrace) { + GPAForSignalHandler = GPA; + PrintfForSignalHandler = Printf; + PrintBacktraceForSignalHandler = PrintBacktrace; + BacktraceForSignalHandler = Backtrace; + + struct sigaction Action; + Action.sa_sigaction = sigSegvHandler; + Action.sa_flags = SA_SIGINFO; + sigaction(SIGSEGV, &Action, &PreviousHandler); + SignalHandlerInstalled = true; +} + +void uninstallSignalHandlers() { + if (SignalHandlerInstalled) { + sigaction(SIGSEGV, &PreviousHandler, nullptr); + SignalHandlerInstalled = false; + } +} + +void dumpReport(uintptr_t ErrorPtr, const gwp_asan::AllocatorState *State, + const gwp_asan::AllocationMetadata *Metadata, + options::Backtrace_t Backtrace, Printf_t Printf, + PrintBacktrace_t PrintBacktrace) { + assert(State && "dumpReport missing Allocator State."); + assert(Metadata && "dumpReport missing Metadata."); + assert(Printf && "dumpReport missing Printf."); + + 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); + + // Print the error header. + printHeader(E, ErrorPtr, State, Metadata, Printf); + + // Print the fault backtrace. + static constexpr unsigned kMaximumStackFramesForCrashTrace = 512; + uintptr_t Trace[kMaximumStackFramesForCrashTrace]; + size_t TraceLength = Backtrace(Trace, kMaximumStackFramesForCrashTrace); + + PrintBacktrace(Trace, TraceLength, Printf); + + 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"); + return; + } + + // Maybe print the deallocation trace. + if (__gwp_asan_is_deallocated(State, Metadata, ErrorPtr)) { + uint64_t ThreadID = + __gwp_asan_get_deallocation_thread_id(State, Metadata, ErrorPtr); + if (ThreadID == AllocationMetadata::kInvalidThreadID) + Printf("0x%zx was deallocated by thread here:\n", ErrorPtr); + else + Printf("0x%zx was deallocated by thread %zu here:\n", ErrorPtr, ThreadID); + TraceLength = __gwp_asan_get_deallocation_trace( + State, Metadata, ErrorPtr, Trace, kMaximumStackFramesForCrashTrace); + PrintBacktrace(Trace, TraceLength, Printf); + } + + // Print the allocation trace. + uint64_t ThreadID = + __gwp_asan_get_allocation_thread_id(State, Metadata, ErrorPtr); + if (ThreadID == AllocationMetadata::kInvalidThreadID) + Printf("0x%zx was allocated by thread here:\n", ErrorPtr); + else + Printf("0x%zx was allocated by thread %zu here:\n", ErrorPtr, ThreadID); + TraceLength = __gwp_asan_get_allocation_trace( + State, Metadata, ErrorPtr, Trace, kMaximumStackFramesForCrashTrace); + PrintBacktrace(Trace, TraceLength, Printf); +} +} // namespace crash_handler +} // namespace gwp_asan diff --git a/compiler-rt/lib/gwp_asan/optional/options_parser.cpp b/compiler-rt/lib/gwp_asan/optional/options_parser.cpp --- a/compiler-rt/lib/gwp_asan/optional/options_parser.cpp +++ b/compiler-rt/lib/gwp_asan/optional/options_parser.cpp @@ -83,8 +83,6 @@ "GWP-ASan ERROR: SampleRate must be > 0 when GWP-ASan is enabled.\n"); exit(EXIT_FAILURE); } - - o->Printf = __sanitizer::Printf; } Options &getOptions() { return *getOptionsInternal(); } diff --git a/compiler-rt/lib/gwp_asan/options.h b/compiler-rt/lib/gwp_asan/options.h --- a/compiler-rt/lib/gwp_asan/options.h +++ b/compiler-rt/lib/gwp_asan/options.h @@ -14,23 +14,6 @@ namespace gwp_asan { namespace options { -// ================================ Requirements =============================== -// This function is required to be implemented by the supporting allocator. The -// sanitizer::Printf() function can be simply used here. -// ================================ Description ================================ -// This function shall produce output according to a strict subset of the C -// standard library's printf() family. This function must support printing the -// following formats: -// 1. integers: "%([0-9]*)?(z|ll)?{d,u,x,X}" -// 2. pointers: "%p" -// 3. strings: "%[-]([0-9]*)?(\\.\\*)?s" -// 4. chars: "%c" -// This function must be implemented in a signal-safe manner. -// =================================== Notes =================================== -// This function has a slightly different signature than the C standard -// library's printf(). Notably, it returns 'void' rather than 'int'. -typedef void (*Printf_t)(const char *Format, ...); - // ================================ Requirements =============================== // This function is required to be either implemented by the supporting // allocator, or one of the two provided implementations may be used @@ -50,32 +33,8 @@ // supporting allocator, and will not have GWP-ASan protections. typedef size_t (*Backtrace_t)(uintptr_t *TraceBuffer, size_t Size); -// ================================ Requirements =============================== -// This function is optional for the supporting allocator, but one of the two -// provided implementations may be used (RTGwpAsanBacktraceLibc or -// RTGwpAsanBacktraceSanitizerCommon). If not provided, a default implementation -// is used which prints the raw pointers only. -// ================================ Description ================================ -// This function shall take the backtrace provided in `TraceBuffer`, and print -// it in a human-readable format using `Print`. Generally, this function shall -// resolve raw pointers to section offsets and print them with the following -// sanitizer-common format: -// " #{frame_number} {pointer} in {function name} ({binary name}+{offset}" -// e.g. " #5 0x420459 in _start (/tmp/uaf+0x420459)" -// This format allows the backtrace to be symbolized offline successfully using -// llvm-symbolizer. -// =================================== Notes =================================== -// This function may directly or indirectly call malloc(), as the -// GuardedPoolAllocator contains a reentrancy barrier to prevent infinite -// recursion. Any allocation made inside this function will be served by the -// supporting allocator, and will not have GWP-ASan protections. -typedef void (*PrintBacktrace_t)(uintptr_t *TraceBuffer, size_t TraceLength, - Printf_t Print); - struct Options { - Printf_t Printf = nullptr; Backtrace_t Backtrace = nullptr; - PrintBacktrace_t PrintBacktrace = nullptr; // Read the options from the included definitions file. #define GWP_ASAN_OPTION(Type, Name, DefaultValue, Description) \ @@ -89,9 +48,7 @@ #include "gwp_asan/options.inc" #undef GWP_ASAN_OPTION - Printf = nullptr; Backtrace = nullptr; - PrintBacktrace = nullptr; } }; } // namespace options diff --git a/compiler-rt/lib/gwp_asan/options.inc b/compiler-rt/lib/gwp_asan/options.inc --- a/compiler-rt/lib/gwp_asan/options.inc +++ b/compiler-rt/lib/gwp_asan/options.inc @@ -30,6 +30,13 @@ "selected for GWP-ASan sampling. Default is 5000. Sample rates " "up to (2^31 - 1) are supported.") +// Developer note - This option is not actually processed by GWP-ASan itself. It +// is included here so that a user can specify whether they want signal handlers +// or not. The supporting allocator should inspect this value to see whether +// signal handlers need to be installed, and then use +// crash_handler::installSignalHandlers() in order to install the handlers. Note +// that in order to support signal handlers, you will need to link against the +// optional crash_handler component. GWP_ASAN_OPTION( bool, InstallSignalHandlers, true, "Install GWP-ASan signal handlers for SIGSEGV during dynamic loading. This " 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 @@ -7,7 +7,9 @@ //===----------------------------------------------------------------------===// #include "gwp_asan/guarded_pool_allocator.h" +#include "gwp_asan/utilities.h" +#include #include #include #include @@ -35,36 +37,22 @@ void *GuardedPoolAllocator::mapMemory(size_t Size, const char *Name) const { void *Ptr = mmap(nullptr, Size, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); - - if (Ptr == MAP_FAILED) { - Printf("Failed to map guarded pool allocator memory, errno: %d\n", errno); - Printf(" mmap(nullptr, %zu, ...) failed.\n", Size); - exit(EXIT_FAILURE); - } + Check(Ptr != MAP_FAILED, "Failed to map guarded pool allocator memory"); MaybeSetMappingName(Ptr, Size, Name); return Ptr; } void GuardedPoolAllocator::unmapMemory(void *Ptr, size_t Size, const char *Name) const { - int Res = munmap(Ptr, Size); - - if (Res != 0) { - Printf("Failed to unmap guarded pool allocator memory, errno: %d\n", errno); - Printf(" unmmap(%p, %zu, ...) failed.\n", Ptr, Size); - exit(EXIT_FAILURE); - } + Check(munmap(Ptr, Size) == 0, + "Failed to unmap guarded pool allocator memory."); MaybeSetMappingName(Ptr, Size, Name); } void GuardedPoolAllocator::markReadWrite(void *Ptr, size_t Size, const char *Name) const { - if (mprotect(Ptr, Size, PROT_READ | PROT_WRITE) != 0) { - Printf("Failed to set guarded pool allocator memory at as RW, errno: %d\n", - errno); - Printf(" mprotect(%p, %zu, RW) failed.\n", Ptr, Size); - exit(EXIT_FAILURE); - } + Check(mprotect(Ptr, Size, PROT_READ | PROT_WRITE) == 0, + "Failed to set guarded pool allocator memory at as RW."); MaybeSetMappingName(Ptr, Size, Name); } @@ -73,14 +61,9 @@ // mmap() a PROT_NONE page over the address to release it to the system, if // we used mprotect() here the system would count pages in the quarantine // against the RSS. - if (mmap(Ptr, Size, PROT_NONE, MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE, -1, - 0) == MAP_FAILED) { - Printf("Failed to set guarded pool allocator memory as inaccessible, " - "errno: %d\n", - errno); - Printf(" mmap(%p, %zu, NONE, ...) failed.\n", Ptr, Size); - exit(EXIT_FAILURE); - } + Check(mmap(Ptr, Size, PROT_NONE, MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE, -1, + 0) != MAP_FAILED, + "Failed to set guarded pool allocator memory as inaccessible."); MaybeSetMappingName(Ptr, Size, Name); } @@ -88,28 +71,6 @@ return sysconf(_SC_PAGESIZE); } -struct sigaction PreviousHandler; -bool SignalHandlerInstalled; - -static void sigSegvHandler(int sig, siginfo_t *info, void *ucontext) { - gwp_asan::GuardedPoolAllocator::reportError( - reinterpret_cast(info->si_addr)); - - // Process any previous handlers. - if (PreviousHandler.sa_flags & SA_SIGINFO) { - PreviousHandler.sa_sigaction(sig, info, ucontext); - } else if (PreviousHandler.sa_handler == SIG_IGN || - PreviousHandler.sa_handler == SIG_DFL) { - // If the previous handler was the default handler, or was ignoring this - // signal, install the default handler and re-raise the signal in order to - // get a core dump and terminate this process. - signal(SIGSEGV, SIG_DFL); - raise(SIGSEGV); - } else { - PreviousHandler.sa_handler(sig); - } -} - void GuardedPoolAllocator::installAtFork() { auto Disable = []() { if (auto *S = getSingleton()) @@ -122,21 +83,6 @@ pthread_atfork(Disable, Enable, Enable); } -void GuardedPoolAllocator::installSignalHandlers() { - struct sigaction Action; - 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() { #ifdef SYS_gettid return syscall(SYS_gettid); diff --git a/compiler-rt/lib/gwp_asan/platform_specific/utilities_posix.cpp b/compiler-rt/lib/gwp_asan/platform_specific/utilities_posix.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/lib/gwp_asan/platform_specific/utilities_posix.cpp @@ -0,0 +1,36 @@ +//===-- utilities_posix.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/utilities.h" + +#ifdef ANDROID +#include +#include +#else // ANDROID +#include +#endif + +namespace gwp_asan { + +#ifdef ANDROID +void Check(bool Condition, const char *Message) { + if (Condition) + return; + android_set_abort_message(Message); + abort(); +} +#else // ANDROID +void Check(bool Condition, const char *Message) { + if (Condition) + return; + fprintf(stderr, "%s", Message); + __builtin_trap(); +} +#endif // ANDROID + +} // namespace gwp_asan 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 @@ -15,6 +15,7 @@ basic.cpp compression.cpp iterate.cpp + crash_handler_api.cpp driver.cpp mutex_test.cpp slot_reuse.cpp @@ -44,6 +45,7 @@ set(GWP_ASAN_TEST_RUNTIME_OBJECTS $ $ + $ $ $ $) 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 @@ -14,7 +14,7 @@ void *Ptr = GPA.allocate(1); GPA.deallocate(Ptr); - std::string DeathRegex = "Double free.*"; + std::string DeathRegex = "Double Free.*"; DeathRegex.append("backtrace\\.cpp:25.*"); DeathRegex.append("was deallocated.*"); @@ -29,7 +29,7 @@ char *Ptr = static_cast(GPA.allocate(1)); GPA.deallocate(Ptr); - std::string DeathRegex = "Use after free.*"; + std::string DeathRegex = "Use After Free.*"; DeathRegex.append("backtrace\\.cpp:40.*"); DeathRegex.append("was deallocated.*"); diff --git a/compiler-rt/lib/gwp_asan/tests/basic.cpp b/compiler-rt/lib/gwp_asan/tests/basic.cpp --- a/compiler-rt/lib/gwp_asan/tests/basic.cpp +++ b/compiler-rt/lib/gwp_asan/tests/basic.cpp @@ -24,7 +24,7 @@ TEST_F(CustomGuardedPoolAllocator, SizedAllocations) { InitNumSlots(1); - std::size_t MaxAllocSize = GPA.maximumAllocationSize(); + std::size_t MaxAllocSize = GPA.getAllocatorState()->maximumAllocationSize(); EXPECT_TRUE(MaxAllocSize > 0); for (unsigned AllocSize = 1; AllocSize <= MaxAllocSize; AllocSize <<= 1) { @@ -37,7 +37,8 @@ } TEST_F(DefaultGuardedPoolAllocator, TooLargeAllocation) { - EXPECT_EQ(nullptr, GPA.allocate(GPA.maximumAllocationSize() + 1)); + EXPECT_EQ(nullptr, + GPA.allocate(GPA.getAllocatorState()->maximumAllocationSize() + 1)); } TEST_F(CustomGuardedPoolAllocator, AllocAllSlots) { diff --git a/compiler-rt/lib/gwp_asan/tests/crash_handler_api.cpp b/compiler-rt/lib/gwp_asan/tests/crash_handler_api.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/lib/gwp_asan/tests/crash_handler_api.cpp @@ -0,0 +1,347 @@ +//===-- crash_handler_api.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 "gwp_asan/crash_handler_interface.h" +#include "gwp_asan/guarded_pool_allocator.h" +#include "gwp_asan/tests/harness.h" + +using Error = gwp_asan::Error; +using GuardedPoolAllocator = gwp_asan::GuardedPoolAllocator; +using AllocationMetadata = gwp_asan::AllocationMetadata; + +// We also disable sibling call optimisations in the build file to ensure that +// the call inside this function doesn't get optimised to a jump. +__attribute__((optnone)) void *GetAllocation(GuardedPoolAllocator *GPA, + size_t Size) { + return GPA->allocate(Size); +} +__attribute__((optnone)) void GetAllocation_End() {} + +// We also disable sibling call optimisations in the build file to ensure that +// the call inside this function doesn't get optimised to a jump. +__attribute__((optnone)) void DoDeallocate(GuardedPoolAllocator *GPA, + void *Ptr) { + GPA->deallocate(Ptr); +} +__attribute__((optnone)) void DoDeallocate_End() {} + +// It's impossible to determine the exact location of the call instruction +// within a C++ function - so we allow our frames to occur in a range. If the +// frame lies between [Start - End] (inclusive), we determine the frame to be +// found. +struct FrameRange { + FrameRange(uintptr_t Start, uintptr_t End) : Start(Start), End(End) {} + uintptr_t Start; + uintptr_t End; +}; + +// A null value (or zero), in general means "don't check this value". +struct ExpectedAPIResult { + Error ErrorType; + // There is no null value for `ErrorType`, so indicate whether we want to + // check this value or not. + bool CheckErrorType = false; + + const char *ErrorNeedle = nullptr; + + // Results from the AllocationMetadata. + bool HasValidMetadata = true; + uintptr_t AllocationAddress = 0; + size_t AllocationSize = 0; + + // Required frames below should be in order (but can be non-adjacent). If the + // required frames are [A, B], then the following matches will succeed: + // - [A, B] + // - [X, Y, A, B] + // - [A, B, X, Y] + // - [X, A, Y, B] + // - [A, A, B, A] + // and the following will fail: + // - [B, A] + // - [X, B, Y, A] + std::vector RequiredAllocationFrames; + std::vector RequiredDeallocationFrames; + + bool IsDeallocated; + bool CheckIsDeallocated = false; +}; + +const gwp_asan::AllocationMetadata *Metadata; +const gwp_asan::AllocatorState *State; + +// Create an ExpectedAPIResult that has the correct required +// allocation/deallocation frames for use with `GetAllocate()` and +// `DoDeallocate()`. Place the allocated pointer in `*Ptr`. +ExpectedAPIResult createTest(GuardedPoolAllocator *GPA, size_t Size, Error E, + void **Ptr, bool WillBeDeallocated) { + Metadata = GPA->getMetadataRegion(); + State = GPA->getAllocatorState(); + + ExpectedAPIResult Result; + Result.ErrorType = E; + Result.CheckErrorType = true; + + *Ptr = GetAllocation(GPA, Size); + EXPECT_NE(*Ptr, nullptr); + + Result.AllocationAddress = reinterpret_cast(*Ptr); + Result.AllocationSize = Size; + + Result.RequiredAllocationFrames = { + FrameRange(reinterpret_cast(&GetAllocation), + reinterpret_cast(&GetAllocation_End))}; + + if (WillBeDeallocated) { + Result.RequiredDeallocationFrames = { + FrameRange(reinterpret_cast(&DoDeallocate), + reinterpret_cast(&DoDeallocate_End))}; + Result.IsDeallocated = true; + Result.CheckIsDeallocated = true; + } else { + Result.IsDeallocated = false; + Result.CheckIsDeallocated = true; + } + return Result; +} + +typedef size_t (*get_frames_func)(const gwp_asan::AllocatorState *State, + const gwp_asan::AllocationMetadata *Metadata, + uintptr_t ErrorPtr, uintptr_t *Buffer, + size_t BufferLen); + +void CheckFrames(const std::vector &Frames, + get_frames_func getFrames, uintptr_t FaultAddress) { + uintptr_t *Buffer = static_cast( + alloca(AllocationMetadata::kMaxTraceLengthToCollect * sizeof(uintptr_t))); + + size_t NumFrames = getFrames(State, Metadata, FaultAddress, Buffer, + AllocationMetadata::kMaxTraceLengthToCollect); + + EXPECT_GE(NumFrames, Frames.size()); + + size_t FramesMatched = 0; + for (size_t i = 0; i < NumFrames && FramesMatched < Frames.size(); ++i) { + if (Buffer[i] >= Frames[FramesMatched].Start && + Buffer[i] <= Frames[FramesMatched].End) + ++FramesMatched; + } + + EXPECT_EQ(FramesMatched, Frames.size()); +} + +ExpectedAPIResult *ExpectedResult = nullptr; +sigjmp_buf SigJumpBuf; +bool CrashHandlerExecuted; + +void CheckExpectedResult(uintptr_t FaultAddress) { + assert(ExpectedResult && "ExpectedAPIResult should be non-null"); + CrashHandlerExecuted = true; + + Error Err = __gwp_asan_diagnose_error(State, Metadata, FaultAddress); + + if (ExpectedResult->CheckErrorType) { + EXPECT_EQ(ExpectedResult->ErrorType, Err); + + uintptr_t InternalFaultAddress = + __gwp_asan_get_internal_crash_address(State); + if (Err == Error::DOUBLE_FREE || Err == Error::INVALID_FREE) { + EXPECT_NE(0u, InternalFaultAddress); + FaultAddress = InternalFaultAddress; + } + } + + if (ExpectedResult->ErrorNeedle) { + EXPECT_NE(nullptr, strstr(ErrorToString(Err), ExpectedResult->ErrorNeedle)) + << "Expected ErrorNeedle not found: \"" << ExpectedResult->ErrorNeedle + << "\" in string: \"" << ErrorToString(Err) << "\""; + } + + AllocationMetadata Meta; + EXPECT_EQ(ExpectedResult->HasValidMetadata, Err != Error::UNKNOWN); + + if (!ExpectedResult->HasValidMetadata) + return; + + if (ExpectedResult->AllocationAddress) + EXPECT_EQ(__gwp_asan_get_allocation_address(State, Metadata, FaultAddress), + ExpectedResult->AllocationAddress); + if (ExpectedResult->AllocationSize) + EXPECT_EQ(__gwp_asan_get_allocation_size(State, Metadata, FaultAddress), + ExpectedResult->AllocationSize); + + if (!ExpectedResult->RequiredAllocationFrames.empty()) + CheckFrames(ExpectedResult->RequiredAllocationFrames, + __gwp_asan_get_allocation_trace, FaultAddress); + + if (!ExpectedResult->RequiredDeallocationFrames.empty()) + CheckFrames(ExpectedResult->RequiredDeallocationFrames, + __gwp_asan_get_deallocation_trace, FaultAddress); + + if (ExpectedResult->CheckIsDeallocated) + EXPECT_EQ(__gwp_asan_is_deallocated(State, Metadata, FaultAddress), + ExpectedResult->IsDeallocated); +} + +void segvHandler(int sig, siginfo_t *info, void *ucontext) { + CheckExpectedResult(reinterpret_cast(info->si_addr)); + siglongjmp(SigJumpBuf, 1); +} + +void CheckAPIOnCrash(ExpectedAPIResult *APIResult) { + CrashHandlerExecuted = false; + ExpectedResult = APIResult; + struct sigaction Action; + Action.sa_sigaction = segvHandler; + Action.sa_flags = SA_SIGINFO; + sigaction(SIGSEGV, &Action, nullptr); +} + +TEST_F(NoSignalHandlerGuardedPoolAllocator, UnknownErrorIfNotAllocated) { + void *Ptr = malloc(1); + while (GPA.pointerIsMine(Ptr)) { + free(Ptr); + Ptr = malloc(1); + } + + uintptr_t IntPtr = reinterpret_cast(Ptr); + + const gwp_asan::AllocatorState *State = GPA.getAllocatorState(); + + EXPECT_FALSE(__gwp_asan_error_is_mine(State, 0)); + EXPECT_FALSE(__gwp_asan_error_is_mine(State, IntPtr)); + + EXPECT_EQ(0u, __gwp_asan_get_internal_crash_address(State)); + + EXPECT_EQ(Error::UNKNOWN, __gwp_asan_diagnose_error(State, Metadata, 0)); + EXPECT_EQ(Error::UNKNOWN, __gwp_asan_diagnose_error(State, Metadata, IntPtr)); +} + +TEST_F(NoSignalHandlerGuardedPoolAllocator, DoubleFree) { + void *Ptr; + ExpectedAPIResult ExpectedResult = createTest( + &GPA, 1, Error::DOUBLE_FREE, &Ptr, /* WillBeDeallocated */ true); + ExpectedResult.ErrorNeedle = "Double Free"; + CheckAPIOnCrash(&ExpectedResult); + + DoDeallocate(&GPA, Ptr); + if (!sigsetjmp(SigJumpBuf, 1)) + DoDeallocate(&GPA, Ptr); + EXPECT_TRUE(CrashHandlerExecuted); +} + +TEST_F(NoSignalHandlerGuardedPoolAllocator, InvalidFreeCheckAlign) { + void *Ptr; + ExpectedAPIResult ExpectedResult = createTest( + &GPA, 1, Error::INVALID_FREE, &Ptr, /* WillBeDeallocated */ false); + ExpectedResult.ErrorNeedle = "Invalid (Wild) Free"; + CheckAPIOnCrash(&ExpectedResult); + if (!sigsetjmp(SigJumpBuf, 1)) + DoDeallocate(&GPA, static_cast(Ptr) + 1); + EXPECT_TRUE(CrashHandlerExecuted); +} + +TEST_F(NoSignalHandlerGuardedPoolAllocator, InvalidFreeCheckSize) { + void *Ptr; + ExpectedAPIResult ExpectedResult = createTest( + &GPA, 1, Error::INVALID_FREE, &Ptr, /* WillBeDeallocated */ false); + CheckAPIOnCrash(&ExpectedResult); + if (!sigsetjmp(SigJumpBuf, 1)) + DoDeallocate(&GPA, static_cast(Ptr) + 1); + EXPECT_TRUE(CrashHandlerExecuted); +} + +TEST_F(NoSignalHandlerGuardedPoolAllocator, UseAfterFree) { + void *Ptr; + ExpectedAPIResult ExpectedResult = createTest( + &GPA, 1, Error::USE_AFTER_FREE, &Ptr, /* WillBeDeallocated */ true); + ExpectedResult.ErrorNeedle = "Use After Free"; + CheckAPIOnCrash(&ExpectedResult); + + DoDeallocate(&GPA, Ptr); + if (!sigsetjmp(SigJumpBuf, 1)) + *(static_cast(Ptr)) = 0; + EXPECT_TRUE(CrashHandlerExecuted); +} + +constexpr unsigned kMaximumAlignmentPadding = 16; + +TEST_F(NoSignalHandlerGuardedPoolAllocator, BufferOverflow) { + void *Ptr; + constexpr unsigned kAllocationSize = 10; + ExpectedAPIResult ExpectedResult = + createTest(&GPA, kAllocationSize, Error::BUFFER_OVERFLOW, &Ptr, + /* WillBeDeallocated */ false); + + uintptr_t IntPtr = reinterpret_cast(Ptr); + // Keep trying until we have a right-aligned allocation. + while (IntPtr % GPA.getAllocatorState()->maximumAllocationSize() < + kMaximumAlignmentPadding) { + GPA.deallocate(Ptr); + Ptr = GetAllocation(&GPA, kAllocationSize); + EXPECT_NE(Ptr, nullptr); + IntPtr = reinterpret_cast(Ptr); + ExpectedResult.AllocationAddress = IntPtr; + } + + ExpectedResult.ErrorNeedle = "Buffer Overflow"; + CheckAPIOnCrash(&ExpectedResult); + + if (!sigsetjmp(SigJumpBuf, 1)) + *(static_cast(Ptr) + kMaximumAlignmentPadding) = 0; + EXPECT_TRUE(CrashHandlerExecuted); +} + +TEST_F(NoSignalHandlerGuardedPoolAllocator, BufferUnderflow) { + void *Ptr; + constexpr unsigned kAllocationSize = 10; + ExpectedAPIResult ExpectedResult = + createTest(&GPA, kAllocationSize, Error::BUFFER_UNDERFLOW, &Ptr, + /* WillBeDeallocated */ false); + + uintptr_t IntPtr = reinterpret_cast(Ptr); + // Keep trying until we have a left-aligned allocation. + while (IntPtr % GPA.getAllocatorState()->maximumAllocationSize() > + kMaximumAlignmentPadding) { + GPA.deallocate(Ptr); + Ptr = GetAllocation(&GPA, kAllocationSize); + EXPECT_NE(Ptr, nullptr); + IntPtr = reinterpret_cast(Ptr); + ExpectedResult.AllocationAddress = IntPtr; + } + + ExpectedResult.ErrorNeedle = "Buffer Underflow"; + CheckAPIOnCrash(&ExpectedResult); + + if (!sigsetjmp(SigJumpBuf, 1)) + *(static_cast(Ptr) - kMaximumAlignmentPadding) = 0; + EXPECT_TRUE(CrashHandlerExecuted); +} + +// Simulate an internal error (or a hardware failure) where the touched +// allocation doesn't correspond to an allocation. +TEST_F(NoSignalHandlerGuardedPoolAllocator, UnknownInternalError) { + Metadata = GPA.getMetadataRegion(); + State = GPA.getAllocatorState(); + + void *Ptr = GPA.allocate(1); + ExpectedAPIResult ExpectedResult; + ExpectedResult.ErrorType = Error::UNKNOWN; + ExpectedResult.CheckErrorType = true; + ExpectedResult.HasValidMetadata = false; + CheckAPIOnCrash(&ExpectedResult); + + if (!sigsetjmp(SigJumpBuf, 1)) + *(static_cast(Ptr) + + GPA.getAllocatorState()->maximumAllocationSize() * 2) = 0; + EXPECT_TRUE(CrashHandlerExecuted); +} 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 @@ -23,7 +23,7 @@ // their own signal-safe Printf function. In LLVM, we use // `optional/printf_sanitizer_common.cpp` which supplies the __sanitizer::Printf // for this purpose. -options::Printf_t getPrintfFunction(); +crash_handler::Printf_t getPrintfFunction(); // First call returns true, all the following calls return false. bool OnlyOnce(); @@ -38,7 +38,6 @@ Opts.setDefaults(); MaxSimultaneousAllocations = Opts.MaxSimultaneousAllocations; - Opts.Printf = gwp_asan::test::getPrintfFunction(); Opts.InstallForkHandlers = gwp_asan::test::OnlyOnce(); GPA.init(Opts); } @@ -62,7 +61,6 @@ Opts.MaxSimultaneousAllocations = MaxSimultaneousAllocationsArg; MaxSimultaneousAllocations = MaxSimultaneousAllocationsArg; - Opts.Printf = gwp_asan::test::getPrintfFunction(); Opts.InstallForkHandlers = gwp_asan::test::OnlyOnce(); GPA.init(Opts); } @@ -81,11 +79,13 @@ gwp_asan::options::Options Opts; Opts.setDefaults(); - Opts.Printf = gwp_asan::test::getPrintfFunction(); Opts.Backtrace = gwp_asan::options::getBacktraceFunction(); - Opts.PrintBacktrace = gwp_asan::options::getPrintBacktraceFunction(); Opts.InstallForkHandlers = gwp_asan::test::OnlyOnce(); GPA.init(Opts); + + gwp_asan::crash_handler::installSignalHandlers( + &GPA, gwp_asan::test::getPrintfFunction(), + gwp_asan::options::getPrintBacktraceFunction(), Opts.Backtrace); } void TearDown() override { GPA.uninitTestOnly(); } @@ -94,4 +94,19 @@ gwp_asan::GuardedPoolAllocator GPA; }; +class NoSignalHandlerGuardedPoolAllocator : public ::testing::Test { +public: + NoSignalHandlerGuardedPoolAllocator() { + gwp_asan::options::Options Opts; + Opts.setDefaults(); + Opts.InstallSignalHandlers = false; + + Opts.Backtrace = gwp_asan::options::getBacktraceFunction(); + GPA.init(Opts); + } + +protected: + gwp_asan::GuardedPoolAllocator GPA; +}; + #endif // GWP_ASAN_TESTS_HARNESS_H_ diff --git a/compiler-rt/lib/gwp_asan/tests/optional/printf_sanitizer_common.cpp b/compiler-rt/lib/gwp_asan/tests/optional/printf_sanitizer_common.cpp --- a/compiler-rt/lib/gwp_asan/tests/optional/printf_sanitizer_common.cpp +++ b/compiler-rt/lib/gwp_asan/tests/optional/printf_sanitizer_common.cpp @@ -6,8 +6,8 @@ // //===----------------------------------------------------------------------===// +#include "gwp_asan/optional/crash_handler.h" #include "sanitizer_common/sanitizer_common.h" -#include "gwp_asan/options.h" namespace gwp_asan { namespace test { @@ -15,8 +15,6 @@ // their own signal-safe Printf function. In LLVM, we use // `optional/printf_sanitizer_common.cpp` which supplies the __sanitizer::Printf // for this purpose. -options::Printf_t getPrintfFunction() { - return __sanitizer::Printf; -} +crash_handler::Printf_t getPrintfFunction() { return __sanitizer::Printf; } }; // namespace test }; // namespace gwp_asan diff --git a/compiler-rt/lib/gwp_asan/tests/thread_contention.cpp b/compiler-rt/lib/gwp_asan/tests/thread_contention.cpp --- a/compiler-rt/lib/gwp_asan/tests/thread_contention.cpp +++ b/compiler-rt/lib/gwp_asan/tests/thread_contention.cpp @@ -24,7 +24,7 @@ // Get ourselves a new allocation. for (unsigned i = 0; i < NumIterations; ++i) { volatile char *Ptr = reinterpret_cast( - GPA->allocate(GPA->maximumAllocationSize())); + GPA->allocate(GPA->getAllocatorState()->maximumAllocationSize())); // Do any other threads have access to this page? EXPECT_EQ(*Ptr, 0); diff --git a/compiler-rt/lib/gwp_asan/utilities.h b/compiler-rt/lib/gwp_asan/utilities.h new file mode 100644 --- /dev/null +++ b/compiler-rt/lib/gwp_asan/utilities.h @@ -0,0 +1,15 @@ +//===-- utilities.h ---------------------------------------------*- 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/definitions.h" + +namespace gwp_asan { +// Checks that `Condition` is true, otherwise fails in a platform-specific way +// with `Message`. +void Check(bool Condition, const char *Message); +} // namespace gwp_asan diff --git a/compiler-rt/lib/scudo/CMakeLists.txt b/compiler-rt/lib/scudo/CMakeLists.txt --- a/compiler-rt/lib/scudo/CMakeLists.txt +++ b/compiler-rt/lib/scudo/CMakeLists.txt @@ -38,7 +38,8 @@ # parsing mechanism of sanitizer_common. Once Scudo has its own flag parsing, # and parses GwpAsan options, you can remove this dependency. list(APPEND SCUDO_MINIMAL_OBJECT_LIBS RTGwpAsan RTGwpAsanOptionsParser - RTGwpAsanBacktraceLibc) + RTGwpAsanBacktraceLibc + RTGwpAsanCrashHandler) list(APPEND SCUDO_CFLAGS -DGWP_ASAN_HOOKS) endif() diff --git a/compiler-rt/lib/scudo/scudo_allocator.cpp b/compiler-rt/lib/scudo/scudo_allocator.cpp --- a/compiler-rt/lib/scudo/scudo_allocator.cpp +++ b/compiler-rt/lib/scudo/scudo_allocator.cpp @@ -674,8 +674,12 @@ gwp_asan::options::initOptions(); gwp_asan::options::Options &Opts = gwp_asan::options::getOptions(); Opts.Backtrace = gwp_asan::options::getBacktraceFunction(); - Opts.PrintBacktrace = gwp_asan::options::getPrintBacktraceFunction(); GuardedAlloc.init(Opts); + + if (Opts.InstallSignalHandlers) + gwp_asan::crash_handler::installSignalHandlers( + &GuardedAlloc, __sanitizer::Printf, + gwp_asan::options::getPrintBacktraceFunction(), Opts.Backtrace); #endif // GWP_ASAN_HOOKS } diff --git a/compiler-rt/lib/scudo/standalone/CMakeLists.txt b/compiler-rt/lib/scudo/standalone/CMakeLists.txt --- a/compiler-rt/lib/scudo/standalone/CMakeLists.txt +++ b/compiler-rt/lib/scudo/standalone/CMakeLists.txt @@ -19,6 +19,11 @@ append_list_if(COMPILER_RT_HAS_FVISIBILITY_HIDDEN_FLAG -fvisibility=hidden SCUDO_CFLAGS) +append_list_if(COMPILER_RT_HAS_OMIT_FRAME_POINTER_FLAG -fno-omit-frame-pointer + SCUDO_CFLAGS) +append_list_if(COMPILER_RT_HAS_OMIT_FRAME_POINTER_FLAG + -mno-omit-leaf-frame-pointer SCUDO_CFLAGS) + if(COMPILER_RT_DEBUG) list(APPEND SCUDO_CFLAGS -O0) else() @@ -107,7 +112,8 @@ set(SCUDO_OBJECT_LIBS) if (COMPILER_RT_HAS_GWP_ASAN) - list(APPEND SCUDO_OBJECT_LIBS RTGwpAsan) + list(APPEND SCUDO_OBJECT_LIBS + RTGwpAsan RTGwpAsanBacktraceLibc RTGwpAsanCrashHandler) list(APPEND SCUDO_CFLAGS -DGWP_ASAN_HOOKS) endif() 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 @@ -24,6 +24,8 @@ #ifdef GWP_ASAN_HOOKS #include "gwp_asan/guarded_pool_allocator.h" +#include "gwp_asan/optional/backtrace.h" +#include "gwp_asan/optional/crash_handler.h" #endif // GWP_ASAN_HOOKS extern "C" inline void EmptyCallback() {} @@ -168,8 +170,13 @@ // Allocator::disable calling GWPASan.disable). Disable GWP-ASan's atfork // handler. Opt.InstallForkHandlers = false; - Opt.Printf = Printf; + Opt.Backtrace = gwp_asan::options::getBacktraceFunction(); GuardedAlloc.init(Opt); + + if (Opt.InstallSignalHandlers) + gwp_asan::crash_handler::installSignalHandlers( + &GuardedAlloc, Printf, gwp_asan::options::getPrintBacktraceFunction(), + Opt.Backtrace); #endif // GWP_ASAN_HOOKS } 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 @@ -43,7 +43,8 @@ macro(add_scudo_unittest testname) cmake_parse_arguments(TEST "" "" "SOURCES;ADDITIONAL_RTOBJECTS" ${ARGN}) if (COMPILER_RT_HAS_GWP_ASAN) - list(APPEND TEST_ADDITIONAL_RTOBJECTS RTGwpAsan) + list(APPEND TEST_ADDITIONAL_RTOBJECTS + RTGwpAsan RTGwpAsanBacktraceLibc RTGwpAsanCrashHandler) endif() if(COMPILER_RT_HAS_SCUDO_STANDALONE) diff --git a/compiler-rt/test/gwp_asan/double_delete.cpp b/compiler-rt/test/gwp_asan/double_delete.cpp --- a/compiler-rt/test/gwp_asan/double_delete.cpp +++ b/compiler-rt/test/gwp_asan/double_delete.cpp @@ -1,9 +1,9 @@ // REQUIRES: gwp_asan // RUN: %clangxx_gwp_asan %s -o %t -// RUN: not %run %t 2>&1 | FileCheck %s +// RUN: %expect_crash %run %t 2>&1 | FileCheck %s // CHECK: GWP-ASan detected a memory error -// CHECK: Double free at 0x{{[a-f0-9]+}} (a 1-byte allocation) +// CHECK: Double Free at 0x{{[a-f0-9]+}} (a 1-byte allocation) #include diff --git a/compiler-rt/test/gwp_asan/double_deletea.cpp b/compiler-rt/test/gwp_asan/double_deletea.cpp --- a/compiler-rt/test/gwp_asan/double_deletea.cpp +++ b/compiler-rt/test/gwp_asan/double_deletea.cpp @@ -1,9 +1,9 @@ // REQUIRES: gwp_asan // RUN: %clangxx_gwp_asan %s -o %t -// RUN: not %run %t 2>&1 | FileCheck %s +// RUN: %expect_crash %run %t 2>&1 | FileCheck %s // CHECK: GWP-ASan detected a memory error -// CHECK: Double free at 0x{{[a-f0-9]+}} (a 50-byte allocation) +// CHECK: Double Free at 0x{{[a-f0-9]+}} (a 50-byte allocation) #include diff --git a/compiler-rt/test/gwp_asan/double_free.cpp b/compiler-rt/test/gwp_asan/double_free.cpp --- a/compiler-rt/test/gwp_asan/double_free.cpp +++ b/compiler-rt/test/gwp_asan/double_free.cpp @@ -1,12 +1,12 @@ // REQUIRES: gwp_asan // RUN: %clangxx_gwp_asan %s -o %t -// RUN: not %run %t 2>&1 | FileCheck %s +// RUN: %expect_crash %run %t 2>&1 | FileCheck %s #include int main() { // CHECK: GWP-ASan detected a memory error - // CHECK: Double free at 0x{{[a-f0-9]+}} (a 10-byte allocation) + // CHECK: Double Free at 0x{{[a-f0-9]+}} (a 10-byte allocation) void *Ptr = malloc(10); free(Ptr); diff --git a/compiler-rt/test/gwp_asan/heap_buffer_overflow.cpp b/compiler-rt/test/gwp_asan/heap_buffer_overflow.cpp --- a/compiler-rt/test/gwp_asan/heap_buffer_overflow.cpp +++ b/compiler-rt/test/gwp_asan/heap_buffer_overflow.cpp @@ -3,7 +3,7 @@ // RUN: %expect_crash %run %t 2>&1 | FileCheck %s // CHECK: GWP-ASan detected a memory error -// CHECK: Buffer overflow at 0x{{[a-f0-9]+}} ({{[1-9][0-9]*}} bytes to the right +// CHECK: Buffer Overflow at 0x{{[a-f0-9]+}} ({{[1-9][0-9]*}} bytes to the right // CHECK-SAME: of a {{[1-9][0-9]*}}-byte allocation #include diff --git a/compiler-rt/test/gwp_asan/heap_buffer_underflow.cpp b/compiler-rt/test/gwp_asan/heap_buffer_underflow.cpp --- a/compiler-rt/test/gwp_asan/heap_buffer_underflow.cpp +++ b/compiler-rt/test/gwp_asan/heap_buffer_underflow.cpp @@ -3,7 +3,7 @@ // RUN: %expect_crash %run %t 2>&1 | FileCheck %s // CHECK: GWP-ASan detected a memory error -// CHECK: Buffer underflow at 0x{{[a-f0-9]+}} (1 byte to the left +// CHECK: Buffer Underflow at 0x{{[a-f0-9]+}} (1 byte to the left // CHECK-SAME: of a {{[1-9][0-9]*}}-byte allocation #include diff --git a/compiler-rt/test/gwp_asan/invalid_free_left.cpp b/compiler-rt/test/gwp_asan/invalid_free_left.cpp --- a/compiler-rt/test/gwp_asan/invalid_free_left.cpp +++ b/compiler-rt/test/gwp_asan/invalid_free_left.cpp @@ -1,9 +1,9 @@ // REQUIRES: gwp_asan // RUN: %clangxx_gwp_asan %s -o %t -// RUN: not %run %t 2>&1 | FileCheck %s +// RUN: %expect_crash %run %t 2>&1 | FileCheck %s // CHECK: GWP-ASan detected a memory error -// CHECK: Invalid (wild) free at 0x{{[a-f0-9]+}} (1 byte to the left of a +// CHECK: Invalid (Wild) Free at 0x{{[a-f0-9]+}} (1 byte to the left of a // CHECK-SAME: 1-byte allocation #include diff --git a/compiler-rt/test/gwp_asan/invalid_free_right.cpp b/compiler-rt/test/gwp_asan/invalid_free_right.cpp --- a/compiler-rt/test/gwp_asan/invalid_free_right.cpp +++ b/compiler-rt/test/gwp_asan/invalid_free_right.cpp @@ -1,9 +1,9 @@ // REQUIRES: gwp_asan // RUN: %clangxx_gwp_asan %s -o %t -// RUN: not %run %t 2>&1 | FileCheck %s +// RUN: %expect_crash %run %t 2>&1 | FileCheck %s // CHECK: GWP-ASan detected a memory error -// CHECK: Invalid (wild) free at 0x{{[a-f0-9]+}} (1 byte to the right of a +// CHECK: Invalid (Wild) Free at 0x{{[a-f0-9]+}} (1 byte to the right of a // CHECK-SAME: 1-byte allocation #include diff --git a/compiler-rt/test/gwp_asan/realloc.cpp b/compiler-rt/test/gwp_asan/realloc.cpp --- a/compiler-rt/test/gwp_asan/realloc.cpp +++ b/compiler-rt/test/gwp_asan/realloc.cpp @@ -1,6 +1,6 @@ // REQUIRES: gwp_asan // RUN: %clangxx_gwp_asan %s -o %t -DTEST_MALLOC -// RUN: not %run %t 2>&1 | FileCheck %s --check-prefix CHECK-MALLOC +// RUN: %expect_crash %run %t 2>&1 | FileCheck %s --check-prefix CHECK-MALLOC // Check both C++98 and C. // RUN: %clangxx_gwp_asan -std=c++98 %s -o %t -DTEST_FREE @@ -23,7 +23,7 @@ free(Ptr + 1); // CHECK-MALLOC: GWP-ASan detected a memory error - // CHECK-MALLOC: Invalid (wild) free at 0x{{[a-f0-9]+}} (1 byte to the right + // CHECK-MALLOC: Invalid (Wild) Free at 0x{{[a-f0-9]+}} (1 byte to the right // CHECK-MALLOC-SAME: of a 1-byte allocation #elif defined(TEST_FREE) char *Ptr = (char *) malloc(1); @@ -36,7 +36,7 @@ *Ptr = 0; // CHECK-FREE: GWP-ASan detected a memory error - // CHECK-FREE: Use after free at 0x{{[a-f0-9]+}} (0 bytes into a 1-byte + // CHECK-FREE: Use After Free at 0x{{[a-f0-9]+}} (0 bytes into a 1-byte // CHECK-FREE-SAME: allocation #endif diff --git a/compiler-rt/test/gwp_asan/use_after_delete.cpp b/compiler-rt/test/gwp_asan/use_after_delete.cpp --- a/compiler-rt/test/gwp_asan/use_after_delete.cpp +++ b/compiler-rt/test/gwp_asan/use_after_delete.cpp @@ -3,7 +3,7 @@ // RUN: %expect_crash %run %t 2>&1 | FileCheck %s // CHECK: GWP-ASan detected a memory error -// CHECK: Use after free at 0x{{[a-f0-9]+}} (0 bytes into a 1-byte allocation +// CHECK: Use After Free at 0x{{[a-f0-9]+}} (0 bytes into a 1-byte allocation #include diff --git a/compiler-rt/test/gwp_asan/use_after_deletea.cpp b/compiler-rt/test/gwp_asan/use_after_deletea.cpp --- a/compiler-rt/test/gwp_asan/use_after_deletea.cpp +++ b/compiler-rt/test/gwp_asan/use_after_deletea.cpp @@ -3,7 +3,7 @@ // RUN: %expect_crash %run %t 2>&1 | FileCheck %s // CHECK: GWP-ASan detected a memory error -// CHECK: Use after free at 0x{{[a-f0-9]+}} (0 bytes into a 10-byte allocation +// CHECK: Use After Free at 0x{{[a-f0-9]+}} (0 bytes into a 10-byte allocation #include diff --git a/compiler-rt/test/gwp_asan/use_after_free.cpp b/compiler-rt/test/gwp_asan/use_after_free.cpp --- a/compiler-rt/test/gwp_asan/use_after_free.cpp +++ b/compiler-rt/test/gwp_asan/use_after_free.cpp @@ -3,7 +3,7 @@ // RUN: %expect_crash %run %t 2>&1 | FileCheck %s // CHECK: GWP-ASan detected a memory error -// CHECK: Use after free at 0x{{[a-f0-9]+}} (0 bytes into a 10-byte allocation +// CHECK: Use After Free at 0x{{[a-f0-9]+}} (0 bytes into a 10-byte allocation #include