NAME
cheri_revoke
cheri_revoke_get_shadow
—
interface to sweeping revocation of
CHERI capabilities
LIBRARY
Standard C Library (libc, -lc)
SYNOPSIS
#include
<cheri/revoke.h>
int
cheri_revoke
(int flags,
cheri_revoke_epoch_t start, struct
cheri_revoke_syscall_info *crsi);
cheri_revoke_get_shadow
(int
flags, void *arena,
void **shadow);
DESCRIPTION
The kernel exposes a CHERI capability revocation service, a
mechanism to revoke capabilities to regions of the address space. Requests
for revocation are made by setting bits in the shadow bitmap and invokving
the
cheri_revoke
()
system call.
The
cheri_revoke_get_shadow
()
system call is used to obtain access to segments of the shadow bitmap and to
global revocation metadata. The flags argument governs
the behavior of the call:
- CHERI_REVOKE_SHADOW_NOVMEM
- arena should be a capability bearing the
SW_VMEM
software permission, as returned by, for example, mmap(2), and it will store to its shadow argument a capability authorizing access to the shadow of the arena. A bit set in the region authorized by shadow corresponds to a request for revocation of capabilities lacking theSW_VMEM
permission and whose base points to the granule corresponding to that bit. - CHERI_REVOKE_SHADOW_INFO_STRUCT
- arena is ignored and shadow
will be set to a read-only pointer to the global
cheri_revoke_info
structure, which the kernel maintains with the current epoch counters.
The
cheri_revoke
()
system call interfaces with this CHERI capability revocation service to
advance or synchronize with the revocation state machine and revocation
epoch clock. Its start argument is used for relaxed
synchronization between multiple users of the revocation service. If the
epoch clock has advanced beyond the provided value, the function will return
immediately. The crsi argument is provided mostly for
experimentation and may be NULL
. The following flags
may be specified in the flags argument:
- CHERI_REVOKE_IGNORE_START
- Causes the start argument to be ignored and the epoch counter obtained by synchronizing with the revocation service to be used instead.
- CHERI_REVOKE_LAST_PASS
- Ensure that the current epoch is closed before returning. In general this
means that the
cheri_revoke
() call will perform a revocation scan of the address space of the calling process. - CHERI_REVOKE_ASYNC
- Rather than performing the revocation scan in the context of the calling
thread, specifying this flag will cause the scan to be performed
asynchronously. A subsequent call to
cheri_revoke
(), withCHERI_REVOKE_ASYNC
specified, will check the status of the scan and update the current epoch if it had completed successfully. If not, the scan will be retried. This flag may not be specified together withCHERI_REVOKE_LAST_PASS
.
EXAMPLES
At allocator startup or upon first need, obtain a capability to
the global cheri_revoke_info
structure, and store it
to an allocator local variable:
static struct cheri_revoke_info *cri; int res = cheri_revoke_get_shadow(CHERI_REVOKE_SHADOW_INFO_STRUCT, NULL, &cri); assert(res == 0);
For a very simple allocator, it suffices to collect
free
() memory into quarantine and then revoke it all
at once:
cheri_revoke_get_shadow(CHERI_REVOKE_SHADOW_NOVMEM, arena, &shadow); /* elided: set bits in shadow corresponding to quarantine */ atomic_thread_fence(memory_order_acq_rel); cheri_revoke_epoch epoch_start = cri->epochs.enqueue; while (!cheri_revoke_epoch_clears(cri->epochs.dequeue, epoch_start)) { cheri_revoke(CHERI_REVOKE_LAST_PASS, epoch_start, NULL); } /* elided: clear bits in shadow corresponding to quarantine */ atomic_thread_fence(memory_order_rel); /* It is now safe to reuse quarantined memory */
The release fence may be merged into a subsequent lock release, but the acquire-release fence is requisite in almost all cases.
For more complex allocators, several steps are necessary.
Whenever allocating a new arena using mmap(2) also obtain access to the shadow space:
struct arena_meta *meta = // ... meta->ptr = mmap(/* ... */); int res = cheri_revoke_get_shadow(CHERI_REVOKE_SHADOW_NOVMEM, meta->ptr, &meta->caprev_sh); assert(res == 0);
When a chunk of quarantine fills, label it with its enqueue epoch counter:
// elided: fill out the shadow bitmap prior to here atomic_thread_fence(memory_order_acq_rel); // publish shadow chunk->caprev_enq = cri->epochs.enqueue; // label this chunk
When revocation becomes necessary, loop until the dequeue epoch counter clears the eldest chunk, and then return all usable chunks to service:
while (!cheri_revoke_epoch_clears(cri->epochs.dequeue, chunk->caprev_enq)) { cheri_revoke(CHERI_REVOKE_LAST_PASS, chunk->caprev_enq, NULL); } more = chunk->next; /* This chunk is certainly reusable now */ reusable(chunk); /* And others may be as well */ for (chunk = more; chunk && caprev_epoch_clears(cri->epochs.dequeue, chunk->caprev_enq); chunk = chunk->next) { reusable(chunk); }
The reusable
() function in this example is
responsible for clearing shadow bits. The allocator as a whole, but not
necessarily the reusable
() function itself, must, as
before, execute a release fence before actually reusing memory.
ERRORS
cheri_revoke_info
() fails with
- [
EPERM
] - if the arena for does not cary the
SW_VMEM
permission. - [
EINVAL
] - if the arena for
CHERI_REVOKE_SHADOW_MEM
is not sufficiently aligned and sized. - [
EINVAL
] - if the shadow parameter for either
CHERI_REVOKE_SHADOW_MEM
orCHERI_REVOKE_SHADOW_INFO
points to an invalid address.
cheri_revoke
() will return 0 if the
start epoch has been cleared by time of return;
otherwise, it reutrns
- [
EAGAIN
] - if the start epoch is presently open or in the future
- [
EINVAL
] - if invalid flags have been specified
- [
EINVAL
] - if start does not describe an epoch and
CHERI_REVOKE_IGNORE_START
was not given in flags.