Sealing opaque types

It is very common in compartmentalised software to want to hand out a handle to some object to callers and later get it back with the guarantee that it has not been tampered with. CHERI provides a sealing mechanism for this kind of use case. A CHERI capability (pointer) can be sealed with another capability that represents a type. The resulting pointer can be passed around as normal but any attempt to dereference it (or even perform arithmetic on it) will fail. The holder of the capability with the permit-unseal capability and an address matching the sealed capability's type can unseal it and get back the original capability. The unseal operation returns null if the sealed capability is not a valid sealed capability of the correct type.

The CHERIoT capability encoding does not leave much space for sealing types (3 bits) and so the RTOS provides an abstraction layer for this as part of the allocator APIs. This example demonstrates using this with the most simple service: one that stores unforgeable immutable integers in opaque types. The caller of this service can create a new identifier object containing an integer and get back an opaque pointer that they cannot access directly. They can then call another function to get the value.

Note that this example is using C++ thread-safe static initialisers to lazily allocate the sealing key. The build system adds the C++ runtime (the line that includes the cxxrt directory) to make these work.

When you run this example, you should see an output line that looks like this:

Caller compartment: Allocated identifier to hold the value 42: 0x800058e0 (v:1 0x800058e0-0x800058ec l:0xc o:0xb p: G RWcgm- -- ---)

Note the o:0xb in this output. This field indicates the sealing type of the pointer and a non-zero value indicates that it is sealed. The hardware prevents the caller from tampering with this pointer or its pointee. You will also see an output line something like this:

Identifier service: Allocated identifier, sealed capability: 0x800058e0 (v:1 0x800058e0-0x800058ec l:0xc o:0xb p: G RWcgm- -- ---)
unsealed capability: 0x800058e8 (v:1 0x800058e8-0x800058ec l:0x4 o:0x0 p: G RWcgm- -- ---)

Note that the address and bounds of the sealed and unsealed capabilities are not the same. This is because the allocator adds a header to the sealed capability to ensure that allocations created with one key can be unsealed only with that key. The last line of output from the compartment shows that these sealed pointers are subject to the same temporal safety guarantees as other pointers: when you free one, it becomes impossible to load a valid pointer to the same sealed object.