diff mbox series

[v2,16/18] spdm: Limit memory consumed by log of received signatures

Message ID 2e6ee6670a5d450bc880e77a892ea0227a2cc3b4.1719771133.git.lukas@wunner.de (mailing list archive)
State New
Delegated to: Bjorn Helgaas
Headers show
Series PCI device authentication | expand

Commit Message

Lukas Wunner June 30, 2024, 7:51 p.m. UTC
The SPDM library has just been amended to keep a log of received
signatures and expose it in sysfs.

Limit the log's memory footprint subject to a sysctl parameter.  Purge
old signatures when adding a new signature which causes the limit to be
exceeded.  Likewise purge old signatures when the sysctl parameter is
reduced.

The latter requires keeping a list of all struct spdm_state and
protecting it with a mutex.  It will come in handy when further global
sysctl parameters are added to the SPDM library.  Unfortunately an
xarray is not a better option in this case as the xarray-integrated
xa_lock() is a spinlock but purging signatures from sysfs may sleep
(due to kernfs_rwsem).

This functionality is introduced in a separate commit on top of basic
signature exposure to split the code into digestible, reviewable chunks.

Signed-off-by: Lukas Wunner <lukas@wunner.de>
---
 Documentation/ABI/testing/sysfs-devices-spdm | 15 ++++
 Documentation/admin-guide/sysctl/index.rst   |  2 +
 Documentation/admin-guide/sysctl/spdm.rst    | 33 ++++++++
 MAINTAINERS                                  |  1 +
 lib/spdm/core.c                              | 11 +++
 lib/spdm/req-sysfs.c                         | 80 +++++++++++++++++++-
 lib/spdm/spdm.h                              | 10 +++
 7 files changed, 150 insertions(+), 2 deletions(-)
 create mode 100644 Documentation/admin-guide/sysctl/spdm.rst

Comments

Jonathan Cameron July 18, 2024, 4:03 p.m. UTC | #1
On Sun, 30 Jun 2024 21:51:00 +0200
Lukas Wunner <lukas@wunner.de> wrote:

> The SPDM library has just been amended to keep a log of received
> signatures and expose it in sysfs.
> 
> Limit the log's memory footprint subject to a sysctl parameter.  Purge
> old signatures when adding a new signature which causes the limit to be
> exceeded.  Likewise purge old signatures when the sysctl parameter is
> reduced.
> 
> The latter requires keeping a list of all struct spdm_state and
> protecting it with a mutex.  It will come in handy when further global
> sysctl parameters are added to the SPDM library.  Unfortunately an
> xarray is not a better option in this case as the xarray-integrated
> xa_lock() is a spinlock but purging signatures from sysfs may sleep
> (due to kernfs_rwsem).
> 
> This functionality is introduced in a separate commit on top of basic
> signature exposure to split the code into digestible, reviewable chunks.
> 
> Signed-off-by: Lukas Wunner <lukas@wunner.de>
Ah. This avoids potential problem in previous patch. Fair enough no need
to check the counter for overflow as long as it's not feasible to set that
sysctl high enough that we still get a collision.

LGTM 
Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
diff mbox series

Patch

diff --git a/Documentation/ABI/testing/sysfs-devices-spdm b/Documentation/ABI/testing/sysfs-devices-spdm
index ae7b3f701ded..8d8ee01672e1 100644
--- a/Documentation/ABI/testing/sysfs-devices-spdm
+++ b/Documentation/ABI/testing/sysfs-devices-spdm
@@ -162,6 +162,21 @@  Description:
 		dissector needs to be fed the concatenation of "transcript"
 		and "signature".
 
+		Because the number prefixed to the filenames is 32 bit, it
+		wraps around to 0 after 4,294,967,295 signatures.  The kernel
+		avoids filename collisions on wraparound by purging old files,
+		subject to the limit set by "sysctl spdm.max_signatures_size"
+		(which defaults to 16 MiB).  It is advisable to regularly save
+		backups on non-volatile storage to retain access to signatures
+		that have been purged (or across reboots)::
+
+		 # tar -u -h -f /path/to/signatures.tar signatures/
+
+		The ctime of each file is the reception time of the signature.
+		However if the signature was received before the device became
+		registered in sysfs, the ctime is the registration time of the
+		device.
+
 
 What:		/sys/devices/.../signatures/[0-9]*_type
 Date:		June 2024
diff --git a/Documentation/admin-guide/sysctl/index.rst b/Documentation/admin-guide/sysctl/index.rst
index 03346f98c7b9..3b48f0039069 100644
--- a/Documentation/admin-guide/sysctl/index.rst
+++ b/Documentation/admin-guide/sysctl/index.rst
@@ -76,6 +76,7 @@  kernel/		global kernel info / tuning
 net/		networking stuff, for documentation look in:
 		<Documentation/networking/>
 proc/		<empty>
+spdm/		Security Protocol and Data Model (SPDM)
 sunrpc/		SUN Remote Procedure Call (NFS)
 vm/		memory management tuning
 		buffer and cache management
@@ -93,6 +94,7 @@  really like to hear about it :-)
    fs
    kernel
    net
