Unified Kernel Images

A Unified Kernel Image (UKI) is a single PE/COFF EFI executable that bundles the entire boot payload — the Linux kernel, the initramfs, the kernel command line, OS-release metadata, and optional extras like a device tree, microcode, or boot splash — into one file, built on top of an EFI stub (systemd-stub) that loads those embedded pieces and starts the kernel (UAPI Group UKI spec). The point is trust: because the whole payload is one PE binary, it can be covered by a single UEFI Secure Boot signature, which closes the long-standing hole where the command line and initramfs were unsigned and thus tamperable even on a Secure-Boot system. A UKI is the boot protocol taken to its logical conclusion — instead of a boot loader assembling kernel + cmdline + initrd at runtime from separate (unsigned) files, those pieces are assembled and signed at build time into a tamper-evident unit that the firmware can verify before executing a single instruction.

UKI stands for Unified Kernel Image; PE/COFF stands for Portable Executable / Common Object File Format, the UEFI application container.

Mental Model

Think of a UKI as a shipping container for the boot. In the classic scheme the boot loader is a forklift that, at boot time, picks up a kernel from here, an initramfs from there, and a command line from a text config file, and stacks them in memory. The problem: on a Secure-Boot system the firmware signs the forklift (the boot loader) and maybe the kernel, but the initramfs and command line are loose cargo — nobody signs them, so an attacker with write access to the EFI System Partition or /boot can swap in a malicious initramfs or append init=/bin/sh to the command line and the firmware will never notice.

A UKI seals all the cargo into one welded container, stamps a tamper-evident signature across the whole thing, and the firmware verifies that. You cannot alter the command line or initramfs without breaking the signature. The “forklift” inside is systemd-stub, an EFI stub that knows how to open the container and lay out its contents.

flowchart TB
  subgraph UKI["uki.efi — one signed PE/COFF binary"]
    STUB[".text — systemd-stub<br/>(EFI stub code)"]
    L[".linux — kernel (required)"]
    I[".initrd — initramfs"]
    C[".cmdline — kernel command line"]
    O[".osrel — /etc/os-release"]
    U[".uname — kernel version"]
    S[".sbat — revocation metadata"]
    P[".pcrsig / .pcrpkey — TPM policy"]
  end
  FW["UEFI firmware<br/>verifies ONE signature<br/>over the whole PE file"] --> UKI
  STUB --> EXP["Stub installs LoadFile2 on<br/>LINUX_EFI_INITRD_MEDIA_GUID,<br/>then StartImage(.linux)"]
  EXP --> KSTUB["Embedded kernel's own EFI stub<br/>fetches .initrd via LoadFile2"]
  KSTUB --> RUN["Kernel runs with sealed<br/>cmdline + initrd"]

A UKI as a single signed container of named PE sections. What it shows: the kernel (.linux), initramfs (.initrd), command line (.cmdline) and metadata all live inside one PE file that the firmware verifies with a single signature; systemd-stub is the .text code that unpacks them. The insight to take: because the signature covers the entire file, the initramfs and cmdline are now signed cargo — the exact thing the old loose-file scheme could not protect.

Mechanical Walk-through

The anatomy: named PE sections

A UKI is “a PE/COFF file with various resources stored in PE sections” (UAPI UKI spec). Each piece of the boot payload is a named section. The canonical sections, verbatim from the spec and the systemd-stub man page (systemd-stub(7)):

  • .linux — the Linux kernel image. This is the only required section for the binary to count as a UKI.
  • .initrd — the initramfs (see The initramfs and initrd).
  • .cmdline — the kernel command line as plain text; systemd-stub unpacks it and feeds it to the kernel.
  • .osrel — the contents of /etc/os-release, so boot menus (e.g. sd-boot) can show a human-readable name and only list real OS entries.
  • .uname — the kernel release string (uname -r output), used for version matching and sorting in menus.
  • .ucode — a CPU microcode initrd, which the kernel must see before the main initrd; placed so it is concatenated first.
  • .splash — a boot splash image (Windows .BMP format) shown before the kernel takes over.
  • .dtb — a single compiled Device Tree Blob; .dtbauto holds multiple device trees with hardware-matching for auto-selection.
  • .sbat — SBAT (Secure Boot Advanced Targeting) revocation metadata in CSV form (see below).
  • .pcrsig — JSON-encoded expected TPM PCR 11 values with signatures, for sealing secrets to a measured boot.
  • .pcrpkey — the public key (PEM) that matches .pcrsig.
  • .profile — multi-profile separators, letting one UKI carry several configurations selectable at boot.

