blob: 445c7780652fb7dcbf3fa05becf32854974c0986 [file] [log] [blame]
// 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;
}