From: Lennart Poettering Date: Wed, 25 Mar 2026 17:15:38 +0000 (+0100) Subject: boot: load extra files for UKIs into memory and register as initrds X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d5572aca2c38aa7f573e6f36e28dcf82860af8b5;p=thirdparty%2Fsystemd.git boot: load extra files for UKIs into memory and register as initrds This generates on-the-fly cpio initrds from 'extra' resources declared in Type #1 entries and installs them via the Linux initrd protocol so that they get passed to the Linux kernel. Replaces: #39286 --- diff --git a/src/boot/boot.c b/src/boot/boot.c index 6ee2aded089..fff35de864b 100644 --- a/src/boot/boot.c +++ b/src/boot/boot.c @@ -3,6 +3,7 @@ #include "bcd.h" #include "bootspec-fundamental.h" #include "console.h" +#include "cpio.h" #include "device-path-util.h" #include "devicetree.h" #include "drivers.h" @@ -38,6 +39,9 @@ #include "version.h" #include "vmm.h" +/* Safety margin, refuse larger extra files (this is not load bearing, only a safety net for robustness reasons). */ +#define EXTRA_SIZE_MAX (1024U * 1024U * 1536U) + /* Magic string for recognizing our own binaries */ #define SD_MAGIC "#### LoaderInfo: systemd-boot " GIT_VERSION " ####" DECLARE_NOALLOC_SECTION(".sdmagic", SD_MAGIC); @@ -2581,7 +2585,9 @@ static EFI_STATUS initrd_prepare( assert(ret_initrd_pages); assert(ret_initrd_size); - if (entry->type != LOADER_LINUX || strv_isempty(entry->initrd)) { + assert(entry->type == LOADER_LINUX); + + if (strv_isempty(entry->initrd)) { *ret_options = NULL; *ret_initrd_pages = (Pages) {}; *ret_initrd_size = 0; @@ -2685,6 +2691,174 @@ static EFI_STATUS initrd_prepare( return EFI_SUCCESS; } +static EFI_STATUS load_extras( + EFI_FILE *root, + const BootEntry *entry, + Pages *ret_initrd_pages, + size_t *ret_initrd_size) { + + EFI_STATUS err; + + assert(root); + assert(entry); + assert(ret_initrd_pages); + assert(ret_initrd_size); + + assert(IN_SET(entry->type, LOADER_UKI, LOADER_UKI_URL)); + + _cleanup_(iovec_done) struct iovec previous_initrd = {}, confext_initrd = {}, sysext_initrd = {}, credential_initrd = {}; + + const struct ExtraResourceInfo { + const char16_t *suffix; + const CpioTarget *target; + struct iovec *iovec; + const char16_t *tpm_description; + } table[] = { + { u".cred", &cpio_target_credentials, &credential_initrd, u"Entry credentials initrd" }, + { u".sysext.raw", &cpio_target_sysext, &sysext_initrd, u"Entry system extension initrd" }, + { u".confext.raw", &cpio_target_confext, &confext_initrd, u"Entry configuration extension initrd" }, + }; + + if (strv_isempty(entry->extras)) + goto nothing; + + uint32_t inode = 1; /* inode counter, so that each item gets a new inode */ + unsigned n = 0; + + STRV_FOREACH(i, entry->extras) { + _cleanup_file_close_ EFI_FILE *handle = NULL; + err = root->Open(root, &handle, *i, EFI_FILE_MODE_READ, /* Attributes= */ 0); + if (err != EFI_SUCCESS) { + log_warning_status(err, "Failed to open extra file '%ls', ignoring: %m", *i); + continue; + } + + _cleanup_free_ EFI_FILE_INFO *info = NULL; + err = get_file_info(handle, &info, /* ret_size= */ NULL); + if (err != EFI_SUCCESS) { + log_warning_status(err, "Failed to get information about file '%ls', ignoring: %m", *i); + continue; + } + + if (FLAGS_SET(info->Attribute, EFI_FILE_DIRECTORY)) { + log_warning("Extra file '%ls' is a directory, ignoring.", *i); + continue; + } + + if (info->FileSize == 0) { + log_warning("Extra file '%ls' is empty, ignoring.", *i); + continue; + } + if (info->FileSize > EXTRA_SIZE_MAX) { + log_warning("Extra file '%ls' is larger than allowed extra file size, ignoring.", *i); + continue; + } + + if (!is_ascii(info->FileName)) { + log_warning("Extra file name '%ls' is not valid ASCII, ignoring.", *i); + continue; + } + if (strlen16(info->FileName) > 255) { /* Max filename size on Linux */ + log_warning("Filename '%ls' too long, ignoring.", *i); + continue; + } + + const struct ExtraResourceInfo *x = NULL; + FOREACH_ELEMENT(j, table) { + if (endswith_no_case(info->FileName, j->suffix)) { + x = j; + break; + } + } + if (!x) { + log_warning("Unrecognized type of extra file '%ls', ignoring.", info->FileName); + continue; + } + + _cleanup_free_ char *content = NULL; + size_t contentsize = 0; /* avoid false maybe-uninitialized warning */ + err = file_handle_read(handle, /* offset= */ 0, info->FileSize, &content, &contentsize); + if (err != EFI_SUCCESS) { + log_warning_status(err, "Failed to read '%ls', ignoring: %m", *i); + continue; + } + + /* 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. Note that we potentially do redundant work here: a prior iteration might already + * have created the prefix for us, but to simplify this we regenerate it anyway. It's very + * little data, and simplifies the implementation here a lot. */ + err = pack_cpio_prefix(x->target, &inode, &x->iovec->iov_base, &x->iovec->iov_len); + if (err != EFI_SUCCESS) + return log_error_status(err, "Failed to pack cpio prefix '%s': %m", x->target->directory); + + err = pack_cpio_one( + info->FileName, + content, contentsize, + x->target, + &inode, + &x->iovec->iov_base, &x->iovec->iov_len); + if (err != EFI_SUCCESS) + return log_error_status(err, "Failed to pack cpio file '%ls': %m", info->FileName); + + n++; + } + + if (n == 0) /* Nothing actually loaded */ + goto nothing; + + FOREACH_ELEMENT(x, table) { + if (x->iovec->iov_len <= 0) + continue; + + err = pack_cpio_trailer(&x->iovec->iov_base, &x->iovec->iov_len); + if (err != EFI_SUCCESS) + return log_error_status(err, "Failed to pack cpio trailer: %m"); + + err = tpm_log_ipl_event( + x->target->tpm_pcr, + POINTER_TO_PHYSICAL_ADDRESS(x->iovec->iov_base), + x->iovec->iov_len, + x->tpm_description, + /* ret_measured= */ NULL); + if (err != EFI_SUCCESS) + return log_error_status( + err, + "Unable to add cpio TPM measurement for PCR %u (%ls): %m", + x->target->tpm_pcr, + x->tpm_description); + } + + /* Be nice: pick up any previously registered initrds and prepend them to what we are generating here */ + err = initrd_read_previous(&previous_initrd); + if (err == EFI_NOT_FOUND) + log_debug_status(err, "No previous initrd installed."); + else if (err != EFI_SUCCESS) + log_warning_status(err, "Failed to read previously registered initrd, ignoring."); + else + log_debug("Successfully loaded previously installed initrd (%zu bytes).", previous_initrd.iov_len); + + err = combine_initrds( + (const struct iovec[]) { + previous_initrd, + credential_initrd, + sysext_initrd, + confext_initrd, + }, + /* n_initrds= */ 4, + ret_initrd_pages, + ret_initrd_size); + if (err != EFI_SUCCESS) + return log_error_status(err, "Failed to combine previous with extra initrds: %m"); + + return EFI_SUCCESS; + +nothing: + *ret_initrd_pages = (Pages) {}; + *ret_initrd_size = 0; + return EFI_SUCCESS; +} + static EFI_STATUS expand_path( EFI_HANDLE parent_image, EFI_DEVICE_PATH *path, @@ -2833,15 +3007,11 @@ static EFI_STATUS call_image_start( return log_error_status(err, "Error loading EFI binary %ls: %m", entry->loader); } - _cleanup_(cleanup_initrd) EFI_HANDLE initrd_handle = NULL; _cleanup_free_ char16_t *options_initrd = NULL; - _cleanup_pages_ Pages initrd_pages = {}; + _cleanup_pages_ Pages initrd_pages = {}; /* Note: please keep order intact: these pages should be released after the initrd handle is released */ + _cleanup_(cleanup_initrd) EFI_HANDLE initrd_handle = NULL; size_t initrd_size = 0; if (image_root) { - err = initrd_prepare(image_root, entry, &options_initrd, &initrd_pages, &initrd_size); - if (err != EFI_SUCCESS) - return log_error_status(err, "Error preparing initrd: %m"); - /* DTBs are loaded by the kernel before ExitBootServices(), and they can be used to map and * assign arbitrary memory ranges, so skip them when secure boot is enabled as the DTB here * is unverified. */ @@ -2851,9 +3021,35 @@ static EFI_STATUS call_image_start( return log_error_status(err, "Error loading %ls: %m", entry->devicetree); } + switch (entry->type) { + + case LOADER_LINUX: + /* For traditional Linux we follow 'initrd' links, because that's how things worked in the good old days */ + err = initrd_prepare(image_root, entry, &options_initrd, &initrd_pages, &initrd_size); + if (err != EFI_SUCCESS) + return log_error_status(err, "Error preparing initrd: %m"); + + break; + + case LOADER_UKI: + case LOADER_UKI_URL: + /* For modern UKIs we'll not bother with 'initrd', but we'll instead support 'extra' + * for loading credentials, sysext and confext. */ + + err = load_extras(image_root, entry, &initrd_pages, &initrd_size); + if (err != EFI_SUCCESS) + return err; /* load_extras() logs on its own */ + break; + + default: + ; + } + err = initrd_register(&IOVEC_MAKE(PHYSICAL_ADDRESS_TO_POINTER(initrd_pages.addr), initrd_size), &initrd_handle); if (err != EFI_SUCCESS) return log_error_status(err, "Error registering initrd: %m"); + + /* NB: the initrd pages remain in our possession, we will free them if executing the image fails below */ } EFI_LOADED_IMAGE_PROTOCOL *loaded_image; diff --git a/src/boot/meson.build b/src/boot/meson.build index dfac98f034a..29fb64efbee 100644 --- a/src/boot/meson.build +++ b/src/boot/meson.build @@ -309,6 +309,7 @@ endif libefi_sources = files( 'chid.c', 'console.c', + 'cpio.c', 'device-path-util.c', 'devicetree.c', 'drivers.c', @@ -341,7 +342,6 @@ systemd_boot_sources = files( stub_sources = files( 'boot-secret.c', - 'cpio.c', 'linux.c', 'splash.c', 'stub.c',