diff --git a/man/systemd-boot.xml b/man/systemd-boot.xml
index b6898ea8f0..52aa582c30 100644
--- a/man/systemd-boot.xml
+++ b/man/systemd-boot.xml
@@ -527,6 +527,18 @@
+
+ LoaderTpm2ActivePcrBanks
+
+ 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 0. Set by the boot loader. Use
+ systemd-analyze1
+ to view this data.
+
+
+
+
LoaderImageIdentifier
diff --git a/man/systemd-stub.xml b/man/systemd-stub.xml
index dd709c2949..fb15861941 100644
--- a/man/systemd-stub.xml
+++ b/man/systemd-stub.xml
@@ -544,6 +544,18 @@
+
+ LoaderTpm2ActivePcrBanks
+
+ 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 0. Set by the boot loader. Use
+ systemd-analyze1
+ to view this data.
+
+
+
+
LoaderImageIdentifier
diff --git a/src/boot/boot.c b/src/boot/boot.c
index 74b2884caf..ec779fa68d 100644
--- a/src/boot/boot.c
+++ b/src/boot/boot.c
@@ -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);
diff --git a/src/boot/export-vars.c b/src/boot/export-vars.c
index 25ca62065a..5c037bdd25 100644
--- a/src/boot/export-vars.c
+++ b/src/boot/export-vars.c
@@ -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);
+ }
}
diff --git a/src/boot/measure.c b/src/boot/measure.c
index 590fed8ec2..5cf6156d62 100644
--- a/src/boot/measure.c
+++ b/src/boot/measure.c
@@ -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;
diff --git a/src/boot/measure.h b/src/boot/measure.h
index 9dde93b94d..4ceed0e0f8 100644
--- a/src/boot/measure.h
+++ b/src/boot/measure.h
@@ -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;
diff --git a/src/boot/proto/tcg.h b/src/boot/proto/tcg.h
index a85288178c..e99c01a4ad 100644
--- a/src/boot/proto/tcg.h
+++ b/src/boot/proto/tcg.h
@@ -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;
};
diff --git a/src/bootctl/bootctl-status.c b/src/bootctl/bootctl-status.c
index 9715c91d26..d8609d8ffd 100644
--- a/src/bootctl/bootctl-status.c
+++ b/src/bootctl/bootctl-status.c
@@ -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;
diff --git a/src/fundamental/efivars-fundamental.h b/src/fundamental/efivars-fundamental.h
index d556e7718f..0f4670a37f 100644
--- a/src/fundamental/efivars-fundamental.h
+++ b/src/fundamental/efivars-fundamental.h
@@ -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)
diff --git a/src/shared/efi-api.c b/src/shared/efi-api.c
index 11165cdb9a..73c09f66f6 100644
--- a/src/shared/efi-api.c
+++ b/src/shared/efi-api.c
@@ -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)