[GWP-ASan] Crash Handler API.
Summary:
Forewarning: This patch looks big in #LOC changed. I promise it's not that bad, it just moves a lot of content from one file to another. I've gone ahead and left inline comments on Phabricator for sections where this has happened.
This patch:
1. Introduces the crash handler API (crash_handler_api.h).
2. Moves information required for out-of-process crash handling into an AllocatorState. This is a trivially-copied POD struct that designed to be recovered from a deceased process, and used by the crash handler to create a GWP-ASan report (along with the other trivially-copied Metadata struct).
3. Implements the crash handler API using the AllocatorState and Metadata.
4. Adds tests for the crash handler.
5. Reimplements the (now optionally linked by the supporting allocator) in-process crash handler (i.e. the segv handler) using the new crash handler API.
6. Minor updates Scudo & Scudo Standalone to fix compatibility.
7. Changed capitalisation of errors (e.g. /s/Use after free/Use After Free).
Reviewers: cryptoad, eugenis, jfb
Reviewed By: eugenis
Subscribers: merge_guards_bot, pcc, jfb, dexonsmith, mgorny, cryptoad, #sanitizers, llvm-commits
Tags: #sanitizers, #llvm
Differential Revision: https://reviews.llvm.org/D73557
GitOrigin-RevId: a62586846fa90054bd9224912b07095d2fca662c
Change-Id: I248090c94ce5c03924131b8b5f19297110963d75
diff --git a/gwp_asan/common.cpp b/gwp_asan/common.cpp
new file mode 100644
index 0000000..4493581
--- /dev/null
+++ b/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 <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
diff --git a/gwp_asan/common.h b/gwp_asan/common.h
new file mode 100644
index 0000000..d197711
--- /dev/null
+++ b/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 <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_
diff --git a/gwp_asan/crash_handler.cpp b/gwp_asan/crash_handler.cpp
new file mode 100644
index 0000000..f287d02
--- /dev/null
+++ b/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 <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
diff --git a/gwp_asan/crash_handler.h b/gwp_asan/crash_handler.h
new file mode 100644
index 0000000..db9e864
--- /dev/null
+++ b/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/gwp_asan/guarded_pool_allocator.cpp b/gwp_asan/guarded_pool_allocator.cpp
index 5e6008e..cb8a183 100644
--- a/gwp_asan/guarded_pool_allocator.cpp
+++ b/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 <inttypes.h>.
@@ -18,13 +20,14 @@
#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 {
@@ -43,17 +46,6 @@
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
@@ -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<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);
@@ -167,16 +102,10 @@
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();
}
@@ -188,7 +117,7 @@
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)
@@ -197,29 +126,34 @@
}
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.
@@ -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<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
@@ -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<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) {
@@ -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<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)
@@ -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, "<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;
diff --git a/gwp_asan/guarded_pool_allocator.h b/gwp_asan/guarded_pool_allocator.h
index 9a9bc10..53d1635 100644
--- a/gwp_asan/guarded_pool_allocator.h
+++ b/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<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
@@ -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;
+ // Returns a pointer to the Metadata region, or nullptr if it doesn't exist.
+ const AllocationMetadata *getMetadataRegion() const { return Metadata; }
- // Dumps an error report (including allocation and deallocation stack traces).
- // An optional error may be provided if the caller knows what the error is
- // ahead of time. This is primarily a helper function to locate the static
- // singleton pointer and call the internal version of this function. This
- // method is never thread safe, and should only be called when fatal errors
- // occur.
- static void reportError(uintptr_t AccessPtr, Error E = Error::UNKNOWN);
-
- // Get the current thread ID, or kInvalidThreadID if failure. Note: This
- // implementation is platform-specific.
- static uint64_t getThreadID();
+ // Returns a pointer to the 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/gwp_asan/optional/backtrace.h b/gwp_asan/optional/backtrace.h
index 6c9ee9f..3a72eb3 100644
--- a/gwp_asan/optional/backtrace.h
+++ b/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/gwp_asan/optional/backtrace_linux_libc.cpp b/gwp_asan/optional/backtrace_linux_libc.cpp
index a656c9b..bb0aad2 100644
--- a/gwp_asan/optional/backtrace_linux_libc.cpp
+++ b/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(" <not found (does your allocator support backtracing?)>\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/gwp_asan/optional/backtrace_sanitizer_common.cpp b/gwp_asan/optional/backtrace_sanitizer_common.cpp
index 5e07fd6..3ac4b52 100644
--- a/gwp_asan/optional/backtrace_sanitizer_common.cpp
+++ b/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/gwp_asan/optional/options_parser.cpp b/gwp_asan/optional/options_parser.cpp
index 6c21672..2e63862 100644
--- a/gwp_asan/optional/options_parser.cpp
+++ b/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/gwp_asan/optional/segv_handler.h b/gwp_asan/optional/segv_handler.h
new file mode 100644
index 0000000..10af150
--- /dev/null
+++ b/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/gwp_asan/optional/segv_handler_posix.cpp b/gwp_asan/optional/segv_handler_posix.cpp
new file mode 100644
index 0000000..f98c16b
--- /dev/null
+++ b/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 <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
diff --git a/gwp_asan/options.h b/gwp_asan/options.h
index ae3f3d4..6fb4310 100644
--- a/gwp_asan/options.h
+++ b/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/gwp_asan/options.inc b/gwp_asan/options.inc
index 97500d3..3b941b1 100644
--- a/gwp_asan/options.inc
+++ b/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/gwp_asan/platform_specific/common_posix.cpp b/gwp_asan/platform_specific/common_posix.cpp
new file mode 100644
index 0000000..38e6ce0
--- /dev/null
+++ b/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/gwp_asan/platform_specific/guarded_pool_allocator_posix.cpp b/gwp_asan/platform_specific/guarded_pool_allocator_posix.cpp
index 6cdb40c..bef4ef5 100644
--- a/gwp_asan/platform_specific/guarded_pool_allocator_posix.cpp
+++ b/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 <assert.h>
#include <errno.h>
#include <signal.h>
#include <stdlib.h>
@@ -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<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())
@@ -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/gwp_asan/platform_specific/utilities_posix.cpp b/gwp_asan/platform_specific/utilities_posix.cpp
new file mode 100644
index 0000000..b292351
--- /dev/null
+++ b/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 <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
diff --git a/gwp_asan/random.cpp b/gwp_asan/random.cpp
index 90493da..d8efe62 100644
--- a/gwp_asan/random.cpp
+++ b/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 <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;
diff --git a/gwp_asan/tests/backtrace.cpp b/gwp_asan/tests/backtrace.cpp
index 6dccdb8..bc81f35 100644
--- a/gwp_asan/tests/backtrace.cpp
+++ b/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<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.*");
diff --git a/gwp_asan/tests/basic.cpp b/gwp_asan/tests/basic.cpp
index 663db91..29f420d 100644
--- a/gwp_asan/tests/basic.cpp
+++ b/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/gwp_asan/tests/crash_handler_api.cpp b/gwp_asan/tests/crash_handler_api.cpp
new file mode 100644
index 0000000..aa7e2a4
--- /dev/null
+++ b/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<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);
+}
diff --git a/gwp_asan/tests/harness.h b/gwp_asan/tests/harness.h
index 0851d7e..4bada14 100644
--- a/gwp_asan/tests/harness.h
+++ b/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/gwp_asan/tests/optional/printf_sanitizer_common.cpp b/gwp_asan/tests/optional/printf_sanitizer_common.cpp
index e823aeb..ea7141b 100644
--- a/gwp_asan/tests/optional/printf_sanitizer_common.cpp
+++ b/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/gwp_asan/tests/thread_contention.cpp b/gwp_asan/tests/thread_contention.cpp
index 33b5748..0992b97 100644
--- a/gwp_asan/tests/thread_contention.cpp
+++ b/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<volatile char *>(
- 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/gwp_asan/utilities.h b/gwp_asan/utilities.h
new file mode 100644
index 0000000..c307b38
--- /dev/null
+++ b/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