git: 726f096f905c - main - tpm20: Validate suspend and resume commands
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Thu, 03 Sep 2026 03:51:17 UTC
The branch main has been updated by kbowling:
URL: https://cgit.FreeBSD.org/src/commit/?id=726f096f905c8e2ad2ac0035355fc2b4ed2718fe
commit 726f096f905c8e2ad2ac0035355fc2b4ed2718fe
Author: Kevin Bowling <kbowling@FreeBSD.org>
AuthorDate: 2026-08-26 10:49:48 +0000
Commit: Kevin Bowling <kbowling@FreeBSD.org>
CommitDate: 2026-09-03 03:49:39 +0000
tpm20: Validate suspend and resume commands
The internal TPM2_Shutdown and TPM2_Startup paths ignored both transport
failures and the TPM response. Suspend could therefore enter S3 without
saved TPM state, while resume could restart entropy harvesting after a
failed state restoration.
Build both commands through one helper, validate their response framing
and TPM return codes, and propagate failures. Retry the standard RETRY
and TESTING responses with bounded exponential backoff. Accept
TPM_RC_INITIALIZE from Startup because firmware may already have started
the TPM during resume.
Do not enter S3 after an unsuccessful state save, and do not restart the
entropy task when TPM state restoration failed. If Shutdown fails after
the entropy task was drained, requeue it before returning so an aborted
suspend does not permanently stop harvesting.
Reviewed by: kevans
MFC after: 2 weeks
Sponsored by: BBOX.io
Differential Revision: https://reviews.freebsd.org/D59195
---
sys/dev/tpm/tpm20.c | 146 +++++++++++++++++++++++++++++++++-------------------
sys/dev/tpm/tpm20.h | 2 +
2 files changed, 95 insertions(+), 53 deletions(-)
diff --git a/sys/dev/tpm/tpm20.c b/sys/dev/tpm/tpm20.c
index 48f33708917d..22189d112656 100644
--- a/sys/dev/tpm/tpm20.c
+++ b/sys/dev/tpm/tpm20.c
@@ -31,6 +31,18 @@
#include "tpm20.h"
#define TPM_HARVEST_SIZE 16
+
+#define TPM2_ST_NO_SESSIONS 0x8001
+#define TPM2_RC_SUCCESS 0x0000
+#define TPM2_RC_INITIALIZE 0x0100
+#define TPM2_RC_TESTING 0x090a
+#define TPM2_RC_RETRY 0x0922
+
+#define TPM2_SU_CLEAR 0x0000
+#define TPM2_SU_STATE 0x0001
+
+#define TPM2_RETRY_INITIAL_MS 20
+#define TPM2_RETRY_MAX_MS (TPM_TIMEOUT_B / 1000)
/*
* Perform a harvest every 10 seconds.
* Since discrete TPMs are painfully slow
@@ -44,6 +56,8 @@ MALLOC_DEFINE(M_TPM20, "tpm_buffer", "buffer for tpm 2.0 driver");
#if defined TPM_HARVEST || defined RANDOM_ENABLE_TPM
static void tpm20_harvest(void *arg, int unused);
#endif
+static int tpm20_command(device_t, uint32_t, uint16_t, uint32_t,
+ const char *);
static int tpm20_restart(device_t dev, bool clear);
static int tpm20_save_state(device_t dev, bool suspend);
@@ -227,8 +241,11 @@ tpm20_release(struct tpm_sc *sc)
int
tpm20_resume(device_t dev)
{
+ int error;
- tpm20_restart(dev, false);
+ error = tpm20_restart(dev, false);
+ if (error != 0)
+ return (error);
#if defined TPM_HARVEST || defined RANDOM_ENABLE_TPM
struct tpm_sc *sc;
@@ -243,13 +260,21 @@ tpm20_resume(device_t dev)
int
tpm20_suspend(device_t dev)
{
+ int error;
+
#if defined TPM_HARVEST || defined RANDOM_ENABLE_TPM
struct tpm_sc *sc;
sc = device_get_softc(dev);
taskqueue_drain_timeout(taskqueue_thread, &sc->harvest_task);
#endif
- return (tpm20_save_state(dev, true));
+ error = tpm20_save_state(dev, true);
+#if defined TPM_HARVEST || defined RANDOM_ENABLE_TPM
+ if (error != 0)
+ taskqueue_enqueue_timeout(taskqueue_thread, &sc->harvest_task,
+ hz * TPM_HARVEST_INTERVAL);
+#endif
+ return (error);
}
int
@@ -308,76 +333,91 @@ tpm20_harvest(void *arg, int unused)
}
#endif /* TPM_HARVEST */
+/*
+ * Send a TPM 2.0 command whose successful response contains only a header.
+ */
static int
-tpm20_restart(device_t dev, bool clear)
+tpm20_command(device_t dev, uint32_t command, uint16_t parameter,
+ uint32_t alternate_rc, const char *name)
{
- struct tpm_sc *sc;
struct tpm_priv *priv;
- uint8_t startup_cmd[] = {
- 0x80, 0x01, /* TPM_ST_NO_SESSIONS tag*/
- 0x00, 0x00, 0x00, 0x0C, /* cmd length */
- 0x00, 0x00, 0x01, 0x44, /* cmd TPM_CC_Startup */
- 0x00, 0x01 /* TPM_SU_STATE */
- };
+ struct tpm_sc *sc;
+ uint32_t response_rc, response_size;
+ uint8_t cmd[12];
+ int delay_ms, error;
sc = device_get_softc(dev);
-
- /*
- * Inform the TPM whether we are resetting or resuming.
- */
- if (clear)
- startup_cmd[11] = 0; /* TPM_SU_CLEAR */
-
if (sc == NULL)
- return (0);
+ return (ENXIO);
- sx_xlock(&sc->dev_lock);
+ be16enc(cmd, TPM2_ST_NO_SESSIONS);
+ be32enc(cmd + 2, sizeof(cmd));
+ be32enc(cmd + 6, command);
+ be16enc(cmd + 10, parameter);
+ sx_xlock(&sc->dev_lock);
priv = sc->internal_priv;
- memcpy(priv->buf, startup_cmd, sizeof(startup_cmd));
-
- /* XXX Ignoring both TPM_TRANSMIT return and tpm's response */
- TPM_TRANSMIT(sc->dev, priv, sizeof(startup_cmd));
-
+ delay_ms = TPM2_RETRY_INITIAL_MS;
+ for (;;) {
+ memcpy(priv->buf, cmd, sizeof(cmd));
+ error = TPM_TRANSMIT(sc->dev, priv, sizeof(cmd));
+ if (error != 0)
+ break;
+ if (priv->len < TPM_HEADER_SIZE) {
+ error = EPROTO;
+ break;
+ }
+
+ response_size = be32dec(priv->buf + 2);
+ response_rc = be32dec(priv->buf + 6);
+ if (be16dec(priv->buf) != TPM2_ST_NO_SESSIONS ||
+ response_size != TPM_HEADER_SIZE ||
+ priv->len != response_size) {
+ error = EPROTO;
+ break;
+ }
+ if (response_rc != TPM2_RC_RETRY &&
+ response_rc != TPM2_RC_TESTING)
+ break;
+ if (delay_ms > TPM2_RETRY_MAX_MS)
+ break;
+ sx_xunlock(&sc->dev_lock);
+ pause("tpm2retry", MAX(hz * delay_ms / 1000, 1));
+ sx_xlock(&sc->dev_lock);
+ delay_ms *= 2;
+ }
sx_xunlock(&sc->dev_lock);
+ if (error != 0) {
+ device_printf(dev, "%s command failed: %d\n", name, error);
+ return (error);
+ }
+ if (response_rc != TPM2_RC_SUCCESS && response_rc != alternate_rc) {
+ device_printf(dev, "%s failed: TPM error 0x%x\n", name,
+ response_rc);
+ return (EIO);
+ }
return (0);
}
static int
-tpm20_save_state(device_t dev, bool suspend)
+tpm20_restart(device_t dev, bool clear)
{
- struct tpm_sc *sc;
- struct tpm_priv *priv;
- uint8_t save_cmd[] = {
- 0x80, 0x01, /* TPM_ST_NO_SESSIONS tag*/
- 0x00, 0x00, 0x00, 0x0C, /* cmd length */
- 0x00, 0x00, 0x01, 0x45, /* cmd TPM_CC_Shutdown */
- 0x00, 0x00 /* TPM_SU_STATE */
- };
-
- sc = device_get_softc(dev);
-
- /*
- * Inform the TPM whether we are going to suspend or reboot/shutdown.
- */
- if (suspend)
- save_cmd[11] = 1; /* TPM_SU_STATE */
+ uint16_t startup_type;
- if (sc == NULL)
- return (0);
-
- sx_xlock(&sc->dev_lock);
-
- priv = sc->internal_priv;
- memcpy(priv->buf, save_cmd, sizeof(save_cmd));
-
- /* XXX Ignoring both TPM_TRANSMIT return and tpm's response */
- TPM_TRANSMIT(sc->dev, priv, sizeof(save_cmd));
+ startup_type = clear ? TPM2_SU_CLEAR : TPM2_SU_STATE;
+ return (tpm20_command(dev, TPM_CC_Startup, startup_type,
+ TPM2_RC_INITIALIZE, "Startup"));
+}
- sx_xunlock(&sc->dev_lock);
+static int
+tpm20_save_state(device_t dev, bool suspend)
+{
+ uint16_t shutdown_type;
- return (0);
+ shutdown_type = suspend ? TPM2_SU_STATE : TPM2_SU_CLEAR;
+ return (tpm20_command(dev, TPM_CC_Shutdown, shutdown_type,
+ TPM2_RC_SUCCESS, "Shutdown"));
}
int32_t
diff --git a/sys/dev/tpm/tpm20.h b/sys/dev/tpm/tpm20.h
index b63bb9a1436e..a004bdea9e06 100644
--- a/sys/dev/tpm/tpm20.h
+++ b/sys/dev/tpm/tpm20.h
@@ -74,6 +74,8 @@
*/
#define TPM_TIMEOUT_LONG 40000000
+#define TPM_CC_Shutdown 0x00000145
+
/* List of commands that require TPM_TIMEOUT_LONG time to complete */
#define TPM_CC_CreatePrimary 0x00000131
#define TPM_CC_Create 0x00000153