Cheriot-Iot Security
Cheriot-Iot Security
Small embedded cores have little area to spare for security features and yet must often
run code written in unsafe languages and, increasingly, are exposed to the hostile Internet.
CHERIoT (Capability Hardware Extension to RISC-V for Internet of Things) builds on
top of CHERI and RISC-V to provide an ISA and software model that lets software depend
on object-granularity spatial memory safety, deterministic use-after-free protection, and
lightweight compartmentalization exposed directly to the C/C++ language model. This
can run existing embedded software components on a clean-slate RTOS that scales up to
large numbers of isolated (yet securely communicating) compartments, even on systems
with under 256 KiB of SRAM.
3
4
Acknowledgments
This document contains some elements from the CHERI ISA Specification1 , which is
licensed under the Creative Commons Attribution 4.0 International License. To view a
copy of this license, visit:
http://creativecommons.org/licenses/by/4.0/
1
https://github.com/CTSRD-CHERI/cheri-specification
5
The CHERI-RISC-V pseudocode is derived from the Sail CHERI-RISC-V model2 , which
has the following license:
This CHERI Sail RISC-V architecture model here, comprising all files and
directories except for the snapshots of the Lem and Sail libraries in the
prover_snapshots directory (which include copies of their licenses), is subject
to the BSD two-clause licence below.
This project has received funding from the European Research Council
(ERC) under the European Union’s Horizon 2020 research and innovation
programme (grant agreement 789108, ELVER).
2
https://github.com/CTSRD-CHERI/sail-cheri-riscv
6
The RISC-V pseudocode is derived from the Sail RISC-V model3 , which has the following
license:
This Sail RISC-V architecture model, comprising all files and
directories except for the snapshots of the Lem and Sail libraries
in the prover_snapshots directory (which include copies of their
licences), is subject to the BSD two-clause licence below.
This project has received funding from the European Research Council
(ERC) under the European Union’s Horizon 2020 research and innovation
programme (grant agreement 789108, ELVER).
3
https://github.com/riscv/sail-riscv
Contents
2 Compartment model 21
2.1 Compartments define spatial ownership . . . . . . . . . . . . . . . . . . 22
2.2 Threads define temporal ownership . . . . . . . . . . . . . . . . . . . . . 23
2.3 Execution at the intersection of threads and compartments . . . . . . . . 24
2.4 Compartment switches enforce compartment isolation . . . . . . . . . . . 24
2.5 Context switches enforce thread isolation . . . . . . . . . . . . . . . . . 25
2.6 Adding shared libraries . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
3 RTOS implementation 29
3.1 Per-thread state . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
3.2 The loader . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
3.3 Interrupt handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
3.4 Synchronization and scheduling primitives . . . . . . . . . . . . . . . . . 31
3.5 The memory allocator . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
3.6 Error handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
3.7 Other components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
7
8 CONTENTS
5 ABI 39
5.1 Compartment layout . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
5.2 Access to globals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
5.3 Export table layout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
5.4 Import table layout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
5.5 Cross-compartment calls . . . . . . . . . . . . . . . . . . . . . . . . . . 43
5.6 Cross-library calls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
5.7 Callbacks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
5.8 Relocations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
6 Known caveats 47
6.1 Shared stacks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
6.2 Explicit leakage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
6.3 Availability . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
II Architecture specification 51
7 The CHERIoT ISA 53
7.1 Starting subset of RV32 . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
7.2 Omitted CHERI features . . . . . . . . . . . . . . . . . . . . . . . . . . 53
7.3 Changes to register file . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
7.4 Instruction encodings . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
7.5 Changes to instruction fetch / control flow . . . . . . . . . . . . . . . . . 54
7.6 Changes to memory accesses . . . . . . . . . . . . . . . . . . . . . . . . 55
7.7 Tagged memory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
7.8 Temporal safety . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
7.9 Controlling access to system registers . . . . . . . . . . . . . . . . . . . 56
7.10 Special capability registers . . . . . . . . . . . . . . . . . . . . . . . . . 57
7.11 Changes to exception handling . . . . . . . . . . . . . . . . . . . . . . . 58
7.12 The AUIPC and AUICGP instructions . . . . . . . . . . . . . . . . . . . 58
7.13 Capability encoding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
7.13.1 Capability permissions . . . . . . . . . . . . . . . . . . . . . . . 59
CONTENTS 9
9 Instruction reference 83
9.1 Sail language used in instruction descriptions . . . . . . . . . . . . . . . 83
9.2 Constant Definitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
9.3 Function Definitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
9.4 CHERIoT Instructions . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
AUICGP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
AUIPCC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
CAndPerm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
CClearTag . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
CGetAddr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
CGetBase . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
CGetHigh . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
CGetLen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
10 CONTENTS
CGetPerm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
CGetTag . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
CGetTop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
CGetType . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
CIncAddr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
CIncAddrImm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
CJAL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
CJALR . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
CLC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
CMove . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
CRepresentableAlignmentMask . . . . . . . . . . . . . . . . . . . . . . 113
CRoundRepresentableLength . . . . . . . . . . . . . . . . . . . . . . . . 114
CSC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
CSeal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
CSetAddr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
CSetBounds . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
CSetBoundsExact . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
CSetBoundsImm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
CSetEqualExact . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
CSetHigh . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
CSpecialRW . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
CSub . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128
CTestSubset . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
CUnseal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
Bibliography 157
12 CONTENTS
Part I
13
Chapter 1
Introduction
The CHERIoT (Capability Hardware Extension to RISC-V for Internet of Things, pro-
nounced like ’chariot’) design is heavily based on prior work by the CHERI project. Our
RISC-V extension is based on the CHERI ISAv8[22] and would not have been possible
without this work.
This document describes the current status of the CHERIoT ISA. This ISA is not in-
tended to be a final CHERI specification for embedded RISC-V devices but is a work-in-
progress that is sufficiently close to final that feedback is valuable. In particular, the current
C extension to RISC-V makes a number of optimization decisions that are not useful in
a CHERI context and so we would likely benefit from an alternative (as yet unspecified)
16-bit instruction extension for embedded CHERI targets.
The CHERIoT project would not have been possible without the existing “big CHERI”
research [6, 22], exploration of green-field CHERI-aware operating systems [8], and work
to adapt CHERI software models to embedded systems [1, 2, 24, 25]. This ISA attempts to
scale CHERI down yet further, into smaller embedded systems than previously considered;
we have taken the opportunity to simultaneously design our ISA, compartment model,
programmer model, compiler, and RTOS [16]. The capability encoding and instruction set
have been tuned to enable this use and validated by running existing embedded software in
compartments. Curious readers are invited to see our study of related works in Appendix E.
This document describes:
15
16 CHAPTER 1. INTRODUCTION
Performance, power, and area costs for the implementation are not part of this and will
be presented in a follow-up publication.
Unforgeable A reference to memory (in particular, the authority to access memory) can
be constructed only from other references.
Monotonic A constructed reference will have no more authority than its progenitor refer-
ence(s) (and may have less).
Spatially Safe References to memory authorize access to a set of memory locations de-
termined when the reference is constructed.
Temporally Safe References to a region of memory will not remain usable across reuse
of memory for a different allocation.
The loader, which is responsible for setting up all of the initial capabilities for everything
in the system. This never accesses any data that was not part of the initial firmware
image.
The switcher, which is responsible for transitions between compartments and between
threads. This is around 300 instructions in hand-written assembly.
The memory allocator, which is responsible for providing the hardware with the infor-
mation required to enforce heap memory safety.
not correctly bounding capabilities it returns. Finally, it could violate temporal safety by
either not marking freed objects as deallocated or by un-marking the memory and reusing
it before revocation.
However, despite all that, the memory allocator does not hold capabilities to normal
compartment memory, only to region(s) reserved for the heap it manages. As such, even a
completely compromised memory allocator can violate safety properties of the heap only;
it cannot directly violate memory safety for non-heap memory.
Lexically-Scoped Delegation
CHERIoT ISA’s capability permission scheme allows software to derive variants of capa-
bilities that can be stored only to stack memory. Additionally, CHERIoT ISA can impose
this derivation transitively per capability: any capability loaded via such a capability be-
comes another such and, so, will impose the same on those loaded through it, and so on.
1.2. SECURITY GOALS 19
Compartments that have not been explicitly granted the rights to run with interrupts
disabled should also not be able to impact availability.
Compartment model
21
22 CHAPTER 2. COMPARTMENT MODEL
priority thread, with interrupts disabled except at explicit yield points. Other threads in
such a system would not be allowed to call any compartment entry points that can invoke
functions that run with interrupts disabled and so the realtime-priority thread can always
resume in the context-switch time. A somewhat softer realtime system may allow a small
number of functions to be invoked from compartments that are exposed to lower-priority
threads. These functions would be audited to ensure that their worst-case execution time
didn’t cause realtime components to miss their guarantees. At the weakest extreme, global
forward progress is purely a best-effort objective and any compartment may be allowed to
call functions that have no guarantees on bounded execution time and run with interrupts
disabled.
We expect that compartments may be provided by untrusted third parties and so it is
important that every cross-compartment interaction is amenable to auditing. In particu-
lar, the linker can see everything that the loader will set up and the loader is required to
explicitly grant access to a compartment for every:
• Only the specific code that the regulator approved may communicate directly with
this device.
• Any code may run on the device but only the TLS compartment may talk to the
network stack and only a compartment that exposes a small set of well-defined APIs
may call the TLS stack.
• There must be no interaction between any of the compartments managing service
A and the compartments managing service B on the device, except yielding via the
scheduler.
PCC is the program-counter capability, which is used to reach code and read-only globals.
CGP is the capability global pointer, which is used to reach read-write globals.
These define a set of code and data that represents a compartment. A compartment
is a single security context. While running in a compartment, any code in the memory
reachable by PCC may be executed, any data in that memory may be read, and any data
in the globals reachable from CGP may be read or written.
Note, in particular, that compartments are responsible for enforcing an object abstrac-
tion on top of their global memory. The C/C++ compiler will automatically insert bounds
when the address of a global is taken but an assembly programmer in a compartment is
able to reach any globals. Our security model assumes that all code within a compartment
trusts all other code within that compartment.
Stacks, reachable from a running thread’s CSP and any capabilities derived from this
(address-taken stack allocations).
Register save areas, reachable only from a special capability register (SCR) that are used
to store a thread’s state on context switch.
Trusted stacks, reachable only from a SCR, which are used to save and restore the stack
pointer on compartment switch (more on this later).
Of these, normal compartment code has access only to the stack. The latter two are a
single allocation that is reached via a SCR. The switcher is the only code that runs (after
the loader has exited) with the rights to access this SCR. Threads’ register files and stacks
dynamically define a set of reachable objects.
24 CHAPTER 2. COMPARTMENT MODEL
The compartment switch routine unseals the target capability and uses it to find the
PCC and CGP of the target compartment, and the offset within the PCC. It can then
construct a target to invoke. In addition, it reads the number of registers that the callee
expects to have passed (which it uses to zero unused argument registers) and the interrupt
status for the callee (which it uses to reenable interrupts immediately prior to invocation,
if required). The RV32E ABI defines only two callee-save registers. The switcher saves
these onto the trusted stack and then zeros all non-argument registers except for CGP and
CSP, which have special handling.
In addition to these steps, the compartment switcher is responsible for preventing the
stack from being used to leak data between compartments (other than via explicit argu-
ments). This requires several steps. First, the stack passed in the CSP register must be
shrunk to allow CHERI’s spatial bounds protection to prevent any access by the callee
to the caller’s portion of the stack. Second, both before a call and before completing the
return transition, the compartment switcher zeroes the portion of the stack that is made
available to the callee. Zeroing the stack seems expensive but recall that in embedded sys-
tems a 2 KiB stack is considered very large. Our stacks are typically 1 KiB. With a 33-bit
memory bus, we need 256 stores (in the worst case) to zero the whole thing. That’s more
expensive than a function call, but not vastly so.
At the end of a compartment transition, the new compartment has access to:
On return, any temporary state is cleared and the caller has access only to explicit
return capabilities.
This does not prevent one compartment from having access to another compartment’s
globals, but there are legitimate reasons for wanting this. For example, a compartment
may derive a read-only (no store permission) capability to one of its globals and use that
to cheaply broadcast state updates to subscribers.
SCR. The register save area and the trusted stack are both reached by the same SCR and
the two switchers (thread and compartment) are the only code in the system that runs with
permission to access this register after the loader has finished.
The context switch routine (part of the switcher’s approximately 300 instructions) is
the only code that is able to violate thread isolation. It has access to two threads simulta-
neously:
Before invoking the scheduler, the switcher will seal the capability to the register save
area (from which the stashed CSP is reachable) and pass it as an argument into the sched-
uler. The scheduler is therefore in the TCB for availability but, crucially, not for confiden-
tiality or integrity.
The scheduler runs with interrupts disabled and selects the next thread to run, returning
a (sealed) capability to the register save area to the switcher. This must be sealed with
the object type that the switcher expects. The loader guarantees that nothing except the
switcher has a permit-seal capability for that type and so the scheduler is able only to
provide register save areas that were previously provided by the loader or the switcher.
The current CHERIoT RTOS scheduler is a very simple priority scheduler that does
round-robin scheduling within a priority level. A more complex one could be added for
use cases that need something more complex without changing the security model. Con-
versely, an even simpler scheduler that exposes a less rich set of inter-thread communica-
tion primitives could be used for safety-critical systems.
The scheduler is a compartment just like any other and so can expose more complex
scheduling operations such as message queues as cross-compartment calls that then ex-
plicitly yield.
RTOS implementation
The CHERIoT RTOS is intended to provide a minimal TCB. The core of the RTOS com-
prises:
A loader, which runs before any untrusted data is encountered and sets up the capabilities
for the rest of the system.
Switch routines, which add up to around 300 instructions in hand-written assembly, for
switching between thread and compartments.
A heap allocator, which allocates memory from a shared heap for use by compartments.
A scheduler, which selects the next thread to run.
Of these, only the loader and the switch routines run with access to the trusted stack
and register save areas (that is, with the access-system-registers permission on their PCC).
This means that these are the only two that can completely compromise all of the security
properties on which the rest of the system is built. The loader runs once on system start
and then erases itself. The loader is not needed on systems where the persistent storage
(e.g. flash) can store tag bits.
The switch routines (currently) add up to a total of around 300 instructions, with no
memory allocation and very little control flow. For comparison, the trusted (unverified)
part of seL4 is 340 instructions [12], so CHERIoT RTOS contains less code in its TCB
than seL4 contains unverified code in its TCB.
The heap allocator holds capabilities to the shared heap and the revocation bitmap for
the shared heap and so is able to violate heap memory safety. The scheduler is more or
less an untrusted compartment, though with a private stack. Importantly, the scheduler
does not have access to the stacks, trusted stacks, or register-save areas of the threads that
it manages. It may choose the next thread to run, but it cannot tamper with a thread’s state.
29
30 CHAPTER 3. RTOS IMPLEMENTATION
Executable capabilities are used for deriving capabilities that will end up installed in
PCC.
Global capabilities do not have permit-store-local and are used for deriving capabilities
for globals, heap memory, and so on.
Local capabilities do not have the global permission and are used only for stacks, trusted
stacks, and register save areas.
Sealing capabilities have no memory-related permissions and are used only for sealing
and unsealing.
Each capability that is derived from a root is derived via a mechanism that validates (at
compile time) that the requested permissions are less than the permissions of the root.
The C++ portion of the loader is stored in a portion of memory that will eventually
become the heap. Once it returns to a small assembly stub, this stub zeroes all of the
memory used by the loader (stack, code, and globals) and almost all of the register file,
ensuring that no capabilities are leaked. It then yields to the scheduler (via an ecall
instruction) and becomes a stackless idle thread.
The loader is responsible for initializing import tables (the capabilities that may point
outside of the compartment, see Chapter 5 for details), preparing each thread’s initial state,
and applying caprelocs (dynamically initialized capabilities stored in globals). For each
capreloc, the loader finds the compartment that it refers to and attempts to derive the target
3.3. INTERRUPT HANDLING 31
from the compartment’s PCC and CGP. If this fails, then the boot image is corrupted and
the loader resets (allowing a first-stage loader to perform A/B installations).
The interrupt handling code is part of the switcher and runs with permission to access
MScratchC. It first saves the register file in the current thread’s register save area and seals
the capability to this area. Next, it loads the other special-register values that describe the
interrupt and prepares a context invoking the scheduler.
The scheduler is always invoked with the same stack, with arguments containing a
sealed capability to the register-save area of the interrupted and yielding thread. It then
returns a sealed capability to a register-save area for a thread to resume. The scheduler
runs with interrupts disabled and provides a simple static priority scheduler with round-
robin scheduling within a priority level. Threads are run when no thread with a higher
priority is runnable. If two threads at the same priority level are runnable, one runs until it
either yields or an interrupt is delivered, then the next one runs.
The scheduler is a compartment and so can expose entry points that can be invoked via the
switcher. These include semaphore and message queue interfaces that will park a thread
(mark it as not runnable) until some event happens.
In addition, an MCall instruction will deliver an interrupt, causing the running thread
to immediately yield. This is used for an explicit yield operation that transfers control
immediately to the switcher and then to the scheduler. This mechanism can be used from
inside the scheduler itself to allow a thread to yield after the scheduler has updated some
data structures related to it.
In combination, these can be used to build synchronization primitives with timeouts.
A thread that wishes to block waiting for an event calls the scheduler, which then records
the conditions that will wake the thread (including the timeout) and yields via an MCall.
One resume, the scheduler can check how long it slept for and return from the cross-
compartment call.
32 CHAPTER 3. RTOS IMPLEMENTATION
talks to a device driver, which is simply another compartment whose import table is used
to grant access to the MMIO region containing the network device’s control registers.
34 CHAPTER 3. RTOS IMPLEMENTATION
Chapter 4
In addition to the existing CHERI C/C++ extensions, we define a small number of addi-
tional extensions that are specific to CHERIoT. CHERI C is already able to compile most
existing embedded code that we have tried with no modifications. Embedded code has
to tolerate targets with Harvard architectures, different pointer sizes for different types of
data, different memory banks, and so on. In comparison, a CHERI target is far more like
a conventional ISA.
We have not had to change the CHERI C model at all for code running within a com-
partment. Our extensions are focused on supporting the compartmentalization model.
35
36 CHAPTER 4. C/C++ LANGUAGE AND TOOLCHAIN EXTENSIONS
This mechanism allows lightweight annotations on functions that are exposed across
compilation units. Software that already supports a DLL-style linkage model may have
macros on public functions for using this. Other software can easily maintain these anno-
tations for CHERI targets with a macro that expands to nothing for non-CHERI targets.
Adding __attribute__((cheri_ccallback)) to a function marks it as a cross-compartment
callback. Taking the address of such a function will give a pointer that can be passed across
compartments and allow the recipient to recursively invoke this compartment.
All functions that are not exposed across compartment boundaries (including library
calls) default to inherit. Cross-compartment calls default to enabled and must be set to
either enabled or disabled.
4.4. LINKING COMPARTMENTS 37
ABI
CHERIoT is a hardware-software co-design project, where the ISA and ABI have been
carefully designed together to provide the desired compartment model and security guar-
antees.
39
40 CHAPTER 5. ABI
Unfortunately, the result of the auipcc instruction may be out of the bounds of PCC.
This does not matter on CHERI systems with 128-bit capabilities because the encoding
guarantees that capabilities remain valid 4096 bytes out of bounds. However, this is not
the case with our 64-bit capability encoding that has much tighter ‘representable bounds’.
The tag bit is cleared if the capability is too far out of bounds, we must therefore modify
the standard CHERI-RISC-V relocation scheme to avoid taking capabilities out of bounds
in the middle of computing an address.
We are able to solve this problem by having at least a 1-bit overlap between the imme-
diate field of the loads and stores (or cincoffset instructions) and the aupicc. The auipcc
instruction and the second instruction must both displace the PCC in the same direction,
keeping the intermediate capability in bounds and hence representable. If the target ad-
dress is after the current instruction then both values must be positive, otherwise both
values must be negative, which is a simple property for the linker to ensure when apply-
ing the relocations. To make this possible we reduce the shift of auipcc by one, meaning
auipcc can always produce a value within the 2 KiB range required.1 This does limit the
maximum offset for a relocation to less than 231 but this is not a problem in practice due to
the limited size of compartments. Any system that needs more than 2 GiB compartments
would likely benefit from a 64-bit address space.
Accesses to read-write globals is very similar. The CGP register is biased by half
the size of the combined globals section (.data, .bss, and so on). This means that the
full immediate range is accessible for displacements. With a 12-bit immediate, a single
compartment can access 4 KiB of globals in a single load or store (or take their address
with a cincoffset instruction). We define a new instruction, auicgp, and a relocation type
that uses it to mirror the PCC-relative addressing mode.
We rely on linker relaxation to optimize both PCC relative and CGP relative relo-
cations. This means that relocations within ±2 KiB of PC or CGP require only one
instruction. Given the security incentive to keep compartments small we expect relaxation
to work well in the common case. In particular, if a compartment has more than 4 KiB
of mutable global state it may be advisable to split it into multiple compartments or use
dynamic allocation.
5.3. EXPORT TABLE LAYOUT 41
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
PCC
header
CGP
error handler offset
1
An alternative solution would be to increase the size of the immediate on loads and stores. On RV32E
this could be achieved using the register selection bits that are freed by moving from 32 to 16 registers. Our
first prototypes did this but we choose to remain compatible with RV32I by modifying auipcc.
42 CHAPTER 5. ABI
Bits Meaning
0-2 Number of argument registers used.
3 Interrupts enabled
4 Interrupts disabled
The compartment switcher is responsible for clearing all registers except for the used
argument registers and so must know how many are used. The compiler fills in this set.
This provides a value from 0 (no arguments) to 7 (all six argument registers used, plus C5
carrying stack arguments.
Exports from compartments must set either the interrupts-enabled or interrupts-disabled
bit. Code running in a different security context always runs with an explicit interrupt
status, to make it easier to reason about compartment behavior. Functions exposed from
shared libraries may set neither, in which case the function will be invoked with the caller’s
interrupt status.
Each export table entry from a compartment is exposed as a symbol of the form __-
export_{compartment name}_{function name}. Each export table entry from a library
is exposed as a symbol of the form __library_export_{library name}_{function
name}. Libraries all use the same name in their export symbols because moving a func-
tion from one library to another does not involve running the target in a different security
context. The existence of multiple libraries is purely to improve auditing: libraries (their
entry points, called functions, and the contents of their code sections) can be individually
tracked, allowing code-signing rules to be driven by specific implementations of individ-
ual libraries. For example, code signing might require a specific FIPS-certified binary of
a crypto library, but allow the shared library providing memcpy to be replaced with a more
optimised version.
The function name in the export symbol is mangled according to the Itanium C++ ABI
rules. This provides some defense against accidental (non-malicious) type mismatches in
the caller and callee.
The loader is responsible for initializing these, based on information provided by the
compiler and linker. Prior to the loader running, import table entries for the first two
categories contain addresses of the corresponding export table entry. Import table entries
for MMIO regions contain the start address and the size of the region. This allows a
compartment to be granted a subset of an MMIO region, down to access to a single byte
(for example, allowing a compartment to poll the ‘ready’ status of a UART but requiring
that it performs a call to the compartment that owns the UART to read or write data with
it). A future version will allow read- or write-only access to MMIO regions.
The loader will populate the import table with capabilities. Each import table entry that
is used for cross-compartment calls will contain a sealed capability that has the bounds of
the target compartment’s export table and whose address points to the correct entry. This
allows the switcher to load the PCC and CGP values from the start and to jump to the
correct address.
The first entry in the import table has the (local) symbol name .compartment_switcher.
It is initialized to 0 at static link time and will be initialized by the loader with a sentry
capability for jumping to the compartment switcher.
ISA has sentries that enable, disable, or inherit the current interrupt status and so cross-
library calls can toggle or preserve the interrupt state. This makes it easy to reason about
the current interrupt state using structured programming idioms.
If a function explicitly changes interrupt state within a compartment then it will be
handled as if it were a library function exported from and consumed by the function. In
this case, the symbol in the export table will be local.
5.7 Callbacks
In some situations, one compartment wishes to provide a callback that another compart-
ment can invoke. In the CHERIoT ABI, this callback is represented as the same form of
sealed capability that would be loaded from the import table. Functions used as cross-
compartment callbacks are both exported and imported by the compartment that wishes to
take their address. Taking the address of such a function is simply a load from the import
table.
As with non-exported functions that change the interrupt status, the symbol in the
export table will be local if the function is not also exported as a directly callable function.
5.8 Relocations
The relocations added to RISC-V for CHERIoT ABI are listed in Table 5.1. As with
existing RISC-V, some of these are in two forms because RISC-V loads and stores place
their immediate operands in different locations. The relocation numbers here are the ones
used in the current prototype and are expected to change prior to standardization.
PCC or CGP relative relocations consist of a pair of either auipcc or auicgp plus a
12-bit immediate instruction. In most cases (when the offset is within ±2 KiB) linker re-
laxation can reduce this to a single instruction. The AUICGP instruction uses an entire major
opcode and is rarely needed because it is uncommon for a compartment to have more than
4 KiB of read-write global data (arguably a large globals section is an indication that a
compartment should be split or refactored). Therefore, in future we could consider alter-
native relocations that don’t require auicgp, such as a three instruction sequence consisting
of lui, addi and cincaddr. This would require more complex linker relaxations to retain
good code size and efficiency and we have not yet attempted it.
5.8. RELOCATIONS 45
Known caveats
There are a small number of known caveats for developers attempting to secure a com-
partment. Future iterations may have compiler mitigations for some of these.
This API takes a structure containing a pointer to a buffer and a length and is expected
to write something via this pointer. If the attacker sets up the outBuffer field to point to
47
48 CHAPTER 6. KNOWN CAVEATS
something on the victim’s stack, then the victim may corrupt its own stack. The victim
must check this. The check_pointer function from cheri.hh validates that pointers do not
do this. This function takes a set of permissions that the capability must have and a size
(optionally: if unspecified it assumes that the pointer must be big enough for one instance
of pointee type) and returns true only if the pointer is not on the current compartment’s
stack, is tagged, unsealed, and has sufficient permissions.
The following snippet is taken from the implementation of one of the scheduler func-
tions.
1 if (!check_pointer<PermissionSet{Permission::Store,
2 Permission::LoadStoreCapability}>(ret))
3 {
4 return -EINVAL;
5 }
This ensures that the void **ret argument can be used to store a capability. If this is
not the case, the function returns early.
Sealing allows pointers to be made unusable by anyone who lacks the matching permit-
unseal capability and unforgeable by anyone who lacks the matching permit-seal
capability.
Deep immutability (the permit-load-mutable permission) provides a mechanism for pass-
ing data structures between compartments that prevents the recipient from mutating
any objects reached via the initial pointer.
Local capabilities (shallowly local, or deeply local capabilities that lack the permit-load-
global permission) provide a mechanism to prevent the callee (including any indirect
callee) from capturing a pointer.
These tools provide a security benefit only if they are used. It is good practice for any
compartment-owned data that is returned to a caller to be sealed. For example, a network
stack returning a connection context should return a sealed capability. As a rule of thumb,
if you are not writing code that is correct in the presence of every possible bit pattern for a
6.3. AVAILABILITY 49
data structure then pointers to that data structure that are shared outside of a compartment
should be sealed.
Any pointer that is const should be marked as deeply immutable. This is not done
automatically because C and C++ both allow the const qualifier to be cast away. Any
pointer-typed function argument pointer that is not expected to be captured by the callee
should be marked as local.
Future versions of the compiler will provide declarative annotations that implicitly
drop these permissions in the caller.
6.3 Availability
In some embedded systems (most control systems), availability is a critical part of the
TCB. In such systems, an attacker who can prevent the system from responding can do
real-world damage. For example, preventing the brake-control system from engaging the
brakes in response to a signal or preventing an emergency cut-out from being delivered
can cause injury or loss of life.
In such systems, the scheduler becomes part of the TCB. The scheduler is not trusted
for confidentiality or integrity and has a limited interface to the rest of the TCB and so can
potentially be replaced by something simpler for these use cases (or something formally
verified to ensure that the highest-priority thread will be scheduled in a timely fashion).
Similarly, any lower-priority thread that can reach a compartment that can invoke an
interrupts-disabled function is part of the TCB for availability. It is important to carefully
audit all such paths to ensure that they will not prevent interrupt delivery for long enough
to violate hard realtime guarantees.
50 CHAPTER 6. KNOWN CAVEATS
Part II
Architecture specification
51
Chapter 7
The CHERIoT ISA extends RV32 [20] with CHERI [22] memory safety features. It is
designed to be a very minimal subset of RISC-V and CHERI that supports strong spatial
and temporal memory safety, and compartmentalization. This is intended to be a concise
description of the architecture and currently assumes some familiarity with both CHERI
and RISC-V.
53
54 CHAPTER 7. THE CHERIOT ISA
as in CHERI-RISC-V. This allows us to drop checks for the alignment of the base of exe-
cutable capabilities as there is no possibility of confusion arising from an unaligned base,
as there is in hybrid mode CHERI-RISC-V.
CJAL and taken branch instructions check the destination address against the bounds
of PCC: if the bounds do not permit at least one 2-byte instruction to be loaded from
the destination then a capability length violation exception is raised on the jump / branch
instruction.
CJALR replaces the JALR instruction and uses capabilities for the target and link register.
It checks that the target capability is tagged, unsealed, executable and the address is in
bounds before it is installed in PCC. If the target is sealed with the reserved ‘sentry’ type
then it is unsealed before jumping to it. The link register, including the current PCC, is
sealed as a sentry.
CSR Read/Write
cycle(h) Read-Only
time(h) Read-Only
instret(h) Read-Only
hmpcounter(h) Read-Only
fflags Read-Write
frm Read-Write
fcsr Read-Write
Table 7.1: CSR allowlist. The accesses shown are the only CSR accesses that are per-
mitted when the installed PCC does not have the P ERMIT _ACCESS _S YSTEM _R EGISTERS
permission bit set.
Special Capability Registers (SCRs) are similar to CSRs in that they affect special func-
tions such as exception delivery, except that they contain capabilities rather than inte-
gers. SCRs are accessed via a new instruction, CSpecialRW, which behaves similarly to the
RISC-V CSRRW instruction. CSpecialRW requires that PCC has P ERMIT _ACCESS _S YSTEM _-
R EGISTERS, otherwise it will raise an exception.
Some SCRs replace existing RISC-V CSRs. Attempting to access the legacy RISC-V
CSR via the CSR* instructions results in a Reserved Instruction exception. Any special
meaning or behavior associated with the CSR applies to the SCR’s address field. For
example, the lower two bits of MTCC.address select the trap mode, and the remaining
bits (including the capability metadata) form the trap base address in the same way as
mtvec. Some RISC-V CSRs have write-any read-legal (WARL) bits that implicitly modify
the written value to restrict the CSR to legal values. This legalization must be applied to the
SCR’s address when reading or writing an SCR. If this results in the capability becoming
unrepresentable then the tag is cleared, as per CSetAddr. If a sealed capability is written to
an SCR with WARL bits then the tag is cleared, even if the bits would be unchanged by
legalization.
Table 7.2 lists the SCRs and their properties: Reset indicates the reset value as one of
the capability roots defined in Section 7.13.
58 CHAPTER 7. THE CHERIOT ISA
cause The cause field reports the capability exception code as described in Table 7.3.
cap idx The cap idx field reports the index of the capability register that caused the last
exception. When the S bit is zero, it is the number of the general purpose register
that caused the capability fault. Otherwise, it is the number of a special purpose
capability register given in Table 7.2 or zero if the fault was caused by PCC.
Value Description
0x00 None
0x01 Bounds Violation
0x02 Tag Violation
0x03 Seal Violation
0x11 P ERMIT _E XECUTE Violation
0x12 P ERMIT _L OAD Violation
0x13 P ERMIT _S TORE Violation
0x15 P ERMIT _S TORE _C APABILITY Violation
0x16 P ERMIT _S TORE _L OCAL _C APABILITY Violation
0x18 P ERMIT _ACCESS _S YSTEM _R EGISTERS Violation
Table 7.3: Capability Exception Codes. All unused codes are reserved.
that this shift is reduced by one compared to the AUIPC as this allows relocations that com-
bine AUIPCC with a 12-bit immediate instruction to always have immediates with matching
signs. This is necessary to ensure any intermediate capabilities created are in-bounds oth-
erwise there is a risk they could be unrepresentable. This does limit the maximum range
of such relocations, but given our compartmentalization model and expected memory lim-
itations this is not a problem in practice.
Additionally, we use major opcode 0x7b to encode AUICGP, which is similar to AUIPCC
except that the immediate is added to capability register $c3 (the global pointer in the
ABI).
EX If P ERMIT _E XECUTE is set then this capability is executable and can be used as the
target of CJALR and in other contexts requiring an executable capability, such as TCC.
60 CHAPTER 7. THE CHERIOT ISA
31 25 24 22 21 18 17 9 8 0
R a reserved bit, which is zero in the root capabilities (and hence all tagged capabilities),
but may be set if untagged data is loaded into a register. In this case its value must be
preserved. This is very important because memory copies are performed with capa-
bility load a store instructions in order to preserve the tag on any capabilities present,
meaning these instructions must also faithfully copy arbitrary untagged data.
p a 6-bit compressed permissions field (see Section 7.13.1)
otype a 3-bit ‘object type’ used for sealing capabilities (see Section 7.13.2)
E a 4-bit exponent used for the bounds encoding (see Section 7.13.3)
B a 9-bit base used for the bounds encoding (see Section 7.13.3)
T a 9-bit top used in the bounds encoding (see Section 7.13.3)
a the 32-bit address of the capability
11 10 9 8 7 6 5 4 3 2 1 0
U0 SE US EX SR MC LD SL LM SD LG GL
Some combinations of permissions are not very useful (e.g. P ERMIT _ACCESS _S YS -
TEM _R EGISTERS but not P ERMIT _E XECUTE ), so permissions are stored in a compressed
format that restricts the available combinations. Figure 7.4 shows the different formats of
the compressed permission field. Each format has some fixed bits (shown as 0s or 1s) that
unambiguously identify the format. A given format unconditionally grants some number
of ‘implicit’ permissions and the non-fixed bits encode the presence or absence of the
permissions indicated by the two-letter abbreviation.
For example the ‘cap-read-write’ format has bits 3 and 4 of the permissions field set to
one. Capabilities with this format implicitly have P ERMIT _L OAD, P ERMIT _L OAD _S TORE _-
C APABILITY and P ERMIT _S TORE while bits 0, 1, 2 and 5 encode P ERMIT _L OAD _G LOBAL,
P ERMIT _L OAD _M UTABLE, P ERMIT _S TORE _L OCAL _C APABILITY and G LOBAL respectively
(the permission is granted if the bit set to one). The logic of this is that each format need
only encode permissions that make sense given the set of implicitly present permissions,
giving a dense encoding of useful permission encodings. The format used to represent a
capability may change if permissions are cleared by CAndPerm or CLC. Figure 7.5 shows a
graphical representation of the possible permissions combinations and possible transitions
between them.
One consequence of this encoding is that is not possible to have a single capability
with all permissions. Instead there are three capability roots corresponding to the three
nodes with no edges leading to them in Figure 7.5. We label these as follows:
⊤M The memory root, with G LOBAL, P ERMIT _L OAD, P ERMIT _S TORE, P ERMIT _L OAD _-
S TORE _C APABILITY, P ERMIT _S TORE _L OCAL _C APABILITY, P ERMIT _L OAD _G LOBAL
and P ERMIT _L OAD _M UTABLE. The bounds are the entire address space.
⊤X The executable root, with G LOBAL, P ERMIT _E XECUTE, P ERMIT _L OAD, P ERMIT _L OAD _-
C APABILITY, P ERMIT _L OAD _G LOBAL, P ERMIT _L OAD _M UTABLE and P ERMIT _AC -
CESS _S YSTEM _R EGISTERS . The bounds are the entire address space.
⊤S the sealing root, with G LOBAL, P ERMIT _S EAL, P ERMIT _U NSEAL, and U SER _P ERM 0.
The bounds are the entire address space even though only a limited set of otype
values can be used with CSeal. This allows sealed, sealing-format capabilities with
an address outside the range of valid otypes to be used as unforgeable tokens by
software.
7.13. CAPABILITY ENCODING 63
5 4 3 2 1 0
On reset the SCRs are initialized to the different capability roots as shown in Table 7.2.
PCC is initialized to ⊤X .
See Appendix B for a description of the constraints on useful permission combinations
that led to the encoding scheme.
mem-cap-rw exe
⊤M ⊤X
LD_LG_LM_MC_SD_SL EX_LD_LG_LM_MC_SR
LM LG SL SR LM LG
SD EX
mem-cap-ro
LG SL LM SL LM LG LD_LG_LM_MC LM LG SR LG SR LM
sealing
⊤S
SE_U0_US LD_MC_SD_SL LD_LG_MC_SD LD_LM_MC_SD LM LG EX_LD_LG_MC EX_LD_LM_MC EX_LD_MC_SR
SD SD EX EX
U0 US SE SL LG LM LD_LG_MC LD_LM_MC LG LM SR
LD MC SD EX
mem-rw
mem-cap-wo
SE US U0 MC LD SD MC
SE US U0 SD LD
SD LD
Figure 7.5: Graph of allowed permission combinations, grouped by encoding format and ordered by inclusion. Edges are labelled with the permission that is
dropped by that transition. Edges implied by transitivity are omitted. G LOBAL is omitted because it is entirely orthogonal.
7.13. CAPABILITY ENCODING 65
1. If the permissions include EX, LD and MC then encode SR, LM and LG using the
executable format.
2. Otherwise, if the permissions include LD, MC and SD then encode SL, LM and LG
using the cap-read-write format.
3. Otherwise, if the permissions include LD and MC then encode LM and LG using
the cap-read-only format.
4. Otherwise, if the permissions include SD and MC then encode using the cap-write-
only format.
5. Otherwise, if the permissions include LD or SD then encode using the data-only
format.
6. Otherwise, encode U0, SE and US using the sealing format.
Any permissions that cannot be encoded using the chosen format are dropped. The pos-
sible clearing of GL and LG or SD and LM during capability loads can be quite easily
performed on the compressed format although note that clearing SD may require switch-
ing format and that SL may be cleared as a side-effect.
Note that this legalization of permissions must happen at all points where permissions
can change (CAndPerm and CLC). For example, the result of CAndPerm followed by CGetPerm
should be consistent regardless of whether the register file stores the permissions in the
compressed or decompressed form. Similarly, storing then loading a capability should not
change the permissions except possibly GL, LG, LM and SD as specified by CLC.
Using otypes: CSeal allows the creation of sealed capabilities with a given value of otype
given a capability to seal and an authorizing capability with P ERMIT _S EAL and the
address set to the desired otype. CUnseal permits unsealing a sealed capability if
provided with a capability with P ERMIT _U NSEAL and bounds that contain the otype
of the capability to be unsealed.
66 CHAPTER 7. THE CHERIOT ISA
Sealed entry capabilities (sentries): Executable capabilities sealed with the special sen-
try otypes can be used with CJALR. The capability is unsealed before jumping to it,
creating a form of call gate. Three kinds of sentry are defined that affect mstatus.
MIE in different ways: either leaving it unchanged, enabling interrupts or disabling
interrupts. Jumping to an interrupt enabling or disabling sentry will set or clear
mstatus.MIE accordingly. Additionally, the link register stored by CJAL and CJALR
is sealed as a sentry with the current interrupt status: if MIE is set it will produce
an interrupt enabling sentry and if it is cleared it will produce an interrupt disabling
sentry.
The otype field uses the following values:
0 unsealed
1 sealed as sentry
2 sealed as interrupt disabling sentry
3 sealed as interrupt enabling sentry
4-5 reserved for use as return sentries in future
6-7 executable capability sealed with given otype
8 reserved (due to encoding)
9-15 non-executable capability sealed with given otype
The otypes 1 − 7 can only be applied to executable capabilities, while memory and sealing
format capabilities can only be sealed with otypes 9 − 15. If the otype field of a memory
or sealing format capability is non-zero then bit 3 is implicitly set i.e. otypes 9 − 15 are
encoded using values 1 − 7. An attempt to use CSeal or CUnseal with a reserved otype, or
with an otype not applicable to the capability format, will clear the capability tag.
where the top bits of the address are ‘corrected’ according to the following formulae:
−1, if amid < B and T ≥ B
(
−1, if amid < B
cb = ct = 1, if amid ≥ B and T < B
0, otherwise
otherwise
0,
These corrections ensure that the decoded bounds remain the same provided the ad-
dress is in [b, b + 2e+9 ), the so-called representable range. They work by testing for con-
ditions that indicate whether the top and address are in the same 2e+9 aligned region as
base. The representable range spans two such regions (one if B = 0), which we will call
the lower and upper regions, with b always lying in the lower region. The ISA is con-
structed to ensure that, for valid capabilities, a and t are in the representable range and
b ≤ t. Therefore if amid < B then a must be in the upper region, where the atop bits are
one greater than those bits in b. Similarly if T < B then t must lie in the upper region and
we can compute the necessary correction based on whether a also lies in the upper region.
To maintain the necessary invariant for this to work CSetAddr and CIncAddr clear the tag if
the address to goes outside the representable range (see Section 7.13.5).
In order to permit the format to represent a range covering the entire address space
using only a 4-bit exponent there is a special case when E has its maximum value. The
effective exponent, e, is defined as:
(
24, if E = 15
e =
E, otherwise
Thus the root memory capability has B = 0, T = 0x100, E = 15 and decodes to the
range [0, 232 ). Note that the decoded top is actually a 33-bit value to accommodate this.
Table 7.4: Capability bounds alignment and maximum length by exponent value. Note
that for e = 24 the maximum length exceeds the size of the address space. The length of
the root capabilities is 232 = 4, 294, 967, 296 so no valid capability will ever exceed this
length.
7.13. CAPABILITY ENCODING 69
to the inequality:
l ≤ 2e × (29 − 1)
l ≤ 2e+9 − 2e
l ≤ 2e+9 approximation
log2 l ≤ e+9
⌊log2 l⌋ ≤ e+9 approximation
msb(l) ≤ e+9 index of most significant set bit is floor of log2
32 − clz(l) ≤ e+9 also expressible as count leading zeros for 32-bit length
23 − clz(l) ≤ e
e = 23 − clz(l) since we require the smallest e
Since e must be greater than or equal to zero the count leading zeros should be limited
to the top 23 bits of l (lengths smaller than 9-bits are expressed with e = 0). Since
the exponent is limited to 4-bits, exponents greater than 14 are mapped to the special
maximum exponent, 24, which is encoded as 15:
(
e, if e ≤ 14
e′ =
24, otherwise
Having chosen the exponent, the relevant bits of base and top, t = b + l, are extracted:
B = b[e′ + 9 : e′ ] T = t[e′ + 9 : e′ ]
The bounds are exact if the bits below e′ in both b and t are all zero. By discarding the
lower bits of b the base is automatically rounded down to a representable value, but if the
top is not exact then we must round it up by incrementing by one to ensure the encoded
range includes the requested top. Note that the calculated e′ was based on the requested
length, but having rounded the bounds the resulting length may be larger and may exceed
the maximum representable length for e′ . To check for this we calculate the encoded length
T − B (in units of 2e ), and compare it with the maximum encodable length, 29 − 1. Note
that B and T above are one bit wider than the encoding can store for this purpose. If the
maximum encodable length is exceeded we increment e by one and recompute B and T ,
this time with a guarantee that the resulting length is encodable. Finally, the oversized B
and T can drop their extra most significant bit in the final encoding.
for which the decoded bounds remain the same. We also wish to maintain the mono-
tonicity invariant that the bounds of a valid capability must be a subset of the bounds of
the valid capability from which it is derived. Therefore, whenever the address of a ca-
pability changes the hardware must check whether the new address remains within the
representable range, otherwise the new bounds would violate this invariant. For example,
if CSetAddr or CIncAddr detects that the new address is outside of the representable range
then the tag of the result is cleared.
The representation guarantees that the bounds remain decodable provided the address,
a, and base, b, satisfy b ≤ a and a < b + 2e+9 . Additionally, if e is 24 then all addresses
are representable. The representable range always includes top, although in some cases
it is the highest representable address. Therefore the representation meets the C-language
requirement that pointers may range within object bounds or ‘one byte past the end’. Other
CHERI implementations include much larger representable ranges than this minimum in
order to accommodate common C programming practices. However, this comes at the cost
of bits in the representation and our experience so far has shown that it is not necessary for
embedded systems.
The following instructions all set the capability address and therefore require a repre-
sentability check:
• AUIPCC
• AUICGP
• CSetAddr
• CIncAddr
Note that although CJAL and CJALR also set the address on the link register, it is guaranteed
to be representable because its address can be at most equal to PCC.top given that the
jump itself is in bounds. Therefore no representability check is required for these instruc-
tions.
Similarly, the value placed in MEPCC on exception should always be representable
given that PC is always in bounds (or equal to PCC.top in the case of stepping off the
end of PCC). One exception to this is if MTCC is configured in vectored mode and a
subsequent exception goes to a PC that is out of the bounds of MTCC. This would cause
a PCC bounds exception and in this case MEPCC might not be representable, in which
case its tag should be cleared. It may be preferable not to support vectored mode, although
note that care should also be taken when legalizing mtvec (MTCC.address) to ensure that
this does not violate sealing or representability. legalization of mepc (clearing the least
significant bit) may also cause values read from MEPCC to be unrepresentable if it has
been written with an unaligned address. This includes the implicit read by MRET. In these
cases the unrepresentable MEPCC that results from the PCC bounds exception should
7.13. CAPABILITY ENCODING 71
The NULL capability is defined as an untagged capability with an address of zero and an
encoding of all zeroes. This definition is for maximum compatibility with the C language,
where it is used to represent NULL pointers. The NULL capability is also used as the
value of the $c0 register and to store integer results by setting the address to the required
value. Although capability fields other than the address are not meaningful on untagged
capabilities they may be queried using the CGetX instructions. Thus it can be observed that
the NULL capability decodes as an untagged, unsealed capability, with no permissions,
base 0 and length 01 . Note that NULL-derived capabilities with a non-zero address may
have non-zero base and top, but will be untagged.
The capability encoding described supports capabilities with zero length, where base is
equal to top. Such capabilities do not authorize access to any memory (or sealing rights),
so it may be tempting to use them as unforgeable tokens (e.g. to implement file handles),
however they come with a big drawback: zero length capabilities can be derived with base
equal to the top of an existing capability, even though that capability does not authorize
access to top. To give an example of this suppose a memory allocator gives out two ca-
pabilities with adjacent ranges [a, b) and [b, c). Later it may receive a call to ‘free’ with a
zero length capability [b, b) and it has no way to tell which of the two ranges it was derived
from. If it relies only on the base of the capability and does not validate the length the
allocator may incorrectly free [b, c). The same problem arises during revocation sweeps
as performed by Cornucopia [9], meaning it is unable to revoke zero length capabilities.
Therefore we strongly discourage the use of zero length capabilities and encourage vali-
dating the length of untrusted capabilities. As an alternative we suggest using capabilities
of length one derived from the sealing root but without P ERMIT _S EAL or P ERMIT _U NSEAL.
In this case U SER _P ERM 0 may be used as a software defined permission.
1
On other CHERI architectures the NULL capability is defined to have maximum length. This could be
achieved by tweaking the encoding (e.g. by inverting the encoded exponent and making the zero value a
special case), but there is no clear advantage to doing this.
72 CHAPTER 7. THE CHERIOT ISA
Implicit stack pointer arithmetic instructions are not useful with CHERI, as adding
an offset with ADD will produce an untagged integer. These instructions are modified to
decode to CIncAddr to produce valid stack-derived capabilities. As a result, C.ADDI16SP
imm is decoded into CIncAddr $csp, $csp, imm and C.ADDI4SPN $rd, imm into CIncAddr
$cd, $csp, imm.
The CHERIoT ISA introduces changes to mappings between certain compressed and
uncompressed instructions, but no changes to the encodings of compressed instructions
themselves. This translates to minimum logic modifications when adding CHERIoT ISA
support to an existing RISC-V CPU. However, experiments in Appendix D show that
further code size reduction can be achieved by introducing changes in the encoding them-
selves, to accommodate RV32E and CHERI instructions.
74 CHAPTER 7. THE CHERIOT ISA
Chapter 8
75
76 CHAPTER 8. INSTRUCTION ENCODING SUMMARY
The standard RV32 load and store instructions are modified to take a capability as the base
address:
31 25 24 20 19 15 14 12 11 7 6 0
The RV64 instructions LD and SD are reused to behave as load capability (LC) and store
capability (SC) respectively:
31 25 24 20 19 15 14 12 11 7 6 0
imm[11:5] cs2 rs1 0x3 imm[4:0] 0x23 CSC cs2, rs1, imm (RV32)
($cgp). This required allocating a new major opcode, although we expect that further sup-
port for linker relaxation may remove the need for AUICGP.
31 12 11 7 6 0
The CHERIoT ISA shares encodings with CHERI-RISC-V. The general-purpose instruc-
tions use the 0x5b major opcode and use the RISC-V R-type or I-type encoding formats.
CHERI-RISC-V uses the funct3 field from bits 14-12 as a top-level opcode, and funct7
as a secondary opcode for standard 3-register operand instructions. Two-register operand
instructions and single-register operand instructions are a subset of the 3-register operand
encodings.
00 01 10 11
0 Two Source & Dest CIncAddrImm CSetBoundsImm -
1 - - - -
00 01 10 11
00000 - CSpecialRW - -
00001 - - - -
00010 CSetBounds CSetBoundsExact - CSeal
00011 CUnseal CAndPerm - -
00100 CSetAddr CIncAddr - -
00101 CSub - CSetHigh -
00110 - - - -
00111 - - - -
01000 CTestSubset CSEQX - -
01001 - - - -
01010 - - - -
01011 - - - -
01100 - - - -
01101 - - - -
01110 - - - -
01111 - - - -
10000 - - - -
10001 - - - -
10010 - - - -
10011 - - - -
10100 - - - -
10101 - - - -
10110 - - - -
10111 - - - -
11000 - - - -
11001 - - - -
11010 - - - -
11011 - - - -
11100 - - - -
11101 - - - -
11110 - - - -
11111 Stores Loads Two Source Source & Dest
†
Reserved for future use.
80 CHAPTER 8. INSTRUCTION ENCODING SUMMARY
00 01 10 11
000 - - - -
001 - - - -
010 - - - -
011 - - - -
100 - - - -
101 - - - -
110 - - - -
111 - - - One Source
†
Reserved for future use.
00 01 10 11
000 - - - -
001 - - - -
010 - - - -
011 - - - -
100 - - - -
101 - - - -
110 - - - -
111 - - - -
†
Reserved for future use.
8.3. ENCODING SUMMARY 81
00 01 10 11
000 CGetPerm CGetType CGetBase CGetLen
001 CGetTag - - -
010 CRRL CRAM CMove CClearTag
011 - - - CGetAddr
100 - - - -
101 - - - CGetHigh
110 CGetTop - - -
111 - - - Dest-Only
†
Reserved for future use.
00 01 10 11
000 - - - -
001 - - - -
010 - - - -
011 - - - -
100 - - - -
101 - - - -
110 - - - -
111 - - - -
82 CHAPTER 8. INSTRUCTION ENCODING SUMMARY
Chapter 9
Instruction reference
In this chapter, we specify each instruction via both informal descriptions and code in
the Sail language. To allow for more succinct code descriptions, we rely on a number of
common function definitions and constants also described in this chapter.
• intSail integers are of arbitrary precision (therefore there are no overflows) but
can be constrained using simple first-order constraints. As a common case integer
range types can be defined using range(a,b) to indicate an integer in the range a to
b inclusive. Operations on integers respect the constraints on their operands so, for
example, if x and y have type range(a, b) then x + y has type range(a + a, b + b).
Integer literals are written in decimal.
• bits(n) is a bit vector of length n. Vectors are indexed using square bracket notation
with index 0 being the least significant bit. Arithmetic and logical operations on
vectors are defined on two vectors of equal length producing a result of the same
83
84 CHAPTER 9. INSTRUCTION REFERENCE
The following operators and expression syntax are used in the Sail code:
• Boolean operators: not, | (logical OR), & (logical AND), ^ (exclusive OR)
• Integer operators: + (addition), - (subtraction), * (multiplication), % (modulo)
Sail operations on integers are the usual mathematical operators. Note a % b is the
modulo operator that, for b > 0 returns a value in the range 0 to b − 1 regardless
of the sign of a. Although Sail integers are notionally infinite in range, CHERI
instructions can be implemented with finite arithmetic.
• Bit vector operators: & (bitwise AND), <_s (signed less than), @ (bit vector concate-
nation)
• Equality: == (equal), != (not equal)
• Vector slice:
v[a..b]
Creates a sub-range of a vector from index a down to b inclusive.
• Local variables:
mutable_var = exp;
let immutable_var = exp;
Mutable variables are introduced by simply assigning to them (optionally prefixed
with keyword var). An explicit type may be given following a colon, but types can
usually be inferred. Sail supports mutable or immutable variables where immutable
ones are introduced by let and assigned only once when created.
• Functional if:
if cond then exp1 else exp2
May return a value, similar to C ternary operator.
9.2. CONSTANT DEFINITIONS 85
• Foreach loop:
foreach(i from start_exp to end_exp) {
body
};
• Function invocation:
func_id (arg1, arg2)
• Field selection from struct:
struct_val.field
Returns the value of the given field from structure.
• Functional update of structure:
{struct_val with field=exp}
A copy of the structure with the named field replaced with another value.
truncate : forall ’m ’n, (’m >= 0 & ’m <= ’n). (bits(’n), int(’m)) -> bits(’m)
truncate(v, n) truncates v, keeping only the least significant n bits.
truncateLSB : forall ’m ’n, (’m >= 0 & ’m <= ’n). (bits(’n), int(’m)) -> bits(’
m)
truncateLSB(v, n) truncates v, keeping only the most significant n bits.
pow2 : forall ’n. int(’n) -> int(2 ^ ’n)
align_down : forall ’n ’m, (’n >= 1 & ’m > ’n). (int(’n), bits(’m)) -> bits(’m)
align_down(n, bv) returns the given bit vector, bv, aligned down to a power of two by
clearing the least significant n bits.
EXTZ
9.3. FUNCTION DEFINITIONS 87
Adds zeros in most significant bits of vector to obtain a vector of desired length.
EXTS
Extends the most significant bits of vector preserving the sign bit.
zeros
Produces a bit vector of all zeros
ones
Produces a bit vector of all ones
The non-exception case should be followed by a call to mem_write_cap with the value to
be written. The write is split between address announcement and value write in order
to permit certain relaxed memory ordering behaviours when integrated with the RMEM
concurrency tool, but the address annoucement may be ignored in sequential execution.
mem_write_cap : (xlenbits, Capability, bool, bool, bool) -> MemoryOpResult(bool
)
mem write_cap(addr, cap, aq, rl,
_ res) writes the Capability value cap (including tag) to the
cap_size aligned address, addr. It returns either MemValue(bool), indicating success (with
store-conditional result) or MemException(e) in case of exception e. aq, rl and res are flags
controlling relaxed memory ordering and atomic accesses (not used on single-core MCU).
Note that the capability format can encode top of 232 , meaning the entire 32-bit address
space can be addressed.
getCapBounds : Capability -> (CapAddrInt, CapLen)
AUICGP
Format
AUICGP cd, imm
31 12 11 7 6 0
imm[31:12] cd 0x7b
Description
Capability register cd is replaced with the contents of CGP (c3), with the address replaced
with CGP.address + imm × 2048.
Semantics
let off : xlenbits = EXTS(imm) << 11;
let cgp_val = C(CGP_IDX); /* $c3 */
let (representable, newCap) = incCapAddr(cgp_val, off);
C(cd) = clearTagIf(newCap, isCapSealed(cgp_val) | not(representable));
RETIRE_SUCCESS
92 CHAPTER 9. INSTRUCTION REFERENCE
AUIPCC
Format
AUIPCC cd, imm
31 12 11 7 6 0
imm[31:12] cd 0x17
Description
Capability register cd is replaced with the contents of PCC, with the address replaced
with PCC.address + imm × 2048.
Semantics
let off : xlenbits = EXTS(imm) << 11;
let (representable, newCap) = setCapAddr(PCC, PC + off);
C(cd) = clearTagIf(newCap, not(representable));
RETIRE_SUCCESS
9.4. CHERIOT INSTRUCTIONS 93
CAndPerm
Format
CAndPerm cd, cs1, rs2
31 25 24 20 19 15 14 12 11 7 6 0
Description
Capability register cd is replaced with the contents of capability register cs1 with the
perms field set to the bitwise AND of its previous value and bits 0 to cap_perms_width
-1 of integer register rs2. If the resulting set of permissions cannot be represented by
the capability encoding then the result will have a (possibly empty) subset of the ANDed
permissions. If cs1 was sealed then cd.tag is cleared.
Semantics
let cs1_val = C(cs1);
let rs2_val = X(rs2);
C(cd) = newCap;
RETIRE_SUCCESS
94 CHAPTER 9. INSTRUCTION REFERENCE
CClearTag
Format
CClearTag cd, cs1
31 25 24 20 19 15 14 12 11 7 6 0
Description
Capability register cd is replaced with the contents of cs1, with the tag field cleared.
Semantics
let cs1_val = C(cs1);
C(cd) = clearTag(cs1_val);
RETIRE_SUCCESS
9.4. CHERIOT INSTRUCTIONS 95
CGetAddr
Format
CGetAddr rd, cs1
31 25 24 20 19 15 14 12 11 7 6 0
Description
Integer register rd is set equal to the address field of capability register cs1.
Semantics
let capVal = C(cs1);
X(rd) = capVal.address;
RETIRE_SUCCESS
96 CHAPTER 9. INSTRUCTION REFERENCE
CGetBase
Format
CGetBase rd, cs1
31 25 24 20 19 15 14 12 11 7 6 0
Description
Integer register rd is set equal to the base field of capability register cs1.
Semantics
let capVal = C(cs1);
X(rd) = getCapBaseBits(capVal);
RETIRE_SUCCESS
9.4. CHERIOT INSTRUCTIONS 97
CGetHigh
Format
CGetHigh rd, cs1
31 25 24 20 19 15 14 12 11 7 6 0
Description
Integer register rd is set equal to the high half of capability register cs1.
The bits returned here are of the in-memory form of the capability, which may differ from
microarchitectural forms in use within implementations. That is, applying CGetHigh to a
capability loaded from address m will yield the same result as loading the high half of the
capability-sized granule at m (that is, bits above XLEN when a capability is interpreted as
a twice-XLEN-bit integer).
Semantics
let capVal : Capability = C(cs1);
X(rd) = capToBits(capVal)[sizeof(xlen) * 2 - 1 .. sizeof(xlen)];
RETIRE_SUCCESS
98 CHAPTER 9. INSTRUCTION REFERENCE
CGetLen
Format
CGetLen rd, cs1
31 25 24 20 19 15 14 12 11 7 6 0
Description
Integer register rd is set equal to the length field of capability register cs1.
Semantics
let capVal = C(cs1);
let len = getCapLength(capVal);
X(rd) = to_bits(sizeof(xlen), if len > cap_max_addr then cap_max_addr else len)
;
RETIRE_SUCCESS
Notes
• Due to the compressed representation of capabilities, the actual length of capabilities
can be 2xlen ; CGetLen will return the maximum value of 2xlen − 1 in this case.
9.4. CHERIOT INSTRUCTIONS 99
CGetPerm
Format
CGetPerm rd, cs1
31 25 24 20 19 15 14 12 11 7 6 0
Description
The least significant bits of integer register rd are set equal to the perms field of capability
register cs1. The other bits of rd are set to zero.
Semantics
let capVal = C(cs1);
X(rd) = EXTZ(getCapPerms(capVal));
RETIRE_SUCCESS
100 CHAPTER 9. INSTRUCTION REFERENCE
CGetTag
Format
CGetTag rd, cs1
31 25 24 20 19 15 14 12 11 7 6 0
Description
The low bit of integer register rd is set to the tag field of cs1. All other bits of rd are
cleared.
Semantics
let capVal = C(cs1);
X(rd) = EXTZ(bool_to_bits(capVal.tag));
RETIRE_SUCCESS
9.4. CHERIOT INSTRUCTIONS 101
CGetTop
Format
CGetTop rd, cs1
31 25 24 20 19 15 14 12 11 7 6 0
Description
Integer register rd is set equal to the top field of capability register cs1.
Semantics
let capVal = C(cs1);
let top = getCapTop(capVal);
X(rd) = to_bits(sizeof(xlen), if top > cap_max_addr then cap_max_addr else top)
;
RETIRE_SUCCESS
Notes
• Due to the compressed representation of capabilities, the actual top of capabilities
can be 2xlen ; CGetTop will return the maximum value of 2xlen − 1 in this case.
102 CHAPTER 9. INSTRUCTION REFERENCE
CGetType
Format
CGetType rd, cs1
31 25 24 20 19 15 14 12 11 7 6 0
Description
Integer register rd is set equal to the otype field of capability register cs1.
Semantics
let capVal = C(cs1);
X(rd) = EXTZ(capVal.otype);
RETIRE_SUCCESS
9.4. CHERIOT INSTRUCTIONS 103
CIncAddr
Format
CIncAddr cd, cs1, rs2
31 25 24 20 19 15 14 12 11 7 6 0
Description
Capability register cd is set equal to capability register cs1 with its address replaced with
cs1.address + rs2. If the resulting capability cannot be represented exactly, or if cs1
was sealed, then cd.tag is cleared. The remaining capability fields are set to what the
in-memory representation of cs1 with the address set to cs1.address + rs2 decodes to.
Semantics
let cs1_val = C(cs1);
let rs2_val = X(rs2);
CIncAddrImm
Format
CIncAddrImm cd, cs1, imm
31 20 19 15 14 12 11 7 6 0
Description
Capability register cd is set equal to capability register cs1 with its address replaced with
cs1.address + imm. If the resulting capability cannot be represented exactly, or if cs1
was sealed, then cd.tag is cleared. The remaining capability fields are set to what the
in-memory representation of cs1 with the address set to cs1.address + imm decodes to.
Semantics
let cs1_val = C(cs1);
let immBits : xlenbits = EXTS(imm);
CJAL
Format
CJAL cd, imm
31 30 21 20 19 12 11 7 6 0
i[20]
i[11]
imm[10:1] imm[19:12] cd 0x6f
Description
Capability register cd is replaced with the next instruction’s PCC and sealed as a sentry.
PCC.address is incremented by imm.
Semantics
let off : xlenbits = EXTS(imm);
let newPC = PC + off;
if not(inCapBounds(PCC, newPC, min_instruction_bytes())) then {
handle_cheri_cap_exception(CapEx_BoundsViolation, PCC_IDX);
RETIRE_FAIL
} else if newPC[1] == bitone & ~(haveRVC()) then {
handle_mem_exception(newPC, E_Fetch_Addr_Align());
RETIRE_FAIL
} else {
let (success, linkCap) = setCapAddr(PCC, nextPC); /* Note that nextPC
accounts for compressed instructions */
assert(success, "Link cap should always be representable.");
assert(not (isCapSealed(linkCap)), "Link cap should always be unsealed");
let sentry_type = if mstatus.MIE() == 0b1 then otype_sentry_ie else
otype_sentry_id;
C(cd) = sealCap(linkCap, to_bits(cap_otype_width, sentry_type));
nextPC = newPC;
RETIRE_SUCCESS
}
Exceptions
An exception is raised if:
CJALR
Format
CJALR cd, cs1, imm
31 20 19 15 14 12 11 7 6 0
Description
Capability register cd is replaced with the next instruction’s PCC and sealed as a sentry.
PCC is replaced with the value of capability register cs1 with its address incremented by
imm and the 0th bit of its address set to 0, and is unsealed if it is a sentry.
Semantics
let cs1_val = C(cs1);
let off : xlenbits = EXTS(imm);
let newPC = [cs1_val.address + off with 0 = bitzero]; /* clear bit zero as for
RISCV JALR */
if not (cs1_val.tag) then {
handle_cheri_reg_exception(CapEx_TagViolation, cs1);
RETIRE_FAIL
} else if isCapSealed(cs1_val) & (not(isCapSentry(cs1_val)) | imm != zeros())
then {
handle_cheri_reg_exception(CapEx_SealViolation, cs1);
RETIRE_FAIL
} else if not (cs1_val.permit_execute) then {
handle_cheri_reg_exception(CapEx_PermitExecuteViolation, cs1);
RETIRE_FAIL
} else if not(inCapBounds(cs1_val, newPC, min_instruction_bytes())) then {
handle_cheri_reg_exception(CapEx_BoundsViolation, cs1);
RETIRE_FAIL
} else if newPC[1] == bitone & ~(haveRVC()) then {
handle_mem_exception(newPC, E_Fetch_Addr_Align());
RETIRE_FAIL
} else {
let (success, linkCap) = setCapAddr(PCC, nextPC); /* Note that nextPC
accounts for compressed instructions */
assert(success, "Link cap should always be representable.");
108 CHAPTER 9. INSTRUCTION REFERENCE
Exceptions
An exception is raised if:
CLC
Format
CLC cd, cs1, imm
31 20 19 15 14 12 11 7 6 0
Description
Capability register cd is replaced with the capability located in memory at cs1.address +
imm, and if cs1.perms does not grant P ERMIT _L OAD _C APABILITY then cd.tag is cleared.
Semantics
let offset : xlenbits = EXTS(imm);
let auth_val = C(cs1);
let vaddrBits = auth_val.address + offset;
if not(auth_val.tag) then {
handle_cheri_reg_exception(CapEx_TagViolation, cs1);
RETIRE_FAIL
} else if isCapSealed(auth_val) then {
handle_cheri_reg_exception(CapEx_SealViolation, cs1);
RETIRE_FAIL
} else if not (auth_val.permit_load) then {
handle_cheri_reg_exception(CapEx_PermitLoadViolation, cs1);
RETIRE_FAIL
} else if not(inCapBounds(auth_val, vaddrBits, cap_size)) then {
handle_cheri_reg_exception(CapEx_BoundsViolation, cs1);
RETIRE_FAIL
} else if not(is_aligned_addr(vaddrBits, cap_size)) then {
handle_mem_exception(vaddrBits, E_Load_Addr_Align());
RETIRE_FAIL
} else match translateAddr(vaddrBits, Read(Cap)) {
TR_Failure(E_Extension(_), _) => { internal_error("unexpected cheri exception
for cap load") },
TR Failure(e, _) => { handle_mem_exception(vaddrBits, e); RETIRE_FAIL },
_
TR_Address(addr, ptw_info) => {
let c = mem_read_cap(addr, false, false, false);
match c {
110 CHAPTER 9. INSTRUCTION REFERENCE
MemValue(v) => {
var cr = v;
if cr.tag & not(auth_val.permit_load_global) then {
cr.global = false;
cr.permit_load_global = false;
};
if cr.tag & not(auth_val.permit_load_mutable) & not(isCapSealed(cr))
then {
cr.permit_store = false;
cr.permit_load_mutable = false;
};
if ptw_info.ptw_lc == PTW_LC_CLEAR | not(auth_val.
permit_load_store_cap) then {
cr.tag = false;
};
/* Sealing capabilities are excluded from revocation */
let isSealingCap = cr.permit_seal | cr.permit_unseal | cr.perm_user0;
if (cr.tag & not(isSealingCap)) then {
let base = getCapBaseBits(cr);
let granule_addr = align_down(log2_revocation_granule_size, base);
let revoked = mem_read_cap_revoked(granule_addr);
cr.tag = cr.tag & not(revoked);
};
C(cd) = cr;
RETIRE_SUCCESS
},
MemException(e) => {handle_mem_exception(vaddrBits, e); RETIRE_FAIL }
}
}
}
Exceptions
An exception is raised if:
CMove
Format
CMove cd, cs1
31 25 24 20 19 15 14 12 11 7 6 0
Description
Capability register cd is replaced with the contents of cs1.
Semantics
C(cd) = C(cs1);
RETIRE_SUCCESS
9.4. CHERIOT INSTRUCTIONS 113
CRepresentableAlignmentMask
Format
CRepresentableAlignmentMask rd, rs1
31 25 24 20 19 15 14 12 11 7 6 0
Description
Integer register rd is set to a mask that can be used to round addresses down to to a value
that is sufficiently aligned to set exact bounds for the nearest representable length of rs1
(as obtained by CRRL).
Semantics
let len = X(rs1);
X(rd) = getRepresentableAlignmentMask(len);
RETIRE_SUCCESS
114 CHAPTER 9. INSTRUCTION REFERENCE
CRoundRepresentableLength
Format
CRoundRepresentableLength rd, rs1
31 25 24 20 19 15 14 12 11 7 6 0
Description
Integer register rd is set to the smallest value greater or equal to rs1 that can be used as
a length to set exact bounds on a capability that has a suitably aligned base (as obtained
with the help of CRAM). Note that this could round up the length to 2xlen in which case rd
will be set to zero. Users should be careful to account for this case.
Semantics
let len = X(rs1);
X(rd) = getRepresentableLength(len);
RETIRE_SUCCESS
9.4. CHERIOT INSTRUCTIONS 115
CSC
Format
CSC cs2, cs1, imm
31 25 24 20 19 15 14 12 11 7 6 0
Description
The capability located in memory at cs1.address + imm is replaced with capability register
cs2.
Semantics
let offset : xlenbits = EXTS(imm);
let auth_val = C(cs1);
let vaddrBits = auth_val.address + offset;
let cs2_val = C(cs2);
if not(auth_val.tag) then {
handle_cheri_reg_exception(CapEx_TagViolation, cs1);
RETIRE_FAIL
} else if isCapSealed(auth_val) then {
handle_cheri_reg_exception(CapEx_SealViolation, cs1);
RETIRE_FAIL
} else if not (auth_val.permit_store) then {
handle_cheri_reg_exception(CapEx_PermitStoreViolation, cs1);
RETIRE_FAIL
} else if not (auth_val.permit_load_store_cap) & cs2_val.tag then {
handle_cheri_reg_exception(CapEx_PermitStoreCapViolation, cs1);
RETIRE_FAIL
} else if not (auth_val.permit_store_local_cap) & cs2_val.tag & not(cs2_val.
global) then {
handle_cheri_reg_exception(CapEx_PermitStoreLocalCapViolation, cs1);
RETIRE_FAIL
} else if not(inCapBounds(auth_val, vaddrBits, cap_size)) then {
handle_cheri_reg_exception(CapEx_BoundsViolation, cs1);
RETIRE_FAIL
} else if not(is_aligned_addr(vaddrBits, cap_size)) then {
handle_mem_exception(vaddrBits, E_SAMO_Addr_Align());
116 CHAPTER 9. INSTRUCTION REFERENCE
RETIRE_FAIL
} else match translateAddr(vaddrBits, Write(if cs2_val.tag then Cap else Data))
{
TR_Failure(e, _) => { handle_mem_exception(vaddrBits, e); RETIRE_FAIL },
TR_Address(addr, _) => {
let eares : MemoryOpResult(unit) = mem_write_ea_cap(addr, false, false,
false);
match (eares) {
MemException(e) => { handle_mem_exception(vaddrBits, e); RETIRE_FAIL },
MemValue(_) => {
let res : MemoryOpResult(bool) = mem_write_cap(addr, cs2_val, false,
false, false);
match (res) {
MemValue(true) => RETIRE_SUCCESS,
MemValue(false) => internal_error("store got false from
mem_write_value"),
MemException(e) => { handle_mem_exception(vaddrBits, e); RETIRE_FAIL
}
}
}
}
}
}
Exceptions
An exception is raised if:
CSeal
Format
CSeal cd, cs1, cs2
31 25 24 20 19 15 14 12 11 7 6 0
Description
Capability register cd is replaced with capability register cs1, and is sealed with otype
equal to the address field of capability register cs2. If cs2 is unable to authorize the
sealing, or if cs1 was already sealed, then the tag field of cd is cleared.
Semantics
let cs1_val = C(cs1);
let cs2_val = C(cs2);
CSetAddr
Format
CSetAddr cd, cs1, rs2
31 25 24 20 19 15 14 12 11 7 6 0
Description
Capability register cd is set equal to capability register cs1 with its address replaced with
rs2. If the resulting capability cannot be represented exactly, or if cs1 was sealed, then
cd.tag is cleared. The remaining capability fields are set to what the in-memory represen-
tation of cs1 with the address set to rs2 decodes to.
Semantics
let cs1_val = C(cs1);
let rs2_val = X(rs2);
CSetBounds
Format
CSetBounds cd, cs1, rs2
31 25 24 20 19 15 14 12 11 7 6 0
Description
Capability register cd is set to capability register cs1 with its base field replaced with
cs1.address and its length field replaced with integer register rs2. If the resulting capa-
bility cannot be represented exactly the base will be rounded down and the length will be
rounded up by the smallest amount needed to form a representable capability covering the
requested bounds. The tag field of the result is cleared if the bounds of the result exceed
the bounds of cs1, or if cs1 was sealed.
Semantics
let cs1_val = C(cs1);
let rs2_val = X(rs2);
Notes
• This Sail code actually does the bounds check on the requested bounds, not the
bounds that result from setCapBounds. This is an important distinction because the
resulting bounds may be larger than the requested bounds, which could potentially
lead to non-monotonic behaviour. However, providing that setCapBounds always
returns the most precise encodable bounds it is safe to do the check on the requested
9.4. CHERIOT INSTRUCTIONS 121
bounds because, in the worst case, it would return the existing bounds. This is
desirable because in hardware the bounds checking can be performed in parallel
with computing the new bounds.
122 CHAPTER 9. INSTRUCTION REFERENCE
CSetBoundsExact
Format
CSetBoundsExact cd, cs1, rs2
31 25 24 20 19 15 14 12 11 7 6 0
Description
Capability register cd is set to capability register cs1 with its base field replaced with
cs1.address and its length field replaced with integer register rs2. If the resulting capa-
bility cannot be represented exactly, the tag field will be cleared (unlike CSetBounds), the
base will be rounded down and the length will be rounded up by the smallest amount
needed to form a representable capability covering the requested bounds. The tag field
of the result is cleared if the bounds of the result exceed the bounds of cs1, or if cs1 was
sealed.
Semantics
let cs1_val = C(cs1);
let rs2_val = X(rs2);
Notes
• The same caveat regarding the order of the bounds check applies as for CSetBounds.
9.4. CHERIOT INSTRUCTIONS 123
CSetBoundsImm
Format
CSetBoundsImm cd, cs1, uimm
31 20 19 15 14 12 11 7 6 0
Description
Capability register cd is set to capability register cs1 with its base field replaced with
cs1.address and its length field replaced with uimm. If the resulting capability cannot
be represented exactly the base will be rounded down and the length will be rounded up
by the smallest amount needed to form a representable capability covering the requested
bounds. The tag field of the result is cleared if the bounds of the result exceed the bounds
of cs1, or if cs1 was sealed.
Semantics
let cs1_val = C(cs1);
Notes
• The same caveat regarding the order of the bounds check applies as for CSetBounds.
124 CHAPTER 9. INSTRUCTION REFERENCE
CSetEqualExact
Format
CSetEqualExact rd, cs1, cs2
31 25 24 20 19 15 14 12 11 7 6 0
Description
Integer register rd is set to 1 if the tag fields and in-memory representations of capability
registers cs1 and cs2 are identical, including any reserved encoding bits, otherwise it is set
to 0.
Semantics
let cs1_val = C(cs1);
let cs2_val = C(cs2);
X(rd) = EXTZ(bool_to_bits(cs1_val == cs2_val));
RETIRE_SUCCESS
9.4. CHERIOT INSTRUCTIONS 125
CSetHigh
Format
CSetHigh cd, cs1, rs2
31 25 24 20 19 15 14 12 11 7 6 0
Description
Capability register cd comes to hold the capability from cs1 with its high bits replaced
with the value in the integer register rs2. The tag of cd is cleared.
rs2 holds the in-memory form of capability bits. That is, this instruction yields the same
result as writing cs1 out to memory, overwriting the high word with rs2, and loading that
capability-sized granule into cd, although without the memory mutation side-effects.
Semantics
let capVal = C(cs1);
let intVal = X(rs2);
let capLow : xlenbits = capToBits(capVal)[sizeof(xlen) - 1 .. 0];
let newCap : Capability = capBitsToCapability(false, intVal @ capLow);
C(cd) = newCap;
RETIRE_SUCCESS
126 CHAPTER 9. INSTRUCTION REFERENCE
CSpecialRW
Format
CSpecialRW cd, scr, cs1
31 25 24 20 19 15 14 12 11 7 6 0
Description
Capability register cd is set equal to special capability register scr, and scr is set equal to
capability register cs1 if cs1 is not C0.
Semantics
let specialExists : bool = match unsigned(scr) {
28 => true,
29 => true,
30 => true,
31 => true,
_ => false
};
if (not(specialExists)) then {
handle_illegal();
RETIRE_FAIL
} else if not(PCC.access_system_regs) then {
handle_cheri_cap_exception(CapEx_AccessSystemRegsViolation, 0b1 @ scr);
RETIRE_FAIL
} else {
let cs1_val = C(cs1);
C(cd) = match unsigned(scr) {
28 => MTCC,
29 => MTDC,
30 => MScratchC,
31 => legalize_epcc(MEPCC),
_ => {assert(false, "unreachable"); undefined}
};
if (cs1 != zeros()) then {
match unsigned(scr) {
28 => MTCC = legalize_tcc(MTCC, cs1_val),
9.4. CHERIOT INSTRUCTIONS 127
Exceptions
An exception is raised if:
Notes
• Writing NULL to a special capability register cannot be done with C0 as that only
performs a read. An alternative implementation would allocate a separate two-
operand CSpecialR instruction and interpret cs1 being C0 as a write of NULL if
the need to use a temporary capability register proves to be overly problematic for
software.
128 CHAPTER 9. INSTRUCTION REFERENCE
CSub
Format
CSub rd, cs1, cs2
31 25 24 20 19 15 14 12 11 7 6 0
Description
Integer register rd is set equal to (cs1.address − cs2.address) mod 2xlen .
Semantics
let cs2_val = C(cs2);
let cs1_val = C(cs1);
CTestSubset
Format
31 25 24 20 19 15 14 12 11 7 6 0
Description
Integer register rd is set to 1 if the tag fields of capability registers cs1 and cs2 are the
same and the bounds and permissions of cs2 are a subset of those of cs1.
Semantics
X(rd) = EXTZ(result);
RETIRE_SUCCESS
130 CHAPTER 9. INSTRUCTION REFERENCE
Notes
• The operand order for this instruction is reversed compared with the normal RISC-V
comparison instructions, but this may be changed in future.
• The otype field is ignored for this instruction, but an alternative implementation
might wish to consider capabilities with distinct otypes as unordered as is done for
the tag field.
9.4. CHERIOT INSTRUCTIONS 131
CUnseal
Format
CUnseal cd, cs1, cs2
31 25 24 20 19 15 14 12 11 7 6 0
Description
Capability register cd is replaced with capability register cs1 and is unsealed, using capa-
bility register cs2 as the authority for the unsealing operation. If cs2.perms does not grant
G LOBAL then cd.perms is stripped of G LOBAL. If cs2 is unable to authorize the unsealing,
the tag field of cd is cleared.
Semantics
let cs1_val = C(cs1);
let cs2_val = C(cs2);
let cs2_addr = unsigned(cs2_val.address);
let (cs2_base, cs2_top) = getCapBounds(cs2_val);
let permitted = cs2_val.tag
& isCapSealed(cs1_val)
& not(isCapSealed(cs2_val))
& (cs2_addr == unsigned(cs1_val.otype))
& cs2_val.permit_unseal
& (cs2_addr >= cs2_base)
& (cs2_addr < cs2_top);
let new global = cs1_val.global & cs2_val.global;
_
let newCap = {unsealCap(cs1_val) with global=new_global};
C(cd) = clearTagIf(newCap, not(permitted));
RETIRE_SUCCESS
132 CHAPTER 9. INSTRUCTION REFERENCE
Appendix A
This chapter contains Sail types and functions that implement the capability encoding
scheme.
EncCapability represents capabilities as stored in memory.
struct EncCapability = {
reserved : bits(1),
cperms : bits(cap_cperms_width),
cotype : bits(cap_cotype_width),
cE : bits(cap_cE_width),
T : bits(cap_mantissa_width),
B : bits(cap_mantissa_width),
address : bits(cap_addr_width)
}
133
134 APPENDIX A. SAIL LISTINGS FOR CAPABILITY ENCODING
permit_store : bool,
permit_load_global : bool,
global : bool,
reserved : bits(1),
E : bits(cap_E_width),
B : bits(cap_mantissa_width),
T : bits(cap_mantissa_width),
otype : bits(cap_otype_width),
address : bits(cap_addr_width)
}
Partially decompress a capability from bits to a Capability struct. Permissions, otype and
exponent are decompressed, but the bounds are left in the form of B and T fields.
function encCapabilityToCapability(t,c) : (bool, EncCapability) -> Capability =
{
var perm_user0 : bool = false;
var permit_seal : bool = false;
var permit_unseal : bool = false;
var permit_execute : bool = false;
var access_system_regs : bool = false;
var permit_load_store_cap : bool = false;
var permit_load : bool = false;
var permit_store_local_cap : bool = false;
var permit_load_mutable : bool = false;
_
var permit store : bool = false;
var permit_load_global : bool = false;
var global : bool = bit_to_bool(c.cperms[5]);
var isExe : bool = false;
match c.cperms[4..0] {
0b11 @ [SL, LM, LG] => {
/* mem-rw-cap format */
permit_load = true;
permit_load_store_cap = true;
permit_store = true;
permit_store_local_cap = bit_to_bool(SL);
permit_load_mutable = bit_to_bool(LM);
permit_load_global = bit_to_bool(LG);
},
0b101 @ [LM, LG] => {
/* mem-ro-cap format */
permit_load = true;
135
permit_load_store_cap = true;
permit_load_mutable = bit_to_bool(LM);
permit_load_global = bit_to_bool(LG);
},
0b10000 => {
/* mem-wo-cap */
permit_store = true;
permit_load_store_cap = true;
},
0b100 @ [LD, SD] => {
/* mem-data */
permit_load = bit_to_bool(LD);
permit_store = bit_to_bool(SD);
},
0b01 @ [SR, LM, LG] => {
/* Executable format */
isExe = true;
permit_execute = true;
permit_load = true;
permit_load_store_cap = true;
access_system_regs = bit_to_bool(SR);
permit_load_mutable = bit_to_bool(LM);
permit_load_global = bit_to_bool(LG);
},
0b00 @ [U0, SE, US] => {
/* Sealing format */
perm_user0 = bit_to_bool(U0);
_
permit seal = bit_to_bool(SE);
permit_unseal = bit_to_bool(US);
}
};
/* The otype of executable caps is mapped to 1-7 and others to 9-15. Unsealed
is always 0. */
let otype = (if isExe | c.cotype == 0b000 then 0b0 else 0b1) @ c.cotype;
/* The 4-bit exponent is expanded to 5 bits, using 0xf to encode a cap_max_E
value that enables representing the entire address space. */
let E = if c.cE == 0xf then cap_max_E_bits else EXTZ(c.cE);
return struct {
tag = t,
perm_user0 = perm_user0 ,
permit_seal = permit_seal ,
136 APPENDIX A. SAIL LISTINGS FOR CAPABILITY ENCODING
permit_unseal = permit_unseal ,
permit_execute = permit_execute ,
access_system_regs = access_system_regs ,
permit_load_store_cap = permit_load_store_cap ,
permit_load = permit_load ,
permit_store_local_cap = permit_store_local_cap,
permit_load_mutable = permit_load_mutable ,
_
permit store = permit_store ,
permit_load_global = permit_load_global ,
global = global ,
reserved = c.reserved,
E = E,
B = c.B,
T = c.T,
otype = otype,
address = c.address
}
}
cperms[2] = bool_to_bit(cap.permit_store_local_cap);
cperms[1] = bool_to_bit(cap.permit_load_mutable);
cperms[0] = bool_to_bit(cap.permit_load_global);
} else if cap.permit_load & cap.permit_load_store_cap then {
/* mem cap-ro */
cperms[4..2] = 0b101;
cperms[1] = bool_to_bit(cap.permit_load_mutable);
cperms[0] = bool_to_bit(cap.permit_load_global);
} else if cap.permit_store & cap.permit_load_store_cap then {
/* mem cap-wo */
cperms[4..0] = 0b10000;
} else if cap.permit_load | cap.permit_store then {
/* mem rw data */
cperms[4..2] = 0b100;
cperms[1] = bool_to_bit(cap.permit_load);
cperms[0] = bool_to_bit(cap.permit_store);
} else {
/* Sealing format */
cperms[4..3] = 0b00;
cperms[2] = bool_to_bit(cap.perm_user0);
cperms[1] = bool_to_bit(cap.permit_seal);
cperms[0] = bool_to_bit(cap.permit_unseal);
};
return struct {
cperms = cperms,
reserved = cap.reserved,
cotype = cap.otype[2..0], /* truncate otype when compressing */
cE = if cap.E == cap_max_E_bits then 0xf else cap.E[3..0],
T = cap.T,
B = cap.B,
address = cap.address
};
}
Returns cap with E, B and T set such that the decoded bounds include the region specified
by base and length. If the region is not precisely representable the base may be rounded
down and the length up. Also returns a boolean indicating whether the bounds are precisely
representable, and sets the address of the returned capability to the requested base.
function setCapBounds(cap, base, length) : (Capability, CapAddrBits,
CapAddrBits) -> (bool, Capability) = {
let ext_base = 0b0 @ base;
/* Compute new top, note extra bit in case of overflow */
let top : CapLenBits = ext_base + (0b0 @ length);
/* Find smallest exponent that can represent required length.
*/
let e : range(0, 23) = 23 - count_leading_zeros(truncateLSB(length, 23));
/* Saturate e at max if it exceeds representable 4-bit value. */
var e_sat : range(0, cap_max_E) = if e > 14 then cap_max_E else e;
/* Extract B and T bits from base and top, include a spare bit so that we can
check for length overflow below. */
var B = truncate(base >> e_sat, cap_mantissa_width + 1);
var T = truncate(top >> e_sat, cap_mantissa_width + 1);
139
The representable alignment mask for a given length depends on the exponent that would
be used to represent a region of that length, assuming the base is sufficiently aligned. To
compute this we resuse the implementation of setCapBounds
function getRepresentableAlignmentMask(len) = {
140 APPENDIX A. SAIL LISTINGS FOR CAPABILITY ENCODING
prop_andperms checks that for any capability and permissions mask CAndPerm will result in
a capability whose permissions are a subset of the original permissions and the mask.
function prop_andperms(b : CapBits, mask : CapPermsBits) -> bool = {
let c = capBitsToCapability(false, b);
let perms = getCapPerms(c);
let newCap = setCapPerms(c, perms & mask);
/* We must encode then decode the resulting Capability to see the effect
* of permissions compression. */
let newCap2 = encodeDecode(newCap);
let newPerms = getCapPerms(newCap2);
/* Check that newperms are a subset of original perms and requested perms
*/
((newPerms & ~(perms)) == zeros()) & ((newPerms & ~(mask)) == zeros())
}
prop_setbounds checks the basic properties of setBounds: that the resutling cap includes
the requested region and that the exact flag is correct. Note that we restrict this to bounds
where the top is less than or equal to the maximum possible, since the encoding cannot
handle all such cases and the ISA should not allow creation of tagged capabilities with
such top anyway.
function prop_setbounds(reqBase : CapAddrBits, reqLen : CapAddrBits) -> bool =
{
let (exact, c) = setCapBounds(root_cap_mem, reqBase, reqLen);
let encodable = capEncodable(c);
let (b2, t2) = getCapBoundsBits(c);
let reqTop = (0b0 @ reqBase) + (0b0 @ reqLen);
let saneTop = reqTop <=_u 0b1@0x00000000;
saneTop --> (
encodable &
(c.address == reqBase) &
((exact & (reqBase == b2) & (reqTop == t2))
| (not(exact) & (b2 <=_u reqBase) & (reqTop <=_u t2)))
)
}
prop_setaddr checks that the flag returned by setCapAddr is sound with respect to the
definition of representability i.e. if it returns ‘representable’ then the bounds remain un-
changed. Note that a conservative implementation of the representability check may return
false even though the bounds are unchanged, so the converse does not necessarily hold.
function prop_setaddr(reqBase : CapAddrBits, reqLen : CapAddrBits, newAddr :
CapAddrBits) -> bool = {
let (exact, c) = setCapBounds(root_cap_mem, reqBase, reqLen);
let (representable, newCap) = setCapAddr(c, newAddr);
let boundsEqual = capBoundsEqual(c, newCap);
representable <--> boundsEqual
}
prop_repbounds_c checks the representable bounds match the minimum guarantee of the
C language i.e. base..one past the end.
function prop_repbounds_c(reqBase : CapAddrBits, reqLen : CapAddrBits, newAddr
: CapAddrBits) -> bool = {
let (exact, c) = setCapBounds(root_cap_mem, reqBase, reqLen);
let reqTop = (0b0 @ reqBase) + (0b0 @ reqLen);
let saneTop = reqTop <=_u 0b1@0x00000000;
let (b, t) = getCapBoundsBits(c);
let (representable, newCap) = setCapAddr(c, newAddr);
let inCBounds = (b <=_u newAddr) & ((0b0 @ newAddr) <=_u t);
(saneTop & inCBounds) --> representable
}
A.1. SMT VALIDATION OF PROPERTIES OF THE CAPABILITY ENCODING 143
prop_repbounds checks the representable bounds match expectations from the encoding,
namely [b, b + 2 ).
e+9
prop_crrl_cram checks that the CRRL and CRAM instructions can be used to derive a repre-
sentable base and length for given requested base and length. Note that CRRL returns zero
for non-zero reqLen in the case of rounding up to the max length.
function prop_crrl_cram(reqBase : CapAddrBits, reqLen : CapAddrBits) -> bool =
{
let mask = getRepresentableAlignmentMask(reqLen);
let reqTop = (0b0 @ reqBase) + (0b0 @ reqLen);
let saneTop = reqTop <=_u 0b1@0x00000000;
let newBase = reqBase & mask;
let newLen = getRepresentableLength(reqLen);
let (exact, c) = setCapBounds(root_cap_mem, newBase, newLen);
(saneTop & ((reqLen == zeros()) | (newLen != zeros())))-->
(exact & reqLen <=_u newLen)
}
144 APPENDIX A. SAIL LISTINGS FOR CAPABILITY ENCODING
Appendix B
If we apply all of these constraints we find there are only 33 useful permission combi-
nations (excluding the global bit, which we take to be orthogonal). Eliminating just one
of these combinations allows us to encode them using a 5-bit encoding. We see limited
uses for write-only capabilities outside MMIO, so we chose write-only store-local as the
least useful combination, leading to the encoding described in Section 7.13.1. This may
be reassessed once we have a clearer picture of use cases for write-only capabilities.
145
146 APPENDIX B. PERMISSION COMPRESSION RATIONALE
Appendix C
1. That incrementing T in the set bounds operation would not be necessary if the lower
e bits of top were decoded as ones, instead of zeros.
2. That zero length capabilities are of little use, and potentially even harmful (see Sec-
tion 7.13.7)
As such we consider revising the existing bounds decoding as follows (note ones instead
of zeros in low bits of top):
and further redefine the decoded top to be an inclusive bound instead of exclusive. The
corrections cb and ct remain the same. This has a number of consequences:
• The set bounds implementation no longer has to increment the value of T if the
requested top is not exactly represented because the decoding naturally rounds up as
desired. This may slightly simplify the implementation.
• The smallest length encodable for a given e is 2e (when B = T ). Zero length
capabilities are no longer supported.
147
148 APPENDIX C. POTENTIAL REVISED BOUND ENCODING
• The largest length encodable for a given e is 2e+9 (when T − B = 29 − 1). However
in this case the representable range is equal to the dereferenceable range, so it may
be necessary to limit the maximum value of T − B to 29 − 2. This would ensure that
‘one past the end’ remains within representable range, but would make the maximum
usable length 511 ∗ 2e , which is the same as the current encoding.
• Bounds can be set to the entire address space using B = 0, T = 29 − 1, e = 23.
This means the maximum exponent is smaller by one and therefore the worst case
granularity is now 223 , or 8 MiB instead of 16 MiB.
Section 7.14 introduces changes to map certain compressed instructions to their CHERI
counterparts, without changing the encoding of compressed instructions themselves. This
brings minimum changes to the instruction decoder of an existing RISC-V CPU. However,
further code size reduction can be achieved if we take advantage of the extra register index
bits freed up by RV32E, or if we drastically redesign certain encodings in C extension
entirely.
149
150APPENDIX D. PROPOSED COMPRESSED INSTRUCTION ENCODING CHANGES
The CHERIoT design is heavily based on prior work by the CHERI project and its myriad
contributors and collaborators. Our targeted use case differs from those of earlier projects,
so we present a brief tour through the existing landscape.
151
152 APPENDIX E. STANDING ON THE SHOULDERS OF GIANTS
By contrast, embedded systems have generally not had a notion of virtual addresses, con-
ducting all their business using physical addresses as directly presented on a peripheral
bus. As such, there has not been a convenient translation layer to press into service as a
memory protection mechanism. Attempts to add lookup-table based “protection without
translation” to physical memory systems, such as ARM’s MPU [13] and RISC-V’s PMP
[21, §3.6], sit uncomfortably on the critical path for memory. The need to adjudicate ev-
ery operation with a minimum of delay necessitates small tables fit into expensive, fast
(T)CAMs. CHERI capabilities, by contrast, directly carry their permissions and bounds,
obviating the need for tables and CAMs and necessitating only the check that each op-
eration is authorized. Removing the need for tabular storage allows for greater system
flexibility, as authority is now per reference rather than per address, and also removes risk
of a (ephemerally) misconfigured table.
information flow system encoded in the GL (“global”) and SL (“store local”) permissions.
Like lacking LM clears SD and LM, lacking LG clears GL and LG: all capabilities viewed
transitively through a capability without LG appear to be local.
fewer than 2700 CHERI-MIPS instructions. Both systems move functionality traditionally
placed in inner rings out to minimally-privileged compartments.
CheriOS software is generally composed of a number of libraries linked together; link-
age policy articulates the trust relationships between callers and callees, offering all of
mutually-trusting, sandbox, safebox, and mutually-distrusting call relationships. The CHERIoT
RTOS also relies on linkage to define a capability graph, but it offers fewer flavors of cross-
compartment calls (most calls are mutually-distrusting with library calls being a minimal
safebox).
CheriOS offers full memory safety for C/C++ programs. As expected, CHERI provides
integrity and monotonicity, and the memory allocators and compiler-generated code in-
sert bounding instructions where appropriate for spatial safety. CheriOS achieves heap
and stack temporal safety by exploiting the large address spaces of server-class machines.
Heap temporal safety is achieved in the TCB by a combination of take-once “reservations”
of address space and quarantining released virtual address space. A hardware barrier
added to CHERI-MIPS facilitates revoking capabilities pointing into a large contiguous
region of released address space, which the TCB may then recycle for new reservations.
Stack temporal safety builds atop heap temporal safety, with the compiler arranging to
construct “slinky stacks” [8, §4.1] that do not reuse possibly-escaped stack allocations’
address space. When a segment of a slinky stack has too little usable address space, it
is recycled as heap memory and another large heap allocation is made to provide a fresh
segment. The CHERIoT RTOS, by contrast, must operate on physical addresses and so
builds its heap temporal safety using additional CHERIoT ISA metadata and its slightly
weaker notion of stack temporal safety using the store-local permission.
2
Specifically, task C/C++ code is compiled in a “hybrid” mode, where pointers lower to integers unless
a capability is explicitly requested. These integers are interpreted relative to an architectural capability
register, either the program counter (for relative control transfers or PC-relative loads) or a “default data
capability” (“DDC,” which interposes legacy integer-based load and store instructions) [22, §2.3.12]. The
CHERIoT ISA does not have these legacy instructions or DDC; all CHERIoT RTOS code is expected to be
compiled “purecap,” with all pointers always lowered to capabilities.
156 APPENDIX E. STANDING ON THE SHOULDERS OF GIANTS
Bibliography
157
158 BIBLIOGRAPHY