Linux Power Management development
 help / color / mirror / Atom feed
From: Matthew Garrett <matthewg@nvidia.com>
To: mjg59@srcf.ucam.org
Cc: keyrings@vger.kernel.org, James.Bottomley@HansenPartnership.com,
	linux-integrity@vger.kernel.org, rafael@kernel.org,
	linux-pm@vger.kernel.org, linux-efi@vger.kernel.org,
	Matthew Garrett <matthewg@nvidia.com>
Subject: [PATCH 08/17] tpm: Add NV define, undefine and write helpers
Date: Thu,  8 Oct 2026 06:20:24 -0700	[thread overview]
Message-ID: <20261008132532.1155166-9-matthewg@nvidia.com> (raw)
In-Reply-To: <20261008132532.1155166-1-matthewg@nvidia.com>

Add tpm2_nv_define(), tpm2_nv_undefine() and tpm2_nv_write() alongside
tpm2_nv_read(). Definition and removal use the owner hierarchy, and
indices are defined as ordinary indices with an empty auth value and no
policy. As with reads, the commands use the kernel's HMAC session when
available, with writes encrypting the data, and use an active audit
session if there is one.

Factor the session setup and transmit/check sequence shared with
tpm2_nv_read() into helpers to reduce boilerplate duplication, and add
the TPMA_NV attribute bits needed to define an index.

Signed-off-by: Matthew Garrett <matthewg@nvidia.com>
---
 drivers/char/tpm/tpm.h      |   4 +
 drivers/char/tpm/tpm2-cmd.c | 229 +++++++++++++++++++++++++++++++++---
 include/linux/tpm_command.h |   8 ++
 3 files changed, 222 insertions(+), 19 deletions(-)

diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h
index 5bd3b5658909..880327e3faa9 100644
--- a/drivers/char/tpm/tpm.h
+++ b/drivers/char/tpm/tpm.h
@@ -115,6 +115,10 @@ int tpm2_pcr_extend(struct tpm_chip *chip, u32 pcr_idx,
 int tpm2_get_random(struct tpm_chip *chip, u8 *dest, size_t max);
 int tpm2_nv_read(struct tpm_chip *chip, u32 nv_index, u32 auth_handle,
 		 u16 offset, u8 *data, u16 len);
+int tpm2_nv_write(struct tpm_chip *chip, u32 nv_index, u32 auth_handle,
+		  u16 offset, const u8 *data, u16 len);
+int tpm2_nv_define(struct tpm_chip *chip, u32 nv_index, u32 attrs, u16 size);
+int tpm2_nv_undefine(struct tpm_chip *chip, u32 nv_index);
 ssize_t tpm2_get_tpm_pt(struct tpm_chip *chip, u32 property_id,
 			u32 *value, const char *desc);
 
diff --git a/drivers/char/tpm/tpm2-cmd.c b/drivers/char/tpm/tpm2-cmd.c
index 5c2831ee3981..0de7bcd9ec07 100644
--- a/drivers/char/tpm/tpm2-cmd.c
+++ b/drivers/char/tpm/tpm2-cmd.c
@@ -326,6 +326,212 @@ int tpm2_get_random(struct tpm_chip *chip, u8 *dest, size_t max)
 /* Minimum MAX_NV_BUFFER_SIZE required by the PC Client TPM profile */
 #define TPM2_NV_BUFFER_MIN	512
 
+static u32 tpm2_nv_buffer_max(struct tpm_chip *chip)
+{
+	u32 val;
+
+	if (tpm2_get_tpm_pt(chip, TPM2_PT_NV_BUFFER_MAX, &val, NULL) || !val)
+		return TPM2_NV_BUFFER_MIN;
+
+	return val;
+}
+
+/*
+ * Start an HMAC session for an NV command.  -EBUSY means an audit session
+ * is already active, which is used instead so the command is audited.
+ */
+static int tpm2_nv_session_begin(struct tpm_chip *chip)
+{
+	int rc = tpm2_start_auth_session(chip, false);
+
+	return rc == -EBUSY ? 0 : rc;
+}
+
+/*
+ * Send a command built with HMAC sessions and check the response.  A TPM
+ * error such as a missing index leaves the session usable, so it is only
+ * ended on a system error.
+ */
+static int tpm2_nv_transmit(struct tpm_chip *chip, struct tpm_buf *buf,
+			    const char *desc)
+{
+	int rc;
+
+	rc = tpm_buf_fill_hmac_session(chip, buf);
+	if (rc)
+		return rc;
+
+	rc = tpm_transmit_cmd(chip, buf, 0, desc);
+	rc = tpm_buf_check_hmac_response(chip, buf, rc);
+	if (rc < 0)
+		tpm2_end_auth_session(chip);
+
+	return rc;
+}
+
+/**
+ * tpm2_nv_define() - define an ordinary NV index in the owner hierarchy
+ * @chip:	TPM chip to use
+ * @nv_index:	the NV index to define
+ * @attrs:	TPMA_NV attributes of the index
+ * @size:	size of the index in bytes
+ *
+ * The index is given an empty auth value and no policy.  The owner
+ * hierarchy must have an empty auth value.
+ *
+ * Return: same as with tpm_transmit_cmd()
+ */
+int tpm2_nv_define(struct tpm_chip *chip, u32 nv_index, u32 attrs, u16 size)
+{
+	struct tpm_buf *buf __free(kfree) = NULL;
+	int rc;
+
+	rc = tpm2_nv_session_begin(chip);
+	if (rc)
+		return rc;
+
+	buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+	if (!buf) {
+		tpm2_end_auth_session(chip);
+		return -ENOMEM;
+	}
+
+	tpm_buf_init(buf, TPM_BUFSIZE);
+	tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_NV_DEFINE_SPACE);
+
+	rc = tpm_buf_append_name(chip, buf, TPM2_RH_OWNER, NULL);
+	if (rc)
+		return rc;
+
+	tpm_buf_append_hmac_session(chip, buf, TPM2_SA_CONTINUE_SESSION,
+				    NULL, 0);
+
+	/* auth (empty) */
+	tpm_buf_append_u16(buf, 0);
+
+	/* publicInfo: TPM2B_NV_PUBLIC */
+	tpm_buf_append_u16(buf, sizeof(u32) + sizeof(u16) + sizeof(u32) +
+			   sizeof(u16) + sizeof(u16));
+	tpm_buf_append_u32(buf, nv_index);
+	tpm_buf_append_u16(buf, TPM_ALG_SHA256);
+	tpm_buf_append_u32(buf, attrs);
+	/* authPolicy (empty) */
+	tpm_buf_append_u16(buf, 0);
+	tpm_buf_append_u16(buf, size);
+
+	return tpm2_nv_transmit(chip, buf, "attempting to define NV index");
+}
+
+/**
+ * tpm2_nv_undefine() - undefine an NV index in the owner hierarchy
+ * @chip:	TPM chip to use
+ * @nv_index:	the NV index to undefine
+ *
+ * The owner hierarchy must have an empty auth value.
+ *
+ * Return: same as with tpm_transmit_cmd()
+ */
+int tpm2_nv_undefine(struct tpm_chip *chip, u32 nv_index)
+{
+	struct tpm_buf *buf __free(kfree) = NULL;
+	int rc;
+
+	rc = tpm2_nv_session_begin(chip);
+	if (rc)
+		return rc;
+
+	buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+	if (!buf) {
+		tpm2_end_auth_session(chip);
+		return -ENOMEM;
+	}
+
+	tpm_buf_init(buf, TPM_BUFSIZE);
+	tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_NV_UNDEFINE_SPACE);
+
+	rc = tpm_buf_append_name(chip, buf, TPM2_RH_OWNER, NULL);
+	if (rc)
+		return rc;
+	rc = tpm_buf_append_name(chip, buf, nv_index, NULL);
+	if (rc)
+		return rc;
+
+	tpm_buf_append_hmac_session(chip, buf, TPM2_SA_CONTINUE_SESSION,
+				    NULL, 0);
+
+	return tpm2_nv_transmit(chip, buf, "attempting to undefine NV index");
+}
+
+/**
+ * tpm2_nv_write() - write data to an NV index
+ * @chip:	TPM chip to use
+ * @nv_index:	the NV index to write
+ * @auth_handle: the handle authorizing the write, which must have an empty
+ *		auth value
+ * @offset:	offset within the NV index to start writing at
+ * @data:	data to write
+ * @len:	number of bytes to write
+ *
+ * Writes are split into chunks no larger than the TPM's NV buffer.  When
+ * HMAC sessions are enabled the write is integrity protected and the data
+ * encrypted.
+ *
+ * Return: same as with tpm_transmit_cmd()
+ */
+int tpm2_nv_write(struct tpm_chip *chip, u32 nv_index, u32 auth_handle,
+		  u16 offset, const u8 *data, u16 len)
+{
+	struct tpm_buf *buf __free(kfree) = NULL;
+	u32 chunk_max;
+	int rc;
+
+	if ((u32)offset + len > U16_MAX + 1)
+		return -EINVAL;
+
+	chunk_max = tpm2_nv_buffer_max(chip);
+
+	rc = tpm2_nv_session_begin(chip);
+	if (rc)
+		return rc;
+
+	buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+	if (!buf) {
+		tpm2_end_auth_session(chip);
+		return -ENOMEM;
+	}
+
+	tpm_buf_init(buf, TPM_BUFSIZE);
+
+	while (len) {
+		u16 chunk = min_t(u32, len, chunk_max);
+
+		tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_NV_WRITE);
+
+		rc = tpm_buf_append_name(chip, buf, auth_handle, NULL);
+		if (rc)
+			return rc;
+		rc = tpm_buf_append_name(chip, buf, nv_index, NULL);
+		if (rc)
+			return rc;
+
+		tpm_buf_append_hmac_session(chip, buf, TPM2_SA_DECRYPT |
+					    TPM2_SA_CONTINUE_SESSION, NULL, 0);
+		tpm_buf_append_u16(buf, chunk);
+		tpm_buf_append(buf, data, chunk);
+		tpm_buf_append_u16(buf, offset);
+
+		rc = tpm2_nv_transmit(chip, buf, "attempting to write NV index");
+		if (rc)
+			return rc;
+
+		data += chunk;
+		offset += chunk;
+		len -= chunk;
+	}
+
+	return 0;
+}
+
 /**
  * tpm2_nv_read() - read data from an NV index
  * @chip:	TPM chip to use
@@ -354,13 +560,10 @@ int tpm2_nv_read(struct tpm_chip *chip, u32 nv_index, u32 auth_handle,
 	if ((u32)offset + len > U16_MAX + 1)
 		return -EINVAL;
 
-	if (tpm2_get_tpm_pt(chip, TPM2_PT_NV_BUFFER_MAX, &chunk_max, NULL) ||
-	    !chunk_max)
-		chunk_max = TPM2_NV_BUFFER_MIN;
+	chunk_max = tpm2_nv_buffer_max(chip);
 
-	rc = tpm2_start_auth_session(chip, false);
-	/* -EBUSY means an audit session is active, which we can use */
-	if (rc && rc != -EBUSY)
+	rc = tpm2_nv_session_begin(chip);
+	if (rc)
 		return rc;
 
 	buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
