Using the vulnerability described in this advisory an attacker may take control of an encrypted Linux computer during the early boot process, manually unlock TPM-based disk encryption and either modify or read sensitive information stored on the computer’s disk. This blog post runs through how this vulnerability was identified and exploited - no tiny soldering required.
This is an exploration of a real security vulnerability we discovered and exploited while working with one of our clients. This vulnerability can be used to gain local root access to a TPM-protected Ubuntu 20.04 Linux computer if it uses RedHat’s Clevis and dracut software to implement unattended unlocking for LUKS full disk encryption. This configuration is desirable when a computer needs to have disk encryption but still allow remote reboots without having someone manually unlock it afterwards. Under normal circumstances, all an attacker who turns up at the encrypted computer would see is a login prompt with no way to gain direct access to the system.
When you turn on your encrypted Linux computer you probably have to enter two passwords before you can use it: first you have to enter the disk encryption password to unlock LUKS (Linux Unified Key Setup) and then you need to enter a user’s password to log in to the unlocked system. This is mildly annoying when the computer is in front of you but a genuine obstacle if you want to encrypt a server or an appliance device in a remote location where someone won’t be present to type in the password every time the computer reboots.
When we discovered this vulnerability, our client was developing an appliance that needed to operate in a potentially hostile environment and reboot periodically without user interaction. This article outlines one method an attacker might use to read the encrypted contents of the computer’s disk or sneakily modify them. This could allow you to steal secrets or plant a cool backdoor!
You might wonder how Microsoft manages to have disk encryption that allows Windows to start up partially and only then requires the user to enter their personal password once? The answer to this and unattended Linux encryption is a Pulse Security favourite: the TPM or Trusted Platform Module. This tiny, hardened computer inside the bigger computer can be used for a variety of tasks requiring a trusted third party - such as automatically supplying the password for an encrypted disk during startup. This requires the TPM to have some knowledge of how to decrypt or unlock the disk encryption password. Bus sniffing attacks are possible when a discrete TPM chip is used, but with firmware-based TPMs (also sometimes called fTPMs) this attack is not always possible. A solution that can be performed entirely in software is preferable and increases the exploitability of the attack.
Generally, a Linux computer using TPM-protected unattended disk encryption will still allow a user to view the output of the boot process and optionally manually enter a decryption password with the keyboard. This allows for situations where the computer fails to boot and needs someone to troubleshoot the startup process. While the unattended TPM unlocking is taking place, the user is still presented with the password prompt and an opportunity to enter input.
Here’s a photo of a laptop running Ubuntu 20.04 waiting for someone to type in a disk decryption password:
Faced with this, we should really perform some fuzzing to see if we can cause unexpected behaviour that might be useful. Successful fuzzing often involves supplying input to a program that the program doesn’t anticipate. In this case, the program is expecting us to provide input using the keyboard. This raises the question of what kinds of input might be unexpected in this situation.
In 2016 a vulnerability was disclosed in Debian’s cryptsetup startup script allowing an attacker to simply hold down the Enter key and gain root access to the early boot environment of an encrypted Debian computer. I wondered if something similar might still be possible in this scenario.
There’s a limited window of time before the TPM will unlock the disk and the boot process will proceed automatically to the login prompt, so how can we effectively fuzz this input opportunity? What if we could type faster than a human being? Using an Atmel ATMEGA32U4 microcontroller (such as you’d find in an Arduino Leonardo development board) we can emulate a keyboard that sends virtual keypresses at essentially the maximum rate that the computer will accept. The following short Arduino program sets up a Leonardo as a keyboard emulator:
One second after being plugged in this program begins to simulate pressing the Enter key on a virtual keyboard every 10 milliseconds. This is about 10x faster than the usual keyboard repeat rate you’d get simply holding down a key, and Linux seems to recognise around 70 characters per second using this method, or one keypress approximately every 15 milliseconds.
Sending keypresses this fast quickly hits the maximum number of password entry retries, while keeping the system from unlocking the disk automatically due to password guess rate limiting, and systemd eventually gives up trying to unlock the disk. It takes a minute or two but the recovery action in this failure scenario is to give us a root shell in the early boot environment:
From here it’s easy to manually use the TPM to unlock the disk with the Clevis tooling and mount the root volume for hacking (it takes a few tries sometimes, but it gets there in the end):
Why does this happen?
dracut relies on systemd to unlock LUKS-encrypted disks in the early boot environment. An “agent” plugin architecture is used by systemd for disk encryption where a request for a system-wide password can be answered by a pool of different responders, including a script automatically supplying a password and the usual method of just “asking the user”. Clevis implements a systemd password agent for unlocking, but the user may also enter a password using the normal interactive agent. Both of these agents are responding to the same request issued by systemd.
Unfortunately, in this case exposing a password prompt to the user gives us an attack surface to influence the behaviour of the early boot process.
It’s a little bit unclear whose fault this problem is exactly, it sits at an unfortunate intersection of a number of different design decisions and implementations. I did find that you can sometimes exploit this issue if you mash the Enter key really fast yourself too, so a keyboard emulator isn’t necessarily required either.
The simplest way to address the most immediate problem: Add
rd.emergency=reboot to the kernel command line. This ensures that if anything fails during the early boot process the computer will reboot immediately rather than dropping into a root shell.
There are some wider questions to consider here but they’re a lot harder to fix. In an ideal world any deviance from the expected execution flow during early boot would result in the TPM refusing to unlock the disk. While PCR extension in TPMs is good for checking if certain specific things did or didn’t happen, and that general platform configuration matches expectations, it isn’t a great defence against the computer just doing something else entirely that doesn’t interact with the TPM. In this case the TPM has no way to know that the early boot environment has dropped to a shell and can’t tell the difference between the boot script asking for the disk unlock secret and an attacker with their hands on the keyboard.
Microsoft avoids some of this problem by unlocking the disk encryption key very early in the boot process and immediately extending PCR11, meaning that by the time a user gets any kind of interactive access to the computer they can no longer do their own unlock.
Physical access to a computer is still hard to secure. There are a surprising number of edge cases that must be navigated to successfully use boot-time TPM PCR measurement for unlocking secure unattended disk encryption. And don’t forget - always fuzz the user input, even if it’s just a keyboard.
Prior Art and Additional Resources: