diff mbox series

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

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

Commit Message

Mikko Rapeli Sept. 2, 2024, 10:58 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 | 158 ++++++++++++++++++++++++++++++++
 1 file changed, 158 insertions(+)
 create mode 100644 meta/classes-recipe/uki.bbclass

Comments

Alexander Kanavin Sept. 2, 2024, 11:11 a.m. UTC | #1
Should this also have a wic based selftest or some other way to ensure it
works?

Alex

On Mon 2. Sep 2024 at 12.58, 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 | 158 ++++++++++++++++++++++++++++++++
>  1 file changed, 158 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..8d4bf317fe
> --- /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-boot-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
> --
> 2.34.1
>
>
> -=-=-=-=-=-=-=-=-=-=-=-
> Links: You receive all messages sent to this group.
> View/Reply Online (#204092):
> https://lists.openembedded.org/g/openembedded-core/message/204092
> Mute This Topic: https://lists.openembedded.org/mt/108224526/1686489
> Group Owner: openembedded-core+owner@lists.openembedded.org
> Unsubscribe: https://lists.openembedded.org/g/openembedded-core/unsub [
> alex.kanavin@gmail.com]
> -=-=-=-=-=-=-=-=-=-=-=-
>
>
Mikko Rapeli Sept. 2, 2024, 11:23 a.m. UTC | #2
Hi,

On Mon, Sep 02, 2024 at 01:11:27PM +0200, Alexander Kanavin wrote:
> Should this also have a wic based selftest or some other way to ensure it
> works?

Yes, but that depends on the UEFI / Arm System Ready compatible firmware.

For qemu, this can be setup using meta-arm and qemuarm64-secureboot machine
config. The patches for UEFI secure boot are currently in review and if approved
I will switch those to boot uki binaries, patches are ready but not submitted
yet.

I don't know if poky alone can provide UEFI firmware to boot with.

Cheers,

-Mikko
Alexander Kanavin Sept. 2, 2024, 12:01 p.m. UTC | #3
On Mon 2. Sep 2024 at 13.23, Mikko Rapeli <mikko.rapeli@linaro.org> wrote:

> Hi,
>
> On Mon, Sep 02, 2024 at 01:11:27PM +0200, Alexander Kanavin wrote:
> > Should this also have a wic based selftest or some other way to ensure it
> > works?
>
> Yes, but that depends on the UEFI / Arm System Ready compatible firmware.
>
> For qemu, this can be setup using meta-arm and qemuarm64-secureboot machine
> config. The patches for UEFI secure boot are currently in review and if
> approved
> I will switch those to boot uki binaries, patches are ready but not
> submitted
> yet.
>
> I don't know if poky alone can provide UEFI firmware to boot with.



Doesn’t ovmf recipe provide exactly that? Can you check (grep poky) if
there are existing tests that involve ovmf (I believe there are but can’t
check from a smartphone)?

Alex

>
>
> Cheers,
>
> -Mikko
>
Mikko Rapeli Sept. 2, 2024, 12:25 p.m. UTC | #4
Hi,

On Mon, Sep 02, 2024 at 02:01:31PM +0200, Alexander Kanavin wrote:
> On Mon 2. Sep 2024 at 13.23, Mikko Rapeli <mikko.rapeli@linaro.org> wrote:
> 
> > Hi,
> >
> > On Mon, Sep 02, 2024 at 01:11:27PM +0200, Alexander Kanavin wrote:
> > > Should this also have a wic based selftest or some other way to ensure it
> > > works?
> >
> > Yes, but that depends on the UEFI / Arm System Ready compatible firmware.
> >
> > For qemu, this can be setup using meta-arm and qemuarm64-secureboot machine
> > config. The patches for UEFI secure boot are currently in review and if
> > approved
> > I will switch those to boot uki binaries, patches are ready but not
> > submitted
> > yet.
> >
> > I don't know if poky alone can provide UEFI firmware to boot with.
> 
> 
> 
> Doesn’t ovmf recipe provide exactly that? Can you check (grep poky) if
> there are existing tests that involve ovmf (I believe there are but can’t
> check from a smartphone)?

I've checked and I have not found matching examples. We have everything working
for UEFI secure boot for multiple ARM64 boards and qemu, including oeqa runtime tests.
Currently the qemu side changes to support UEFI secure boot are queued to meta-arm[1].
They could in theory be proposed to poky as well but there is no
matching machine config for that. meta-arm provides u-boot and many other
firmware SW components, including fTPM. ovmf seems to be only for x86,
same for the meta-secure-core side examples for UEFI secure boot.

