diff mbox series

[v6,1/8] uki.bbclass: add class for building Unified Kernel Images (UKI)

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

Commit Message

Mikko Rapeli Oct. 9, 2024, 11:26 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>
Acked-by: Erik Schilling <erik.schilling@linaro.org>
Signed-off-by: Mikko Rapeli <mikko.rapeli@linaro.org>
---
 meta/classes-recipe/uki.bbclass | 197 ++++++++++++++++++++++++++++++++
 1 file changed, 197 insertions(+)
 create mode 100644 meta/classes-recipe/uki.bbclass

Comments

Ricardo Salveti Oct. 9, 2024, 7:53 p.m. UTC | #1
On Wed, Oct 9, 2024 at 8:27 AM Mikko Rapeli via lists.openembedded.org
<mikko.rapeli=linaro.org@lists.openembedded.org> 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>
> Acked-by: Erik Schilling <erik.schilling@linaro.org>
> Signed-off-by: Mikko Rapeli <mikko.rapeli@linaro.org>
> ---
>  meta/classes-recipe/uki.bbclass | 197 ++++++++++++++++++++++++++++++++
>  1 file changed, 197 insertions(+)
>  create mode 100644 meta/classes-recipe/uki.bbclass
>
> diff --git a/meta/classes-recipe/uki.bbclass b/meta/classes-recipe/uki.bbclass
> new file mode 100644
> index 0000000000..fac50ea8ca
> --- /dev/null
> +++ b/meta/classes-recipe/uki.bbclass
> @@ -0,0 +1,197 @@
> +# Unified kernel image (UKI) class
> +#
> +# This bbclass merges kernel, initrd etc as a UKI standard UEFI binary,
> +# to be loaded with UEFI firmware and systemd-boot on target HW.
> +# 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 contains:
> +#
> +#   - 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.
> +#   - kernel
> +#   - initramfs
> +#   - kernel command line
> +#   - uname -r kernel version
> +#   - /etc/os-release to create a boot menu with version details
> +#   - optionally secure boot signature(s)
> +#   - other metadata (e.g. TPM PCR measurements)
> +#
> +# Usage instructions:
> +#
> +#   - requires UEFI compatible firmware on target, e.g. qemuarm64-secureboot u-boot based
> +#     from meta-arm or qemux86 ovmf/edk2 based firmware for x86_64
> +#
> +#   - Distro/build config:
> +#
> +#     INIT_MANAGER = "systemd"
> +#     MACHINE_FEATURES:append = " efi"
> +#     DISTRO_FEATURES:append = " systemd"
> +#     DISTRO_FEATURES_NATIVE:append = " systemd"
> +#     EFI_PROVIDER = "systemd-boot"
> +#     INITRAMFS_IMAGE = "core-image-minimal-initramfs"
> +#
> +#   - image recipe:
> +#
> +#     inherit uki

Wouldn't it be better if this was a kernel class instead, similar to
how it is done with fitimage (via kernel-fitimage.bbclass)?

I see a lot of similarities here, and it is confusing that one is done
as a kernel class and the other is added by including in the image
recipe instead.

Thanks,
--
Ricardo Salveti
Mikko Rapeli Oct. 10, 2024, 9:06 a.m. UTC | #2
Hi,

On Wed, Oct 09, 2024 at 04:53:58PM -0300, Ricardo Salveti wrote:
> Wouldn't it be better if this was a kernel class instead, similar to
> how it is done with fitimage (via kernel-fitimage.bbclass)?
> 
> I see a lot of similarities here, and it is confusing that one is done
> as a kernel class and the other is added by including in the image
> recipe instead.

Interesting idea. I'll have a look. Maybe this could be an improvement
on top of the uki.bbclass. UKI combines kernel, initramfs image etc
so the implementation can be an image class, kernel class or some
magic post processing (e.g. wic) but needs to be generated using
systemd tooling. Thus I think image class it is for now. I agree
that setting this up is not nice. Need to configure initrd, kernel
command line, kernel, systemd-boot etc and all the config switches
are independent. At least the selftests will contain a fully
working example so users can replicate that in their builds.

Cheers,

-Mikko
Ricardo Salveti Oct. 10, 2024, 3:21 p.m. UTC | #3
On Thu, Oct 10, 2024 at 6:07 AM Mikko Rapeli <mikko.rapeli@linaro.org> wrote:
>
> Hi,
>
> On Wed, Oct 09, 2024 at 04:53:58PM -0300, Ricardo Salveti wrote:
> > Wouldn't it be better if this was a kernel class instead, similar to
> > how it is done with fitimage (via kernel-fitimage.bbclass)?
> >
> > I see a lot of similarities here, and it is confusing that one is done
> > as a kernel class and the other is added by including in the image
> > recipe instead.
>
> Interesting idea. I'll have a look. Maybe this could be an improvement
> on top of the uki.bbclass.

Yes, the current patch set seems to be quite functional and we can
work based on it.

> UKI combines kernel, initramfs image etc
> so the implementation can be an image class, kernel class or some
> magic post processing (e.g. wic) but needs to be generated using
> systemd tooling. Thus I think image class it is for now. I agree
> that setting this up is not nice. Need to configure initrd, kernel
> command line, kernel, systemd-boot etc and all the config switches
> are independent. At least the selftests will contain a fully
> working example so users can replicate that in their builds.

Right, for fitimage we have a similar build dependency chain, and why
I was wondering if it could be also made available via a kernel-class.

Thanks,
diff mbox series

Patch

