git: 4bcd10f7c049 - main - tpm: Harvest entropy from TPM 1.2 devices

From: Kevin Bowling <kbowling_at_FreeBSD.org>
Date: Fri, 30 Oct 2026 22:26:06 UTC
The branch main has been updated by kbowling:

URL: https://cgit.FreeBSD.org/src/commit/?id=4bcd10f7c04988056c7b28c4cbe65804d7f26d93

commit 4bcd10f7c04988056c7b28c4cbe65804d7f26d93
Author:     Kevin Bowling <kbowling@FreeBSD.org>
AuthorDate: 2026-08-27 14:27:45 +0000
Commit:     Kevin Bowling <kbowling@FreeBSD.org>
CommitDate: 2026-10-30 22:24:16 +0000

    tpm: Harvest entropy from TPM 1.2 devices
    
    The TPM 2.0 driver periodically contributes TPM-generated random bytes
    to the kernel entropy pool, but the TPM 1.2 driver does not.
    
    Issue TPM_GetRandom every ten seconds and submit successful responses
    as RANDOM_PURE_TPM.  Keep each exchange under the TPM transaction lock.
    Defer harvesting while the character device is open or a command is
    pending so the task cannot consume a user response.  Drain the task for
    suspend and detach, rearming it after resume or a failed suspend.
    
    Document the expanded scope of RANDOM_ENABLE_TPM and update the kernel
    configuration comments that described it as TPM 2.0-only.
    
    Validated on a STMicroelectronics TPM 1.2 TIS device.  DTrace observed
    periodic entropy submissions; a raw GetRandom response remained intact
    while the device was held open across a harvest interval; and module
    unload/reload deregistered and restored the entropy source cleanly.
    
    Tested by:      zarychtam_plan-b.pwste.edu.pl
    MFC after:      2 weeks
    Sponsored by:   BBOX.io
    Differential Revision:  https://reviews.freebsd.org/D59239
---
 share/man/man4/tpm.4     |  10 ++-
 sys/arm/conf/GENERIC     |   2 +-
 sys/arm64/conf/std.arm64 |   2 +-
 sys/dev/tpm/tpm.c        | 199 ++++++++++++++++++++++++++++++++++++++++++++++-
 sys/dev/tpm/tpmvar.h     |   6 ++
 sys/riscv/conf/GENERIC   |   2 +-
 6 files changed, 216 insertions(+), 5 deletions(-)

diff --git a/share/man/man4/tpm.4 b/share/man/man4/tpm.4
index 408b1f30022b..4a37fa2f8458 100644
--- a/share/man/man4/tpm.4
+++ b/share/man/man4/tpm.4
@@ -13,7 +13,7 @@
 .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 .\"
-.Dd October 31, 2018
+.Dd August 27, 2026
 .Dt TPM 4
 .Os
 .Sh NAME
@@ -21,6 +21,7 @@
 .Nd Trusted Platform Module
 .Sh SYNOPSIS
 .Cd device tpm
+.Cd options RANDOM_ENABLE_TPM
 .Pp
 In
 .Xr device.hints 5 :
@@ -43,6 +44,12 @@ store cryptographic keys.
 The driver can be configured to use an IRQ by providing a free ISA
 interrupt vector in
 .Pa /boot/device.hints .
+.Pp
+When the kernel is configured with
+.Cd "options RANDOM_ENABLE_TPM" ,
+the driver periodically obtains random bytes from the TPM and supplies them
+to
+.Xr random 4 .
 .Sh HARDWARE
 The
 .Nm
@@ -66,6 +73,7 @@ Winbond WEC WPCT200
 .El
 .Sh SEE ALSO
 .Xr intro 4 ,
+.Xr random 4 ,
 .Xr device.hints 5 ,
 .Xr config 8
 .Pp
diff --git a/sys/arm/conf/GENERIC b/sys/arm/conf/GENERIC
index b0a2eb92554b..c74c8d1f56a7 100644
--- a/sys/arm/conf/GENERIC
+++ b/sys/arm/conf/GENERIC
@@ -218,7 +218,7 @@ device		smsc			# SMSC LAN91C111
 
 # random(4)
 device		tpm			# Trusted Platform Module
-options 	RANDOM_ENABLE_TPM	# enable entropy from TPM 2.0
+options 	RANDOM_ENABLE_TPM	# enable entropy from TPM devices
 options 	RANDOM_ENABLE_KBD
 options 	RANDOM_ENABLE_MOUSE
 
diff --git a/sys/arm64/conf/std.arm64 b/sys/arm64/conf/std.arm64
index 47d803a7c37e..28d62b1657f9 100644
--- a/sys/arm64/conf/std.arm64
+++ b/sys/arm64/conf/std.arm64
@@ -111,6 +111,6 @@ device		smbios
 
 # random(4)
 device		tpm			# Trusted Platform Module
