Module Name:    src
Committed By:   riastradh
Date:           Sat May 29 08:45:29 UTC 2021

Modified Files:
        src/sys/dev/ic: tpm.c tpmreg.h

Log Message:
tpm(4): Handle TPM 2.0 random source too, and loop on short reads.

Tested on ThinkPad T480.


To generate a diff of this commit:
cvs rdiff -u -r1.20 -r1.21 src/sys/dev/ic/tpm.c
cvs rdiff -u -r1.8 -r1.9 src/sys/dev/ic/tpmreg.h

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/sys/dev/ic/tpm.c
diff -u src/sys/dev/ic/tpm.c:1.20 src/sys/dev/ic/tpm.c:1.21
--- src/sys/dev/ic/tpm.c:1.20	Sat May 22 01:24:27 2021
+++ src/sys/dev/ic/tpm.c	Sat May 29 08:45:29 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: tpm.c,v 1.20 2021/05/22 01:24:27 thorpej Exp $	*/
+/*	$NetBSD: tpm.c,v 1.21 2021/05/29 08:45:29 riastradh Exp $	*/
 
 /*
  * Copyright (c) 2019 The NetBSD Foundation, Inc.
@@ -48,7 +48,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: tpm.c,v 1.20 2021/05/22 01:24:27 thorpej Exp $");
+__KERNEL_RCSID(0, "$NetBSD: tpm.c,v 1.21 2021/05/29 08:45:29 riastradh Exp $");
 
 #include <sys/param.h>
 #include <sys/types.h>
@@ -327,10 +327,9 @@ tpm_tis12_probe(bus_space_tag_t bt, bus_
 	return 0;
 }
 
-static void
-tpm_tis12_rng_work(struct work *wk, void *cookie)
+static int
+tpm12_rng(struct tpm_softc *sc, unsigned *entropybitsp)
 {
-	struct tpm_softc *sc = cookie;
 	/*
 	 * TPM Specification Version 1.2, Main Part 3: Commands,
 	 * Sec. 13.6 TPM_GetRandom
@@ -344,25 +343,12 @@ tpm_tis12_rng_work(struct work *wk, void
 		uint32_t randomBytesSize;
 		uint8_t	bytes[64];
 	} __packed response;
-	bool busy, endwrite = false, endread = false;
+	bool endwrite = false, endread = false;
 	size_t nread;
 	uint16_t tag;
-	uint32_t pktlen, code, nbytes;
+	uint32_t pktlen, code, nbytes, entropybits = 0;
 	int rv;
 
-	/* Acknowledge the request.  */
-	sc->sc_rndpending = 0;
-
-	/* Lock userland out of the tpm, or fail if it's already open.  */
-	mutex_enter(&sc->sc_lock);
-	busy = sc->sc_busy;
-	sc->sc_busy = true;
-	mutex_exit(&sc->sc_lock);
-	if (busy) {		/* tough */
-		aprint_debug_dev(sc->sc_dev, "%s: device in use\n", __func__);
-		return;
-	}
-
 	/* Encode the command.  */
 	memset(&command, 0, sizeof(command));
 	command.hdr.tag = htobe16(TPM_TAG_RQU_COMMAND);
@@ -465,9 +451,199 @@ tpm_tis12_rng_work(struct work *wk, void
 	 * perhaps, cargocultily) estimate half a bit of entropy per
 	 * bit of data.
 	 */
