Guest article by maxice8
Recently I got myself into package
sd-boot from the Systemd project
into Alpine Linux. I previously packaged it as an April’s fools joke
for Void Linux Here.
After receiving some negative feedback on including it, more specifically about the fact I had to include 23 patches (which could be trimmed but would be more work than just copying the work of the OpenEmbedded and NixOS Musl people) to make it compile.
A side-effect of packaging
sd-boot is that I got into, and learnt a
few things about booting directly from UEFI. Including:
- Creating Unified Kernel Images as described here.
- Creating all the components necessary for Secure Boot.
- Signing the Unified Kernel Image to boot under Secure Boot.
Unified Kernel Images
Unified Kernel Images, according to the documentation is:
A unified kernel image is a single EFI PE executable combining an EFI stub loader, a kernel image, an initramfs image, and the kernel command line.
So basically a big file with all the components necessary for booting directly from UEFI instead of relying on a bootloader. Easier for signing.
The first step is installing the packages necessary to perform all the steps necessary to have a working Unified Kernel Image.
For this we are using Alpine Linux since it’s what I use (Change the commands to equivalents of your distribution).
We need to install the
binutils package which provides the
gummiboot which provides the EFI stub loader in
Both are in the main repository, so installing them is a matter of just
apk with the proper commands:
# apk add binutils gummiboot
With all the packages installed we can make the Unified Kernel Image.
One of the components of the Unified Kernel Image is the initramfs. On other bootloaders you can just specify more than initramfs, having the CPU microcode coming before the actual initramfs.
Not so much with the Unified Kernel Image. For that we will have to create a single initramfs that includes the CPU microcode.
$ cat /boot/intel-ucode.img /boot/initramfs-lts > /tmp/initramfs-lts
Simply enough, that will work. Remember to change the intel-ucode for whatever AMD uses or to ignore this section altogether if you don’t need it.
Now that we have our initramfs with the microcode we can use
to create the image. We will have 4 sections:
- .osrel, where the contents of
/etc/os-releaseare present (some distributions have it in
/usr/lib/os-release, but not Alpine Linux)
- .cmdline, where the options passed to the command line of the kernel
are present, we will use
/proc/cmdlineto get from our currently running kernel, but you can write your own into a file and use it instead.
- .linux, the kernel itself goes here.
- .initrd, the initramfs itself goes here.
We will use the EFI stub loader from
gummiboot which is present in
/usr/lib/gummiboot/linuxx64.efi.stub and the Unified Kernel Image
will be written to
objcopy invocation goes like so:
objcopy \ --add-section .osrel="/etc/os-release" --change-section-vma .osrel=0x20000 \ --add-section .cmdline="/proc/cmdline" --change-section-vma .cmdline=0x30000 \ --add-section .linux="/boot/linux-lts" --change-section-vma .linux=0x40000 \ --add-section .initrd="/tmp/unified-initramfs" --change-section-vma .initrd=0x3000000 \ /usr/lib/gummiboot/linuxx64.efi.stub /boot/alpine.efi
And we have a functional Unified Kernel Image.
$ file /boot/alpine.efi /boot/alpine.efi: PE32+ executable (EFI application) x86-64 (stripped to external PDB), for MS Windows
One of the things I wanted to do when re-creating my boot scheme by
sd-boot was to have Secure Boot enabled. Just for challenging
myself into new territory.
Arch Linux Wiki has a full page on it, and they point to an extensive article on Secure Boot. I won’t waste anytime trying to explain Secure Boot, the article referred from the Arch Linux Wiki is written by someone that is much more qualified to write about it than me. (That is your call to give it a read if you are interested, I’ll wait)
In my approach I made my own keys, which seemed easier than using Shim or PreLoader.
First we install the components necessary, we will need
efitools. It is
available only in
edge because it was in the
I have moved it to main so it should make it to 3.12 but not 3.11 and
below. So enable the testing repository and install it.
We will also use the
sbsign binary from the
It has the same situation as
efitools, was in testing, moved to
main, should be available in Alpine Linux 3.12 but not 3.11 and below.
In the meantime we will also use
uuidgen from the
package, so if you didn’t have that package installed already, do it.
# apk add efitools sbsigntool util-linux
You will also need the
openssl command from the
openssl package (
libressl if your Alpine Linux version uses that). But since that
is a system package that is very widely used, I will assume you already
The following are literally copied from the Arch Linux Wiki.
$ uuidgen --random > GUID.txt $ openssl req -newkey rsa:4096 -nodes -keyout PK.key -new -x509 -sha256 -days 3650 -subj "/CN=my Platform Key/" -out PK.crt $ openssl x509 -outform DER -in PK.crt -out PK.cer $ cert-to-efi-sig-list -g "$(< GUID.txt)" PK.crt PK.esl $ sign-efi-sig-list -g "$(< GUID.txt)" -k PK.key -c PK.crt PK PK.esl PK.auth $ sign-efi-sig-list -g "$(< GUID.txt)" -c PK.crt -k PK.key PK /dev/null rm_PK.auth $ openssl req -newkey rsa:4096 -nodes -keyout KEK.key -new -x509 -sha256 -days 3650 -subj "/CN=my Key Exchange Key/" -out KEK.crt $ openssl x509 -outform DER -in KEK.crt -out KEK.cer $ cert-to-efi-sig-list -g "$(< GUID.txt)" KEK.crt KEK.esl $ sign-efi-sig-list -g "$(< GUID.txt)" -k PK.key -c PK.crt KEK KEK.esl KEK.auth $ openssl req -newkey rsa:4096 -nodes -keyout db.key -new -x509 -sha256 -days 3650 -subj "/CN=my Signature Database key/" -out db.crt $ openssl x509 -outform DER -in db.crt -out db.cer $ cert-to-efi-sig-list -g "$(< GUID.txt)" db.crt db.esl $ sign-efi-sig-list -g "$(< GUID.txt)" -k KEK.key -c KEK.crt db db.esl db.auth
Now that the keys were made we need to add them to the Motherboard. As of writing this I’m using a Dell Inspiron 5566. The Dell firmware is really really nice and allows me to do all the required operations by just clicking around their interface.
That means you will have to figure your way in your motherboard’s firmware.
First copy the required files to the FAT boot partition that the motherboard can see.
# cp *.cer *.esl *.auth /boot
Then reboot into your motherboard’s firmware and enroll them. You can remove the files afterward.
# rm -f /boot/*.cer /boot/*.esl /boot/*.auth
Now that our keys are enrolled we can sign our Unified Kernel Image:
$ sbsign --key db.key --cert db.crt --output /boot/alpine.efi /boot/alpine.efi
The last step is creating an UEFI boot entry, stored normally in NVRAM as UEFI config variables with boot configuration along them.
To deal with UEFI config variables in Linux we need the
command which is part of the
busybox package, it will be
replaced with the
mount command from the
if the latter is installed, but both are OK.
To manipulate the UEFI config variables we will use the
efibootmgr command from the
efibootmgr package. It is in
the community repository.
# apk add efibootmgr
First we need to mount the UEFI config variables with the efivarfs filesystem. According to the kernel documentation it can be done like this:
# mount -t efivarfs none /sys/firmware/efi/efivars
Now we create the boot entry with
efibootmgr. I’ll show the final
efibootmgr first and explain each switch/flag used
separately below it.
# efibootmgr \ --create \ --disk /dev/sda \ --part 1 \ --label "Alpine Linux" \ --loader "\alpine.efi"
--createcall should be obvious, we want to create the boot entry, with it we also add it to the boot order, if you do not wish to add to it to the boot order then
--create-onlycan be used.
--disk /dev/sdapoints to the disk where the EFI partition is, if your EFI partition is in another disk then change it appropriately.
--part 1is the number of the partition, since my EFI partition is in /dev/sda1 then this is 1, but if your partition is in /dev/sda5 then it is 5.
--label "Alpine Linux"label used, this will appear in the Motherboard Firmware for editing and when you press F12 (or whatever F-Key) to pick a different boot option. If you use another distribution then change it to it.
--loader "\alpine.efi"the location of the Unified Kernel Image, in relation to the EFI partition, since we used /boot/alpine.efi then we set it to \alpine.efi (NOTE: the backslash is how it should be, this is Windows stuff).
After calling the invocation you can use
efibootmgr to see if was added
$ efibootmgr BootCurrent: 0000 Timeout: 0 seconds BootOrder: 0000 Boot0000* Alpine Linux
This is my system, I have executed the order 66 on all other boot options, your should still have the other options like “Windows Boot Manager”. The important thing here is that the “Alpine Linux” entry appears.
If you wish to have it default to booting Alpine Linux then have it be the first boot entry that appears in BootOrder.