From 7f8a43eff0d800f21e9f873010637d08da13da67 Mon Sep 17 00:00:00 2001 From: Antonio Alvarez Feijoo Date: Wed, 7 Dec 2022 16:52:27 +0100 Subject: [PATCH] cryptsetup: retry TPM2 unseal operation if it fails with TPM2_RC_PCR_CHANGED MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Quoting "Trusted Platform Module Library - Part 3: Commands (Rev. 01.59)": "pcrUpdateCounter – this parameter is updated by TPM2_PolicyPCR(). This value may only be set once during a policy. Each time TPM2_PolicyPCR() executes, it checks to see if policySession->pcrUpdateCounter has its default state, indicating that this is the first TPM2_PolicyPCR(). If it has its default value, then policySession->pcrUpdateCounter is set to the current value of pcrUpdateCounter. If policySession->pcrUpdateCounter does not have its default value and its value is not the same as pcrUpdateCounter, the TPM shall return TPM_RC_PCR_CHANGED. If this parameter and pcrUpdateCounter are not the same, it indicates that PCR have changed since checked by the previous TPM2_PolicyPCR(). Since they have changed, the previous PCR validation is no longer valid." The TPM will return TPM_RC_PCR_CHANGED if any PCR value changes (no matter which) between validating the PCRs binded to the enrollment and unsealing the HMAC key, so this patch adds a retry mechanism in this case. Fixes #24906 (cherry picked from commit 0254e4d66af7aa893b31b2326335ded5dde48b51) Related: RHEL-16182 --- src/shared/tpm2-util.c | 81 ++++++++++++++++++++++++------------------ 1 file changed, 46 insertions(+), 35 deletions(-) diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index aca7f22e54..d1a4e9cd11 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -1608,6 +1608,8 @@ finish: return r; } +#define RETRY_UNSEAL_MAX 30u + int tpm2_unseal(const char *device, uint32_t hash_pcr_mask, uint16_t pcr_bank, @@ -1719,44 +1721,53 @@ int tpm2_unseal(const char *device, if (r < 0) goto finish; - r = tpm2_make_policy_session( - c.esys_context, - primary, - hmac_session, - TPM2_SE_POLICY, - hash_pcr_mask, - pcr_bank, - pubkey, pubkey_size, - pubkey_pcr_mask, - signature, - !!pin, - &session, - &policy_digest, - /* ret_pcr_bank= */ NULL); - if (r < 0) - goto finish; + for (unsigned i = RETRY_UNSEAL_MAX;; i--) { + r = tpm2_make_policy_session( + c.esys_context, + primary, + hmac_session, + TPM2_SE_POLICY, + hash_pcr_mask, + pcr_bank, + pubkey, pubkey_size, + pubkey_pcr_mask, + signature, + !!pin, + &session, + &policy_digest, + /* ret_pcr_bank= */ NULL); + if (r < 0) + goto finish; - /* If we know the policy hash to expect, and it doesn't match, we can shortcut things here, and not - * wait until the TPM2 tells us to go away. */ - if (known_policy_hash_size > 0 && - memcmp_nn(policy_digest->buffer, policy_digest->size, known_policy_hash, known_policy_hash_size) != 0) - return log_error_errno(SYNTHETIC_ERRNO(EPERM), - "Current policy digest does not match stored policy digest, cancelling " - "TPM2 authentication attempt."); + /* If we know the policy hash to expect, and it doesn't match, we can shortcut things here, and not + * wait until the TPM2 tells us to go away. */ + if (known_policy_hash_size > 0 && + memcmp_nn(policy_digest->buffer, policy_digest->size, known_policy_hash, known_policy_hash_size) != 0) + return log_error_errno(SYNTHETIC_ERRNO(EPERM), + "Current policy digest does not match stored policy digest, cancelling " + "TPM2 authentication attempt."); - log_debug("Unsealing HMAC key."); + log_debug("Unsealing HMAC key."); - rc = sym_Esys_Unseal( - c.esys_context, - hmac_key, - session, - hmac_session, /* use HMAC session to enable parameter encryption */ - ESYS_TR_NONE, - &unsealed); - if (rc != TSS2_RC_SUCCESS) { - r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), - "Failed to unseal HMAC key in TPM: %s", sym_Tss2_RC_Decode(rc)); - goto finish; + rc = sym_Esys_Unseal( + c.esys_context, + hmac_key, + session, + hmac_session, /* use HMAC session to enable parameter encryption */ + ESYS_TR_NONE, + &unsealed); + if (rc == TPM2_RC_PCR_CHANGED && i > 0) { + log_debug("A PCR value changed during the TPM2 policy session, restarting HMAC key unsealing (%u tries left).", i); + session = tpm2_flush_context_verbose(c.esys_context, session); + continue; + } + if (rc != TSS2_RC_SUCCESS) { + r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to unseal HMAC key in TPM: %s", sym_Tss2_RC_Decode(rc)); + goto finish; + } + + break; } secret = memdup(unsealed->buffer, unsealed->size);