@@ -390,22 +593,10 @@ int tpm2_nv_read(struct tpm_chip *chip, u32 nv_index, u32 auth_handle,
 		tpm_buf_append_u16(buf, chunk);
 		tpm_buf_append_u16(buf, offset);
 
-		rc = tpm_buf_fill_hmac_session(chip, buf);
+		rc = tpm2_nv_transmit(chip, buf, "attempting to read NV index");
 		if (rc)
 			return rc;
 
-		rc = tpm_transmit_cmd(chip, buf, 0, "attempting to read NV index");
-		rc = tpm_buf_check_hmac_response(chip, buf, rc);
-		if (rc) {
-			/*
-			 * A TPM error such as a missing index leaves the
-			 * session usable, so only end it on a system error.
-			 */
-			if (rc < 0)
-				tpm2_end_auth_session(chip);
-			return rc;
-		}
-
 		/* skip the parameter size */
 		off = TPM_HEADER_SIZE + sizeof(u32);
 		if (tpm_buf_length(buf) < off + sizeof(u16))
diff --git a/include/linux/tpm_command.h b/include/linux/tpm_command.h
index 9c393f98bac2..c8e867d8bd4b 100644
--- a/include/linux/tpm_command.h
+++ b/include/linux/tpm_command.h
@@ -284,6 +284,14 @@ enum tpm2_permanent_handles {
 	TPM2_RH_ENDORSEMENT	= 0x4000000B,
 };
 
+/* TPMA_NV, for an ordinary NV index (TPM_NT_ORDINARY in bits 4-7) */
+enum tpm2_nv_attributes {
+	TPM2_NV_AUTHWRITE	= BIT(2),
+	TPM2_NV_AUTHREAD	= BIT(18),
+	TPM2_NV_NO_DA		= BIT(25),
+	TPM2_NV_WRITTEN		= BIT(29),
+};
+
 /* TPMS_ATTEST.magic */
 #define TPM2_GENERATED_VALUE	0xff544347
 
-- 
2.43.0


  parent reply	other threads:[~2026-10-08 13:26 UTC|newest]

Thread overview: 31+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-08 13:20 [RFC] Make hibernation work with lockdown Matthew Garrett
2026-10-08 13:20 ` [PATCH 01/17] tpm: Define a kernel-owned TPM NV index that can't be modified by userland Matthew Garrett
2026-10-08 13:41   ` Matthew Garrett
2026-10-08 16:24     ` Jarkko Sakkinen
2026-10-08 16:23   ` Jarkko Sakkinen
2026-10-09  8:33     ` Matthew Garrett
2026-10-08 17:06   ` Ilias Apalodimas
2026-10-08 13:20 ` [PATCH 02/17] efi: Add a mechanism to modify TPM state depending on kernel security features Matthew Garrett
2026-10-08 16:38   ` Jarkko Sakkinen
2026-10-08 13:20 ` [PATCH 03/17] tpm: Allow tpm2_start_auth_session() to start an audit session Matthew Garrett
2026-10-08 13:20 ` [PATCH 04/17] tpm: Log commands executed in " Matthew Garrett
2026-10-08 13:20 ` [PATCH 05/17] tpm: Add a kernel attestation key and signed audit digest retrieval Matthew Garrett
2026-10-08 16:45   ` James Bottomley
2026-10-09  8:29     ` Matthew Garrett
2026-10-08 13:20 ` [PATCH 06/17] tpm: Use TPM2_NV_ReadPublic to read NV index names Matthew Garrett
2026-10-08 13:20 ` [PATCH 07/17] tpm: Add in-kernel support for reading NV indices Matthew Garrett
2026-10-08 13:20 ` Matthew Garrett [this message]
2026-10-08 13:20 ` [PATCH 09/17] tpm: Provision the kernel NV index at registration Matthew Garrett
2026-10-08 13:20 ` [PATCH 10/17] tpm: Move the bounds-checked response reader to a header Matthew Garrett
2026-10-08 13:20 ` [PATCH 11/17] tpm: Add kernel signing key creation with audited provenance Matthew Garrett
2026-10-08 17:00   ` James Bottomley
2026-10-09  8:31     ` Matthew Garrett
2026-10-08 13:20 ` [PATCH 12/17] tpm: Add signing with the kernel signing key Matthew Garrett
2026-10-08 13:20 ` [PATCH 13/17] tpm: Add verification of kernel signing key provenance Matthew Garrett
2026-10-08 13:20 ` [PATCH 14/17] PM: hibernate: Add image digest and signature page infrastructure Matthew Garrett
2026-10-08 13:20 ` [PATCH 15/17] PM: hibernate: Sign and verify images with a kernel-generated TPM key Matthew Garrett
2026-10-08 13:20 ` [PATCH 16/17] PM: hibernate: Refuse to verify images with a virtual TPM Matthew Garrett
2026-10-08 13:20 ` [PATCH 17/17] PM: hibernate: Allow hibernation under lockdown with signed images Matthew Garrett
2026-10-08 16:53   ` Jarkko Sakkinen
2026-10-09  8:31     ` Matthew Garrett
2026-10-08 15:56 ` [RFC] Make hibernation work with lockdown Jarkko Sakkinen

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261008132532.1155166-9-matthewg@nvidia.com \
    --to=matthewg@nvidia.com \
    --cc=James.Bottomley@HansenPartnership.com \
    --cc=keyrings@vger.kernel.org \
    --cc=linux-efi@vger.kernel.org \
    --cc=linux-integrity@vger.kernel.org \
    --cc=linux-pm@vger.kernel.org \
    --cc=mjg59@srcf.ucam.org \
    --cc=rafael@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox