]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
boot: add LoaderTpm2ActivePcrBanks runtime variable
authorLuca Boccassi <luca.boccassi@gmail.com>
Sat, 5 Jul 2025 22:04:35 +0000 (23:04 +0100)
committerLuca Boccassi <luca.boccassi@gmail.com>
Mon, 14 Jul 2025 19:56:22 +0000 (20:56 +0100)
It turns out checking sysfs is not 100% reliable to figure out whether
the firmware had TPM2 support enabled or not. For example with EDK2 arm64, the
default upstream build config bundles TPM2 support with SecureBoot support,
so if the latter is disabled, TPM2 is also unavailable. But still, the ACPI
TPM2 table is created just as if it was enabled. So /sys/firmware/acpi/tables/TPM2
exists and looks correct, but there are no measurements, neither the firmware
nor the loader/stub can do them, and /sys/kernel/security/tpm0/binary_bios_measurements
does not exist.

The loader can use the apposite UEFI protocol to check, which is a more
definitive answer. Given userspace can also make use of this information, export
the bitmask with the list of active banks as-is. If it's not 0, then we can be
sure a working TPM2 was available in EFI mode.

Partially fixes https://github.com/systemd/systemd/issues/38071

man/systemd-boot.xml
man/systemd-stub.xml
src/boot/boot.c
src/boot/export-vars.c
src/boot/measure.c
src/boot/measure.h
src/boot/proto/tcg.h
src/bootctl/bootctl-status.c
src/fundamental/efivars-fundamental.h
src/shared/efi-api.c

index b6898ea8f017a5bcc3347e4d9cac3a5b4b158537..52aa582c30b1ff626b531c5949a2a8d1548d7840 100644 (file)
         <xi:include href="version-info.xml" xpointer="v240"/></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>LoaderTpm2ActivePcrBanks</varname></term>
+
+        <listitem><para>Hexadecimal string representation of a bitmask with values defined by the TCG EFI
+        Protocol Specification for TPM 2.0 as EFI_TCG2_BOOT_HASH_ALG_*. If no TPM2 support or no active
+        banks were detected, will be set to <constant>0</constant>. Set by the boot loader. Use
+        <citerefentry><refentrytitle>systemd-analyze</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+        to view this data.</para>
+
+        <xi:include href="version-info.xml" xpointer="v258"/></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><varname>LoaderImageIdentifier</varname></term>
 
index dd709c2949e1661859762e3a3686c6e111697616..fb15861941078da02f9b55f72174de7606373eca 100644 (file)
         <xi:include href="version-info.xml" xpointer="v250"/></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>LoaderTpm2ActivePcrBanks</varname></term>
+
+        <listitem><para>Hexadecimal string representation of a bitmask with values defined by the TCG EFI
+        Protocol Specification for TPM 2.0 as EFI_TCG2_BOOT_HASH_ALG_*. If no TPM2 support or no active
+        banks were detected, will be set to <constant>0</constant>. Set by the boot loader. Use
+        <citerefentry><refentrytitle>systemd-analyze</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+        to view this data.</para>
+
+        <xi:include href="version-info.xml" xpointer="v258"/></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><varname>LoaderImageIdentifier</varname></term>
 
index 74b2884caf0098d344f49af2986049856c72bef6..ec779fa68dd43de2afff155f51594f24c56b91dd 100644 (file)
@@ -2849,6 +2849,7 @@ static void export_loader_variables(
                 EFI_LOADER_FEATURE_REPORT_URL |
                 EFI_LOADER_FEATURE_TYPE1_UKI |
                 EFI_LOADER_FEATURE_TYPE1_UKI_URL |
+                EFI_LOADER_FEATURE_TPM2_ACTIVE_PCR_BANKS |
                 0;
 
         assert(loaded_image);
index 25ca62065a39f528f1d4c8566621dea5e85c1c84..5c037bdd25235967712da3fa063a5b9ead8f7923 100644 (file)
@@ -3,6 +3,7 @@
 #include "device-path-util.h"
 #include "efi-efivars.h"
 #include "export-vars.h"
+#include "measure.h"
 #include "part-discovery.h"
 #include "url-discovery.h"
 #include "util.h"
@@ -51,4 +52,12 @@ void export_common_variables(EFI_LOADED_IMAGE_PROTOCOL *loaded_image) {
                 s = xasprintf("UEFI %u.%02u", ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff);
                 efivar_set_str16(MAKE_GUID_PTR(LOADER), u"LoaderFirmwareType", s, 0);
         }
+
+        /* ditto for LoaderTpm2ActivePcrBanks */
+        if (efivar_get_raw(MAKE_GUID_PTR(LOADER), u"LoaderTpm2ActivePcrBanks", NULL, NULL) != EFI_SUCCESS) {
+                uint32_t active_pcr_banks = tpm_get_active_pcr_banks();
+                _cleanup_free_ char16_t *s = NULL;
+                s = xasprintf("0x%08x", active_pcr_banks);
+                efivar_set_str16(MAKE_GUID_PTR(LOADER), u"LoaderTpm2ActivePcrBanks", s, 0);
+        }
 }
index 590fed8ec27630e6b7f322ba425e8c5dfde8a9ef..5cf6156d62228d33e1cb6d48818c09fcf183d1d6 100644 (file)
@@ -183,6 +183,24 @@ bool tpm_present(void) {
         return tcg2_interface_check();
 }
 