-options 	RANDOM_ENABLE_TPM	# enable entropy from TPM 2.0
+options 	RANDOM_ENABLE_TPM	# enable entropy from TPM devices
 options 	RANDOM_ENABLE_KBD
 options 	RANDOM_ENABLE_MOUSE
diff --git a/sys/dev/tpm/tpm.c b/sys/dev/tpm/tpm.c
index 42c31ba75518..41f75aaa3047 100644
--- a/sys/dev/tpm/tpm.c
+++ b/sys/dev/tpm/tpm.c
@@ -24,6 +24,7 @@
 #include <sys/kernel.h>
 #include <sys/malloc.h>
 #include <sys/proc.h>
+#include <sys/random.h>
 
 #include <sys/module.h>
 #include <sys/conf.h>
@@ -43,8 +44,20 @@
 
 #include "tpm_if.h"
 
+#if defined TPM_HARVEST || defined RANDOM_ENABLE_TPM
+#include <dev/random/randomdev.h>
+#include <dev/random/random_harvestq.h>
+#endif
+
 MALLOC_DEFINE(M_TPM, "tpm12_buffer", "buffer for TPM 1.2 responses");
 
+#if defined TPM_HARVEST || defined RANDOM_ENABLE_TPM
+static const struct random_source random_tpm12 = {
+	.rs_ident = "TPM",
+	.rs_source = RANDOM_PURE_TPM,
+};
+#endif
+
 #define	TPM_BUFSIZ	1024
 
 #define TPM_HDRSIZE	10
@@ -54,11 +67,15 @@ MALLOC_DEFINE(M_TPM, "tpm12_buffer", "buffer for TPM 1.2 responses");
 #define	TPM_TAG_RQU_COMMAND	0x00c1
 #define	TPM_TAG_RSP_COMMAND	0x00c4
 #define	TPM_ORD_SAVESTATE	0x00000098
+#define	TPM_ORD_GETRANDOM	0x00000046
 #define	TPM_WARN_RETRY		0x00000800
 
 #define	TPM_SAVESTATE_RETRIES	50
 #define	TPM_SAVESTATE_RETRY_MS	100
 
+#define	TPM_HARVEST_SIZE	16
+#define	TPM_HARVEST_INTERVAL	10
+
 #define	TPM_ACCESS			0x0000	/* access register */
 #define	TPM_ACCESS_ESTABLISHMENT	0x01	/* establishment */
 #define	TPM_ACCESS_REQUEST_USE		0x02	/* request using locality */
@@ -196,6 +213,11 @@ int tpm_legacy_end(struct tpm_softc *, int, int);
 static int tpm_attach_finish(struct tpm_softc *);
 static void tpm_delayed_attach(void *);
 static int tpm_transmit_header(struct tpm_softc *, uint32_t, uint32_t *);
+#if defined TPM_HARVEST || defined RANDOM_ENABLE_TPM
+static int tpm_get_random(struct tpm_softc *, uint8_t *, size_t *);
+static void tpm_harvest(void *, int);
+static void tpm_harvest_enqueue(const uint8_t *, size_t);
+#endif
 static void tpm_tis12_abort(struct tpm_softc *);
 static int tpm_tis12_devid_index(uint32_t);
 static int tpm_tis12_quiesce(struct tpm_softc *);
@@ -254,6 +276,9 @@ tpm_attach(device_t dev)
 	sc->sc_intr_received = false;
 	sc->sc_irq_config.valid = false;
 	TASK_INIT(&sc->sc_intr_task, 0, tpm_tis12_intr_task, sc);
+#if defined TPM_HARVEST || defined RANDOM_ENABLE_TPM
+	sc->sc_harvest_registered = false;
+#endif
 
 	sc->mem_rid = 0;
 	sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->mem_rid,
@@ -366,7 +391,19 @@ tpm_attach_finish(struct tpm_softc *sc)
 	args.mda_gid = GID_WHEEL;
 	args.mda_mode = 0600;
 	args.mda_si_drv1 = sc;
-	return (make_dev_s(&args, &sc->sc_cdev, "tpm"));
+	error = make_dev_s(&args, &sc->sc_cdev, "tpm");
+	if (error != 0)
+		return (error);
+
+#if defined TPM_HARVEST || defined RANDOM_ENABLE_TPM
+	TIMEOUT_TASK_INIT(taskqueue_thread, &sc->sc_harvest_task, 0,
+	    tpm_harvest, sc);
+	random_source_register(&random_tpm12);
+	sc->sc_harvest_registered = true;
+	taskqueue_enqueue_timeout(taskqueue_thread, &sc->sc_harvest_task, 0);
+#endif
+
+	return (0);
 }
 
 static void
