|  | Compartmentalised hello world | 
|  | ============================= | 
|  |  | 
|  | The [hello world example](../1.hello_world) used a single compartment, which had direct access to the UART device. | 
|  | In general, it is bad practice to grant direct access to devices to most compartments and so in this example we'll start moving the UART access to a separate compartment. | 
|  |  | 
|  | *Note:* The `debug.hh` interfaces use direct access to the UART, but only in debug builds. | 
|  | Debug builds are often allowed to weaken security rules in exchange for a better debugging experience. | 
|  |  | 
|  | In this version, [`uart.cc`](uart.cc) exposes a single function that writes a string to the UART. | 
|  | The code in [`hello.cc`](hello.cc) calls this with a global string and again with an on-stack buffer. | 
|  |  | 
|  | What do we gain from this compartmentalisation? | 
|  | In an example this simple, not a huge amount (especially given that neither compartment operates in a way that an attacker could influence, since we don't have any external inputs), but the principles here apply to more complex examples. | 
|  | We can statically audit the firmware image to know that: | 
|  |  | 
|  | - The uart compartment is the only one that has direct access to the UART device. | 
|  | - The hello compartment is the only one that calls the `write` entry point on the UART compartment. | 
|  |  | 
|  | If an attacker compromised the hello compartment then the would still be able to call the `write` function to write arbitrary output to the UART, but they would not be able to read or write any of the control registers of the UART. | 
|  |  | 
|  | This is the kind of isolation that device drivers typically provide in operating systems and our uart compartment is an example of one of the simplest possible device drivers: | 
|  |  | 
|  | - It provides useful interfaces to the underlying hardware that abstract over its implementation. | 
|  | - It protects the hardware interfaces from use by arbitrary software on the system. | 
|  |  | 
|  | Note in addition that we've added one more attribute to the function exposed here: `[[cheri::interrupt_state(disabled)]]`. | 
|  | This means that the uart compartment will write the entire string to the UART, so two different threads writing to it will not interfere. | 
|  | This is not ideal for something like a `write` function, which can take a caller-controlled amount of time to complete, because it prevents any other thread from making progress, even ones that don't touch the uart. | 
|  |  | 
|  | ## Validating the compartmentalisation model | 
|  |  | 
|  | The goal of this refactoring was to ensure that only the `uart` compartment has access to the UART device. | 
|  | How do we know if we achieved that? | 
|  | This is where the [cheriot-audit](https://github.com/CHERIoT-Platform/cheriot-audit) tool comes in. | 
|  | If you're using the dev container, this is installed in `/cheriot-tools/bin/`, if you've built it somewhere else then replace `/cheriot-tools/bin/` with the correct path. | 
|  |  | 
|  | First, let's use it to see which compartments or libraries have access to the UART: | 
|  |  | 
|  | ``` | 
|  | $ /cheriot-tools/bin/cheriot-audit -b ../../sdk/boards/sail.json -j build/cheriot/cheriot/release/hello_compartment.json -q 'data.compartment.compartments_with_mmio_import(data.board.devices.uart)' | 
|  | ["debug", "scheduler"] | 
|  | ``` | 
|  |  | 
|  | This uses `-b` to find the board description file and `-j` the report that the linker generated during the build. | 
|  | The query passed with `-q` asks which compartments or libraries import the UART's MMIO capability. | 
|  | This tells us that the `debug` library and the `scheduler` compartment both have direct access to the UART. | 
|  | The latter is an artefact of how some simulators exit: simulation builds will have the UART exposed to the scheduler. | 
|  |  | 
|  | Note that our `uart` compartment isn't on this list. | 
|  | It accesses the UART via the `debug` library. | 
|  | This means that, in addition to the compartments and libraries that have direct access to the UART, we need to determine which compartments have access to the `debug` library. | 
|  | Let's run that as another query: | 
|  |  | 
|  | ``` | 
|  | $ /cheriot-tools/bin/cheriot-audit -b ../../sdk/boards/sail.json -j build/cheriot/cheriot/release/hello_compartment.json -q 'data.compartment.compartments_calling("debug")' | 
|  | ["uart"] | 
|  | ``` | 
|  |  | 
|  | Great! | 
|  | Our compartmentalisation goal has been achieved! | 
|  |  | 
|  | Note that it was *not* in the first version of this example (ooops!). | 
|  | The `fail-simulator-on-error.h` header adds an error handler that writes debug messages via direct access to the UART. | 
|  | This was difficult to spot manually, but would show up in auditing easily. | 
|  | Try uncommenting this line in `hello.cc`: | 
|  |  | 
|  | ```c++ | 
|  | //#include <fail-simulator-on-error.h> | 
|  | ``` | 
|  |  | 
|  | Rerunning the above query should now show the following output: | 
|  |  | 
|  | ```json | 
|  | ["hello", "uart"] | 
|  | ``` | 
|  |  | 
|  | Now that we have something that is correct, we'd like to make sure that it *remains* correct. | 
|  | We do this by writing a Rego module that encapsulates our policy. | 
|  |  | 
|  | First, let's start with a rule that checks access to the MMIO region. | 
|  |  | 
|  | ```rego | 
|  | # For non-simulation platforms, we only allow the debug library to access the | 
|  | # UART | 
|  | uart_access_valid { | 
|  | data.compartment.mmio_allow_list("uart", {"debug"}) | 
|  | } | 
|  |  | 
|  | # For simulation platforms, we allow the scheduler to access the UART as well | 
|  | uart_access_valid { | 
|  | data.compartment.mmio_allow_list("uart", {"debug", "scheduler"}) | 
|  | data.board.simulation | 
|  | } | 
|  | ``` | 
|  |  | 
|  | Note that we've specified this rule twice. | 
|  | Rego rules match if either instance is true. | 
|  | The expressions within the braces are anded together: all must be true for the rule to be true. | 
|  | This means that we're happy with either of two cases: | 
|  |  | 
|  | - The only thing with access to the `uart` device is the `debug` library. | 
|  | - The only things with access to the `uart` device are the `debug` library and the `scheduler` compartment *and* we are targeting a simulator. | 
|  |  | 
|  | We now want to add a rule that holds only if this is true and if the only caller of the `debug` library is the `uart` compartment: | 
|  |  | 
|  | ```rego | 
|  | # Check that the UART is accessible only to the authorised libraries and | 
|  | # compartments and that only the `uart` compartment can call the library that | 
|  | # has direct access. | 
|  | valid { | 
|  | uart_access_valid | 
|  | data.compartment.compartment_allow_list("debug", {"uart"}) | 
|  | } | 
|  | ``` | 
|  |  | 
|  | With this, we can now run the tool to check whether the built firmware complies with our policy: | 
|  |  | 
|  | ``` | 
|  | $ /cheriot-tools/bin/cheriot-audit -b ../../sdk/boards/sail.json -j build/cheriot/cheriot/release/hello_compartment.json -m policy.rego  -q 'data.hello_compartment.valid' | 
|  | true | 
|  | ``` | 
|  |  | 
|  | If this outputs `true`, it worked! | 
|  | Try modifying the code and see if it still passes. | 
|  | Configuring the build with allocator debugging enabled will cause the policy check to fail, so this can even catch cases where you left debugging access to the UART enabled in a production build. |