systemd/0531-tpm2-add-tpm2_get_capa...

275 lines
12 KiB
Diff
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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);