+   spdm
    sunrpc
    user
    vm
diff --git a/Documentation/admin-guide/sysctl/spdm.rst b/Documentation/admin-guide/sysctl/spdm.rst
new file mode 100644
index 000000000000..0f3846c83cd4
--- /dev/null
+++ b/Documentation/admin-guide/sysctl/spdm.rst
@@ -0,0 +1,33 @@ 
+.. SPDX-License-Identifier: GPL-2.0
+
+=================================
+Documentation for /proc/sys/spdm/
+=================================
+
+Copyright (C) 2024 Intel Corporation
+
+This directory allows tuning Security Protocol and Data Model (SPDM)
+parameters.  SPDM enables device authentication, measurement, key
+exchange and encrypted sessions.
+
+max_signatures_size
+===================
+
+Maximum amount of memory occupied by the log of signatures (per device,
+in bytes, 16 MiB by default).
+
+The log is meant for re-verification of signatures by remote attestation
+services which do not trust the kernel to have verified the signatures
+correctly or which want to apply policy constraints of their own.
+A signature is computed over the transcript (a concatenation of all
+SPDM messages exchanged with the device during an authentication
+sequence).  The transcript can be a few kBytes or up to several MBytes
+in size, hence this parameter prevents the log from consuming too much
+memory.
+
+The kernel always stores the most recent signature in the log even if it
+exceeds ``max_signatures_size``.  Additionally as many older signatures
+are kept in the log as this limit allows.
+
+If you reduce the limit, signatures are purged immediately to free up
+memory.
diff --git a/MAINTAINERS b/MAINTAINERS
index 1ed5817e698c..41f35bbb8f1a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20154,6 +20154,7 @@  L:	linux-pci@vger.kernel.org
 S:	Maintained
 T:	git git://git.kernel.org/pub/scm/linux/kernel/git/devsec/spdm.git
 F:	Documentation/ABI/testing/sysfs-devices-spdm
+F:	Documentation/admin-guide/sysctl/spdm.rst
 F:	drivers/pci/cma*
 F:	include/linux/spdm.h
 F:	lib/spdm/
diff --git a/lib/spdm/core.c b/lib/spdm/core.c
index d962a1344760..b6a46bdbb2f9 100644
--- a/lib/spdm/core.c
+++ b/lib/spdm/core.c
@@ -20,6 +20,9 @@ 
 #include <crypto/hash.h>
 #include <crypto/public_key.h>
 