diff --git a/meta/classes-recipe/uki.bbclass b/meta/classes-recipe/uki.bbclass
new file mode 100644
index 0000000000..fac50ea8ca
--- /dev/null
+++ b/meta/classes-recipe/uki.bbclass
@@ -0,0 +1,197 @@ 
+# Unified kernel image (UKI) class
+#
+# This bbclass merges kernel, initrd etc as a UKI standard UEFI binary,
+# to be loaded with UEFI firmware and systemd-boot on target HW.
+# 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 contains:
+#
+#   - 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.
+#   - kernel
+#   - initramfs
+#   - kernel command line
+#   - uname -r kernel version
+#   - /etc/os-release to create a boot menu with version details
+#   - optionally secure boot signature(s)
+#   - other metadata (e.g. TPM PCR measurements)
+#
+# Usage instructions:
+#
+#   - requires UEFI compatible firmware on target, e.g. qemuarm64-secureboot u-boot based
+#     from meta-arm or qemux86 ovmf/edk2 based firmware for x86_64
+#
+#   - Distro/build config:
+#
+#     INIT_MANAGER = "systemd"
+#     MACHINE_FEATURES:append = " efi"
+#     DISTRO_FEATURES:append = " systemd"
+#     DISTRO_FEATURES_NATIVE:append = " systemd"
+#     EFI_PROVIDER = "systemd-boot"
+#     INITRAMFS_IMAGE = "core-image-minimal-initramfs"
+#
+#   - image recipe:
+#
+#     inherit uki
+#
+#   - qemuboot/runqemu changes in image recipe or build config:
+#
+#     # Kernel command line must be inside the signed uki
+#     QB_KERNEL_ROOT = ""
+#     # kernel is in the uki image, not loaded separately
+#     QB_DEFAULT_KERNEL = "none"
+#
+#   - for UEFI secure boot, systemd-boot and uki (including kernel) can
+#     be signed but require sbsign-tool-native (recipe available from meta-secure-core,
+#     see also qemuarm64-secureboot from meta-arm). Set variable
+#     UKI_SB_KEY to path of private key and UKI_SB_CERT for certificate.
+#     Note that systemd-boot also need to be signed with the same key.
+#
+#   - at runtime, UEFI firmware will load and boot systemd-boot which
+#     creates a menu from all detected uki binaries. No need to manually
+#     setup boot menu entries.
+#
+#   - see efi-uki-bootdisk.wks.in how to create ESP partition which hosts systemd-boot,
+#     config file(s) for systemd-boot and the UKI binaries.
+#
+
+DEPENDS += "\
+    os-release \
+    systemd-boot \
+    systemd-boot-native \
+    virtual/${TARGET_PREFIX}binutils \
+    virtual/kernel \
+"
+
+inherit 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_KERNEL_FILENAME ?= "${KERNEL_IMAGETYPE}"
+UKI_CMDLINE ?= "rootwait root=LABEL=root console=${KERNEL_CONSOLE}"
+# secure boot keys and cert, needs sbsign-tools-native (meta-secure-core)
+#UKI_SB_KEY ?= ""
+#UKI_SB_CERT ?= ""
+
+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
+
+    # base ukify command, can be extended if needed
+    ukify_cmd = d.getVar('UKIFY_CMD')
+
+    deploy_dir_image = d.getVar('DEPLOY_DIR_IMAGE')
+
+    # architecture
+    target_arch = d.getVar('EFI_ARCH')
+    if target_arch:
+        ukify_cmd += " --efi-arch %s" % (target_arch)
+
+    # systemd stubs
+    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)
+
+    # 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
+    kernel_filename = d.getVar('UKI_KERNEL_FILENAME') or None
+    if kernel_filename:
+        kernel = "%s/%s" % (deploy_dir_image, kernel_filename)
+        if not os.path.exists(kernel):
+            bb.fatal(f"ERROR: cannot find %s" % (kernel))
+        ukify_cmd += " --linux=%s" % (kernel)
+        # not always needed, ukify can detect version from kernel binary
+        kernel_version = d.getVar('KERNEL_VERSION')
+        if kernel_version:
+            ukify_cmd += "--uname %s" % (kernel_version)
+    else:
+        bb.fatal("ERROR - UKI_KERNEL_FILENAME not set")
+
+    # command line
+    cmdline = d.getVar('UKI_CMDLINE')
+    if cmdline:
+        ukify_cmd += " --cmdline='%s'" % (cmdline)
+
+    # 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)
+
+    # custom config for ukify
+    if os.path.exists(d.getVar('UKI_CONFIG_FILE')):
+        ukify_cmd += " --config=%s" % (d.getVar('UKI_CONFIG_FILE'))
+
+    # systemd tools
+    ukify_cmd += " --tools=%s%s/lib/systemd/tools" % \
+        (d.getVar("RECIPE_SYSROOT_NATIVE"), d.getVar("prefix"))
+
+    # version
+    ukify_cmd += " --os-release=@%s%s/lib/os-release" % \
+        (d.getVar("RECIPE_SYSROOT"), 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"
+
+    # securebooot signing, also for kernel
+    key = d.getVar('UKI_SB_KEY')
+    if key:
+        ukify_cmd += " --sign-kernel --secureboot-private-key='%s'" % (key)
+    cert = d.getVar('UKI_SB_CERT')
+    if cert:
+        ukify_cmd += " --secureboot-certificate='%s'" % (cert)
+
+    # custom output UKI filename
+    output = " --output=%s/%s" % (d.getVar('DEPLOY_DIR_IMAGE'), d.getVar('UKI_FILENAME'))
+    ukify_cmd += " %s" % (output)
+
+    # Run the ukify command
+    bb.warn("uki: running command: %s" % (ukify_cmd))
+    bb.process.run(ukify_cmd, shell=True)
+}
+addtask uki after do_rootfs before do_deploy do_image_complete do_image_wic