1
0
mirror of https://github.com/systemd/systemd.git synced 2024-11-01 17:51:22 +03:00

tpm2: check if PCR values make sense before using them

Fixes: #20684
This commit is contained in:
Lennart Poettering 2021-09-13 11:23:41 +02:00
parent 2b92a67261
commit 321a9d9ee5
2 changed files with 155 additions and 28 deletions

View File

@ -29,6 +29,7 @@ TSS2_RC (*sym_Esys_GetCapability)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, E
TSS2_RC (*sym_Esys_GetRandom)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, UINT16 bytesRequested, TPM2B_DIGEST **randomBytes) = NULL;
TSS2_RC (*sym_Esys_Initialize)(ESYS_CONTEXT **esys_context, TSS2_TCTI_CONTEXT *tcti, TSS2_ABI_VERSION *abiVersion) = NULL;
TSS2_RC (*sym_Esys_Load)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_PRIVATE *inPrivate, const TPM2B_PUBLIC *inPublic, ESYS_TR *objectHandle) = NULL;
TSS2_RC (*sym_Esys_PCR_Read)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1,ESYS_TR shandle2, ESYS_TR shandle3, const TPML_PCR_SELECTION *pcrSelectionIn, UINT32 *pcrUpdateCounter, TPML_PCR_SELECTION **pcrSelectionOut, TPML_DIGEST **pcrValues);
TSS2_RC (*sym_Esys_PolicyGetDigest)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_DIGEST **policyDigest) = NULL;
TSS2_RC (*sym_Esys_PolicyPCR)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *pcrDigest, const TPML_PCR_SELECTION *pcrs) = NULL;
TSS2_RC (*sym_Esys_StartAuthSession)(ESYS_CONTEXT *esysContext, ESYS_TR tpmKey, ESYS_TR bind, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_NONCE *nonceCaller, TPM2_SE sessionType, const TPMT_SYM_DEF *symmetric, TPMI_ALG_HASH authHash, ESYS_TR *sessionHandle) = NULL;
@ -56,6 +57,7 @@ int dlopen_tpm2(void) {
DLSYM_ARG(Esys_GetRandom),
DLSYM_ARG(Esys_Initialize),
DLSYM_ARG(Esys_Load),
DLSYM_ARG(Esys_PCR_Read),
DLSYM_ARG(Esys_PolicyGetDigest),
DLSYM_ARG(Esys_PolicyPCR),
DLSYM_ARG(Esys_StartAuthSession),
@ -381,16 +383,108 @@ static int tpm2_make_primary(
return 0;
}
static void tpm2_pcr_mask_to_selecion(uint32_t mask, uint16_t bank, TPML_PCR_SELECTION *ret) {
assert(ret);
/* We only do 24bit here, as that's what PC TPMs are supposed to support */
assert(mask <= 0xFFFFFFU);
*ret = (TPML_PCR_SELECTION) {
.count = 1,
.pcrSelections[0].hash = bank,
.pcrSelections[0].sizeofSelect = 3,
.pcrSelections[0].pcrSelect[0] = mask & 0xFF,
.pcrSelections[0].pcrSelect[1] = (mask >> 8) & 0xFF,
.pcrSelections[0].pcrSelect[2] = (mask >> 16) & 0xFF,
};
}
static unsigned find_nth_bit(uint32_t mask, unsigned n) {
uint32_t bit = 1;
assert(n < 32);
/* Returns the bit index of the nth set bit, e.g. mask=0b101001, n=3 → 5 */
for (unsigned i = 0; i < sizeof(mask)*8; i++) {
if (bit & mask) {
if (n == 0)
return i;
n--;
}
bit <<= 1;
}
return UINT_MAX;
}
static int tpm2_pcr_mask_good(
ESYS_CONTEXT *c,
TPMI_ALG_HASH bank,
uint32_t mask) {
_cleanup_(Esys_Freep) TPML_DIGEST *pcr_values = NULL;
TPML_PCR_SELECTION selection;
bool good = false;
TSS2_RC rc;
assert(c);
/* So we have the problem that some systems might have working TPM2 chips, but the firmware doesn't
* actually measure into them, or only into a suboptimal bank. If so, the PCRs should be all zero or
* all 0xFF. Detect that, so that we can warn and maybe pick a better bank. */
tpm2_pcr_mask_to_selecion(mask, bank, &selection);
rc = sym_Esys_PCR_Read(
c,
ESYS_TR_NONE,
ESYS_TR_NONE,
ESYS_TR_NONE,
&selection,
NULL,
NULL,
&pcr_values);
if (rc != TSS2_RC_SUCCESS)
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Failed to read TPM2 PCRs: %s", sym_Tss2_RC_Decode(rc));
/* If at least one of the selected PCR values is something other than all 0x00 or all 0xFF we are happy. */
for (unsigned i = 0; i < pcr_values->count; i++) {
if (DEBUG_LOGGING) {
_cleanup_free_ char *h = NULL;
unsigned j;
h = hexmem(pcr_values->digests[i].buffer, pcr_values->digests[i].size);
j = find_nth_bit(mask, i);
assert(j != UINT_MAX);
log_debug("PCR %u value: %s", j, strna(h));
}
if (!memeqbyte(0x00, pcr_values->digests[i].buffer, pcr_values->digests[i].size) &&
!memeqbyte(0xFF, pcr_values->digests[i].buffer, pcr_values->digests[i].size))
good = true;
}
return good;
}
static int tpm2_get_best_pcr_bank(
ESYS_CONTEXT *c,
uint32_t pcr_mask,
TPMI_ALG_HASH *ret) {
_cleanup_(Esys_Freep) TPMS_CAPABILITY_DATA *pcap = NULL;
TPMI_ALG_HASH hash = TPM2_ALG_SHA1;
bool found = false;
TPMI_ALG_HASH supported_hash = 0, hash_with_valid_pcr = 0;
TPMI_YES_NO more;
TSS2_RC rc;
assert(c);
rc = sym_Esys_GetCapability(
c,
ESYS_TR_NONE,
@ -409,6 +503,11 @@ static int tpm2_get_best_pcr_bank(
for (size_t i = 0; i < pcap->data.assignedPCR.count; i++) {
bool valid = true;
int good;
/* For now we are only interested in the SHA1 and SHA256 banks */
if (!IN_SET(pcap->data.assignedPCR.pcrSelections[i].hash, TPM2_ALG_SHA256, TPM2_ALG_SHA1))
continue;
/* As per
* https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClient_PFP_r1p05_v23_pub.pdf a
@ -436,28 +535,58 @@ static int tpm2_get_best_pcr_bank(
continue;
}
if (pcap->data.assignedPCR.pcrSelections[i].hash == TPM2_ALG_SHA256) {
hash = TPM2_ALG_SHA256;
found = true;
break;
}
good = tpm2_pcr_mask_good(c, pcap->data.assignedPCR.pcrSelections[i].hash, pcr_mask);
if (good < 0)
return good;
if (pcap->data.assignedPCR.pcrSelections[i].hash == TPM2_ALG_SHA1)
found = true;
if (pcap->data.assignedPCR.pcrSelections[i].hash == TPM2_ALG_SHA256) {
supported_hash = TPM2_ALG_SHA256;
if (good) {
/* Great, SHA256 is supported and has initialized PCR values, we are done. */
hash_with_valid_pcr = TPM2_ALG_SHA256;
break;
}
} else {
assert(pcap->data.assignedPCR.pcrSelections[i].hash == TPM2_ALG_SHA1);
if (supported_hash == 0)
supported_hash = TPM2_ALG_SHA1;
if (good && hash_with_valid_pcr == 0)
hash_with_valid_pcr = TPM2_ALG_SHA1;
}
}
if (!found)
/* We preferably pick SHA256, but only if its PCRs are initialized or neither the SHA1 nor the SHA256
* PCRs are initialized. If SHA256 is not supported but SHA1 is and its PCRs are too, we prefer
* SHA1.
*
* We log at LOG_NOTICE level whenever we end up using the SHA1 bank or when the PCRs we bind to are
* not initialized. */
if (hash_with_valid_pcr == TPM2_ALG_SHA256) {
assert(supported_hash == TPM2_ALG_SHA256);
log_debug("TPM2 device supports SHA256 PCR bank and SHA256 PCRs are valid, yay!");
*ret = TPM2_ALG_SHA256;
} else if (hash_with_valid_pcr == TPM2_ALG_SHA1) {
if (supported_hash == TPM2_ALG_SHA256)
log_notice("TPM2 device supports both SHA1 and SHA256 PCR banks, but only SHA1 PCRs are valid, falling back to SHA1 bank. This reduces the security level substantially.");
else {
assert(supported_hash == TPM2_ALG_SHA1);
log_notice("TPM2 device lacks support for SHA256 PCR bank, but SHA1 bank is supported and SHA1 PCRs are valid, falling back to SHA1 bank. This reduces the security level substantially.");
}
*ret = TPM2_ALG_SHA1;
} else if (supported_hash == TPM2_ALG_SHA256) {
log_notice("TPM2 device supports SHA256 PCR bank but none of the selected PCRs are valid! Firmware apparently did not initialize any of the selected PCRs. Proceeding anyway with SHA256 bank. PCR policy effectively unenforced!");
*ret = TPM2_ALG_SHA256;
} else if (supported_hash == TPM2_ALG_SHA1) {
log_notice("TPM2 device lacks support for SHA256 bank, but SHA1 bank is supported, but none of the selected PCRs are valid! Firmware apparently did not initialize any of the selected PCRs. Proceeding anyway with SHA1 bank. PCR policy effectively unenforced!");
*ret = TPM2_ALG_SHA1;
} else
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"TPM2 module supports neither SHA1 nor SHA256 PCR banks, cannot operate.");
if (hash == TPM2_ALG_SHA256)
log_debug("TPM2 device supports SHA256 PCR banks, yay!");
else {
assert(hash == TPM2_ALG_SHA1);
log_debug("TPM2 device lacks support for SHA256 PCR banks, falling back to SHA1 banks.");
}
*ret = hash;
return 0;
}
@ -478,15 +607,8 @@ static int tpm2_make_pcr_session(
.aes = TPM2_ALG_CFB,
}
};
TPML_PCR_SELECTION pcr_selection = {
.count = 1,
.pcrSelections[0].hash = TPM2_ALG_SHA256, /* overridden below, depending on TPM2 capabilities */
.pcrSelections[0].sizeofSelect = 3,
.pcrSelections[0].pcrSelect[0] = pcr_mask & 0xFF,
.pcrSelections[0].pcrSelect[1] = (pcr_mask >> 8) & 0xFF,
.pcrSelections[0].pcrSelect[2] = (pcr_mask >> 16) & 0xFF,
};
_cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL;
TPML_PCR_SELECTION pcr_selection;
ESYS_TR session = ESYS_TR_NONE;
TSS2_RC rc;
int r;
@ -496,13 +618,17 @@ static int tpm2_make_pcr_session(
log_debug("Starting authentication session.");
if (pcr_bank != UINT16_MAX)
pcr_selection.pcrSelections[0].hash = pcr_bank;
tpm2_pcr_mask_to_selecion(pcr_mask, pcr_bank, &pcr_selection);
else {
TPMI_ALG_HASH h;
/* No bank configured, pick automatically. Some TPM2 devices only can do SHA1. If we detect
* that use that, but preferably use SHA256 */
r = tpm2_get_best_pcr_bank(c, &pcr_selection.pcrSelections[0].hash);
r = tpm2_get_best_pcr_bank(c, pcr_mask, &h);
if (r < 0)
return r;
tpm2_pcr_mask_to_selecion(pcr_mask, h, &pcr_selection);
}
rc = sym_Esys_StartAuthSession(

View File

@ -19,6 +19,7 @@ extern TSS2_RC (*sym_Esys_GetCapability)(ESYS_CONTEXT *esysContext, ESYS_TR shan
extern TSS2_RC (*sym_Esys_GetRandom)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, UINT16 bytesRequested, TPM2B_DIGEST **randomBytes);
extern TSS2_RC (*sym_Esys_Initialize)(ESYS_CONTEXT **esys_context, TSS2_TCTI_CONTEXT *tcti, TSS2_ABI_VERSION *abiVersion);
extern TSS2_RC (*sym_Esys_Load)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_PRIVATE *inPrivate, const TPM2B_PUBLIC *inPublic, ESYS_TR *objectHandle);
extern TSS2_RC (*sym_Esys_PCR_Read)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1,ESYS_TR shandle2, ESYS_TR shandle3, const TPML_PCR_SELECTION *pcrSelectionIn, UINT32 *pcrUpdateCounter, TPML_PCR_SELECTION **pcrSelectionOut, TPML_DIGEST **pcrValues);
extern TSS2_RC (*sym_Esys_PolicyGetDigest)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_DIGEST **policyDigest);
extern TSS2_RC (*sym_Esys_PolicyPCR)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *pcrDigest, const TPML_PCR_SELECTION *pcrs);
extern TSS2_RC (*sym_Esys_StartAuthSession)(ESYS_CONTEXT *esysContext, ESYS_TR tpmKey, ESYS_TR bind, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_NONCE *nonceCaller, TPM2_SE sessionType, const TPMT_SYM_DEF *symmetric, TPMI_ALG_HASH authHash, ESYS_TR *sessionHandle);