diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2021-07-15 01:25:49 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2021-07-15 01:25:49 +0000 |
commit | 4a1e82a80aa08dc780a6f82af736df159325eae0 (patch) | |
tree | 5e1e85016be16b395eef3d2c25a902ee8fa01c58 | |
parent | 50332c5d6f14f080bcba05cc580776aa710a6851 (diff) | |
parent | de43ad5cc600ece030fb75f7e3318ab0b01386d5 (diff) | |
download | gwp_asan-android12-mainline-conscrypt-release.tar.gz |
Snap for 7550844 from de43ad5cc600ece030fb75f7e3318ab0b01386d5 to mainline-conscrypt-releaseandroid-mainline-12.0.0_r8android-mainline-12.0.0_r25android12-mainline-conscrypt-release
Change-Id: Ia718c6bf290c8ac6840e2d7ce4dc2bc3294b741e
55 files changed, 1503 insertions, 669 deletions
@@ -14,13 +14,47 @@ // limitations under the License. // +package { + default_applicable_licenses: ["external_gwp_asan_license"], +} + +// Added automatically by a large-scale-change that took the approach of +// 'apply every license found to every target'. While this makes sure we respect +// every license restriction, it may not be entirely correct. +// +// e.g. GPL in an MIT project might only apply to the contrib/ directory. +// +// Please consider splitting the single license below into multiple licenses, +// taking care not to lose any license_kind information, and overriding the +// default license using the 'licenses: [...]' property on targets as needed. +// +// For unused files, consider creating a 'fileGroup' with "//visibility:private" +// to attach the license to, and including a comment whether the files may be +// used in the current project. +// See: http://go/android-license-faq +license { + name: "external_gwp_asan_license", + visibility: [":__subpackages__"], + license_kinds: [ + "SPDX-license-identifier-Apache-2.0", + "SPDX-license-identifier-BSD", + "SPDX-license-identifier-MIT", + "SPDX-license-identifier-NCSA", + ], + license_text: [ + "LICENSE.TXT", + ], +} + cc_defaults { name: "gwp_asan_defaults", host_supported: true, vendor_available: true, + product_available: true, recovery_available: true, native_bridge_supported: true, ramdisk_available: true, + vendor_ramdisk_available: true, // GWP-ASan currently has no support for darwin. target: { @@ -28,6 +62,7 @@ cc_defaults { enabled: false, }, }, + min_sdk_version: "S", } cc_defaults { @@ -56,8 +91,8 @@ cc_library_headers { "//apex_available:platform", "com.android.runtime", // GWP-ASan headers are currently referenced by the following additional APEXes + "com.android.art", "com.android.art.debug", - "com.android.art.release", "com.android.media", "com.android.media.swcodec", ], @@ -78,7 +113,6 @@ cc_library_static { "gwp_asan/platform_specific/guarded_pool_allocator_posix.cpp", "gwp_asan/platform_specific/mutex_posix.cpp", "gwp_asan/platform_specific/utilities_posix.cpp", - "gwp_asan/random.cpp", "gwp_asan/stack_trace_compressor.cpp", ], // GWP-ASan requires platform (non-emulated) TLS. We use thread local @@ -127,6 +161,16 @@ cc_fuzz { static_libs: ["gwp_asan"], } +cc_fuzz { + name: "options_parser_fuzzer", + host_supported: true, + srcs: [ + "tools/options_parser_fuzzer.cpp", + "gwp_asan/optional/options_parser.cpp", + ], + static_libs: ["gwp_asan"], +} + // These unit tests are also continuously run upstream in LLVM's buildbots. cc_test { name: "gwp_asan_unittest", @@ -136,9 +180,12 @@ cc_test { "gwp_asan_crash_handler", "libasync_safe", "liblog", - "libunwindstack", + "libunwindstack_no_dex", "liblzma", // Dependency from libunwindstack. ], + shared_libs: [ + "libbase", + ], ldflags: [ // Ensure that ICF doesn't clobber DeallocateMemory2 into @@ -149,6 +196,7 @@ cc_test { srcs: [ "android/test_backtrace.cpp", "android/test_printf.cpp", + "gwp_asan/optional/options_parser.cpp", "gwp_asan/optional/segv_handler_posix.cpp", "gwp_asan/tests/alignment.cpp", "gwp_asan/tests/backtrace.cpp", @@ -160,6 +208,7 @@ cc_test { "gwp_asan/tests/iterate.cpp", "gwp_asan/tests/late_init.cpp", "gwp_asan/tests/mutex_test.cpp", + "gwp_asan/tests/options.cpp", "gwp_asan/tests/slot_reuse.cpp", "gwp_asan/tests/thread_contention.cpp", ], @@ -178,8 +227,4 @@ cc_test { // and so we disable it (as we rely on optnone for tests/backtrace.cpp). "-fno-experimental-new-pass-manager", ], - - // Late initialisation tests should run isolated, as the platform IE TLS - // PRNG should be initialised to its default state. - isolated: true, } @@ -16,5 +16,6 @@ third_party { value: "https://github.com/llvm/llvm-project.git" } version: "8f5e1755ca385566c0352a9bd292218cebfd3d0b" + license_type: NOTICE last_upgrade_date { year: 2019 month: 8 day: 21 } } @@ -1 +0,0 @@ -LICENSE
\ No newline at end of file diff --git a/TEST_MAPPING b/TEST_MAPPING index a4db69b..9328166 100644 --- a/TEST_MAPPING +++ b/TEST_MAPPING @@ -1,7 +1,18 @@ { "presubmit": [ { + "name": "CtsGwpAsanTestCases" + }, + { + "name": "debuggerd_test" + }, + { "name": "gwp_asan_unittest" } + ], + "imports": [ + { + "path": "bionic" + } ] } diff --git a/android/test_backtrace.cpp b/android/test_backtrace.cpp index 193f490..4a6d20d 100644 --- a/android/test_backtrace.cpp +++ b/android/test_backtrace.cpp @@ -21,9 +21,7 @@ #include <unwindstack/LocalUnwinder.h> #include <unwindstack/Unwinder.h> -namespace gwp_asan { -namespace options { - +namespace { // In reality, on Android, we use two separate unwinders. GWP-ASan internally // uses a fast, frame-pointer unwinder for allocation/deallocation stack traces // (android_unsafe_frame_pointer_chase, provided by bionic libc). When a process @@ -42,70 +40,17 @@ size_t BacktraceUnwindstack(uintptr_t *TraceBuffer, size_t Size) { if (!unwinder.Unwind(&frames, Size)) { return 0; } - for (const auto& frame : frames) { + for (const auto &frame : frames) { *TraceBuffer = frame.pc; TraceBuffer++; } return frames.size(); } -Backtrace_t getBacktraceFunction() { - return BacktraceUnwindstack; -} - -// Build a frame for symbolization using the maps from the provided unwinder. -// The constructed frame contains just enough information to be used to -// symbolize a GWP-ASan stack trace. -static unwindstack::FrameData BuildFrame(unwindstack::Unwinder* unwinder, uintptr_t pc) { - unwindstack::FrameData frame; - - unwindstack::Maps* maps = unwinder->GetMaps(); - unwindstack::MapInfo* map_info = maps->Find(pc); - if (!map_info) { - frame.rel_pc = pc; - return frame; - } - - unwindstack::Elf* elf = - map_info->GetElf(unwinder->GetProcessMemory(), unwindstack::Regs::CurrentArch()); - - uint64_t relative_pc = elf->GetRelPc(pc, map_info); - - // Create registers just to get PC adjustment. Doesn't matter what they point - // to. - unwindstack::Regs* regs = unwindstack::Regs::CreateFromLocal(); - uint64_t pc_adjustment = regs->GetPcAdjustment(relative_pc, elf); - relative_pc -= pc_adjustment; - // The debug PC may be different if the PC comes from the JIT. - uint64_t debug_pc = relative_pc; - - // If we don't have a valid ELF file, check the JIT. - if (!elf->valid()) { - unwindstack::JitDebug jit_debug(unwinder->GetProcessMemory()); - uint64_t jit_pc = pc - pc_adjustment; - unwindstack::Elf* jit_elf = jit_debug.GetElf(maps, jit_pc); - if (jit_elf != nullptr) { - debug_pc = jit_pc; - elf = jit_elf; - } - } - - // Copy all the things we need into the frame for symbolization. - frame.rel_pc = relative_pc; - frame.pc = pc - pc_adjustment; - frame.map_name = map_info->name; - frame.map_elf_start_offset = map_info->elf_start_offset; - frame.map_exact_offset = map_info->offset; - frame.map_start = map_info->start; - frame.map_end = map_info->end; - frame.map_flags = map_info->flags; - frame.map_load_bias = elf->GetLoadBias(); - - if (!elf->GetFunctionName(relative_pc, &frame.function_name, &frame.function_offset)) { - frame.function_name = ""; - frame.function_offset = 0; - } - return frame; +// We don't need any custom handling for the Segv backtrace - the unwindstack +// unwinder has no problems with unwinding through a signal handler. +size_t SegvBacktrace(uintptr_t *TraceBuffer, size_t Size, void * /*Context*/) { + return BacktraceUnwindstack(TraceBuffer, Size); } // This function is a good mimic as to what's happening in the out-of-process @@ -114,21 +59,33 @@ static unwindstack::FrameData BuildFrame(unwindstack::Unwinder* unwinder, uintpt // function called from a signal handler, and is extraordinarily not // signal-safe, but works for our purposes. void PrintBacktraceUnwindstack(uintptr_t *TraceBuffer, size_t TraceLength, - crash_handler::Printf_t Print) { + gwp_asan::Printf_t Print) { unwindstack::UnwinderFromPid unwinder( - AllocationMetadata::kMaxTraceLengthToCollect, getpid()); - unwinder.Init(unwindstack::Regs::CurrentArch()); + gwp_asan::AllocationMetadata::kMaxTraceLengthToCollect, getpid()); unwinder.SetRegs(unwindstack::Regs::CreateFromLocal()); + if (!unwinder.Init()) { + Print(" Unable to init unwinder: %s\n", unwinder.LastErrorCodeString()); + return; + } for (size_t i = 0; i < TraceLength; ++i) { - unwindstack::FrameData frame_data = BuildFrame(&unwinder, TraceBuffer[i]); + unwindstack::FrameData frame_data = + unwinder.BuildFrameFromPcOnly(TraceBuffer[i]); frame_data.num = i; Print(" %s\n", unwinder.FormatFrame(frame_data).c_str()); } } -crash_handler::PrintBacktrace_t getPrintBacktraceFunction() { +} // anonymous namespace + +namespace gwp_asan { +namespace backtrace { +options::Backtrace_t getBacktraceFunction() { return BacktraceUnwindstack; } + +PrintBacktrace_t getPrintBacktraceFunction() { return PrintBacktraceUnwindstack; } -} // namespace options + +SegvBacktrace_t getSegvBacktraceFunction() { return SegvBacktrace; } +} // namespace backtrace } // namespace gwp_asan diff --git a/android/test_printf.cpp b/android/test_printf.cpp index 27bb0de..d5b0607 100644 --- a/android/test_printf.cpp +++ b/android/test_printf.cpp @@ -2,8 +2,7 @@ #include <stdarg.h> #include <unistd.h> -#include "gwp_asan/optional/segv_handler.h" -#include "gwp_asan/options.h" +#include "gwp_asan/optional/printf.h" namespace { void PrintfWrapper(const char *Format, ...) { @@ -12,12 +11,12 @@ void PrintfWrapper(const char *Format, ...) { async_safe_fatal_va_list("GWP-ASan", Format, List); va_end(List); } -}; // anonymous namespace +} // anonymous namespace namespace gwp_asan { namespace test { // Android version of the Printf() function for use in gwp_asan_unittest. You -// can find the declaration of this function in gwp_asan/tests/harness.h -crash_handler::Printf_t getPrintfFunction() { return PrintfWrapper; } -}; // namespace test -}; // namespace gwp_asan +// can find the declaration of this function in gwp_asan/optional/printf.h +Printf_t getPrintfFunction() { return PrintfWrapper; } +} // namespace test +} // namespace gwp_asan diff --git a/gwp_asan/common.cpp b/gwp_asan/common.cpp index 3438c4b..b0f6c58 100644 --- a/gwp_asan/common.cpp +++ b/gwp_asan/common.cpp @@ -34,10 +34,13 @@ const char *ErrorToString(const Error &E) { __builtin_trap(); } +constexpr size_t AllocationMetadata::kStackFrameStorageBytes; +constexpr size_t AllocationMetadata::kMaxTraceLengthToCollect; + void AllocationMetadata::RecordAllocation(uintptr_t AllocAddr, size_t AllocSize) { Addr = AllocAddr; - Size = AllocSize; + RequestedSize = AllocSize; IsDeallocated = false; AllocationTrace.ThreadID = getThreadID(); diff --git a/gwp_asan/common.h b/gwp_asan/common.h index d197711..7ce367e 100644 --- a/gwp_asan/common.h +++ b/gwp_asan/common.h @@ -49,7 +49,7 @@ struct AllocationMetadata { static constexpr size_t kMaxTraceLengthToCollect = 128; // Records the given allocation metadata into this struct. - void RecordAllocation(uintptr_t Addr, size_t Size); + void RecordAllocation(uintptr_t Addr, size_t RequestedSize); // Record that this allocation is now deallocated. void RecordDeallocation(); @@ -70,7 +70,7 @@ struct AllocationMetadata { // valid, as the allocation has never occurred. uintptr_t Addr = 0; // Represents the actual size of the allocation. - size_t Size = 0; + size_t RequestedSize = 0; CallSiteInfo AllocationTrace; CallSiteInfo DeallocationTrace; @@ -83,6 +83,8 @@ struct AllocationMetadata { // crash handler. This, in conjunction with the Metadata array, forms the entire // set of information required for understanding a GWP-ASan crash. struct AllocatorState { + constexpr 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 { diff --git a/gwp_asan/crash_handler.cpp b/gwp_asan/crash_handler.cpp index c3b9e14..6b4c39e 100644 --- a/gwp_asan/crash_handler.cpp +++ b/gwp_asan/crash_handler.cpp @@ -1,4 +1,4 @@ -//===-- crash_handler_interface.cpp -----------------------------*- C++ -*-===// +//===-- crash_handler.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. @@ -10,6 +10,8 @@ #include "gwp_asan/stack_trace_compressor.h" #include <assert.h> +#include <stdint.h> +#include <string.h> using AllocationMetadata = gwp_asan::AllocationMetadata; using Error = gwp_asan::Error; @@ -101,7 +103,7 @@ uintptr_t __gwp_asan_get_allocation_address( size_t __gwp_asan_get_allocation_size( const gwp_asan::AllocationMetadata *AllocationMeta) { - return AllocationMeta->Size; + return AllocationMeta->RequestedSize; } uint64_t __gwp_asan_get_allocation_thread_id( @@ -112,9 +114,15 @@ uint64_t __gwp_asan_get_allocation_thread_id( size_t __gwp_asan_get_allocation_trace( const gwp_asan::AllocationMetadata *AllocationMeta, uintptr_t *Buffer, size_t BufferLen) { - return gwp_asan::compression::unpack( + uintptr_t UncompressedBuffer[AllocationMetadata::kMaxTraceLengthToCollect]; + size_t UnpackedLength = gwp_asan::compression::unpack( AllocationMeta->AllocationTrace.CompressedTrace, - AllocationMeta->AllocationTrace.TraceSize, Buffer, BufferLen); + AllocationMeta->AllocationTrace.TraceSize, UncompressedBuffer, + AllocationMetadata::kMaxTraceLengthToCollect); + if (UnpackedLength < BufferLen) + BufferLen = UnpackedLength; + memcpy(Buffer, UncompressedBuffer, BufferLen * sizeof(*Buffer)); + return UnpackedLength; } bool __gwp_asan_is_deallocated( @@ -130,9 +138,15 @@ uint64_t __gwp_asan_get_deallocation_thread_id( size_t __gwp_asan_get_deallocation_trace( const gwp_asan::AllocationMetadata *AllocationMeta, uintptr_t *Buffer, size_t BufferLen) { - return gwp_asan::compression::unpack( + uintptr_t UncompressedBuffer[AllocationMetadata::kMaxTraceLengthToCollect]; + size_t UnpackedLength = gwp_asan::compression::unpack( AllocationMeta->DeallocationTrace.CompressedTrace, - AllocationMeta->DeallocationTrace.TraceSize, Buffer, BufferLen); + AllocationMeta->DeallocationTrace.TraceSize, UncompressedBuffer, + AllocationMetadata::kMaxTraceLengthToCollect); + if (UnpackedLength < BufferLen) + BufferLen = UnpackedLength; + memcpy(Buffer, UncompressedBuffer, BufferLen * sizeof(*Buffer)); + return UnpackedLength; } #ifdef __cplusplus diff --git a/gwp_asan/crash_handler.h b/gwp_asan/crash_handler.h index 631c319..4a95069 100644 --- a/gwp_asan/crash_handler.h +++ b/gwp_asan/crash_handler.h @@ -1,4 +1,4 @@ -//===-- crash_handler_interface.h -------------------------------*- C++ -*-===// +//===-- 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. diff --git a/gwp_asan/definitions.h b/gwp_asan/definitions.h index 563c408..bec0290 100644 --- a/gwp_asan/definitions.h +++ b/gwp_asan/definitions.h @@ -1,4 +1,4 @@ -//===-- gwp_asan_definitions.h ----------------------------------*- C++ -*-===// +//===-- definitions.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. diff --git a/gwp_asan/guarded_pool_allocator.cpp b/gwp_asan/guarded_pool_allocator.cpp index b2602e4..d784927 100644 --- a/gwp_asan/guarded_pool_allocator.cpp +++ b/gwp_asan/guarded_pool_allocator.cpp @@ -8,24 +8,11 @@ #include "gwp_asan/guarded_pool_allocator.h" -#include "gwp_asan/optional/segv_handler.h" #include "gwp_asan/options.h" -#include "gwp_asan/random.h" #include "gwp_asan/utilities.h" -// RHEL creates the PRIu64 format macro (for printing uint64_t's) only when this -// macro is defined before including <inttypes.h>. -#ifndef __STDC_FORMAT_MACROS -#define __STDC_FORMAT_MACROS 1 -#endif - #include <assert.h> -#include <inttypes.h> -#include <signal.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <time.h> +#include <stddef.h> using AllocationMetadata = gwp_asan::AllocationMetadata; using Error = gwp_asan::Error; @@ -39,14 +26,15 @@ namespace { // init-order-fiasco. GuardedPoolAllocator *SingletonPtr = nullptr; -class ScopedBoolean { -public: - ScopedBoolean(bool &B) : Bool(B) { Bool = true; } - ~ScopedBoolean() { Bool = false; } +size_t roundUpTo(size_t Size, size_t Boundary) { + return (Size + Boundary - 1) & ~(Boundary - 1); +} -private: - bool &Bool; -}; +uintptr_t getPageAddr(uintptr_t Ptr, uintptr_t PageSize) { + return Ptr & ~(PageSize - 1); +} + +bool isPowerOfTwo(uintptr_t X) { return (X & (X - 1)) == 0; } } // anonymous namespace // Gets the singleton implementation of this class. Thread-compatible until @@ -64,7 +52,7 @@ void GuardedPoolAllocator::init(const options::Options &Opts) { return; Check(Opts.SampleRate >= 0, "GWP-ASan Error: SampleRate is < 0."); - Check(Opts.SampleRate <= INT32_MAX, "GWP-ASan Error: SampleRate is > 2^31."); + Check(Opts.SampleRate < (1 << 30), "GWP-ASan Error: SampleRate is >= 2^30."); Check(Opts.MaxSimultaneousAllocations >= 0, "GWP-ASan Error: MaxSimultaneousAllocations is < 0."); @@ -73,25 +61,27 @@ void GuardedPoolAllocator::init(const options::Options &Opts) { State.MaxSimultaneousAllocations = Opts.MaxSimultaneousAllocations; - State.PageSize = getPlatformPageSize(); - - PerfectlyRightAlign = Opts.PerfectlyRightAlign; + const size_t PageSize = getPlatformPageSize(); + // getPageAddr() and roundUpTo() assume the page size to be a power of 2. + assert((PageSize & (PageSize - 1)) == 0); + State.PageSize = PageSize; size_t PoolBytesRequired = - State.PageSize * (1 + State.MaxSimultaneousAllocations) + + PageSize * (1 + State.MaxSimultaneousAllocations) + State.MaxSimultaneousAllocations * State.maximumAllocationSize(); - void *GuardedPoolMemory = mapMemory(PoolBytesRequired, kGwpAsanGuardPageName); + assert(PoolBytesRequired % PageSize == 0); + void *GuardedPoolMemory = reserveGuardedPool(PoolBytesRequired); - size_t BytesRequired = State.MaxSimultaneousAllocations * sizeof(*Metadata); + size_t BytesRequired = + roundUpTo(State.MaxSimultaneousAllocations * sizeof(*Metadata), PageSize); Metadata = reinterpret_cast<AllocationMetadata *>( - mapMemory(BytesRequired, kGwpAsanMetadataName)); - markReadWrite(Metadata, BytesRequired, kGwpAsanMetadataName); + map(BytesRequired, kGwpAsanMetadataName)); // Allocate memory and set up the free pages queue. - BytesRequired = State.MaxSimultaneousAllocations * sizeof(*FreeSlots); - FreeSlots = reinterpret_cast<size_t *>( - mapMemory(BytesRequired, kGwpAsanFreeSlotsName)); - markReadWrite(FreeSlots, BytesRequired, kGwpAsanFreeSlotsName); + BytesRequired = roundUpTo( + State.MaxSimultaneousAllocations * sizeof(*FreeSlots), PageSize); + FreeSlots = + reinterpret_cast<size_t *>(map(BytesRequired, kGwpAsanFreeSlotsName)); // Multiply the sample rate by 2 to give a good, fast approximation for (1 / // SampleRate) chance of sampling. @@ -101,8 +91,9 @@ void GuardedPoolAllocator::init(const options::Options &Opts) { AdjustedSampleRatePlusOne = 2; initPRNG(); - ThreadLocals.NextSampleCounter = - (getRandomUnsigned32() % (AdjustedSampleRatePlusOne - 1)) + 1; + getThreadLocals()->NextSampleCounter = + ((getRandomUnsigned32() % (AdjustedSampleRatePlusOne - 1)) + 1) & + ThreadLocalPackedVariables::NextSampleCounterMask; State.GuardedPagePool = reinterpret_cast<uintptr_t>(GuardedPoolMemory); State.GuardedPagePoolEnd = @@ -112,9 +103,15 @@ void GuardedPoolAllocator::init(const options::Options &Opts) { installAtFork(); } -void GuardedPoolAllocator::disable() { PoolMutex.lock(); } +void GuardedPoolAllocator::disable() { + PoolMutex.lock(); + BacktraceMutex.lock(); +} -void GuardedPoolAllocator::enable() { PoolMutex.unlock(); } +void GuardedPoolAllocator::enable() { + PoolMutex.unlock(); + BacktraceMutex.unlock(); +} void GuardedPoolAllocator::iterate(void *Base, size_t Size, iterate_callback Cb, void *Arg) { @@ -123,48 +120,96 @@ void GuardedPoolAllocator::iterate(void *Base, size_t Size, iterate_callback Cb, const AllocationMetadata &Meta = Metadata[i]; if (Meta.Addr && !Meta.IsDeallocated && Meta.Addr >= Start && Meta.Addr < Start + Size) - Cb(Meta.Addr, Meta.Size, Arg); + Cb(Meta.Addr, Meta.RequestedSize, Arg); } } void GuardedPoolAllocator::uninitTestOnly() { if (State.GuardedPagePool) { - unmapMemory(reinterpret_cast<void *>(State.GuardedPagePool), - State.GuardedPagePoolEnd - State.GuardedPagePool, - kGwpAsanGuardPageName); + unreserveGuardedPool(); State.GuardedPagePool = 0; State.GuardedPagePoolEnd = 0; } if (Metadata) { - unmapMemory(Metadata, State.MaxSimultaneousAllocations * sizeof(*Metadata), - kGwpAsanMetadataName); + unmap(Metadata, + roundUpTo(State.MaxSimultaneousAllocations * sizeof(*Metadata), + State.PageSize)); Metadata = nullptr; } if (FreeSlots) { - unmapMemory(FreeSlots, - State.MaxSimultaneousAllocations * sizeof(*FreeSlots), - kGwpAsanFreeSlotsName); + unmap(FreeSlots, + roundUpTo(State.MaxSimultaneousAllocations * sizeof(*FreeSlots), + State.PageSize)); FreeSlots = nullptr; } + *getThreadLocals() = ThreadLocalPackedVariables(); } -static uintptr_t getPageAddr(uintptr_t Ptr, uintptr_t PageSize) { - return Ptr & ~(PageSize - 1); +// Note, minimum backing allocation size in GWP-ASan is always one page, and +// each slot could potentially be multiple pages (but always in +// page-increments). Thus, for anything that requires less than page size +// alignment, we don't need to allocate extra padding to ensure the alignment +// can be met. +size_t GuardedPoolAllocator::getRequiredBackingSize(size_t Size, + size_t Alignment, + size_t PageSize) { + assert(isPowerOfTwo(Alignment) && "Alignment must be a power of two!"); + assert(Alignment != 0 && "Alignment should be non-zero"); + assert(Size != 0 && "Size should be non-zero"); + + if (Alignment <= PageSize) + return Size; + + return Size + Alignment - PageSize; } -void *GuardedPoolAllocator::allocate(size_t Size) { +uintptr_t GuardedPoolAllocator::alignUp(uintptr_t Ptr, size_t Alignment) { + assert(isPowerOfTwo(Alignment) && "Alignment must be a power of two!"); + assert(Alignment != 0 && "Alignment should be non-zero"); + if ((Ptr & (Alignment - 1)) == 0) + return Ptr; + + Ptr += Alignment - (Ptr & (Alignment - 1)); + return Ptr; +} + +uintptr_t GuardedPoolAllocator::alignDown(uintptr_t Ptr, size_t Alignment) { + assert(isPowerOfTwo(Alignment) && "Alignment must be a power of two!"); + assert(Alignment != 0 && "Alignment should be non-zero"); + if ((Ptr & (Alignment - 1)) == 0) + return Ptr; + + Ptr -= Ptr & (Alignment - 1); + return Ptr; +} + +void *GuardedPoolAllocator::allocate(size_t Size, size_t Alignment) { // GuardedPagePoolEnd == 0 when GWP-ASan is disabled. If we are disabled, fall // back to the supporting allocator. - if (State.GuardedPagePoolEnd == 0) + if (State.GuardedPagePoolEnd == 0) { + getThreadLocals()->NextSampleCounter = + (AdjustedSampleRatePlusOne - 1) & + ThreadLocalPackedVariables::NextSampleCounterMask; return nullptr; + } - // Protect against recursivity. - if (ThreadLocals.RecursiveGuard) + if (Size == 0) + Size = 1; + if (Alignment == 0) + Alignment = alignof(max_align_t); + + if (!isPowerOfTwo(Alignment) || Alignment > State.maximumAllocationSize() || + Size > State.maximumAllocationSize()) return nullptr; - ScopedBoolean SB(ThreadLocals.RecursiveGuard); - if (Size == 0 || Size > State.maximumAllocationSize()) + size_t BackingSize = getRequiredBackingSize(Size, Alignment, State.PageSize); + if (BackingSize > State.maximumAllocationSize()) + return nullptr; + + // Protect against recursivity. + if (getThreadLocals()->RecursiveGuard) return nullptr; + ScopedRecursiveGuard SRG; size_t Index; { @@ -175,27 +220,35 @@ void *GuardedPoolAllocator::allocate(size_t Size) { if (Index == kInvalidSlotID) return nullptr; - uintptr_t Ptr = State.slotToAddr(Index); - // Should we right-align this allocation? - if (getRandomUnsigned32() % 2 == 0) { - AlignmentStrategy Align = AlignmentStrategy::DEFAULT; - if (PerfectlyRightAlign) - Align = AlignmentStrategy::PERFECT; - Ptr += - State.maximumAllocationSize() - rightAlignedAllocationSize(Size, Align); - } - AllocationMetadata *Meta = addrToMetadata(Ptr); + uintptr_t SlotStart = State.slotToAddr(Index); + AllocationMetadata *Meta = addrToMetadata(SlotStart); + uintptr_t SlotEnd = State.slotToAddr(Index) + State.maximumAllocationSize(); + uintptr_t UserPtr; + // Randomly choose whether to left-align or right-align the allocation, and + // then apply the necessary adjustments to get an aligned pointer. + if (getRandomUnsigned32() % 2 == 0) + UserPtr = alignUp(SlotStart, Alignment); + else + UserPtr = alignDown(SlotEnd - Size, Alignment); + + assert(UserPtr >= SlotStart); + assert(UserPtr + Size <= SlotEnd); // 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, State.PageSize)), - Size, kGwpAsanAliveSlotName); + const size_t PageSize = State.PageSize; + allocateInGuardedPool( + reinterpret_cast<void *>(getPageAddr(UserPtr, PageSize)), + roundUpTo(Size, PageSize)); - Meta->RecordAllocation(Ptr, Size); - Meta->AllocationTrace.RecordBacktrace(Backtrace); + Meta->RecordAllocation(UserPtr, Size); + { + ScopedLock UL(BacktraceMutex); + Meta->AllocationTrace.RecordBacktrace(Backtrace); + } - return reinterpret_cast<void *>(Ptr); + return reinterpret_cast<void *>(UserPtr); } void GuardedPoolAllocator::trapOnAddress(uintptr_t Address, Error E) { @@ -209,7 +262,7 @@ void GuardedPoolAllocator::trapOnAddress(uintptr_t Address, Error E) { } void GuardedPoolAllocator::stop() { - ThreadLocals.RecursiveGuard = true; + getThreadLocals()->RecursiveGuard = true; PoolMutex.tryLock(); } @@ -240,14 +293,15 @@ void GuardedPoolAllocator::deallocate(void *Ptr) { // 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); + if (!getThreadLocals()->RecursiveGuard) { + ScopedRecursiveGuard SRG; + ScopedLock UL(BacktraceMutex); Meta->DeallocationTrace.RecordBacktrace(Backtrace); } } - markInaccessible(reinterpret_cast<void *>(SlotStart), - State.maximumAllocationSize(), kGwpAsanGuardPageName); + deallocateInGuardedPool(reinterpret_cast<void *>(SlotStart), + State.maximumAllocationSize()); // And finally, lock again to release the slot back into the pool. ScopedLock L(PoolMutex); @@ -259,7 +313,7 @@ size_t GuardedPoolAllocator::getSize(const void *Ptr) { ScopedLock L(PoolMutex); AllocationMetadata *Meta = addrToMetadata(reinterpret_cast<uintptr_t>(Ptr)); assert(Meta->Addr == reinterpret_cast<uintptr_t>(Ptr)); - return Meta->Size; + return Meta->RequestedSize; } AllocationMetadata *GuardedPoolAllocator::addrToMetadata(uintptr_t Ptr) const { @@ -286,7 +340,12 @@ void GuardedPoolAllocator::freeSlot(size_t SlotIndex) { FreeSlots[FreeSlotsLength++] = SlotIndex; } -GWP_ASAN_TLS_INITIAL_EXEC -GuardedPoolAllocator::ThreadLocalPackedVariables - GuardedPoolAllocator::ThreadLocals; +uint32_t GuardedPoolAllocator::getRandomUnsigned32() { + uint32_t RandomState = getThreadLocals()->RandomState; + RandomState ^= RandomState << 13; + RandomState ^= RandomState >> 17; + RandomState ^= RandomState << 5; + getThreadLocals()->RandomState = RandomState; + return RandomState; +} } // namespace gwp_asan diff --git a/gwp_asan/guarded_pool_allocator.h b/gwp_asan/guarded_pool_allocator.h index ae00506..6d2ce25 100644 --- a/gwp_asan/guarded_pool_allocator.h +++ b/gwp_asan/guarded_pool_allocator.h @@ -13,11 +13,13 @@ #include "gwp_asan/definitions.h" #include "gwp_asan/mutex.h" #include "gwp_asan/options.h" -#include "gwp_asan/random.h" -#include "gwp_asan/stack_trace_compressor.h" +#include "gwp_asan/platform_specific/guarded_pool_allocator_fuchsia.h" // IWYU pragma: keep +#include "gwp_asan/platform_specific/guarded_pool_allocator_posix.h" // IWYU pragma: keep +#include "gwp_asan/platform_specific/guarded_pool_allocator_tls.h" #include <stddef.h> #include <stdint.h> +// IWYU pragma: no_include <__stddef_max_align_t.h> namespace gwp_asan { // This class is the primary implementation of the allocator portion of GWP- @@ -37,7 +39,7 @@ public: // GWP-ASan. The constructor value-initialises the class such that if no // further initialisation takes place, calls to shouldSample() and // pointerIsMine() will return false. - constexpr GuardedPoolAllocator(){}; + constexpr GuardedPoolAllocator() {} GuardedPoolAllocator(const GuardedPoolAllocator &) = delete; GuardedPoolAllocator &operator=(const GuardedPoolAllocator &) = delete; @@ -78,11 +80,12 @@ public: // class must be valid when zero-initialised, and we wish to sample as // infrequently as possible when this is the case, hence we underflow to // UINT32_MAX. - if (GWP_ASAN_UNLIKELY(ThreadLocals.NextSampleCounter == 0)) - ThreadLocals.NextSampleCounter = - (getRandomUnsigned32() % (AdjustedSampleRatePlusOne - 1)) + 1; + if (GWP_ASAN_UNLIKELY(getThreadLocals()->NextSampleCounter == 0)) + getThreadLocals()->NextSampleCounter = + ((getRandomUnsigned32() % (AdjustedSampleRatePlusOne - 1)) + 1) & + ThreadLocalPackedVariables::NextSampleCounterMask; - return GWP_ASAN_UNLIKELY(--ThreadLocals.NextSampleCounter == 0); + return GWP_ASAN_UNLIKELY(--getThreadLocals()->NextSampleCounter == 0); } // Returns whether the provided pointer is a current sampled allocation that @@ -91,10 +94,13 @@ public: return State.pointerIsMine(Ptr); } - // Allocate memory in a guarded slot, and return a pointer to the new - // allocation. Returns nullptr if the pool is empty, the requested size is too - // large for this pool to handle, or the requested size is zero. - void *allocate(size_t Size); + // Allocate memory in a guarded slot, with the specified `Alignment`. Returns + // nullptr if the pool is empty, if the alignnment is not a power of two, or + // if the size/alignment makes the allocation too large for this pool to + // handle. By default, uses strong alignment (i.e. `max_align_t`), see + // http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2293.htm for discussion of + // alignment issues in the standard. + void *allocate(size_t Size, size_t Alignment = alignof(max_align_t)); // Deallocate memory in a guarded slot. The provided pointer must have been // allocated using this pool. This will set the guarded slot as inaccessible. @@ -109,6 +115,18 @@ public: // Returns a pointer to the AllocatorState region. const AllocatorState *getAllocatorState() const { return &State; } + // Exposed as protected for testing. +protected: + // Returns the actual allocation size required to service an allocation with + // the provided Size and Alignment. + static size_t getRequiredBackingSize(size_t Size, size_t Alignment, + size_t PageSize); + + // Returns the provided pointer that meets the specified alignment, depending + // on whether it's left or right aligned. + static uintptr_t alignUp(uintptr_t Ptr, size_t Alignment); + static uintptr_t alignDown(uintptr_t Ptr, size_t Alignment); + private: // Name of actively-occupied slot mappings. static constexpr const char *kGwpAsanAliveSlotName = "GWP-ASan Alive Slot"; @@ -124,15 +142,30 @@ private: // memory into this process in a platform-specific way. Pointer and size // arguments are expected to be page-aligned. These functions will never // return on error, instead electing to kill the calling process on failure. - // Note that memory is initially mapped inaccessible. In order for RW - // mappings, call mapMemory() followed by markReadWrite() on the returned - // pointer. Each mapping is named on platforms that support it, primarily - // Android. This name must be a statically allocated string, as the Android - // kernel uses the string pointer directly. - void *mapMemory(size_t Size, const char *Name) const; - void unmapMemory(void *Ptr, size_t Size, const char *Name) const; - void markReadWrite(void *Ptr, size_t Size, const char *Name) const; - void markInaccessible(void *Ptr, size_t Size, const char *Name) const; + // The pool memory is initially reserved and inaccessible, and RW mappings are + // subsequently created and destroyed via allocateInGuardedPool() and + // deallocateInGuardedPool(). Each mapping is named on platforms that support + // it, primarily Android. This name must be a statically allocated string, as + // the Android kernel uses the string pointer directly. + void *map(size_t Size, const char *Name) const; + void unmap(void *Ptr, size_t Size) const; + + // The pool is managed separately, as some platforms (particularly Fuchsia) + // manage virtual memory regions as a chunk where individual pages can still + // have separate permissions. These platforms maintain metadata about the + // region in order to perform operations. The pool is unique as it's the only + // thing in GWP-ASan that treats pages in a single VM region on an individual + // basis for page protection. + // The pointer returned by reserveGuardedPool() is the reserved address range + // of (at least) Size bytes. + void *reserveGuardedPool(size_t Size); + // allocateInGuardedPool() Ptr and Size must be a subrange of the previously + // reserved pool range. + void allocateInGuardedPool(void *Ptr, size_t Size) const; + // deallocateInGuardedPool() Ptr and Size must be an exact pair previously + // passed to allocateInGuardedPool(). + void deallocateInGuardedPool(void *Ptr, size_t Size) const; + void unreserveGuardedPool(); // Get the page size from the platform-specific implementation. Only needs to // be called once, and the result should be cached in PageSize in this class. @@ -163,6 +196,10 @@ private: // A mutex to protect the guarded slot and metadata pool for this class. Mutex PoolMutex; + // Some unwinders can grab the libdl lock. In order to provide atfork + // protection, we need to ensure that we allow an unwinding thread to release + // the libdl lock before forking. + Mutex BacktraceMutex; // 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. @@ -191,22 +228,21 @@ private: // the sample rate. uint32_t AdjustedSampleRatePlusOne = 0; - // Pack the thread local variables into a struct to ensure that they're in - // the same cache line for performance reasons. These are the most touched - // variables in GWP-ASan. - struct alignas(8) ThreadLocalPackedVariables { - constexpr ThreadLocalPackedVariables() {} - // Thread-local decrementing counter that indicates that a given allocation - // should be sampled when it reaches zero. - uint32_t NextSampleCounter = 0; - // Guard against recursivity. Unwinders often contain complex behaviour that - // may not be safe for the allocator (i.e. the unwinder calls dlopen(), - // which calls malloc()). When recursive behaviour is detected, we will - // automatically fall back to the supporting allocator to supply the - // allocation. - bool RecursiveGuard = false; + // Additional platform specific data structure for the guarded pool mapping. + PlatformSpecificMapData GuardedPagePoolPlatformData = {}; + + class ScopedRecursiveGuard { + public: + ScopedRecursiveGuard() { getThreadLocals()->RecursiveGuard = true; } + ~ScopedRecursiveGuard() { getThreadLocals()->RecursiveGuard = false; } }; - static GWP_ASAN_TLS_INITIAL_EXEC ThreadLocalPackedVariables ThreadLocals; + + // Initialise the PRNG, platform-specific. + void initPRNG(); + + // xorshift (32-bit output), extremely fast PRNG that uses arithmetic + // operations only. Seeded using platform-specific mechanisms by initPRNG(). + uint32_t getRandomUnsigned32(); }; } // namespace gwp_asan diff --git a/gwp_asan/mutex.h b/gwp_asan/mutex.h index c29df4c..34b91a2 100644 --- a/gwp_asan/mutex.h +++ b/gwp_asan/mutex.h @@ -9,14 +9,11 @@ #ifndef GWP_ASAN_MUTEX_H_ #define GWP_ASAN_MUTEX_H_ -#ifdef __unix__ -#include <pthread.h> -#else -#error "GWP-ASan is not supported on this platform." -#endif +#include "gwp_asan/platform_specific/mutex_fuchsia.h" // IWYU pragma: keep +#include "gwp_asan/platform_specific/mutex_posix.h" // IWYU pragma: keep namespace gwp_asan { -class Mutex { +class Mutex final : PlatformMutex { public: constexpr Mutex() = default; ~Mutex() = default; @@ -28,11 +25,6 @@ public: bool tryLock(); // Unlock the mutex. void unlock(); - -private: -#ifdef __unix__ - pthread_mutex_t Mu = PTHREAD_MUTEX_INITIALIZER; -#endif // defined(__unix__) }; class ScopedLock { diff --git a/gwp_asan/optional/backtrace.h b/gwp_asan/optional/backtrace.h index 3a72eb3..9bb12af 100644 --- a/gwp_asan/optional/backtrace.h +++ b/gwp_asan/optional/backtrace.h @@ -9,21 +9,45 @@ #ifndef GWP_ASAN_OPTIONAL_BACKTRACE_H_ #define GWP_ASAN_OPTIONAL_BACKTRACE_H_ -#include "gwp_asan/optional/segv_handler.h" +#include "gwp_asan/optional/printf.h" #include "gwp_asan/options.h" namespace gwp_asan { -namespace options { -// Functions to get the platform-specific and implementation-specific backtrace -// and backtrace printing functions when RTGwpAsanBacktraceLibc or -// RTGwpAsanBacktraceSanitizerCommon are linked. Use these functions to get the -// backtrace function for populating the Options::Backtrace and -// Options::PrintBacktrace when initialising the GuardedPoolAllocator. Please -// note any thread-safety descriptions for the implementation of these functions -// that you use. -Backtrace_t getBacktraceFunction(); -crash_handler::PrintBacktrace_t getPrintBacktraceFunction(); -} // namespace options +namespace backtrace { +// ================================ 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 backtrace function that's suitable for +// unwinding through a signal handler. This is important primarily for frame- +// pointer based unwinders, DWARF or other unwinders can simply provide the +// normal backtrace function as the implementation here. On POSIX, SignalContext +// should be the `ucontext_t` from the signal handler. +typedef size_t (*SegvBacktrace_t)(uintptr_t *TraceBuffer, size_t Size, + void *SignalContext); + +// Returns platform-specific provided implementations of Backtrace_t for use +// inside the GWP-ASan core allocator. +options::Backtrace_t getBacktraceFunction(); + +// Returns platform-specific provided implementations of PrintBacktrace_t and +// SegvBacktrace_t for use in the optional SEGV handler. +PrintBacktrace_t getPrintBacktraceFunction(); +SegvBacktrace_t getSegvBacktraceFunction(); +} // namespace backtrace } // namespace gwp_asan #endif // GWP_ASAN_OPTIONAL_BACKTRACE_H_ diff --git a/gwp_asan/optional/backtrace_fuchsia.cpp b/gwp_asan/optional/backtrace_fuchsia.cpp new file mode 100644 index 0000000..09b0325 --- /dev/null +++ b/gwp_asan/optional/backtrace_fuchsia.cpp @@ -0,0 +1,27 @@ +//===-- backtrace_fuchsia.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/optional/backtrace.h" + +#include <zircon/sanitizer.h> + +namespace gwp_asan { +namespace backtrace { + +// Fuchsia's C library provides safe, fast, best-effort backtraces itself. +options::Backtrace_t getBacktraceFunction() { + return __sanitizer_fast_backtrace; +} + +// These are only used in fatal signal handling, which is not used on Fuchsia. + +PrintBacktrace_t getPrintBacktraceFunction() { return nullptr; } +SegvBacktrace_t getSegvBacktraceFunction() { return nullptr; } + +} // namespace backtrace +} // namespace gwp_asan diff --git a/gwp_asan/optional/backtrace_linux_libc.cpp b/gwp_asan/optional/backtrace_linux_libc.cpp index bb0aad2..ea8e72b 100644 --- a/gwp_asan/optional/backtrace_linux_libc.cpp +++ b/gwp_asan/optional/backtrace_linux_libc.cpp @@ -13,7 +13,9 @@ #include <stdlib.h> #include <string.h> +#include "gwp_asan/definitions.h" #include "gwp_asan/optional/backtrace.h" +#include "gwp_asan/optional/printf.h" #include "gwp_asan/options.h" namespace { @@ -23,8 +25,16 @@ size_t Backtrace(uintptr_t *TraceBuffer, size_t Size) { return backtrace(reinterpret_cast<void **>(TraceBuffer), Size); } +// We don't need any custom handling for the Segv backtrace - the libc unwinder +// has no problems with unwinding through a signal handler. Force inlining here +// to avoid the additional frame. +GWP_ASAN_ALWAYS_INLINE size_t SegvBacktrace(uintptr_t *TraceBuffer, size_t Size, + void * /*Context*/) { + return Backtrace(TraceBuffer, Size); +} + static void PrintBacktrace(uintptr_t *Trace, size_t TraceLength, - gwp_asan::crash_handler::Printf_t Printf) { + gwp_asan::Printf_t Printf) { if (TraceLength == 0) { Printf(" <not found (does your allocator support backtracing?)>\n\n"); return; @@ -47,10 +57,11 @@ static void PrintBacktrace(uintptr_t *Trace, size_t TraceLength, } // anonymous namespace namespace gwp_asan { -namespace options { -Backtrace_t getBacktraceFunction() { return Backtrace; } -crash_handler::PrintBacktrace_t getPrintBacktraceFunction() { - return PrintBacktrace; -} -} // namespace options +namespace backtrace { + +options::Backtrace_t getBacktraceFunction() { return Backtrace; } +PrintBacktrace_t getPrintBacktraceFunction() { return PrintBacktrace; } +SegvBacktrace_t getSegvBacktraceFunction() { return SegvBacktrace; } + +} // namespace backtrace } // namespace gwp_asan diff --git a/gwp_asan/optional/backtrace_sanitizer_common.cpp b/gwp_asan/optional/backtrace_sanitizer_common.cpp index 3ac4b52..e6cce86 100644 --- a/gwp_asan/optional/backtrace_sanitizer_common.cpp +++ b/gwp_asan/optional/backtrace_sanitizer_common.cpp @@ -22,30 +22,47 @@ void __sanitizer::BufferedStackTrace::UnwindImpl(uptr pc, uptr bp, void *context, bool request_fast, u32 max_depth) { - if (!StackTrace::WillUseFastUnwind(request_fast)) { - return Unwind(max_depth, pc, bp, context, 0, 0, request_fast); - } - Unwind(max_depth, pc, 0, context, 0, 0, false); + if (!StackTrace::WillUseFastUnwind(request_fast)) + return Unwind(max_depth, pc, 0, context, 0, 0, false); + + uptr top = 0; + uptr bottom = 0; + GetThreadStackTopAndBottom(/*at_initialization*/ false, &top, &bottom); + + return Unwind(max_depth, pc, bp, context, top, bottom, request_fast); } namespace { -size_t Backtrace(uintptr_t *TraceBuffer, size_t Size) { +size_t BacktraceCommon(uintptr_t *TraceBuffer, size_t Size, void *Context) { + // Use the slow sanitizer unwinder in the segv handler. Fast frame pointer + // unwinders can end up dropping frames because the kernel sigreturn() frame's + // return address is the return address at time of fault. This has the result + // of never actually capturing the PC where the signal was raised. + bool UseFastUnwind = (Context == nullptr); + __sanitizer::BufferedStackTrace Trace; Trace.Reset(); if (Size > __sanitizer::kStackTraceMax) Size = __sanitizer::kStackTraceMax; Trace.Unwind((__sanitizer::uptr)__builtin_return_address(0), - (__sanitizer::uptr)__builtin_frame_address(0), - /* ucontext */ nullptr, - /* fast unwind */ true, Size - 1); + (__sanitizer::uptr)__builtin_frame_address(0), Context, + UseFastUnwind, Size - 1); memcpy(TraceBuffer, Trace.trace, Trace.size * sizeof(uintptr_t)); return Trace.size; } +size_t Backtrace(uintptr_t *TraceBuffer, size_t Size) { + return BacktraceCommon(TraceBuffer, Size, nullptr); +} + +size_t SegvBacktrace(uintptr_t *TraceBuffer, size_t Size, void *Context) { + return BacktraceCommon(TraceBuffer, Size, Context); +} + static void PrintBacktrace(uintptr_t *Trace, size_t TraceLength, - gwp_asan::crash_handler::Printf_t Printf) { + gwp_asan::Printf_t Printf) { __sanitizer::StackTrace StackTrace; StackTrace.trace = reinterpret_cast<__sanitizer::uptr *>(Trace); StackTrace.size = TraceLength; @@ -60,21 +77,23 @@ static void PrintBacktrace(uintptr_t *Trace, size_t TraceLength, } // anonymous namespace namespace gwp_asan { -namespace options { +namespace backtrace { + // This function is thread-compatible. It must be synchronised in respect to any // other calls to getBacktraceFunction(), calls to getPrintBacktraceFunction(), // and calls to either of the functions that they return. Furthermore, this may // require synchronisation with any calls to sanitizer_common that use flags. // Generally, this function will be called during the initialisation of the // allocator, which is done in a thread-compatible manner. -Backtrace_t getBacktraceFunction() { +options::Backtrace_t getBacktraceFunction() { // The unwinder requires the default flags to be set. __sanitizer::SetCommonFlagsDefaults(); __sanitizer::InitializeCommonFlags(); return Backtrace; } -crash_handler::PrintBacktrace_t getPrintBacktraceFunction() { - return PrintBacktrace; -} -} // namespace options + +PrintBacktrace_t getPrintBacktraceFunction() { return PrintBacktrace; } +SegvBacktrace_t getSegvBacktraceFunction() { return SegvBacktrace; } + +} // namespace backtrace } // namespace gwp_asan diff --git a/gwp_asan/optional/options_parser.cpp b/gwp_asan/optional/options_parser.cpp index 2e63862..6023412 100644 --- a/gwp_asan/optional/options_parser.cpp +++ b/gwp_asan/optional/options_parser.cpp @@ -7,84 +7,251 @@ //===----------------------------------------------------------------------===// #include "gwp_asan/optional/options_parser.h" +#include "gwp_asan/optional/printf.h" +#include "gwp_asan/utilities.h" +#include <assert.h> #include <stdarg.h> #include <stdint.h> #include <stdlib.h> #include <string.h> -#include "gwp_asan/options.h" -#include "sanitizer_common/sanitizer_common.h" -#include "sanitizer_common/sanitizer_flag_parser.h" -#include "sanitizer_common/sanitizer_flags.h" - -namespace gwp_asan { -namespace options { namespace { -void registerGwpAsanFlags(__sanitizer::FlagParser *parser, Options *o) { -#define GWP_ASAN_OPTION(Type, Name, DefaultValue, Description) \ - RegisterFlag(parser, #Name, Description, &o->Name); +enum class OptionType : uint8_t { + OT_bool, + OT_int, +}; + +#define InvokeIfNonNull(Printf, ...) \ + do { \ + if (Printf) \ + Printf(__VA_ARGS__); \ + } while (0); + +class OptionParser { +public: + explicit OptionParser(gwp_asan::Printf_t PrintfForWarnings) + : Printf(PrintfForWarnings) {} + void registerOption(const char *Name, const char *Desc, OptionType Type, + void *Var); + void parseString(const char *S); + void printOptionDescriptions(); + +private: + // Calculate at compile-time how many options are available. +#define GWP_ASAN_OPTION(...) +1 + static constexpr size_t MaxOptions = 0 #include "gwp_asan/options.inc" + ; #undef GWP_ASAN_OPTION + + struct Option { + const char *Name; + const char *Desc; + OptionType Type; + void *Var; + } Options[MaxOptions]; + + size_t NumberOfOptions = 0; + const char *Buffer = nullptr; + uintptr_t Pos = 0; + gwp_asan::Printf_t Printf = nullptr; + + void skipWhitespace(); + void parseOptions(); + bool parseOption(); + bool setOptionToValue(const char *Name, const char *Value); +}; + +void OptionParser::printOptionDescriptions() { + InvokeIfNonNull(Printf, "GWP-ASan: Available options:\n"); + for (size_t I = 0; I < NumberOfOptions; ++I) + InvokeIfNonNull(Printf, "\t%s\n\t\t- %s\n", Options[I].Name, + Options[I].Desc); +} + +bool isSeparator(char C) { + return C == ' ' || C == ',' || C == ':' || C == '\n' || C == '\t' || + C == '\r'; +} + +bool isSeparatorOrNull(char C) { return !C || isSeparator(C); } + +void OptionParser::skipWhitespace() { + while (isSeparator(Buffer[Pos])) + ++Pos; +} + +bool OptionParser::parseOption() { + const uintptr_t NameStart = Pos; + while (Buffer[Pos] != '=' && !isSeparatorOrNull(Buffer[Pos])) + ++Pos; + + const char *Name = Buffer + NameStart; + if (Buffer[Pos] != '=') { + InvokeIfNonNull(Printf, "GWP-ASan: Expected '=' when parsing option '%s'.", + Name); + return false; + } + const uintptr_t ValueStart = ++Pos; + const char *Value; + if (Buffer[Pos] == '\'' || Buffer[Pos] == '"') { + const char Quote = Buffer[Pos++]; + while (Buffer[Pos] != 0 && Buffer[Pos] != Quote) + ++Pos; + if (Buffer[Pos] == 0) { + InvokeIfNonNull(Printf, "GWP-ASan: Unterminated string in option '%s'.", + Name); + return false; + } + Value = Buffer + ValueStart + 1; + ++Pos; // consume the closing quote + } else { + while (!isSeparatorOrNull(Buffer[Pos])) + ++Pos; + Value = Buffer + ValueStart; + } + + return setOptionToValue(Name, Value); } -const char *getCompileDefinitionGwpAsanDefaultOptions() { -#ifdef GWP_ASAN_DEFAULT_OPTIONS - return SANITIZER_STRINGIFY(GWP_ASAN_DEFAULT_OPTIONS); -#else - return ""; -#endif +void OptionParser::parseOptions() { + while (true) { + skipWhitespace(); + if (Buffer[Pos] == 0) + break; + if (!parseOption()) { + InvokeIfNonNull(Printf, "GWP-ASan: Options parsing failed.\n"); + return; + } + } +} + +void OptionParser::parseString(const char *S) { + if (!S) + return; + Buffer = S; + Pos = 0; + parseOptions(); +} + +bool parseBool(const char *Value, bool *b) { + if (strncmp(Value, "0", 1) == 0 || strncmp(Value, "no", 2) == 0 || + strncmp(Value, "false", 5) == 0) { + *b = false; + return true; + } + if (strncmp(Value, "1", 1) == 0 || strncmp(Value, "yes", 3) == 0 || + strncmp(Value, "true", 4) == 0) { + *b = true; + return true; + } + return false; +} + +bool OptionParser::setOptionToValue(const char *Name, const char *Value) { + for (size_t I = 0; I < NumberOfOptions; ++I) { + const uintptr_t Len = strlen(Options[I].Name); + if (strncmp(Name, Options[I].Name, Len) != 0 || Name[Len] != '=') + continue; + bool Ok = false; + switch (Options[I].Type) { + case OptionType::OT_bool: + Ok = parseBool(Value, reinterpret_cast<bool *>(Options[I].Var)); + if (!Ok) + InvokeIfNonNull( + Printf, "GWP-ASan: Invalid boolean value '%s' for option '%s'.\n", + Value, Options[I].Name); + break; + case OptionType::OT_int: + char *ValueEnd; + *reinterpret_cast<int *>(Options[I].Var) = + static_cast<int>(strtol(Value, &ValueEnd, 10)); + Ok = + *ValueEnd == '"' || *ValueEnd == '\'' || isSeparatorOrNull(*ValueEnd); + if (!Ok) + InvokeIfNonNull( + Printf, "GWP-ASan: Invalid integer value '%s' for option '%s'.\n", + Value, Options[I].Name); + break; + } + return Ok; + } + + InvokeIfNonNull(Printf, "GWP-ASan: Unknown option '%s'.", Name); + return true; +} + +void OptionParser::registerOption(const char *Name, const char *Desc, + OptionType Type, void *Var) { + assert(NumberOfOptions < MaxOptions && + "GWP-ASan Error: Ran out of space for options.\n"); + Options[NumberOfOptions].Name = Name; + Options[NumberOfOptions].Desc = Desc; + Options[NumberOfOptions].Type = Type; + Options[NumberOfOptions].Var = Var; + ++NumberOfOptions; +} + +void registerGwpAsanOptions(OptionParser *parser, + gwp_asan::options::Options *o) { +#define GWP_ASAN_OPTION(Type, Name, DefaultValue, Description) \ + parser->registerOption(#Name, Description, OptionType::OT_##Type, &o->Name); +#include "gwp_asan/options.inc" +#undef GWP_ASAN_OPTION } const char *getGwpAsanDefaultOptions() { return (__gwp_asan_default_options) ? __gwp_asan_default_options() : ""; } -Options *getOptionsInternal() { - static Options GwpAsanFlags; - return &GwpAsanFlags; +gwp_asan::options::Options *getOptionsInternal() { + static gwp_asan::options::Options GwpAsanOptions; + return &GwpAsanOptions; } } // anonymous namespace -void initOptions() { - __sanitizer::SetCommonFlagsDefaults(); +namespace gwp_asan { +namespace options { +void initOptions(const char *OptionsStr, Printf_t PrintfForWarnings) { Options *o = getOptionsInternal(); o->setDefaults(); - __sanitizer::FlagParser Parser; - registerGwpAsanFlags(&Parser, o); - - // Override from compile definition. - Parser.ParseString(getCompileDefinitionGwpAsanDefaultOptions()); + OptionParser Parser(PrintfForWarnings); + registerGwpAsanOptions(&Parser, o); - // Override from user-specified string. - Parser.ParseString(getGwpAsanDefaultOptions()); + // Override from the weak function definition in this executable. + Parser.parseString(getGwpAsanDefaultOptions()); - // Override from environment. - Parser.ParseString(__sanitizer::GetEnv("GWP_ASAN_OPTIONS")); + // Override from the provided options string. + Parser.parseString(OptionsStr); - __sanitizer::InitializeCommonFlags(); - if (__sanitizer::Verbosity()) - __sanitizer::ReportUnrecognizedFlags(); + if (o->help) + Parser.printOptionDescriptions(); if (!o->Enabled) return; - // Sanity checks for the parameters. if (o->MaxSimultaneousAllocations <= 0) { - __sanitizer::Printf("GWP-ASan ERROR: MaxSimultaneousAllocations must be > " - "0 when GWP-ASan is enabled.\n"); - exit(EXIT_FAILURE); + InvokeIfNonNull( + PrintfForWarnings, + "GWP-ASan ERROR: MaxSimultaneousAllocations must be > 0 when GWP-ASan " + "is enabled.\n"); + o->Enabled = false; } - - if (o->SampleRate < 1) { - __sanitizer::Printf( + if (o->SampleRate <= 0) { + InvokeIfNonNull( + PrintfForWarnings, "GWP-ASan ERROR: SampleRate must be > 0 when GWP-ASan is enabled.\n"); - exit(EXIT_FAILURE); + o->Enabled = false; } } +void initOptions(Printf_t PrintfForWarnings) { + initOptions(getenv("GWP_ASAN_OPTIONS"), PrintfForWarnings); +} + Options &getOptions() { return *getOptionsInternal(); } } // namespace options diff --git a/gwp_asan/optional/options_parser.h b/gwp_asan/optional/options_parser.h index 7a6bfaf..a5a0628 100644 --- a/gwp_asan/optional/options_parser.h +++ b/gwp_asan/optional/options_parser.h @@ -9,14 +9,15 @@ #ifndef GWP_ASAN_OPTIONAL_OPTIONS_PARSER_H_ #define GWP_ASAN_OPTIONAL_OPTIONS_PARSER_H_ -#include "gwp_asan/optional/backtrace.h" +#include "gwp_asan/optional/printf.h" #include "gwp_asan/options.h" -#include "sanitizer_common/sanitizer_common.h" namespace gwp_asan { namespace options { -// Parse the options from the GWP_ASAN_FLAGS environment variable. -void initOptions(); +// Parse the options from the GWP_ASAN_OPTIONS environment variable. +void initOptions(Printf_t PrintfForWarnings = nullptr); +// Parse the options from the provided string. +void initOptions(const char *OptionsStr, Printf_t PrintfForWarnings = nullptr); // Returns the initialised options. Call initOptions() prior to calling this // function. Options &getOptions(); @@ -24,8 +25,7 @@ Options &getOptions(); } // namespace gwp_asan extern "C" { -SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE const char * -__gwp_asan_default_options(); +__attribute__((weak)) const char *__gwp_asan_default_options(); } #endif // GWP_ASAN_OPTIONAL_OPTIONS_PARSER_H_ diff --git a/gwp_asan/optional/printf.h b/gwp_asan/optional/printf.h new file mode 100644 index 0000000..1004a2c --- /dev/null +++ b/gwp_asan/optional/printf.h @@ -0,0 +1,33 @@ +//===-- printf.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_PRINTF_H_ +#define GWP_ASAN_OPTIONAL_PRINTF_H_ + +namespace gwp_asan { + +// ================================ Requirements =============================== +// This function is required to be provided by the supporting allocator iff the +// allocator wants to use any of the optional components. +// ================================ 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, ...); + +} // namespace gwp_asan +#endif // GWP_ASAN_OPTIONAL_PRINTF_H_ diff --git a/gwp_asan/optional/segv_handler.h b/gwp_asan/optional/segv_handler.h index 10af150..87d9fe1 100644 --- a/gwp_asan/optional/segv_handler.h +++ b/gwp_asan/optional/segv_handler.h @@ -1,4 +1,4 @@ -//===-- crash_handler.h -----------------------------------------*- C++ -*-===// +//===-- segv_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. @@ -6,59 +6,15 @@ // //===----------------------------------------------------------------------===// -#ifndef GWP_ASAN_OPTIONAL_CRASH_HANDLER_H_ -#define GWP_ASAN_OPTIONAL_CRASH_HANDLER_H_ +#ifndef GWP_ASAN_OPTIONAL_SEGV_HANDLER_H_ +#define GWP_ASAN_OPTIONAL_SEGV_HANDLER_H_ #include "gwp_asan/guarded_pool_allocator.h" -#include "gwp_asan/options.h" +#include "gwp_asan/optional/backtrace.h" +#include "gwp_asan/optional/printf.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(); - +namespace segv_handler { // 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 @@ -66,16 +22,12 @@ PrintBacktrace_t getBasicPrintBacktraceFunction(); // 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); + gwp_asan::backtrace::PrintBacktrace_t PrintBacktrace, + gwp_asan::backtrace::SegvBacktrace_t SegvBacktrace); +// Uninistall the signal handlers, test-only. 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 segv_handler } // namespace gwp_asan -#endif // GWP_ASAN_OPTIONAL_CRASH_HANDLER_H_ +#endif // GWP_ASAN_OPTIONAL_SEGV_HANDLER_H_ diff --git a/gwp_asan/optional/segv_handler_fuchsia.cpp b/gwp_asan/optional/segv_handler_fuchsia.cpp new file mode 100644 index 0000000..966d7d0 --- /dev/null +++ b/gwp_asan/optional/segv_handler_fuchsia.cpp @@ -0,0 +1,22 @@ +//===-- segv_handler_fuchsia.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/optional/segv_handler.h" + +// GWP-ASan on Fuchsia doesn't currently support signal handlers. + +namespace gwp_asan { +namespace segv_handler { +void installSignalHandlers(gwp_asan::GuardedPoolAllocator * /* GPA */, + Printf_t /* Printf */, + backtrace::PrintBacktrace_t /* PrintBacktrace */, + backtrace::SegvBacktrace_t /* SegvBacktrace */) {} + +void uninstallSignalHandlers() {} +} // namespace segv_handler +} // namespace gwp_asan diff --git a/gwp_asan/optional/segv_handler_posix.cpp b/gwp_asan/optional/segv_handler_posix.cpp index 22589b8..5c9bb9f 100644 --- a/gwp_asan/optional/segv_handler_posix.cpp +++ b/gwp_asan/optional/segv_handler_posix.cpp @@ -1,4 +1,4 @@ -//===-- crash_handler_posix.cpp ---------------------------------*- C++ -*-===// +//===-- segv_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. @@ -12,62 +12,30 @@ #include "gwp_asan/optional/segv_handler.h" #include "gwp_asan/options.h" +// RHEL creates the PRIu64 format macro (for printing uint64_t's) only when this +// macro is defined before including <inttypes.h>. +#ifndef __STDC_FORMAT_MACROS +#define __STDC_FORMAT_MACROS 1 +#endif + #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; +using gwp_asan::Printf_t; +using gwp_asan::backtrace::PrintBacktrace_t; +using gwp_asan::backtrace::SegvBacktrace_t; -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); - } -} +namespace { struct ScopedEndOfReportDecorator { - ScopedEndOfReportDecorator(gwp_asan::crash_handler::Printf_t Printf) - : Printf(Printf) {} + ScopedEndOfReportDecorator(gwp_asan::Printf_t Printf) : Printf(Printf) {} ~ScopedEndOfReportDecorator() { Printf("*** End GWP-ASan report ***\n"); } - gwp_asan::crash_handler::Printf_t Printf; + gwp_asan::Printf_t Printf; }; // Prints the provided error and metadata information. @@ -117,51 +85,10 @@ void printHeader(Error E, uintptr_t AccessPtr, 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) { + SegvBacktrace_t SegvBacktrace, Printf_t Printf, + PrintBacktrace_t PrintBacktrace, void *Context) { assert(State && "dumpReport missing Allocator State."); assert(Metadata && "dumpReport missing Metadata."); assert(Printf && "dumpReport missing Printf."); @@ -194,7 +121,8 @@ void dumpReport(uintptr_t ErrorPtr, const gwp_asan::AllocatorState *State, // Print the fault backtrace. static constexpr unsigned kMaximumStackFramesForCrashTrace = 512; uintptr_t Trace[kMaximumStackFramesForCrashTrace]; - size_t TraceLength = Backtrace(Trace, kMaximumStackFramesForCrashTrace); + size_t TraceLength = + SegvBacktrace(Trace, kMaximumStackFramesForCrashTrace, Context); PrintBacktrace(Trace, TraceLength, Printf); @@ -204,7 +132,7 @@ void dumpReport(uintptr_t ErrorPtr, const gwp_asan::AllocatorState *State, // Maybe print the deallocation trace. if (__gwp_asan_is_deallocated(AllocMeta)) { uint64_t ThreadID = __gwp_asan_get_deallocation_thread_id(AllocMeta); - if (ThreadID == kInvalidThreadID) + if (ThreadID == gwp_asan::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); @@ -215,7 +143,7 @@ void dumpReport(uintptr_t ErrorPtr, const gwp_asan::AllocatorState *State, // Print the allocation trace. uint64_t ThreadID = __gwp_asan_get_allocation_thread_id(AllocMeta); - if (ThreadID == kInvalidThreadID) + if (ThreadID == gwp_asan::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); @@ -223,5 +151,75 @@ void dumpReport(uintptr_t ErrorPtr, const gwp_asan::AllocatorState *State, AllocMeta, Trace, kMaximumStackFramesForCrashTrace); PrintBacktrace(Trace, TraceLength, Printf); } -} // namespace crash_handler + +struct sigaction PreviousHandler; +bool SignalHandlerInstalled; +gwp_asan::GuardedPoolAllocator *GPAForSignalHandler; +Printf_t PrintfForSignalHandler; +PrintBacktrace_t PrintBacktraceForSignalHandler; +SegvBacktrace_t BacktraceForSignalHandler; + +static void sigSegvHandler(int sig, siginfo_t *info, void *ucontext) { + if (GPAForSignalHandler) { + GPAForSignalHandler->stop(); + + dumpReport(reinterpret_cast<uintptr_t>(info->si_addr), + GPAForSignalHandler->getAllocatorState(), + GPAForSignalHandler->getMetadataRegion(), + BacktraceForSignalHandler, PrintfForSignalHandler, + PrintBacktraceForSignalHandler, ucontext); + } + + // 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); + } +} +} // anonymous namespace + +namespace gwp_asan { +namespace segv_handler { + +void installSignalHandlers(gwp_asan::GuardedPoolAllocator *GPA, Printf_t Printf, + PrintBacktrace_t PrintBacktrace, + SegvBacktrace_t SegvBacktrace) { + assert(GPA && "GPA wasn't provided to installSignalHandlers."); + assert(Printf && "Printf wasn't provided to installSignalHandlers."); + assert(PrintBacktrace && + "PrintBacktrace wasn't provided to installSignalHandlers."); + assert(SegvBacktrace && + "SegvBacktrace wasn't provided to installSignalHandlers."); + GPAForSignalHandler = GPA; + PrintfForSignalHandler = Printf; + PrintBacktraceForSignalHandler = PrintBacktrace; + BacktraceForSignalHandler = SegvBacktrace; + + 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; + } +} +} // namespace segv_handler } // namespace gwp_asan diff --git a/gwp_asan/options.inc b/gwp_asan/options.inc index 6cdddfb..9900a2a 100644 --- a/gwp_asan/options.inc +++ b/gwp_asan/options.inc @@ -10,17 +10,18 @@ #error "Define GWP_ASAN_OPTION prior to including this file!" #endif -GWP_ASAN_OPTION(bool, Enabled, true, "Is GWP-ASan enabled? Defaults to true.") +#ifndef GWP_ASAN_DEFAULT_ENABLED +#define GWP_ASAN_DEFAULT_ENABLED true +#endif -GWP_ASAN_OPTION( - bool, PerfectlyRightAlign, false, - "When allocations are right-aligned, should we perfectly align them up to " - "the page boundary? By default (false), we round up allocation size to the " - "nearest power of two (1, 2, 4, 8, 16) up to a maximum of 16-byte " - "alignment for performance reasons. For Bionic, we use 8-byte alignment by " - "default. Setting this to true can find single byte buffer-overflows for " - "multibyte allocations at the cost of performance, and may be incompatible " - "with some architectures.") +#ifndef GWP_ASAN_STRINGIFY +#define GWP_ASAN_STRINGIFY(S) GWP_ASAN_STRINGIFY_(S) +#define GWP_ASAN_STRINGIFY_(S) #S +#endif + +GWP_ASAN_OPTION(bool, Enabled, GWP_ASAN_DEFAULT_ENABLED, + "Is GWP-ASan enabled? Defaults to " GWP_ASAN_STRINGIFY( + GWP_ASAN_DEFAULT_ENABLED) ".") GWP_ASAN_OPTION(int, MaxSimultaneousAllocations, 16, "Number of simultaneously-guarded allocations available in the " @@ -29,7 +30,7 @@ GWP_ASAN_OPTION(int, MaxSimultaneousAllocations, 16, GWP_ASAN_OPTION(int, SampleRate, 5000, "The probability (1 / SampleRate) that an allocation is " "selected for GWP-ASan sampling. Default is 5000. Sample rates " - "up to (2^31 - 1) are supported.") + "up to (2^30 - 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 @@ -51,3 +52,18 @@ GWP_ASAN_OPTION( GWP_ASAN_OPTION(bool, InstallForkHandlers, true, "Install GWP-ASan atfork handlers to acquire internal locks " "before fork and release them after.") + +GWP_ASAN_OPTION(bool, help, false, "Print a summary of the available options.") + +// ============================================================================= +// ==== WARNING +// ============================================================================= +// If you are adding flags to GWP-ASan, please note that GWP-ASan flag strings +// may be parsed by trusted system components (on Android, GWP-ASan flag strings +// are parsed by libc during the dynamic loader). This means that GWP-ASan +// should never feature flags like log paths on disk, because this can lead to +// arbitrary file write and thus privilege escalation. For an example, see the +// setuid ASan log_path exploits: https://www.exploit-db.com/exploits/46241. +// +// Please place all new flags above this warning, so that the warning always +// stays at the bottom. diff --git a/gwp_asan/platform_specific/common_fuchsia.cpp b/gwp_asan/platform_specific/common_fuchsia.cpp new file mode 100644 index 0000000..b469ef8 --- /dev/null +++ b/gwp_asan/platform_specific/common_fuchsia.cpp @@ -0,0 +1,15 @@ +//===-- common_fuchsia.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 { +// This is only used for AllocationTrace.ThreadID and allocation traces are not +// yet supported on Fuchsia. +uint64_t getThreadID() { return kInvalidThreadID; } +} // namespace gwp_asan diff --git a/gwp_asan/platform_specific/common_posix.cpp b/gwp_asan/platform_specific/common_posix.cpp index e44e629..0637fc2 100644 --- a/gwp_asan/platform_specific/common_posix.cpp +++ b/gwp_asan/platform_specific/common_posix.cpp @@ -1,4 +1,4 @@ -//===-- common_posix.cpp ---------------------------------*- C++ -*-===// +//===-- 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. @@ -8,7 +8,9 @@ #include "gwp_asan/common.h" -#include <sys/syscall.h> +#include <stdint.h> +#include <sys/syscall.h> // IWYU pragma: keep +// IWYU pragma: no_include <syscall.h> #include <unistd.h> namespace gwp_asan { diff --git a/gwp_asan/platform_specific/guarded_pool_allocator_fuchsia.cpp b/gwp_asan/platform_specific/guarded_pool_allocator_fuchsia.cpp new file mode 100644 index 0000000..ca5231a --- /dev/null +++ b/gwp_asan/platform_specific/guarded_pool_allocator_fuchsia.cpp @@ -0,0 +1,104 @@ +//===-- guarded_pool_allocator_fuchsia.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/guarded_pool_allocator.h" +#include "gwp_asan/utilities.h" + +#include <assert.h> +#include <stdint.h> +#include <string.h> +#include <zircon/process.h> +#include <zircon/syscalls.h> + +namespace gwp_asan { +void GuardedPoolAllocator::initPRNG() { + _zx_cprng_draw(&getThreadLocals()->RandomState, sizeof(uint32_t)); +} + +void *GuardedPoolAllocator::map(size_t Size, const char *Name) const { + assert((Size % State.PageSize) == 0); + zx_handle_t Vmo; + zx_status_t Status = _zx_vmo_create(Size, 0, &Vmo); + Check(Status == ZX_OK, "Failed to create Vmo"); + _zx_object_set_property(Vmo, ZX_PROP_NAME, Name, strlen(Name)); + zx_vaddr_t Addr; + Status = _zx_vmar_map(_zx_vmar_root_self(), + ZX_VM_PERM_READ | ZX_VM_PERM_WRITE | ZX_VM_ALLOW_FAULTS, + 0, Vmo, 0, Size, &Addr); + Check(Status == ZX_OK, "Vmo mapping failed"); + _zx_handle_close(Vmo); + return reinterpret_cast<void *>(Addr); +} + +void GuardedPoolAllocator::unmap(void *Ptr, size_t Size) const { + assert((reinterpret_cast<uintptr_t>(Ptr) % State.PageSize) == 0); + assert((Size % State.PageSize) == 0); + zx_status_t Status = _zx_vmar_unmap(_zx_vmar_root_self(), + reinterpret_cast<zx_vaddr_t>(Ptr), Size); + Check(Status == ZX_OK, "Vmo unmapping failed"); +} + +void *GuardedPoolAllocator::reserveGuardedPool(size_t Size) { + assert((Size % State.PageSize) == 0); + zx_vaddr_t Addr; + const zx_status_t Status = _zx_vmar_allocate( + _zx_vmar_root_self(), + ZX_VM_CAN_MAP_READ | ZX_VM_CAN_MAP_WRITE | ZX_VM_CAN_MAP_SPECIFIC, 0, + Size, &GuardedPagePoolPlatformData.Vmar, &Addr); + Check(Status == ZX_OK, "Failed to reserve guarded pool allocator memory"); + _zx_object_set_property(GuardedPagePoolPlatformData.Vmar, ZX_PROP_NAME, + kGwpAsanGuardPageName, strlen(kGwpAsanGuardPageName)); + return reinterpret_cast<void *>(Addr); +} + +void GuardedPoolAllocator::unreserveGuardedPool() { + const zx_handle_t Vmar = GuardedPagePoolPlatformData.Vmar; + assert(Vmar != ZX_HANDLE_INVALID && Vmar != _zx_vmar_root_self()); + Check(_zx_vmar_destroy(Vmar) == ZX_OK, "Failed to destroy a vmar"); + Check(_zx_handle_close(Vmar) == ZX_OK, "Failed to close a vmar"); + GuardedPagePoolPlatformData.Vmar = ZX_HANDLE_INVALID; +} + +void GuardedPoolAllocator::allocateInGuardedPool(void *Ptr, size_t Size) const { + assert((reinterpret_cast<uintptr_t>(Ptr) % State.PageSize) == 0); + assert((Size % State.PageSize) == 0); + zx_handle_t Vmo; + zx_status_t Status = _zx_vmo_create(Size, 0, &Vmo); + Check(Status == ZX_OK, "Failed to create vmo"); + _zx_object_set_property(Vmo, ZX_PROP_NAME, kGwpAsanAliveSlotName, + strlen(kGwpAsanAliveSlotName)); + const zx_handle_t Vmar = GuardedPagePoolPlatformData.Vmar; + assert(Vmar != ZX_HANDLE_INVALID && Vmar != _zx_vmar_root_self()); + const size_t Offset = + reinterpret_cast<uintptr_t>(Ptr) - State.GuardedPagePool; + zx_vaddr_t P; + Status = _zx_vmar_map(Vmar, + ZX_VM_PERM_READ | ZX_VM_PERM_WRITE | + ZX_VM_ALLOW_FAULTS | ZX_VM_SPECIFIC, + Offset, Vmo, 0, Size, &P); + Check(Status == ZX_OK, "Vmo mapping failed"); + _zx_handle_close(Vmo); +} + +void GuardedPoolAllocator::deallocateInGuardedPool(void *Ptr, + size_t Size) const { + assert((reinterpret_cast<uintptr_t>(Ptr) % State.PageSize) == 0); + assert((Size % State.PageSize) == 0); + const zx_handle_t Vmar = GuardedPagePoolPlatformData.Vmar; + assert(Vmar != ZX_HANDLE_INVALID && Vmar != _zx_vmar_root_self()); + const zx_status_t Status = + _zx_vmar_unmap(Vmar, reinterpret_cast<zx_vaddr_t>(Ptr), Size); + Check(Status == ZX_OK, "Vmar unmapping failed"); +} + +size_t GuardedPoolAllocator::getPlatformPageSize() { + return _zx_system_get_page_size(); +} + +void GuardedPoolAllocator::installAtFork() {} +} // namespace gwp_asan diff --git a/gwp_asan/platform_specific/guarded_pool_allocator_fuchsia.h b/gwp_asan/platform_specific/guarded_pool_allocator_fuchsia.h new file mode 100644 index 0000000..fbd7d3a --- /dev/null +++ b/gwp_asan/platform_specific/guarded_pool_allocator_fuchsia.h @@ -0,0 +1,22 @@ +//===-- guarded_pool_allocator_fuchsia.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 +// +//===----------------------------------------------------------------------===// + +#if defined(__Fuchsia__) +#ifndef GWP_ASAN_GUARDED_POOL_ALLOCATOR_FUCHSIA_H_ +#define GWP_ASAN_GUARDED_POOL_ALLOCATOR_FUCHSIA_H_ + +#include <zircon/types.h> + +namespace gwp_asan { +struct PlatformSpecificMapData { + zx_handle_t Vmar; +}; +} // namespace gwp_asan + +#endif // GWP_ASAN_GUARDED_POOL_ALLOCATOR_FUCHSIA_H_ +#endif // defined(__Fuchsia__) diff --git a/gwp_asan/platform_specific/guarded_pool_allocator_posix.cpp b/gwp_asan/platform_specific/guarded_pool_allocator_posix.cpp index a8767a4..adb7330 100644 --- a/gwp_asan/platform_specific/guarded_pool_allocator_posix.cpp +++ b/gwp_asan/platform_specific/guarded_pool_allocator_posix.cpp @@ -6,16 +6,17 @@ // //===----------------------------------------------------------------------===// +#include "gwp_asan/common.h" #include "gwp_asan/guarded_pool_allocator.h" +#include "gwp_asan/platform_specific/guarded_pool_allocator_tls.h" #include "gwp_asan/utilities.h" #include <assert.h> -#include <errno.h> -#include <signal.h> +#include <pthread.h> +#include <stdint.h> #include <stdlib.h> -#include <string.h> #include <sys/mman.h> -#include <sys/types.h> +#include <time.h> #include <unistd.h> #ifdef ANDROID @@ -24,6 +25,7 @@ #define PR_SET_VMA_ANON_NAME 0 #endif // ANDROID +namespace { void MaybeSetMappingName(void *Mapping, size_t Size, const char *Name) { #ifdef ANDROID prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, Mapping, Size, Name); @@ -31,39 +33,64 @@ void MaybeSetMappingName(void *Mapping, size_t Size, const char *Name) { // Anonymous mapping names are only supported on Android. return; } +} // anonymous namespace namespace gwp_asan { -void *GuardedPoolAllocator::mapMemory(size_t Size, const char *Name) const { - void *Ptr = - mmap(nullptr, Size, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + +void GuardedPoolAllocator::initPRNG() { + getThreadLocals()->RandomState = + static_cast<uint32_t>(time(nullptr) + getThreadID()); +} + +void *GuardedPoolAllocator::map(size_t Size, const char *Name) const { + assert((Size % State.PageSize) == 0); + void *Ptr = mmap(nullptr, Size, PROT_READ | PROT_WRITE, + MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); 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 { +void GuardedPoolAllocator::unmap(void *Ptr, size_t Size) const { + assert((reinterpret_cast<uintptr_t>(Ptr) % State.PageSize) == 0); + assert((Size % State.PageSize) == 0); 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 { +void *GuardedPoolAllocator::reserveGuardedPool(size_t Size) { + assert((Size % State.PageSize) == 0); + void *Ptr = + mmap(nullptr, Size, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + Check(Ptr != MAP_FAILED, "Failed to reserve guarded pool allocator memory"); + MaybeSetMappingName(Ptr, Size, kGwpAsanGuardPageName); + return Ptr; +} + +void GuardedPoolAllocator::unreserveGuardedPool() { + unmap(reinterpret_cast<void *>(State.GuardedPagePool), + State.GuardedPagePoolEnd - State.GuardedPagePool); +} + +void GuardedPoolAllocator::allocateInGuardedPool(void *Ptr, size_t Size) const { + assert((reinterpret_cast<uintptr_t>(Ptr) % State.PageSize) == 0); + assert((Size % State.PageSize) == 0); Check(mprotect(Ptr, Size, PROT_READ | PROT_WRITE) == 0, - "Failed to set guarded pool allocator memory at as RW."); - MaybeSetMappingName(Ptr, Size, Name); + "Failed to allocate in guarded pool allocator memory"); + MaybeSetMappingName(Ptr, Size, kGwpAsanAliveSlotName); } -void GuardedPoolAllocator::markInaccessible(void *Ptr, size_t Size, - const char *Name) const { +void GuardedPoolAllocator::deallocateInGuardedPool(void *Ptr, + size_t Size) const { + assert((reinterpret_cast<uintptr_t>(Ptr) % State.PageSize) == 0); + assert((Size % State.PageSize) == 0); // 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. 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); + "Failed to deallocate in guarded pool allocator memory"); + MaybeSetMappingName(Ptr, Size, kGwpAsanGuardPageName); } size_t GuardedPoolAllocator::getPlatformPageSize() { @@ -81,5 +108,4 @@ void GuardedPoolAllocator::installAtFork() { }; pthread_atfork(Disable, Enable, Enable); } - } // namespace gwp_asan diff --git a/gwp_asan/platform_specific/guarded_pool_allocator_posix.h b/gwp_asan/platform_specific/guarded_pool_allocator_posix.h new file mode 100644 index 0000000..7f4ba0d --- /dev/null +++ b/gwp_asan/platform_specific/guarded_pool_allocator_posix.h @@ -0,0 +1,18 @@ +//===-- guarded_pool_allocator_posix.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 +// +//===----------------------------------------------------------------------===// + +#if defined(__unix__) +#ifndef GWP_ASAN_GUARDED_POOL_ALLOCATOR_POSIX_H_ +#define GWP_ASAN_GUARDED_POOL_ALLOCATOR_POSIX_H_ + +namespace gwp_asan { +struct PlatformSpecificMapData {}; +} // namespace gwp_asan + +#endif // GWP_ASAN_GUARDED_POOL_ALLOCATOR_POSIX_H_ +#endif // defined(__unix__) diff --git a/gwp_asan/platform_specific/guarded_pool_allocator_tls.h b/gwp_asan/platform_specific/guarded_pool_allocator_tls.h new file mode 100644 index 0000000..3e2055d --- /dev/null +++ b/gwp_asan/platform_specific/guarded_pool_allocator_tls.h @@ -0,0 +1,55 @@ +//===-- guarded_pool_allocator_tls.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_GUARDED_POOL_ALLOCATOR_TLS_H_ +#define GWP_ASAN_GUARDED_POOL_ALLOCATOR_TLS_H_ + +#include "gwp_asan/definitions.h" + +#include <stdint.h> + +namespace gwp_asan { +// Pack the thread local variables into a struct to ensure that they're in +// the same cache line for performance reasons. These are the most touched +// variables in GWP-ASan. +struct ThreadLocalPackedVariables { + constexpr ThreadLocalPackedVariables() + : RandomState(0xacd979ce), NextSampleCounter(0), RecursiveGuard(false) {} + // Initialised to a magic constant so that an uninitialised GWP-ASan won't + // regenerate its sample counter for as long as possible. The xorshift32() + // algorithm used below results in getRandomUnsigned32(0xacd979ce) == + // 0xfffffffe. + uint32_t RandomState; + // Thread-local decrementing counter that indicates that a given allocation + // should be sampled when it reaches zero. + uint32_t NextSampleCounter : 31; + // The mask is needed to silence conversion errors. + static const uint32_t NextSampleCounterMask = (1U << 31) - 1; + // Guard against recursivity. Unwinders often contain complex behaviour that + // may not be safe for the allocator (i.e. the unwinder calls dlopen(), + // which calls malloc()). When recursive behaviour is detected, we will + // automatically fall back to the supporting allocator to supply the + // allocation. + bool RecursiveGuard : 1; +}; +static_assert(sizeof(ThreadLocalPackedVariables) == sizeof(uint64_t), + "thread local data does not fit in a uint64_t"); +} // namespace gwp_asan + +#ifdef GWP_ASAN_PLATFORM_TLS_HEADER +#include GWP_ASAN_PLATFORM_TLS_HEADER +#else +namespace gwp_asan { +inline ThreadLocalPackedVariables *getThreadLocals() { + alignas(8) static GWP_ASAN_TLS_INITIAL_EXEC ThreadLocalPackedVariables Locals; + return &Locals; +} +} // namespace gwp_asan +#endif // GWP_ASAN_PLATFORM_TLS_HEADER + +#endif // GWP_ASAN_GUARDED_POOL_ALLOCATOR_TLS_H_ diff --git a/gwp_asan/platform_specific/mutex_fuchsia.cpp b/gwp_asan/platform_specific/mutex_fuchsia.cpp new file mode 100644 index 0000000..0431a82 --- /dev/null +++ b/gwp_asan/platform_specific/mutex_fuchsia.cpp @@ -0,0 +1,21 @@ +//===-- mutex_fuchsia.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/mutex.h" + +#include <lib/sync/mutex.h> + +namespace gwp_asan { +void Mutex::lock() __TA_NO_THREAD_SAFETY_ANALYSIS { sync_mutex_lock(&Mu); } + +bool Mutex::tryLock() __TA_NO_THREAD_SAFETY_ANALYSIS { + return sync_mutex_trylock(&Mu) == ZX_OK; +} + +void Mutex::unlock() __TA_NO_THREAD_SAFETY_ANALYSIS { sync_mutex_unlock(&Mu); } +} // namespace gwp_asan diff --git a/gwp_asan/platform_specific/mutex_fuchsia.h b/gwp_asan/platform_specific/mutex_fuchsia.h new file mode 100644 index 0000000..edfc1a6 --- /dev/null +++ b/gwp_asan/platform_specific/mutex_fuchsia.h @@ -0,0 +1,23 @@ +//===-- mutex_fuchsia.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 +// +//===----------------------------------------------------------------------===// + +#if defined(__Fuchsia__) +#ifndef GWP_ASAN_MUTEX_FUCHSIA_H_ +#define GWP_ASAN_MUTEX_FUCHSIA_H_ + +#include <lib/sync/mutex.h> + +namespace gwp_asan { +class PlatformMutex { +protected: + sync_mutex_t Mu = {}; +}; +} // namespace gwp_asan + +#endif // GWP_ASAN_MUTEX_FUCHSIA_H_ +#endif // defined(__Fuchsia__) diff --git a/gwp_asan/platform_specific/mutex_posix.h b/gwp_asan/platform_specific/mutex_posix.h new file mode 100644 index 0000000..7f02391 --- /dev/null +++ b/gwp_asan/platform_specific/mutex_posix.h @@ -0,0 +1,23 @@ +//===-- mutex_posix.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 +// +//===----------------------------------------------------------------------===// + +#if defined(__unix__) +#ifndef GWP_ASAN_MUTEX_POSIX_H_ +#define GWP_ASAN_MUTEX_POSIX_H_ + +#include <pthread.h> + +namespace gwp_asan { +class PlatformMutex { +protected: + pthread_mutex_t Mu = PTHREAD_MUTEX_INITIALIZER; +}; +} // namespace gwp_asan + +#endif // GWP_ASAN_MUTEX_POSIX_H_ +#endif // defined(__unix__) diff --git a/gwp_asan/platform_specific/utilities_fuchsia.cpp b/gwp_asan/platform_specific/utilities_fuchsia.cpp new file mode 100644 index 0000000..bc9d3a4 --- /dev/null +++ b/gwp_asan/platform_specific/utilities_fuchsia.cpp @@ -0,0 +1,19 @@ +//===-- utilities_fuchsia.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" + +#include <string.h> +#include <zircon/sanitizer.h> + +namespace gwp_asan { +void die(const char *Message) { + __sanitizer_log_write(Message, strlen(Message)); + __builtin_trap(); +} +} // namespace gwp_asan diff --git a/gwp_asan/platform_specific/utilities_posix.cpp b/gwp_asan/platform_specific/utilities_posix.cpp index 45b1939..7357963 100644 --- a/gwp_asan/platform_specific/utilities_posix.cpp +++ b/gwp_asan/platform_specific/utilities_posix.cpp @@ -6,12 +6,10 @@ // //===----------------------------------------------------------------------===// -#include "gwp_asan/definitions.h" -#include "gwp_asan/utilities.h" - -#include <assert.h> +#include <features.h> // IWYU pragma: keep (for __BIONIC__ macro) #ifdef __BIONIC__ +#include "gwp_asan/definitions.h" #include <stdlib.h> extern "C" GWP_ASAN_WEAK void android_set_abort_message(const char *); #else // __BIONIC__ @@ -19,71 +17,14 @@ extern "C" GWP_ASAN_WEAK void android_set_abort_message(const char *); #endif namespace gwp_asan { - +void die(const char *Message) { #ifdef __BIONIC__ -void Check(bool Condition, const char *Message) { - if (Condition) - return; if (&android_set_abort_message != nullptr) android_set_abort_message(Message); abort(); -} #else // __BIONIC__ -void Check(bool Condition, const char *Message) { - if (Condition) - return; fprintf(stderr, "%s", Message); __builtin_trap(); -} #endif // __BIONIC__ - -// See `bionic/tests/malloc_test.cpp` in the Android source for documentation -// regarding their alignment guarantees. We always round up to the closest -// 8-byte window. As GWP-ASan's malloc(X) can always get exactly an X-sized -// allocation, an allocation that rounds up to 16-bytes will always be given a -// 16-byte aligned allocation. -static size_t alignBionic(size_t RealAllocationSize) { - if (RealAllocationSize % 8 == 0) - return RealAllocationSize; - return RealAllocationSize + 8 - (RealAllocationSize % 8); -} - -static size_t alignPowerOfTwo(size_t RealAllocationSize) { - if (RealAllocationSize <= 2) - return RealAllocationSize; - if (RealAllocationSize <= 4) - return 4; - if (RealAllocationSize <= 8) - return 8; - if (RealAllocationSize % 16 == 0) - return RealAllocationSize; - return RealAllocationSize + 16 - (RealAllocationSize % 16); } - -#ifdef __BIONIC__ -static constexpr AlignmentStrategy PlatformDefaultAlignment = - AlignmentStrategy::BIONIC; -#else // __BIONIC__ -static constexpr AlignmentStrategy PlatformDefaultAlignment = - AlignmentStrategy::POWER_OF_TWO; -#endif // __BIONIC__ - -size_t rightAlignedAllocationSize(size_t RealAllocationSize, - AlignmentStrategy Align) { - assert(RealAllocationSize > 0); - if (Align == AlignmentStrategy::DEFAULT) - Align = PlatformDefaultAlignment; - - switch (Align) { - case AlignmentStrategy::BIONIC: - return alignBionic(RealAllocationSize); - case AlignmentStrategy::POWER_OF_TWO: - return alignPowerOfTwo(RealAllocationSize); - case AlignmentStrategy::PERFECT: - return RealAllocationSize; - case AlignmentStrategy::DEFAULT: - __builtin_unreachable(); - } -} - } // namespace gwp_asan diff --git a/gwp_asan/random.cpp b/gwp_asan/random.cpp deleted file mode 100644 index 2180f92..0000000 --- a/gwp_asan/random.cpp +++ /dev/null @@ -1,31 +0,0 @@ -//===-- random.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/random.h" -#include "gwp_asan/common.h" - -#include <time.h> - -// Initialised to a magic constant so that an uninitialised GWP-ASan won't -// regenerate its sample counter for as long as possible. The xorshift32() -// algorithm used below results in getRandomUnsigned32(0xff82eb50) == -// 0xfffffea4. -GWP_ASAN_TLS_INITIAL_EXEC uint32_t RandomState = 0xff82eb50; - -namespace gwp_asan { -void initPRNG() { - RandomState = time(nullptr) + getThreadID(); -} - -uint32_t getRandomUnsigned32() { - RandomState ^= RandomState << 13; - RandomState ^= RandomState >> 17; - RandomState ^= RandomState << 5; - return RandomState; -} -} // namespace gwp_asan diff --git a/gwp_asan/random.h b/gwp_asan/random.h deleted file mode 100644 index 953b989..0000000 --- a/gwp_asan/random.h +++ /dev/null @@ -1,23 +0,0 @@ -//===-- random.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_RANDOM_H_ -#define GWP_ASAN_RANDOM_H_ - -#include <stdint.h> - -namespace gwp_asan { -// Initialise the PRNG, using time and thread ID as the seed. -void initPRNG(); - -// xorshift (32-bit output), extremely fast PRNG that uses arithmetic operations -// only. Seeded using walltime. -uint32_t getRandomUnsigned32(); -} // namespace gwp_asan - -#endif // GWP_ASAN_RANDOM_H_ diff --git a/gwp_asan/tests/alignment.cpp b/gwp_asan/tests/alignment.cpp index bf98f1f..5f24a9a 100644 --- a/gwp_asan/tests/alignment.cpp +++ b/gwp_asan/tests/alignment.cpp @@ -6,39 +6,109 @@ // //===----------------------------------------------------------------------===// +#include "gwp_asan/guarded_pool_allocator.h" #include "gwp_asan/tests/harness.h" -#include "gwp_asan/utilities.h" - -TEST(AlignmentTest, PowerOfTwo) { - std::vector<std::pair<size_t, size_t>> AskedSizeToAlignedSize = { - {1, 1}, {2, 2}, {3, 4}, {4, 4}, {5, 8}, {7, 8}, - {8, 8}, {9, 16}, {15, 16}, {16, 16}, {17, 32}, {31, 32}, - {32, 32}, {33, 48}, {4095, 4096}, {4096, 4096}, - }; - - for (const auto &KV : AskedSizeToAlignedSize) { - EXPECT_EQ(KV.second, - gwp_asan::rightAlignedAllocationSize( - KV.first, gwp_asan::AlignmentStrategy::POWER_OF_TWO)); + +class AlignmentTestGPA : public gwp_asan::GuardedPoolAllocator { +public: + static size_t getRequiredBackingSize(size_t Size, size_t Alignment, + size_t PageSize) { + return GuardedPoolAllocator::getRequiredBackingSize(Size, Alignment, + PageSize); + } + static uintptr_t alignUp(uintptr_t Ptr, size_t Alignment) { + return GuardedPoolAllocator::alignUp(Ptr, Alignment); } + static uintptr_t alignDown(uintptr_t Ptr, size_t Alignment) { + return GuardedPoolAllocator::alignDown(Ptr, Alignment); + } +}; + +// Global assumptions for these tests: +// 1. Page size is 0x1000. +// 2. All tests assume a slot is multipage, between 0x4000 - 0x8000. While we +// don't use multipage slots right now, this tests more boundary conditions +// and allows us to add this feature at a later date without rewriting the +// alignment functionality. +// These aren't actual requirements of the allocator - but just simplifies the +// numerics of the testing. +TEST(AlignmentTest, LeftAlignedAllocs) { + // Alignment < Page Size. + EXPECT_EQ(0x4000, AlignmentTestGPA::alignUp( + /* Ptr */ 0x4000, /* Alignment */ 0x1)); + // Alignment == Page Size. + EXPECT_EQ(0x4000, AlignmentTestGPA::alignUp( + /* Ptr */ 0x4000, /* Alignment */ 0x1000)); + // Alignment > Page Size. + EXPECT_EQ(0x4000, AlignmentTestGPA::alignUp( + /* Ptr */ 0x4000, /* Alignment */ 0x4000)); } -TEST(AlignmentTest, AlignBionic) { - std::vector<std::pair<size_t, size_t>> AskedSizeToAlignedSize = { - {1, 8}, {2, 8}, {3, 8}, {4, 8}, {5, 8}, {7, 8}, - {8, 8}, {9, 16}, {15, 16}, {16, 16}, {17, 24}, {31, 32}, - {32, 32}, {33, 40}, {4095, 4096}, {4096, 4096}, - }; +TEST(AlignmentTest, SingleByteAllocs) { + // Alignment < Page Size. + EXPECT_EQ(0x1, + AlignmentTestGPA::getRequiredBackingSize( + /* Size */ 0x1, /* Alignment */ 0x1, /* PageSize */ 0x1000)); + EXPECT_EQ(0x7fff, AlignmentTestGPA::alignDown( + /* Ptr */ 0x8000 - 0x1, /* Alignment */ 0x1)); - for (const auto &KV : AskedSizeToAlignedSize) { - EXPECT_EQ(KV.second, gwp_asan::rightAlignedAllocationSize( - KV.first, gwp_asan::AlignmentStrategy::BIONIC)); - } + // Alignment == Page Size. + EXPECT_EQ(0x1, + AlignmentTestGPA::getRequiredBackingSize( + /* Size */ 0x1, /* Alignment */ 0x1000, /* PageSize */ 0x1000)); + EXPECT_EQ(0x7000, AlignmentTestGPA::alignDown( + /* Ptr */ 0x8000 - 0x1, /* Alignment */ 0x1000)); + + // Alignment > Page Size. + EXPECT_EQ(0x3001, + AlignmentTestGPA::getRequiredBackingSize( + /* Size */ 0x1, /* Alignment */ 0x4000, /* PageSize */ 0x1000)); + EXPECT_EQ(0x4000, AlignmentTestGPA::alignDown( + /* Ptr */ 0x8000 - 0x1, /* Alignment */ 0x4000)); } -TEST(AlignmentTest, PerfectAlignment) { - for (size_t i = 1; i <= 4096; ++i) { - EXPECT_EQ(i, gwp_asan::rightAlignedAllocationSize( - i, gwp_asan::AlignmentStrategy::PERFECT)); - } +TEST(AlignmentTest, PageSizedAllocs) { + // Alignment < Page Size. + EXPECT_EQ(0x1000, + AlignmentTestGPA::getRequiredBackingSize( + /* Size */ 0x1000, /* Alignment */ 0x1, /* PageSize */ 0x1000)); + EXPECT_EQ(0x7000, AlignmentTestGPA::alignDown( + /* Ptr */ 0x8000 - 0x1000, /* Alignment */ 0x1)); + + // Alignment == Page Size. + EXPECT_EQ(0x1000, AlignmentTestGPA::getRequiredBackingSize( + /* Size */ 0x1000, /* Alignment */ 0x1000, + /* PageSize */ 0x1000)); + EXPECT_EQ(0x7000, AlignmentTestGPA::alignDown( + /* Ptr */ 0x8000 - 0x1000, /* Alignment */ 0x1000)); + + // Alignment > Page Size. + EXPECT_EQ(0x4000, AlignmentTestGPA::getRequiredBackingSize( + /* Size */ 0x1000, /* Alignment */ 0x4000, + /* PageSize */ 0x1000)); + EXPECT_EQ(0x4000, AlignmentTestGPA::alignDown( + /* Ptr */ 0x8000 - 0x1000, /* Alignment */ 0x4000)); +} + +TEST(AlignmentTest, MoreThanPageAllocs) { + // Alignment < Page Size. + EXPECT_EQ(0x2fff, + AlignmentTestGPA::getRequiredBackingSize( + /* Size */ 0x2fff, /* Alignment */ 0x1, /* PageSize */ 0x1000)); + EXPECT_EQ(0x5001, AlignmentTestGPA::alignDown( + /* Ptr */ 0x8000 - 0x2fff, /* Alignment */ 0x1)); + + // Alignment == Page Size. + EXPECT_EQ(0x2fff, AlignmentTestGPA::getRequiredBackingSize( + /* Size */ 0x2fff, /* Alignment */ 0x1000, + /* PageSize */ 0x1000)); + EXPECT_EQ(0x5000, AlignmentTestGPA::alignDown( + /* Ptr */ 0x8000 - 0x2fff, /* Alignment */ 0x1000)); + + // Alignment > Page Size. + EXPECT_EQ(0x5fff, AlignmentTestGPA::getRequiredBackingSize( + /* Size */ 0x2fff, /* Alignment */ 0x4000, + /* PageSize */ 0x1000)); + EXPECT_EQ(0x4000, AlignmentTestGPA::alignDown( + /* Ptr */ 0x8000 - 0x2fff, /* Alignment */ 0x4000)); } diff --git a/gwp_asan/tests/backtrace.cpp b/gwp_asan/tests/backtrace.cpp index b3d4427..9515065 100644 --- a/gwp_asan/tests/backtrace.cpp +++ b/gwp_asan/tests/backtrace.cpp @@ -8,6 +8,7 @@ #include <string> +#include "gwp_asan/common.h" #include "gwp_asan/crash_handler.h" #include "gwp_asan/tests/harness.h" @@ -76,9 +77,46 @@ TEST(Backtrace, Short) { TEST(Backtrace, ExceedsStorableLength) { gwp_asan::AllocationMetadata Meta; Meta.AllocationTrace.RecordBacktrace( - [](uintptr_t * /* TraceBuffer */, size_t /* Size */) -> size_t { - return SIZE_MAX; // Wow, that's big! + [](uintptr_t *TraceBuffer, size_t Size) -> size_t { + // Need to inintialise the elements that will be packed. + memset(TraceBuffer, 0u, Size * sizeof(*TraceBuffer)); + + // Indicate that there were more frames, and we just didn't have enough + // room to store them. + return Size * 2; + }); + // Retrieve a frame from the collected backtrace, make sure it works E2E. + uintptr_t TraceOutput; + EXPECT_EQ(gwp_asan::AllocationMetadata::kMaxTraceLengthToCollect, + __gwp_asan_get_allocation_trace(&Meta, &TraceOutput, 1)); +} + +TEST(Backtrace, ExceedsRetrievableAllocLength) { + gwp_asan::AllocationMetadata Meta; + constexpr size_t kNumFramesToStore = 3u; + Meta.AllocationTrace.RecordBacktrace( + [](uintptr_t *TraceBuffer, size_t /* Size */) -> size_t { + memset(TraceBuffer, kNumFramesToStore, + kNumFramesToStore * sizeof(*TraceBuffer)); + return kNumFramesToStore; + }); + uintptr_t TraceOutput; + // Ask for one element, get told that there's `kNumFramesToStore` available. + EXPECT_EQ(kNumFramesToStore, + __gwp_asan_get_allocation_trace(&Meta, &TraceOutput, 1)); +} + +TEST(Backtrace, ExceedsRetrievableDeallocLength) { + gwp_asan::AllocationMetadata Meta; + constexpr size_t kNumFramesToStore = 3u; + Meta.DeallocationTrace.RecordBacktrace( + [](uintptr_t *TraceBuffer, size_t /* Size */) -> size_t { + memset(TraceBuffer, kNumFramesToStore, + kNumFramesToStore * sizeof(*TraceBuffer)); + return kNumFramesToStore; }); uintptr_t TraceOutput; - EXPECT_EQ(1u, __gwp_asan_get_allocation_trace(&Meta, &TraceOutput, 1)); + // Ask for one element, get told that there's `kNumFramesToStore` available. + EXPECT_EQ(kNumFramesToStore, + __gwp_asan_get_deallocation_trace(&Meta, &TraceOutput, 1)); } diff --git a/gwp_asan/tests/basic.cpp b/gwp_asan/tests/basic.cpp index 29f420d..88e7ed1 100644 --- a/gwp_asan/tests/basic.cpp +++ b/gwp_asan/tests/basic.cpp @@ -39,6 +39,37 @@ TEST_F(CustomGuardedPoolAllocator, SizedAllocations) { TEST_F(DefaultGuardedPoolAllocator, TooLargeAllocation) { EXPECT_EQ(nullptr, GPA.allocate(GPA.getAllocatorState()->maximumAllocationSize() + 1)); + EXPECT_EQ(nullptr, GPA.allocate(SIZE_MAX, 0)); + EXPECT_EQ(nullptr, GPA.allocate(SIZE_MAX, 1)); + EXPECT_EQ(nullptr, GPA.allocate(0, SIZE_MAX / 2)); + EXPECT_EQ(nullptr, GPA.allocate(1, SIZE_MAX / 2)); + EXPECT_EQ(nullptr, GPA.allocate(SIZE_MAX, SIZE_MAX / 2)); +} + +TEST_F(DefaultGuardedPoolAllocator, ZeroSizeAndAlignmentAllocations) { + void *P; + EXPECT_NE(nullptr, (P = GPA.allocate(0, 0))); + GPA.deallocate(P); + EXPECT_NE(nullptr, (P = GPA.allocate(1, 0))); + GPA.deallocate(P); + EXPECT_NE(nullptr, (P = GPA.allocate(0, 1))); + GPA.deallocate(P); +} + +TEST_F(DefaultGuardedPoolAllocator, NonPowerOfTwoAlignment) { + EXPECT_EQ(nullptr, GPA.allocate(0, 3)); + EXPECT_EQ(nullptr, GPA.allocate(1, 3)); + EXPECT_EQ(nullptr, GPA.allocate(0, SIZE_MAX)); + EXPECT_EQ(nullptr, GPA.allocate(1, SIZE_MAX)); +} + +// Added multi-page slots? You'll need to expand this test. +TEST_F(DefaultGuardedPoolAllocator, TooBigForSinglePageSlots) { + EXPECT_EQ(nullptr, GPA.allocate(0x1001, 0)); + EXPECT_EQ(nullptr, GPA.allocate(0x1001, 1)); + EXPECT_EQ(nullptr, GPA.allocate(0x1001, 0x1000)); + EXPECT_EQ(nullptr, GPA.allocate(1, 0x2000)); + EXPECT_EQ(nullptr, GPA.allocate(0, 0x2000)); } TEST_F(CustomGuardedPoolAllocator, AllocAllSlots) { diff --git a/gwp_asan/tests/compression.cpp b/gwp_asan/tests/compression.cpp index 7a5894d..2423c86 100644 --- a/gwp_asan/tests/compression.cpp +++ b/gwp_asan/tests/compression.cpp @@ -7,7 +7,7 @@ //===----------------------------------------------------------------------===// #include "gwp_asan/stack_trace_compressor.h" -#include "gtest/gtest.h" +#include "gwp_asan/tests/harness.h" namespace gwp_asan { namespace compression { diff --git a/gwp_asan/tests/crash_handler_api.cpp b/gwp_asan/tests/crash_handler_api.cpp index 10a014e..4cdb569 100644 --- a/gwp_asan/tests/crash_handler_api.cpp +++ b/gwp_asan/tests/crash_handler_api.cpp @@ -16,7 +16,7 @@ using GuardedPoolAllocator = gwp_asan::GuardedPoolAllocator; using AllocationMetadata = gwp_asan::AllocationMetadata; using AllocatorState = gwp_asan::AllocatorState; -class CrashHandlerAPITest : public ::testing::Test { +class CrashHandlerAPITest : public Test { public: void SetUp() override { setupState(); } @@ -29,7 +29,7 @@ protected: size_t Slot = State.getNearestSlot(Addr); Metadata[Slot].Addr = Addr; - Metadata[Slot].Size = Size; + Metadata[Slot].RequestedSize = Size; Metadata[Slot].IsDeallocated = IsDeallocated; Metadata[Slot].AllocationTrace.ThreadID = 123; Metadata[Slot].DeallocationTrace.ThreadID = 321; @@ -80,7 +80,8 @@ protected: __gwp_asan_get_metadata(&State, Metadata, ErrorPtr); EXPECT_NE(nullptr, Meta); EXPECT_EQ(Metadata[Index].Addr, __gwp_asan_get_allocation_address(Meta)); - EXPECT_EQ(Metadata[Index].Size, __gwp_asan_get_allocation_size(Meta)); + EXPECT_EQ(Metadata[Index].RequestedSize, + __gwp_asan_get_allocation_size(Meta)); EXPECT_EQ(Metadata[Index].AllocationTrace.ThreadID, __gwp_asan_get_allocation_thread_id(Meta)); diff --git a/gwp_asan/tests/driver.cpp b/gwp_asan/tests/driver.cpp index b402cec..02ab360 100644 --- a/gwp_asan/tests/driver.cpp +++ b/gwp_asan/tests/driver.cpp @@ -6,7 +6,7 @@ // //===----------------------------------------------------------------------===// -#include "gtest/gtest.h" +#include "gwp_asan/tests/harness.h" int main(int argc, char **argv) { testing::InitGoogleTest(&argc, argv); diff --git a/gwp_asan/tests/harness.cpp b/gwp_asan/tests/harness.cpp index 77c25ee..e668c73 100644 --- a/gwp_asan/tests/harness.cpp +++ b/gwp_asan/tests/harness.cpp @@ -1,4 +1,12 @@ -#include "harness.h" +//===-- harness.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/tests/harness.h" namespace gwp_asan { namespace test { diff --git a/gwp_asan/tests/harness.h b/gwp_asan/tests/harness.h index e47254e..a61b856 100644 --- a/gwp_asan/tests/harness.h +++ b/gwp_asan/tests/harness.h @@ -11,10 +11,17 @@ #include <stdarg.h> +#if defined(__Fuchsia__) +#include <zxtest/zxtest.h> +using Test = ::zxtest::Test; +#else #include "gtest/gtest.h" +using Test = ::testing::Test; +#endif #include "gwp_asan/guarded_pool_allocator.h" #include "gwp_asan/optional/backtrace.h" +#include "gwp_asan/optional/printf.h" #include "gwp_asan/optional/segv_handler.h" #include "gwp_asan/options.h" @@ -24,7 +31,7 @@ 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. -crash_handler::Printf_t getPrintfFunction(); +Printf_t getPrintfFunction(); // First call returns true, all the following calls return false. bool OnlyOnce(); @@ -32,7 +39,7 @@ bool OnlyOnce(); }; // namespace test }; // namespace gwp_asan -class DefaultGuardedPoolAllocator : public ::testing::Test { +class DefaultGuardedPoolAllocator : public Test { public: void SetUp() override { gwp_asan::options::Options Opts; @@ -51,7 +58,7 @@ protected: MaxSimultaneousAllocations; }; -class CustomGuardedPoolAllocator : public ::testing::Test { +class CustomGuardedPoolAllocator : public Test { public: void InitNumSlots(decltype(gwp_asan::options::Options::MaxSimultaneousAllocations) @@ -74,24 +81,25 @@ protected: MaxSimultaneousAllocations; }; -class BacktraceGuardedPoolAllocator : public ::testing::Test { +class BacktraceGuardedPoolAllocator : public Test { public: void SetUp() override { gwp_asan::options::Options Opts; Opts.setDefaults(); - Opts.Backtrace = gwp_asan::options::getBacktraceFunction(); + Opts.Backtrace = gwp_asan::backtrace::getBacktraceFunction(); Opts.InstallForkHandlers = gwp_asan::test::OnlyOnce(); GPA.init(Opts); - gwp_asan::crash_handler::installSignalHandlers( + gwp_asan::segv_handler::installSignalHandlers( &GPA, gwp_asan::test::getPrintfFunction(), - gwp_asan::options::getPrintBacktraceFunction(), Opts.Backtrace); + gwp_asan::backtrace::getPrintBacktraceFunction(), + gwp_asan::backtrace::getSegvBacktraceFunction()); } void TearDown() override { GPA.uninitTestOnly(); - gwp_asan::crash_handler::uninstallSignalHandlers(); + gwp_asan::segv_handler::uninstallSignalHandlers(); } protected: diff --git a/gwp_asan/tests/iterate.cpp b/gwp_asan/tests/iterate.cpp index c40df15..2b8635d 100644 --- a/gwp_asan/tests/iterate.cpp +++ b/gwp_asan/tests/iterate.cpp @@ -8,6 +8,9 @@ #include "gwp_asan/tests/harness.h" +#include <set> +#include <vector> + TEST_F(CustomGuardedPoolAllocator, Iterate) { InitNumSlots(7); std::vector<std::pair<void *, size_t>> Allocated; diff --git a/gwp_asan/tests/late_init.cpp b/gwp_asan/tests/late_init.cpp index c7d62c8..8a94727 100644 --- a/gwp_asan/tests/late_init.cpp +++ b/gwp_asan/tests/late_init.cpp @@ -8,7 +8,7 @@ #include "gwp_asan/guarded_pool_allocator.h" #include "gwp_asan/options.h" -#include "gtest/gtest.h" +#include "gwp_asan/tests/harness.h" TEST(LateInit, CheckLateInitIsOK) { gwp_asan::GuardedPoolAllocator GPA; diff --git a/gwp_asan/tests/mutex_test.cpp b/gwp_asan/tests/mutex_test.cpp index 5bc53b9..f68619c 100644 --- a/gwp_asan/tests/mutex_test.cpp +++ b/gwp_asan/tests/mutex_test.cpp @@ -7,7 +7,7 @@ //===----------------------------------------------------------------------===// #include "gwp_asan/mutex.h" -#include "gtest/gtest.h" +#include "gwp_asan/tests/harness.h" #include <atomic> #include <mutex> diff --git a/gwp_asan/tests/options.cpp b/gwp_asan/tests/options.cpp new file mode 100644 index 0000000..ffa4bca --- /dev/null +++ b/gwp_asan/tests/options.cpp @@ -0,0 +1,63 @@ +//===-- options.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/tests/harness.h" + +#include "gwp_asan/optional/options_parser.h" +#include "gwp_asan/options.h" + +#include <stdarg.h> + +static char Message[1024]; +void MessageRecorder(const char *Format, ...) { + va_list Args; + va_start(Args, Format); + vsprintf(Message + strlen(Message), Format, Args); + va_end(Args); +} + +TEST(GwpAsanOptionsTest, Basic) { + Message[0] = '\0'; + gwp_asan::options::initOptions("Enabled=0:SampleRate=4:" + "InstallSignalHandlers=false", + MessageRecorder); + gwp_asan::options::Options Opts = gwp_asan::options::getOptions(); + EXPECT_EQ('\0', Message[0]); + EXPECT_FALSE(Opts.Enabled); + EXPECT_FALSE(Opts.InstallSignalHandlers); + EXPECT_EQ(4, Opts.SampleRate); +} + +void RunErrorTest(const char *OptionsStr, const char *ErrorNeedle) { + Message[0] = '\0'; + gwp_asan::options::initOptions(OptionsStr, MessageRecorder); + EXPECT_NE('\0', Message[0]) + << "Options string \"" << OptionsStr << "\" didn't generate an error."; + EXPECT_NE(nullptr, strstr(Message, ErrorNeedle)) + << "Couldn't find error needle \"" << ErrorNeedle + << "\" in haystack created by options string \"" << OptionsStr + << "\". Error was: \"" << Message << "\"."; +} + +TEST(GwpAsanOptionsTest, FailureModes) { + RunErrorTest("Enabled=2", "Invalid boolean value '2' for option 'Enabled'"); + RunErrorTest("Enabled=1:MaxSimultaneousAllocations=0", + "MaxSimultaneousAllocations must be > 0"); + RunErrorTest("Enabled=1:MaxSimultaneousAllocations=-1", + "MaxSimultaneousAllocations must be > 0"); + RunErrorTest("Enabled=1:SampleRate=0", "SampleRate must be > 0"); + RunErrorTest("Enabled=1:SampleRate=-1", "SampleRate must be > 0"); + RunErrorTest("Enabled=", "Invalid boolean value '' for option 'Enabled'"); + RunErrorTest("==", "Unknown option '=='"); + RunErrorTest("Enabled==0", "Invalid boolean value '=0' for option 'Enabled'"); + RunErrorTest("Enabled:", "Expected '=' when parsing option 'Enabled:'"); + RunErrorTest("Enabled:=", "Expected '=' when parsing option 'Enabled:='"); + RunErrorTest("SampleRate=NOT_A_NUMBER", + "Invalid integer value 'NOT_A_NUMBER' for option 'SampleRate'"); + RunErrorTest("NOT_A_VALUE=0", "Unknown option 'NOT_A_VALUE"); +} diff --git a/gwp_asan/tests/optional/printf_sanitizer_common.cpp b/gwp_asan/tests/platform_specific/printf_sanitizer_common.cpp index ea7141b..102b1db 100644 --- a/gwp_asan/tests/optional/printf_sanitizer_common.cpp +++ b/gwp_asan/tests/platform_specific/printf_sanitizer_common.cpp @@ -6,15 +6,14 @@ // //===----------------------------------------------------------------------===// -#include "gwp_asan/optional/segv_handler.h" +#include "gwp_asan/optional/printf.h" + #include "sanitizer_common/sanitizer_common.h" namespace gwp_asan { namespace test { -// This printf-function getter allows other platforms (e.g. Android) to define -// their own signal-safe Printf function. In LLVM, we use -// `optional/printf_sanitizer_common.cpp` which supplies the __sanitizer::Printf -// for this purpose. -crash_handler::Printf_t getPrintfFunction() { return __sanitizer::Printf; } -}; // namespace test -}; // namespace gwp_asan + +Printf_t getPrintfFunction() { return __sanitizer::Printf; } + +} // namespace test +} // namespace gwp_asan diff --git a/gwp_asan/tests/slot_reuse.cpp b/gwp_asan/tests/slot_reuse.cpp index ee4b671..f2a77b0 100644 --- a/gwp_asan/tests/slot_reuse.cpp +++ b/gwp_asan/tests/slot_reuse.cpp @@ -8,6 +8,8 @@ #include "gwp_asan/tests/harness.h" +#include <set> + void singleByteGoodAllocDealloc(gwp_asan::GuardedPoolAllocator *GPA) { void *Ptr = GPA->allocate(1); EXPECT_NE(nullptr, Ptr); diff --git a/gwp_asan/utilities.h b/gwp_asan/utilities.h index 71d525f..d8bc0e4 100644 --- a/gwp_asan/utilities.h +++ b/gwp_asan/utilities.h @@ -6,26 +6,23 @@ // //===----------------------------------------------------------------------===// +#ifndef GWP_ASAN_UTILITIES_H_ +#define GWP_ASAN_UTILITIES_H_ + #include "gwp_asan/definitions.h" #include <stddef.h> -#include <stdint.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); - -enum class AlignmentStrategy { - // Default => POWER_OF_TWO on most platforms, BIONIC for Android Bionic. - DEFAULT, - POWER_OF_TWO, - BIONIC, - PERFECT, -}; +// Terminates in a platform-specific way with `Message`. +void die(const char *Message); -// Returns the real size of a right-aligned allocation. -size_t rightAlignedAllocationSize( - size_t RealAllocationSize, - AlignmentStrategy Align = AlignmentStrategy::DEFAULT); +// Checks that `Condition` is true, otherwise dies with `Message`. +GWP_ASAN_ALWAYS_INLINE void Check(bool Condition, const char *Message) { + if (Condition) + return; + die(Message); +} } // namespace gwp_asan + +#endif // GWP_ASAN_UTILITIES_H_ diff --git a/tools/options_parser_fuzzer.cpp b/tools/options_parser_fuzzer.cpp new file mode 100644 index 0000000..2d87f12 --- /dev/null +++ b/tools/options_parser_fuzzer.cpp @@ -0,0 +1,12 @@ +#include <cstddef> +#include <cstdint> + +#include <fuzzer/FuzzedDataProvider.h> + +#include "gwp_asan/optional/options_parser.h" + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { + FuzzedDataProvider Fdp(Data, Size); + gwp_asan::options::initOptions(Fdp.ConsumeRemainingBytesAsString().c_str()); + return 0; +} |