1 //===-- guarded_pool_allocator.h --------------------------------*- C++ -*-===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #ifndef GWP_ASAN_GUARDED_POOL_ALLOCATOR_H_ 10 #define GWP_ASAN_GUARDED_POOL_ALLOCATOR_H_ 11 12 #include "gwp_asan/definitions.h" 13 #include "gwp_asan/mutex.h" 14 #include "gwp_asan/options.h" 15 #include "gwp_asan/random.h" 16 17 #include <stddef.h> 18 #include <stdint.h> 19 20 namespace gwp_asan { 21 // This class is the primary implementation of the allocator portion of GWP- 22 // ASan. It is the sole owner of the pool of sequentially allocated guarded 23 // slots. It should always be treated as a singleton. 24 25 // Functions in the public interface of this class are thread-compatible until 26 // init() is called, at which point they become thread-safe (unless specified 27 // otherwise). 28 class GuardedPoolAllocator { 29 public: 30 static constexpr uint64_t kInvalidThreadID = UINT64_MAX; 31 32 enum class Error { 33 UNKNOWN, 34 USE_AFTER_FREE, 35 DOUBLE_FREE, 36 INVALID_FREE, 37 BUFFER_OVERFLOW, 38 BUFFER_UNDERFLOW 39 }; 40 41 struct AllocationMetadata { 42 // Maximum number of stack trace frames to collect for allocations + frees. 43 // TODO(hctim): Implement stack frame compression, a-la Chromium. 44 static constexpr size_t kMaximumStackFrames = 64; 45 46 // Records the given allocation metadata into this struct. 47 void RecordAllocation(uintptr_t Addr, size_t Size, 48 options::Backtrace_t Backtrace); 49 50 // Record that this allocation is now deallocated. 51 void RecordDeallocation(options::Backtrace_t Backtrace); 52 53 struct CallSiteInfo { 54 // The backtrace to the allocation/deallocation. If the first value is 55 // zero, we did not collect a trace. 56 uintptr_t Trace[kMaximumStackFrames] = {}; 57 // The thread ID for this trace, or kInvalidThreadID if not available. 58 uint64_t ThreadID = kInvalidThreadID; 59 }; 60 61 // The address of this allocation. 62 uintptr_t Addr = 0; 63 // Represents the actual size of the allocation. 64 size_t Size = 0; 65 66 CallSiteInfo AllocationTrace; 67 CallSiteInfo DeallocationTrace; 68 69 // Whether this allocation has been deallocated yet. 70 bool IsDeallocated = false; 71 }; 72 73 // During program startup, we must ensure that memory allocations do not land 74 // in this allocation pool if the allocator decides to runtime-disable 75 // GWP-ASan. The constructor value-initialises the class such that if no 76 // further initialisation takes place, calls to shouldSample() and 77 // pointerIsMine() will return false. 78 constexpr GuardedPoolAllocator(){}; 79 GuardedPoolAllocator(const GuardedPoolAllocator &) = delete; 80 GuardedPoolAllocator &operator=(const GuardedPoolAllocator &) = delete; 81 82 // Note: This class is expected to be a singleton for the lifetime of the 83 // program. If this object is initialised, it will leak the guarded page pool 84 // and metadata allocations during destruction. We can't clean up these areas 85 // as this may cause a use-after-free on shutdown. 86 ~GuardedPoolAllocator() = default; 87 88 // Initialise the rest of the members of this class. Create the allocation 89 // pool using the provided options. See options.inc for runtime configuration 90 // options. 91 void init(const options::Options &Opts); 92 93 // Return whether the allocation should be randomly chosen for sampling. 94 ALWAYS_INLINE bool shouldSample() { 95 // NextSampleCounter == 0 means we "should regenerate the counter". 96 // == 1 means we "should sample this allocation". 97 if (UNLIKELY(ThreadLocals.NextSampleCounter == 0)) 98 ThreadLocals.NextSampleCounter = 99 (getRandomUnsigned32() % AdjustedSampleRate) + 1; 100 101 return UNLIKELY(--ThreadLocals.NextSampleCounter == 0); 102 } 103 104 // Returns whether the provided pointer is a current sampled allocation that 105 // is owned by this pool. 106 ALWAYS_INLINE bool pointerIsMine(const void *Ptr) const { 107 uintptr_t P = reinterpret_cast<uintptr_t>(Ptr); 108 return GuardedPagePool <= P && P < GuardedPagePoolEnd; 109 } 110 111 // Allocate memory in a guarded slot, and return a pointer to the new 112 // allocation. Returns nullptr if the pool is empty, the requested size is too 113 // large for this pool to handle, or the requested size is zero. 114 void *allocate(size_t Size); 115 116 // Deallocate memory in a guarded slot. The provided pointer must have been 117 // allocated using this pool. This will set the guarded slot as inaccessible. 118 void deallocate(void *Ptr); 119 120 // Returns the size of the allocation at Ptr. 121 size_t getSize(const void *Ptr); 122 123 // Returns the largest allocation that is supported by this pool. Any 124 // allocations larger than this should go to the regular system allocator. 125 size_t maximumAllocationSize() const; 126 127 // Dumps an error report (including allocation and deallocation stack traces). 128 // An optional error may be provided if the caller knows what the error is 129 // ahead of time. This is primarily a helper function to locate the static 130 // singleton pointer and call the internal version of this function. This 131 // method is never thread safe, and should only be called when fatal errors 132 // occur. 133 static void reportError(uintptr_t AccessPtr, Error E = Error::UNKNOWN); 134 135 // Get the current thread ID, or kInvalidThreadID if failure. Note: This 136 // implementation is platform-specific. 137 static uint64_t getThreadID(); 138 139 private: 140 static constexpr size_t kInvalidSlotID = SIZE_MAX; 141 142 // These functions anonymously map memory or change the permissions of mapped 143 // memory into this process in a platform-specific way. Pointer and size 144 // arguments are expected to be page-aligned. These functions will never 145 // return on error, instead electing to kill the calling process on failure. 146 // Note that memory is initially mapped inaccessible. In order for RW 147 // mappings, call mapMemory() followed by markReadWrite() on the returned 148 // pointer. 149 void *mapMemory(size_t Size) const; 150 void markReadWrite(void *Ptr, size_t Size) const; 151 void markInaccessible(void *Ptr, size_t Size) const; 152 153 // Get the page size from the platform-specific implementation. Only needs to 154 // be called once, and the result should be cached in PageSize in this class. 155 static size_t getPlatformPageSize(); 156 157 // Install the SIGSEGV crash handler for printing use-after-free and heap- 158 // buffer-{under|over}flow exceptions. This is platform specific as even 159 // though POSIX and Windows both support registering handlers through 160 // signal(), we have to use platform-specific signal handlers to obtain the 161 // address that caused the SIGSEGV exception. 162 static void installSignalHandlers(); 163 164 // Returns the index of the slot that this pointer resides in. If the pointer 165 // is not owned by this pool, the result is undefined. 166 size_t addrToSlot(uintptr_t Ptr) const; 167 168 // Returns the address of the N-th guarded slot. 169 uintptr_t slotToAddr(size_t N) const; 170 171 // Returns a pointer to the metadata for the owned pointer. If the pointer is 172 // not owned by this pool, the result is undefined. 173 AllocationMetadata *addrToMetadata(uintptr_t Ptr) const; 174 175 // Returns the address of the page that this pointer resides in. 176 uintptr_t getPageAddr(uintptr_t Ptr) const; 177 178 // Gets the nearest slot to the provided address. 179 size_t getNearestSlot(uintptr_t Ptr) const; 180 181 // Returns whether the provided pointer is a guard page or not. The pointer 182 // must be within memory owned by this pool, else the result is undefined. 183 bool isGuardPage(uintptr_t Ptr) const; 184 185 // Reserve a slot for a new guarded allocation. Returns kInvalidSlotID if no 186 // slot is available to be reserved. 187 size_t reserveSlot(); 188 189 // Unreserve the guarded slot. 190 void freeSlot(size_t SlotIndex); 191 192 // Returns the offset (in bytes) between the start of a guarded slot and where 193 // the start of the allocation should take place. Determined using the size of 194 // the allocation and the options provided at init-time. 195 uintptr_t allocationSlotOffset(size_t AllocationSize) const; 196 197 // Returns the diagnosis for an unknown error. If the diagnosis is not 198 // Error::INVALID_FREE or Error::UNKNOWN, the metadata for the slot 199 // responsible for the error is placed in *Meta. 200 Error diagnoseUnknownError(uintptr_t AccessPtr, AllocationMetadata **Meta); 201 202 void reportErrorInternal(uintptr_t AccessPtr, Error E); 203 204 // Cached page size for this system in bytes. 205 size_t PageSize = 0; 206 207 // A mutex to protect the guarded slot and metadata pool for this class. 208 Mutex PoolMutex; 209 // The number of guarded slots that this pool holds. 210 size_t MaxSimultaneousAllocations = 0; 211 // Record the number allocations that we've sampled. We store this amount so 212 // that we don't randomly choose to recycle a slot that previously had an 213 // allocation before all the slots have been utilised. 214 size_t NumSampledAllocations = 0; 215 // Pointer to the pool of guarded slots. Note that this points to the start of 216 // the pool (which is a guard page), not a pointer to the first guarded page. 217 uintptr_t GuardedPagePool = UINTPTR_MAX; 218 uintptr_t GuardedPagePoolEnd = 0; 219 // Pointer to the allocation metadata (allocation/deallocation stack traces), 220 // if any. 221 AllocationMetadata *Metadata = nullptr; 222 223 // Pointer to an array of free slot indexes. 224 size_t *FreeSlots = nullptr; 225 // The current length of the list of free slots. 226 size_t FreeSlotsLength = 0; 227 228 // See options.{h, inc} for more information. 229 bool PerfectlyRightAlign = false; 230 231 // Printf function supplied by the implementing allocator. We can't (in 232 // general) use printf() from the cstdlib as it may malloc(), causing infinite 233 // recursion. 234 options::Printf_t Printf = nullptr; 235 options::Backtrace_t Backtrace = nullptr; 236 options::PrintBacktrace_t PrintBacktrace = nullptr; 237 238 // The adjusted sample rate for allocation sampling. Default *must* be 239 // nonzero, as dynamic initialisation may call malloc (e.g. from libstdc++) 240 // before GPA::init() is called. This would cause an error in shouldSample(), 241 // where we would calculate modulo zero. This value is set UINT32_MAX, as when 242 // GWP-ASan is disabled, we wish to never spend wasted cycles recalculating 243 // the sample rate. 244 uint32_t AdjustedSampleRate = UINT32_MAX; 245 246 // Pack the thread local variables into a struct to ensure that they're in 247 // the same cache line for performance reasons. These are the most touched 248 // variables in GWP-ASan. 249 struct alignas(8) ThreadLocalPackedVariables { 250 constexpr ThreadLocalPackedVariables() {} 251 // Thread-local decrementing counter that indicates that a given allocation 252 // should be sampled when it reaches zero. 253 uint32_t NextSampleCounter = 0; 254 // Guard against recursivity. Unwinders often contain complex behaviour that 255 // may not be safe for the allocator (i.e. the unwinder calls dlopen(), 256 // which calls malloc()). When recursive behaviour is detected, we will 257 // automatically fall back to the supporting allocator to supply the 258 // allocation. 259 bool RecursiveGuard = false; 260 }; 261 static TLS_INITIAL_EXEC ThreadLocalPackedVariables ThreadLocals; 262 }; 263 } // namespace gwp_asan 264 265 #endif // GWP_ASAN_GUARDED_POOL_ALLOCATOR_H_ 266