The stub also contains the ordinary PE sections every executable has — .text, .data, .reloc, etc.

What systemd-stub does at boot

systemd-stub (shipped on disk as per-architecture files linuxx64.efi.stub, linuxaa64.efi.stub, linuxia32.efi.stub) “is a simple UEFI boot stub” (systemd-stub(7)). When the firmware (or a boot loader, or shim) StartImage()s the UKI, control enters the stub’s code in .text. The stub:

  1. Locates its own embedded sections by walking the PE section table of the running image — it reads .cmdline, finds .initrd, .ucode, .dtb, etc. inside itself.
  2. Measures the sections into the TPM (on TPM-equipped systems) before using them — see the measured-boot section below.
  3. Installs a LoadFile2 protocol on the LINUX_EFI_INITRD_MEDIA_GUID device path, backed by the embedded .initrd (plus .ucode prepended). This is the crucial handshake: systemd-stub does not hand the initrd directly to the kernel. Instead it advertises the initrd through exactly the protocol the kernel’s own EFI stub knows how to consume. The stub then StartImage()s the embedded .linux (which itself is a stub-enabled kernel), and that inner stub calls LoadFile2 on LINUX_EFI_INITRD_MEDIA_GUID and pulls in the initrd — the same efi_load_initrd_dev_path() path traced in The EFI Stub (efi-stub-helper.c, v6.12). This is why the LoadFile2 initrd mechanism (Linux v5.8+, patch) exists and why UKIs and the kernel stub are designed together.
  4. Applies the embedded command line. Under Secure Boot, the embedded .cmdline is authoritative: “if UEFI SecureBoot is enabled and the ‘.cmdline’ section is present in the executed image, any attempts to override the kernel command line by passing one as invocation parameters to the EFI binary are ignored” (systemd-stub(7)). That is the lock — you cannot append init=/bin/sh from a boot menu to a signed UKI. (Without Secure Boot, or with no .cmdline section, an externally-passed command line can take effect.)

Why this closes the unsigned-initramfs hole

The security argument is the whole reason UKIs exist. On a Secure-Boot system without UKIs, the firmware verifies the boot loader and the kernel, but the initramfs is a separate file the boot loader reads at runtime and the command line is editable text. An attacker who can write to /boot or the ESP can:

  • replace the initramfs with one that, say, captures the LUKS passphrase, or
  • edit the command line to drop into a root shell (init=/bin/sh) or disable security features.

Neither requires breaking any signature, because neither file has one. The UKI fixes this by construction: the kernel, initramfs, and command line are all sections of one PE file, and Secure Boot verifies the signature over the entire file. Change any byte of any section and verification fails. As the Boot Loader Specification puts it for Type 2 entries, this bundling enables “SecureBoot protection across the entire boot payload — a feature Type 1 entries cannot provide” (BLS, via spec summary).

SBAT — revoking vulnerable UKIs without exhausting dbx

Signing the payload solves tampering; SBAT (Secure Boot Advanced Targeting) solves revocation when a signed-but-vulnerable image needs to be retired. Historically, UEFI revoked bad binaries by adding their hashes to the dbx blacklist variable. That does not scale: the BootHole vulnerability (CVE-2020-10713) would have consumed roughly a third of typical dbx storage with ~150 hashes plus certificates (shim SBAT.md).

SBAT instead revokes by component + generation number. The .sbat PE section is a CSV listing each component, a security generation integer, vendor, package, version, and a URL (shim SBAT.md):

sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md
grub,2,Free Software Foundation,grub,2.05,https://www.gnu.org/software/grub/
grub.fedora,2,The Fedora Project,grub2,2.04-33.fc33,https://src.fedoraproject.org/rpms/grub2

shim/firmware compare each component’s embedded generation against the minimum recorded in the UEFI SbatLevel revocation variable; if an image’s generation is below the threshold, it is rejected. A single revocation entry retires every build of a component below that generation — one entry replaces dozens of hashes, preserving scarce dbx space. For a UKI this means a class of vulnerable images can be revoked by bumping a number, not by enumerating hashes.

