From c363bb18699e09e263ab5632e364a1aea4082dfc Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 20 Mar 2026 22:41:08 +0100 Subject: [PATCH 1/5] boot: change initrd_register() so that it replaces any previously registered LINUX_INITRD_MEDIA device So far, if an initrd is already registered we'd silently not register one again. Let's make this more reliable and systematic, and register ours, overriding what is previously set. (Note, in a later commit we'll incorporate any previously set initrd, which hence makes this all incremental instead of destructive as it might appear now) --- src/boot/initrd.c | 63 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 50 insertions(+), 13 deletions(-) diff --git a/src/boot/initrd.c b/src/boot/initrd.c index 4babe1671d..b81334ea06 100644 --- a/src/boot/initrd.c +++ b/src/boot/initrd.c @@ -71,8 +71,6 @@ EFI_STATUS initrd_register( EFI_HANDLE *ret_initrd_handle) { EFI_STATUS err; - EFI_DEVICE_PATH *dp = (EFI_DEVICE_PATH *) &efi_initrd_device_path; - EFI_HANDLE handle; assert(ret_initrd_handle); @@ -82,12 +80,43 @@ EFI_STATUS initrd_register( if (!iovec_is_set(initrd)) return EFI_SUCCESS; - /* Check if a LINUX_INITRD_MEDIA_GUID DevicePath is already registered. LocateDevicePath checks for - * the "closest DevicePath" and returns its handle, whereas InstallMultipleProtocolInterfaces() only - * matches identical DevicePaths. */ - err = BS->LocateDevicePath(MAKE_GUID_PTR(EFI_LOAD_FILE2_PROTOCOL), &dp, &handle); - if (err != EFI_NOT_FOUND) /* InitrdMedia is already registered */ - return EFI_ALREADY_STARTED; + /* We want to override the LINUX_INITRD_MEDIA device, let's hence first unregister any existing + * one. We don't really expect multiple of these to be registered, but who knows? Let's kill all we + * can find. */ + for (unsigned attempt = 0;; attempt++) { + + if (attempt >= 16) + return log_debug_status(EFI_DEVICE_ERROR, "Unable to free LINUX_INITRD_MEDIA device path after %u attempts, giving up.", attempt); + + EFI_DEVICE_PATH *dp = (EFI_DEVICE_PATH *) &efi_initrd_device_path; + EFI_HANDLE handle = NULL; + err = BS->LocateDevicePath(MAKE_GUID_PTR(EFI_LOAD_FILE2_PROTOCOL), &dp, &handle); + if (err == EFI_NOT_FOUND) /* Yay! All gone */ + break; + if (err != EFI_SUCCESS) + return log_debug_status(err, "Failed to locate LINUX_INITRD_MEDIA device: %m"); + + /* Get the *actually* installed pointer for the device path */ + err = BS->HandleProtocol(handle, MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), (void**) &dp); + if (err != EFI_SUCCESS) + return log_debug_status(err, "Failed to acquire DevicePath protocol on LINUX_INITRD_MEDIA device: %m"); + + /* Take away the device path protocol */ + err = BS->UninstallMultipleProtocolInterfaces( + handle, + MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), dp, + /* sentinel= */ NULL); + if (err != EFI_SUCCESS) + return log_debug_status(err, "Unable to release DevicePath protocol from old handle: %m"); + + /* NB: we leave the handle around (and thus leave the LoadFile2 protocol installed), because + * the owner might be unhappy if we destroy it for them. It will no longer have the device + * path we want to take possession of on it though. The assumption here is that whoever + * registered the device path is OK with the device path being taken away, even if it might + * not be OK with the handle being invalidated as a whole. */ + + log_debug("Successfully unregistered previous LINUX_INITRD_MEDIA device."); + } _cleanup_free_ struct initrd_loader *loader = xnew(struct initrd_loader, 1); *loader = (struct initrd_loader) { @@ -122,12 +151,20 @@ EFI_STATUS initrd_unregister(EFI_HANDLE initrd_handle) { if (err != EFI_SUCCESS) return log_debug_status(err, "Failed to acquire LoadFile2 protocol on our own initrd handle: %m"); - /* uninstall all protocols thus destroying the handle */ + /* We uninstall the DevicePath and the LoadFile2 protocol in separate steps. That's because we want + * to gracefully handle the former (because it's OK if something else takes over the device path), + * but be strict on the latter, because that's genuinely ours */ + + (void) BS->UninstallMultipleProtocolInterfaces( + initrd_handle, + MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), &efi_initrd_device_path, + /* sentinel= */ NULL); + + /* This second call will also invalidate the handle, because it should be the last protocol on the handle */ err = BS->UninstallMultipleProtocolInterfaces( - initrd_handle, MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), - &efi_initrd_device_path, MAKE_GUID_PTR(EFI_LOAD_FILE2_PROTOCOL), - loader, - NULL); + initrd_handle, + MAKE_GUID_PTR(EFI_LOAD_FILE2_PROTOCOL), loader, + /* sentinel= */ NULL); if (err != EFI_SUCCESS) return log_debug_status(err, "Failed to uninstall LoadFile2 protocol from our own initrd handle: %m"); From 426134b0e7112a09eae941d97f68b2fa1b76c4b7 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 20 Mar 2026 21:48:12 +0100 Subject: [PATCH 2/5] stub: load previous initrd that is already configured, too This changes the initrd combination logic to also include any initrd already configured via the "LINUX_INITRD_MEDIA_GUID" device in the initrd we pass to the linux kernel. Or in other words: with this systemd-stub starts operating purely incremental: it will extend any previously installed initrd with its own stuff, so that both the previous initrd(s) and systemd-stub's are in effect. --- src/boot/initrd.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ src/boot/initrd.h | 2 ++ src/boot/stub.c | 44 +++++++++++++++++++++++++++++++------------- 3 files changed, 77 insertions(+), 13 deletions(-) diff --git a/src/boot/initrd.c b/src/boot/initrd.c index b81334ea06..4780715e79 100644 --- a/src/boot/initrd.c +++ b/src/boot/initrd.c @@ -171,3 +171,47 @@ EFI_STATUS initrd_unregister(EFI_HANDLE initrd_handle) { free(loader); return EFI_SUCCESS; } + +EFI_STATUS initrd_read_previous(struct iovec *ret_initrd) { + EFI_STATUS err; + + /* If there's already an initrd registered, read it out, so that we can incorporate it in ours */ + + assert(ret_initrd); + + /* Get from the device path to the handle */ + EFI_DEVICE_PATH *dp = (EFI_DEVICE_PATH *) &efi_initrd_device_path; + EFI_HANDLE handle; + err = BS->LocateDevicePath(MAKE_GUID_PTR(EFI_LOAD_FILE2_PROTOCOL), &dp, &handle); + if (err != EFI_SUCCESS) + return err; + + /* Get from the handle to the protocol */ + EFI_LOAD_FILE2_PROTOCOL *protocol = NULL; + err = BS->HandleProtocol(handle, MAKE_GUID_PTR(EFI_LOAD_FILE2_PROTOCOL), (void**) &protocol); + if (err != EFI_SUCCESS) + return err; + + size_t size = 0; + err = protocol->LoadFile(protocol, dp, /* bootPolicy= */ false, &size, /* Buffer= */ NULL); + if (err == EFI_SUCCESS) /* Success? Kinda unexpected given we set Buffer to NULL, but it probably + * means, that the file is zero-sized, let's treat it as such. */ + size = 0; + else if (err != EFI_BUFFER_TOO_SMALL) + return err; + + if (size == 0) + return EFI_NOT_FOUND; /* Treat empty initrds like missing ones */ + + _cleanup_free_ void *data = xmalloc(size); + err = protocol->LoadFile(protocol, dp, /* bootPolicy= */ false, &size, data); + if (err != EFI_SUCCESS) + return err; + + *ret_initrd = (struct iovec) { + .iov_base = TAKE_PTR(data), + .iov_len = size, + }; + + return EFI_SUCCESS; +} diff --git a/src/boot/initrd.h b/src/boot/initrd.h index 50987c497d..d34f8ef4c4 100644 --- a/src/boot/initrd.h +++ b/src/boot/initrd.h @@ -13,3 +13,5 @@ static inline void cleanup_initrd(EFI_HANDLE *initrd_handle) { (void) initrd_unregister(*initrd_handle); *initrd_handle = NULL; } + +EFI_STATUS initrd_read_previous(struct iovec *ret_initrd); diff --git a/src/boot/stub.c b/src/boot/stub.c index e2a8569cb3..664ee7cb85 100644 --- a/src/boot/stub.c +++ b/src/boot/stub.c @@ -10,6 +10,7 @@ #include "efi-string.h" #include "export-vars.h" #include "graphics.h" +#include "initrd.h" #include "iovec-util-fundamental.h" #include "linux.h" #include "measure.h" @@ -32,13 +33,10 @@ /* The list of initrds we combine into one, in the order we want to merge them */ enum { - /* The first two are part of the PE binary */ - INITRD_UCODE, - INITRD_BASE, - - /* The rest are dynamically generated, and hence in dynamic memory */ - _INITRD_DYNAMIC_FIRST, - INITRD_CREDENTIAL = _INITRD_DYNAMIC_FIRST, + INITRD_UCODE, /* Part of the PE binary */ + INITRD_PREVIOUS, /* initrd already configured via the EFI protocol before we were invoked */ + INITRD_BASE, /* Part of the PE binary */ + INITRD_CREDENTIAL, INITRD_GLOBAL_CREDENTIAL, INITRD_SYSEXT, INITRD_GLOBAL_SYSEXT, @@ -52,6 +50,8 @@ enum { _INITRD_MAX, }; +#define INITRD_IS_STATIC(idx) IN_SET(idx, INITRD_UCODE, INITRD_BASE) + /* magic string to find in the binary image */ DECLARE_NOALLOC_SECTION(".sdmagic", "#### LoaderInfo: systemd-stub " GIT_VERSION " ####"); @@ -550,6 +550,21 @@ static void extend_initrds( iovec_array_extend(all_initrds, n_all_initrds, *i); } +static void acquire_previous_initrd(struct iovec initrds[static _INITRD_MAX]) { + EFI_STATUS err; + + /* NB: the assumption here is that any previously installed initrd are measured by whatever + * registered them, and we just pass them on here. */ + + err = initrd_read_previous(initrds + INITRD_PREVIOUS); + if (err == EFI_NOT_FOUND) + log_debug_status(err, "No previous initrd registered."); + else if (err != EFI_SUCCESS) + log_warning_status(err, "Failed to read previously registered initrd, ignoring."); + else + log_debug("Successfully loaded previously registered initrd (%zu bytes).", initrds[INITRD_PREVIOUS].iov_len); +} + static EFI_STATUS load_addons( EFI_HANDLE stub_image, EFI_LOADED_IMAGE_PROTOCOL *loaded_image, @@ -821,10 +836,11 @@ static void cmdline_append_and_measure_smbios(char16_t **cmdline, int *parameter static void initrds_free(struct iovec (*initrds)[_INITRD_MAX]) { assert(initrds); - /* Free the dynamic initrds, but leave the non-dynamic ones around */ + /* Free the non-static initrds, but leave the static (i.e. PE embedded) ones around */ - for (size_t i = _INITRD_DYNAMIC_FIRST; i < _INITRD_MAX; i++) - iovec_done((*initrds) + i); + for (size_t i = 0; i < _INITRD_MAX; i++) + if (!INITRD_IS_STATIC(i)) + iovec_done((*initrds) + i); } static void generate_sidecar_initrds( @@ -1309,6 +1325,7 @@ static EFI_STATUS run(EFI_HANDLE image) { install_addon_devicetrees(&dt_state, dt_addons, n_dt_addons, ¶meters_measured); /* Generate & find all initrds */ + acquire_previous_initrd(initrds); generate_sidecar_initrds(loaded_image, initrds, ¶meters_measured, &sysext_measured, &confext_measured); generate_embedded_initrds(loaded_image, sections, initrds); generate_boot_secret_initrd(boot_secret, initrds); @@ -1319,9 +1336,10 @@ static EFI_STATUS run(EFI_HANDLE image) { * We want addons to take precedence over the base initrds, so the order is: * 1. Ucode addons * 2. UKI ucode - * 3. UKI initrd - * 4. Generated initrds - * 5. initrd addons */ + * 3. Previous initrds + * 4. UKI initrd + * 5. Generated initrds + * 6. initrd addons */ measure_and_append_ucode_addons(&all_initrds, &n_all_initrds, ucode_addons, n_ucode_addons, ¶meters_measured); extend_initrds(initrds, &all_initrds, &n_all_initrds); measure_and_append_initrd_addons(&all_initrds, &n_all_initrds, initrd_addons, n_initrd_addons, ¶meters_measured); From 0e98e6ccd1a4ebb0835e4ced407c89bed5847aa9 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 25 Mar 2026 18:11:45 +0100 Subject: [PATCH 3/5] boot: introduce a common structure for cpio target dirs There are only a few target dirs we place resources in when generating on-the-fly initrd cpios. These dirs have very specific attributes. Instead of repeating this everywhere, let's encapsulate them in a new explicit structure, that we can reuse at various places. This is preparation for placing extra resources of Type #1 entry also in them without having to encode access modes at multiple places redundantly. --- src/boot/cpio.c | 104 ++++++++++++++++++++++++++++++++++-------------- src/boot/cpio.h | 23 ++++++++--- src/boot/stub.c | 32 ++++----------- 3 files changed, 99 insertions(+), 60 deletions(-) diff --git a/src/boot/cpio.c b/src/boot/cpio.c index 7ad4b470fa..77bf7a8352 100644 --- a/src/boot/cpio.c +++ b/src/boot/cpio.c @@ -56,8 +56,7 @@ static EFI_STATUS pack_cpio_one( const char16_t *fname, const void *contents, size_t contents_size, - const char *target_dir_prefix, - uint32_t access_mode, + const CpioTarget *target, uint32_t *inode_counter, void **cpio_buffer, size_t *cpio_buffer_size) { @@ -67,7 +66,7 @@ static EFI_STATUS pack_cpio_one( assert(fname); assert(contents || contents_size == 0); - assert(target_dir_prefix); + assert(target); assert(inode_counter); assert(cpio_buffer); assert(cpio_buffer_size); @@ -84,7 +83,7 @@ static EFI_STATUS pack_cpio_one( l = 6 + 13*8 + 1 + 1; /* Fixed CPIO header size, slash separator, and NUL byte after the file name */ - target_dir_prefix_size = strlen8(target_dir_prefix); + target_dir_prefix_size = strlen8(target->directory); if (l > SIZE_MAX - target_dir_prefix_size) return EFI_OUT_OF_RESOURCES; l += target_dir_prefix_size; @@ -121,11 +120,11 @@ static EFI_STATUS pack_cpio_one( a = mempcpy(a, "070701", 6); /* magic ID */ - a = write_cpio_word(a, (*inode_counter)++); /* inode */ - a = write_cpio_word(a, access_mode | 0100000 /* = S_IFREG */); /* mode */ - a = write_cpio_word(a, 0); /* uid */ - a = write_cpio_word(a, 0); /* gid */ - a = write_cpio_word(a, 1); /* nlink */ + a = write_cpio_word(a, (*inode_counter)++); /* inode */ + a = write_cpio_word(a, target->access_mode | 0100000 /* = S_IFREG */); /* mode */ + a = write_cpio_word(a, 0); /* uid */ + a = write_cpio_word(a, 0); /* gid */ + a = write_cpio_word(a, 1); /* nlink */ /* Note: we don't make any attempt to propagate the mtime here, for two reasons: it's a mess given * that FAT usually is assumed to operate with timezoned timestamps, while UNIX does not. More @@ -141,7 +140,7 @@ static EFI_STATUS pack_cpio_one( a = write_cpio_word(a, target_dir_prefix_size + fname_size + 2); /* fname size */ a = write_cpio_word(a, 0); /* "crc" */ - a = mempcpy(a, target_dir_prefix, target_dir_prefix_size); + a = mempcpy(a, target->directory, target_dir_prefix_size); *(a++) = '/'; a = mangle_filename(a, fname); @@ -226,15 +225,14 @@ static EFI_STATUS pack_cpio_dir( } static EFI_STATUS pack_cpio_prefix( - const char *path, - uint32_t dir_mode, + const CpioTarget *target, uint32_t *inode_counter, void **cpio_buffer, size_t *cpio_buffer_size) { EFI_STATUS err; - assert(path); + assert(target); assert(inode_counter); assert(cpio_buffer); assert(cpio_buffer_size); @@ -243,7 +241,7 @@ static EFI_STATUS pack_cpio_prefix( * (similar to mkdir -p behaviour) all leading paths are created with 0555 access mode, only the * final dir is created with the specified directory access mode. */ - for (const char *p = path;;) { + for (const char *p = target->directory;;) { const char *e; e = strchr8(p, '/'); @@ -253,7 +251,7 @@ static EFI_STATUS pack_cpio_prefix( if (e > p) { _cleanup_free_ char *t = NULL; - t = xstrndup8(path, e - path); + t = xstrndup8(target->directory, e - target->directory); if (!t) return EFI_OUT_OF_RESOURCES; @@ -265,7 +263,7 @@ static EFI_STATUS pack_cpio_prefix( p = e + 1; } - return pack_cpio_dir(path, dir_mode, inode_counter, cpio_buffer, cpio_buffer_size); + return pack_cpio_dir(target->directory, target->dir_mode, inode_counter, cpio_buffer, cpio_buffer_size); } static EFI_STATUS pack_cpio_trailer( @@ -307,9 +305,7 @@ EFI_STATUS pack_cpio( const char16_t *dropin_dir, const char16_t *match_suffix, const char16_t *exclude_suffix, - const char *target_dir_prefix, - uint32_t dir_mode, - uint32_t access_mode, + const CpioTarget *target, uint32_t tpm_pcr, const char16_t *tpm_description, struct iovec *ret_buffer, @@ -325,7 +321,7 @@ EFI_STATUS pack_cpio( EFI_STATUS err; assert(loaded_image); - assert(target_dir_prefix); + assert(target); assert(ret_buffer); if (!loaded_image->DeviceHandle) @@ -400,7 +396,7 @@ EFI_STATUS pack_cpio( /* Generate the leading directory inodes right before adding the first files, to the * archive. Otherwise the cpio archive cannot be unpacked, since the leading dirs won't exist. */ - err = pack_cpio_prefix(target_dir_prefix, dir_mode, &inode, &buffer, &buffer_size); + err = pack_cpio_prefix(target, &inode, &buffer, &buffer_size); if (err != EFI_SUCCESS) return log_error_status(err, "Failed to pack cpio prefix: %m"); @@ -417,8 +413,7 @@ EFI_STATUS pack_cpio( err = pack_cpio_one( items[i], content, contentsize, - target_dir_prefix, - access_mode, + target, &inode, &buffer, &buffer_size); if (err != EFI_SUCCESS) @@ -453,10 +448,8 @@ nothing: EFI_STATUS pack_cpio_literal( const void *data, size_t data_size, - const char *target_dir_prefix, + const CpioTarget *target, const char16_t *target_filename, - uint32_t dir_mode, - uint32_t access_mode, uint32_t tpm_pcr, const char16_t *tpm_description, struct iovec *ret_buffer, @@ -468,22 +461,21 @@ EFI_STATUS pack_cpio_literal( EFI_STATUS err; assert(data || data_size == 0); - assert(target_dir_prefix); + assert(target); assert(target_filename); assert(ret_buffer); /* Generate the leading directory inodes right before adding the first files, to the * archive. Otherwise the cpio archive cannot be unpacked, since the leading dirs won't exist. */ - err = pack_cpio_prefix(target_dir_prefix, dir_mode, &inode, &buffer, &buffer_size); + err = pack_cpio_prefix(target, &inode, &buffer, &buffer_size); if (err != EFI_SUCCESS) return log_error_status(err, "Failed to pack cpio prefix: %m"); err = pack_cpio_one( target_filename, data, data_size, - target_dir_prefix, - access_mode, + target, &inode, &buffer, &buffer_size); if (err != EFI_SUCCESS) @@ -505,3 +497,55 @@ EFI_STATUS pack_cpio_literal( *ret_buffer = IOVEC_MAKE(TAKE_PTR(buffer), buffer_size); return EFI_SUCCESS; } + +/* The following are canonical definitions of the various cpio target directories we place resources in. We + * define them here in a single canonical list of targets because we need to reuse them at various places + * (well, some of them at least), and we don't want the access modes to deviate slightly on each use. */ + +const CpioTarget cpio_target_credentials = { + .directory = ".extra/credentials", + .dir_mode = 0500, + .access_mode = 0400, +}; + +const CpioTarget cpio_target_global_credentials = { + .directory = ".extra/global_credentials", + .dir_mode = 0500, + .access_mode = 0400, +}; + +const CpioTarget cpio_target_sysext = { + .directory = ".extra/sysext", + .dir_mode = 0555, + .access_mode = 0444, +}; + +const CpioTarget cpio_target_global_sysext = { + .directory = ".extra/global_sysext", + .dir_mode = 0555, + .access_mode = 0444, +}; + +const CpioTarget cpio_target_confext = { + .directory = ".extra/confext", + .dir_mode = 0555, + .access_mode = 0444, +}; + +const CpioTarget cpio_target_global_confext = { + .directory = ".extra/global_confext", + .dir_mode = 0555, + .access_mode = 0444, +}; + +const CpioTarget cpio_target_meta = { + .directory = ".extra", + .dir_mode = 0555, + .access_mode = 0444, +}; + +const CpioTarget cpio_target_meta_secret = { + .directory = ".extra", + .dir_mode = 0555, + .access_mode = 0400, +}; diff --git a/src/boot/cpio.h b/src/boot/cpio.h index bb741278fd..f5c7b9fdec 100644 --- a/src/boot/cpio.h +++ b/src/boot/cpio.h @@ -4,14 +4,18 @@ #include "efi.h" #include "proto/loaded-image.h" +typedef struct CpioTarget { + const char *directory; /* Path to directory where to place resources */ + uint32_t dir_mode; /* Access mode for the directory */ + uint32_t access_mode; /* Access mode for the files in the directory */ +} CpioTarget; + EFI_STATUS pack_cpio( EFI_LOADED_IMAGE_PROTOCOL *loaded_image, const char16_t *dropin_dir, const char16_t *match_suffix, const char16_t *exclude_suffix, - const char *target_dir_prefix, - uint32_t dir_mode, - uint32_t access_mode, + const CpioTarget *target, uint32_t tpm_pcr, const char16_t *tpm_description, struct iovec *ret_buffer, @@ -20,11 +24,18 @@ EFI_STATUS pack_cpio( EFI_STATUS pack_cpio_literal( const void *data, size_t data_size, - const char *target_dir_prefix, + const CpioTarget *target, const char16_t *target_filename, - uint32_t dir_mode, - uint32_t access_mode, uint32_t tpm_pcr, const char16_t *tpm_description, struct iovec *ret_buffer, bool *ret_measured); + +extern const CpioTarget cpio_target_credentials; +extern const CpioTarget cpio_target_global_credentials; +extern const CpioTarget cpio_target_sysext; +extern const CpioTarget cpio_target_global_sysext; +extern const CpioTarget cpio_target_confext; +extern const CpioTarget cpio_target_global_confext; +extern const CpioTarget cpio_target_meta; +extern const CpioTarget cpio_target_meta_secret; diff --git a/src/boot/stub.c b/src/boot/stub.c index 664ee7cb85..00ffa2889c 100644 --- a/src/boot/stub.c +++ b/src/boot/stub.c @@ -862,9 +862,7 @@ static void generate_sidecar_initrds( /* dropin_dir= */ NULL, u".cred", /* exclude_suffix= */ NULL, - ".extra/credentials", - /* dir_mode= */ 0500, - /* access_mode= */ 0400, + &cpio_target_credentials, /* tpm_pcr= */ TPM2_PCR_KERNEL_CONFIG, u"Credentials initrd", initrds + INITRD_CREDENTIAL, @@ -875,9 +873,7 @@ static void generate_sidecar_initrds( u"\\loader\\credentials", u".cred", /* exclude_suffix= */ NULL, - ".extra/global_credentials", - /* dir_mode= */ 0500, - /* access_mode= */ 0400, + &cpio_target_global_credentials, /* tpm_pcr= */ TPM2_PCR_KERNEL_CONFIG, u"Global credentials initrd", initrds + INITRD_GLOBAL_CREDENTIAL, @@ -888,9 +884,7 @@ static void generate_sidecar_initrds( /* dropin_dir= */ NULL, u".raw", /* ideally we'd pick up only *.sysext.raw here, but for compat we pick up *.raw instead … */ u".confext.raw", /* … but then exclude *.confext.raw again */ - ".extra/sysext", - /* dir_mode= */ 0555, - /* access_mode= */ 0444, + &cpio_target_sysext, /* tpm_pcr= */ TPM2_PCR_SYSEXTS, u"System extension initrd", initrds + INITRD_SYSEXT, @@ -901,9 +895,7 @@ static void generate_sidecar_initrds( u"\\loader\\extensions", u".raw", /* as above */ u".confext.raw", - ".extra/global_sysext", - /* dir_mode= */ 0555, - /* access_mode= */ 0444, + &cpio_target_global_sysext, /* tpm_pcr= */ TPM2_PCR_SYSEXTS, u"Global system extension initrd", initrds + INITRD_GLOBAL_SYSEXT, @@ -914,9 +906,7 @@ static void generate_sidecar_initrds( /* dropin_dir= */ NULL, u".confext.raw", /* exclude_suffix= */ NULL, - ".extra/confext", - /* dir_mode= */ 0555, - /* access_mode= */ 0444, + &cpio_target_confext, /* tpm_pcr= */ TPM2_PCR_KERNEL_CONFIG, u"Configuration extension initrd", initrds + INITRD_CONFEXT, @@ -927,9 +917,7 @@ static void generate_sidecar_initrds( u"\\loader\\extensions", u".confext.raw", /* exclude_suffix= */ NULL, - ".extra/global_confext", - /* dir_mode= */ 0555, - /* access_mode= */ 0444, + &cpio_target_global_confext, /* tpm_pcr= */ TPM2_PCR_KERNEL_CONFIG, u"Global configuration extension initrd", initrds + INITRD_GLOBAL_CONFEXT, @@ -980,10 +968,8 @@ static void generate_embedded_initrds( (void) pack_cpio_literal( (const uint8_t*) loaded_image->ImageBase + sections[t->section].memory_offset, sections[t->section].memory_size, - ".extra", + &cpio_target_meta, t->filename, - /* dir_mode= */ 0555, - /* access_mode= */ 0444, /* tpm_pcr= */ UINT32_MAX, /* tpm_description= */ NULL, initrds + t->initrd_index, @@ -1004,10 +990,8 @@ static void generate_boot_secret_initrd( (void) pack_cpio_literal( boot_secret, BOOT_SECRET_SIZE, - ".extra", + &cpio_target_meta_secret, u"boot-secret", - /* dir_mode= */ 0555, - /* access_mode= */ 0400, /* tpm_pcr= */ UINT32_MAX, /* tpm_description= */ NULL, initrds + INITRD_BOOT_SECRET, From f6b5fcc3cfbeae5a685899628a4688357f0fc354 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 25 Mar 2026 18:15:13 +0100 Subject: [PATCH 4/5] boot: export more helpers from cpio.c We want to reuse this later in systemd-boot, hence make these helpers public. --- src/boot/cpio.c | 6 +++--- src/boot/cpio.h | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/boot/cpio.c b/src/boot/cpio.c index 77bf7a8352..81792b00a8 100644 --- a/src/boot/cpio.c +++ b/src/boot/cpio.c @@ -52,7 +52,7 @@ static char *pad4(char *p, const char *start) { return p; } -static EFI_STATUS pack_cpio_one( +EFI_STATUS pack_cpio_one( const char16_t *fname, const void *contents, size_t contents_size, @@ -224,7 +224,7 @@ static EFI_STATUS pack_cpio_dir( return EFI_SUCCESS; } -static EFI_STATUS pack_cpio_prefix( +EFI_STATUS pack_cpio_prefix( const CpioTarget *target, uint32_t *inode_counter, void **cpio_buffer, @@ -266,7 +266,7 @@ static EFI_STATUS pack_cpio_prefix( return pack_cpio_dir(target->directory, target->dir_mode, inode_counter, cpio_buffer, cpio_buffer_size); } -static EFI_STATUS pack_cpio_trailer( +EFI_STATUS pack_cpio_trailer( void **cpio_buffer, size_t *cpio_buffer_size) { diff --git a/src/boot/cpio.h b/src/boot/cpio.h index f5c7b9fdec..3c311bc714 100644 --- a/src/boot/cpio.h +++ b/src/boot/cpio.h @@ -10,6 +10,25 @@ typedef struct CpioTarget { uint32_t access_mode; /* Access mode for the files in the directory */ } CpioTarget; +EFI_STATUS pack_cpio_one( + const char16_t *fname, + const void *contents, + size_t contents_size, + const CpioTarget *target, + uint32_t *inode_counter, + void **cpio_buffer, + size_t *cpio_buffer_size); + +EFI_STATUS pack_cpio_prefix( + const CpioTarget *target, + uint32_t *inode_counter, + void **cpio_buffer, + size_t *cpio_buffer_size); + +EFI_STATUS pack_cpio_trailer( + void **cpio_buffer, + size_t *cpio_buffer_size); + EFI_STATUS pack_cpio( EFI_LOADED_IMAGE_PROTOCOL *loaded_image, const char16_t *dropin_dir, From 9d7fa23d38bff0b2986e0ee754a210b336ffd409 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 25 Mar 2026 18:09:01 +0100 Subject: [PATCH 5/5] boot: share combine_initrds() between stub/boot We'd like to use combine_initrds() later in systemd-boot, hence move it out of stub.c and into shared code. --- src/boot/initrd.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ src/boot/initrd.h | 4 ++++ src/boot/stub.c | 44 -------------------------------------------- 3 files changed, 49 insertions(+), 44 deletions(-) diff --git a/src/boot/initrd.c b/src/boot/initrd.c index 4780715e79..044e011f60 100644 --- a/src/boot/initrd.c +++ b/src/boot/initrd.c @@ -215,3 +215,48 @@ EFI_STATUS initrd_read_previous(struct iovec *ret_initrd) { return EFI_SUCCESS; } + +EFI_STATUS combine_initrds( + const struct iovec initrds[], size_t n_initrds, + Pages *ret_initrd_pages, size_t *ret_initrd_size) { + + size_t n = 0; + + /* Combine initrds by concatenation in memory */ + + assert(initrds || n_initrds == 0); + assert(ret_initrd_pages); + assert(ret_initrd_size); + + FOREACH_ARRAY(i, initrds, n_initrds) { + /* some initrds (the ones from UKI sections) need padding, pad all to be safe */ + size_t initrd_size = ALIGN4(i->iov_len); + if (n > SIZE_MAX - initrd_size) + return EFI_OUT_OF_RESOURCES; + + n += initrd_size; + } + + _cleanup_pages_ Pages pages = xmalloc_initrd_pages(n); + uint8_t *p = PHYSICAL_ADDRESS_TO_POINTER(pages.addr); + + FOREACH_ARRAY(i, initrds, n_initrds) { + size_t pad; + + p = mempcpy(p, i->iov_base, i->iov_len); + + pad = ALIGN4(i->iov_len) - i->iov_len; + if (pad == 0) + continue; + + memzero(p, pad); + p += pad; + } + + assert(PHYSICAL_ADDRESS_TO_POINTER(pages.addr + n) == p); + + *ret_initrd_pages = TAKE_STRUCT(pages); + *ret_initrd_size = n; + + return EFI_SUCCESS; +} diff --git a/src/boot/initrd.h b/src/boot/initrd.h index d34f8ef4c4..cfcb8d1c6f 100644 --- a/src/boot/initrd.h +++ b/src/boot/initrd.h @@ -2,6 +2,8 @@ #pragma once #include "efi.h" +#include "iovec-util-fundamental.h" +#include "util.h" EFI_STATUS initrd_register( const struct iovec *initrd, @@ -15,3 +17,5 @@ static inline void cleanup_initrd(EFI_HANDLE *initrd_handle) { } EFI_STATUS initrd_read_previous(struct iovec *ret_initrd); + +EFI_STATUS combine_initrds(const struct iovec initrds[], size_t n_initrds, Pages *ret_initrd_pages, size_t *ret_initrd_size); diff --git a/src/boot/stub.c b/src/boot/stub.c index 00ffa2889c..8632a603a2 100644 --- a/src/boot/stub.c +++ b/src/boot/stub.c @@ -102,50 +102,6 @@ static void combine_measured_flag(int *value, int measured) { *value = *value < 0 ? measured : *value && measured; } -/* Combine initrds by concatenation in memory */ -static EFI_STATUS combine_initrds( - const struct iovec initrds[], size_t n_initrds, - Pages *ret_initrd_pages, size_t *ret_initrd_size) { - - size_t n = 0; - - assert(initrds || n_initrds == 0); - assert(ret_initrd_pages); - assert(ret_initrd_size); - - FOREACH_ARRAY(i, initrds, n_initrds) { - /* some initrds (the ones from UKI sections) need padding, pad all to be safe */ - size_t initrd_size = ALIGN4(i->iov_len); - if (n > SIZE_MAX - initrd_size) - return EFI_OUT_OF_RESOURCES; - - n += initrd_size; - } - - _cleanup_pages_ Pages pages = xmalloc_initrd_pages(n); - uint8_t *p = PHYSICAL_ADDRESS_TO_POINTER(pages.addr); - - FOREACH_ARRAY(i, initrds, n_initrds) { - size_t pad; - - p = mempcpy(p, i->iov_base, i->iov_len); - - pad = ALIGN4(i->iov_len) - i->iov_len; - if (pad == 0) - continue; - - memzero(p, pad); - p += pad; - } - - assert(PHYSICAL_ADDRESS_TO_POINTER(pages.addr + n) == p); - - *ret_initrd_pages = TAKE_STRUCT(pages); - *ret_initrd_size = n; - - return EFI_SUCCESS; -} - static void export_stub_variables(EFI_LOADED_IMAGE_PROTOCOL *loaded_image, unsigned profile) { static const uint64_t stub_features = EFI_STUB_FEATURE_REPORT_BOOT_PARTITION | /* We set LoaderDevicePartUUID */