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 09/17] tpm: Provision the kernel NV index at registration
Date: Thu, 8 Oct 2026 06:20:25 -0700 [thread overview]
Message-ID: <20261008132532.1155166-10-matthewg@nvidia.com> (raw)
In-Reply-To: <20261008132532.1155166-1-matthewg@nvidia.com>
The kernel NV index is going to be used to show that the kernel, and
not userspace, was in control of the TPM at a given point in time.
The kernel stores a magic value in it, runs the commands in an audit
session that reads the index, and resets it to zero before releasing
the TPM. Userspace cannot modify the index, so a log showing the magic
value can only have come from the kernel.
Before we expose the TPM to userland, make sure the index exists as an 8
byte ordinary index with an empty auth value, no policy, and the
expected attributes by comparing its name. An index with a different
public area, or one whose auth value turns out not to be empty, is
undefined and defined again to prevent userland or another OS from
leaving the magic value hanging around. The index is then reset to zero
if it holds anything else, which also clears a magic value left behind
by a crash. The owner hierarchy must have an empty auth value.
Add tpm2_kernel_nv_set_magic() and tpm2_kernel_nv_clear() for use by the
kernel while it holds the ops lock. Mark the chip whenever the index may
hold the magic value, and have tpm_dev_transmit() retry the reset and
refuse userspace commands while it is marked. The chip starts out
marked, so that a magic value left behind by a crash is never exposed,
and the mark is only removed once the index is known not to hold the
magic value. This means that if a region has been defined that the kernel
cannot remove (eg, because it was configured to require auth) we can fail
safe.
Signed-off-by: Matthew Garrett <matthewg@nvidia.com>
---
drivers/char/tpm/Makefile | 1 +
drivers/char/tpm/tpm-chip.c | 30 ++++
drivers/char/tpm/tpm-dev-common.c | 10 ++
drivers/char/tpm/tpm.h | 14 ++
drivers/char/tpm/tpm2-kernel-nv.c | 267 ++++++++++++++++++++++++++++++
include/linux/tpm.h | 3 +
include/linux/tpm_command.h | 1 +
7 files changed, 326 insertions(+)
create mode 100644 drivers/char/tpm/tpm2-kernel-nv.c
diff --git a/drivers/char/tpm/Makefile b/drivers/char/tpm/Makefile
index 10a4ed388dea..88a96fee0934 100644
--- a/drivers/char/tpm/Makefile
+++ b/drivers/char/tpm/Makefile
@@ -18,6 +18,7 @@ tpm-y += eventlog/tpm2.o
tpm-y += tpm-buf.o
tpm-y += tpm2-sessions.o
tpm-$(CONFIG_TCG_TPM2_HMAC) += tpm2-ak.o
+tpm-$(CONFIG_TCG_TPM2_HMAC) += tpm2-kernel-nv.o
tpm-$(CONFIG_ACPI) += tpm_ppi.o eventlog/acpi.o
tpm-$(CONFIG_EFI) += eventlog/efi.o
diff --git a/drivers/char/tpm/tpm-chip.c b/drivers/char/tpm/tpm-chip.c
index cb10f2d1eace..1ec294d081ae 100644
--- a/drivers/char/tpm/tpm-chip.c
+++ b/drivers/char/tpm/tpm-chip.c
@@ -584,6 +584,34 @@ EXPORT_SYMBOL_GPL(tpm_chip_bootstrap);
* This function should be only called after the chip initialization is
* complete.
*/
+/*
+ * Make sure the kernel NV index is defined and does not hold the magic
+ * value before userspace is given access to the TPM.
+ */
+static void tpm_chip_provision_kernel_nv(struct tpm_chip *chip)
+{
+ int rc;
+
+ if (!IS_ENABLED(CONFIG_TCG_TPM2_HMAC) ||
+ !(chip->flags & TPM_CHIP_FLAG_TPM2))
+ return;
+
+ /*
+ * A crash while the magic value was set leaves it in the index, so
+ * assume the worst until provisioning shows otherwise.
+ */
+ chip->flags |= TPM_CHIP_FLAG_KERNEL_NV_DIRTY;
+
+ if (tpm_try_get_ops(chip))
+ return;
+
+ rc = tpm2_kernel_nv_provision(chip);
+ tpm_put_ops(chip);
+
+ if (rc)
+ dev_warn(&chip->dev, "kernel NV index unavailable: %d\n", rc);
+}
+
int tpm_chip_register(struct tpm_chip *chip)
{
int rc;
@@ -602,6 +630,8 @@ int tpm_chip_register(struct tpm_chip *chip)
if (rc)
goto out_ppi;
+ tpm_chip_provision_kernel_nv(chip);
+
rc = tpm_add_char_device(chip);
if (rc)
goto out_hwrng;
diff --git a/drivers/char/tpm/tpm-dev-common.c b/drivers/char/tpm/tpm-dev-common.c
index 1fd93cdaf306..44323f55da51 100644
--- a/drivers/char/tpm/tpm-dev-common.c
+++ b/drivers/char/tpm/tpm-dev-common.c
@@ -94,6 +94,16 @@ static ssize_t tpm_dev_transmit(struct tpm_chip *chip, struct tpm_space *space,
tpm2_dev_cmd_is_blocked(buf, bufsiz))
return -EPERM;
+ /*
+ * Never give userspace access while the kernel NV index may hold
+ * the magic value.
+ */
+ if (chip->flags & TPM_CHIP_FLAG_KERNEL_NV_DIRTY) {
+ tpm2_kernel_nv_clear(chip);
+ if (chip->flags & TPM_CHIP_FLAG_KERNEL_NV_DIRTY)
+ return -EPERM;
+ }
+
if (chip->flags & TPM_CHIP_FLAG_TPM2)
tpm2_end_auth_session(chip);
diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h
index 880327e3faa9..54e8d5820b6d 100644
--- a/drivers/char/tpm/tpm.h
+++ b/drivers/char/tpm/tpm.h
@@ -152,11 +152,25 @@ void tpm_dev_common_exit(void);
int tpm2_sessions_init(struct tpm_chip *chip);
void tpm2_free_auth(struct tpm2_auth *auth);
int tpm2_audit_session_handle(struct tpm_chip *chip, u32 *handle);
+
+extern const u8 tpm2_kernel_nv_magic[TPM2_KERNEL_NV_SIZE];
+void tpm2_kernel_nv_name(bool written, u8 name[TPM2_NULL_NAME_SIZE]);
+int tpm2_kernel_nv_provision(struct tpm_chip *chip);
+int tpm2_kernel_nv_set_magic(struct tpm_chip *chip);
+int tpm2_kernel_nv_clear(struct tpm_chip *chip);
#else
static inline int tpm2_sessions_init(struct tpm_chip *chip)
{
return 0;
}
+static inline int tpm2_kernel_nv_provision(struct tpm_chip *chip)
+{
+ return 0;
+}
+static inline int tpm2_kernel_nv_clear(struct tpm_chip *chip)
+{
+ return 0;
+}
#endif
#endif
diff --git a/drivers/char/tpm/tpm2-kernel-nv.c b/drivers/char/tpm/tpm2-kernel-nv.c
new file mode 100644
index 000000000000..cd9ec0f24c2f
--- /dev/null
+++ b/drivers/char/tpm/tpm2-kernel-nv.c
@@ -0,0 +1,267 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Management of the kernel-owned NV index.
+ *
+ * TPM2_KERNEL_NV_INDEX is an 8 byte ordinary NV index with an empty auth
+ * value. Userspace is prevented from modifying it by the /dev/tpm command
+ * filter, so while it holds tpm2_kernel_nv_magic the kernel is the only
+ * party that can have put it there. The kernel sets the magic value only
+ * while it holds the chip's ops lock and resets the index to zero before
+ * releasing it, so userspace never observes the magic value.
+ */
+
+#include <linux/slab.h>
+#include <linux/unaligned.h>
+#include <crypto/sha2.h>
+#include "tpm.h"
+
+#define TPM2_KERNEL_NV_ATTRS \
+ (TPM2_NV_AUTHWRITE | TPM2_NV_AUTHREAD | TPM2_NV_NO_DA)
+
+const u8 tpm2_kernel_nv_magic[TPM2_KERNEL_NV_SIZE] = "LNXKEYGN";
+static const u8 tpm2_kernel_nv_zero[TPM2_KERNEL_NV_SIZE];
+
+/**
+ * tpm2_kernel_nv_name() - compute the expected name of the kernel NV index
+ * @written: whether the index has been written (TPMA_NV_WRITTEN is set)
+ * @name: filled with the name: the SHA256 algorithm ID followed by the
+ * hash of the index's TPMS_NV_PUBLIC
+ */
+void tpm2_kernel_nv_name(bool written, u8 name[TPM2_NULL_NAME_SIZE])
+{
+ u32 attrs = TPM2_KERNEL_NV_ATTRS | (written ? TPM2_NV_WRITTEN : 0);
+ u8 pub[14];
+
+ /* TPMS_NV_PUBLIC */
+ put_unaligned_be32(TPM2_KERNEL_NV_INDEX, &pub[0]);
+ put_unaligned_be16(TPM_ALG_SHA256, &pub[4]);
+ put_unaligned_be32(attrs, &pub[6]);
+ /* authPolicy (empty) */
+ put_unaligned_be16(0, &pub[10]);
+ put_unaligned_be16(TPM2_KERNEL_NV_SIZE, &pub[12]);
+
+ put_unaligned_be16(TPM_ALG_SHA256, name);
+ sha256(pub, sizeof(pub), name + 2);
+}
+
+/*
+ * Read the name of the kernel NV index. Returns 0 and fills @name, a
+ * positive TPM error code if the index does not exist, or -errno.
+ */
+static int tpm2_kernel_nv_read_name(struct tpm_chip *chip,
+ u8 name[TPM2_NULL_NAME_SIZE])
+{
+ struct tpm_buf *buf __free(kfree) = NULL;
+ off_t offset = TPM_HEADER_SIZE;
+ u16 len;
+ int rc;
+
+ buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ tpm_buf_init(buf, TPM_BUFSIZE);
+ tpm_buf_reset(buf, TPM2_ST_NO_SESSIONS, TPM2_CC_NV_READ_PUBLIC);
+ tpm_buf_append_u32(buf, TPM2_KERNEL_NV_INDEX);
+
+ rc = tpm_transmit_cmd(chip, buf, 0, NULL);
+ if (rc)
+ return rc;
+
+ /* skip nvPublic */
+ if (tpm_buf_length(buf) < offset + sizeof(u16))
+ return -EIO;
+ offset += sizeof(u16) + get_unaligned_be16(&buf->data[offset]);
+
+ /* nvName */
+ if (tpm_buf_length(buf) < offset + sizeof(u16))
+ return -EIO;
+ len = get_unaligned_be16(&buf->data[offset]);
+ offset += sizeof(u16);
+ if (len != TPM2_NULL_NAME_SIZE || tpm_buf_length(buf) < offset + len)
+ return -EIO;
+
+ memcpy(name, &buf->data[offset], len);
+ return 0;
+}
+
+static int tpm2_kernel_nv_write(struct tpm_chip *chip, const u8 *value)
+{
+ return tpm2_nv_write(chip, TPM2_KERNEL_NV_INDEX, TPM2_KERNEL_NV_INDEX,
+ 0, value, TPM2_KERNEL_NV_SIZE);
+}
+
+/*
+ * Determine whether the index may hold the magic value, without changing
+ * it. Only a definite answer that it does not is trusted: the index does
+ * not exist, has never been written, or holds some other value.
+ */
+static bool tpm2_kernel_nv_may_hold_magic(struct tpm_chip *chip)
+{
+ u8 name[TPM2_NULL_NAME_SIZE], value[TPM2_KERNEL_NV_SIZE];
+ int rc;
+
+ rc = tpm2_kernel_nv_read_name(chip, name);
+ if (rc > 0 && tpm2_rc_value(rc) == TPM2_RC_HANDLE)
+ return false;
+ if (rc)
+ return true;
+
+ rc = tpm2_nv_read(chip, TPM2_KERNEL_NV_INDEX, TPM2_KERNEL_NV_INDEX, 0,
+ value, sizeof(value));
+ if (rc == TPM2_RC_NV_UNINITIALIZED)
+ return false;
+ if (rc)
+ return true;
+
+ return !memcmp(value, tpm2_kernel_nv_magic, sizeof(value));
+}
+
+static void tpm2_kernel_nv_update_dirty(struct tpm_chip *chip, bool dirty)
+{
+ if (dirty)
+ chip->flags |= TPM_CHIP_FLAG_KERNEL_NV_DIRTY;
+ else
+ chip->flags &= ~TPM_CHIP_FLAG_KERNEL_NV_DIRTY;
+}
+
+static int tpm2_kernel_nv_recreate(struct tpm_chip *chip)
+{
+ int rc;
+
+ rc = tpm2_nv_undefine(chip, TPM2_KERNEL_NV_INDEX);
+ if (rc)
+ return rc;
+
+ rc = tpm2_nv_define(chip, TPM2_KERNEL_NV_INDEX, TPM2_KERNEL_NV_ATTRS,
+ TPM2_KERNEL_NV_SIZE);
+ if (rc)
+ return rc;
+
+ return tpm2_kernel_nv_write(chip, tpm2_kernel_nv_zero);
+}
+
+/*
+ * Reset the index to zero unless it already is, to avoid an NV write on
+ * every boot.
+ */
+static int tpm2_kernel_nv_ensure_zero(struct tpm_chip *chip)
+{
+ u8 value[TPM2_KERNEL_NV_SIZE];
+ int rc;
+
+ rc = tpm2_nv_read(chip, TPM2_KERNEL_NV_INDEX, TPM2_KERNEL_NV_INDEX, 0,
+ value, sizeof(value));
+ if (!rc && !memcmp(value, tpm2_kernel_nv_zero, sizeof(value)))
+ return 0;
+
+ return tpm2_kernel_nv_clear(chip);
+}
+
+static int __tpm2_kernel_nv_provision(struct tpm_chip *chip)
+{
+ u8 expected[TPM2_NULL_NAME_SIZE], name[TPM2_NULL_NAME_SIZE];
+ int rc;
+
+ rc = tpm2_kernel_nv_read_name(chip, name);
+ if (rc < 0)
+ return rc;
+
+ if (rc > 0) {
+ /* the index does not exist */
+ rc = tpm2_nv_define(chip, TPM2_KERNEL_NV_INDEX,
+ TPM2_KERNEL_NV_ATTRS, TPM2_KERNEL_NV_SIZE);
+ if (rc)
+ return rc;
+
+ return tpm2_kernel_nv_clear(chip);
+ }
+
+ tpm2_kernel_nv_name(true, expected);
+ if (memcmp(name, expected, sizeof(name))) {
+ tpm2_kernel_nv_name(false, expected);
+ if (memcmp(name, expected, sizeof(name))) {
+ dev_warn(&chip->dev,
+ "kernel NV index has unexpected attributes, redefining\n");
+ rc = tpm2_kernel_nv_recreate(chip);
+ if (rc)
+ return rc;
+
+ return tpm2_kernel_nv_clear(chip);
+ }
+ }
+
+ rc = tpm2_kernel_nv_ensure_zero(chip);
+ if (rc > 0) {
+ /* auth value is not empty: the index was not defined by us */
+ dev_warn(&chip->dev,
+ "kernel NV index cannot be written, redefining\n");
+ rc = tpm2_kernel_nv_recreate(chip);
+ if (rc)
+ return rc;
+
+ return tpm2_kernel_nv_clear(chip);
+ }
+
+ return rc;
+}
+
+/**
+ * tpm2_kernel_nv_provision() - ensure the kernel NV index is usable
+ * @chip: the TPM chip
+ *
+ * Makes sure the kernel NV index exists with the expected public area and
+ * an empty auth value, and that it holds zero. An index with an unexpected
+ * public area, or one that cannot be written with an empty auth value, is
+ * undefined and defined again. The owner hierarchy must have an empty auth
+ * value. If this fails and the index may still hold the magic value, the
+ * chip is marked so that userspace commands are refused. The caller must
+ * hold the chip's ops lock.
+ *
+ * Return: 0 on success, -errno or a TPM error code on failure.
+ */
+int tpm2_kernel_nv_provision(struct tpm_chip *chip)
+{
+ int rc = __tpm2_kernel_nv_provision(chip);
+
+ tpm2_kernel_nv_update_dirty(chip,
+ rc && tpm2_kernel_nv_may_hold_magic(chip));
+ return rc;
+}
+
+/**
+ * tpm2_kernel_nv_set_magic() - store the magic value in the kernel NV index
+ * @chip: the TPM chip
+ *
+ * The caller must hold the chip's ops lock, and must call
+ * tpm2_kernel_nv_clear() before releasing it.
+ *
+ * Return: 0 on success, -errno or a TPM error code on failure.
+ */
+int tpm2_kernel_nv_set_magic(struct tpm_chip *chip)
+{
+ /* assume the worst until the index is known to be clear again */
+ chip->flags |= TPM_CHIP_FLAG_KERNEL_NV_DIRTY;
+
+ return tpm2_kernel_nv_write(chip, tpm2_kernel_nv_magic);
+}
+
+/**
+ * tpm2_kernel_nv_clear() - reset the kernel NV index to zero
+ * @chip: the TPM chip
+ *
+ * If the index cannot be reset, it may still hold the magic value, so the
+ * chip is marked so that userspace commands are refused until a reset
+ * succeeds. The caller must hold the chip's ops lock.
+ *
+ * Return: 0 on success, -errno or a TPM error code on failure.
+ */
+int tpm2_kernel_nv_clear(struct tpm_chip *chip)
+{
+ int rc;
+
+ rc = tpm2_kernel_nv_write(chip, tpm2_kernel_nv_zero);
+ tpm2_kernel_nv_update_dirty(chip,
+ rc && tpm2_kernel_nv_may_hold_magic(chip));
+ return rc;
+}
diff --git a/include/linux/tpm.h b/include/linux/tpm.h
index d5a3320efe8b..35ba30a2674d 100644
--- a/include/linux/tpm.h
+++ b/include/linux/tpm.h
@@ -183,6 +183,7 @@ static inline enum tpm2_mso_type tpm2_handle_mso(u32 handle)
* undefine or modify it.
*/
#define TPM2_KERNEL_NV_INDEX 0x014c4853
+#define TPM2_KERNEL_NV_SIZE 8
#define TPM_VID_INTEL 0x8086
#define TPM_VID_WINBOND 0x1050
@@ -203,6 +204,8 @@ enum tpm_chip_flags {
TPM_CHIP_FLAG_HWRNG_DISABLED = BIT(9),
TPM_CHIP_FLAG_DISABLE = BIT(10),
TPM_CHIP_FLAG_SYNC = BIT(11),
+ /* the kernel NV index may hold the magic value */
+ TPM_CHIP_FLAG_KERNEL_NV_DIRTY = BIT(12),
};
#define to_tpm_chip(d) container_of(d, struct tpm_chip, dev)
diff --git a/include/linux/tpm_command.h b/include/linux/tpm_command.h
index c8e867d8bd4b..cef85f532b6c 100644
--- a/include/linux/tpm_command.h
+++ b/include/linux/tpm_command.h
@@ -203,6 +203,7 @@ enum tpm2_return_codes {
TPM2_RC_HANDLE = 0x008B,
TPM2_RC_INTEGRITY = 0x009F,
TPM2_RC_INITIALIZE = 0x0100, /* RC_VER1 */
+ TPM2_RC_NV_UNINITIALIZED = 0x014A,
TPM2_RC_FAILURE = 0x0101,
TPM2_RC_DISABLED = 0x0120,
TPM2_RC_UPGRADE = 0x012D,
--
2.43.0
next prev 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 ` [PATCH 08/17] tpm: Add NV define, undefine and write helpers Matthew Garrett
2026-10-08 13:20 ` Matthew Garrett [this message]
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-10-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