Manual Page Search Parameters

CHERI_REVOKE(2) System Calls Manual CHERI_REVOKE(2)

cheri_revoke cheri_revoke_get_shadowinterface to sweeping revocation of CHERI capabilities

Standard C Library (libc, -lc)

#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);

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 () system call.

The () 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 the SW_VMEM permission and whose 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 () 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(), with CHERI_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 with CHERI_REVOKE_LAST_PASS.

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 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 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.

cheri_revoke_info() fails with

[]
if the arena for does not cary the SW_VMEM permission.
[]
if the arena for CHERI_REVOKE_SHADOW_MEM is not sufficiently aligned and sized.
[]
if the shadow parameter for either CHERI_REVOKE_SHADOW_MEM or CHERI_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

[]
if the start epoch is presently open or in the future
[]
if invalid flags have been specified
[]
if start does not describe an epoch and CHERI_REVOKE_IGNORE_START was not given in flags.
February 13, 2024 dev