Jun 12, 2024

Arch Linux with UKI & Secure Boot

The Linux boot sequence can be seen as a bit convoluted, as multiple options exist, and the more recent advent of UEFI has shuffled this space again. While GRUB is still a very popular option, especially since it is compatible with both traditional BIOS boot and more recent UEFI firmwares, it is not the only one.

But the step of going through a third party software to boot your Linux kernel is not a compulsory step anymore. Linux kernels are able to boot as standalone UEFI images for a while now, and so as long as you had a firmware capable of providing your boot arguments, you could bypass the bootloader stage. Even more recently, Linux now has something called UKI, or “Unified Kernel Image”. It is a way of taking a Linux kernel, its initramfs, and its command line, and make it a truly standalone bootable EFI image that can be simply booted from anything capable of booting EFI images.

Arch Linux is a generic distribution geared towards tinkerers. It notably has no defaults in regard to its bootloader. And it fully supports (albeit with a bit of tinkering) booting from an UKI image.

Prerequisites

We are going to make an Arch Linux installation boot from UEFI. This can be done either from an existing installation, or during the initial installation process. The only true requirement is to be on an UEFI-compatible machine and booting into this mode. Of course, this also means having an UEFI boot partition. In this article, I have mounted mine at /boot/efi, although some installs mount it directly into /boot. It does not matter much, but you might have to correct some paths if it is the case for you.

This guide will also show how to make it compatible with Secure Boot. Secure Boot is a bit of a problematic feature in the Linux community, and some people prefer to leave it out. I am not going to argue for or against it, I am just presenting how to make it compatible, but this part is completely optional. However, if you want to go with Secure Boot, you need to boot your machine with Secure Boot enabled and in Setup mode.

Arch Linux now provides multiple choices for the initramfs. mkinitcpio has been the historical and still default choice today, but it is also possible to use dracut or booster. dracut supports making an UKI image directly, and there is systemd-ukify that can make an UKI image from separate kernel and initramfs. However, this article will focus on mkinitcpio.

Making the installation

mkinitcpio

On your existing system, or on a system at the bootloader phase of the installation, we will need to configure mkinitcpio to write to an UKI image. Ensure the mkinitcpio package is installed. Then, we will edit the preset file for mkinitcpio, located at /etc/mkinitcpio.d/linux.preset (or a similar name depending on your kernel flavour of choice like linux-lts.preset). Disable the classical initramfs generation by commenting the default_image and fallback_image options, then enable UKI generation by uncommenting the default_uki and fallback_uki options. Ensure the paths are correct. For a UEFI boot partition mounted at /boot/efi:

default_uki="/boot/efi/EFI/Linux/arch-linux.efi"
fallback_uki="/boot/efi/EFI/Linux/arch-linux-fallback.efi"

Finally, mkinitcpio expects the parent folder to already exist which is not by default the case. Use mkdir to ensure the folder is created.

Command Line

In a traditional Linux setup, the kernel options (or command line) is stored and passed by the bootloader, and usually configured there. Here, we have no bootloader, but still need to provide the command line options.

The UKI build process assemble the command line from files in /etc/cmdline.d. You can create .conf files in this folder containing command line arguments (which also allows you to group related arguments together). Simplest example is to create a /etc/cmdline.d/root.conf with the root configuration:

root="/dev/vda2" rw

Of course, adapt it to your configuration and needs.

Building the image

Now that things are configured, you can just build the image like you do traditionally:

mkinitcpio -P

The process should end without errors, and you should find the new files at the location indicated above.

Registering

UEFI boot programs are normally registered into the firmware settings as boot entries. If we want to boot our kernel, we need to register our kernel in the firmware with efibootmgr. First, ensure the efibootmgr package is installed.

efibootmgr --create --disk=/dev/vda --part=1 --label="Arch Linux" --loader='\EFI\Linux\arch-linux.efi'
efibootmgr --create-only --disk=/dev/vda --part=1 --label="Arch Linux Fallback" --loader='\EFI\Linux\arch-linux-fallback.efi'

Note that the second command for the fallback image is created with --create-only instead of --create. This way, it will not be booted automatically, but still be available as a boot option.

Rebooting

Everything is set up! Now, you can finalise the installation steps (if applicable) and reboot your machine, then check that your system boots directly into Arch Linux. You can also trigger the UEFI boot menu to check for the additional entry for the fallback image.

Congratulations! You now boot entirely through your UKI image.

Secure Boot

As said above, this step is optional, if you do not want to use Secure Boot.

Secure Boot is an addition to UEFI that ensures that your boot sequence has not been tampered with, by requiring all boot images to be signed with a valid certificate. By default, machines have two Microsoft-provided certificates, one for Windows, one for third party systems, the latter being used by some distributions to provide some level of support to Secure Boot. You can also register your own certificate into the machine to sign your own binaries, which is what we are going to set up here. Once setup correctly, the signing process is automatic.

Install the sbctl package. Ensure that your machine has Secure Boot is in setup mode by issuing sbctl status:

Installed:      ✓ sbctl is installed
Owner GUID:     00000000-0000-4000-0000-000000000000
Setup Mode:     ✗ Enabled
Secure Boot:    ✗ Disabled
Vendor Keys:    none

Note: sbctl might also indicate that it is not installed, but this will correct itself in the next steps.

