diff mbox series

uki: Add support for building Unified Kernel Images

Message ID 20230901233231.1109712-1-michelle.linto91@gmail.com
State New
Headers show
Series uki: Add support for building Unified Kernel Images | expand

Commit Message

Michelle Lin Sept. 1, 2023, 11:32 p.m. UTC
Currently, there is not a class to support the building of unified kernel
images. Adding a uki.bbclass to support the creation of UKIs. This class calls
the systemd Ukify tool, which will combine the kernel/initrd/stub components to
build the UKI. To sign the UKI (i.e. SecureBoot, TPM PCR signing), the keys/cert
files are to be specified in a separate configuration file, and the path to the
file is passed to the Ukify tool. UKIs are supported by UEFI and can improve
security through predicted TPM PCR states, and reduce the build burden due to
its single PE binary format.

Signed-off-by: Michelle Lin <michelle.linto91@gmail.com>
---
 meta/classes/uki.bbclass                 | 140 +++++++++++++++++++++++
 meta/recipes-core/systemd/systemd_254.bb |  23 ++++
 2 files changed, 163 insertions(+)
 create mode 100644 meta/classes/uki.bbclass

Comments

Alejandro Enedino Hernandez Samaniego Sept. 2, 2023, 1:04 a.m. UTC | #1
Acked-by: Alejandro Hernandez<alhe@linux.microsoft.com>