@@ -401,6 +438,15 @@ tpm_detach(device_t dev)
 	sx_xunlock(&sc->sc_lock);
 	taskqueue_drain(taskqueue_thread, &sc->sc_intr_task);
 
+#if defined TPM_HARVEST || defined RANDOM_ENABLE_TPM
+	if (sc->sc_harvest_registered) {
+		taskqueue_drain_timeout(taskqueue_thread,
+		    &sc->sc_harvest_task);
+		random_source_deregister(&random_tpm12);
+		sc->sc_harvest_registered = false;
+	}
+#endif
+
 	/*
 	 * Prevent new methods from touching the transport.  Do not hold the
 	 * lock while destroy_dev() drains methods and cdevpriv destructors
@@ -1039,6 +1085,142 @@ tpm_transmit_header(struct tpm_softc *sc, uint32_t ordinal, uint32_t *tpm_rc)
 	return (0);
 }
 
+#if defined TPM_HARVEST || defined RANDOM_ENABLE_TPM
+/*
+ * Fetch random bytes as one command/response transaction.  The caller owns
+ * sc_lock so a character-device request cannot be interposed between the
+ * write and read phases.
+ */
+static int
+tpm_get_random(struct tpm_softc *sc, uint8_t *entropy, size_t *entropy_size)
+{
+	uint8_t command[TPM_HDRSIZE + sizeof(uint32_t)];
+	uint8_t response[TPM_HDRSIZE + sizeof(uint32_t) + TPM_HARVEST_SIZE];
+	size_t count, response_size;
+	uint32_t random_size, tpm_rc;
+	int end_error, error;
+
+	sx_assert(&sc->sc_lock, SA_XLOCKED);
+	*entropy_size = 0;
+	random_size = 0;
+
+	be16enc(command, TPM_TAG_RQU_COMMAND);
+	be32enc(command + 2, sizeof(command));
+	be32enc(command + 6, TPM_ORD_GETRANDOM);
+	be32enc(command + TPM_HDRSIZE, TPM_HARVEST_SIZE);
+
+	error = sc->sc_start(sc, UIO_WRITE);
+	if (error != 0)
+		return (error);
+	error = sc->sc_write(sc, command, sizeof(command));
+	end_error = sc->sc_end(sc, UIO_WRITE, error);
+	if (error == 0)
+		error = end_error;
+	if (error != 0)
+		return (error);
+
+	error = sc->sc_start(sc, UIO_READ);
+	if (error != 0)
+		return (error);
+	count = 0;
+	error = sc->sc_read(sc, response, TPM_HDRSIZE, &count,
+	    TPM_PARAM_SIZE);
+	if (error != 0)
+		goto end;
+	if (count != TPM_HDRSIZE ||
+	    be16dec(response) != TPM_TAG_RSP_COMMAND) {
+		error = EPROTO;
+		goto end;
+	}
+
+	response_size = be32dec(response + 2);
+	if (response_size < TPM_HDRSIZE || response_size > sizeof(response)) {
+		error = EPROTO;
+		goto end;
+	}
+	if (response_size > TPM_HDRSIZE) {
+		count = 0;
+		error = sc->sc_read(sc, response + TPM_HDRSIZE,
+		    (int)(response_size - TPM_HDRSIZE), &count,
+		    TPM_PARAM_SIZE);
+		if (error != 0)
+			goto end;
+		if (count != response_size - TPM_HDRSIZE) {
+			error = EIO;
+			goto end;
+		}
+	}
+
+	tpm_rc = be32dec(response + 6);
+	if (tpm_rc != 0) {
+		error = EIO;
+		goto end;
+	}
+	if (response_size < TPM_HDRSIZE + sizeof(uint32_t)) {
+		error = EPROTO;
+		goto end;
+	}
+	random_size = be32dec(response + TPM_HDRSIZE);
+	if (random_size > response_size - TPM_HDRSIZE - sizeof(uint32_t)) {
+		error = EPROTO;
+		goto end;
+	}
+	random_size = MIN(random_size, TPM_HARVEST_SIZE);
+
+end:
+	end_error = sc->sc_end(sc, UIO_READ, error);
+	if (error == 0)
+		error = end_error;
+	if (error == 0) {
+		memcpy(entropy, response + TPM_HDRSIZE + sizeof(uint32_t),
+		    random_size);
+		*entropy_size = random_size;
+	}
+	return (error);
+}
+
+static void
+tpm_harvest_enqueue(const uint8_t *entropy, size_t entropy_size)
+{
+	size_t chunk_size;
+
+	/* Preserve all RNG bytes rather than squashing an oversized event. */
+	chunk_size = sizeof(((struct harvest_event *)0)->he_entropy);
+	while (entropy_size != 0) {
+		size_t size;
+
+		size = MIN(entropy_size, chunk_size);
+		random_harvest_queue(entropy, (u_int)size, RANDOM_PURE_TPM);
+		entropy += size;
+		entropy_size -= size;
+	}
+}
+
+static void
+tpm_harvest(void *arg, int pending)
+{
+	struct tpm_softc *sc;
+	uint8_t entropy[TPM_HARVEST_SIZE];
+	size_t entropy_size;
+
+	sc = arg;
+	entropy_size = 0;
+	sx_xlock(&sc->sc_lock);
+	if (sc->sc_dying || sc->sc_suspend != 0)
+		goto out;
+
+	/* Do not interpose between a user's command and response. */
+	if ((sc->sc_flags & TPM_OPEN) == 0 && !sc->sc_command_pending)
+		(void)tpm_get_random(sc, entropy, &entropy_size);
+	taskqueue_enqueue_timeout(taskqueue_thread, &sc->sc_harvest_task,
+	    hz * TPM_HARVEST_INTERVAL);
+out:
+	sx_xunlock(&sc->sc_lock);
+	if (entropy_size != 0)
+		tpm_harvest_enqueue(entropy, entropy_size);
+}
+#endif
+
 /* Save TPM state on suspend. */
 int
 tpm_suspend(device_t dev)
