diff options
| author | maxim-yurchuk <[email protected]> | 2025-09-11 18:10:35 +0300 | 
|---|---|---|
| committer | maxim-yurchuk <[email protected]> | 2025-09-11 18:29:46 +0300 | 
| commit | f4413504c29db8e1e7cce3a2103e89ecb4073271 (patch) | |
| tree | 1bf7e20cd558f17fc8ec8c18f73efda775fc447a /contrib/libs/clang20-rt/lib/gwp_asan/guarded_pool_allocator.cpp | |
| parent | 542e74eb48caba00fb12eef75125e376ab12c0c9 (diff) | |
Add clang20-rt into ydb sync config
commit_hash:a644e6487e802adc4b59a62ef53000053f586377
Diffstat (limited to 'contrib/libs/clang20-rt/lib/gwp_asan/guarded_pool_allocator.cpp')
| -rw-r--r-- | contrib/libs/clang20-rt/lib/gwp_asan/guarded_pool_allocator.cpp | 472 | 
1 files changed, 472 insertions, 0 deletions
diff --git a/contrib/libs/clang20-rt/lib/gwp_asan/guarded_pool_allocator.cpp b/contrib/libs/clang20-rt/lib/gwp_asan/guarded_pool_allocator.cpp new file mode 100644 index 00000000000..a5f1ecd4b26 --- /dev/null +++ b/contrib/libs/clang20-rt/lib/gwp_asan/guarded_pool_allocator.cpp @@ -0,0 +1,472 @@ +//===-- guarded_pool_allocator.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/crash_handler.h" +#include "gwp_asan/options.h" +#include "gwp_asan/utilities.h" + +#include <assert.h> +#include <stddef.h> + +using AllocationMetadata = gwp_asan::AllocationMetadata; +using Error = gwp_asan::Error; + +namespace gwp_asan { +namespace { +// Forward declare the pointer to the singleton version of this class. +// Instantiated during initialisation, this allows the signal handler +// to find this class in order to deduce the root cause of failures. Must not be +// referenced by users outside this translation unit, in order to avoid +// init-order-fiasco. +GuardedPoolAllocator *SingletonPtr = nullptr; + +size_t roundUpTo(size_t Size, size_t Boundary) { +  return (Size + Boundary - 1) & ~(Boundary - 1); +} + +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 +// init() is called, thread-safe afterwards. +GuardedPoolAllocator *GuardedPoolAllocator::getSingleton() { +  return SingletonPtr; +} + +void GuardedPoolAllocator::init(const options::Options &Opts) { +  // Note: We return from the constructor here if GWP-ASan is not available. +  // This will stop heap-allocation of class members, as well as mmap() of the +  // guarded slots. +  if (!Opts.Enabled || Opts.SampleRate == 0 || +      Opts.MaxSimultaneousAllocations == 0) +    return; + +  check(Opts.SampleRate >= 0, "GWP-ASan Error: SampleRate is < 0."); +  check(Opts.SampleRate < (1 << 30), "GWP-ASan Error: SampleRate is >= 2^30."); +  check(Opts.MaxSimultaneousAllocations >= 0, +        "GWP-ASan Error: MaxSimultaneousAllocations is < 0."); + +  check(SingletonPtr == nullptr, +        "There's already a live GuardedPoolAllocator!"); +  SingletonPtr = this; +  Backtrace = Opts.Backtrace; + +  State.VersionMagic = {{AllocatorVersionMagic::kAllocatorVersionMagic[0], +                         AllocatorVersionMagic::kAllocatorVersionMagic[1], +                         AllocatorVersionMagic::kAllocatorVersionMagic[2], +                         AllocatorVersionMagic::kAllocatorVersionMagic[3]}, +                        AllocatorVersionMagic::kAllocatorVersion, +                        0}; + +  State.MaxSimultaneousAllocations = Opts.MaxSimultaneousAllocations; + +  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; + +  // Number of pages required = +  //  + MaxSimultaneousAllocations * maximumAllocationSize (N pages per slot) +  //  + MaxSimultaneousAllocations (one guard on the left side of each slot) +  //  + 1 (an extra guard page at the end of the pool, on the right side) +  //  + 1 (an extra page that's used for reporting internally-detected crashes, +  //       like double free and invalid free, to the signal handler; see +  //       raiseInternallyDetectedError() for more info) +  size_t PoolBytesRequired = +      PageSize * (2 + State.MaxSimultaneousAllocations) + +      State.MaxSimultaneousAllocations * State.maximumAllocationSize(); +  assert(PoolBytesRequired % PageSize == 0); +  void *GuardedPoolMemory = reserveGuardedPool(PoolBytesRequired); + +  size_t BytesRequired = +      roundUpTo(State.MaxSimultaneousAllocations * sizeof(*Metadata), PageSize); +  Metadata = reinterpret_cast<AllocationMetadata *>( +      map(BytesRequired, kGwpAsanMetadataName)); + +  // Allocate memory and set up the free pages queue. +  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. +  if (Opts.SampleRate != 1) +    AdjustedSampleRatePlusOne = static_cast<uint32_t>(Opts.SampleRate) * 2 + 1; +  else +    AdjustedSampleRatePlusOne = 2; + +  initPRNG(); +  getThreadLocals()->NextSampleCounter = +      ((getRandomUnsigned32() % (AdjustedSampleRatePlusOne - 1)) + 1) & +      ThreadLocalPackedVariables::NextSampleCounterMask; + +  State.GuardedPagePool = reinterpret_cast<uintptr_t>(GuardedPoolMemory); +  State.GuardedPagePoolEnd = +      reinterpret_cast<uintptr_t>(GuardedPoolMemory) + PoolBytesRequired; + +  if (Opts.InstallForkHandlers) +    installAtFork(); +} + +void GuardedPoolAllocator::disable() { +  PoolMutex.lock(); +  BacktraceMutex.lock(); +} + +void GuardedPoolAllocator::enable() { +  PoolMutex.unlock(); +  BacktraceMutex.unlock(); +} + +void GuardedPoolAllocator::iterate(void *Base, size_t Size, iterate_callback Cb, +                                   void *Arg) { +  uintptr_t Start = reinterpret_cast<uintptr_t>(Base); +  for (size_t i = 0; i < State.MaxSimultaneousAllocations; ++i) { +    const AllocationMetadata &Meta = Metadata[i]; +    if (Meta.Addr && !Meta.IsDeallocated && Meta.Addr >= Start && +        Meta.Addr < Start + Size) +      Cb(Meta.Addr, Meta.RequestedSize, Arg); +  } +} + +void GuardedPoolAllocator::uninitTestOnly() { +  if (State.GuardedPagePool) { +    unreserveGuardedPool(); +    State.GuardedPagePool = 0; +    State.GuardedPagePoolEnd = 0; +  } +  if (Metadata) { +    unmap(Metadata, +          roundUpTo(State.MaxSimultaneousAllocations * sizeof(*Metadata), +                    State.PageSize)); +    Metadata = nullptr; +  } +  if (FreeSlots) { +    unmap(FreeSlots, +          roundUpTo(State.MaxSimultaneousAllocations * sizeof(*FreeSlots), +                    State.PageSize)); +    FreeSlots = nullptr; +  } +  *getThreadLocals() = ThreadLocalPackedVariables(); +  SingletonPtr = nullptr; +} + +// 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; +} + +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) { +    getThreadLocals()->NextSampleCounter = +        (AdjustedSampleRatePlusOne - 1) & +        ThreadLocalPackedVariables::NextSampleCounterMask; +    return nullptr; +  } + +  if (Size == 0) +    Size = 1; +  if (Alignment == 0) +    Alignment = alignof(max_align_t); + +  if (!isPowerOfTwo(Alignment) || Alignment > State.maximumAllocationSize() || +      Size > State.maximumAllocationSize()) +    return nullptr; + +  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; +  { +    ScopedLock L(PoolMutex); +    Index = reserveSlot(); +  } + +  if (Index == kInvalidSlotID) +    return nullptr; + +  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. +  const size_t PageSize = State.PageSize; +  allocateInGuardedPool( +      reinterpret_cast<void *>(getPageAddr(UserPtr, PageSize)), +      roundUpTo(Size, PageSize)); + +  Meta->RecordAllocation(UserPtr, Size); +  { +    ScopedLock UL(BacktraceMutex); +    Meta->AllocationTrace.RecordBacktrace(Backtrace); +  } + +  return reinterpret_cast<void *>(UserPtr); +} + +void GuardedPoolAllocator::raiseInternallyDetectedError(uintptr_t Address, +                                                        Error E) { +  // Disable the allocator before setting the internal failure state. In +  // non-recoverable mode, the allocator will be permanently disabled, and so +  // things will be accessed without locks. +  disable(); + +  // Races between internally- and externally-raised faults can happen. Right +  // now, in this thread we've locked the allocator in order to raise an +  // internally-detected fault, and another thread could SIGSEGV to raise an +  // externally-detected fault. What will happen is that the other thread will +  // wait in the signal handler, as we hold the allocator's locks from the +  // disable() above. We'll trigger the signal handler by touching the +  // internal-signal-raising address below, and the signal handler from our +  // thread will get to run first as we will continue to hold the allocator +  // locks until the enable() at the end of this function. Be careful though, if +  // this thread receives another SIGSEGV after the disable() above, but before +  // touching the internal-signal-raising address below, then this thread will +  // get an "externally-raised" SIGSEGV while *also* holding the allocator +  // locks, which means this thread's signal handler will deadlock. This could +  // be resolved with a re-entrant lock, but asking platforms to implement this +  // seems unnecessary given the only way to get a SIGSEGV in this critical +  // section is either a memory safety bug in the couple lines of code below (be +  // careful!), or someone outside uses `kill(this_thread, SIGSEGV)`, which +  // really shouldn't happen. + +  State.FailureType = E; +  State.FailureAddress = Address; + +  // Raise a SEGV by touching a specific address that identifies to the crash +  // handler that this is an internally-raised fault. Changing this address? +  // Don't forget to update __gwp_asan_get_internal_crash_address. +  volatile char *p = +      reinterpret_cast<char *>(State.internallyDetectedErrorFaultAddress()); +  *p = 0; + +  // This should never be reached in non-recoverable mode. Ensure that the +  // signal handler called handleRecoverablePostCrashReport(), which was +  // responsible for re-setting these fields. +  assert(State.FailureType == Error::UNKNOWN); +  assert(State.FailureAddress == 0u); + +  // In recoverable mode, the signal handler (after dumping the crash) marked +  // the page containing the InternalFaultSegvAddress as read/writeable, to +  // allow the second touch to succeed after returning from the signal handler. +  // Now, we need to mark the page as non-read/write-able again, so future +  // internal faults can be raised. +  deallocateInGuardedPool( +      reinterpret_cast<void *>(getPageAddr( +          State.internallyDetectedErrorFaultAddress(), State.PageSize)), +      State.PageSize); + +  // And now we're done with patching ourselves back up, enable the allocator. +  enable(); +} + +void GuardedPoolAllocator::deallocate(void *Ptr) { +  assert(pointerIsMine(Ptr) && "Pointer is not mine!"); +  uintptr_t UPtr = reinterpret_cast<uintptr_t>(Ptr); +  size_t Slot = State.getNearestSlot(UPtr); +  uintptr_t SlotStart = State.slotToAddr(Slot); +  AllocationMetadata *Meta = addrToMetadata(UPtr); + +  // If this allocation is responsible for crash, never recycle it. Turn the +  // deallocate() call into a no-op. +  if (Meta->HasCrashed) +    return; + +  if (Meta->Addr != UPtr) { +    raiseInternallyDetectedError(UPtr, Error::INVALID_FREE); +    return; +  } +  if (Meta->IsDeallocated) { +    raiseInternallyDetectedError(UPtr, Error::DOUBLE_FREE); +    return; +  } + +  // Intentionally scope the mutex here, so that other threads can access the +  // pool during the expensive markInaccessible() call. +  { +    ScopedLock L(PoolMutex); + +    // Ensure that the deallocation is recorded before marking the page as +    // inaccessible. Otherwise, a racy use-after-free will have inconsistent +    // metadata. +    Meta->RecordDeallocation(); + +    // Ensure that the unwinder is not called if the recursive flag is set, +    // otherwise non-reentrant unwinders may deadlock. +    if (!getThreadLocals()->RecursiveGuard) { +      ScopedRecursiveGuard SRG; +      ScopedLock UL(BacktraceMutex); +      Meta->DeallocationTrace.RecordBacktrace(Backtrace); +    } +  } + +  deallocateInGuardedPool(reinterpret_cast<void *>(SlotStart), +                          State.maximumAllocationSize()); + +  // And finally, lock again to release the slot back into the pool. +  ScopedLock L(PoolMutex); +  freeSlot(Slot); +} + +// Thread-compatible, protected by PoolMutex. +static bool PreviousRecursiveGuard; + +void GuardedPoolAllocator::preCrashReport(void *Ptr) { +  assert(pointerIsMine(Ptr) && "Pointer is not mine!"); +  uintptr_t InternalCrashAddr = __gwp_asan_get_internal_crash_address( +      &State, reinterpret_cast<uintptr_t>(Ptr)); +  if (!InternalCrashAddr) +    disable(); + +  // If something in the signal handler calls malloc() while dumping the +  // GWP-ASan report (e.g. backtrace_symbols()), make sure that GWP-ASan doesn't +  // service that allocation. `PreviousRecursiveGuard` is protected by the +  // allocator locks taken in disable(), either explicitly above for +  // externally-raised errors, or implicitly in raiseInternallyDetectedError() +  // for internally-detected errors. +  PreviousRecursiveGuard = getThreadLocals()->RecursiveGuard; +  getThreadLocals()->RecursiveGuard = true; +} + +void GuardedPoolAllocator::postCrashReportRecoverableOnly(void *SignalPtr) { +  uintptr_t SignalUPtr = reinterpret_cast<uintptr_t>(SignalPtr); +  uintptr_t InternalCrashAddr = +      __gwp_asan_get_internal_crash_address(&State, SignalUPtr); +  uintptr_t ErrorUptr = InternalCrashAddr ?: SignalUPtr; + +  AllocationMetadata *Metadata = addrToMetadata(ErrorUptr); +  Metadata->HasCrashed = true; + +  allocateInGuardedPool( +      reinterpret_cast<void *>(getPageAddr(SignalUPtr, State.PageSize)), +      State.PageSize); + +  // Clear the internal state in order to not confuse the crash handler if a +  // use-after-free or buffer-overflow comes from a different allocation in the +  // future. +  if (InternalCrashAddr) { +    State.FailureType = Error::UNKNOWN; +    State.FailureAddress = 0; +  } + +  size_t Slot = State.getNearestSlot(ErrorUptr); +  // If the slot is available, remove it permanently. +  for (size_t i = 0; i < FreeSlotsLength; ++i) { +    if (FreeSlots[i] == Slot) { +      FreeSlots[i] = FreeSlots[FreeSlotsLength - 1]; +      FreeSlotsLength -= 1; +      break; +    } +  } + +  getThreadLocals()->RecursiveGuard = PreviousRecursiveGuard; +  if (!InternalCrashAddr) +    enable(); +} + +size_t GuardedPoolAllocator::getSize(const void *Ptr) { +  assert(pointerIsMine(Ptr)); +  ScopedLock L(PoolMutex); +  AllocationMetadata *Meta = addrToMetadata(reinterpret_cast<uintptr_t>(Ptr)); +  assert(Meta->Addr == reinterpret_cast<uintptr_t>(Ptr)); +  return Meta->RequestedSize; +} + +AllocationMetadata *GuardedPoolAllocator::addrToMetadata(uintptr_t Ptr) const { +  return &Metadata[State.getNearestSlot(Ptr)]; +} + +size_t GuardedPoolAllocator::reserveSlot() { +  // Avoid potential reuse of a slot before we have made at least a single +  // allocation in each slot. Helps with our use-after-free detection. +  if (NumSampledAllocations < State.MaxSimultaneousAllocations) +    return NumSampledAllocations++; + +  if (FreeSlotsLength == 0) +    return kInvalidSlotID; + +  size_t ReservedIndex = getRandomUnsigned32() % FreeSlotsLength; +  size_t SlotIndex = FreeSlots[ReservedIndex]; +  FreeSlots[ReservedIndex] = FreeSlots[--FreeSlotsLength]; +  return SlotIndex; +} + +void GuardedPoolAllocator::freeSlot(size_t SlotIndex) { +  assert(FreeSlotsLength < State.MaxSimultaneousAllocations); +  FreeSlots[FreeSlotsLength++] = SlotIndex; +} + +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  | 
