guarded_pool_allocator.h
9.41 KB
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_