Writing GRUB modules in Rust ---------------------------- GRUB (the GRand Unified Bootloader) is a bootloader used by a large number of Linux distributions. It's a fairly large codebase, and it's mostly written in C - some of which dates back to the 90s. As a result, it's perhaps unsurprising that it's seen a relatively large number of CVEs, which is a problem given that the security of the boot chain depends on it. Around 40% of GRUB CVEs have been down to memory safety issues, issues which are effectively impossible to create in Rust. This presentation covered the use of Rust to write modules for GRUB. The goal is not to rewrite GRUB entirely, but to make it possible for individual GRUB modules to be either written in Rust to begin with, or for more security sensitive modules to be ported to Rust in order to avoid certain categories of vulnerability. GRUB modules are simply ELF objects, and Rust already has support for targeting these - however, Rust does not support all architectures supported by GRUB (eg, IA64), and as a result there may be a requirement to continue shipping C alternatives to any modules implemented in Rust. The effort so far provides Rust bindings for various GRUB interfaces (such as memory allocation), and attempts to expose GRUB APIs in reasonably idiomatic Rust. This allows features like automatic de-registration of commands if they pass out of scope using a Rust Drop function. There are a few complexities - exported functions need to be flagged as using the C ABI and not using symbol mangling in order to allow the GRUB module loader to find them correctly, but functions contained entirely within the module don't need to worry about this. There are some wider questions. Is it reasonable to rely on Rust's Cargo ecosystem? One of the risks is that long chains of dependencies might pull in code that results in licensing complexities. In addition, right now any failed memory allocation is hardwired to call grub_fatal(), exiting the loader entirely. This hasn't been triggered in the real world, but scenarios like (for instance) attempting to render a large PNG on a memory-constrained device should be handled gracefully rather than exiting. In terms of wider ecosystem support, there's some overlap with the effort to support Rust within the Linux kernel. Unfortunately this results in a different problem - the kernel is GPLv2, and GRUB is GPLv3+. As a result, the work produced by the Rust for Linux effort cannot be used directly. There's a sufficiently small number of contributors to that that it may be possible to relicense it in a way that would allow both projects to collaborate - on the other hand, GRUB's status as a GNU project means that most code contributions involve copyright assignment to the FSF, which could make things more difficult. Daniel Kiper, one of the GRUB maintainers, is enthusiastic about incorporating this into an upcoming GRUB release. He did have a couple of concerns - one is that GRUB's internationalisation support is currently based around C, and that infrastructure would have to be reimplemented. Another is whether this would result in an increase in the size of the binaries. A trivial "Hello World" module in Rust comes in at 12K, but that includes the Rust memory allocation support. If this code lived in the GRUB core instead, the overhead would be much smaller. It didn't seem that duplication of C functionality in Rust was a huge concern. Even if some platforms continued to rely on the C implementation, the security benefits of being able to implement an HTTP client or a PNG parser in Rust rather than C would seem to outweigh the overhead of maintaining the C code as well. Platforms that Rust doesn't support are no longer manufactured, so this seems like a problem that will resolve itself in time. The only real pushback against the current patches was that they did not separate build system changes that were core to supporting Rust and changes that simply added the "Hello World" module. This is expected to become clearer as additional modules are added. Firmware and Bootloader Logging Specification --------------------------------------------- There are multiple events and sources of data in the firmware environment that are potentially of interest in the running OS, but no generalised mechanism for making that information available. The goal of the Firmware and Bootloader Logging Specification is to provide that - while initially implemented as part of the Trenchboot project and targeting GRUB, it seems like it would also be useful for other boot components such as Shim. The specification defines a basic structure of a linked list of log headers, each of which points to a buffer that contains the actual data. The header includes a version number (currently 1), the size (enabling implementations to skip over events they don't understand), a field containing a UUID to describe who produced the event, and a set of flags to provide some metadata. The event itself includes its own version and size fields, and another instance of the producer UUID (intended to allow the event to be parseable without needing access to the header). Finally, it points to the location of the log message itself. The message again includes size, a timestamp describing when it was produced, a level that describes the importance of the message, a facility that indicates the source of the message, and a type field that allows for a simple textual representation of the message type. Finally, the message itself is included. The current implementation patches the grub_printf() function in order to log messages to a temporary buffer that's dynamically allocated as necessary to store the data. At the point where an OS is booted, this is copied to a stable location. Currently, a field is added to the Linux boot_params structure to allow the kernel to be provided with a pointer to the logs. It's not yet clear where this spec should live - it's not fundamentally tied to UEFI, so maybe putting it in the Multiboot spec would be sensible. The use of boot_params also currently ties it to x86, and so there's interest in architecture-agnostic mechanisms for providing it to the kernel. This could be handled by placing it in a UEFI configuration table, or producing an ACPI table or adding a pointer to the Device Tree. One question raised was how this interacts with the TPM event log, another source of firmware-generated data that's accessible to the OS. The answer is that right now it doesn't interact at all, and is intended to be much more general. The TPM event log includes information that is intended to be security relevant, while this log is execpted to contain informational material. Another suggestion was that for systems with a BMC, the log could be passed to the BMC in some way in order to make it easier to diagnose firmware issues. While not implemented, this seemed of interest. Finally, there was a question of whether we needed to care about non-UEFI platforms at all? The answer was that Chromebooks are not UEFI, and that while Coreboot does support a UEFI payload, not all Coreboot targets are UEFI. There's been interest from the Coreboot community in supporting this specification. Linux and DRTM on ARM --------------------- Booting involves a chain of components passing control to each other. The more components in that chain and the more complicated they are, the harder it is to determine what the actual security claims are. DRTM (Dynamic Root of Trust Measurement) allows for the final state of the boot environment to be measured, rather than measuring each component in turn. This allows launch of the OS to be tied to a measurement that the system is within the expected state, that no other code is currently being executed, that the platform has implemented appropriate security mechanisms (such as DMA protection), and that the memory map and ACPI tables are being provided via a trustworthy source. ARM has the concept of two "worlds" - secure and normal. The secure world is broadly analogous to System Management Mode on x86, where the CPU executes code that is not visible to the normal OS and has access to resources that the OS does not. Unlike SMM, the secure world on ARM systems has its own complete set of execution levels, allowing code running in the secure world to be itself either privileged or unprivileged. The focus of this talk was on measurement of the normal world, with the security of the secure world being considered out of scope. A straightforward implementation of DRTM on ARM would be for the measurement code to be executed in EL3 (a CPU privilege level intended for firmware use) in the normal world, with the result being passed to the secure world for recording of the measurement. The results of this measurement are then placed in a data block that tells the OS which regions are protected, an address map that avoids the OS having to rely on information provided by the firmware post-measurement, an event log, validated ACPI tables and space for implementation-specific data. One notable component that isn't measured is the UEFI runtime services code, which is provided by firmware but executed in the context of the kernel. This isn't an ARM specific issue, but isn't well solved in Linux as yet. Windows apparently handles this by executing the runtime services code in a sandbox. TPMs have a concept of "localities", effectively privilege levels. Some TPM functionality is only available within certain localities. Many ARM systems don't actually have a physical TPM. Instead, TPM functionality is provided by a component running inside the secure world. This exposes a Command Response Buffer (CRB) interface to the normal world for control. This makes it harder to implement proper locality support, which is a requirement for DRTM (it's necessary to prove that the DRTM values were actually generated by a DRTM event, rather than being faked by other software). This seems like it could be solved by having the entry point to the secure world CRB examine the normal world state to determine the source of the request. TrenchBoot Secure Launch Status ------------------------------- TrenchBoot is a project to support directly launching Linux within a DRTM environment. GRUB is the most commonly used bootloader on DRTM-supporting platforms, so the focus so far has been on implementing support for Intel and AMD DRTM functionality for Secure Launch. Secure Launch requires additional information to be passed between the bootloader and the kernel - this was initially handled using the setup_header functionality in the kernel, but this proved overly restrictive in terms of how much information could be passed. Another issue is that the kernel needs to measure data early in the secure launch process, but has not yet reached the stage where it has access to TPM drivers. This has been resolved by implementing a lightweight TPM driver for the early boot stage. Refactoring the existing kernel TPM support to allow it to be used in both scenarios was contemplated, but determined to be overly complicated. It's also impossible to simply use the firmware's TPM drivers - control has passed from the firmware to the OS at this point, and they are no longer available. So far, no realistic plan for avoiding the duplicated functionality has been put forward. A question was asked about how this ties in with the TPM event log. DRTM maintains its own event log, and measurements from the secure launch stages are logged there. In theory the system is in a sufficiently controlled state during measurements that it's impossible to interfere with them before they're passed to the TPM. The current implementation relies on control being passed to the kernel after ExitBootServices() is called, removing access to firmware functionality. This means that it skips the kernel-provided EFI stub code, which in turn means that certain functionality doesn't work (such as passing the TPM event log up to the running OS). A suggestion for avoiding this was to support shipping the EFI setup code as an independent object, and executing this in the bootloader before passing control to the kernel. This would avoid bootloaders having to re-implement functionality that exists in the kernel already, and avoid the kernel having to commit to a stable ABI for EFI handoff data.