systemd uki support is really generic and not at all specific to arm
architectures. That's why I think it belongs to poky. Yes, the tests
need to be somewhere else currently unless test target HW already
has UEFI compatible firmware, but even with that the deployment of
signing keys/certs needs to be done separately.

[1] https://lists.yoctoproject.org/g/meta-arm/topic/patch_v4_00_13/108164747

Cheers,

-Mikko
Alexander Kanavin Sept. 2, 2024, 1:03 p.m. UTC | #5
On Mon, 2 Sept 2024 at 14:25, Mikko Rapeli <mikko.rapeli@linaro.org> wrote:
> I've checked and I have not found matching examples. We have everything working
> for UEFI secure boot for multiple ARM64 boards and qemu, including oeqa runtime tests.
> Currently the qemu side changes to support UEFI secure boot are queued to meta-arm[1].
> They could in theory be proposed to poky as well but there is no
> matching machine config for that. meta-arm provides u-boot and many other
> firmware SW components, including fTPM. ovmf seems to be only for x86,
> same for the meta-secure-core side examples for UEFI secure boot.
>
> systemd uki support is really generic and not at all specific to arm
> architectures. That's why I think it belongs to poky. Yes, the tests
> need to be somewhere else currently unless test target HW already
> has UEFI compatible firmware, but even with that the deployment of
> signing keys/certs needs to be done separately.
>
> [1] https://lists.yoctoproject.org/g/meta-arm/topic/patch_v4_00_13/108164747

I've checked now. There is support for UKI in
scripts/lib/wic/plugins/source/bootimg-efi.py

and there's a test for it in

meta/lib/oeqa/selftest/cases/wic.py (see
test_efi_plugin_unified_kernel_image_qemu)
meta-selftest/wic/test_efi_plugin.wks

Which begs the question: why add the class at all? Does it do
something that can't be done by extending wic code? Can you adapt your
work to use the wic plugin using the above as example?

Alex
Mikko Rapeli Sept. 2, 2024, 1:15 p.m. UTC | #6
Hi,

On Mon, Sep 02, 2024 at 03:03:45PM +0200, Alexander Kanavin wrote:
> On Mon, 2 Sept 2024 at 14:25, Mikko Rapeli <mikko.rapeli@linaro.org> wrote:
> > I've checked and I have not found matching examples. We have everything working
> > for UEFI secure boot for multiple ARM64 boards and qemu, including oeqa runtime tests.
> > Currently the qemu side changes to support UEFI secure boot are queued to meta-arm[1].
> > They could in theory be proposed to poky as well but there is no
> > matching machine config for that. meta-arm provides u-boot and many other
> > firmware SW components, including fTPM. ovmf seems to be only for x86,
> > same for the meta-secure-core side examples for UEFI secure boot.
> >
> > systemd uki support is really generic and not at all specific to arm
> > architectures. That's why I think it belongs to poky. Yes, the tests
> > need to be somewhere else currently unless test target HW already
> > has UEFI compatible firmware, but even with that the deployment of
> > signing keys/certs needs to be done separately.
> >
> > [1] https://lists.yoctoproject.org/g/meta-arm/topic/patch_v4_00_13/108164747
> 
> I've checked now. There is support for UKI in
> scripts/lib/wic/plugins/source/bootimg-efi.py
> 
> and there's a test for it in
> 
> meta/lib/oeqa/selftest/cases/wic.py (see
> test_efi_plugin_unified_kernel_image_qemu)
> meta-selftest/wic/test_efi_plugin.wks
> 
> Which begs the question: why add the class at all? Does it do
> something that can't be done by extending wic code? Can you adapt your
> work to use the wic plugin using the above as example?

Well, I wasn't aware of those implementations nor do I know how to use them.

I can try to figure out.

Cheers,

-Mikko
Mikko Rapeli Sept. 2, 2024, 2:14 p.m. UTC | #7
Hi,

