Unified kernel image - ArchWiki

Jump to content
From ArchWiki

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:

- a UEFI stub loader like systemd-stub(7),
- the kernel command line,
- microcode,
- an initramfs image,
- a kernel image,
- a splash screen.

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

### 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, 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

You can use systemd's kernel-install(8) to automatically install kernels in the UKI format to the esp both for custom kernels and for kernel packages (installed using Pacman) by switching Pacman hooks from mkinitcpio to kernel-install.

`kernel-install` is not an initramfs generator, but it is a framework where packages can hook into the installation/generation of kernels of the system, through its "plugin" system. During its execution it will call the proper initramfs generator of the system (i.e.: mkinitcpio). The plugins are involved in kernel image/initramfs generation, signing, installation, etc. Packages that care about doing something during kernel installation can be notified by installing their own "plugin" for `kernel-install`. (The "plugins" are located in `/usr/lib/kernel/install.d/`.)

There are configuration options like "layout" available that affect where and how the kernel is installed when `kernel-install` is getting called.

mkinitcpio ships with a `kernel-install` plugin that generates the appropriate image (a UKI image for layout=uki). Other programs, such as sbctl, also ship with a `kernel-install` plugin.

To setup kernel-install to produce UKIs:

- Set the kernel-install `layout` to `uki`. e.g.:

      # echo "layout=uki" >> /etc/kernel/install.conf

- 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.

## See also

- Unified kernel image specification
- mkinitcpio v31 and UEFI stubs

Category:

- Boot loaders

Hidden category:

- Pages or sections flagged with Template:Accuracy