diff mbox series

[3/3] uki.bbclass: add class for building Unified Kernel Images (UKI)

Message ID 20240902094117.31156-4-mikko.rapeli@linaro.org
State New
Headers show
Series systemd uki support | expand

Commit Message

Mikko Rapeli Sept. 2, 2024, 9:41 a.m. UTC
From: Michelle Lin <michelle.linto91@gmail.com>

This class calls systemd ukify tool, which will combine
kernel/initrd/stub components to build the UKI. To sign the UKI
(i.e. SecureBoot), the keys/cert files can be specified
in a configuration file or UEFI binary signing can be done
via separate steps, see qemuarm64-secureboot in meta-arm.
UKIs are loaded by UEFI firmware on target which can improve
security by loading only correctly signed kernel, initrd and kernel
command line.

Using systemd-measure to pre-calculate TPM PCR values and sign them is
not supported since that requires a TPM device on the build host. Thus
"ConditionSecurity=measured-uki" default from systemd 256 does not work
but "ConditionSecurity=tpm2" in combination with secure boot will.
These can be used to boot securely into systemd-boot, kernel, kernel
command line and initrd which then securely mounts a read-only dm-verity
/usr partition and creates a TPM encrypted read-write / rootfs.

Tested via qemuarm64-secureboot in meta-arm with
https://lists.yoctoproject.org/g/meta-arm/topic/patch_v3_02_13/108031399
and a few more changes needed, will be posted separately.

Signed-off-by: Michelle Lin <michelle.linto91@gmail.com>
Cc: Erik Schilling <erik.schilling@linaro.org>
Signed-off-by: Mikko Rapeli <mikko.rapeli@linaro.org>
---
 meta/classes-recipe/uki.bbclass | 158 ++++++++++++++++++++++++++++++++
 1 file changed, 158 insertions(+)
 create mode 100644 meta/classes-recipe/uki.bbclass

Comments

Erik Schilling Sept. 2, 2024, 10:36 a.m. UTC | #1
On Mon Sep 2, 2024 at 11:41 AM CEST, Mikko Rapeli wrote:
> From: Michelle Lin <michelle.linto91@gmail.com>
>
> This class calls systemd ukify tool, which will combine
> kernel/initrd/stub components to build the UKI. To sign the UKI
> (i.e. SecureBoot), the keys/cert files can be specified
> in a configuration file or UEFI binary signing can be done
> via separate steps, see qemuarm64-secureboot in meta-arm.
> UKIs are loaded by UEFI firmware on target which can improve
> security by loading only correctly signed kernel, initrd and kernel
> command line.
>
> Using systemd-measure to pre-calculate TPM PCR values and sign them is
> not supported since that requires a TPM device on the build host. Thus
> "ConditionSecurity=measured-uki" default from systemd 256 does not work
> but "ConditionSecurity=tpm2" in combination with secure boot will.
> These can be used to boot securely into systemd-boot, kernel, kernel
> command line and initrd which then securely mounts a read-only dm-verity
> /usr partition and creates a TPM encrypted read-write / rootfs.
>
> Tested via qemuarm64-secureboot in meta-arm with
> https://lists.yoctoproject.org/g/meta-arm/topic/patch_v3_02_13/108031399
> and a few more changes needed, will be posted separately.
>
> Signed-off-by: Michelle Lin <michelle.linto91@gmail.com>
> Cc: Erik Schilling <erik.schilling@linaro.org>
> Signed-off-by: Mikko Rapeli <mikko.rapeli@linaro.org>

> ---
>  meta/classes-recipe/uki.bbclass | 158 ++++++++++++++++++++++++++++++++
>  1 file changed, 158 insertions(+)
>  create mode 100644 meta/classes-recipe/uki.bbclass

Acked-by: Erik Schilling <erik.schilling@linaro.org>
diff mbox series

Patch

