]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
cryptsetup: retry TPM2 unseal operation if it fails with TPM2_RC_PCR_CHANGED
authorAntonio Alvarez Feijoo <antonio.feijoo@suse.com>
Wed, 7 Dec 2022 15:52:27 +0000 (16:52 +0100)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Thu, 8 Dec 2022 09:37:28 +0000 (10:37 +0100)
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

src/shared/tpm2-util.c

index 327caa439f0b3a71052ca5d93056cb0ef25c6bed..ba8dfb041d8b07ffb2e177b19cc6326c8359b159 100644 (file)
@@ -1565,6 +1565,8 @@ finish:
         return r;
 }
 
+#define RETRY_UNSEAL_MAX 30u
+
 int tpm2_unseal(const char *device,
                 uint32_t hash_pcr_mask,
                 uint16_t pcr_bank,
@@ -1676,44 +1678,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);