-	rnd_add_data(&sc->sc_rnd, response.bytes, nbytes, (NBBY/2)*nbytes);
+	CTASSERT(sizeof(response.bytes) <= UINT_MAX/(NBBY/2));
+	entropybits = (NBBY/2)*nbytes;
+	rnd_add_data(&sc->sc_rnd, response.bytes, nbytes, entropybits);
+
+out:	/* End the read or write if still ongoing.  */
+	if (endread)
+		rv = (*sc->sc_intf->end)(sc, UIO_READ, rv);
+	if (endwrite)
+		rv = (*sc->sc_intf->end)(sc, UIO_WRITE, rv);
+
+	*entropybitsp = entropybits;
+	return rv;
+}
+
+static int
+tpm20_rng(struct tpm_softc *sc, unsigned *entropybitsp)
+{
+	/*
+	 * Trusted Platform Module Library, Family "2.0", Level 00
+	 * Revision 01.38, Part 3: Commands, Sec. 16.1 `TPM2_GetRandom'
+	 *
+	 * https://trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-3-Commands-01.38.pdf#page=133
+	 */
+	struct {
+		struct tpm_header hdr;
+		uint16_t bytesRequested;
+	} __packed command;
+	struct response {
+		struct tpm_header hdr;
+		uint16_t randomBytesSize;
+		uint8_t bytes[64];
+	} __packed response;
+	bool endwrite = false, endread = false;
+	size_t nread;
+	uint16_t tag;
+	uint32_t pktlen, code, nbytes, entropybits = 0;
+	int rv;
+
+	/* Encode the command.  */
+	memset(&command, 0, sizeof(command));
+	command.hdr.tag = htobe16(TPM2_ST_NO_SESSIONS);
+	command.hdr.length = htobe32(sizeof(command));
+	command.hdr.code = htobe32(TPM2_CC_GetRandom);
+	command.bytesRequested = htobe16(sizeof(response.bytes));
+
+	/* Write the command.   */
+	if ((rv = (*sc->sc_intf->start)(sc, UIO_WRITE)) != 0) {
+		device_printf(sc->sc_dev, "start write failed, error=%d\n",
+		    rv);
+		goto out;
+	}
+	endwrite = true;
+	if ((rv = (*sc->sc_intf->write)(sc, &command, sizeof(command))) != 0) {
+		device_printf(sc->sc_dev, "write failed, error=%d\n", rv);
+		goto out;
+	}
+	rv = (*sc->sc_intf->end)(sc, UIO_WRITE, 0);
+	endwrite = false;
+	if (rv) {
+		device_printf(sc->sc_dev, "end write failed, error=%d\n", rv);
+		goto out;
+	}
+
+	/* Read the response header.  */
+	if ((rv = (*sc->sc_intf->start)(sc, UIO_READ)) != 0) {
+		device_printf(sc->sc_dev, "start write failed, error=%d\n",
+		    rv);
+		goto out;
+	}
+	endread = true;
+	if ((rv = (*sc->sc_intf->read)(sc, &response.hdr, sizeof(response.hdr),
+		    &nread, 0)) != 0) {
+		device_printf(sc->sc_dev, "read failed, error=%d\n", rv);
+		goto out;
+	}
+
+	/* Verify the response header looks sensible.  */
+	if (nread != sizeof(response.hdr)) {
+		device_printf(sc->sc_dev, "read %zu bytes, expected %zu",
+		    nread, sizeof(response.hdr));
+		goto out;
+	}
+	tag = be16toh(response.hdr.tag);
+	pktlen = be32toh(response.hdr.length);
+	code = be32toh(response.hdr.code);
+	if (tag != TPM2_ST_NO_SESSIONS ||
+	    pktlen < offsetof(struct response, bytes) ||
+	    pktlen > sizeof(response) ||
+	    code != 0) {
+		/*
+		 * If the tpm itself is busy (e.g., it has yet to run a
+		 * self-test, or it's in a timeout period to defend
+		 * against brute force attacks), then we can try again
+		 * later.  Otherwise, give up.
+		 */
+		if (code & TPM2_RC_WARN) {
+			aprint_debug_dev(sc->sc_dev, "%s: tpm busy,"
+			    " code=TPM_RC_WARN+0x%x\n",
+			    __func__, code & ~TPM2_RC_WARN);
+			rv = 0;
+		} else {
+			device_printf(sc->sc_dev, "bad tpm response:"
+			    " tag=%u len=%u code=0x%x\n", tag, pktlen, code);
+			hexdump(aprint_debug, "tpm response header",
+			    (const void *)&response.hdr,
+			    sizeof(response.hdr));
+			rv = EIO;
+		}
+		goto out;
+	}
+
+	/* Read the response payload.  */
+	if ((rv = (*sc->sc_intf->read)(sc,
+		    (char *)&response + nread, pktlen - nread,
+		    NULL, TPM_PARAM_SIZE)) != 0) {
+		device_printf(sc->sc_dev, "read failed, error=%d\n", rv);
+		goto out;
+	}
+	endread = false;
+	if ((rv = (*sc->sc_intf->end)(sc, UIO_READ, 0)) != 0) {
+		device_printf(sc->sc_dev, "end read failed, error=%d\n", rv);
+		goto out;
+	}
+
+	/* Verify the number of bytes read looks sensible.  */
+	nbytes = be16toh(response.randomBytesSize);
+	if (nbytes > pktlen - offsetof(struct response, bytes)) {
+		device_printf(sc->sc_dev, "overlong GetRandom length:"
+		    " %u, max %zu\n",
+		    nbytes, pktlen - offsetof(struct response, bytes));
+		nbytes = pktlen - offsetof(struct response, bytes);
+	}
+
+	/*
+	 * Enter the data into the entropy pool.  Conservatively (or,
+	 * perhaps, cargocultily) estimate half a bit of entropy per
+	 * bit of data.
+	 */
+	CTASSERT(sizeof(response.bytes) <= UINT_MAX/(NBBY/2));
+	entropybits = (NBBY/2)*nbytes;
+	rnd_add_data(&sc->sc_rnd, response.bytes, nbytes, entropybits);
+
+out:	/* End the read or write if still ongoing.  */
+	if (endread)
+		rv = (*sc->sc_intf->end)(sc, UIO_READ, rv);
+	if (endwrite)
+		rv = (*sc->sc_intf->end)(sc, UIO_WRITE, rv);
+
+	*entropybitsp = entropybits;
+	return rv;
+}
+
+static void
+tpm_rng_work(struct work *wk, void *cookie)
+{
+	struct tpm_softc *sc = cookie;
+	unsigned nbytes, entropybits;
+	bool busy;
+	int rv;
+
+	/* Acknowledge the request.  */
+	nbytes = atomic_swap_uint(&sc->sc_rndpending, 0);
+
+	/* Lock userland out of the tpm, or fail if it's already open.  */
+	mutex_enter(&sc->sc_lock);
+	busy = sc->sc_busy;
+	sc->sc_busy = true;
+	mutex_exit(&sc->sc_lock);
+	if (busy) {		/* tough */
+		aprint_debug_dev(sc->sc_dev, "%s: device in use\n", __func__);
+		return;
+	}
+
+	/*
+	 * Issue as many commands as needed to fulfill the request, but
+	 * stop if anything fails.
+	 */
+	for (; nbytes; nbytes -= MIN(nbytes, MAX(1, entropybits/NBBY))) {
+		switch (sc->sc_ver) {
+		case TPM_1_2:
+			rv = tpm12_rng(sc, &entropybits);
+			break;
+		case TPM_2_0:
+			rv = tpm20_rng(sc, &entropybits);
+			break;
+		default:
+			panic("bad tpm version: %d", sc->sc_ver);
+		}
+		if (rv)
+			break;
+	}
 
-out:	/*
+	/*
 	 * If the tpm is busted, no sense in trying again -- most
 	 * likely, it is deactivated, and by the spec it cannot be
 	 * reactivated until after a reboot.
@@ -478,12 +654,6 @@ out:	/*
 		/* XXX worker thread can't workqueue_destroy its own queue */
 	}
 
-	/* End the read or write if still ongoing.  */
-	if (endread)
-		rv = (*sc->sc_intf->end)(sc, UIO_READ, rv);
-	if (endwrite)
-		rv = (*sc->sc_intf->end)(sc, UIO_WRITE, rv);
-
 	/* Relinquish the tpm back to userland.  */
 	mutex_enter(&sc->sc_lock);
 	KASSERT(sc->sc_busy);
@@ -492,11 +662,12 @@ out:	/*
 }
 
 static void
-tpm_tis12_rng_get(size_t nbytes, void *cookie)
+tpm_rng_get(size_t nbytes, void *cookie)
 {
 	struct tpm_softc *sc = cookie;
 
-	if (atomic_swap_uint(&sc->sc_rndpending, 1) == 0)
+	if (atomic_swap_uint(&sc->sc_rndpending, MIN(nbytes, UINT_MAX/NBBY))
+	    == 0)
 		workqueue_enqueue(sc->sc_rndwq, &sc->sc_rndwk, NULL);
 }
 
@@ -521,9 +692,9 @@ tpm_tis12_init(struct tpm_softc *sc)
 
 	/* XXX Run this at higher priority?  */
 	if ((rv = workqueue_create(&sc->sc_rndwq, device_xname(sc->sc_dev),
-		    tpm_tis12_rng_work, sc, PRI_NONE, IPL_VM, WQ_MPSAFE)) != 0)
+		    tpm_rng_work, sc, PRI_NONE, IPL_VM, WQ_MPSAFE)) != 0)
 		return rv;