+uint32_t tpm_get_active_pcr_banks(void) {
+        uint32_t active_pcr_banks = 0;
+        EFI_TCG2_PROTOCOL *tpm2;
+        EFI_STATUS err;
+
+        tpm2 = tcg2_interface_check();
+        if (!tpm2)
+                return 0;
+
+        err = tpm2->GetActivePcrBanks(tpm2, &active_pcr_banks);
+        if (err != EFI_SUCCESS) {
+                log_warning_status(err, "Failed to get TPM2 active PCR banks, assuming none: %m");
+                return 0;
+        }
+
+        return active_pcr_banks;
+}
+
 static EFI_STATUS tcg2_log_ipl_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t buffer_size, const char16_t *description, bool *ret_measured) {
         EFI_TCG2_PROTOCOL *tpm2;
         EFI_STATUS err = EFI_SUCCESS;
index 9dde93b94d2fb14db97045049a4906cc3e00b2bf..4ceed0e0f8126c0b17b22d250dc4d4121ab0cdd5 100644 (file)
@@ -6,6 +6,7 @@
 #if ENABLE_TPM
 
 bool tpm_present(void);
+uint32_t tpm_get_active_pcr_banks(void);
 
 /* Routines for boot-time TPM PCR measurement as well as submitting an event log entry about it. The latter
  * can be done with two different event log record types. For old stuff we use EV_IPL (which is legacy, and
@@ -28,6 +29,10 @@ static inline bool tpm_present(void) {
         return false;
 }
 
+static inline uint32_t tpm_get_active_pcr_banks(void) {
+        return 0;
+}
+
 static inline EFI_STATUS tpm_log_ipl_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t buffer_size, const char16_t *description, bool *ret_measured) {
         if (ret_measured)
                 *ret_measured = false;
index a85288178cd0d400b00118ee087d3973ecfdd767..e99c01a4ad1f75ce4da245132fc44d6b08ce55a3 100644 (file)
@@ -79,7 +79,9 @@ struct EFI_TCG2_PROTOCOL {
                         uint64_t DataToHashLen,
                         EFI_TCG2_EVENT *EfiTcgEvent);
         void *SubmitCommand;
-        void *GetActivePcrBanks;
+        EFI_STATUS (EFIAPI *GetActivePcrBanks)(
+                        EFI_TCG2_PROTOCOL *This,
+                        uint32_t *ActivePcrBanks);
         void *SetActivePcrBanks;
         void *GetResultOfSetActivePcrBanks;
 };
index 9715c91d2690a90d940fedd4f0f322c13fe87a62..d8609d8ffd684f02d92dc1c510e18b74500a8e30 100644 (file)
@@ -413,6 +413,7 @@ int verb_status(int argc, char *argv[], void *userdata) {
                         { EFI_LOADER_FEATURE_REPORT_URL,              "Loader reports network boot URL"       },
                         { EFI_LOADER_FEATURE_TYPE1_UKI,               "Support Type #1 uki field"             },
                         { EFI_LOADER_FEATURE_TYPE1_UKI_URL,           "Support Type #1 uki-url field"         },
+                        { EFI_LOADER_FEATURE_TPM2_ACTIVE_PCR_BANKS,   "Loader reports TPM2 active PCR banks"  },
                 };
                 static const struct {
                         uint64_t flag;
index d556e7718f8552ac44d62975cac8d79cfeacef8f..0f4670a37fd01c3efc7d3ea6628ec49bcd482d76 100644 (file)
@@ -27,6 +27,7 @@
 #define EFI_LOADER_FEATURE_REPORT_URL              (UINT64_C(1) << 15)
 #define EFI_LOADER_FEATURE_TYPE1_UKI               (UINT64_C(1) << 16)
 #define EFI_LOADER_FEATURE_TYPE1_UKI_URL           (UINT64_C(1) << 17)
+#define EFI_LOADER_FEATURE_TPM2_ACTIVE_PCR_BANKS   (UINT64_C(1) << 18)
 
 /* Features of the stub, i.e. systemd-stub */
 #define EFI_STUB_FEATURE_REPORT_BOOT_PARTITION     (UINT64_C(1) << 0)
index 11165cdb9ac16d0b79e45e042db0a8c0d95ccee7..73c09f66f6d9915ecfba84f0254837dd38b3843c 100644 (file)
@@ -10,6 +10,7 @@
 #include "fd-util.h"
 #include "fileio.h"
 #include "log.h"
+#include "parse-util.h"
 #include "sort-util.h"
 #include "stat-util.h"
 #include "stdio-util.h"
@@ -516,6 +517,27 @@ int efi_get_boot_options(uint16_t **ret_options) {
 #endif
 }
 
+#if ENABLE_EFI
+static int loader_has_tpm2(void) {
+        _cleanup_free_ char *active_pcr_banks = NULL;
+        uint32_t active_pcr_banks_value;
+        int r;
+
+        r = efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderTpm2ActivePcrBanks"), &active_pcr_banks);
+        if (r < 0) {
+                if (r != -ENOENT)
+                        log_debug_errno(r, "Failed to read LoaderTpm2ActivePcrBanks variable: %m");
+                return r;
+        }
+
+        r = safe_atou32_full(active_pcr_banks, 16, &active_pcr_banks_value);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to parse LoaderTpm2ActivePcrBanks variable: %m");
+
+        return active_pcr_banks_value != 0;
+}
+#endif
+
 bool efi_has_tpm2(void) {
 #if ENABLE_EFI
         static int cache = -1;
@@ -530,9 +552,17 @@ bool efi_has_tpm2(void) {
         if (!is_efi_boot())
                 return (cache = false);
 
+        /* Secondly, check if the loader told us, as that is the most accurate source of information
+         * regarding the firmware's setup */
+        r = loader_has_tpm2();
+        if (r >= 0)
+                return (cache = r);
+
         /* Then, check if the ACPI table "TPM2" exists, which is the TPM2 event log table, see:
          * https://trustedcomputinggroup.org/wp-content/uploads/TCG_ACPIGeneralSpecification_v1.20_r8.pdf
-         * This table exists whenever the firmware knows ACPI and is hooked up to TPM2. */
+         * This table exists whenever the firmware knows ACPI and is hooked up to TPM2.
+         * Note that in some cases, for example with EDK2 2025.2 with the default arm64 config, this ACPI
+         * table is present even if TPM2 support is not enabled in the firmware. */
         if (access("/sys/firmware/acpi/tables/TPM2", F_OK) >= 0)
                 return (cache = true);
         if (errno != ENOENT)