diff options
Diffstat (limited to 'compiler-rt/lib/gwp_asan/guarded_pool_allocator.h')
-rw-r--r-- | compiler-rt/lib/gwp_asan/guarded_pool_allocator.h | 175 |
1 files changed, 57 insertions, 118 deletions
diff --git a/compiler-rt/lib/gwp_asan/guarded_pool_allocator.h b/compiler-rt/lib/gwp_asan/guarded_pool_allocator.h index 7e6e13769d32..ae00506c5692 100644 --- a/compiler-rt/lib/gwp_asan/guarded_pool_allocator.h +++ b/compiler-rt/lib/gwp_asan/guarded_pool_allocator.h @@ -9,6 +9,7 @@ #ifndef GWP_ASAN_GUARDED_POOL_ALLOCATOR_H_ #define GWP_ASAN_GUARDED_POOL_ALLOCATOR_H_ +#include "gwp_asan/common.h" #include "gwp_asan/definitions.h" #include "gwp_asan/mutex.h" #include "gwp_asan/options.h" @@ -28,56 +29,8 @@ namespace gwp_asan { // otherwise). class GuardedPoolAllocator { public: - static constexpr uint64_t kInvalidThreadID = UINT64_MAX; - - enum class Error { - UNKNOWN, - USE_AFTER_FREE, - DOUBLE_FREE, - INVALID_FREE, - BUFFER_OVERFLOW, - BUFFER_UNDERFLOW - }; - - struct AllocationMetadata { - // The number of bytes used to store a compressed stack frame. On 64-bit - // platforms, assuming a compression ratio of 50%, this should allow us to - // store ~64 frames per trace. - static constexpr size_t kStackFrameStorageBytes = 256; - - // Maximum number of stack frames to collect on allocation/deallocation. The - // actual number of collected frames may be less than this as the stack - // frames are compressed into a fixed memory range. - static constexpr size_t kMaxTraceLengthToCollect = 128; - - // Records the given allocation metadata into this struct. - void RecordAllocation(uintptr_t Addr, size_t Size, - options::Backtrace_t Backtrace); - - // Record that this allocation is now deallocated. - void RecordDeallocation(options::Backtrace_t Backtrace); - - struct CallSiteInfo { - // The compressed backtrace to the allocation/deallocation. - uint8_t CompressedTrace[kStackFrameStorageBytes]; - // The thread ID for this trace, or kInvalidThreadID if not available. - uint64_t ThreadID = kInvalidThreadID; - // The size of the compressed trace (in bytes). Zero indicates that no - // trace was collected. - size_t TraceSize = 0; - }; - - // The address of this allocation. - uintptr_t Addr = 0; - // Represents the actual size of the allocation. - size_t Size = 0; - - CallSiteInfo AllocationTrace; - CallSiteInfo DeallocationTrace; - - // Whether this allocation has been deallocated yet. - bool IsDeallocated = false; - }; + // Name of the GWP-ASan mapping that for `Metadata`. + static constexpr const char *kGwpAsanMetadataName = "GWP-ASan Metadata"; // During program startup, we must ensure that memory allocations do not land // in this allocation pool if the allocator decides to runtime-disable @@ -98,14 +51,36 @@ public: // pool using the provided options. See options.inc for runtime configuration // options. void init(const options::Options &Opts); + void uninitTestOnly(); + + // Functions exported for libmemunreachable's use on Android. disable() + // installs a lock in the allocator that prevents any thread from being able + // to allocate memory, until enable() is called. + void disable(); + void enable(); + + typedef void (*iterate_callback)(uintptr_t base, size_t size, void *arg); + // Execute the callback Cb for every allocation the lies in [Base, Base + + // Size). Must be called while the allocator is disabled. The callback can not + // allocate. + void iterate(void *Base, size_t Size, iterate_callback Cb, void *Arg); + + // This function is used to signal the allocator to indefinitely stop + // functioning, as a crash has occurred. This stops the allocator from + // servicing any further allocations permanently. + void stop(); // Return whether the allocation should be randomly chosen for sampling. GWP_ASAN_ALWAYS_INLINE bool shouldSample() { // NextSampleCounter == 0 means we "should regenerate the counter". // == 1 means we "should sample this allocation". + // AdjustedSampleRatePlusOne is designed to intentionally underflow. This + // 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() % AdjustedSampleRate) + 1; + (getRandomUnsigned32() % (AdjustedSampleRatePlusOne - 1)) + 1; return GWP_ASAN_UNLIKELY(--ThreadLocals.NextSampleCounter == 0); } @@ -113,8 +88,7 @@ public: // Returns whether the provided pointer is a current sampled allocation that // is owned by this pool. GWP_ASAN_ALWAYS_INLINE bool pointerIsMine(const void *Ptr) const { - uintptr_t P = reinterpret_cast<uintptr_t>(Ptr); - return GuardedPagePool <= P && P < GuardedPagePoolEnd; + return State.pointerIsMine(Ptr); } // Allocate memory in a guarded slot, and return a pointer to the new @@ -129,23 +103,21 @@ public: // Returns the size of the allocation at Ptr. size_t getSize(const void *Ptr); - // Returns the largest allocation that is supported by this pool. Any - // allocations larger than this should go to the regular system allocator. - size_t maximumAllocationSize() const; - - // Dumps an error report (including allocation and deallocation stack traces). - // An optional error may be provided if the caller knows what the error is - // ahead of time. This is primarily a helper function to locate the static - // singleton pointer and call the internal version of this function. This - // method is never thread safe, and should only be called when fatal errors - // occur. - static void reportError(uintptr_t AccessPtr, Error E = Error::UNKNOWN); + // Returns a pointer to the Metadata region, or nullptr if it doesn't exist. + const AllocationMetadata *getMetadataRegion() const { return Metadata; } - // Get the current thread ID, or kInvalidThreadID if failure. Note: This - // implementation is platform-specific. - static uint64_t getThreadID(); + // Returns a pointer to the AllocatorState region. + const AllocatorState *getAllocatorState() const { return &State; } private: + // Name of actively-occupied slot mappings. + static constexpr const char *kGwpAsanAliveSlotName = "GWP-ASan Alive Slot"; + // Name of the guard pages. This includes all slots that are not actively in + // use (i.e. were never used, or have been free()'d).) + static constexpr const char *kGwpAsanGuardPageName = "GWP-ASan Guard Page"; + // Name of the mapping for `FreeSlots`. + static constexpr const char *kGwpAsanFreeSlotsName = "GWP-ASan Metadata"; + static constexpr size_t kInvalidSlotID = SIZE_MAX; // These functions anonymously map memory or change the permissions of mapped @@ -154,43 +126,22 @@ private: // 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. - void *mapMemory(size_t Size) const; - void markReadWrite(void *Ptr, size_t Size) const; - void markInaccessible(void *Ptr, size_t Size) const; + // 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; // 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. static size_t getPlatformPageSize(); - // Install the SIGSEGV crash handler for printing use-after-free and heap- - // buffer-{under|over}flow exceptions. This is platform specific as even - // though POSIX and Windows both support registering handlers through - // signal(), we have to use platform-specific signal handlers to obtain the - // address that caused the SIGSEGV exception. - static void installSignalHandlers(); - - // Returns the index of the slot that this pointer resides in. If the pointer - // is not owned by this pool, the result is undefined. - size_t addrToSlot(uintptr_t Ptr) const; - - // Returns the address of the N-th guarded slot. - uintptr_t slotToAddr(size_t N) const; - // Returns a pointer to the metadata for the owned pointer. If the pointer is // not owned by this pool, the result is undefined. AllocationMetadata *addrToMetadata(uintptr_t Ptr) const; - // Returns the address of the page that this pointer resides in. - uintptr_t getPageAddr(uintptr_t Ptr) const; - - // Gets the nearest slot to the provided address. - size_t getNearestSlot(uintptr_t Ptr) const; - - // Returns whether the provided pointer is a guard page or not. The pointer - // must be within memory owned by this pool, else the result is undefined. - bool isGuardPage(uintptr_t Ptr) const; - // Reserve a slot for a new guarded allocation. Returns kInvalidSlotID if no // slot is available to be reserved. size_t reserveSlot(); @@ -198,33 +149,24 @@ private: // Unreserve the guarded slot. void freeSlot(size_t SlotIndex); - // Returns the offset (in bytes) between the start of a guarded slot and where - // the start of the allocation should take place. Determined using the size of - // the allocation and the options provided at init-time. - uintptr_t allocationSlotOffset(size_t AllocationSize) const; + // Raise a SEGV and set the corresponding fields in the Allocator's State in + // order to tell the crash handler what happened. Used when errors are + // detected internally (Double Free, Invalid Free). + void trapOnAddress(uintptr_t Address, Error E); - // Returns the diagnosis for an unknown error. If the diagnosis is not - // Error::INVALID_FREE or Error::UNKNOWN, the metadata for the slot - // responsible for the error is placed in *Meta. - Error diagnoseUnknownError(uintptr_t AccessPtr, AllocationMetadata **Meta); + static GuardedPoolAllocator *getSingleton(); - void reportErrorInternal(uintptr_t AccessPtr, Error E); + // Install a pthread_atfork handler. + void installAtFork(); - // Cached page size for this system in bytes. - size_t PageSize = 0; + gwp_asan::AllocatorState State; // A mutex to protect the guarded slot and metadata pool for this class. Mutex PoolMutex; - // The number of guarded slots that this pool holds. - size_t MaxSimultaneousAllocations = 0; // Record the number allocations that we've sampled. We store this amount so // that we don't randomly choose to recycle a slot that previously had an // allocation before all the slots have been utilised. size_t NumSampledAllocations = 0; - // Pointer to the pool of guarded slots. Note that this points to the start of - // the pool (which is a guard page), not a pointer to the first guarded page. - uintptr_t GuardedPagePool = UINTPTR_MAX; - uintptr_t GuardedPagePoolEnd = 0; // Pointer to the allocation metadata (allocation/deallocation stack traces), // if any. AllocationMetadata *Metadata = nullptr; @@ -237,12 +179,9 @@ private: // See options.{h, inc} for more information. bool PerfectlyRightAlign = false; - // Printf function supplied by the implementing allocator. We can't (in - // general) use printf() from the cstdlib as it may malloc(), causing infinite - // recursion. - options::Printf_t Printf = nullptr; + // Backtrace function provided by the supporting allocator. See `options.h` + // for more information. options::Backtrace_t Backtrace = nullptr; - options::PrintBacktrace_t PrintBacktrace = nullptr; // The adjusted sample rate for allocation sampling. Default *must* be // nonzero, as dynamic initialisation may call malloc (e.g. from libstdc++) @@ -250,7 +189,7 @@ private: // where we would calculate modulo zero. This value is set UINT32_MAX, as when // GWP-ASan is disabled, we wish to never spend wasted cycles recalculating // the sample rate. - uint32_t AdjustedSampleRate = UINT32_MAX; + 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 |