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 01/17] tpm: Define a kernel-owned TPM NV index that can't be modified by userland
Date: Thu, 8 Oct 2026 06:20:17 -0700 [thread overview]
Message-ID: <20261008132532.1155166-2-matthewg@nvidia.com> (raw)
In-Reply-To: <20261008132532.1155166-1-matthewg@nvidia.com>
TPM NV indexes can be used for various purposes, including using them as
PCRs without depleting the limited number of hardware PCRs. It would be
beneficial to have one that's under control of the kernel in order to be
able to prove whether certain TPM operations occurred within the kernel
or not.
Reserve NV index 0x014c4853 for use by the kernel, and filter commands
submitted through /dev/tpm* and /dev/tpmrm* so that userspace cannot
undefine or modify it. Blocking definition is more awkward so let's
allow that for now, not being able to modify it means there's nothing
interesting they can do there. The filter simply validates which handle
the relevant set of commands is referring to and returns -EPERM if it's
the kernel one.
NV indexes are from allocated ranges and this is a range allocated to
Linux, so there should be no userland depending on the ability to access
this - but let's gate it behind a default N config option anyway.
Signed-off-by: Matthew Garrett <matthewg@nvidia.com>
---
drivers/char/tpm/Kconfig | 8 ++++
drivers/char/tpm/tpm-dev-common.c | 67 +++++++++++++++++++++++++++++++
include/linux/tpm.h | 6 +++
include/linux/tpm_command.h | 10 +++++
4 files changed, 91 insertions(+)
diff --git a/drivers/char/tpm/Kconfig b/drivers/char/tpm/Kconfig
index 5f672f2c01b0..a454b63edca5 100644
--- a/drivers/char/tpm/Kconfig
+++ b/drivers/char/tpm/Kconfig
@@ -253,5 +253,13 @@ config TCG_SVSM
level (usually VMPL0). To compile this driver as a module, choose M
here; the module will be called tpm_svsm.
+config TCG_TPM_KERNEL_NVINDEX
+ bool "Kernel-only NV index"
+ help
+ Allocate a TPM NV index and block userland from modifying it. This
+ allows the kernel to develop a unique state that distinguishes it
+ from userspace, making it possible to prove that a TPM object
+ was created by the kernel.
+
source "drivers/char/tpm/st33zp24/Kconfig"
endif # TCG_TPM
diff --git a/drivers/char/tpm/tpm-dev-common.c b/drivers/char/tpm/tpm-dev-common.c
index f942c0c8e402..1fd93cdaf306 100644
--- a/drivers/char/tpm/tpm-dev-common.c
+++ b/drivers/char/tpm/tpm-dev-common.c
@@ -15,18 +15,85 @@
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
+#include <linux/unaligned.h>
#include <linux/workqueue.h>
#include "tpm.h"
#include "tpm-dev.h"
static struct workqueue_struct *tpm_dev_wq;
+#ifdef CONFIG_TCG_TPM_KERNEL_NVINDEX
+/*
+ * Commands that undefine or modify an NV index, and the position of the
+ * NV index in the command's handle area.
+ */
+static const struct {
+ u32 cc;
+ unsigned int handle;
+} tpm2_nv_modify_cmds[] = {
+ { TPM2_CC_NV_UNDEFINE_SPACE_SPECIAL, 0 },
+ { TPM2_CC_NV_UNDEFINE_SPACE, 1 },
+ { TPM2_CC_NV_INCREMENT, 1 },
+ { TPM2_CC_NV_SET_BITS, 1 },
+ { TPM2_CC_NV_EXTEND, 1 },
+ { TPM2_CC_NV_WRITE, 1 },
+ { TPM2_CC_NV_WRITE_LOCK, 1 },
+ { TPM2_CC_NV_CHANGE_AUTH, 0 },
+ { TPM2_CC_NV_READ_LOCK, 1 },
+};
+
+/*
+ * Returns true if a command from userspace attempts to define, undefine
+ * or modify the kernel-owned NV index.
+ */
+static bool tpm2_dev_cmd_is_blocked(const u8 *buf, size_t bufsiz)
+{
+ const struct tpm_header *header = (const void *)buf;
+ size_t len = min_t(size_t, be32_to_cpu(header->length), bufsiz);
+ u32 cc;
+ int i, index;
+
+ if (len < TPM_HEADER_SIZE)
+ return false;
+
+ cc = be32_to_cpu(header->ordinal);
+
+ for (i = 0; i < ARRAY_SIZE(tpm2_nv_modify_cmds); i++) {
+ size_t offset;
+
+ if (tpm2_nv_modify_cmds[i].cc != cc)
+ continue;
+
+ offset = TPM_HEADER_SIZE +
+ tpm2_nv_modify_cmds[i].handle * sizeof(u32);
+ if (len < offset + sizeof(u32))
+ return false;
+
+ index = get_unaligned_be32(&buf[offset]);
+
+ if (index == TPM2_KERNEL_NV_INDEX)
+ return true;
+ }
+
+ return false;
+}
+#else
+static inline bool tpm2_dev_cmd_is_blocked(const u8 *buf, size_t bufsiz)
+{
+ return false;
+}
+#endif
+
static ssize_t tpm_dev_transmit(struct tpm_chip *chip, struct tpm_space *space,
u8 *buf, size_t bufsiz)
{
struct tpm_header *header = (void *)buf;
ssize_t ret, len;
+ if ((chip->flags & TPM_CHIP_FLAG_TPM2) &&
+ tpm2_dev_cmd_is_blocked(buf, bufsiz))
+ return -EPERM;
+
if (chip->flags & TPM_CHIP_FLAG_TPM2)
tpm2_end_auth_session(chip);
diff --git a/include/linux/tpm.h b/include/linux/tpm.h
index 0db277af45c3..b6b862c3be3b 100644
--- a/include/linux/tpm.h
+++ b/include/linux/tpm.h
@@ -178,6 +178,12 @@ static inline enum tpm2_mso_type tpm2_handle_mso(u32 handle)
return handle >> 24;
}
+/*
+ * NV index reserved for use by the kernel. Userspace is not permitted to
+ * undefine or modify it.
+ */
+#define TPM2_KERNEL_NV_INDEX 0x014c4853
+
#define TPM_VID_INTEL 0x8086
#define TPM_VID_WINBOND 0x1050
#define TPM_VID_STM 0x104A
diff --git a/include/linux/tpm_command.h b/include/linux/tpm_command.h
index fc446a1282e2..79f547ca6dbf 100644
--- a/include/linux/tpm_command.h
+++ b/include/linux/tpm_command.h
@@ -214,14 +214,24 @@ enum tpm2_return_codes {
enum tpm2_command_codes {
TPM2_CC_FIRST = 0x011F,
+ TPM2_CC_NV_UNDEFINE_SPACE_SPECIAL = 0x011F,
TPM2_CC_HIERARCHY_CONTROL = 0x0121,
+ TPM2_CC_NV_UNDEFINE_SPACE = 0x0122,
TPM2_CC_HIERARCHY_CHANGE_AUTH = 0x0129,
+ TPM2_CC_NV_DEFINE_SPACE = 0x012A,
TPM2_CC_CREATE_PRIMARY = 0x0131,
+ TPM2_CC_NV_INCREMENT = 0x0134,
+ TPM2_CC_NV_SET_BITS = 0x0135,
+ TPM2_CC_NV_EXTEND = 0x0136,
+ TPM2_CC_NV_WRITE = 0x0137,
+ TPM2_CC_NV_WRITE_LOCK = 0x0138,
+ TPM2_CC_NV_CHANGE_AUTH = 0x013B,
TPM2_CC_SEQUENCE_COMPLETE = 0x013E,
TPM2_CC_SELF_TEST = 0x0143,
TPM2_CC_STARTUP = 0x0144,
TPM2_CC_SHUTDOWN = 0x0145,
TPM2_CC_NV_READ = 0x014E,
+ TPM2_CC_NV_READ_LOCK = 0x014F,
TPM2_CC_CREATE = 0x0153,
TPM2_CC_LOAD = 0x0157,
TPM2_CC_SEQUENCE_UPDATE = 0x015C,
--
2.43.0
next prev parent reply other threads:[~2026-10-08 13:25 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 ` Matthew Garrett [this message]
2026-10-08 13:41 ` [PATCH 01/17] tpm: Define a kernel-owned TPM NV index that can't be modified by userland 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 ` [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-2-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