Intro
This guide details how to build a Unified Kernel Image (UKI) on Qubes, which has the benefit of opening the ability to cryptographically verify the state of all EFI code, as well as kernel boot parameters. This acknowledges the classic form of the “Evil Maid” Attack.
Currently, Qubes OS does not offer strong guarantees for boot security on most devices. The existing options for protecting the unencrypted bootloader include:
- Enabling Qubes Anti- Evil Maid (AEM): Requires a TPM 1.2 and Intel TXT.
- Having a separate USB stick: Must keep this on you at all times to be secure.
- Using an SSD encryption password: Is often not supported, relies on a historically buggy firmware encryption mechanism, and offers no way to tell if you have been compromised.
During the creation of the UKI, your original EFI entry is not modified, so you can still boot to that (with secure boot disabled) as a backup if anything fails.
Building the Unified Kernel Image (UKI)
Through GitHub threads shared with me by user ryona, there is a script called uki-generate that can be used to create a UKI on Qubes using existing files within /boot and an additional xen.cfg. You can find the script here: qubes-core-admin-linux/uki-generate at main · QubesOS/qubes-core-admin-linux · GitHub
Note: This script will be used to generate an EFI binary, which is a very sensitive type of file. After you copy the script code, it is recommended to look over it yourself, or even better, verify the git GPG signature of the last commit modifying the script.
1. Copy the Script to an Offline Qube
I used the vault qube for the entire process, but you can also create a dedicated AppVM for this purpose. If you use an alternative build qube, replace vault with the name of your qube in all of the commands within this guide.
In your build qube:
nano ~/uki-generate
Paste the script, then save the file. Finally, make it executable:
chmod +x ~/uki-generate
2. Copy the Relevant Boot Files to Your Build Qube
In dom0:
# Replace these file names with your actual matching files
# if you are on a newer version of Qubes.
sudo qvm-copy-to-vm vault \
/boot/efi/EFI/qubes/xen-4.19.3.efi \
/boot/vmlinuz-6.12.7-1.qubes.fc41.x86_64 \
/boot/initramfs-6.12.7-1.qubes.fc41.x86_64.img \
Then, in your build qube:
mv ~/QubesIncoming/dom0/* ~/
3. Create your xen.cfg
In dom0:
cat /proc/cmdline
Copy the boot parameters (everyting after “kernel”; root=/dev/mapper ...), then Copy dom0 clipboard via the clipboard manager applet, and then pass it to your build qube with Ctrl+Shift+V.
In the build qube:
nano ~/xen.cfg
Then, write your config file. You’ll want your xen.cfg file to look like this in the end:
[global]
default=qubes
[qubes]
options=console=none loglvl=all
kernel=placeholder YOUR KERNEL PARAMTERS
ramdisk=placeholder
This is an example using mine. After kernel=placeholder, paste the string you copied from dom0 (root=/dev/mapper...) in place of YOUR KERNEL PARAMETERS.
4. Building the UKI
Finally you can run the script to build your EFI binary. Replace the filenames if they are different for your version of Qubes.
~/uki-generate \
~/xen-4.19.3.efi \
~/xen.cfg \
~/vmlinuz-6.12.59-1.qubes.fc41.x86_64 \
~/initramfs-6.12.59-1.qubes.fc41.x86_64.img \
~/xen-unified.efi
If this goes through without errors, you are ready to test it.
5. Booting to the UKI
Copying to dom0 is very sensitive, more so booting from a copied EFI entry; ensure you have built it securely before doing this step.
In dom0:
qvm-run --pass-io vault 'cat /home/user/xen-unified.efi' > ~/xen-unified.efi
sudo mv ~/xen-unified.efi /boot/efi/efi/qubes/xen-unified.efi
Now, to register this new boot option in your UEFI:
# Replace /dev/nvme0n1 with the path to your
# drive, such as /dev/sda
sudo efibootmgr --create --disk /dev/nvme0n1 \
--part 1 --label "Qubes OS UKI" \
--loader "\EFI\qubes\xen-unified.efi"
Then, run efibootmgr | grep "Qubes OS UKI" to see that your boot option has registered.
Once this is finished, you can now test your UKI boot entry. Reboot and spam whichever key lets you choose boot options (F12 in my instance), or access it from your standard UEFI setup if available. You should find an option called Qubes OS UKI. Boot from this.
The boot process should look normal, except that instead of the GRUB, you see a brief black screen with white text showing certain configuration details. If you can decrypt your disk and log in normally, you’ve now verified that the UKI works.
Security Notes
A UKI alone does not improve your boot security inherently; it simply opens opportunities for proper cryptographic verification. You must use another method to verify the boot process.
To enable Secure Boot, you will want to enroll your own key in the authorized signatures database and self-sign the EFI binary. To enter user mode, this key must be signed by an enrolled KEK that is signed by the PK. Only do this if you know you’re doing, since a custom Secure Boot chain can leave a system unusable until the motherboard is reset, especially on systems with a dGPU.
Secure boot is not a particularly strong protection by itself. If your UEFI can be reset by shorting the CMOS (most hardware), then someone can disable Secure Boot and replace your bootloader with any OS they want. Unless you check your secure boot policy every boot, then you likely won’t notice that this attack has occurred before decrypting your data.
TPM-based attestation is still necessary to reliably ensure boot integrity. The easiest way to do that is through LUKS TPM 2.0 unsealing via systemd-cryptenroll in dom0 with a TPM PIN (bound to PCR 4, or if you have Secure Boot PCR 7). This has its security concerns, as attacks have historically been able to find vulnerabilities in trusted platform modules key storage (FaulTPM, for instance). If you do decide to go this route, ensure that your specific CPU model is not known to be vulnerable, and enable “Pluton TPM” in your UEFI security settings if available.
Many threat models do not permit the potential vulnerabilities with storing encryption keys in the TPM. It may be possible to authenticate the system to the user using a non-static integrity verification code in initramfs, using tpm2-totp for instance, before the user types in their LUKS passphrase. If this is implemented, the user would compare the code on their screen to their own authenticator app such as Aegis. If the codes match, then that means the assigned PCRs (such as PCR 4, and you can include others as well to detect modified firmware, for instance) match the expected values, so the bootloader is safe and the LUKS passphrase can be entered. If you know of an implementation, then please let me know and I will add that to this post.
Another remedy to the TPM trust issue is mathematically requiring both a TPM unsealed key and a software passphrase, but I’m not aware of any software that lets you easily implement this in your initramfs (current TPM PIN implementations for LUKS only use your PIN to authenticate to the TPM, not as a part of the final secret).