Reports now enabled by default #33
davidchisnall
announced in
Announcements
Replies: 1 comment
-
As an example of what can be done with the linker report, here is the output of a work-in-progress compartment graph visualiser I wrote when run on the hello compartment example: |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
As of #32, we are now generating compartment reports when we link. Our compartment model was designed to make it possible to statically audit the contents of a firmware image that contains components from different providers. Our goal was for the linker to generate a report that let external tooling audit the shape of the compartment graph. @rengolin did an initial implementation of this and I have now updated it and added support for the newer features of the ABI. It should still be regarded as experimental (there are almost certainly bugs and the format is not guaranteed to be stable!), but it's now enabled by default.
Now, near the end of the build of a firmware image, you should see a line something like this:
This JSON file contains a couple of top-level things that let you identify the binary that it corresponds to, something like this:
The
file
is just the path where the image was created and is not particularly useful for long-term auditing, but thefinal_hash
lets you identify the binary by its SHA256 hash.The rest of the file is split into two objects. The first of these,
core
describes the core of the TCB: the loader and the switcher. For example, you will see something like this for the switcher:The switcher is provided as a single input section and you can check the hash of this to ensure that it matches the version that you expect. If it changes, you will need to look at the audit trail for the build to understand why and may choose not to sign / trust firmware images until you have done so.
Beyond the core, there's also an entry in the top-level
compartments
object for each compartment or shared library. This contains a report of the contents, as for the core components, but for most compartments it will include at least one of animports
andexports
section. The exports describe the things that this compartment provides. For example, looking in the allocator compartment we see this at the start of the exports array:The first entry says that this provides a sealing key and give a unique identifier for it as a sealing key kind. The second entry gives the symbol of an exported function. This function runs with interrupts enabled, takes one in-register argument, and (probably useful only if you have access to the binary) starts at offset 152 within the code section of the compartment.
Now let's look at the import tables for a compartment that uses the allocator, specifically the allocator test. The first entries look a bit like this:
These are used for our software capability system, which is built on top of the hardware capability system. The allocator test compartment will receive a sealed capability to an object that only the owner of the type can unseal. The initial contents of this are listed in the
contents
field. This is an instance of theAllocatorCapabilityState
structure and so we expect the first field to be the quota and the rest to be zeroes. The first field is asize_t
, so 4 bytes:0x00001000
. This means that whoever holds this capability is permitted to allocate up to 4096 bytes of memory. Thesealing_type
section describes the name of the compartment and the name of the sealing key that are used to unseal this. The symbol is the unique identifier (which contains both of these values) that you can cross-reference to see which compartment may unseal this object. This corresponds to the exported sealing type from the allocator, above.The next entry should appear only in debug builds:
The start value is not very helpful in decimal, and is a lot more recognisable in hex: 0x10000000. If you look in the
board description file for the sail simulator
you will see that this corresponds to the UART: when debugging, we give all compartments direct access to the UART.There are also few shared-library functions. For example:
Library functions are called directly (via a sentry capability), they don't go via the compartment switcher. Most of these will have C++ name-mangled names, but the atomic library functions do not because clang hard-codes their names in the front end. If the symbol is mangled, the
function
gives you a demangled name. This is not important for auditing, but is useful if you happen to be a human and are reading this output. Theexport_symbol
lets you find the corresponding library that exports them. Theprovided_by
key should be the same as the one found in the compartment that exports these symbols, and is provided in both places for ease of use.There are also several cross-compartment calls, including the one exported from the allocator above:
Again, this has the demangled name and the compartment file that provided it for ease of debugging, but the key information is the
export_symbol
, which can be cross-referenced against the entry above.With all of this information, you can build a complete graph of which compartments call which others (and with which entry points), which compartments are able to disable interrupts (and which compartments can call those!). You can audit which software-defined capabilities any compartment has. You can check that a shared library has precisely the code that you expect.
We hope that this can be used to build very strong supply-chain integrity for embedded systems.
Beta Was this translation helpful? Give feedback.
All reactions