1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249
|
//===-- guarded_pool_allocator.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_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"
#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-
// ASan. It is the sole owner of the pool of sequentially allocated guarded
// slots. It should always be treated as a singleton.
// Functions in the public interface of this class are thread-compatible until
// init() is called, at which point they become thread-safe (unless specified
// otherwise).
class GuardedPoolAllocator {
public:
// 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
// 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() {}
GuardedPoolAllocator(const GuardedPoolAllocator &) = delete;
GuardedPoolAllocator &operator=(const GuardedPoolAllocator &) = delete;
// Note: This class is expected to be a singleton for the lifetime of the
// program. If this object is initialised, it will leak the guarded page pool
// and metadata allocations during destruction. We can't clean up these areas
// as this may cause a use-after-free on shutdown.
~GuardedPoolAllocator() = default;
// Initialise the rest of the members of this class. Create the allocation
// 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(getThreadLocals()->NextSampleCounter == 0))
getThreadLocals()->NextSampleCounter =
((getRandomUnsigned32() % (AdjustedSampleRatePlusOne - 1)) + 1) &
ThreadLocalPackedVariables::NextSampleCounterMask;
return GWP_ASAN_UNLIKELY(--getThreadLocals()->NextSampleCounter == 0);
}
// 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 {
return State.pointerIsMine(Ptr);
}
// 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.
void deallocate(void *Ptr);
// Returns the size of the allocation at Ptr.
size_t getSize(const void *Ptr);
// Returns a pointer to the Metadata region, or nullptr if it doesn't exist.
const AllocationMetadata *getMetadataRegion() const { return Metadata; }
// 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";
// 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
// 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.
// 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.
static size_t getPlatformPageSize();
// 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;
// Reserve a slot for a new guarded allocation. Returns kInvalidSlotID if no
// slot is available to be reserved.
size_t reserveSlot();
// Unreserve the guarded slot.
void freeSlot(size_t SlotIndex);
// 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);
static GuardedPoolAllocator *getSingleton();
// Install a pthread_atfork handler.
void installAtFork();
gwp_asan::AllocatorState State;
// 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.
size_t NumSampledAllocations = 0;
// Pointer to the allocation metadata (allocation/deallocation stack traces),
// if any.
AllocationMetadata *Metadata = nullptr;
// Pointer to an array of free slot indexes.
size_t *FreeSlots = nullptr;
// The current length of the list of free slots.
size_t FreeSlotsLength = 0;
// See options.{h, inc} for more information.
bool PerfectlyRightAlign = false;
// Backtrace function provided by the supporting allocator. See `options.h`
// for more information.
options::Backtrace_t Backtrace = nullptr;
// The adjusted sample rate for allocation sampling. Default *must* be
// nonzero, as dynamic initialisation may call malloc (e.g. from libstdc++)
// before GPA::init() is called. This would cause an error in shouldSample(),
// 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 AdjustedSampleRatePlusOne = 0;
// Additional platform specific data structure for the guarded pool mapping.
PlatformSpecificMapData GuardedPagePoolPlatformData = {};
class ScopedRecursiveGuard {
public:
ScopedRecursiveGuard() { getThreadLocals()->RecursiveGuard = true; }
~ScopedRecursiveGuard() { getThreadLocals()->RecursiveGuard = false; }
};
// 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
#endif // GWP_ASAN_GUARDED_POOL_ALLOCATOR_H_
|