Unified kernel image

From ArchWiki
(Redirected from UKI)

A unified kernel image (UKI) is a single executable which can be booted directly from UEFI firmware, or automatically sourced by boot-loaders with little or no configuration.

Although Arch supported kernels themselves can be loaded by UEFI firmware, a unified image allows to incorporate all or a subset of the following:

The resulting executable, and therefore all these elements can then be easily signed for use with Secure Boot.

Note: In the entire article esp denotes the mountpoint of the EFI system partition.

Preparing a unified kernel image

There are several ways to generate a UKI image and install it to the proper place (the esp/Linux directory). Currently several tools compete for doing this functionality, so choose one of the following based on your needs and your likings.

Note: You only need to perform one of the subsections.

mkinitcpio

Kernel command line

mkinitcpio supports reading kernel parameters from command line files in the /etc/cmdline.d directory. Mkinitcpio will concatenate the contents of all files with a .conf extension in this directory and use them to generate the kernel command line. Any lines in the command line file that start with a # character are treated as comments and ignored by mkinitcpio. Take care to remove entries pointing to microcode and initramfs.

For example:

/etc/cmdline.d/root.conf
root=UUID=0a3407de-014b-458b-b5c1-848e92a327a3 rw
Tip:
  • If your root file system is on a non-default Btrfs subvolume, make sure to set necessary mount flags in rootflags. See Btrfs#Mounting subvolume as root.
  • For example, if your system subvolume ID is 256 (you can see your subvolume ID using btrfs subvolume list btrfs_mountpoint, or you can see the flags in /etc/fstab), you should add rootflags=subvolid=256 to your kernel command line.
  • It is not necessary to copy all flags in /etc/fstab since rootflags is only used during boot. Systemd will read fstab, remount and apply flags listed there automatically after boot.
/etc/cmdline.d/security.conf
# enable apparmor
lsm=landlock,lockdown,yama,integrity,apparmor,bpf audit=1 audit_backlog_limit=256

Alternatively, /etc/kernel/cmdline can be used to configure the kernel command line.

For example:

/etc/kernel/cmdline
root=UUID=0a3407de-014b-458b-b5c1-848e92a327a3 rw quiet bgrt_disable
Tip:
  • The root= parameter may be omitted if the root partition is automounted by systemd.
  • The bgrt_disable parameter tells Linux to not display the OEM logo after loading the ACPI tables.

.preset file

