| // Copyright Microsoft and CHERIoT Contributors. |
| // SPDX-License-Identifier: MIT |
| |
| #include "../allocator/software_revoker.h" |
| #include <array> |
| #include <cheri.hh> |
| #include <debug.hh> |
| #include <utility> |
| |
| using CHERI::Capability; |
| using CHERI::Permission; |
| |
| /** |
| * We need an array of the three allocations that provide the globals at the |
| * start of our PCC, but the compiler doesn't currently provide a good way of |
| * doing this, so do it with an assembly stub for loading the three |
| * capabilities. |
| */ |
| __asm__(" .section .text, \"ax\", @progbits\n" |
| " .p2align 3\n" |
| "globals:\n" |
| " .zero 3*8\n" |
| " .globl get_globals\n" |
| "get_globals:\n" |
| " sll a0, a0, 3\n" |
| "1:\n" |
| " auipcc ca1, %cheriot_compartment_hi(globals)\n" |
| " cincoffset ca1, ca1, %cheriot_compartment_lo_i(1b)\n" |
| " cincoffset ca1, ca1, a0\n" |
| " clc ca0, 0(ca1)\n" |
| " cjr cra\n"); |
| |
| extern "C" volatile void *volatile *get_globals(int idx); |
| |
| namespace |
| { |
| /** |
| * The index of the current range to scan. Must be 0-2 or negative. |
| */ |
| int currentRange; |
| /** |
| * The current offset within the scanned range, in pointer-sized units. |
| */ |
| size_t offset; |
| /** |
| * The length of the region that's being scanned, in pointer-sized units. |
| */ |
| size_t length; |
| /** |
| * The current revocation epoch. If the low bit is 1, revocation is |
| * running. |
| */ |
| uint32_t epoch; |
| |
| /** |
| * The current state of the revoker's state machine. |
| */ |
| enum class State |
| { |
| /** |
| * Revocation is not currently happening. |
| */ |
| NotRunning, |
| /** |
| * The revoker is scanning globals. |
| */ |
| ScanningGlobals, |
| /** |
| * The revoker is scanning the heap. |
| */ |
| ScanningHeap, |
| /** |
| * The revoker is scanning stacks (including trusted stacks and the |
| * register-save areas). |
| */ |
| ScanningStacks |
| } state; |
| |
| /** |
| * Advance the state machine to the next state. |
| */ |
| std::pair<int, State> next() |
| { |
| switch (state) |
| { |
| case State::NotRunning: |
| return {0, State::ScanningGlobals}; |
| case State::ScanningGlobals: |
| return {1, State::ScanningHeap}; |
| case State::ScanningHeap: |
| return {2, State::ScanningStacks}; |
| case State::ScanningStacks: |
| return {-1, State::NotRunning}; |
| } |
| } |
| |
| /** |
| * The number of capabilities to scan per tick. This probably needs some |
| * tuning. Invoking the revoker costs around 400 cycles on Flute, so we're |
| * likely to be spending about half of our total time on domain transitions |
| * with a value <100. |
| */ |
| static constexpr size_t TickSize = 4096; |
| |
| /** |
| * Advance the state machine to the next state. |
| */ |
| void advance() |
| { |
| // If we're starting a revocation pass, increment the epoch counter (it |
| // should now be odd). |
| if (state == State::NotRunning) |
| { |
| epoch++; |
| assert((epoch & 1) == 1); |
| } |
| // Find the next state. |
| auto [nextRange, nextState] = next(); |
| currentRange = nextRange; |
| offset = 0; |
| // If we have a new range, set the length to something sensible. |
| if (nextRange != -1) |
| { |
| length = __builtin_cheri_length_get(get_globals(currentRange)) / |
| sizeof(void *); |
| } |
| state = nextState; |
| // If we've finished a run, increment the epoch counter (it should now |
| // be even). |
| if (state == State::NotRunning) |
| { |
| epoch++; |
| assert((epoch & 1) == 0); |
| } |
| } |
| |
| /** |
| * Scan a fixed-size range of the current memory region. |
| */ |
| void scan_range() |
| { |
| size_t end = offset + std::min(length - offset, TickSize); |
| auto current = get_globals(currentRange); |
| // With interrupts disabled, loading and storing a capability will |
| // clear the tag on anything that has been revoked via the load |
| // barrier. |
| for (int i = offset; i < end; i++) |
| { |
| current[i] = current[i]; |
| } |
| // Record the amount that we've scanned. |
| offset = end; |
| // Advance to the next state if we've finished scanning this range. |
| if (offset == length) |
| { |
| advance(); |
| } |
| } |
| |
| } // namespace |
| |
| void revoker_tick() |
| { |
| // If we've been asked to run, make sure that we're running. |
| if (state == State::NotRunning) |
| { |
| advance(); |
| } |
| // Do some work. |
| return scan_range(); |
| } |
| |
| const uint32_t *revoker_epoch_get() |
| { |
| Capability<uint32_t> epochPtr{&epoch}; |
| epochPtr.permissions() &= {Permission::Load, Permission::Global}; |
| return epochPtr; |
| } |