On Mon, Sep 02, 2024 at 04:15:21PM +0300, Mikko Rapeli via lists.openembedded.org wrote:
> On Mon, Sep 02, 2024 at 03:03:45PM +0200, Alexander Kanavin wrote:
> > On Mon, 2 Sept 2024 at 14:25, Mikko Rapeli <mikko.rapeli@linaro.org> wrote:
> > > I've checked and I have not found matching examples. We have everything working
> > > for UEFI secure boot for multiple ARM64 boards and qemu, including oeqa runtime tests.
> > > Currently the qemu side changes to support UEFI secure boot are queued to meta-arm[1].
> > > They could in theory be proposed to poky as well but there is no
> > > matching machine config for that. meta-arm provides u-boot and many other
> > > firmware SW components, including fTPM. ovmf seems to be only for x86,
> > > same for the meta-secure-core side examples for UEFI secure boot.
> > >
> > > systemd uki support is really generic and not at all specific to arm
> > > architectures. That's why I think it belongs to poky. Yes, the tests
> > > need to be somewhere else currently unless test target HW already
> > > has UEFI compatible firmware, but even with that the deployment of
> > > signing keys/certs needs to be done separately.
> > >
> > > [1] https://lists.yoctoproject.org/g/meta-arm/topic/patch_v4_00_13/108164747
> > 
> > I've checked now. There is support for UKI in
> > scripts/lib/wic/plugins/source/bootimg-efi.py
> > 
> > and there's a test for it in
> > 
> > meta/lib/oeqa/selftest/cases/wic.py (see
> > test_efi_plugin_unified_kernel_image_qemu)
> > meta-selftest/wic/test_efi_plugin.wks
> > 
> > Which begs the question: why add the class at all? Does it do
> > something that can't be done by extending wic code? Can you adapt your
> > work to use the wic plugin using the above as example?
> 
> Well, I wasn't aware of those implementations nor do I know how to use them.
> 
> I can try to figure out.

So, this is a wic image format specific re-implementation of systemd ukify.py script.
Calling not systemd ukify.py but objcopy directly. No control over kernel command
line, but could possible be added with simple patch. I don't see how to hook uki
signing into the mix with custom keys. Maybe a post processing step to the .wic
image build.

Since this version is merged I presume ukify.bbclass will be rejected.

systemd is the origins of UKI spec and they host the reference implementation
in ukify.py. I would prefer to use those.

I went through quite some pain when getting uki.bbclass to work with TPM devices and
dm-verity from meta-security which involved splitting image into dm-verity image
and .wic image recipes. Now this .wic format specific implementation could
help there, but still leaves kernel command line and signing open. I would
like to upstream these setups so that other users could also implement
secure boot with UEFI all the way to userspace. Testing with qemuarm64 and UEFI
firmware from meta-security based on u-boot is rather straight forward once
details like where to store/generate signing keys are sorted out.

Cheers,

-Mikko
Alexander Kanavin Sept. 2, 2024, 2:30 p.m. UTC | #8
On Mon, 2 Sept 2024 at 16:15, Mikko Rapeli <mikko.rapeli@linaro.org> wrote:

> So, this is a wic image format specific re-implementation of systemd ukify.py script.
> Calling not systemd ukify.py but objcopy directly. No control over kernel command
> line, but could possible be added with simple patch. I don't see how to hook uki
> signing into the mix with custom keys. Maybe a post processing step to the .wic
> image build.
>
> Since this version is merged I presume ukify.bbclass will be rejected.

Not rejected per se; I would suggest that the existing wic
implementation is rewritten to use the class (and hopefully becomes
radically simpler and shorter)!

It's fine to not be entirely backwards compatible; this is master, and
we can break things.

Alex
Mikko Rapeli Sept. 4, 2024, 7:56 a.m. UTC | #9
Hi,

On Mon, Sep 02, 2024 at 04:30:53PM +0200, Alexander Kanavin wrote:
> On Mon, 2 Sept 2024 at 16:15, Mikko Rapeli <mikko.rapeli@linaro.org> wrote:
> 
> > So, this is a wic image format specific re-implementation of systemd ukify.py script.
> > Calling not systemd ukify.py but objcopy directly. No control over kernel command
> > line, but could possible be added with simple patch. I don't see how to hook uki
> > signing into the mix with custom keys. Maybe a post processing step to the .wic
> > image build.
> >
> > Since this version is merged I presume ukify.bbclass will be rejected.
> 
> Not rejected per se; I would suggest that the existing wic
> implementation is rewritten to use the class (and hopefully becomes
> radically simpler and shorter)!
> 
> It's fine to not be entirely backwards compatible; this is master, and
> we can break things.

Does the implementation have to be a wic plugin?

