Discussion
Loading...

Post

Log in
  • About
  • Code of conduct
  • Privacy
  • Users
  • Instances
  • About Bonfire
David Chisnall (*Now with 50% more sarcasm!*)
David Chisnall (*Now with 50% more sarcasm!*)
@david_chisnall@infosec.exchange  ·  activity timestamp 2 weeks ago

Mostly when I talk about #CHERIoT, I'm talking about the security, because that's the biggest and most obvious feature.

But since I've ranted a bit recently about Free Software things that don't empower users, I wanted to take a minute to write about how CHERIoT RTOS and is built on the principles that I think can provide maintainable systems.

The CHERIoT software is designed around compartments, which are somewhat like MULTICS shared libraries. They are isolated things that can own some mutable state and expose entry points, which are functions. Code in one compartment can invoke code in another compartment by simply calling a function that the other compartment exposes. This is a security domain transition. The callee has access to objects that were passed by reference, but there is no implicit sharing (and the hardware lets you share read-only view of data structures, or share for the duration of the call, and so on).

This model means that a lot of things that you run on the platform are tiny. This include the RTOS itself. There are four core bits of the RTOS:

  • The switcher, which is the equivalent of the kernel, and is around 350 instructions. This is the most privileged part of the system and implements the fundamental bits of the programming model that are too complex to put in hardware. It is carefully co-designed with the hardware architecture though, and we expect very few users will ever modify it. We're working to formally verify it.
  • The scheduler is trusted for availability, but not for confidentiality and integrity. It is just another compartment. We provide a simple fixed-priority scheduler with priority inheriting futexes. We think it's a nice design for embedded systems, but if you want to build your own scheduler you really just need to implement the function that the switcher calls that says 'this is the thread that was interrupted, go and talk to the interrupt controller and figure out which new thread you want to run now' and the futex APIs.
  • The heap allocator. This owns the region of memory used for the heap and works with the hardware to enforce spatial and temporal safety. We provide an implementation of this in C++, some folks at UBC are working on a reimplementation in Rust using Verus. If this is slower / bigger, we'll support both, if it's as good then we'll probably replace ours.

The RTOS also contains a bunch of optional shared libraries and makes it easy to wrap other components. For example, we have a network stack that puts BearSSL, the FreeRTOS TCP/IP stack, and the FreeRTOS MQTT libraries in compartments and adds a control-plane compartment that manages authorisation for socket creation and firewall control, a firewall compartment, and a DNS resolver compartment.

Most of these are small. If you want to replace BearSSL with WolfSSL or something similar, that should be easy. If you don't want to use it at all, that's fine too, you can replace the entire network stack with something different if you want to.

The goal is to have a load of building blocks that device vendors and end users can easily reuse if they want to, but that they can easily replace with their own things if they don't like ours.

We built an auditing framework that makes it easy to add CI-time or code-signing-time checks to enforce rules like 'only the firewall can talk to the network interface device directly' or 'only the TLS compartment may call the TCP send and receive functions'. We did this to support the conflicting requirements of proposed right-to-repair legislation (which should give end users the right to modify the software on their device) and other safety regulations (which may restrict the ability to modify things like the code talking to software-defined radio, or the safety-critical parts of a medical device). This enables people who build on the platform to create devices that empower their end users, even in domains where allowing replacement of the firmware may be illegal.

  • Copy link
  • Flag this post
  • Block
KGsocial
KGsocial
@KGsocial@mastodon.social replied  ·  activity timestamp 2 weeks ago

@david_chisnall

Pt 1 of 2

A Quality Reference Summary!

Thank you for taking care and time over it.

Alas, my plans for using this to defeat and safely ML sensor some of the most common network facing software attacks now lay in ruins. At least within a UK based business.

  • Copy link
  • Flag this comment
  • Block
rzeta0
rzeta0
@rzeta0@kolektiva.social replied  ·  activity timestamp 2 weeks ago

@david_chisnall

does spatial memory safety mean that memory is only accessible (ro/rw) only during the duration of a pre-registered and known sys call ?

or is it something else?

  • Copy link
  • Flag this comment
  • Block
David Chisnall (*Now with 50% more sarcasm!*)
David Chisnall (*Now with 50% more sarcasm!*)
@david_chisnall@infosec.exchange replied  ·  activity timestamp 2 weeks ago

@rzeta0

Spatial memory safety means that, given a pointer to an object, nothing you do with that pointer can give you access to a different object. It's sometimes called bounds safety. Similarly, nothing you can do in the absence of a pointer to an object can give you access to that object.

  • Copy link
  • Flag this comment
  • Block
rzeta0
rzeta0
@rzeta0@kolektiva.social replied  ·  activity timestamp 2 weeks ago

@david_chisnall

Sorry my typo. I meant to ask about temporal not spatial.

  • Copy link
  • Flag this comment
  • Block
David Chisnall (*Now with 50% more sarcasm!*)
David Chisnall (*Now with 50% more sarcasm!*)
@david_chisnall@infosec.exchange replied  ·  activity timestamp 2 weeks ago

@rzeta0

Generally, our temporal safety means that, after an object is deallocated, all pointers to it are invalidated. There are a couple of hardware mechanisms that provide this. They're a bit complicated, you can see our 2023 MICRO paper for how they work.

We also have a scoped delegation model, which is built on a lightweight information-flow mechanism that's been in CHERI for ages and some of our own use. This 2-bit model says that pointers are local or global. You can turn a global pointer into a local one, but not vice versa (so global is similar to a permission). There is a store-local permission and a local pointer can be stored only through pointers with store-local permission.

Our stack is the only user-accessible memory with store-local permission (the thread's register-save area has it as well, but that pointer can be used only by the switcher), and it is local. This means that a pointer derived from the stack pointer (i.e. any on-stack object) can (unless you explicitly drop the store-local permission) hold pointers to other stack objects, but the heap and globals cannot.

We also have a transitive version of this permission that's applied on loads: a permit-load-global permission. If a pointer lacks this permission, then any pointer loaded using that pointer has both permit-load-global and global stripped when it is loaded. That sounds complex, but it basically means I can give you a pointer without permit-load-global and without global and no object that you reach by pointer chasing from there will have global permission.

This combination means that I can give you a pointer and have guarantees that you can't store pointers to that object (without global) or any object reachable from that pointer (without permit-load-global) anywhere except on a stack (or in registers).

This same mechanism means that pointers to one thread's stack can't point to another thread's stack.

When you do a cross-compartment call, the switcher truncates the bounds of the stack pointer to be only the region that the callers haven't used and then zeroes that region (we have some lightweight hardware that makes this fast). On the return path, it zeroes the used bit of the shared region again. This means that stack spills or on-stack objects don't leak.

All of these add up to a thing where you can remove one or two permissions from a pointer (register-to-register operation) and then pass it as a function argument and have a guarantee that the callee can't capture either it or (optionally) anything reachable from it.

  • Copy link
  • Flag this comment
  • Block

bonfire.cafe

A space for Bonfire maintainers and contributors to communicate

bonfire.cafe: About · Code of conduct · Privacy · Users · Instances
Bonfire social · 1.0.1 no JS en
Automatic federation enabled
Log in
  • Explore
  • About
  • Members
  • Code of Conduct