systemd/0535-tpm2-add-tpm2_get_capa...

409 lines
18 KiB
Diff

From 88bac2c3213ab4a5036732ff9bc4f5dc2b4287b7 Mon Sep 17 00:00:00 2001
From: Dan Streetman <ddstreet@ieee.org>
Date: Fri, 17 Feb 2023 12:50:31 -0500
Subject: [PATCH] tpm2: add tpm2_get_capability_handle(),
tpm2_esys_handle_from_tpm_handle()
Add tpm2_get_capability_handle() to query if a "TPM handle" (meaning, a
location/address in TPM storage) is populated in the TPM, and
tpm2_get_capability_handles() to query for a specific number of handles.
Add tpm2_esys_handle_from_tpm_handle() to create an "esys handle" (an opaque
reference for use with the TPM EAPI that represents a TPM handle address) for an
existing TPM handle.
Since the TPM handle already exists in the TPM, this also also requires
updating the cleanup code for Tpm2Handle objects to close the object (free its
resources only from the EAPI code, but leave the handle in the TPM) instead of
flush the object (which frees its EAPI resources and removes it from the TPM).
(cherry picked from commit c8a85240316898a6de95c9b2565edd08f8450182)
Related: RHEL-16182
---
src/shared/tpm2-util.c | 244 ++++++++++++++++++++++++++++++++---------
src/shared/tpm2-util.h | 3 +-
2 files changed, 193 insertions(+), 54 deletions(-)
diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c
index bf36b4de95..3278863f4d 100644
--- a/src/shared/tpm2-util.c
+++ b/src/shared/tpm2-util.c
@@ -50,6 +50,7 @@ static TSS2_RC (*sym_Esys_ReadPublic)(ESYS_CONTEXT *esysContext, ESYS_TR objectH
static 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;
static TSS2_RC (*sym_Esys_Startup)(ESYS_CONTEXT *esysContext, TPM2_SU startupType) = NULL;
static TSS2_RC (*sym_Esys_TestParms)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPMT_PUBLIC_PARMS *parameters) = NULL;
+static TSS2_RC (*sym_Esys_TR_Close)(ESYS_CONTEXT *esys_context, ESYS_TR *rsrc_handle) = NULL;
static TSS2_RC (*sym_Esys_TR_Deserialize)(ESYS_CONTEXT *esys_context, uint8_t const *buffer, size_t buffer_size, ESYS_TR *esys_handle) = NULL;
static TSS2_RC (*sym_Esys_TR_FromTPMPublic)(ESYS_CONTEXT *esysContext, TPM2_HANDLE tpm_handle, ESYS_TR optionalSession1, ESYS_TR optionalSession2, ESYS_TR optionalSession3, ESYS_TR *object) = NULL;
static TSS2_RC (*sym_Esys_TR_GetName)(ESYS_CONTEXT *esysContext, ESYS_TR handle, TPM2B_NAME **name) = NULL;
@@ -97,6 +98,7 @@ int dlopen_tpm2(void) {
DLSYM_ARG(Esys_StartAuthSession),
DLSYM_ARG(Esys_Startup),
DLSYM_ARG(Esys_TestParms),
+ DLSYM_ARG(Esys_TR_Close),
DLSYM_ARG(Esys_TR_Deserialize),
DLSYM_ARG(Esys_TR_FromTPMPublic),
DLSYM_ARG(Esys_TR_GetName),
@@ -252,6 +254,84 @@ int tpm2_supports_alg(Tpm2Context *c, TPM2_ALG_ID alg) {
return tpm2_get_capability_alg(c, alg, NULL);
}
+/* Query the TPM for populated handles.
+ *
+ * This provides an array of handle indexes populated in the TPM, starting at the requested handle. The array will
+ * contain only populated handle addresses (which might not include the requested handle). The number of
+ * handles will be no more than the 'max' number requested. This will not search past the end of the handle
+ * range (i.e. handle & 0xff000000).
+ *
+ * Returns 0 if all populated handles in the range (starting at the requested handle) were provided (or no
+ * handles were in the range), or 1 if there are more populated handles in the range, or < 0 on any error. */
+static int tpm2_get_capability_handles(
+ Tpm2Context *c,
+ TPM2_HANDLE start,
+ size_t max,
+ TPM2_HANDLE **ret_handles,
+ size_t *ret_n_handles) {
+
+ _cleanup_free_ TPM2_HANDLE *handles = NULL;
+ size_t n_handles = 0;
+ TPM2_HANDLE current = start;
+ int r = 0;
+
+ assert(c);
+ assert(ret_handles);
+ assert(ret_n_handles);
+
+ while (max > 0) {
+ TPMU_CAPABILITIES capability;
+ r = tpm2_get_capability(c, TPM2_CAP_HANDLES, current, (uint32_t) max, &capability);
+ if (r < 0)
+ return r;
+
+ TPML_HANDLE handle_list = capability.handles;
+ if (handle_list.count == 0)
+ break;
+
+ assert(handle_list.count <= max);
+
+ if (n_handles > SIZE_MAX - handle_list.count)
+ return log_oom();
+
+ if (!GREEDY_REALLOC(handles, n_handles + handle_list.count))
+ return log_oom();
+
+ memcpy_safe(&handles[n_handles], handle_list.handle, sizeof(handles[0]) * handle_list.count);
+
+ max -= handle_list.count;
+ n_handles += handle_list.count;
+
+ /* Update current to the handle index after the last handle in the list. */
+ current = handles[n_handles - 1] + 1;
+
+ if (r == 0)
+ /* No more handles in this range. */
+ break;
+ }
+
+ *ret_handles = TAKE_PTR(handles);
+ *ret_n_handles = n_handles;
+
+ return r;
+}
+
+#define TPM2_HANDLE_RANGE(h) ((TPM2_HANDLE)((h) & TPM2_HR_RANGE_MASK))
+#define TPM2_HANDLE_TYPE(h) ((TPM2_HT)(TPM2_HANDLE_RANGE(h) >> TPM2_HR_SHIFT))
+
+/* Returns 1 if the handle is populated in the TPM, 0 if not, and < 0 on any error. */
+static int tpm2_get_capability_handle(Tpm2Context *c, TPM2_HANDLE handle) {
+ _cleanup_free_ TPM2_HANDLE *handles = NULL;
+ size_t n_handles = 0;
+ int r;
+
+ r = tpm2_get_capability_handles(c, handle, 1, &handles, &n_handles);
+ if (r < 0)
+ return r;
+
+ return n_handles == 0 ? false : handles[0] == handle;
+}
+
/* Returns 1 if the TPM supports the parms, or 0 if the TPM does not support the parms. */
bool tpm2_test_parms(Tpm2Context *c, TPMI_ALG_PUBLIC alg, const TPMU_PUBLIC_PARMS *parms) {
TSS2_RC rc;
@@ -454,17 +534,25 @@ int tpm2_context_new(const char *device, Tpm2Context **ret_context) {
return 0;
}
-static void tpm2_handle_flush(ESYS_CONTEXT *esys_context, ESYS_TR esys_handle) {
+static void tpm2_handle_cleanup(ESYS_CONTEXT *esys_context, ESYS_TR esys_handle, bool flush) {
+ TSS2_RC rc;
+
if (!esys_context || esys_handle == ESYS_TR_NONE)
return;
- TSS2_RC rc = sym_Esys_FlushContext(esys_context, esys_handle);
+ /* Closing the handle removes its reference from the esys_context, but leaves the corresponding
+ * handle in the actual TPM. Flushing the handle removes its reference from the esys_context as well
+ * as removing its corresponding handle from the actual TPM. */
+ if (flush)
+ rc = sym_Esys_FlushContext(esys_context, esys_handle);
+ else
+ rc = sym_Esys_TR_Close(esys_context, &esys_handle);
if (rc != TSS2_RC_SUCCESS) /* We ignore failures here (besides debug logging), since this is called
* in error paths, where we cannot do anything about failures anymore. And
* when it is called in successful codepaths by this time we already did
* what we wanted to do, and got the results we wanted so there's no
* reason to make this fail more loudly than necessary. */
- log_debug("Failed to flush TPM handle, ignoring: %s", sym_Tss2_RC_Decode(rc));
+ log_debug("Failed to %s TPM handle, ignoring: %s", flush ? "flush" : "close", sym_Tss2_RC_Decode(rc));
}
Tpm2Handle *tpm2_handle_free(Tpm2Handle *handle) {
@@ -472,8 +560,8 @@ Tpm2Handle *tpm2_handle_free(Tpm2Handle *handle) {
return NULL;
_cleanup_(tpm2_context_unrefp) Tpm2Context *context = (Tpm2Context*)handle->tpm2_context;
- if (context && !handle->keep)
- tpm2_handle_flush(context->esys_context, handle->esys_handle);
+ if (context)
+ tpm2_handle_cleanup(context->esys_context, handle->esys_handle, handle->flush);
return mfree(handle);
}
@@ -490,6 +578,7 @@ int tpm2_handle_new(Tpm2Context *context, Tpm2Handle **ret_handle) {
*handle = (Tpm2Handle) {
.tpm2_context = tpm2_context_ref(context),
.esys_handle = ESYS_TR_NONE,
+ .flush = true,
};
*ret_handle = TAKE_PTR(handle);
@@ -497,6 +586,81 @@ int tpm2_handle_new(Tpm2Context *context, Tpm2Handle **ret_handle) {
return 0;
}
+/* Create a Tpm2Handle object that references a pre-existing handle in the TPM, at the TPM2_HANDLE address
+ * provided. This should be used only for persistent, transient, or NV handles. Returns 1 on success, 0 if
+ * the requested handle is not present in the TPM, or < 0 on error. */
+static int tpm2_esys_handle_from_tpm_handle(
+ Tpm2Context *c,
+ const Tpm2Handle *session,
+ TPM2_HANDLE tpm_handle,
+ Tpm2Handle **ret_handle) {
+
+ TSS2_RC rc;
+ int r;
+
+ assert(c);
+ assert(tpm_handle > 0);
+ assert(ret_handle);
+
+ /* Let's restrict this, at least for now, to allow only some handle types. */
+ switch (TPM2_HANDLE_TYPE(tpm_handle)) {
+ case TPM2_HT_PERSISTENT:
+ case TPM2_HT_NV_INDEX:
+ case TPM2_HT_TRANSIENT:
+ break;
+ case TPM2_HT_PCR:
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Refusing to create ESYS handle for PCR handle 0x%08" PRIx32 ".",
+ tpm_handle);
+ case TPM2_HT_HMAC_SESSION:
+ case TPM2_HT_POLICY_SESSION:
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Refusing to create ESYS handle for session handle 0x%08" PRIx32 ".",
+ tpm_handle);
+ case TPM2_HT_PERMANENT: /* Permanent handles are defined, e.g. ESYS_TR_RH_OWNER. */
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Refusing to create ESYS handle for permanent handle 0x%08" PRIx32 ".",
+ tpm_handle);
+ default:
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Refusing to create ESYS handle for unknown handle 0x%08" PRIx32 ".",
+ tpm_handle);
+ }
+
+ r = tpm2_get_capability_handle(c, tpm_handle);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ log_debug("TPM handle 0x%08" PRIx32 " not populated.", tpm_handle);
+ *ret_handle = NULL;
+ return 0;
+ }
+
+ _cleanup_(tpm2_handle_freep) Tpm2Handle *handle = NULL;
+ r = tpm2_handle_new(c, &handle);
+ if (r < 0)
+ return r;
+
+ /* Since we didn't create this handle in the TPM (this is only creating an ESYS_TR handle for the
+ * pre-existing TPM handle), we shouldn't flush (or evict) it on cleanup. */
+ handle->flush = false;
+
+ rc = sym_Esys_TR_FromTPMPublic(
+ c->esys_context,
+ tpm_handle,
+ session ? session->esys_handle : ESYS_TR_NONE,
+ ESYS_TR_NONE,
+ ESYS_TR_NONE,
+ &handle->esys_handle);
+ if (rc != TSS2_RC_SUCCESS)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to read public info: %s", sym_Tss2_RC_Decode(rc));
+
+ *ret_handle = TAKE_PTR(handle);
+
+ return 1;
+}
+
#define TPM2_CREDIT_RANDOM_FLAG_PATH "/run/systemd/tpm-rng-credited"
static int tpm2_credit_random(Tpm2Context *c) {
@@ -660,60 +824,32 @@ const TPM2B_PUBLIC *tpm2_get_primary_template(Tpm2SRKTemplateFlags flags) {
*/
static int tpm2_get_srk(
Tpm2Context *c,
+ const Tpm2Handle *session,
TPMI_ALG_PUBLIC *ret_alg,
- Tpm2Handle *ret_primary) {
+ Tpm2Handle **ret_handle) {
- TPMI_YES_NO more_data;
- ESYS_TR primary_tr = ESYS_TR_NONE;
- _cleanup_(Esys_Freep) TPMS_CAPABILITY_DATA *cap_data = NULL;
+ int r;
assert(c);
- assert(ret_primary);
-
- TSS2_RC rc = sym_Esys_GetCapability(c->esys_context,
- ESYS_TR_NONE,
- ESYS_TR_NONE,
- ESYS_TR_NONE,
- TPM2_CAP_HANDLES,
- SRK_HANDLE,
- 1,
- &more_data,
- &cap_data);
- if (rc != TSS2_RC_SUCCESS)
- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
- "Failed to enumerate handles searching for SRK: %s",
- sym_Tss2_RC_Decode(rc));
-
- /* Did Not find SRK, indicate this by returning 0 */
- if (cap_data->data.handles.count == 0 || cap_data->data.handles.handle[0] != SRK_HANDLE) {
- ret_primary->esys_handle = ESYS_TR_NONE;
+ _cleanup_(tpm2_handle_freep) Tpm2Handle *handle = NULL;
+ r = tpm2_esys_handle_from_tpm_handle(c, session, SRK_HANDLE, &handle);
+ if (r < 0)
+ return r;
+ if (r == 0) { /* SRK not found */
if (ret_alg)
- *ret_alg = 0;
+ *ret_alg = TPM2_ALG_ERROR;
+ if (ret_handle)
+ *ret_handle = NULL;
return 0;
}
- log_debug("Found SRK on TPM.");
-
- /* convert the raw handle to an ESYS_TR */
- TPM2_HANDLE handle = cap_data->data.handles.handle[0];
- rc = sym_Esys_TR_FromTPMPublic(c->esys_context,
- handle,
- ESYS_TR_NONE,
- ESYS_TR_NONE,
- ESYS_TR_NONE,
- &primary_tr);
- if (rc != TSS2_RC_SUCCESS)
- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
- "Failed to convert ray handle to ESYS_TR for SRK: %s",
- sym_Tss2_RC_Decode(rc));
-
/* Get the algorithm if the caller wants it */
_cleanup_(Esys_Freep) TPM2B_PUBLIC *out_public = NULL;
if (ret_alg) {
- rc = sym_Esys_ReadPublic(
+ TSS2_RC rc = sym_Esys_ReadPublic(
c->esys_context,
- primary_tr,
+ handle->esys_handle,
ESYS_TR_NONE,
ESYS_TR_NONE,
ESYS_TR_NONE,
@@ -726,7 +862,8 @@ static int tpm2_get_srk(
sym_Tss2_RC_Decode(rc));
}
- ret_primary->esys_handle = primary_tr;
+ if (ret_handle)
+ *ret_handle = TAKE_PTR(handle);
if (ret_alg)
*ret_alg = out_public->publicArea.type;
@@ -759,9 +896,6 @@ static int tpm2_make_primary(
ts = now(CLOCK_MONOTONIC);
_cleanup_(tpm2_handle_freep) Tpm2Handle *primary = NULL;
- r = tpm2_handle_new(c, &primary);
- if (r < 0)
- return r;
/* we only need the SRK lock when making the SRK since its not atomic, transient
* primary creations don't even matter if they stomp on each other, the TPM will
@@ -776,7 +910,7 @@ static int tpm2_make_primary(
/* Find existing SRK and use it if present */
if (use_srk_model) {
TPMI_ALG_PUBLIC got_alg = TPM2_ALG_NULL;
- r = tpm2_get_srk(c, &got_alg, primary);
+ r = tpm2_get_srk(c, NULL, &got_alg, &primary);
if (r < 0)
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Failed to establish if SRK is present");
@@ -796,6 +930,10 @@ static int tpm2_make_primary(
log_debug("Did not find SRK, generating...");
}
+ r = tpm2_handle_new(c, &primary);
+ if (r < 0)
+ return r;
+
if (IN_SET(alg, 0, TPM2_ALG_ECC)) {
primary_template = tpm2_get_primary_template(base_flags | TPM2_SRK_TEMPLATE_ECC);
@@ -866,7 +1004,7 @@ static int tpm2_make_primary(
if (rc != TSS2_RC_SUCCESS)
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Failed to persist SRK within TPM: %s", sym_Tss2_RC_Decode(rc));
- primary->keep = true;
+ primary->flush = false;
}
if (ret_primary)
@@ -2846,7 +2984,7 @@ int tpm2_unseal(const char *device,
if (r < 0)
return r;
- primary->keep = true;
+ primary->flush = false;
log_debug("Found existing SRK key to use, deserializing ESYS_TR");
rc = sym_Esys_TR_Deserialize(
diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h
index a03bee148b..26d25f7ee7 100644
--- a/src/shared/tpm2-util.h
+++ b/src/shared/tpm2-util.h
@@ -80,7 +80,8 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(Tpm2Context*, tpm2_context_unref);
typedef struct {
Tpm2Context *tpm2_context;
ESYS_TR esys_handle;
- bool keep;
+
+ bool flush;
} Tpm2Handle;
#define _tpm2_handle(c, h) { .tpm2_context = (c), .esys_handle = (h), }