wic is another wrapper over bitbake and I'd need to teach openssl native
and python3native things to it which seems a bit too much. uki.bbclass
is much simpler and wic can process the produced .efi file when creating
the ESP boot partition. I can try to fix the tests to work with uki.bbclass.

Cheers,

-Mikko
Alexander Kanavin Sept. 4, 2024, 9:17 a.m. UTC | #10
On Wed, 4 Sept 2024 at 09:56, Mikko Rapeli <mikko.rapeli@linaro.org> wrote:
> > Not rejected per se; I would suggest that the existing wic
> > implementation is rewritten to use the class (and hopefully becomes
> > radically simpler and shorter)!
> >
> > It's fine to not be entirely backwards compatible; this is master, and
> > we can break things.
>
> Does the implementation have to be a wic plugin?
>
> wic is another wrapper over bitbake and I'd need to teach openssl native
> and python3native things to it which seems a bit too much. uki.bbclass
> is much simpler and wic can process the produced .efi file when creating
> the ESP boot partition. I can try to fix the tests to work with uki.bbclass.

No, implementation can stay in the bbclass. But I don't exactly
remember how the whole thing fits together, and whether you can keep
what the bbclass functionality in the bbclass and have wic call into
it (perhaps indirectly) or vice versa.

I just would like to avoid the situation where there are two entirely
different implementations of the same thing in core, and only one of
them gets tested. If wic can work together with the bbclass, then the
existing wic selftests will test the bbclass as well.

Alex
Mikko Rapeli Sept. 4, 2024, 10:04 a.m. UTC | #11
Adding Alexandre and Kristian since they contributed the uki support,

On Wed, Sep 04, 2024 at 11:17:29AM +0200, Alexander Kanavin wrote:
> On Wed, 4 Sept 2024 at 09:56, Mikko Rapeli <mikko.rapeli@linaro.org> wrote:
> > > Not rejected per se; I would suggest that the existing wic
> > > implementation is rewritten to use the class (and hopefully becomes
> > > radically simpler and shorter)!
> > >
> > > It's fine to not be entirely backwards compatible; this is master, and
> > > we can break things.
> >
> > Does the implementation have to be a wic plugin?
> >
> > wic is another wrapper over bitbake and I'd need to teach openssl native
> > and python3native things to it which seems a bit too much. uki.bbclass
> > is much simpler and wic can process the produced .efi file when creating
> > the ESP boot partition. I can try to fix the tests to work with uki.bbclass.
> 
> No, implementation can stay in the bbclass. But I don't exactly
> remember how the whole thing fits together, and whether you can keep
> what the bbclass functionality in the bbclass and have wic call into
> it (perhaps indirectly) or vice versa.

Current uki support implementation is in a wic plugin,
scripts/lib/wic/plugins/source/bootimg-efi.py. It can't call into bbclass'es.
It can run binaries from recipe sysroot but re-implements the bitbake recipe
runtime environment via scripts/lib/wic/misc.py function exec_native_cmd().
It doesn't support running python3 or openssl binaries, currently, so it
doesn't work with systemd ukify script. Adding that support is possible
but feels like duplicating the bitbake environment in wic.

I can understand the plugin based design if wic is meant to also run outside of
bitbake build environment, but for non-trivial tools like in ukify, python3,
openssl etc this can't easily be done. ukify tool only runs well in the bitbake
build environment with specific versions of those tools and their dependencies.

I would prefer to keep the uki generation in a bbclass where all runtime
dependencies can be managed with bitbake, and use wic for baking
the uki .efi binaries into partitions and images.

Alexandre and Kristian, do you have preferences about uki generation
via bbclass vs wic plugins? Do you expect to run wic outside of bitbake?
If you've added secure boot signing, how did you add that with wic plugin
based uki generation?

> I just would like to avoid the situation where there are two entirely
> different implementations of the same thing in core, and only one of
> them gets tested. If wic can work together with the bbclass, then the
> existing wic selftests will test the bbclass as well.

Understood. I can convert the existing wic plugin code and tests to work
with uki.bbclass generated .efi file and use wic to generate the partitions
and image files only. There's been some adaptation work in the uki generation
due to systemd/systemd-boot implementation and uki specification changes so I
think using the systemd side implementation would be more future proof, though
of course APIs can change there too.

Cheers,

-Mikko
diff mbox series

Patch

diff --git a/meta/classes-recipe/uki.bbclass b/meta/classes-recipe/uki.bbclass
new file mode 100644
index 0000000000..8d4bf317fe
--- /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-boot-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