From d1c36f11d55a0f91f9bf09ada699c346faaaf005 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 2 Jun 2025 13:13:34 +0200 Subject: [PATCH 01/10] tpm2-util: correct copy/pasted error message --- src/shared/tpm2-util.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index 710ed4e81b..d4e81083d1 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -2117,7 +2117,7 @@ int tpm2_create_primary( /* creationHash= */ NULL, /* creationTicket= */ NULL); if (rc == TPM2_RC_BAD_AUTH) - return log_debug_errno(SYNTHETIC_ERRNO(EDEADLK), "Authorization failure while attempting to enroll SRK into TPM."); + return log_debug_errno(SYNTHETIC_ERRNO(EDEADLK), "Authorization failure while attempting to create primary key."); if (rc != TSS2_RC_SUCCESS) return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to generate primary key in TPM: %s", From ccd2bf48b2d736e335f217d5621a817aa1b6beeb Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 2 Jun 2025 11:20:52 +0200 Subject: [PATCH 02/10] pcrlock: add "is-supported" verb that checks if the local TPM supports the commands we need for pcrlock systemd-pcrlock requires support for the PolicyAuthorizeNV command, which is not implemented in the first TPM2 releases. We also strictly require SHA-256 support. Hence add a tool for checking for both of these. This is a tighter version of "systemd-analyze has-tpm2", that checks for the precise feature that systemd-pcrlock needs, on top of basic TPM2 functionality. Fixes: #37607 --- man/systemd-pcrlock.xml | 31 ++++++++++++++ src/pcrlock/pcrlock.c | 65 +++++++++++++++++++++++++++++- src/shared/tpm2-util.h | 15 +++++-- test/units/TEST-70-TPM2.pcrlock.sh | 1 + 4 files changed, 107 insertions(+), 5 deletions(-) diff --git a/man/systemd-pcrlock.xml b/man/systemd-pcrlock.xml index 89b989ea2d..de77e7da14 100644 --- a/man/systemd-pcrlock.xml +++ b/man/systemd-pcrlock.xml @@ -183,6 +183,28 @@ + + is-supported + + Checks if the local TPM2 supports all functionality for + systemd-pcrlock to work correctly. This does similar tests as + systemd-analyze has-tpm2, but also checks for supported of the TPM2 operations + requires by systemd-pcrlock. Outputs one of no, + partial (in case some parts of TPM2 support are avaialable in hardware, firmware, + OS, but not complete), obsolete (if TPM2 support is available in hardware, + firmware and OS, but the operations required for systemd-pcrlock are missing), + yes. Returns an exit status of zero if full support is available, otherwise + non-zero. + + If combined with , suppresses the output of the string. + + Currently, this checks for support for the PolicAuthorizeNV TPM2 command, as well as for + support of the SHA-256 hash algorithm. + + + + + lock-firmware-code unlock-firmware-code @@ -562,6 +584,15 @@ + + + + If specified suppresses output when invoked for + is-supported. + + + + diff --git a/src/pcrlock/pcrlock.c b/src/pcrlock/pcrlock.c index 02860e6ca5..d3b72cc664 100644 --- a/src/pcrlock/pcrlock.c +++ b/src/pcrlock/pcrlock.c @@ -85,6 +85,7 @@ static bool arg_force = false; static BootEntryTokenType arg_entry_token_type = BOOT_ENTRY_TOKEN_AUTO; static char *arg_entry_token = NULL; static bool arg_varlink = false; +static bool arg_quiet = false; STATIC_DESTRUCTOR_REGISTER(arg_components, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_pcrlock_path, freep); @@ -4938,6 +4939,60 @@ static int verb_remove_policy(int argc, char *argv[], void *userdata) { return remove_policy(); } +static int test_tpm2_support_pcrlock(Tpm2Support *ret) { + int r; + + assert(ret); + + /* First check basic support */ + Tpm2Support s = tpm2_support(); + + /* If basic support is available, let's also check the things we need for systemd-pcrlock */ + if (s == TPM2_SUPPORT_FULL) { + _cleanup_(tpm2_context_unrefp) Tpm2Context *tc = NULL; + r = tpm2_context_new_or_warn(/* device= */ NULL, &tc); + if (r < 0) + return r; + + /* We strictly need TPM2_CC_PolicyAuthorizeNV for systemd-pcrlock to work */ + SET_FLAG(s, TPM2_SUPPORT_AUTHORIZE_NV, tpm2_supports_command(tc, TPM2_CC_PolicyAuthorizeNV)); + + log_debug("PolicyAuthorizeNV supported: %s", yes_no(FLAGS_SET(s, TPM2_SUPPORT_AUTHORIZE_NV))); + + /* We also strictly need SHA-256 to work */ + SET_FLAG(s, TPM2_SUPPORT_SHA256, tpm2_supports_alg(tc, TPM2_ALG_SHA256)); + + log_debug("SHA-256 supported: %s", yes_no(FLAGS_SET(s, TPM2_SUPPORT_SHA256))); + } + + *ret = s; + return 0; +} + +static int verb_is_supported(int argc, char *argv[], void *userdata) { + int r; + + Tpm2Support s; + r = test_tpm2_support_pcrlock(&s); + if (r < 0) + return r; + + if (!arg_quiet) { + if (s == (TPM2_SUPPORT_FULL|TPM2_SUPPORT_API_PCRLOCK)) + printf("%syes%s\n", ansi_green(), ansi_normal()); + else if (FLAGS_SET(s, TPM2_SUPPORT_FULL)) + printf("%sobsolete%s\n", ansi_red(), ansi_normal()); + else if (s == TPM2_SUPPORT_NONE) + printf("%sno%s\n", ansi_red(), ansi_normal()); + else + printf("%spartial%s\n", ansi_yellow(), ansi_normal()); + } + + assert_cc(TPM2_SUPPORT_API_PCRLOCK <= 255); /* make sure this is safe to use as process exit status */ + + return ~s & TPM2_SUPPORT_API_PCRLOCK; +} + static int help(int argc, char *argv[], void *userdata) { _cleanup_free_ char *link = NULL; int r; @@ -4955,6 +5010,7 @@ static int help(int argc, char *argv[], void *userdata) { " predict Predict PCR values\n" " make-policy Predict PCR values and generate TPM2 policy from it\n" " remove-policy Remove TPM2 policy\n" + " is-supported Tests if TPM2 supports necessary features\n" "\n%3$sProtections:%4$s\n" " lock-firmware-code Generate a .pcrlock file from current firmware code\n" " unlock-firmware-code Remove .pcrlock file for firmware code\n" @@ -4997,6 +5053,7 @@ static int help(int argc, char *argv[], void *userdata) { " --force Write policy even if it matches existing policy\n" " --entry-token=machine-id|os-id|os-image-id|auto|literal:…\n" " Boot entry token to use for this installation\n" + " -q --quiet Suppress unnecessary output\n" "\nSee the %2$s for details.\n", program_invocation_short_name, link, @@ -5040,6 +5097,7 @@ static int parse_argv(int argc, char *argv[]) { { "policy", required_argument, NULL, ARG_POLICY }, { "force", no_argument, NULL, ARG_FORCE }, { "entry-token", required_argument, NULL, ARG_ENTRY_TOKEN }, + { "quiet", no_argument, NULL, 'q' }, {} }; @@ -5049,7 +5107,7 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + while ((c = getopt_long(argc, argv, "hq", options, NULL)) >= 0) switch (c) { case 'h': @@ -5193,6 +5251,10 @@ static int parse_argv(int argc, char *argv[]) { return r; break; + case 'q': + arg_quiet = true; + break; + case '?': return -EINVAL; @@ -5257,6 +5319,7 @@ static int pcrlock_main(int argc, char *argv[]) { { "unlock-raw", VERB_ANY, 1, 0, verb_unlock_simple }, { "make-policy", VERB_ANY, 1, 0, verb_make_policy }, { "remove-policy", VERB_ANY, 1, 0, verb_remove_policy }, + { "is-supported", VERB_ANY, 1, 0, verb_is_supported }, {} }; diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index 4da7e09864..d81c362c44 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -450,11 +450,18 @@ typedef enum Tpm2Support { TPM2_SUPPORT_LIBRARIES = 1 << 4, /* we can dlopen the tpm2 libraries */ TPM2_SUPPORT_API = TPM2_SUPPORT_FIRMWARE|TPM2_SUPPORT_DRIVER|TPM2_SUPPORT_SYSTEM|TPM2_SUPPORT_SUBSYSTEM|TPM2_SUPPORT_LIBRARIES, - /* Flags below are not returned by systemd-analyze has-tpm2 as exit status. */ - TPM2_SUPPORT_LIBTSS2_ESYS = 1 << 5, /* we can dlopen libtss2-esys.so.0 */ - TPM2_SUPPORT_LIBTSS2_RC = 1 << 6, /* we can dlopen libtss2-rc.so.0 */ - TPM2_SUPPORT_LIBTSS2_MU = 1 << 7, /* we can dlopen libtss2-mu.so.0 */ + /* Flags below are used by pcrlock, to indicate hardware specific features. It's not used by systemd-analyze has-tpm2. */ + TPM2_SUPPORT_AUTHORIZE_NV = 1 << 5, /* chip supports PolicyAuthorizeNV */ + TPM2_SUPPORT_SHA256 = 1 << 6, /* chip supports SHA-256 */ + TPM2_SUPPORT_API_PCRLOCK = TPM2_SUPPORT_API | TPM2_SUPPORT_AUTHORIZE_NV | TPM2_SUPPORT_SHA256, + + /* Flags below are not returned by systemd-analyze has-tpm2 nor by systemd-pcrlock as exit status. */ + TPM2_SUPPORT_LIBTSS2_ESYS = 1 << 7, /* we can dlopen libtss2-esys.so.0 */ + TPM2_SUPPORT_LIBTSS2_RC = 1 << 8, /* we can dlopen libtss2-rc.so.0 */ + TPM2_SUPPORT_LIBTSS2_MU = 1 << 9, /* we can dlopen libtss2-mu.so.0 */ TPM2_SUPPORT_LIBTSS2_ALL = TPM2_SUPPORT_LIBTSS2_ESYS|TPM2_SUPPORT_LIBTSS2_RC|TPM2_SUPPORT_LIBTSS2_MU, + + /* Combined flags for generic (i.e. not tool-specific) support */ TPM2_SUPPORT_FULL = TPM2_SUPPORT_API|TPM2_SUPPORT_LIBTSS2_ALL, } Tpm2Support; diff --git a/test/units/TEST-70-TPM2.pcrlock.sh b/test/units/TEST-70-TPM2.pcrlock.sh index 38274991db..38e082bb88 100755 --- a/test/units/TEST-70-TPM2.pcrlock.sh +++ b/test/units/TEST-70-TPM2.pcrlock.sh @@ -52,6 +52,7 @@ set -e test $ret -eq 1 SYSTEMD_COLORS=256 "$SD_PCRLOCK" +"$SD_PCRLOCK" is-supported "$SD_PCRLOCK" cel --no-pager --json=pretty "$SD_PCRLOCK" log --pcr="$PCRS" "$SD_PCRLOCK" log --json=pretty --pcr="$PCRS" From 1144f07dc69cc04138d90000b1782c4a2b27cd94 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 2 Jun 2025 11:49:33 +0200 Subject: [PATCH 03/10] measure: generate a more useful error in case SHA1 support is disabled by distro Fixes: #36743 --- man/systemd-measure.xml | 4 ++++ src/measure/measure-tool.c | 4 +++- src/shared/openssl-util.c | 11 +++++++++-- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/man/systemd-measure.xml b/man/systemd-measure.xml index 014769f921..c144eda064 100644 --- a/man/systemd-measure.xml +++ b/man/systemd-measure.xml @@ -173,6 +173,10 @@ specified, defaults to the four banks sha1, sha256, sha384, sha512. + Note that some operating systems disable support for SHA1-based signatures, in which case this + operation will fail. Please consult your OS' documentation for details on how to override the OS + security policy around this. + diff --git a/src/measure/measure-tool.c b/src/measure/measure-tool.c index b7cb216c8f..0d5ceb9faf 100644 --- a/src/measure/measure-tool.c +++ b/src/measure/measure-tool.c @@ -952,8 +952,10 @@ static int build_policy_digest(bool sign) { size_t ss = 0; if (privkey) { r = digest_and_sign(p->md, privkey, pcr_policy_digest.buffer, pcr_policy_digest.size, &sig, &ss); + if (r == -EADDRNOTAVAIL) + return log_error_errno(r, "Hash algorithm '%s' not available while signing. (Maybe OS security policy disables this algorithm?)", EVP_MD_name(p->md)); if (r < 0) - return log_error_errno(r, "Failed to sign PCR policy: %m"); + return log_error_errno(r, "Failed to sign PCR policy with hash algorithm '%s': %m", EVP_MD_name(p->md)); } _cleanup_free_ void *pubkey_fp = NULL; diff --git a/src/shared/openssl-util.c b/src/shared/openssl-util.c index 656278b52b..180d6f202f 100644 --- a/src/shared/openssl-util.c +++ b/src/shared/openssl-util.c @@ -1082,6 +1082,8 @@ int digest_and_sign( const void *data, size_t size, void **ret, size_t *ret_size) { + int r; + assert(privkey); assert(ret); assert(ret_size); @@ -1099,8 +1101,13 @@ int digest_and_sign( if (!mdctx) return log_openssl_errors("Failed to create new EVP_MD_CTX"); - if (EVP_DigestSignInit(mdctx, NULL, md, NULL, privkey) != 1) - return log_openssl_errors("Failed to initialize signature context"); + if (EVP_DigestSignInit(mdctx, NULL, md, NULL, privkey) != 1) { + /* Distro security policies often disable support for SHA-1. Let's return a recognizable + * error for that case. */ + bool invalid_digest = ERR_GET_REASON(ERR_peek_last_error()) == EVP_R_INVALID_DIGEST; + r = log_openssl_errors("Failed to initialize signature context"); + return invalid_digest ? -EADDRNOTAVAIL : r; +} /* Determine signature size */ size_t ss; From 3ab04fbc4c5d7009eade670b1ea48c6982b9f372 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 2 Jun 2025 12:23:51 +0200 Subject: [PATCH 04/10] pcrlock: make error messages more useful if PCR state doesn't match policy during PolicyOR Fixes: #35187 --- src/pcrlock/pcrlock.c | 2 +- src/shared/tpm2-util.c | 26 +++++++++++++++++++++----- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/pcrlock/pcrlock.c b/src/pcrlock/pcrlock.c index d3b72cc664..652692ea81 100644 --- a/src/pcrlock/pcrlock.c +++ b/src/pcrlock/pcrlock.c @@ -4632,7 +4632,7 @@ static int make_policy(bool force, RecoveryPinMode recovery_pin_mode) { &old_policy.prediction, old_policy.algorithm); if (r < 0) - return log_error_errno(r, "Failed to submit super PCR policy: %m"); + return r; r = tpm2_policy_authorize_nv( tc, diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index d4e81083d1..103cf50581 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -3705,8 +3705,11 @@ int tpm2_policy_or( ESYS_TR_NONE, ESYS_TR_NONE, &hash_list); + if ((rc & ~(TPM2_RC_N_MASK|TPM2_RC_P)) == TPM2_RC_VALUE) /* Return a recognizable error if none of the OR branches matched */ + return log_debug_errno(SYNTHETIC_ERRNO(ENOANO), + "None of the PolicyOR branches matched the current policy state."); if (rc != TSS2_RC_SUCCESS) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to add OR policy to TPM: %s", sym_Tss2_RC_Decode(rc)); @@ -6780,8 +6783,17 @@ int tpm2_policy_super_pcr( session, &pcr_selection, ¤t_policy_digest); + if (r == -EUCLEAN) { + _cleanup_free_ char *j = NULL; + + for (uint32_t pcr = 0; pcr < TPM2_PCRS_MAX; pcr++) + if (single_value_pcrs & (UINT32_C(1) << pcr)) + (void) strextendf_with_separator(&j, ", ", "%" PRIu32, pcr); + + return log_error_errno(r, "Combined value for PCR(s) %s encoded in policy does not match the current TPM state. Either the system has been tempered with or the provided policy is incorrect.", strna(j)); + } if (r < 0) - return r; + return log_error_errno(r, "Failed to submit PCR policy to TPM: %m"); previous_policy_digest = *current_policy_digest; } @@ -6810,8 +6822,10 @@ int tpm2_policy_super_pcr( session, &pcr_selection, ¤t_policy_digest); + if (r == -EUCLEAN) + return log_error_errno(r, "Value for PCR %" PRIu32 " encoded in policy does not match the current TPM state. Either the system has been tempered with or the provided policy is incorrect.", pcr); if (r < 0) - return r; + return log_error_errno(r, "Failed to submit PCR policy to TPM: %m"); _cleanup_free_ TPM2B_DIGEST *branches = NULL; branches = new0(TPM2B_DIGEST, n_branches); @@ -6836,7 +6850,7 @@ int tpm2_policy_super_pcr( /* n_pcr_values= */ 1, &pcr_policy_digest); if (r < 0) - return r; + return log_error_errno(r, "Failed to calculate PolicyPCR: %m"); branches[i++] = pcr_policy_digest; } @@ -6850,8 +6864,10 @@ int tpm2_policy_super_pcr( branches, n_branches, ¤t_policy_digest); + if (r == -ENOANO) + return log_error_errno(r, "None of the alternative values for PCR %" PRIu32 " encoded in policy match the current TPM state. Either the system has been tempered with or the provided policy is incorrect.", pcr); if (r < 0) - return r; + return log_error_errno(r, "Failed to submit OR policy to TPM: %m"); previous_policy_digest = *current_policy_digest; } From c7ce6fd30e8d67075081d69ec27a62d26d750d60 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 2 Jun 2025 12:32:33 +0200 Subject: [PATCH 05/10] pcrlock: also refuse lacking SHA-256 support early when creating policy This will fail eventually, but let's be explicit early here. --- src/pcrlock/pcrlock.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pcrlock/pcrlock.c b/src/pcrlock/pcrlock.c index 652692ea81..4232d99e0c 100644 --- a/src/pcrlock/pcrlock.c +++ b/src/pcrlock/pcrlock.c @@ -4511,6 +4511,8 @@ static int make_policy(bool force, RecoveryPinMode recovery_pin_mode) { if (!tpm2_supports_command(tc, TPM2_CC_PolicyAuthorizeNV)) return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 does not support PolicyAuthorizeNV command, refusing."); + if (!tpm2_supports_alg(tc, TPM2_ALG_SHA256)) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 does not support SHA-256 hash algorithm, refusing."); _cleanup_(tpm2_handle_freep) Tpm2Handle *srk_handle = NULL; From f31a87331f0703896708416dbb19aed4ed9b7fe9 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 2 Jun 2025 13:12:28 +0200 Subject: [PATCH 06/10] tpm2-util: also generate recognizable error if PolicyAuthorizeNV fails --- src/shared/tpm2-util.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index 103cf50581..7e2a84bfee 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -3661,6 +3661,11 @@ int tpm2_policy_authorize_nv( ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE); + if ((rc & ~(TPM2_RC_N_MASK|TPM2_RC_P)) == TPM2_RC_VALUE) /* Return a recognizable error if the policy + * in the NV index does not match what we + * just put together */ + return log_debug_errno(SYNTHETIC_ERRNO(EREMCHG), + "Submitted policy does not match policy stored in PolicyAuthorizeNV."); if (rc != TSS2_RC_SUCCESS) return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to add AuthorizeNV policy to TPM: %s", From 8335765bb8080d5ab86ad1b43acfd25c6a022d65 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 2 Jun 2025 13:13:59 +0200 Subject: [PATCH 07/10] tpm2-util: add a brief summary of tpm2_unseal() return values in a comment --- src/shared/tpm2-util.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index 7e2a84bfee..6a6756c990 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -5508,6 +5508,20 @@ int tpm2_unseal(Tpm2Context *c, const struct iovec *srk, struct iovec *ret_secret) { + /* Returns the following errors: + * + * -EREMOTE → blob is from a different TPM + * -EDEADLK → couldn't create primary key because authorization failure + * -ENOLCK → TPM is in dictionary lockout mode + * -EREMCHG → submitted policy doesn't match NV index stored policy (in case of PolicyAuthorizeNV) + * -ENOANO → none of the PolicyOR branches of a policy matched current state + * -EUCLEAN → PCR state doesn't match expectations + * -EPERM → stored policy does not match TPM state + * -ENOTRECOVERABLE → all other kinds of TPM errors + * + * Of these all four of EREMCHG, ENOANO, EUCLEAN, EPERM can all mean that PCR state is not matching + * expectations. */ + TSS2_RC rc; int r; From 1e08f92250178c075a6813f42298a0ab1823a6cc Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 2 Jun 2025 13:13:17 +0200 Subject: [PATCH 08/10] tpm2: add a clear error message for all errors that suggest a PCR violation Fixes: #35187 --- src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c | 2 ++ src/shared/creds-util.c | 2 ++ src/shared/cryptsetup-tpm2.c | 6 +++++- src/shared/tpm2-util.h | 3 +++ 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c index 956487de77..708d8a7645 100644 --- a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c +++ b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c @@ -111,6 +111,8 @@ int acquire_luks2_key( ret_decrypted_key); if (r == -EREMOTE) return log_error_errno(r, "TPM key integrity check failed. Key enrolled in superblock most likely does not belong to this TPM."); + if (ERRNO_IS_NEG_TPM2_UNSEAL_BAD_PCR(r)) + return log_error_errno(r, "TPM policy does not match current system state. Either system has been tempered with or policy out-of-date: %m"); if (r < 0) return log_error_errno(r, "Failed to unseal secret using TPM2: %m"); diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c index 93bf121ccf..384b19293a 100644 --- a/src/shared/creds-util.c +++ b/src/shared/creds-util.c @@ -1366,6 +1366,8 @@ int decrypt_credential_and_warn( &tpm2_key); if (r == -EREMOTE) return log_error_errno(r, "TPM key integrity check failed. Key enrolled in superblock most likely does not belong to this TPM."); + if (ERRNO_IS_NEG_TPM2_UNSEAL_BAD_PCR(r)) + return log_error_errno(r, "TPM policy does not match current system state. Either system has been tempered with or policy out-of-date: %m"); if (r < 0) return log_error_errno(r, "Failed to unseal secret using TPM2: %m"); #else diff --git a/src/shared/cryptsetup-tpm2.c b/src/shared/cryptsetup-tpm2.c index 7fc0bf9fdd..b62b0f8b33 100644 --- a/src/shared/cryptsetup-tpm2.c +++ b/src/shared/cryptsetup-tpm2.c @@ -173,6 +173,8 @@ int acquire_tpm2_key( ret_decrypted_key); if (r == -EREMOTE) return log_error_errno(r, "TPM key integrity check failed. Key enrolled in superblock most likely does not belong to this TPM."); + if (ERRNO_IS_NEG_TPM2_UNSEAL_BAD_PCR(r)) + return log_error_errno(r, "TPM policy does not match current system state. Either system has been tempered with or policy out-of-date: %m"); if (r < 0) return log_error_errno(r, "Failed to unseal secret using TPM2: %m"); @@ -223,12 +225,14 @@ int acquire_tpm2_key( ret_decrypted_key); if (r == -EREMOTE) return log_error_errno(r, "TPM key integrity check failed. Key enrolled in superblock most likely does not belong to this TPM."); + if (ERRNO_IS_NEG_TPM2_UNSEAL_BAD_PCR(r)) + return log_error_errno(r, "TPM policy does not match current system state. Either system has been tempered with or policy out-of-date: %m"); if (r < 0) { log_error_errno(r, "Failed to unseal secret using TPM2: %m"); /* We get this error in case there is an authentication policy mismatch. This should * not happen, but this avoids confusing behavior, just in case. */ - if (!IN_SET(r, -EPERM, -ENOLCK)) + if (r != -ENOLCK) continue; } diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index d81c362c44..9b6b143619 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -283,6 +283,9 @@ int tpm2_get_or_create_srk(Tpm2Context *c, const Tpm2Handle *session, TPM2B_PUBL int tpm2_seal(Tpm2Context *c, uint32_t seal_key_handle, const TPM2B_DIGEST policy_hash[], size_t n_policy, const char *pin, struct iovec *ret_secret, struct iovec **ret_blobs, size_t *ret_n_blobs, uint16_t *ret_primary_alg, struct iovec *ret_srk); int tpm2_unseal(Tpm2Context *c, uint32_t hash_pcr_mask, uint16_t pcr_bank, const struct iovec *pubkey, uint32_t pubkey_pcr_mask, sd_json_variant *signature, const char *pin, const Tpm2PCRLockPolicy *pcrlock_policy, uint16_t primary_alg, const struct iovec blobs[], size_t n_blobs, const struct iovec known_policy_hash[], size_t n_known_policy_hash, const struct iovec *srk, struct iovec *ret_secret); +/* tpm2_unseal() returns a bunch of different errors for various flavours of PCR issues, let's group them */ +#define ERRNO_IS_NEG_TPM2_UNSEAL_BAD_PCR(r) IN_SET(r, -EREMCHG, -ENOANO, -EUCLEAN, -EPERM) + #if HAVE_OPENSSL int tpm2_tpm2b_public_to_openssl_pkey(const TPM2B_PUBLIC *public, EVP_PKEY **ret); int tpm2_tpm2b_public_from_openssl_pkey(const EVP_PKEY *pkey, TPM2B_PUBLIC *ret); From 8756482804b53e4fc8ead270843da4856e9dce82 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 3 Jun 2025 16:11:35 +0200 Subject: [PATCH 09/10] tpm2-util: use BIT_FOREACH() more --- src/shared/tpm2-util.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index 6a6756c990..e7bc86faa1 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -6671,14 +6671,11 @@ int tpm2_calculate_policy_super_pcr( /* First we look for all PCRs that have exactly one allowed hash value, and generate a single PolicyPCR policy from them */ _cleanup_free_ Tpm2PCRValue *single_values = NULL; size_t n_single_values = 0; - for (uint32_t pcr = 0; pcr < TPM2_PCRS_MAX; pcr++) { - if (!BIT_SET(prediction->pcrs, pcr)) - continue; - + BIT_FOREACH(pcr, prediction->pcrs) { if (ordered_set_size(prediction->results[pcr]) != 1) continue; - log_debug("Including PCR %" PRIu32 " in single value PolicyPCR expression", pcr); + log_debug("Including PCR %i in single value PolicyPCR expression", pcr); Tpm2PCRPredictionResult *banks = ASSERT_PTR(ordered_set_first(prediction->results[pcr])); From 9c335d2458075cec9f9230af64b28c8905fb8362 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 3 Jun 2025 16:11:57 +0200 Subject: [PATCH 10/10] tpm2-util: guarantee that verb_has_tpm2_generic() returns usable exit status --- src/shared/tpm2-util.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index e7bc86faa1..1c1ce441a2 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -7842,6 +7842,8 @@ int verb_has_tpm2_generic(bool quiet) { print_field(" ", "libtss2-mu.so.0", FLAGS_SET(s, TPM2_SUPPORT_LIBTSS2_MU)); } + assert_cc(TPM2_SUPPORT_API <= 255); /* make sure this is safe to use as process exit status */ + /* Return inverted bit flags. So that TPM2_SUPPORT_FULL becomes EXIT_SUCCESS and the other values * become some reasonable values 1…7. i.e. the flags we return here tell what is missing rather than * what is there, acknowledging the fact that for process exit statuses it is customary to return