CHERIoT RTOS C++ [Re: The Case for Rust (in any system)]

From: David Chisnall <theraven_at_freebsd.org>
Date: Sun, 08 Sep 2024 08:53:57 UTC
Changing the title so people who don’t care can easily ignore this tangent.

> On 7 Sep 2024, at 20:20, Paul Floyd <paulf2718@gmail.com> wrote:
> 
> Out of curiosity, did that mean limiting the ABI use (no RTTI or exceptions). Did it also allow using different compilers (say clang and GCC)?

We’re targeting tiny embedded systems. The total code size for the core RTOS (scheduler, memory allocator, switcher) is around 10 KiB, so exceptions are totally infeasible. A stack unwinded would be as big as the total amount of RAM on some of the SoCs we want to be able to support. We also don’t use RTTI for a similar reason. We do have a tiny C++ runtime for lazy static initialisation.

I wish the C++ standard would lose its allergy to subsetting, because the standard not subsetting does not prevent it happening, it just means everyone does it differently. In terms of library support, we claim to be a freestanding implementation and also provide a bunch of things from hosted implementations. A lot of the normal standard library types are not well suited to environments where memory allocation can fail and where exceptions don’t work (the libc++ code calls abort in these cases, we want things that return std::optional for things that might fail).

We are co-designing the hardware and software. We have some FPGA prototyping platforms (lowRISC has built a really nice one) and the company I cofounded will ship commercial silicon next year. We have some extensions beyond CHERI that enable deterministic heap temporal safety as well. As soon as you free an object, all pointers to it become unusable (and then we have some additional hardware that makes it possible to safely reuse the memory). 

So it’s not quite normal C++. We can inspect a pointer and tell what its bounds are, what its permissions are, and whether it’s valid. We can also transform it into a tamper-proof ‘sealed’ pointer that can be unsealed only if you hold the relevant unsealing token. This can protect things like socket handles: the TCP/IP stack hands out a sealed pointer to the socket state and can check when you pass it back that it is the correct type and can then unseal it, but you everything else that pointer is a value that can be copied around but can’t be used. We can do some fun things (it’s possible to represent all of the state of a std::vector with a single pointer, for example) and, in some places, we skip static memory safety checks and instead write code that expects to fail and return to the calling compartment if it encounters a memory-safety bug.

This set of primitives lets us build a rich compartmentalisation model where communication between compartments looks like a function call and sharing memory is accomplished by passing pointers (which may have restricted permissions, including shallow or deep immutability and no-capture guarantees). On top of this, we have built auditing tooling that lets you see which compartments call which entry points in others, which compartments have access to which MMIO regions, and so on.

Our ABI defines a bunch of things that are extensions to the Itanium ABI such as calling conventions for cross-compartment calls, shared libraries (which, for us, are stateless and don’t involve a security domain crossing, whereas compartments do).

We don’t have support for more than one compiler at the moment because our extensions are implemented only in clang. There is nothing stopping anyone adding the same support in other compilers though and I’d be happy to help anyone who wanted to do gcc bring up.

More details (code, formal model of the ISA, programmers’ handbook, and so on here):

https://cheriot.org/

David