Measured boot and PCRs

Beyond verifying the payload, systemd-stub measures it into the Trusted Platform Module (TPM) so that secrets (LUKS keys, TPM-sealed credentials) can be released only to a known-good boot state. Per systemd-stub(7):

  • The contents of the embedded sections (.linux, .osrel, .cmdline, .initrd, .ucode, .splash, .dtb, .uname, .sbat, .pcrpkey) are measured into PCR 11, using event type EV_IPL, interleaving each section name (NUL-terminated ASCII) with its contents.
  • An overridden command line, the selected profile number, credentials, and config extensions are measured into PCR 12.
  • System extensions go into PCR 13.
  • The firmware measures the whole UKI binary into PCR 4 as part of normal UEFI measured boot.
  • Separately, the kernel itself measures all initrds it receives into PCR 9 (systemd-stub(7)).

The .pcrsig/.pcrpkey sections let a UKI carry a signed policy over the expected PCR 11 value, so a TPM can release a secret based on “the boot matched this signed kernel image” rather than a brittle exact-hash match.

Uncertain

Verify: the exact set of sections measured into PCR 11 and the precise PCR-12/13 split. The systemd-stub(7) summary says “11 of these 12 sections are measured into PCR 11” and lists examples but the exact membership and event encoding evolve across systemd versions. Reason: PCR assignments are a systemd implementation detail that has shifted (and the man page being read is “latest,” not pinned to a specific systemd release). To resolve: read the systemd-stub(7) and systemd.pcrlock pages for the exact systemd version shipped, and confirm against the UAPI TPM2 PCR Measurements spec. uncertain

Configuration and Worked Examples

Building a UKI with ukify

ukify “combines components (usually a kernel, an initrd, and the systemd-stub UEFI stub) to create a UAPI.5 Unified Kernel Image — a single PE binary that boots the system” (ukify(1)). A minimal build:

ukify build \
  --linux=/lib/modules/6.12.0/vmlinuz \
  --initrd=/boot/initramfs-6.12.0.img \
  --cmdline='root=UUID=... ro quiet'
  • build — the subcommand (others are genkey, inspect).
  • --linux= — the kernel; becomes the .linux section (required).
  • --initrd= — the initramfs; becomes .initrd. May be repeated (e.g. a microcode initrd first).
  • --cmdline= — text (or @file) placed in .cmdline.
  • Output (with no --output) is ./vmlinuz.unsigned.efi, an unsigned UKI (ukify(1)).

A signed, measured production build adds metadata and keys:

ukify build \
  --linux=/lib/modules/6.12.0/vmlinuz \
  --initrd=/boot/microcode.img \
  --initrd=/boot/initramfs-6.12.0.img \
  --cmdline=@/etc/kernel/cmdline \
  --os-release=@/etc/os-release \
  --uname=6.12.0 \
  --stub=/usr/lib/systemd/boot/efi/linuxx64.efi.stub \
  --secureboot-private-key=/etc/keys/sb.key \
  --secureboot-certificate=/etc/keys/sb.crt \
  --pcr-private-key=/etc/keys/pcr.key \
  --output=/efi/EFI/Linux/myos-6.12.0.efi
  • --os-release / --uname — populate .osrel and .uname so boot menus can label the entry.
  • --stub — the systemd-stub to use as the executable shell.
  • --secureboot-private-key / --secureboot-certificate — sign the whole resulting PE for Secure Boot.
  • --pcr-private-key — sign the expected PCR-11 policy into .pcrsig/.pcrpkey.
  • --output to /EFI/Linux/ — the canonical location for a Boot Loader Spec Type 2 entry (next section).

mkosi wraps ukify to produce signed UKIs as part of building bootable OS images.

Where a UKI lives — Boot Loader Spec Type 2

The Boot Loader Specification distinguishes Type 1 entries (text .conf files in $BOOT/loader/entries/ pointing at separate kernel/initrd files) from Type 2 entries — the UKIs. Per the spec, “the primary place for such unified images is the /EFI/Linux/ directory in $BOOT,” and a boot loader like sd-boot also scans /EFI/Linux/ on the ESP and presents a merged menu (BLS). So dropping a signed myos-6.12.0.efi into /EFI/Linux/ is all it takes for sd-boot to discover and offer it — no per-entry config file, because the .osrel/.uname sections are the metadata.