+LIST_HEAD(spdm_state_list); /* list of all struct spdm_state */
+DEFINE_MUTEX(spdm_state_mutex); /* protects spdm_state_list */
+
 static int spdm_err(struct device *dev, struct spdm_error_rsp *rsp)
 {
 	switch (rsp->error_code) {
@@ -404,6 +407,10 @@  struct spdm_state *spdm_create(struct device *dev, spdm_transport *transport,
 	mutex_init(&spdm_state->lock);
 	INIT_LIST_HEAD(&spdm_state->log);
 
+	mutex_lock(&spdm_state_mutex);
+	list_add_tail(&spdm_state->list, &spdm_state_list);
+	mutex_unlock(&spdm_state_mutex);
+
 	return spdm_state;
 }
 EXPORT_SYMBOL_GPL(spdm_create);
@@ -417,6 +424,10 @@  void spdm_destroy(struct spdm_state *spdm_state)
 {
 	u8 slot;
 
+	mutex_lock(&spdm_state_mutex);
+	list_del(&spdm_state->list);
+	mutex_unlock(&spdm_state_mutex);
+
 	for_each_set_bit(slot, &spdm_state->provisioned_slots, SPDM_SLOTS)
 		kvfree(spdm_state->slot[slot]);
 
diff --git a/lib/spdm/req-sysfs.c b/lib/spdm/req-sysfs.c
index d3c4ca7dbbaa..c782054f8e18 100644
--- a/lib/spdm/req-sysfs.c
+++ b/lib/spdm/req-sysfs.c
@@ -185,6 +185,8 @@  const struct attribute_group spdm_signatures_group = {
 	.bin_attrs = spdm_signatures_bin_attrs,
 };
 
+static unsigned int spdm_max_log_sz = SZ_16M; /* per device */
+
 /**
  * struct spdm_log_entry - log entry representing one received SPDM signature
  *
@@ -332,13 +334,31 @@  static ssize_t spdm_read_combined_prefix(struct file *file,
 	return count;
 }
 
-static void spdm_destroy_log_entry(struct spdm_log_entry *log)
+static void spdm_destroy_log_entry(struct spdm_state *spdm_state,
+				   struct spdm_log_entry *log)
 {
+	spdm_state->log_sz -= log->transcript.size + log->sig.size +
+			      sizeof(*log);
+
 	list_del(&log->list);
 	kvfree(log->transcript.private);
 	kfree(log);
 }
 
+static void spdm_shrink_log(struct spdm_state *spdm_state)
+{
+	while (spdm_state->log_sz > spdm_max_log_sz &&
+	       !list_is_singular(&spdm_state->log)) {
+		struct spdm_log_entry *log =
+			list_first_entry(&spdm_state->log, typeof(*log), list);
+
+		if (device_is_registered(spdm_state->dev))
+			spdm_unpublish_log_entry(&spdm_state->dev->kobj, log);
+
+		spdm_destroy_log_entry(spdm_state, log);
+	}
+}
+
 /**
  * spdm_create_log_entry() - Allocate log entry for one received SPDM signature
  *
@@ -444,6 +464,11 @@  void spdm_create_log_entry(struct spdm_state *spdm_state,
 
 	list_add_tail(&log->list, &spdm_state->log);
 	spdm_state->log_counter++;
+	spdm_state->log_sz += log->transcript.size + log->sig.size +
+			      sizeof(*log);
+
+	/* Purge oldest log entries if max log size is exceeded */
+	spdm_shrink_log(spdm_state);
 
 	/* Steal transcript pointer ahead of spdm_free_transcript() */
 	spdm_state->transcript = NULL;
@@ -504,5 +529,56 @@  void spdm_destroy_log(struct spdm_state *spdm_state)
 	struct spdm_log_entry *log, *tmp;
 
 	list_for_each_entry_safe(log, tmp, &spdm_state->log, list)
-		spdm_destroy_log_entry(log);
+		spdm_destroy_log_entry(spdm_state, log);
+}
+
+#ifdef CONFIG_SYSCTL
+static int proc_max_log_sz(struct ctl_table *table, int write,
+			   void *buffer, size_t *lenp, loff_t *ppos)
+{
+	unsigned int old_max_log_sz = spdm_max_log_sz;
+	struct spdm_state *spdm_state;
+	int rc;
+
+	rc = proc_douintvec_minmax(table, write, buffer, lenp, ppos);
+	if (rc)
+		return rc;
+
+	/* Purge oldest log entries if max log size has been reduced */
+	if (write && spdm_max_log_sz < old_max_log_sz) {
+		mutex_lock(&spdm_state_mutex);
+		list_for_each_entry(spdm_state, &spdm_state_list, list) {
+			mutex_lock(&spdm_state->lock);
+			spdm_shrink_log(spdm_state);
+			mutex_unlock(&spdm_state->lock);
+		}
+		mutex_unlock(&spdm_state_mutex);
+	}
+
+	return 0;
+}
+
+static struct ctl_table spdm_ctl_table[] = {
+	{
+		.procname	= "max_signatures_size",
+		.data		= &spdm_max_log_sz,
+		.maxlen		= sizeof(spdm_max_log_sz),
+		.mode		= 0644,
+		.proc_handler	= proc_max_log_sz,
+		.extra1		= SYSCTL_ZERO,
+				  /*
+				   * 2 GiB limit avoids filename collision on
+				   * wraparound of unsigned 32-bit log_counter
+				   */
+		.extra2		= SYSCTL_INT_MAX,
+	},
+	{ }
+};
+
+static int __init spdm_init(void)
+{
+	register_sysctl_init("spdm", spdm_ctl_table);
+	return 0;
 }
+fs_initcall(spdm_init);
+#endif /* CONFIG_SYSCTL */
diff --git a/lib/spdm/spdm.h b/lib/spdm/spdm.h
index a63c2922af5d..448107c92db7 100644
--- a/lib/spdm/spdm.h
+++ b/lib/spdm/spdm.h
@@ -420,6 +420,8 @@  struct spdm_error_rsp {
  * @dev: Responder device.  Used for error reporting and passed to @transport.
  *	Attributes in sysfs appear below this device's directory.
  * @lock: Serializes multiple concurrent spdm_authenticate() calls.
+ * @list: List node.  Added to spdm_state_list.  Used to iterate over all
+ *	SPDM-capable devices when a global sysctl parameter is changed.
  * @authenticated: Whether device was authenticated successfully.
  * @dev: Responder device.  Used for error reporting and passed to @transport.
  * @transport: Transport function to perform one message exchange.
@@ -468,12 +470,16 @@  struct spdm_error_rsp {
  * @transcript_max: Allocation size of @transcript.  Multiple of PAGE_SIZE.
  * @log: Linked list of past authentication events.  Each list entry is of type
  *	struct spdm_log_entry and is exposed as several files in sysfs.
+ * @log_sz: Memory occupied by @log (in bytes) to enforce the limit set by
+ *	spdm_max_log_sz.  Includes, for every entry, the struct spdm_log_entry
+ *	itself and the transcript with trailing signature.
  * @log_counter: Number of generated log entries so far.  Will be prefixed to
  *	the sysfs files of the next generated log entry.
  */
 struct spdm_state {
 	struct device *dev;
 	struct mutex lock;
+	struct list_head list;
 	unsigned int authenticated:1;
 
 	/* Transport */
@@ -513,9 +519,13 @@  struct spdm_state {
 
 	/* Signatures Log */
 	struct list_head log;
+	size_t log_sz;
 	u32 log_counter;
 };
 
+extern struct list_head spdm_state_list;
+extern struct mutex spdm_state_mutex;
+
 ssize_t spdm_exchange(struct spdm_state *spdm_state,
 		      void *req, size_t req_sz, void *rsp, size_t rsp_sz);