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
|
//===-- 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/random.h"
#include "gwp_asan/stack_trace_compressor.h"
#include <stddef.h>
#include <stdint.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(ThreadLocals.NextSampleCounter == 0))
ThreadLocals.NextSampleCounter =
(getRandomUnsigned32() % (AdjustedSampleRatePlusOne - 1)) + 1;
return GWP_ASAN_UNLIKELY(--ThreadLocals.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, and return a pointer to the new
// allocation. Returns nullptr if the pool is empty, the requested size is too
// large for this pool to handle, or the requested size is zero.
void *allocate(size_t Size);
// 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; }
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.
// Note that memory is initially mapped inaccessible. In order for RW
// mappings, call mapMemory() followed by markReadWrite() on the returned
// 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();
// 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;
// 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;
// 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
// variables in GWP-ASan.
struct alignas(8) ThreadLocalPackedVariables {
constexpr ThreadLocalPackedVariables() {}
// Thread-local decrementing counter that indicates that a given allocation
// should be sampled when it reaches zero.
uint32_t NextSampleCounter = 0;
// Guard against recursivity. Unwinders often contain complex behaviour that
// may not be safe for the allocator (i.e. the unwinder calls dlopen(),
// which calls malloc()). When recursive behaviour is detected, we will
// automatically fall back to the supporting allocator to supply the
// allocation.
bool RecursiveGuard = false;
};
static GWP_ASAN_TLS_INITIAL_EXEC ThreadLocalPackedVariables ThreadLocals;
};
} // namespace gwp_asan
#endif // GWP_ASAN_GUARDED_POOL_ALLOCATOR_H_
|