Unified kernel image

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. It is the combination of a UEFI boot stub program like
systemd-stub(7)
, a Linux
kernel image
, an
initramfs
, and
further resources
in a single UEFI PE file.
This file, and therefore all these elements can then easily be
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
mkinitcpio
will assemble the UKI itself unless
systemd-ukify
is installed. In which case, UKI creation will be offloaded to
ukify
unless this is explicitly disabled with the
--no-ukify
option. Note: when offloading to
ukify
mkinitcpio forcibly supplies it a configuration file from
{/etc,/usr/lib}/kernel/uki.conf
.
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
This article or section is being considered for removal.
Reason:
Duplicates
Kernel parameters#Parameter list
. (Discuss in
Talk:Unified kernel image#About removing btrfs kernel cmdline notices
)
If your root file system is on a non-default Btrfs subvolume, make sure to set necessary mount flags in
rootflags
. For example, if your system subvolume ID is
256
, you should add
rootflags=subvolid=256
to the kernel command line. See
Btrfs#Mounting subvolume as root
.
It is not necessary to copy all flags in
/etc/fstab
since
rootflags
is only used during boot.
systemd-remount-fs.service(8)
will read
/etc/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"

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"
Warning
Placing the kernel (
ALL_kver
) in an unencrypted file system (e.g. in
/boot/
if the
EFI system partition
is mounted at
/boot
), exposes it to potential manipulation from bootable drives or other operating systems when dual-booting.
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 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
Updates to systemd-stub (part of
systemd
), microcode (both
intel-ucode
and
amd-ucode
), and
linux
kernel will automatically trigger a UKI rebuild. But you may want to review other
pacman hooks
in the
/etc/pacman.d/hooks/
directory.
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
Kernel-install
is part of
systemd
and requires
systemd-ukify
to build unified kernel images. Make sure kernel-install is properly
set up
.
To generate UKIs, install
systemd-ukify
and set the
kernel-install
layout
to
uki
:
/etc/kernel/install.conf
layout=uki
Note
By default,
mkinitcpio
will generate the initramfs and
#ukify
will thereafter include it inside the UKI.
Any configuration for
#ukify
must be done in
/etc/kernel/uki.conf
in order to be used by
kernel-install
, e.g.
/etc/kernel/uki.conf
[UKI]
Splash=/usr/share/systemd/bootctl/splash-arch.bmp
Tip
You can also copy the template from
/usr/lib/kernel/uki.conf
to
/etc/kernel/uki.conf
and uncomment the section titles and useful lines. Do
not
set the kernel command line in this file, it will be ignored. Use
Kernel-install#Kernel command line
.
Alternatively, for mkinitcpio to generate the UKI, set it as the default
uki_generator
:
/etc/kernel/install.conf
layout=uki
uki_generator=mkinitcpio
In that case,
systemd-ukify
is not necessary. You can also set a different
initrd_generator
, see
kernel-install(8)
.
Reinstall the kernel packages that you use in order for the change to take effect.
dracut
See
dracut#Unified kernel image
and
dracut#Generate a new initramfs on kernel upgrade
.
ukify
Install
the
systemd-ukify
package. Since
ukify
cannot generate an initramfs on its own, if required, it must be generated using, e.g.,
dracut
,
mkinitcpio
or
booster
.
A minimal working example can look something like this:
# ukify build --linux=
/boot/vmlinuz-linux
\
              --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
must always be placed
first
, before the main initramfs image. E.g.
--initrd=/boot/intel-ucode.img --initrd=/boot/initramfs-linux.img
.
Tip
To skip having to 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
.
For further information, see
ukify(1)
.
Note
To have the UKI update automatically when kernel, microcode, or initramfs change, just use ukify with, e.g.,
kernel-install
or
mkinitcpio
. See the preceding sections
#kernel-install
and
#mkinitcpio
for details.
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 initramfs, as follows:
$ cat
esp
/
cpu_manufacturer
-ucode.img
esp
/initramfs-linux.img > /tmp/combined_initramfs.img
When building the unified kernel image, pass in
/tmp/combined_initramfs.img
as the initramfs. 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"))
$ initramfs_offs=$((splash_offs + $(stat -Lc%s "/usr/share/systemd/bootctl/splash-arch.bmp")))
$ initramfs_offs=$((initramfs_offs + "$align" - initramfs_offs % "$align"))
$ linux_offs=$((initramfs_offs + $(stat -Lc%s "
initramfs-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="
initramfs-file
" \
    --change-section-vma .initrd=$(printf 0x%x $initramfs_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/
To create a UKI with
xen
, see
[3]
.
Signing the UKIs for Secure Boot
sbctl
sbctl
provides a
kernel-install
script, a
mkinitcpio
post-hook, and pacman hooks to sign updated binaries.
mkinitcpio
By using a
mkinitcpio post hook
, 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

key=
/path/to/
db.key
cert=
/path/to/
db.crt

if ! sbverify --cert "$cert" "$uki" &>/dev/null; then
    sbsign "$uki" --key "$key" --cert "$cert" --output "$uki"
fi
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.
ukify
To automatically sign UKIs using
systemd-sbsign(1)
, set the following in your configuration file (you can copy and edit the template from
/usr/lib/kernel/uki.conf
):
/etc/kernel/uki.conf
[UKI]
SecureBootSigningTool=systemd-sbsign
SignKernel=true
SecureBootPrivateKey=
/etc/kernel/secure-boot-private-key.pem
SecureBootCertificate=
/etc/kernel/secure-boot-certificate.pem
Note
Signing with
systemd-sbsign(1)
currently needs
SignKernel=true
to be set. See
[4]
.
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
.
It is possible to place the UKI in
the fallback boot path
esp
/EFI/BOOT/BOOTx64.EFI
(or
BOOTIA32.EFI
for 32-bit IA32 UEFI). Using the fallback boot path avoids need to explicitly create a UEFI boot entry in the NVRAM.
Limine
Limine
does not automatically detect unified kernel images (UKIs). However,
limine.conf
can be manually configured to load them.
Example 1: Booting a UKI from the default EFI system partition
If a UKI file is stored in
esp
/EFI/Linux/
, add the following configuration to
limine.conf
:
limine.conf
/Arch Linux
  protocol: efi
  path: boot():/EFI/Linux/arch-linux.efi
Example 2: Booting a UKI from another partition
If a UKI file is located on a different
FAT32
partition, use
uuid(
PARTUUID
)
with the
PARTUUID
instead:
limine.conf
/Arch Linux
  protocol: efi
  path: uuid(
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
):/EFI/Linux/arch-linux.efi
For more details about supported paths and configuration options, see the
Limine Paths documentation
.
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
}
Keep in mind 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
GRUB
can chainload 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/sd
X
--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
All Systems Go! talk on UKIs
Unified kernel image specification
mkinitcpio v31 and UEFI stubs