stboot system

System documentation for stboot #

This document describes the way stboot boots a system, and the intended security properties.

Supported systems #

Currently, only UEFI systems are supported.

Getting stboot to run #

The stboot executable is a user-space program intended to run under the Linux kernel. To get the UEFI firmware to run stboot, it is packaged into an initramfs (together with auxiliary configuration and data files described later). The initramfs is packaged, together with a kernel image and kernel command line, as a Unified Kernel Image (UKI). A UKI can be understood as a self-extracting UEFI executable.

The firmware treats the UKI as an arbitrary UEFI executable, and executes it. When startup code in the UKI is running, it arranges for kernel, command line, and initramfs to be located at proper places in memory, and jumps to the kernel’s entry point.

The integrity of the rest of the boot process depends on the integrity of the stboot UKI and the firmware’s behavior when starting it. Ensuring this integrity is not within the control of stboot: one will need additional security tools, e.g., ensuring physical security of the machine, restricting access to firmware console, and using secure boot to prevent the firmware from running unauthorized code.

Init process #

It is possible to have stboot run as the init (pid = 1) process, e.g., by making the initramfs contain a file /init that actually is the stboot executable. If stboot runs as init, it uses u-root’s libinit package for system initialization, in particular, loading of configured kernel modules. To get kernel modules loaded, e.g., needed network drivers, u-root (and hence, stboot) supports two different conventions:

  1. Put the kernel modules in a flat directory /lib/modules; all *.ko files in that directory are loaded. Unfortunately, the modules are loaded in some arbitrary order, so this works reliably only for modules with no dependencies.

  2. Use a directory /lib/modules/<kernel-release>/ with the tree of kernel modules and a corresponding modules.dep file, together with a directory /lib/modules-load.d/ with one or more .conf files listing one module per line. Additional modules can also be provided as a comma-separated list to the “modules_load” kernel command line flag. When using this convention, dependencies of listed modules are loaded automatically.

It is not required that stboot runs as the init process, though. E.g., one could use u-root or systemd as the init process, and arrange so that stboot is started after kernel module loading and other system initialization is done. For stboot to be interruptible using Ctrl-C, it must then be started in a process environment where it has a controlling terminal. When using u-root’s init process, there’s one known issue: mounting of efivarfs fails if efivarfs is a loadable kernel module, see https://github.com/u-root/u-root/issues/2993.

Loading stboot configuration #

Host configuration #

The host configuration is defined separately. It is read by stboot from /etc/host_configuration.json in the initramfs if that file exists, otherwise from an EFI variable.

The settings defined here describes how to configure networking, and the location of the OS package to boot. If provided in the initramfs, integrity of the host config file is the same as the integrity of the stboot executable itself. If read from EFI variables, there may be additional attack vectors, e.g, via root compromise of the operating system ultimately started by stboot.

Unauthorized changes could trivially break booting. In addition, an attacker changing to the OS package URL have similar implications as an attacker controlling the local network: the attacker could make the machine boot any OS package that is allowed by the stboot trust policy, e.g., an old version, or a package intended for a different machine.

If no host config is found, stboot enters provisioning mode: it looks for an OS package /ospkg/provision.zip, regardless of the fetch method specified in the trust policy (described below). If found, this OS package is booted. When running, the provisioning OS package is expected to create a valid host configuration, to be used at next boot. Using an OS package including the stprov tool is one way to do that.

Trust policy #

The trust policy is defined separately. It is read by stboot from the “/etc/trust_policy” directory in the initramfs.

A trust policy consists of a file with the trusted root certificate for OS package signatures, a file with one or more trusted root certificates for TLS connections, and a JSON file with configuration. The integrity of the trust policy is the same as the integrity of the stboot executable itself.

The trust policy configuration specifies two things, a fetch method and a signature threshold. The fetch method says if OS packages should be loaded from the network or from the initramfs. To accept an OS package to be booted, stboot requires multiple independent leaf signatures, controlled by the signature threshold. Refer to the trust policy and OS package documentation which describes this in more detail.

Fetching and verifying the OS package #

Getting the OS package starts with the URL or filename in the host configuration. The interpretation of this field depends on the fetch method configured in the trust policy. A successful fetch produces a ZIP archive, which must include an OS-package manifest file, and a “descriptor” that acts as a detached signature on the archive. The format of the descriptor and the manifest can be found in the OS package documentation which is defined separately.

When the fetch method is initramfs, the OS-package pointer is interpreted as a base filename. Appending “.json” gives the descriptor file, and appending “.zip” gives the archive file. Both files have the directory name “/ospkg/” prepended. The URL inside the descriptor is ignored in this case.

When the fetch method is network, the OS-package pointer is interpreted as a comma-separated list of URLs, which stboot tries to use one at a time. Each URL provides the location of a descriptor file. A URL field in the descriptor specifies the corresponding archive file, which is also downloaded. If either download fails, or parsing the descriptor fails, stboot continues to the next URL. It is mandatory to configure at least one trusted root certificate for establishing secure HTTPS connections. These root certificates are found in the trust policy.

The descriptor is a JSON file containing a list of certificates, and a list of signatures on the ZIP file. In the case of network fetch, it also includes the URL of the archive file.

Next, stboot verifies signatures. For a signature to be valid, it must correspond to a public key in one of the descriptor’s certificates, and that certificate must be signed by the root certificate configured in the trust policy. There must be no duplicate keys in the list of certificates, and no more than one signature for each of the keys. Refer to the trust policy and OS package documentation for details.

If the OS package ZIP archive passes the signature checks, stboot examines the contents of the archive. The archive must include a file manifest.json. This file specifies the location of kernel image and initramfs inside the archive, as well as a kernel command line.

Booting the OS package #

The kernel image, initramfs and command line are passed to the kexec system call.

Error handling #

Fatal configuration errors, e.g., missing or invalid trust policy, results in a reboot after a short time delay. If no provisioning OS package is available, the same applies if any later step of the boot process fails (e.g., network errors, missing signatures, failed kexec).

If a provisioning OS package is available, error handling is more forgiving. If no host config is found, stboot unconditionally boots the provisioning OS package. If a host config exists but booting according to that config fails, or if stboot is interrupted by pressing the Ctrl-C key, stboot asks the user on the console if they want to boot the provisioning OS package. If the user responds by pressing enter (or types a line starting with “y” or “Y”), stboot boots the provisioning OS package. If the user responds differently, or if there is no response within 30 seconds, the system is rebooted in the same way as if there were no provisioning OS package.

A provisioning OS package typically gives the console user full control over the system. Hence, including a provisioning OS package may be an avenue for attack, depending on the system’s environment and threat model. Concretely, if the stboot image includes a provisioning OS package, and an attacker can get console access, then the attacker could exploit the provisioning fallback to run arbitrary code. This type of attack bypasses early boot protection measures such as a BIOS password and custom secure boot keys.

Features under consideration #

Support for non-UEFI systems #

Support for systems with coreboot will likely be added in a later release. Support for non-x86 systems is also desirable.

Using TPM (Trusted Platform Module) measurements #

For machines that have Trusted Platform Module (TPM), the System Transparency project aims to use boot time measurements and remote attestation to limit the damage from attacks that interfere with the firmware’s boot process, and makes the firmware execute rogue code before stboot gets in control of the system.

Transparent logging #

The System Transparency project aims to extend the signature checks to require transparent logging of OS package signatures, to enable detection of key compromise or other misuse of the signing keys.