Failure Modes and Common Misunderstandings

  • “I edited the kernel command line and nothing changed.” Expected under Secure Boot: a signed UKI’s embedded .cmdline is authoritative and externally-passed command lines are ignored (systemd-stub(7)). This is the feature, not a bug — but it surprises people used to editing GRUB’s command line. To change the cmdline you rebuild and re-sign the UKI (or use the credential/addon mechanism).

  • UKI too large for the ESP. A UKI embeds the kernel and the full initramfs, so it is large (tens to >100 MB), and many ESPs are small (e.g. 100–256 MB). Stacking several kernel versions as UKIs can fill a small ESP. Plan ESP sizing accordingly; this is a frequently-cited adoption pain point.

  • Rebuild-on-every-change. With separate files, regenerating just the initramfs (e.g. after adding a driver) is cheap. With a UKI, any change to kernel, initramfs, or cmdline requires rebuilding and re-signing the whole binary. This is the trade-off for a single signature.

  • Confusing the two stubs. A UKI contains two EFI stubs that cooperate: systemd-stub (the UKI’s .text, which unpacks sections) and the kernel’s own EFI stub inside .linux (which fetches the initrd via LoadFile2). They are different code with different jobs; the LINUX_EFI_INITRD_MEDIA_GUID/LoadFile2 protocol is the contract between them.

  • SBAT self-revocation. Bumping the SbatLevel revocation to retire an old UKI can also brick a system still booting that old UKI. SBAT revocation is powerful and must be rolled out carefully alongside updated images.

Alternatives and When to Choose Them

  • Bare EFISTUB + separate initrd — the kernel boots itself, but the initrd and cmdline are still separate (unsigned) files. Simpler to build and smaller per-update, but does not close the unsigned-initramfs hole. Choose it when Secure Boot of the full payload is not a requirement.
  • GRUB Bootloader + separate kernel/initrd (BLS Type 1) — maximum flexibility (rich menu, edit cmdline at boot, boot from many filesystems), but the loose-file trust gap remains and the chain has more binaries to sign/patch. Choose it for complex multi-boot setups or where editable boot config is required.
  • UKI (BLS Type 2) — choose it when you need the entire boot payload signed and measured: confidential/edge devices, locked-down servers, TPM-sealed disk encryption, and immutable/atomic distributions. The cost is large binaries and rebuild-on-change.

Production Notes — adoption state (as of mid-2026)

UKIs are a systemd-driven direction and the standard is governed by the UAPI Group (uapi-group.org), not a single vendor. Fedora has driven the most visible distro rollout, deliberately phased (Fedora wiki, LWN):

  • Phase 1 (from Fedora 38, 2023): building blocks — ship a UKI as an optional sub-RPM, teach kernel-install to install/update UKIs, add boot-loader support, mainly targeting VMs for development.
  • Phase 2 (from Fedora 40): support booting UKIs directly with the path shim.efi → UKI, no boot loader (no GRUB or sd-boot) in between, and a UEFI-only cloud image variant using UKIs on x86_64 and aarch64.

Crucially, as of these change proposals UKIs remained opt-in, not the universal default: Fedora explicitly stated that whether UKIs become the default everywhere is a later decision (cloud/specific use cases may switch first), and that support for non-UKI kernels is not planned to be removed (Fedora Phase 2). Arch documents UKIs as a fully supported, user-assembled option (ArchWiki).

Uncertain

Verify: the current default status of UKIs in major distros as of mid-2026. The cited Fedora change proposals (Phases 1 and 2, targeting F38/F40) show UKIs as opt-in with cloud/specific images adopting first — they do not establish UKI-by-default for general desktop/server installs, and the proposals predate mid-2026. Reason: distro defaults move release-to-release and the fetched proposals are point-in-time. To resolve: check the release notes of the current Fedora/RHEL/SUSE/Ubuntu versions for whether UKIs are the default boot path. uncertain

UKIs slot naturally into the broader “image-based, signed, measured” boot trend — the same momentum behind the EFI stub replacing GRUB and the deprecation of the EFI handover protocol. By making the boot payload a single signable, measurable, revocable artifact, UKIs align the Linux boot with the way Secure Boot and TPM-based attestation were always meant to work.

See Also