diff --git a/docs/TPM2_PCR_MEASUREMENTS.md b/docs/TPM2_PCR_MEASUREMENTS.md
index 462a86b36c0..81c8aaa156e 100644
--- a/docs/TPM2_PCR_MEASUREMENTS.md
+++ b/docs/TPM2_PCR_MEASUREMENTS.md
@@ -120,6 +120,16 @@ Devicetree addons are measured individually as a tagged event.
→ **Measured hash** covers the content of the Devicetree.
+### PCR 12, `EV_EVENT_TAG`, "Ucode addons"
+
+Ucode addons are measured individually as a tagged event.
+
+→ **Event Tag** `0xdac08e1a`
+
+→ **Description** the addon filename.
+
+→ **Measured hash** covers the contents of the ucode initrd.
+
### PCR 12, `EV_IPL`, "Per-UKI Credentials initrd"
→ **Description** in the event log record is the constant string "Credentials
diff --git a/man/systemd-stub.xml b/man/systemd-stub.xml
index 2724c57ef92..bb0376ed865 100644
--- a/man/systemd-stub.xml
+++ b/man/systemd-stub.xml
@@ -182,10 +182,10 @@
Similarly, files
foo.efi.extra.d/*.addon.efi are loaded and verified as
- PE binaries, and a .cmdline section is parsed from them. Addons are supposed to be
- used to pass additional kernel command line parameters or Devicetree blobs, regardless of the kernel
- image being booted, for example to allow platform vendors to ship platform-specific
- configuration.
+ PE binaries, and a .cmdline or .ucode section is parsed from them.
+ Addons are supposed to be used to pass additional kernel command line parameters, or Devicetree blobs,
+ and microcode updates, regardless of the kernel image being booted, for example to allow platform vendors
+ to ship platform-specific configuration.
In case Secure Boot is enabled, these files will be validated using keys in UEFI DB, Shim's DB or
Shim's MOK, and will be rejected otherwise. Additionally, if both the addon and the UKI contain a
@@ -199,7 +199,9 @@
Addon files are sorted, loaded, and measured into TPM PCR 12 (if a TPM is present) and appended
to the kernel command line. UKI command line options are listed first, then options from addons in
/loader/addons/*.addon.efi, and finally UKI-specific addons. Device tree blobs are
- loaded and measured following the same algorithm. Addons are always loaded in the same order based on
+ loaded and measured following the same algorithm. Microcode addons are passed to the kernel in inverse
+ order (UKI specific addons, global addons, UKI embedded section). This is because the microcode update
+ driver stops on the first matching filename. Addons are always loaded in the same order based on
the filename, so that, given the same set of addons, the same set of measurements can be expected in
PCR12. However, note that the filename is not protected by the PE signature, and as such an attacker
with write access to the ESP could potentially rename these files to change the order in which they are
@@ -215,9 +217,10 @@
measured into TPM PCR 12 (if a TPM is present).
Additionally, files /loader/addons/*.addon.efi are loaded and
- verified as PE binaries, and .cmdline and/or .dtb sections are
- parsed from them. This is supposed to be used to pass additional command line parameters or Devicetree
- blobs to the kernel, regardless of the kernel being booted.
+ verified as PE binaries, and .cmdline, .dtb and/or
+ .ucodesections are parsed from them. This is supposed to be used to pass additional
+ command line parameters, Devicetree blobs and microcode updates to the kernel, regardless of the
+ kernel being booted.
These mechanisms may be used to parameterize and extend trusted (i.e. signed), immutable initrd
diff --git a/src/boot/efi/stub.c b/src/boot/efi/stub.c
index d48cbf2861b..87aa8979105 100644
--- a/src/boot/efi/stub.c
+++ b/src/boot/efi/stub.c
@@ -104,7 +104,6 @@ static EFI_STATUS combine_initrds(
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;
@@ -298,30 +297,30 @@ static void cmdline_append_and_measure_addons(
*cmdline_append = xasprintf("%ls %ls", tmp, copy);
}
-typedef struct DevicetreeAddon {
+typedef struct NamedAddon {
char16_t *filename;
struct iovec blob;
-} DevicetreeAddon;
+} NamedAddon;
-static void devicetree_addon_done(DevicetreeAddon *a) {
+static void named_addon_done(NamedAddon *a) {
assert(a);
a->filename = mfree(a->filename);
iovec_done(&a->blob);
}
-static void devicetree_addon_free_many(DevicetreeAddon *a, size_t n) {
+static void named_addon_free_many(NamedAddon *a, size_t n) {
assert(a || n == 0);
FOREACH_ARRAY(i, a, n)
- devicetree_addon_done(i);
+ named_addon_done(i);
free(a);
}
static void install_addon_devicetrees(
struct devicetree_state *dt_state,
- DevicetreeAddon *addons,
+ NamedAddon *addons,
size_t n_addons,
int *parameters_measured) {
@@ -357,14 +356,78 @@ static void install_addon_devicetrees(
}
}
+static inline void iovec_array_extend(struct iovec **arr, size_t *n_arr, struct iovec elem) {
+ assert(arr);
+ assert(n_arr);
+
+ if (!iovec_is_set(&elem))
+ return;
+
+ *arr = xrealloc(*arr, *n_arr * sizeof(struct iovec), (*n_arr + 1) * sizeof(struct iovec));
+ (*arr)[(*n_arr)++] = elem;
+}
+
+static void measure_and_append_ucode_addons(
+ struct iovec **all_initrds,
+ size_t *n_all_initrds,
+ NamedAddon *ucode_addons,
+ size_t n_ucode_addons,
+ int *sections_measured) {
+
+ EFI_STATUS err;
+
+ assert(all_initrds);
+ assert(n_all_initrds);
+ assert(ucode_addons || n_ucode_addons == 0);
+ assert(sections_measured);
+
+ /* Ucode addons need to be measured and copied into all_initrds in reverse order,
+ * the kernel takes the first one it finds. */
+ for (ssize_t i = n_ucode_addons - 1; i >= 0; i--) {
+ bool m = false;
+ err = tpm_log_tagged_event(
+ TPM2_PCR_KERNEL_CONFIG,
+ POINTER_TO_PHYSICAL_ADDRESS(ucode_addons[i].blob.iov_base),
+ ucode_addons[i].blob.iov_len,
+ UCODE_ADDON_EVENT_TAG_ID,
+ ucode_addons[i].filename,
+ &m);
+ if (err != EFI_SUCCESS)
+ return (void) log_error_status(
+ err,
+ "Unable to extend PCR %i with UCODE addon '%ls': %m",
+ TPM2_PCR_KERNEL_CONFIG,
+ ucode_addons[i].filename);
+
+ combine_measured_flag(sections_measured, m);
+
+ iovec_array_extend(all_initrds, n_all_initrds, ucode_addons[i].blob);
+ }
+}
+
+static void extend_initrds(
+ struct iovec initrds[static _INITRD_MAX],
+ struct iovec **all_initrds,
+ size_t *n_all_initrds) {
+
+ assert(initrds);
+ assert(all_initrds);
+ assert(n_all_initrds);
+
+ FOREACH_ARRAY(i, initrds, _INITRD_MAX)
+ iovec_array_extend(all_initrds, n_all_initrds, *i);
+}
+
static EFI_STATUS load_addons(
EFI_HANDLE stub_image,
EFI_LOADED_IMAGE_PROTOCOL *loaded_image,
const char16_t *prefix,
const char *uname,
char16_t **cmdline, /* Both input+output, extended with new addons we find */
- DevicetreeAddon **devicetree_addons, /* Ditto */
- size_t *n_devicetree_addons) {
+ NamedAddon **devicetree_addons, /* Ditto */
+ size_t *n_devicetree_addons,
+ NamedAddon **ucode_addons, /* Ditto */
+ size_t *n_ucode_addons) {
_cleanup_(strv_freep) char16_t **items = NULL;
_cleanup_(file_closep) EFI_FILE *root = NULL;
@@ -429,11 +492,12 @@ static EFI_STATUS load_addons(
err = pe_memory_locate_sections(loaded_addon->ImageBase, unified_sections, sections);
if (err != EFI_SUCCESS ||
(!PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_CMDLINE) &&
- !PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_DTB))) {
+ !PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_DTB) &&
+ !PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_UCODE))) {
if (err == EFI_SUCCESS)
err = EFI_NOT_FOUND;
log_error_status(err,
- "Unable to locate embedded .cmdline/.dtb sections in %ls, ignoring: %m",
+ "Unable to locate embedded .cmdline/.dtb/.ucode sections in %ls, ignoring: %m",
items[i]);
continue;
}
@@ -463,15 +527,28 @@ static EFI_STATUS load_addons(
if (devicetree_addons && PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_DTB)) {
*devicetree_addons = xrealloc(*devicetree_addons,
- *n_devicetree_addons * sizeof(DevicetreeAddon),
- (*n_devicetree_addons + 1) * sizeof(DevicetreeAddon));
+ *n_devicetree_addons * sizeof(NamedAddon),
+ (*n_devicetree_addons + 1) * sizeof(NamedAddon));
- (*devicetree_addons)[(*n_devicetree_addons)++] = (DevicetreeAddon) {
+ (*devicetree_addons)[(*n_devicetree_addons)++] = (NamedAddon) {
.blob = {
.iov_base = xmemdup((const uint8_t*) loaded_addon->ImageBase + sections[UNIFIED_SECTION_DTB].memory_offset, sections[UNIFIED_SECTION_DTB].size),
.iov_len = sections[UNIFIED_SECTION_DTB].size,
},
- .filename = xstrdup16(items[i]),
+ .filename = xstrdup16(items[i]),
+ };
+ }
+
+ if (ucode_addons && PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_UCODE)) {
+ *ucode_addons = xrealloc(*ucode_addons,
+ *n_ucode_addons * sizeof(NamedAddon),
+ (*n_ucode_addons + 1) * sizeof(NamedAddon));
+ (*ucode_addons)[(*n_ucode_addons)++] = (NamedAddon) {
+ .blob = {
+ .iov_base = xmemdup((const uint8_t*) loaded_addon->ImageBase + sections[UNIFIED_SECTION_UCODE].memory_offset, sections[UNIFIED_SECTION_UCODE].size),
+ .iov_len = sections[UNIFIED_SECTION_UCODE].size,
+ },
+ .filename = xstrdup16(items[i]),
};
}
}
@@ -587,23 +664,6 @@ static void initrds_free(struct iovec (*initrds)[_INITRD_MAX]) {
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 void generate_sidecar_initrds(
EFI_LOADED_IMAGE_PROTOCOL *loaded_image,
struct iovec initrds[static _INITRD_MAX],
@@ -782,8 +842,10 @@ static void load_all_addons(
EFI_LOADED_IMAGE_PROTOCOL *loaded_image,
const char *uname,
char16_t **cmdline_addons,
- DevicetreeAddon **dt_addons,
- size_t *n_dt_addons) {
+ NamedAddon **dt_addons,
+ size_t *n_dt_addons,
+ NamedAddon **ucode_addons,
+ size_t *n_ucode_addons) {
EFI_STATUS err;
@@ -791,6 +853,8 @@ static void load_all_addons(
assert(cmdline_addons);
assert(dt_addons);
assert(n_dt_addons);
+ assert(ucode_addons);
+ assert(n_ucode_addons);
err = load_addons(
image,
@@ -799,7 +863,9 @@ static void load_all_addons(
uname,
cmdline_addons,
dt_addons,
- n_dt_addons);
+ n_dt_addons,
+ ucode_addons,
+ n_ucode_addons);
if (err != EFI_SUCCESS)
log_error_status(err, "Error loading global addons, ignoring: %m");
@@ -815,7 +881,9 @@ static void load_all_addons(
uname,
cmdline_addons,
dt_addons,
- n_dt_addons);
+ n_dt_addons,
+ ucode_addons,
+ n_ucode_addons);
if (err != EFI_SUCCESS)
log_error_status(err, "Error loading UKI-specific addons, ignoring: %m");
}
@@ -863,8 +931,10 @@ static EFI_STATUS run(EFI_HANDLE image) {
PeSectionVector sections[ELEMENTSOF(unified_sections)] = {};
EFI_LOADED_IMAGE_PROTOCOL *loaded_image;
_cleanup_free_ char *uname = NULL;
- DevicetreeAddon *dt_addons = NULL;
- size_t n_dt_addons = 0;
+ NamedAddon *dt_addons = NULL, *ucode_addons = NULL;
+ size_t n_dt_addons = 0, n_ucode_addons = 0;
+ _cleanup_free_ struct iovec *all_initrds = NULL;
+ size_t n_all_initrds = 0;
EFI_STATUS err;
err = BS->HandleProtocol(image, MAKE_GUID_PTR(EFI_LOADED_IMAGE_PROTOCOL), (void **) &loaded_image);
@@ -890,8 +960,9 @@ static EFI_STATUS run(EFI_HANDLE image) {
/* Now that we have the UKI sections loaded, also load global first and then local (per-UKI)
* addons. The data is loaded at once, and then used later. */
- CLEANUP_ARRAY(dt_addons, n_dt_addons, devicetree_addon_free_many);
- load_all_addons(image, loaded_image, uname, &cmdline_addons, &dt_addons, &n_dt_addons);
+ CLEANUP_ARRAY(dt_addons, n_dt_addons, named_addon_free_many);
+ CLEANUP_ARRAY(ucode_addons, n_ucode_addons, named_addon_free_many);
+ load_all_addons(image, loaded_image, uname, &cmdline_addons, &dt_addons, &n_dt_addons, &ucode_addons, &n_ucode_addons);
/* If we have any extra command line to add via PE addons, load them now and append, and measure the
* additions together, after the embedded options, but before the smbios ones, so that the order is
@@ -912,16 +983,20 @@ static EFI_STATUS run(EFI_HANDLE image) {
generate_embedded_initrds(loaded_image, sections, initrds);
lookup_embedded_initrds(loaded_image, sections, initrds);
+ /* Measures ucode addons and puts them into all_initrds */
+ measure_and_append_ucode_addons(&all_initrds, &n_all_initrds, ucode_addons, n_ucode_addons, ¶meters_measured);
+ /* Adds all other initrds to all_initrds */
+ extend_initrds(initrds, &all_initrds, &n_all_initrds);
+
/* Export variables indicating what we measured */
export_pcr_variables(sections_measured, parameters_measured, sysext_measured, confext_measured);
/* Combine the initrds into one */
_cleanup_pages_ Pages initrd_pages = {};
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 (n_all_initrds > 1) {
+ /* There will always be a base initrd, if this counter is higher, we need to combine them */
+ err = combine_initrds(all_initrds, n_all_initrds, &initrd_pages, &final_initrd.iov_len);
if (err != EFI_SUCCESS)
return err;
@@ -930,7 +1005,7 @@ static EFI_STATUS run(EFI_HANDLE image) {
/* 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];
+ final_initrd = all_initrds[0];
struct iovec kernel = IOVEC_MAKE(
(const uint8_t*) loaded_image->ImageBase + sections[UNIFIED_SECTION_LINUX].memory_offset,
diff --git a/src/fundamental/tpm2-pcr.h b/src/fundamental/tpm2-pcr.h
index d0d5b74d0c5..ff957afcd0b 100644
--- a/src/fundamental/tpm2-pcr.h
+++ b/src/fundamental/tpm2-pcr.h
@@ -49,3 +49,6 @@ enum {
/* The tag used for EV_EVENT_TAG event log records covering Devicetree blobs */
#define DEVICETREE_ADDON_EVENT_TAG_ID UINT32_C(0x6c46f751)
+
+/* The tag used for EV_EVENT_TAG event log records covering ucode addons (effectively initrds) */
+#define UCODE_ADDON_EVENT_TAG_ID UINT32_C(0xdac08e1a)