Tamper-Evident Boot with Heads
Learn about how the cutting-edge, free software Heads project detects BIOS and kernel tampering, all with keys under your control.
Disclaimer: I work for Purism, and my experience with Heads began as part of supporting it on Purism's hardware. As a technical writer, I personally find ads that mask themselves as articles in technical publications disingenuous, and this article in no way is intended to be an advertisement for my employer. However, in writing this deep dive piece, I found that mentioning Purism was unavoidable in some places without leaving out important information about Heads—in particular, the list of overall supported hardware and an explanation of Heads' HOTP alternative to TOTP authentication, because it requires a specific piece Purism hardware.
Some of the earliest computer viruses attacked the boot sector—that bit of code at the beginning of the hard drive in the Master Boot Record that allowed you to boot into your operating system. The reasons for this have to do with stealth and persistence. Viruses on the filesystem itself would be erased if users re-installed their operating systems, but if they didn't erase the boot sector as part of the re-install process, boot sector viruses could stick around and re-infect the operating system.
Antivirus software vendors ultimately added the ability to scan the boot sector for known viruses, so the problem was solved, right? Unfortunately, as computers, operating systems and BIOSes became more sophisticated, so did the boot-sector attacks. Modern attacks take over before the OS is launched and infect the OS itself, so when you try to search for the attack through the OS, the OS tells you everything is okay.
That's not to say modern defenses to this type of attack don't exist. Most modern approaches involve proprietary software that locks down the system so that it can boot only code that's signed by a vendor (typically Microsoft, Apple, Google or one of their approved third-party vendors). The downside, besides the proprietary nature of this defense, is that you are beholden to the vendor to bless whatever code you want to run, or else you have to disable this security feature completely (if you can).
Fortunately, an alternative exists that is not only free software, but that also takes a completely different approach to boot security by alerting you to tampering instead of blocking untrusted code. This approach, Heads, can detect tampering not only in the BIOS itself but also in all of your important boot files in the /boot directory, including the kernel, initrd and even your grub config. The result is a trusted boot environment with keys fully under your own control.
In this article, I describe some of the existing boot security approaches in more detail, along with some of their limitations, and then I describe how Heads works, and how to build and install it on your own system.
Why Boot Security Matters
To understand why having a secure boot process matters so much, it's useful to understand one of the most common threats on a Linux system: rootkits. A rootkit is a piece of software attackers can use to exploit vulnerabilities in the kernel or other software on the system that has root privileges, so it can turn normal user-level access into root-level access. This ability to escalate to root privileges is important, because although in the old days, all network services ran as root, these days, servers more often run as regular users. If attackers find a flaw in a network service and exploit it so they are able to run commands locally, they will only be able to run those commands as the same user. The rootkit allows them to turn those local user privileges into root privileges, whereby they then can move on to the next step, which is installing backdoors into your system, so they can get back in later undetected.
Although sometimes attackers will install a backdoor that just has a service listening on an obscure port all the time, kernel backdoors are preferred because once they exploit the kernel, they then can mask any attempts by your OS to detect the attack. After all, if you want to know what files are in a directory, or which processes are running, you have to ask the kernel. If you can exploit the kernel, you can hide your malicious processes or files from prying eyes. Many rootkits also will set up a kernel backdoor for attackers automatically as part of the automated attack.
Rootkits aren't only a threat on servers; it's just that servers are accessible on the network all the time, and they run software that listens for requests. Although modern Linux desktop installs don't have any services listening on the network, there still are plenty of ways for attackers to launch code locally as your user—via the web browser is one of the most common ways, and malicious file attachments in email is another.
The whole point of a rootkit is to make it difficult for you to detect it from the running OS, but you still always can boot the system from a live USB-based OS and examine the hard drive. Or, you could re-install the OS completely and be rid of the threat. Yet even in that case, you are relying on the BIOS to boot your live USB-based OS. Your BIOS is the first code your CPU executes when it boots. Once it loads, it detects the hardware on your system, initializes it, and then lets you boot either from an internal hard drive or perhaps from external USB or DVD media. If attackers were able to modify your BIOS, in theory, they could just re-install their backdoor in any kernel it loads and persist even with re-installing the OS or examining it from a live USB disk.
The BIOS then becomes the root of trust for the entire rest of the system. Until you can trust it, you can't fully trust the rest of the code that executes after it.
Next I describe some of the current approaches to secure the boot process, all of which involve executing only pre-approved code.
Other Boot Security Methods
It's easier to understand how Heads works, and how it is different from the existing approaches, once you understand how the existing approaches work. The main two approaches that provide boot security on modern systems are UEFI Secure Boot and Intel Trusted Boot.
UEFI Secure Boot
Of all of the different approaches to secure the boot process, UEFI Secure Boot is the most popular, and it's included in just about every modern laptop and desktop you would buy. The way that Secure Boot works is that the UEFI flash chip contains certificates for Microsoft and its approved third-party vendors. UEFI boot firmware that works with Secure Boot contains a signature created by the private keys of either Microsoft or its approved vendors. Secure Boot then checks that signature against its certificates, and if the signature matches, it allows the boot firmware to execute. If the signature doesn't match or is missing, Secure Boot will not allow it to run.
Because it was initially designed for Windows, and initially Windows was the only OS that used it, Secure Boot often is thought of as a Microsoft-only technology, and many in the FOSS community spoke out against it because of the risk that it could be used to lock out a system from loading Linux. It's true that initially you could use Secure Boot only with Windows, but Linux distribution vendors like Red Hat and Ubuntu worked with Microsoft to get a boot "shim" signed that would allow them to load GRUB and boot their OSes.
Of course, there still are plenty of Linux distributions that haven't gotten boot shim code signed by Microsoft, including Debian. This means that if you want to install Debian on a system with Secure Boot, you first must go into your UEFI settings and disable Secure Boot entirely before you are allowed to boot the USB installer—that is, if your UEFI software allows you to disable Secure Boot. Some lower-cost computers these days ship with stripped-down UEFI firmware that allow only a very minimal level of configuration, and on these systems, Secure Boot often is no longer optional.
Secure Boot does have a mechanism that would allow you to replace the existing vendor certificates with your own, and that might be an option for Linux users who want to use Secure Boot on systems that don't use Microsoft-signed boot firmware. The process itself is somewhat complicated though, and the end result would boot your own custom-signed code but then would lock out anything not signed with your own signatures, such as a typical USB OS installer. Again, this is an option only if you first can disable Secure Boot to load your untrusted OS and modify UEFI, or else attempt the modification from a trusted OS.
Intel Trusted Boot
Along with Secure Boot, modern Intel computers also have the option of a security mechanism called Intel Trusted Boot. This mechanism takes advantage of the special capabilities of the Trusted Platform Module (TPM) chip on a system. The TPM is a standalone chip available on some motherboards that can act as its own Hardware Security Module (HSM) by generating its own cryptographic keys and performing cryptographic operations on-chip independent of the system CPU. The TPM also contains Platform Configuration Registers (PCRs) that can contain measurements of executed code in the form of a chain of hashes. Generally, different PCRs are used to store measurements of different phases of the boot process.
Intel Trusted Boot works by sending the measurements of code as it is executing over to the TPM where it is hashed and stored in a corresponding PCR. As new code is executed, it also gets hashed and combined with hashes of previous code in the PCR. The TPM allows you to seal secrets (disk decryption keys are common) within it that are unlocked only if the PCRs contain previously stored values. Combined with Secure Boot, Intel Trusted Boot allows you to detect tampering in boot-time executables.
Secure Boot Limitations
Secure Boot is the main way vendors provide boot-time security on modern computers, but it has quite a few limitations. The first big limitation is also its biggest claimed feature—that it requires boot code to be signed by keys under the vendor's control. This means if you did happen to want to run custom boot code, you must work with vendors to get them to sign your binary or else replace all of their certificates with your own and run only your own code.
Another limitation is that although Secure Boot ensures that you are running code that has been signed, it doesn't ensure that you are running the same boot code that you ran previously. An attacker who was able to get access to one of the vendor signing keys could create a boot-time executable that would pass Secure Boot protections. What would happen to existing computers if one of the Microsoft (or other vendor) signing keys were leaked or forced to be shared with a nation state?
Secure Boot is also proprietary software, so you have to take vendors at their word that there are no backdoors within it, and you also have less visibility into what code might be signed. In addition, Secure Boot validates only executables. It can't validate your initrd files or GRUB configs—both places where attackers could add malicious changes. Ultimately, the issue with Secure Boot is that it takes control of your computer and its security out of your hands and into the hands of vendors. If you fully trust your vendor, perhaps you are fine with that trade-off, but many people would prefer to have full control over their own software and hardware.
Introduction to Heads
Heads was created by Trammell Hudson to solve some of the trust issues and other limitations of Secure Boot by replacing it with a system that focuses on detecting tampering instead of blocking it. The idea with Heads is to capture a stable, trusted state in the BIOS and boot code, and then ensure that at each subsequent boot, the BIOS and boot code haven't changed. Heads is written under a free software license, so it not only can be inspected, but it also is reproducibly built, so if you were to get a pre-built Heads ROM, you also could build the same revision of Heads yourself and get the same result, thereby proving that the code wasn't tampered with at some point in the build process.
Heads loads from within the open-source coreboot BIOS (or optionally LinuxBoot for some server platforms) and is actually its own standalone Linux kernel and runtime environment that performs tamper checks and then boots into your system kernel once everything checks out. Unlike with Secure Boot, it detects tampering using keys that are fully under your control—keys you can change at any point.
Because Heads relies on coreboot or LinuxBoot, its current hardware support is somewhat limited to hardware that both supports either coreboot or LinuxBoot, has a TPM, and has someone who has defined that board's configuration, including coreboot settings and other options, and submitted it to Heads. Currently, that list is pretty small: Lenovo ThinkPad X220 and X230, the Purism Librem laptop line and a handful of servers.
How Heads Works
On the surface, Heads works similarly to Intel Trusted Boot in that it uses the TPM to verify measurements of itself to then unlock a secret. That's where the similarities end though, as Heads approaches boot security in a much different way, because its aim is to provide tamper detection, not tamper proofing. Heads will alert you to tampering, but it still provides you the ability to boot whatever software you want.
You can break down the default Heads boot process into a few main phases:
- The coreboot BIOS starts and loads the Heads kernel and initrd.
- As code executes, measurements are sent over to the TPM chip.
- Heads presents a TOTP/HOTP code to prove to the user that it hasn't been tampered with.
- The user selects a boot option.
- Heads checks all the files in /boot for tampering before loading the OS.
- If the files all check out, Heads boots the OS.
Heads uses two different sets of keys to detect tampering. First it uses a shared secret stored in the TPM and also on either a TOTP authenticator application on your phone or on a special USB security token like the Librem Key. This shared secret is used to prove the BIOS itself hasn't been tampered with. The next set of keys is a set of trusted GPG public keys within a GPG keyring that you add to the Heads ROM. Once you know the BIOS hasn't been tampered with, you can trust that the GPG keyring it has within it hasn't been modified to add an untrusted key. Heads then uses that trusted keyring to verify all of the signatures on the files in /boot. In both cases, these are secrets that are fully under your control, and you can change them and reset signatures at any point.
Next, let's look at more specifics of how Heads works by focusing on each of these two secrets and how they are used in their respective parts of the boot process.
Boot Security and the TPM
The very first thing Heads must do is prove to you that it can be trusted and that it hasn't been tampered with. The challenge is, if it has been tampered with, couldn't it lie to you and tell you everything is okay? This is where the TPM comes in. When you first set up Heads, you go through a process to reset the TPM and set up a new admin password (called taking ownership), and then Heads will generate a random secret and store it in the TPM (called sealing) along with the current valid measurements it will take to unlock that secret.
Once the secret is sealed in the TPM, Heads will convert that secret into a QR code and display it on the screen, so you can scan it with your phone to add it to your TOTP authenticator application of choice (FreeOTP is a free software option that works on Android, for instance). If you have added Librem Key support into Heads, you also can store a copy of the secret onto Purism's Librem Key USB security token.
When Heads boots, it then sends measurements of the code it executes over to the TPM. If the BIOS has been tampered with, those measurements won't match what was there before, and the TPM will not unlock the shared secret. In that case, Heads will output an error to the screen alerting you to the problem. If the measurements do match, the TPM will unlock the shared secret, send it to Heads, and Heads will combine the secret with the current time to convert it to a six-digit TOTP code that it will display on the screen. You then can compare that code to the six-digit TOTP code in your phone's app, and if they match, you know that the secret was valid. Alternatively, if you enabled Librem Key support, you could insert the device at boot, and Heads would generate a six-digit HOTP code and send it over USB. If it matched the code the Librem Key generated on itself, the Librem Key would blink green; otherwise, it would blink red.
Figure 1. The Default Heads Boot Screen
So if an attacker modifies the BIOS, the TPM will generate an error, but what if the attacker then resets the TPM with a different secret using the measurements from the tampered BIOS? Those measurements would match, and the TPM would unlock the new secret, but that secret would generate a different six-digit code from what your phone or Librem Key would generate, and you would know something suspicious was happening. Because the TPM is designed to be a tamper-proof device, you cannot extract the shared secret from it without providing valid measurements. If you reset the TPM, that secret is also erased.
Boot Security and GPG Keys
Once you have verified that the BIOS is trustworthy, you can move on to booting your OS. But before Heads will boot into the OS, it first checks all of the files within the /boot partition to make sure they haven't changed from when you last signed all of them. When you first set up Heads, you add one or more public GPG keys to a keyring within the Heads runtime environment. Heads provides a mechanism not only to add GPG keys to a standalone Heads coreboot ROM file, but you also can add them to the running BIOS. In that case, Heads actually will pull down a copy of the running BIOS, modify it on the fly, and then reflash it.
Once you have a set of trusted GPG keys in the Heads keyring, you then can sign the files within /boot with your corresponding GPG private key using the Heads GUI. Heads will create a file containing sha256sums for all of the files within /boot and then sign that file with your GPG private key and store the signature in /boot as well. This will require that you have some kind of USB security token that has OpenPGP smartcard support, and Heads will prompt you to insert your USB GPG key whenever you sign these files.
When you tell Heads to boot into your OS, it first gets flashed into the BIOS, and it can read your GRUB config file and provide you with a boot menu based on the options in that config file.
Also, whenever you update or add a new kernel, change an existing initrd file, or modify your GRUB config, Heads will detect the change, showing you an error at the next boot. Along with that error will be an option to re-sign all of the files in /boot, in the case that you changed the files yourself. If you didn't expect those files to change, of course, then this could be a sign of tampering!
Building and Installing Heads
Heads is reproducibly built, which means that it's designed so that if multiple people were to build the same specific release of Heads with the same build options at different times on different systems, they should get the exact same binary. Because Heads runs on specific BIOS chips, it needs to cross-compile the kernel and other software for that platform, which means that in addition to building a complete Linux kernel and coreboot, you also will need to build a cross-compiler and supporting tools when you build Heads.
Your local system also will need certain system libraries so you can build coreboot and Heads. On a Debian-based system, you can use apt to install them:
sudo apt install git build-essential bison flex m4 zlib1g-dev ↪gnat libpci-dev libusb-dev libusb-1.0-0-dev dmidecode ↪bsdiff python2.7 pv libelf-dev pkg-config cmake
For other systems, use your packaging tool to install the equivalent packages for your platform. Once those are installed, the next step is to get the most recent Heads source code and go into the root of that build directory:
git clone https://github.com/osresearch/heads.git cd heads/
The next step is to pull down any binary blobs your board might need
for coreboot to boot. Go to the blobs/ directory inside Heads, and see
if your board has a directory represented in there. If so,
cd to it
and read the instructions for how to pull down your binary blobs for
coreboot. For instance, on Librem hardware:
cd blobs/librem_skl/ ./get_blobs.sh
Once you have gotten any blobs you may need, move back to the root of
the Heads build directory. From there, you will see a boards/ directory,
and within it are directories for each of the motherboards that Heads
supports. Each of those boards has a corresponding configuration file
inside its respective directory that set important options, such as what
partition to use for /boot and for USB boot devices, what kernel options
(if any) to pass along to the OS when it boots, and which init script
to load into. These configuration files are already set up for the most
part to work with the corresponding motherboard, but you should review
the configuration file for your board and confirm in particular that
CONFIG_USB_BOOT_DEV variables are pointing to
the correct /boot and USB boot device, respectively.
Once you are finished editing the configuration file, it's time to build
Heads. Change back to the root of the Heads source code and set the
particular board with an environment variable while running the
command. So for instance, to build for a ThinkPad X230, you would type:
The first time you build Heads, it will take quite a long time! Just be patient as it builds GCC, coreboot, the Linux kernel and a number of other pieces of software. Subsequent builds will be a lot faster. If the build fails at some point in the process, make a note of what package it was attempting to build, and then check the corresponding build log for that software inside the logs/ directory. More often than not, if you see a build failure for a particular piece of software, it's because you are missing a development library on your system. Reviewing the log file should tell you which libraries are missing.
Once Heads completes the build process, it will dump the corresponding coreboot ROM image into boards/<boardname>/coreboot.rom, so in the case of the above X230 example, it would be in boards/x230/coreboot.rom. You now are ready to install Heads as your BIOS by flashing that ROM image.
Once you have built your Heads coreboot ROM, the next step is to flash it over the top of your existing BIOS. How you flash Heads on your computer will vary depending on the specific motherboard you have for a number of reasons. First, each laptop uses its own set of flashrom options that are specific to the BIOS chip it has on board, so you will need to reference the flashrom options appropriate for your board. Check out the initrd/bin/flash.sh script from within the Heads code base for an example script that provides flashrom options for the supported boards. Note that this script is designed to be run from within the Heads environment itself with a relatively new version of flashrom (1.0 or above). Older flash chips (like on the ThinkPad boards) should work with older flashrom versions that you should be able to install via a package on your current Linux distribution, but newer boards (like on the Purism Librem laptops) will require a newer (1.0) flashrom program. In the latter case, Purism provides instructions here to pull down and build a current flashrom.
Another reason that flashing Heads varies for different platforms is that although you can update coreboot from within your own operating system using flashrom if it is already installed, if coreboot isn't already installed, some laptops require an initial hardware flash. For instance, unless you bought it from a special vendor, the Lenovo ThinkPad laptops come with a proprietary vendor-provided BIOS instead of coreboot, so they require an initial hardware flash to overwrite the vendor BIOS. This hardware flash means opening the laptop to expose the BIOS chip, connecting a Pomona clip to it that's attached to one of the many hardware platforms that support flashrom, such as a Raspberry Pi or Beaglebone Black. I cover these steps, including how to back up the existing BIOS, in a past Hack and / article: "Flash ROMs with a Raspberry Pi".
Figure 2. Hardware Flashing a BIOS with a Raspberry Pi
If your hardware already has coreboot installed, you should be able
to install Heads purely from software by running flashrom from within
the native OS. For instance, the Purism Librem laptops come with coreboot
already installed (and plan to offer Heads as a pre-installed option
in the near future), so you can use flashrom from within the regular
operating system to flash the Heads BIOS without opening the machine. In
this case, you will want to run flashrom first with the
option, so it
will pull down a backup of your existing BIOS to store on a USB thumb
drive in case you ever want to revert back.
Once you have flashed Heads for the first time and rebooted, Heads will guide you through the initial setup. First you'll be prompted to add at least one public GPG key to the Heads keyring, which will require that you have the public key on some sort of USB thumb drive ending in .asc. Heads will mount the USB drive and find all possible .asc files on the device and then prompt you as to which of them you want to add. Once you have added the key, Heads will reflash the BIOS and reboot.
Once Heads reboots with GPG keys in place, it will get a TPM error, because the TPM has not yet been set up, so it will guide you through setting up a password for your TPM and creating the initial TOTP/HOTP secret. After it reboots another time, you finally should see the default Heads boot menu that lets you select between your default boot option (not yet configured) or opening an Advanced menu of options.
If you select default boot with no default boot option set, it will detect that state and guide you through selecting a boot option. At that point, it also should detect that you have not yet signed any files in /boot, and it also will guide you through that process (you will need your USB security token containing your GPG private keys at that point).
Once all of the files have been signed and your default boot option has been set, you should be able to treat Heads much like a regular GRUB menu—boot the computer, confirm there are no alerts and just press Enter to boot into your default OS. Note that as you update software on your underlying OS, if your package updates change or add any files to the /boot directory, you'll get an alert the next time you reboot that files may have been tampered with. If you know that this was caused by your package update and not something malicious, you can just re-sign all of the files in /boot with your private GPG key.
You can apply updates to Heads completely within the Heads menu. Within the Advanced options menu is a submenu that allows you to flash the BIOS. Within this menu, you can insert a USB drive containing *.rom files and have Heads flash them over the top of your current Heads ROM. There are two main flashing options: flash a ROM and flash a cleaned ROM. The first option is pretty self-explanatory, but in the case of a cleaned ROM, Heads will flash the BIOS, but it won't copy over any existing GPG public keys or other custom changes you may have made to Heads on top of the default ROM. Use this option if you ever want to revert back to a pure factory state (or flash some other non-Heads BIOS), and otherwise use the default flashing option to copy your keyring to the updated Heads ROM.
Although installing and using Heads is not for the faint of heart, if you have experimented with coreboot on systems in the past, it's not that much more complicated. If you want the best in boot security, the effort is definitely worth it, as you will end up with a system that can alert you you both to BIOS and kernel-level tampering but with keys completely under your control.