On 9/1/23 17:32, Michelle Lin wrote:
> Currently, there is not a class to support the building of unified kernel
> images. Adding a uki.bbclass to support the creation of UKIs. This class calls
> the systemd Ukify tool, which will combine the kernel/initrd/stub components to
> build the UKI. To sign the UKI (i.e. SecureBoot, TPM PCR signing), the keys/cert
> files are to be specified in a separate configuration file, and the path to the
> file is passed to the Ukify tool. UKIs are supported by UEFI and can improve
> security through predicted TPM PCR states, and reduce the build burden due to
> its single PE binary format.
>
> Signed-off-by: Michelle Lin<michelle.linto91@gmail.com>
> ---
>   meta/classes/uki.bbclass                 | 140 +++++++++++++++++++++++
>   meta/recipes-core/systemd/systemd_254.bb |  23 ++++
>   2 files changed, 163 insertions(+)
>   create mode 100644 meta/classes/uki.bbclass
>
> diff --git a/meta/classes/uki.bbclass b/meta/classes/uki.bbclass
> new file mode 100644
> index 0000000000..2eff387c75
> --- /dev/null
> +++ b/meta/classes/uki.bbclass
> @@ -0,0 +1,140 @@
> +#
> +# Unified kernel image (UKI) class
> +#
> +#
> +# This bbclass is designed to repack an Overlake image as a UKI, to be booted on a qemuarm64 with SecureBoot
> +# signing and embedded with TPM PCR measurements.
> +#
> +# The UKI is composed by:
> +#   - 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)
> +#
> +#
> +#
> +
> +# List build time dependencies
> +DEPENDS += "systemd-native \
> +            sbsigntool-native \
> +            virtual/${TARGET_PREFIX}binutils \
> +            "
> +
> +REQUIRED_DISTRO_FEATURES += "usrmerge systemd"
> +
> +inherit features_check
> +require ../conf/image-uefi.conf
> +
> +INITRD_IMAGE ?= "core-image-minimal-initramfs"
> +
> +INITRD_LIVE ?= "${@ ('${DEPLOY_DIR_IMAGE}/' + d.getVar('INITRD_IMAGE') + '-${MACHINE}.cpio.gz') if d.getVar('INITRD_IMAGE') else ''}"
> +
> +UKI_CONFIG_FILE ?= "${WORKDIR}/core-image-minimal-uki.conf"
> +UKI_FILENAME ?= "${@ 'UKI.signed.efi' if d.getVar('UKI_CONFIG_FILE') else 'UKI.unsigned.efi'}"
> +
> +do_uki[depends] += " \
> +                        systemd-boot:do_deploy \
> +                        virtual/kernel:do_deploy \
> +                     "
> +
> +# INITRD_IMAGE is added to INITRD_LIVE, which we use to create our initrd, so depend on it if it is set
> +# So we want to generate the initrd image if INITRD_IMAGE exists
> +do_uki[depends] += "${@ '${INITRD_IMAGE}:do_image_complete' if d.getVar('INITRD_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 subprocess
> +
> +    # Construct the ukify command
> +    ukify_cmd = ("ukify build")
> +
> +    # Handle the creation of an initrd image by reading and concatenating multiple cpio files.
> +    # If the INITRD_LIVE variable is defined and not empty, it opens the necessary files, reads their contents,
> +    # and constructs a list.
> +    if d.getVar('INITRD_LIVE'):
> +        initrd_list = ""
> +        for cpio in d.getVar('INITRD_LIVE').split():
> +            # get a list of initrds
> +            initrd_list += cpio + ' '
> +
> +        ukify_cmd += " --initrd=%s" % initrd_list
> +    else:
> +        bb.fatal("ERROR - Required argument: INITRD")
> +
> +    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")
> +
> +    # Architecture
> +    target_arch = d.getVar('EFI_ARCH')
> +    ukify_cmd += " --efi-arch %s" % target_arch
> +
> +    # Stub
> +    stub = "%s/linux%s.efi.stub" % (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'):
> +        first_dtb = d.getVar('KERNEL_DEVICETREE').split()[0]
> +        dtb_path = "%s/%s" % (deploy_dir_image, first_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 to sign the UKI.
> +    if os.path.exists(d.getVar('UKI_CONFIG_FILE')):
> +        ukify_cmd += " --config=%s" % d.getVar('UKI_CONFIG_FILE')
> +        ukify_cmd += " --tools=%s%s/lib/systemd/tools" % (d.getVar("RECIPE_SYSROOT_NATIVE"), d.getVar("prefix"))
> +        bb.note("Pulling keys from config file")
> +    else:
> +        bb.note("Generating unsigned UKI")
> +
> +    # Custom UKI name
> +    output = " --output=%s" % d.getVar('UKI_FILENAME')
> +    ukify_cmd += " %s" % output
> +
> +    # Set env to determine where bitbake should look for dynamic libraries
> +    env = os.environ.copy() # get the env variables
> +    env['LD_LIBRARY_PATH'] = d.expand("${RECIPE_SYSROOT_NATIVE}/usr/lib/systemd:${LD_LIBRARY_PATH}")
> +
> +    # Run the ukify command
> +    subprocess.check_call(ukify_cmd, env=env, shell=True)
> +}
> +
> +inherit deploy
> +
> +do_deploy () {
> +    # Copy generated UKI into DEPLOYDIR
> +	install ${B}/${UKI_FILENAME} ${DEPLOYDIR}
> +}
> +
> +addtask uki before do_deploy do_image after do_rootfs
> +addtask deploy before do_build after do_compile
> \ No newline at end of file
> diff --git a/meta/recipes-core/systemd/systemd_254.bb b/meta/recipes-core/systemd/systemd_254.bb
> index 8d5cf13095..65f132abb8 100644
> --- a/meta/recipes-core/systemd/systemd_254.bb
> +++ b/meta/recipes-core/systemd/systemd_254.bb
> @@ -6,6 +6,9 @@ PE = "1"
>   
>   DEPENDS = "intltool-native gperf-native libcap util-linux python3-jinja2-native"
>   
> +# The Ukify tool requires this module
> +DEPENDS:append:class-native = " python3-pefile-native"
> +
>   SECTION = "base/shell"
>   
>   inherit useradd pkgconfig meson perlnative update-rc.d update-alternatives qemu systemd gettext bash-completion manpages features_check
> @@ -18,6 +21,8 @@ REQUIRED_DISTRO_FEATURES += "usrmerge"
>   # that we don't build both udev and systemd in world builds.
>   REQUIRED_DISTRO_FEATURES += "systemd"
>   
> +REQUIRED_DISTRO_FEATURES:class-native = ""
> +
>   SRC_URI += " \
>              file://touchscreen.rules  \
>              file://00-create-volatile.conf  \
> @@ -120,6 +125,8 @@ PACKAGECONFIG:remove:libc-musl = " \
>   #https://github.com/seccomp/libseccomp/issues/347
>   PACKAGECONFIG:remove:mipsarch = "seccomp"
>   
> +PACKAGECONFIG:class-native = "serial-getty-generator openssl tpm2 efi"
> +
>   TARGET_CC_ARCH:append:libc-musl = " -D__UAPI_DEF_ETHHDR=0 -D_LARGEFILE64_SOURCE"
>   
>   # Some of the dependencies are weak-style recommends - if not available at runtime,
> @@ -260,6 +267,9 @@ EXTRA_OEMESON += "-Dkexec-path=${sbindir}/kexec \
>                     -Dloadkeys-path=${bindir}/loadkeys \
>                     -Dsetfont-path=${bindir}/setfont"
>   
> +EXTRA_OEMESON:append:class-native = " -Dbootloader=true \
> +                                      -Dman=false \
> +                                    "
>   # The 60 seconds is watchdog's default vaule.
>   WATCHDOG_TIMEOUT ??= "60"
>   
> @@ -380,6 +390,14 @@ do_install() {
>       fi
>   }
>   
> +do_install:class-native() {
> +	meson_do_install
> +	install -d ${D}${bindir}
> +	install -m 0755 ${S}/src/ukify/ukify.py ${D}${bindir}/ukify
> +	install -d ${D}${prefix}/lib/systemd/tools
> +	install -m 0755 ${B}/systemd-measure ${D}${prefix}/lib/systemd/tools
> +}
> +
>   python populate_packages:prepend (){
>       systemdlibdir = d.getVar("rootlibdir")
>       do_split_packages(d, systemdlibdir, r'^lib(.*)\.so\.*', 'lib%s', 'Systemd %s library', extra_depends='', allow_links=True)
> @@ -702,6 +720,9 @@ RRECOMMENDS:${PN} += "systemd-extra-utils \
>                         ${@bb.utils.contains('PACKAGECONFIG', 'logind', 'pam-plugin-umask', '', d)} \
>   "
>   
> +RRECOMMENDS:${PN}:class-native = ""
> +RDEPENDS:${PN}:class-native = ""
> +
>   INSANE_SKIP:${PN} += "dev-so libdir"
>   INSANE_SKIP:${PN}-dbg += "libdir"
>   INSANE_SKIP:${PN}-doc += " libdir" @@ -852,3 +873,5 @@ pkg_postinst:udev-hwdb () { pkg_prerm:udev-hwdb 
> () { rm -f $D${sysconfdir}/udev/hwdb.bin } + +BBCLASSEXTEND += "native"
>
> -=-=-=-=-=-=-=-=-=-=-=-
> Links: You receive all messages sent to this group.
> View/Reply Online (#187017):https://lists.openembedded.org/g/openembedded-core/message/187017
> Mute This Topic:https://lists.openembedded.org/mt/101106095/4354175
> Group Owner:openembedded-core+owner@lists.openembedded.org
> Unsubscribe:https://lists.openembedded.org/g/openembedded-core/unsub  [alhe@linux.microsoft.com]
> -=-=-=-=-=-=-=-=-=-=-=-
>
Richard Purdie Sept. 2, 2023, 6:53 a.m. UTC | #2
On Fri, 2023-09-01 at 23:32 +0000, Michelle Lin wrote:
> Currently, there is not a class to support the building of unified kernel
> images. Adding a uki.bbclass to support the creation of UKIs. This class calls
> the systemd Ukify tool, which will combine the kernel/initrd/stub components to
> build the UKI. To sign the UKI (i.e. SecureBoot, TPM PCR signing), the keys/cert
> files are to be specified in a separate configuration file, and the path to the
> file is passed to the Ukify tool. UKIs are supported by UEFI and can improve
> security through predicted TPM PCR states, and reduce the build burden due to
> its single PE binary format.
> 
> Signed-off-by: Michelle Lin <michelle.linto91@gmail.com>
> ---
>  meta/classes/uki.bbclass                 | 140 +++++++++++++++++++++++
>  meta/recipes-core/systemd/systemd_254.bb |  23 ++++
>  2 files changed, 163 insertions(+)
>  create mode 100644 meta/classes/uki.bbclass
> 
> diff --git a/meta/classes/uki.bbclass b/meta/classes/uki.bbclass
> new file mode 100644
> index 0000000000..2eff387c75
> --- /dev/null
> +++ b/meta/classes/uki.bbclass
> @@ -0,0 +1,140 @@
> +#
> +# Unified kernel image (UKI) class
> +#
> +#
> +# This bbclass is designed to repack an Overlake image as a UKI, to be booted on a qemuarm64 with SecureBoot
> +# signing and embedded with TPM PCR measurements.
> +#
> +# The UKI is composed by:
> +#   - 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)
> +#
> +#
> +#
> +
> +# List build time dependencies
> +DEPENDS += "systemd-native \
> +            sbsigntool-native \
> +            virtual/${TARGET_PREFIX}binutils \
> +            "
> +
> +REQUIRED_DISTRO_FEATURES += "usrmerge systemd"
> +
> +inherit features_check
> +require ../conf/image-uefi.conf
> +
> +INITRD_IMAGE ?= "core-image-minimal-initramfs"
> +
> +INITRD_LIVE ?= "${@ ('${DEPLOY_DIR_IMAGE}/' + d.getVar('INITRD_IMAGE') + '-${MACHINE}.cpio.gz') if d.getVar('INITRD_IMAGE') else ''}"
> +
> +UKI_CONFIG_FILE ?= "${WORKDIR}/core-image-minimal-uki.conf"
> +UKI_FILENAME ?= "${@ 'UKI.signed.efi' if d.getVar('UKI_CONFIG_FILE') else 'UKI.unsigned.efi'}"
> +
> +do_uki[depends] += " \
> +                        systemd-boot:do_deploy \
> +                        virtual/kernel:do_deploy \
> +                     "
> +
> +# INITRD_IMAGE is added to INITRD_LIVE, which we use to create our initrd, so depend on it if it is set
> +# So we want to generate the initrd image if INITRD_IMAGE exists
> +do_uki[depends] += "${@ '${INITRD_IMAGE}:do_image_complete' if d.getVar('INITRD_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 subprocess
> +
> +    # Construct the ukify command
> +    ukify_cmd = ("ukify build")
> +
> +    # Handle the creation of an initrd image by reading and concatenating multiple cpio files. 
> +    # If the INITRD_LIVE variable is defined and not empty, it opens the necessary files, reads their contents, 
> +    # and constructs a list.
> +    if d.getVar('INITRD_LIVE'):
> +        initrd_list = ""
> +        for cpio in d.getVar('INITRD_LIVE').split():
> +            # get a list of initrds
> +            initrd_list += cpio + ' '
> +        
> +        ukify_cmd += " --initrd=%s" % initrd_list
> +    else:
> +        bb.fatal("ERROR - Required argument: INITRD")
> +    
> +    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")
> +
> +    # Architecture
> +    target_arch = d.getVar('EFI_ARCH')
> +    ukify_cmd += " --efi-arch %s" % target_arch
> +
> +    # Stub
> +    stub = "%s/linux%s.efi.stub" % (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'):
> +        first_dtb = d.getVar('KERNEL_DEVICETREE').split()[0]
> +        dtb_path = "%s/%s" % (deploy_dir_image, first_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 to sign the UKI.
> +    if os.path.exists(d.getVar('UKI_CONFIG_FILE')):
> +        ukify_cmd += " --config=%s" % d.getVar('UKI_CONFIG_FILE')
> +        ukify_cmd += " --tools=%s%s/lib/systemd/tools" % (d.getVar("RECIPE_SYSROOT_NATIVE"), d.getVar("prefix"))
> +        bb.note("Pulling keys from config file")
> +    else: 
> +        bb.note("Generating unsigned UKI")
> +
> +    # Custom UKI name
> +    output = " --output=%s" % d.getVar('UKI_FILENAME')
> +    ukify_cmd += " %s" % output
> +
> +    # Set env to determine where bitbake should look for dynamic libraries
> +    env = os.environ.copy() # get the env variables
> +    env['LD_LIBRARY_PATH'] = d.expand("${RECIPE_SYSROOT_NATIVE}/usr/lib/systemd:${LD_LIBRARY_PATH}")
> +    
> +    # Run the ukify command
> +    subprocess.check_call(ukify_cmd, env=env, shell=True)
> +}
> + 
> +inherit deploy
> +
> +do_deploy () {
> +    # Copy generated UKI into DEPLOYDIR
> +	install ${B}/${UKI_FILENAME} ${DEPLOYDIR}
> +}
> +
> +addtask uki before do_deploy do_image after do_rootfs
> +addtask deploy before do_build after do_compile
> \ No newline at end of file
> diff --git a/meta/recipes-core/systemd/systemd_254.bb b/meta/recipes-core/systemd/systemd_254.bb
> index 8d5cf13095..65f132abb8 100644
> --- a/meta/recipes-core/systemd/systemd_254.bb
> +++ b/meta/recipes-core/systemd/systemd_254.bb
> @@ -6,6 +6,9 @@ PE = "1"
>  
>  DEPENDS = "intltool-native gperf-native libcap util-linux python3-jinja2-native"
>  
> +# The Ukify tool requires this module
> +DEPENDS:append:class-native = " python3-pefile-native"

Do we need to add this to OE-Core? I think this will cause current
builds to fail?

> +
>  SECTION = "base/shell"
>  
>  inherit useradd pkgconfig meson perlnative update-rc.d update-alternatives qemu systemd gettext bash-completion manpages features_check
> @@ -18,6 +21,8 @@ REQUIRED_DISTRO_FEATURES += "usrmerge"
>  # that we don't build both udev and systemd in world builds.
>  REQUIRED_DISTRO_FEATURES += "systemd"
>  
> +REQUIRED_DISTRO_FEATURES:class-native = ""
> +
>  SRC_URI += " \
>             file://touchscreen.rules \
>             file://00-create-volatile.conf \
> @@ -120,6 +125,8 @@ PACKAGECONFIG:remove:libc-musl = " \
>  # https://github.com/seccomp/libseccomp/issues/347
>  PACKAGECONFIG:remove:mipsarch = "seccomp"
>  
> +PACKAGECONFIG:class-native = "serial-getty-generator openssl tpm2 efi"
> +
>  TARGET_CC_ARCH:append:libc-musl = " -D__UAPI_DEF_ETHHDR=0 -D_LARGEFILE64_SOURCE"
>  
>  # Some of the dependencies are weak-style recommends - if not available at runtime,
> @@ -260,6 +267,9 @@ EXTRA_OEMESON += "-Dkexec-path=${sbindir}/kexec \
>                    -Dloadkeys-path=${bindir}/loadkeys \
>                    -Dsetfont-path=${bindir}/setfont"
>  
> +EXTRA_OEMESON:append:class-native = " -Dbootloader=true \
> +                                      -Dman=false \
> +                                    "
>  # The 60 seconds is watchdog's default vaule.
>  WATCHDOG_TIMEOUT ??= "60"
>  
> @@ -380,6 +390,14 @@ do_install() {
>      fi
>  }
>  
> +do_install:class-native() {
> +	meson_do_install
> +	install -d ${D}${bindir}
> +	install -m 0755 ${S}/src/ukify/ukify.py ${D}${bindir}/ukify
> +	install -d ${D}${prefix}/lib/systemd/tools
> +	install -m 0755 ${B}/systemd-measure ${D}${prefix}/lib/systemd/tools
> +}
> +
>  python populate_packages:prepend (){
>      systemdlibdir = d.getVar("rootlibdir")
>      do_split_packages(d, systemdlibdir, r'^lib(.*)\.so\.*', 'lib%s', 'Systemd %s library', extra_depends='', allow_links=True)
> @@ -702,6 +720,9 @@ RRECOMMENDS:${PN} += "systemd-extra-utils \
>                        ${@bb.utils.contains('PACKAGECONFIG', 'logind', 'pam-plugin-umask', '', d)} \
>  "
>  
> +RRECOMMENDS:${PN}:class-native = ""
> +RDEPENDS:${PN}:class-native = ""
> +
>  INSANE_SKIP:${PN} += "dev-so libdir"
>  INSANE_SKIP:${PN}-dbg += "libdir"
>  INSANE_SKIP:${PN}-doc += " libdir"
> @@ -852,3 +873,5 @@ pkg_postinst:udev-hwdb () {
>  pkg_prerm:udev-hwdb () {
>  	rm -f $D${sysconfdir}/udev/hwdb.bin
>  }
> +
> +BBCLASSEXTEND += "native"

We've long avoided a systemd-native recipe as the meaning can be easily
confused and I'm not thrilled to be adding one now.

Perhaps this should be as a separate systemd-tools-native recipe to
make it clear this isn't full systemd?

How much of systemd does this recipe compile?

Cheers,

Richard
Mikko Rapeli Sept. 4, 2023, 6:23 a.m. UTC | #3
Hi,

On Fri, Sep 01, 2023 at 11:32:31PM +0000, Michelle Lin wrote:
> Currently, there is not a class to support the building of unified kernel
> images. Adding a uki.bbclass to support the creation of UKIs. This class calls
> the systemd Ukify tool, which will combine the kernel/initrd/stub components to
> build the UKI. To sign the UKI (i.e. SecureBoot, TPM PCR signing), the keys/cert
> files are to be specified in a separate configuration file, and the path to the
> file is passed to the Ukify tool. UKIs are supported by UEFI and can improve
> security through predicted TPM PCR states, and reduce the build burden due to
> its single PE binary format.

Thanks, I'm interesting in using this. Could you add a oeqa selftest for this
class too? Something which builds a UKI image and then does something to verify it
is what's expected, maybe full boot with e.g. qemu is too hard to do?

Cheers,

-Mikko
Alejandro Enedino Hernandez Samaniego Sept. 6, 2023, 10:29 p.m. UTC | #4
On 9/2/23 00:53, Richard Purdie wrote:
> On Fri, 2023-09-01 at 23:32 +0000, Michelle Lin wrote:
>> Currently, there is not a class to support the building of unified kernel
>> images. Adding a uki.bbclass to support the creation of UKIs. This class calls
>> the systemd Ukify tool, which will combine the kernel/initrd/stub components to
>> build the UKI. To sign the UKI (i.e. SecureBoot, TPM PCR signing), the keys/cert
>> files are to be specified in a separate configuration file, and the path to the
>> file is passed to the Ukify tool. UKIs are supported by UEFI and can improve
>> security through predicted TPM PCR states, and reduce the build burden due to
>> its single PE binary format.
>>
>> Signed-off-by: Michelle Lin <michelle.linto91@gmail.com>
>> ---
>>   meta/classes/uki.bbclass                 | 140 +++++++++++++++++++++++
>>   meta/recipes-core/systemd/systemd_254.bb |  23 ++++
>>   2 files changed, 163 insertions(+)
>>   create mode 100644 meta/classes/uki.bbclass
>>
>> diff --git a/meta/classes/uki.bbclass b/meta/classes/uki.bbclass
>> new file mode 100644
>> index 0000000000..2eff387c75
>> --- /dev/null
>> +++ b/meta/classes/uki.bbclass
>> @@ -0,0 +1,140 @@
>> +#
>> +# Unified kernel image (UKI) class
>> +#
>> +#
>> +# This bbclass is designed to repack an Overlake image as a UKI, to be booted on a qemuarm64 with SecureBoot
>> +# signing and embedded with TPM PCR measurements.
>> +#
>> +# The UKI is composed by:
>> +#   - 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)
>> +#
>> +#
>> +#
>> +
>> +# List build time dependencies
>> +DEPENDS += "systemd-native \
>> +            sbsigntool-native \
>> +            virtual/${TARGET_PREFIX}binutils \
>> +            "
>> +
>> +REQUIRED_DISTRO_FEATURES += "usrmerge systemd"
>> +
>> +inherit features_check
>> +require ../conf/image-uefi.conf
>> +
>> +INITRD_IMAGE ?= "core-image-minimal-initramfs"
>> +
>> +INITRD_LIVE ?= "${@ ('${DEPLOY_DIR_IMAGE}/' + d.getVar('INITRD_IMAGE') + '-${MACHINE}.cpio.gz') if d.getVar('INITRD_IMAGE') else ''}"
>> +
>> +UKI_CONFIG_FILE ?= "${WORKDIR}/core-image-minimal-uki.conf"
>> +UKI_FILENAME ?= "${@ 'UKI.signed.efi' if d.getVar('UKI_CONFIG_FILE') else 'UKI.unsigned.efi'}"
>> +
>> +do_uki[depends] += " \
>> +                        systemd-boot:do_deploy \
>> +                        virtual/kernel:do_deploy \
>> +                     "
>> +
>> +# INITRD_IMAGE is added to INITRD_LIVE, which we use to create our initrd, so depend on it if it is set
>> +# So we want to generate the initrd image if INITRD_IMAGE exists
>> +do_uki[depends] += "${@ '${INITRD_IMAGE}:do_image_complete' if d.getVar('INITRD_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 subprocess
>> +
>> +    # Construct the ukify command
>> +    ukify_cmd = ("ukify build")
>> +
>> +    # Handle the creation of an initrd image by reading and concatenating multiple cpio files.
>> +    # If the INITRD_LIVE variable is defined and not empty, it opens the necessary files, reads their contents,
>> +    # and constructs a list.
>> +    if d.getVar('INITRD_LIVE'):
>> +        initrd_list = ""
>> +        for cpio in d.getVar('INITRD_LIVE').split():
>> +            # get a list of initrds
>> +            initrd_list += cpio + ' '
>> +
>> +        ukify_cmd += " --initrd=%s" % initrd_list
>> +    else:
>> +        bb.fatal("ERROR - Required argument: INITRD")
>> +
>> +    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")
>> +
>> +    # Architecture
>> +    target_arch = d.getVar('EFI_ARCH')
>> +    ukify_cmd += " --efi-arch %s" % target_arch
>> +
>> +    # Stub
>> +    stub = "%s/linux%s.efi.stub" % (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'):
>> +        first_dtb = d.getVar('KERNEL_DEVICETREE').split()[0]
>> +        dtb_path = "%s/%s" % (deploy_dir_image, first_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 to sign the UKI.
>> +    if os.path.exists(d.getVar('UKI_CONFIG_FILE')):
>> +        ukify_cmd += " --config=%s" % d.getVar('UKI_CONFIG_FILE')
>> +        ukify_cmd += " --tools=%s%s/lib/systemd/tools" % (d.getVar("RECIPE_SYSROOT_NATIVE"), d.getVar("prefix"))
>> +        bb.note("Pulling keys from config file")
>> +    else:
>> +        bb.note("Generating unsigned UKI")
>> +
>> +    # Custom UKI name
>> +    output = " --output=%s" % d.getVar('UKI_FILENAME')
>> +    ukify_cmd += " %s" % output
>> +
>> +    # Set env to determine where bitbake should look for dynamic libraries
>> +    env = os.environ.copy() # get the env variables
>> +    env['LD_LIBRARY_PATH'] = d.expand("${RECIPE_SYSROOT_NATIVE}/usr/lib/systemd:${LD_LIBRARY_PATH}")
>> +
>> +    # Run the ukify command
>> +    subprocess.check_call(ukify_cmd, env=env, shell=True)
>> +}
>> +
>> +inherit deploy
>> +
>> +do_deploy () {
>> +    # Copy generated UKI into DEPLOYDIR
>> +	install ${B}/${UKI_FILENAME} ${DEPLOYDIR}
>> +}
>> +
>> +addtask uki before do_deploy do_image after do_rootfs
>> +addtask deploy before do_build after do_compile
>> \ No newline at end of file
>> diff --git a/meta/recipes-core/systemd/systemd_254.bb b/meta/recipes-core/systemd/systemd_254.bb
>> index 8d5cf13095..65f132abb8 100644
>> --- a/meta/recipes-core/systemd/systemd_254.bb
>> +++ b/meta/recipes-core/systemd/systemd_254.bb
>> @@ -6,6 +6,9 @@ PE = "1"
>>   
>>   DEPENDS = "intltool-native gperf-native libcap util-linux python3-jinja2-native"
>>   
>> +# The Ukify tool requires this module
>> +DEPENDS:append:class-native = " python3-pefile-native"
> Do we need to add this to OE-Core? I think this will cause current
> builds to fail?


I think you are right, there are some dependencies from this that are 
not present in OE-core, this probably belongs in a different layer, at 
least for the time being.


Alejandro

>
>> +
>>   SECTION = "base/shell"
>>   
>>   inherit useradd pkgconfig meson perlnative update-rc.d update-alternatives qemu systemd gettext bash-completion manpages features_check
>> @@ -18,6 +21,8 @@ REQUIRED_DISTRO_FEATURES += "usrmerge"
>>   # that we don't build both udev and systemd in world builds.
>>   REQUIRED_DISTRO_FEATURES += "systemd"
>>   
>> +REQUIRED_DISTRO_FEATURES:class-native = ""
>> +
>>   SRC_URI += " \
>>              file://touchscreen.rules \
>>              file://00-create-volatile.conf \
>> @@ -120,6 +125,8 @@ PACKAGECONFIG:remove:libc-musl = " \
>>   # https://github.com/seccomp/libseccomp/issues/347
>>   PACKAGECONFIG:remove:mipsarch = "seccomp"
>>   
>> +PACKAGECONFIG:class-native = "serial-getty-generator openssl tpm2 efi"
>> +
>>   TARGET_CC_ARCH:append:libc-musl = " -D__UAPI_DEF_ETHHDR=0 -D_LARGEFILE64_SOURCE"
>>   
>>   # Some of the dependencies are weak-style recommends - if not available at runtime,
>> @@ -260,6 +267,9 @@ EXTRA_OEMESON += "-Dkexec-path=${sbindir}/kexec \
>>                     -Dloadkeys-path=${bindir}/loadkeys \
>>                     -Dsetfont-path=${bindir}/setfont"
>>   
>> +EXTRA_OEMESON:append:class-native = " -Dbootloader=true \
>> +                                      -Dman=false \
>> +                                    "
>>   # The 60 seconds is watchdog's default vaule.
>>   WATCHDOG_TIMEOUT ??= "60"
>>   
>> @@ -380,6 +390,14 @@ do_install() {
>>       fi
>>   }
>>   
>> +do_install:class-native() {
>> +	meson_do_install
>> +	install -d ${D}${bindir}
>> +	install -m 0755 ${S}/src/ukify/ukify.py ${D}${bindir}/ukify
>> +	install -d ${D}${prefix}/lib/systemd/tools
>> +	install -m 0755 ${B}/systemd-measure ${D}${prefix}/lib/systemd/tools
>> +}
>> +
>>   python populate_packages:prepend (){
>>       systemdlibdir = d.getVar("rootlibdir")
>>       do_split_packages(d, systemdlibdir, r'^lib(.*)\.so\.*', 'lib%s', 'Systemd %s library', extra_depends='', allow_links=True)
>> @@ -702,6 +720,9 @@ RRECOMMENDS:${PN} += "systemd-extra-utils \
>>                         ${@bb.utils.contains('PACKAGECONFIG', 'logind', 'pam-plugin-umask', '', d)} \
>>   "
>>   
>> +RRECOMMENDS:${PN}:class-native = ""
>> +RDEPENDS:${PN}:class-native = ""
>> +
>>   INSANE_SKIP:${PN} += "dev-so libdir"
>>   INSANE_SKIP:${PN}-dbg += "libdir"
>>   INSANE_SKIP:${PN}-doc += " libdir"
>> @@ -852,3 +873,5 @@ pkg_postinst:udev-hwdb () {
>>   pkg_prerm:udev-hwdb () {
>>   	rm -f $D${sysconfdir}/udev/hwdb.bin
>>   }
>> +
>> +BBCLASSEXTEND += "native"
> We've long avoided a systemd-native recipe as the meaning can be easily
> confused and I'm not thrilled to be adding one now.
>
> Perhaps this should be as a separate systemd-tools-native recipe to
> make it clear this isn't full systemd?
>
> How much of systemd does this recipe compile?
>
> Cheers,
>
> Richard
>
>
>
> -=-=-=-=-=-=-=-=-=-=-=-
> Links: You receive all messages sent to this group.
> View/Reply Online (#187021): https://lists.openembedded.org/g/openembedded-core/message/187021
> Mute This Topic: https://lists.openembedded.org/mt/101106095/4354175
> Group Owner: openembedded-core+owner@lists.openembedded.org
> Unsubscribe: https://lists.openembedded.org/g/openembedded-core/unsub [alhe@linux.microsoft.com]
> -=-=-=-=-=-=-=-=-=-=-=-
>
Erik Schilling Nov. 16, 2023, 11:01 a.m. UTC | #5
On Sat Sep 2, 2023 at 1:32 AM CEST, Michelle Lin wrote:
> Currently, there is not a class to support the building of unified kernel
> images. Adding a uki.bbclass to support the creation of UKIs. This class calls
> the systemd Ukify tool, which will combine the kernel/initrd/stub components to
> build the UKI. To sign the UKI (i.e. SecureBoot, TPM PCR signing), the keys/cert
> files are to be specified in a separate configuration file, and the path to the
> file is passed to the Ukify tool. UKIs are supported by UEFI and can improve
> security through predicted TPM PCR states, and reduce the build burden due to
> its single PE binary format.
>
> Signed-off-by: Michelle Lin <michelle.linto91@gmail.com>
> ---
>  meta/classes/uki.bbclass                 | 140 +++++++++++++++++++++++
>  meta/recipes-core/systemd/systemd_254.bb |  23 ++++
>  2 files changed, 163 insertions(+)
>  create mode 100644 meta/classes/uki.bbclass

Thanks a lot for submitting this!

We are very interested into this. Do you have plans to respin this?

Happy to help where possible :).

- Erik
Dmitry Baryshkov Nov. 20, 2023, 12:48 p.m. UTC | #6
On Thu, 7 Sept 2023 at 01:29, Alejandro Hernandez Samaniego
<alhe@linux.microsoft.com> wrote:
>
>
> On 9/2/23 00:53, Richard Purdie wrote:
> > On Fri, 2023-09-01 at 23:32 +0000, Michelle Lin wrote:
> >> Currently, there is not a class to support the building of unified kernel
> >> images. Adding a uki.bbclass to support the creation of UKIs. This class calls
> >> the systemd Ukify tool, which will combine the kernel/initrd/stub components to
> >> build the UKI. To sign the UKI (i.e. SecureBoot, TPM PCR signing), the keys/cert
> >> files are to be specified in a separate configuration file, and the path to the
> >> file is passed to the Ukify tool. UKIs are supported by UEFI and can improve
> >> security through predicted TPM PCR states, and reduce the build burden due to
> >> its single PE binary format.
> >>
> >> Signed-off-by: Michelle Lin <michelle.linto91@gmail.com>
> >> ---
> >>   meta/classes/uki.bbclass                 | 140 +++++++++++++++++++++++
> >>   meta/recipes-core/systemd/systemd_254.bb |  23 ++++
> >>   2 files changed, 163 insertions(+)
> >>   create mode 100644 meta/classes/uki.bbclass
> >>
> >> diff --git a/meta/classes/uki.bbclass b/meta/classes/uki.bbclass
> >> new file mode 100644
> >> index 0000000000..2eff387c75
> >> --- /dev/null
> >> +++ b/meta/classes/uki.bbclass
> >> @@ -0,0 +1,140 @@
> >> +#
> >> +# Unified kernel image (UKI) class
> >> +#
> >> +#
> >> +# This bbclass is designed to repack an Overlake image as a UKI, to be booted on a qemuarm64 with SecureBoot
> >> +# signing and embedded with TPM PCR measurements.
> >> +#
> >> +# The UKI is composed by:
> >> +#   - 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)
> >> +#
> >> +#
> >> +#
> >> +
> >> +# List build time dependencies
> >> +DEPENDS += "systemd-native \
> >> +            sbsigntool-native \
> >> +            virtual/${TARGET_PREFIX}binutils \
> >> +            "
> >> +
> >> +REQUIRED_DISTRO_FEATURES += "usrmerge systemd"
> >> +
> >> +inherit features_check
> >> +require ../conf/image-uefi.conf
> >> +
> >> +INITRD_IMAGE ?= "core-image-minimal-initramfs"
> >> +
> >> +INITRD_LIVE ?= "${@ ('${DEPLOY_DIR_IMAGE}/' + d.getVar('INITRD_IMAGE') + '-${MACHINE}.cpio.gz') if d.getVar('INITRD_IMAGE') else ''}"
> >> +
> >> +UKI_CONFIG_FILE ?= "${WORKDIR}/core-image-minimal-uki.conf"
> >> +UKI_FILENAME ?= "${@ 'UKI.signed.efi' if d.getVar('UKI_CONFIG_FILE') else 'UKI.unsigned.efi'}"
> >> +
> >> +do_uki[depends] += " \
> >> +                        systemd-boot:do_deploy \
> >> +                        virtual/kernel:do_deploy \
> >> +                     "
> >> +
> >> +# INITRD_IMAGE is added to INITRD_LIVE, which we use to create our initrd, so depend on it if it is set
> >> +# So we want to generate the initrd image if INITRD_IMAGE exists
> >> +do_uki[depends] += "${@ '${INITRD_IMAGE}:do_image_complete' if d.getVar('INITRD_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 subprocess
> >> +
> >> +    # Construct the ukify command
> >> +    ukify_cmd = ("ukify build")
> >> +
> >> +    # Handle the creation of an initrd image by reading and concatenating multiple cpio files.
> >> +    # If the INITRD_LIVE variable is defined and not empty, it opens the necessary files, reads their contents,
> >> +    # and constructs a list.
> >> +    if d.getVar('INITRD_LIVE'):
> >> +        initrd_list = ""
> >> +        for cpio in d.getVar('INITRD_LIVE').split():
> >> +            # get a list of initrds
> >> +            initrd_list += cpio + ' '
> >> +
> >> +        ukify_cmd += " --initrd=%s" % initrd_list
> >> +    else:
> >> +        bb.fatal("ERROR - Required argument: INITRD")
> >> +
> >> +    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")
> >> +
> >> +    # Architecture
> >> +    target_arch = d.getVar('EFI_ARCH')
> >> +    ukify_cmd += " --efi-arch %s" % target_arch
> >> +
> >> +    # Stub
> >> +    stub = "%s/linux%s.efi.stub" % (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'):
> >> +        first_dtb = d.getVar('KERNEL_DEVICETREE').split()[0]
> >> +        dtb_path = "%s/%s" % (deploy_dir_image, first_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 to sign the UKI.
> >> +    if os.path.exists(d.getVar('UKI_CONFIG_FILE')):
> >> +        ukify_cmd += " --config=%s" % d.getVar('UKI_CONFIG_FILE')
> >> +        ukify_cmd += " --tools=%s%s/lib/systemd/tools" % (d.getVar("RECIPE_SYSROOT_NATIVE"), d.getVar("prefix"))
> >> +        bb.note("Pulling keys from config file")
> >> +    else:
> >> +        bb.note("Generating unsigned UKI")
> >> +
> >> +    # Custom UKI name
> >> +    output = " --output=%s" % d.getVar('UKI_FILENAME')
> >> +    ukify_cmd += " %s" % output
> >> +
> >> +    # Set env to determine where bitbake should look for dynamic libraries
> >> +    env = os.environ.copy() # get the env variables
> >> +    env['LD_LIBRARY_PATH'] = d.expand("${RECIPE_SYSROOT_NATIVE}/usr/lib/systemd:${LD_LIBRARY_PATH}")
> >> +
> >> +    # Run the ukify command
> >> +    subprocess.check_call(ukify_cmd, env=env, shell=True)
> >> +}
> >> +
> >> +inherit deploy
> >> +
> >> +do_deploy () {
> >> +    # Copy generated UKI into DEPLOYDIR
> >> +    install ${B}/${UKI_FILENAME} ${DEPLOYDIR}
> >> +}
> >> +
> >> +addtask uki before do_deploy do_image after do_rootfs
> >> +addtask deploy before do_build after do_compile
> >> \ No newline at end of file
> >> diff --git a/meta/recipes-core/systemd/systemd_254.bb b/meta/recipes-core/systemd/systemd_254.bb
> >> index 8d5cf13095..65f132abb8 100644
> >> --- a/meta/recipes-core/systemd/systemd_254.bb
> >> +++ b/meta/recipes-core/systemd/systemd_254.bb
> >> @@ -6,6 +6,9 @@ PE = "1"
> >>
> >>   DEPENDS = "intltool-native gperf-native libcap util-linux python3-jinja2-native"
> >>
> >> +# The Ukify tool requires this module
> >> +DEPENDS:append:class-native = " python3-pefile-native"
> > Do we need to add this to OE-Core? I think this will cause current
> > builds to fail?
>
>
> I think you are right, there are some dependencies from this that are
> not present in OE-core, this probably belongs in a different layer, at
> least for the time being.

I think that ukify is useful in OE-core. Having it in another layer
would mean that we can not use it from the BSP layer.

As the ukify is usable even without sbsign and pesign, I think we can
do either of the following things:

- Mark sbsign and pesign as recomendations instead of dependencies

- Add PACAKGECONFIG for the signing support. If it is not enabled,
replace sbsign and pesign support with the error message.
Bruce Ashfield Nov. 20, 2023, 1:26 p.m. UTC | #7
On Mon, Nov 20, 2023 at 7:49 AM Dmitry Baryshkov <dbaryshkov@gmail.com> wrote:
>
> On Thu, 7 Sept 2023 at 01:29, Alejandro Hernandez Samaniego
> <alhe@linux.microsoft.com> wrote:
> >
> >
> > On 9/2/23 00:53, Richard Purdie wrote:
> > > On Fri, 2023-09-01 at 23:32 +0000, Michelle Lin wrote:
> > >> Currently, there is not a class to support the building of unified kernel
> > >> images. Adding a uki.bbclass to support the creation of UKIs. This class calls
> > >> the systemd Ukify tool, which will combine the kernel/initrd/stub components to
> > >> build the UKI. To sign the UKI (i.e. SecureBoot, TPM PCR signing), the keys/cert
> > >> files are to be specified in a separate configuration file, and the path to the
> > >> file is passed to the Ukify tool. UKIs are supported by UEFI and can improve
> > >> security through predicted TPM PCR states, and reduce the build burden due to
> > >> its single PE binary format.
> > >>
> > >> Signed-off-by: Michelle Lin <michelle.linto91@gmail.com>
> > >> ---
> > >>   meta/classes/uki.bbclass                 | 140 +++++++++++++++++++++++
> > >>   meta/recipes-core/systemd/systemd_254.bb |  23 ++++
> > >>   2 files changed, 163 insertions(+)
> > >>   create mode 100644 meta/classes/uki.bbclass
> > >>
> > >> diff --git a/meta/classes/uki.bbclass b/meta/classes/uki.bbclass
> > >> new file mode 100644
> > >> index 0000000000..2eff387c75
> > >> --- /dev/null
> > >> +++ b/meta/classes/uki.bbclass
> > >> @@ -0,0 +1,140 @@
> > >> +#
> > >> +# Unified kernel image (UKI) class
> > >> +#
> > >> +#
> > >> +# This bbclass is designed to repack an Overlake image as a UKI, to be booted on a qemuarm64 with SecureBoot
> > >> +# signing and embedded with TPM PCR measurements.
> > >> +#
> > >> +# The UKI is composed by:
> > >> +#   - 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)
> > >> +#
> > >> +#
> > >> +#
> > >> +
> > >> +# List build time dependencies
> > >> +DEPENDS += "systemd-native \
> > >> +            sbsigntool-native \
> > >> +            virtual/${TARGET_PREFIX}binutils \
> > >> +            "
> > >> +
> > >> +REQUIRED_DISTRO_FEATURES += "usrmerge systemd"
> > >> +
> > >> +inherit features_check
> > >> +require ../conf/image-uefi.conf
> > >> +
> > >> +INITRD_IMAGE ?= "core-image-minimal-initramfs"
> > >> +
> > >> +INITRD_LIVE ?= "${@ ('${DEPLOY_DIR_IMAGE}/' + d.getVar('INITRD_IMAGE') + '-${MACHINE}.cpio.gz') if d.getVar('INITRD_IMAGE') else ''}"
> > >> +
> > >> +UKI_CONFIG_FILE ?= "${WORKDIR}/core-image-minimal-uki.conf"
> > >> +UKI_FILENAME ?= "${@ 'UKI.signed.efi' if d.getVar('UKI_CONFIG_FILE') else 'UKI.unsigned.efi'}"
> > >> +
> > >> +do_uki[depends] += " \
> > >> +                        systemd-boot:do_deploy \
> > >> +                        virtual/kernel:do_deploy \
> > >> +                     "
> > >> +
> > >> +# INITRD_IMAGE is added to INITRD_LIVE, which we use to create our initrd, so depend on it if it is set
> > >> +# So we want to generate the initrd image if INITRD_IMAGE exists
> > >> +do_uki[depends] += "${@ '${INITRD_IMAGE}:do_image_complete' if d.getVar('INITRD_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 subprocess
> > >> +
> > >> +    # Construct the ukify command
> > >> +    ukify_cmd = ("ukify build")
> > >> +
> > >> +    # Handle the creation of an initrd image by reading and concatenating multiple cpio files.
> > >> +    # If the INITRD_LIVE variable is defined and not empty, it opens the necessary files, reads their contents,
> > >> +    # and constructs a list.
> > >> +    if d.getVar('INITRD_LIVE'):
> > >> +        initrd_list = ""
> > >> +        for cpio in d.getVar('INITRD_LIVE').split():
> > >> +            # get a list of initrds
> > >> +            initrd_list += cpio + ' '
> > >> +
> > >> +        ukify_cmd += " --initrd=%s" % initrd_list
> > >> +    else:
> > >> +        bb.fatal("ERROR - Required argument: INITRD")
> > >> +
> > >> +    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")
> > >> +
> > >> +    # Architecture
> > >> +    target_arch = d.getVar('EFI_ARCH')
> > >> +    ukify_cmd += " --efi-arch %s" % target_arch
> > >> +
> > >> +    # Stub
> > >> +    stub = "%s/linux%s.efi.stub" % (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'):
> > >> +        first_dtb = d.getVar('KERNEL_DEVICETREE').split()[0]
> > >> +        dtb_path = "%s/%s" % (deploy_dir_image, first_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 to sign the UKI.
> > >> +    if os.path.exists(d.getVar('UKI_CONFIG_FILE')):
> > >> +        ukify_cmd += " --config=%s" % d.getVar('UKI_CONFIG_FILE')
> > >> +        ukify_cmd += " --tools=%s%s/lib/systemd/tools" % (d.getVar("RECIPE_SYSROOT_NATIVE"), d.getVar("prefix"))
> > >> +        bb.note("Pulling keys from config file")
> > >> +    else:
> > >> +        bb.note("Generating unsigned UKI")
> > >> +
> > >> +    # Custom UKI name
> > >> +    output = " --output=%s" % d.getVar('UKI_FILENAME')
> > >> +    ukify_cmd += " %s" % output
> > >> +
> > >> +    # Set env to determine where bitbake should look for dynamic libraries
> > >> +    env = os.environ.copy() # get the env variables
> > >> +    env['LD_LIBRARY_PATH'] = d.expand("${RECIPE_SYSROOT_NATIVE}/usr/lib/systemd:${LD_LIBRARY_PATH}")
> > >> +
> > >> +    # Run the ukify command
> > >> +    subprocess.check_call(ukify_cmd, env=env, shell=True)
> > >> +}
> > >> +
> > >> +inherit deploy
> > >> +
> > >> +do_deploy () {
> > >> +    # Copy generated UKI into DEPLOYDIR
> > >> +    install ${B}/${UKI_FILENAME} ${DEPLOYDIR}
> > >> +}
> > >> +
> > >> +addtask uki before do_deploy do_image after do_rootfs
> > >> +addtask deploy before do_build after do_compile
> > >> \ No newline at end of file
> > >> diff --git a/meta/recipes-core/systemd/systemd_254.bb b/meta/recipes-core/systemd/systemd_254.bb
> > >> index 8d5cf13095..65f132abb8 100644
> > >> --- a/meta/recipes-core/systemd/systemd_254.bb
> > >> +++ b/meta/recipes-core/systemd/systemd_254.bb
> > >> @@ -6,6 +6,9 @@ PE = "1"
> > >>
> > >>   DEPENDS = "intltool-native gperf-native libcap util-linux python3-jinja2-native"
> > >>
> > >> +# The Ukify tool requires this module
> > >> +DEPENDS:append:class-native = " python3-pefile-native"
> > > Do we need to add this to OE-Core? I think this will cause current
> > > builds to fail?
> >
> >
> > I think you are right, there are some dependencies from this that are
> > not present in OE-core, this probably belongs in a different layer, at
> > least for the time being.
>
> I think that ukify is useful in OE-core. Having it in another layer
> would mean that we can not use it from the BSP layer.
>

Can you elaborate on that first point ? That sounds like an internal
restriction / rule, so it really doesn't factor into whether or not
something needs to be in oe-core or not.

The answer itself doesn't matter to me, I just didn't want someone
in the future to be searching and think that their BSP layer can
only use functionality from oe-core.

Bruce

> As the ukify is usable even without sbsign and pesign, I think we can
> do either of the following things:
>
> - Mark sbsign and pesign as recomendations instead of dependencies
>
> - Add PACAKGECONFIG for the signing support. If it is not enabled,
> replace sbsign and pesign support with the error message.
>
> --
> With best wishes
> Dmitry
>
> -=-=-=-=-=-=-=-=-=-=-=-
> Links: You receive all messages sent to this group.
> View/Reply Online (#190876): https://lists.openembedded.org/g/openembedded-core/message/190876
> Mute This Topic: https://lists.openembedded.org/mt/101106095/1050810
> Group Owner: openembedded-core+owner@lists.openembedded.org
> Unsubscribe: https://lists.openembedded.org/g/openembedded-core/unsub [bruce.ashfield@gmail.com]
> -=-=-=-=-=-=-=-=-=-=-=-
>
Erik Schilling Nov. 21, 2023, 2:44 p.m. UTC | #8
> > +BBCLASSEXTEND += "native"
>
> We've long avoided a systemd-native recipe as the meaning can be easily
> confused and I'm not thrilled to be adding one now.
>
> Perhaps this should be as a separate systemd-tools-native recipe to
> make it clear this isn't full systemd?

There is another catch: ukify depends on sbsign for some options. Here,
this dependency is not expressed as RDEPENDS on the systemd
components but only on the uki class. That of course gets around the
meta-security-core dependency for systemd, but not sure how pretty that
is.

So we got:
* python3-pefile in meta-python
* sbsigntool in meta-signing-key [meta-security-core]

It looks like we have these options:

1. Add the systemd-tools (or however we call it) recipe and the uki
   class in meta-signing-key or friends.

   This might become a bit icky with different systemd recipes scattered
   over different repos...

2. Do not put a RDEPENDS += "sbsigntool" into the systemd-tools recipe.
   Move python3-pefile to oe-core.

   This means that some ukify options will fail. Users will need to add
   [R]DEPENDS on their recipes if they want signing. This would allow
   adding the systemd-tools recipe in oe-core while adding the rest in
   meta-security-core.

3. Also move the signing tools to oe-core.

   Next to the python module, this also requires to move sbsigntool
   to oe-core... In the end it allows to set the RDEPENDS in
   systemd-tools.

I got no particular strong feeling on any of those outcomes... Any
opinions? 
Dmitry Baryshkov Nov. 22, 2023, 1:06 a.m. UTC | #9
On Mon, 20 Nov 2023 at 15:26, Bruce Ashfield <bruce.ashfield@gmail.com> wrote:
>
> On Mon, Nov 20, 2023 at 7:49 AM Dmitry Baryshkov <dbaryshkov@gmail.com> wrote:
> >
> > On Thu, 7 Sept 2023 at 01:29, Alejandro Hernandez Samaniego
> > <alhe@linux.microsoft.com> wrote:
> > >
> > >
> > > On 9/2/23 00:53, Richard Purdie wrote:
> > > > On Fri, 2023-09-01 at 23:32 +0000, Michelle Lin wrote:
> > > >> Currently, there is not a class to support the building of unified kernel
> > > >> images. Adding a uki.bbclass to support the creation of UKIs. This class calls
> > > >> the systemd Ukify tool, which will combine the kernel/initrd/stub components to
> > > >> build the UKI. To sign the UKI (i.e. SecureBoot, TPM PCR signing), the keys/cert
> > > >> files are to be specified in a separate configuration file, and the path to the
> > > >> file is passed to the Ukify tool. UKIs are supported by UEFI and can improve
> > > >> security through predicted TPM PCR states, and reduce the build burden due to
> > > >> its single PE binary format.
> > > >>
> > > >> Signed-off-by: Michelle Lin <michelle.linto91@gmail.com>
> > > >> ---
> > > >>   meta/classes/uki.bbclass                 | 140 +++++++++++++++++++++++
> > > >>   meta/recipes-core/systemd/systemd_254.bb |  23 ++++
> > > >>   2 files changed, 163 insertions(+)
> > > >>   create mode 100644 meta/classes/uki.bbclass
> > > >>
> > > >> diff --git a/meta/classes/uki.bbclass b/meta/classes/uki.bbclass
> > > >> new file mode 100644
> > > >> index 0000000000..2eff387c75
> > > >> --- /dev/null
> > > >> +++ b/meta/classes/uki.bbclass
> > > >> @@ -0,0 +1,140 @@
> > > >> +#
> > > >> +# Unified kernel image (UKI) class
> > > >> +#
> > > >> +#
> > > >> +# This bbclass is designed to repack an Overlake image as a UKI, to be booted on a qemuarm64 with SecureBoot
> > > >> +# signing and embedded with TPM PCR measurements.
> > > >> +#
> > > >> +# The UKI is composed by:
> > > >> +#   - 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)
> > > >> +#
> > > >> +#
> > > >> +#
> > > >> +
> > > >> +# List build time dependencies
> > > >> +DEPENDS += "systemd-native \
> > > >> +            sbsigntool-native \
> > > >> +            virtual/${TARGET_PREFIX}binutils \
> > > >> +            "
> > > >> +
> > > >> +REQUIRED_DISTRO_FEATURES += "usrmerge systemd"
> > > >> +
> > > >> +inherit features_check
> > > >> +require ../conf/image-uefi.conf
> > > >> +
> > > >> +INITRD_IMAGE ?= "core-image-minimal-initramfs"
> > > >> +
> > > >> +INITRD_LIVE ?= "${@ ('${DEPLOY_DIR_IMAGE}/' + d.getVar('INITRD_IMAGE') + '-${MACHINE}.cpio.gz') if d.getVar('INITRD_IMAGE') else ''}"
> > > >> +
> > > >> +UKI_CONFIG_FILE ?= "${WORKDIR}/core-image-minimal-uki.conf"
> > > >> +UKI_FILENAME ?= "${@ 'UKI.signed.efi' if d.getVar('UKI_CONFIG_FILE') else 'UKI.unsigned.efi'}"
> > > >> +
> > > >> +do_uki[depends] += " \
> > > >> +                        systemd-boot:do_deploy \
> > > >> +                        virtual/kernel:do_deploy \
> > > >> +                     "
> > > >> +
> > > >> +# INITRD_IMAGE is added to INITRD_LIVE, which we use to create our initrd, so depend on it if it is set
> > > >> +# So we want to generate the initrd image if INITRD_IMAGE exists
> > > >> +do_uki[depends] += "${@ '${INITRD_IMAGE}:do_image_complete' if d.getVar('INITRD_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 subprocess
> > > >> +
> > > >> +    # Construct the ukify command
> > > >> +    ukify_cmd = ("ukify build")
> > > >> +
> > > >> +    # Handle the creation of an initrd image by reading and concatenating multiple cpio files.
> > > >> +    # If the INITRD_LIVE variable is defined and not empty, it opens the necessary files, reads their contents,
> > > >> +    # and constructs a list.
> > > >> +    if d.getVar('INITRD_LIVE'):
> > > >> +        initrd_list = ""
> > > >> +        for cpio in d.getVar('INITRD_LIVE').split():
> > > >> +            # get a list of initrds
> > > >> +            initrd_list += cpio + ' '
> > > >> +
> > > >> +        ukify_cmd += " --initrd=%s" % initrd_list
> > > >> +    else:
> > > >> +        bb.fatal("ERROR - Required argument: INITRD")
> > > >> +
> > > >> +    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")
> > > >> +
> > > >> +    # Architecture
> > > >> +    target_arch = d.getVar('EFI_ARCH')
> > > >> +    ukify_cmd += " --efi-arch %s" % target_arch
> > > >> +
> > > >> +    # Stub
> > > >> +    stub = "%s/linux%s.efi.stub" % (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'):
> > > >> +        first_dtb = d.getVar('KERNEL_DEVICETREE').split()[0]
> > > >> +        dtb_path = "%s/%s" % (deploy_dir_image, first_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 to sign the UKI.
> > > >> +    if os.path.exists(d.getVar('UKI_CONFIG_FILE')):
> > > >> +        ukify_cmd += " --config=%s" % d.getVar('UKI_CONFIG_FILE')
> > > >> +        ukify_cmd += " --tools=%s%s/lib/systemd/tools" % (d.getVar("RECIPE_SYSROOT_NATIVE"), d.getVar("prefix"))
> > > >> +        bb.note("Pulling keys from config file")
> > > >> +    else:
> > > >> +        bb.note("Generating unsigned UKI")
> > > >> +
> > > >> +    # Custom UKI name
> > > >> +    output = " --output=%s" % d.getVar('UKI_FILENAME')
> > > >> +    ukify_cmd += " %s" % output
> > > >> +
> > > >> +    # Set env to determine where bitbake should look for dynamic libraries
> > > >> +    env = os.environ.copy() # get the env variables
> > > >> +    env['LD_LIBRARY_PATH'] = d.expand("${RECIPE_SYSROOT_NATIVE}/usr/lib/systemd:${LD_LIBRARY_PATH}")
> > > >> +
> > > >> +    # Run the ukify command
> > > >> +    subprocess.check_call(ukify_cmd, env=env, shell=True)
> > > >> +}
> > > >> +
> > > >> +inherit deploy
> > > >> +
> > > >> +do_deploy () {
> > > >> +    # Copy generated UKI into DEPLOYDIR
> > > >> +    install ${B}/${UKI_FILENAME} ${DEPLOYDIR}
> > > >> +}
> > > >> +
> > > >> +addtask uki before do_deploy do_image after do_rootfs
> > > >> +addtask deploy before do_build after do_compile
> > > >> \ No newline at end of file
> > > >> diff --git a/meta/recipes-core/systemd/systemd_254.bb b/meta/recipes-core/systemd/systemd_254.bb
> > > >> index 8d5cf13095..65f132abb8 100644
> > > >> --- a/meta/recipes-core/systemd/systemd_254.bb
> > > >> +++ b/meta/recipes-core/systemd/systemd_254.bb
> > > >> @@ -6,6 +6,9 @@ PE = "1"
> > > >>
> > > >>   DEPENDS = "intltool-native gperf-native libcap util-linux python3-jinja2-native"
> > > >>
> > > >> +# The Ukify tool requires this module
> > > >> +DEPENDS:append:class-native = " python3-pefile-native"
> > > > Do we need to add this to OE-Core? I think this will cause current
> > > > builds to fail?
> > >
> > >
> > > I think you are right, there are some dependencies from this that are
> > > not present in OE-core, this probably belongs in a different layer, at
> > > least for the time being.
> >
> > I think that ukify is useful in OE-core. Having it in another layer
> > would mean that we can not use it from the BSP layer.
> >
>
> Can you elaborate on that first point ? That sounds like an internal
> restriction / rule, so it really doesn't factor into whether or not
> something needs to be in oe-core or not.

Hmm, maybe I misinterpreted the Yocyo compatibility requirements.
Anyway, if we are allowed to use other layers, then it's even better.
Thanks for pointing that out.

>
> The answer itself doesn't matter to me, I just didn't want someone
> in the future to be searching and think that their BSP layer can
> only use functionality from oe-core.
>
> Bruce
>
> > As the ukify is usable even without sbsign and pesign, I think we can
> > do either of the following things:
> >
> > - Mark sbsign and pesign as recomendations instead of dependencies
> >
> > - Add PACAKGECONFIG for the signing support. If it is not enabled,
> > replace sbsign and pesign support with the error message.
> >
> > --
> > With best wishes
> > Dmitry
> >
> > -=-=-=-=-=-=-=-=-=-=-=-
> > Links: You receive all messages sent to this group.
> > View/Reply Online (#190876): https://lists.openembedded.org/g/openembedded-core/message/190876
> > Mute This Topic: https://lists.openembedded.org/mt/101106095/1050810
> > Group Owner: openembedded-core+owner@lists.openembedded.org
> > Unsubscribe: https://lists.openembedded.org/g/openembedded-core/unsub [bruce.ashfield@gmail.com]
> > -=-=-=-=-=-=-=-=-=-=-=-
> >
>
>
> --
> - Thou shalt not follow the NULL pointer, for chaos and madness await
> thee at its end
> - "Use the force Harry" - Gandalf, Star Trek II
Dmitry Baryshkov Nov. 22, 2023, 1:09 a.m. UTC | #10
On Tue, 21 Nov 2023 at 16:44, Erik Schilling <erik.schilling@linaro.org> wrote:
>
> > > +BBCLASSEXTEND += "native"
> >
> > We've long avoided a systemd-native recipe as the meaning can be easily
> > confused and I'm not thrilled to be adding one now.
> >
> > Perhaps this should be as a separate systemd-tools-native recipe to
> > make it clear this isn't full systemd?
>
> There is another catch: ukify depends on sbsign for some options. Here,
> this dependency is not expressed as RDEPENDS on the systemd
> components but only on the uki class. That of course gets around the
> meta-security-core dependency for systemd, but not sure how pretty that
> is.
>
> So we got:
> * python3-pefile in meta-python
> * sbsigntool in meta-signing-key [meta-security-core]
>
> It looks like we have these options:
>
> 1. Add the systemd-tools (or however we call it) recipe and the uki
>    class in meta-signing-key or friends.
>
>    This might become a bit icky with different systemd recipes scattered
>    over different repos...
>
> 2. Do not put a RDEPENDS += "sbsigntool" into the systemd-tools recipe.
>    Move python3-pefile to oe-core.
>
>    This means that some ukify options will fail. Users will need to add
>    [R]DEPENDS on their recipes if they want signing. This would allow
>    adding the systemd-tools recipe in oe-core while adding the rest in
>    meta-security-core.
>
> 3. Also move the signing tools to oe-core.
>
>    Next to the python module, this also requires to move sbsigntool
>    to oe-core... In the end it allows to set the RDEPENDS in
>    systemd-tools.
>
> I got no particular strong feeling on any of those outcomes... Any
> opinions? 
Dmitry Baryshkov Nov. 28, 2023, 12:32 p.m. UTC | #11
On Sat, 2 Sept 2023 at 02:32, Michelle Lin <michelle.linto91@gmail.com> wrote:
>
> Currently, there is not a class to support the building of unified kernel
> images. Adding a uki.bbclass to support the creation of UKIs. This class calls
> the systemd Ukify tool, which will combine the kernel/initrd/stub components to
> build the UKI. To sign the UKI (i.e. SecureBoot, TPM PCR signing), the keys/cert
> files are to be specified in a separate configuration file, and the path to the
> file is passed to the Ukify tool. UKIs are supported by UEFI and can improve
> security through predicted TPM PCR states, and reduce the build burden due to
> its single PE binary format.
>
> Signed-off-by: Michelle Lin <michelle.linto91@gmail.com>
> ---
>  meta/classes/uki.bbclass                 | 140 +++++++++++++++++++++++
>  meta/recipes-core/systemd/systemd_254.bb |  23 ++++
>  2 files changed, 163 insertions(+)
>  create mode 100644 meta/classes/uki.bbclass
>
> diff --git a/meta/classes/uki.bbclass b/meta/classes/uki.bbclass
> new file mode 100644
> index 0000000000..2eff387c75
> --- /dev/null
> +++ b/meta/classes/uki.bbclass
> @@ -0,0 +1,140 @@
> +#
> +# Unified kernel image (UKI) class
> +#
> +#
> +# This bbclass is designed to repack an Overlake image as a UKI, to be booted on a qemuarm64 with SecureBoot
> +# signing and embedded with TPM PCR measurements.
> +#
> +# The UKI is composed by:
> +#   - 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)
> +#
> +#
> +#
> +
> +# List build time dependencies
> +DEPENDS += "systemd-native \
> +            sbsigntool-native \
> +            virtual/${TARGET_PREFIX}binutils \
> +            "
> +
> +REQUIRED_DISTRO_FEATURES += "usrmerge systemd"
> +
> +inherit features_check
> +require ../conf/image-uefi.conf
> +
> +INITRD_IMAGE ?= "core-image-minimal-initramfs"
> +
> +INITRD_LIVE ?= "${@ ('${DEPLOY_DIR_IMAGE}/' + d.getVar('INITRD_IMAGE') + '-${MACHINE}.cpio.gz') if d.getVar('INITRD_IMAGE') else ''}"
> +
> +UKI_CONFIG_FILE ?= "${WORKDIR}/core-image-minimal-uki.conf"
> +UKI_FILENAME ?= "${@ 'UKI.signed.efi' if d.getVar('UKI_CONFIG_FILE') else 'UKI.unsigned.efi'}"
> +
> +do_uki[depends] += " \
> +                        systemd-boot:do_deploy \
> +                        virtual/kernel:do_deploy \
> +                     "
> +
> +# INITRD_IMAGE is added to INITRD_LIVE, which we use to create our initrd, so depend on it if it is set
> +# So we want to generate the initrd image if INITRD_IMAGE exists
> +do_uki[depends] += "${@ '${INITRD_IMAGE}:do_image_complete' if d.getVar('INITRD_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 subprocess
> +
> +    # Construct the ukify command
> +    ukify_cmd = ("ukify build")
> +
> +    # Handle the creation of an initrd image by reading and concatenating multiple cpio files.
> +    # If the INITRD_LIVE variable is defined and not empty, it opens the necessary files, reads their contents,
> +    # and constructs a list.
> +    if d.getVar('INITRD_LIVE'):
> +        initrd_list = ""
> +        for cpio in d.getVar('INITRD_LIVE').split():
> +            # get a list of initrds
> +            initrd_list += cpio + ' '
> +
> +        ukify_cmd += " --initrd=%s" % initrd_list
> +    else:
> +        bb.fatal("ERROR - Required argument: INITRD")
> +
> +    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")
> +
> +    # Architecture
> +    target_arch = d.getVar('EFI_ARCH')
> +    ukify_cmd += " --efi-arch %s" % target_arch
> +
> +    # Stub
> +    stub = "%s/linux%s.efi.stub" % (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'):
> +        first_dtb = d.getVar('KERNEL_DEVICETREE').split()[0]
> +        dtb_path = "%s/%s" % (deploy_dir_image, first_dtb)
> +
> +        if not os.path.exists(dtb_path):
> +            bb.fatal(f"ERROR: cannot find {dtb_path}.")
> +
> +        ukify_cmd += " --devicetree %s" % dtb_path

Hmm, I have not noticed this before. This doesn't look generic enough.
KERNEL_DEVICETREE can have several DT files and the first one is not
in any way special. It should not be picked up for the UKI image.
E.g. in our (meta-qcom / qcom-armv8a) case the KERNEL_DEVICETREE lists
dtb for all supported machines, ranging from the old dragonboard410c
up to the latest HDKs.

> +
> +    # Add option to pass a config file to sign the UKI.
> +    if os.path.exists(d.getVar('UKI_CONFIG_FILE')):
> +        ukify_cmd += " --config=%s" % d.getVar('UKI_CONFIG_FILE')
> +        ukify_cmd += " --tools=%s%s/lib/systemd/tools" % (d.getVar("RECIPE_SYSROOT_NATIVE"), d.getVar("prefix"))
> +        bb.note("Pulling keys from config file")
> +    else:
> +        bb.note("Generating unsigned UKI")
> +
> +    # Custom UKI name
> +    output = " --output=%s" % d.getVar('UKI_FILENAME')
> +    ukify_cmd += " %s" % output
> +
> +    # Set env to determine where bitbake should look for dynamic libraries
> +    env = os.environ.copy() # get the env variables
> +    env['LD_LIBRARY_PATH'] = d.expand("${RECIPE_SYSROOT_NATIVE}/usr/lib/systemd:${LD_LIBRARY_PATH}")
> +
> +    # Run the ukify command
> +    subprocess.check_call(ukify_cmd, env=env, shell=True)
> +}
> +
> +inherit deploy
> +
> +do_deploy () {
> +    # Copy generated UKI into DEPLOYDIR
> +       install ${B}/${UKI_FILENAME} ${DEPLOYDIR}
> +}
> +
> +addtask uki before do_deploy do_image after do_rootfs
> +addtask deploy before do_build after do_compile
> \ No newline at end of file
> diff --git a/meta/recipes-core/systemd/systemd_254.bb b/meta/recipes-core/systemd/systemd_254.bb
> index 8d5cf13095..65f132abb8 100644
> --- a/meta/recipes-core/systemd/systemd_254.bb
> +++ b/meta/recipes-core/systemd/systemd_254.bb
> @@ -6,6 +6,9 @@ PE = "1"
>
>  DEPENDS = "intltool-native gperf-native libcap util-linux python3-jinja2-native"
>
> +# The Ukify tool requires this module
> +DEPENDS:append:class-native = " python3-pefile-native"
> +
>  SECTION = "base/shell"
>
>  inherit useradd pkgconfig meson perlnative update-rc.d update-alternatives qemu systemd gettext bash-completion manpages features_check
> @@ -18,6 +21,8 @@ REQUIRED_DISTRO_FEATURES += "usrmerge"
>  # that we don't build both udev and systemd in world builds.
>  REQUIRED_DISTRO_FEATURES += "systemd"
>
> +REQUIRED_DISTRO_FEATURES:class-native = ""
> +
>  SRC_URI += " \
>             file://touchscreen.rules \
>             file://00-create-volatile.conf \
> @@ -120,6 +125,8 @@ PACKAGECONFIG:remove:libc-musl = " \
>  # https://github.com/seccomp/libseccomp/issues/347
>  PACKAGECONFIG:remove:mipsarch = "seccomp"
>
> +PACKAGECONFIG:class-native = "serial-getty-generator openssl tpm2 efi"
> +
>  TARGET_CC_ARCH:append:libc-musl = " -D__UAPI_DEF_ETHHDR=0 -D_LARGEFILE64_SOURCE"
>
>  # Some of the dependencies are weak-style recommends - if not available at runtime,
> @@ -260,6 +267,9 @@ EXTRA_OEMESON += "-Dkexec-path=${sbindir}/kexec \
>                    -Dloadkeys-path=${bindir}/loadkeys \
>                    -Dsetfont-path=${bindir}/setfont"
>
> +EXTRA_OEMESON:append:class-native = " -Dbootloader=true \
> +                                      -Dman=false \
> +                                    "
>  # The 60 seconds is watchdog's default vaule.
>  WATCHDOG_TIMEOUT ??= "60"
>
> @@ -380,6 +390,14 @@ do_install() {
>      fi
>  }
>
> +do_install:class-native() {
> +       meson_do_install
> +       install -d ${D}${bindir}
> +       install -m 0755 ${S}/src/ukify/ukify.py ${D}${bindir}/ukify
> +       install -d ${D}${prefix}/lib/systemd/tools
> +       install -m 0755 ${B}/systemd-measure ${D}${prefix}/lib/systemd/tools
> +}
> +
>  python populate_packages:prepend (){
>      systemdlibdir = d.getVar("rootlibdir")
>      do_split_packages(d, systemdlibdir, r'^lib(.*)\.so\.*', 'lib%s', 'Systemd %s library', extra_depends='', allow_links=True)
> @@ -702,6 +720,9 @@ RRECOMMENDS:${PN} += "systemd-extra-utils \
>                        ${@bb.utils.contains('PACKAGECONFIG', 'logind', 'pam-plugin-umask', '', d)} \
>  "
>
> +RRECOMMENDS:${PN}:class-native = ""
> +RDEPENDS:${PN}:class-native = ""
> +
>  INSANE_SKIP:${PN} += "dev-so libdir"
>  INSANE_SKIP:${PN}-dbg += "libdir"
>  INSANE_SKIP:${PN}-doc += " libdir"
> @@ -852,3 +873,5 @@ pkg_postinst:udev-hwdb () {
>  pkg_prerm:udev-hwdb () {
>         rm -f $D${sysconfdir}/udev/hwdb.bin
>  }
> +
> +BBCLASSEXTEND += "native"
> --
> 2.34.1
>
>
> -=-=-=-=-=-=-=-=-=-=-=-
> Links: You receive all messages sent to this group.
> View/Reply Online (#187017): https://lists.openembedded.org/g/openembedded-core/message/187017
> Mute This Topic: https://lists.openembedded.org/mt/101106095/3618183
> Group Owner: openembedded-core+owner@lists.openembedded.org
> Unsubscribe: https://lists.openembedded.org/g/openembedded-core/unsub [dbaryshkov@gmail.com]
> -=-=-=-=-=-=-=-=-=-=-=-
>
Mikko Rapeli Nov. 28, 2023, 12:51 p.m. UTC | #12
Hi,

On Tue, Nov 28, 2023 at 02:32:14PM +0200, Dmitry Baryshkov wrote:
> On Sat, 2 Sept 2023 at 02:32, Michelle Lin <michelle.linto91@gmail.com> wrote:
> >
> > Currently, there is not a class to support the building of unified kernel
> > images. Adding a uki.bbclass to support the creation of UKIs. This class calls
> > the systemd Ukify tool, which will combine the kernel/initrd/stub components to
> > build the UKI. To sign the UKI (i.e. SecureBoot, TPM PCR signing), the keys/cert
> > files are to be specified in a separate configuration file, and the path to the
> > file is passed to the Ukify tool. UKIs are supported by UEFI and can improve
> > security through predicted TPM PCR states, and reduce the build burden due to
> > its single PE binary format.
> >
> > Signed-off-by: Michelle Lin <michelle.linto91@gmail.com>
> > ---
> >  meta/classes/uki.bbclass                 | 140 +++++++++++++++++++++++
> >  meta/recipes-core/systemd/systemd_254.bb |  23 ++++
> >  2 files changed, 163 insertions(+)
> >  create mode 100644 meta/classes/uki.bbclass
> >
> > diff --git a/meta/classes/uki.bbclass b/meta/classes/uki.bbclass
> > new file mode 100644
> > index 0000000000..2eff387c75
> > --- /dev/null
> > +++ b/meta/classes/uki.bbclass
> > @@ -0,0 +1,140 @@
> > +#
> > +# Unified kernel image (UKI) class
> > +#
> > +#
> > +# This bbclass is designed to repack an Overlake image as a UKI, to be booted on a qemuarm64 with SecureBoot
> > +# signing and embedded with TPM PCR measurements.
> > +#
> > +# The UKI is composed by:
> > +#   - 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)
> > +#
> > +#
> > +#
> > +
> > +# List build time dependencies
> > +DEPENDS += "systemd-native \
> > +            sbsigntool-native \
> > +            virtual/${TARGET_PREFIX}binutils \
> > +            "
> > +
> > +REQUIRED_DISTRO_FEATURES += "usrmerge systemd"
> > +
> > +inherit features_check
> > +require ../conf/image-uefi.conf
> > +
> > +INITRD_IMAGE ?= "core-image-minimal-initramfs"
> > +
> > +INITRD_LIVE ?= "${@ ('${DEPLOY_DIR_IMAGE}/' + d.getVar('INITRD_IMAGE') + '-${MACHINE}.cpio.gz') if d.getVar('INITRD_IMAGE') else ''}"
> > +
> > +UKI_CONFIG_FILE ?= "${WORKDIR}/core-image-minimal-uki.conf"
> > +UKI_FILENAME ?= "${@ 'UKI.signed.efi' if d.getVar('UKI_CONFIG_FILE') else 'UKI.unsigned.efi'}"
> > +
> > +do_uki[depends] += " \
> > +                        systemd-boot:do_deploy \
> > +                        virtual/kernel:do_deploy \
> > +                     "
> > +
> > +# INITRD_IMAGE is added to INITRD_LIVE, which we use to create our initrd, so depend on it if it is set
> > +# So we want to generate the initrd image if INITRD_IMAGE exists
> > +do_uki[depends] += "${@ '${INITRD_IMAGE}:do_image_complete' if d.getVar('INITRD_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 subprocess
> > +
> > +    # Construct the ukify command
> > +    ukify_cmd = ("ukify build")
> > +
> > +    # Handle the creation of an initrd image by reading and concatenating multiple cpio files.
> > +    # If the INITRD_LIVE variable is defined and not empty, it opens the necessary files, reads their contents,
> > +    # and constructs a list.
> > +    if d.getVar('INITRD_LIVE'):
> > +        initrd_list = ""
> > +        for cpio in d.getVar('INITRD_LIVE').split():
> > +            # get a list of initrds
> > +            initrd_list += cpio + ' '
> > +
> > +        ukify_cmd += " --initrd=%s" % initrd_list
> > +    else:
> > +        bb.fatal("ERROR - Required argument: INITRD")
> > +
> > +    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")
> > +
> > +    # Architecture
> > +    target_arch = d.getVar('EFI_ARCH')
> > +    ukify_cmd += " --efi-arch %s" % target_arch
> > +
> > +    # Stub
> > +    stub = "%s/linux%s.efi.stub" % (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'):
> > +        first_dtb = d.getVar('KERNEL_DEVICETREE').split()[0]
> > +        dtb_path = "%s/%s" % (deploy_dir_image, first_dtb)
> > +
> > +        if not os.path.exists(dtb_path):
> > +            bb.fatal(f"ERROR: cannot find {dtb_path}.")
> > +
> > +        ukify_cmd += " --devicetree %s" % dtb_path
> 
> Hmm, I have not noticed this before. This doesn't look generic enough.
> KERNEL_DEVICETREE can have several DT files and the first one is not
> in any way special. It should not be picked up for the UKI image.
> E.g. in our (meta-qcom / qcom-armv8a) case the KERNEL_DEVICETREE lists
> dtb for all supported machines, ranging from the old dragonboard410c
> up to the latest HDKs.

So all decice tree files should be looped in. I hope ukify.py supports
this but IMO it should.

Cheers,

-Mikko
 
> > +
> > +    # Add option to pass a config file to sign the UKI.
> > +    if os.path.exists(d.getVar('UKI_CONFIG_FILE')):
> > +        ukify_cmd += " --config=%s" % d.getVar('UKI_CONFIG_FILE')
> > +        ukify_cmd += " --tools=%s%s/lib/systemd/tools" % (d.getVar("RECIPE_SYSROOT_NATIVE"), d.getVar("prefix"))
> > +        bb.note("Pulling keys from config file")
> > +    else:
> > +        bb.note("Generating unsigned UKI")
> > +
> > +    # Custom UKI name
> > +    output = " --output=%s" % d.getVar('UKI_FILENAME')
> > +    ukify_cmd += " %s" % output
> > +
> > +    # Set env to determine where bitbake should look for dynamic libraries
> > +    env = os.environ.copy() # get the env variables
> > +    env['LD_LIBRARY_PATH'] = d.expand("${RECIPE_SYSROOT_NATIVE}/usr/lib/systemd:${LD_LIBRARY_PATH}")
> > +
> > +    # Run the ukify command
> > +    subprocess.check_call(ukify_cmd, env=env, shell=True)
> > +}
> > +
> > +inherit deploy
> > +
> > +do_deploy () {
> > +    # Copy generated UKI into DEPLOYDIR
> > +       install ${B}/${UKI_FILENAME} ${DEPLOYDIR}
> > +}
> > +
> > +addtask uki before do_deploy do_image after do_rootfs
> > +addtask deploy before do_build after do_compile
> > \ No newline at end of file
> > diff --git a/meta/recipes-core/systemd/systemd_254.bb b/meta/recipes-core/systemd/systemd_254.bb
> > index 8d5cf13095..65f132abb8 100644
> > --- a/meta/recipes-core/systemd/systemd_254.bb
> > +++ b/meta/recipes-core/systemd/systemd_254.bb
> > @@ -6,6 +6,9 @@ PE = "1"
> >
> >  DEPENDS = "intltool-native gperf-native libcap util-linux python3-jinja2-native"
> >
> > +# The Ukify tool requires this module
> > +DEPENDS:append:class-native = " python3-pefile-native"
> > +
> >  SECTION = "base/shell"
> >
> >  inherit useradd pkgconfig meson perlnative update-rc.d update-alternatives qemu systemd gettext bash-completion manpages features_check
> > @@ -18,6 +21,8 @@ REQUIRED_DISTRO_FEATURES += "usrmerge"
> >  # that we don't build both udev and systemd in world builds.
> >  REQUIRED_DISTRO_FEATURES += "systemd"
> >
> > +REQUIRED_DISTRO_FEATURES:class-native = ""
> > +
> >  SRC_URI += " \
> >             file://touchscreen.rules \
> >             file://00-create-volatile.conf \
> > @@ -120,6 +125,8 @@ PACKAGECONFIG:remove:libc-musl = " \
> >  # https://github.com/seccomp/libseccomp/issues/347
> >  PACKAGECONFIG:remove:mipsarch = "seccomp"
> >
> > +PACKAGECONFIG:class-native = "serial-getty-generator openssl tpm2 efi"
> > +
> >  TARGET_CC_ARCH:append:libc-musl = " -D__UAPI_DEF_ETHHDR=0 -D_LARGEFILE64_SOURCE"
> >
> >  # Some of the dependencies are weak-style recommends - if not available at runtime,
> > @@ -260,6 +267,9 @@ EXTRA_OEMESON += "-Dkexec-path=${sbindir}/kexec \
> >                    -Dloadkeys-path=${bindir}/loadkeys \
> >                    -Dsetfont-path=${bindir}/setfont"
> >
> > +EXTRA_OEMESON:append:class-native = " -Dbootloader=true \
> > +                                      -Dman=false \
> > +                                    "
> >  # The 60 seconds is watchdog's default vaule.
> >  WATCHDOG_TIMEOUT ??= "60"
> >
> > @@ -380,6 +390,14 @@ do_install() {
> >      fi
> >  }
> >
> > +do_install:class-native() {
> > +       meson_do_install
> > +       install -d ${D}${bindir}
> > +       install -m 0755 ${S}/src/ukify/ukify.py ${D}${bindir}/ukify
> > +       install -d ${D}${prefix}/lib/systemd/tools
> > +       install -m 0755 ${B}/systemd-measure ${D}${prefix}/lib/systemd/tools
> > +}
> > +
> >  python populate_packages:prepend (){
> >      systemdlibdir = d.getVar("rootlibdir")
> >      do_split_packages(d, systemdlibdir, r'^lib(.*)\.so\.*', 'lib%s', 'Systemd %s library', extra_depends='', allow_links=True)
> > @@ -702,6 +720,9 @@ RRECOMMENDS:${PN} += "systemd-extra-utils \
> >                        ${@bb.utils.contains('PACKAGECONFIG', 'logind', 'pam-plugin-umask', '', d)} \
> >  "
> >
> > +RRECOMMENDS:${PN}:class-native = ""
> > +RDEPENDS:${PN}:class-native = ""
> > +
> >  INSANE_SKIP:${PN} += "dev-so libdir"
> >  INSANE_SKIP:${PN}-dbg += "libdir"
> >  INSANE_SKIP:${PN}-doc += " libdir"
> > @@ -852,3 +873,5 @@ pkg_postinst:udev-hwdb () {
> >  pkg_prerm:udev-hwdb () {
> >         rm -f $D${sysconfdir}/udev/hwdb.bin
> >  }
> > +
> > +BBCLASSEXTEND += "native"
> > --
> > 2.34.1
> >
> >
> > 
> >
> 
> 
> -- 
> With best wishes
> Dmitry

> 
> -=-=-=-=-=-=-=-=-=-=-=-
> Links: You receive all messages sent to this group.
> View/Reply Online (#191364): https://lists.openembedded.org/g/openembedded-core/message/191364
> Mute This Topic: https://lists.openembedded.org/mt/101106095/7159507
> Group Owner: openembedded-core+owner@lists.openembedded.org
> Unsubscribe: https://lists.openembedded.org/g/openembedded-core/unsub [mikko.rapeli@linaro.org]
> -=-=-=-=-=-=-=-=-=-=-=-
>
Dmitry Baryshkov Nov. 28, 2023, 1:08 p.m. UTC | #13
On Tue, 28 Nov 2023 at 14:52, Mikko Rapeli <mikko.rapeli@linaro.org> wrote:
>
> Hi,
>
> On Tue, Nov 28, 2023 at 02:32:14PM +0200, Dmitry Baryshkov wrote:
> > On Sat, 2 Sept 2023 at 02:32, Michelle Lin <michelle.linto91@gmail.com> wrote:
> > >
> > > Currently, there is not a class to support the building of unified kernel
> > > images. Adding a uki.bbclass to support the creation of UKIs. This class calls
> > > the systemd Ukify tool, which will combine the kernel/initrd/stub components to
> > > build the UKI. To sign the UKI (i.e. SecureBoot, TPM PCR signing), the keys/cert
> > > files are to be specified in a separate configuration file, and the path to the
> > > file is passed to the Ukify tool. UKIs are supported by UEFI and can improve
> > > security through predicted TPM PCR states, and reduce the build burden due to
> > > its single PE binary format.
> > >
> > > Signed-off-by: Michelle Lin <michelle.linto91@gmail.com>
> > > ---
> > >  meta/classes/uki.bbclass                 | 140 +++++++++++++++++++++++
> > >  meta/recipes-core/systemd/systemd_254.bb |  23 ++++
> > >  2 files changed, 163 insertions(+)
> > >  create mode 100644 meta/classes/uki.bbclass
> > >
> > > diff --git a/meta/classes/uki.bbclass b/meta/classes/uki.bbclass
> > > new file mode 100644
> > > index 0000000000..2eff387c75
> > > --- /dev/null
> > > +++ b/meta/classes/uki.bbclass
> > > @@ -0,0 +1,140 @@
> > > +#
> > > +# Unified kernel image (UKI) class
> > > +#
> > > +#
> > > +# This bbclass is designed to repack an Overlake image as a UKI, to be booted on a qemuarm64 with SecureBoot
> > > +# signing and embedded with TPM PCR measurements.
> > > +#
> > > +# The UKI is composed by:
> > > +#   - 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)
> > > +#
> > > +#
> > > +#
> > > +
> > > +# List build time dependencies
> > > +DEPENDS += "systemd-native \
> > > +            sbsigntool-native \
> > > +            virtual/${TARGET_PREFIX}binutils \
> > > +            "
> > > +
> > > +REQUIRED_DISTRO_FEATURES += "usrmerge systemd"
> > > +
> > > +inherit features_check
> > > +require ../conf/image-uefi.conf
> > > +
> > > +INITRD_IMAGE ?= "core-image-minimal-initramfs"
> > > +
> > > +INITRD_LIVE ?= "${@ ('${DEPLOY_DIR_IMAGE}/' + d.getVar('INITRD_IMAGE') + '-${MACHINE}.cpio.gz') if d.getVar('INITRD_IMAGE') else ''}"
> > > +
> > > +UKI_CONFIG_FILE ?= "${WORKDIR}/core-image-minimal-uki.conf"
> > > +UKI_FILENAME ?= "${@ 'UKI.signed.efi' if d.getVar('UKI_CONFIG_FILE') else 'UKI.unsigned.efi'}"
> > > +
> > > +do_uki[depends] += " \
> > > +                        systemd-boot:do_deploy \
> > > +                        virtual/kernel:do_deploy \
> > > +                     "
> > > +
> > > +# INITRD_IMAGE is added to INITRD_LIVE, which we use to create our initrd, so depend on it if it is set
> > > +# So we want to generate the initrd image if INITRD_IMAGE exists
> > > +do_uki[depends] += "${@ '${INITRD_IMAGE}:do_image_complete' if d.getVar('INITRD_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 subprocess
> > > +
> > > +    # Construct the ukify command
> > > +    ukify_cmd = ("ukify build")
> > > +
> > > +    # Handle the creation of an initrd image by reading and concatenating multiple cpio files.
> > > +    # If the INITRD_LIVE variable is defined and not empty, it opens the necessary files, reads their contents,
> > > +    # and constructs a list.
> > > +    if d.getVar('INITRD_LIVE'):
> > > +        initrd_list = ""
> > > +        for cpio in d.getVar('INITRD_LIVE').split():
> > > +            # get a list of initrds
> > > +            initrd_list += cpio + ' '
> > > +
> > > +        ukify_cmd += " --initrd=%s" % initrd_list
> > > +    else:
> > > +        bb.fatal("ERROR - Required argument: INITRD")
> > > +
> > > +    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")
> > > +
> > > +    # Architecture
> > > +    target_arch = d.getVar('EFI_ARCH')
> > > +    ukify_cmd += " --efi-arch %s" % target_arch
> > > +
> > > +    # Stub
> > > +    stub = "%s/linux%s.efi.stub" % (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'):
> > > +        first_dtb = d.getVar('KERNEL_DEVICETREE').split()[0]
> > > +        dtb_path = "%s/%s" % (deploy_dir_image, first_dtb)
> > > +
> > > +        if not os.path.exists(dtb_path):
> > > +            bb.fatal(f"ERROR: cannot find {dtb_path}.")
> > > +
> > > +        ukify_cmd += " --devicetree %s" % dtb_path
> >
> > Hmm, I have not noticed this before. This doesn't look generic enough.
> > KERNEL_DEVICETREE can have several DT files and the first one is not
> > in any way special. It should not be picked up for the UKI image.
> > E.g. in our (meta-qcom / qcom-armv8a) case the KERNEL_DEVICETREE lists
> > dtb for all supported machines, ranging from the old dragonboard410c
> > up to the latest HDKs.
>
> So all decice tree files should be looped in. I hope ukify.py supports
> this but IMO it should.

My preference would be if we can skip them completely. I'd prefer to
be able to build the generic UKI image even if KERNEL_DEVICETREE is
defined (and maybe put DTs to the addons).

> Cheers,
>
> -Mikko
>
> > > +
> > > +    # Add option to pass a config file to sign the UKI.
> > > +    if os.path.exists(d.getVar('UKI_CONFIG_FILE')):
> > > +        ukify_cmd += " --config=%s" % d.getVar('UKI_CONFIG_FILE')
> > > +        ukify_cmd += " --tools=%s%s/lib/systemd/tools" % (d.getVar("RECIPE_SYSROOT_NATIVE"), d.getVar("prefix"))
> > > +        bb.note("Pulling keys from config file")
> > > +    else:
> > > +        bb.note("Generating unsigned UKI")
> > > +
> > > +    # Custom UKI name
> > > +    output = " --output=%s" % d.getVar('UKI_FILENAME')
> > > +    ukify_cmd += " %s" % output
> > > +
> > > +    # Set env to determine where bitbake should look for dynamic libraries
> > > +    env = os.environ.copy() # get the env variables
> > > +    env['LD_LIBRARY_PATH'] = d.expand("${RECIPE_SYSROOT_NATIVE}/usr/lib/systemd:${LD_LIBRARY_PATH}")
> > > +
> > > +    # Run the ukify command
> > > +    subprocess.check_call(ukify_cmd, env=env, shell=True)
> > > +}
> > > +
> > > +inherit deploy
> > > +
> > > +do_deploy () {
> > > +    # Copy generated UKI into DEPLOYDIR
> > > +       install ${B}/${UKI_FILENAME} ${DEPLOYDIR}
> > > +}
> > > +
> > > +addtask uki before do_deploy do_image after do_rootfs
> > > +addtask deploy before do_build after do_compile
> > > \ No newline at end of file
> > > diff --git a/meta/recipes-core/systemd/systemd_254.bb b/meta/recipes-core/systemd/systemd_254.bb
> > > index 8d5cf13095..65f132abb8 100644
> > > --- a/meta/recipes-core/systemd/systemd_254.bb
> > > +++ b/meta/recipes-core/systemd/systemd_254.bb
> > > @@ -6,6 +6,9 @@ PE = "1"
> > >
> > >  DEPENDS = "intltool-native gperf-native libcap util-linux python3-jinja2-native"
> > >
> > > +# The Ukify tool requires this module
> > > +DEPENDS:append:class-native = " python3-pefile-native"
> > > +
> > >  SECTION = "base/shell"
> > >
> > >  inherit useradd pkgconfig meson perlnative update-rc.d update-alternatives qemu systemd gettext bash-completion manpages features_check
> > > @@ -18,6 +21,8 @@ REQUIRED_DISTRO_FEATURES += "usrmerge"
> > >  # that we don't build both udev and systemd in world builds.
> > >  REQUIRED_DISTRO_FEATURES += "systemd"
> > >
> > > +REQUIRED_DISTRO_FEATURES:class-native = ""
> > > +
> > >  SRC_URI += " \
> > >             file://touchscreen.rules \
> > >             file://00-create-volatile.conf \
> > > @@ -120,6 +125,8 @@ PACKAGECONFIG:remove:libc-musl = " \
> > >  # https://github.com/seccomp/libseccomp/issues/347
> > >  PACKAGECONFIG:remove:mipsarch = "seccomp"
> > >
> > > +PACKAGECONFIG:class-native = "serial-getty-generator openssl tpm2 efi"
> > > +
> > >  TARGET_CC_ARCH:append:libc-musl = " -D__UAPI_DEF_ETHHDR=0 -D_LARGEFILE64_SOURCE"
> > >
> > >  # Some of the dependencies are weak-style recommends - if not available at runtime,
> > > @@ -260,6 +267,9 @@ EXTRA_OEMESON += "-Dkexec-path=${sbindir}/kexec \
> > >                    -Dloadkeys-path=${bindir}/loadkeys \
> > >                    -Dsetfont-path=${bindir}/setfont"
> > >
> > > +EXTRA_OEMESON:append:class-native = " -Dbootloader=true \
> > > +                                      -Dman=false \
> > > +                                    "
> > >  # The 60 seconds is watchdog's default vaule.
> > >  WATCHDOG_TIMEOUT ??= "60"
> > >
> > > @@ -380,6 +390,14 @@ do_install() {
> > >      fi
> > >  }
> > >
> > > +do_install:class-native() {
> > > +       meson_do_install
> > > +       install -d ${D}${bindir}
> > > +       install -m 0755 ${S}/src/ukify/ukify.py ${D}${bindir}/ukify
> > > +       install -d ${D}${prefix}/lib/systemd/tools
> > > +       install -m 0755 ${B}/systemd-measure ${D}${prefix}/lib/systemd/tools
> > > +}
> > > +
> > >  python populate_packages:prepend (){
> > >      systemdlibdir = d.getVar("rootlibdir")
> > >      do_split_packages(d, systemdlibdir, r'^lib(.*)\.so\.*', 'lib%s', 'Systemd %s library', extra_depends='', allow_links=True)
> > > @@ -702,6 +720,9 @@ RRECOMMENDS:${PN} += "systemd-extra-utils \
> > >                        ${@bb.utils.contains('PACKAGECONFIG', 'logind', 'pam-plugin-umask', '', d)} \
> > >  "
> > >
> > > +RRECOMMENDS:${PN}:class-native = ""
> > > +RDEPENDS:${PN}:class-native = ""
> > > +
> > >  INSANE_SKIP:${PN} += "dev-so libdir"
> > >  INSANE_SKIP:${PN}-dbg += "libdir"
> > >  INSANE_SKIP:${PN}-doc += " libdir"
> > > @@ -852,3 +873,5 @@ pkg_postinst:udev-hwdb () {
> > >  pkg_prerm:udev-hwdb () {
> > >         rm -f $D${sysconfdir}/udev/hwdb.bin
> > >  }
> > > +
> > > +BBCLASSEXTEND += "native"
> > > --
> > > 2.34.1
> > >
> > >
> > >
> > >
> >
> >
> > --
> > With best wishes
> > Dmitry
>
> >
> > -=-=-=-=-=-=-=-=-=-=-=-
> > Links: You receive all messages sent to this group.
> > View/Reply Online (#191364): https://lists.openembedded.org/g/openembedded-core/message/191364
> > Mute This Topic: https://lists.openembedded.org/mt/101106095/7159507
> > Group Owner: openembedded-core+owner@lists.openembedded.org
> > Unsubscribe: https://lists.openembedded.org/g/openembedded-core/unsub [mikko.rapeli@linaro.org]
> > -=-=-=-=-=-=-=-=-=-=-=-
> >
>
diff mbox series

Patch

diff --git a/meta/classes/uki.bbclass b/meta/classes/uki.bbclass
new file mode 100644
index 0000000000..2eff387c75
--- /dev/null
+++ b/meta/classes/uki.bbclass
@@ -0,0 +1,140 @@ 
+#
+# Unified kernel image (UKI) class
+#
+#
+# This bbclass is designed to repack an Overlake image as a UKI, to be booted on a qemuarm64 with SecureBoot
+# signing and embedded with TPM PCR measurements.
+#
+# The UKI is composed by:
+#   - 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)
+#
+#
+#
+
+# List build time dependencies
+DEPENDS += "systemd-native \
+            sbsigntool-native \
+            virtual/${TARGET_PREFIX}binutils \
+            "
+
+REQUIRED_DISTRO_FEATURES += "usrmerge systemd"
+
+inherit features_check
+require ../conf/image-uefi.conf
+
+INITRD_IMAGE ?= "core-image-minimal-initramfs"
+
+INITRD_LIVE ?= "${@ ('${DEPLOY_DIR_IMAGE}/' + d.getVar('INITRD_IMAGE') + '-${MACHINE}.cpio.gz') if d.getVar('INITRD_IMAGE') else ''}"
+
+UKI_CONFIG_FILE ?= "${WORKDIR}/core-image-minimal-uki.conf"
+UKI_FILENAME ?= "${@ 'UKI.signed.efi' if d.getVar('UKI_CONFIG_FILE') else 'UKI.unsigned.efi'}"
+
+do_uki[depends] += " \
+                        systemd-boot:do_deploy \
+                        virtual/kernel:do_deploy \
+                     "
+
+# INITRD_IMAGE is added to INITRD_LIVE, which we use to create our initrd, so depend on it if it is set
+# So we want to generate the initrd image if INITRD_IMAGE exists
+do_uki[depends] += "${@ '${INITRD_IMAGE}:do_image_complete' if d.getVar('INITRD_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 subprocess
+
+    # Construct the ukify command
+    ukify_cmd = ("ukify build")
+
+    # Handle the creation of an initrd image by reading and concatenating multiple cpio files. 
+    # If the INITRD_LIVE variable is defined and not empty, it opens the necessary files, reads their contents, 
+    # and constructs a list.
+    if d.getVar('INITRD_LIVE'):
+        initrd_list = ""
+        for cpio in d.getVar('INITRD_LIVE').split():
+            # get a list of initrds
+            initrd_list += cpio + ' '
+        
+        ukify_cmd += " --initrd=%s" % initrd_list
+    else:
+        bb.fatal("ERROR - Required argument: INITRD")
+    
+    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")
+
+    # Architecture
+    target_arch = d.getVar('EFI_ARCH')
+    ukify_cmd += " --efi-arch %s" % target_arch
+
+    # Stub
+    stub = "%s/linux%s.efi.stub" % (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'):
+        first_dtb = d.getVar('KERNEL_DEVICETREE').split()[0]
+        dtb_path = "%s/%s" % (deploy_dir_image, first_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 to sign the UKI.
+    if os.path.exists(d.getVar('UKI_CONFIG_FILE')):
+        ukify_cmd += " --config=%s" % d.getVar('UKI_CONFIG_FILE')
+        ukify_cmd += " --tools=%s%s/lib/systemd/tools" % (d.getVar("RECIPE_SYSROOT_NATIVE"), d.getVar("prefix"))
+        bb.note("Pulling keys from config file")
+    else: 
+        bb.note("Generating unsigned UKI")
+
+    # Custom UKI name
+    output = " --output=%s" % d.getVar('UKI_FILENAME')
+    ukify_cmd += " %s" % output
+
+    # Set env to determine where bitbake should look for dynamic libraries
+    env = os.environ.copy() # get the env variables
+    env['LD_LIBRARY_PATH'] = d.expand("${RECIPE_SYSROOT_NATIVE}/usr/lib/systemd:${LD_LIBRARY_PATH}")
+    
+    # Run the ukify command
+    subprocess.check_call(ukify_cmd, env=env, shell=True)
+}
+ 
+inherit deploy
+
+do_deploy () {
+    # Copy generated UKI into DEPLOYDIR
+	install ${B}/${UKI_FILENAME} ${DEPLOYDIR}
+}
+
+addtask uki before do_deploy do_image after do_rootfs
+addtask deploy before do_build after do_compile
\ No newline at end of file
diff --git a/meta/recipes-core/systemd/systemd_254.bb b/meta/recipes-core/systemd/systemd_254.bb
index 8d5cf13095..65f132abb8 100644
--- a/meta/recipes-core/systemd/systemd_254.bb
+++ b/meta/recipes-core/systemd/systemd_254.bb
@@ -6,6 +6,9 @@  PE = "1"
 
 DEPENDS = "intltool-native gperf-native libcap util-linux python3-jinja2-native"
 
+# The Ukify tool requires this module
+DEPENDS:append:class-native = " python3-pefile-native"
+
 SECTION = "base/shell"
 
 inherit useradd pkgconfig meson perlnative update-rc.d update-alternatives qemu systemd gettext bash-completion manpages features_check
@@ -18,6 +21,8 @@  REQUIRED_DISTRO_FEATURES += "usrmerge"
 # that we don't build both udev and systemd in world builds.
 REQUIRED_DISTRO_FEATURES += "systemd"
 
+REQUIRED_DISTRO_FEATURES:class-native = ""
+
 SRC_URI += " \
            file://touchscreen.rules \
            file://00-create-volatile.conf \
@@ -120,6 +125,8 @@  PACKAGECONFIG:remove:libc-musl = " \
 # https://github.com/seccomp/libseccomp/issues/347
 PACKAGECONFIG:remove:mipsarch = "seccomp"
 
+PACKAGECONFIG:class-native = "serial-getty-generator openssl tpm2 efi"
+
 TARGET_CC_ARCH:append:libc-musl = " -D__UAPI_DEF_ETHHDR=0 -D_LARGEFILE64_SOURCE"
 
 # Some of the dependencies are weak-style recommends - if not available at runtime,
@@ -260,6 +267,9 @@  EXTRA_OEMESON += "-Dkexec-path=${sbindir}/kexec \
                   -Dloadkeys-path=${bindir}/loadkeys \
                   -Dsetfont-path=${bindir}/setfont"
 
+EXTRA_OEMESON:append:class-native = " -Dbootloader=true \
+                                      -Dman=false \
+                                    "
 # The 60 seconds is watchdog's default vaule.
 WATCHDOG_TIMEOUT ??= "60"
 
@@ -380,6 +390,14 @@  do_install() {
     fi
 }
 
+do_install:class-native() {
+	meson_do_install
+	install -d ${D}${bindir}
+	install -m 0755 ${S}/src/ukify/ukify.py ${D}${bindir}/ukify
+	install -d ${D}${prefix}/lib/systemd/tools
+	install -m 0755 ${B}/systemd-measure ${D}${prefix}/lib/systemd/tools
+}
+
 python populate_packages:prepend (){
     systemdlibdir = d.getVar("rootlibdir")
     do_split_packages(d, systemdlibdir, r'^lib(.*)\.so\.*', 'lib%s', 'Systemd %s library', extra_depends='', allow_links=True)
@@ -702,6 +720,9 @@  RRECOMMENDS:${PN} += "systemd-extra-utils \
                       ${@bb.utils.contains('PACKAGECONFIG', 'logind', 'pam-plugin-umask', '', d)} \
 "
 
+RRECOMMENDS:${PN}:class-native = ""
+RDEPENDS:${PN}:class-native = ""
+
 INSANE_SKIP:${PN} += "dev-so libdir"
 INSANE_SKIP:${PN}-dbg += "libdir"
 INSANE_SKIP:${PN}-doc += " libdir"
@@ -852,3 +873,5 @@  pkg_postinst:udev-hwdb () {
 pkg_prerm:udev-hwdb () {
 	rm -f $D${sysconfdir}/udev/hwdb.bin
 }
+
+BBCLASSEXTEND += "native"