I use a pretty simple setup for booting my systems.
- UEFI firmware loads a signed bootloader (
systemd-boot
in my case, butgummiboot
is basically the same for non-systemd systems). - The bootloader loads a signed executable that bundles the initrd, cmdline and kernel (“the bundle” from here on).
- The
initrd
prompts for the encryption passphrase, decrypts everything, and boots the actual OS.
That’s all the OS boot process, but some extra steps happen before I see an actual desktop:
- Once a successful boot completes, the currently booted bundle is backed up, in case I break the main one before the next reboot.
- Upon booting,
tty1
automatically logs into my user account and starts upsway
(the compositor). sway
itself starts all other user-session services by triggering a systemd target.
From this point on, it’s assumed that the EFI partition is mounted in /efi
.
systemd-boot ¶
(reminder: gummiboot
is basically the same tool for non-systemd setups)
First of all, copy the bootloader into the EFI partition and register it with the UEFI firmware. Both things can be done with one command:
bootctl install
It will automatically look for signed bundles in EFI/Linux
. Since I usually
have my primary bundle and a backup one, I manually configure it to prefer the
main one:
# The bundle is located in /efi/EFI/Linux/arch.efi:
bootctl set-default arch.efi
That’s all the setup and configuration that systemd-boot will require.
I don’t configure a menu with a timeout since I want the system to boot directly without any prompt. In case I need to boot from the recovery one, the menu can be triggered by spamming Space during startup.
Note that the menu will only allow picking another stub to boot from, but
overriding the cmdline
is not possible since it’s embedded in the bundle.
sbctl ¶
sbctl
is a tool to manage SecureBoot keys and generate signed bundles with
the initrd
, cmdline
and kernel. Keeping all items in a single signed
bundle is important, for example: if the cmdline
is just a file in the same
partition, it can be altered, which would allow easily executing unsigned code.
The UEFI firmware first needs to be configured to use a key that will be
generated and managed by sbctl
.
Prepare the firmware ¶
- Reboot into the firmware/UEFI menu.
- Disable Secure Boot.
- Turn on custom key mode (this implies “using user-provisioned keys”).
- Turn on setup mode (this implies “mode where the user can alter keys”).
Set up the OS ¶
- Reboot into ArchLinux (or your distro of choice).
- Create and enroll keys:
sudo sbctl create-keys
sudo sbctl enroll-key
Generate the bundles ¶
sbctl
will read the cmdline from /etc/kernel/cmdline
, so make sure to put
your current cmdline there. You can do this with:
cat /proc/cmdline | sudo tee /etc/kernel/cmdline
My partitions has the right Partition Type
set, so systemd knows what
partition to pick up based on its id. This follows the Discoverable Partitions
Specification. The Partition Type can be safely
updated for partitions, even if currently mounted (since this updates the
GPT, not the partition itself).
Then, generate the sbctl
bundle:
sbctl bundle --sign /boot/EFI/Linux/arch.efi
The bundle will use your existing initrd
and kernel
. So each time
mkinitcpio
generates a new initrd
, you need to rebuild the bundle.
Fortunately, sbctl
includes pacman hooks to re-build the bundle when you
update the kernel or the initrd
.
The file /etc/kernel/cmdline
is read on each re-generation, so don’t delete
it. It’s also the file you want to update when you need to make changes to your
cmdline.
Finally, you should also sign the bootloader itself:
# This is the entry explicitly in the UEFI.
# The entry MAY be deleted during firmware updates.
sbctl sign --save /efi/EFI/systemd/systemd-bootx64.efi
# This is default fallback in case there are no entries in the UEFI.
# If you dualboot, some aggressive OSs may overwrite it:
sbctl sign --save /efi/EFI/BOOT/BOOTX64.EFI
Turning on SecureBoot ¶
- Reboot into the firmware/UEFI menu.
- Enable Secure Boot (this implies “check that everything is signed”).
- Leave custom key mode on (e.g.: continue using the user-provisioned keys).
- Turn off setup mode (e.g.: disallow further altering the configured keys).
Reboot and cleanup ¶
You should be able to reboot with SecureBoot turned on. The amount of moving parts in the whole boot process are extremely few, and it’s also very low-maintenance.
I’d recommend also removing all other unnecessary boot entries from the firmware using:
sudo efibootmgr --delete-bootnum --bootnum 00XX
If you’re unsure which one you should leave, delete all of them and re-run
bootctl install
afterwards.
Breakage recovery ¶
This setup is unlikely to break by itself – but as with any Arch setup, it may break due to the user fiddling with things (e.g.: a broken cmdline).
I have a small tool that creates the above-mentioned backup bundle after every successful boot. If I break the main bundle and it doesn’t boot, then that image is kept around, and I can reboot off it.
As mentioned above, tapping Space during startup shows the boot entry menu.
Credit ¶
sb-backup
relies on go-uefi
and sbctl
, so thanks to
the author for doing all the hard lifting.
Update 1 ¶
It seems that using SecureBoot with a discrete GPU can be problematic in some cases. Since the GPU’s firmware is not signed, it may fail to load in some cases due initialisation order, and may result in a bricked motherboard.
I’d recommend you do some serious research before trying this if you have a discrete GPU. See this reddit thread for more details.