git: 726f096f905c - main - tpm20: Validate suspend and resume commands

From: Kevin Bowling <kbowling_at_FreeBSD.org>
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