@@ -1053,6 +1235,11 @@ tpm_suspend(device_t dev)
 	 * invalidate that saved state, so retry SaveState before entering S3.
 	 */
 	sc = device_get_softc(dev);
+#if defined TPM_HARVEST || defined RANDOM_ENABLE_TPM
+	if (sc->sc_harvest_registered)
+		taskqueue_drain_timeout(taskqueue_thread,
+		    &sc->sc_harvest_task);
+#endif
 	sx_xlock(&sc->sc_lock);
 	if (sc->sc_dying) {
 		error = ENXIO;
@@ -1088,6 +1275,11 @@ tpm_suspend(device_t dev)
 
 out:
 	sx_xunlock(&sc->sc_lock);
+#if defined TPM_HARVEST || defined RANDOM_ENABLE_TPM
+	if (error != 0 && sc->sc_harvest_registered)
+		taskqueue_enqueue_timeout(taskqueue_thread,
+		    &sc->sc_harvest_task, hz * TPM_HARVEST_INTERVAL);
+#endif
 	return (error);
 }
 
@@ -1117,6 +1309,11 @@ tpm_resume(device_t dev)
 
 out:
 	sx_xunlock(&sc->sc_lock);
+#if defined TPM_HARVEST || defined RANDOM_ENABLE_TPM
+	if (error == 0 && sc->sc_harvest_registered)
+		taskqueue_enqueue_timeout(taskqueue_thread,
+		    &sc->sc_harvest_task, hz * TPM_HARVEST_INTERVAL);
+#endif
 	return (error);
 }
 
diff --git a/sys/dev/tpm/tpmvar.h b/sys/dev/tpm/tpmvar.h
index 12cee8e92c90..24e928c25f8d 100644
--- a/sys/dev/tpm/tpmvar.h
+++ b/sys/dev/tpm/tpmvar.h
@@ -20,6 +20,8 @@
 #define _TPMVAR_H
 
 #ifdef __FreeBSD__
+#include "opt_tpm.h"
+
 #include <sys/condvar.h>
 #include <sys/kernel.h>
 #include <sys/lock.h>
@@ -77,6 +79,10 @@ struct tpm_softc {
 	bool sc_dying;
 	bool sc_locality;
 	bool sc_command_pending;
+#if defined TPM_HARVEST || defined RANDOM_ENABLE_TPM
+	struct timeout_task sc_harvest_task;
+	bool sc_harvest_registered;
+#endif
 #endif
 
 #ifndef __FreeBSD__
diff --git a/sys/riscv/conf/GENERIC b/sys/riscv/conf/GENERIC
index 7b47bd1ac407..55449f779b69 100644
--- a/sys/riscv/conf/GENERIC
+++ b/sys/riscv/conf/GENERIC
@@ -218,7 +218,7 @@ device		iicoc		# OpenCores I2C controller support
 
 # random(4)
 device		tpm			# Trusted Platform Module
-options 	RANDOM_ENABLE_TPM	# enable entropy from TPM 2.0
+options 	RANDOM_ENABLE_TPM	# enable entropy from TPM devices
 
 # Include SoC specific configuration
 include		"std.allwinner"