With the latest PureBoot R19 pre-release we have added a number of new changes including improved GUI workflows and new security features and published a ROM image so the wider community can test it before it turns into the next stable release. To test it, existing PureBoot users can download the R19-pre1 .rom file that corresponds to their Librem computer and flash it like any other PureBoot release.
In this post I want to highlight a new experimental security feature we added in this release that will extend the tamper detection PureBoot already does with the boot firmware and the /boot directory into the main root file system. This will allow you to detect attacks that modify system binaries (like /bin/bash) with backdoored versions. I also want to give some background on this feature and my thought process behind it so people understand where I’m coming from and why I made the design decisions I did.
Currently, PureBoot detects tampering starting with the boot firmware itself, and then to files in the /boot directory, so you can detect rootkits that modify the kernel, initrd, or GRUB config files. With PureBoot in place, the chain of trust starts with the first code your system runs and into the kernel for your particular Linux distribution.
I’ve been thinking a long time about how to extend tamper detection from /boot into the main file system. I wanted to give people the ability to test their system for tampering from within some sort of trusted environment, and there were a few approaches we could take to do it.
One of the more advanced tools out there to detect file system tampering is dm-verity which was initially designed by the ChromeOS team for use in ChromeOS to extend boot verification into the OS so it would only load approved binaries and files in general. This module exists in the kernel and once enabled will check a file on demand against its known-good hash as the file is accessed. If the hashes don’t match, the IO fails.
While this is certainly an approach that could work, it also requires significantly more effort on the user side, major changes within the OS, and just in general adds more complexity. I’m not saying it’s a bad approach, just that it’s one that would take more time to implement, as well as only be available for people running PureOS (since that would be where we would start development, it would be up to other distributions to mimic our approach). For those reasons I decided against it.
My initial plan was to extend the chain of trust over to the initrd file (the initial root disk that the kernel uses to bootstrap the system and detect the actual root disk). With this approach we would have root hash verification occur inside the initrd after the kernel takes over and initrd unlocks the root file system, but immediately before pivot_root changes over to it.
There are some advantages to the initrd approach, but the primary disadvantage is that the reliance on initrd makes it distribution-specific. This is something I ran into when developing the Librem Key LUKS unlock feature for PureBoot. We’d develop a script that would work only with PureOS and hope that we might upstream it some day at least into Debian. Since other distributions handle initrd updates different from the Debian ecosystem, people would have to adapt our effort to all the different initrd update approaches out there.
Another idea I had was to create a limited, trusted live USB disk that contained the appropriate scripts and then a user could boot into that and perform their checks from that trusted environment. This would work but it means either maintaining a separate ISO just for that process or otherwise incorporating it into our PureOS ISO, all just to run commands we already had in PureBoot–a trusted environment. So I decided against the live disk approach.
An alternative to the initrd and live disk approaches is to handle everything from PureBoot. With that approach, root hashes are initially generated from within PureBoot and stored in /boot alongside (but in a separate file to) the /boot hashes. I like this approach because PureBoot is already a limited and trusted environment, so the underlying system is limited in its ability to interfere with scanning run from here. This also has the benefit of being distribution-agnostic, so you can use this with PureOS, Qubes, or any other distribution you already use with PureBoot.
By default we don’t automatically scan files on the root disk, because depending on the disk it might take a minute or more. Instead it’s something a user can choose to do at any time from the PureBoot menu whenever they perform system updates, or whenever the hardware was outside of their custody.
The normal, manual workflow would look something like this:
In the case of PureOS, since we default to using Packagekit for software updates, the system will automatically reboot to a more limited environment to apply updates so this workflow is a bit simpler to manage.
We also added the option (off by default) to automatically scan root at each boot. This is something you can change from the same PureBoot menu you use to change other PureBoot configuration settings. We turn root scanning off by default due to the dramatic increase in boot time, but if you turn it on, then at each boot, after PureBoot verifies the files in /boot haven’t been tampered with, it moves on to prompt you to unlock your root disk and then performs scanning there. It will alert you to any changes and allow you to re-sign them (in the case they are the result of software updates) or otherwise you can abort the boot process and investigate.
Currently the way that PureBoot scans the /boot file system for tampering, it only detects changes to existing files it has hashed previously. It doesn’t detect the addition of a new file to that directory. This is less of a concern within the /boot directory since successful attacks there would need to modify existing files.
We currently use the same approach for scanning the root file system. This means that you would be able to detect an attack that modifies existing files (like /bin/bash or /bin/ls) but not an attack that added a new file to the file system. Unlike with /boot, there are many more types of rootkit attacks against the main root file system beyond just replacing existing files. There are a number of services that run as root that automatically load files they find in well-known directories.
We also only scan /bin, /boot, /lib, /sbin, and /usr by default (but that’s configurable). We don’t scan /home or /var directories by default, since files in those directories are changed frequently and we don’t scan /etc by default because systems like cups modify files in that directory at each boot, creating false alarms.
One trivial attack would be to add a file to /etc/sudoers.d/ that grants a user (or all users!) passwordless root access, and another would be to add a new service that runs as root the next time the system boots. This current implementation also would not protect you from attackers who modify files in a user’s home directory (such as ~/.ssh/authorized_keys) or crontabs in /var.
Security is like golf. You should try to get closer to the hole (a secure system) with each stroke. You’ll probably use different clubs along the way. Some people only swing for holes in one and spend most of their time no closer to the hole.
So while there are still plenty of avenues for attack on a system, and we certainly don’t think adding this feature stops all possible tampering, having the ability to scan existing system files for tampering does help detect a common class of attack and gets us closer to the goal of a secure system. Just like we extended tamper detection from /boot into parts of root, we hope to extend the types of root tampering we can detect with future releases. Once we feel good about the stability of this feature and its implementation, we also want to submit it upstream it into the main Heads project.
If you are interested in helping us improve this feature, please try out our latest R19-pre1 PureBoot release. Bug reports and merge requests are welcome!