stub: rework initrd handling around "struct iovec"

Let's maintain an array of "struct iovec" for the initrds. It becomes a
ton easier and shorter to process/combine the various initrds then.
This commit is contained in:
Lennart Poettering
2024-06-26 10:20:26 +02:00
parent 79d076be37
commit ac32323271
3 changed files with 100 additions and 103 deletions

View File

@@ -311,8 +311,7 @@ EFI_STATUS pack_cpio(
uint32_t access_mode,
uint32_t tpm_pcr,
const char16_t *tpm_description,
void **ret_buffer,
size_t *ret_buffer_size,
struct iovec *ret_buffer,
bool *ret_measured) {
_cleanup_(file_closep) EFI_FILE *root = NULL, *extra_dir = NULL;
@@ -327,7 +326,6 @@ EFI_STATUS pack_cpio(
assert(loaded_image);
assert(target_dir_prefix);
assert(ret_buffer);
assert(ret_buffer_size);
if (!loaded_image->DeviceHandle)
goto nothing;
@@ -439,14 +437,11 @@ EFI_STATUS pack_cpio(
tpm_pcr,
tpm_description);
*ret_buffer = TAKE_PTR(buffer);
*ret_buffer_size = buffer_size;
*ret_buffer = IOVEC_MAKE(TAKE_PTR(buffer), buffer_size);
return EFI_SUCCESS;
nothing:
*ret_buffer = NULL;
*ret_buffer_size = 0;
*ret_buffer = (struct iovec) {};
if (ret_measured)
*ret_measured = false;
@@ -463,8 +458,7 @@ EFI_STATUS pack_cpio_literal(
uint32_t access_mode,
uint32_t tpm_pcr,
const char16_t *tpm_description,
void **ret_buffer,
size_t *ret_buffer_size,
struct iovec *ret_buffer,
bool *ret_measured) {
uint32_t inode = 1; /* inode counter, so that each item gets a new inode */
@@ -476,7 +470,6 @@ EFI_STATUS pack_cpio_literal(
assert(target_dir_prefix);
assert(target_filename);
assert(ret_buffer);
assert(ret_buffer_size);
/* 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. */
@@ -508,8 +501,6 @@ EFI_STATUS pack_cpio_literal(
tpm_pcr,
tpm_description);
*ret_buffer = TAKE_PTR(buffer);
*ret_buffer_size = buffer_size;
*ret_buffer = IOVEC_MAKE(TAKE_PTR(buffer), buffer_size);
return EFI_SUCCESS;
}

View File

@@ -2,6 +2,7 @@
#pragma once
#include "efi.h"
#include "iovec-util-fundamental.h"
#include "proto/loaded-image.h"
EFI_STATUS pack_cpio(
@@ -14,8 +15,7 @@ EFI_STATUS pack_cpio(
uint32_t access_mode,
uint32_t tpm_pcr,
const char16_t *tpm_description,
void **ret_buffer,
size_t *ret_buffer_size,
struct iovec *ret_buffer,
bool *ret_measured);
EFI_STATUS pack_cpio_literal(
@@ -27,6 +27,5 @@ EFI_STATUS pack_cpio_literal(
uint32_t access_mode,
uint32_t tpm_pcr,
const char16_t *tpm_description,
void **ret_buffer,
size_t *ret_buffer_size,
struct iovec *ret_buffer,
bool *ret_measured);

View File

@@ -4,6 +4,7 @@
#include "device-path-util.h"
#include "devicetree.h"
#include "graphics.h"
#include "iovec-util-fundamental.h"
#include "linux.h"
#include "measure.h"
#include "memory-util-fundamental.h"
@@ -21,6 +22,23 @@
#include "version.h"
#include "vmm.h"
/* 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_GLOBAL_CREDENTIAL,
INITRD_SYSEXT,
INITRD_CONFEXT,
INITRD_PCRSIG,
INITRD_PCRPKEY,
_INITRD_MAX,
};
/* magic string to find in the binary image */
DECLARE_NOALLOC_SECTION(".sdmagic", "#### LoaderInfo: systemd-stub " GIT_VERSION " ####");
@@ -47,21 +65,19 @@ static void combine_measured_flag(int *value, int measured) {
/* Combine initrds by concatenation in memory */
static EFI_STATUS combine_initrds(
const void * const initrds[], const size_t initrd_sizes[], size_t n_initrds,
Pages *ret_initr_pages, size_t *ret_initrd_size) {
struct iovec initrds[], size_t n_initrds,
Pages *ret_initrd_pages, size_t *ret_initrd_size) {
size_t n = 0;
assert(ret_initr_pages);
assert(initrds || n_initrds == 0);
assert(ret_initrd_pages);
assert(ret_initrd_size);
for (size_t i = 0; i < n_initrds; i++) {
if (!initrds[i])
continue;
FOREACH_ARRAY(i, initrds, n_initrds) {
/* some initrds (the ones from UKI sections) need padding, pad all to be safe */
/* some initrds (the ones from UKI sections) need padding,
* pad all to be safe */
size_t initrd_size = ALIGN4(initrd_sizes[i]);
size_t initrd_size = ALIGN4(i->iov_len);
if (n > SIZE_MAX - initrd_size)
return EFI_OUT_OF_RESOURCES;
@@ -74,24 +90,22 @@ static EFI_STATUS combine_initrds(
EFI_SIZE_TO_PAGES(n),
UINT32_MAX /* Below 4G boundary. */);
uint8_t *p = PHYSICAL_ADDRESS_TO_POINTER(pages.addr);
for (size_t i = 0; i < n_initrds; i++) {
if (!initrds[i])
continue;
FOREACH_ARRAY(i, initrds, n_initrds) {
size_t pad;
p = mempcpy(p, initrds[i], initrd_sizes[i]);
p = mempcpy(p, i->iov_base, i->iov_len);
pad = ALIGN4(initrd_sizes[i]) - initrd_sizes[i];
if (pad > 0) {
memzero(p, pad);
p += pad;
}
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_initr_pages = pages;
*ret_initrd_pages = pages;
*ret_initrd_size = n;
pages.n_pages = 0;
@@ -628,14 +642,39 @@ static void lookup_uname(
sections[UNIFIED_SECTION_UNAME].size);
}
static void initrds_free(struct iovec (*initrds)[_INITRD_MAX]) {
assert(initrds);
/* Free the dynamic initrds, but leave the non-dynamic ones around */
for (size_t i = _INITRD_DYNAMIC_FIRST; i < _INITRD_MAX; i++)
iovec_done((*initrds) + i);
}
static bool initrds_need_combine(struct iovec initrds[static _INITRD_MAX]) {
assert(initrds);
/* Returns true if we have any initrds set that aren't the base initrd. In that case we need to
* merge, otherwise we can pass the embedded initrd as is */
for (size_t i = 0; i < _INITRD_MAX; i++) {
if (i == INITRD_BASE)
continue;
if (iovec_is_set(initrds + i))
return true;
}
return false;
}
static EFI_STATUS run(EFI_HANDLE image) {
_cleanup_free_ void *credential_initrd = NULL, *global_credential_initrd = NULL, *sysext_initrd = NULL, *confext_initrd = NULL, *pcrsig_initrd = NULL, *pcrpkey_initrd = NULL;
size_t credential_initrd_size = 0, global_credential_initrd_size = 0, sysext_initrd_size = 0, confext_initrd_size = 0, pcrsig_initrd_size = 0, pcrpkey_initrd_size = 0;
_cleanup_(initrds_free) struct iovec initrds[_INITRD_MAX] = {};
void **dt_bases_addons_global = NULL, **dt_bases_addons_uki = NULL;
char16_t **dt_filenames_addons_global = NULL, **dt_filenames_addons_uki = NULL;
_cleanup_free_ size_t *dt_sizes_addons_global = NULL, *dt_sizes_addons_uki = NULL;
size_t linux_size, initrd_size = 0, ucode_size = 0, dt_size = 0, n_dts_addons_global = 0, n_dts_addons_uki = 0;
EFI_PHYSICAL_ADDRESS linux_base, initrd_base = 0, ucode_base = 0, dt_base = 0;
size_t dt_size = 0, n_dts_addons_global = 0, n_dts_addons_uki = 0;
EFI_PHYSICAL_ADDRESS dt_base = 0;
_cleanup_(devicetree_cleanup) struct devicetree_state dt_state = {};
EFI_LOADED_IMAGE_PROTOCOL *loaded_image;
PeSectionVector sections[ELEMENTSOF(unified_sections)] = {};
@@ -743,8 +782,7 @@ static EFI_STATUS run(EFI_HANDLE image) {
/* access_mode= */ 0400,
/* tpm_pcr= */ TPM2_PCR_KERNEL_CONFIG,
u"Credentials initrd",
&credential_initrd,
&credential_initrd_size,
initrds + INITRD_CREDENTIAL,
&m) == EFI_SUCCESS)
combine_measured_flag(&parameters_measured, m);
@@ -757,8 +795,7 @@ static EFI_STATUS run(EFI_HANDLE image) {
/* access_mode= */ 0400,
/* tpm_pcr= */ TPM2_PCR_KERNEL_CONFIG,
u"Global credentials initrd",
&global_credential_initrd,
&global_credential_initrd_size,
initrds + INITRD_GLOBAL_CREDENTIAL,
&m) == EFI_SUCCESS)
combine_measured_flag(&parameters_measured, m);
@@ -771,8 +808,7 @@ static EFI_STATUS run(EFI_HANDLE image) {
/* access_mode= */ 0444,
/* tpm_pcr= */ TPM2_PCR_SYSEXTS,
u"System extension initrd",
&sysext_initrd,
&sysext_initrd_size,
initrds + INITRD_CONFEXT,
&m) == EFI_SUCCESS)
combine_measured_flag(&sysext_measured, m);
@@ -785,12 +821,10 @@ static EFI_STATUS run(EFI_HANDLE image) {
/* access_mode= */ 0444,
/* tpm_pcr= */ TPM2_PCR_KERNEL_CONFIG,
u"Configuration extension initrd",
&confext_initrd,
&confext_initrd_size,
initrds + INITRD_SYSEXT,
&m) == EFI_SUCCESS)
combine_measured_flag(&confext_measured, m);
if (PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_DTB)) {
dt_size = sections[UNIFIED_SECTION_DTB].size;
dt_base = POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + sections[UNIFIED_SECTION_DTB].memory_offset;
@@ -843,8 +877,7 @@ static EFI_STATUS run(EFI_HANDLE image) {
/* access_mode= */ 0444,
/* tpm_pcr= */ UINT32_MAX,
/* tpm_description= */ NULL,
&pcrsig_initrd,
&pcrsig_initrd_size,
initrds + INITRD_PCRSIG,
/* ret_measured= */ NULL);
/* If the public key used for the PCR signatures was embedded in the PE image, then let's wrap it in
@@ -861,68 +894,42 @@ static EFI_STATUS run(EFI_HANDLE image) {
/* access_mode= */ 0444,
/* tpm_pcr= */ UINT32_MAX,
/* tpm_description= */ NULL,
&pcrpkey_initrd,
&pcrpkey_initrd_size,
initrds + INITRD_PCRPKEY,
/* ret_measured= */ NULL);
linux_size = sections[UNIFIED_SECTION_LINUX].size;
linux_base = POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + sections[UNIFIED_SECTION_LINUX].memory_offset;
struct iovec kernel = IOVEC_MAKE(
(const uint8_t*) loaded_image->ImageBase + sections[UNIFIED_SECTION_LINUX].memory_offset,
sections[UNIFIED_SECTION_LINUX].size);
if (PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_INITRD)) {
initrd_size = sections[UNIFIED_SECTION_INITRD].size;
initrd_base = POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + sections[UNIFIED_SECTION_INITRD].memory_offset;
}
if (PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_INITRD))
initrds[INITRD_BASE] = IOVEC_MAKE(
(const uint8_t*) loaded_image->ImageBase + sections[UNIFIED_SECTION_INITRD].memory_offset,
sections[UNIFIED_SECTION_INITRD].size);
if (PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_UCODE)) {
ucode_size = sections[UNIFIED_SECTION_UCODE].size;
ucode_base = POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + sections[UNIFIED_SECTION_UCODE].memory_offset;
}
if (PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_UCODE))
initrds[INITRD_UCODE] = IOVEC_MAKE(
(const uint8_t*) loaded_image->ImageBase + sections[UNIFIED_SECTION_UCODE].memory_offset,
sections[UNIFIED_SECTION_UCODE].size);
_cleanup_pages_ Pages initrd_pages = {};
if (ucode_base || credential_initrd || global_credential_initrd || sysext_initrd || confext_initrd || pcrsig_initrd || pcrpkey_initrd) {
/* If we have generated initrds dynamically or there is a microcode initrd, combine them with the built-in initrd. */
err = combine_initrds(
(const void*const[]) {
/* Microcode must always be first as kernel only scans uncompressed cpios
* and later initrds might be compressed. */
PHYSICAL_ADDRESS_TO_POINTER(ucode_base),
PHYSICAL_ADDRESS_TO_POINTER(initrd_base),
credential_initrd,
global_credential_initrd,
sysext_initrd,
confext_initrd,
pcrsig_initrd,
pcrpkey_initrd,
},
(const size_t[]) {
ucode_size,
initrd_size,
credential_initrd_size,
global_credential_initrd_size,
sysext_initrd_size,
confext_initrd_size,
pcrsig_initrd_size,
pcrpkey_initrd_size,
},
8,
&initrd_pages, &initrd_size);
struct iovec final_initrd;
if (initrds_need_combine(initrds)) {
/* If we have generated initrds dynamically or there is a microcode initrd, combine them with
* the built-in initrd. */
err = combine_initrds(initrds, _INITRD_MAX, &initrd_pages, &final_initrd.iov_len);
if (err != EFI_SUCCESS)
return err;
initrd_base = initrd_pages.addr;
final_initrd.iov_base = PHYSICAL_ADDRESS_TO_POINTER(initrd_pages.addr);
/* Given these might be large let's free them explicitly, quickly. */
credential_initrd = mfree(credential_initrd);
global_credential_initrd = mfree(global_credential_initrd);
sysext_initrd = mfree(sysext_initrd);
confext_initrd = mfree(confext_initrd);
pcrsig_initrd = mfree(pcrsig_initrd);
pcrpkey_initrd = mfree(pcrpkey_initrd);
}
/* Given these might be large let's free them explicitly before we pass control to Linux */
initrds_free(&initrds);
} else
final_initrd = initrds[INITRD_BASE];
err = linux_exec(image, cmdline,
PHYSICAL_ADDRESS_TO_POINTER(linux_base), linux_size,
PHYSICAL_ADDRESS_TO_POINTER(initrd_base), initrd_size);
kernel.iov_base, kernel.iov_len,
final_initrd.iov_base, final_initrd.iov_len);
graphics_mode(false);
return err;
}