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 @@ -3,14 +3,20 @@ include_directories(..) set(GWP_ASAN_SOURCES + common.cpp + crash_handler.cpp + platform_specific/common_posix.cpp 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 ) set(GWP_ASAN_HEADERS + common.h + crash_handler.h definitions.h guarded_pool_allocator.h mutex.h @@ -18,6 +24,7 @@ options.inc random.h stack_trace_compressor.h + utilities.h ) # Ensure that GWP-ASan meets the delegated requirements of some supporting @@ -50,6 +57,9 @@ options.h options.inc ) +set(GWP_ASAN_SEGV_HANDLER_HEADERS + optional/segv_handler.h + options.h) set(GWP_ASAN_OPTIONS_PARSER_CFLAGS ${GWP_ASAN_CFLAGS} @@ -94,6 +104,11 @@ SOURCES optional/backtrace_linux_libc.cpp ADDITIONAL_HEADERS ${GWP_ASAN_BACKTRACE_HEADERS} CFLAGS ${GWP_ASAN_CFLAGS}) + add_compiler_rt_object_libraries(RTGwpAsanSegvHandler + ARCHS ${GWP_ASAN_SUPPORTED_ARCH} + SOURCES optional/segv_handler_posix.cpp + ADDITIONAL_HEADERS ${GWP_ASAN_SEGV_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/common.h b/compiler-rt/lib/gwp_asan/common.h new file mode 100644 --- /dev/null +++ b/compiler-rt/lib/gwp_asan/common.h @@ -0,0 +1,125 @@ +//===-- common.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 code that is common between the crash handler and the +// GuardedPoolAllocator. + +#ifndef GWP_ASAN_COMMON_H_ +#define GWP_ASAN_COMMON_H_ + +#include "gwp_asan/definitions.h" +#include "gwp_asan/options.h" + +#include +#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); + +static constexpr uint64_t kInvalidThreadID = UINT64_MAX; +// Get the current thread ID, or kInvalidThreadID if failure. Note: This +// implementation is platform-specific. +uint64_t getThreadID(); + +// 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 { + // 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; +}; + +} // namespace gwp_asan +#endif // GWP_ASAN_COMMON_H_ diff --git a/compiler-rt/lib/gwp_asan/common.cpp b/compiler-rt/lib/gwp_asan/common.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/lib/gwp_asan/common.cpp @@ -0,0 +1,100 @@ +//===-- common.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/common.h" +#include "gwp_asan/stack_trace_compressor.h" + +#include + +using AllocationMetadata = gwp_asan::AllocationMetadata; +using Error = gwp_asan::Error; + +namespace gwp_asan { + +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"; + } + __builtin_trap(); +} + +void AllocationMetadata::RecordAllocation(uintptr_t AllocAddr, + size_t AllocSize) { + Addr = AllocAddr; + Size = AllocSize; + IsDeallocated = false; + + AllocationTrace.ThreadID = getThreadID(); + DeallocationTrace.TraceSize = 0; + DeallocationTrace.ThreadID = kInvalidThreadID; +} + +void AllocationMetadata::RecordDeallocation() { + IsDeallocated = true; + DeallocationTrace.ThreadID = getThreadID(); +} + +void AllocationMetadata::CallSiteInfo::RecordBacktrace( + options::Backtrace_t Backtrace) { + TraceSize = 0; + if (!Backtrace) + return; + + uintptr_t UncompressedBuffer[kMaxTraceLengthToCollect]; + size_t BacktraceLength = + Backtrace(UncompressedBuffer, kMaxTraceLengthToCollect); + TraceSize = + compression::pack(UncompressedBuffer, BacktraceLength, CompressedTrace, + AllocationMetadata::kStackFrameStorageBytes); +} + +size_t AllocatorState::maximumAllocationSize() const { return PageSize; } + +uintptr_t AllocatorState::slotToAddr(size_t N) const { + return GuardedPagePool + (PageSize * (1 + N)) + (maximumAllocationSize() * N); +} + +bool AllocatorState::isGuardPage(uintptr_t Ptr) const { + assert(pointerIsMine(reinterpret_cast(Ptr))); + size_t PageOffsetFromPoolStart = (Ptr - GuardedPagePool) / PageSize; + size_t PagesPerSlot = maximumAllocationSize() / PageSize; + return (PageOffsetFromPoolStart % (PagesPerSlot + 1)) == 0; +} + +static size_t addrToSlot(const AllocatorState *State, uintptr_t Ptr) { + size_t ByteOffsetFromPoolStart = Ptr - State->GuardedPagePool; + return ByteOffsetFromPoolStart / + (State->maximumAllocationSize() + State->PageSize); +} + +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(this, Ptr); + + if (Ptr % PageSize <= PageSize / 2) + return addrToSlot(this, Ptr - PageSize); // Round down. + return addrToSlot(this, Ptr + PageSize); // Round up. +} + +} // namespace gwp_asan diff --git a/compiler-rt/lib/gwp_asan/crash_handler.h b/compiler-rt/lib/gwp_asan/crash_handler.h new file mode 100644 --- /dev/null +++ b/compiler-rt/lib/gwp_asan/crash_handler.h @@ -0,0 +1,132 @@ +//===-- 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/common.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 may or may not 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 ErrorPtr = 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); + +// Returns a pointer to the metadata for the allocation that's responsible for +// the crash. This metadata should not be dereferenced directly due to API +// compatibility issues, but should be instead passed to functions below for +// information retrieval. Returns nullptr if there is no metadata available for +// this crash. +const gwp_asan::AllocationMetadata * +__gwp_asan_get_metadata(const gwp_asan::AllocatorState *State, + const gwp_asan::AllocationMetadata *Metadata, + uintptr_t ErrorPtr); + +// +---------------------------------------------------------------------------+ +// | 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 `AllocationMeta` argument, which should be +// retrieved via. __gwp_asan_get_metadata. + +// Returns the start of the allocation whose metadata is in `AllocationMeta`. +uintptr_t __gwp_asan_get_allocation_address( + const gwp_asan::AllocatorState *State, + const gwp_asan::AllocationMetadata *AllocationMeta); + +// Returns the size of the allocation whose metadata is in `AllocationMeta` +size_t __gwp_asan_get_allocation_size( + const gwp_asan::AllocatorState *State, + const gwp_asan::AllocationMetadata *AllocationMeta); + +// Returns the Thread ID that allocated the memory that caused the error at +// `ErrorPtr`. This function may not be called if __gwp_asan_has_metadata() +// returns false. +uint64_t __gwp_asan_get_allocation_thread_id( + const gwp_asan::AllocatorState *State, + const gwp_asan::AllocationMetadata *AllocationMeta); + +// Retrieve the allocation trace for the allocation whose metadata is in +// `AllocationMeta`, 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`. +size_t __gwp_asan_get_allocation_trace( + const gwp_asan::AllocatorState *State, + const gwp_asan::AllocationMetadata *AllocationMeta, uintptr_t *Buffer, + size_t BufferLen); + +// Returns whether the allocation whose metadata is in `AllocationMeta` has been +// deallocated. This function may not be called if __gwp_asan_has_metadata() +// returns false. +bool __gwp_asan_is_deallocated( + const gwp_asan::AllocatorState *State, + const gwp_asan::AllocationMetadata *AllocationMeta); + +// Returns the Thread ID that deallocated the memory whose metadata is in +// `AllocationMeta`. This function may not be called if +// __gwp_asan_is_deallocated() returns false. +uint64_t __gwp_asan_get_deallocation_thread_id( + const gwp_asan::AllocatorState *State, + const gwp_asan::AllocationMetadata *AllocationMeta); + +// Retrieve the deallocation trace for the allocation whose metadata is in +// `AllocationMeta`, 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_is_deallocated() returns false. +size_t __gwp_asan_get_deallocation_trace( + const gwp_asan::AllocatorState *State, + const gwp_asan::AllocationMetadata *AllocationMeta, uintptr_t *Buffer, + size_t BufferLen); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // GWP_ASAN_INTERFACE_H_ diff --git a/compiler-rt/lib/gwp_asan/crash_handler.cpp b/compiler-rt/lib/gwp_asan/crash_handler.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/lib/gwp_asan/crash_handler.cpp @@ -0,0 +1,147 @@ +//===-- crash_handler_interface.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/common.h" +#include "gwp_asan/stack_trace_compressor.h" + +#include + +using AllocationMetadata = gwp_asan::AllocationMetadata; +using Error = gwp_asan::Error; + +#ifdef __cplusplus +extern "C" { +#endif + +bool __gwp_asan_error_is_mine(const gwp_asan::AllocatorState *State, + uintptr_t ErrorPtr) { + assert(State && "State should not be nullptr."); + if (State->FailureType != Error::UNKNOWN && State->FailureAddress != 0) + return true; + + return ErrorPtr < State->GuardedPagePoolEnd && + State->GuardedPagePool <= ErrorPtr; +} + +uintptr_t +__gwp_asan_get_internal_crash_address(const gwp_asan::AllocatorState *State) { + return State->FailureAddress; +} + +static const AllocationMetadata * +addrToMetadata(const gwp_asan::AllocatorState *State, + const AllocationMetadata *Metadata, uintptr_t Ptr) { + // Note - Similar implementation in guarded_pool_allocator.cpp. + return &Metadata[State->getNearestSlot(Ptr)]; +} + +gwp_asan::Error +__gwp_asan_diagnose_error(const gwp_asan::AllocatorState *State, + const 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 (State->isGuardPage(ErrorPtr)) { + size_t Slot = State->getNearestSlot(ErrorPtr); + const AllocationMetadata *SlotMeta = + addrToMetadata(State, Metadata, State->slotToAddr(Slot)); + + // Ensure that this slot was allocated once upon a time. + if (!SlotMeta->Addr) + return Error::UNKNOWN; + + if (SlotMeta->Addr < ErrorPtr) + return Error::BUFFER_OVERFLOW; + return Error::BUFFER_UNDERFLOW; + } + + // Access wasn't a guard page, check for use-after-free. + const AllocationMetadata *SlotMeta = + addrToMetadata(State, Metadata, ErrorPtr); + if (SlotMeta->IsDeallocated) { + return Error::USE_AFTER_FREE; + } + + // If we have reached here, the error is still unknown. + return Error::UNKNOWN; +} + +const gwp_asan::AllocationMetadata * +__gwp_asan_get_metadata(const gwp_asan::AllocatorState *State, + const gwp_asan::AllocationMetadata *Metadata, + uintptr_t ErrorPtr) { + if (!__gwp_asan_error_is_mine(State, ErrorPtr)) + return nullptr; + + if (ErrorPtr >= State->GuardedPagePoolEnd || + State->GuardedPagePool > ErrorPtr) + return nullptr; + + const AllocationMetadata *Meta = addrToMetadata(State, Metadata, ErrorPtr); + if (Meta->Addr == 0) + return nullptr; + + return Meta; +} + +uintptr_t __gwp_asan_get_allocation_address( + const gwp_asan::AllocatorState *State, + const gwp_asan::AllocationMetadata *AllocationMeta) { + return AllocationMeta->Addr; +} + +size_t __gwp_asan_get_allocation_size( + const gwp_asan::AllocatorState *State, + const gwp_asan::AllocationMetadata *AllocationMeta) { + return AllocationMeta->Size; +} + +uint64_t __gwp_asan_get_allocation_thread_id( + const gwp_asan::AllocatorState *State, + const gwp_asan::AllocationMetadata *AllocationMeta) { + return AllocationMeta->AllocationTrace.ThreadID; +} + +size_t __gwp_asan_get_allocation_trace( + const gwp_asan::AllocatorState *State, + const gwp_asan::AllocationMetadata *AllocationMeta, uintptr_t *Buffer, + size_t BufferLen) { + return gwp_asan::compression::unpack( + AllocationMeta->AllocationTrace.CompressedTrace, + AllocationMeta->AllocationTrace.TraceSize, Buffer, BufferLen); +} + +bool __gwp_asan_is_deallocated( + const gwp_asan::AllocatorState *State, + const gwp_asan::AllocationMetadata *AllocationMeta) { + return AllocationMeta->IsDeallocated; +} + +uint64_t __gwp_asan_get_deallocation_thread_id( + const gwp_asan::AllocatorState *State, + const gwp_asan::AllocationMetadata *AllocationMeta) { + return AllocationMeta->DeallocationTrace.ThreadID; +} + +size_t __gwp_asan_get_deallocation_trace( + const gwp_asan::AllocatorState *State, + const gwp_asan::AllocationMetadata *AllocationMeta, uintptr_t *Buffer, + size_t BufferLen) { + return gwp_asan::compression::unpack( + AllocationMeta->DeallocationTrace.CompressedTrace, + AllocationMeta->DeallocationTrace.TraceSize, Buffer, BufferLen); +} + +#ifdef __cplusplus +} // extern "C" +#endif 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 @@ -9,6 +9,7 @@ #ifndef GWP_ASAN_GUARDED_POOL_ALLOCATOR_H_ #define GWP_ASAN_GUARDED_POOL_ALLOCATOR_H_ +#include "gwp_asan/common.h" #include "gwp_asan/definitions.h" #include "gwp_asan/mutex.h" #include "gwp_asan/options.h" @@ -31,57 +32,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 +53,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 +88,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,21 +103,11 @@ // 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); + // Returns a pointer to the Metadata region, or nullptr if it doesn't exist. + const AllocationMetadata *getMetadataRegion() const { return Metadata; } - // Get the current thread ID, or kInvalidThreadID if failure. Note: This - // implementation is platform-specific. - static uint64_t getThreadID(); + // Returns a pointer to the AllocatorState region. + const AllocatorState *getAllocatorState() const { return &State; } private: // Name of actively-occupied slot mappings. @@ -191,35 +138,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 +154,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 +184,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/segv_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,17 +46,6 @@ private: bool &Bool; }; - -void defaultPrintStackTrace(uintptr_t *Trace, size_t TraceLength, - options::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 // Gets the singleton implementation of this class. Thread-compatible until @@ -62,48 +54,6 @@ return SingletonPtr; } -void GuardedPoolAllocator::AllocationMetadata::RecordAllocation( - uintptr_t AllocAddr, size_t AllocSize, options::Backtrace_t Backtrace) { - 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; - 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 GuardedPoolAllocator::AllocationMetadata::RecordDeallocation( - 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(); -} - void GuardedPoolAllocator::init(const options::Options &Opts) { // Note: We return from the constructor here if GWP-ASan is not available. // This will stop heap-allocation of class members, as well as mmap() of the @@ -112,47 +62,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); @@ -167,16 +102,10 @@ ThreadLocals.NextSampleCounter = (getRandomUnsigned32() % (AdjustedSampleRatePlusOne - 1)) + 1; - 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(); } @@ -188,7 +117,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) @@ -197,29 +126,34 @@ } 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(); +} + +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. @@ -227,7 +161,7 @@ return nullptr; ScopedBoolean SB(ThreadLocals.RecursiveGuard); - if (Size == 0 || Size > maximumAllocationSize()) + if (Size == 0 || Size > State.maximumAllocationSize()) return nullptr; size_t Index; @@ -239,29 +173,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 by touching first guard page. + volatile char *p = reinterpret_cast(State.GuardedPagePool); + *p = 0; + __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 @@ -269,22 +221,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) { @@ -295,38 +253,14 @@ return Meta->Size; } -size_t GuardedPoolAllocator::maximumAllocationSize() const { return PageSize; } - AllocationMetadata *GuardedPoolAllocator::addrToMetadata(uintptr_t Ptr) const { - return &Metadata[addrToSlot(Ptr)]; -} - -size_t GuardedPoolAllocator::addrToSlot(uintptr_t Ptr) const { - assert(pointerIsMine(reinterpret_cast(Ptr))); - size_t ByteOffsetFromPoolStart = Ptr - GuardedPagePool; - return ByteOffsetFromPoolStart / (maximumAllocationSize() + PageSize); -} - -uintptr_t GuardedPoolAllocator::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 { - assert(pointerIsMine(reinterpret_cast(Ptr))); - size_t PageOffsetFromPoolStart = (Ptr - GuardedPagePool) / PageSize; - size_t PagesPerSlot = maximumAllocationSize() / PageSize; - return (PageOffsetFromPoolStart % (PagesPerSlot + 1)) == 0; + return &Metadata[State.getNearestSlot(Ptr)]; } 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) @@ -339,7 +273,7 @@ } void GuardedPoolAllocator::freeSlot(size_t SlotIndex) { - assert(FreeSlotsLength < MaxSimultaneousAllocations); + assert(FreeSlotsLength < State.MaxSimultaneousAllocations); FreeSlots[FreeSlotsLength++] = SlotIndex; } @@ -350,7 +284,7 @@ if (!ShouldRightAlign) return 0; - uintptr_t Offset = maximumAllocationSize(); + uintptr_t Offset = State.maximumAllocationSize(); if (!PerfectlyRightAlign) { if (Size == 3) Size = 4; @@ -363,209 +297,6 @@ return Offset; } -void GuardedPoolAllocator::reportError(uintptr_t AccessPtr, Error E) { - if (SingletonPtr) - SingletonPtr->reportErrorInternal(AccessPtr, E); -} - -size_t GuardedPoolAllocator::getNearestSlot(uintptr_t Ptr) const { - if (Ptr <= GuardedPagePool + PageSize) - return 0; - if (Ptr > GuardedPagePoolEnd - PageSize) - return MaxSimultaneousAllocations - 1; - - if (!isGuardPage(Ptr)) - return addrToSlot(Ptr); - - if (Ptr % PageSize <= PageSize / 2) - return addrToSlot(Ptr - PageSize); // Round down. - return addrToSlot(Ptr + PageSize); // Round up. -} - -Error GuardedPoolAllocator::diagnoseUnknownError(uintptr_t AccessPtr, - AllocationMetadata **Meta) { - // 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)); - - // Ensure that this slot was allocated once upon a time. - if (!SlotMeta->Addr) - return Error::UNKNOWN; - *Meta = SlotMeta; - - if (SlotMeta->Addr < AccessPtr) - return Error::BUFFER_OVERFLOW; - return Error::BUFFER_UNDERFLOW; - } - - // Access wasn't a guard page, check for use-after-free. - AllocationMetadata *SlotMeta = addrToMetadata(AccessPtr); - 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; - 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); -} - -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); -} - -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); - - AllocationMetadata *Meta = nullptr; - - 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; - } - - // 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"); - } - - if (Meta) - printAllocDeallocTraces(AccessPtr, Meta, Printf, PrintBacktrace); -} - GWP_ASAN_TLS_INITIAL_EXEC GuardedPoolAllocator::ThreadLocalPackedVariables GuardedPoolAllocator::ThreadLocals; 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 @@ -9,6 +9,7 @@ #ifndef GWP_ASAN_OPTIONAL_BACKTRACE_H_ #define GWP_ASAN_OPTIONAL_BACKTRACE_H_ +#include "gwp_asan/optional/segv_handler.h" #include "gwp_asan/options.h" namespace gwp_asan { @@ -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/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/optional/segv_handler.h b/compiler-rt/lib/gwp_asan/optional/segv_handler.h new file mode 100644 --- /dev/null +++ b/compiler-rt/lib/gwp_asan/optional/segv_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/segv_handler_posix.cpp b/compiler-rt/lib/gwp_asan/optional/segv_handler_posix.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/lib/gwp_asan/optional/segv_handler_posix.cpp @@ -0,0 +1,231 @@ +//===-- 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/common.h" +#include "gwp_asan/crash_handler.h" +#include "gwp_asan/guarded_pool_allocator.h" +#include "gwp_asan/optional/segv_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_DFL) { + // If the previous handler was the default handler, cause a core dump. + signal(SIGSEGV, SIG_DFL); + raise(SIGSEGV); + } else if (PreviousHandler.sa_handler == SIG_IGN) { + // If the previous segv handler was SIGIGN, crash iff we were responsible + // for the crash. + if (__gwp_asan_error_is_mine(GPAForSignalHandler->getAllocatorState(), + reinterpret_cast(info->si_addr))) { + 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 && Metadata != nullptr) { + uintptr_t Address = + __gwp_asan_get_allocation_address(State, Metadata); + size_t Size = __gwp_asan_get_allocation_size(State, Metadata); + 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 = gwp_asan::getThreadID(); + constexpr size_t kThreadBufferLen = 24; + char ThreadBuffer[kThreadBufferLen]; + if (ThreadID == gwp_asan::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); + + 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; + } + + const gwp_asan::AllocationMetadata *AllocMeta = + __gwp_asan_get_metadata(State, Metadata, ErrorPtr); + + // Print the error header. + printHeader(E, ErrorPtr, State, AllocMeta, 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 (AllocMeta == nullptr) + return; + + // Maybe print the deallocation trace. + if (__gwp_asan_is_deallocated(State, AllocMeta)) { + uint64_t ThreadID = + __gwp_asan_get_deallocation_thread_id(State, AllocMeta); + if (ThreadID == 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, AllocMeta, Trace, kMaximumStackFramesForCrashTrace); + PrintBacktrace(Trace, TraceLength, Printf); + } + + // Print the allocation trace. + uint64_t ThreadID = + __gwp_asan_get_allocation_thread_id(State, AllocMeta); + if (ThreadID == 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, AllocMeta, Trace, kMaximumStackFramesForCrashTrace); + PrintBacktrace(Trace, TraceLength, Printf); +} +} // namespace crash_handler +} // namespace gwp_asan 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 @@ -15,23 +15,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 // (RTGwpAsanBacktraceLibc or RTGwpAsanBacktraceSanitizerCommon). @@ -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/common_posix.cpp b/compiler-rt/lib/gwp_asan/platform_specific/common_posix.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/lib/gwp_asan/platform_specific/common_posix.cpp @@ -0,0 +1,21 @@ +//===-- common_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/common.h" + +namespace gwp_asan { + +uint64_t getThreadID() { +#ifdef SYS_gettid + return syscall(SYS_gettid); +#else + return kInvalidThreadID; +#endif +} + +} // namespace gwp_asan 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,27 +83,4 @@ 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); -#else - return kInvalidThreadID; -#endif -} - } // namespace gwp_asan 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/random.cpp b/compiler-rt/lib/gwp_asan/random.cpp --- a/compiler-rt/lib/gwp_asan/random.cpp +++ b/compiler-rt/lib/gwp_asan/random.cpp @@ -7,14 +7,13 @@ //===----------------------------------------------------------------------===// #include "gwp_asan/random.h" -#include "gwp_asan/guarded_pool_allocator.h" +#include "gwp_asan/common.h" #include namespace gwp_asan { uint32_t getRandomUnsigned32() { - thread_local uint32_t RandomState = - time(nullptr) + GuardedPoolAllocator::getThreadID(); + thread_local uint32_t RandomState = time(nullptr) + getThreadID(); RandomState ^= RandomState << 13; RandomState ^= RandomState >> 17; RandomState ^= RandomState << 5; 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 @@ -45,6 +46,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,211 @@ +//===-- 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 "gwp_asan/crash_handler.h" +#include "gwp_asan/guarded_pool_allocator.h" +#include "gwp_asan/stack_trace_compressor.h" +#include "gwp_asan/tests/harness.h" + +using Error = gwp_asan::Error; +using GuardedPoolAllocator = gwp_asan::GuardedPoolAllocator; +using AllocationMetadata = gwp_asan::AllocationMetadata; +using AllocatorState = gwp_asan::AllocatorState; + +class CrashHandlerAPITest : public ::testing::Test { +public: + void SetUp() override { setupState(); } + +protected: + size_t metadata(uintptr_t Addr, uintptr_t Size, bool IsDeallocated) { + // Should only be allocating the 0x3000, 0x5000, 0x7000, 0x9000 pages. + EXPECT_GE(Addr, 0x3000u); + EXPECT_LT(Addr, 0xa000u); + + size_t Slot = State.getNearestSlot(Addr); + + Metadata[Slot].Addr = Addr; + Metadata[Slot].Size = Size; + Metadata[Slot].IsDeallocated = IsDeallocated; + Metadata[Slot].AllocationTrace.ThreadID = 123; + Metadata[Slot].DeallocationTrace.ThreadID = 321; + setupBacktraces(&Metadata[Slot]); + + return Slot; + } + + void setupState() { + State.GuardedPagePool = 0x2000; + State.GuardedPagePoolEnd = 0xb000; + State.MaxSimultaneousAllocations = 4; // 0x3000, 0x5000, 0x7000, 0x9000. + State.PageSize = 0x1000; + } + + void setupBacktraces(AllocationMetadata *Meta) { + Meta->AllocationTrace.TraceSize = gwp_asan::compression::pack( + BacktraceConstants, kNumBacktraceConstants, + Meta->AllocationTrace.CompressedTrace, + AllocationMetadata::kStackFrameStorageBytes); + + if (Meta->IsDeallocated) + Meta->DeallocationTrace.TraceSize = gwp_asan::compression::pack( + BacktraceConstants, kNumBacktraceConstants, + Meta->DeallocationTrace.CompressedTrace, + AllocationMetadata::kStackFrameStorageBytes); + } + + void checkBacktrace(const AllocationMetadata *Meta, bool IsDeallocated) { + uintptr_t Buffer[kNumBacktraceConstants]; + size_t NumBacktraceConstants = kNumBacktraceConstants; + EXPECT_EQ(NumBacktraceConstants, + __gwp_asan_get_allocation_trace(&State, Meta, Buffer, + kNumBacktraceConstants)); + for (size_t i = 0; i < kNumBacktraceConstants; ++i) + EXPECT_EQ(Buffer[i], BacktraceConstants[i]); + + if (IsDeallocated) { + EXPECT_EQ(NumBacktraceConstants, + __gwp_asan_get_deallocation_trace(&State, Meta, Buffer, + kNumBacktraceConstants)); + for (size_t i = 0; i < kNumBacktraceConstants; ++i) + EXPECT_EQ(Buffer[i], BacktraceConstants[i]); + } + } + + void checkMetadata(size_t Index, uintptr_t ErrorPtr) { + const AllocationMetadata *Meta = + __gwp_asan_get_metadata(&State, Metadata, ErrorPtr); + EXPECT_NE(nullptr, Meta); + EXPECT_EQ(Metadata[Index].Addr, + __gwp_asan_get_allocation_address(&State, Meta)); + EXPECT_EQ(Metadata[Index].Size, + __gwp_asan_get_allocation_size(&State, Meta)); + EXPECT_EQ(Metadata[Index].AllocationTrace.ThreadID, + __gwp_asan_get_allocation_thread_id(&State, Meta)); + + bool IsDeallocated = __gwp_asan_is_deallocated(&State, Meta); + EXPECT_EQ(Metadata[Index].IsDeallocated, IsDeallocated); + checkBacktrace(Meta, IsDeallocated); + + if (!IsDeallocated) + return; + + EXPECT_EQ(Metadata[Index].DeallocationTrace.ThreadID, + __gwp_asan_get_deallocation_thread_id(&State, Meta)); + } + + static constexpr size_t kNumBacktraceConstants = 4; + static uintptr_t BacktraceConstants[kNumBacktraceConstants]; + AllocatorState State = {}; + AllocationMetadata Metadata[4] = {}; +}; + +uintptr_t CrashHandlerAPITest::BacktraceConstants[kNumBacktraceConstants] = { + 0xdeadbeef, 0xdeadc0de, 0xbadc0ffee, 0xcafef00d}; + +TEST_F(CrashHandlerAPITest, PointerNotMine) { + uintptr_t UnknownPtr = reinterpret_cast(&State); + + EXPECT_FALSE(__gwp_asan_error_is_mine(&State, 0)); + EXPECT_FALSE(__gwp_asan_error_is_mine(&State, UnknownPtr)); + + EXPECT_EQ(Error::UNKNOWN, __gwp_asan_diagnose_error(&State, Metadata, 0)); + EXPECT_EQ(Error::UNKNOWN, + __gwp_asan_diagnose_error(&State, Metadata, UnknownPtr)); + + EXPECT_EQ(nullptr, __gwp_asan_get_metadata(&State, Metadata, 0)); + EXPECT_EQ(nullptr, __gwp_asan_get_metadata(&State, Metadata, UnknownPtr)); +} + +TEST_F(CrashHandlerAPITest, PointerNotAllocated) { + uintptr_t FailureAddress = 0x9000; + + EXPECT_TRUE(__gwp_asan_error_is_mine(&State, FailureAddress)); + EXPECT_EQ(Error::UNKNOWN, + __gwp_asan_diagnose_error(&State, Metadata, FailureAddress)); + EXPECT_EQ(0u, __gwp_asan_get_internal_crash_address(&State)); + EXPECT_EQ(nullptr, __gwp_asan_get_metadata(&State, Metadata, FailureAddress)); +} + +TEST_F(CrashHandlerAPITest, DoubleFree) { + size_t Index = + metadata(/* Addr */ 0x7000, /* Size */ 0x20, /* IsDeallocated */ true); + uintptr_t FailureAddress = 0x7000; + + State.FailureType = Error::DOUBLE_FREE; + State.FailureAddress = FailureAddress; + + EXPECT_TRUE(__gwp_asan_error_is_mine(&State)); + EXPECT_EQ(Error::DOUBLE_FREE, + __gwp_asan_diagnose_error(&State, Metadata, 0x0)); + EXPECT_EQ(FailureAddress, __gwp_asan_get_internal_crash_address(&State)); + checkMetadata(Index, FailureAddress); +} + +TEST_F(CrashHandlerAPITest, InvalidFree) { + size_t Index = + metadata(/* Addr */ 0x7000, /* Size */ 0x20, /* IsDeallocated */ false); + uintptr_t FailureAddress = 0x7001; + + State.FailureType = Error::INVALID_FREE; + State.FailureAddress = FailureAddress; + + EXPECT_TRUE(__gwp_asan_error_is_mine(&State)); + EXPECT_EQ(Error::INVALID_FREE, + __gwp_asan_diagnose_error(&State, Metadata, 0x0)); + EXPECT_EQ(FailureAddress, __gwp_asan_get_internal_crash_address(&State)); + checkMetadata(Index, FailureAddress); +} + +TEST_F(CrashHandlerAPITest, InvalidFreeNoMetadata) { + uintptr_t FailureAddress = 0x7001; + + State.FailureType = Error::INVALID_FREE; + State.FailureAddress = FailureAddress; + + EXPECT_TRUE(__gwp_asan_error_is_mine(&State)); + EXPECT_EQ(Error::INVALID_FREE, + __gwp_asan_diagnose_error(&State, Metadata, 0x0)); + EXPECT_EQ(FailureAddress, __gwp_asan_get_internal_crash_address(&State)); + EXPECT_EQ(nullptr, __gwp_asan_get_metadata(&State, Metadata, FailureAddress)); +} + +TEST_F(CrashHandlerAPITest, UseAfterFree) { + size_t Index = + metadata(/* Addr */ 0x7000, /* Size */ 0x20, /* IsDeallocated */ true); + uintptr_t FailureAddress = 0x7001; + + EXPECT_TRUE(__gwp_asan_error_is_mine(&State, FailureAddress)); + EXPECT_EQ(Error::USE_AFTER_FREE, + __gwp_asan_diagnose_error(&State, Metadata, FailureAddress)); + EXPECT_EQ(0u, __gwp_asan_get_internal_crash_address(&State)); + checkMetadata(Index, FailureAddress); +} + +TEST_F(CrashHandlerAPITest, BufferOverflow) { + size_t Index = + metadata(/* Addr */ 0x5f00, /* Size */ 0x100, /* IsDeallocated */ false); + uintptr_t FailureAddress = 0x6000; + + EXPECT_TRUE(__gwp_asan_error_is_mine(&State, FailureAddress)); + EXPECT_EQ(Error::BUFFER_OVERFLOW, + __gwp_asan_diagnose_error(&State, Metadata, FailureAddress)); + EXPECT_EQ(0u, __gwp_asan_get_internal_crash_address(&State)); + checkMetadata(Index, FailureAddress); +} + +TEST_F(CrashHandlerAPITest, BufferUnderflow) { + size_t Index = + metadata(/* Addr */ 0x3000, /* Size */ 0x10, /* IsDeallocated*/ false); + uintptr_t FailureAddress = 0x2fff; + + EXPECT_TRUE(__gwp_asan_error_is_mine(&State, FailureAddress)); + EXPECT_EQ(Error::BUFFER_UNDERFLOW, + __gwp_asan_diagnose_error(&State, Metadata, FailureAddress)); + EXPECT_EQ(0u, __gwp_asan_get_internal_crash_address(&State)); + checkMetadata(Index, FailureAddress); +} 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 @@ -16,6 +16,7 @@ #include "gwp_asan/guarded_pool_allocator.h" #include "gwp_asan/optional/backtrace.h" #include "gwp_asan/options.h" +#include "gwp_asan/optional/segv_handler.h" namespace gwp_asan { namespace test { @@ -23,7 +24,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 +39,6 @@ Opts.setDefaults(); MaxSimultaneousAllocations = Opts.MaxSimultaneousAllocations; - Opts.Printf = gwp_asan::test::getPrintfFunction(); Opts.InstallForkHandlers = gwp_asan::test::OnlyOnce(); GPA.init(Opts); } @@ -62,7 +62,6 @@ Opts.MaxSimultaneousAllocations = MaxSimultaneousAllocationsArg; MaxSimultaneousAllocations = MaxSimultaneousAllocationsArg; - Opts.Printf = gwp_asan::test::getPrintfFunction(); Opts.InstallForkHandlers = gwp_asan::test::OnlyOnce(); GPA.init(Opts); } @@ -81,14 +80,19 @@ 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(); } + void TearDown() override { + GPA.uninitTestOnly(); + gwp_asan::crash_handler::uninstallSignalHandlers(); + } protected: gwp_asan::GuardedPoolAllocator GPA; 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/segv_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 + RTGwpAsanSegvHandler) 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,13 @@ append_list_if(COMPILER_RT_HAS_FVISIBILITY_HIDDEN_FLAG -fvisibility=hidden SCUDO_CFLAGS) +if (COMPILER_RT_HAS_GWP_ASAN) + 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) +endif() # COMPILER_RT_HAS_GWP_ASAN + if(COMPILER_RT_DEBUG) list(APPEND SCUDO_CFLAGS -O0) else() @@ -107,7 +114,8 @@ set(SCUDO_OBJECT_LIBS) if (COMPILER_RT_HAS_GWP_ASAN) - list(APPEND SCUDO_OBJECT_LIBS RTGwpAsan) + list(APPEND SCUDO_OBJECT_LIBS + RTGwpAsan RTGwpAsanBacktraceLibc RTGwpAsanSegvHandler) 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/segv_handler.h" #endif // GWP_ASAN_HOOKS extern "C" inline void EmptyCallback() {} @@ -169,8 +171,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 } @@ -180,6 +187,8 @@ TSDRegistry.unmapTestOnly(); Primary.unmapTestOnly(); #ifdef GWP_ASAN_HOOKS + if (getFlags()->GWP_ASAN_InstallSignalHandlers) + gwp_asan::crash_handler::uninstallSignalHandlers(); GuardedAlloc.uninitTestOnly(); #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 RTGwpAsanSegvHandler) 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