]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
boot: measure select SMBIOS objects explicitly
authorLennart Poettering <lennart@amutable.com>
Thu, 21 May 2026 15:26:02 +0000 (17:26 +0200)
committerLennart Poettering <lennart@amutable.com>
Thu, 21 May 2026 16:34:01 +0000 (18:34 +0200)
man/systemd-boot.xml
man/systemd-stub.xml
src/boot/boot.c
src/boot/measure-smbios.c [new file with mode: 0644]
src/boot/measure-smbios.h [new file with mode: 0644]
src/boot/meson.build
src/boot/smbios.c
src/boot/smbios.h
src/boot/stub.c
src/fundamental/efivars.h
src/fundamental/tpm2-pcr.h

index 1acf5d083e5805597cf6f09f98c1f93d8f09f9ae..2162c7ffb2bba3e611598898b70907ea8d3b78f9 100644 (file)
@@ -622,6 +622,19 @@ extra       /6a9857a393724b7a981ebb5b8495b9ea/1.2.3/baz.confext.raw</programlist
         <xi:include href="version-info.xml" xpointer="v258"/></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>LoaderPcrSMBIOS</varname></term>
+
+        <listitem><para>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.
+        <literal>1</literal>). Set by the boot loader if a measurement was successfully completed, and remains
+        unset otherwise. (Note that <command>systemd-stub</command> performs the same measurement when booted
+        directly, bypassing the boot loader.)</para>
+
+        <xi:include href="version-info.xml" xpointer="v261"/></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><varname>LoaderImageIdentifier</varname></term>
 
index 95f62ca66b56abebd60ceaa12daf59cf68a35d6f..a5172fed1cc5ce6b9acd0e5b97c0fcc29f78a4a4 100644 (file)
         <xi:include href="version-info.xml" xpointer="v257"/></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>LoaderPcrSMBIOS</varname></term>
+
+        <listitem><para>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.
+        <literal>1</literal>). This variable is set if a measurement was successfully completed, and remains
+        unset otherwise. <command>systemd-stub</command> 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.</para>
+
+        <xi:include href="version-info.xml" xpointer="v261"/></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><varname>LoaderBootSecret</varname></term>
 
index 9f833f02b1b0fefb8bca4fccbae71a472d82ae44..3ba430a8442060b77f7bd8c240cd14ea32e33f03 100644 (file)
@@ -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 (file)
index 0000000..2379f4d
--- /dev/null
@@ -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 (file)
index 0000000..758b2c4
--- /dev/null
@@ -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);
index 2dc9c64256305c81a237748615f645c47db7b19e..d2524145d01636d1afd97eb4cd2f4a6fb75e717d 100644 (file)
@@ -323,6 +323,7 @@ libefi_sources = files(
         'hii.c',
         'initrd.c',
         'measure.c',
+        'measure-smbios.c',
         'part-discovery.c',
         'pe.c',
         'random-seed.c',
index 0bbcf30123d18d84ee41b0166f76418d31a595ae..46e9817acc88d4ea059a7c9f19b21b8b48943420 100644 (file)
@@ -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);
index 694ef568e6818b115229e78ab69383cf565179ba..226ebf758f129d05d89e7ce87dd43b5a0a3fd53d 100644 (file)
@@ -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;
index 6411102ec435e115bdf9f9c50de3723cfd6bbda8..712a82e739850242612c470d243217557e2e23e9 100644 (file)
@@ -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, &parameters_measured);
index eddda7c23a0c873e2af35db58955ce74b27753db..88bc97d75d6c363b4aad97ee51a8718feca4ba80 100644 (file)
@@ -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,
index 1d39ba59596f390a316347a2f093cedd99da1e2c..72835e886b78d9d486c53e2b24d55bec7caba0d9 100644 (file)
@@ -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)