How to ditch grub for UEFI (secure)booting


Problem statement

I’ve had my fair share of quarrels with grub. I taught it to secureboot Alpine with everything encrypted, then convinced 2.06 to give up on “verification requested but nobody cares”, which even survived an upgrade from Alpine 3.15 to 3.18.

However, an upgrade to grub 2.12 broke my setup again, and in the process I discovered a neat way to secure boot without grub1.

This is a few quick notes about that solution.

Discussion

When researching the 2.12 issue with grub, I stumbled upon Using a UKI (UEFI only) section of Alpine Linux’s Bootloaders wiki.

There it says that there’s a way to package kernel, initrd, command line, and optionally a splash image – to a single EFI PE binary. The efi-mkuki script that Jakub Jirůtka wrote ~3 years ago.

The pertient part of efi-mkuki is:

objcopy \
	--add-section .osrel="$osrel"     --change-section-vma .osrel=0x20000    \
	--add-section .cmdline="$cmdline" --change-section-vma .cmdline=0x30000  \
	--add-section .splash="$splash"   --change-section-vma .splash=0x40000   \
	--add-section .linux="$linux"     --change-section-vma .linux=0x2000000  \
	--add-section .initrd="$initrd"   --change-section-vma .initrd=0x3000000 \
	"$efistub" "$output"

which stitches the various bits2 together into a coherent whole. Which you can then sbsign for secureboot.

In other words: heaven. No fiddling with modules, no caring about verification, no random breakages during upgrade, and all that jazz.

Solution

Must be hard to do this… right? Right?

Super hard:

apk add efi-mkuki
efi-mkuki -o all.efi /boot/vmlinuz-lts /boot/initramfs-lts
sbsign --key /boot/secureboot/sb.key --cert /boot/secureboot/sb.crt all.efi

Now you stick all.efi to your EFI partition, as EFI/boot/bootx64.efi, and Bob’s your uncle. If you want this automatic, then you can (on Alpine) apk add kernel-hooks, and then throw a trivial hook into /etc/kernel-hooks.d/ to do the two steps. Untested, but probably would work:

$ cat /etc/kernel-hooks.d/make-me-a-boot-efi
#!/bin/sh
CERT=/boot/secureboot/sb.crt
KEY=/boot/secureboot/sb.key
KERNEL=/boot/vmlinuz-lts
INITRD=/boot/initramfs-lts
TARGET=/efi/EFI/boot/bootx64.efi
set -e
efi-mkuki -o "$TARGET.tmp" "$KERNEL" "$INITRD"
sbsign --key "$KEY" --cert "$CERT" --output "$TARGET.tmps" "$TARGET.tmp"
mv -f "$TARGET.tmps" "$TARGET"
rm -f "$TARGET.tmp"
$ chmod a+x /etc/kernel-hooks.d/make-me-a-boot-efi

Finish off with apk fix kernel-hooks to test it. ;)

Bonus: How to make a bootdisk out of this

So how does one make a USB bootdisk using this approach, you might ask?

Again, super simple. Let’s assume you have the all.efi from the previous section.

Then:

# partition /dev/sdX, make one partition with `ef` type:
dd if=/dev/zero of=/dev/sdX bs=4k count=1k
echo "/dev/sdX1: start=1M, type=ef" | sfdisk --quiet --label dos /dev/sdX
# make it dosfs
mkfs.vfat -F32 /dev/sdX1
# stuff the right bits where they need to go
mount /dev/sdX1 /media/sdX1
mkdir -p /media/sdX1/EFI/boot
cp all.efi /media/sdX1/EFI/boot/bootx64.efi
umount /media/sdX1

Closing words

The difference between grub and this is vast. No more fussing with modules, configs, debugging3. Either the *.efi boots, or it doesn’t.

There’s that much less that can go wrong. I like this a lot.

  1. If it weren’t for the inconvenient fact that my disk-encryption secrets are baked in my initrd. So for the time being, I’m still stuck with that grubby fella.

  2. One of these bits is gummiboot which was in the meantime assimilated by the evil borg of systemd. Fortunately, the gummiboot-48.1 stub is still a viable option.

  3. For my use-case, that is. I’m pretty sure some grub fanboy out there will come up with some completely relevant “but but”. Yeah, sure, whatever floats your boat. I’m the captain of USS simpleton. Deal with it.