If it is not, you will have to reboot your machine into the firmware interface to enable it. Some firmware will go into Setup mode by choosing to erase all certificates.

sbctl is a nice command to simplify the process of dealing with Secure Boot. To create a certificate and record it into our firmware, we issue the following commands:

sbctl create-keys
sbctl enroll-keys -m

-m tells sbctl to also insert the standard Microsoft certificates, which allows to boot Windows. Even if you plan on never booting Windows, some systems might have UEFI-drivers to load on boot that use one of these certificates. Not including them could break your boot sequence. You can technically do without, but be sure of what you are doing there.

Now that we have our certificates, we need to sign our UKI images. Fortunately, sbctl also includes a hook for mkinitcpio to do it for you. You should just have to regenerate images with mkinitcpio -P. Once this done, ensure it is all good with sbctl verify:

Verifying file database and EFI images in /boot/efi...
✗ /boot/efi/EFI/Linux/arch-linux.efi is not signed
✗ /boot/efi/EFI/Linux/arch-linux-fallback.efi is not signed

If you have static UEFI binaries, like the UEFI Shell or a third party bootloader like rEFInd, you will have to sign those too. They should be given to you by sbctl verify. For each binary that is to be signed, issue the following command:

sbctl sign -s /boot/efi/EFI/…

The -s flag tells sbctl to remember this path. In the future, if you modify any of those files, you can just issue sbctl sign-all to check and resign every known file.

Ensure that sbctl verify validates your boot files before rebooting! If you reboot without signing your files, your system will refuse to boot, now that the certificates are registered.

You can now reboot your machine to check that it still boots. Once you reboot, sbctl status should tell you that all is well:

Installed:      ✓ sbctl is installed
Owner GUID:     00000000-0000-4000-0000-000000000000
Setup Mode:     ✓ Disabled
Secure Boot:    ✓ Enabled
Vendor Keys:    microsoft

Bonuses

While this is enough for a functional setup, I have some personal tweaks to this that may be useful to you.

Automatic root decryption

Requires Secure Boot, a TPM, and a LUKS 2 encrypted root.

If your root partition is encrypted, you have to enter the passphrase at every boot. If you have a TPM, you can use it to automatically provide this passphrase to the system. While this seem like a downgrade in security, your system is normally still password protected at the login prompt, so your data should be safe, but it is your choice between more security and ease of use. However, it becomes dangerous if coupled with auto-login!

The boot process and the TPM will ensure that your system has not been tempered with. It means that it will compare Secure Boot certificates at boot and will refuse to use the saved credentials if they are modified, which is even stronger than the base Secure Boot check.

This does not save your passphrase in the firmware. It uses a different LUKS slot with its own data saved into the TPM. The safety of the passphrase itself is not affected.

systemd provides the systemd-cryptenroll command to make the process easy. First, check that your TPM device is detected:

systemd-cryptenroll --tpm2-device=list

If it is visible, you can then use it with your root partition:

systemd-cryptenroll --tpm2-device=auto /dev/vda2

Replace /dev/vda2 by your root partition physical device (the encrypted layer, not the /dev/mapper mapping). It should ask you for the current encryption passphrase before registering the new TPM slot.

Reboot, and you should not have to input your passphrase anymore.

Note: Security of this depends on what is allowed to boot on your machine. I would advise to lock down your UEFI settings with a password, including the boot menu if possible, making changing the default boot device impossible for an external person. Of course, they can reset the firmware with a physical access, but that would also reset the boot certificates and make the TPM decryption impossible. If you still keep a bootloader, be careful of what it allows to load.

rEFInd

If you have another system you often use besides your Arch Linux, you may want to still keep a bootloader, depending on how tedious your UEFI firmware is to override the default boot setting. rEFInd is a generic UEFI bootloader, but a very capable one, as well as providing a graphical user interface to choose where to boot from.

While rEFInd will find and boot your Linux configuration in the current state, a few tweaks will improve the experience a bit.

First step is to install the refind package, then run its built-in script to make the installation automatically:

refind-install

Now, we need to tweak mkinitcpio configuration a bit. If you already have your UKI images generated at this point, I would advise you to remove completely the /boot/efi/EFI/Linux folder.

rEFInd has some automatic heuristics to try to guess what kind of system is an image for to display an appropriate icon. However, it seems to not be good with UKI images (yet), but we can help it. If it cannot determine the type of image, it will use the name of the parent folder as a hint. In the current configuration, it will see the Linux folder, and will display a generic Tux icon. If you want to have Arch Linux branding, we need to rename the folder to arch. Go back to the linux.preset file from earlier, and change the two lines to replace Linux with arch. Do not close the file yet, we have another change to make.

For distributions that may install different kernel versions at the same time, rEFInd will try to determine which version is the latest to use it as default. However, it gets a bit lost with Arch Linux and believes that the default and fallback images are of the same system, but since no kernel version is given in the file name, it will fall back to the file creation date to determine the newer (and default) kernel. By default, mkinitcpio creates the default image first, and the fallback one in second, which, in our case, would make the fallback image the default.

There is a little trick to solve this: make mkinitcpio build the images the other way around. In the .preset file, simply switch the presets around:

PRESETS=('fallback' 'default')

You can now close the preset file. Recreate the parent folder with mkdir, then run mkinitcpio -P to re-create the images in the right order.

If you have Secure Boot set up, do not forget to sign its image with sbctl.