275 lines
12 KiB
Diff
275 lines
12 KiB
Diff
From ca70ae72c223e6f0bc4b41efee13a847b2968734 Mon Sep 17 00:00:00 2001
|
||
From: Dan Streetman <ddstreet@ieee.org>
|
||
Date: Sun, 1 Jan 2023 20:19:12 -0500
|
||
Subject: [PATCH] tpm2: add tpm2_get_capability(), tpm2_cache_capabilities(),
|
||
tpm2_capability_pcrs()
|
||
|
||
This adds a function to query specific capabilities from the TPM. That is then
|
||
used in a function to query the allocation of PCRs in the TPM, i.e. which PCR
|
||
banks and indexes are available, and caches the PCR allocation when the TPM
|
||
context is created.
|
||
|
||
(cherry picked from commit 3a35d6cdd29f0303b9fffff2f34461b2be0cb1c7)
|
||
|
||
Related: RHEL-16182
|
||
---
|
||
src/shared/tpm2-util.c | 159 ++++++++++++++++++++++++++++-------------
|
||
src/shared/tpm2-util.h | 3 +
|
||
2 files changed, 113 insertions(+), 49 deletions(-)
|
||
|
||
diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c
|
||
index 6f62dd609a..460ebe62c7 100644
|
||
--- a/src/shared/tpm2-util.c
|
||
+++ b/src/shared/tpm2-util.c
|
||
@@ -130,6 +130,94 @@ static inline void Esys_Freep(void *p) {
|
||
sym_Esys_Free(*(void**) p);
|
||
}
|
||
|
||
+/* Get a specific TPM capability (or capabilities).
|
||
+ *
|
||
+ * Returns 0 if there are no more capability properties of the requested type, or 1 if there are more, or < 0
|
||
+ * on any error. Both 0 and 1 indicate this completed successfully, but do not indicate how many capability
|
||
+ * properties were provided in 'ret_capability_data'. To find the number of provided properties, check the
|
||
+ * specific type's 'count' field (e.g. for TPM2_CAP_ALGS, check ret_capability_data->algorithms.count).
|
||
+ *
|
||
+ * This calls TPM2_GetCapability() and does not alter the provided data, so it is important to understand how
|
||
+ * that TPM function works. It is recommended to check the TCG TPM specification Part 3 ("Commands") section
|
||
+ * on TPM2_GetCapability() for full details, but a short summary is: if this returns 0, all available
|
||
+ * properties have been provided in ret_capability_data, or no properties were available. If this returns 1,
|
||
+ * there are between 1 and "count" properties provided in ret_capability_data, and there are more available.
|
||
+ * Note that this may provide less than "count" properties even if the TPM has more available. Also, each
|
||
+ * capability category may have more specific requirements than described here; see the spec for exact
|
||
+ * details. */
|
||
+static int tpm2_get_capability(
|
||
+ Tpm2Context *c,
|
||
+ TPM2_CAP capability,
|
||
+ uint32_t property,
|
||
+ uint32_t count,
|
||
+ TPMU_CAPABILITIES *ret_capability_data) {
|
||
+
|
||
+ _cleanup_(Esys_Freep) TPMS_CAPABILITY_DATA *capabilities = NULL;
|
||
+ TPMI_YES_NO more;
|
||
+ TSS2_RC rc;
|
||
+
|
||
+ assert(c);
|
||
+
|
||
+ log_debug("Getting TPM2 capability 0x%04" PRIx32 " property 0x%04" PRIx32 " count %" PRIu32 ".",
|
||
+ capability, property, count);
|
||
+
|
||
+ rc = sym_Esys_GetCapability(
|
||
+ c->esys_context,
|
||
+ ESYS_TR_NONE,
|
||
+ ESYS_TR_NONE,
|
||
+ ESYS_TR_NONE,
|
||
+ capability,
|
||
+ property,
|
||
+ count,
|
||
+ &more,
|
||
+ &capabilities);
|
||
+ if (rc != TSS2_RC_SUCCESS)
|
||
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
|
||
+ "Failed to get TPM2 capability 0x%04" PRIx32 " property 0x%04" PRIx32 ": %s",
|
||
+ capability, property, sym_Tss2_RC_Decode(rc));
|
||
+
|
||
+ if (capabilities->capability != capability)
|
||
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
|
||
+ "TPM provided wrong capability: 0x%04" PRIx32 " instead of 0x%04" PRIx32 ".",
|
||
+ capabilities->capability, capability);
|
||
+
|
||
+ if (ret_capability_data)
|
||
+ *ret_capability_data = capabilities->data;
|
||
+
|
||
+ return more == TPM2_YES;
|
||
+}
|
||
+
|
||
+static int tpm2_cache_capabilities(Tpm2Context *c) {
|
||
+ TPMU_CAPABILITIES capability;
|
||
+ int r;
|
||
+
|
||
+ assert(c);
|
||
+
|
||
+ /* Cache the PCR capabilities, which are safe to cache, as the only way they can change is
|
||
+ * TPM2_PCR_Allocate(), which changes the allocation after the next _TPM_Init(). If the TPM is
|
||
+ * reinitialized while we are using it, all our context and sessions will be invalid, so we can
|
||
+ * safely assume the TPM PCR allocation will not change while we are using it. */
|
||
+ r = tpm2_get_capability(
|
||
+ c,
|
||
+ TPM2_CAP_PCRS,
|
||
+ /* property= */ 0,
|
||
+ /* count= */ 1,
|
||
+ &capability);
|
||
+ if (r < 0)
|
||
+ return r;
|
||
+ if (r == 1)
|
||
+ /* This should never happen. Part 3 ("Commands") of the TCG TPM2 spec in the section for
|
||
+ * TPM2_GetCapability states: "TPM_CAP_PCRS – Returns the current allocation of PCR in a
|
||
+ * TPML_PCR_SELECTION. The property parameter shall be zero. The TPM will always respond to
|
||
+ * this command with the full PCR allocation and moreData will be NO." */
|
||
+ log_warning("TPM bug: reported multiple PCR sets; using only first set.");
|
||
+ c->capability_pcrs = capability.assignedPCR;
|
||
+
|
||
+ return 0;
|
||
+}
|
||
+
|
||
+#define tpm2_capability_pcrs(c) ((c)->capability_pcrs)
|
||
+
|
||
static Tpm2Context *tpm2_context_free(Tpm2Context *c) {
|
||
if (!c)
|
||
return NULL;
|
||
@@ -250,6 +338,10 @@ int tpm2_context_new(const char *device, Tpm2Context **ret_context) {
|
||
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
|
||
"Failed to start up TPM: %s", sym_Tss2_RC_Decode(rc));
|
||
|
||
+ r = tpm2_cache_capabilities(context);
|
||
+ if (r < 0)
|
||
+ return r;
|
||
+
|
||
*ret_context = TAKE_PTR(context);
|
||
|
||
return 0;
|
||
@@ -1173,48 +1265,33 @@ static int tpm2_get_best_pcr_bank(
|
||
uint32_t pcr_mask,
|
||
TPMI_ALG_HASH *ret) {
|
||
|
||
- _cleanup_(Esys_Freep) TPMS_CAPABILITY_DATA *pcap = NULL;
|
||
+ TPML_PCR_SELECTION pcrs;
|
||
TPMI_ALG_HASH supported_hash = 0, hash_with_valid_pcr = 0;
|
||
- TPMI_YES_NO more;
|
||
- TSS2_RC rc;
|
||
int r;
|
||
|
||
assert(c);
|
||
+ assert(ret);
|
||
|
||
- rc = sym_Esys_GetCapability(
|
||
- c->esys_context,
|
||
- ESYS_TR_NONE,
|
||
- ESYS_TR_NONE,
|
||
- ESYS_TR_NONE,
|
||
- TPM2_CAP_PCRS,
|
||
- 0,
|
||
- 1,
|
||
- &more,
|
||
- &pcap);
|
||
- if (rc != TSS2_RC_SUCCESS)
|
||
- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
|
||
- "Failed to determine TPM2 PCR bank capabilities: %s", sym_Tss2_RC_Decode(rc));
|
||
-
|
||
- assert(pcap->capability == TPM2_CAP_PCRS);
|
||
-
|
||
- for (size_t i = 0; i < pcap->data.assignedPCR.count; i++) {
|
||
+ pcrs = tpm2_capability_pcrs(c);
|
||
+ FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(selection, &pcrs) {
|
||
+ TPMI_ALG_HASH hash = selection->hash;
|
||
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))
|
||
+ if (!IN_SET(hash, TPM2_ALG_SHA256, TPM2_ALG_SHA1))
|
||
continue;
|
||
|
||
- r = tpm2_bank_has24(pcap->data.assignedPCR.pcrSelections + i);
|
||
+ r = tpm2_bank_has24(selection);
|
||
if (r < 0)
|
||
return r;
|
||
if (!r)
|
||
continue;
|
||
|
||
- good = tpm2_pcr_mask_good(c, pcap->data.assignedPCR.pcrSelections[i].hash, pcr_mask);
|
||
+ good = tpm2_pcr_mask_good(c, hash, pcr_mask);
|
||
if (good < 0)
|
||
return good;
|
||
|
||
- if (pcap->data.assignedPCR.pcrSelections[i].hash == TPM2_ALG_SHA256) {
|
||
+ if (hash == TPM2_ALG_SHA256) {
|
||
supported_hash = TPM2_ALG_SHA256;
|
||
if (good) {
|
||
/* Great, SHA256 is supported and has initialized PCR values, we are done. */
|
||
@@ -1222,7 +1299,7 @@ static int tpm2_get_best_pcr_bank(
|
||
break;
|
||
}
|
||
} else {
|
||
- assert(pcap->data.assignedPCR.pcrSelections[i].hash == TPM2_ALG_SHA1);
|
||
+ assert(hash == TPM2_ALG_SHA1);
|
||
|
||
if (supported_hash == 0)
|
||
supported_hash = TPM2_ALG_SHA1;
|
||
@@ -1271,42 +1348,26 @@ int tpm2_get_good_pcr_banks(
|
||
TPMI_ALG_HASH **ret) {
|
||
|
||
_cleanup_free_ TPMI_ALG_HASH *good_banks = NULL, *fallback_banks = NULL;
|
||
- _cleanup_(Esys_Freep) TPMS_CAPABILITY_DATA *pcap = NULL;
|
||
+ TPML_PCR_SELECTION pcrs;
|
||
size_t n_good_banks = 0, n_fallback_banks = 0;
|
||
- TPMI_YES_NO more;
|
||
- TSS2_RC rc;
|
||
int r;
|
||
|
||
assert(c);
|
||
assert(ret);
|
||
|
||
- rc = sym_Esys_GetCapability(
|
||
- c->esys_context,
|
||
- ESYS_TR_NONE,
|
||
- ESYS_TR_NONE,
|
||
- ESYS_TR_NONE,
|
||
- TPM2_CAP_PCRS,
|
||
- 0,
|
||
- 1,
|
||
- &more,
|
||
- &pcap);
|
||
- if (rc != TSS2_RC_SUCCESS)
|
||
- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
|
||
- "Failed to determine TPM2 PCR bank capabilities: %s", sym_Tss2_RC_Decode(rc));
|
||
-
|
||
- assert(pcap->capability == TPM2_CAP_PCRS);
|
||
-
|
||
- for (size_t i = 0; i < pcap->data.assignedPCR.count; i++) {
|
||
+ pcrs = tpm2_capability_pcrs(c);
|
||
+ FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(selection, &pcrs) {
|
||
+ TPMI_ALG_HASH hash = selection->hash;
|
||
|
||
/* Let's see if this bank is superficially OK, i.e. has at least 24 enabled registers */
|
||
- r = tpm2_bank_has24(pcap->data.assignedPCR.pcrSelections + i);
|
||
+ r = tpm2_bank_has24(selection);
|
||
if (r < 0)
|
||
return r;
|
||
if (!r)
|
||
continue;
|
||
|
||
/* Let's now see if this bank has any of the selected PCRs actually initialized */
|
||
- r = tpm2_pcr_mask_good(c, pcap->data.assignedPCR.pcrSelections[i].hash, pcr_mask);
|
||
+ r = tpm2_pcr_mask_good(c, hash, pcr_mask);
|
||
if (r < 0)
|
||
return r;
|
||
|
||
@@ -1317,12 +1378,12 @@ int tpm2_get_good_pcr_banks(
|
||
if (!GREEDY_REALLOC(good_banks, n_good_banks+1))
|
||
return log_oom();
|
||
|
||
- good_banks[n_good_banks++] = pcap->data.assignedPCR.pcrSelections[i].hash;
|
||
+ good_banks[n_good_banks++] = hash;
|
||
} else {
|
||
if (!GREEDY_REALLOC(fallback_banks, n_fallback_banks+1))
|
||
return log_oom();
|
||
|
||
- fallback_banks[n_fallback_banks++] = pcap->data.assignedPCR.pcrSelections[i].hash;
|
||
+ fallback_banks[n_fallback_banks++] = hash;
|
||
}
|
||
}
|
||
|
||
diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h
|
||
index 9bf9e44878..5e5d9e2604 100644
|
||
--- a/src/shared/tpm2-util.h
|
||
+++ b/src/shared/tpm2-util.h
|
||
@@ -67,6 +67,9 @@ typedef struct {
|
||
void *tcti_dl;
|
||
TSS2_TCTI_CONTEXT *tcti_context;
|
||
ESYS_CONTEXT *esys_context;
|
||
+
|
||
+ /* Some selected cached capabilities of the TPM */
|
||
+ TPML_PCR_SELECTION capability_pcrs;
|
||
} Tpm2Context;
|
||
|
||
int tpm2_context_new(const char *device, Tpm2Context **ret_context);
|