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
This commit is contained in:
Lennart Poettering
2026-04-29 13:36:56 +02:00
parent feba5dcc5f
commit d5572aca2c
2 changed files with 204 additions and 8 deletions
+203 -7
View File
@@ -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;
+1 -1
View File
@@ -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',