Skip to content

Commit

Permalink
Add changes from Hugo's comments.
Browse files Browse the repository at this point in the history
  • Loading branch information
davidchisnall committed Feb 5, 2024
1 parent 9628304 commit 8c5b5d4
Show file tree
Hide file tree
Showing 4 changed files with 26 additions and 8 deletions.
19 changes: 17 additions & 2 deletions text/compartments.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,17 @@ Compartments in CHERIoT are somewhere between libraries and processes in mainstr
They have private code and globals and constitute a security boundary.
They can export functions to be called form other compartments and can call functions exported from other compartments.
Libraries are lightweight stateless compartments.
Libraries are a lightweight way of reusing code without duplicating it into different compartments.
Calling a library function does not involve crossing a security boundary.
It is possible for libraries to hold secrets but, unless library functions are written in very careful assembly, they should assume that any private state in the library can leak to callers.
Libraries contain code and read-only data but do not have mutable globals.
It is possible for libraries to hold secrets but, unless library functions are written in very careful assembly, they should assume that any (immutable) globals in the library can leak to callers.
Each library entry point is exposed as a sentry capability (see <<sealing_intro>>) to the callers, which means that the caller cannot directly read its code or (immutable) data.
WARNING: If a library traps, the error handler for the caller compartment may see the register file for the middle of the library.
Similarly, the compiler may spill arbitrary values onto the stack or leave them in registers at the end of a library function.
As such, you should assume that anything processed in a library written in a compiled language will leak to the caller and anything written in assembly must be *very* careful to avoid leaking secrets.
This is not normally a problem because most libraries just exist as an alternative to compiling the same functions into multiple compartments.
For example, the functions that implement locks on top of futexes (see <<futex>>) are in a library to reduce overall code size, but simply copying the implementations of these functions into each caller would have no security implications.
== Understanding the structure of a compartment
Expand Down Expand Up @@ -217,6 +225,13 @@ The spilled register file does not contain a tagged value for the program counte
This is to prevent library functions that run with interrupts disabled or with access to secrets from accidentally leaking on faults.
All other registers will be preserved exactly as they are in the register file.

NOTE: Error handlers are somewhat similar to UNIX signal handlers, but with some important differences.
They are invoked for synchronous faults, not arbitrary event notification.
Importantly, they are required only to handle the current compartment's errors.
You cannot, for example, call `malloc` in a signal handler because it would deadlock (or corrupt state) if the signal arrives during a call to `malloc` or `free`.
In contrast, if a call to `heap_allocate` fails then that error will be handled in the allocator compartment.
Your error handler will never be invoked in the middle of a call to the allocator and so it is fine to use error handlers to release locks and free memory.

At the end of your error handler, you have two choices.
You can either ask the switcher to resume, installing your modified register file (rederiving the PCC from the compartment's code capability), or you can ask it to continue unwinding.

Expand Down
9 changes: 6 additions & 3 deletions text/core_rtos.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ This risk is mitigated in several ways:
When the switcher receives an interrupt (including an explicit yield), it delegates the decision about what to run next to the _scheduler_.
The scheduler has direct access to the interrupt controller but, in most respects, is just another compartment.

The switcher also holds a capability to a small stack that is used when it is invoked from the switcher.
The switcher also holds a capability to a small stack for use by the scheduler.
This is not quite a full thread.
It cannot make cross-compartment calls and is not independently schedulable.
When the switcher takes an interrupt, it invokes the switcher's entry point on this stack.
Expand All @@ -89,10 +89,12 @@ It is not; however, in the TCB for confidentiality or integrity.
The scheduler has no mechanism to inspect the state of an interrupted thread.
When invoked explicitly, it is called with a normal cross-compartment call and so has no access to anything other than the arguments.

== Sharing memory with the allocator
== Sharing memory from the allocator

The final core component is the memory allocator, which provides the shared heap.
The final core component is the memory allocator, which provides the heap, which is used for all dynamic memory allocations.
This is discussed in detail in <<shared_heap>>.
Sharing memory between compartments in CHERIoT requires nothing more than passing pointers (until you start to add availability requirements in the presence of mutual distrust).
This means that you can allocate objects (or complex object graphs) from a few bytes up to the entire memory of the system and share them with other compartments.

The allocator has access to the shadow bitmap and hardware revocation engine that enforce temporal safety for the heap, and is responsible for setting bounds on allocated memory.
It is therefore trusted for confidentiality and integrity of memory allocated from the heap.
Expand All @@ -101,6 +103,7 @@ If it incorrectly configures revocation state or reuses memory too early then a

The allocator is not able to bypass capability permissions, it simply holds a capability that spans the whole of heap memory.
As such, it is in the TCB only with respect to heap allocations.
It cannot access globals (or code), held in other compartments and so a compartment that does not use the heap does not need to trust the allocator.

== Building firmware images

Expand Down
4 changes: 2 additions & 2 deletions text/language_extensions.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -175,10 +175,10 @@ These provide methods that are modelled to allow you to pretend that they give d
For example, you can write:

[,cpp]
---
----
capability.address() += 4;
capability.permissions() &= permissionSet;
---
----

This modifies the address of `capability`, increasing it by four, and removes all permissions not present in `permissionSet`.
Other operations are also defined to be orthogonal.
Expand Down
2 changes: 1 addition & 1 deletion text/memory.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ Defining these does not prevent memory allocation, you can still define non-defa

Sometimes a compartment needs to be able to allocate memory but that memory is not logically owned by the compartment.
This pattern appears even in the core of the RTOS.
The scheduler allocates memory for things like message queues, on behalf of a caller.
The compartment that provides message queues, for example, allocates memory on behalf of a caller, it does not hold the right to allocate memory on its own behalf.
It does this by taking an allocator capability as an argument and forwarding it to the allocator.

Often, if a compartment is allocating on behalf of a caller, it needs to ensure that the caller doesn't tamper with the object.
Expand Down

0 comments on commit 8c5b5d4

Please sign in to comment.