-
Notifications
You must be signed in to change notification settings - Fork 49
/
Copy pathrevoker.h
494 lines (452 loc) · 13.7 KB
/
revoker.h
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
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
// Copyright Microsoft and CHERIoT Contributors.
// SPDX-License-Identifier: MIT
#pragma once
#include "alloc_config.h"
#include "software_revoker.h"
#include <concepts>
#include <riscvreg.h>
#include <stdint.h>
#include <utils.hh>
#if __has_include(<platform-hardware_revoker.hh>)
# include <platform-hardware_revoker.hh>
#elif defined(TEMPORAL_SAFETY) && !defined(SOFTWARE_REVOKER)
# error Hardware revoker requested but no hardware_revoker.hh found
#endif
namespace Revocation
{
/**
* Concept for a hardware revoker. Boards can provide their own definition
* of this, which must be found in `<hardware_revoker.hh>` in a path
* provided by the board search.
*/
template<typename T>
concept IsHardwareRevokerDevice = requires(T v, uint32_t epoch)
{
{v.init()};
{
v.system_epoch_get()
} -> std::same_as<uint32_t>;
{
v.template has_revocation_finished_for_epoch<true>(epoch)
} -> std::same_as<uint32_t>;
{
v.template has_revocation_finished_for_epoch<false>(epoch)
} -> std::same_as<uint32_t>;
{
v.system_bg_revoker_kick()
} -> std::same_as<void>;
};
/**
* If this revoker supports an interrupt to notify of completion then it
* must have a method that blocks waiting for the interrupt to fire. This
* method should return true if the epoch has been reached or false if the
* timeout expired.
*/
template<typename T>
concept SupportsInterruptNotification = requires(T v,
Timeout *timeout,
uint32_t epoch)
{
{
v.wait_for_completion(timeout, epoch)
} -> std::same_as<bool>;
};
/**
* Class for interacting with the shadow bitmap. This bitmap controls the
* behaviour of a hardware load barrier, which will invalidate capabilities
* in the register file if they point revoked memory. Only memory that is
* intended for use as a heap is required to use the load barrier and so
* the revocation bitmap is not required to span the whole of the address
* space.
*
* Template arguments are the size of the value to be used for reads and
* updates, which may vary depending on the width of the interface to the
* shadow memory, and the base address of the memory covered by the shadow
* bitmap.
*
* This currently assumes that all revocable memory is in a single
* contiguous region.
*/
template<typename WordT, size_t TCMBaseAddr>
class Bitmap
{
/// The pointer to the shadow bitmap region.
WordT *shadowCap;
/// The number of bits in a single load or store of the shadow memory.
static constexpr size_t ShadowWordSizeBits =
utils::bytes2bits(sizeof(*shadowCap));
/// The shift required to translate an offset in bytes in memory into
/// an offset in bytes in the shadow memory.
static constexpr size_t ShadowWordShift =
utils::log2<ShadowWordSizeBits>();
/// The mask used to compute which bits to update in a word of the
/// revocation bitmap.
static constexpr size_t ShadowWordMask = (1U << ShadowWordShift) - 1;
protected:
/// Initialise this class with a capability to the shadow bitmap.
void init()
{
shadowCap = const_cast<WordT *>(MMIO_CAPABILITY(WordT, shadow));
}
static constexpr size_t shadow_offset_bits(ptraddr_t addr)
{
return (addr - TCMBaseAddr) >> MallocAlignShift;
}
static constexpr size_t shadow_word_index(size_t offsetBits)
{
return offsetBits >> ShadowWordShift;
}
static constexpr WordT shadow_mask_bits_below_address(ptraddr_t addr)
{
size_t capoffset = shadow_offset_bits(addr);
return (WordT(1) << (capoffset & ShadowWordMask)) - 1;
}
static constexpr WordT shadow_mask_bits_above_address(ptraddr_t addr)
{
return ~shadow_mask_bits_below_address(addr);
}
/*
* Some quick verification that the union of these two masks is
* all bits set.
*/
static_assert((shadow_mask_bits_above_address(0) ^
shadow_mask_bits_below_address(0)) == WordT(-1));
static_assert((shadow_mask_bits_above_address(7) ^
shadow_mask_bits_below_address(7)) == WordT(-1));
static_assert((shadow_mask_bits_above_address(ShadowWordSizeBits - 1) ^
shadow_mask_bits_below_address(ShadowWordSizeBits -
1)) == WordT(-1));
public:
/**
* @brief Set or clear the single shadow bit for an address.
*
* @param fill true to set, false to clear the bit
*/
void shadow_paint_single(ptraddr_t addr, bool fill)
{
Debug::Assert(addr > TCMBaseAddr,
"Address {} is below the TCM base {}",
addr,
TCMBaseAddr);
size_t capoffset = shadow_offset_bits(addr);
WordT shadowWord = shadowCap[shadow_word_index(capoffset)];
WordT mask = (WordT(1) << (capoffset & ShadowWordMask));
if (fill)
{
shadowWord |= mask;
}
else
{
shadowWord &= ~mask;
}
shadowCap[shadow_word_index(capoffset)] = shadowWord;
}
/**
* @brief Set or clear the shadow bits for all addresses within [base,
* top).
*
* @param Fill true to set, false to clear the bits
*/
template<bool Fill>
__always_inline void shadow_paint_range(ptraddr_t base, ptraddr_t top)
{
size_t baseCapOffset = shadow_offset_bits(base);
size_t topCapOffset = shadow_offset_bits(top);
size_t baseWordIx = shadow_word_index(baseCapOffset);
size_t topWordIx = shadow_word_index(topCapOffset);
WordT maskHi = shadow_mask_bits_below_address(top);
WordT maskLo = shadow_mask_bits_above_address(base);
if (baseWordIx == topWordIx)
{
/*
* This object is entirely contained within one word of the
* bitmap. We must AND the mask{Hi,Lo} together, since those
* masks were assuming that the object ran to (or past) the
* respective ends of the word.
*/
WordT mask = maskHi & maskLo;
if (Fill)
{
shadowCap[baseWordIx] |= mask;
}
else
{
shadowCap[baseWordIx] &= ~mask;
}
return;
}
/*
* Otherwise, there are at least two words of the bitmap that need
* to be updated with the masks, and possibly some in between that
* just need to be set wholesale.
*
* We paint ranges "backwards", from highest address to lowest, so
* that we never create a window in which an interior pointer has a
* clear shadow bit while the lower adjacent address has an asserted
* shadow bit, as that would open the door to confusing the interior
* pointer with a pointer to the start of an object (recall that
* object headers are marked in the shadow bitmap).
*
* When clearing ranges, the order matters less. A correct
* allocator will have run revocation first, and so there should be
* no interior pointers (outside the allocator, anyway) to worry us.
*/
WordT midWord;
if constexpr (Fill)
{
shadowCap[topWordIx] |= maskHi;
midWord = ~WordT(0);
}
else
{
shadowCap[topWordIx] &= ~maskHi;
midWord = 0;
}
/*
* This loop is underflow-safe, since topWordIx is strictly greater
* than baseWordIx after the test for equality above.
*/
for (size_t shadowWordIx = topWordIx - 1; baseWordIx < shadowWordIx;
shadowWordIx--)
{
shadowCap[shadowWordIx] = midWord;
}
if constexpr (Fill)
{
shadowCap[baseWordIx] |= maskLo;
}
else
{
shadowCap[baseWordIx] &= ~maskLo;
}
}
// Return the shadow bit at address addr.
bool shadow_bit_get(size_t addr)
{
size_t capoffset = shadow_offset_bits(addr);
WordT shadowWord = shadowCap[shadow_word_index(capoffset)];
WordT mask = (WordT(1) << (capoffset & ShadowWordMask));
return (shadowWord & mask) != 0;
}
/**
* @brief Check whether the argument of free() is valid.
* Because we use shadow bits to mark allocation boundaries, this has to
* be a method of a revoker instance.
*
* @return true if the input is valid
*/
bool is_free_cap_valid(CHERI::Capability<void> cap)
{
ptraddr_t base = cap.base();
return cap.is_valid() && (base & MallocAlignMask) == 0 &&
shadow_bit_get(base - MallocAlignment) == 1 &&
shadow_bit_get(base) == 0;
}
};
template<typename WordT,
size_t TCMBaseAddr,
template<typename, size_t>
typename Revoker>
requires IsHardwareRevokerDevice<Revoker<WordT, TCMBaseAddr>>
class HardwareAccelerator : public Bitmap<WordT, TCMBaseAddr>,
public Revoker<WordT, TCMBaseAddr>
{
public:
/**
* Currently the only hardware revoker implementation is async which
* sweeps memory in the background.
*/
static constexpr bool IsAsynchronous = true;
/**
* Initialise a revoker instance.
*/
void init()
{
Bitmap<WordT, TCMBaseAddr>::init();
Revoker<WordT, TCMBaseAddr>::init();
}
};
/**
* Null implementation of the revoker interface. Provides no temporal
* safety.
*/
class NoTemporalSafety
{
public:
/**
* If there is no temporal memory safety, we treat sweeping as
* synchronous but infinitely fast which returns immediately.
*/
static constexpr bool IsAsynchronous = false;
void init() {}
void shadow_paint_single(size_t, bool) {}
template<bool Fill>
void shadow_paint_range(size_t, size_t)
{
}
uint32_t system_epoch_get()
{
return 0;
}
template<bool AllowPartial = true>
uint32_t has_revocation_finished_for_epoch(uint32_t previousEpoch)
{
return true;
}
void system_bg_revoker_kick() {}
bool is_free_cap_valid(void *)
{
return true;
}
bool shadow_bit_get(size_t addr)
{
Debug::Assert(false,
"shadow_bit_get should not be called on the revoker "
"with no temporal safety, its result is meaningless");
return false;
}
};
/**
* Software revoker for use with a hardware load barrier. Uses a separate
* (very privileged!) compartment to scan all of memory.
*/
template<typename WordT, size_t TCMBaseAddr>
class SoftwareRevoker : public Bitmap<WordT, TCMBaseAddr>
{
private:
/**
* A (read-only) pointer to the revocation epoch. Incremented once
* when revocation starts and once when it finishes.
*/
const uint32_t *epoch;
public:
/**
* Software sweeping is implemented synchronously now. The sweeping is
* done when memory is under pressure or malloc() failed. malloc() and
* free() only return when a certain amount of sweeping is done.
*/
static constexpr bool IsAsynchronous = false;
/**
* Initialise the software revoker.
*/
void init()
{
Bitmap<WordT, TCMBaseAddr>::init();
epoch = revoker_epoch_get();
}
/**
* Returns the revocation epoch. This is the number of revocations
* that have started or finished. It will be even if revocation is not
* running.
*/
uint32_t system_epoch_get()
{
return *epoch;
}
/**
* Queries whether the specified revocation epoch has finished.
*/
template<bool AllowPartial = false>
uint32_t has_revocation_finished_for_epoch(uint32_t previousEpoch)
{
auto current = *epoch;
// If the revoker is running, prod it to do a bit more work every
// time that it's queried.
if ((current & 1) == 1)
{
revoker_tick();
current = *epoch;
}
// We want to know if current is greater than epoch, but current
// may have wrapped. Perform unsigned subtraction (guaranteed to
// wrap) and then coerce the result to a signed value. This will
// be correct unless we have more than 2^31 revocations in between
// checks
std::make_signed_t<decltype(current)> distance =
current - previousEpoch;
if (AllowPartial)
{
return distance >= 0;
}
// The allocator stores the epoch when things can be popped, which
// is always a complete (even) epoch.
Debug::Assert((previousEpoch & 1) == 0, "Epoch must be even");
// If the current epoch is odd then the epoch needs to be at least
// two more, to capture the fact that this is a complete epoch.
decltype(distance) minimumRequired = 1 + (previousEpoch & 1);
return distance > minimumRequired;
}
/// Start revocation running.
void system_bg_revoker_kick()
{
revoker_tick();
}
};
template<typename WordT, size_t TCMBaseAddr>
class FakeRevoker : public Bitmap<WordT, TCMBaseAddr>
{
private:
/**
* A (read-only) pointer to the revocation epoch. Incremented once
* when revocation starts and once when it finishes.
*/
uint32_t epoch;
public:
/**
* Software sweeping is implemented synchronously now. The sweeping is
* done when memory is under pressure or malloc() failed. malloc() and
* free() only return when a certain amount of sweeping is done.
*/
static constexpr bool IsAsynchronous = false;
/**
* Initialise the software revoker.
*/
void init()
{
Bitmap<WordT, TCMBaseAddr>::init();
epoch = 0;
}
/**
* Returns the revocation epoch. This is the number of revocations
* that have started or finished. It will be even if revocation is not
* running.
*/
uint32_t system_epoch_get()
{
return epoch;
}
/**
* Queries whether the specified revocation epoch has finished.
*/
template<bool AllowPartial = false>
uint32_t has_revocation_finished_for_epoch(uint32_t previousEpoch)
{
return true;
}
/// Start revocation running. Fake revocation completes instantly.
void system_bg_revoker_kick()
{
epoch++;
}
};
/**
* The revoker to use for this configuration.
*
* FIXME: This should not hard-code the start address.
*/
using Revoker =
#ifdef TEMPORAL_SAFETY
# ifdef SOFTWARE_REVOKER
SoftwareRevoker<uint32_t, REVOKABLE_MEMORY_START>
# else
HardwareAccelerator<uint32_t, REVOKABLE_MEMORY_START, HardwareRevoker>
# endif
#else
# ifdef CHERIOT_FAKE_REVOKER
FakeRevoker<uint32_t, REVOKABLE_MEMORY_START>;
# else
NoTemporalSafety
# endif
#endif
;
} // namespace Revocation