Next, modify /etc/mkinitcpio.d/linux.preset, or the preset that you are using, as follows, with the appropriate mount point of the EFI system partition :

  • Un-comment (i.e. remove #) the PRESET_uki= parameter for each item in PRESETS=,
  • Optionally, comment out PRESET_image= to avoid storing a redundant initramfs-*.img file,
  • Optionally, add or un-comment the --splash parameter to each PRESET_options= line for which you want to add a splash image.

Here is a working example linux.preset for the linux kernel and the Arch splash screen.

/etc/mkinitcpio.d/linux.preset
# mkinitcpio preset file for the 'linux' package

#ALL_config="/etc/mkinitcpio.conf"
ALL_kver="/boot/vmlinuz-linux"
ALL_microcode=(/boot/*-ucode.img)

PRESETS=('default' 'fallback')

#default_config="/etc/mkinitcpio.conf"
#default_image="/boot/initramfs-linux.img"
default_uki="esp/EFI/Linux/arch-linux.efi"
default_options="--splash=/usr/share/systemd/bootctl/splash-arch.bmp"

#fallback_config="/etc/mkinitcpio.conf"
#fallback_image="/boot/initramfs-linux-fallback.img"
fallback_uki="esp/EFI/Linux/arch-linux-fallback.efi"
fallback_options="-S autodetect"
Tip:
  • If all you want to do is boot from the unified kernel images, you can mount the ESP to /efi and only those need to reside on the ESP partition.
  • You can append --cmdline /etc/kernel/fallback_cmdline to fallback_options to use different a different cmdline than above for the fallback image (e.g. without quiet).
  • To omit embedding the kernel command line, add --no-cmdline to PRESET_options=. Kernel parameters will need to be passed via the boot loader.
Note: PRESET_uki options were previously known as PRESET_efi_image, changed November 2022 (see archlinux/mkinitcpio/mkinitcpio#134), with older option deprecated but working for now.

pacman hook

A pacman hook is needed to trigger a rebuild after a microcode upgrade.

/etc/pacman.d/hooks/ucode.hook
[Trigger]
Operation=Install
Operation=Upgrade
Operation=Remove
Type=Package
# Change to appropriate microcode package
Target=amd-ucode
# Change the linux part below and in the Exec line if a different kernel is used
Target=linux

[Action]
Description=Update Microcode module in initcpio
Depends=mkinitcpio
When=PostTransaction
NeedsTargets
Exec=/bin/sh -c 'while read -r trg; do case $trg in linux) exit 0; esac; done; /usr/bin/mkinitcpio -P'
Tip: Consider merging this hook with other hooks that monitor the kernel package, such as the one for the NVIDIA driver, to avoid repeated mkinitcpio runs.

Signing the UKIs for Secure Boot

By using a mkinitcpio post hook (mkinitcpio(8) § ABOUT POST HOOKS), the generated unified kernel images can be signed for Secure Boot. Create the following file and make it executable:

/etc/initcpio/post/uki-sbsign
#!/usr/bin/env bash

uki="$3"
[[ -n "$uki" ]] || exit 0

keypairs=(/path/to/db.key /path/to/db.crt)

for (( i=0; i<${#keypairs[@]}; i+=2 )); do
    key="${keypairs[$i]}" cert="${keypairs[(( i + 1 ))]}"
    if ! sbverify --cert "$cert" "$uki" &>/dev/null; then
        sbsign --key "$key" --cert "$cert" --output "$uki" "$uki"
    fi
done

Replace /path/to/db.key and /path/to/db.crt with the paths to the key pair you want to use for signing the image.

Building the UKIs

Finally, make sure that the directory for the UKIs exists and regenerate the initramfs. For example, for the linux preset:

# mkdir -p esp/EFI/Linux
# mkinitcpio -p linux

Optionally, remove any leftover initramfs-*.img from /boot or /efi.

kernel-install

This article or section is a candidate for merging with kernel-install.

Notes: This should probably move to the kernel-install article to avoid duplication (Discuss in Talk:Unified kernel image)

You can use systemd's kernel-install to deploy kernel images/initramfs images or UKI images to the proper place. For kernels installed as a package by Pacman this is performed by a pacman hook of mkinitcpio by default. So to use kernel-install you need to switch the Pacman hooks from mkinitcpio to kernel-install.

The UKI image can be generated by mkinitcpio directly or by kernel-install unifying the kernel image and the generated standalone initramfs image into a single file (by the ukify tool of kernel-install, which requires systemd-ukify as of 2024-01-15, see below). Nevertheless mkinitcpio will be called by kernel-install to generate an appropriate image (a UKI image for layout=uki and uki_generator=mkinitcpio, otherwise a standalone initramfs image for ukify).

To setup kernel-install to produce UKIs:

  • Set the kernel-install layout to uki. e.g.:
    # echo "layout=uki" >> /etc/kernel/install.conf
  • By default when the layout is set to uki kernel-install uses its own ukify tool (must install systemd-ukify now, see below) to generate a uki image from the initramfs and the kernel image. You can use mkinitpcio to generate an uki image directly. If want to use mkinitcpio to generate the uki image, set uki_generator to mkinitpcio. e.g.:
    # echo "uki_generator=mkinitcpio" >> /etc/kernel/install.conf
Note: As of 2024-01-15, mkinitcpio will no longer build the UKI unless uki_generator is set to mkinitcpio directly, which it did implicitly before if systemd-ukify was not explicitly installed.
  • Mask the direct kernel installation Pacman hooks:
    # ln -s /dev/null /etc/pacman.d/hooks/60-mkinitcpio-remove.hook
    # ln -s /dev/null /etc/pacman.d/hooks/90-mkinitcpio-install.hook
    
  • Create a Pacman hook for kernel-install. You can use pacman-hook-kernel-installAUR.
  • Remove and reinstall the kernel packages that you use.

dracut

See dracut#Unified kernel image and dracut#Generate a new initramfs on kernel upgrade.

sbctl

Install the sbctl package. Store the kernel command line in /etc/kernel/cmdline. Use the sbctl bundle command with the --save parameter to create a bundle and have it be regenerated by a Pacman hook at appropriate times:

# sbctl bundle --save esp/EFI/Linux/arch-linux.efi

To create more EFI binaries for other kernels and initramfs images, repeat the above command with parameters --kernel-img and --initramfs, see sbctl(8) § EFI BINARY COMMANDS. The EFI binaries can be regenerated at any time with sbctl generate-bundles.

ukify

Install the systemd-ukify package. Since ukify cannot generate an initramfs on its own, if required, it must be generated using dracut, mkinitcpio or booster.

A minimal working example can look something like this:

# ukify build --linux=/boot/vmlinuz-linux --initrd=/boot/intel-ucode.img \
--initrd=/boot/initramfs-linux.img \
--cmdline="quiet rw"
Note: If external microcode initramfs images are used (/boot/amd-ucode.img or /boot/intel-ucode.img), they must always be placed first, before the main initramfs image (e.g. /boot/initramfs-linux.img).

Then, copy the resulting file to the EFI system partition:

# cp filename.efi esp/EFI/Linux/
Tip:
  • To skip having copy over the resulting EFI executable to the EFI System Partition, use the --output=esp/EFI/Linux/filename.efi command line option to ukify.
  • When specifying the --cmdline option, one can specify a file name to read the kernel parameters from (e.g. /etc/kernel/cmdline by adding the @ symbol before the file name, like --cmdline=@/path/to/cmdline.

An example for automatic UKI building with a systemd service for normal kernel image with intel ucode and /efi mounted ESP:

/etc/ukify.conf
[UKI]
Linux=/boot/vmlinuz-linux
Initrd=/boot/intel-ucode.img /boot/initramfs-linux.img
Cmdline=@/etc/kernel/cmdline
OSRelease=@/etc/os-release
Splash=/usr/share/systemd/bootctl/splash-arch.bmp
Note: If the initramfs generator already bundles CPU microcode by default, then only specify the initramfs image in Initrd=/boot/initramfs-linux.img.

The factual accuracy of this article or section is disputed.

Reason: Automatic update of ukify-generated images via systemd path units is unreliable (Discuss in Talk:Unified kernel image#Automatic update of ukify-generated images via systemd path units is unreliable)
/etc/systemd/system/run_ukify.service
[Unit]
Description=Run systemd ukify
[Service]
Type=oneshot
ExecStart=/usr/bin/ukify build --config=/etc/ukify.conf --output esp/EFI/Linux/archlinux-linux.efi
/etc/systemd/system/run_ukify.path
[Unit]
Description=Run systemd ukify
[Path]
PathChanged=/boot/initramfs-linux.img
PathChanged=/boot/intel-ucode.img
Unit=run_ukify.service
[Install]
WantedBy=multi-user.target

Then enable run_ukify.path.

Manually

Put the kernel command line you want to use in a file, and create the bundle file using objcopy(1).

For microcode, first concatenate the microcode file and your initrd, as follows:

$ cat esp/cpu_manufacturer-ucode.img esp/initramfs-linux.img > /tmp/combined_initrd.img

When building the unified kernel image, pass in /tmp/combined_initrd.img as the initrd. This file can be removed afterwards.

Note: For IA32 UEFI, replace /usr/lib/systemd/boot/efi/linuxx64.efi.stub with /usr/lib/systemd/boot/efi/linuxia32.efi.stub in the following commands.
$ align="$(objdump -p /usr/lib/systemd/boot/efi/linuxx64.efi.stub | awk '{ if ($1 == "SectionAlignment"){print $2} }')"
$ align=$((16#$align))
$ osrel_offs="$(objdump -h "/usr/lib/systemd/boot/efi/linuxx64.efi.stub" | awk 'NF==7 {size=strtonum("0x"$3); offset=strtonum("0x"$4)} END {print size + offset}')"
$ osrel_offs=$((osrel_offs + "$align" - osrel_offs % "$align"))
$ cmdline_offs=$((osrel_offs + $(stat -Lc%s "/usr/lib/os-release")))
$ cmdline_offs=$((cmdline_offs + "$align" - cmdline_offs % "$align"))
$ splash_offs=$((cmdline_offs + $(stat -Lc%s "/etc/kernel/cmdline")))
$ splash_offs=$((splash_offs + "$align" - splash_offs % "$align"))
$ initrd_offs=$((splash_offs + $(stat -Lc%s "/usr/share/systemd/bootctl/splash-arch.bmp")))
$ initrd_offs=$((initrd_offs + "$align" - initrd_offs % "$align"))
$ linux_offs=$((initrd_offs + $(stat -Lc%s "initrd-file")))
$ linux_offs=$((linux_offs + "$align" - linux_offs % "$align"))

$ objcopy \
    --add-section .osrel="/usr/lib/os-release" --change-section-vma .osrel=$(printf 0x%x $osrel_offs) \
    --add-section .cmdline="/etc/kernel/cmdline" \
    --change-section-vma .cmdline=$(printf 0x%x $cmdline_offs) \
    --add-section .splash="/usr/share/systemd/bootctl/splash-arch.bmp" \
    --change-section-vma .splash=$(printf 0x%x $splash_offs) \
    --add-section .initrd="initrd-file" \
    --change-section-vma .initrd=$(printf 0x%x $initrd_offs) \
    --add-section .linux="vmlinuz-file" \
    --change-section-vma .linux=$(printf 0x%x $linux_offs) \
    "/usr/lib/systemd/boot/efi/linuxx64.efi.stub" "linux.efi"

A few things to note:

  • The offsets are dynamically calculated so no sections overlap, as recommended in [1].
  • The sections are aligned to what the SectionAlignment field of the PE stub indicates (usually 0x1000).
  • The kernel image must be in the last section, to prevent in-place decompression from overwriting the sections that follow, as stated in [2].

After creating the image, copy it to the EFI system partition:

# cp linux.efi esp/EFI/Linux/

Booting

Note: When Secure Boot is active, unified kernel images with an embedded .cmdline ignore all command line options passed to them (either using a boot entry or interactively). When Secure Boot is not active, the options passed via the command line override the embedded .cmdline.

systemd-boot

systemd-boot searches in esp/EFI/Linux/ for unified kernel images, and there is no further configuration needed. See sd-boot(7) § FILES

rEFInd

rEFInd will autodetect unified kernel images on your EFI system partition, and is capable of loading them. They can also be manually specified in refind.conf, by default located at:

esp/EFI/refind/refind.conf
menuentry "Arch Linux" {
    icon \EFI\refind\icons\os_arch.png
    ostype Linux
    loader \EFI\Linux\arch-linux.efi
}

Recall that no kernel parameters from esp/EFI/refind_linux.conf will be passed when booting this way. If the UKI was generated without a .cmdline section, specify the kernel parameters in the menu entry with an options line.

GRUB

Similar to rEFInd, GRUB can chainload EFI UKIs as described in GRUB#Chainloading a unified kernel image.

Directly from UEFI

efibootmgr can be used to create a UEFI boot entry for the .efi file:

# efibootmgr --create --disk /dev/sdX --part partition_number --label "Arch Linux" --loader '\EFI\Linux\arch-linux.efi' --unicode

See efibootmgr(8) for an explanation of the options.

Note: UEFI specification uses backward slash \ as path separator but efibootmgr can automatically convert UNIX-style / path separators.

See also