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