Unified Kernel Image
UKIs can run on UEFI systems and simplify the distribution of small kernel images. For example, they simplify network booting with iPXE. UKIs make rootfs and kernels composable, making it possible to derive a rootfs for multiple kernel versions with one file for each pair.
A Unified Kernel Image (UKI) is a combination of a UEFI boot stub program, a Linux kernel image, an initramfs, and further resources in a single UEFI PE file (device tree, cpu µcode, splash screen, secure boot sig/key, ...). This file can either be directly invoked by the UEFI firmware or through a boot loader.
The simple way
If you use a systemd-based OS and want to use UKIs, you're probably better off using systemd-boot.
In this post, we aim to create a ready-to-boot UKI for another machine rather than the one we are creating it from.
We can use ukify, which is a dedicated tool to create UKIs, but I'd like to get a little more in-depth on how they are constructed.
Linux EFI
The Linux kernel should be compiled with CONFIG_EFI_STUB=y
see https://docs.kernel.org/admin-guide/efi-stub.html
note
When building the kernel with EFI support, the final artifact name in the
arch/"$ARCH"/boot/
directory changes. ForX86_64
, it will be namedbzImage
, but for other architectures, it will bevmlinuz.efi
.
Let's find a pre-compiled kernel in the Arch Linux repository[1]:
# We're only interested in the vmlinuz file
|
We can see with readpe
that the file is indeed a PE executable:readpe vmlinuz
DOS Header
Magic number: 0x5a4d (MZ)
Bytes in last page: 0
Pages in file: 0
Relocations: 0
Size of header in paragraphs: 0
Minimum extra paragraphs: 0
Maximum extra paragraphs: 0
Initial (relative) SS value: 0
Initial SP value: 0
Initial IP value: 0
Initial (relative) CS value: 0
Address of relocation table: 0
Overlay number: 0
OEM identifier: 0
OEM information: 0
PE header offset: 0x40
COFF/File header
Machine: 0x8664 IMAGE_FILE_MACHINE_AMD64
Number of sections: 4
Date/time stamp: 0 (Thu, 01 Jan 1970 00:00:00 UTC)
Symbol Table offset: 0
Number of symbols: 1
Size of optional header: 0xa0
Characteristics: 0x206
Characteristics names
IMAGE_FILE_EXECUTABLE_IMAGE
IMAGE_FILE_LINE_NUMS_STRIPPED
IMAGE_FILE_DEBUG_STRIPPED
Optional/Image header
Magic number: 0x20b (PE32+)
Linker major version: 2
Linker minor version: 20
Size of .text section: 0xc99000
Size of .data section: 0x5f000
Size of .bss section: 0
Entrypoint: 0xc93579
Address of .text section: 0x5000
ImageBase: 0
Alignment of sections: 0x1000
Alignment factor: 0x200
Major version of required OS: 0
Minor version of required OS: 0
Major version of image: 3
Minor version of image: 0
Major version of subsystem: 0
Minor version of subsystem: 0
Size of image: 0xcfd000
Size of headers: 0x1000
Checksum: 0
Subsystem required: 0xa (IMAGE_SUBSYSTEM_EFI_APPLICATION)
DLL characteristics: 0x100
DLL characteristics names
IMAGE_DLLCHARACTERISTICS_NX_COMPAT
Size of stack to reserve: 0
Size of stack to commit: 0
Size of heap space to reserve: 0
Size of heap space to commit: 0
Data directories
Imported functions
Exported functions
Sections
Section
Name: .setup
Virtual Size: 0x3000 (12288 bytes)
Virtual Address: 0x1000
Size Of Raw Data: 0x3000 (12288 bytes)
Pointer To Raw Data: 0x1000
Number Of Relocations: 0
Characteristics: 0x42000040
Characteristic Names
IMAGE_SCN_CNT_INITIALIZED_DATA
IMAGE_SCN_MEM_DISCARDABLE
IMAGE_SCN_MEM_READ
Section
Name: .compat
Virtual Size: 0x1000 (4096 bytes)
Virtual Address: 0x4000
Size Of Raw Data: 0x1000 (4096 bytes)
Pointer To Raw Data: 0x4000
Number Of Relocations: 0
Characteristics: 0x42000040
Characteristic Names
IMAGE_SCN_CNT_INITIALIZED_DATA
IMAGE_SCN_MEM_DISCARDABLE
IMAGE_SCN_MEM_READ
Section
Name: .text
Virtual Size: 0xc99000 (13209600 bytes)
Virtual Address: 0x5000
Size Of Raw Data: 0xc99000 (13209600 bytes)
Pointer To Raw Data: 0x5000
Number Of Relocations: 0
Characteristics: 0x60000020
Characteristic Names
IMAGE_SCN_CNT_CODE
IMAGE_SCN_MEM_EXECUTE
IMAGE_SCN_MEM_READ
Section
Name: .data
Virtual Size: 0x5f000 (389120 bytes)
Virtual Address: 0xc9e000
Size Of Raw Data: 0x1200 (4608 bytes)
Pointer To Raw Data: 0xc9e000
Number Of Relocations: 0
Characteristics: 0xc0000040
Characteristic Names
IMAGE_SCN_CNT_INITIALIZED_DATA
IMAGE_SCN_MEM_READ
IMAGE_SCN_MEM_WRITE
With this, we can already boot Linux directly from the UEFI:
# Press ESC to enter uefi shell
The kernel crashed because there is no root FS, but it successfully booted as an EFI app.
SD Boot
Linux doesn't support UKIs directly, so we need glue code to copy UKI sections to memory and pass control to Linux. In the future UKI might directly be supported by the kernel, see nmbl.
For now, we need to compile systemd efi stub as the bootloader to put in the UKI so it copies Linux and initrd to the right place in memory, displays the splash screen, measures secure boot values, and finally gives control to Linux with the cmdline and dtb.
Populating UKI
UKIs have a PE
file layout with standardized sections[2]. We will use the following:
.linux
where we put the kernel; we can just copyvmlinuz
.osrel
a key-value file with metadata about the booted image.initrd
the initramfs that will be loaded with the kernel
Previously we built a minimal rootfs that we are going to use in our UKI
Creating a UKI involves mainly objcopy
with the right parameters to pack all data into one PE
file.
We create two files that will be the UKI metadata and kernel command line:
# cmdline.txt
console=ttyS0 loglevel=6
# osrel.txt
NAME=UKI
PRETTY_NAME=UKI
VERSION_CODENAME=1
ID=1
BUILD_ID=1
We have uki_base.efi
our shell UKI image; we now just have to fill the aforementioned sections.
The composition can be done in any order using multiple calls to objcopy
.
# sd_boot_stub randomize the virtual memory address (vma) so we first need to get the `IMAGE_BASE` to put the section after the base.
# we give enough room between sections vma so they don't overlap for big files
# copy files into UKI section
Test with Qemu
Create a directory and file efi/boot/bootx64.efi
[3], then run Qemu:
Conclusion
We created a single UKI file that contains a kernel, initramfs, cmdline, and metadata. It is easy to distribute and boot with any UEFI-compatible BIOS of the same architecture. We can imagine giving the UKI to U-Boot or iPXE to boot it in even more environments.
Next time, we will see how to sign the UKI so it works with secure boot.
More sections can be found in ukify manpage. ↩
A FAT partition with a file
efi/boot/bootx64.efi
will be booted automatically by most UEFI implementations. The file suffix depends on the target CPU architecture. ↩