-	rndsource_setcb(&sc->sc_rnd, tpm_tis12_rng_get, sc);
+	rndsource_setcb(&sc->sc_rnd, tpm_rng_get, sc);
 	rnd_attach_source(&sc->sc_rnd, device_xname(sc->sc_dev),
 	    RND_TYPE_RNG,
 	    RND_FLAG_COLLECT_VALUE|RND_FLAG_ESTIMATE_VALUE|RND_FLAG_HASCB);

Index: src/sys/dev/ic/tpmreg.h
diff -u src/sys/dev/ic/tpmreg.h:1.8 src/sys/dev/ic/tpmreg.h:1.9
--- src/sys/dev/ic/tpmreg.h:1.8	Mon Jan  4 18:26:59 2021
+++ src/sys/dev/ic/tpmreg.h	Sat May 29 08:45:29 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: tpmreg.h,v 1.8 2021/01/04 18:26:59 riastradh Exp $	*/
+/*	$NetBSD: tpmreg.h,v 1.9 2021/05/29 08:45:29 riastradh Exp $	*/
 
 /*
  * Copyright (c) 2019 The NetBSD Foundation, Inc.
@@ -134,4 +134,38 @@ struct tpm_header {
 
 #define	TPM_NON_FATAL			0x800
 
+/* -------------------------------------------------------------------------- */
+
+/*
+ * Trusted Platform Module Library Specification, Family "2.0",
+ * Level 00, Revision 01.59 -- November 2019
+ *
+ * https://trustedcomputinggroup.org/resource/tpm-library-specification/
+ *
+ * Where this spec names things TPM_* that don't obviously coincide
+ * with the 1.2 things, we name them TPM2_*.
+ */
+
+/* https://trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-4-Supporting-Routines-01.38-code.pdf#page=172 */
+#define	TPM2_ST_RSP_COMMAND		0x00c4
+#define	TPM2_ST_NULL			0x8000
+#define	TPM2_ST_NO_SESSIONS		0x8001
+#define	TPM2_ST_SESSIONS		0x8002
+/* ... */
+
+/* https://trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-2-Structures-01.38.pdf#page=45 */
+#define	TPM2_CC_GetRandom		0x0000017b
+
+/* https://trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-2-Structures-01.38.pdf#page=53 */
+#define	TPM2_RC_SUCCESS			0x000
+#define	TPM2_RC_BAD_TAG			0x01e
+
+#define	TPM2_RC_VER1			0x100
+
+#define	TPM2_RC_FMT1			0x080
+
+#define	TPM2_RC_WARN			0x900
+#define	TPM2_RC_TESTING			(TPM2_RC_WARN + 0x00a)
+#define	TPM2_RC_RETRY			(TPM2_RC_WARN + 0x022)
+
 #endif	/* DEV_IC_TPMREG_H */

Reply via email to