[PATCH v3 2/4] tpm: Add O_EXCL for exclusive /dev/tpm access

Jonathan McDowell
2025-10-20T11:30:53Z

From: Jonathan McDowell <noodles@meta.com>

The /dev/tpm interface allows for only a single user, but does not
prevent access to the TPM via other paths (either internal kernel
interfaces or the /dev/tpmrm interface). Pave the way to being to able
request fully exclusive access by supporting the use of the O_EXCL flag
on device open.

Signed-off-by: Jonathan McDowell <noodles@meta.com>
---
v2: No changes.
v3: Move earlier in series to prevent bisection breakage.
Check for O_RDWR as well as O_EXCL to guard against clients doing
odd things.
Retain single /dev/tpm# user even without O_EXCL.

drivers/char/tpm/tpm-chip.c | 1 +
drivers/char/tpm/tpm-dev.c | 35 ++++++++++++++++++++++++++++++++---
drivers/char/tpm/tpm-dev.h | 1 +
include/linux/tpm.h | 4 +++-
4 files changed, 37 insertions(+), 4 deletions(-)

diff --git a/drivers/char/tpm/tpm-chip.c b/drivers/char/tpm/tpm-chip.c
index 30d00219f9f3..ba906966721a 100644
--- a/drivers/char/tpm/tpm-chip.c
+++ b/drivers/char/tpm/tpm-chip.c
@@ -302,6 +302,7 @@ struct tpm_chip *tpm_chip_alloc(struct device *pdev,

mutex_init(&chip->tpm_mutex);
init_rwsem(&chip->ops_sem);
+ init_rwsem(&chip->open_lock);

chip->ops = ops;

diff --git a/drivers/char/tpm/tpm-dev.c b/drivers/char/tpm/tpm-dev.c
index 97c94b5e9340..819f3e1546da 100644
--- a/drivers/char/tpm/tpm-dev.c
+++ b/drivers/char/tpm/tpm-dev.c
@@ -19,20 +19,41 @@ static int tpm_open(struct inode *inode, struct file *file)
{
struct tpm_chip *chip;
struct file_priv *priv;
+ int rc;

chip = container_of(inode->i_cdev, struct tpm_chip, cdev);

- /* It's assured that the chip will be opened just once,
- * by the check of is_open variable, which is protected
- * by driver_lock. */
+ /*
+ * It's assured that the chip will be opened just once via the direct
+ * /dev/tpm# interface by the check of is_open variable, which is
+ * protected by driver_lock.
+ */
if (test_and_set_bit(0, &chip->is_open)) {
dev_dbg(&chip->dev, "Another process owns this TPM\n");
return -EBUSY;
}

+ /*
+ * If a client uses the O_EXCL flag then it expects to be the only TPM
+ * user across all access paths, so we treat it as a write lock.
+ * Otherwise we use a read lock, allowing for concurrent openers.
+ * We make sure the client is opening the device for reading + writing;
+ * opening for exclusive access doesn't make sense if not.
+ */
+ if ((file->f_flags & (O_ACCMODE|O_EXCL)) == (O_RDWR|O_EXCL))
+ rc = down_write_trylock(&chip->open_lock);
+ else
+ rc = down_read_trylock(&chip->open_lock);
+
+ if (!rc) {
+ dev_dbg(&chip->dev, "Another process owns this TPM\n");
+ return -EBUSY;
+ }
+
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (priv == NULL)
goto out;
+ priv->exclusive = (file->f_flags & O_EXCL);

tpm_common_open(file, chip, priv, NULL);

@@ -40,6 +61,10 @@ static int tpm_open(struct inode *inode, struct file *file)

out:
clear_bit(0, &chip->is_open);
+ if (file->f_flags & O_EXCL)
+ up_write(&chip->open_lock);
+ else
+ up_read(&chip->open_lock);
return -ENOMEM;
}

@@ -52,6 +77,10 @@ static int tpm_release(struct inode *inode, struct file *file)

tpm_common_release(file, priv);
clear_bit(0, &priv->chip->is_open);
+ if (priv->exclusive)
+ up_write(&priv->chip->open_lock);
+ else
+ up_read(&priv->chip->open_lock);
kfree(priv);

return 0;
diff --git a/drivers/char/tpm/tpm-dev.h b/drivers/char/tpm/tpm-dev.h
index f3742bcc73e3..0ad8504c73e4 100644
--- a/drivers/char/tpm/tpm-dev.h
+++ b/drivers/char/tpm/tpm-dev.h
@@ -17,6 +17,7 @@ struct file_priv {
ssize_t response_length;
bool response_read;
bool command_enqueued;
+ bool exclusive;

u8 data_buffer[TPM_BUFSIZE];
};
diff --git a/include/linux/tpm.h b/include/linux/tpm.h
index dc0338a783f3..3bd3da5cb97e 100644
--- a/include/linux/tpm.h
+++ b/include/linux/tpm.h
@@ -22,6 +22,7 @@
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/highmem.h>
+#include <linux/rwsem.h>
#include <crypto/hash_info.h>
#include <crypto/aes.h>

@@ -168,7 +169,8 @@ struct tpm_chip {
unsigned int flags;

int dev_num; /* /dev/tpm# */
- unsigned long is_open; /* only one allowed */
+ unsigned long is_open; /* only one tpm# allowed */
+ struct rw_semaphore open_lock;

char hwrng_name[64];
struct hwrng hwrng;
--
2.51.0