diff --git a/meta/classes-recipe/uki.bbclass b/meta/classes-recipe/uki.bbclass
new file mode 100644
index 0000000000..472eb476a0
--- /dev/null
+++ b/meta/classes-recipe/uki.bbclass
@@ -0,0 +1,158 @@ 
+# Unified kernel image (UKI) class
+#
+# This bbclass merges kernel, initrd etc as a UKI standard UEFI binary,
+# to be loaded with UEFI firmware on target. SecureBoot signing is
+# supported via add ons. TPM PCR pre-calculation is not supported since
+# systemd-measure tooling is meant to run on target, not in cross compile
+# environment.
+#
+# See:
+# https://www.freedesktop.org/software/systemd/man/latest/ukify.html
+# https://uapi-group.org/specifications/specs/unified_kernel_image/
+#
+# The UKI is composed from
+#   - an UEFI stub
+#     The linux kernel can generate a UEFI stub, however the one from systemd-boot can fetch
+#     the command line from a separate section of the EFI application, avoiding the need to
+#     rebuild the kernel.
+#   - the kernel
+#   - an initramfs
+#   - other metadata (e.g. PCR measurements)
+#
+# Usage instructions:
+#   - requires UEFI compatible firmware on target, e.g. qemuarm64-secureboot from meta-arm
+#   - Distro config:
+#     INIT_MANAGER = "systemd"
+#     DISTRO_FEATURES += "systemd"
+#     DISTRO_FEATURES_NATIVE += "systemd"
+#     DISTRO_FEATURES += "efi"
+#     DISTRO_FEATURES += "uki"
+#     INITRAMFS_IMAGE ?= "core-image-minimal-initramfs"
+#     HOSTTOOLS += "getent ping"
+#     EFI_PROVIDER = "systemd-boot"
+#   - image recipe:
+#     INHERIT_UKI = "${@bb.utils.contains('DISTRO_FEATURES', 'uki', 'uki', '', d)}"
+#     inherit ${INHERIT_UKI}
+#   - qemuboot/runqemu changes in image recipe:
+#     # Detected by passing kernel parameter
+#     QB_KERNEL_ROOT = ""
+#     # kernel is in the image, should not be loaded separately
+#     QB_DEFAULT_KERNEL = "none"
+#   - for UEFI secure boot, systemd-boot, uki and linux kernel need
+#     to be signed with sbsign (recipe available from meta-secure-core,
+#     see also qemuarm64-secureboot from meta-arm)
+
+DEPENDS += "\
+    systemd \
+    systemd-boot \
+    systemd-tools-native \
+    virtual/${TARGET_PREFIX}binutils \
+    virtual/kernel \
+"
+
+REQUIRED_DISTRO_FEATURES += "usrmerge systemd uki"
+
+inherit features_check image-artifact-names
+require ../conf/image-uefi.conf
+
+INITRAMFS_IMAGE ?= "core-image-minimal-initramfs"
+
+INITRD_ARCHIVE ?= "${INITRAMFS_IMAGE}-${MACHINE}.${INITRAMFS_FSTYPES}"
+
+do_image_complete[depends] += "${INITRAMFS_IMAGE}:do_image_complete"
+
+UKIFY_CMD ?= "ukify build"
+UKI_CONFIG_FILE ?= "${UNPACKDIR}/uki.conf"
+UKI_FILENAME ?= "uki.efi"
+UKI_CMDLINE ?= "rootwait root=/dev/vda2"
+
+IMAGE_EFI_BOOT_FILES ?= "${UKI_FILENAME};EFI/Linux/${UKI_FILENAME}"
+
+do_uki[depends] += " \
+                        systemd-boot:do_deploy \
+                        virtual/kernel:do_deploy \
+                     "
+do_uki[depends] += "${@ '${INITRAMFS_IMAGE}:do_image_complete' if d.getVar('INITRAMFS_IMAGE') else ''}"
+
+# ensure that the build directory is empty everytime we generate a newly-created uki
+do_uki[cleandirs] = "${B}"
+# influence the build directory at the start of the builds
+do_uki[dirs] = "${B}"
+
+# we want to allow specifying files in SRC_URI, such as for signing the UKI
+python () {
+    d.delVarFlag("do_fetch","noexec")
+    d.delVarFlag("do_unpack","noexec")
+}
+
+# main task
+python do_uki() {
+    import glob
+    import bb.process
+
+    # Construct the ukify command
+    ukify_cmd = d.getVar('UKIFY_CMD')
+
+    deploy_dir_image = d.getVar('DEPLOY_DIR_IMAGE')
+
+    # initrd
+    initramfs_image = "%s" % (d.getVar('INITRD_ARCHIVE'))
+    ukify_cmd += " --initrd=%s" % os.path.join(deploy_dir_image, initramfs_image)
+
+    deploy_dir_image = d.getVar('DEPLOY_DIR_IMAGE')
+
+    # Kernel
+    if d.getVar('KERNEL_IMAGETYPE'):
+        kernel = "%s/%s" % (deploy_dir_image, d.getVar('KERNEL_IMAGETYPE'))
+        kernel_version = d.getVar('KERNEL_VERSION')
+        if not os.path.exists(kernel):
+            bb.fatal(f"ERROR: cannot find {kernel}.")
+
+        ukify_cmd += " --linux=%s --uname %s" % (kernel, kernel_version)
+    else:
+        bb.fatal("ERROR - Required argument: KERNEL")
+
+    # Command line
+    cmdline = d.getVar('UKI_CMDLINE')
+    if cmdline:
+        ukify_cmd += " --cmdline='%s'" % cmdline
+
+    # Architecture
+    target_arch = d.getVar('EFI_ARCH')
+    if target_arch:
+        ukify_cmd += " --efi-arch %s" % target_arch
+
+    # systemd stubs from deploy
+    stub = "%s/linux%s.efi.stub" % (d.getVar('DEPLOY_DIR_IMAGE'), target_arch)
+    if not os.path.exists(stub):
+        bb.fatal(f"ERROR: cannot find {stub}.")
+    ukify_cmd += " --stub %s" % stub
+
+    # Add option for dtb
+    if d.getVar('KERNEL_DEVICETREE'):
+        for dtb in d.getVar('KERNEL_DEVICETREE').split():
+            dtb_path = "%s/%s" % (deploy_dir_image, dtb)
+            if not os.path.exists(dtb_path):
+                bb.fatal(f"ERROR: cannot find {dtb_path}.")
+            ukify_cmd += " --devicetree %s" % dtb_path
+
+    # Add option to pass a config file the UKI
+    if os.path.exists(d.getVar('UKI_CONFIG_FILE')):
+        ukify_cmd += " --config=%s" % d.getVar('UKI_CONFIG_FILE')
+
+    # Tools
+    ukify_cmd += " --tools=%s%s/lib/systemd/tools" % (d.getVar("RECIPE_SYSROOT_NATIVE"), d.getVar("prefix"))
+
+    # TODO: tpm2 measure for secure boot, depends on systemd-native and TPM tooling
+    # needed in systemd > 254 to fulfill ConditionSecurity=measured-uki
+    # Requires TPM device on build host, thus not supported at build time.
+    #ukify_cmd += " --measure"
+
+    # Custom UKI name
+    output = " --output=%s/%s" % (d.getVar('DEPLOY_DIR_IMAGE'), d.getVar('UKI_FILENAME'))
+    ukify_cmd += " %s" % output
+
+    # Run the ukify command
+    bb.process.run(ukify_cmd, shell=True)
+}
+addtask uki after do_rootfs before do_deploy do_image_complete do_image_wic