git: 74c539fd1cef - main - tpm20: Move user copies outside the lifecycle lock

From: Kevin Bowling <kbowling_at_FreeBSD.org>
Date: Thu, 03 Sep 2026 04:05:14 UTC
The branch main has been updated by kbowling:

URL: https://cgit.FreeBSD.org/src/commit/?id=74c539fd1cefdbc993411274c613434c3f23b4b1

commit 74c539fd1cefdbc993411274c613434c3f23b4b1
Author:     Kevin Bowling <kbowling@FreeBSD.org>
AuthorDate: 2026-08-27 14:06:55 +0000
Commit:     Kevin Bowling <kbowling@FreeBSD.org>
CommitDate: 2026-09-03 04:03:32 +0000

    tpm20: Move user copies outside the lifecycle lock
    
    The TPM 2.0 character-device methods held the global device lock
    while uiomove() accessed user memory.  User page faults could therefore
    delay suspend or detach even though the read response was already
    buffered.
    
    Add a per-open sleepable lock to serialize operations on each response
    buffer.  Stage commands under that lock before acquiring the device
    lock, and copy them into the response buffer only after the lifecycle
    checks succeed.  This preserves an unread response when suspend or
    detach rejects a write.  Release the device lock before copying buffered
    responses out.  Also advance the response offset by the bytes actually
    copied when uiomove() returns after a partial transfer.
    
    Validated on an Intel TPM 2.0 TIS device.  PCR reads and GetRandom
    passed under 16-process mixed command load.  A response was consumed
    correctly in 5-byte, 7-byte, and remainder reads.  Module unload/reload
    recreated the device and entropy source without lock diagnostics.
    Source inspection confirmed rejected writes preserve unread responses.
    
    Reviewed by:    kevans
    MFC after:      2 weeks
    Sponsored by:   BBOX.io
    Differential Revision:  https://reviews.freebsd.org/D59245
---
 sys/dev/tpm/tpm20.c | 37 +++++++++++++++++++++++++++++--------
 sys/dev/tpm/tpm20.h |  1 +
 2 files changed, 30 insertions(+), 8 deletions(-)

diff --git a/sys/dev/tpm/tpm20.c b/sys/dev/tpm/tpm20.c
index bebf0af400fa..6c96f1ce848d 100644
--- a/sys/dev/tpm/tpm20.c
+++ b/sys/dev/tpm/tpm20.c
@@ -86,6 +86,7 @@ tpm20_read(struct cdev *dev, struct uio *uio, int flags)
 	struct tpm_priv *priv;
 	size_t bytes_to_transfer;
 	size_t offset;
+	ssize_t resid;
 	int result;
 
 	sc = (struct tpm_sc *)dev->si_drv1;
@@ -93,27 +94,36 @@ tpm20_read(struct cdev *dev, struct uio *uio, int flags)
 	if (result != 0)
 		return (result);
 
+	sx_xlock(&priv->io_lock);
 	sx_xlock(&sc->dev_lock);
 	if (atomic_load_bool(&sc->dying)) {
 		result = ENXIO;
-		goto out;
+		goto out_locked;
 	}
 	if (sc->suspended) {
 		result = EBUSY;
-		goto out;
+		goto out_locked;
 	}
 	offset = priv->offset;
 	bytes_to_transfer = MIN(priv->len, uio->uio_resid);
+	sx_xunlock(&sc->dev_lock);
+
 	if (bytes_to_transfer > 0) {
-		result = uiomove((caddr_t) priv->buf + offset, bytes_to_transfer, uio);
+		resid = uio->uio_resid;
+		result = uiomove((caddr_t)priv->buf + offset,
+		    (int)bytes_to_transfer, uio);
+		bytes_to_transfer = resid - uio->uio_resid;
 		priv->offset += bytes_to_transfer;
 		priv->len -= bytes_to_transfer;
 	} else {
 		result = 0;
 	}
+	sx_xunlock(&priv->io_lock);
+	return (result);
 
-out:
+out_locked:
 	sx_xunlock(&sc->dev_lock);
+	sx_xunlock(&priv->io_lock);
 	return (result);
 }
 
@@ -122,6 +132,7 @@ tpm20_write(struct cdev *dev, struct uio *uio, int flags)
 {
 	struct tpm_sc *sc;
 	struct tpm_priv *priv;
+	uint8_t *command;
 	size_t byte_count;
 	int result;
 
@@ -143,6 +154,12 @@ tpm20_write(struct cdev *dev, struct uio *uio, int flags)
 		return (E2BIG);
 	}
 
+	command = malloc(byte_count, M_TPM20, M_WAITOK);
+	sx_xlock(&priv->io_lock);
+	result = uiomove(command, byte_count, uio);
+	if (result != 0)
+		goto out_priv;
+
 	sx_xlock(&sc->dev_lock);
 	if (atomic_load_bool(&sc->dying)) {
 		result = ENXIO;
@@ -153,14 +170,16 @@ tpm20_write(struct cdev *dev, struct uio *uio, int flags)
 		goto out;
 	}
 
-	result = uiomove(priv->buf, byte_count, uio);
-	if (result != 0)
-		goto out;
-
+	memcpy(priv->buf, command, byte_count);
 	result = TPM_TRANSMIT(sc->dev, priv, byte_count);
 
 out:
 	sx_xunlock(&sc->dev_lock);
+	if (result != 0)
+		uio->uio_resid = byte_count;
+out_priv:
+	sx_xunlock(&priv->io_lock);
+	free(command, M_TPM20);
 	return (result);
 }
 
@@ -170,6 +189,7 @@ tpm20_priv_alloc(void)
 	struct tpm_priv *priv;
 
 	priv = malloc(sizeof (*priv), M_TPM20, M_WAITOK | M_ZERO);
+	sx_init(&priv->io_lock, "TPM per-open I/O lock");
 	return (priv);
 }
 
@@ -178,6 +198,7 @@ tpm20_priv_dtor(void *data)
 {
 	struct tpm_priv *priv = data;
 
+	sx_destroy(&priv->io_lock);
 	free(priv, M_TPM20);
 }
 
diff --git a/sys/dev/tpm/tpm20.h b/sys/dev/tpm/tpm20.h
index 1d7a11265b02..f210cd84c331 100644
--- a/sys/dev/tpm/tpm20.h
+++ b/sys/dev/tpm/tpm20.h
@@ -110,6 +110,7 @@
 MALLOC_DECLARE(M_TPM20);
 
 struct tpm_priv {
+	struct sx	io_lock;
 	uint8_t 	buf[TPM_BUFSIZE];
 	size_t		offset;
 	size_t		len;