From: Lennart Poettering Date: Thu, 21 May 2026 15:26:02 +0000 (+0200) Subject: boot: measure select SMBIOS objects explicitly X-Git-Tag: v261-rc1~26^2~5 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=29c6d1c12549;p=thirdparty%2Fsystemd.git boot: measure select SMBIOS objects explicitly --- diff --git a/man/systemd-boot.xml b/man/systemd-boot.xml index 1acf5d083e5..2162c7ffb2b 100644 --- a/man/systemd-boot.xml +++ b/man/systemd-boot.xml @@ -622,6 +622,19 @@ extra /6a9857a393724b7a981ebb5b8495b9ea/1.2.3/baz.confext.raw + + LoaderPcrSMBIOS + + The PCR register index select SMBIOS structures are measured into — type 1 + (system information, with the volatile "Wake-up Type" field zeroed out), type 2 (baseboard + information) and type 11 (OEM strings). Formatted as decimal ASCII string (e.g. + 1). Set by the boot loader if a measurement was successfully completed, and remains + unset otherwise. (Note that systemd-stub performs the same measurement when booted + directly, bypassing the boot loader.) + + + + LoaderImageIdentifier diff --git a/man/systemd-stub.xml b/man/systemd-stub.xml index 95f62ca66b5..a5172fed1cc 100644 --- a/man/systemd-stub.xml +++ b/man/systemd-stub.xml @@ -664,6 +664,20 @@ + + LoaderPcrSMBIOS + + The PCR register index select SMBIOS structures are measured into — type 1 + (system information, with the volatile "Wake-up Type" field zeroed out), type 2 (baseboard + information) and type 11 (OEM strings). Formatted as decimal ASCII string (e.g. + 1). This variable is set if a measurement was successfully completed, and remains + unset otherwise. systemd-stub performs this measurement only if it has not already + been done in the same boot (e.g. by the boot loader), as indicated by this variable being set + already. + + + + LoaderBootSecret diff --git a/src/boot/boot.c b/src/boot/boot.c index 9f833f02b1b..3ba430a8442 100644 --- a/src/boot/boot.c +++ b/src/boot/boot.c @@ -17,6 +17,7 @@ #include "iovec-util.h" #include "line-edit.h" #include "measure.h" +#include "measure-smbios.h" #include "memory-util.h" #include "part-discovery.h" #include "pe.h" @@ -3267,6 +3268,7 @@ static void export_loader_variables( EFI_LOADER_FEATURE_TYPE1_UKI_URL | EFI_LOADER_FEATURE_TPM2_ACTIVE_PCR_BANKS | EFI_LOADER_FEATURE_KEYBOARD_LAYOUT | + EFI_LOADER_FEATURE_SMBIOS_MEASURED | 0; assert(loaded_image); @@ -3434,6 +3436,10 @@ static EFI_STATUS run(EFI_HANDLE image) { export_common_variables(loaded_image); export_loader_variables(loaded_image, init_usec); + /* Measure SMBIOS data into PCR 1. This is done early, and suppressed if sd-stub later runs in + * the same boot (and vice versa), via the LoaderPcrSMBIOS EFI variable. */ + measure_smbios(); + (void) load_drivers(image, loaded_image, root_dir); _cleanup_free_ char16_t *loaded_image_path = NULL; diff --git a/src/boot/measure-smbios.c b/src/boot/measure-smbios.c new file mode 100644 index 00000000000..2379f4de9e0 --- /dev/null +++ b/src/boot/measure-smbios.c @@ -0,0 +1,96 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "efi-efivars.h" +#include "efi-log.h" +#include "measure.h" +#include "measure-smbios.h" +#include "smbios.h" +#include "tpm2-pcr.h" +#include "util.h" + +static void measure_smbios_raw( + const void *p, + size_t size, + uint32_t event_id, + const char16_t *description, + bool *measured) { + + EFI_STATUS err; + bool m = false; + + assert(p); + assert(description); + assert(measured); + + err = tpm_log_tagged_event( + TPM2_PCR_PLATFORM_CONFIG, + POINTER_TO_PHYSICAL_ADDRESS(p), + size, + event_id, + description, + &m); + if (err != EFI_SUCCESS) + log_error_status(err, "Unable to measure SMBIOS structure (%ls), ignoring: %m", description); + + *measured = *measured || m; +} + +static void measure_smbios_type1(const SmbiosHeader *header, size_t size, bool *measured) { + assert(header); + assert(measured); + + /* The wake-up type field varies depending on how the machine was powered on (cold boot, resume + * from sleep, AC restore, …), which would make the measurement non-reproducible. Hence measure a + * copy with that field zeroed out. */ + + assert(size >= sizeof(SmbiosTableType1)); + + _cleanup_free_ SmbiosTableType1 *copy = xmemdup(header, size); + copy->wake_up_type = 0; + + measure_smbios_raw(copy, size, SMBIOS_TYPE1_EVENT_TAG_ID, u"smbios:type1", measured); +} + +static bool measure_smbios_object(const SmbiosHeader *header, size_t size, void *userdata) { + bool *measured = ASSERT_PTR(userdata); + + switch (header->type) { + + case 1: /* System Information */ + measure_smbios_type1(header, size, measured); + break; + + case 2: /* Baseboard Information */ + measure_smbios_raw(header, size, SMBIOS_TYPE2_EVENT_TAG_ID, u"smbios:type2", measured); + break; + + case 11: /* OEM Strings */ + measure_smbios_raw(header, size, SMBIOS_TYPE11_EVENT_TAG_ID, u"smbios:type11", measured); + break; + + default: + break; + } + + return true; /* Keep iterating: there may be more than one matching structure (e.g. type 11). */ +} + +void measure_smbios(void) { + bool measured = false; + + if (!tpm_present()) + return; + + /* If the measurement was already done this boot (e.g. by sd-boot before it chainloaded us), don't + * do it again — re-extending PCR 1 would invalidate the value. */ + if (efivar_get_raw(MAKE_GUID_PTR(LOADER), u"LoaderPcrSMBIOS", /* ret_data= */ NULL, /* ret_size= */ NULL) == EFI_SUCCESS) + return; + + /* Measure SMBIOS type 1 (system information), type 2 (baseboard information) and type 11 (OEM + * strings) into PCR 1, in a single pass over the SMBIOS table. */ + smbios_foreach(measure_smbios_object, &measured); + + /* If we measured something, tell the OS which PCR we used (and suppress a second pass). */ + if (measured) + (void) efivar_set_uint64_str16(MAKE_GUID_PTR(LOADER), u"LoaderPcrSMBIOS", TPM2_PCR_PLATFORM_CONFIG, /* flags= */ 0); +} diff --git a/src/boot/measure-smbios.h b/src/boot/measure-smbios.h new file mode 100644 index 00000000000..758b2c45b43 --- /dev/null +++ b/src/boot/measure-smbios.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +/* Measures SMBIOS type 1 (system information, with the volatile "Wake-up Type" field masked) and all + * type 11 (OEM strings) structures into PCR 1, and records the PCR index in the transient + * LoaderPcrSMBIOS EFI variable. Called by both sd-boot and sd-stub; the presence of LoaderPcrSMBIOS + * suppresses a redundant second measurement when both run during the same boot. */ +void measure_smbios(void); diff --git a/src/boot/meson.build b/src/boot/meson.build index 2dc9c642563..d2524145d01 100644 --- a/src/boot/meson.build +++ b/src/boot/meson.build @@ -323,6 +323,7 @@ libefi_sources = files( 'hii.c', 'initrd.c', 'measure.c', + 'measure-smbios.c', 'part-discovery.c', 'pe.c', 'random-seed.c', diff --git a/src/boot/smbios.c b/src/boot/smbios.c index 0bbcf30123d..46e9817acc8 100644 --- a/src/boot/smbios.c +++ b/src/boot/smbios.c @@ -39,12 +39,6 @@ typedef struct { uint64_t table_address; } _packed_ Smbios3EntryPoint; -typedef struct { - uint8_t type; - uint8_t length; - uint8_t handle[2]; -} _packed_ SmbiosHeader; - typedef struct { SmbiosHeader header; uint8_t vendor; @@ -56,18 +50,6 @@ typedef struct { uint8_t bios_characteristics_ext[2]; } _packed_ SmbiosTableType0; -typedef struct { - SmbiosHeader header; - uint8_t manufacturer; - uint8_t product_name; - uint8_t version; - uint8_t serial_number; - EFI_GUID uuid; - uint8_t wake_up_type; - uint8_t sku_number; - uint8_t family; -} _packed_ SmbiosTableType1; - typedef struct { SmbiosHeader header; uint8_t manufacturer; @@ -103,6 +85,44 @@ static const void* find_smbios_configuration_table(uint64_t *ret_size) { return NULL; } +/* Given 'p' pointing at a structure header with 'size' bytes left in the table from there onwards, + * returns a pointer just past the end of this structure (i.e. the start of the next one), accounting + * for the formatted area and the trailing string set (terminated by a double NUL byte). Returns NULL + * if the structure is malformed or runs past the end of the table. */ +static const uint8_t* smbios_structure_end(const uint8_t *p, uint64_t size) { + assert(p); + + if (size < sizeof(SmbiosHeader)) + return NULL; + + const SmbiosHeader *header = (const SmbiosHeader *) p; + if (size < header->length) + return NULL; + + /* Skip over formatted area. */ + const uint8_t *q = p + header->length; + size -= header->length; + + /* Special case: if there are no strings appended, we'll see two NUL bytes. */ + if (size >= 2 && q[0] == 0 && q[1] == 0) + return q + 2; + + /* Skip over a populated string table. */ + bool first = true; + for (;;) { + const uint8_t *e = memchr(q, 0, size); + if (!e) + return NULL; + + if (!first && e == q) /* Double NUL byte means we've reached the end of the string table. */ + return q + 1; + + size -= e + 1 - q; + q = e + 1; + first = false; + } +} + static const SmbiosHeader* get_smbios_table(uint8_t type, size_t min_size, uint64_t *ret_size_left) { uint64_t size; const uint8_t *p = find_smbios_configuration_table(&size); @@ -132,34 +152,12 @@ static const SmbiosHeader* get_smbios_table(uint8_t type, size_t min_size, uint6 return header; /* Yay! */ } - /* Skip over formatted area. */ - size -= header->length; - p += header->length; - - /* Special case: if there are no strings appended, we'll see two NUL bytes, skip over them */ - if (size >= 2 && p[0] == 0 && p[1] == 0) { - size -= 2; - p += 2; - continue; - } - - /* Skip over a populated string table. */ - bool first = true; - for (;;) { - const uint8_t *e = memchr(p, 0, size); - if (!e) - goto not_found; - - if (!first && e == p) {/* Double NUL byte means we've reached the end of the string table. */ - p++; - size--; - break; - } + const uint8_t *next = smbios_structure_end(p, size); + if (!next) + goto not_found; - size -= e + 1 - p; - p = e + 1; - first = false; - } + size -= next - p; + p = next; } not_found: @@ -169,6 +167,40 @@ not_found: return NULL; } +void smbios_foreach(SmbiosForeachFunc func, void *userdata) { + assert(func); + + uint64_t size; + const uint8_t *p = find_smbios_configuration_table(&size); + if (!p) + return; + + /* Walks the SMBIOS table exactly once, invoking 'func' for every structure. 'func' receives the + * structure's header (which carries its type) and the structure's total length (formatted area + + * trailing string set); returning false stops the iteration early. */ + + for (;;) { + if (size < sizeof(SmbiosHeader)) + return; + + const SmbiosHeader *header = (const SmbiosHeader *) p; + + /* End of table. */ + if (header->type == 127) + return; + + const uint8_t *next = smbios_structure_end(p, size); + if (!next) + return; + + if (!func(header, next - p, userdata)) + return; + + size -= next - p; + p = next; + } +} + bool smbios_in_hypervisor(void) { /* Look up BIOS Information (Type 0). */ const SmbiosTableType0 *type0 = (const SmbiosTableType0 *) get_smbios_table(0, sizeof(SmbiosTableType0), /* ret_size_left= */ NULL); diff --git a/src/boot/smbios.h b/src/boot/smbios.h index 694ef568e68..226ebf758f1 100644 --- a/src/boot/smbios.h +++ b/src/boot/smbios.h @@ -3,10 +3,35 @@ #include "efi.h" +typedef struct { + uint8_t type; + uint8_t length; + uint8_t handle[2]; +} _packed_ SmbiosHeader; + +typedef struct { + SmbiosHeader header; + uint8_t manufacturer; + uint8_t product_name; + uint8_t version; + uint8_t serial_number; + EFI_GUID uuid; + uint8_t wake_up_type; + uint8_t sku_number; + uint8_t family; +} _packed_ SmbiosTableType1; + bool smbios_in_hypervisor(void); const char* smbios_find_oem_string(const char *name, const char *after); +/* Invoked by smbios_foreach() for each SMBIOS structure. 'header' points at the structure (which + * carries its type), and 'size' is the structure's total length (formatted area + trailing string + * set). Returning false stops the iteration. */ +typedef bool (*SmbiosForeachFunc)(const SmbiosHeader *header, size_t size, void *userdata); + +void smbios_foreach(SmbiosForeachFunc func, void *userdata); + typedef struct RawSmbiosInfo { const char *manufacturer; const char *product_name; diff --git a/src/boot/stub.c b/src/boot/stub.c index 6411102ec43..712a82e7398 100644 --- a/src/boot/stub.c +++ b/src/boot/stub.c @@ -14,6 +14,7 @@ #include "iovec-util.h" #include "linux.h" #include "measure.h" +#include "measure-smbios.h" #include "memory-util.h" #include "part-discovery.h" #include "pe.h" @@ -116,6 +117,7 @@ static void export_stub_variables(EFI_LOADED_IMAGE_PROTOCOL *loaded_image, unsig EFI_STUB_FEATURE_MULTI_PROFILE_UKI | /* We grok the "@1" profile command line argument */ EFI_STUB_FEATURE_REPORT_STUB_PARTITION | /* We set StubDevicePartUUID + StubImageIdentifier */ EFI_STUB_FEATURE_REPORT_URL | /* We set StubDeviceURL + LoaderDeviceURL */ + EFI_STUB_FEATURE_SMBIOS_MEASURED | /* We measure SMBIOS data into PCR 1 */ 0; assert(loaded_image); @@ -1252,6 +1254,10 @@ static EFI_STATUS run(EFI_HANDLE image) { export_common_variables(loaded_image); export_stub_variables(loaded_image, profile); + /* Measure SMBIOS data into PCR 1, unless sd-boot already did so in the same boot (tracked via + * the LoaderPcrSMBIOS EFI variable). */ + measure_smbios(); + /* First load the base device tree, then fix it up using addons - global first, then per-UKI. */ install_embedded_devicetree(loaded_image, sections, &dt_state); install_addon_devicetrees(&dt_state, dt_addons, n_dt_addons, ¶meters_measured); diff --git a/src/fundamental/efivars.h b/src/fundamental/efivars.h index eddda7c23a0..88bc97d75d6 100644 --- a/src/fundamental/efivars.h +++ b/src/fundamental/efivars.h @@ -30,6 +30,7 @@ #define EFI_LOADER_FEATURE_TPM2_ACTIVE_PCR_BANKS (UINT64_C(1) << 18) #define EFI_LOADER_FEATURE_ENTRY_PREFERRED (UINT64_C(1) << 19) #define EFI_LOADER_FEATURE_KEYBOARD_LAYOUT (UINT64_C(1) << 20) +#define EFI_LOADER_FEATURE_SMBIOS_MEASURED (UINT64_C(1) << 21) /* Features of the stub, i.e. systemd-stub */ #define EFI_STUB_FEATURE_REPORT_BOOT_PARTITION (UINT64_C(1) << 0) @@ -44,6 +45,7 @@ #define EFI_STUB_FEATURE_MULTI_PROFILE_UKI (UINT64_C(1) << 9) #define EFI_STUB_FEATURE_REPORT_STUB_PARTITION (UINT64_C(1) << 10) #define EFI_STUB_FEATURE_REPORT_URL (UINT64_C(1) << 11) +#define EFI_STUB_FEATURE_SMBIOS_MEASURED (UINT64_C(1) << 12) typedef enum SecureBootMode { SECURE_BOOT_UNSUPPORTED, diff --git a/src/fundamental/tpm2-pcr.h b/src/fundamental/tpm2-pcr.h index 1d39ba59596..72835e886b7 100644 --- a/src/fundamental/tpm2-pcr.h +++ b/src/fundamental/tpm2-pcr.h @@ -56,3 +56,12 @@ enum { /* The tag used for EV_EVENT_TAG event log records covering the selected UKI profile */ #define UKI_PROFILE_EVENT_TAG_ID UINT32_C(0x13aed6db) + +/* The tag used for EV_EVENT_TAG event log records covering the SMBIOS type 1 (system information) structure */ +#define SMBIOS_TYPE1_EVENT_TAG_ID UINT32_C(0xd5cb7cbc) + +/* The tag used for EV_EVENT_TAG event log records covering the SMBIOS type 2 (baseboard information) structure */ +#define SMBIOS_TYPE2_EVENT_TAG_ID UINT32_C(0xe0d47bc8) + +/* The tag used for EV_EVENT_TAG event log records covering SMBIOS type 11 (OEM strings) structures */ +#define SMBIOS_TYPE11_EVENT_TAG_ID UINT32_C(0xc0b3bd23)