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
options.inc
random.h
stack_trace_compressor.h
+ utilities.h
)
# Ensure that GWP-ASan meets the delegated requirements of some supporting
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}
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
--- /dev/null
+//===-- 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 <assert.h>
+
+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<void *>(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
--- /dev/null
+//===-- 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 <stddef.h>
+#include <stdint.h>
+
+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<uintptr_t>(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_
--- /dev/null
+//===-- 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 <assert.h>
+
+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
--- /dev/null
+//===-- 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_
#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 <inttypes.h>.
#include <assert.h>
#include <inttypes.h>
+#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
-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 {
private:
bool &Bool;
};
-
-void defaultPrintStackTrace(uintptr_t *Trace, size_t TraceLength,
- options::Printf_t Printf) {
- if (TraceLength == 0)
- Printf(" <unknown (does your allocator support backtracing?)>\n");
-
- for (size_t i = 0; i < TraceLength; ++i) {
- Printf(" #%zu 0x%zx in <unknown>\n", i, Trace[i]);
- }
- Printf("\n");
-}
} // anonymous namespace
// Gets the singleton implementation of this class. Thread-compatible until
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
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<AllocationMetadata *>(
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<size_t *>(
mapMemory(BytesRequired, kGwpAsanFreeSlotsName));
markReadWrite(FreeSlots, BytesRequired, kGwpAsanFreeSlotsName);
ThreadLocals.NextSampleCounter =
(getRandomUnsigned32() % (AdjustedSampleRatePlusOne - 1)) + 1;
- GuardedPagePool = reinterpret_cast<uintptr_t>(GuardedPoolMemory);
- GuardedPagePoolEnd =
+ State.GuardedPagePool = reinterpret_cast<uintptr_t>(GuardedPoolMemory);
+ State.GuardedPagePoolEnd =
reinterpret_cast<uintptr_t>(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();
}
void GuardedPoolAllocator::iterate(void *Base, size_t Size, iterate_callback Cb,
void *Arg) {
uintptr_t Start = reinterpret_cast<uintptr_t>(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)
}
void GuardedPoolAllocator::uninitTestOnly() {
- if (GuardedPagePool) {
- unmapMemory(reinterpret_cast<void *>(GuardedPagePool),
- GuardedPagePoolEnd - GuardedPagePool, kGwpAsanGuardPageName);
- GuardedPagePool = 0;
- GuardedPagePoolEnd = 0;
+ if (State.GuardedPagePool) {
+ unmapMemory(reinterpret_cast<void *>(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.
return nullptr;
ScopedBoolean SB(ThreadLocals.RecursiveGuard);
- if (Size == 0 || Size > maximumAllocationSize())
+ if (Size == 0 || Size > State.maximumAllocationSize())
return nullptr;
size_t Index;
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<void *>(getPageAddr(Ptr)), Size,
- kGwpAsanAliveSlotName);
+ markReadWrite(reinterpret_cast<void *>(getPageAddr(Ptr, State.PageSize)),
+ Size, kGwpAsanAliveSlotName);
- Meta->RecordAllocation(Ptr, Size, Backtrace);
+ Meta->RecordAllocation(Ptr, Size);
+ Meta->AllocationTrace.RecordBacktrace(Backtrace);
return reinterpret_cast<void *>(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<char*>(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<uintptr_t>(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
{
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<void *>(SlotStart), maximumAllocationSize(),
- kGwpAsanGuardPageName);
+ markInaccessible(reinterpret_cast<void *>(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) {
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<void *>(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<void *>(Ptr)));
- return Ptr & ~(static_cast<uintptr_t>(PageSize) - 1);
-}
-
-bool GuardedPoolAllocator::isGuardPage(uintptr_t Ptr) const {
- assert(pointerIsMine(reinterpret_cast<void *>(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)
}
void GuardedPoolAllocator::freeSlot(size_t SlotIndex) {
- assert(FreeSlotsLength < MaxSimultaneousAllocations);
+ assert(FreeSlotsLength < State.MaxSimultaneousAllocations);
FreeSlots[FreeSlotsLength++] = SlotIndex;
}
if (!ShouldRightAlign)
return 0;
- uintptr_t Offset = maximumAllocationSize();
+ uintptr_t Offset = State.maximumAllocationSize();
if (!PerfectlyRightAlign) {
if (Size == 3)
Size = 4;
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, "<unknown>");
- 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 <unknown> 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 <unknown> 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<void *>(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(" <unknown (does your allocator support backtracing?)>\n\n");
- }
-
- if (Meta)
- printAllocDeallocTraces(AccessPtr, Meta, Printf, PrintBacktrace);
-}
-
GWP_ASAN_TLS_INITIAL_EXEC
GuardedPoolAllocator::ThreadLocalPackedVariables
GuardedPoolAllocator::ThreadLocals;
#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"
// 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
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".
// 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<uintptr_t>(Ptr);
- return P < GuardedPagePoolEnd && GuardedPagePool <= P;
+ return State.pointerIsMine(Ptr);
}
// Allocate memory in a guarded slot, and return a pointer to the new
// 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.
// 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();
// 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;
// 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++)
#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 {
// 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
}
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(" <not found (does your allocator support backtracing?)>\n\n");
return;
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
}
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;
__sanitizer::InitializeCommonFlags();
return Backtrace;
}
-PrintBacktrace_t getPrintBacktraceFunction() { return PrintBacktrace; }
+crash_handler::PrintBacktrace_t getPrintBacktraceFunction() {
+ return PrintBacktrace;
+}
} // namespace options
} // namespace gwp_asan
"GWP-ASan ERROR: SampleRate must be > 0 when GWP-ASan is enabled.\n");
exit(EXIT_FAILURE);
}
-
- o->Printf = __sanitizer::Printf;
}
Options &getOptions() { return *getOptionsInternal(); }
--- /dev/null
+//===-- 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_
--- /dev/null
+//===-- 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 <assert.h>
+#include <inttypes.h>
+#include <signal.h>
+#include <stdio.h>
+
+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<uintptr_t>(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<uintptr_t>(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, "<unknown>");
+ 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(" <unknown (does your allocator support backtracing?)>\n");
+
+ for (size_t i = 0; i < TraceLength; ++i) {
+ Printf(" #%zu 0x%zx in <unknown>\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 <unknown> 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 <unknown> 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
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).
// 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) \
#include "gwp_asan/options.inc"
#undef GWP_ASAN_OPTION
- Printf = nullptr;
Backtrace = nullptr;
- PrintBacktrace = nullptr;
}
};
} // namespace options
"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 "
--- /dev/null
+//===-- 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
//===----------------------------------------------------------------------===//
#include "gwp_asan/guarded_pool_allocator.h"
+#include "gwp_asan/utilities.h"
+#include <assert.h>
#include <errno.h>
#include <signal.h>
#include <stdlib.h>
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);
}
// 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);
}
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<uintptr_t>(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())
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
--- /dev/null
+//===-- 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 <android/set_abort_message.h>
+#include <stdlib.h>
+#else // ANDROID
+#include <stdio.h>
+#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
//===----------------------------------------------------------------------===//
#include "gwp_asan/random.h"
-#include "gwp_asan/guarded_pool_allocator.h"
+#include "gwp_asan/common.h"
#include <time.h>
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;
basic.cpp
compression.cpp
iterate.cpp
+ crash_handler_api.cpp
driver.cpp
mutex_test.cpp
slot_reuse.cpp
set(GWP_ASAN_TEST_RUNTIME_OBJECTS
$<TARGET_OBJECTS:RTGwpAsan.${arch}>
$<TARGET_OBJECTS:RTGwpAsanBacktraceSanitizerCommon.${arch}>
+ $<TARGET_OBJECTS:RTGwpAsanSegvHandler.${arch}>
$<TARGET_OBJECTS:RTSanitizerCommon.${arch}>
$<TARGET_OBJECTS:RTSanitizerCommonLibc.${arch}>
$<TARGET_OBJECTS:RTSanitizerCommonSymbolizer.${arch}>)
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.*");
char *Ptr = static_cast<char *>(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.*");
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) {
}
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) {
--- /dev/null
+//===-- 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<uintptr_t>(&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);
+}
#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 {
// 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();
Opts.setDefaults();
MaxSimultaneousAllocations = Opts.MaxSimultaneousAllocations;
- Opts.Printf = gwp_asan::test::getPrintfFunction();
Opts.InstallForkHandlers = gwp_asan::test::OnlyOnce();
GPA.init(Opts);
}
Opts.MaxSimultaneousAllocations = MaxSimultaneousAllocationsArg;
MaxSimultaneousAllocations = MaxSimultaneousAllocationsArg;
- Opts.Printf = gwp_asan::test::getPrintfFunction();
Opts.InstallForkHandlers = gwp_asan::test::OnlyOnce();
GPA.init(Opts);
}
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;
//
//===----------------------------------------------------------------------===//
+#include "gwp_asan/optional/segv_handler.h"
#include "sanitizer_common/sanitizer_common.h"
-#include "gwp_asan/options.h"
namespace gwp_asan {
namespace test {
// 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
// Get ourselves a new allocation.
for (unsigned i = 0; i < NumIterations; ++i) {
volatile char *Ptr = reinterpret_cast<volatile char *>(
- GPA->allocate(GPA->maximumAllocationSize()));
+ GPA->allocate(GPA->getAllocatorState()->maximumAllocationSize()));
// Do any other threads have access to this page?
EXPECT_EQ(*Ptr, 0);
--- /dev/null
+//===-- 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
# 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()
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
}
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()
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()
#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() {}
// 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
}
TSDRegistry.unmapTestOnly();
Primary.unmapTestOnly();
#ifdef GWP_ASAN_HOOKS
+ if (getFlags()->GWP_ASAN_InstallSignalHandlers)
+ gwp_asan::crash_handler::uninstallSignalHandlers();
GuardedAlloc.uninitTestOnly();
#endif // GWP_ASAN_HOOKS
}
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)
// 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 <cstdlib>
// 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 <cstdlib>
// 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 <cstdlib>
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);
// 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 <cstdlib>
// 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 <cstdlib>
// 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 <cstdlib>
// 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 <cstdlib>
// 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
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);
*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
// 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 <cstdlib>
// 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 <cstdlib>
// 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 <cstdlib>