Skip to content

Commit

Permalink
cryptsetup: retry TPM2 unseal operation if it fails with TPM2_RC_PCR_…
Browse files Browse the repository at this point in the history
…CHANGED

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 systemd#24906

(cherry picked from commit 0254e4d)

[antonio.feijoo: adjust context]
[antonio.feijoo: fixes bsc#1204944]
  • Loading branch information
aafeijoo-suse authored and fbuihuu committed Dec 13, 2022
1 parent ab0f962 commit 79eb37a
Showing 1 changed file with 34 additions and 23 deletions.
57 changes: 34 additions & 23 deletions src/shared/tpm2-util.c
Expand Up @@ -610,6 +610,8 @@ int tpm2_seal(
return r;
}

#define RETRY_UNSEAL_MAX 30u

int tpm2_unseal(
const char *device,
uint32_t pcr_mask,
Expand Down Expand Up @@ -672,17 +674,6 @@ int tpm2_unseal(
if (r < 0)
return r;

r = tpm2_make_pcr_session(c.esys_context, pcr_mask, &session, &policy_digest);
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.");

r = tpm2_make_primary(c.esys_context, &primary);
if (r < 0)
return r;
Expand All @@ -704,19 +695,39 @@ int tpm2_unseal(
goto finish;
}

log_debug("Unsealing HMAC key.");
for (unsigned i = RETRY_UNSEAL_MAX;; i--) {
r = tpm2_make_pcr_session(c.esys_context, pcr_mask, &session, &policy_digest);
if (r < 0)
goto finish;

rc = sym_Esys_Unseal(
c.esys_context,
hmac_key,
session,
ESYS_TR_NONE,
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;
/* 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.");

rc = sym_Esys_Unseal(
c.esys_context,
hmac_key,
session,
ESYS_TR_NONE,
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 = 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);
Expand Down

0 comments on commit 79eb37a

Please sign in to comment.