1 //===-- guarded_pool_allocator.cpp ------------------------------*- 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 #include "gwp_asan/guarded_pool_allocator.h" 10 11 #include "gwp_asan/options.h" 12 13 #include <assert.h> 14 #include <stdlib.h> 15 #include <string.h> 16 #include <time.h> 17 18 using AllocationMetadata = gwp_asan::GuardedPoolAllocator::AllocationMetadata; 19 using Error = gwp_asan::GuardedPoolAllocator::Error; 20 21 namespace gwp_asan { 22 namespace { 23 // Forward declare the pointer to the singleton version of this class. 24 // Instantiated during initialisation, this allows the signal handler 25 // to find this class in order to deduce the root cause of failures. Must not be 26 // referenced by users outside this translation unit, in order to avoid 27 // init-order-fiasco. 28 GuardedPoolAllocator *SingletonPtr = nullptr; 29 } // anonymous namespace 30 31 // Gets the singleton implementation of this class. Thread-compatible until 32 // init() is called, thread-safe afterwards. 33 GuardedPoolAllocator *getSingleton() { return SingletonPtr; } 34 35 void GuardedPoolAllocator::AllocationMetadata::RecordAllocation( 36 uintptr_t AllocAddr, size_t AllocSize) { 37 Addr = AllocAddr; 38 Size = AllocSize; 39 IsDeallocated = false; 40 41 // TODO(hctim): Implement stack trace collection. 42 // TODO(hctim): Ask the caller to provide the thread ID, so we don't waste 43 // other thread's time getting the thread ID under lock. 44 AllocationTrace.ThreadID = getThreadID(); 45 DeallocationTrace.ThreadID = kInvalidThreadID; 46 AllocationTrace.Trace[0] = 0; 47 DeallocationTrace.Trace[0] = 0; 48 } 49 50 void GuardedPoolAllocator::AllocationMetadata::RecordDeallocation() { 51 IsDeallocated = true; 52 // TODO(hctim): Implement stack trace collection. 53 DeallocationTrace.ThreadID = getThreadID(); 54 } 55 56 void GuardedPoolAllocator::init(const options::Options &Opts) { 57 // Note: We return from the constructor here if GWP-ASan is not available. 58 // This will stop heap-allocation of class members, as well as mmap() of the 59 // guarded slots. 60 if (!Opts.Enabled || Opts.SampleRate == 0 || 61 Opts.MaxSimultaneousAllocations == 0) 62 return; 63 64 // TODO(hctim): Add a death unit test for this. 65 if (SingletonPtr) { 66 (*SingletonPtr->Printf)( 67 "GWP-ASan Error: init() has already been called.\n"); 68 exit(EXIT_FAILURE); 69 } 70 71 if (Opts.SampleRate < 0) { 72 Opts.Printf("GWP-ASan Error: SampleRate is < 0.\n"); 73 exit(EXIT_FAILURE); 74 } 75 76 if (Opts.SampleRate > INT32_MAX) { 77 Opts.Printf("GWP-ASan Error: SampleRate is > 2^31.\n"); 78 exit(EXIT_FAILURE); 79 } 80 81 if (Opts.MaxSimultaneousAllocations < 0) { 82 Opts.Printf("GWP-ASan Error: MaxSimultaneousAllocations is < 0.\n"); 83 exit(EXIT_FAILURE); 84 } 85 86 SingletonPtr = this; 87 88 MaxSimultaneousAllocations = Opts.MaxSimultaneousAllocations; 89 90 PageSize = getPlatformPageSize(); 91 92 PerfectlyRightAlign = Opts.PerfectlyRightAlign; 93 Printf = Opts.Printf; 94 95 size_t PoolBytesRequired = 96 PageSize * (1 + MaxSimultaneousAllocations) + 97 MaxSimultaneousAllocations * maximumAllocationSize(); 98 void *GuardedPoolMemory = mapMemory(PoolBytesRequired); 99 100 size_t BytesRequired = MaxSimultaneousAllocations * sizeof(*Metadata); 101 Metadata = reinterpret_cast<AllocationMetadata *>(mapMemory(BytesRequired)); 102 markReadWrite(Metadata, BytesRequired); 103 104 // Allocate memory and set up the free pages queue. 105 BytesRequired = MaxSimultaneousAllocations * sizeof(*FreeSlots); 106 FreeSlots = reinterpret_cast<size_t *>(mapMemory(BytesRequired)); 107 markReadWrite(FreeSlots, BytesRequired); 108 109 // Multiply the sample rate by 2 to give a good, fast approximation for (1 / 110 // SampleRate) chance of sampling. 111 if (Opts.SampleRate != 1) 112 AdjustedSampleRate = static_cast<uint32_t>(Opts.SampleRate) * 2; 113 else 114 AdjustedSampleRate = 1; 115 116 GuardedPagePool = reinterpret_cast<uintptr_t>(GuardedPoolMemory); 117 GuardedPagePoolEnd = 118 reinterpret_cast<uintptr_t>(GuardedPoolMemory) + PoolBytesRequired; 119 120 // Ensure that signal handlers are installed as late as possible, as the class 121 // is not thread-safe until init() is finished, and thus a SIGSEGV may cause a 122 // race to members if recieved during init(). 123 if (Opts.InstallSignalHandlers) 124 installSignalHandlers(); 125 } 126 127 void *GuardedPoolAllocator::allocate(size_t Size) { 128 if (Size == 0 || Size > maximumAllocationSize()) 129 return nullptr; 130 131 size_t Index; 132 { 133 ScopedLock L(PoolMutex); 134 Index = reserveSlot(); 135 } 136 137 if (Index == kInvalidSlotID) 138 return nullptr; 139 140 uintptr_t Ptr = slotToAddr(Index); 141 Ptr += allocationSlotOffset(Size); 142 AllocationMetadata *Meta = addrToMetadata(Ptr); 143 144 // If a slot is multiple pages in size, and the allocation takes up a single 145 // page, we can improve overflow detection by leaving the unused pages as 146 // unmapped. 147 markReadWrite(reinterpret_cast<void *>(getPageAddr(Ptr)), Size); 148 149 Meta->RecordAllocation(Ptr, Size); 150 151 return reinterpret_cast<void *>(Ptr); 152 } 153 154 void GuardedPoolAllocator::deallocate(void *Ptr) { 155 assert(pointerIsMine(Ptr) && "Pointer is not mine!"); 156 uintptr_t UPtr = reinterpret_cast<uintptr_t>(Ptr); 157 uintptr_t SlotStart = slotToAddr(addrToSlot(UPtr)); 158 AllocationMetadata *Meta = addrToMetadata(UPtr); 159 if (Meta->Addr != UPtr) { 160 reportError(UPtr, Error::INVALID_FREE); 161 exit(EXIT_FAILURE); 162 } 163 164 // Intentionally scope the mutex here, so that other threads can access the 165 // pool during the expensive markInaccessible() call. 166 { 167 ScopedLock L(PoolMutex); 168 if (Meta->IsDeallocated) { 169 reportError(UPtr, Error::DOUBLE_FREE); 170 exit(EXIT_FAILURE); 171 } 172 173 // Ensure that the deallocation is recorded before marking the page as 174 // inaccessible. Otherwise, a racy use-after-free will have inconsistent 175 // metadata. 176 Meta->RecordDeallocation(); 177 } 178 179 markInaccessible(reinterpret_cast<void *>(SlotStart), 180 maximumAllocationSize()); 181 182 // And finally, lock again to release the slot back into the pool. 183 ScopedLock L(PoolMutex); 184 freeSlot(addrToSlot(UPtr)); 185 } 186 187 size_t GuardedPoolAllocator::getSize(const void *Ptr) { 188 assert(pointerIsMine(Ptr)); 189 ScopedLock L(PoolMutex); 190 AllocationMetadata *Meta = addrToMetadata(reinterpret_cast<uintptr_t>(Ptr)); 191 assert(Meta->Addr == reinterpret_cast<uintptr_t>(Ptr)); 192 return Meta->Size; 193 } 194 195 size_t GuardedPoolAllocator::maximumAllocationSize() const { return PageSize; } 196 197 AllocationMetadata *GuardedPoolAllocator::addrToMetadata(uintptr_t Ptr) const { 198 return &Metadata[addrToSlot(Ptr)]; 199 } 200 201 size_t GuardedPoolAllocator::addrToSlot(uintptr_t Ptr) const { 202 assert(pointerIsMine(reinterpret_cast<void *>(Ptr))); 203 size_t ByteOffsetFromPoolStart = Ptr - GuardedPagePool; 204 return ByteOffsetFromPoolStart / (maximumAllocationSize() + PageSize); 205 } 206 207 uintptr_t GuardedPoolAllocator::slotToAddr(size_t N) const { 208 return GuardedPagePool + (PageSize * (1 + N)) + (maximumAllocationSize() * N); 209 } 210 211 uintptr_t GuardedPoolAllocator::getPageAddr(uintptr_t Ptr) const { 212 assert(pointerIsMine(reinterpret_cast<void *>(Ptr))); 213 return Ptr & ~(static_cast<uintptr_t>(PageSize) - 1); 214 } 215 216 bool GuardedPoolAllocator::isGuardPage(uintptr_t Ptr) const { 217 assert(pointerIsMine(reinterpret_cast<void *>(Ptr))); 218 size_t PageOffsetFromPoolStart = (Ptr - GuardedPagePool) / PageSize; 219 size_t PagesPerSlot = maximumAllocationSize() / PageSize; 220 return (PageOffsetFromPoolStart % (PagesPerSlot + 1)) == 0; 221 } 222 223 size_t GuardedPoolAllocator::reserveSlot() { 224 // Avoid potential reuse of a slot before we have made at least a single 225 // allocation in each slot. Helps with our use-after-free detection. 226 if (NumSampledAllocations < MaxSimultaneousAllocations) 227 return NumSampledAllocations++; 228 229 if (FreeSlotsLength == 0) 230 return kInvalidSlotID; 231 232 size_t ReservedIndex = getRandomUnsigned32() % FreeSlotsLength; 233 size_t SlotIndex = FreeSlots[ReservedIndex]; 234 FreeSlots[ReservedIndex] = FreeSlots[--FreeSlotsLength]; 235 return SlotIndex; 236 } 237 238 void GuardedPoolAllocator::freeSlot(size_t SlotIndex) { 239 assert(FreeSlotsLength < MaxSimultaneousAllocations); 240 FreeSlots[FreeSlotsLength++] = SlotIndex; 241 } 242 243 uintptr_t GuardedPoolAllocator::allocationSlotOffset(size_t Size) const { 244 assert(Size > 0); 245 246 bool ShouldRightAlign = getRandomUnsigned32() % 2 == 0; 247 if (!ShouldRightAlign) 248 return 0; 249 250 uintptr_t Offset = maximumAllocationSize(); 251 if (!PerfectlyRightAlign) { 252 if (Size == 3) 253 Size = 4; 254 else if (Size > 4 && Size <= 8) 255 Size = 8; 256 else if (Size > 8 && (Size % 16) != 0) 257 Size += 16 - (Size % 16); 258 } 259 Offset -= Size; 260 return Offset; 261 } 262 263 void GuardedPoolAllocator::reportError(uintptr_t AccessPtr, Error E) { 264 if (SingletonPtr) 265 SingletonPtr->reportErrorInternal(AccessPtr, E); 266 } 267 268 size_t GuardedPoolAllocator::getNearestSlot(uintptr_t Ptr) const { 269 if (Ptr <= GuardedPagePool + PageSize) 270 return 0; 271 if (Ptr > GuardedPagePoolEnd - PageSize) 272 return MaxSimultaneousAllocations - 1; 273 274 if (!isGuardPage(Ptr)) 275 return addrToSlot(Ptr); 276 277 if (Ptr % PageSize <= PageSize / 2) 278 return addrToSlot(Ptr - PageSize); // Round down. 279 return addrToSlot(Ptr + PageSize); // Round up. 280 } 281 282 Error GuardedPoolAllocator::diagnoseUnknownError(uintptr_t AccessPtr, 283 AllocationMetadata **Meta) { 284 // Let's try and figure out what the source of this error is. 285 if (isGuardPage(AccessPtr)) { 286 size_t Slot = getNearestSlot(AccessPtr); 287 AllocationMetadata *SlotMeta = addrToMetadata(slotToAddr(Slot)); 288 289 // Ensure that this slot was allocated once upon a time. 290 if (!SlotMeta->Addr) 291 return Error::UNKNOWN; 292 *Meta = SlotMeta; 293 294 if (SlotMeta->Addr < AccessPtr) 295 return Error::BUFFER_OVERFLOW; 296 return Error::BUFFER_UNDERFLOW; 297 } 298 299 // Access wasn't a guard page, check for use-after-free. 300 AllocationMetadata *SlotMeta = addrToMetadata(AccessPtr); 301 if (SlotMeta->IsDeallocated) { 302 *Meta = SlotMeta; 303 return Error::USE_AFTER_FREE; 304 } 305 306 // If we have reached here, the error is still unknown. There is no metadata 307 // available. 308 return Error::UNKNOWN; 309 } 310 311 // Prints the provided error and metadata information. Returns true if there is 312 // additional context that can be provided, false otherwise (i.e. returns false 313 // if Error == {UNKNOWN, INVALID_FREE without metadata}). 314 bool printErrorType(Error E, uintptr_t AccessPtr, AllocationMetadata *Meta, 315 options::Printf_t Printf) { 316 switch (E) { 317 case Error::UNKNOWN: 318 Printf("GWP-ASan couldn't automatically determine the source of the " 319 "memory error when accessing 0x%zx. It was likely caused by a wild " 320 "memory access into the GWP-ASan pool.\n", 321 AccessPtr); 322 return false; 323 case Error::USE_AFTER_FREE: 324 Printf("Use after free occurred when accessing memory at: 0x%zx\n", 325 AccessPtr); 326 break; 327 case Error::DOUBLE_FREE: 328 Printf("Double free occurred when trying to free memory at: 0x%zx\n", 329 AccessPtr); 330 break; 331 case Error::INVALID_FREE: 332 Printf( 333 "Invalid (wild) free occurred when trying to free memory at: 0x%zx\n", 334 AccessPtr); 335 // It's possible for an invalid free to fall onto a slot that has never been 336 // allocated. If this is the case, there is no valid metadata. 337 if (Meta == nullptr) 338 return false; 339 break; 340 case Error::BUFFER_OVERFLOW: 341 Printf("Buffer overflow occurred when accessing memory at: 0x%zx\n", 342 AccessPtr); 343 break; 344 case Error::BUFFER_UNDERFLOW: 345 Printf("Buffer underflow occurred when accessing memory at: 0x%zx\n", 346 AccessPtr); 347 break; 348 } 349 350 Printf("0x%zx is ", AccessPtr); 351 if (AccessPtr < Meta->Addr) 352 Printf("located %zu bytes to the left of a %zu-byte allocation located at " 353 "0x%zx\n", 354 Meta->Addr - AccessPtr, Meta->Size, Meta->Addr); 355 else if (AccessPtr > Meta->Addr) 356 Printf("located %zu bytes to the right of a %zu-byte allocation located at " 357 "0x%zx\n", 358 AccessPtr - Meta->Addr, Meta->Size, Meta->Addr); 359 else 360 Printf("a %zu-byte allocation\n", Meta->Size); 361 return true; 362 } 363 364 void printThreadInformation(Error E, uintptr_t AccessPtr, 365 AllocationMetadata *Meta, 366 options::Printf_t Printf) { 367 Printf("0x%zx was allocated by thread ", AccessPtr); 368 if (Meta->AllocationTrace.ThreadID == UINT64_MAX) 369 Printf("UNKNOWN.\n"); 370 else 371 Printf("%zu.\n", Meta->AllocationTrace.ThreadID); 372 373 if (E == Error::USE_AFTER_FREE || E == Error::DOUBLE_FREE) { 374 Printf("0x%zx was freed by thread ", AccessPtr); 375 if (Meta->AllocationTrace.ThreadID == UINT64_MAX) 376 Printf("UNKNOWN.\n"); 377 else 378 Printf("%zu.\n", Meta->AllocationTrace.ThreadID); 379 } 380 } 381 382 struct ScopedEndOfReportDecorator { 383 ScopedEndOfReportDecorator(options::Printf_t Printf) : Printf(Printf) {} 384 ~ScopedEndOfReportDecorator() { Printf("*** End GWP-ASan report ***\n"); } 385 options::Printf_t Printf; 386 }; 387 388 void GuardedPoolAllocator::reportErrorInternal(uintptr_t AccessPtr, 389 Error E) { 390 if (!pointerIsMine(reinterpret_cast<void *>(AccessPtr))) { 391 return; 392 } 393 394 // Attempt to prevent races to re-use the same slot that triggered this error. 395 // This does not guarantee that there are no races, because another thread can 396 // take the locks during the time that the signal handler is being called. 397 PoolMutex.tryLock(); 398 399 Printf("*** GWP-ASan detected a memory error ***\n"); 400 ScopedEndOfReportDecorator Decorator(Printf); 401 402 AllocationMetadata *Meta = nullptr; 403 404 if (E == Error::UNKNOWN) { 405 E = diagnoseUnknownError(AccessPtr, &Meta); 406 } else { 407 size_t Slot = getNearestSlot(AccessPtr); 408 Meta = addrToMetadata(slotToAddr(Slot)); 409 // Ensure that this slot has been previously allocated. 410 if (!Meta->Addr) 411 Meta = nullptr; 412 } 413 414 // Print the error information, and if there is no valid metadata, stop here. 415 if (!printErrorType(E, AccessPtr, Meta, Printf)) { 416 return; 417 } 418 419 // Ensure that we have a valid metadata pointer from this point forward. 420 if (Meta == nullptr) { 421 Printf("GWP-ASan internal unreachable error. Metadata is not null.\n"); 422 return; 423 } 424 425 printThreadInformation(E, AccessPtr, Meta, Printf); 426 // TODO(hctim): Implement stack unwinding here. Ask the caller to provide us 427 // with the base pointer, and we unwind the stack to give a stack trace for 428 // the access. 429 // TODO(hctim): Implement dumping here of allocation/deallocation traces. 430 } 431 432 TLS_INITIAL_EXEC uint64_t GuardedPoolAllocator::NextSampleCounter = 0; 433 } // namespace gwp_asan 434