Secure Calls
“Secure calls” are the current name for Cheriette inter-compartment calls. They are not in principle a new idea, every CHERI OS has a notion of a “compartment” and a CHERI capability-secured way of context switching between them. Cross-compartment calls are a CHERI-enforced RPC mechanism..
Cheriette borrows the caller-callee stack sharing from CompartOS & CHERIoT, and the general idea from CheriOS.
In Cheriette, secure calls are especially fast, because Cheriette uses a single global address space, and all process context is held in CHERI capabilities, explicitly none in the MMU. That means that switching between compartments using Secure Calls is as fast as setting up the call, saving registers to the stack and calling cinvoke, and zeroing the stack on return.
From an engineering perspective, secure calls in Cheriette have interfaces that mimick function calls. The fact that secure calls cross trust-domain boundaries means that the caller has to save and zero all non-argument registers before crossing the trust domain boundary and the callee has to zero out the stack it used before returning.
Instead of zeroing out the “shared” stack before returning to the caller, the handler stack could also
be allocated on-demand by an intermediary in-between the call. This design requires a trusted intermediary, and begs the question of where the intermediary gets its stack from. Depending on the design, the intermediary becomes a point of serialization for all secure calls in the system.
be pre-allocated and associated with the “channel” used for the secure call. This design requires no runtime allocation or sealing, but limits the number of concurrent calls for all tasks that share a channel to the number of pre-allocated stacks (In the current Cheriette, all tasks share the same “channel”, because a channel so far only had to be sealed pointers to the handler) .
What these alternatives have in common is that the handler stack is kept secret either by being sealed and “locked” to the channel or by being managed by an intermediary. The intermediary may have to zero out stacks for re-use on occasion, but that could happen asynchronously.
For now, Cheriette re-uses a restricted subset of the caller stack as the handler stack. This design is simplest, and requires no additional allocation decisions. Philosophically, this design decision follows the principle that the caller must provide the resources for the handler to run, not the other way around. In this way, the caller and handler merge into a single, continuous stream of execution that can be arbitrarly nested or even recursive.
Theory of Operation
High-level:
Task begins secure call
Create continuation cap that handler can return to
Create continuation data cap that handler passes back
Seal continuation cap
Seal complete stack cap
Store sealed complete stack cap in continuation data
Create restricted handler stack cap
Set stack pointer to restricted stack cap
cinvoke(sealed pc, sealed data)
Extract call arguments from register values
Call handler
Store return value in continuation data cap
Zero out restricted stack capability
Return through continuation cap
Recover complete stack from sealed cap
Return to task
CheriOS
CheriOS keeps a dedicated stack per compartment that a thread could enter and switches to that stack when the thread enters the compartment. This requires creating and maintaining stacks per thread.
See: cherios/system/dylink/src/platform/riscv/stubs.S
CompartOS
CompartOS uses a per-thread stack, which is donated
to the called
compartment and reset on return.
See: https://www.cl.cam.ac.uk/techreports/UCAM-CL-TR-976.pdf
CherIoT
Uses restricted caller stack in callee.
See: cheriot-rtos/core/switcher/tstack.h