Skip to contentSkip to conversation

lib: Rust implementation of SPDM

RFC Superseded [email protected] 13 participants 112 messages Feb 11 – May 5

Subsystems (11)

v3 of 6 Superseded Latest is v6 →

lore
Share & tools
lore
#0 lore raw

v3 cover lib: Rust implementation of SPDM

From: Alistair Francis <[email protected]>

Security Protocols and Data Models (SPDM) [1] is used for authentication, attestation and key exchange. SPDM is generally used over a range of transports, such as PCIe, MCTP/SMBus/I3C, ATA, SCSI, NVMe or TCP.

From the kernels perspective SPDM is used to authenticate and attest devices. In this threat model a device is considered untrusted until it can be verified by the kernel and userspace using SPDM. As such SPDM data is untrusted data that can be mallicious.

The SPDM specification is also complex, with the 1.2.1 spec being almost 200 pages and the 1.3.0 spec being almost 250 pages long.

As such we have the kernel parsing untrusted responses from a complex specification, which sounds like a possible exploit vector. This is the type of place where Rust excels!

This series implements a SPDM requester in Rust.

This is very similar to Lukas' implementation [2]. This series includes patches and files from Lukas' C SPDM implementation, which isn't in mainline.

This is a standalone series and doesn't depend on Lukas' implementation.

To help with maintaining compatibility it's designed in a way to match Lukas' design and the state struct stores the same information, although in a Rust struct instead of the original C one.

This series exposes the data to userspace via netlink, with a single sysfs atrribute to allow reauthentication.

All of the patches are included in the RFC, as it depends on some patches that aren't upstream yet.

Now that Rust is no longer experimental I have picked this back up. If the community is generally on board with a Rust implementation I can work on sending a non-RFC version and push towards getting that merged.

The entire tree can be seen here: https://github.com/alistair23/linux/tree/alistair/spdm-rust

I'm testing the netlink data by running the following

```shell
cargo run -- --qemu-server response

qemu-system-x86_64 \
  -nic none \
  -object rng-random,filename=/dev/urandom,id=rng0 \
  -device virtio-rng-pci,rng=rng0 \
  -drive file=deploy/images/qemux86-64/core-image-pcie-qemux86-64.rootfs.ext4,if=virtio,format=raw \
  -usb -device usb-tablet -usb -device usb-kbd \
  -cpu Skylake-Client \
  -machine q35,i8042=off \
  -smp 4 -m 2G \
  -drive file=blknvme,if=none,id=mynvme,format=raw \
  -device nvme,drive=mynvme,serial=deadbeef,spdm_port=2323,spdm_trans=doe \
  -snapshot \
  -serial mon:stdio -serial null -nographic \
  -kernel deploy/images/qemux86-64/bzImage \
  -append 'root=/dev/vda rw  console=ttyS0 console=ttyS1 oprofile.timer=1 tsc=reliable no_timer_check rcupdate.rcu_expedited=1 swiotlb=0 '

spdm_utils identify &
sleep 1
echo re > /sys/devices/pci0000:00/0000:00:03.0/authenticated
while openssl x509 -noout -text; do :; done < ~/retrieved_slot_id0
```

1: https://www.dmtf.org/standards/spdm
2: https://lore.kernel.org/all/[email protected]/
3: https://github.com/l1k/linux/commits/spdm-future/
4: https://lore.kernel.org/rust-for-linux/[email protected]/
5: https://lore.kernel.org/rust-for-linux/[email protected]/

v3:
- Use netlink to send information to userspace
- Don't autogenerate Rust helpers
v2:
- Drop support for Rust and C implementations
- Include patches from Lukas to reduce series deps
- Large code cleanups based on more testing
- Support support for authentication

Alistair Francis (18):
rust: add bindings for hash.h
rust: error: impl From<FromBytesWithNulError> for Kernel Error
lib: rspdm: Initial commit of Rust SPDM
lib: rspdm: Support SPDM get_version
lib: rspdm: Support SPDM get_capabilities
lib: rspdm: Support SPDM negotiate_algorithms
lib: rspdm: Support SPDM get_digests
lib: rspdm: Support SPDM get_certificate
crypto: asymmetric_keys - Load certificate parsing early in boot
KEYS: Load keyring and certificates early in boot PCI/CMA: Support built in X.509 certificates
crypto: sha: Load early in boot
crypto: ecdsa: Load early in boot
lib: rspdm: Support SPDM certificate validation
rust: allow extracting the buffer from a CString
lib: rspdm: Support SPDM challenge
PCI/CMA: Expose in sysfs whether devices are authenticated
rust: add bindings for hash_info

Benno Lossin (1):
rust: add untrusted data abstraction

Jonathan Cameron (1):
PCI/CMA: Authenticate devices on enumeration

Lukas Wunner (7):
X.509: Make certificate parser public
X.509: Parse Subject Alternative Name in certificates X.509: Move certificate length retrieval into new helper
certs: Create blacklist keyring earlier
PCI/CMA: Validate Subject Alternative Name in certificates PCI/CMA: Reauthenticate devices on reset and resume
rspdm: Multicast received signatures via netlink

 Documentation/ABI/testing/sysfs-devices-spdm |   31 +
 Documentation/netlink/specs/spdm.yaml        |  136 +++
 MAINTAINERS                                  |   14 +
 certs/blacklist.c                            |    4 +-
 certs/system_keyring.c                       |    8 +-
 crypto/asymmetric_keys/asymmetric_type.c     |    2 +-
 crypto/asymmetric_keys/x509_cert_parser.c    |    9 +
 crypto/asymmetric_keys/x509_loader.c         |   38 +-
 crypto/asymmetric_keys/x509_parser.h         |   42 +-
 crypto/asymmetric_keys/x509_public_key.c     |    2 +-
 crypto/ecdsa.c                               |    2 +-
 crypto/sha256.c                              |    2 +-
 crypto/sha3.c                                |    2 +-
 crypto/sha512.c                              |    2 +-
 drivers/pci/Kconfig                          |   16 +
 drivers/pci/Makefile                         |    4 +
 drivers/pci/cma.asn1                         |   41 +
 drivers/pci/cma.c                            |  269 +++++
 drivers/pci/doe.c                            |    5 +-
 drivers/pci/pci-driver.c                     |    1 +
 drivers/pci/pci-sysfs.c                      |    3 +
 drivers/pci/pci.c                            |   12 +-
 drivers/pci/pci.h                            |   15 +
 drivers/pci/pcie/err.c                       |    3 +
 drivers/pci/probe.c                          |    1 +
 drivers/pci/remove.c                         |    1 +
 include/keys/asymmetric-type.h               |    2 +
 include/keys/system_keyring.h                |    4 +
 include/keys/x509-parser.h                   |   57 +
 include/linux/oid_registry.h                 |    3 +
 include/linux/pci-doe.h                      |    4 +
 include/linux/pci.h                          |   16 +
 include/linux/spdm.h                         |   39 +
 include/uapi/linux/spdm_netlink.h            |   49 +
 lib/Kconfig                                  |   17 +
 lib/Makefile                                 |    2 +
 lib/rspdm/Makefile                           |   12 +
 lib/rspdm/consts.rs                          |  213 ++++
 lib/rspdm/lib.rs                             |  180 +++
 lib/rspdm/netlink-autogen.c                  |   33 +
 lib/rspdm/netlink-autogen.h                  |   22 +
 lib/rspdm/req-netlink.c                      |  197 ++++
 lib/rspdm/req-sysfs.c                        |   98 ++
 lib/rspdm/spdm.h                             |   45 +
 lib/rspdm/state.rs                           | 1093 ++++++++++++++++++
 lib/rspdm/sysfs.rs                           |   38 +
 lib/rspdm/validator.rs                       |  489 ++++++++
 rust/bindings/bindings_helper.h              |   11 +
 rust/helpers/hash.c                          |   18 +
 rust/helpers/hash_info.c                     |    8 +
 rust/helpers/helpers.c                       |    2 +
 rust/kernel/error.rs                         |   10 +
 rust/kernel/lib.rs                           |    1 +
 rust/kernel/str.rs                           |    5 +
 rust/kernel/validate.rs                      |  605 ++++++++++
 55 files changed, 3866 insertions(+), 72 deletions(-)
 create mode 100644 Documentation/ABI/testing/sysfs-devices-spdm
 create mode 100644 Documentation/netlink/specs/spdm.yaml
 create mode 100644 drivers/pci/cma.asn1
 create mode 100644 drivers/pci/cma.c
 create mode 100644 include/keys/x509-parser.h
 create mode 100644 include/linux/spdm.h
 create mode 100644 include/uapi/linux/spdm_netlink.h
 create mode 100644 lib/rspdm/Makefile
 create mode 100644 lib/rspdm/consts.rs
 create mode 100644 lib/rspdm/lib.rs
 create mode 100644 lib/rspdm/netlink-autogen.c
 create mode 100644 lib/rspdm/netlink-autogen.h
 create mode 100644 lib/rspdm/req-netlink.c
 create mode 100644 lib/rspdm/req-sysfs.c
 create mode 100644 lib/rspdm/spdm.h
 create mode 100644 lib/rspdm/state.rs
 create mode 100644 lib/rspdm/sysfs.rs
 create mode 100644 lib/rspdm/validator.rs
 create mode 100644 rust/helpers/hash.c
 create mode 100644 rust/helpers/hash_info.c
 create mode 100644 rust/kernel/validate.rs
Signature · 1 line
2.52.0
#1 lore raw

RFC v3 1/27 rust: add untrusted data abstraction

From: Benno Lossin <[email protected]>

When reading data from userspace, hardware or other external untrusted sources, the data must be validated before it is used for logic within the kernel. This abstraction provides a generic newtype wrapper Untrusted; it prevents direct access to the inner type. The only way to use the underlying data is to call .validate() on such a value.

Doing so utilizes the new Validate trait that is responsible for all of the validation logic. This trait gives access to the inner value of Untrusted by means of another newtype wrapper Unvalidated. In contrast to Untrusted, Unvalidated allows direct access and additionally provides several helper functions for slices.

Having these two different newtype wrappers is an idea from Simona Vetter. It has several benefits: it fully prevents safe access to the underlying value of Untrusted without going through the Validate API. Additionally, it allows one to grep for validation logic by simply looking for Unvalidated<.

Any API that reads data from an untrusted source should return Untrusted<T> where T is the type of the underlying untrusted data. This generic allows other abstractions to return their custom type wrapped by Untrusted, signaling to the caller that the data must be validated before use. This allows those abstractions to be used both in a trusted and untrusted manner, increasing their generality. Additionally, using the arbitrary self types feature, APIs can be designed to explicitly read untrusted data:

    impl MyCustomDataSource {
        pub fn read(self: &Untrusted<Self>) -> &Untrusted<[u8]>;
    }

Message-ID: <[email protected]>

---
 rust/kernel/lib.rs      |   1 +
 rust/kernel/validate.rs | 605 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 606 insertions(+)
 create mode 100644 rust/kernel/validate.rs
rust/kernel/lib.rs +1 -0
index 696f62f85eb5..915499704405 100644
@@ -154,6 +154,7 @@
 pub mod uaccess;
 #[cfg(CONFIG_USB = "y")]
 pub mod usb;
+pub mod validate;
 pub mod workqueue;
 pub mod xarray;
 
rust/kernel/validate.rs new file +605 -0

605 lines not on this page · raw message

Signature · 1 line
2.52.0
#2 lore raw

RFC v3 2/27 X.509: Make certificate parser public

In reply to [email protected]

From: Lukas Wunner <[email protected]>

The upcoming support for PCI device authentication with CMA-SPDM (PCIe r6.1 sec 6.31) requires validating the Subject Alternative Name in X.509 certificates.

High-level functions for X.509 parsing such as key_create_or_update() throw away the internal, low-level struct x509_certificate after extracting the struct public_key and public_key_signature from it. The Subject Alternative Name is thus inaccessible when using those functions.

Afford CMA-SPDM access to the Subject Alternative Name by making struct x509_certificate public, together with the functions for parsing an X.509 certificate into such a struct and freeing such a struct.

The private header file x509_parser.h previously included <linux/time.h> for the definition of time64_t. That definition was since moved to <linux/time64.h> by commit 361a3bf00582 ("time64: Add time64.h header and define struct timespec64"), so adjust the #include directive as part of the move to the new public header file <keys/x509-parser.h>.

No functional change intended.

---
 crypto/asymmetric_keys/x509_parser.h | 42 +--------------------
 include/keys/x509-parser.h           | 55 ++++++++++++++++++++++++++++
 2 files changed, 56 insertions(+), 41 deletions(-)
 create mode 100644 include/keys/x509-parser.h
crypto/asymmetric_keys/x509_parser.h +1 -41
index b7aeebdddb36..39f1521b773d 100644
@@ -5,51 +5,11 @@
  * Written by David Howells ([email protected])
  */
 
-#include <linux/cleanup.h>
-#include <linux/time.h>
-#include <crypto/public_key.h>
-#include <keys/asymmetric-type.h>
-#include <crypto/sha2.h>
-
-struct x509_certificate {
-	struct x509_certificate *next;
-	struct x509_certificate *signer;	/* Certificate that signed this one */
-	struct public_key *pub;			/* Public key details */
-	struct public_key_signature *sig;	/* Signature parameters */
-	u8		sha256[SHA256_DIGEST_SIZE]; /* Hash for blacklist purposes */
-	char		*issuer;		/* Name of certificate issuer */
-	char		*subject;		/* Name of certificate subject */
-	struct asymmetric_key_id *id;		/* Issuer + Serial number */
-	struct asymmetric_key_id *skid;		/* Subject + subjectKeyId (optional) */
-	time64_t	valid_from;
-	time64_t	valid_to;
-	const void	*tbs;			/* Signed data */
-	unsigned	tbs_size;		/* Size of signed data */
-	unsigned	raw_sig_size;		/* Size of signature */
-	const void	*raw_sig;		/* Signature data */
-	const void	*raw_serial;		/* Raw serial number in ASN.1 */
-	unsigned	raw_serial_size;
-	unsigned	raw_issuer_size;
-	const void	*raw_issuer;		/* Raw issuer name in ASN.1 */
-	const void	*raw_subject;		/* Raw subject name in ASN.1 */
-	unsigned	raw_subject_size;
-	unsigned	raw_skid_size;
-	const void	*raw_skid;		/* Raw subjectKeyId in ASN.1 */
-	unsigned	index;
-	bool		seen;			/* Infinite recursion prevention */
-	bool		verified;
-	bool		self_signed;		/* T if self-signed (check unsupported_sig too) */
-	bool		unsupported_sig;	/* T if signature uses unsupported crypto */
-	bool		blacklisted;
-};
+#include <keys/x509-parser.h>
 
 /*
  * x509_cert_parser.c
  */
-extern void x509_free_certificate(struct x509_certificate *cert);
-DEFINE_FREE(x509_free_certificate, struct x509_certificate *,
-	    if (!IS_ERR(_T)) x509_free_certificate(_T))
-extern struct x509_certificate *x509_cert_parse(const void *data, size_t datalen);
 extern int x509_decode_time(time64_t *_t,  size_t hdrlen,
 			    unsigned char tag,
 			    const unsigned char *value, size_t vlen);
include/keys/x509-parser.h new file +55 -0

55 lines not on this page · raw message

Signature · 1 line
2.52.0
#3 lore raw

RFC v3 3/27 X.509: Parse Subject Alternative Name in certificates

In reply to [email protected]

From: Lukas Wunner <[email protected]>

The upcoming support for PCI device authentication with CMA-SPDM (PCIe r6.1 sec 6.31) requires validating the Subject Alternative Name in X.509 certificates.

Store a pointer to the Subject Alternative Name upon parsing for consumption by CMA-SPDM.

---
 crypto/asymmetric_keys/x509_cert_parser.c | 9 +++++++++
 include/keys/x509-parser.h                | 2 ++
 2 files changed, 11 insertions(+)
crypto/asymmetric_keys/x509_cert_parser.c +9 -0
index 2fe094f5caf3..363acd87dba1 100644
@@ -596,6 +596,15 @@ int x509_process_extension(void *context, size_t hdrlen,
 		return 0;
 	}
 
+	if (ctx->last_oid == OID_subjectAltName) {
+		if (ctx->cert->raw_san)
+			return -EBADMSG;
+
+		ctx->cert->raw_san = v;
+		ctx->cert->raw_san_size = vlen;
+		return 0;
+	}
+
 	if (ctx->last_oid == OID_keyUsage) {
 		/*
 		 * Get hold of the keyUsage bit string
include/keys/x509-parser.h +2 -0
index 8b68e720693a..4e6a05a8c7a6 100644
@@ -38,6 +38,8 @@ struct x509_certificate {
 	unsigned	raw_subject_size;
 	unsigned	raw_skid_size;
 	const void	*raw_skid;		/* Raw subjectKeyId in ASN.1 */
+	const void	*raw_san;		/* Raw subjectAltName in ASN.1 */
+	unsigned	raw_san_size;
 	unsigned	index;
 	bool		seen;			/* Infinite recursion prevention */
 	bool		verified;
Signature · 1 line
2.52.0
#4 lore raw

RFC v3 4/27 X.509: Move certificate length retrieval into new helper

In reply to [email protected]

From: Lukas Wunner <[email protected]>

The upcoming in-kernel SPDM library (Security Protocol and Data Model,
https://www.dmtf.org/dsp/DSP0274) needs to retrieve the length from ASN.1 DER-encoded X.509 certificates.

Such code already exists in x509_load_certificate_list(), so move it into a new helper for reuse by SPDM.

Export the helper so that SPDM can be tristate. (Some upcoming users of the SPDM libray may be modular, such as SCSI and ATA.)

No functional change intended.

---
 crypto/asymmetric_keys/x509_loader.c | 38 +++++++++++++++++++---------
 include/keys/asymmetric-type.h       |  2 ++
 2 files changed, 28 insertions(+), 12 deletions(-)
crypto/asymmetric_keys/x509_loader.c +26 -12
index a41741326998..25ff027fad1d 100644
@@ -4,28 +4,42 @@
 #include <linux/key.h>
 #include <keys/asymmetric-type.h>
 
+ssize_t x509_get_certificate_length(const u8 *p, unsigned long buflen)
+{
+	ssize_t plen;
+
+	/* Each cert begins with an ASN.1 SEQUENCE tag and must be more
+	 * than 256 bytes in size.
+	 */
+	if (buflen < 4)
+		return -EINVAL;
+
+	if (p[0] != 0x30 &&
+	    p[1] != 0x82)
+		return -EINVAL;
+
+	plen = (p[2] << 8) | p[3];
+	plen += 4;
+	if (plen > buflen)
+		return -EINVAL;
+
+	return plen;
+}
+EXPORT_SYMBOL_GPL(x509_get_certificate_length);
+
 int x509_load_certificate_list(const u8 cert_list[],
 			       const unsigned long list_size,
 			       const struct key *keyring)
 {
 	key_ref_t key;
 	const u8 *p, *end;
-	size_t plen;
+	ssize_t plen;
 
 	p = cert_list;
 	end = p + list_size;
 	while (p < end) {
-		/* Each cert begins with an ASN.1 SEQUENCE tag and must be more
-		 * than 256 bytes in size.
-		 */
-		if (end - p < 4)
-			goto dodgy_cert;
-		if (p[0] != 0x30 &&
-		    p[1] != 0x82)
-			goto dodgy_cert;
-		plen = (p[2] << 8) | p[3];
-		plen += 4;
-		if (plen > end - p)
+		plen = x509_get_certificate_length(p, end - p);
+		if (plen < 0)
 			goto dodgy_cert;
 
 		key = key_create_or_update(make_key_ref(keyring, 1),
include/keys/asymmetric-type.h +2 -0
index 1b91c8f98688..301efa952e26 100644
@@ -84,6 +84,8 @@ extern struct key *find_asymmetric_key(struct key *keyring,
 				       const struct asymmetric_key_id *id_2,
 				       bool partial);
 
+ssize_t x509_get_certificate_length(const u8 *p, unsigned long buflen);
+
 int x509_load_certificate_list(const u8 cert_list[], const unsigned long list_size,
 			       const struct key *keyring);
 
Signature · 1 line
2.52.0
#5 lore raw

RFC v3 5/27 certs: Create blacklist keyring earlier

In reply to [email protected]

From: Lukas Wunner <[email protected]>

The upcoming support for PCI device authentication with CMA-SPDM (PCIe r6.2 sec 6.31) requires parsing X.509 certificates upon device enumeration, which happens in a subsys_initcall().

Parsing X.509 certificates accesses the blacklist keyring:
x509_cert_parse()
x509_get_sig_params()
is_hash_blacklisted()
keyring_search()

So far the keyring is created much later in a device_initcall(). Avoid a NULL pointer dereference on access to the keyring by creating it one initcall level earlier than PCI device enumeration, i.e. in an arch_initcall().

---
 certs/blacklist.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
certs/blacklist.c +2 -2
index 675dd7a8f07a..34185415d451 100644
@@ -311,7 +311,7 @@ static int restrict_link_for_blacklist(struct key *dest_keyring,
  * Initialise the blacklist
  *
  * The blacklist_init() function is registered as an initcall via
- * device_initcall().  As a result if the blacklist_init() function fails for
+ * arch_initcall().  As a result if the blacklist_init() function fails for
  * any reason the kernel continues to execute.  While cleanly returning -ENODEV
  * could be acceptable for some non-critical kernel parts, if the blacklist
  * keyring fails to load it defeats the certificate/key based deny list for
@@ -356,7 +356,7 @@ static int __init blacklist_init(void)
 /*
  * Must be initialised before we try and load the keys into the keyring.
  */
-device_initcall(blacklist_init);
+arch_initcall(blacklist_init);
 
 #ifdef CONFIG_SYSTEM_REVOCATION_LIST
 /*
Signature · 1 line
2.52.0
#6 lore raw

RFC v3 6/27 rust: add bindings for hash.h

In reply to [email protected]

From: Alistair Francis <[email protected]>

Make the functions crypto_shash_descsize(), crypto_shash_digestsize() and crypto_free_shash() available to Rust.

---
 rust/bindings/bindings_helper.h |  2 ++
 rust/helpers/hash.c             | 18 ++++++++++++++++++
 rust/helpers/helpers.c          |  1 +
 3 files changed, 21 insertions(+)
 create mode 100644 rust/helpers/hash.c
rust/bindings/bindings_helper.h +2 -0
index a067038b4b42..0075c4b62c29 100644
@@ -34,6 +34,7 @@
 #include <drm/drm_file.h>
 #include <drm/drm_gem.h>
 #include <drm/drm_ioctl.h>
+#include <crypto/hash.h>
 #include <kunit/test.h>
 #include <linux/auxiliary_bus.h>
 #include <linux/bitmap.h>
@@ -60,6 +61,7 @@
 #include <linux/fs.h>
 #include <linux/i2c.h>
 #include <linux/ioport.h>
+#include <linux/hash.h>
 #include <linux/jiffies.h>
 #include <linux/jump_label.h>
 #include <linux/mdio.h>
rust/helpers/hash.c new file +18 -0
new file mode 100644
index 000000000000..8ddb84668841
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <crypto/hash.h>
+
+unsigned int rust_helper_crypto_shash_descsize(struct crypto_shash *tfm)
+{
+	return crypto_shash_descsize(tfm);
+}
+
+unsigned int rust_helper_crypto_shash_digestsize(struct crypto_shash *tfm)
+{
+	return crypto_shash_digestsize(tfm);
+}
+
+void rust_helper_crypto_free_shash(struct crypto_shash *tfm)
+{
+	crypto_free_shash(tfm);
+}
rust/helpers/helpers.c +1 -0
index a3c42e51f00a..4b08b4f1d3a3 100644
@@ -30,6 +30,7 @@
 #include "dma.c"
 #include "drm.c"
 #include "err.c"
+#include "hash.c"
 #include "irq.c"
 #include "fs.c"
 #include "io.c"
Signature · 1 line
2.52.0
reviewer
#7 lore raw

Re: [RFC v3 06/27] rust: add bindings for hash.h

Quoted 44 lines · On 2026-02-11 03:29, [email protected] wrote:

From: Alistair Francis <[email protected]>

Make the functions crypto_shash_descsize(), crypto_shash_digestsize() and crypto_free_shash() available to Rust.

---
 rust/bindings/bindings_helper.h |  2 ++
 rust/helpers/hash.c             | 18 ++++++++++++++++++
 rust/helpers/helpers.c          |  1 +
 3 files changed, 21 insertions(+)
 create mode 100644 rust/helpers/hash.c
rust/bindings/bindings_helper.h +2 -0
index a067038b4b42..0075c4b62c29 100644
@@ -34,6 +34,7 @@
 #include <drm/drm_file.h>
 #include <drm/drm_gem.h>
 #include <drm/drm_ioctl.h>
+#include <crypto/hash.h>
 #include <kunit/test.h>
 #include <linux/auxiliary_bus.h>
 #include <linux/bitmap.h>
@@ -60,6 +61,7 @@
 #include <linux/fs.h>
 #include <linux/i2c.h>
 #include <linux/ioport.h>
+#include <linux/hash.h>
 #include <linux/jiffies.h>
 #include <linux/jump_label.h>
 #include <linux/mdio.h>
rust/helpers/hash.c new file +5 -0
new file mode 100644
index 000000000000..8ddb84668841
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <crypto/hash.h>
+
+unsigned int rust_helper_crypto_shash_descsize(struct crypto_shash 
Quoted 1 line

*tfm)

Please prefix all helpers with __rust_helper.

Same for patch 26.

Best,
Gary

Quoted 26 lines
rust/helpers/hash.c new file +5 -0
+{
+	return crypto_shash_descsize(tfm);
+}
+
+unsigned int rust_helper_crypto_shash_digestsize(struct crypto_shash 

*tfm)

rust/helpers/hash.c new file +8 -0
+{
+	return crypto_shash_digestsize(tfm);
+}
+
+void rust_helper_crypto_free_shash(struct crypto_shash *tfm)
+{
+	crypto_free_shash(tfm);
+}
rust/helpers/helpers.c +1 -0
index a3c42e51f00a..4b08b4f1d3a3 100644
@@ -30,6 +30,7 @@
 #include "dma.c"
 #include "drm.c"
 #include "err.c"
+#include "hash.c"
 #include "irq.c"
 #include "fs.c"
 #include "io.c"
#8 lore raw

Re: [RFC v3 06/27] rust: add bindings for hash.h

In reply to [email protected]
Quoted 18 lines · On Wed, 11 Feb 2026 13:29:13 +1000 [email protected] wrote:

From: Alistair Francis <[email protected]>

Make the functions crypto_shash_descsize(), crypto_shash_digestsize() and crypto_free_shash() available to Rust.

---
 rust/bindings/bindings_helper.h |  2 ++
 rust/helpers/hash.c             | 18 ++++++++++++++++++
 rust/helpers/helpers.c          |  1 +
 3 files changed, 21 insertions(+)
 create mode 100644 rust/helpers/hash.c
rust/bindings/bindings_helper.h +0 -0
index a067038b4b42..0075c4b62c29 100644

rust/bindings/bindings_helper.h line 37

Quoted 4 lines
rust/bindings/bindings_helper.h +1 -0
@@ -34,6 +34,7 @@
 #include <drm/drm_file.h>
 #include <drm/drm_gem.h>
 #include <drm/drm_ioctl.h>
+#include <crypto/hash.h>

I'm not sure on ordering conventions but this feels odd.. Maybe before the drm inclues?

Quoted 4 lines
rust/bindings/bindings_helper.h +0 -0
 #include <kunit/test.h>
 #include <linux/auxiliary_bus.h>
 #include <linux/bitmap.h>
@@ -60,6 +61,7 @@
#9 lore raw

RFC v3 7/27 rust: error: impl From<FromBytesWithNulError> for Kernel Error

In reply to [email protected]

From: Alistair Francis <[email protected]>

Implement From<FromBytesWithNulError> for the Kernel Error type

---
 rust/kernel/error.rs | 7 +++++++
 1 file changed, 7 insertions(+)
rust/kernel/error.rs +7 -0
index 258b12afdcba..569d9d032ab3 100644
@@ -12,6 +12,7 @@
     str::CStr,
 };
 
+use core::ffi::FromBytesWithNulError;
 use core::num::NonZeroI32;
 use core::num::TryFromIntError;
 use core::str::Utf8Error;
@@ -251,6 +252,12 @@ fn from(e: core::convert::Infallible) -> Error {
     }
 }
 
+impl From<FromBytesWithNulError> for Error {
+    fn from(_: FromBytesWithNulError) -> Error {
+        code::EINVAL
+    }
+}
+
 /// A [`Result`] with an [`Error`] error type.
 ///
 /// To be used as the return type for functions that may fail.
Signature · 1 line
2.52.0
reviewer
#10 lore raw

Re: [RFC v3 07/27] rust: error: impl From<FromBytesWithNulError> for Kernel Error

Quoted 22 lines · On 2026-02-11 03:29, [email protected] wrote:

From: Alistair Francis <[email protected]>

Implement From<FromBytesWithNulError> for the Kernel Error type

---
 rust/kernel/error.rs | 7 +++++++
 1 file changed, 7 insertions(+)
rust/kernel/error.rs +1 -0
index 258b12afdcba..569d9d032ab3 100644
@@ -12,6 +12,7 @@
     str::CStr,
 };
 
+use core::ffi::FromBytesWithNulError;
 use core::num::NonZeroI32;
 use core::num::TryFromIntError;
 use core::str::Utf8Error;

rust/kernel/error.rs fn from(e: core::convert::Infallible) -> Error { · line 259

Quoted 8 lines
rust/kernel/error.rs +5 -0
@@ -251,6 +252,12 @@ fn from(e: core::convert::Infallible) -> Error {
     }
 }
 
+impl From<FromBytesWithNulError> for Error {
+    fn from(_: FromBytesWithNulError) -> Error {
+        code::EINVAL
+    }
+}

Are we sure that FromBytesWithNulError maps cleanly to the EINVAL error code?

Anyhow, please add #[inline] for such simple functions.

Best,
Gary

Quoted 4 lines
rust/kernel/error.rs +1 -0
+
 /// A [`Result`] with an [`Error`] error type.
 ///
 /// To be used as the return type for functions that may fail.
#11 lore raw

Re: [RFC v3 07/27] rust: error: impl From<FromBytesWithNulError> for Kernel Error

Quoted 27 lines · Gary Guo
Quoted 25 lines · On 2026-02-11 03:29, [email protected] wrote:

From: Alistair Francis <[email protected]>

Implement From<FromBytesWithNulError> for the Kernel Error type

---
 rust/kernel/error.rs | 7 +++++++
 1 file changed, 7 insertions(+)
rust/kernel/error.rs +1 -0
index 258b12afdcba..569d9d032ab3 100644
@@ -12,6 +12,7 @@
     str::CStr,
 };
 
+use core::ffi::FromBytesWithNulError;
 use core::num::NonZeroI32;
 use core::num::TryFromIntError;
 use core::str::Utf8Error;
@@ -251,6 +252,12 @@ fn from(e: core::convert::Infallible) -> Error {
     }
 }
 
Quoted 8 lines

rust/kernel/error.rs fn from(e: core::convert::Infallible) -> Error { · line 259

Quoted 5 lines
rust/kernel/error.rs +5 -0
+impl From<FromBytesWithNulError> for Error {
+    fn from(_: FromBytesWithNulError) -> Error {
+        code::EINVAL
+    }
+}

Are we sure that FromBytesWithNulError maps cleanly to the EINVAL error code?

FromBytesWithNulError means "An error indicating that a nul byte was not in the expected position." [1]

To me that maps with EINVAL

1: https://doc.rust-lang.org/std/ffi/enum.FromBytesWithNulError.html

Quoted 2 lines

Anyhow, please add #[inline] for such simple functions.

I can, just noting that none of the others have #[inline]

Alistair

Quoted 8 lines

Best,
Gary

Quoted 4 lines
rust/kernel/error.rs +1 -0
+
 /// A [`Result`] with an [`Error`] error type.
 ///
 /// To be used as the return type for functions that may fail.
reviewer
#12 lore raw

Re: [RFC v3 07/27] rust: error: impl From<FromBytesWithNulError> for Kernel Error

Quoted 41 lines · Alistair Francis
Quoted 27 lines · Gary Guo
Quoted 25 lines · On 2026-02-11 03:29, [email protected] wrote:

From: Alistair Francis <[email protected]>

Implement From<FromBytesWithNulError> for the Kernel Error type

---
 rust/kernel/error.rs | 7 +++++++
 1 file changed, 7 insertions(+)
rust/kernel/error.rs +1 -0
index 258b12afdcba..569d9d032ab3 100644
@@ -12,6 +12,7 @@
     str::CStr,
 };
 
+use core::ffi::FromBytesWithNulError;
 use core::num::NonZeroI32;
 use core::num::TryFromIntError;
 use core::str::Utf8Error;
@@ -251,6 +252,12 @@ fn from(e: core::convert::Infallible) -> Error {
     }
 }
 
Quoted 8 lines

rust/kernel/error.rs fn from(e: core::convert::Infallible) -> Error { · line 259

Quoted 5 lines
rust/kernel/error.rs +5 -0
+impl From<FromBytesWithNulError> for Error {
+    fn from(_: FromBytesWithNulError) -> Error {
+        code::EINVAL
+    }
+}

Are we sure that FromBytesWithNulError maps cleanly to the EINVAL error code?

FromBytesWithNulError means "An error indicating that a nul byte was not in the expected position." [1]

Quoted 8 lines

To me that maps with EINVAL

1: https://doc.rust-lang.org/std/ffi/enum.FromBytesWithNulError.html

Quoted 2 lines

Anyhow, please add #[inline] for such simple functions.

I can, just noting that none of the others have #[inline]

I think add #[inline] to these could be a nice easy issue for new contributors. If you want, you can open an issue on our github. Alice

#13 lore raw

RFC v3 8/27 lib: rspdm: Initial commit of Rust SPDM

In reply to [email protected]

From: Alistair Francis <[email protected]>

This is the initial commit of the Rust SPDM library. It is based on and compatible with the C SPDM library in the kernel (lib/spdm).

---
 MAINTAINERS                     |  12 ++
 include/linux/spdm.h            |  39 ++++++
 lib/Kconfig                     |  17 +++
 lib/Makefile                    |   2 +
 lib/rspdm/Makefile              |  10 ++
 lib/rspdm/consts.rs             | 117 +++++++++++++++++
 lib/rspdm/lib.rs                | 119 +++++++++++++++++
 lib/rspdm/state.rs              | 220 ++++++++++++++++++++++++++++++++
 lib/rspdm/validator.rs          |  66 ++++++++++
 rust/bindings/bindings_helper.h |   2 +
 rust/kernel/error.rs            |   3 +
 11 files changed, 607 insertions(+)
 create mode 100644 include/linux/spdm.h
 create mode 100644 lib/rspdm/Makefile
 create mode 100644 lib/rspdm/consts.rs
 create mode 100644 lib/rspdm/lib.rs
 create mode 100644 lib/rspdm/state.rs
 create mode 100644 lib/rspdm/validator.rs
MAINTAINERS +12 -0
index 149deedafe2c..a5c4ec16081c 100644
@@ -23693,6 +23693,18 @@ M:	Security Officers <[email protected]>
 S:	Supported
 F:	Documentation/process/security-bugs.rst
 
+SECURITY PROTOCOL AND DATA MODEL (SPDM)
+M:	Jonathan Cameron <[email protected]>
+M:	Lukas Wunner <[email protected]>
+M:	Alistair Francis <[email protected]>
+L:	[email protected]
+L:	[email protected]
+L:	[email protected]
+S:	Maintained
+T:	git git://git.kernel.org/pub/scm/linux/kernel/git/devsec/spdm.git
+F:	include/linux/spdm.h
+F:	lib/rspdm/
+
 SECURITY SUBSYSTEM
 M:	Paul Moore <[email protected]>
 M:	James Morris <[email protected]>
include/linux/spdm.h new file +39 -0
new file mode 100644
index 000000000000..9835a3202a0e
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * DMTF Security Protocol and Data Model (SPDM)
+ * https://www.dmtf.org/dsp/DSP0274
+ *
+ * Copyright (C) 2021-22 Huawei
+ *     Jonathan Cameron <[email protected]>
+ *
+ * Copyright (C) 2022-24 Intel Corporation
+ */
+
+#ifndef _SPDM_H_
+#define _SPDM_H_
+
+#include <linux/types.h>
+
+struct key;
+struct device;
+struct spdm_state;
+struct x509_certificate;
+
+typedef ssize_t (spdm_transport)(void *priv, struct device *dev,
+				 const void *request, size_t request_sz,
+				 void *response, size_t response_sz);
+
+typedef int (spdm_validate)(struct device *dev, u8 slot,
+			    struct x509_certificate *leaf_cert);
+
+struct spdm_state *spdm_create(struct device *dev, spdm_transport *transport,
+			       void *transport_priv, u32 transport_sz,
+			       struct key *keyring, spdm_validate *validate);
+
+int spdm_authenticate(struct spdm_state *spdm_state);
+
+void spdm_destroy(struct spdm_state *spdm_state);
+
+extern const struct attribute_group spdm_attr_group;
+
+#endif
lib/Kconfig +17 -0
index 2923924bea78..c21f9f9a5221 100644
@@ -604,6 +604,23 @@ config LWQ_TEST
 	help
           Run boot-time test of light-weight queuing.
 
+config RSPDM
+	bool "Rust SPDM"
+	select RUST
+	select CRYPTO
+	select KEYS
+	select ASYMMETRIC_KEY_TYPE
+	select ASYMMETRIC_PUBLIC_KEY_SUBTYPE
+	select X509_CERTIFICATE_PARSER
+	help
+	  The Rust implementation of the Security Protocol and Data Model (SPDM)
+	  allows for device authentication, measurement, key exchange and
+	  encrypted sessions.
+
+	  Crypto algorithms negotiated with SPDM are limited to those enabled
+	  in .config.  Users of SPDM therefore need to also select
+	  any algorithms they deem mandatory.
+
 endmenu
 
 config GENERIC_IOREMAP
lib/Makefile +2 -0
index 22d8742bba57..2f530471e8dc 100644
@@ -286,6 +286,8 @@ obj-$(CONFIG_PERCPU_TEST) += percpu_test.o
 obj-$(CONFIG_ASN1) += asn1_decoder.o
 obj-$(CONFIG_ASN1_ENCODER) += asn1_encoder.o
 
+obj-$(CONFIG_RSPDM) += rspdm/
+
 obj-$(CONFIG_FONT_SUPPORT) += fonts/
 
 #
lib/rspdm/Makefile new file +10 -0
new file mode 100644
index 000000000000..1f62ee2a882d
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Rust implementation of the DMTF Security Protocol and Data Model (SPDM)
+# https://www.dmtf.org/dsp/DSP0274
+#
+# Copyright (C) 2024 Western Digital
+
+obj-$(CONFIG_RSPDM) += spdm.o
+
+spdm-y := lib.o
lib/rspdm/consts.rs new file +117 -0

117 lines not on this page · raw message

lib/rspdm/lib.rs new file +119 -0

119 lines not on this page · raw message

lib/rspdm/state.rs new file +220 -0

220 lines not on this page · raw message

lib/rspdm/validator.rs new file +66 -0

66 lines not on this page · raw message

rust/bindings/bindings_helper.h +2 -0
index 0075c4b62c29..5043eee2a8d6 100644
@@ -84,7 +84,9 @@
 #include <linux/slab.h>
 #include <linux/task_work.h>
 #include <linux/tracepoint.h>
+#include <linux/spdm.h>
 #include <linux/usb.h>
+#include <linux/uaccess.h>
 #include <linux/wait.h>
 #include <linux/workqueue.h>
 #include <linux/xarray.h>
rust/kernel/error.rs +3 -0
index 569d9d032ab3..c9f57082e481 100644
@@ -88,6 +88,9 @@ macro_rules! declare_err {
     declare_err!(EIOCBQUEUED, "iocb queued, will get completion event.");
     declare_err!(ERECALLCONFLICT, "Conflict with recalled state.");
     declare_err!(ENOGRACE, "NFS file lock reclaim refused.");
+    declare_err!(ECONNRESET, "Connection reset by peer.");
+    declare_err!(EMSGSIZE, "Message too long.");
+    declare_err!(EINPROGRESS, "Operation now in progress.");
 }
 
 /// Generic integer kernel error.
Signature · 1 line
2.52.0
#14 lore raw

Re: [RFC v3 08/27] lib: rspdm: Initial commit of Rust SPDM

Quoted 6 lines · On Wed, 11 Feb 2026 13:29:15 +1000 [email protected] wrote:

From: Alistair Francis <[email protected]>

This is the initial commit of the Rust SPDM library. It is based on and compatible with the C SPDM library in the kernel (lib/spdm).

The comments that follow are based on my very limited rust knowledge. Hence may be garbage.

Quoted 25 lines
---
 MAINTAINERS                     |  12 ++
 include/linux/spdm.h            |  39 ++++++
 lib/Kconfig                     |  17 +++
 lib/Makefile                    |   2 +
 lib/rspdm/Makefile              |  10 ++
 lib/rspdm/consts.rs             | 117 +++++++++++++++++
 lib/rspdm/lib.rs                | 119 +++++++++++++++++
 lib/rspdm/state.rs              | 220 ++++++++++++++++++++++++++++++++
 lib/rspdm/validator.rs          |  66 ++++++++++
 rust/bindings/bindings_helper.h |   2 +
 rust/kernel/error.rs            |   3 +
 11 files changed, 607 insertions(+)
 create mode 100644 include/linux/spdm.h
 create mode 100644 lib/rspdm/Makefile
 create mode 100644 lib/rspdm/consts.rs
 create mode 100644 lib/rspdm/lib.rs
 create mode 100644 lib/rspdm/state.rs
 create mode 100644 lib/rspdm/validator.rs
MAINTAINERS +0 -0
index 149deedafe2c..a5c4ec16081c 100644

MAINTAINERS M: Security Officers <[email protected]> · line 23697

Quoted 5 lines
MAINTAINERS +2 -0
@@ -23693,6 +23693,18 @@ M:	Security Officers <[email protected]>
 S:	Supported
 F:	Documentation/process/security-bugs.rst
 
+SECURITY PROTOCOL AND DATA MODEL (SPDM)
+M:	Jonathan Cameron <[email protected]>

Evil way to get me to learn rust. Ah well, I guess I can't put it off for ever.

Quoted 24 lines
MAINTAINERS +9 -0
+M:	Lukas Wunner <[email protected]>
+M:	Alistair Francis <[email protected]>
+L:	[email protected]
+L:	[email protected]
+L:	[email protected]
+S:	Maintained
+T:	git git://git.kernel.org/pub/scm/linux/kernel/git/devsec/spdm.git
+F:	include/linux/spdm.h
+F:	lib/rspdm/
 
 
lib/rspdm/consts.rs new file +7 -0
new file mode 100644
index 000000000000..40ce60eba2f3
@@ -0,0 +1,117 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Copyright (C) 2024 Western Digital
+
+//! Constants used by the library
+//!
+//! Rust implementation of the DMTF Security Protocol and Data Model (SPDM)

lib/rspdm/consts.rs line 17

Quoted 10 lines
lib/rspdm/consts.rs new file +10 -0
+//! <https://www.dmtf.org/dsp/DSP0274>
+
+pub(crate) const SPDM_REQ: u8 = 0x80;
+pub(crate) const SPDM_ERROR: u8 = 0x7f;
+
+#[expect(dead_code)]
+#[derive(Clone, Copy)]
+pub(crate) enum SpdmErrorCode {
+    InvalidRequest = 0x01,
+    InvalidSession = 0x02,

This is reserved by the time we reach 1.2.1 and disappeared somewhere in the middle of 1.1.0 (present) and 1.1.2 (reserved) Probably good to leave some breadcrumbs for what spec versions could use this error code. That will reduce confusion for future readers. You have this info in the parsing code, but I'd like it here as well.

Quoted 19 lines
lib/rspdm/consts.rs new file +19 -0
+    Busy = 0x03,
+    UnexpectedRequest = 0x04,
+    Unspecified = 0x05,
+    DecryptError = 0x06,
+    UnsupportedRequest = 0x07,
+    RequestInFlight = 0x08,
+    InvalidResponseCode = 0x09,
+    SessionLimitExceeded = 0x0a,
+    SessionRequired = 0x0b,
+    ResetRequired = 0x0c,
+    ResponseTooLarge = 0x0d,
+    RequestTooLarge = 0x0e,
+    LargeResponse = 0x0f,
+    MessageLost = 0x10,
+    InvalidPolicy = 0x11,
+    VersionMismatch = 0x41,
+    ResponseNotReady = 0x42,
+    RequestResynch = 0x43,
+    OperationFailed = 0x44,

lib/rspdm/consts.rs line 46

Quoted 10 lines
lib/rspdm/consts.rs new file +10 -0
+    NoPendingRequests = 0x45,
+    VendorDefinedError = 0xff,
+}
+
+impl core::fmt::LowerHex for SpdmErrorCode {
+    /// A debug print format for the SpdmSessionInfo struct
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        match self {
+            SpdmErrorCode::InvalidRequest => {
+                writeln!(f, "0x01")?;

No way to get a string from an enum value in rust? Having to check these against the enum above is a bit tedious.

Quoted 15 lines
lib/rspdm/consts.rs new file +5 -0
+            }
 
+        }
+        Ok(())
+    }
+}
lib/rspdm/lib.rs new file +3 -0
new file mode 100644
index 000000000000..2bb716140e0a
@@ -0,0 +1,119 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Copyright (C) 2024 Western Digital

lib/rspdm/lib.rs line 13

Quoted 10 lines
lib/rspdm/lib.rs new file +10 -0
+
+//! Top level library for SPDM
+//!
+//! Rust implementation of the DMTF Security Protocol and Data Model (SPDM)
+//! <https://www.dmtf.org/dsp/DSP0274>
+//!
+//! Top level library, including C compatible public functions to be called
+//! from other subsytems.
+//!
+//! This mimics the C SPDM implementation in the kernel

The one that never gets merged if this goes according to plan ;)

Quoted 26 lines
lib/rspdm/state.rs new file +20 -0
new file mode 100644
index 000000000000..68861f30e3fa
@@ -0,0 +1,220 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Copyright (C) 2024 Western Digital
+
+//! The `SpdmState` struct and implementation.
+//!
+//! Rust implementation of the DMTF Security Protocol and Data Model (SPDM)
+//! <https://www.dmtf.org/dsp/DSP0274>
+
+use core::ffi::c_void;
+use kernel::prelude::*;
+use kernel::{
+    bindings,
+    error::{code::EINVAL, to_result, Error},
+    validate::Untrusted,
+};
+
+use crate::consts::{SpdmErrorCode, SPDM_ERROR, SPDM_REQ};
+use crate::validator::{SpdmErrorRsp, SpdmHeader};
+

lib/rspdm/state.rs line 30

Quoted 10 lines
lib/rspdm/state.rs new file +10 -0
+/// The current SPDM session state for a device. Based on the
+/// C `struct spdm_state`.
+///
+/// `dev`: Responder device.  Used for error reporting and passed to @transport.
+/// `transport`: Transport function to perform one message exchange.
+/// `transport_priv`: Transport private data.
+/// `transport_sz`: Maximum message size the transport is capable of (in bytes).
+///  Used as DataTransferSize in GET_CAPABILITIES exchange.
+/// `keyring`: Keyring against which to check the first certificate in
+///  responder's certificate chain.

Given the discussions, seems this will go away (for now anyway).

Quoted 39 lines
lib/rspdm/state.rs new file +38 -0
+/// `validate`: Function to validate additional leaf certificate requirements.
+///
+/// `version`: Maximum common supported version of requester and responder.
+///  Negotiated during GET_VERSION exchange.
+#[expect(dead_code)]
+pub struct SpdmState {
+    pub(crate) dev: *mut bindings::device,
+    pub(crate) transport: bindings::spdm_transport,
+    pub(crate) transport_priv: *mut c_void,
+    pub(crate) transport_sz: u32,
+    pub(crate) keyring: *mut bindings::key,
+    pub(crate) validate: bindings::spdm_validate,
+
+    // Negotiated state
+    pub(crate) version: u8,
+}
+
+impl SpdmState {
+    pub(crate) fn new(
+        dev: *mut bindings::device,
+        transport: bindings::spdm_transport,
+        transport_priv: *mut c_void,
+        transport_sz: u32,
+        keyring: *mut bindings::key,
+        validate: bindings::spdm_validate,
+    ) -> Self {
+        SpdmState {
+            dev,
+            transport,
+            transport_priv,
+            transport_sz,
+            keyring,
+            validate,
+            version: 0x10,
+        }
+    }
 
+
+    /// Start a SPDM exchange

lib/rspdm/state.rs

Quoted 10 lines
lib/rspdm/state.rs new file +10 -0
+    ///
+    /// The data in `request_buf` is sent to the device and the response is
+    /// stored in `response_buf`.
+    pub(crate) fn spdm_exchange(
+        &self,
+        request_buf: &mut [u8],
+        response_buf: &mut [u8],
+    ) -> Result<i32, Error> {
+        let header_size = core::mem::size_of::<SpdmHeader>();
+        let request: &mut SpdmHeader = Untrusted::new_mut(request_buf).validate_mut()?;

Why are we treating the request, which doesn't come from the device as untrusted? Just for convenience on checking we formatted it right at the upper levels or is the idea that might ultimately be coming from userspace?

Quoted 102 lines
lib/rspdm/state.rs new file +43 -0
+        let response: &SpdmHeader = Untrusted::new_ref(response_buf).validate()?;
+
+        let transport_function = self.transport.ok_or(EINVAL)?;
+        // SAFETY: `transport_function` is provided by the new(), we are
+        // calling the function.
+        let length = unsafe {
+            transport_function(
+                self.transport_priv,
+                self.dev,
+                request_buf.as_ptr() as *const c_void,
+                request_buf.len(),
+                response_buf.as_mut_ptr() as *mut c_void,
+                response_buf.len(),
+            ) as i32
+        };
+        to_result(length)?;
+
+        if (length as usize) < header_size {
+            return Ok(length); // Truncated response is handled by callers
+        }
+        if response.code == SPDM_ERROR {
+            if length as usize >= core::mem::size_of::<SpdmErrorRsp>() {
+                // SAFETY: The response buffer will be at least as large as
+                // `SpdmErrorRsp` so we can cast the buffer to `SpdmErrorRsp` which
+                // is a packed struct.
+                self.spdm_err(unsafe { &*(response_buf.as_ptr() as *const SpdmErrorRsp) })?;
+            } else {
+                return Err(EINVAL);
+            }
+        }
+
+        if response.code != request.code & !SPDM_REQ {
+            pr_err!(
+                "Response code {:#x} does not match request code {:#x}\n",
+                response.code,
+                request.code
+            );
+            to_result(-(bindings::EPROTO as i32))?;
+        }
+
+        Ok(length)
+    }
+}
lib/rspdm/validator.rs new file +53 -0
new file mode 100644
index 000000000000..a0a3a2f46952
@@ -0,0 +1,66 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Copyright (C) 2024 Western Digital
+
+//! Related structs and their Validate implementations.
+//!
+//! Rust implementation of the DMTF Security Protocol and Data Model (SPDM)
+//! <https://www.dmtf.org/dsp/DSP0274>
+
+use crate::consts::SpdmErrorCode;
+use core::mem;
+use kernel::prelude::*;
+use kernel::{
+    error::{code::EINVAL, Error},
+    validate::{Unvalidated, Validate},
+};
+
+#[repr(C, packed)]
+pub(crate) struct SpdmHeader {
+    pub(crate) version: u8,
+    pub(crate) code: u8, /* RequestResponseCode */
+    pub(crate) param1: u8,
+    pub(crate) param2: u8,
+}
+
+impl Validate<&Unvalidated<[u8]>> for &SpdmHeader {
+    type Err = Error;
+
+    fn validate(unvalidated: &Unvalidated<[u8]>) -> Result<Self, Self::Err> {
+        let raw = unvalidated.raw();
+        if raw.len() < mem::size_of::<SpdmHeader>() {
+            return Err(EINVAL);
+        }
+
+        let ptr = raw.as_ptr();
+        // CAST: `SpdmHeader` only contains integers and has `repr(C)`.
+        let ptr = ptr.cast::<SpdmHeader>();
+        // SAFETY: `ptr` came from a reference and the cast above is valid.
+        Ok(unsafe { &*ptr })
+    }
+}
+
+impl Validate<&mut Unvalidated<[u8]>> for &mut SpdmHeader {
+    type Err = Error;
+
+    fn validate(unvalidated: &mut Unvalidated<[u8]>) -> Result<Self, Self::Err> {
+        let raw = unvalidated.raw_mut();
+        if raw.len() < mem::size_of::<SpdmHeader>() {
+            return Err(EINVAL);
+        }
+
+        let ptr = raw.as_mut_ptr();
+        // CAST: `SpdmHeader` only contains integers and has `repr(C)`.

lib/rspdm/validator.rs line 63

Quoted 10 lines
lib/rspdm/validator.rs new file +10 -0
+        let ptr = ptr.cast::<SpdmHeader>();
+        // SAFETY: `ptr` came from a reference and the cast above is valid.
+        Ok(unsafe { &mut *ptr })
+    }
+}
+
+#[repr(C, packed)]
+pub(crate) struct SpdmErrorRsp {
+    pub(crate) version: u8,
+    pub(crate) code: u8,

Maybe document here that this will always be SPDM_ERROR 0x7F

rust/bindings/bindings_helper.h line 87

Quoted 12 lines
lib/rspdm/validator.rs new file +3 -0
+    pub(crate) error_code: SpdmErrorCode,
+    pub(crate) error_data: u8,
+}
rust/bindings/bindings_helper.h +1 -0
index 0075c4b62c29..5043eee2a8d6 100644
@@ -84,7 +84,9 @@
 #include <linux/slab.h>
 #include <linux/task_work.h>
 #include <linux/tracepoint.h>
+#include <linux/spdm.h>

Smells like this is alphabetical order. So move it up a bit?

Quoted 5 lines
rust/bindings/bindings_helper.h +1 -0
 #include <linux/usb.h>
+#include <linux/uaccess.h>
 #include <linux/wait.h>
 #include <linux/workqueue.h>
 #include <linux/xarray.h>
#15 lore raw

Re: [RFC v3 08/27] lib: rspdm: Initial commit of Rust SPDM

Quoted 41 lines · Jonathan Cameron
Quoted 6 lines · On Wed, 11 Feb 2026 13:29:15 +1000 [email protected] wrote:

From: Alistair Francis <[email protected]>

This is the initial commit of the Rust SPDM library. It is based on and compatible with the C SPDM library in the kernel (lib/spdm).

The comments that follow are based on my very limited rust knowledge. Hence may be garbage.

Quoted 28 lines
---
 MAINTAINERS                     |  12 ++
 include/linux/spdm.h            |  39 ++++++
 lib/Kconfig                     |  17 +++
 lib/Makefile                    |   2 +
 lib/rspdm/Makefile              |  10 ++
 lib/rspdm/consts.rs             | 117 +++++++++++++++++
 lib/rspdm/lib.rs                | 119 +++++++++++++++++
 lib/rspdm/state.rs              | 220 ++++++++++++++++++++++++++++++++
 lib/rspdm/validator.rs          |  66 ++++++++++
 rust/bindings/bindings_helper.h |   2 +
 rust/kernel/error.rs            |   3 +
 11 files changed, 607 insertions(+)
 create mode 100644 include/linux/spdm.h
 create mode 100644 lib/rspdm/Makefile
 create mode 100644 lib/rspdm/consts.rs
 create mode 100644 lib/rspdm/lib.rs
 create mode 100644 lib/rspdm/state.rs
 create mode 100644 lib/rspdm/validator.rs
MAINTAINERS +0 -0
index 149deedafe2c..a5c4ec16081c 100644
@@ -23693,6 +23693,18 @@ M:   Security Officers <[email protected]>
 S:   Supported
 F:   Documentation/process/security-bugs.rst
 
Quoted 5 lines

MAINTAINERS M: Security Officers <[email protected]> · line 23697

Quoted 2 lines
MAINTAINERS +2 -0
+SECURITY PROTOCOL AND DATA MODEL (SPDM)
+M:   Jonathan Cameron <[email protected]>

Evil way to get me to learn rust. Ah well, I guess I can't put it off for ever.

It's a great excuse!

Quoted 65 lines
Quoted 25 lines
MAINTAINERS +9 -0
+M:   Lukas Wunner <[email protected]>
+M:   Alistair Francis <[email protected]>
+L:   [email protected]
+L:   [email protected]
+L:   [email protected]
+S:   Maintained
+T:   git git://git.kernel.org/pub/scm/linux/kernel/git/devsec/spdm.git
+F:   include/linux/spdm.h
+F:   lib/rspdm/
 
 
lib/rspdm/consts.rs new file +7 -0
new file mode 100644
index 000000000000..40ce60eba2f3
@@ -0,0 +1,117 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Copyright (C) 2024 Western Digital
+
+//! Constants used by the library
+//!
+//! Rust implementation of the DMTF Security Protocol and Data Model (SPDM)

lib/rspdm/consts.rs line 17

Quoted 10 lines
lib/rspdm/consts.rs new file +10 -0
+//! <https://www.dmtf.org/dsp/DSP0274>
+
+pub(crate) const SPDM_REQ: u8 = 0x80;
+pub(crate) const SPDM_ERROR: u8 = 0x7f;
+
+#[expect(dead_code)]
+#[derive(Clone, Copy)]
+pub(crate) enum SpdmErrorCode {
+    InvalidRequest = 0x01,
+    InvalidSession = 0x02,

This is reserved by the time we reach 1.2.1 and disappeared somewhere in the middle of 1.1.0 (present) and 1.1.2 (reserved) Probably good to leave some breadcrumbs for what spec versions could use this error code. That will reduce confusion for future readers. You have this info in the parsing code, but I'd like it here as well.

Quoted 23 lines
lib/rspdm/consts.rs new file +23 -0
+    Busy = 0x03,
+    UnexpectedRequest = 0x04,
+    Unspecified = 0x05,
+    DecryptError = 0x06,
+    UnsupportedRequest = 0x07,
+    RequestInFlight = 0x08,
+    InvalidResponseCode = 0x09,
+    SessionLimitExceeded = 0x0a,
+    SessionRequired = 0x0b,
+    ResetRequired = 0x0c,
+    ResponseTooLarge = 0x0d,
+    RequestTooLarge = 0x0e,
+    LargeResponse = 0x0f,
+    MessageLost = 0x10,
+    InvalidPolicy = 0x11,
+    VersionMismatch = 0x41,
+    ResponseNotReady = 0x42,
+    RequestResynch = 0x43,
+    OperationFailed = 0x44,
+    NoPendingRequests = 0x45,
+    VendorDefinedError = 0xff,
+}
+
Quoted 8 lines

lib/rspdm/consts.rs line 46

Quoted 6 lines
lib/rspdm/consts.rs new file +6 -0
+impl core::fmt::LowerHex for SpdmErrorCode {
+    /// A debug print format for the SpdmSessionInfo struct
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        match self {
+            SpdmErrorCode::InvalidRequest => {
+                writeln!(f, "0x01")?;

No way to get a string from an enum value in rust?

There is actually, this is unnecessary

Quoted 121 lines

Having to check these against the enum above is a bit tedious.

Quoted 15 lines
lib/rspdm/consts.rs new file +5 -0
+            }
 
+        }
+        Ok(())
+    }
+}
lib/rspdm/lib.rs new file +3 -0
new file mode 100644
index 000000000000..2bb716140e0a
@@ -0,0 +1,119 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Copyright (C) 2024 Western Digital

lib/rspdm/lib.rs line 13

Quoted 10 lines
lib/rspdm/lib.rs new file +10 -0
+
+//! Top level library for SPDM
+//!
+//! Rust implementation of the DMTF Security Protocol and Data Model (SPDM)
+//! <https://www.dmtf.org/dsp/DSP0274>
+//!
+//! Top level library, including C compatible public functions to be called
+//! from other subsytems.
+//!
+//! This mimics the C SPDM implementation in the kernel

The one that never gets merged if this goes according to plan ;)

Quoted 26 lines
lib/rspdm/state.rs new file +20 -0
new file mode 100644
index 000000000000..68861f30e3fa
@@ -0,0 +1,220 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Copyright (C) 2024 Western Digital
+
+//! The `SpdmState` struct and implementation.
+//!
+//! Rust implementation of the DMTF Security Protocol and Data Model (SPDM)
+//! <https://www.dmtf.org/dsp/DSP0274>
+
+use core::ffi::c_void;
+use kernel::prelude::*;
+use kernel::{
+    bindings,
+    error::{code::EINVAL, to_result, Error},
+    validate::Untrusted,
+};
+
+use crate::consts::{SpdmErrorCode, SPDM_ERROR, SPDM_REQ};
+use crate::validator::{SpdmErrorRsp, SpdmHeader};
+

lib/rspdm/state.rs line 30

Quoted 10 lines
lib/rspdm/state.rs new file +10 -0
+/// The current SPDM session state for a device. Based on the
+/// C `struct spdm_state`.
+///
+/// `dev`: Responder device.  Used for error reporting and passed to @transport.
+/// `transport`: Transport function to perform one message exchange.
+/// `transport_priv`: Transport private data.
+/// `transport_sz`: Maximum message size the transport is capable of (in bytes).
+///  Used as DataTransferSize in GET_CAPABILITIES exchange.
+/// `keyring`: Keyring against which to check the first certificate in
+///  responder's certificate chain.

Given the discussions, seems this will go away (for now anyway).

Quoted 39 lines
lib/rspdm/state.rs new file +38 -0
+/// `validate`: Function to validate additional leaf certificate requirements.
+///
+/// `version`: Maximum common supported version of requester and responder.
+///  Negotiated during GET_VERSION exchange.
+#[expect(dead_code)]
+pub struct SpdmState {
+    pub(crate) dev: *mut bindings::device,
+    pub(crate) transport: bindings::spdm_transport,
+    pub(crate) transport_priv: *mut c_void,
+    pub(crate) transport_sz: u32,
+    pub(crate) keyring: *mut bindings::key,
+    pub(crate) validate: bindings::spdm_validate,
+
+    // Negotiated state
+    pub(crate) version: u8,
+}
+
+impl SpdmState {
+    pub(crate) fn new(
+        dev: *mut bindings::device,
+        transport: bindings::spdm_transport,
+        transport_priv: *mut c_void,
+        transport_sz: u32,
+        keyring: *mut bindings::key,
+        validate: bindings::spdm_validate,
+    ) -> Self {
+        SpdmState {
+            dev,
+            transport,
+            transport_priv,
+            transport_sz,
+            keyring,
+            validate,
+            version: 0x10,
+        }
+    }
 
+
+    /// Start a SPDM exchange

lib/rspdm/state.rs

Quoted 10 lines
lib/rspdm/state.rs new file +10 -0
+    ///
+    /// The data in `request_buf` is sent to the device and the response is
+    /// stored in `response_buf`.
+    pub(crate) fn spdm_exchange(
+        &self,
+        request_buf: &mut [u8],
+        response_buf: &mut [u8],
+    ) -> Result<i32, Error> {
+        let header_size = core::mem::size_of::<SpdmHeader>();
+        let request: &mut SpdmHeader = Untrusted::new_mut(request_buf).validate_mut()?;
Quoted 3 lines

Why are we treating the request, which doesn't come from the device as untrusted? Just for convenience on checking we formatted it right at the upper levels or is the idea that might ultimately be coming from userspace?

Just for convenience, it's just a easy way to convert it to a SpdmHeader and perform some simple sanity checks. Your right it's not actually untrusted

Alistair

Quoted 138 lines
Quoted 103 lines
lib/rspdm/state.rs new file +43 -0
+        let response: &SpdmHeader = Untrusted::new_ref(response_buf).validate()?;
+
+        let transport_function = self.transport.ok_or(EINVAL)?;
+        // SAFETY: `transport_function` is provided by the new(), we are
+        // calling the function.
+        let length = unsafe {
+            transport_function(
+                self.transport_priv,
+                self.dev,
+                request_buf.as_ptr() as *const c_void,
+                request_buf.len(),
+                response_buf.as_mut_ptr() as *mut c_void,
+                response_buf.len(),
+            ) as i32
+        };
+        to_result(length)?;
+
+        if (length as usize) < header_size {
+            return Ok(length); // Truncated response is handled by callers
+        }
+        if response.code == SPDM_ERROR {
+            if length as usize >= core::mem::size_of::<SpdmErrorRsp>() {
+                // SAFETY: The response buffer will be at least as large as
+                // `SpdmErrorRsp` so we can cast the buffer to `SpdmErrorRsp` which
+                // is a packed struct.
+                self.spdm_err(unsafe { &*(response_buf.as_ptr() as *const SpdmErrorRsp) })?;
+            } else {
+                return Err(EINVAL);
+            }
+        }
+
+        if response.code != request.code & !SPDM_REQ {
+            pr_err!(
+                "Response code {:#x} does not match request code {:#x}\n",
+                response.code,
+                request.code
+            );
+            to_result(-(bindings::EPROTO as i32))?;
+        }
+
+        Ok(length)
+    }
+}
lib/rspdm/validator.rs new file +53 -0
new file mode 100644
index 000000000000..a0a3a2f46952
@@ -0,0 +1,66 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Copyright (C) 2024 Western Digital
+
+//! Related structs and their Validate implementations.
+//!
+//! Rust implementation of the DMTF Security Protocol and Data Model (SPDM)
+//! <https://www.dmtf.org/dsp/DSP0274>
+
+use crate::consts::SpdmErrorCode;
+use core::mem;
+use kernel::prelude::*;
+use kernel::{
+    error::{code::EINVAL, Error},
+    validate::{Unvalidated, Validate},
+};
+
+#[repr(C, packed)]
+pub(crate) struct SpdmHeader {
+    pub(crate) version: u8,
+    pub(crate) code: u8, /* RequestResponseCode */
+    pub(crate) param1: u8,
+    pub(crate) param2: u8,
+}
+
+impl Validate<&Unvalidated<[u8]>> for &SpdmHeader {
+    type Err = Error;
+
+    fn validate(unvalidated: &Unvalidated<[u8]>) -> Result<Self, Self::Err> {
+        let raw = unvalidated.raw();
+        if raw.len() < mem::size_of::<SpdmHeader>() {
+            return Err(EINVAL);
+        }
+
+        let ptr = raw.as_ptr();
+        // CAST: `SpdmHeader` only contains integers and has `repr(C)`.
+        let ptr = ptr.cast::<SpdmHeader>();
+        // SAFETY: `ptr` came from a reference and the cast above is valid.
+        Ok(unsafe { &*ptr })
+    }
+}
+
+impl Validate<&mut Unvalidated<[u8]>> for &mut SpdmHeader {
+    type Err = Error;
+
+    fn validate(unvalidated: &mut Unvalidated<[u8]>) -> Result<Self, Self::Err> {
+        let raw = unvalidated.raw_mut();
+        if raw.len() < mem::size_of::<SpdmHeader>() {
+            return Err(EINVAL);
+        }
+
+        let ptr = raw.as_mut_ptr();
+        // CAST: `SpdmHeader` only contains integers and has `repr(C)`.

lib/rspdm/validator.rs line 63

Quoted 10 lines
lib/rspdm/validator.rs new file +10 -0
+        let ptr = ptr.cast::<SpdmHeader>();
+        // SAFETY: `ptr` came from a reference and the cast above is valid.
+        Ok(unsafe { &mut *ptr })
+    }
+}
+
+#[repr(C, packed)]
+pub(crate) struct SpdmErrorRsp {
+    pub(crate) version: u8,
+    pub(crate) code: u8,

Maybe document here that this will always be SPDM_ERROR 0x7F

rust/bindings/bindings_helper.h line 87

Quoted 12 lines
lib/rspdm/validator.rs new file +3 -0
+    pub(crate) error_code: SpdmErrorCode,
+    pub(crate) error_data: u8,
+}
rust/bindings/bindings_helper.h +1 -0
index 0075c4b62c29..5043eee2a8d6 100644
@@ -84,7 +84,9 @@
 #include <linux/slab.h>
 #include <linux/task_work.h>
 #include <linux/tracepoint.h>
+#include <linux/spdm.h>

Smells like this is alphabetical order. So move it up a bit?

Quoted 5 lines
rust/bindings/bindings_helper.h +1 -0
 #include <linux/usb.h>
+#include <linux/uaccess.h>
 #include <linux/wait.h>
 #include <linux/workqueue.h>
 #include <linux/xarray.h>
#16 lore raw

RFC v3 9/27 PCI/CMA: Authenticate devices on enumeration

In reply to [email protected]

From: Jonathan Cameron <[email protected]>

Component Measurement and Authentication (CMA, PCIe r6.2 sec 6.31) allows for measurement and authentication of PCIe devices. It is based on the Security Protocol and Data Model specification (SPDM,
https://www.dmtf.org/dsp/DSP0274).

CMA-SPDM in turn forms the basis for Integrity and Data Encryption (IDE, PCIe r6.2 sec 6.33) because the key material used by IDE is transmitted over a CMA-SPDM session.

As a first step, authenticate CMA-capable devices on enumeration. A subsequent commit will expose the result in sysfs.

When allocating SPDM session state with spdm_create(), the maximum SPDM message length needs to be passed. Make the PCI_DOE_MAX_LENGTH macro public and calculate the maximum payload length from it.

Credits: Jonathan wrote a proof-of-concept of this CMA implementation. Lukas reworked it for upstream. Wilfred contributed fixes for issues discovered during testing.

---
 MAINTAINERS             |   1 +
 drivers/pci/Kconfig     |  16 +++++++
 drivers/pci/Makefile    |   2 +
 drivers/pci/cma.c       | 101 ++++++++++++++++++++++++++++++++++++++++
 drivers/pci/doe.c       |   3 --
 drivers/pci/pci.h       |   8 ++++
 drivers/pci/probe.c     |   1 +
 drivers/pci/remove.c    |   1 +
 include/linux/pci-doe.h |   4 ++
 include/linux/pci.h     |   4 ++
 10 files changed, 138 insertions(+), 3 deletions(-)
 create mode 100644 drivers/pci/cma.c
MAINTAINERS +1 -0
index a5c4ec16081c..58898260fde8 100644
@@ -23702,6 +23702,7 @@ L:	[email protected]
 L:	[email protected]
 S:	Maintained
 T:	git git://git.kernel.org/pub/scm/linux/kernel/git/devsec/spdm.git
+F:	drivers/pci/cma.c
 F:	include/linux/spdm.h
 F:	lib/rspdm/
 
drivers/pci/Kconfig +16 -0
index e3f848ffb52a..7ea403799d78 100644
@@ -125,6 +125,22 @@ config PCI_ATS
 config PCI_IDE
 	bool
 
+config PCI_CMA
+	bool "Component Measurement and Authentication (CMA-SPDM)"
+	select CRYPTO_ECDSA
+	select CRYPTO_RSA
+	select CRYPTO_SHA256
+	select CRYPTO_SHA512
+	select PCI_DOE
+	select RSPDM
+	help
+	  Authenticate devices on enumeration per PCIe r6.2 sec 6.31.
+	  A PCI DOE mailbox is used as transport for DMTF SPDM based
+	  authentication, measurement and secure channel establishment.
+
+config PCI_DOE
+	bool
+
 config PCI_TSM
 	bool "PCI TSM: Device security protocol support"
 	select PCI_IDE
drivers/pci/Makefile +2 -0
index e10cfe5a280b..f026f5dbb938 100644
@@ -40,6 +40,8 @@ obj-$(CONFIG_PCI_DYNAMIC_OF_NODES) += of_property.o
 obj-$(CONFIG_PCI_NPEM)		+= npem.o
 obj-$(CONFIG_PCIE_TPH)		+= tph.o
 
+obj-$(CONFIG_PCI_CMA)		+= cma.o
+
 # Endpoint library must be initialized before its users
 obj-$(CONFIG_PCI_ENDPOINT)	+= endpoint/
 
drivers/pci/cma.c new file +101 -0

101 lines not on this page · raw message

drivers/pci/doe.c +0 -3
index 62be9c8dbc52..344e01496a8e 100644
@@ -31,9 +31,6 @@
 #define PCI_DOE_FLAG_CANCEL	0
 #define PCI_DOE_FLAG_DEAD	1
 
-/* Max data object length is 2^18 dwords */
-#define PCI_DOE_MAX_LENGTH	(1 << 18)
-
 /**
  * struct pci_doe_mb - State for a single DOE mailbox
  *
drivers/pci/pci.h +8 -0
index 0e67014aa001..1ab1a39e8df3 100644
@@ -599,6 +599,14 @@ static inline void pci_doe_destroy(struct pci_dev *pdev) { }
 static inline void pci_doe_disconnected(struct pci_dev *pdev) { }
 #endif
 
+#ifdef CONFIG_PCI_CMA
+void pci_cma_init(struct pci_dev *pdev);
+void pci_cma_destroy(struct pci_dev *pdev);
+#else
+static inline void pci_cma_init(struct pci_dev *pdev) { }
+static inline void pci_cma_destroy(struct pci_dev *pdev) { }
+#endif
+
 #ifdef CONFIG_PCI_NPEM
 void pci_npem_create(struct pci_dev *dev);
 void pci_npem_remove(struct pci_dev *dev);
drivers/pci/probe.c +1 -0
index 41183aed8f5d..665532a07cfc 100644
@@ -2706,6 +2706,7 @@ static void pci_init_capabilities(struct pci_dev *dev)
 	pci_rebar_init(dev);		/* Resizable BAR */
 	pci_dev3_init(dev);		/* Device 3 capabilities */
 	pci_ide_init(dev);		/* Link Integrity and Data Encryption */
+	pci_cma_init(dev);		/* Component Measurement & Auth */
 
 	pcie_report_downtraining(dev);
 	pci_init_reset_methods(dev);
drivers/pci/remove.c +1 -0
index 417a9ea59117..b98d96919089 100644
@@ -69,6 +69,7 @@ static void pci_destroy_dev(struct pci_dev *dev)
 	list_del(&dev->bus_list);
 	up_write(&pci_bus_sem);
 
+	pci_cma_destroy(dev);
 	pci_doe_destroy(dev);
 	pci_ide_destroy(dev);
 	pcie_aspm_exit_link_state(dev);
include/linux/pci-doe.h +4 -0
index bd4346a7c4e7..7540396336de 100644
@@ -19,6 +19,10 @@ struct pci_doe_mb;
 #define PCI_DOE_FEATURE_CMA 1
 #define PCI_DOE_FEATURE_SSESSION 2
 
+/* Max data object length is 2^18 dwords (including 2 dwords for header) */
+#define PCI_DOE_MAX_LENGTH	(1 << 18)
+#define PCI_DOE_MAX_PAYLOAD	((PCI_DOE_MAX_LENGTH - 2) * sizeof(u32))
+
 struct pci_doe_mb *pci_find_doe_mailbox(struct pci_dev *pdev, u16 vendor,
 					u8 type);
 
include/linux/pci.h +4 -0
index 9357e9b00e1c..7384846ade19 100644
@@ -39,6 +39,7 @@
 #include <linux/io.h>
 #include <linux/resource_ext.h>
 #include <linux/msi_api.h>
+#include <linux/spdm.h>
 #include <uapi/linux/pci.h>
 
 #include <linux/pci_ids.h>
@@ -542,6 +543,9 @@ struct pci_dev {
 #ifdef CONFIG_PCI_DOE
 	struct xarray	doe_mbs;	/* Data Object Exchange mailboxes */
 #endif
+#ifdef CONFIG_PCI_CMA
+	struct spdm_state *spdm_state;	/* Security Protocol and Data Model */
+#endif
 #ifdef CONFIG_PCI_NPEM
 	struct npem	*npem;		/* Native PCIe Enclosure Management */
 #endif
Signature · 1 line
2.52.0
#17 lore raw

Re: [RFC v3 09/27] PCI/CMA: Authenticate devices on enumeration

Quoted 14 lines · On 11/02/26 08:59, [email protected] wrote:
drivers/pci/Kconfig +6 -0
index e3f848ffb52a..7ea403799d78 100644
@@ -125,6 +125,22 @@ config PCI_ATS
  config PCI_IDE
  	bool
 
+config PCI_CMA
+	bool "Component Measurement and Authentication (CMA-SPDM)"
+	select CRYPTO_ECDSA
+	select CRYPTO_RSA
+	select CRYPTO_SHA256
+	select CRYPTO_SHA512

drivers/pci/Kconfig config PCI_ATS · line 143

Quoted 10 lines
drivers/pci/Kconfig +10 -0
+	select PCI_DOE
+	select RSPDM
+	help
+	  Authenticate devices on enumeration per PCIe r6.2 sec 6.31.
+	  A PCI DOE mailbox is used as transport for DMTF SPDM based
+	  authentication, measurement and secure channel establishment.
+
+config PCI_DOE
+	bool
+

config PCI_DOE is already in this Kconfig file just below config PCI_TSM

Quoted 35 lines
drivers/pci/Kconfig +0 -0
  config PCI_TSM
  	bool "PCI TSM: Device security protocol support"
  	select PCI_IDE
drivers/pci/Makefile +2 -0
index e10cfe5a280b..f026f5dbb938 100644
@@ -40,6 +40,8 @@ obj-$(CONFIG_PCI_DYNAMIC_OF_NODES) += of_property.o
  obj-$(CONFIG_PCI_NPEM)		+= npem.o
  obj-$(CONFIG_PCIE_TPH)		+= tph.o
 
+obj-$(CONFIG_PCI_CMA)		+= cma.o
+
  # Endpoint library must be initialized before its users
  obj-$(CONFIG_PCI_ENDPOINT)	+= endpoint/
 
drivers/pci/cma.c new file +13 -0
new file mode 100644
index 000000000000..7463cd1179f0
@@ -0,0 +1,101 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Component Measurement and Authentication (CMA-SPDM, PCIe r6.2 sec 6.31)
+ *
+ * Copyright (C) 2021 Huawei
+ *     Jonathan Cameron <[email protected]>
+ *
+ * Copyright (C) 2022-24 Intel Corporation
+ */
+
+#define dev_fmt(fmt) "CMA: " fmt
+
+#include <linux/pci.h>

drivers/pci/cma.c line 23

Quoted 10 lines
drivers/pci/cma.c new file +10 -0
+#include <linux/pci-doe.h>
+#include <linux/pm_runtime.h>
+#include <linux/spdm.h>
+
+#include "pci.h"
+
+/* Keyring that userspace can poke certs into */
+static struct key *pci_cma_keyring;
+
+#define PCI_DOE_FEATURE_CMA 1

This macro is already present in <linux/pci-doe.h>, which have been included in this file.

Regards,
Aksh Garg

#18 lore raw

RFC v3 10/27 PCI/CMA: Validate Subject Alternative Name in certificates

In reply to [email protected]

From: Lukas Wunner <[email protected]>

PCIe r6.1 sec 6.31.3 stipulates requirements for Leaf Certificates presented by devices, in particular the presence of a Subject Alternative Name which encodes the Vendor ID, Device ID, Device Serial Number, etc.

This prevents a mismatch between the device identity in Config Space and the certificate. A device cannot misappropriate a certificate from a different device without also spoofing Config Space. As a corollary, it cannot dupe an arbitrary driver into binding to it. Only drivers which bind to the device identity in the Subject Alternative Name work (PCIe r6.1 sec 6.31 "Implementation Note: Overview of Threat Model").

The Subject Alternative Name is signed, hence constitutes a signed copy of a Config Space portion. It's the same concept as web certificates which contain a set of domain names in the Subject Alternative Name for identity verification.

Parse the Subject Alternative Name using a small ASN.1 module and validate its contents. The theory of operation is explained in a comment at the top of the newly inserted code.

This functionality is introduced in a separate commit on top of basic CMA-SPDM support to split the code into digestible, reviewable chunks.

The CMA OID added here is taken from the official OID Repository (it's not documented in the PCIe Base Spec):
https://oid-rep.orange-labs.fr/get/2.23.147

Side notes:

* PCIe r6.2 removes the spec language on the Subject Alternative Name. It still "requires the leaf certificate to include the information typically used by system software for device driver binding", but no longer specifies how that information is encoded into the certificate.

According to the editor of the PCIe Base Spec and the author of the CMA 1.1 ECN (which caused this change), FPGA cards which mutate their device identity at runtime (due to a firmware update) were thought as unable to satisfy the previous spec language. The Protocol Working Group could not agree on a better solution and therefore dropped the spec language entirely. They acknowledge that the requirement is now under-spec'd. Because products already exist which adhere to the Subject Alternative Name requirement per PCIe r6.1 sec 6.31.3, they recommended to "push through" and use it as the de facto standard.

The FPGA concerns are easily overcome by reauthenticating the device after a firmware update, either via sysfs or pci_cma_reauthenticate() (added by a subsequent commit).

* PCIe r6.1 sec 6.31.3 strongly recommends to verify that "the information provided in the Subject Alternative Name entry is signed by the vendor indicated by the Vendor ID." In other words, the root certificate on pci_cma_keyring which signs the device's certificate chain must have been created for a particular Vendor ID.

Unfortunately the spec neglects to define how the Vendor ID shall be encoded into the root certificate. So the recommendation cannot be implemented at this point and it is thus possible that a vendor signs device certificates of a different vendor.

* Instead of a Subject Alternative Name, Leaf Certificates may include "a Reference Integrity Manifest, e.g., see Trusted Computing Group" or "a pointer to a location where such a Reference Integrity Manifest can be obtained" (PCIe r6.1 sec 6.31.3).

A Reference Integrity Manifest contains "golden" measurements which can be compared to actual measurements retrieved from a device. It serves a different purpose than the Subject Alternative Name, hence it is unclear why the spec says only either of them is necessary. It is also unclear how a Reference Integrity Manifest shall be encoded into a certificate.

Hence ignore the Reference Integrity Manifest requirement.

---
 drivers/pci/Makefile         |   4 +-
 drivers/pci/cma.asn1         |  41 ++++++++++++
 drivers/pci/cma.c            | 123 ++++++++++++++++++++++++++++++++++-
 include/linux/oid_registry.h |   3 +
 4 files changed, 169 insertions(+), 2 deletions(-)
 create mode 100644 drivers/pci/cma.asn1
drivers/pci/Makefile +3 -1
index f026f5dbb938..2fc161e15670 100644
@@ -40,7 +40,9 @@ obj-$(CONFIG_PCI_DYNAMIC_OF_NODES) += of_property.o
 obj-$(CONFIG_PCI_NPEM)		+= npem.o
 obj-$(CONFIG_PCIE_TPH)		+= tph.o
 
-obj-$(CONFIG_PCI_CMA)		+= cma.o
+obj-$(CONFIG_PCI_CMA)		+= cma.o cma.asn1.o
+$(obj)/cma.o:			$(obj)/cma.asn1.h
+$(obj)/cma.asn1.o:		$(obj)/cma.asn1.c $(obj)/cma.asn1.h
 
 # Endpoint library must be initialized before its users
 obj-$(CONFIG_PCI_ENDPOINT)	+= endpoint/
drivers/pci/cma.asn1 new file +41 -0

41 lines not on this page · raw message

drivers/pci/cma.c +122 -1

145 lines not on this page · raw message

include/linux/oid_registry.h +3 -0
index ebce402854de..113f4e802ec4 100644
@@ -150,6 +150,9 @@ enum OID {
 	OID_id_ml_dsa_65,			/* 2.16.840.1.101.3.4.3.18 */
 	OID_id_ml_dsa_87,			/* 2.16.840.1.101.3.4.3.19 */
 
+	/* PCI */
+	OID_CMA,			/* 2.23.147 */
+
 	OID__NR
 };
 
Signature · 1 line
2.52.0
#19 lore raw

RFC v3 11/27 PCI/CMA: Reauthenticate devices on reset and resume

In reply to [email protected]

From: Lukas Wunner <[email protected]>

CMA-SPDM state is lost when a device undergoes a Conventional Reset. (But not a Function Level Reset, PCIe r6.2 sec 6.6.2.) A D3cold to D0 transition implies a Conventional Reset (PCIe r6.2 sec 5.8).

Thus, reauthenticate devices on resume from D3cold and on recovery from a Secondary Bus Reset or DPC-induced Hot Reset.

The requirement to reauthenticate devices on resume from system sleep (and in the future reestablish IDE encryption) is the reason why SPDM needs to be in-kernel: During ->resume_noirq, which is the first phase after system sleep, the PCI core walks down the hierarchy, puts each device in D0, restores its config space and invokes the driver's ->resume_noirq callback. The driver is afforded the right to access the device already during this phase.

To retain this usage model in the face of authentication and encryption, CMA-SPDM reauthentication and IDE reestablishment must happen during the ->resume_noirq phase, before the driver's first access to the device. The driver is thus afforded seamless authenticated and encrypted access until the last moment before suspend and from the first moment after resume.

During the ->resume_noirq phase, device interrupts are not yet enabled. It is thus impossible to defer CMA-SPDM reauthentication to a user space component on an attached disk or on the network, making an in-kernel SPDM implementation mandatory.

The same catch-22 exists on recovery from a Conventional Reset: A user space SPDM implementation might live on a device which underwent reset, rendering its execution impossible.

---
 drivers/pci/cma.c        | 15 +++++++++++++++
 drivers/pci/pci-driver.c |  1 +
 drivers/pci/pci.c        | 12 ++++++++++--
 drivers/pci/pci.h        |  2 ++
 drivers/pci/pcie/err.c   |  3 +++
 5 files changed, 31 insertions(+), 2 deletions(-)
drivers/pci/cma.c +15 -0
index e974d489c7a2..f2c435b04b92 100644
@@ -195,6 +195,21 @@ void pci_cma_init(struct pci_dev *pdev)
 	spdm_authenticate(pdev->spdm_state);
 }
 
+/**
+ * pci_cma_reauthenticate() - Perform CMA-SPDM authentication again
+ * @pdev: Device to reauthenticate
+ *
+ * Can be called by drivers after device identity has mutated,
+ * e.g. after downloading firmware to an FPGA device.
+ */
+void pci_cma_reauthenticate(struct pci_dev *pdev)
+{
+	if (!pdev->spdm_state)
+		return;
+
+	spdm_authenticate(pdev->spdm_state);
+}
+
 void pci_cma_destroy(struct pci_dev *pdev)
 {
 	if (!pdev->spdm_state)
drivers/pci/pci-driver.c +1 -0
index a9590601835a..d1a574e21b0b 100644
@@ -589,6 +589,7 @@ static void pci_pm_default_resume_early(struct pci_dev *pci_dev)
 	pci_pm_power_up_and_verify_state(pci_dev);
 	pci_restore_state(pci_dev);
 	pci_pme_restore(pci_dev);
+	pci_cma_reauthenticate(pci_dev);
 }
 
 static void pci_pm_bridge_power_up_actions(struct pci_dev *pci_dev)
drivers/pci/pci.c +10 -2
index 13dbb405dc31..7f03e07150a0 100644
@@ -4887,8 +4887,16 @@ static int pci_reset_bus_function(struct pci_dev *dev, bool probe)
 
 	rc = pci_dev_reset_slot_function(dev, probe);
 	if (rc != -ENOTTY)
-		return rc;
-	return pci_parent_bus_reset(dev, probe);
+		goto done;
+
+	rc = pci_parent_bus_reset(dev, probe);
+
+done:
+	/* CMA-SPDM state is lost upon a Conventional Reset */
+	if (!probe)
+		pci_cma_reauthenticate(dev);
+
+	return rc;
 }
 
 static int cxl_reset_bus_function(struct pci_dev *dev, bool probe)
drivers/pci/pci.h +2 -0
index 1ab1a39e8df3..f87a4b7c92b8 100644
@@ -602,9 +602,11 @@ static inline void pci_doe_disconnected(struct pci_dev *pdev) { }
 #ifdef CONFIG_PCI_CMA
 void pci_cma_init(struct pci_dev *pdev);
 void pci_cma_destroy(struct pci_dev *pdev);
+void pci_cma_reauthenticate(struct pci_dev *pdev);
 #else
 static inline void pci_cma_init(struct pci_dev *pdev) { }
 static inline void pci_cma_destroy(struct pci_dev *pdev) { }
+static inline void pci_cma_reauthenticate(struct pci_dev *pdev) { }
 #endif
 
 #ifdef CONFIG_PCI_NPEM
drivers/pci/pcie/err.c +3 -0
index bebe4bc111d7..700663c38e31 100644
@@ -151,6 +151,9 @@ static int report_slot_reset(struct pci_dev *dev, void *data)
 	pci_ers_result_t vote, *result = data;
 	const struct pci_error_handlers *err_handler;
 
+	/* CMA-SPDM state is lost upon a Conventional Reset */
+	pci_cma_reauthenticate(dev);
+
 	device_lock(&dev->dev);
 	pdrv = dev->driver;
 	if (!pci_dev_set_io_state(dev, pci_channel_io_normal) ||
Signature · 1 line
2.52.0
#20 lore raw

RFC v3 12/27 lib: rspdm: Support SPDM get_version

In reply to [email protected]

From: Alistair Francis <[email protected]>

Support the GET_VERSION SPDM command.

---
 lib/rspdm/consts.rs    | 17 +++++++++++
 lib/rspdm/lib.rs       |  6 +++-
 lib/rspdm/state.rs     | 58 ++++++++++++++++++++++++++++++++++--
 lib/rspdm/validator.rs | 67 ++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 144 insertions(+), 4 deletions(-)
lib/rspdm/consts.rs +17 -0
index 40ce60eba2f3..38f48f0863e2 100644
@@ -7,6 +7,20 @@
 //! Rust implementation of the DMTF Security Protocol and Data Model (SPDM)
 //! <https://www.dmtf.org/dsp/DSP0274>
 
+use crate::validator::SpdmHeader;
+use core::mem;
+
+/* SPDM versions supported by this implementation */
+pub(crate) const SPDM_VER_10: u8 = 0x10;
+#[allow(dead_code)]
+pub(crate) const SPDM_VER_11: u8 = 0x11;
+#[allow(dead_code)]
+pub(crate) const SPDM_VER_12: u8 = 0x12;
+pub(crate) const SPDM_VER_13: u8 = 0x13;
+
+pub(crate) const SPDM_MIN_VER: u8 = SPDM_VER_10;
+pub(crate) const SPDM_MAX_VER: u8 = SPDM_VER_13;
+
 pub(crate) const SPDM_REQ: u8 = 0x80;
 pub(crate) const SPDM_ERROR: u8 = 0x7f;
 
@@ -115,3 +129,6 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
         Ok(())
     }
 }
+
+pub(crate) const SPDM_GET_VERSION: u8 = 0x84;
+pub(crate) const SPDM_GET_VERSION_LEN: usize = mem::size_of::<SpdmHeader>() + 255;
lib/rspdm/lib.rs +5 -1
index 2bb716140e0a..08abcbb21247 100644
@@ -108,7 +108,11 @@
 /// Return 0 on success or a negative errno.  In particular, -EPROTONOSUPPORT
 /// indicates authentication is not supported by the device.
 #[no_mangle]
-pub unsafe extern "C" fn spdm_authenticate(_state: &'static mut SpdmState) -> c_int {
+pub unsafe extern "C" fn spdm_authenticate(state: &'static mut SpdmState) -> c_int {
+    if let Err(e) = state.get_version() {
+        return e.to_errno() as c_int;
+    }
+
     0
 }
 
lib/rspdm/state.rs +55 -3

80 lines not on this page · raw message

lib/rspdm/validator.rs +67 -0

82 lines not on this page · raw message

Signature · 1 line
2.52.0
#21 lore raw

Re: [RFC v3 12/27] lib: rspdm: Support SPDM get_version

This is cool! some minor comments inline.

Quoted 13 lines


 /// The current SPDM session state for a device. Based on the /// C struct spdm_state.

lib/rspdm/state.rs +1 -1
@@ -61,7 +64,7 @@ pub(crate) fn new(
              transport_sz,
              keyring,
              validate,
-            version: 0x10,
+            version: SPDM_MIN_VER,
          }
      }

(quoted diff) +0 -0
@@ -217,4 +220,53 @@ pub(crate) fn spdm_exchange(
Quoted 5 lines


         Ok(length)
     }
+
+    /// Negoiate a supported SPDM version and store the information

typo: s/Negoiate/Negotiate

Quoted 10 lines

+    /// in the SpdmState.
+    pub(crate) fn get_version(&mut self) -> Result<(), Error> { +        let mut request = GetVersionReq::default();
+        request.version = self.version;
+
+        // SAFETY: request is repr(C) and packed, so we can convert it to a slice
+        let request_buf = unsafe {
+            from_raw_parts_mut(
+                &mut request as *mut _ as *mut u8,

Quoted 8 lines

+                core::mem::size_of::<GetVersionReq>(), +            )
+        };
+
+        let mut response_vec: KVec<u8> = KVec::with_capacity(SPDM_GET_VERSION_LEN, GFP_KERNEL)?; +        // SAFETY: request is repr(C) and packed, so we can convert it to a slice

The SAFETY comment here refers to request when setting up the response buffer, seems unneeded?

Wilfred

#22 lore raw

Re: [RFC v3 12/27] lib: rspdm: Support SPDM get_version

In reply to [email protected]
Quoted 5 lines · On Wed, 11 Feb 2026 13:29:19 +1000 [email protected] wrote:

From: Alistair Francis <[email protected]>

Support the GET_VERSION SPDM command.

Hi Alistair,

Various minor comments inline. Biggest one is what I think is some confusing le16 handling that to me is in the wrong place.

Jonathan

Quoted 13 lines
---
 lib/rspdm/consts.rs    | 17 +++++++++++
 lib/rspdm/lib.rs       |  6 +++-
 lib/rspdm/state.rs     | 58 ++++++++++++++++++++++++++++++++++--
 lib/rspdm/validator.rs | 67 ++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 144 insertions(+), 4 deletions(-)
lib/rspdm/consts.rs +0 -0
index 40ce60eba2f3..38f48f0863e2 100644

lib/rspdm/consts.rs fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { · line 134

Quoted 6 lines
lib/rspdm/consts.rs +3 -0
@@ -115,3 +129,6 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
         Ok(())
     }
 }
+
+pub(crate) const SPDM_GET_VERSION: u8 = 0x84;
+pub(crate) const SPDM_GET_VERSION_LEN: usize = mem::size_of::<SpdmHeader>() + 255;

I'd either add a comment on where the 255 comes from. Also do we have a U8_MAX definition we can use? Or some way to get it from code?

lib/rspdm/lib.rs line 111

Quoted 10 lines
lib/rspdm/lib.rs +1 -1
index 2bb716140e0a..08abcbb21247 100644
@@ -108,7 +108,11 @@
 /// Return 0 on success or a negative errno.  In particular, -EPROTONOSUPPORT
 /// indicates authentication is not supported by the device.
 #[no_mangle]
-pub unsafe extern "C" fn spdm_authenticate(_state: &'static mut SpdmState) -> c_int {
+pub unsafe extern "C" fn spdm_authenticate(state: &'static mut SpdmState) -> c_int {

Is there more to this rename than it appears? Can it get pushed back to earlier patch?

Quoted 20 lines
lib/rspdm/lib.rs +4 -0
+    if let Err(e) = state.get_version() {
+        return e.to_errno() as c_int;
+    }
+
     0
 }
 
lib/rspdm/state.rs +1 -0
index 68861f30e3fa..3ad53ec05044 100644
@@ -8,6 +8,7 @@
 //! <https://www.dmtf.org/dsp/DSP0274>
 
 use core::ffi::c_void;
+use core::slice::from_raw_parts_mut;
 use kernel::prelude::*;
 use kernel::{
     bindings,

lib/rspdm/state.rs line 20

Quoted 7 lines
lib/rspdm/state.rs +2 -2
@@ -15,8 +16,10 @@
     validate::Untrusted,
 };
 
-use crate::consts::{SpdmErrorCode, SPDM_ERROR, SPDM_REQ};
-use crate::validator::{SpdmErrorRsp, SpdmHeader};
+use crate::consts::{
+    SpdmErrorCode, SPDM_ERROR, SPDM_GET_VERSION_LEN, SPDM_MAX_VER, SPDM_MIN_VER, SPDM_REQ,

Is the Rust linter fussy about these being on one line? Feels like we'd get less churn over time if we formatted this as one per line.

lib/rspdm/state.rs pub(crate) fn new( · line 67

Quoted 11 lines
lib/rspdm/state.rs +3 -1
+};
+use crate::validator::{GetVersionReq, GetVersionRsp, SpdmErrorRsp, SpdmHeader};
 
 /// The current SPDM session state for a device. Based on the
 /// C `struct spdm_state`.
@@ -61,7 +64,7 @@ pub(crate) fn new(
             transport_sz,
             keyring,
             validate,
-            version: 0x10,
+            version: SPDM_MIN_VER,

Pull that def back to earlier patch to keep the churn confined.

Quoted 21 lines
lib/rspdm/state.rs +14 -0
         }
     }
 
@@ -217,4 +220,53 @@ pub(crate) fn spdm_exchange(
 
         Ok(length)
     }
+
+    /// Negoiate a supported SPDM version and store the information
+    /// in the `SpdmState`.
+    pub(crate) fn get_version(&mut self) -> Result<(), Error> {
+        let mut request = GetVersionReq::default();
+        request.version = self.version;
+
+        // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
+        let request_buf = unsafe {
+            from_raw_parts_mut(
+                &mut request as *mut _ as *mut u8,
+                core::mem::size_of::<GetVersionReq>(),
+            )
+        };

lib/rspdm/state.rs pub(crate) fn spdm_exchange( · line 246

Quoted 10 lines
lib/rspdm/state.rs +10 -0
+
+        let mut response_vec: KVec<u8> = KVec::with_capacity(SPDM_GET_VERSION_LEN, GFP_KERNEL)?;
+        // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
+        let response_buf =
+            unsafe { from_raw_parts_mut(response_vec.as_mut_ptr(), SPDM_GET_VERSION_LEN) };
+
+        let rc = self.spdm_exchange(request_buf, response_buf)?;
+
+        // SAFETY: `rc` is the length of data read, which will be smaller
+        // then the capacity of the vector

than?

lib/rspdm/state.rs pub(crate) fn spdm_exchange( · line 255

Quoted 9 lines
lib/rspdm/state.rs +9 -0
+        unsafe { response_vec.inc_len(rc as usize) };
+
+        let response: &mut GetVersionRsp = Untrusted::new_mut(&mut response_vec).validate_mut()?;
+
+        let mut foundver = false;
+        for i in 0..response.version_number_entry_count {
+            // Creating a reference on a packed struct will result in
+            // undefined behaviour, so we operate on the raw data directly
+            let unaligned = core::ptr::addr_of_mut!(response.version_number_entries) as *mut u16;

Maybe pull that out of the loop like you do below?

lib/rspdm/state.rs pub(crate) fn spdm_exchange( · line 257

Quoted 2 lines
lib/rspdm/state.rs +2 -0
+            let addr = unaligned.wrapping_add(i as usize);
+            let version = (unsafe { core::ptr::read_unaligned::<u16>(addr) } >> 8) as u8;

Given the endian conversion below, this is correct, but I think leaving it as the __le16 until here is better. We could also just pull out the correct byte and ignore the other one. It's a spec quirk that they decided to have it defined as a __le16 rather than u8[2] as the fields are confined one byte or the other.

I wonder if we should also reject any alpha versions? I.e. check the bottom 4 bits are 0.

Quoted 37 lines
lib/rspdm/state.rs +14 -0
+
+            if version >= self.version && version <= SPDM_MAX_VER {
+                self.version = version;
+                foundver = true;
+            }
+        }
+
+        if !foundver {
+            pr_err!("No common supported version\n");
+            to_result(-(bindings::EPROTO as i32))?;
+        }
+
+        Ok(())
+    }
 }
lib/rspdm/validator.rs +3 -0
index a0a3a2f46952..f69be6aa6280 100644
@@ -7,6 +7,7 @@
 //! Rust implementation of the DMTF Security Protocol and Data Model (SPDM)
 //! <https://www.dmtf.org/dsp/DSP0274>
 
+use crate::bindings::{__IncompleteArrayField, __le16};
 use crate::consts::SpdmErrorCode;
 use core::mem;
 use kernel::prelude::*;
@@ -15,6 +16,8 @@
     validate::{Unvalidated, Validate},
 };
 
+use crate::consts::SPDM_GET_VERSION;
+
 #[repr(C, packed)]
 pub(crate) struct SpdmHeader {
     pub(crate) version: u8,

lib/rspdm/validator.rs pub(crate) struct SpdmErrorRsp { · line 71

Quoted 5 lines
lib/rspdm/validator.rs +2 -0
@@ -64,3 +67,67 @@ pub(crate) struct SpdmErrorRsp {
     pub(crate) error_code: SpdmErrorCode,
     pub(crate) error_data: u8,
 }
+
+#[repr(C, packed)]

Why packed?

lib/rspdm/validator.rs pub(crate) struct SpdmErrorRsp { · line 82

Quoted 11 lines
lib/rspdm/validator.rs +11 -0
+pub(crate) struct GetVersionReq {
+    pub(crate) version: u8,
+    pub(crate) code: u8,
+    pub(crate) param1: u8,
+    pub(crate) param2: u8,
+}
+
+impl Default for GetVersionReq {
+    fn default() -> Self {
+        GetVersionReq {
+            version: 0,

Given this only takes one value we could default it to 0x10 (even though it's written above to keep the state machine in sync).

Quoted 16 lines
lib/rspdm/validator.rs +16 -0
+            code: SPDM_GET_VERSION,
+            param1: 0,
+            param2: 0,
+        }
+    }
+}
+
+#[repr(C, packed)]
+pub(crate) struct GetVersionRsp {
+    pub(crate) version: u8,
+    pub(crate) code: u8,
+    param1: u8,
+    param2: u8,
+    reserved: u8,
+    pub(crate) version_number_entry_count: u8,
+    pub(crate) version_number_entries: __IncompleteArrayField<__le16>,

lib/rspdm/validator.rs pub(crate) struct SpdmErrorRsp { · line 108

Quoted 10 lines
lib/rspdm/validator.rs +10 -0
+}
+
+impl Validate<&mut Unvalidated<KVec<u8>>> for &mut GetVersionRsp {
+    type Err = Error;
+
+    fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> {
+        let raw = unvalidated.raw_mut();
+        if raw.len() < mem::size_of::<GetVersionRsp>() {
+            return Err(EINVAL);
+        }

I guess it probably belongs at the header validation point rather that here, but can we also check the version matches what was requested. Possibly an additional check here that it is 0x10 (as this one is a special case where only that value is correct).

lib/rspdm/validator.rs pub(crate) struct SpdmErrorRsp { · line 111

Quoted 3 lines
lib/rspdm/validator.rs +3 -0
+
+        let version_number_entries = *(raw.get(5).ok_or(ENOMEM))? as usize;
+        let total_expected_size = version_number_entries * 2 + 6;

Instead of 6 can we use mem::size_of::<GetVersionRsp>()? Ideally something similar for the 2 as well even if it's a bit verbose. (if I was lazy in the C code you get to make it better here :)

lib/rspdm/validator.rs pub(crate) struct SpdmErrorRsp { · line 122

Quoted 11 lines
lib/rspdm/validator.rs +11 -0
+        if raw.len() < total_expected_size {
+            return Err(EINVAL);
+        }
+
+        let ptr = raw.as_mut_ptr();
+        // CAST: `GetVersionRsp` only contains integers and has `repr(C)`.
+        let ptr = ptr.cast::<GetVersionRsp>();
+        // SAFETY: `ptr` came from a reference and the cast above is valid.
+        let rsp: &mut GetVersionRsp = unsafe { &mut *ptr };
+
+        // Creating a reference on a packed struct will result in

Silly question, but why is it packed? We might need to do this for some structures in SPDM but do we have transports that mess up the alignment enough that where the messages themselves don't need to be packed we have to mark the structures so anyway? Probably good to document this somewhere.

lib/rspdm/validator.rs pub(crate) struct SpdmErrorRsp { · line 128

Quoted 6 lines
lib/rspdm/validator.rs +6 -0
+        // undefined behaviour, so we operate on the raw data directly
+        let unaligned = core::ptr::addr_of_mut!(rsp.version_number_entries) as *mut u16;
+        for version_offset in 0..version_number_entries {
+            let addr = unaligned.wrapping_add(version_offset);
+            let version = unsafe { core::ptr::read_unaligned::<u16>(addr) };
+            unsafe { core::ptr::write_unaligned::<u16>(addr, version.to_le()) }

I'd like to see a comment on why this seems to be doing an endian swap if we are on big endian. Looking back at the c code, it has an endian swap but only as part of the search for a version to use (finding the largest both device and and kernel support).

Maybe I'm reading it wrong but isn't this putting cpu endian data into a structure element that is of type __le16?

Quoted 5 lines
lib/rspdm/validator.rs +5 -0
+        }
+
+        Ok(rsp)
+    }
+}
#23 lore raw

Re: [RFC v3 12/27] lib: rspdm: Support SPDM get_version

Quoted 28 lines · Jonathan Cameron
Quoted 5 lines · On Wed, 11 Feb 2026 13:29:19 +1000 [email protected] wrote:

From: Alistair Francis <[email protected]>

Support the GET_VERSION SPDM command.

Hi Alistair,

Various minor comments inline. Biggest one is what I think is some confusing le16 handling that to me is in the wrong place.

Jonathan

Quoted 11 lines
---
 lib/rspdm/consts.rs    | 17 +++++++++++
 lib/rspdm/lib.rs       |  6 +++-
 lib/rspdm/state.rs     | 58 ++++++++++++++++++++++++++++++++++--
 lib/rspdm/validator.rs | 67 ++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 144 insertions(+), 4 deletions(-)
lib/rspdm/consts.rs +0 -0
index 40ce60eba2f3..38f48f0863e2 100644
Quoted 10 lines

lib/rspdm/consts.rs fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { · line 134

Quoted 7 lines
lib/rspdm/consts.rs +3 -0
@@ -115,3 +129,6 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
         Ok(())
     }
 }
+
+pub(crate) const SPDM_GET_VERSION: u8 = 0x84;
+pub(crate) const SPDM_GET_VERSION_LEN: usize = mem::size_of::<SpdmHeader>() + 255;

I'd either add a comment on where the 255 comes from. Also do we have a U8_MAX definition we can use? Or some way to get it from code?

Fixed, can just use u8::MAX

Quoted 13 lines

lib/rspdm/lib.rs line 111

Quoted 11 lines
lib/rspdm/lib.rs +1 -1
index 2bb716140e0a..08abcbb21247 100644
@@ -108,7 +108,11 @@
 /// Return 0 on success or a negative errno.  In particular, -EPROTONOSUPPORT
 /// indicates authentication is not supported by the device.
 #[no_mangle]
-pub unsafe extern "C" fn spdm_authenticate(_state: &'static mut SpdmState) -> c_int {
+pub unsafe extern "C" fn spdm_authenticate(state: &'static mut SpdmState) -> c_int {

Is there more to this rename than it appears? Can it get pushed back to earlier patch?

Rust will complain about state being unused, by adding a _ to the front we don't get the warning in the previous patch. So in this patch I rename it back to state as it's now used.

This is a public function that matches the C implementation, hence the arguments being fixed in the previous patch.

Quoted 24 lines
Quoted 24 lines
lib/rspdm/lib.rs +4 -0
+    if let Err(e) = state.get_version() {
+        return e.to_errno() as c_int;
+    }
+
     0
 }
 
lib/rspdm/state.rs +1 -0
index 68861f30e3fa..3ad53ec05044 100644
@@ -8,6 +8,7 @@
 //! <https://www.dmtf.org/dsp/DSP0274>
 
 use core::ffi::c_void;
+use core::slice::from_raw_parts_mut;
 use kernel::prelude::*;
 use kernel::{
     bindings,
@@ -15,8 +16,10 @@
     validate::Untrusted,
 };
 
Quoted 7 lines

lib/rspdm/state.rs line 20

Quoted 4 lines
lib/rspdm/state.rs +2 -2
-use crate::consts::{SpdmErrorCode, SPDM_ERROR, SPDM_REQ};
-use crate::validator::{SpdmErrorRsp, SpdmHeader};
+use crate::consts::{
+    SpdmErrorCode, SPDM_ERROR, SPDM_GET_VERSION_LEN, SPDM_MAX_VER, SPDM_MIN_VER, SPDM_REQ,

Is the Rust linter fussy about these being on one line? Feels like we'd get less churn over time if we formatted this as one per line.

This is just how the linter formats it when I run LLVM=1 make rustfmt. I don't really want to be manually formatting things

Quoted 4 lines
Quoted 4 lines
lib/rspdm/state.rs +2 -0
+};
+use crate::validator::{GetVersionReq, GetVersionRsp, SpdmErrorRsp, SpdmHeader};
 
Quoted 10 lines

lib/rspdm/state.rs pub(crate) fn new( · line 67

Quoted 8 lines
lib/rspdm/state.rs +1 -1
 /// The current SPDM session state for a device. Based on the
 /// C `struct spdm_state`.
@@ -61,7 +64,7 @@ pub(crate) fn new(
             transport_sz,
             keyring,
             validate,
-            version: 0x10,
+            version: SPDM_MIN_VER,

Pull that def back to earlier patch to keep the churn confined.

Fixed

Quoted 26 lines
Quoted 26 lines
lib/rspdm/state.rs +18 -0
         }
     }
 
@@ -217,4 +220,53 @@ pub(crate) fn spdm_exchange(
 
         Ok(length)
     }
+
+    /// Negoiate a supported SPDM version and store the information
+    /// in the `SpdmState`.
+    pub(crate) fn get_version(&mut self) -> Result<(), Error> {
+        let mut request = GetVersionReq::default();
+        request.version = self.version;
+
+        // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
+        let request_buf = unsafe {
+            from_raw_parts_mut(
+                &mut request as *mut _ as *mut u8,
+                core::mem::size_of::<GetVersionReq>(),
+            )
+        };
+
+        let mut response_vec: KVec<u8> = KVec::with_capacity(SPDM_GET_VERSION_LEN, GFP_KERNEL)?;
+        // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
+        let response_buf =
Quoted 8 lines

lib/rspdm/state.rs pub(crate) fn spdm_exchange( · line 246

Quoted 6 lines
lib/rspdm/state.rs +6 -0
+            unsafe { from_raw_parts_mut(response_vec.as_mut_ptr(), SPDM_GET_VERSION_LEN) };
+
+        let rc = self.spdm_exchange(request_buf, response_buf)?;
+
+        // SAFETY: `rc` is the length of data read, which will be smaller
+        // then the capacity of the vector

than?

Fixed

Quoted 12 lines

lib/rspdm/state.rs pub(crate) fn spdm_exchange( · line 255

Quoted 10 lines
lib/rspdm/state.rs +9 -0
+        unsafe { response_vec.inc_len(rc as usize) };
+
+        let response: &mut GetVersionRsp = Untrusted::new_mut(&mut response_vec).validate_mut()?;
+
+        let mut foundver = false;
+        for i in 0..response.version_number_entry_count {
+            // Creating a reference on a packed struct will result in
+            // undefined behaviour, so we operate on the raw data directly
+            let unaligned = core::ptr::addr_of_mut!(response.version_number_entries) as *mut u16;

Maybe pull that out of the loop like you do below?

Fixed

Quoted 5 lines

lib/rspdm/state.rs pub(crate) fn spdm_exchange( · line 257

Quoted 3 lines
lib/rspdm/state.rs +2 -0
+            let addr = unaligned.wrapping_add(i as usize);
+            let version = (unsafe { core::ptr::read_unaligned::<u16>(addr) } >> 8) as u8;

Given the endian conversion below, this is correct, but I think leaving it as the __le16 until here is better. We could also just pull out the correct byte and ignore the other one.

Hmm... I'm not sure I follow.

The SPDM spec describes this as a u16, so I'm reading the entire value and just taking the upper bits for the Major/Minor version

So I'm not sure what you think I should do differently?

Quoted 5 lines

It's a spec quirk that they decided to have it defined as a __le16 rather than u8[2] as the fields are confined one byte or the other.

I wonder if we should also reject any alpha versions? I.e. check the bottom 4 bits are 0.

Hmmm... That seems a bit unnecessary. Maybe a warning though?

Quoted 32 lines
Quoted 32 lines
lib/rspdm/state.rs +14 -0
+
+            if version >= self.version && version <= SPDM_MAX_VER {
+                self.version = version;
+                foundver = true;
+            }
+        }
+
+        if !foundver {
+            pr_err!("No common supported version\n");
+            to_result(-(bindings::EPROTO as i32))?;
+        }
+
+        Ok(())
+    }
 }
lib/rspdm/validator.rs +1 -0
index a0a3a2f46952..f69be6aa6280 100644
@@ -7,6 +7,7 @@
 //! Rust implementation of the DMTF Security Protocol and Data Model (SPDM)
 //! <https://www.dmtf.org/dsp/DSP0274>
 
+use crate::bindings::{__IncompleteArrayField, __le16};
 use crate::consts::SpdmErrorCode;
 use core::mem;
 use kernel::prelude::*;
@@ -15,6 +16,8 @@
     validate::{Unvalidated, Validate},
 };
 
Quoted 13 lines

lib/rspdm/validator.rs pub(crate) struct SpdmErrorRsp { · line 71

Quoted 11 lines
lib/rspdm/validator.rs +4 -0
+use crate::consts::SPDM_GET_VERSION;
+
 #[repr(C, packed)]
 pub(crate) struct SpdmHeader {
     pub(crate) version: u8,
@@ -64,3 +67,67 @@ pub(crate) struct SpdmErrorRsp {
     pub(crate) error_code: SpdmErrorCode,
     pub(crate) error_data: u8,
 }
+
+#[repr(C, packed)]

Why packed?

We cast it from a struct to a slice (array), so we need it to be packed to avoid gaps in the slice (array)

Quoted 15 lines

lib/rspdm/validator.rs pub(crate) struct SpdmErrorRsp { · line 82

Quoted 12 lines
lib/rspdm/validator.rs +11 -0
+pub(crate) struct GetVersionReq {
+    pub(crate) version: u8,
+    pub(crate) code: u8,
+    pub(crate) param1: u8,
+    pub(crate) param2: u8,
+}
+
+impl Default for GetVersionReq {
+    fn default() -> Self {
+        GetVersionReq {
+            version: 0,

Given this only takes one value we could default it to 0x10 (even though it's written above to keep the state machine in sync).

We could, I do find that less clear though. Explicit writing it as part of get_version() to me is really clear what is happening

Quoted 24 lines
Quoted 24 lines
lib/rspdm/validator.rs +23 -0
+            code: SPDM_GET_VERSION,
+            param1: 0,
+            param2: 0,
+        }
+    }
+}
+
+#[repr(C, packed)]
+pub(crate) struct GetVersionRsp {
+    pub(crate) version: u8,
+    pub(crate) code: u8,
+    param1: u8,
+    param2: u8,
+    reserved: u8,
+    pub(crate) version_number_entry_count: u8,
+    pub(crate) version_number_entries: __IncompleteArrayField<__le16>,
+}
+
+impl Validate<&mut Unvalidated<KVec<u8>>> for &mut GetVersionRsp {
+    type Err = Error;
+
+    fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> {
+        let raw = unvalidated.raw_mut();
Quoted 8 lines

lib/rspdm/validator.rs pub(crate) struct SpdmErrorRsp { · line 108

Quoted 3 lines
lib/rspdm/validator.rs +3 -0
+        if raw.len() < mem::size_of::<GetVersionRsp>() {
+            return Err(EINVAL);
+        }

I guess it probably belongs at the header validation point rather that here, but can we also check the version matches what was requested. Possibly an additional check here that it is 0x10 (as this one is a special case where only that value is correct).

Good point, I have added a check here

Quoted 6 lines

lib/rspdm/validator.rs pub(crate) struct SpdmErrorRsp { · line 111

Quoted 4 lines
lib/rspdm/validator.rs +3 -0
+
+        let version_number_entries = *(raw.get(5).ok_or(ENOMEM))? as usize;
+        let total_expected_size = version_number_entries * 2 + 6;

Instead of 6 can we use mem::size_of::<GetVersionRsp>()?

Yep! Good idea

Quoted 2 lines

Ideally something similar for the 2 as well even if it's a bit verbose. (if I was lazy in the C code you get to make it better here :)

let total_expected_size = version_number_entries * mem::size_of::<__le16>() + mem::size_of::<GetVersionRsp>();

Quoted 18 lines

lib/rspdm/validator.rs pub(crate) struct SpdmErrorRsp { · line 122

Quoted 13 lines
lib/rspdm/validator.rs +11 -0
+        if raw.len() < total_expected_size {
+            return Err(EINVAL);
+        }
+
+        let ptr = raw.as_mut_ptr();
+        // CAST: `GetVersionRsp` only contains integers and has `repr(C)`.
+        let ptr = ptr.cast::<GetVersionRsp>();
+        // SAFETY: `ptr` came from a reference and the cast above is valid.
+        let rsp: &mut GetVersionRsp = unsafe { &mut *ptr };
+
+        // Creating a reference on a packed struct will result in

Silly question, but why is it packed? We might need to do this for some structures in SPDM but do we have transports that mess up the alignment enough that where the messages themselves don't need to be packed we have to mark the structures so anyway? Probably good to document this somewhere.

Like above, the Rust structs contain the information which we cast to the slice (u8 array). So we want it to be packed to avoid gaps in the buffer.

Quoted 8 lines

lib/rspdm/validator.rs pub(crate) struct SpdmErrorRsp { · line 128

Quoted 7 lines
lib/rspdm/validator.rs +6 -0
+        // undefined behaviour, so we operate on the raw data directly
+        let unaligned = core::ptr::addr_of_mut!(rsp.version_number_entries) as *mut u16;
+        for version_offset in 0..version_number_entries {
+            let addr = unaligned.wrapping_add(version_offset);
+            let version = unsafe { core::ptr::read_unaligned::<u16>(addr) };
+            unsafe { core::ptr::write_unaligned::<u16>(addr, version.to_le()) }
Quoted 7 lines

I'd like to see a comment on why this seems to be doing an endian swap if we are on big endian. Looking back at the c code, it has an endian swap but only as part of the search for a version to use (finding the largest both device and and kernel support).

Maybe I'm reading it wrong but isn't this putting cpu endian data into a structure element that is of type __le16?

On second thought I don't think this is required at all. It's converting the LE data from the SPDM response to LE. Which is just unnecessary no-op (LE) or a bug (BE) depending on the CPU endianness.

Alistair

Quoted 7 lines
Quoted 6 lines
lib/rspdm/validator.rs +5 -0
+        }
+
+        Ok(rsp)
+    }
+}
#24 lore raw

Re: [RFC v3 12/27] lib: rspdm: Support SPDM get_version

Quoted 3 lines · Alistair Francis

This is a public function that matches the C implementation, hence the arguments being fixed in the previous patch.

Since you have it in a C header and in bindings_helper.h, then you should be able to use #[export], which will do a typecheck that the signatures match (it also does the no_mangle, so you can replace it):

https://rust.docs.kernel.org/macros/attr.export.html

Quoted 2 lines

This is just how the linter formats it when I run LLVM=1 make rustfmt. I don't really want to be manually formatting things

Please use the "kernel vertical style" for imports (it is done automatically by rustfmt too by adding the //):

https://docs.kernel.org/rust/coding-guidelines.html#imports

We have size_of, align_of and so on in the prelude, so you can use those directly:

https://rust.docs.kernel.org/kernel/prelude/index.html

I hope that helps!

Cheers,
Miguel

#25 lore raw

Re: [RFC v3 12/27] lib: rspdm: Support SPDM get_version

Quoted 5 lines · Miguel Ojeda

We have size_of, align_of and so on in the prelude, so you can use those directly:

https://rust.docs.kernel.org/kernel/prelude/index.html

Sorry, I cut the quotes too much -- this was supposed to reply to the total_expected_size part of the conversation.

Cheers,
Miguel

#26 lore raw

Re: [RFC v3 12/27] lib: rspdm: Support SPDM get_version

In reply to Alistair Francis
Quoted 5 lines
Quoted 5 lines
Quoted 5 lines
lib/rspdm/state.rs +2 -0
+            let addr = unaligned.wrapping_add(i as usize);
+            let version = (unsafe { core::ptr::read_unaligned::<u16>(addr) } >> 8) as u8;  
Quoted 7 lines
Quoted 2 lines

Given the endian conversion below, this is correct, but I think leaving it as the __le16 until here is better. We could also just pull out the correct byte and ignore the other one.

Hmm... I'm not sure I follow.

The SPDM spec describes this as a u16, so I'm reading the entire value and just taking the upper bits for the Major/Minor version

Miss read by me I think... Or tied up with endian conversion I think you say you are getting rid of below.

Quoted 10 lines

So I'm not sure what you think I should do differently?

Quoted 5 lines

It's a spec quirk that they decided to have it defined as a __le16 rather than u8[2] as the fields are confined one byte or the other.

I wonder if we should also reject any alpha versions? I.e. check the bottom 4 bits are 0.

Hmmm... That seems a bit unnecessary. Maybe a warning though?

Warning works for me.

Quoted 47 lines
Quoted 33 lines
Quoted 33 lines
lib/rspdm/state.rs +14 -0
+
+            if version >= self.version && version <= SPDM_MAX_VER {
+                self.version = version;
+                foundver = true;
+            }
+        }
+
+        if !foundver {
+            pr_err!("No common supported version\n");
+            to_result(-(bindings::EPROTO as i32))?;
+        }
+
+        Ok(())
+    }
 }
lib/rspdm/validator.rs +1 -0
index a0a3a2f46952..f69be6aa6280 100644
@@ -7,6 +7,7 @@
 //! Rust implementation of the DMTF Security Protocol and Data Model (SPDM)
 //! <https://www.dmtf.org/dsp/DSP0274>
 
+use crate::bindings::{__IncompleteArrayField, __le16};
 use crate::consts::SpdmErrorCode;
 use core::mem;
 use kernel::prelude::*;
@@ -15,6 +16,8 @@
     validate::{Unvalidated, Validate},
 };
 
Quoted 13 lines

lib/rspdm/validator.rs pub(crate) struct SpdmErrorRsp { · line 71

Quoted 11 lines
lib/rspdm/validator.rs +4 -0
+use crate::consts::SPDM_GET_VERSION;
+
 #[repr(C, packed)]
 pub(crate) struct SpdmHeader {
     pub(crate) version: u8,
@@ -64,3 +67,67 @@ pub(crate) struct SpdmErrorRsp {
     pub(crate) error_code: SpdmErrorCode,
     pub(crate) error_data: u8,
 }
+
+#[repr(C, packed)]  

Why packed?

Quoted 2 lines

We cast it from a struct to a slice (array), so we need it to be packed to avoid gaps in the slice (array)

Ah ok. I'm learning slowly...

Quoted 21 lines
Quoted 21 lines
Quoted 5 lines
lib/rspdm/validator.rs +3 -0
+pub(crate) struct GetVersionReq {
+    pub(crate) version: u8,
+    pub(crate) code: u8,

lib/rspdm/validator.rs pub(crate) struct SpdmErrorRsp { · line 128

Quoted 10 lines
lib/rspdm/validator.rs +10 -0
+    pub(crate) param1: u8,
+    pub(crate) param2: u8,
+}
+
+        // undefined behaviour, so we operate on the raw data directly
+        let unaligned = core::ptr::addr_of_mut!(rsp.version_number_entries) as *mut u16;
+        for version_offset in 0..version_number_entries {
+            let addr = unaligned.wrapping_add(version_offset);
+            let version = unsafe { core::ptr::read_unaligned::<u16>(addr) };
+            unsafe { core::ptr::write_unaligned::<u16>(addr, version.to_le()) }  

I'd like to see a comment on why this seems to be doing an endian swap if we are on big endian. Looking back at the c code, it has an endian swap but only as part of the search for a version to use (finding the largest both device and and kernel support).

Quoted 6 lines
Quoted 2 lines

Maybe I'm reading it wrong but isn't this putting cpu endian data into a structure element that is of type __le16?

On second thought I don't think this is required at all. It's converting the LE data from the SPDM response to LE. Which is just unnecessary no-op (LE) or a bug (BE) depending on the CPU endianness.

I think that makes sense but I'll take a close look at v4.

J

Quoted 10 lines

Alistair

Quoted 7 lines
Quoted 6 lines
lib/rspdm/validator.rs +5 -0
+        }
+
+        Ok(rsp)
+    }
+}  
#27 lore raw

RFC v3 13/27 lib: rspdm: Support SPDM get_capabilities

In reply to [email protected]

From: Alistair Francis <[email protected]>

Support the GET_CAPABILITIES SPDM command.

---
 lib/rspdm/consts.rs    | 18 ++++++++--
 lib/rspdm/lib.rs       |  4 +++
 lib/rspdm/state.rs     | 68 +++++++++++++++++++++++++++++++++++--
 lib/rspdm/validator.rs | 76 +++++++++++++++++++++++++++++++++++++++++-
 4 files changed, 161 insertions(+), 5 deletions(-)
lib/rspdm/consts.rs +16 -2
index 38f48f0863e2..a6a66e2af1b5 100644
@@ -12,9 +12,7 @@
 
 /* SPDM versions supported by this implementation */
 pub(crate) const SPDM_VER_10: u8 = 0x10;
-#[allow(dead_code)]
 pub(crate) const SPDM_VER_11: u8 = 0x11;
-#[allow(dead_code)]
 pub(crate) const SPDM_VER_12: u8 = 0x12;
 pub(crate) const SPDM_VER_13: u8 = 0x13;
 
@@ -132,3 +130,19 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
 
 pub(crate) const SPDM_GET_VERSION: u8 = 0x84;
 pub(crate) const SPDM_GET_VERSION_LEN: usize = mem::size_of::<SpdmHeader>() + 255;
+
+pub(crate) const SPDM_GET_CAPABILITIES: u8 = 0xe1;
+pub(crate) const SPDM_MIN_DATA_TRANSFER_SIZE: u32 = 42;
+
+// SPDM cryptographic timeout of this implementation:
+// Assume calculations may take up to 1 sec on a busy machine, which equals
+// roughly 1 << 20.  That's within the limits mandated for responders by CMA
+// (1 << 23 usec, PCIe r6.2 sec 6.31.3) and DOE (1 sec, PCIe r6.2 sec 6.30.2).
+// Used in GET_CAPABILITIES exchange.
+pub(crate) const SPDM_CTEXPONENT: u8 = 20;
+
+pub(crate) const SPDM_CERT_CAP: u32 = 1 << 1;
+pub(crate) const SPDM_CHAL_CAP: u32 = 1 << 2;
+
+pub(crate) const SPDM_REQ_CAPS: u32 = SPDM_CERT_CAP | SPDM_CHAL_CAP;
+pub(crate) const SPDM_RSP_MIN_CAPS: u32 = SPDM_CERT_CAP | SPDM_CHAL_CAP;
lib/rspdm/lib.rs +4 -0
index 08abcbb21247..5f6653ada59d 100644
@@ -113,6 +113,10 @@
         return e.to_errno() as c_int;
     }
 
+    if let Err(e) = state.get_capabilities() {
+        return e.to_errno() as c_int;
+    }
+
     0
 }
 
lib/rspdm/state.rs +66 -2

97 lines not on this page · raw message

lib/rspdm/validator.rs +75 -1

85 lines not on this page · raw message

Signature · 1 line
2.52.0
#28 lore raw

Re: [RFC v3 13/27] lib: rspdm: Support SPDM get_capabilities

[snip]

Quoted 25 lines

+
+    /// Obtain the supported capabilities from an SPDM session and store the
+    /// information in the SpdmState.
+    pub(crate) fn get_capabilities(&mut self) -> Result<(), Error> { +        let mut request = GetCapabilitiesReq::default(); +        request.version = self.version;
+
+        let (req_sz, rsp_sz) = match self.version {
+            SPDM_VER_10 => (4, 8),
+            SPDM_VER_11 => (8, 8),
+            _ => {
+                request.data_transfer_size = self.transport_sz.to_le();
+                request.max_spdm_msg_size = request.data_transfer_size;

lib/rspdm/state.rs +9 -0
+
+                (
+                    core::mem::size_of::<GetCapabilitiesReq>(),
+                    core::mem::size_of::<GetCapabilitiesRsp>(),
+                )
+            }
+        };
+
+        // SAFETY: `request` is repr(C) and packed, so we can
Quoted 8 lines

convert it to a slice
+        let request_buf = unsafe { from_raw_parts_mut(&mut request as *mut _ as *mut u8, req_sz) };
+
+        let mut response_vec: KVec<u8> = KVec::with_capacity(rsp_sz, GFP_KERNEL)?;
+        // SAFETY: request is repr(C) and packed, so we can convert it to a slice

Same here about the SAFETY comment.

Quoted 20 lines

+        let response_buf = unsafe { from_raw_parts_mut(response_vec.as_mut_ptr(), rsp_sz) };

lib/rspdm/state.rs +9 -0
+
+        let rc = self.spdm_exchange(request_buf, response_buf)?;
+
+        if rc < (rsp_sz as i32) {
+            pr_err!("Truncated capabilities response\n");
+            to_result(-(bindings::EIO as i32))?;
+        }
+
+        // SAFETY: `rc` is the length of data read, which will be

smaller
+        // then the capacity of the vector
+        unsafe { response_vec.inc_len(rc as usize) };

lib/rspdm/state.rs +6 -0
+
+        let response: &mut GetCapabilitiesRsp =
+            Untrusted::new_mut(&mut response_vec).validate_mut()?;
+
+        self.rsp_caps = u32::from_le(response.flags);
+        if (self.rsp_caps & SPDM_RSP_MIN_CAPS) != SPDM_RSP_MIN_CAPS
Quoted 1 line

{

Might be useful for debugging to have a pr_err() here.

Wilfred

#29 lore raw

Re: [RFC v3 13/27] lib: rspdm: Support SPDM get_capabilities

In reply to [email protected]
Quoted 5 lines · On Wed, 11 Feb 2026 13:29:20 +1000 [email protected] wrote:

From: Alistair Francis <[email protected]>

Support the GET_CAPABILITIES SPDM command.

A few comments inline.
Looks in pretty good shape to me but definitely needs review by those more familiar with rust than me!

Quoted 22 lines
---
 lib/rspdm/consts.rs    | 18 ++++++++--
 lib/rspdm/lib.rs       |  4 +++
 lib/rspdm/state.rs     | 68 +++++++++++++++++++++++++++++++++++--
 lib/rspdm/validator.rs | 76 +++++++++++++++++++++++++++++++++++++++++-
 4 files changed, 161 insertions(+), 5 deletions(-)
lib/rspdm/consts.rs +0 -2
index 38f48f0863e2..a6a66e2af1b5 100644
@@ -12,9 +12,7 @@
 
 /* SPDM versions supported by this implementation */
 pub(crate) const SPDM_VER_10: u8 = 0x10;
-#[allow(dead_code)]
 pub(crate) const SPDM_VER_11: u8 = 0x11;
-#[allow(dead_code)]
 pub(crate) const SPDM_VER_12: u8 = 0x12;
 pub(crate) const SPDM_VER_13: u8 = 0x13;
 

lib/rspdm/consts.rs fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { · line 135

Quoted 6 lines
lib/rspdm/consts.rs +3 -0
@@ -132,3 +130,19 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
 
 pub(crate) const SPDM_GET_VERSION: u8 = 0x84;
 pub(crate) const SPDM_GET_VERSION_LEN: usize = mem::size_of::<SpdmHeader>() + 255;
+
+pub(crate) const SPDM_GET_CAPABILITIES: u8 = 0xe1;
+pub(crate) const SPDM_MIN_DATA_TRANSFER_SIZE: u32 = 42;

Spec reference? C code refers to /* SPDM 1.2.0 margin no 226 */ It's 269 in 1.3.1

lib/rspdm/consts.rs fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { · line 144

Quoted 9 lines
lib/rspdm/consts.rs +9 -0
+
+// SPDM cryptographic timeout of this implementation:
+// Assume calculations may take up to 1 sec on a busy machine, which equals
+// roughly 1 << 20.  That's within the limits mandated for responders by CMA
+// (1 << 23 usec, PCIe r6.2 sec 6.31.3) and DOE (1 sec, PCIe r6.2 sec 6.30.2).
+// Used in GET_CAPABILITIES exchange.
+pub(crate) const SPDM_CTEXPONENT: u8 = 20;
+
+pub(crate) const SPDM_CERT_CAP: u32 = 1 << 1;

Can we use bit_u32() or similar here?
We've tried to move to BIT() for similar defines in the C code, so I'm looking for something along those lines. Provides a form of documentation that they are single bit fields.

Quoted 24 lines
lib/rspdm/consts.rs +4 -0
+pub(crate) const SPDM_CHAL_CAP: u32 = 1 << 2;
+
+pub(crate) const SPDM_REQ_CAPS: u32 = SPDM_CERT_CAP | SPDM_CHAL_CAP;
+pub(crate) const SPDM_RSP_MIN_CAPS: u32 = SPDM_CERT_CAP | SPDM_CHAL_CAP;
lib/rspdm/lib.rs +4 -0
index 08abcbb21247..5f6653ada59d 100644
@@ -113,6 +113,10 @@
         return e.to_errno() as c_int;
     }
 
+    if let Err(e) = state.get_capabilities() {
+        return e.to_errno() as c_int;
+    }
+
     0
 }
 
lib/rspdm/state.rs +0 -0
index 3ad53ec05044..0efad7f341cd 100644

lib/rspdm/state.rs line 22

Quoted 7 lines
lib/rspdm/state.rs +3 -1
@@ -17,9 +17,12 @@
 };
 
 use crate::consts::{
-    SpdmErrorCode, SPDM_ERROR, SPDM_GET_VERSION_LEN, SPDM_MAX_VER, SPDM_MIN_VER, SPDM_REQ,
+    SpdmErrorCode, SPDM_ERROR, SPDM_GET_VERSION_LEN, SPDM_MAX_VER, SPDM_MIN_DATA_TRANSFER_SIZE,
+    SPDM_MIN_VER, SPDM_REQ, SPDM_RSP_MIN_CAPS, SPDM_VER_10, SPDM_VER_11, SPDM_VER_12,
+};

This is why I was getting bit grumpy in previous about long lines. Fair enough if this is only option, but it's not nice for a review process that relies on patches!

Quoted 63 lines
lib/rspdm/state.rs +33 -1
+use crate::validator::{
+    GetCapabilitiesReq, GetCapabilitiesRsp, GetVersionReq, GetVersionRsp, SpdmErrorRsp, SpdmHeader,
 };
-use crate::validator::{GetVersionReq, GetVersionRsp, SpdmErrorRsp, SpdmHeader};
 
 /// The current SPDM session state for a device. Based on the
 /// C `struct spdm_state`.
@@ -35,6 +38,8 @@
 ///
 /// `version`: Maximum common supported version of requester and responder.
 ///  Negotiated during GET_VERSION exchange.
+/// @rsp_caps: Cached capabilities of responder.
+///  Received during GET_CAPABILITIES exchange.
 #[expect(dead_code)]
 pub struct SpdmState {
     pub(crate) dev: *mut bindings::device,
@@ -46,6 +51,7 @@ pub struct SpdmState {
 
     // Negotiated state
     pub(crate) version: u8,
+    pub(crate) rsp_caps: u32,
 }
 
 impl SpdmState {
@@ -65,6 +71,7 @@ pub(crate) fn new(
             keyring,
             validate,
             version: SPDM_MIN_VER,
+            rsp_caps: 0,
         }
     }
 
@@ -269,4 +276,61 @@ pub(crate) fn get_version(&mut self) -> Result<(), Error> {
 
         Ok(())
     }
+
+    /// Obtain the supported capabilities from an SPDM session and store the
+    /// information in the `SpdmState`.
+    pub(crate) fn get_capabilities(&mut self) -> Result<(), Error> {
+        let mut request = GetCapabilitiesReq::default();
+        request.version = self.version;
+
+        let (req_sz, rsp_sz) = match self.version {
+            SPDM_VER_10 => (4, 8),
+            SPDM_VER_11 => (8, 8),
+            _ => {
+                request.data_transfer_size = self.transport_sz.to_le();
+                request.max_spdm_msg_size = request.data_transfer_size;
+
+                (
+                    core::mem::size_of::<GetCapabilitiesReq>(),
+                    core::mem::size_of::<GetCapabilitiesRsp>(),
+                )
+            }
+        };
+
+        // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
+        let request_buf = unsafe { from_raw_parts_mut(&mut request as *mut _ as *mut u8, req_sz) };
+
+        let mut response_vec: KVec<u8> = KVec::with_capacity(rsp_sz, GFP_KERNEL)?;
+        // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
+        let response_buf = unsafe { from_raw_parts_mut(response_vec.as_mut_ptr(), rsp_sz) };

lib/rspdm/state.rs pub(crate) fn get_version(&mut self) -> Result<(), Error> { · line 315

Quoted 10 lines
lib/rspdm/state.rs +10 -0
+
+        let rc = self.spdm_exchange(request_buf, response_buf)?;
+
+        if rc < (rsp_sz as i32) {
+            pr_err!("Truncated capabilities response\n");
+            to_result(-(bindings::EIO as i32))?;
+        }
+
+        // SAFETY: `rc` is the length of data read, which will be smaller
+        // then the capacity of the vector

Fairly sure it's exactly the size of the of vector in this case. Bit (0) of param1 isn't set sop the algorithms block is never included.

lib/rspdm/state.rs pub(crate) fn get_version(&mut self) -> Result<(), Error> { · line 321

Quoted 6 lines
lib/rspdm/state.rs +6 -0
+        unsafe { response_vec.inc_len(rc as usize) };
+
+        let response: &mut GetCapabilitiesRsp =
+            Untrusted::new_mut(&mut response_vec).validate_mut()?;
+
+        self.rsp_caps = u32::from_le(response.flags);

Isn't response packed? So why don't we need the read_unaligned stuff in this case but did in the previous patch?

Quoted 47 lines
lib/rspdm/state.rs +14 -0
+        if (self.rsp_caps & SPDM_RSP_MIN_CAPS) != SPDM_RSP_MIN_CAPS {
+            to_result(-(bindings::EPROTONOSUPPORT as i32))?;
+        }
+
+        if self.version >= SPDM_VER_12 {
+            if response.data_transfer_size < SPDM_MIN_DATA_TRANSFER_SIZE {
+                pr_err!("Malformed capabilities response\n");
+                to_result(-(bindings::EPROTO as i32))?;
+            }
+            self.transport_sz = self.transport_sz.min(response.data_transfer_size);
+        }
+
+        Ok(())
+    }
 }
lib/rspdm/validator.rs +16 -1
index f69be6aa6280..cd792c46767a 100644
@@ -16,7 +16,7 @@
     validate::{Unvalidated, Validate},
 };
 
-use crate::consts::SPDM_GET_VERSION;
+use crate::consts::{SPDM_CTEXPONENT, SPDM_GET_CAPABILITIES, SPDM_GET_VERSION, SPDM_REQ_CAPS};
 
 #[repr(C, packed)]
 pub(crate) struct SpdmHeader {
@@ -131,3 +131,77 @@ fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err>
         Ok(rsp)
     }
 }
+
+#[repr(C, packed)]
+pub(crate) struct GetCapabilitiesReq {
+    pub(crate) version: u8,
+    pub(crate) code: u8,
+    pub(crate) param1: u8,
+    pub(crate) param2: u8,
+
+    reserved1: u8,
+    pub(crate) ctexponent: u8,
+    reserved2: u16,
+
+    pub(crate) flags: u32,
+
+    /* End of SPDM 1.1 structure */

lib/rspdm/validator.rs fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> · line 158

Quoted 10 lines
lib/rspdm/validator.rs +10 -0
+    pub(crate) data_transfer_size: u32,
+    pub(crate) max_spdm_msg_size: u32,
+}
+
+impl Default for GetCapabilitiesReq {
+    fn default() -> Self {
+        GetCapabilitiesReq {
+            version: 0,
+            code: SPDM_GET_CAPABILITIES,
+            param1: 0,

Maybe add some comment on impacts of bit 0.

Quoted 7 lines
lib/rspdm/validator.rs +7 -0
+            param2: 0,
+            reserved1: 0,
+            ctexponent: SPDM_CTEXPONENT,
+            reserved2: 0,
+            flags: (SPDM_REQ_CAPS as u32).to_le(),
+            data_transfer_size: 0,
+            max_spdm_msg_size: 0,

lib/rspdm/validator.rs fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> · line 175

Quoted 10 lines
lib/rspdm/validator.rs +10 -0
+        }
+    }
+}
+
+#[repr(C, packed)]
+pub(crate) struct GetCapabilitiesRsp {
+    pub(crate) version: u8,
+    pub(crate) code: u8,
+    pub(crate) param1: u8,
+    pub(crate) param2: u8,

param 2 is reserved in 1.3.1 Maybe not pub(crate)?
If it is not reserved in some particular version perhaps add a comment.

Quoted 10 lines
lib/rspdm/validator.rs +10 -0
+
+    reserved1: u8,
+    pub(crate) ctexponent: u8,
+    reserved2: u16,
+
+    pub(crate) flags: u32,
+
+    /* End of SPDM 1.1 structure */
+    pub(crate) data_transfer_size: u32,
+    pub(crate) max_spdm_msg_size: u32,

lib/rspdm/validator.rs fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> · line 195

Quoted 10 lines
lib/rspdm/validator.rs +10 -0
+
+    pub(crate) supported_algorithms: __IncompleteArrayField<__le16>,
+}
+
+impl Validate<&mut Unvalidated<KVec<u8>>> for &mut GetCapabilitiesRsp {
+    type Err = Error;
+
+    fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> {
+        let raw = unvalidated.raw_mut();
+        if raw.len() < mem::size_of::<GetCapabilitiesRsp>() {

So we fail if 1.1? If that's the case I think we should fail when doing the version establishment in the previous patch.

The c code had some size mangling to deal with this difference.

Quoted 12 lines
lib/rspdm/validator.rs +12 -0
+            return Err(EINVAL);
+        }
+
+        let ptr = raw.as_mut_ptr();
+        // CAST: `GetCapabilitiesRsp` only contains integers and has `repr(C)`.
+        let ptr = ptr.cast::<GetCapabilitiesRsp>();
+        // SAFETY: `ptr` came from a reference and the cast above is valid.
+        let rsp: &mut GetCapabilitiesRsp = unsafe { &mut *ptr };
+
+        Ok(rsp)
+    }
+}
#30 lore raw

Re: [RFC v3 13/27] lib: rspdm: Support SPDM get_capabilities

Quoted 5 lines · Jonathan Cameron

Can we use bit_u32() or similar here?
We've tried to move to BIT() for similar defines in the C code, so I'm looking for something along those lines. Provides a form of documentation that they are single bit fields.

We have these so far:

https://rust.docs.kernel.org/kernel/bits/

Out-of-tree long ago we had also a bit<T> generic const function
(https://github.com/Rust-for-Linux/linux/blob/rust/rust/kernel/types.rs#L317-L349).
I think we also discussed a bit! macro at some point.

Quoted 3 lines

This is why I was getting bit grumpy in previous about long lines. Fair enough if this is only option, but it's not nice for a review process that relies on patches!

This should be using the kernel imports style, which should make a case like this more readable:

https://docs.kernel.org/rust/coding-guidelines.html#imports

I hope that helps!

Cheers,
Miguel

#31 lore raw

Re: [RFC v3 13/27] lib: rspdm: Support SPDM get_capabilities

In reply to Jonathan Cameron
Quoted 44 lines · Jonathan Cameron
Quoted 5 lines · On Wed, 11 Feb 2026 13:29:20 +1000 [email protected] wrote:

From: Alistair Francis <[email protected]>

Support the GET_CAPABILITIES SPDM command.

A few comments inline.
Looks in pretty good shape to me but definitely needs review by those more familiar with rust than me!

Quoted 22 lines
---
 lib/rspdm/consts.rs    | 18 ++++++++--
 lib/rspdm/lib.rs       |  4 +++
 lib/rspdm/state.rs     | 68 +++++++++++++++++++++++++++++++++++--
 lib/rspdm/validator.rs | 76 +++++++++++++++++++++++++++++++++++++++++-
 4 files changed, 161 insertions(+), 5 deletions(-)
lib/rspdm/consts.rs +0 -2
index 38f48f0863e2..a6a66e2af1b5 100644
@@ -12,9 +12,7 @@
 
 /* SPDM versions supported by this implementation */
 pub(crate) const SPDM_VER_10: u8 = 0x10;
-#[allow(dead_code)]
 pub(crate) const SPDM_VER_11: u8 = 0x11;
-#[allow(dead_code)]
 pub(crate) const SPDM_VER_12: u8 = 0x12;
 pub(crate) const SPDM_VER_13: u8 = 0x13;
 

lib/rspdm/consts.rs fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { · line 135

Quoted 6 lines
lib/rspdm/consts.rs +3 -0
@@ -132,3 +130,19 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
 
 pub(crate) const SPDM_GET_VERSION: u8 = 0x84;
 pub(crate) const SPDM_GET_VERSION_LEN: usize = mem::size_of::<SpdmHeader>() + 255;
+
+pub(crate) const SPDM_GET_CAPABILITIES: u8 = 0xe1;
+pub(crate) const SPDM_MIN_DATA_TRANSFER_SIZE: u32 = 42;

Spec reference? C code refers to /* SPDM 1.2.0 margin no 226 */ It's 269 in 1.3.1

Quoted 11 lines

lib/rspdm/consts.rs fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { · line 144

Quoted 9 lines
lib/rspdm/consts.rs +9 -0
+
+// SPDM cryptographic timeout of this implementation:
+// Assume calculations may take up to 1 sec on a busy machine, which equals
+// roughly 1 << 20.  That's within the limits mandated for responders by CMA
+// (1 << 23 usec, PCIe r6.2 sec 6.31.3) and DOE (1 sec, PCIe r6.2 sec 6.30.2).
+// Used in GET_CAPABILITIES exchange.
+pub(crate) const SPDM_CTEXPONENT: u8 = 20;
+
+pub(crate) const SPDM_CERT_CAP: u32 = 1 << 1;

Can we use bit_u32() or similar here?

Yep, fixed

Quoted 31 lines

We've tried to move to BIT() for similar defines in the C code, so I'm looking for something along those lines. Provides a form of documentation that they are single bit fields.

Quoted 26 lines
lib/rspdm/consts.rs +4 -0
+pub(crate) const SPDM_CHAL_CAP: u32 = 1 << 2;
+
+pub(crate) const SPDM_REQ_CAPS: u32 = SPDM_CERT_CAP | SPDM_CHAL_CAP;
+pub(crate) const SPDM_RSP_MIN_CAPS: u32 = SPDM_CERT_CAP | SPDM_CHAL_CAP;
lib/rspdm/lib.rs +4 -0
index 08abcbb21247..5f6653ada59d 100644
@@ -113,6 +113,10 @@
         return e.to_errno() as c_int;
     }
 
+    if let Err(e) = state.get_capabilities() {
+        return e.to_errno() as c_int;
+    }
+
     0
 }
 
lib/rspdm/state.rs +0 -0
index 3ad53ec05044..0efad7f341cd 100644
@@ -17,9 +17,12 @@
 };
 
Quoted 9 lines

lib/rspdm/state.rs line 22

Quoted 5 lines
lib/rspdm/state.rs +3 -1
 use crate::consts::{
-    SpdmErrorCode, SPDM_ERROR, SPDM_GET_VERSION_LEN, SPDM_MAX_VER, SPDM_MIN_VER, SPDM_REQ,
+    SpdmErrorCode, SPDM_ERROR, SPDM_GET_VERSION_LEN, SPDM_MAX_VER, SPDM_MIN_DATA_TRANSFER_SIZE,
+    SPDM_MIN_VER, SPDM_REQ, SPDM_RSP_MIN_CAPS, SPDM_VER_10, SPDM_VER_11, SPDM_VER_12,
+};

This is why I was getting bit grumpy in previous about long lines. Fair enough if this is only option, but it's not nice for a review process that relies on patches!

Also fixed

Quoted 70 lines
Quoted 70 lines
lib/rspdm/state.rs +38 -1
+use crate::validator::{
+    GetCapabilitiesReq, GetCapabilitiesRsp, GetVersionReq, GetVersionRsp, SpdmErrorRsp, SpdmHeader,
 };
-use crate::validator::{GetVersionReq, GetVersionRsp, SpdmErrorRsp, SpdmHeader};
 
 /// The current SPDM session state for a device. Based on the
 /// C `struct spdm_state`.
@@ -35,6 +38,8 @@
 ///
 /// `version`: Maximum common supported version of requester and responder.
 ///  Negotiated during GET_VERSION exchange.
+/// @rsp_caps: Cached capabilities of responder.
+///  Received during GET_CAPABILITIES exchange.
 #[expect(dead_code)]
 pub struct SpdmState {
     pub(crate) dev: *mut bindings::device,
@@ -46,6 +51,7 @@ pub struct SpdmState {
 
     // Negotiated state
     pub(crate) version: u8,
+    pub(crate) rsp_caps: u32,
 }
 
 impl SpdmState {
@@ -65,6 +71,7 @@ pub(crate) fn new(
             keyring,
             validate,
             version: SPDM_MIN_VER,
+            rsp_caps: 0,
         }
     }
 
@@ -269,4 +276,61 @@ pub(crate) fn get_version(&mut self) -> Result<(), Error> {
 
         Ok(())
     }
+
+    /// Obtain the supported capabilities from an SPDM session and store the
+    /// information in the `SpdmState`.
+    pub(crate) fn get_capabilities(&mut self) -> Result<(), Error> {
+        let mut request = GetCapabilitiesReq::default();
+        request.version = self.version;
+
+        let (req_sz, rsp_sz) = match self.version {
+            SPDM_VER_10 => (4, 8),
+            SPDM_VER_11 => (8, 8),
+            _ => {
+                request.data_transfer_size = self.transport_sz.to_le();
+                request.max_spdm_msg_size = request.data_transfer_size;
+
+                (
+                    core::mem::size_of::<GetCapabilitiesReq>(),
+                    core::mem::size_of::<GetCapabilitiesRsp>(),
+                )
+            }
+        };
+
+        // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
+        let request_buf = unsafe { from_raw_parts_mut(&mut request as *mut _ as *mut u8, req_sz) };
+
+        let mut response_vec: KVec<u8> = KVec::with_capacity(rsp_sz, GFP_KERNEL)?;
+        // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
+        let response_buf = unsafe { from_raw_parts_mut(response_vec.as_mut_ptr(), rsp_sz) };
+
+        let rc = self.spdm_exchange(request_buf, response_buf)?;
+
+        if rc < (rsp_sz as i32) {
+            pr_err!("Truncated capabilities response\n");
Quoted 8 lines

lib/rspdm/state.rs pub(crate) fn get_version(&mut self) -> Result<(), Error> { · line 315

Quoted 5 lines
lib/rspdm/state.rs +5 -0
+            to_result(-(bindings::EIO as i32))?;
+        }
+
+        // SAFETY: `rc` is the length of data read, which will be smaller
+        // then the capacity of the vector

Fairly sure it's exactly the size of the of vector in this case. Bit (0) of param1 isn't set sop the algorithms block is never included.

It is, my point was that it won't overflow. I'll adjust this to be more clear

Quoted 10 lines

lib/rspdm/state.rs pub(crate) fn get_version(&mut self) -> Result<(), Error> { · line 321

Quoted 7 lines
lib/rspdm/state.rs +6 -0
+        unsafe { response_vec.inc_len(rc as usize) };
+
+        let response: &mut GetCapabilitiesRsp =
+            Untrusted::new_mut(&mut response_vec).validate_mut()?;
+
+        self.rsp_caps = u32::from_le(response.flags);

Isn't response packed? So why don't we need the read_unaligned stuff in this case but did in the previous patch?

We need it in the previous patch as we are using a pointer to iterate over the dynamic length version_number_entries array, that doesn't apply here

Quoted 97 lines
Quoted 48 lines
lib/rspdm/state.rs +14 -0
+        if (self.rsp_caps & SPDM_RSP_MIN_CAPS) != SPDM_RSP_MIN_CAPS {
+            to_result(-(bindings::EPROTONOSUPPORT as i32))?;
+        }
+
+        if self.version >= SPDM_VER_12 {
+            if response.data_transfer_size < SPDM_MIN_DATA_TRANSFER_SIZE {
+                pr_err!("Malformed capabilities response\n");
+                to_result(-(bindings::EPROTO as i32))?;
+            }
+            self.transport_sz = self.transport_sz.min(response.data_transfer_size);
+        }
+
+        Ok(())
+    }
 }
lib/rspdm/validator.rs +16 -1
index f69be6aa6280..cd792c46767a 100644
@@ -16,7 +16,7 @@
     validate::{Unvalidated, Validate},
 };
 
-use crate::consts::SPDM_GET_VERSION;
+use crate::consts::{SPDM_CTEXPONENT, SPDM_GET_CAPABILITIES, SPDM_GET_VERSION, SPDM_REQ_CAPS};
 
 #[repr(C, packed)]
 pub(crate) struct SpdmHeader {
@@ -131,3 +131,77 @@ fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err>
         Ok(rsp)
     }
 }
+
+#[repr(C, packed)]
+pub(crate) struct GetCapabilitiesReq {
+    pub(crate) version: u8,
+    pub(crate) code: u8,
+    pub(crate) param1: u8,
+    pub(crate) param2: u8,
+
+    reserved1: u8,
+    pub(crate) ctexponent: u8,
+    reserved2: u16,
+
+    pub(crate) flags: u32,
+
+    /* End of SPDM 1.1 structure */

lib/rspdm/validator.rs fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> · line 158

Quoted 10 lines
lib/rspdm/validator.rs +10 -0
+    pub(crate) data_transfer_size: u32,
+    pub(crate) max_spdm_msg_size: u32,
+}
+
+impl Default for GetCapabilitiesReq {
+    fn default() -> Self {
+        GetCapabilitiesReq {
+            version: 0,
+            code: SPDM_GET_CAPABILITIES,
+            param1: 0,

Maybe add some comment on impacts of bit 0.

Quoted 7 lines
lib/rspdm/validator.rs +7 -0
+            param2: 0,
+            reserved1: 0,
+            ctexponent: SPDM_CTEXPONENT,
+            reserved2: 0,
+            flags: (SPDM_REQ_CAPS as u32).to_le(),
+            data_transfer_size: 0,
+            max_spdm_msg_size: 0,

lib/rspdm/validator.rs fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> · line 175

Quoted 10 lines
lib/rspdm/validator.rs +10 -0
+        }
+    }
+}
+
+#[repr(C, packed)]
+pub(crate) struct GetCapabilitiesRsp {
+    pub(crate) version: u8,
+    pub(crate) code: u8,
+    pub(crate) param1: u8,
+    pub(crate) param2: u8,

param 2 is reserved in 1.3.1 Maybe not pub(crate)?
If it is not reserved in some particular version perhaps add a comment.

Quoted 15 lines
lib/rspdm/validator.rs +15 -0
+
+    reserved1: u8,
+    pub(crate) ctexponent: u8,
+    reserved2: u16,
+
+    pub(crate) flags: u32,
+
+    /* End of SPDM 1.1 structure */
+    pub(crate) data_transfer_size: u32,
+    pub(crate) max_spdm_msg_size: u32,
+
+    pub(crate) supported_algorithms: __IncompleteArrayField<__le16>,
+}
+
+impl Validate<&mut Unvalidated<KVec<u8>>> for &mut GetCapabilitiesRsp {
Quoted 8 lines

lib/rspdm/validator.rs fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> · line 195

Quoted 5 lines
lib/rspdm/validator.rs +5 -0
+    type Err = Error;
+
+    fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> {
+        let raw = unvalidated.raw_mut();
+        if raw.len() < mem::size_of::<GetCapabilitiesRsp>() {

So we fail if 1.1? If that's the case I think we should fail when doing the version establishment in the previous patch.

Good catch, we shouldn't fail. I'll fix this

Alistair

Quoted 16 lines

The c code had some size mangling to deal with this difference.

Quoted 12 lines
lib/rspdm/validator.rs +12 -0
+            return Err(EINVAL);
+        }
+
+        let ptr = raw.as_mut_ptr();
+        // CAST: `GetCapabilitiesRsp` only contains integers and has `repr(C)`.
+        let ptr = ptr.cast::<GetCapabilitiesRsp>();
+        // SAFETY: `ptr` came from a reference and the cast above is valid.
+        let rsp: &mut GetCapabilitiesRsp = unsafe { &mut *ptr };
+
+        Ok(rsp)
+    }
+}
#32 lore raw

RFC v3 14/27 lib: rspdm: Support SPDM negotiate_algorithms

In reply to [email protected]

From: Alistair Francis <[email protected]>

Support the NEGOTIATE_ALGORITHMS SPDM command.

---
 lib/rspdm/consts.rs    |  53 ++++++++++
 lib/rspdm/lib.rs       |  16 ++-
 lib/rspdm/state.rs     | 214 ++++++++++++++++++++++++++++++++++++++++-
 lib/rspdm/validator.rs | 115 +++++++++++++++++++++-
 4 files changed, 391 insertions(+), 7 deletions(-)
lib/rspdm/consts.rs +53 -0
index a6a66e2af1b5..e8a05fd4299b 100644
@@ -143,6 +143,59 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
 
 pub(crate) const SPDM_CERT_CAP: u32 = 1 << 1;
 pub(crate) const SPDM_CHAL_CAP: u32 = 1 << 2;
+pub(crate) const SPDM_MEAS_CAP_MASK: u32 = 3 << 3;
+pub(crate) const SPDM_KEY_EX_CAP: u32 = 1 << 9;
 
 pub(crate) const SPDM_REQ_CAPS: u32 = SPDM_CERT_CAP | SPDM_CHAL_CAP;
 pub(crate) const SPDM_RSP_MIN_CAPS: u32 = SPDM_CERT_CAP | SPDM_CHAL_CAP;
+
+pub(crate) const SPDM_NEGOTIATE_ALGS: u8 = 0xe3;
+
+pub(crate) const SPDM_MEAS_SPEC_DMTF: u8 = 1 << 0;
+
+pub(crate) const SPDM_ASYM_RSASSA_2048: u32 = 1 << 0;
+pub(crate) const _SPDM_ASYM_RSAPSS_2048: u32 = 1 << 1;
+pub(crate) const SPDM_ASYM_RSASSA_3072: u32 = 1 << 2;
+pub(crate) const _SPDM_ASYM_RSAPSS_3072: u32 = 1 << 3;
+pub(crate) const SPDM_ASYM_ECDSA_ECC_NIST_P256: u32 = 1 << 4;
+pub(crate) const SPDM_ASYM_RSASSA_4096: u32 = 1 << 5;
+pub(crate) const _SPDM_ASYM_RSAPSS_4096: u32 = 1 << 6;
+pub(crate) const SPDM_ASYM_ECDSA_ECC_NIST_P384: u32 = 1 << 7;
+pub(crate) const SPDM_ASYM_ECDSA_ECC_NIST_P521: u32 = 1 << 8;
+pub(crate) const _SPDM_ASYM_SM2_ECC_SM2_P256: u32 = 1 << 9;
+pub(crate) const _SPDM_ASYM_EDDSA_ED25519: u32 = 1 << 10;
+pub(crate) const _SPDM_ASYM_EDDSA_ED448: u32 = 1 << 11;
+
+pub(crate) const SPDM_HASH_SHA_256: u32 = 1 << 0;
+pub(crate) const SPDM_HASH_SHA_384: u32 = 1 << 1;
+pub(crate) const SPDM_HASH_SHA_512: u32 = 1 << 2;
+
+#[cfg(CONFIG_CRYPTO_RSA)]
+pub(crate) const SPDM_ASYM_RSA: u32 =
+    SPDM_ASYM_RSASSA_2048 | SPDM_ASYM_RSASSA_3072 | SPDM_ASYM_RSASSA_4096;
+#[cfg(not(CONFIG_CRYPTO_RSA))]
+pub(crate) const SPDM_ASYM_RSA: u32 = 0;
+
+#[cfg(CONFIG_CRYPTO_ECDSA)]
+pub(crate) const SPDM_ASYM_ECDSA: u32 =
+    SPDM_ASYM_ECDSA_ECC_NIST_P256 | SPDM_ASYM_ECDSA_ECC_NIST_P384 | SPDM_ASYM_ECDSA_ECC_NIST_P521;
+#[cfg(not(CONFIG_CRYPTO_ECDSA))]
+pub(crate) const SPDM_ASYM_ECDSA: u32 = 0;
+
+#[cfg(CONFIG_CRYPTO_SHA256)]
+pub(crate) const SPDM_HASH_SHA2_256: u32 = SPDM_HASH_SHA_256;
+#[cfg(not(CONFIG_CRYPTO_SHA256))]
+pub(crate) const SPDM_HASH_SHA2_256: u32 = 0;
+
+#[cfg(CONFIG_CRYPTO_SHA512)]
+pub(crate) const SPDM_HASH_SHA2_384_512: u32 = SPDM_HASH_SHA_384 | SPDM_HASH_SHA_512;
+#[cfg(not(CONFIG_CRYPTO_SHA512))]
+pub(crate) const SPDM_HASH_SHA2_384_512: u32 = 0;
+
+pub(crate) const SPDM_ASYM_ALGOS: u32 = SPDM_ASYM_RSA | SPDM_ASYM_ECDSA;
+pub(crate) const SPDM_HASH_ALGOS: u32 = SPDM_HASH_SHA2_256 | SPDM_HASH_SHA2_384_512;
+
+/* Maximum number of ReqAlgStructs sent by this implementation */
+// pub(crate) const SPDM_MAX_REQ_ALG_STRUCT: usize = 4;
+
+pub(crate) const SPDM_OPAQUE_DATA_FMT_GENERAL: u8 = 1 << 1;
lib/rspdm/lib.rs +15 -1
index 5f6653ada59d..33f706a55e09 100644
@@ -117,6 +117,10 @@
         return e.to_errno() as c_int;
     }
 
+    if let Err(e) = state.negotiate_algs() {
+        return e.to_errno() as c_int;
+    }
+
     0
 }
 
@@ -124,4 +128,14 @@
 ///
 /// @spdm_state: SPDM session state
 #[no_mangle]
-pub unsafe extern "C" fn spdm_destroy(_state: &'static mut SpdmState) {}
+pub unsafe extern "C" fn spdm_destroy(state: &'static mut SpdmState) {
+    if let Some(desc) = &mut state.desc {
+        unsafe {
+            bindings::kfree(*desc as *mut _ as *mut c_void);
+        }
+    }
+
+    unsafe {
+        bindings::crypto_free_shash(state.shash);
+    }
+}
lib/rspdm/state.rs +210 -4

248 lines not on this page · raw message

lib/rspdm/validator.rs +113 -2

130 lines not on this page · raw message

Signature · 1 line
2.52.0
#33 lore raw

Re: [RFC v3 14/27] lib: rspdm: Support SPDM negotiate_algorithms

Quoted 5 lines · On Wed, 11 Feb 2026 13:29:21 +1000 [email protected] wrote:

From: Alistair Francis <[email protected]>

Support the NEGOTIATE_ALGORITHMS SPDM command.

Hi Alistair,

A few comments inline.

Thanks,

Jonathan

Quoted 11 lines
---
 lib/rspdm/consts.rs    |  53 ++++++++++
 lib/rspdm/lib.rs       |  16 ++-
 lib/rspdm/state.rs     | 214 ++++++++++++++++++++++++++++++++++++++++-
 lib/rspdm/validator.rs | 115 +++++++++++++++++++++-
 4 files changed, 391 insertions(+), 7 deletions(-)
lib/rspdm/consts.rs +0 -0
index a6a66e2af1b5..e8a05fd4299b 100644

lib/rspdm/consts.rs fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { · line 177

Quoted 6 lines
lib/rspdm/consts.rs +5 -0
 
+#[cfg(CONFIG_CRYPTO_RSA)]
+pub(crate) const SPDM_ASYM_RSA: u32 =
+    SPDM_ASYM_RSASSA_2048 | SPDM_ASYM_RSASSA_3072 | SPDM_ASYM_RSASSA_4096;
+#[cfg(not(CONFIG_CRYPTO_RSA))]
+pub(crate) const SPDM_ASYM_RSA: u32 = 0;

Maybe add a comment on why setting these to 0 makes sense when the support isn't built in because how how they are used.

Quoted 4 lines
lib/rspdm/state.rs +0 -0
index 0efad7f341cd..d0b10f27cd9c 100644

...

Quoted 12 lines

/// The current SPDM session state for a device. Based on the

lib/rspdm/state.rs +6 -0
@@ -40,6 +46,28 @@
 ///  Negotiated during GET_VERSION exchange.
 /// @rsp_caps: Cached capabilities of responder.
 ///  Received during GET_CAPABILITIES exchange.
+/// @base_asym_alg: Asymmetric key algorithm for signature verification of
+///  CHALLENGE_AUTH and MEASUREMENTS messages.
+///  Selected by responder during NEGOTIATE_ALGORITHMS exchange.
+/// @base_hash_alg: Hash algorithm for signature verification of
+///  CHALLENGE_AUTH and MEASUREMENTS messages.
+///  Selected by responder during NEGOTIATE_ALGORITHMS exchange.

lib/rspdm/state.rs line 64

Quoted 10 lines
lib/rspdm/state.rs +10 -0
+/// @meas_hash_alg: Hash algorithm for measurement blocks.
+///  Selected by responder during NEGOTIATE_ALGORITHMS exchange.
+/// @base_asym_enc: Human-readable name of @base_asym_alg's signature encoding.
+///  Passed to crypto subsystem when calling verify_signature().
+/// @sig_len: Signature length of @base_asym_alg (in bytes).
+///  S or SigLen in SPDM specification.
+/// @base_hash_alg_name: Human-readable name of @base_hash_alg.
+///  Passed to crypto subsystem when calling crypto_alloc_shash() and
+///  verify_signature().
+/// @base_hash_alg_name: Human-readable name of @base_hash_alg.

Duplicate

Quoted 33 lines
lib/rspdm/state.rs +19 -0
+///  Passed to crypto subsystem when calling crypto_alloc_shash() and
+///  verify_signature().
+/// @shash: Synchronous hash handle for @base_hash_alg computation.
+/// @desc: Synchronous hash context for @base_hash_alg computation.
+/// @hash_len: Hash length of @base_hash_alg (in bytes).
+///  H in SPDM specification.
 #[expect(dead_code)]
 pub struct SpdmState {
     pub(crate) dev: *mut bindings::device,
@@ -52,6 +80,19 @@ pub struct SpdmState {
     // Negotiated state
     pub(crate) version: u8,
     pub(crate) rsp_caps: u32,
+    pub(crate) base_asym_alg: u32,
+    pub(crate) base_hash_alg: u32,
+    pub(crate) meas_hash_alg: u32,
+
+    /* Signature algorithm */
+    base_asym_enc: &'static CStr,
+    sig_len: usize,
+
+    /* Hash algorithm */
+    base_hash_alg_name: &'static CStr,
+    pub(crate) shash: *mut bindings::crypto_shash,
+    pub(crate) desc: Option<&'static mut bindings::shash_desc>,
+    pub(crate) hash_len: usize,
 }
 
@@ -333,4 +383,160 @@ pub(crate) fn get_capabilities(&mut self) -> Result<(), Error> {
 
         Ok(())
     }
 

lib/rspdm/state.rs pub(crate) fn get_capabilities(&mut self) -> Result<(), Error> { · line 396

Quoted 10 lines
lib/rspdm/state.rs +10 -0
+
+    pub(crate) fn negotiate_algs(&mut self) -> Result<(), Error> {
+        let mut request = NegotiateAlgsReq::default();
+        request.version = self.version;
+
+        if self.version >= SPDM_VER_12 && (self.rsp_caps & SPDM_KEY_EX_CAP) == SPDM_KEY_EX_CAP {
+            request.other_params_support = SPDM_OPAQUE_DATA_FMT_GENERAL;
+        }
+
+        // TODO support more algs

Meh. Why?
:)

lib/rspdm/state.rs pub(crate) fn get_capabilities(&mut self) -> Result<(), Error> { · line 481

Quoted 4 lines
lib/rspdm/state.rs +4 -0
+        let reg_alg_entries = 0;
+
+        let req_sz = core::mem::size_of::<NegotiateAlgsReq>()
+            + core::mem::size_of::<RegAlg>() * reg_alg_entries;

If we are going to include the space for reg_alg_entries, then I'd like to also have ExtAsymCount and ExtHashCount.

Or just don't include any of them at this stage. Picking one thing we haven't implemented feels a little misleading.

Not that the c code was consistent on this as it handles responses with reg_alg_entries != 0 but not requests (a thing that can't ever happen!) I'll blame a younger more foolish engineer ;)

Quoted 6 lines
lib/rspdm/state.rs +6 -0
+        let rsp_sz = core::mem::size_of::<NegotiateAlgsRsp>()
+            + core::mem::size_of::<RegAlg>() * reg_alg_entries;
+
+        request.length = req_sz as u16;
+        request.param1 = reg_alg_entries as u8;
+

lib/rspdm/state.rs pub(crate) fn get_capabilities(&mut self) -> Result<(), Error> { · line 497

Quoted 10 lines
lib/rspdm/state.rs +10 -0
+        // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
+        let request_buf = unsafe { from_raw_parts_mut(&mut request as *mut _ as *mut u8, req_sz) };
+
+        let mut response_vec: KVec<u8> = KVec::with_capacity(rsp_sz, GFP_KERNEL)?;
+        // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
+        let response_buf = unsafe { from_raw_parts_mut(response_vec.as_mut_ptr(), rsp_sz) };
+
+        let rc = self.spdm_exchange(request_buf, response_buf)?;
+
+        if rc < (rsp_sz as i32) {

So this is fun. We need an exact match for now, but longer term response is absolutely allowed to be smaller if the requester has asked for things the responder doesn't support.

lib/rspdm/state.rs pub(crate) fn get_capabilities(&mut self) -> Result<(), Error> { · line 503

Quoted 6 lines
lib/rspdm/state.rs +6 -0
+            pr_err!("Truncated capabilities response\n");
+            to_result(-(bindings::EIO as i32))?;
+        }
+
+        // SAFETY: `rc` is the length of data read, which will be smaller
+        // then the capacity of the vector

I think it's exactly the length at this point. But it may be less in future.

Quoted 64 lines
lib/rspdm/state.rs +38 -0
+        unsafe { response_vec.inc_len(rc as usize) };
+
+        let response: &mut NegotiateAlgsRsp =
+            Untrusted::new_mut(&mut response_vec).validate_mut()?;
+
+        self.base_asym_alg = response.base_asym_sel;
+        self.base_hash_alg = response.base_hash_sel;
+        self.meas_hash_alg = response.measurement_hash_algo;
+
+        if self.base_asym_alg & SPDM_ASYM_ALGOS == 0 || self.base_hash_alg & SPDM_HASH_ALGOS == 0 {
+            pr_err!("No common supported algorithms\n");
+            to_result(-(bindings::EPROTO as i32))?;
+        }
+
+        // /* Responder shall select exactly 1 alg (SPDM 1.0.0 table 14) */
+        if self.base_asym_alg.count_ones() != 1
+            || self.base_hash_alg.count_ones() != 1
+            || response.ext_asym_sel_count != 0
+            || response.ext_hash_sel_count != 0
+            || response.param1 > request.param1
+            || response.other_params_sel != request.other_params_support
+        {
+            pr_err!("Malformed algorithms response\n");
+            to_result(-(bindings::EPROTO as i32))?;
+        }
+
+        if self.rsp_caps & SPDM_MEAS_CAP_MASK == SPDM_MEAS_CAP_MASK
+            && (self.meas_hash_alg.count_ones() != 1
+                || response.measurement_specification_sel != SPDM_MEAS_SPEC_DMTF)
+        {
+            pr_err!("Malformed algorithms response\n");
+            to_result(-(bindings::EPROTO as i32))?;
+        }
+
+        self.update_response_algs()?;
+
+        Ok(())
+    }
 }
lib/rspdm/validator.rs +14 -0
index cd792c46767a..036a077c71c3 100644
 
 #[repr(C, packed)]
 pub(crate) struct SpdmHeader {
@@ -205,3 +208,111 @@ fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err>
         Ok(rsp)
     }
 }
+
+#[repr(C, packed)]
+pub(crate) struct RegAlg {
+    pub(crate) alg_type: u8,
+    pub(crate) alg_count: u8,
+    pub(crate) alg_supported: u16,
+    pub(crate) alg_external: __IncompleteArrayField<__le32>,
+}
+
+#[repr(C, packed)]
+pub(crate) struct NegotiateAlgsReq {
+    pub(crate) version: u8,
+    pub(crate) code: u8,
+    pub(crate) param1: u8,

lib/rspdm/validator.rs fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> · line 234

Quoted 10 lines
lib/rspdm/validator.rs +10 -0
+    pub(crate) param2: u8,
+
+    pub(crate) length: u16,
+    pub(crate) measurement_specification: u8,
+    pub(crate) other_params_support: u8,
+
+    pub(crate) base_asym_algo: u32,
+    pub(crate) base_hash_algo: u32,
+
+    reserved1: [u8; 12],

Maybe it's worth always using arrays of u8 for reserved values just for consistency. In previous patch you had a u16 for example.

Quoted 7 lines
lib/rspdm/validator.rs +7 -0
+
+    pub(crate) ext_asym_count: u8,
+    pub(crate) ext_hash_count: u8,
+    reserved2: u8,
+    pub(crate) mel_specification: u8,
+
+    pub(crate) ext_asym: __IncompleteArrayField<__le32>,

lib/rspdm/validator.rs fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> · line 251

Quoted 10 lines
lib/rspdm/validator.rs +10 -0
+    pub(crate) ext_hash: __IncompleteArrayField<__le32>,
+    pub(crate) req_alg_struct: __IncompleteArrayField<RegAlg>,
+}
+
+impl Default for NegotiateAlgsReq {
+    fn default() -> Self {
+        NegotiateAlgsReq {
+            version: 0,
+            code: SPDM_NEGOTIATE_ALGS,
+            param1: 0,

I would call out the meaning of 0 here in some fashion. maybe just a comment to say it's the size of the req_alg_struct.

lib/rspdm/validator.rs fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> · line 253

Quoted 2 lines
lib/rspdm/validator.rs +2 -0
+            param2: 0,
+            length: 0,

Maybe should set length default to match what is configure for various counts? So 32 I think.

Quoted 11 lines
lib/rspdm/validator.rs +11 -0
+            measurement_specification: SPDM_MEAS_SPEC_DMTF,
+            other_params_support: 0,
+            base_asym_algo: SPDM_ASYM_ALGOS.to_le(),
+            base_hash_algo: SPDM_HASH_ALGOS.to_le(),
+            reserved1: [0u8; 12],
+            ext_asym_count: 0,
+            ext_hash_count: 0,
+            reserved2: 0,
+            mel_specification: 0,
+            ext_asym: __IncompleteArrayField::new(),
+            ext_hash: __IncompleteArrayField::new(),

lib/rspdm/validator.rs fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> · line 274

Quoted 10 lines
lib/rspdm/validator.rs +10 -0
+            req_alg_struct: __IncompleteArrayField::new(),
+        }
+    }
+}
+
+#[repr(C, packed)]
+pub(crate) struct NegotiateAlgsRsp {
+    pub(crate) version: u8,
+    pub(crate) code: u8,
+    pub(crate) param1: u8,

As above, perhaps a comment to indicate this has the size of resp_alg_Struct

lib/rspdm/validator.rs fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> · line 275

Quoted 1 line
lib/rspdm/validator.rs +1 -0
+    pub(crate) param2: u8,

Reserved so maybe drop the pub(create) as you've done for other response fields that are always reserved.

lib/rspdm/validator.rs fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> · line 287

Quoted 12 lines
lib/rspdm/validator.rs +12 -0
+
+    pub(crate) length: u16,
+    pub(crate) measurement_specification_sel: u8,
+    pub(crate) other_params_sel: u8,
+
+    pub(crate) measurement_hash_algo: u32,
+    pub(crate) base_asym_sel: u32,
+    pub(crate) base_hash_sel: u32,
+
+    reserved1: [u8; 11],
+
+    pub(crate) mel_specification_sel: u8,

Maybe add comments to indicate when fields were added? I don't think this structure changed length which makes it a bit easier to handle.

lib/rspdm/validator.rs fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> · line 294

Quoted 7 lines
lib/rspdm/validator.rs +7 -0
+    pub(crate) ext_asym_sel_count: u8,
+    pub(crate) ext_hash_sel_count: u8,
+    reserved2: [u8; 2],
+
+    pub(crate) ext_asym: __IncompleteArrayField<__le32>,
+    pub(crate) ext_hash: __IncompleteArrayField<__le32>,
+    pub(crate) req_alg_struct: __IncompleteArrayField<RegAlg>,

resp_alg_struct

Quoted 24 lines
lib/rspdm/validator.rs +24 -0
+}
+
+impl Validate<&mut Unvalidated<KVec<u8>>> for &mut NegotiateAlgsRsp {
+    type Err = Error;
+
+    fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> {
+        let raw = unvalidated.raw_mut();
+        if raw.len() < mem::size_of::<NegotiateAlgsRsp>() {
+            return Err(EINVAL);
+        }
+
+        let ptr = raw.as_mut_ptr();
+        // CAST: `NegotiateAlgsRsp` only contains integers and has `repr(C)`.
+        let ptr = ptr.cast::<NegotiateAlgsRsp>();
+        // SAFETY: `ptr` came from a reference and the cast above is valid.
+        let rsp: &mut NegotiateAlgsRsp = unsafe { &mut *ptr };
+
+        rsp.base_asym_sel = rsp.base_asym_sel.to_le();
+        rsp.base_hash_sel = rsp.base_hash_sel.to_le();
+        rsp.measurement_hash_algo = rsp.measurement_hash_algo.to_le();
+
+        Ok(rsp)
+    }
+}
#34 lore raw

RFC v3 15/27 lib: rspdm: Support SPDM get_digests

In reply to [email protected]

From: Alistair Francis <[email protected]>

Support the GET_DIGESTS SPDM command.

---
 lib/rspdm/consts.rs    |  4 ++
 lib/rspdm/lib.rs       |  4 ++
 lib/rspdm/state.rs     | 87 ++++++++++++++++++++++++++++++++++++++++--
 lib/rspdm/validator.rs | 52 ++++++++++++++++++++++++-
 4 files changed, 142 insertions(+), 5 deletions(-)
lib/rspdm/consts.rs +4 -0
index e8a05fd4299b..4d39ca2cb584 100644
@@ -16,6 +16,8 @@
 pub(crate) const SPDM_VER_12: u8 = 0x12;
 pub(crate) const SPDM_VER_13: u8 = 0x13;
 
+pub(crate) const SPDM_SLOTS: usize = 8;
+
 pub(crate) const SPDM_MIN_VER: u8 = SPDM_VER_10;
 pub(crate) const SPDM_MAX_VER: u8 = SPDM_VER_13;
 
@@ -170,6 +172,8 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
 pub(crate) const SPDM_HASH_SHA_384: u32 = 1 << 1;
 pub(crate) const SPDM_HASH_SHA_512: u32 = 1 << 2;
 
+pub(crate) const SPDM_GET_DIGESTS: u8 = 0x81;
+
 #[cfg(CONFIG_CRYPTO_RSA)]
 pub(crate) const SPDM_ASYM_RSA: u32 =
     SPDM_ASYM_RSASSA_2048 | SPDM_ASYM_RSASSA_3072 | SPDM_ASYM_RSASSA_4096;
lib/rspdm/lib.rs +4 -0
index 33f706a55e09..a7af86d1dc0b 100644
@@ -121,6 +121,10 @@
         return e.to_errno() as c_int;
     }
 
+    if let Err(e) = state.get_digests() {
+        return e.to_errno() as c_int;
+    }
+
     0
 }
 
lib/rspdm/state.rs +84 -3

135 lines not on this page · raw message

lib/rspdm/validator.rs +50 -2

61 lines not on this page · raw message

Signature · 1 line
2.52.0
#35 lore raw

Re: [RFC v3 15/27] lib: rspdm: Support SPDM get_digests

Quoted 5 lines · On Wed, 11 Feb 2026 13:29:22 +1000 [email protected] wrote:

From: Alistair Francis <[email protected]>

Support the GET_DIGESTS SPDM command.

A few things inline.

Thanks,

J

Quoted 19 lines
lib/rspdm/state.rs +4 -0
index d0b10f27cd9c..2606b825c494 100644
 
 
 /// The current SPDM session state for a device. Based on the
@@ -54,6 +54,10 @@
 ///  Selected by responder during NEGOTIATE_ALGORITHMS exchange.
 /// @meas_hash_alg: Hash algorithm for measurement blocks.
 ///  Selected by responder during NEGOTIATE_ALGORITHMS exchange.
+/// @supported_slots: Bitmask of responder's supported certificate slots.
+///  Received during GET_DIGESTS exchange (from SPDM 1.3).
+/// @provisioned_slots: Bitmask of responder's provisioned certificate slots.
+///  Received during GET_DIGESTS exchange.
 /// @base_asym_enc: Human-readable name of @base_asym_alg's signature encoding.
 ///  Passed to crypto subsystem when calling verify_signature().
 /// @sig_len: Signature length of @base_asym_alg (in bytes).

lib/rspdm/state.rs line 77

Quoted 6 lines
lib/rspdm/state.rs +3 -0
@@ -68,6 +72,9 @@
 /// @desc: Synchronous hash context for @base_hash_alg computation.
 /// @hash_len: Hash length of @base_hash_alg (in bytes).
 ///  H in SPDM specification.
+/// @slot: Certificate chain in each of the 8 slots.  NULL pointer if a slot is
+///  not populated.  Prefixed by the 4 + H header per SPDM 1.0.0 table 15.
+/// @slot_sz: Certificate chain size (in bytes).

That's not matching the code..

Quoted 44 lines
lib/rspdm/state.rs +24 -0
 #[expect(dead_code)]
 pub struct SpdmState {
     pub(crate) dev: *mut bindings::device,
@@ -83,6 +90,8 @@ pub struct SpdmState {
     pub(crate) base_asym_alg: u32,
     pub(crate) base_hash_alg: u32,
     pub(crate) meas_hash_alg: u32,
+    pub(crate) supported_slots: u8,
+    pub(crate) provisioned_slots: u8,
 
     /* Signature algorithm */
     base_asym_enc: &'static CStr,
@@ -93,6 +102,9 @@ pub struct SpdmState {
     pub(crate) shash: *mut bindings::crypto_shash,
     pub(crate) desc: Option<&'static mut bindings::shash_desc>,
     pub(crate) hash_len: usize,
+
+    // Certificates
+    pub(crate) certs: [KVec<u8>; SPDM_SLOTS],
 }
 
@@ -539,4 +554,70 @@ pub(crate) fn negotiate_algs(&mut self) -> Result<(), Error> {
 
         Ok(())
     }
+
+    pub(crate) fn get_digests(&mut self) -> Result<(), Error> {
+        let mut request = GetDigestsReq::default();
+        request.version = self.version;
+
+        let req_sz = core::mem::size_of::<GetDigestsReq>();
+        let rsp_sz = core::mem::size_of::<GetDigestsRsp>() + SPDM_SLOTS * self.hash_len;
+
+        // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
+        let request_buf = unsafe { from_raw_parts_mut(&mut request as *mut _ as *mut u8, req_sz) };
+
+        let mut response_vec: KVec<u8> = KVec::with_capacity(rsp_sz, GFP_KERNEL)?;
+        // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
+        let response_buf = unsafe { from_raw_parts_mut(response_vec.as_mut_ptr(), rsp_sz) };
+
+        let rc = self.spdm_exchange(request_buf, response_buf)?;
+
+        if rc < (core::mem::size_of::<GetDigestsRsp>() as i32) {
+            pr_err!("Truncated digests response\n");

lib/rspdm/state.rs pub(crate) fn negotiate_algs(&mut self) -> Result<(), Error> { · line 585

Quoted 10 lines
lib/rspdm/state.rs +10 -0
+            to_result(-(bindings::EIO as i32))?;
+        }
+
+        // SAFETY: `rc` is the length of data read, which will be smaller
+        // then the capacity of the vector
+        unsafe { response_vec.inc_len(rc as usize) };
+
+        let response: &mut GetDigestsRsp = Untrusted::new_mut(&mut response_vec).validate_mut()?;
+
+        if rc

Perhaps another local variable to represent what this is better than the rc name does?

Quoted 11 lines
lib/rspdm/state.rs +11 -0
+            < (core::mem::size_of::<GetDigestsReq>()
+                + response.param2.count_ones() as usize * self.hash_len) as i32
+        {
+            pr_err!("Truncated digests response\n");
+            to_result(-(bindings::EIO as i32))?;
+        }
+
+        let mut deprovisioned_slots = self.provisioned_slots & !response.param2;
+        while (deprovisioned_slots.trailing_zeros() as usize) < SPDM_SLOTS {
+            let slot = deprovisioned_slots.trailing_zeros() as usize;
+            self.certs[slot].clear();

lib/rspdm/state.rs pub(crate) fn negotiate_algs(&mut self) -> Result<(), Error> { · line 606

Quoted 10 lines
lib/rspdm/state.rs +10 -0
+            deprovisioned_slots &= !(1 << slot);
+        }
+
+        self.provisioned_slots = response.param2;
+        if self.provisioned_slots == 0 {
+            pr_err!("No certificates provisioned\n");
+            to_result(-(bindings::EPROTO as i32))?;
+        }
+
+        if self.version >= 0x13 && (response.param2 & !response.param1 != 0) {

Should we use the define for the version?

lib/rspdm/state.rs pub(crate) fn negotiate_algs(&mut self) -> Result<(), Error> { · line 618

Quoted 12 lines
lib/rspdm/state.rs +12 -0
+            pr_err!("Malformed digests response\n");
+            to_result(-(bindings::EPROTO as i32))?;
+        }
+
+        let supported_slots = if self.version >= 0x13 {
+            response.param1
+        } else {
+            0xFF
+        };
+
+        if self.supported_slots != supported_slots {
+            self.supported_slots = supported_slots;

Why not set it unconditionally? Is this expected to have side effects?

Quoted 9 lines
lib/rspdm/state.rs +4 -0
+        }
+
+        Ok(())
+    }
 }
lib/rspdm/validator.rs +0 -0
index 036a077c71c3..2150a23997db 100644

lib/rspdm/validator.rs fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> · line 346

Quoted 10 lines
lib/rspdm/validator.rs +9 -0
 
+
+#[repr(C, packed)]
+pub(crate) struct GetDigestsRsp {
+    pub(crate) version: u8,
+    pub(crate) code: u8,
+    pub(crate) param1: u8,
+    pub(crate) param2: u8,
+
+    pub(crate) digests: __IncompleteArrayField<u8>,

Maybe include KeyPairIDs, certificatinfo, and KeyUsageMask in this structure definition (1.3.1 has them)
Or at very least a comment to say they are a job for another day.

None of those exist yet as we didn't ask for MULTI_KEY_CAP.

Quoted 20 lines
lib/rspdm/validator.rs +20 -0
+}
+
+impl Validate<&mut Unvalidated<KVec<u8>>> for &mut GetDigestsRsp {
+    type Err = Error;
+
+    fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> {
+        let raw = unvalidated.raw_mut();
+        if raw.len() < mem::size_of::<GetDigestsRsp>() {
+            return Err(EINVAL);
+        }
+
+        let ptr = raw.as_mut_ptr();
+        // CAST: `GetDigestsRsp` only contains integers and has `repr(C)`.
+        let ptr = ptr.cast::<GetDigestsRsp>();
+        // SAFETY: `ptr` came from a reference and the cast above is valid.
+        let rsp: &mut GetDigestsRsp = unsafe { &mut *ptr };
+
+        Ok(rsp)
+    }
+}
#36 lore raw

RFC v3 16/27 lib: rspdm: Support SPDM get_certificate

In reply to [email protected]

From: Alistair Francis <[email protected]>

Support the GET_CERTIFICATE SPDM command.

---
 lib/rspdm/consts.rs    |   2 +
 lib/rspdm/lib.rs       |  11 ++++
 lib/rspdm/state.rs     | 127 ++++++++++++++++++++++++++++++++++++++++-
 lib/rspdm/validator.rs |  64 ++++++++++++++++++++-
 4 files changed, 200 insertions(+), 4 deletions(-)
lib/rspdm/consts.rs +2 -0
index 4d39ca2cb584..eaf2132da290 100644
@@ -174,6 +174,8 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
 
 pub(crate) const SPDM_GET_DIGESTS: u8 = 0x81;
 
+pub(crate) const SPDM_GET_CERTIFICATE: u8 = 0x82;
+
 #[cfg(CONFIG_CRYPTO_RSA)]
 pub(crate) const SPDM_ASYM_RSA: u32 =
     SPDM_ASYM_RSASSA_2048 | SPDM_ASYM_RSASSA_3072 | SPDM_ASYM_RSASSA_4096;
lib/rspdm/lib.rs +11 -0
index a7af86d1dc0b..b065b3f70356 100644
@@ -125,6 +125,17 @@
         return e.to_errno() as c_int;
     }
 
+    let mut provisioned_slots = state.provisioned_slots;
+    while (provisioned_slots as usize) > 0 {
+        let slot = provisioned_slots.trailing_zeros() as u8;
+
+        if let Err(e) = state.get_certificate(slot) {
+            return e.to_errno() as c_int;
+        }
+
+        provisioned_slots &= !(1 << slot);
+    }
+
     0
 }
 
lib/rspdm/state.rs +125 -2

143 lines not on this page · raw message

lib/rspdm/validator.rs +62 -2

73 lines not on this page · raw message

Signature · 1 line
2.52.0
#37 lore raw

Re: [RFC v3 16/27] lib: rspdm: Support SPDM get_certificate

Quoted 5 lines · On Wed, 11 Feb 2026 13:29:23 +1000 [email protected] wrote:

From: Alistair Francis <[email protected]>

Support the GET_CERTIFICATE SPDM command.

Minor things inline. The endian handling in general needs some care + possibly some tests.

Quoted 30 lines
lib/rspdm/state.rs +15 -0
index 2606b825c494..1e5656144611 100644
@@ -26,8 +26,9 @@
     SPDM_REQ, SPDM_RSP_MIN_CAPS, SPDM_SLOTS, SPDM_VER_10, SPDM_VER_11, SPDM_VER_12,
 };
 
 impl SpdmState {
     pub(crate) fn new(
         dev: *mut bindings::device,
@@ -620,4 +629,118 @@ pub(crate) fn get_digests(&mut self) -> Result<(), Error> {
 
         Ok(())
     }
+
+    fn get_cert_exchange(
+        &mut self,
+        request_buf: &mut [u8],
+        response_vec: &mut KVec<u8>,
+        rsp_sz: usize,
+    ) -> Result<&mut GetCertificateRsp, Error> {
+        // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
+        let response_buf = unsafe { from_raw_parts_mut(response_vec.as_mut_ptr(), rsp_sz) };
+
+        let rc = self.spdm_exchange(request_buf, response_buf)?;
+
+        if rc < (core::mem::size_of::<GetCertificateReq>() as i32) {
+            pr_err!("Truncated certificate response\n");
+            to_result(-(bindings::EIO as i32))?;

lib/rspdm/state.rs pub(crate) fn get_digests(&mut self) -> Result<(), Error> { · line 656

Quoted 10 lines
lib/rspdm/state.rs +10 -0
+        }
+
+        // SAFETY: `rc` is the length of data read, which will be smaller
+        // then the capacity of the vector
+        unsafe { response_vec.inc_len(rc as usize) };
+
+        let response: &mut GetCertificateRsp = Untrusted::new_mut(response_vec).validate_mut()?;
+
+        if rc
+            < (core::mem::size_of::<GetCertificateRsp>() + response.portion_length as usize) as i32

As below, I'd keep the type matching the spec and have the little endian to cpu conversion out here.

Quoted 5 lines
lib/rspdm/state.rs +5 -0
+        {
+            pr_err!("Truncated certificate response\n");
+            to_result(-(bindings::EIO as i32))?;
+        }
+

lib/rspdm/state.rs pub(crate) fn get_digests(&mut self) -> Result<(), Error> { · line 671

Quoted 10 lines
lib/rspdm/state.rs +10 -0
+        Ok(response)
+    }
+
+    pub(crate) fn get_certificate(&mut self, slot: u8) -> Result<(), Error> {
+        let mut request = GetCertificateReq::default();
+        request.version = self.version;
+        request.param1 = slot;
+
+        let req_sz = core::mem::size_of::<GetCertificateReq>();
+        let rsp_sz = ((core::mem::size_of::<GetCertificateRsp>() + 0xffff) as u32)

Similar to earlier comment, do we have U16_MAX or similar available?

lib/rspdm/state.rs pub(crate) fn get_digests(&mut self) -> Result<(), Error> { · line 674

Quoted 3 lines
lib/rspdm/state.rs +3 -0
+            .min(self.transport_sz) as usize;
+
+        request.offset = 0;

That's the default, so worth setting here?

lib/rspdm/state.rs pub(crate) fn get_digests(&mut self) -> Result<(), Error> { · line 675

Quoted 1 line
lib/rspdm/state.rs +1 -0
+        request.length = (rsp_sz - core::mem::size_of::<GetCertificateRsp>()).to_le() as u16;

Why store it in a u16 if it is le16?

Quoted 13 lines
lib/rspdm/state.rs +13 -0
+
+        // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
+        let request_buf = unsafe { from_raw_parts_mut(&mut request as *mut _ as *mut u8, req_sz) };
+
+        let mut response_vec: KVec<u8> = KVec::with_capacity(rsp_sz, GFP_KERNEL)?;
+
+        let response = self.get_cert_exchange(request_buf, &mut response_vec, rsp_sz)?;
+
+        let total_cert_len =
+            ((response.portion_length + response.remainder_length) & 0xFFFF) as usize;
+
+        let mut certs_buf: KVec<u8> = KVec::new();
+

lib/rspdm/state.rs pub(crate) fn get_digests(&mut self) -> Result<(), Error> { · line 698

Quoted 10 lines
lib/rspdm/state.rs +10 -0
+        certs_buf.extend_from_slice(
+            &response_vec[8..(8 + response.portion_length as usize)],
+            GFP_KERNEL,
+        )?;
+
+        let mut offset: usize = response.portion_length as usize;
+        let mut remainder_length = response.remainder_length as usize;
+
+        while remainder_length > 0 {
+            request.offset = offset.to_le() as u16;

Similar to other places, why not just make the type __le16 and avoid need to cast.

lib/rspdm/state.rs pub(crate) fn get_digests(&mut self) -> Result<(), Error> { · line 701

Quoted 3 lines
lib/rspdm/state.rs +3 -0
+            request.length = (remainder_length
+                .min(rsp_sz - core::mem::size_of::<GetCertificateRsp>()))
+            .to_le() as u16;

Likewise.

Quoted 23 lines
lib/rspdm/state.rs +23 -0
+
+            let request_buf =
+                unsafe { from_raw_parts_mut(&mut request as *mut _ as *mut u8, req_sz) };
+
+            let response = self.get_cert_exchange(request_buf, &mut response_vec, rsp_sz)?;
+
+            if response.portion_length == 0
+                || (response.param1 & 0xF) != slot
+                || offset as u16 + response.portion_length + response.remainder_length
+                    != total_cert_len as u16
+            {
+                pr_err!("Malformed certificate response\n");
+                to_result(-(bindings::EPROTO as i32))?;
+            }
+
+            certs_buf.extend_from_slice(
+                &response_vec[8..(8 + response.portion_length as usize)],
+                GFP_KERNEL,
+            )?;
+            offset += response.portion_length as usize;
+            remainder_length = response.remainder_length as usize;
+        }
+

lib/rspdm/state.rs pub(crate) fn get_digests(&mut self) -> Result<(), Error> { · line 734

Quoted 10 lines
lib/rspdm/state.rs +10 -0
+        let header_length = core::mem::size_of::<SpdmCertChain>() + self.hash_len;
+
+        let ptr = certs_buf.as_mut_ptr();
+        // SAFETY: `SpdmCertChain` is repr(C) and packed, so we can convert it from a slice
+        let ptr = ptr.cast::<SpdmCertChain>();
+        // SAFETY: `ptr` came from a reference and the cast above is valid.
+        let certs: &mut SpdmCertChain = unsafe { &mut *ptr };
+
+        if total_cert_len < header_length
+            || total_cert_len != usize::from_le(certs.length as usize)

That's a confusing bit of casting as you are interpretting an __le16 as a usize before doing the endian conversion? Seems unlikely to get what you want on a big endian machine.

Quoted 45 lines
lib/rspdm/state.rs +11 -0
+            || total_cert_len != certs_buf.len()
+        {
+            pr_err!("Malformed certificate chain in slot {slot}\n");
+            to_result(-(bindings::EPROTO as i32))?;
+        }
+
+        self.certs[slot as usize].clear();
+        self.certs[slot as usize].extend_from_slice(&certs_buf, GFP_KERNEL)?;
+
+        Ok(())
+    }
 }
lib/rspdm/validator.rs +20 -0
index 2150a23997db..a8bc3378676f 100644
@@ -17,8 +17,9 @@
 };
 
 #[repr(C, packed)]
@@ -364,3 +365,62 @@ fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err>
         Ok(rsp)
     }
 }
 
+#[repr(C, packed)]
+pub(crate) struct GetCertificateRsp {
+    pub(crate) version: u8,
+    pub(crate) code: u8,
+    pub(crate) param1: u8,
+    pub(crate) param2: u8,
+
+    pub(crate) portion_length: u16,
+    pub(crate) remainder_length: u16,
+
+    pub(crate) cert_chain: __IncompleteArrayField<u8>,
+}
+
+impl Validate<&mut Unvalidated<KVec<u8>>> for &mut GetCertificateRsp {
+    type Err = Error;
+
+    fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> {
+        let raw = unvalidated.raw_mut();
+        if raw.len() < mem::size_of::<GetCertificateRsp>() {
+            return Err(EINVAL);

lib/rspdm/validator.rs fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> · line 422

Quoted 10 lines
lib/rspdm/validator.rs +10 -0
+        }
+
+        let ptr = raw.as_mut_ptr();
+        // CAST: `GetCertificateRsp` only contains integers and has `repr(C)`.
+        let ptr = ptr.cast::<GetCertificateRsp>();
+        // SAFETY: `ptr` came from a reference and the cast above is valid.
+        let rsp: &mut GetCertificateRsp = unsafe { &mut *ptr };
+
+        rsp.portion_length = rsp.portion_length.to_le();
+        rsp.remainder_length = rsp.remainder_length.to_le();

Why to_le()? I can understand from_le() but then I'm a bit dubious about the types. My gut feeling is that the validate code should leave these in little endian and we should convert them only at time of use.

Quoted 4 lines
lib/rspdm/validator.rs +4 -0
+
+        Ok(rsp)
+    }
+}
#38 lore raw

Re: [RFC v3 16/27] lib: rspdm: Support SPDM get_certificate

Quoted 75 lines · Jonathan Cameron
Quoted 5 lines · On Wed, 11 Feb 2026 13:29:23 +1000 [email protected] wrote:

From: Alistair Francis <[email protected]>

Support the GET_CERTIFICATE SPDM command.

Minor things inline. The endian handling in general needs some care + possibly some tests.

Quoted 30 lines
lib/rspdm/state.rs +15 -0
index 2606b825c494..1e5656144611 100644
@@ -26,8 +26,9 @@
     SPDM_REQ, SPDM_RSP_MIN_CAPS, SPDM_SLOTS, SPDM_VER_10, SPDM_VER_11, SPDM_VER_12,
 };
 
 impl SpdmState {
     pub(crate) fn new(
         dev: *mut bindings::device,
@@ -620,4 +629,118 @@ pub(crate) fn get_digests(&mut self) -> Result<(), Error> {
 
         Ok(())
     }
+
+    fn get_cert_exchange(
+        &mut self,
+        request_buf: &mut [u8],
+        response_vec: &mut KVec<u8>,
+        rsp_sz: usize,
+    ) -> Result<&mut GetCertificateRsp, Error> {
+        // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
+        let response_buf = unsafe { from_raw_parts_mut(response_vec.as_mut_ptr(), rsp_sz) };
+
+        let rc = self.spdm_exchange(request_buf, response_buf)?;
+
+        if rc < (core::mem::size_of::<GetCertificateReq>() as i32) {
+            pr_err!("Truncated certificate response\n");
+            to_result(-(bindings::EIO as i32))?;

lib/rspdm/state.rs pub(crate) fn get_digests(&mut self) -> Result<(), Error> { · line 656

Quoted 10 lines
lib/rspdm/state.rs +10 -0
+        }
+
+        // SAFETY: `rc` is the length of data read, which will be smaller
+        // then the capacity of the vector
+        unsafe { response_vec.inc_len(rc as usize) };
+
+        let response: &mut GetCertificateRsp = Untrusted::new_mut(response_vec).validate_mut()?;
+
+        if rc
+            < (core::mem::size_of::<GetCertificateRsp>() + response.portion_length as usize) as i32

As below, I'd keep the type matching the spec and have the little endian to cpu conversion out here.

Quoted 5 lines
lib/rspdm/state.rs +5 -0
+        {
+            pr_err!("Truncated certificate response\n");
+            to_result(-(bindings::EIO as i32))?;
+        }
+

lib/rspdm/state.rs pub(crate) fn get_digests(&mut self) -> Result<(), Error> { · line 671

Quoted 10 lines
lib/rspdm/state.rs +10 -0
+        Ok(response)
+    }
+
+    pub(crate) fn get_certificate(&mut self, slot: u8) -> Result<(), Error> {
+        let mut request = GetCertificateReq::default();
+        request.version = self.version;
+        request.param1 = slot;
+
+        let req_sz = core::mem::size_of::<GetCertificateReq>();
+        let rsp_sz = ((core::mem::size_of::<GetCertificateRsp>() + 0xffff) as u32)

Similar to earlier comment, do we have U16_MAX or similar available?

Quoted 5 lines

lib/rspdm/state.rs pub(crate) fn get_digests(&mut self) -> Result<(), Error> { · line 674

Quoted 3 lines
lib/rspdm/state.rs +3 -0
+            .min(self.transport_sz) as usize;
+
+        request.offset = 0;

That's the default, so worth setting here?

I like being explicit :)

Quoted 4 lines

lib/rspdm/state.rs pub(crate) fn get_digests(&mut self) -> Result<(), Error> { · line 675

Quoted 2 lines
lib/rspdm/state.rs +1 -0
+        request.length = (rsp_sz - core::mem::size_of::<GetCertificateRsp>()).to_le() as u16;

Why store it in a u16 if it is le16?

core::mem::size_of gives us a usize. So this ensures it's little endian then casts it to a u16.

We could cast it to a __le16. u16 is a standard Rust type (compared to __le16 which is a internal kernel type), so I prefer u16 as it matches other Rust implementations.

Quoted 19 lines
Quoted 19 lines
lib/rspdm/state.rs +18 -0
+
+        // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
+        let request_buf = unsafe { from_raw_parts_mut(&mut request as *mut _ as *mut u8, req_sz) };
+
+        let mut response_vec: KVec<u8> = KVec::with_capacity(rsp_sz, GFP_KERNEL)?;
+
+        let response = self.get_cert_exchange(request_buf, &mut response_vec, rsp_sz)?;
+
+        let total_cert_len =
+            ((response.portion_length + response.remainder_length) & 0xFFFF) as usize;
+
+        let mut certs_buf: KVec<u8> = KVec::new();
+
+        certs_buf.extend_from_slice(
+            &response_vec[8..(8 + response.portion_length as usize)],
+            GFP_KERNEL,
+        )?;
+
Quoted 8 lines

lib/rspdm/state.rs pub(crate) fn get_digests(&mut self) -> Result<(), Error> { · line 698

Quoted 5 lines
lib/rspdm/state.rs +5 -0
+        let mut offset: usize = response.portion_length as usize;
+        let mut remainder_length = response.remainder_length as usize;
+
+        while remainder_length > 0 {
+            request.offset = offset.to_le() as u16;

Similar to other places, why not just make the type __le16 and avoid need to cast.

Same as above

Quoted 6 lines

lib/rspdm/state.rs pub(crate) fn get_digests(&mut self) -> Result<(), Error> { · line 701

Quoted 4 lines
lib/rspdm/state.rs +3 -0
+            request.length = (remainder_length
+                .min(rsp_sz - core::mem::size_of::<GetCertificateRsp>()))
+            .to_le() as u16;

Likewise.

and above

Quoted 30 lines
Quoted 30 lines
lib/rspdm/state.rs +29 -0
+
+            let request_buf =
+                unsafe { from_raw_parts_mut(&mut request as *mut _ as *mut u8, req_sz) };
+
+            let response = self.get_cert_exchange(request_buf, &mut response_vec, rsp_sz)?;
+
+            if response.portion_length == 0
+                || (response.param1 & 0xF) != slot
+                || offset as u16 + response.portion_length + response.remainder_length
+                    != total_cert_len as u16
+            {
+                pr_err!("Malformed certificate response\n");
+                to_result(-(bindings::EPROTO as i32))?;
+            }
+
+            certs_buf.extend_from_slice(
+                &response_vec[8..(8 + response.portion_length as usize)],
+                GFP_KERNEL,
+            )?;
+            offset += response.portion_length as usize;
+            remainder_length = response.remainder_length as usize;
+        }
+
+        let header_length = core::mem::size_of::<SpdmCertChain>() + self.hash_len;
+
+        let ptr = certs_buf.as_mut_ptr();
+        // SAFETY: `SpdmCertChain` is repr(C) and packed, so we can convert it from a slice
+        let ptr = ptr.cast::<SpdmCertChain>();
+        // SAFETY: `ptr` came from a reference and the cast above is valid.
Quoted 8 lines

lib/rspdm/state.rs pub(crate) fn get_digests(&mut self) -> Result<(), Error> { · line 734

Quoted 4 lines
lib/rspdm/state.rs +4 -0
+        let certs: &mut SpdmCertChain = unsafe { &mut *ptr };
+
+        if total_cert_len < header_length
+            || total_cert_len != usize::from_le(certs.length as usize)

That's a confusing bit of casting as you are interpretting an __le16 as a usize before doing the endian conversion? Seems unlikely to get what you want on a big endian machine.

Yeah, I think this is wrong

Quoted 52 lines
Quoted 52 lines
lib/rspdm/state.rs +11 -0
+            || total_cert_len != certs_buf.len()
+        {
+            pr_err!("Malformed certificate chain in slot {slot}\n");
+            to_result(-(bindings::EPROTO as i32))?;
+        }
+
+        self.certs[slot as usize].clear();
+        self.certs[slot as usize].extend_from_slice(&certs_buf, GFP_KERNEL)?;
+
+        Ok(())
+    }
 }
lib/rspdm/validator.rs +26 -0
index 2150a23997db..a8bc3378676f 100644
@@ -17,8 +17,9 @@
 };
 
 #[repr(C, packed)]
@@ -364,3 +365,62 @@ fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err>
         Ok(rsp)
     }
 }
 
+#[repr(C, packed)]
+pub(crate) struct GetCertificateRsp {
+    pub(crate) version: u8,
+    pub(crate) code: u8,
+    pub(crate) param1: u8,
+    pub(crate) param2: u8,
+
+    pub(crate) portion_length: u16,
+    pub(crate) remainder_length: u16,
+
+    pub(crate) cert_chain: __IncompleteArrayField<u8>,
+}
+
+impl Validate<&mut Unvalidated<KVec<u8>>> for &mut GetCertificateRsp {
+    type Err = Error;
+
+    fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> {
+        let raw = unvalidated.raw_mut();
+        if raw.len() < mem::size_of::<GetCertificateRsp>() {
+            return Err(EINVAL);
+        }
+
+        let ptr = raw.as_mut_ptr();
+        // CAST: `GetCertificateRsp` only contains integers and has `repr(C)`.
+        let ptr = ptr.cast::<GetCertificateRsp>();
+        // SAFETY: `ptr` came from a reference and the cast above is valid.
Quoted 8 lines

lib/rspdm/validator.rs fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> · line 422

Quoted 4 lines
lib/rspdm/validator.rs +4 -0
+        let rsp: &mut GetCertificateRsp = unsafe { &mut *ptr };
+
+        rsp.portion_length = rsp.portion_length.to_le();
+        rsp.remainder_length = rsp.remainder_length.to_le();

Why to_le()? I can understand from_le() but then I'm a bit dubious about the types. My gut feeling is that the validate code should leave these in little endian and we should convert them only at time of use.

Fixed

Alistair

Quoted 6 lines
Quoted 5 lines
lib/rspdm/validator.rs +4 -0
+
+        Ok(rsp)
+    }
+}
reviewer
#39 lore raw

Re: [RFC v3 16/27] lib: rspdm: Support SPDM get_certificate

Quoted 86 lines · Alistair Francis
Quoted 75 lines · Jonathan Cameron
Quoted 5 lines · On Wed, 11 Feb 2026 13:29:23 +1000 [email protected] wrote:

From: Alistair Francis <[email protected]>

Support the GET_CERTIFICATE SPDM command.

Minor things inline. The endian handling in general needs some care + possibly some tests.

Quoted 30 lines
lib/rspdm/state.rs +15 -0
index 2606b825c494..1e5656144611 100644
@@ -26,8 +26,9 @@
     SPDM_REQ, SPDM_RSP_MIN_CAPS, SPDM_SLOTS, SPDM_VER_10, SPDM_VER_11, SPDM_VER_12,
 };
 
 impl SpdmState {
     pub(crate) fn new(
         dev: *mut bindings::device,
@@ -620,4 +629,118 @@ pub(crate) fn get_digests(&mut self) -> Result<(), Error> {
 
         Ok(())
     }
+
+    fn get_cert_exchange(
+        &mut self,
+        request_buf: &mut [u8],
+        response_vec: &mut KVec<u8>,
+        rsp_sz: usize,
+    ) -> Result<&mut GetCertificateRsp, Error> {
+        // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
+        let response_buf = unsafe { from_raw_parts_mut(response_vec.as_mut_ptr(), rsp_sz) };
+
+        let rc = self.spdm_exchange(request_buf, response_buf)?;
+
+        if rc < (core::mem::size_of::<GetCertificateReq>() as i32) {
+            pr_err!("Truncated certificate response\n");
+            to_result(-(bindings::EIO as i32))?;

lib/rspdm/state.rs pub(crate) fn get_digests(&mut self) -> Result<(), Error> { · line 656

Quoted 10 lines
lib/rspdm/state.rs +10 -0
+        }
+
+        // SAFETY: `rc` is the length of data read, which will be smaller
+        // then the capacity of the vector
+        unsafe { response_vec.inc_len(rc as usize) };
+
+        let response: &mut GetCertificateRsp = Untrusted::new_mut(response_vec).validate_mut()?;
+
+        if rc
+            < (core::mem::size_of::<GetCertificateRsp>() + response.portion_length as usize) as i32

As below, I'd keep the type matching the spec and have the little endian to cpu conversion out here.

Quoted 5 lines
lib/rspdm/state.rs +5 -0
+        {
+            pr_err!("Truncated certificate response\n");
+            to_result(-(bindings::EIO as i32))?;
+        }
+

lib/rspdm/state.rs pub(crate) fn get_digests(&mut self) -> Result<(), Error> { · line 671

Quoted 10 lines
lib/rspdm/state.rs +10 -0
+        Ok(response)
+    }
+
+    pub(crate) fn get_certificate(&mut self, slot: u8) -> Result<(), Error> {
+        let mut request = GetCertificateReq::default();
+        request.version = self.version;
+        request.param1 = slot;
+
+        let req_sz = core::mem::size_of::<GetCertificateReq>();
+        let rsp_sz = ((core::mem::size_of::<GetCertificateRsp>() + 0xffff) as u32)

Similar to earlier comment, do we have U16_MAX or similar available?

Quoted 5 lines

lib/rspdm/state.rs pub(crate) fn get_digests(&mut self) -> Result<(), Error> { · line 674

Quoted 3 lines
lib/rspdm/state.rs +3 -0
+            .min(self.transport_sz) as usize;
+
+        request.offset = 0;

That's the default, so worth setting here?

I like being explicit :)

Quoted 1 line
Quoted 6 lines
Quoted 3 lines

lib/rspdm/state.rs pub(crate) fn get_digests(&mut self) -> Result<(), Error> { · line 675

Quoted 1 line
lib/rspdm/state.rs +1 -0
+        request.length = (rsp_sz - core::mem::size_of::<GetCertificateRsp>()).to_le() as u16;

Why store it in a u16 if it is le16?

core::mem::size_of gives us a usize. So this ensures it's little endian then casts it to a u16.

This is broken code then.

In BE architectures, .to_le() flips the bytes around, and you do the truncation *after* the byteswap, which would give you 0.

You'd need ((...) as u16).to_le()

Best,
Gary

Quoted 156 lines

We could cast it to a __le16. u16 is a standard Rust type (compared to __le16 which is a internal kernel type), so I prefer u16 as it matches other Rust implementations.

Quoted 19 lines
Quoted 19 lines
lib/rspdm/state.rs +18 -0
+
+        // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
+        let request_buf = unsafe { from_raw_parts_mut(&mut request as *mut _ as *mut u8, req_sz) };
+
+        let mut response_vec: KVec<u8> = KVec::with_capacity(rsp_sz, GFP_KERNEL)?;
+
+        let response = self.get_cert_exchange(request_buf, &mut response_vec, rsp_sz)?;
+
+        let total_cert_len =
+            ((response.portion_length + response.remainder_length) & 0xFFFF) as usize;
+
+        let mut certs_buf: KVec<u8> = KVec::new();
+
+        certs_buf.extend_from_slice(
+            &response_vec[8..(8 + response.portion_length as usize)],
+            GFP_KERNEL,
+        )?;
+
Quoted 8 lines

lib/rspdm/state.rs pub(crate) fn get_digests(&mut self) -> Result<(), Error> { · line 698

Quoted 5 lines
lib/rspdm/state.rs +5 -0
+        let mut offset: usize = response.portion_length as usize;
+        let mut remainder_length = response.remainder_length as usize;
+
+        while remainder_length > 0 {
+            request.offset = offset.to_le() as u16;

Similar to other places, why not just make the type __le16 and avoid need to cast.

Same as above

Quoted 6 lines

lib/rspdm/state.rs pub(crate) fn get_digests(&mut self) -> Result<(), Error> { · line 701

Quoted 4 lines
lib/rspdm/state.rs +3 -0
+            request.length = (remainder_length
+                .min(rsp_sz - core::mem::size_of::<GetCertificateRsp>()))
+            .to_le() as u16;

Likewise.

and above

Quoted 30 lines
Quoted 30 lines
lib/rspdm/state.rs +29 -0
+
+            let request_buf =
+                unsafe { from_raw_parts_mut(&mut request as *mut _ as *mut u8, req_sz) };
+
+            let response = self.get_cert_exchange(request_buf, &mut response_vec, rsp_sz)?;
+
+            if response.portion_length == 0
+                || (response.param1 & 0xF) != slot
+                || offset as u16 + response.portion_length + response.remainder_length
+                    != total_cert_len as u16
+            {
+                pr_err!("Malformed certificate response\n");
+                to_result(-(bindings::EPROTO as i32))?;
+            }
+
+            certs_buf.extend_from_slice(
+                &response_vec[8..(8 + response.portion_length as usize)],
+                GFP_KERNEL,
+            )?;
+            offset += response.portion_length as usize;
+            remainder_length = response.remainder_length as usize;
+        }
+
+        let header_length = core::mem::size_of::<SpdmCertChain>() + self.hash_len;
+
+        let ptr = certs_buf.as_mut_ptr();
+        // SAFETY: `SpdmCertChain` is repr(C) and packed, so we can convert it from a slice
+        let ptr = ptr.cast::<SpdmCertChain>();
+        // SAFETY: `ptr` came from a reference and the cast above is valid.
Quoted 8 lines

lib/rspdm/state.rs pub(crate) fn get_digests(&mut self) -> Result<(), Error> { · line 734

Quoted 4 lines
lib/rspdm/state.rs +4 -0
+        let certs: &mut SpdmCertChain = unsafe { &mut *ptr };
+
+        if total_cert_len < header_length
+            || total_cert_len != usize::from_le(certs.length as usize)

That's a confusing bit of casting as you are interpretting an __le16 as a usize before doing the endian conversion? Seems unlikely to get what you want on a big endian machine.

Yeah, I think this is wrong

Quoted 52 lines
Quoted 52 lines
lib/rspdm/state.rs +11 -0
+            || total_cert_len != certs_buf.len()
+        {
+            pr_err!("Malformed certificate chain in slot {slot}\n");
+            to_result(-(bindings::EPROTO as i32))?;
+        }
+
+        self.certs[slot as usize].clear();
+        self.certs[slot as usize].extend_from_slice(&certs_buf, GFP_KERNEL)?;
+
+        Ok(())
+    }
 }
lib/rspdm/validator.rs +26 -0
index 2150a23997db..a8bc3378676f 100644
@@ -17,8 +17,9 @@
 };
 
 #[repr(C, packed)]
@@ -364,3 +365,62 @@ fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err>
         Ok(rsp)
     }
 }
 
+#[repr(C, packed)]
+pub(crate) struct GetCertificateRsp {
+    pub(crate) version: u8,
+    pub(crate) code: u8,
+    pub(crate) param1: u8,
+    pub(crate) param2: u8,
+
+    pub(crate) portion_length: u16,
+    pub(crate) remainder_length: u16,
+
+    pub(crate) cert_chain: __IncompleteArrayField<u8>,
+}
+
+impl Validate<&mut Unvalidated<KVec<u8>>> for &mut GetCertificateRsp {
+    type Err = Error;
+
+    fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> {
+        let raw = unvalidated.raw_mut();
+        if raw.len() < mem::size_of::<GetCertificateRsp>() {
+            return Err(EINVAL);
+        }
+
+        let ptr = raw.as_mut_ptr();
+        // CAST: `GetCertificateRsp` only contains integers and has `repr(C)`.
+        let ptr = ptr.cast::<GetCertificateRsp>();
+        // SAFETY: `ptr` came from a reference and the cast above is valid.
Quoted 8 lines

lib/rspdm/validator.rs fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> · line 422

Quoted 4 lines
lib/rspdm/validator.rs +4 -0
+        let rsp: &mut GetCertificateRsp = unsafe { &mut *ptr };
+
+        rsp.portion_length = rsp.portion_length.to_le();
+        rsp.remainder_length = rsp.remainder_length.to_le();

Why to_le()? I can understand from_le() but then I'm a bit dubious about the types. My gut feeling is that the validate code should leave these in little endian and we should convert them only at time of use.

Fixed

Alistair

Quoted 6 lines
Quoted 5 lines
lib/rspdm/validator.rs +4 -0
+
+        Ok(rsp)
+    }
+}
#40 lore raw

RFC v3 17/27 crypto: asymmetric_keys - Load certificate parsing early in boot

In reply to [email protected]

From: Alistair Francis <[email protected]>

Work is ongoing to support PCIe device attestation and authentication. As part of this a PCIe device will provide a X.509 certificate chain via the SPDM protocol to the kernel.

Linux should verify the chain before enabling the device, which means we need the certificate store ready before arch initilisation (where PCIe init happens). Move the certificate and keyring init to postcore to ensure it's loaded before PCIe devices.

This patch enables X.509 certificate parsing and asymmetric key support early in the boot process so that it can be used by the key store and SPDM to verify the certificate chain provided by a PCIe device via SPDM before we enable it.

---
 crypto/asymmetric_keys/asymmetric_type.c | 2 +-
 crypto/asymmetric_keys/x509_public_key.c | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)
crypto/asymmetric_keys/asymmetric_type.c +1 -1
index 2326743310b1..3f209299ee3a 100644
@@ -677,5 +677,5 @@ static void __exit asymmetric_key_cleanup(void)
 	unregister_key_type(&key_type_asymmetric);
 }
 
-module_init(asymmetric_key_init);
+postcore_initcall(asymmetric_key_init);
 module_exit(asymmetric_key_cleanup);
crypto/asymmetric_keys/x509_public_key.c +1 -1
index 27b4fea37845..2b308345bc6f 100644
@@ -258,7 +258,7 @@ static void __exit x509_key_exit(void)
 	unregister_asymmetric_key_parser(&x509_key_parser);
 }
 
-module_init(x509_key_init);
+postcore_initcall(x509_key_init);
 module_exit(x509_key_exit);
 
 MODULE_DESCRIPTION("X.509 certificate parser");
Signature · 1 line
2.52.0
#41 lore raw

RFC v3 18/27 KEYS: Load keyring and certificates early in boot

In reply to [email protected]

From: Alistair Francis <[email protected]>

Work is ongoing to support PCIe device attestation and authentication. As part of this a PCIe device will provide a certificate chain via the SPDM protocol to the kernel.

Linux should verify the chain before enabling the device, which means we need the certificate store ready before arch initilisation (where PCIe init happens). Move the certificate and keyring init to postcore to ensure it's loaded before PCIe devices.

This allows us to verify the certificate chain provided by a PCIe device via SPDM before we enable it.

---
 certs/system_keyring.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
certs/system_keyring.c +2 -2
index 9de610bf1f4b..f3d8ea4f70b4 100644
@@ -260,7 +260,7 @@ static __init int system_trusted_keyring_init(void)
 /*
  * Must be initialised before we try and load the keys into the keyring.
  */
-device_initcall(system_trusted_keyring_init);
+postcore_initcall(system_trusted_keyring_init);
 
 __init int load_module_cert(struct key *keyring)
 {
@@ -293,7 +293,7 @@ static __init int load_system_certificate_list(void)
 
 	return x509_load_certificate_list(p, size, builtin_trusted_keys);
 }
-late_initcall(load_system_certificate_list);
+postcore_initcall(load_system_certificate_list);
 
 #ifdef CONFIG_SYSTEM_DATA_VERIFICATION
 
Signature · 1 line
2.52.0
#42 lore raw

RFC v3 19/27 PCI/CMA: Support built in X.509 certificates

In reply to [email protected]

From: Alistair Francis <[email protected]>

Support building the X.509 certificates into the CMA certificate store. This allows certificates to be built into the kernel which can be used to authenticate PCIe devices via SPDM.

---
 certs/system_keyring.c        |  4 ----
 drivers/pci/cma.c             | 28 ++++++++++++++++++++++++++++
 include/keys/system_keyring.h |  4 ++++
 3 files changed, 32 insertions(+), 4 deletions(-)
certs/system_keyring.c +0 -4
index f3d8ea4f70b4..adfc24139133 100644
@@ -28,10 +28,6 @@ static struct key *machine_trusted_keys;
 static struct key *platform_trusted_keys;
 #endif
 
-extern __initconst const u8 system_certificate_list[];
-extern __initconst const unsigned long system_certificate_list_size;
-extern __initconst const unsigned long module_cert_size;
-
 /**
  * restrict_link_by_builtin_trusted - Restrict keyring addition by built-in CA
  * @dest_keyring: Keyring being linked to.
drivers/pci/cma.c +28 -0
index f2c435b04b92..8d64008594e2 100644
@@ -10,6 +10,7 @@
 
 #define dev_fmt(fmt) "CMA: " fmt
 
+#include <keys/system_keyring.h>
 #include <keys/x509-parser.h>
 #include <linux/asn1_decoder.h>
 #include <linux/oid_registry.h>
@@ -218,8 +219,31 @@ void pci_cma_destroy(struct pci_dev *pdev)
 	spdm_destroy(pdev->spdm_state);
 }
 
+/*
+ * Load the compiled-in list of X.509 certificates.
+ */
+static int load_system_certificate_list(void)
+{
+	const u8 *p;
+	unsigned long size;
+
+	pr_notice("Loading compiled-in X.509 certificates for CMA\n");
+
+#ifdef CONFIG_MODULE_SIG
+	p = system_certificate_list;
+	size = system_certificate_list_size;
+#else
+	p = system_certificate_list + module_cert_size;
+	size = system_certificate_list_size - module_cert_size;
+#endif
+
+	return x509_load_certificate_list(p, size, pci_cma_keyring);
+}
+
 __init static int pci_cma_keyring_init(void)
 {
+	int rc;
+
 	pci_cma_keyring = keyring_alloc(".cma", KUIDT_INIT(0), KGIDT_INIT(0),
 					current_cred(),
 					(KEY_POS_ALL & ~KEY_POS_SETATTR) |
@@ -232,6 +256,10 @@ __init static int pci_cma_keyring_init(void)
 		return PTR_ERR(pci_cma_keyring);
 	}
 
+	rc = load_system_certificate_list();
+	if (rc)
+		return rc;
+
 	return 0;
 }
 arch_initcall(pci_cma_keyring_init);
include/keys/system_keyring.h +4 -0
index a6c2897bcc63..35a33412e175 100644
@@ -130,4 +130,8 @@ static inline void set_platform_trusted_keys(struct key *keyring)
 }
 #endif
 
+extern __initconst const u8 system_certificate_list[];
+extern __initconst const unsigned long system_certificate_list_size;
+extern __initconst const unsigned long module_cert_size;
+
 #endif /* _KEYS_SYSTEM_KEYRING_H */
Signature · 1 line
2.52.0
#43 lore raw

RFC v3 20/27 crypto: sha: Load early in boot

In reply to [email protected]

From: Alistair Francis <[email protected]>

Work is ongoing to support PCIe device attestation and authentication. As part of this probing a PCIe device will require generating hashes via the SPDM protocol to the kernel.

Linux should verify the device before enabling the device, which means we need the crypto functions to be ready before arch initilisation (where PCIe init happens). Move the crypto init to postcore to
ensure it's loaded before PCIe devices.

This allows us to verify the certificate chain provided by a PCIe device via SPDM before we enable it.

---
 crypto/sha256.c | 2 +-
 crypto/sha3.c   | 2 +-
 crypto/sha512.c | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)
crypto/sha256.c +1 -1
index fb81defe084c..0ebe96f89733 100644
@@ -398,7 +398,7 @@ static int __init crypto_sha256_mod_init(void)
 {
 	return crypto_register_shashes(algs, ARRAY_SIZE(algs));
 }
-module_init(crypto_sha256_mod_init);
+postcore_initcall(crypto_sha256_mod_init);
 
 static void __exit crypto_sha256_mod_exit(void)
 {
crypto/sha3.c +1 -1
index 8f364979ec89..d37ce694b50a 100644
@@ -145,7 +145,7 @@ static int __init crypto_sha3_mod_init(void)
 {
 	return crypto_register_shashes(algs, ARRAY_SIZE(algs));
 }
-module_init(crypto_sha3_mod_init);
+postcore_initcall(crypto_sha3_mod_init);
 
 static void __exit crypto_sha3_mod_exit(void)
 {
crypto/sha512.c +1 -1
index d320fe53913f..6effa6043b55 100644
@@ -404,7 +404,7 @@ static int __init crypto_sha512_mod_init(void)
 {
 	return crypto_register_shashes(algs, ARRAY_SIZE(algs));
 }
-module_init(crypto_sha512_mod_init);
+postcore_initcall(crypto_sha512_mod_init);
 
 static void __exit crypto_sha512_mod_exit(void)
 {
Signature · 1 line
2.52.0
Reviewed-by on v3 20/27
#44 lore raw

Re: [RFC v3 20/27] crypto: sha: Load early in boot

Quoted 11 lines · On Wed, 11 Feb 2026 13:29:27 +1000 [email protected] wrote:

From: Alistair Francis <[email protected]>

Work is ongoing to support PCIe device attestation and authentication. As part of this probing a PCIe device will require generating hashes via the SPDM protocol to the kernel.

Linux should verify the device before enabling the device, which means we need the crypto functions to be ready before arch initilisation (where PCIe init happens). Move the crypto init to postcore to
ensure it's loaded before PCIe devices.

Quoted 4 lines

This allows us to verify the certificate chain provided by a PCIe device via SPDM before we enable it.

#45 lore raw

RFC v3 21/27 crypto: ecdsa: Load early in boot

In reply to [email protected]

From: Alistair Francis <[email protected]>

Work is ongoing to support PCIe device attestation and authentication. As part of this probing a PCIe device will require signing via the SPDM protocol to the kernel.

Linux should verify the device before enabling the device, which means we need the crypto functions to be ready before arch initilisation (where PCIe init happens). Move the crypto init to postcore to
ensure it's loaded before PCIe devices.

This allows us to verify the certificate chain provided by a PCIe device via SPDM before we enable it.

---
 crypto/ecdsa.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
crypto/ecdsa.c +1 -1
index ce8e4364842f..b225911c5266 100644
@@ -334,7 +334,7 @@ static void __exit ecdsa_exit(void)
 	crypto_unregister_sig(&ecdsa_nist_p521);
 }
 
-module_init(ecdsa_init);
+postcore_initcall(ecdsa_init);
 module_exit(ecdsa_exit);
 
 MODULE_LICENSE("GPL");
Signature · 1 line
2.52.0
Reviewed-by on v3 21/27
#46 lore raw

Re: [RFC v3 21/27] crypto: ecdsa: Load early in boot

Quoted 11 lines · On Wed, 11 Feb 2026 13:29:28 +1000 [email protected] wrote:

From: Alistair Francis <[email protected]>

Work is ongoing to support PCIe device attestation and authentication. As part of this probing a PCIe device will require signing via the SPDM protocol to the kernel.

Linux should verify the device before enabling the device, which means we need the crypto functions to be ready before arch initilisation (where PCIe init happens). Move the crypto init to postcore to
ensure it's loaded before PCIe devices.

Quoted 4 lines

This allows us to verify the certificate chain provided by a PCIe device via SPDM before we enable it.

Whilst others may disagree, I think it still makes sense to check signatures against the provided public leaf cert even if we aren't checking the cert chain in kernel. That will need this.

Quoted 17 lines
---
 crypto/ecdsa.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
crypto/ecdsa.c +1 -1
index ce8e4364842f..b225911c5266 100644
@@ -334,7 +334,7 @@ static void __exit ecdsa_exit(void)
 	crypto_unregister_sig(&ecdsa_nist_p521);
 }
 
-module_init(ecdsa_init);
+postcore_initcall(ecdsa_init);
 module_exit(ecdsa_exit);
 
 MODULE_LICENSE("GPL");
#47 lore raw

RFC v3 22/27 lib: rspdm: Support SPDM certificate validation

In reply to [email protected]

From: Alistair Francis <[email protected]>

Support validating the SPDM certificate chain.

---
 lib/rspdm/lib.rs                | 17 ++++++
 lib/rspdm/state.rs              | 93 ++++++++++++++++++++++++++++++++-
 rust/bindings/bindings_helper.h |  2 +
 3 files changed, 111 insertions(+), 1 deletion(-)
lib/rspdm/lib.rs +17 -0
index b065b3f70356..c016306116a3 100644
@@ -136,6 +136,17 @@
         provisioned_slots &= !(1 << slot);
     }
 
+    let mut provisioned_slots = state.provisioned_slots;
+    while (provisioned_slots as usize) > 0 {
+        let slot = provisioned_slots.trailing_zeros() as u8;
+
+        if let Err(e) = state.validate_cert_chain(slot) {
+            return e.to_errno() as c_int;
+        }
+
+        provisioned_slots &= !(1 << slot);
+    }
+
     0
 }
 
@@ -144,6 +155,12 @@
 /// @spdm_state: SPDM session state
 #[no_mangle]
 pub unsafe extern "C" fn spdm_destroy(state: &'static mut SpdmState) {
+    if let Some(leaf_key) = &mut state.leaf_key {
+        unsafe {
+            bindings::public_key_free(*leaf_key);
+        }
+    }
+
     if let Some(desc) = &mut state.desc {
         unsafe {
             bindings::kfree(*desc as *mut _ as *mut c_void);
lib/rspdm/state.rs +92 -1

115 lines not on this page · raw message

rust/bindings/bindings_helper.h +2 -0
index 5043eee2a8d6..52ad3e98e036 100644
@@ -64,6 +64,8 @@
 #include <linux/hash.h>
 #include <linux/jiffies.h>
 #include <linux/jump_label.h>
+#include <keys/asymmetric-type.h>
+#include <keys/x509-parser.h>
 #include <linux/mdio.h>
 #include <linux/mm.h>
 #include <linux/miscdevice.h>
Signature · 1 line
2.52.0
#48 lore raw

Re: [RFC v3 22/27] lib: rspdm: Support SPDM certificate validation

Quoted 5 lines · On Wed, 11 Feb 2026 13:29:29 +1000 [email protected] wrote:

From: Alistair Francis <[email protected]>

Support validating the SPDM certificate chain.

Only minor thing inline + observation on the code that I think we want to remove for now wrt to checking the root cert.

Quoted 26 lines
lib/rspdm/state.rs +17 -0
index 1e5656144611..728b920beace 100644
@@ -743,4 +746,92 @@ pub(crate) fn get_certificate(&mut self, slot: u8) -> Result<(), Error> {
 
         Ok(())
     }
+
+    pub(crate) fn validate_cert_chain(&mut self, slot: u8) -> Result<(), Error> {
+        let cert_chain_buf = &self.certs[slot as usize];
+        let cert_chain_len = cert_chain_buf.len();
+        let header_len = 4 + self.hash_len;
+
+        let mut offset = header_len;
+        let mut prev_cert: Option<*mut bindings::x509_certificate> = None;
+
+        while offset < cert_chain_len {
+            let cert_len = unsafe {
+                bindings::x509_get_certificate_length(
+                    &cert_chain_buf[offset..] as *const _ as *const u8,
+                    cert_chain_len - offset,
+                )
+            };
+

lib/rspdm/state.rs pub(crate) fn get_certificate(&mut self, slot: u8) -> Result<(), Error> { · line 775

Quoted 10 lines
lib/rspdm/state.rs +10 -0
+            if cert_len < 0 {
+                pr_err!("Invalid certificate length\n");
+                to_result(cert_len as i32)?;
+            }
+
+            let _is_leaf_cert = if offset + cert_len as usize == cert_chain_len {
+                true
+            } else {
+                false
+            };

Same as the following?

               let _is_leaf_cert = offset + cert_len as usize == cert_chain_len;
Quoted 13 lines
lib/rspdm/state.rs +13 -0
+
+            let cert_ptr = unsafe {
+                from_err_ptr(bindings::x509_cert_parse(
+                    &cert_chain_buf[offset..] as *const _ as *const c_void,
+                    cert_len as usize,
+                ))?
+            };
+            let cert = unsafe { *cert_ptr };
+
+            if cert.unsupported_sig || cert.blacklisted {
+                to_result(-(bindings::EKEYREJECTED as i32))?;
+            }
+

lib/rspdm/state.rs pub(crate) fn get_certificate(&mut self, slot: u8) -> Result<(), Error> { · line 798

Quoted 10 lines
lib/rspdm/state.rs +10 -0
+            if let Some(prev) = prev_cert {
+                // Check against previous certificate
+                let rc = unsafe { bindings::public_key_verify_signature((*prev).pub_, cert.sig) };
+
+                if rc < 0 {
+                    pr_err!("Signature validation error\n");
+                    to_result(rc)?;
+                }
+            } else {
+                // Check aginst root keyring

So this is the check that Dan and Jason are proposing we drop (for now).

Works for me as easy to bring back later and lets us move forward in the meantime. As long as userspace gets nothing to indicate that we have in any way checked this root cert we are making no false claims.

Quoted 39 lines
lib/rspdm/state.rs +38 -0
+                let key = unsafe {
+                    from_err_ptr(bindings::find_asymmetric_key(
+                        self.keyring,
+                        (*cert.sig).auth_ids[0],
+                        (*cert.sig).auth_ids[1],
+                        (*cert.sig).auth_ids[2],
+                        false,
+                    ))?
+                };
+
+                let rc = unsafe { bindings::verify_signature(key, cert.sig) };
+                unsafe { bindings::key_put(key) };
+
+                if rc < 0 {
+                    pr_err!("Root signature validation error\n");
+                    to_result(rc)?;
+                }
+            }
+
+            if let Some(prev) = prev_cert {
+                unsafe { bindings::x509_free_certificate(prev) };
+            }
+
+            prev_cert = Some(cert_ptr);
+            offset += cert_len as usize;
+        }
+
+        if let Some(prev) = prev_cert {
+            if let Some(validate) = self.validate {
+                let rc = unsafe { validate(self.dev, slot, prev) };
+                to_result(rc)?;
+            }
+
+            self.leaf_key = unsafe { Some((*prev).pub_) };
+        }
+
+        Ok(())
+    }
 }
#49 lore raw

Re: [RFC v3 22/27] lib: rspdm: Support SPDM certificate validation

Quoted 50 lines · Jonathan Cameron
Quoted 5 lines · On Wed, 11 Feb 2026 13:29:29 +1000 [email protected] wrote:

From: Alistair Francis <[email protected]>

Support validating the SPDM certificate chain.

Only minor thing inline + observation on the code that I think we want to remove for now wrt to checking the root cert.

Quoted 26 lines
lib/rspdm/state.rs +17 -0
index 1e5656144611..728b920beace 100644
@@ -743,4 +746,92 @@ pub(crate) fn get_certificate(&mut self, slot: u8) -> Result<(), Error> {
 
         Ok(())
     }
+
+    pub(crate) fn validate_cert_chain(&mut self, slot: u8) -> Result<(), Error> {
+        let cert_chain_buf = &self.certs[slot as usize];
+        let cert_chain_len = cert_chain_buf.len();
+        let header_len = 4 + self.hash_len;
+
+        let mut offset = header_len;
+        let mut prev_cert: Option<*mut bindings::x509_certificate> = None;
+
+        while offset < cert_chain_len {
+            let cert_len = unsafe {
+                bindings::x509_get_certificate_length(
+                    &cert_chain_buf[offset..] as *const _ as *const u8,
+                    cert_chain_len - offset,
+                )
+            };
+

lib/rspdm/state.rs pub(crate) fn get_certificate(&mut self, slot: u8) -> Result<(), Error> { · line 775

Quoted 10 lines
lib/rspdm/state.rs +10 -0
+            if cert_len < 0 {
+                pr_err!("Invalid certificate length\n");
+                to_result(cert_len as i32)?;
+            }
+
+            let _is_leaf_cert = if offset + cert_len as usize == cert_chain_len {
+                true
+            } else {
+                false
+            };

Same as the following?

Quoted 1 line
               let _is_leaf_cert = offset + cert_len as usize == cert_chain_len;

Yep, same thing. My way is the Rust-y way to do it

Although _is_leaf_cert is unused, so I'll just remove it

Quoted 27 lines
Quoted 15 lines
lib/rspdm/state.rs +13 -0
+
+            let cert_ptr = unsafe {
+                from_err_ptr(bindings::x509_cert_parse(
+                    &cert_chain_buf[offset..] as *const _ as *const c_void,
+                    cert_len as usize,
+                ))?
+            };
+            let cert = unsafe { *cert_ptr };
+
+            if cert.unsupported_sig || cert.blacklisted {
+                to_result(-(bindings::EKEYREJECTED as i32))?;
+            }
+

lib/rspdm/state.rs pub(crate) fn get_certificate(&mut self, slot: u8) -> Result<(), Error> { · line 798

Quoted 10 lines
lib/rspdm/state.rs +10 -0
+            if let Some(prev) = prev_cert {
+                // Check against previous certificate
+                let rc = unsafe { bindings::public_key_verify_signature((*prev).pub_, cert.sig) };
+
+                if rc < 0 {
+                    pr_err!("Signature validation error\n");
+                    to_result(rc)?;
+                }
+            } else {
+                // Check aginst root keyring

So this is the check that Dan and Jason are proposing we drop (for now).

Quoted 3 lines

Works for me as easy to bring back later and lets us move forward in the meantime. As long as userspace gets nothing to indicate that we have in any way checked this root cert we are making no false claims.

Will do!

Alistair

Quoted 42 lines
Quoted 40 lines
lib/rspdm/state.rs +38 -0
+                let key = unsafe {
+                    from_err_ptr(bindings::find_asymmetric_key(
+                        self.keyring,
+                        (*cert.sig).auth_ids[0],
+                        (*cert.sig).auth_ids[1],
+                        (*cert.sig).auth_ids[2],
+                        false,
+                    ))?
+                };
+
+                let rc = unsafe { bindings::verify_signature(key, cert.sig) };
+                unsafe { bindings::key_put(key) };
+
+                if rc < 0 {
+                    pr_err!("Root signature validation error\n");
+                    to_result(rc)?;
+                }
+            }
+
+            if let Some(prev) = prev_cert {
+                unsafe { bindings::x509_free_certificate(prev) };
+            }
+
+            prev_cert = Some(cert_ptr);
+            offset += cert_len as usize;
+        }
+
+        if let Some(prev) = prev_cert {
+            if let Some(validate) = self.validate {
+                let rc = unsafe { validate(self.dev, slot, prev) };
+                to_result(rc)?;
+            }
+
+            self.leaf_key = unsafe { Some((*prev).pub_) };
+        }
+
+        Ok(())
+    }
 }
#50 lore raw

Re: [RFC v3 22/27] lib: rspdm: Support SPDM certificate validation

Quoted 2 lines · Alistair Francis

Yep, same thing. My way is the Rust-y way to do it

Hmm... In what way? I would say it is the other way around.

Clippy should be flagging this, too.

Cheers,
Miguel

#51 lore raw

Re: [RFC v3 22/27] lib: rspdm: Support SPDM certificate validation

Quoted 6 lines · Miguel Ojeda
Quoted 2 lines · Alistair Francis

Yep, same thing. My way is the Rust-y way to do it

Hmm... In what way? I would say it is the other way around.

I think it's Rust-ier as it uses the fact that
* if statements can be used as expressions, which is a cool feature of Rust
* Rust is explicit about comparisons being bools (no if (value) for example)

So I find the if statement expression returning explicit bools very Rust-y and easier to read

Alistair

Quoted 5 lines

Clippy should be flagging this, too.

Cheers,
Miguel

#52 lore raw

Re: [RFC v3 22/27] lib: rspdm: Support SPDM certificate validation

Quoted 3 lines · Alistair Francis

I think it's Rust-ier as it uses the fact that
* if statements can be used as expressions, which is a cool feature of Rust

Sure, but a feature being nice doesn't mean it is idiomatic to apply it in every context.

More generally, regarding cool features (whether in C or C++ or Rust etc.), I would say one should strive to keep code as simple as possible, i.e. we use fancy features when they buy us something, not just because we can. The kernel applies this principle too.

Quoted 1 line

* Rust is explicit about comparisons being bools (no if (value) for example)

Yes, but I am not sure how this is related.

Quoted 2 lines

So I find the if statement expression returning explicit bools very Rust-y and easier to read

If you mean that that seeing true and false may be clearer because it is more explicit, then that sounds OK (whether someone agrees or not), but it is a different argument than being idiomatic.

In any case, I replied initially because the code should be Clippy-clean, and currently we have that lint enabled -- does it not trigger in your particular case? i.e. even if you have removed the code, it would be nice to know why it didn't trigger, or if you were not testing with CLIPPY=1.

Anyway, if you think it is idiomatic Rust, then I would suggest discussing it with the Clippy folks, because they have it as a warning by default.

Thanks!

Cheers,
Miguel

#53 lore raw

RFC v3 23/27 rust: allow extracting the buffer from a CString

In reply to [email protected]

From: Alistair Francis <[email protected]>

The kernel CString is a wrapper aroud a KVec. This patch allows retrieving the underlying buffer and consuming the CString. This allows users to create a CString from a string and then retrieve the underlying buffer.

---
 rust/kernel/str.rs | 5 +++++
 1 file changed, 5 insertions(+)
rust/kernel/str.rs +5 -0
index fa87779d2253..17426db83c9f 100644
@@ -844,6 +844,11 @@ pub fn try_from_fmt(args: fmt::Arguments<'_>) -> Result<Self, Error> {
         // exist in the buffer.
         Ok(Self { buf })
     }
+
+    /// Return the internal buffer while consuming the original [`CString`]
+    pub fn into_vec(self) -> KVec<u8> {
+        self.buf
+    }
 }
 
 impl Deref for CString {
Signature · 1 line
2.52.0
reviewer
#54 lore raw

Re: [RFC v3 23/27] rust: allow extracting the buffer from a CString

Quoted 24 lines · On 2026-02-11 03:29, [email protected] wrote:

From: Alistair Francis <[email protected]>

The kernel CString is a wrapper aroud a KVec. This patch allows retrieving the underlying buffer and consuming the CString. This allows users to create a CString from a string and then retrieve the underlying buffer.

---
 rust/kernel/str.rs | 5 +++++
 1 file changed, 5 insertions(+)
rust/kernel/str.rs +2 -0
index fa87779d2253..17426db83c9f 100644
@@ -844,6 +844,11 @@ pub fn try_from_fmt(args: fmt::Arguments<'_>) -> 
 Result<Self, Error> {
         // exist in the buffer.
         Ok(Self { buf })
     }
+
+    /// Return the internal buffer while consuming the original 

[CString]

rust/kernel/str.rs pub fn try_from_fmt(args: fmt::Arguments<'_>) -> Result<Self, Error> { · line 851

Quoted 3 lines
rust/kernel/str.rs +3 -0
+    pub fn into_vec(self) -> KVec<u8> {
+        self.buf
+    }

#[inline] here, too.

Feel free to add my R-b after adding it.

Best,
Gary

Quoted 3 lines

}

impl Deref for CString {

#55 lore raw

RFC v3 24/27 lib: rspdm: Support SPDM challenge

In reply to [email protected]

From: Alistair Francis <[email protected]>

Support the CHALLENGE SPDM command.

---
 lib/rspdm/consts.rs             |   6 +
 lib/rspdm/lib.rs                |   8 +-
 lib/rspdm/state.rs              | 218 +++++++++++++++++++++++++++++++-
 lib/rspdm/validator.rs          |  65 +++++++++-
 rust/bindings/bindings_helper.h |   1 +
 5 files changed, 289 insertions(+), 9 deletions(-)
lib/rspdm/consts.rs +6 -0
index eaf2132da290..904d8272a1d0 100644
@@ -176,6 +176,8 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
 
 pub(crate) const SPDM_GET_CERTIFICATE: u8 = 0x82;
 
+pub(crate) const SPDM_CHALLENGE: u8 = 0x83;
+
 #[cfg(CONFIG_CRYPTO_RSA)]
 pub(crate) const SPDM_ASYM_RSA: u32 =
     SPDM_ASYM_RSASSA_2048 | SPDM_ASYM_RSASSA_3072 | SPDM_ASYM_RSASSA_4096;
@@ -205,3 +207,7 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
 // pub(crate) const SPDM_MAX_REQ_ALG_STRUCT: usize = 4;
 
 pub(crate) const SPDM_OPAQUE_DATA_FMT_GENERAL: u8 = 1 << 1;
+
+pub(crate) const SPDM_PREFIX_SZ: usize = 64;
+pub(crate) const SPDM_COMBINED_PREFIX_SZ: usize = 100;
+pub(crate) const SPDM_MAX_OPAQUE_DATA: usize = 1024;
lib/rspdm/lib.rs +7 -1
index c016306116a3..05fa471bb1e2 100644
@@ -136,17 +136,23 @@
         provisioned_slots &= !(1 << slot);
     }
 
+    let mut verify = true;
     let mut provisioned_slots = state.provisioned_slots;
     while (provisioned_slots as usize) > 0 {
         let slot = provisioned_slots.trailing_zeros() as u8;
 
         if let Err(e) = state.validate_cert_chain(slot) {
-            return e.to_errno() as c_int;
+            pr_debug!("Certificate in slot {slot} failed to verify: {e:?}");
+            verify = false;
         }
 
         provisioned_slots &= !(1 << slot);
     }
 
+    if let Err(e) = state.challenge(state.provisioned_slots.trailing_zeros() as u8, verify) {
+        return e.to_errno() as c_int;
+    }
+
     0
 }
 
lib/rspdm/state.rs +211 -7

304 lines not on this page · raw message

lib/rspdm/validator.rs +64 -1

74 lines not on this page · raw message

rust/bindings/bindings_helper.h +1 -0
index 52ad3e98e036..35e4378fb9dc 100644
@@ -35,6 +35,7 @@
 #include <drm/drm_gem.h>
 #include <drm/drm_ioctl.h>
 #include <crypto/hash.h>
+#include <crypto/public_key.h>
 #include <kunit/test.h>
 #include <linux/auxiliary_bus.h>
 #include <linux/bitmap.h>
Signature · 1 line
2.52.0
#56 lore raw

Re: [RFC v3 24/27] lib: rspdm: Support SPDM challenge

Quoted 5 lines · On Wed, 11 Feb 2026 13:29:31 +1000 [email protected] wrote:

From: Alistair Francis <[email protected]>

Support the CHALLENGE SPDM command.

Nicely broken out. I was wondering when the transcript for the hash might show up, but you sensibly kept that delight for only being done when you need it. Might be worth talking a bit more about that in the patch description!

Minor comments inline.

J

Quoted 4 lines
lib/rspdm/state.rs +0 -0
index 728b920beace..a4d803af48fe 100644

...

lib/rspdm/state.rs pub(crate) fn validate_cert_chain(&mut self, slot: u8) -> Result<(), Error> { · line 883

Quoted 8 lines
lib/rspdm/state.rs +4 -0
@@ -834,4 +877,165 @@ pub(crate) fn validate_cert_chain(&mut self, slot: u8) -> Result<(), Error> {
 
         Ok(())
     }
+
+    pub(crate) fn challenge_rsp_len(&mut self, nonce_len: usize, opaque_len: usize) -> usize {
+        let mut length =
+            core::mem::size_of::<SpdmHeader>() + self.hash_len + nonce_len + opaque_len + 2;

As below, perhaps add a comment at least that MSHLength == 0

Quoted 5 lines
lib/rspdm/state.rs +5 -0
+
+        if self.version >= 0x13 {
+            length += 8;
+        }
+

lib/rspdm/state.rs pub(crate) fn validate_cert_chain(&mut self, slot: u8) -> Result<(), Error> { · line 898

Quoted 10 lines
lib/rspdm/state.rs +10 -0
+        length + self.sig_len
+    }
+
+    fn verify_signature(&mut self, response_vec: &mut [u8]) -> Result<(), Error> {
+        let sig_start = response_vec.len() - self.sig_len;
+        let mut sig = bindings::public_key_signature::default();
+        let mut mhash: KVec<u8> = KVec::new();
+
+        sig.s = &mut response_vec[sig_start..] as *mut _ as *mut u8;
+        sig.s_size = self.sig_len as u32;

Perhaps it makes sense to extract the signature at the caller and only pass that in here rather than the whole response_vec.

Quoted 3 lines
lib/rspdm/state.rs +3 -0
+        sig.encoding = self.base_asym_enc.as_ptr() as *const u8;
+        sig.hash_algo = self.base_hash_alg_name.as_ptr() as *const u8;
+

...

Quoted 8 lines
lib/rspdm/state.rs +8 -0
+    pub(crate) fn challenge(&mut self, slot: u8, verify: bool) -> Result<(), Error> {
+        let mut request = ChallengeReq::default();
+        request.version = self.version;
+        request.param1 = slot;
+
+        let nonce_len = request.nonce.len();
+
+        if let Some(nonce) = &self.next_nonce {

lib/rspdm/state.rs pub(crate) fn validate_cert_chain(&mut self, slot: u8) -> Result<(), Error> { · line 981

Quoted 10 lines
lib/rspdm/state.rs +10 -0
+            request.nonce.copy_from_slice(&nonce);
+            self.next_nonce = None;
+        } else {
+            unsafe {
+                bindings::get_random_bytes(&mut request.nonce as *mut _ as *mut c_void, nonce_len)
+            };
+        }
+
+        let req_sz = if self.version <= 0x12 {
+            core::mem::size_of::<ChallengeReq>() - 8

No means to do offset_of type stuff in Rust? Would make the sizing explicitly reflect the structure.

Quoted 17 lines
lib/rspdm/state.rs +17 -0
+        } else {
+            core::mem::size_of::<ChallengeReq>()
+        };
+
+        let rsp_sz = self.challenge_rsp_len(nonce_len, SPDM_MAX_OPAQUE_DATA);
+
+        // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
+        let request_buf = unsafe { from_raw_parts_mut(&mut request as *mut _ as *mut u8, req_sz) };
+
+        let mut response_vec: KVec<u8> = KVec::with_capacity(rsp_sz, GFP_KERNEL)?;
+        // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
+        let response_buf = unsafe { from_raw_parts_mut(response_vec.as_mut_ptr(), rsp_sz) };
+
+        let rc = self.spdm_exchange(request_buf, response_buf)?;
+
+        if rc < (core::mem::size_of::<ChallengeRsp>() as i32) {
+            pr_err!("Truncated challenge response\n");

lib/rspdm/state.rs pub(crate) fn validate_cert_chain(&mut self, slot: u8) -> Result<(), Error> { · line 1008

Quoted 10 lines
lib/rspdm/state.rs +10 -0
+            to_result(-(bindings::EIO as i32))?;
+        }
+
+        // SAFETY: `rc` is the length of data read, which will be smaller
+        // then the capacity of the vector
+        unsafe { response_vec.inc_len(rc as usize) };
+
+        let _response: &mut ChallengeRsp = Untrusted::new_mut(&mut response_vec).validate_mut()?;
+
+        let opaque_len_offset = core::mem::size_of::<SpdmHeader>() + self.hash_len + nonce_len;

Might be worth adding something to reflect that this also includes the MSHLength but that is 0 as we didn't ask for a measurement summary hash. Would make it a tiny bit easier to correlate with the spec.

Quoted 41 lines
lib/rspdm/state.rs +32 -0
+        let opaque_len = u16::from_le_bytes(
+            response_vec[opaque_len_offset..(opaque_len_offset + 2)]
+                .try_into()
+                .unwrap_or([0, 0]),
+        );
+
+        let rsp_sz = self.challenge_rsp_len(nonce_len, opaque_len as usize);
+
+        if rc < rsp_sz as i32 {
+            pr_err!("Truncated challenge response\n");
+            to_result(-(bindings::EIO as i32))?;
+        }
+
+        self.transcript
+            .extend_from_slice(&response_vec[..rsp_sz], GFP_KERNEL)?;
+
+        if verify {
+            /* Verify signature at end of transcript against leaf key */
+            match self.verify_signature(&mut response_vec[..rsp_sz]) {
+                Ok(()) => {
+                    pr_info!("Authenticated with certificate slot {slot}");
+                    self.authenticated = true;
+                }
+                Err(e) => {
+                    pr_err!("Cannot verify challenge_auth signature: {e:?}");
+                    self.authenticated = false;
+                }
+            };
+        }
+
+        Ok(())
+    }
 }
lib/rspdm/validator.rs +3 -0
index a8bc3378676f..f8a5337841f0 100644
 
+
+#[repr(C, packed)]
+pub(crate) struct ChallengeRsp {

lib/rspdm/validator.rs fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> · line 463

Quoted 10 lines
lib/rspdm/validator.rs +10 -0
+    pub(crate) version: u8,
+    pub(crate) code: u8,
+    pub(crate) param1: u8,
+    pub(crate) param2: u8,
+
+    pub(crate) cert_chain_hash: __IncompleteArrayField<u8>,
+    pub(crate) nonce: [u8; 32],
+    pub(crate) message_summary_hash: __IncompleteArrayField<u8>,
+
+    pub(crate) opaque_data_len: u16,

Similar to other places, I'd use a __le16 and convert at place of use only.

Quoted 12 lines
lib/rspdm/validator.rs +12 -0
+    pub(crate) opaque_data: __IncompleteArrayField<u8>,
+
+    pub(crate) context: [u8; 8],
+    pub(crate) signature: __IncompleteArrayField<u8>,
+}
+
+impl Validate<&mut Unvalidated<KVec<u8>>> for &mut ChallengeRsp {
+    type Err = Error;
+
+    fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> {
+        let raw = unvalidated.raw_mut();
+        if raw.len() < mem::size_of::<ChallengeRsp>() {

lib/rspdm/validator.rs fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> · line 485

Quoted 10 lines
lib/rspdm/validator.rs +10 -0
+            return Err(EINVAL);
+        }
+
+        let ptr = raw.as_mut_ptr();
+        // CAST: `ChallengeRsp` only contains integers and has `repr(C)`.
+        let ptr = ptr.cast::<ChallengeRsp>();
+        // SAFETY: `ptr` came from a reference and the cast above is valid.
+        let rsp: &mut ChallengeRsp = unsafe { &mut *ptr };
+
+        // rsp.opaque_data_len = rsp.opaque_data_len.to_le();

Not sure why this is commented out. But as above, I'd leave it alone anyway.

Quoted 4 lines
lib/rspdm/validator.rs +4 -0
+
+        Ok(rsp)
+    }
+}
#57 lore raw

RFC v3 25/27 PCI/CMA: Expose in sysfs whether devices are authenticated

In reply to [email protected]

From: Alistair Francis <[email protected]>

From: Lukas Wunner <[email protected]>

The PCI core has just been amended to authenticate CMA-capable devices on enumeration and store the result in an "authenticated" bit in struct pci_dev->spdm_state.

Expose the bit to user space through an eponymous sysfs attribute.

Allow user space to trigger reauthentication (e.g. after it has updated the CMA keyring) by writing to the sysfs attribute.

Implement the attribute in the SPDM library so that other bus types besides PCI may take advantage of it. They just need to add spdm_attr_group to the attribute groups of their devices and amend the dev_to_spdm_state() helper which retrieves the spdm_state for a given device.

The helper may return an ERR_PTR if it couldn't be determined whether SPDM is supported by the device. The sysfs attribute is visible in that case but returns an error on access. This prevents downgrade attacks where an attacker disturbs memory allocation or DOE communication in order to create the appearance that SPDM is unsupported.

Subject to further discussion, a future commit might add a user-defined policy to forbid driver binding to devices which failed authentication, similar to the "authorized" attribute for USB.

Alternatively, authentication success might be signaled to user space through a uevent, whereupon it may bind a (blacklisted) driver. A uevent signaling authentication failure might similarly cause user space to unbind or outright remove the potentially malicious device.

Traffic from devices which failed authentication could also be filtered through ACS I/O Request Blocking Enable (PCIe r6.2 sec 7.7.11.3) or through Link Disable (PCIe r6.2 sec 7.5.3.7). Unlike an IOMMU, that will not only protect the host, but also prevent malicious peer-to-peer traffic to other devices.

[ Changes by AF:
- Drop lib/spdm changes and replace with Rust implementation
]

---
 Documentation/ABI/testing/sysfs-devices-spdm | 31 +++++++
 MAINTAINERS                                  |  3 +-
 drivers/pci/cma.c                            | 12 ++-
 drivers/pci/doe.c                            |  2 +
 drivers/pci/pci-sysfs.c                      |  3 +
 drivers/pci/pci.h                            |  5 +
 include/linux/pci.h                          | 12 +++
 lib/rspdm/Makefile                           |  1 +
 lib/rspdm/lib.rs                             |  1 +
 lib/rspdm/req-sysfs.c                        | 98 ++++++++++++++++++++
 lib/rspdm/spdm.h                             | 17 ++++
 lib/rspdm/sysfs.rs                           | 38 ++++++++
 12 files changed, 218 insertions(+), 5 deletions(-)
 create mode 100644 Documentation/ABI/testing/sysfs-devices-spdm
 create mode 100644 lib/rspdm/req-sysfs.c
 create mode 100644 lib/rspdm/spdm.h
 create mode 100644 lib/rspdm/sysfs.rs
Documentation/ABI/testing/sysfs-devices-spdm new file +31 -0
new file mode 100644
index 000000000000..018acde950ff
@@ -0,0 +1,31 @@
+What:		/sys/devices/.../authenticated
+Date:		June 2025
+Contact:	Lukas Wunner <[email protected]>
+Description:
+		This file contains 1 if the device authenticated successfully
+		with SPDM (Security Protocol and Data Model).  It contains 0 if
+		the device failed authentication (and may thus be malicious).
+
+		Writing "re" to this file causes reauthentication.
+		That may be opportune after updating the device keyring.
+		The device keyring of the PCI bus is named ".cma"
+		(Component Measurement and Authentication).
+
+		Reauthentication may also be necessary after device identity
+		has mutated, e.g. after downloading firmware to an FPGA device.
+
+		The file is not visible if authentication is unsupported
+		by the device.
+
+		If the kernel could not determine whether authentication is
+		supported because memory was low or communication with the
+		device was not working, the file is visible but accessing it
+		fails with error code ENOTTY.
+
+		This prevents downgrade attacks where an attacker consumes
+		memory or disturbs communication in order to create the
+		appearance that a device does not support authentication.
+
+		The reason why authentication support could not be determined
+		is apparent from "dmesg".  To re-probe authentication support
+		of PCI devices, exercise the "remove" and "rescan" attributes.
MAINTAINERS +2 -1
index 58898260fde8..eae4722e40bf 100644
@@ -23702,7 +23702,8 @@ L:	[email protected]
 L:	[email protected]
 S:	Maintained
 T:	git git://git.kernel.org/pub/scm/linux/kernel/git/devsec/spdm.git
-F:	drivers/pci/cma.c
+F:	Documentation/ABI/testing/sysfs-devices-spdm
+F:	drivers/pci/cma*
 F:	include/linux/spdm.h
 F:	lib/rspdm/
 
drivers/pci/cma.c +8 -4
index 8d64008594e2..55b8fe6d3121 100644
@@ -172,8 +172,10 @@ void pci_cma_init(struct pci_dev *pdev)
 {
 	struct pci_doe_mb *doe;
 
-	if (IS_ERR(pci_cma_keyring))
+	if (IS_ERR(pci_cma_keyring)) {
+		pdev->spdm_state = ERR_PTR(-ENOTTY);
 		return;
+	}
 
 	if (!pci_is_pcie(pdev))
 		return;
@@ -186,8 +188,10 @@ void pci_cma_init(struct pci_dev *pdev)
 	pdev->spdm_state = spdm_create(&pdev->dev, pci_doe_transport, doe,
 				       PCI_DOE_MAX_PAYLOAD, pci_cma_keyring,
 				       pci_cma_validate);
-	if (!pdev->spdm_state)
+	if (!pdev->spdm_state) {
+		pdev->spdm_state = ERR_PTR(-ENOTTY);
 		return;
+	}
 
 	/*
 	 * Keep spdm_state allocated even if initial authentication fails
@@ -205,7 +209,7 @@ void pci_cma_init(struct pci_dev *pdev)
  */
 void pci_cma_reauthenticate(struct pci_dev *pdev)
 {
-	if (!pdev->spdm_state)
+	if (IS_ERR_OR_NULL(pdev->spdm_state))
 		return;
 
 	spdm_authenticate(pdev->spdm_state);
@@ -213,7 +217,7 @@ void pci_cma_reauthenticate(struct pci_dev *pdev)
 
 void pci_cma_destroy(struct pci_dev *pdev)
 {
-	if (!pdev->spdm_state)
+	if (IS_ERR_OR_NULL(pdev->spdm_state))
 		return;
 
 	spdm_destroy(pdev->spdm_state);
drivers/pci/doe.c +2 -0
index 344e01496a8e..96ca77ecba90 100644
@@ -857,6 +857,7 @@ void pci_doe_init(struct pci_dev *pdev)
 		if (IS_ERR(doe_mb)) {
 			pci_err(pdev, "[%x] failed to create mailbox: %ld\n",
 				offset, PTR_ERR(doe_mb));
+			pci_cma_disable(pdev);
 			continue;
 		}
 
@@ -865,6 +866,7 @@ void pci_doe_init(struct pci_dev *pdev)
 			pci_err(pdev, "[%x] failed to insert mailbox: %d\n",
 				offset, rc);
 			pci_doe_destroy_mb(doe_mb);
+			pci_cma_disable(pdev);
 		}
 	}
 }
drivers/pci/pci-sysfs.c +3 -0
index c2df915ad2d2..b2b2096f8162 100644
@@ -1853,6 +1853,9 @@ const struct attribute_group *pci_dev_attr_groups[] = {
 #ifdef CONFIG_PCIEASPM
 	&aspm_ctrl_attr_group,
 #endif
+#ifdef CONFIG_PCI_CMA
+	&spdm_attr_group,
+#endif
 #ifdef CONFIG_PCI_DOE
 	&pci_doe_sysfs_group,
 #endif
drivers/pci/pci.h +5 -0
index f87a4b7c92b8..80bd998e77e4 100644
@@ -603,10 +603,15 @@ static inline void pci_doe_disconnected(struct pci_dev *pdev) { }
 void pci_cma_init(struct pci_dev *pdev);
 void pci_cma_destroy(struct pci_dev *pdev);
 void pci_cma_reauthenticate(struct pci_dev *pdev);
+static inline void pci_cma_disable(struct pci_dev *pdev)
+{
+	pdev->spdm_state = ERR_PTR(-ENOTTY);
+}
 #else
 static inline void pci_cma_init(struct pci_dev *pdev) { }
 static inline void pci_cma_destroy(struct pci_dev *pdev) { }
 static inline void pci_cma_reauthenticate(struct pci_dev *pdev) { }
+static inline void pci_cma_disable(struct pci_dev *pdev) { }
 #endif
 
 #ifdef CONFIG_PCI_NPEM
include/linux/pci.h +12 -0
index 7384846ade19..44e7bd6f2076 100644
@@ -2827,6 +2827,18 @@ static inline bool pci_is_thunderbolt_attached(struct pci_dev *pdev)
 void pci_uevent_ers(struct pci_dev *pdev, enum  pci_ers_result err_type);
 #endif
 
+#ifdef CONFIG_PCI_CMA
+static inline struct spdm_state *pci_dev_to_spdm_state(struct pci_dev *pdev)
+{
+	return pdev->spdm_state;
+}
+#else
+static inline struct spdm_state *pci_dev_to_spdm_state(struct pci_dev *pdev)
+{
+	return NULL;
+}
+#endif
+
 #include <linux/dma-mapping.h>
 
 #define pci_emerg(pdev, fmt, arg...)	dev_emerg(&(pdev)->dev, fmt, ##arg)
lib/rspdm/Makefile +1 -0
index 1f62ee2a882d..f15b1437196b 100644
@@ -8,3 +8,4 @@
 obj-$(CONFIG_RSPDM) += spdm.o
 
 spdm-y := lib.o
+spdm-$(CONFIG_SYSFS) += req-sysfs.o
lib/rspdm/lib.rs +1 -0
index 05fa471bb1e2..c0e3e039c14f 100644
@@ -24,6 +24,7 @@
 
 mod consts;
 mod state;
+pub mod sysfs;
 mod validator;
 
 /// spdm_create() - Allocate SPDM session
lib/rspdm/req-sysfs.c new file +98 -0

98 lines not on this page · raw message

lib/rspdm/spdm.h new file +17 -0
new file mode 100644
index 000000000000..43ef56a073c0
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * DMTF Security Protocol and Data Model (SPDM)
+ * https://www.dmtf.org/dsp/DSP0274
+ *
+ * Copyright (C) 2021-22 Huawei
+ *     Jonathan Cameron <[email protected]>
+ *
+ * Copyright (C) 2022-25 Intel Corporation
+ */
+
+#ifndef _LIB_SPDM_H_
+#define _LIB_SPDM_H_
+
+int spdm_chall(struct spdm_state *spdm_state);
+
+#endif /* _LIB_SPDM_H_ */
lib/rspdm/sysfs.rs new file +38 -0
new file mode 100644
index 000000000000..d0e7f6b3de40
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Copyright (C) 2024 Western Digital
+
+//! Rust sysfs helper functions
+//!
+//! Rust implementation of the DMTF Security Protocol and Data Model (SPDM)
+//! <https://www.dmtf.org/dsp/DSP0274>
+
+use crate::SpdmState;
+use kernel::prelude::*;
+use kernel::{bindings, str::CString};
+
+/// Helper function for the sysfs `authenticated_show()`.
+#[no_mangle]
+pub extern "C" fn rust_authenticated_show(spdm_state: *mut SpdmState, buf: *mut u8) -> isize {
+    // SAFETY: The opaque pointer will be directly from the `spdm_create()`
+    // function, so we can safely reconstruct it.
+    let state = unsafe { KBox::from_raw(spdm_state) };
+
+    let fmt = match CString::try_from_fmt(fmt!("{}\n", state.authenticated)) {
+        Ok(f) => f,
+        Err(_e) => return 0,
+    };
+
+    // SAFETY: Calling a kernel C function with valid arguments
+    unsafe { bindings::sysfs_emit(buf, fmt.as_char_ptr()) as isize }
+}
+
+/// Helper function to trigger a SPDM challenge
+#[no_mangle]
+pub unsafe extern "C" fn spdm_chall(state: &'static mut SpdmState) -> c_int {
+    if let Err(e) = state.challenge(state.provisioned_slots.trailing_zeros() as u8, false) {
+        return e.to_errno() as c_int;
+    }
+
+    0
+}
Signature · 1 line
2.52.0
#58 lore raw

RFC v3 26/27 rust: add bindings for hash_info

In reply to [email protected]

From: Alistair Francis <[email protected]>

C code can get the hash_digest_size by indexing into the global hash_digest_size array. We can't do that in the Rust code, so let's add a Rust helper function to allow us to get the hash_digest_size.

---
 rust/helpers/hash_info.c | 8 ++++++++
 rust/helpers/helpers.c   | 1 +
 2 files changed, 9 insertions(+)
 create mode 100644 rust/helpers/hash_info.c
rust/helpers/hash_info.c new file +8 -0
new file mode 100644
index 000000000000..d2cbc08b57d1
@@ -0,0 +1,8 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <crypto/hash_info.h>
+
+int rust_helper_get_hash_digest_size(uint32_t offset)
+{
+	return hash_digest_size[offset];
+}
rust/helpers/helpers.c +1 -0
index 4b08b4f1d3a3..34db9eb5e43e 100644
@@ -31,6 +31,7 @@
 #include "drm.c"
 #include "err.c"
 #include "hash.c"
+#include "hash_info.c"
 #include "irq.c"
 #include "fs.c"
 #include "io.c"
Signature · 1 line
2.52.0
#59 lore raw

RFC v3 27/27 rspdm: Multicast received signatures via netlink

In reply to [email protected]

From: Lukas Wunner <[email protected]>

This is based on Lukas's patch from [1]. This exposes all of the SPDM information to userspace via netlink. This includes the certificate chain and communication transcript.

1: https://github.com/l1k/linux/commit/fe90b5700ee9bc595a21c030192eac4060eaeae1

Cc James Bottomley <[email protected]>
Cc Jérôme Glisse <[email protected]>
Cc Jason Gunthorpe <[email protected]>

[ Change by AF:
- Fixup yaml spec issues
- Include certificate chain
- Port to support Rust SPDM
]

---
 Documentation/netlink/specs/spdm.yaml | 136 ++++++++++++++++++
 include/uapi/linux/spdm_netlink.h     |  49 +++++++
 lib/rspdm/Makefile                    |   1 +
 lib/rspdm/netlink-autogen.c           |  33 +++++
 lib/rspdm/netlink-autogen.h           |  22 +++
 lib/rspdm/req-netlink.c               | 197 ++++++++++++++++++++++++++
 lib/rspdm/spdm.h                      |  28 ++++
 lib/rspdm/state.rs                    |  52 +++++++
 rust/bindings/bindings_helper.h       |   4 +
 9 files changed, 522 insertions(+)
 create mode 100644 Documentation/netlink/specs/spdm.yaml
 create mode 100644 include/uapi/linux/spdm_netlink.h
 create mode 100644 lib/rspdm/netlink-autogen.c
 create mode 100644 lib/rspdm/netlink-autogen.h
 create mode 100644 lib/rspdm/req-netlink.c
Documentation/netlink/specs/spdm.yaml new file +136 -0
new file mode 100644
index 000000000000..1eb349bdc0d1
@@ -0,0 +1,136 @@
+# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
+
+name: spdm
+
+doc: |
+  DMTF Security Protocol and Data Model (SPDM)
+  https://www.dmtf.org/dsp/DSP0274
+
+protocol: genetlink
+
+uapi-header: linux/spdm_netlink.h
+
+definitions:
+  -
+    type: enum
+    name: spdm-reqrsp-code
+    doc: SPDM request or response code of a signed message (SPDM 1.0.0 table 4)
+    entries:
+      -
+        name: challenge-auth
+      -
+        name: endpoint-info
+      -
+        name: measurements
+      -
+        name: key-exchange-rsp
+      -
+        name: finish
+  -
+    type: enum
+    name: hash-algo
+    doc: SPDM-supported hash algorithm (SPDM 1.0.0 table 13)
+    entries:
+      -
+        name: sha256
+      -
+        name: sha384
+      -
+        name: sha512
+      -
+        name: sha3-256
+      -
+        name: sha3-384
+      -
+        name: sha3-512
+    header: uapi/linux/hash_info.h
+
+attribute-sets:
+  -
+    name: sig
+    doc: |
+      Signature received from a device, together with all ancillary data
+      needed for re-verification.
+
+      Meant for remote attestation services which do not trust the kernel
+      to have verified the signature correctly or which want to apply
+      policy constraints of their own.
+    attributes:
+      -
+        name: device
+        doc: |
+          Path under sysfs of the device generating the signature.
+        type: string
+      -
+        name: rsp-code
+        doc: |
+          SPDM response code of the message containing the signature,
+          to determine what kind of event caused signature generation.
+          Equivalent to the "context" string of SPDM 1.2.0 sec 15,
+          but represented numerically for easier parsing.
+        type: u8
+        enum: spdm-reqrsp-code
+      -
+        name: slot
+        doc: |
+          Certificate slot used for signature generation.  Note that
+          if the slot has since been provisioned with a different
+          certificate chain, re-verification of the signature will fail.
+        type: u8
+      -
+        name: hash-algo
+        doc: |
+          Hash algorithm used for signature generation.
+        type: u16
+        enum: hash-algo
+      -
+        name: sig-offset
+        doc: |
+          Offset of signature in @transcript.  The signature is located
+          at the end of @transcript, hence its size equals @transcript
+          size minus this offset.
+        type: u32
+      -
+        name: req-nonce-offset
+        doc: |
+          Offset of 32 byte nonce chosen by requester in @transcript.
+          Allows remote attestation services to verify freshness
+          (uniqueness) and entropy adequacy of the nonce.
+        type: u32
+      -
+        name: rsp-nonce-offset
+        doc: |
+          Offset of 32 byte nonce chosen by responder in @transcript.
+          Allows remote attestation services to verify freshness
+          (uniqueness) and entropy adequacy of the nonce.
+        type: u32
+      -
+        name: combined-spdm-prefix
+        doc: |
+          Only included in the message with SPDM version 1.2.0 or newer.
+        type: binary
+        checks:
+          exact-len: 100
+      -
+        name: certificate-chain
+        type: binary
+      -
+        name: transcript
+        type: binary
+        multi-attr: true
+
+operations:
+  list:
+    -
+      name: sig
+      doc: Signature event
+      attribute-set: sig
+      event:
+        attributes:
+          - sig
+      mcgrp: sig
+
+mcast-groups:
+  list:
+    -
+      name: sig
include/uapi/linux/spdm_netlink.h new file +49 -0

49 lines not on this page · raw message

lib/rspdm/Makefile +1 -0
index f15b1437196b..2f29d0a62c1e 100644
@@ -8,4 +8,5 @@
 obj-$(CONFIG_RSPDM) += spdm.o
 
 spdm-y := lib.o
+spdm-$(CONFIG_NET) += req-netlink.o netlink-autogen.o
 spdm-$(CONFIG_SYSFS) += req-sysfs.o
lib/rspdm/netlink-autogen.c new file +33 -0
new file mode 100644
index 000000000000..4dc950133514
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
+/* Do not edit directly, auto-generated from: */
+/*	Documentation/netlink/specs/spdm.yaml */
+/* YNL-GEN kernel source */
+/* To regenerate run: tools/net/ynl/ynl-regen.sh */
+
+#include <net/netlink.h>
+#include <net/genetlink.h>
+
+#include "netlink-autogen.h"
+
+#include <uapi/linux/spdm_netlink.h>
+#include <uapi/linux/hash_info.h>
+
+/* Ops table for spdm */
+static const struct genl_split_ops spdm_nl_ops[] = {
+};
+
+static const struct genl_multicast_group spdm_nl_mcgrps[] = {
+	[SPDM_NLGRP_SIG] = { "sig", },
+};
+
+struct genl_family spdm_nl_family __ro_after_init = {
+	.name		= SPDM_FAMILY_NAME,
+	.version	= SPDM_FAMILY_VERSION,
+	.netnsok	= true,
+	.parallel_ops	= true,
+	.module		= THIS_MODULE,
+	.split_ops	= spdm_nl_ops,
+	.n_split_ops	= ARRAY_SIZE(spdm_nl_ops),
+	.mcgrps		= spdm_nl_mcgrps,
+	.n_mcgrps	= ARRAY_SIZE(spdm_nl_mcgrps),
+};
lib/rspdm/netlink-autogen.h new file +22 -0
new file mode 100644
index 000000000000..2797d194604f
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
+/* Do not edit directly, auto-generated from: */
+/*	Documentation/netlink/specs/spdm.yaml */
+/* YNL-GEN kernel header */
+/* To regenerate run: tools/net/ynl/ynl-regen.sh */
+
+#ifndef _LINUX_SPDM_GEN_H
+#define _LINUX_SPDM_GEN_H
+
+#include <net/netlink.h>
+#include <net/genetlink.h>
+
+#include <uapi/linux/spdm_netlink.h>
+#include <uapi/linux/hash_info.h>
+
+enum {
+	SPDM_NLGRP_SIG,
+};
+
+extern struct genl_family spdm_nl_family;
+
+#endif /* _LINUX_SPDM_GEN_H */
lib/rspdm/req-netlink.c new file +197 -0

197 lines not on this page · raw message

lib/rspdm/spdm.h +28 -0
index 43ef56a073c0..36bc1b47a796 100644
@@ -12,6 +12,34 @@
 #ifndef _LIB_SPDM_H_
 #define _LIB_SPDM_H_
 
+#include <uapi/linux/hash_info.h>
+
+#ifdef CONFIG_NET
+int spdm_netlink_sig_event(struct device *dev,
+			   u8 version,
+			   const void *transcript,
+			   size_t transcript_len,
+			   const void *cert_chain,
+			   size_t cert_chain_len,
+			   enum hash_algo base_hash_alg,
+			   size_t sig_len,
+			   int rsp_code, u8 slot,
+			   size_t req_nonce_off, size_t rsp_nonce_off,
+			   const char *spdm_context);
+#else
+static inline int spdm_netlink_sig_event(struct device *dev,
+			   u8 version,
+			   const void *transcript,
+			   size_t transcript_len,
+			   const void *cert_chain,
+			   size_t cert_chain_len,
+			   enum hash_algo base_hash_alg,
+			   size_t sig_len,
+			   int rsp_code, u8 slot,
+			   size_t req_nonce_off, size_t rsp_nonce_off,
+			   const char *spdm_context) { return 0; }
+#endif
+
 int spdm_chall(struct spdm_state *spdm_state);
 
 #endif /* _LIB_SPDM_H_ */
lib/rspdm/state.rs +52 -0

64 lines not on this page · raw message

rust/bindings/bindings_helper.h +4 -0
index 35e4378fb9dc..64326e5f2490 100644
@@ -156,3 +156,7 @@ const vm_flags_t RUST_CONST_HELPER_VM_NOHUGEPAGE = VM_NOHUGEPAGE;
 #include "../../drivers/android/binder/rust_binder_events.h"
 #include "../../drivers/android/binder/page_range_helper.h"
 #endif
+
+#if IS_ENABLED(CONFIG_RSPDM)
+#include "../../lib/rspdm/spdm.h"
+#endif
Signature · 1 line
2.52.0
maintainer
#60 lore raw

Re: [RFC v3 27/27] rspdm: Multicast received signatures via netlink

Quoted 5 lines · On Wed, Feb 11, 2026 at 01:29:34PM +1000, [email protected] wrote:

This is based on Lukas's patch from [1]. This exposes all of the SPDM information to userspace via netlink. This includes the certificate chain and communication transcript.

1: https://github.com/l1k/linux/commit/fe90b5700ee9bc595a21c030192eac4060eaeae1

Just a heads-up, the above-linked commit is from Dec 11, but I spent the calm days around Christmas and New Year testing and improving the SPDM netlink code. I ended up squashing quite a few bugs and refactoring things for clarity. Latest version is on my development branch:

https://github.com/l1k/linux/commits/doe

The branch contains a separate commit introducing a nla_put_blob() helper to add a zero-copy blob attribute to an skb. And the SPDM netlink code uses that helper instead of open coding its contents.

The SPDM netlink code can be tested with:

tools/net/ynl/pyynl/cli.py \
     --spec Documentation/netlink/specs/spdm.yaml \
     --subscribe sig  --sleep 10

Just reauthenticate the device and the signature with ancillary data gets dumped via netlink.

The commit message and kernel-doc of the SPDM netlink commit is still a WIP because right after the calm of the holidays I got swamped (again) with AER work that my employeer prioritizes. But the code is now well-tested.

Thanks,

Lukas

#61 lore raw
In reply to [email protected]
Quoted 1 line · alistair23@ wrote:

From: Alistair Francis <[email protected]>

Hi Alistair, quite a bit to digest here and details to dig into. Before getting into that, I will say that at a broad strokes level, no immune response to the core proposal of depending on a Rust SPDM library and forgoing a C SPDM library.

Most of that allergy relief comes from how this organizes the C to Rust interactions. The core SPDM implementation calls out to C for the presentation layer (Netlink) or is invoked by sysfs. That makes it amenable for sharing those presentation mechanics.

Specifically, my primary concern is integration and refactoring opportunities with the PCI TSM implementation [1]. The PCI TSM case should use the same uABI transport for requesting + conveying device certificate chain, SPDM transcript, and device measurements as PCI CMA. Note that in the TSM case the SPDM implementation is in platform firmware and will bypass this library. The TSM SPDM session is mutually exclusive with the CMA SPDM session.

[1]: http://lore.kernel.org/[email protected]

Quoted 16 lines

Security Protocols and Data Models (SPDM) [1] is used for authentication, attestation and key exchange. SPDM is generally used over a range of transports, such as PCIe, MCTP/SMBus/I3C, ATA, SCSI, NVMe or TCP.

From the kernels perspective SPDM is used to authenticate and attest devices. In this threat model a device is considered untrusted until it can be verified by the kernel and userspace using SPDM. As such SPDM data is untrusted data that can be mallicious.

The SPDM specification is also complex, with the 1.2.1 spec being almost 200 pages and the 1.3.0 spec being almost 250 pages long.

As such we have the kernel parsing untrusted responses from a complex specification, which sounds like a possible exploit vector. This is the type of place where Rust excels!

Quoted 6 lines

This series implements a SPDM requester in Rust.

This is very similar to Lukas' implementation [2]. This series includes patches and files from Lukas' C SPDM implementation, which isn't in mainline.

This is a standalone series and doesn't depend on Lukas' implementation.

So, I *am* allergic to how this series references Lukas' work by pointing to random points in his personal git tree. I trust that was done for RFC purposes, but it would have helped to call that out in the changelog and set expectations about the ideal path to coordinate with that work.

Quoted 7 lines

To help with maintaining compatibility it's designed in a way to match Lukas' design and the state struct stores the same information, although in a Rust struct instead of the original C one.

This series exposes the data to userspace via netlink, with a single sysfs atrribute to allow reauthentication.

Quoted 6 lines

All of the patches are included in the RFC, as it depends on some patches that aren't upstream yet.

Now that Rust is no longer experimental I have picked this back up. If the community is generally on board with a Rust implementation I can work on sending a non-RFC version and push towards getting that merged.

As long as this stays explicitly designed to minimize exposure to "refactor across language boundary" events (as initially seems to be the case), then it seems workable.

Quoted 23 lines

The entire tree can be seen here: https://github.com/alistair23/linux/tree/alistair/spdm-rust

I'm testing the netlink data by running the following

```shell
cargo run -- --qemu-server response

qemu-system-x86_64 \
-nic none \
-object rng-random,filename=/dev/urandom,id=rng0 \
-device virtio-rng-pci,rng=rng0 \
-drive file=deploy/images/qemux86-64/core-image-pcie-qemux86-64.rootfs.ext4,if=virtio,format=raw \
-usb -device usb-tablet -usb -device usb-kbd \
-cpu Skylake-Client \
-machine q35,i8042=off \
-smp 4 -m 2G \
-drive file=blknvme,if=none,id=mynvme,format=raw \
-device nvme,drive=mynvme,serial=deadbeef,spdm_port=2323,spdm_trans=doe \
-snapshot \
-serial mon:stdio -serial null -nographic \
-kernel deploy/images/qemux86-64/bzImage \
-append 'root=/dev/vda rw console=ttyS0 console=ttyS1 oprofile.timer=1 tsc=reliable no_timer_check rcupdate.rcu_expedited=1 swiotlb=0 '

Quoted 3 lines

spdm_utils identify &
sleep 1
echo re > /sys/devices/pci0000:00/0000:00:03.0/authenticated

So this is where it will collide with TSM that also emits an authenticated attribute. See Documentation/ABI/testing/sysfs-bus-pci.

The rough plan Lukas and I worked out is that switching between TSM and CMA based authentication would use sysfs visibility to coordinate. I.e. TSM to CMA conversion hides the TSM "authenticated" attribute and unhides the CMA attribute of the same name.

The most significant unsolved point of contention between TSM and CMA is the policy on when authentication is mandated and the driver probe policy. The proposed model for enforcing device security for Confidential Computing is make it completely amenable to userspace policy. Draft details here [2] to be refreshed "soon" when I send out the next version of that.

[2]: http://lore.kernel.org/[email protected]

To be clear I am ok if there is an incremental option to have auto_cma and/or auto_tsm that arranges for authentication or link encryption to happen without asking. I take issue with auto_cma being the only hard coded option.

#62 lore raw
Quoted 9 lines · On Thu, Feb 12, 2026 at 3:56 PM <[email protected]> wrote:
Quoted 1 line · alistair23@ wrote:

From: Alistair Francis <[email protected]>

Hi Alistair, quite a bit to digest here and details to dig into. Before getting into that, I will say that at a broad strokes level, no immune response to the core proposal of depending on a Rust SPDM library and forgoing a C SPDM library.

Quoted 4 lines

Most of that allergy relief comes from how this organizes the C to Rust interactions. The core SPDM implementation calls out to C for the presentation layer (Netlink) or is invoked by sysfs. That makes it amenable for sharing those presentation mechanics.

Great! That was my goal.

Also, with netlink some of the C code is autogenerated, so a Rust version would be tricky to do well.

Quoted 5 lines

Specifically, my primary concern is integration and refactoring opportunities with the PCI TSM implementation [1]. The PCI TSM case should use the same uABI transport for requesting + conveying device certificate chain, SPDM transcript, and device measurements as PCI CMA.

I do think broadly that they can use the same uABI. I'm sure there will be refactoring required though.

Quoted 3 lines

Note that in the TSM case the SPDM implementation is in platform firmware and will bypass this library. The TSM SPDM session is mutually exclusive with the CMA SPDM session.

As long as we can get the details from the platform firmware (which I assume is possible) then we can present them to userspace via the same netlink ABI.

Quoted 24 lines

[1]: http://lore.kernel.org/[email protected]

Quoted 21 lines

Security Protocols and Data Models (SPDM) [1] is used for authentication, attestation and key exchange. SPDM is generally used over a range of transports, such as PCIe, MCTP/SMBus/I3C, ATA, SCSI, NVMe or TCP.

From the kernels perspective SPDM is used to authenticate and attest devices. In this threat model a device is considered untrusted until it can be verified by the kernel and userspace using SPDM. As such SPDM data is untrusted data that can be mallicious.

The SPDM specification is also complex, with the 1.2.1 spec being almost 200 pages and the 1.3.0 spec being almost 250 pages long.

As such we have the kernel parsing untrusted responses from a complex specification, which sounds like a possible exploit vector. This is the type of place where Rust excels!

This series implements a SPDM requester in Rust.

This is very similar to Lukas' implementation [2]. This series includes patches and files from Lukas' C SPDM implementation, which isn't in mainline.

Quoted 7 lines
Quoted 1 line

This is a standalone series and doesn't depend on Lukas' implementation.

So, I *am* allergic to how this series references Lukas' work by pointing to random points in his personal git tree. I trust that was done for RFC purposes, but it would have helped to call that out in the changelog and set expectations about the ideal path to coordinate with that work.

I was trying to convey that a lot of this work was done by Lukas, I didn't want to appear to be claiming otherwise or stepping on toes, hence the links to his git tree.

As for an ideal path forward, what I think would be really useful is if Lukas' prep patches get merged. Lukas sent them in his original series and they have been reviewed there. I have picked them up here to help with testing, but they could go upstream today and set the ground work for either a C or Rust implementation.

That would reduce the size of any future series as well.

From there it would be worth thinking about a C or Rust implementation and maybe just focus on one and work towards that.

Quoted 46 lines
Quoted 8 lines

To help with maintaining compatibility it's designed in a way to match Lukas' design and the state struct stores the same information, although in a Rust struct instead of the original C one.

This series exposes the data to userspace via netlink, with a single sysfs atrribute to allow reauthentication.

Quoted 6 lines

All of the patches are included in the RFC, as it depends on some patches that aren't upstream yet.

Now that Rust is no longer experimental I have picked this back up. If the community is generally on board with a Rust implementation I can work on sending a non-RFC version and push towards getting that merged.

As long as this stays explicitly designed to minimize exposure to "refactor across language boundary" events (as initially seems to be the case), then it seems workable.

Quoted 23 lines

The entire tree can be seen here: https://github.com/alistair23/linux/tree/alistair/spdm-rust

I'm testing the netlink data by running the following

```shell
cargo run -- --qemu-server response

qemu-system-x86_64 \
-nic none \
-object rng-random,filename=/dev/urandom,id=rng0 \
-device virtio-rng-pci,rng=rng0 \
-drive file=deploy/images/qemux86-64/core-image-pcie-qemux86-64.rootfs.ext4,if=virtio,format=raw \
-usb -device usb-tablet -usb -device usb-kbd \
-cpu Skylake-Client \
-machine q35,i8042=off \
-smp 4 -m 2G \
-drive file=blknvme,if=none,id=mynvme,format=raw \
-device nvme,drive=mynvme,serial=deadbeef,spdm_port=2323,spdm_trans=doe \
-snapshot \
-serial mon:stdio -serial null -nographic \
-kernel deploy/images/qemux86-64/bzImage \
-append 'root=/dev/vda rw console=ttyS0 console=ttyS1 oprofile.timer=1 tsc=reliable no_timer_check rcupdate.rcu_expedited=1 swiotlb=0 '

Quoted 3 lines

spdm_utils identify &
sleep 1
echo re > /sys/devices/pci0000:00/0000:00:03.0/authenticated

Quoted 7 lines

So this is where it will collide with TSM that also emits an authenticated attribute. See Documentation/ABI/testing/sysfs-bus-pci.

The rough plan Lukas and I worked out is that switching between TSM and CMA based authentication would use sysfs visibility to coordinate. I.e. TSM to CMA conversion hides the TSM "authenticated" attribute and unhides the CMA attribute of the same name.

That sounds fine to me

Quoted 8 lines

The most significant unsolved point of contention between TSM and CMA is the policy on when authentication is mandated and the driver probe policy. The proposed model for enforcing device security for Confidential Computing is make it completely amenable to userspace policy. Draft details here [2] to be refreshed "soon" when I send out the next version of that.

Quoted 6 lines

[2]: http://lore.kernel.org/[email protected]

To be clear I am ok if there is an incremental option to have auto_cma and/or auto_tsm that arranges for authentication or link encryption to happen without asking. I take issue with auto_cma being the only hard coded option.

Ok, so we would need a way for users to select TSM or CMA and specify policy information?

Alistair

#63 lore raw
In reply to [email protected]
Quoted 98 lines · On Thu, Feb 12, 2026 at 3:56 PM <[email protected]> wrote:
Quoted 1 line · alistair23@ wrote:

From: Alistair Francis <[email protected]>

Hi Alistair, quite a bit to digest here and details to dig into. Before getting into that, I will say that at a broad strokes level, no immune response to the core proposal of depending on a Rust SPDM library and forgoing a C SPDM library.

Most of that allergy relief comes from how this organizes the C to Rust interactions. The core SPDM implementation calls out to C for the presentation layer (Netlink) or is invoked by sysfs. That makes it amenable for sharing those presentation mechanics.

Specifically, my primary concern is integration and refactoring opportunities with the PCI TSM implementation [1]. The PCI TSM case should use the same uABI transport for requesting + conveying device certificate chain, SPDM transcript, and device measurements as PCI CMA. Note that in the TSM case the SPDM implementation is in platform firmware and will bypass this library. The TSM SPDM session is mutually exclusive with the CMA SPDM session.

[1]: http://lore.kernel.org/[email protected]

Quoted 16 lines

Security Protocols and Data Models (SPDM) [1] is used for authentication, attestation and key exchange. SPDM is generally used over a range of transports, such as PCIe, MCTP/SMBus/I3C, ATA, SCSI, NVMe or TCP.

From the kernels perspective SPDM is used to authenticate and attest devices. In this threat model a device is considered untrusted until it can be verified by the kernel and userspace using SPDM. As such SPDM data is untrusted data that can be mallicious.

The SPDM specification is also complex, with the 1.2.1 spec being almost 200 pages and the 1.3.0 spec being almost 250 pages long.

As such we have the kernel parsing untrusted responses from a complex specification, which sounds like a possible exploit vector. This is the type of place where Rust excels!

Quoted 6 lines

This series implements a SPDM requester in Rust.

This is very similar to Lukas' implementation [2]. This series includes patches and files from Lukas' C SPDM implementation, which isn't in mainline.

This is a standalone series and doesn't depend on Lukas' implementation.

So, I *am* allergic to how this series references Lukas' work by pointing to random points in his personal git tree. I trust that was done for RFC purposes, but it would have helped to call that out in the changelog and set expectations about the ideal path to coordinate with that work.

Quoted 7 lines

To help with maintaining compatibility it's designed in a way to match Lukas' design and the state struct stores the same information, although in a Rust struct instead of the original C one.

This series exposes the data to userspace via netlink, with a single sysfs atrribute to allow reauthentication.

Quoted 6 lines

All of the patches are included in the RFC, as it depends on some patches that aren't upstream yet.

Now that Rust is no longer experimental I have picked this back up. If the community is generally on board with a Rust implementation I can work on sending a non-RFC version and push towards getting that merged.

As long as this stays explicitly designed to minimize exposure to "refactor across language boundary" events (as initially seems to be the case), then it seems workable.

Quoted 23 lines

The entire tree can be seen here: https://github.com/alistair23/linux/tree/alistair/spdm-rust

I'm testing the netlink data by running the following

```shell
cargo run -- --qemu-server response

qemu-system-x86_64 \
-nic none \
-object rng-random,filename=/dev/urandom,id=rng0 \
-device virtio-rng-pci,rng=rng0 \
-drive file=deploy/images/qemux86-64/core-image-pcie-qemux86-64.rootfs.ext4,if=virtio,format=raw \
-usb -device usb-tablet -usb -device usb-kbd \
-cpu Skylake-Client \
-machine q35,i8042=off \
-smp 4 -m 2G \
-drive file=blknvme,if=none,id=mynvme,format=raw \
-device nvme,drive=mynvme,serial=deadbeef,spdm_port=2323,spdm_trans=doe \
-snapshot \
-serial mon:stdio -serial null -nographic \
-kernel deploy/images/qemux86-64/bzImage \
-append 'root=/dev/vda rw console=ttyS0 console=ttyS1 oprofile.timer=1 tsc=reliable no_timer_check rcupdate.rcu_expedited=1 swiotlb=0 '

Quoted 3 lines

spdm_utils identify &
sleep 1
echo re > /sys/devices/pci0000:00/0000:00:03.0/authenticated

Quoted 7 lines

So this is where it will collide with TSM that also emits an authenticated attribute. See Documentation/ABI/testing/sysfs-bus-pci.

The rough plan Lukas and I worked out is that switching between TSM and CMA based authentication would use sysfs visibility to coordinate. I.e. TSM to CMA conversion hides the TSM "authenticated" attribute and unhides the CMA attribute of the same name.

That seems straightforward and is already documented upstream as well, so that's pretty easy.

Quoted 9 lines

The most significant unsolved point of contention between TSM and CMA is the policy on when authentication is mandated and the driver probe policy. The proposed model for enforcing device security for Confidential Computing is make it completely amenable to userspace policy. Draft details here [2] to be refreshed "soon" when I send out the next version of that.

[2]: http://lore.kernel.org/[email protected]

CMA will eventually need to support some sort of drive probe policy as well, but that can wait until later and isn't going to be dealt with in this series.

Quoted 5 lines

To be clear I am ok if there is an incremental option to have auto_cma and/or auto_tsm that arranges for authentication or link encryption to happen without asking. I take issue with auto_cma being the only hard coded option.

I have been working through all of the comments and discussions and I think I have addressed everything, except for this one.

To summerise, is the high level issue is how do we know if we should use CMA or TSM?

Do you have any more thoughts on this?

Alistair

#64 lore raw
Quoted 131 lines · Alistair Francis
Quoted 98 lines · On Thu, Feb 12, 2026 at 3:56 PM <[email protected]> wrote:
Quoted 1 line · alistair23@ wrote:

From: Alistair Francis <[email protected]>

Hi Alistair, quite a bit to digest here and details to dig into. Before getting into that, I will say that at a broad strokes level, no immune response to the core proposal of depending on a Rust SPDM library and forgoing a C SPDM library.

Most of that allergy relief comes from how this organizes the C to Rust interactions. The core SPDM implementation calls out to C for the presentation layer (Netlink) or is invoked by sysfs. That makes it amenable for sharing those presentation mechanics.

Specifically, my primary concern is integration and refactoring opportunities with the PCI TSM implementation [1]. The PCI TSM case should use the same uABI transport for requesting + conveying device certificate chain, SPDM transcript, and device measurements as PCI CMA. Note that in the TSM case the SPDM implementation is in platform firmware and will bypass this library. The TSM SPDM session is mutually exclusive with the CMA SPDM session.

[1]: http://lore.kernel.org/[email protected]

Quoted 16 lines

Security Protocols and Data Models (SPDM) [1] is used for authentication, attestation and key exchange. SPDM is generally used over a range of transports, such as PCIe, MCTP/SMBus/I3C, ATA, SCSI, NVMe or TCP.

From the kernels perspective SPDM is used to authenticate and attest devices. In this threat model a device is considered untrusted until it can be verified by the kernel and userspace using SPDM. As such SPDM data is untrusted data that can be mallicious.

The SPDM specification is also complex, with the 1.2.1 spec being almost 200 pages and the 1.3.0 spec being almost 250 pages long.

As such we have the kernel parsing untrusted responses from a complex specification, which sounds like a possible exploit vector. This is the type of place where Rust excels!

Quoted 6 lines

This series implements a SPDM requester in Rust.

This is very similar to Lukas' implementation [2]. This series includes patches and files from Lukas' C SPDM implementation, which isn't in mainline.

This is a standalone series and doesn't depend on Lukas' implementation.

So, I *am* allergic to how this series references Lukas' work by pointing to random points in his personal git tree. I trust that was done for RFC purposes, but it would have helped to call that out in the changelog and set expectations about the ideal path to coordinate with that work.

Quoted 7 lines

To help with maintaining compatibility it's designed in a way to match Lukas' design and the state struct stores the same information, although in a Rust struct instead of the original C one.

This series exposes the data to userspace via netlink, with a single sysfs atrribute to allow reauthentication.

Quoted 6 lines

All of the patches are included in the RFC, as it depends on some patches that aren't upstream yet.

Now that Rust is no longer experimental I have picked this back up. If the community is generally on board with a Rust implementation I can work on sending a non-RFC version and push towards getting that merged.

As long as this stays explicitly designed to minimize exposure to "refactor across language boundary" events (as initially seems to be the case), then it seems workable.

Quoted 23 lines

The entire tree can be seen here: https://github.com/alistair23/linux/tree/alistair/spdm-rust

I'm testing the netlink data by running the following

```shell
cargo run -- --qemu-server response

qemu-system-x86_64 \
-nic none \
-object rng-random,filename=/dev/urandom,id=rng0 \
-device virtio-rng-pci,rng=rng0 \
-drive file=deploy/images/qemux86-64/core-image-pcie-qemux86-64.rootfs.ext4,if=virtio,format=raw \
-usb -device usb-tablet -usb -device usb-kbd \
-cpu Skylake-Client \
-machine q35,i8042=off \
-smp 4 -m 2G \
-drive file=blknvme,if=none,id=mynvme,format=raw \
-device nvme,drive=mynvme,serial=deadbeef,spdm_port=2323,spdm_trans=doe \
-snapshot \
-serial mon:stdio -serial null -nographic \
-kernel deploy/images/qemux86-64/bzImage \
-append 'root=/dev/vda rw console=ttyS0 console=ttyS1 oprofile.timer=1 tsc=reliable no_timer_check rcupdate.rcu_expedited=1 swiotlb=0 '

Quoted 3 lines

spdm_utils identify &
sleep 1
echo re > /sys/devices/pci0000:00/0000:00:03.0/authenticated

Quoted 7 lines

So this is where it will collide with TSM that also emits an authenticated attribute. See Documentation/ABI/testing/sysfs-bus-pci.

The rough plan Lukas and I worked out is that switching between TSM and CMA based authentication would use sysfs visibility to coordinate. I.e. TSM to CMA conversion hides the TSM "authenticated" attribute and unhides the CMA attribute of the same name.

That seems straightforward and is already documented upstream as well, so that's pretty easy.

Quoted 9 lines

The most significant unsolved point of contention between TSM and CMA is the policy on when authentication is mandated and the driver probe policy. The proposed model for enforcing device security for Confidential Computing is make it completely amenable to userspace policy. Draft details here [2] to be refreshed "soon" when I send out the next version of that.

[2]: http://lore.kernel.org/[email protected]

CMA will eventually need to support some sort of drive probe policy as well, but that can wait until later and isn't going to be dealt with in this series.

Quoted 5 lines

To be clear I am ok if there is an incremental option to have auto_cma and/or auto_tsm that arranges for authentication or link encryption to happen without asking. I take issue with auto_cma being the only hard coded option.

Quoted 7 lines

I have been working through all of the comments and discussions and I think I have addressed everything, except for this one.

To summerise, is the high level issue is how do we know if we should use CMA or TSM?

Do you have any more thoughts on this?

+ Dan's Kernel.org email

Alistair

#65 lore raw
In reply to Alistair Francis
Quoted 1 line · Alistair Francis

On Thu, Feb 12, 2026 at 3:56 PM <[email protected]> wrote:

[..]

Quoted 11 lines
Quoted 8 lines

So this is where it will collide with TSM that also emits an authenticated attribute. See Documentation/ABI/testing/sysfs-bus-pci.

The rough plan Lukas and I worked out is that switching between TSM and CMA based authentication would use sysfs visibility to coordinate. I.e. TSM to CMA conversion hides the TSM "authenticated" attribute and unhides the CMA attribute of the same name.

That seems straightforward and is already documented upstream as well, so that's pretty easy.

Later in the thread I proposed an alternative that instead of supporting 2 flavors of uapi through "authenticated", instead implement CMA as another TSM driver [1].

[1]: http://lore.kernel.org/[email protected]

Quoted 7 lines
Quoted 7 lines

The most significant unsolved point of contention between TSM and CMA is the policy on when authentication is mandated and the driver probe policy. The proposed model for enforcing device security for Confidential Computing is make it completely amenable to userspace policy. Draft details here [2] to be refreshed "soon" when I send out the next version of that.

Quoted 5 lines
Quoted 1 line

[2]: http://lore.kernel.org/[email protected]

CMA will eventually need to support some sort of drive probe policy as well, but that can wait until later and isn't going to be dealt with in this series.

Makes sense, and Greg wants to a see a more universal "device trust" mechanism for this. This also means CMA as a TSM driver gets that mechanism "for free" when the PCI/TSM effort moves it forward.

Quoted 5 lines
Quoted 4 lines

To be clear I am ok if there is an incremental option to have auto_cma and/or auto_tsm that arranges for authentication or link encryption to happen without asking. I take issue with auto_cma being the only hard coded option.

Quoted 7 lines

I have been working through all of the comments and discussions and I think I have addressed everything, except for this one.

To summerise, is the high level issue is how do we know if we should use CMA or TSM?

Do you have any more thoughts on this?

So most of the thinking is in that [1] above, and the new mechanism would just be to auto-connect to the first TSM driver that appears, or auto-connect to the TSM driver with the most capbility. For example, auto-connect to the CMA-TSM at initial discovery, switch to Platform-TSM if the device additionally supports IDE.

maintainer
#66 lore raw
Quoted 5 lines · Dan Williams

Later in the thread I proposed an alternative that instead of supporting 2 flavors of uapi through "authenticated", instead implement CMA as another TSM driver [1].

[1]: http://lore.kernel.org/[email protected]

Please keep in mind though that CMA is just the PCIe adaption of SPDM, SPDM is not only needed for PCIe but also SCSI, ATA and possibly others and so implementing CMA as a TSM driver must not preclude use of SPDM in other subsystems.

Thanks,

Lukas

#67 lore raw
Quoted 6 lines · Lukas Wunner
Quoted 4 lines · Dan Williams

Later in the thread I proposed an alternative that instead of supporting 2 flavors of uapi through "authenticated", instead implement CMA as another TSM driver [1].

Quoted 6 lines
Quoted 1 line

[1]: http://lore.kernel.org/[email protected]

Please keep in mind though that CMA is just the PCIe adaption of SPDM, SPDM is not only needed for PCIe but also SCSI, ATA and possibly others and so implementing CMA as a TSM driver must not preclude use of SPDM in other subsystems.

That should be fine as the current SPDM implementation is self-contained, but thanks for raising that.

Just to make sure I'm not going in the wrong direction, the idea would be to build on

https://lore.kernel.org/all/[email protected]/

and add something like this?

```
static const struct pci_tsm_ops pci_cma_tsm_ops = {
    .link_ops = {
        .probe        = pci_cma_tsm_probe,
        .remove        = pci_cma_tsm_remove,
        .connect    = pci_cma_tsm_connect,
        .disconnect    = pci_cma_tsm_disconnect,
    },
    .refresh_evidence    = pci_cma_tsm_refresh,
};
```

The docs for struct pci_tsm_ops seem pretty TSM specific, so I just wanted to double check before going ahead.

That means all of the netlink stuff in this series can be dropped and we just use the TSM netlink (which might need some adjustments then, I'll have to double check)

Alistair

Quoted 4 lines

Thanks,

Lukas

#68 lore raw
Quoted 36 lines · Alistair Francis
Quoted 6 lines · Lukas Wunner
Quoted 4 lines · Dan Williams

Later in the thread I proposed an alternative that instead of supporting 2 flavors of uapi through "authenticated", instead implement CMA as another TSM driver [1].

Quoted 6 lines
Quoted 1 line

[1]: http://lore.kernel.org/[email protected]

Please keep in mind though that CMA is just the PCIe adaption of SPDM, SPDM is not only needed for PCIe but also SCSI, ATA and possibly others and so implementing CMA as a TSM driver must not preclude use of SPDM in other subsystems.

That should be fine as the current SPDM implementation is self-contained, but thanks for raising that.

Just to make sure I'm not going in the wrong direction, the idea would be to build on

https://lore.kernel.org/all/[email protected]/

and add something like this?

```
static const struct pci_tsm_ops pci_cma_tsm_ops = {
    .link_ops = {
        .probe        = pci_cma_tsm_probe,
        .remove        = pci_cma_tsm_remove,
        .connect    = pci_cma_tsm_connect,
        .disconnect    = pci_cma_tsm_disconnect,
    },
    .refresh_evidence    = pci_cma_tsm_refresh,
};
```
Quoted 6 lines

The docs for struct pci_tsm_ops seem pretty TSM specific, so I just wanted to double check before going ahead.

That means all of the netlink stuff in this series can be dropped and we just use the TSM netlink (which might need some adjustments then, I'll have to double check)

Right, the above looks what I was expecting.

As for netlink, Lukas is right about non CMA use cases. The netlink interface will either need to move to be a generic "device evidence" facility, not scoped to PCI/TSM, or itself become a library that different producers of SPDM material can use to export it to userspace.

#69 lore raw
Quoted 49 lines · Dan Williams (nvidia)
Quoted 48 lines · Alistair Francis
Quoted 11 lines · Lukas Wunner
Quoted 8 lines · Dan Williams

Later in the thread I proposed an alternative that instead of supporting
2 flavors of uapi through "authenticated", instead implement CMA as
another TSM driver [1].

[1]:
http://lore.kernel.org/[email protected]

Quoted 7 lines

Please keep in mind though that CMA is just the PCIe adaption of SPDM,
SPDM is not only needed for PCIe but also SCSI, ATA and possibly others
and so implementing CMA as a TSM driver must not preclude use of SPDM
in other subsystems.

That should be fine as the current SPDM implementation is self-contained, but thanks for raising that.

Just to make sure I'm not going in the wrong direction, the idea would
be to build on

https://lore.kernel.org/all/[email protected]/

and add something like this?

```
static const struct pci_tsm_ops pci_cma_tsm_ops = {
    .link_ops = {
        .probe        = pci_cma_tsm_probe,
        .remove        = pci_cma_tsm_remove,
        .connect    = pci_cma_tsm_connect,
        .disconnect    = pci_cma_tsm_disconnect,
    },
    .refresh_evidence    = pci_cma_tsm_refresh,
};
```

The docs for struct pci_tsm_ops seem pretty TSM specific, so I just
wanted to double check before going ahead.

Quoted 7 lines
Quoted 5 lines

That means all of the netlink stuff in this series can be dropped and
we just use the TSM netlink (which might need some adjustments then,
I'll have to double check)

Right, the above looks what I was expecting.

Great!

Quoted 6 lines

As for netlink, Lukas is right about non CMA use cases. The netlink interface will either need to move to be a generic "device evidence" facility, not scoped to PCI/TSM, or itself become a library that different producers of SPDM material can use to export it to userspace.

Yep, I think we have two options

1. This series approach, where SPDM measurements use their own netlink interface and each transport mechanism looks exactly the same to userspace. Which means we don't leverage TSM.

The obvious advantage is that each transport is more or less invisible to userspace, but we don't tightly integrate with the transport protocol.

2. The PCI TSM approach, where each SPDM transport mechanism has it's own transport dependent way to provide the information to userspace. PCI uses TSM, ATA/SCSI use their own thing ect.

The advantage here is we can more tightly integrate with the existing infrastructure.

I'm open to either approach

Alistair

#70 lore raw
In reply to [email protected]
Quoted 11 lines · On Wed, Feb 11, 2026 at 01:29:07PM +1000, [email protected] wrote:

From: Alistair Francis <[email protected]>

Security Protocols and Data Models (SPDM) [1] is used for authentication, attestation and key exchange. SPDM is generally used over a range of transports, such as PCIe, MCTP/SMBus/I3C, ATA, SCSI, NVMe or TCP.

Quoted 1 line

From the kernels perspective SPDM is used to authenticate and attest devices.

In this threat model a device is considered untrusted until it can be verified by the kernel and userspace using SPDM. As such SPDM data is untrusted data that can be mallicious.

Quoted 6 lines

The SPDM specification is also complex, with the 1.2.1 spec being almost 200 pages and the 1.3.0 spec being almost 250 pages long.

As such we have the kernel parsing untrusted responses from a complex specification, which sounds like a possible exploit vector. This is the type of place where Rust excels!

I was arguing for exactly this at a recent conference, so I'm glad to see it. It is a great meaningful usecase for rust in the kernel.

IIRC the netlink was my suggestion too, it really needs a careful look on its own. It is much better than sysfs, but comes with its own pitfalls.

You might want to try to break this up into two parts, one just dumps a large text file in debugfs where there are not uAPI rules. This would let the rust work proceed.

And another to introduce a proper uAPI for the data.

It will be easier to get the right people interested in both parts if it is split up I think, given the size.

Jason

#71 lore raw
Quoted 16 lines · Jason Gunthorpe
Quoted 14 lines · On Wed, Feb 11, 2026 at 01:29:07PM +1000, [email protected] wrote:

From: Alistair Francis <[email protected]>

Security Protocols and Data Models (SPDM) [1] is used for authentication, attestation and key exchange. SPDM is generally used over a range of transports, such as PCIe, MCTP/SMBus/I3C, ATA, SCSI, NVMe or TCP.

Quoted 1 line

From the kernels perspective SPDM is used to authenticate and attest devices.

In this threat model a device is considered untrusted until it can be verified by the kernel and userspace using SPDM. As such SPDM data is untrusted data that can be mallicious.

The SPDM specification is also complex, with the 1.2.1 spec being almost 200 pages and the 1.3.0 spec being almost 250 pages long.

Quoted 6 lines
Quoted 3 lines

As such we have the kernel parsing untrusted responses from a complex specification, which sounds like a possible exploit vector. This is the type of place where Rust excels!

I was arguing for exactly this at a recent conference, so I'm glad to see it. It is a great meaningful usecase for rust in the kernel.

Woo, someone is on board!

Quoted 4 lines

IIRC the netlink was my suggestion too, it really needs a careful look on its own. It is much better than sysfs, but comes with its own pitfalls.

It was!

Quoted 4 lines

You might want to try to break this up into two parts, one just dumps a large text file in debugfs where there are not uAPI rules. This would let the rust work proceed.

I'm currently thinking of splitting this in 3 parts. One for the ground work patches (crypto), one for SPDM (without netlink) and then a final one for netlink support.

I feel that this RFC proves netlink can work, which is important. For SPDM the authenticated syfs attribute is enough to provide usefulness to userspace to merge that while sending the netlink later.

That way hopefully everything is a little more self contained, but still has users and value to justify being merged.

That's a good idea about debugfs though, I'll keep that in mind

Alistair

Quoted 7 lines

And another to introduce a proper uAPI for the data.

It will be easier to get the right people interested in both parts if it is split up I think, given the size.

Jason

#72 lore raw

[ add Aneesh, Alexey, and Yilun ]

Alistair Francis wrote:
[..]

Quoted 10 lines

I'm currently thinking of splitting this in 3 parts. One for the ground work patches (crypto), one for SPDM (without netlink) and then a final one for netlink support.

I feel that this RFC proves netlink can work, which is important. For SPDM the authenticated syfs attribute is enough to provide usefulness to userspace to merge that while sending the netlink later.

That way hopefully everything is a little more self contained, but still has users and value to justify being merged.

The split sounds good. I chatted with Lukas a bit this morning he rightly pointed out that the crypto pre-work does not really stand on its own without a user.

However, I notice that Aneesh needs x509 certificate parsing for his TSM driver [1], I think TDX would benefit from the same to offload needing to specify the wall-clock time to the module [2] for cert verification, and SEV-TIO (already upstream) is currently missing any facility for the host to attest the device.

[1]: http://lore.kernel.org/[email protected]
[2]: http://lore.kernel.org/[email protected]

So one proposal to get the x509 pre-work upstream is to extend the TSM core (drivers/pci/tsm.c) to export the certificates in sysfs, and update the existing "authenticated" attribute to reflect the result of cert chain validation.

This provides immediate value to the TSM "connect" flow and is smaller than depending on the SPDM library as the first consumer. It also feels like useful refactoring to split 'struct spdm_state' into context for a session initiator (SPDM library) and context for the artifacts (certs + measurements produced by either SPDM library or a TSM).

#73 lore raw
Quoted 4 lines · On Wed, Feb 18, 2026 at 03:40:10PM -0800, [email protected] wrote:

So one proposal to get the x509 pre-work upstream is to extend the TSM core (drivers/pci/tsm.c) to export the certificates in sysfs, and update the existing "authenticated" attribute to reflect the result of cert chain validation.

Why do we want the validate the cert chain in the kernel? That sounds like something the verifier should do?

And not sure we should be dumping any certs in sysfs if the plan for the other stuff is netlink, it should be consistent I think.

Though it is a good idea to do something with the TSM too..

Jason

#74 lore raw
Quoted 9 lines · Jason Gunthorpe
Quoted 4 lines · On Wed, Feb 18, 2026 at 03:40:10PM -0800, [email protected] wrote:

So one proposal to get the x509 pre-work upstream is to extend the TSM core (drivers/pci/tsm.c) to export the certificates in sysfs, and update the existing "authenticated" attribute to reflect the result of cert chain validation.

Why do we want the validate the cert chain in the kernel? That sounds like something the verifier should do?

This is more for the CMA case where Lukas was imagining automatic revalidation of device certificates coming out of resume before userspace is present. If someone wanted to use a TSM for device-auth + link encryption outside of TDISP and Confidential VMs, then it could use the same mechanism.

However, error handling / automatic reset recovery is out of scope for the first phase of the TDISP enabling. Also all the early TDISP use cases seem focused on datacenter where there is no need for the VMM to authenticate the device. I am ok to defer the "authenticate while userspace is unavailable" scheme to keep this simple.

Quoted 2 lines

And not sure we should be dumping any certs in sysfs if the plan for the other stuff is netlink, it should be consistent I think.

Lukas was only putting the dynamic / transactional pieces in netlink. Specifically device signature events (multicast) and device measurement collection with a nonce.

The static cert chain blobs can certainly also be in netlink... but no real driving need like there was for the other flows. I am also encouraged by Lukas's work to handle large blobs over netlink [1], but no real need to add that as a dependency to this simple mission of "just enough of a real user to land the crypto prep patches".

[1]: https://github.com/l1k/linux/commit/af9b939fc30b

#75 lore raw
Quoted 8 lines · On Wed, Feb 18, 2026 at 09:05:28PM -0800, [email protected] wrote:
Quoted 7 lines · Jason Gunthorpe
Quoted 4 lines · On Wed, Feb 18, 2026 at 03:40:10PM -0800, [email protected] wrote:

So one proposal to get the x509 pre-work upstream is to extend the TSM core (drivers/pci/tsm.c) to export the certificates in sysfs, and update the existing "authenticated" attribute to reflect the result of cert chain validation.

Quoted 8 lines
Quoted 2 lines

Why do we want the validate the cert chain in the kernel? That sounds like something the verifier should do?

This is more for the CMA case where Lukas was imagining automatic revalidation of device certificates coming out of resume before userspace is present. If someone wanted to use a TSM for device-auth + link encryption outside of TDISP and Confidential VMs, then it could use the same mechanism.

I think we should have one flow for this based on what we talked about for TDSIP.

We are thinking about many interesting models, and some of them include running an external verifier on this no-VM case well. Kernel auto acceptance is not desirable for the same reasons it is not desirable in a TVM.

If we do some automatic re-accept for RAS/resume it should be strongly tied to some target pre-set by the userspace acceptance process - ie "the device must present exactly this specific cert chain and nothing else", and probably more too since we may want to exclude swapping out device FW versions or similar.

Not sure how that fits into an sysfs file.

Quoted 7 lines
Quoted 2 lines

And not sure we should be dumping any certs in sysfs if the plan for the other stuff is netlink, it should be consistent I think.

Lukas was only putting the dynamic / transactional pieces in netlink. Specifically device signature events (multicast) and device measurement collection with a nonce.

Quoted 5 lines

The static cert chain blobs can certainly also be in netlink... but no real driving need like there was for the other flows. I am also encouraged by Lukas's work to handle large blobs over netlink [1], but no real need to add that as a dependency to this simple mission of "just enough of a real user to land the crypto prep patches".

It could, but also it seems like it just makes it more complicated to force the verifying agent to use a combination of netlink and sysfs.

Jason

maintainer
#76 lore raw
In reply to Jason Gunthorpe
Quoted 2 lines · Jason Gunthorpe

And not sure we should be dumping any certs in sysfs if the plan for the other stuff is netlink, it should be consistent I think.

It has turned out to be super convenient to expose the 8 slots with certificate chains in sysfs for direct examination with openssl and similar tools, without having to go through netlink.

Originally the plan was to make the certificates/slot[0..7] files also writable and the kernel would implicitly perform a SET_CERTIFICATE SPDM exchange with the device when writing to those files. Unfortunately with SPDM 1.3 the spec editors made things more complicated, *cough* sorry *more flexible* with additional CertModel and KeyPairID attributes. That additional complexity makes it less suitable for sysfs, hence for *provisioning* netlink is indeed the better choice. But just for *reading* the certificates in the slots, sysfs exposure is very useful.

Thanks,

Lukas

#77 lore raw
Quoted 7 lines · Lukas Wunner
Quoted 2 lines · Jason Gunthorpe

And not sure we should be dumping any certs in sysfs if the plan for the other stuff is netlink, it should be consistent I think.

It has turned out to be super convenient to expose the 8 slots with certificate chains in sysfs for direct examination with openssl and similar tools, without having to go through netlink.

Honestly, I'm reluctant to add permanent sysfs uAPI just for temporary debugging. Put it in debugfs.

There should be a tool suite that runs on top of this stuff and presents a sensible user experiance, with man pages and so on.

Having to find/remember some baroque openssl command line with a million options is not reasonable for a production kind of environment.

Jason

maintainer
#78 lore raw
Quoted 8 lines · Jason Gunthorpe

Kernel auto acceptance is not desirable for the same reasons it is not desirable in a TVM.

If we do some automatic re-accept for RAS/resume it should be strongly tied to some target pre-set by the userspace acceptance process - ie "the device must present exactly this specific cert chain and nothing else", and probably more too since we may want to exclude swapping out device FW versions or similar.

The way this works in my series (and I presume Alistair's) is that trusted root certificates for devices need to be added to the .cma keyring.

This can be done from user space using keyctl(1) or some other utility that can talk to the kernel's existing keyctl ABI.

It is also possible to link the .builtin_trusted_keys or .platform keyrings into the .cma keyring and thereby declare anything trusted that is used for UEFI Secure Boot. Likewise blacklisted certificates added to the .blacklisted keyring are rejected for SPDM device authentication. These are existing, well-established roots of trust in the kernel that CMA simply inherits. I think it is reasonable to base auto-acceptance on these existing mechanisms. No need to reinvent the wheel.

If you want to trust exactly one specific End Entity certificate, you could provision an SPDM slot with that certificate, add it to the .cma keyring and clean out all the other 7 SPDM certificate slots on the device.

Quoted 6 lines
Quoted 3 lines

It has turned out to be super convenient to expose the 8 slots with certificate chains in sysfs for direct examination with openssl and similar tools, without having to go through netlink.

Honestly, I'm reluctant to add permanent sysfs uAPI just for temporary debugging. Put it in debugfs.

Exposure of the certificates in the SPDM slots is not for debugging, it's just super convenient for day-to-day use.

Quoted 3 lines

Having to find/remember some baroque openssl command line with a million options is not reasonable for a production kind of environment.

Personally I find something like the following neither baroque nor unreasonable:

# What's the certificate chain in slot0?
openssl storeutl -text /sys/bus/pci/devices/0000:03:00.0/certificates/slot0

# Fingerprint of root cert in slot0, does it match what vendor claims? openssl x509 -fingerprint -in /sys/bus/pci/devices/0000:03:00.0/certificates/slot0

# Looks good, let's trust it:
keyctl padd asymmetric "" %:.cma < /sys/bus/pci/devices/0000:03:00.0/certificates/slot0

Quoted 2 lines

There should be a tool suite that runs on top of this stuff and presents a sensible user experiance, with man pages and so on.

Device authentication is currently trickling down from server to client to embedded/edge devices and you don't want to require users to install a tool suite on space-constrained embedded devices where busybox + openssl is otherwise enough, doubly so with skyrocketing eMMC prices.

Thanks,

Lukas

#79 lore raw
Quoted 10 lines · Lukas Wunner
Quoted 8 lines · Jason Gunthorpe

Kernel auto acceptance is not desirable for the same reasons it is not desirable in a TVM.

If we do some automatic re-accept for RAS/resume it should be strongly tied to some target pre-set by the userspace acceptance process - ie "the device must present exactly this specific cert chain and nothing else", and probably more too since we may want to exclude swapping out device FW versions or similar.

Quoted 6 lines

The way this works in my series (and I presume Alistair's) is that trusted root certificates for devices need to be added to the .cma keyring.

This can be done from user space using keyctl(1) or some other utility that can talk to the kernel's existing keyctl ABI.

I really don't like this from a verification perspective. We don't want the kernel checking signatures, that is the verifier's job.

And a general keyring based proeprty is not at all the same as 'this device must present exactly the same certification and attesation after resume'

Quoted 4 lines

authentication. These are existing, well-established roots of trust in the kernel that CMA simply inherits. I think it is reasonable to base auto-acceptance on these existing mechanisms. No need to reinvent the wheel.

It depends what you are building. We've been focused on external verification so this is not at all desirable.

Something else, maybe some kind of internal verification for embedded is a quite different story. The two cases still need to compose sensibly within the kernel though.

Quoted 7 lines
Quoted 3 lines

Having to find/remember some baroque openssl command line with a million options is not reasonable for a production kind of environment.

Personally I find something like the following neither baroque nor unreasonable:

Quoted 8 lines

# What's the certificate chain in slot0?
openssl storeutl -text /sys/bus/pci/devices/0000:03:00.0/certificates/slot0

# Fingerprint of root cert in slot0, does it match what vendor claims? openssl x509 -fingerprint -in /sys/bus/pci/devices/0000:03:00.0/certificates/slot0

# Looks good, let's trust it:
keyctl padd asymmetric "" %:.cma < /sys/bus/pci/devices/0000:03:00.0/certificates/slot0

That's exactly the baroque I'm talking about, no server admin is going to want to grapple with that..

Quoted 5 lines

Device authentication is currently trickling down from server to client to embedded/edge devices and you don't want to require users to install a tool suite on space-constrained embedded devices where busybox + openssl is otherwise enough, doubly so with skyrocketing eMMC prices.

Maybe, but is this a real thing where someone would run busybox and want some minimal self-verification?

Jason

maintainer
#80 lore raw
Quoted 10 lines · Jason Gunthorpe
Quoted 6 lines · Lukas Wunner

The way this works in my series (and I presume Alistair's) is that trusted root certificates for devices need to be added to the .cma keyring.

This can be done from user space using keyctl(1) or some other utility that can talk to the kernel's existing keyctl ABI.

I really don't like this from a verification perspective. We don't want the kernel checking signatures, that is the verifier's job.

On resume from system sleep, the device is put into D0 already in the ->resume_noirq() phase and drivers are free to access it already at that point. However a verifier in user space cannot be queried at that point because user space is still frozen.

Likewise after recovery from DPC or AER, the device has been reset and needs to be reauthenticated, yet user space may be unavailable because the device that has been reset may contain the root partition or may be the NIC that you need to query your remote attestation service.

There is no way around some form of in-kernel device authentication to accommodate such use cases.

Quoted 3 lines

And a general keyring based proeprty is not at all the same as 'this device must present exactly the same certification and attesation after resume'

Well please be constructive and propose something better.

Quoted 7 lines
Quoted 4 lines

authentication. These are existing, well-established roots of trust in the kernel that CMA simply inherits. I think it is reasonable to base auto-acceptance on these existing mechanisms. No need to reinvent the wheel.

It depends what you are building. We've been focused on external verification so this is not at all desirable.

No problem at all. The kernel will merely use the .cma keyring for its own notion of an authenticated device.

However if there is no trusted root cert in the .cma keyring, the kernel will still multicast the signature received from the device via netlink, so your user space tool can ask the remote attestation service and if it responds affirmatively, you trust the device.

So you can either use the .cma keyring for in-kernel authentication or you can use your user space utility.

But you can't rely on user space if you want seamless re-authentication after a system sleep transition or error recovery.

We can discuss a way for user space to force the kernel into considering a device authenticated. E.g. writing "force" to the "authenticated" attribute may tell the kernel that it's a trustworthy device irrespective of the .cma keyring. So you'd perform remote attestation and if successful, tell the kernel to consider the device trusted.

Quoted 11 lines
Quoted 8 lines

# What's the certificate chain in slot0?
openssl storeutl -text /sys/bus/pci/devices/0000:03:00.0/certificates/slot0

# Fingerprint of root cert in slot0, does it match what vendor claims? openssl x509 -fingerprint -in /sys/bus/pci/devices/0000:03:00.0/certificates/slot0

# Looks good, let's trust it:
keyctl padd asymmetric "" %:.cma < /sys/bus/pci/devices/0000:03:00.0/certificates/slot0

That's exactly the baroque I'm talking about, no server admin is going to want to grapple with that..

I used to be an admin for 2 decades and my experience is that openssl usage has just become muscle memory, but YMMV. :)

Thanks,

Lukas

#81 lore raw
Quoted 17 lines · Lukas Wunner
Quoted 10 lines · Jason Gunthorpe
Quoted 6 lines · Lukas Wunner

The way this works in my series (and I presume Alistair's) is that trusted root certificates for devices need to be added to the .cma keyring.

This can be done from user space using keyctl(1) or some other utility that can talk to the kernel's existing keyctl ABI.

I really don't like this from a verification perspective. We don't want the kernel checking signatures, that is the verifier's job.

On resume from system sleep, the device is put into D0 already in the ->resume_noirq() phase and drivers are free to access it already at that point. However a verifier in user space cannot be queried at that point because user space is still frozen.

Quoted 7 lines

Likewise after recovery from DPC or AER, the device has been reset and needs to be reauthenticated, yet user space may be unavailable because the device that has been reset may contain the root partition or may be the NIC that you need to query your remote attestation service.

There is no way around some form of in-kernel device authentication to accommodate such use cases.

I'm arguing there are two very different steps here that must be kept separate. Verification is done when the device is first seen and the kernel is told it is OK to use the device.

A same-device check is performed if an already verified and accepted device resumes or RAS's in some way.

same-device does not mean run a kernel verification against the kernel keyring, as a second verification could be tricked into accepting something that has changed and defeat the userspace verifier.

Instead the implementation should capture information when the device is accepted by the verifier and on resume/RAS it should compare the device against that captured information and determine if it is still the same device.

The key north star must be that the userspace verifier alone decides if the device is acceptable and if the kernel is configured to auto-re-accept the device later on RAS/resume it must not permit a device that is different from what userspace approved. In other words it is not a verification on resume, it is just a kernel side confirmation the device hasn't been changed.

Hence no keyring should be involved in resume.

I understand the tempatation to just run a kernel verification twice, and it is OK if your use cases are limited to a kernel verifier, but it doesn't compose very well with a userspace verfier at all and gives us two very different frameworks.

Quoted 5 lines
Quoted 3 lines

And a general keyring based proeprty is not at all the same as 'this device must present exactly the same certification and attesation after resume'

Well please be constructive and propose something better.

I have said what I'd like in a few ways now.

Quoted 6 lines

We can discuss a way for user space to force the kernel into considering a device authenticated. E.g. writing "force" to the "authenticated" attribute may tell the kernel that it's a trustworthy device irrespective of the .cma keyring. So you'd perform remote attestation and if successful, tell the kernel to consider the device trusted.

We definitely need to ensure we build something where userspace is in fully in charge.

I don't have an objection to an optional, useful for embedded, kernel side verifier that uses the cma ring.

What I'm focusing on is that it be coherent with the whole complex verifier flows we need, not just its own seperate different thing. So replacing the userspace verifier with a kernel verifier is no issue. Making it so you HAVE to use the kernel verifier to get certain functionality like RAS is objectionable.

Quoted 6 lines
Quoted 6 lines
Quoted 6 lines

# What's the certificate chain in slot0?
openssl storeutl -text /sys/bus/pci/devices/0000:03:00.0/certificates/slot0

# Fingerprint of root cert in slot0, does it match what vendor claims? openssl x509 -fingerprint -in /sys/bus/pci/devices/0000:03:00.0/certificates/slot0

Quoted 8 lines
Quoted 5 lines
Quoted 2 lines

# Looks good, let's trust it:
keyctl padd asymmetric "" %:.cma < /sys/bus/pci/devices/0000:03:00.0/certificates/slot0

That's exactly the baroque I'm talking about, no server admin is going to want to grapple with that..

I used to be an admin for 2 decades and my experience is that openssl usage has just become muscle memory, but YMMV. :)

This is also not a very realistic scenario, there is no point to copy a certificate from a device into the cma key ring. If you want to trust the device as is without any verification then just accept it as is with a trivial userspace "verifier".

If you want a kernel side verifier the realistic scenario is to bake the permitted certificates into the image or kernel and/or have busybox load them into the keyring at boot.

Jaason

#82 lore raw
Quoted 42 lines · Jason Gunthorpe
Quoted 17 lines · Lukas Wunner
Quoted 10 lines · Jason Gunthorpe
Quoted 6 lines · Lukas Wunner

The way this works in my series (and I presume Alistair's) is that trusted root certificates for devices need to be added to the .cma keyring.

This can be done from user space using keyctl(1) or some other utility that can talk to the kernel's existing keyctl ABI.

I really don't like this from a verification perspective. We don't want the kernel checking signatures, that is the verifier's job.

On resume from system sleep, the device is put into D0 already in the ->resume_noirq() phase and drivers are free to access it already at that point. However a verifier in user space cannot be queried at that point because user space is still frozen.

Quoted 7 lines

Likewise after recovery from DPC or AER, the device has been reset and needs to be reauthenticated, yet user space may be unavailable because the device that has been reset may contain the root partition or may be the NIC that you need to query your remote attestation service.

There is no way around some form of in-kernel device authentication to accommodate such use cases.

I'm arguing there are two very different steps here that must be kept separate. Verification is done when the device is first seen and the kernel is told it is OK to use the device.

A same-device check is performed if an already verified and accepted device resumes or RAS's in some way.

same-device does not mean run a kernel verification against the kernel keyring, as a second verification could be tricked into accepting something that has changed and defeat the userspace verifier.

Instead the implementation should capture information when the device is accepted by the verifier and on resume/RAS it should compare the device against that captured information and determine if it is still the same device.

Quoted 8 lines

The key north star must be that the userspace verifier alone decides if the device is acceptable and if the kernel is configured to auto-re-accept the device later on RAS/resume it must not permit a device that is different from what userspace approved. In other words it is not a verification on resume, it is just a kernel side confirmation the device hasn't been changed.

Hence no keyring should be involved in resume.

I am also struggling to see a role for the .cma keyring as long as the kernel eventually has a method to cache cert-chain, measurements, and for TDISP, interface report digests. Support for a recovery flow is not the first dragon to slay though as just establishing device trust in the first instance without RAS concerns is a significant amount of work.

Linux should not over index on native bare-metal CMA because that mechanism only tells you that the SPDM session with device firmware (DSM) is authenticated, it does nothing to ensure that the kernel's view of the device's MMIO is and remains associated with that DSM. Better than nothing, yes, but it certainly assumes a less sophisticated threat model than TDISP.

So the current 'authenticated' PCI sysfs attribute can simply indicate "SPDM collateral (cert chain + measurements) available", and leave all the decisions about what do with that collateral to userspace. For cases where the full lock + accept TDISP flow is not available the only policy knob that userspace has is to decline to attach a driver.

Once we have that userspace can optionally tell the kernel to cache digests for automatic re-accept / keep driver bound, or userspace can plan to do another round trip with the verifier for recovery if the device bounces out of the TCB.

My current thought is take and adapt the netlink interface to retrieve cert chains, change the certificate slot for the authentication attempt, and retrieve device measurements. None of that requires the x509 parser. With that in place native CMA SPDM can be modeled as just another TSM driver that only addresses a subset of the TDISP threat model.

There are 2 flows depending on whether the TSM driver suports the comprehensive security of the "lock+accept" model or not:

---

# $tsmN is a class device registered by one of amd-tio, intel-tdx, # arm-cca, or when this spdm library is available a kernel-pci-cma TSM

# driver

# The "lock+accept" model is not available for any of the current tsm

# drivers on bare metal, only the "connect" flow. The connect flow gets
# you an SPDM secure session at a minimum and optionally IDE as well. It
# is a less comprehensive security model than TDISP
echo $tsmN > /sys/bus/pci/devices/$pci_dev/tsm/connect

# Alternatively, when a TDISP interface is assigned, the TSM driver

# publishes "lock+accept" attributes. This provides the comprehensive
# security model that closes "DSM is and remains associated to device
# MMIO" TOCTOU problem.
echo $tsmN > /sys/bus/pci/devices/$pci_dev/tsm/lock

# In either of the above cases the
# /sys/bus/pci/devices/$pci_dev/authenticated attribute toggles to 1 and

# userspace is able to use PCI netlink to gather evidence with nonces
...collect (with netlink) / validate evidence...

# When verifier is satisfied bind the driver, or in the Confidential # Computing / TDISP case, first "accept" the device so that it is

# allowed to access private memory
echo 1 > /sys/bus/pci/devices/$pci_dev/tsm/accept
echo $pci_dev > /sys/bus/pci/drivers/$driver/bind

---

maintainer
#83 lore raw
In reply to Jason Gunthorpe
Quoted 6 lines · Jason Gunthorpe

I'm arguing there are two very different steps here that must be kept separate. Verification is done when the device is first seen and the kernel is told it is OK to use the device.

A same-device check is performed if an already verified and accepted device resumes or RAS's in some way.

[...]

Quoted 1 line

Hence no keyring should be involved in resume.

No, that doesn't work. You cannot rely on information that the device presents. The information may be spoofed. The only secure way to ascertain that the device you're talking to is what it purports to be is to ask it for a signature and verify that the signature was made with a device certificate which is signed by a root certificate that you trust. And the way to trust that root certificate is to add it to a keyring.

There is no way around that. What you have in mind could be achieved by generating a secret token after first authentication and then using that secret token after RAS / resume for re-authentication. But I just looked through the SPDM spec and it doesn't support that kind of thing.

A userspace verifier is incompatible with authentication after RAS and resume. It's a technical limitation that you'll have to live with, I'm sorry. If you could come up with, say, an eBPF verifier that user space loads into the kernel, that might be a workable solution.

Quoted 2 lines

I don't have an objection to an optional, useful for embedded, kernel side verifier that uses the cma ring.

Good. My patches merely aim to be a spec-compliant implementation of PCIe r7.0 sec 6.31 (Component Measurement and Authentication/SPDM).

That spec section does not talk about user space verifiers.

Moreover PCIe r6.1 page 115 states "CMA requires SPDM Version 1.0 or above", so the baseline that we need to support for spec compliance is the oldest spec version with the least features. So even if a future spec version introduced a feature to establish a shared secret on first authentication and use that for subsequent authentication, we'd still be constrained by the SPDM 1.0 feature set for compliance with older devices.

Quoted 6 lines
Quoted 6 lines
Quoted 6 lines
Quoted 6 lines

# What's the certificate chain in slot0?
openssl storeutl -text /sys/bus/pci/devices/0000:03:00.0/certificates/slot0

# Fingerprint of root cert in slot0, does it match what vendor claims? openssl x509 -fingerprint -in /sys/bus/pci/devices/0000:03:00.0/certificates/slot0

Quoted 7 lines
Quoted 2 lines
Quoted 2 lines
Quoted 2 lines

# Looks good, let's trust it:
keyctl padd asymmetric "" %:.cma < /sys/bus/pci/devices/0000:03:00.0/certificates/slot0

This is also not a very realistic scenario, there is no point to copy a certificate from a device into the cma key ring. If you want to trust the device as is without any verification then just accept it as is with a trivial userspace "verifier".

No, in the above example the device is not trusted "as is without any verification". The verification that happens above is a manual comparison of the fingerprint with one provided by the vendor.

Thanks,

Lukas

#84 lore raw
Quoted 8 lines · Lukas Wunner
Quoted 6 lines · Jason Gunthorpe

I'm arguing there are two very different steps here that must be kept separate. Verification is done when the device is first seen and the kernel is told it is OK to use the device.

A same-device check is performed if an already verified and accepted device resumes or RAS's in some way.

[...]

Quoted 8 lines
Quoted 1 line

Hence no keyring should be involved in resume.

No, that doesn't work. You cannot rely on information that the device presents. The information may be spoofed. The only secure way to ascertain that the device you're talking to is what it purports to be is to ask it for a signature and verify that the signature was made with a device certificate which is signed by a root certificate that you trust.

Yes, and in the most general case the nonce/etc for this must come through userspace from an external verifier agent.

Quoted 2 lines

And the way to trust that root certificate is to
add it to a keyring.

Not quite, the way to have a *kernel verifier* is to have a kernel verifier use the cma keyring in this way. If you use a userspace verifier the kernel keyring has no role.

Quoted 5 lines

There is no way around that. What you have in mind could be achieved by generating a secret token after first authentication and then using that secret token after RAS / resume for re-authentication. But I just looked through the SPDM spec and it doesn't support that kind of thing.

The kernel just records the information the device presented and checks it is the same. Yes on the same-device check it will have to make a new nonce and push it through, but it is only checking the results against the previously cached information for this device, not a general keyring.

Quoted 4 lines

A userspace verifier is incompatible with authentication after RAS and resume. It's a technical limitation that you'll have to live with, I'm sorry. If you could come up with, say, an eBPF verifier that user space loads into the kernel, that might be a workable solution.

You are ignoring the proposal - the idea is to NOT VERIFY on resume, but to only do a same-device check.

A same-device check should have no policy components that require a verifier. It gives the device a new fresh nonce, obtains a new signature, checks the signature works with the previously cached public key(s).

IOW the resume/RAS acceptance criteria is that the second nonce was signed with the same private key(s) that the first nonce was signed with.

In many devices this will be entirely sufficient. It should be common that each physical device has a unique private key with a certificate chain to the manufactures secured certificate. Since the private key is globally unique proving the device has it during resume is the majority of a same-device check.

From there the kernel can do additional checks, sanity check the presented certificate list is exactly the same, check the attestation report hasn't changed to ensure the device hasn't been replaced with an insecure dowrev or something. (possibly the driver will have to provide some helper to same-device check its attestation report)

No need for spec changes, no need for a key ring, very different flow from verification.

The kernel should simply monitor the signature requests from the verifier, internally confirm the reply is correct and record all the public keys that were successfully used on this struct device.

Quoted 1 line

That spec section does not talk about user space verifiers.

Linux will have its own sw model, the spec is just the protocol definition. In the CC world everyone just knows the verifier needs to be external.. How else could it even work?

Quoted 8 lines
Quoted 4 lines

This is also not a very realistic scenario, there is no point to copy a certificate from a device into the cma key ring. If you want to trust the device as is without any verification then just accept it as is with a trivial userspace "verifier".

No, in the above example the device is not trusted "as is without any verification". The verification that happens above is a manual comparison of the fingerprint with one provided by the vendor.

"manual comparision" is some debugging flow, like I pointed out at the start.

Jason

maintainer
#85 lore raw
Quoted 3 lines · Jason Gunthorpe

IOW the resume/RAS acceptance criteria is that the second nonce was signed with the same private key(s) that the first nonce was signed with.

What you seem to have in mind is essentially a "trust on first use" model where trust is given to a specific device certificate (i.e. leaf certificate), not to a root certificate.

Whereas the SPDM spec (and by extension CMA, i.e. PCIe r7.0 sec 6.31) envisions a model where each of the 8 slots may contain a certificate *chain* (comprising a root certificate, optional intermediate certificates and a leaf certificate). In this model, trust is given to root certificates. These could be vendors, but it's also possible that e.g. a CSP operates its own CA and provisions one of the 8 slots with a custom certificate chain anchored in its own CA.

Your "trust on first use" model could be made to fit into the SPDM model by adding the leaf certificate (= device certificate) to the .cma keyring and provisioning one of the certificate slots with just the leaf certificate (and nothing else in the cert chain). The verifier in user space could add the leaf certificate to the keyring and provision a slot with it after having verified the device successfully.

That would make verification in user space compatible with RAS & resume, at least the certificate validation part.

An alternative solution would be to have the verifier in user space operate its own mini CA. The root certificate of that mini CA would be added to the .cma keyring. Upon successful verification, the verifier would create a short-lived certificate for the device which is signed by its mini CA. The verifier would provision one of the 8 cert slots of the device with this custom short-lived certificate and thereby allow the kernel to re-authenticate the device without reliance on user space until the short-lived certificate expires. User space would have to monitor expiration of those certificates and re-new them to ensure continued re-authentication.

I'm adding Mathieu and Thomas to cc as they have been exploring ways to take advantage of the .cma keyring for Confidential Computing use cases.

Quoted 3 lines

Linux will have its own sw model, the spec is just the protocol definition. In the CC world everyone just knows the verifier needs to be external.. How else could it even work?

There are products out there which support CMA but not TDISP. In other words, the CC world isn't everything. The modest goal of this series is to allow authentication of devices in compliance with PCIe r7.0 sec 6.31 and the SPDM spec. I understand there are features and authentication modes which are important for the Confidential Computing world, but CoCo needs to fit into the spec-defined mechanisms.

Thanks,

Lukas

#86 lore raw
Quoted 4 lines · Lukas Wunner
Quoted 3 lines · Jason Gunthorpe

IOW the resume/RAS acceptance criteria is that the second nonce was signed with the same private key(s) that the first nonce was signed with.

[..]

Quoted 11 lines
Quoted 3 lines

Linux will have its own sw model, the spec is just the protocol definition. In the CC world everyone just knows the verifier needs to be external.. How else could it even work?

There are products out there which support CMA but not TDISP. In other words, the CC world isn't everything. The modest goal of this series is to allow authentication of devices in compliance with PCIe r7.0 sec 6.31 and the SPDM spec. I understand there are features and authentication modes which are important for the Confidential Computing world, but CoCo needs to fit into the spec-defined mechanisms.

The TDISP proposal from Jason and I bears repeating because it is a superset of what a CMA-only solution needs, and security guarantees it provides. I also submit that "identity revalidation over reset/resume" is not a *primary* concern. It is certainly *a* concern that needs to be part of the ongoing discussion to avoid painting ourselves into a corner, but certainly a complexity that is incremental to the base enabling. Recall CMA is only a building block to trusting the rest of the device interface outside of the SPDM session.

Userspace is in charge of all trust and verification decisions. A TSM driver, whether that driver is in-kernel-SPDM-library, or platform TSM, establishes a session with a device with a given certificate slot. The session establishment makes cert-chain+transcript available to userspace and caches the public-key. If userspace does not like that result, it opts to not bind a driver to that device, or retries with a different cert slot.

If later we want to support a "same device" capability in scenarios where a userspace round trip is infeasible then that is incremental ABI. That ABI would allow userspace to cache golden cert-chain+measurements. The resume path can revalidate that identity description with a fresh nonce and the cached public key.

For TDISP the violence of dropping the device out of the TCB likely needs more sophistication than golden measurement revalidation. For CMA mere trust in the root cert is not sufficient for many of the adversarial device threat models, so the kernel need not carry that responsibility.

Aneesh and I are putting together some POC patches along these lines.

#87 lore raw
Quoted 35 lines · On Sat, 21 Feb 2026 15:29:55 -0800 [email protected] wrote:
Quoted 4 lines · Lukas Wunner
Quoted 3 lines · Jason Gunthorpe

IOW the resume/RAS acceptance criteria is that the second nonce was signed with the same private key(s) that the first nonce was signed with.

[..]

Quoted 11 lines
Quoted 3 lines

Linux will have its own sw model, the spec is just the protocol definition. In the CC world everyone just knows the verifier needs to be external.. How else could it even work?

There are products out there which support CMA but not TDISP. In other words, the CC world isn't everything. The modest goal of this series is to allow authentication of devices in compliance with PCIe r7.0 sec 6.31 and the SPDM spec. I understand there are features and authentication modes which are important for the Confidential Computing world, but CoCo needs to fit into the spec-defined mechanisms.

The TDISP proposal from Jason and I bears repeating because it is a superset of what a CMA-only solution needs, and security guarantees it provides. I also submit that "identity revalidation over reset/resume" is not a *primary* concern. It is certainly *a* concern that needs to be part of the ongoing discussion to avoid painting ourselves into a corner, but certainly a complexity that is incremental to the base enabling. Recall CMA is only a building block to trusting the rest of the device interface outside of the SPDM session.

Userspace is in charge of all trust and verification decisions. A TSM driver, whether that driver is in-kernel-SPDM-library, or platform TSM, establishes a session with a device with a given certificate slot. The session establishment makes cert-chain+transcript available to userspace and caches the public-key. If userspace does not like that result, it opts to not bind a driver to that device, or retries with a different cert slot.

Quoted 5 lines

If later we want to support a "same device" capability in scenarios where a userspace round trip is infeasible then that is incremental ABI. That ABI would allow userspace to cache golden cert-chain+measurements. The resume path can revalidate that identity description with a fresh nonce and the cached public key.

From a simple case of 'what is here' in this set, the only bit I'm seeing change in order to implement what I think you and Jason are describing is we don't bother checking the cert chain in kernel for the first time: We provide that to userspace to decide if it's good. If I understand correctly, this will be at the end of the full sequence once we've pushed through a nonce and gotten signatures + measurements. Same as checking a TSM provided transcript. That was sort of happening anyway if we consider the .cma keyring as just providing a short cut filter if we didn't trust the device provided root cert.
User space got the transcripts before it had to make any decision on binding and could do anything it liked with them.

For that caching the public key bit, I'm not clear on whether you intend to do that from kernel side (which I think I'd prefer) or have user space squirt that key back in again? If we are doing it in kernel we can at least always verify the transcript is self consistent and refuse to give anything to user space if it's not consistent (other than debug material). By self consistent I mean we verify the signature against the device provided cert (might as well verify whole chain as that's trivial given we have to partly parse them anyway to find the cert). I don't think it maters hugely if we do this in kernel beyond advantage of removing a few foot guns and simplifying the userpace interface to "I'm fine with the provided transcript for use when I'm not here next time" write. Disadvantage is we use the cert parsing code (which is there anyway) and parse it all twice - once in kernel and probably again in userspace or at some remote verifier.

Measurement verification (in kernel) is potentially a trickier bit of ABI design as the space of what might be in there and what matters for a given device is complex to say the least. Only a small part of that is spec defined.

I can see there may be some use cases where we relax things beyond this (potentially including .cma keyring and root cert only)

Jonathan

Quoted 10 lines

For TDISP the violence of dropping the device out of the TCB likely needs more sophistication than golden measurement revalidation. For CMA mere trust in the root cert is not sufficient for many of the adversarial device threat models, so the kernel need not carry that responsibility.

Aneesh and I are putting together some POC patches along these lines.

#88 lore raw

Jonathan Cameron wrote:
[..]

Quoted 11 lines

From a simple case of 'what is here' in this set, the only bit I'm seeing change in order to implement what I think you and Jason are describing is we don't bother checking the cert chain in kernel for the first time: We provide that to userspace to decide if it's good. If I understand correctly, this will be at the end of the full sequence once we've pushed through a nonce and gotten signatures + measurements. Same as checking a TSM provided transcript. That was sort of happening anyway if we consider the .cma keyring as just providing a short cut filter if we didn't trust the device provided root cert.
User space got the transcripts before it had to make any decision on binding and could do anything it liked with them.

Exactly, the kernel checking the cert is not sufficient to establish trust in the device interface (Link + MMIO). If userspace is making a driver-bind or TDISP accept decision, it needs certs+measurements+interface-report and does not benefit from the kernel also validating the certificate.

Quoted 5 lines

For that caching the public key bit, I'm not clear on whether you intend to do that from kernel side (which I think I'd prefer) or have user space squirt that key back in again? If we are doing it in kernel we can at least always verify the transcript is self consistent and refuse to give anything to user space if it's not consistent (other than debug material).

Quoted 8 lines

By self consistent I mean we verify the signature against the device provided cert (might as well verify whole chain as that's trivial given we have to partly parse them anyway to find the cert). I don't think it maters hugely if we do this in kernel beyond advantage of removing a few foot guns and simplifying the userpace interface to "I'm fine with the provided transcript for use when I'm not here next time" write. Disadvantage is we use the cert parsing code (which is there anyway) and parse it all twice - once in kernel and probably again in userspace or at some remote verifier.

Right, the proposal is cache the public-key from pci_tsm_ops::connect() and at least require that the resulting transcript from that session establishment is properly self-signed. No point in continuing with a TSM implementation that is not self-consistent.

Quoted 7 lines

Measurement verification (in kernel) is potentially a trickier bit of ABI design as the space of what might be in there and what matters for a given device is complex to say the least. Only a small part of that is spec defined.

I can see there may be some use cases where we relax things beyond this (potentially including .cma keyring and root cert only)

So I expect there will be an extended tail of problems to solve from same device and same measurements checks, to full recovery into the TCB. A .cma keyring may be a part of that eventually, but the "as simple as possible, but no simpler" starting point is userspace device policy wrapped around self-signed evidence.

If the device interface is adversarial, mere trust in the SPDM session is insufficient to protect against the type of attacks that re-checking the certificate only after reset/resume is meant to mitigate.

#89 lore raw
Quoted 14 lines · On Mon, Feb 23, 2026 at 11:11:23AM -0800, [email protected] wrote:

Jonathan Cameron wrote:
[..]

Quoted 11 lines

From a simple case of 'what is here' in this set, the only bit I'm seeing change in order to implement what I think you and Jason are describing is we don't bother checking the cert chain in kernel for the first time: We provide that to userspace to decide if it's good. If I understand correctly, this will be at the end of the full sequence once we've pushed through a nonce and gotten signatures + measurements. Same as checking a TSM provided transcript. That was sort of happening anyway if we consider the .cma keyring as just providing a short cut filter if we didn't trust the device provided root cert.
User space got the transcripts before it had to make any decision on binding and could do anything it liked with them.

Quoted 5 lines

Exactly, the kernel checking the cert is not sufficient to establish trust in the device interface (Link + MMIO). If userspace is making a driver-bind or TDISP accept decision, it needs certs+measurements+interface-report and does not benefit from the kernel also validating the certificate.

Right, and from that position you have to ask *why* would the kernel ever check the certs, what use case is that supporting, and I can't come up with any beyond some kernel-internal simple verifier. So why do we have a cma keyring at all?

I'm willing to be convinced there is some compelling embedded reason why putting the verifier in the kernel instead of userspcae saves a meaningful amount of flash - but I don't think we should start there on the kernel side. First kernel steps should be to enable the userspace verifier flow, which serves all use cases and offers the highest policy flexibility.

Resume/RAS is handled by a much stronger same device check, not a weaker kernel verifier with a root of trust in a keyring.

If somebody eventually wants a weaker check on resume then come with that explanation and justification and lets talk about it later. I would probably offer an eBPF policy hook not a CMA keyring though...

Quoted 14 lines
Quoted 5 lines

For that caching the public key bit, I'm not clear on whether you intend to do that from kernel side (which I think I'd prefer) or have user space squirt that key back in again? If we are doing it in kernel we can at least always verify the transcript is self consistent and refuse to give anything to user space if it's not consistent (other than debug material).

Quoted 8 lines

By self consistent I mean we verify the signature against the device provided cert (might as well verify whole chain as that's trivial given we have to partly parse them anyway to find the cert). I don't think it maters hugely if we do this in kernel beyond advantage of removing a few foot guns and simplifying the userpace interface to "I'm fine with the provided transcript for use when I'm not here next time" write. Disadvantage is we use the cert parsing code (which is there anyway) and parse it all twice - once in kernel and probably again in userspace or at some remote verifier.

Quoted 4 lines

Right, the proposal is cache the public-key from pci_tsm_ops::connect() and at least require that the resulting transcript from that session establishment is properly self-signed. No point in continuing with a TSM implementation that is not self-consistent.

I don't have a strong feeling either way, but agree having the kernel do it automatically and magically seems overall better if it is possible.

What I want to achieve is a very strong robust 'same device check' such that if the same device check passes then the userspace verifier would accept the device again as nothing it could be sensitive to would change.

As I said in the other email I expect the private keys to be unique to the physical device so checking the leaf certificates is most of the way to a same device check. You then need to confirm the FW configuration of the device is identical.

Quoted 4 lines
Quoted 4 lines

Measurement verification (in kernel) is potentially a trickier bit of ABI design as the space of what might be in there and what matters for a given device is complex to say the least. Only a small part of that is spec defined.

Yes, I suggested we may have the device driver contribute this. From a kernel perspective we can set out what 'same device check' means and the drivers have to implement this by processing thier own device specific attestation.

It is not the same as trying to do "measurement verificaiton" where you may have a broad policy of what versions and configurations are acceptable. Here we want simple "measurement has not changed", which I think is reasonable to do in the kernel.

I gather these attestation reports are complex to parse so it would make sense to do all that in rust...

Quoted 8 lines
Quoted 2 lines

I can see there may be some use cases where we relax things beyond this (potentially including .cma keyring and root cert only)

So I expect there will be an extended tail of problems to solve from same device and same measurements checks, to full recovery into the TCB. A .cma keyring may be a part of that eventually, but the "as simple as possible, but no simpler" starting point is userspace device policy wrapped around self-signed evidence.

I'm also willing to entertain a cma keyring, but only with a strong justification and fitting into this overall security model after we establish the initial basic flows in their most secure versions.

Quoted 3 lines

If the device interface is adversarial, mere trust in the SPDM session is insufficient to protect against the type of attacks that re-checking the certificate only after reset/resume is meant to mitigate.

Right, if you want to exclude physical attacks checking certificates only is clearly not sufficient.

Jason

#90 lore raw
In reply to [email protected]

dan.j.williams@ wrote:
[..]

Quoted 14 lines
Quoted 5 lines

For that caching the public key bit, I'm not clear on whether you intend to do that from kernel side (which I think I'd prefer) or have user space squirt that key back in again? If we are doing it in kernel we can at least always verify the transcript is self consistent and refuse to give anything to user space if it's not consistent (other than debug material).

Quoted 8 lines

By self consistent I mean we verify the signature against the device provided cert (might as well verify whole chain as that's trivial given we have to partly parse them anyway to find the cert). I don't think it maters hugely if we do this in kernel beyond advantage of removing a few foot guns and simplifying the userpace interface to "I'm fine with the provided transcript for use when I'm not here next time" write. Disadvantage is we use the cert parsing code (which is there anyway) and parse it all twice - once in kernel and probably again in userspace or at some remote verifier.

Quoted 4 lines

Right, the proposal is cache the public-key from pci_tsm_ops::connect() and at least require that the resulting transcript from that session establishment is properly self-signed. No point in continuing with a TSM implementation that is not self-consistent.

So I ended up dropping this bit of the proposal because there is no need for the kernel to be involved in any decision about the validity and sufficiency of device evidence. Userspace has everything it needs to make that initial determination. "Authenticated" simply means "evidence ready".

Automatic device recovery into the TCB is a separate concern that needs to be prepared to handle more than just "is this device able to generate a fresh signature with the same cert chain that userspace saw before". Yes, that is a minimal requirement but not sufficient for many cases. For example cases that want to validate measurements, interface reports, or opt-out of recovery because SPDM session loss is fatal.

I had a chat with Lukas about what I think that looks like this morning and offered to summarize that in this thread. Note, this description is assuming the drivers/pci/tsm/evidence.c implementation started here [1].

[1]: http://lore.kernel.org/[email protected]

Authenticate a device
=====================
Look in /sys/class/tsm to find a tsmN device which will be either an instance associated with native kernel PCI CMA/SPDM or a platform tsm like the one provided by AMD SEV-TIO, ARM CCA, Intel TDX, etc...

echo tsmN > /sys/bus/pci/devices/$device/tsm/connect

Once that succeeds the PCI/TSM evidence netlink interface is available to dump any signatures created during that session establishment.

After userspace is happy with that evidence it can bind a driver.

If running in a confidential VM where the TSM driver is capable of securing more than just an SPDM session the interface is:

echo tsmN > /sys/bus/pci/devices/$device/tsm/lock

Similar evidence can be collected, and when userspace is happy with it it can accept the device:

echo 1 > /sys/bus/pci/devices/$device/tsm/accept

...and bind a driver.

Auto-recover device (future work)
=================================
By default, devices fall out of the TCB on recovery events for the TDISP case and need userspace to rerun the lock and accept flow. For the native SPDM CMA case the default is that the kernel continues to operate the device post recovery and only userspace polling could detect device replacement.

To go beyond those defaults the kernel needs userpsace to tell it how to re-validate the device. I think that can be as simple as a netlink message to store hashes of cert chains or measurements and then use those in a new challenge / response with the device with a kernel decided nonce. It can also be more sophisiticated than that. The point is that it will be use case dependendent, policy driven, and separate from the "connect" or "lock" flow.

The equivalent behavior to what is present in this SPDM proposal is extend drivers/pci/tsm/evidence.c to add a netlink operation that tells the kernel to cache the public-key and challenge the device regenerate a valid signature. Then plumb all the recovery paths to call a new 'struct pci_tsm_ops::revalidate()' operation in all the same places where this patch set wants to reauthenticate. Later when something more sophisticated than "challenge the device to create a signature" comes along it can reuse those revalidate() hooks.

Auto-authenticate devices (future work)
=======================================
One of the nice aspects of the .cma keyring proposal is that it would be amenable to CONFIG_SYSTEM_EXTRA_CERTIFICATE. Give the kernel the certificate to validate device signatures without needing to ask userspace. That still comes with the caveat that it is not sufficient for the cases that care about more than mere signature verification. I am not opposed to that being built just that it is not a policy that is implied by the device "authenticated" state. It is policy that is incremental to the base which makes no assumptions about kernel verification.

#91 lore raw
Quoted 5 lines · On Wed, Mar 04, 2026 at 08:17:24PM -0800, [email protected] wrote:

So I ended up dropping this bit of the proposal because there is no need for the kernel to be involved in any decision about the validity and sufficiency of device evidence. Userspace has everything it needs to make that initial determination. "Authenticated" simply means "evidence ready".

Right, this caching was only for the automatic device recovery flow.

I think the kernel still probably needs to check the signed nonce against the device's public key as part of the SPDM handshake, but it doesn't need to do any validation of the public key.

Quoted 6 lines

Automatic device recovery into the TCB is a separate concern that needs to be prepared to handle more than just "is this device able to generate a fresh signature with the same cert chain that userspace saw before". Yes, that is a minimal requirement but not sufficient for many cases. For example cases that want to validate measurements, interface reports, or opt-out of recovery because SPDM session loss is fatal.

Yeah, but it is a pretty good starting point :)

Quoted 16 lines

Authenticate a device
=====================
Look in /sys/class/tsm to find a tsmN device which will be either an instance associated with native kernel PCI CMA/SPDM or a platform tsm like the one provided by AMD SEV-TIO, ARM CCA, Intel TDX, etc...

echo tsmN > /sys/bus/pci/devices/$device/tsm/connect

Once that succeeds the PCI/TSM evidence netlink interface is available to dump any signatures created during that session establishment.

After userspace is happy with that evidence it can bind a driver.

If running in a confidential VM where the TSM driver is capable of securing more than just an SPDM session the interface is:

Quoted 8 lines

echo tsmN > /sys/bus/pci/devices/$device/tsm/lock

Similar evidence can be collected, and when userspace is happy with it it can accept the device:

echo 1 > /sys/bus/pci/devices/$device/tsm/accept

...and bind a driver.

Makes sense to me, userspace can figure out what flow to use.

Quoted 7 lines

Auto-recover device (future work)
=================================
By default, devices fall out of the TCB on recovery events for the TDISP case and need userspace to rerun the lock and accept flow. For the native SPDM CMA case the default is that the kernel continues to operate the device post recovery and only userspace polling could detect device replacement.

Even with SPDM the kernel should know if the SPDM session has to be restarted right? It could squirt out a netlink multicast message, or a uevent on these events, so the polling is not baked into the architecture?

Quoted 5 lines

To go beyond those defaults the kernel needs userpsace to tell it how to re-validate the device. I think that can be as simple as a netlink message to store hashes of cert chains or measurements and then use those in a new challenge / response with the device with a kernel decided nonce.

Yeah, I see several reasonable options:

1) Above, kernel is informed of hashes and does an exact content compare

2) The driver operating the device has a built in same-device policy and deep parses the evidence for equivilance after the private key is validated, carefully avoiding volatile evidence bytes.

3) User provides a BPF program and it runs on the evidence

4) Standards bodies define a generic "same device check" algorithm for the evidence and core kernel just does this for compatible devices

Quoted 8 lines

The equivalent behavior to what is present in this SPDM proposal is extend drivers/pci/tsm/evidence.c to add a netlink operation that tells the kernel to cache the public-key and challenge the device regenerate a valid signature. Then plumb all the recovery paths to call a new 'struct pci_tsm_ops::revalidate()' operation in all the same places where this patch set wants to reauthenticate. Later when something more sophisticated than "challenge the device to create a signature" comes along it can reuse those revalidate() hooks.

That's a nice simple starting point.

Quoted 11 lines

Auto-authenticate devices (future work)
=======================================
One of the nice aspects of the .cma keyring proposal is that it would be amenable to CONFIG_SYSTEM_EXTRA_CERTIFICATE. Give the kernel the certificate to validate device signatures without needing to ask userspace. That still comes with the caveat that it is not sufficient for the cases that care about more than mere signature verification. I am not opposed to that being built just that it is not a policy that is implied by the device "authenticated" state. It is policy that is incremental to the base which makes no assumptions about kernel verification.

I have no specific objection to this, and we have many example of kernel having built-in policy with an option for userspace to take control. But I'd like to hear a solid reason for a use case that requires it. I don't really buy the argument that userspace is too much flash consumption if all the implementation is doing is checking a few certs using an already present TLS library.

Jason

#92 lore raw
Quoted 8 lines · Jason Gunthorpe
Quoted 5 lines · On Wed, Mar 04, 2026 at 08:17:24PM -0800, [email protected] wrote:

So I ended up dropping this bit of the proposal because there is no need for the kernel to be involved in any decision about the validity and sufficiency of device evidence. Userspace has everything it needs to make that initial determination. "Authenticated" simply means "evidence ready".

Quoted 5 lines

Right, this caching was only for the automatic device recovery flow.

I think the kernel still probably needs to check the signed nonce against the device's public key as part of the SPDM handshake, but it doesn't need to do any validation of the public key.

Yes, and I think this is a subtle detail that I was failing to grok / communicate previously. Of course the native SPDM implementation needs to be able to perform a challenge response to establish the session. All of the platform TSMs also internally carry out that protocol. The difference with platform TSMs being that the kernel only sees the resulting evidence.

So when / if the PCI/TSM evidence implementation grows kernel-internal revalidate support it will be growing something like pdev->tsm->pubkey that is installed / cached "after the fact" for platform TSMs. For the native SPDM driver, pdev->tsm->pubkey can be installed at 'struct pci_tsm_ops::connect()' time because it already has it parsed for its own internal purposes.

Quoted 44 lines
Quoted 6 lines

Automatic device recovery into the TCB is a separate concern that needs to be prepared to handle more than just "is this device able to generate a fresh signature with the same cert chain that userspace saw before". Yes, that is a minimal requirement but not sufficient for many cases. For example cases that want to validate measurements, interface reports, or opt-out of recovery because SPDM session loss is fatal.

Yeah, but it is a pretty good starting point :)

Quoted 16 lines

Authenticate a device
=====================
Look in /sys/class/tsm to find a tsmN device which will be either an instance associated with native kernel PCI CMA/SPDM or a platform tsm like the one provided by AMD SEV-TIO, ARM CCA, Intel TDX, etc...

echo tsmN > /sys/bus/pci/devices/$device/tsm/connect

Once that succeeds the PCI/TSM evidence netlink interface is available to dump any signatures created during that session establishment.

After userspace is happy with that evidence it can bind a driver.

If running in a confidential VM where the TSM driver is capable of securing more than just an SPDM session the interface is:

Quoted 8 lines

echo tsmN > /sys/bus/pci/devices/$device/tsm/lock

Similar evidence can be collected, and when userspace is happy with it it can accept the device:

echo 1 > /sys/bus/pci/devices/$device/tsm/accept

...and bind a driver.

Makes sense to me, userspace can figure out what flow to use.

Quoted 7 lines

Auto-recover device (future work)
=================================
By default, devices fall out of the TCB on recovery events for the TDISP case and need userspace to rerun the lock and accept flow. For the native SPDM CMA case the default is that the kernel continues to operate the device post recovery and only userspace polling could detect device replacement.

Quoted 4 lines

Even with SPDM the kernel should know if the SPDM session has to be restarted right? It could squirt out a netlink multicast message, or a uevent on these events, so the polling is not baked into the architecture?

Right, "SPDM session lost" is a reasonable notification to put into the interface and "future work" bucket. The polling comment was with respect to the near term limitation of this minimally viable first step.

Quoted 7 lines
Quoted 5 lines

To go beyond those defaults the kernel needs userpsace to tell it how to re-validate the device. I think that can be as simple as a netlink message to store hashes of cert chains or measurements and then use those in a new challenge / response with the device with a kernel decided nonce.

Yeah, I see several reasonable options:

+1 to these.

Quoted 6 lines

1) Above, kernel is informed of hashes and does an exact content compare

2) The driver operating the device has a built in same-device policy and deep parses the evidence for equivilance after the private key is validated, carefully avoiding volatile evidence bytes.

Yes, Lukas was also pointing out that the existing driver reset/resume handlers could signal "revalidated" as well if the PCI layer had no opinion.

...but the point is that it is a menu of options, not a static policy.

Quoted 14 lines

3) User provides a BPF program and it runs on the evidence

4) Standards bodies define a generic "same device check" algorithm for the evidence and core kernel just does this for compatible devices

Quoted 8 lines

The equivalent behavior to what is present in this SPDM proposal is extend drivers/pci/tsm/evidence.c to add a netlink operation that tells the kernel to cache the public-key and challenge the device regenerate a valid signature. Then plumb all the recovery paths to call a new 'struct pci_tsm_ops::revalidate()' operation in all the same places where this patch set wants to reauthenticate. Later when something more sophisticated than "challenge the device to create a signature" comes along it can reuse those revalidate() hooks.

Quoted 1 line

That's a nice simple starting point.

Appreciate it. Still want to close the loop with Lukas to make sure everything he wants to do for native SPDM can fit in this scheme, but I am feeling increasingly confident.

#93 lore raw
Quoted 15 lines · On Thu, 5 Mar 2026 11:49:27 -0800 [email protected] wrote:
Quoted 8 lines · Jason Gunthorpe
Quoted 5 lines · On Wed, Mar 04, 2026 at 08:17:24PM -0800, [email protected] wrote:

So I ended up dropping this bit of the proposal because there is no need for the kernel to be involved in any decision about the validity and sufficiency of device evidence. Userspace has everything it needs to make that initial determination. "Authenticated" simply means "evidence ready".

Quoted 5 lines

Right, this caching was only for the automatic device recovery flow.

I think the kernel still probably needs to check the signed nonce against the device's public key as part of the SPDM handshake, but it doesn't need to do any validation of the public key.

Quoted 6 lines

Yes, and I think this is a subtle detail that I was failing to grok / communicate previously. Of course the native SPDM implementation needs to be able to perform a challenge response to establish the session. All of the platform TSMs also internally carry out that protocol. The difference with platform TSMs being that the kernel only sees the resulting evidence.

I had a half written email on this but you two already covered much of it and discussion is heading where I was going anyway :)

For native / CMA:

I agree that the kernel 'should' check the signature in the final challenge/auth message so as to validate the whole flow was valid. Fine to leave cert checking to userspace - that was earlier in the flow anyway. I'd also like it to do sanity checks on the cert chain (for internal consistency and the few requirements PCI puts on the leaf cert), some of which it needs to do to pull the public key out of the leaf anyway. I think that's all cheap stuff to detect failures early.

As far as I can tell, the signature check isn't strictly necessary as userspace has all the info to do that signature check on the transcript. Bit of nasty parsing code but not particularly bad and likely to be in tooling anyway for debug purposes. (I'm assuming that's why there is a 'probably' above :)

We only _need_ the kernel to check the signature (in challenge/auth) for the (future) recovery flow.

As Alastair's rust demonstrates though it's low cost in terms of complexity to ensure internal consistency. I'm assuming the various TSMs are doing similar?

Anyhow, all that really says is I'd like the internal consistency of the SPDM session checking in now. Leave checking we actually trust the cert + measurements to user space (and all the stuff below on recovery comes later).

Quoted 7 lines

So when / if the PCI/TSM evidence implementation grows kernel-internal revalidate support it will be growing something like pdev->tsm->pubkey that is installed / cached "after the fact" for platform TSMs. For the native SPDM driver, pdev->tsm->pubkey can be installed at 'struct pci_tsm_ops::connect()' time because it already has it parsed for its own internal purposes.

Agreed it'll be something along those lines.

Quoted 91 lines
Quoted 45 lines
Quoted 7 lines

Automatic device recovery into the TCB is a separate concern that needs to be prepared to handle more than just "is this device able to generate a fresh signature with the same cert chain that userspace saw before". Yes, that is a minimal requirement but not sufficient for many cases. For example cases that want to validate measurements, interface reports, or opt-out of recovery because SPDM session loss is fatal.

Yeah, but it is a pretty good starting point :)

Quoted 16 lines

Authenticate a device
=====================
Look in /sys/class/tsm to find a tsmN device which will be either an instance associated with native kernel PCI CMA/SPDM or a platform tsm like the one provided by AMD SEV-TIO, ARM CCA, Intel TDX, etc...

echo tsmN > /sys/bus/pci/devices/$device/tsm/connect

Once that succeeds the PCI/TSM evidence netlink interface is available to dump any signatures created during that session establishment.

After userspace is happy with that evidence it can bind a driver.

If running in a confidential VM where the TSM driver is capable of securing more than just an SPDM session the interface is:

Quoted 8 lines

echo tsmN > /sys/bus/pci/devices/$device/tsm/lock

Similar evidence can be collected, and when userspace is happy with it it can accept the device:

echo 1 > /sys/bus/pci/devices/$device/tsm/accept

...and bind a driver.

Makes sense to me, userspace can figure out what flow to use.

Quoted 7 lines

Auto-recover device (future work)
=================================
By default, devices fall out of the TCB on recovery events for the TDISP case and need userspace to rerun the lock and accept flow. For the native SPDM CMA case the default is that the kernel continues to operate the device post recovery and only userspace polling could detect device replacement.

Quoted 4 lines

Even with SPDM the kernel should know if the SPDM session has to be restarted right? It could squirt out a netlink multicast message, or a uevent on these events, so the polling is not baked into the architecture?

Right, "SPDM session lost" is a reasonable notification to put into the interface and "future work" bucket. The polling comment was with respect to the near term limitation of this minimally viable first step.

Quoted 7 lines
Quoted 5 lines

To go beyond those defaults the kernel needs userpsace to tell it how to re-validate the device. I think that can be as simple as a netlink message to store hashes of cert chains or measurements and then use those in a new challenge / response with the device with a kernel decided nonce.

Yeah, I see several reasonable options:

+1 to these.

Quoted 6 lines

1) Above, kernel is informed of hashes and does an exact content compare

2) The driver operating the device has a built in same-device policy and deep parses the evidence for equivilance after the private key is validated, carefully avoiding volatile evidence bytes.

Yes, Lukas was also pointing out that the existing driver reset/resume handlers could signal "revalidated" as well if the PCI layer had no opinion.

...but the point is that it is a menu of options, not a static policy.

Quoted 14 lines

3) User provides a BPF program and it runs on the evidence

4) Standards bodies define a generic "same device check" algorithm for the evidence and core kernel just does this for compatible devices

Quoted 8 lines

The equivalent behavior to what is present in this SPDM proposal is extend drivers/pci/tsm/evidence.c to add a netlink operation that tells the kernel to cache the public-key and challenge the device regenerate a valid signature. Then plumb all the recovery paths to call a new 'struct pci_tsm_ops::revalidate()' operation in all the same places where this patch set wants to reauthenticate. Later when something more sophisticated than "challenge the device to create a signature" comes along it can reuse those revalidate() hooks.

Quoted 5 lines
Quoted 1 line

That's a nice simple starting point.

Appreciate it. Still want to close the loop with Lukas to make sure everything he wants to do for native SPDM can fit in this scheme, but I am feeling increasingly confident.

So far all sounds good to me.

Jonathan

#94 lore raw
Quoted 4 lines · Jonathan Cameron

Anyhow, all that really says is I'd like the internal consistency of the SPDM session checking in now. Leave checking we actually trust the cert + measurements to user space (and all the stuff below on recovery comes later).

I think kernel doing internal consistency (non policy) checks is fine

We just need to be careful to construct things so it is compatible with an external verifier. Even in this bare SPDM mode we still want to hook it into external verification.

I don't know alot, but I understand this requires the nonce (?) to come from userspace? I suppose the flow is the usual crypto something like
- Kernel negotiates a DH session/CSPRNG with the peer, generates symmetric keys from the CSPRNG
- Kernel forwards a nonce challenge and peer signs it, somehow mixing in CSPRNG data to bind to the DH session
- Kernel checks the signature against the public key and confirms the CSPRNG and nonce are correct (only kernel can do this since the CSPRNG must not leak out of the kernel)
- Kernel forwards the signature from the peer to userspace and userspace re-checks the signature against the public key and validates the nonce is the one it issued, and checks that the public key is acceptable. Ignores the CSPRNG data.
- Presumably in a real TSM there will be a merkle tree signed by the TSM's platform key that binds together all the TVM measurements and the SPDM activity so the verifier can see a complete picture?

ie that the verifier pushed nonce A authenticated SPDM session B which is part of PCI device C that is part of TVM D.

Should kernel be extending TPM PCRs or something in this bare SPDM mode to provide something similar using the TPM to produce platform evidence?

Of course all of this should try to align with the way TSMs are working so we have as uniform as possible uAPI for evidence transfer. Though obviously a kernel SPDM has to be distinguisable from any other TSM from a verifier POV.

Jason

#95 lore raw
Quoted 7 lines · On Mon, 9 Mar 2026 09:31:48 -0300 Jason Gunthorpe <[email protected]> wrote:
Quoted 4 lines · Jonathan Cameron

Anyhow, all that really says is I'd like the internal consistency of the SPDM session checking in now. Leave checking we actually trust the cert + measurements to user space (and all the stuff below on recovery comes later).

Quoted 8 lines

I think kernel doing internal consistency (non policy) checks is fine

We just need to be careful to construct things so it is compatible with an external verifier. Even in this bare SPDM mode we still want to hook it into external verification.

I don't know alot, but I understand this requires the nonce (?) to come from userspace?

Going out of my area of expertise... So take the following with a pinch of salt.

Yes to userspace provided nonce. My understanding is that can come from the verifier that wants to ensure freshness.

I'm not sure exactly what our security model is in the native CMA case, so what software we can trust on the host. I.e. does the DH session actually need to be between the kernel and the peer?

Quoted 6 lines

I suppose the flow is the usual crypto something
like
- Kernel negotiates a DH session/CSPRNG with the peer, generates symmetric keys from the CSPRNG
- Kernel forwards a nonce challenge and peer signs it, somehow mixing in CSPRNG data to bind to the DH session

We have a small amount of context (8 bytes) that we can put anything as part of challenge/auth (alongside the nonce) It will part of the signed response. Would that work for something from with the CSPRNG, mixed so that you can't go from that context to the CSPRNG value?

Quoted 14 lines

- Kernel checks the signature against the public key and confirms the CSPRNG and nonce are correct (only kernel can do this since the CSPRNG must not leak out of the kernel)
- Kernel forwards the signature from the peer to userspace and userspace re-checks the signature against the public key and validates the nonce is the one it issued, and checks that the public key is acceptable. Ignores the CSPRNG data.
- Presumably in a real TSM there will be a merkle tree signed by the TSM's platform key that binds together all the TVM measurements and the SPDM activity so the verifier can see a complete picture?

ie that the verifier pushed nonce A authenticated SPDM session B which is part of PCI device C that is part of TVM D.

Quoted 8 lines

Should kernel be extending TPM PCRs or something in this bare SPDM mode to provide something similar using the TPM to produce platform evidence?

Of course all of this should try to align with the way TSMs are working so we have as uniform as possible uAPI for evidence transfer. Though obviously a kernel SPDM has to be distinguisable from any other TSM from a verifier POV.

Agreed. Very interesting to know what exactly is going in the TSM SPDM exchanges as hopefully that will reflect best practice. If we are really lucky they won't all do different things ;)

Jonathan

Quoted 2 lines

Jason

#96 lore raw
Quoted 3 lines · Jonathan Cameron

I'm not sure exactly what our security model is in the native CMA case, so what software we can trust on the host. I.e. does the DH session actually need to be between the kernel and the peer?

Yes, absolutely a DH session is required in all cases, it is the only way to generate a PCI encryption shared secret and exclude a MIM.

For native the verifer should be able to measure the running kernel using the TPM, and then confirm the measured kernel and the peer have established a MIM free PCIe encryption, and finally measure the peer device.

For instance, imagine an enhanced version of "Network-Bound Disk Encryption" where the storage key is not released unless the server also validates PCI properties (physically encrypted with no MIM, right devices, etc)

Quoted 11 lines
Quoted 6 lines

I suppose the flow is the usual crypto something
like
- Kernel negotiates a DH session/CSPRNG with the peer, generates symmetric keys from the CSPRNG
- Kernel forwards a nonce challenge and peer signs it, somehow mixing in CSPRNG data to bind to the DH session

We have a small amount of context (8 bytes) that we can put anything as part of challenge/auth (alongside the nonce) It will part of the signed response. Would that work for something from with the CSPRNG, mixed so that you can't go from that context to the CSPRNG value?

I assume SPDM is doing this already somehow otherwise there is no way to setup the PCI encryption keys. The fundamental purpose of the signature is to exclude a MIM by signing something that proves there is no MIM, and that is usually a value that both sides independently pull out of the CSPRNG. If they derive the same value then they have the same CSPRNG and DH tells us nobody else can have it. But there are other approaches too..

Quoted 8 lines
Quoted 4 lines

Of course all of this should try to align with the way TSMs are working so we have as uniform as possible uAPI for evidence transfer. Though obviously a kernel SPDM has to be distinguisable from any other TSM from a verifier POV.

Agreed. Very interesting to know what exactly is going in the TSM SPDM exchanges as hopefully that will reflect best practice. If we are really lucky they won't all do different things ;)

Yeah, and I don't really know the details, just have some general idea how attestation and PCI link encryption should work in broad strokes.

But I know people who do, so if we can get a series that clearly lays out the proposed kernel flow I can possibly get someone to compare it..

The baseline I expect is a merkle tree signed by the root of trust (TPM or platform TSM) that encompases everything down to the required bits of the SPDM negotiation to prove no MIM.

Jason

#97 lore raw
Quoted 8 lines · On Mon, 9 Mar 2026 12:59:09 -0300 Jason Gunthorpe <[email protected]> wrote:
Quoted 3 lines · Jonathan Cameron

I'm not sure exactly what our security model is in the native CMA case, so what software we can trust on the host. I.e. does the DH session actually need to be between the kernel and the peer?

Yes, absolutely a DH session is required in all cases, it is the only way to generate a PCI encryption shared secret and exclude a MIM.

Ah. I was missing what you wanted with the DH part. For some reason wasn't thinking about IDE (maybe because this patch set doesn't get you there for native). Though as I understand it some of the native usecases for CMA aren't using link encryption (different security model from CoCo).

Yes, if you want to avoid MIM you need to bring up IDE etc - the basic fact that both ends can still talk to each other after enabling it given they have to have the same keys and those went over the secure channel, is part of the security around that. I think you can attest before that point and rely on the link 'failing' if there was a man in the middle. The verifier would want the evidence IDE was up before giving you any secrets.

Whether anyone actually implements root ports via standard DOE flows or everyone does this a custom way at the host is an open question. There are various mix and match situations where the Coco / TSM flows mean we have an encrypted channel, but we are doing our own native attestation (to a different DOE, say on a VF). Anyhow, others on this thread have more info (I think) on the corner cases of how this is going to be used than I do.

The secure channel establishment and key exchange comes later in the SPDM flow than this patch set currently covers. This bit just gets you to the point where you know you are ultimately talking to right device - you don't know there isn't a MIM at this point.

Next up is KEY_EXCHANGE / KEY_EXCHANGE_RSP / FINISH / FINISH_RSP that will exchange the public ephemeral keys and ultimately get us a secure channel. Then we then switch to secured-CMA which is the PCIe spec and is basically the format for wrapping up the session IDs etc + the encrypted channel. Lukas' github has the next few bits required, but not sure he's all the way there yet?

That then gets used for key exchange messages related to IDE via the PCI spec defined IDE_KM protocol. (which is fairly simple compared to the other steps - only about 8 pages :)).

So we may need to get a bit further before we can do the attestation flow that would be equivalent to what CoCo needs. I'm thinking we may need one more set of measurements to verify both ends think IDE is up.

For now the host will know it didn't bring up IDE.

Quoted 23 lines

For native the verifer should be able to measure the running kernel using the TPM, and then confirm the measured kernel and the peer have established a MIM free PCIe encryption, and finally measure the peer device.

For instance, imagine an enhanced version of "Network-Bound Disk Encryption" where the storage key is not released unless the server also validates PCI properties (physically encrypted with no MIM, right devices, etc)

Quoted 11 lines
Quoted 6 lines

I suppose the flow is the usual crypto something
like
- Kernel negotiates a DH session/CSPRNG with the peer, generates symmetric keys from the CSPRNG
- Kernel forwards a nonce challenge and peer signs it, somehow mixing in CSPRNG data to bind to the DH session

We have a small amount of context (8 bytes) that we can put anything as part of challenge/auth (alongside the nonce) It will part of the signed response. Would that work for something from with the CSPRNG, mixed so that you can't go from that context to the CSPRNG value?

Quoted 7 lines

I assume SPDM is doing this already somehow otherwise there is no way to setup the PCI encryption keys. The fundamental purpose of the signature is to exclude a MIM by signing something that proves there is no MIM, and that is usually a value that both sides independently pull out of the CSPRNG. If they derive the same value then they have the same CSPRNG and DH tells us nobody else can have it. But there are other approaches too..

For SPDM it's this or pre shared key stuff which requires some out of band way path. So probably ignore that.

Quoted 13 lines
Quoted 9 lines
Quoted 5 lines

Of course all of this should try to align with the way TSMs are working so we have as uniform as possible uAPI for evidence transfer. Though obviously a kernel SPDM has to be distinguisable from any other TSM from a verifier POV.

Agreed. Very interesting to know what exactly is going in the TSM SPDM exchanges as hopefully that will reflect best practice. If we are really lucky they won't all do different things ;)

Yeah, and I don't really know the details, just have some general idea how attestation and PCI link encryption should work in broad strokes.

Quoted 7 lines

But I know people who do, so if we can get a series that clearly lays out the proposed kernel flow I can possibly get someone to compare it..

The baseline I expect is a merkle tree signed by the root of trust (TPM or platform TSM) that encompases everything down to the required bits of the SPDM negotiation to prove no MIM.

Agreed, that's my basic undestanding as well and likewise, can get some appropriate folk to check it out once we have the full thing described.

Jonathan

Quoted 3 lines

Jason

#98 lore raw
Quoted 9 lines · Jonathan Cameron
Quoted 6 lines · On Mon, 9 Mar 2026 12:59:09 -0300 Jason Gunthorpe <[email protected]> wrote:
Quoted 3 lines · Jonathan Cameron

I'm not sure exactly what our security model is in the native CMA case, so what software we can trust on the host. I.e. does the DH session actually need to be between the kernel and the peer?

Quoted 7 lines
Quoted 2 lines

Yes, absolutely a DH session is required in all cases, it is the only way to generate a PCI encryption shared secret and exclude a MIM.

Ah. I was missing what you wanted with the DH part. For some reason wasn't thinking about IDE (maybe because this patch set doesn't get you there for native). Though as I understand it some of the native usecases for CMA aren't using link encryption (different security model from CoCo).

Yeah, there are models where you could collect evidence and not have any IDE setup where you have greater trust in physical security.

Quoted 4 lines

Yes, if you want to avoid MIM you need to bring up IDE etc - the basic fact that both ends can still talk to each other after enabling it given they have to have the same keys and those went over the secure channel, is part of the security around that.

No.. With DH systems something can sit in the middle and encrypt/decrypt and you can't detect that unless you sign something derived from the DH the other side can validate.

Quoted 2 lines

Whether anyone actually implements root ports via standard DOE flows or everyone does this a custom way at the host is an open question.

I'm expecting Linux will be able to setup Link IDE, either through a platform TSM as you say, or through someone plugging in the IDE registers into some Linux drivers.. I certainly don't want to close that door by bad uAPI design.

Quoted 4 lines

The secure channel establishment and key exchange comes later in the SPDM flow than this patch set currently covers. This bit just gets you to the point where you know you are ultimately talking to right device - you don't know there isn't a MIM at this point.

Hmm, like I said I don't really know the flow, but something has to bind the DH into the evidence for it to be useful, if that comes after (seems backwards, but OK) then as long as the evidence reporting and controlling uAPI is happy with all these different flows OK..

Jason

#99 lore raw

Jason Gunthorpe wrote:
[..]

Quoted 7 lines
Quoted 2 lines

Whether anyone actually implements root ports via standard DOE flows or everyone does this a custom way at the host is an open question.

I'm expecting Linux will be able to setup Link IDE, either through a platform TSM as you say, or through someone plugging in the IDE registers into some Linux drivers.. I certainly don't want to close that door by bad uAPI design.

Right now there is no extra uAPI for IDE. It is an implicit detail of the given TSM whether the "connect" operation additionally establishes IDE. The result of whether or not "connect" established selective-stream-IDE with the device is conveyed in the arrival of "stream" links in sysfs, see:

Documentation/ABI/testing/sysfs-devices-pci-host-bridge

You also asked:

Quoted 6 lines

Yeah, and I don't really know the details, just have some general idea how attestation and PCI link encryption should work in broad strokes.

But I know people who do, so if we can get a series that clearly lays out the proposed kernel flow I can possibly get someone to compare it..

tl;dr: can you point them at http://lore.kernel.org/[email protected]

A couple notes that the host kernel is unable to establish IDE without a platform TSM on all but Intel platforms (that I know of). At a minimum, this is why I think native SPDM should behave as a TSM driver. Platform TSM involvement for IDE is the predominant architecture in the ecosystem.

As for link encryption and attestation it is all rooted in the launch attestation of the VM. Once you trust that the TSM that claims to be present is valid then you trust all of that TSMs ABIs to enforce confidentiality and integrity.

Now, a TSM is free to decide, "I do not need PCI link encryption because I have apriori knowledge that $device has a connection to the system that meets confidentiality + integrity expectations". So link encryption is present for discrete devices, but maybe not integrated devices.

Assuming VM launch attesation gets you trust in the guest TSM driver responses, then the attestation flow to the kernel is mostly just marshaling blobs and digests:

1/ Host collects a fresh copy of device measurements with a guest provided nonce (response emitted by PCI/TSM netlink, nonce received via guest-to-host communication, see AF_VSOCK comment in 2/).

2/ Host marshals cert chain, measurements (signed transcript with nonce from 1/), and interface report blob to guest via an untrusted channel. I am currently thinking just use a common transport like AF_VSOCK to get those blobs into the guest and not have each implementation reinvent that blob transfer wheel.

3/ Guest needs to validate that blobs are indeed the ones the TSM expects. Each TSM has a private message protocol to request digests of the blob contents for this purpose.

At no point is the guest offered explicit PCI link encryption details, nor the host for that matter. I think some TSMs might include the key exchange steps in the SPDM transcript. However, that happens within an SPDM secure session, so host can not otherwise observe it. SPDM does support mutual authentication so the device could in theory challenge whether it is talking to a device-approved TSM.

The open question I generated typing this up, is that if a common transport is used to get the blobs into guest userspace, that userspace still needs to push the "interface report" blob into the guest kernel. Kernel needs that to determine how to map private vs shared MMIO. I still think I prefer that to each implementation having a set of implementation specific message passing ioctls() to do the same.

#100 lore raw
In reply to Lukas Wunner
Quoted 8 lines · Lukas Wunner
Quoted 3 lines · Jason Gunthorpe

IOW the resume/RAS acceptance criteria is that the second nonce was signed with the same private key(s) that the first nonce was signed with.

What you seem to have in mind is essentially a "trust on first use" model where trust is given to a specific device certificate (i.e. leaf certificate), not to a root certificate.

Not really, please read my email again.

I said userspace does the verification, using all the certificate chains and beyond. Then once verified the kernel only does a 'same device' check that ensures the device hasn't changed from what was originally verified.

Spec supports this just fine.

Quoted 3 lines

certificates. These could be vendors, but it's also possible that e.g. a CSP operates its own CA and provisions one of the 8 slots with a custom certificate chain anchored in its own CA.

And the userspace verifier is free to check all of this.

Quoted 3 lines

An alternative solution would be to have the verifier in user space operate its own mini CA. The root certificate of that mini CA would be added to the .cma keyring.

No! Why are you trying to massively over complicate this? The proposal is very simple :(

Quoted 5 lines
Quoted 3 lines

Linux will have its own sw model, the spec is just the protocol definition. In the CC world everyone just knows the verifier needs to be external.. How else could it even work?

There are products out there which support CMA but not TDISP.

Sure, but that doesn't mean anything for verification.

Most models I've seen for using this stuff are "cloud connected" things where the cloud is going to measure and attest the end device before giving it anything sensitive.

That's remote verification, and what you absolutely don't want is some way for the attacker to pass remote verification, then replace the device and somehow pass a much weaker local only verification and attack the security.

This is why I'm insistent the starting point for resmue is a very strong same-device check that prevents attackers from replacing the device with something that wouldn't pass remote verification.

If you don't do this and instead try to revalidate the certificate chains the kernel can be tricked into accepting a different device on resume and that will completely destroy the entire security model.

Quoted 3 lines

In other words, the CC world isn't everything. The modest goal of this series is to allow authentication of devices in compliance with PCIe r7.0 sec 6.31 and the SPDM spec.

As Dan and I keep saying you should focus on enabling userspace verifier as the very first modest step and then come with proposals to add additional things like resume and perhaps a kernel-internal verifier.

I don't see a role for a cma keyring outside a kernel-internel verifier (and I'm skeptical this is desirable in the first place since a userspace implementation would not be such a burden)

Jason

maintainer
#101 lore raw
Quoted 7 lines · Jason Gunthorpe

This is why I'm insistent the starting point for resmue is a very strong same-device check that prevents attackers from replacing the device with something that wouldn't pass remote verification.

If you don't do this and instead try to revalidate the certificate chains the kernel can be tricked into accepting a different device on resume and that will completely destroy the entire security model.

Finding a different device on resume is par for the course for hotplug.

Quoted 4 lines

As Dan and I keep saying you should focus on enabling userspace verifier as the very first modest step and then come with proposals to add additional things like resume and perhaps a kernel-internal verifier.

There is nothing to "add". Seamless re-verification on resume and error recovery is already implemented in my patches. I don't see the point of throwing that out the window and start from scratch just because you think it doesn't have priority.

Quoted 2 lines

I don't see a role for a cma keyring outside a kernel-internel verifier

That sounds like we have minimal consensus.

I'm coming from a very different direction, I want this to seamlessly integrate with all the infrastructure we already have in the PCI core (hotplug, suspend/resume, error recovery, ...), so I made sure it does.

I don't share the view that CMA is merely a building block for TDISP. It's useful on its own.

I also believe that the vast majority of users will simply need this to ensure the devices they attach to their chromebooks, phones etc are authentic (seems important given the reports of counterfeit hard drives). A .cma keyring is good enough for Grandma's chromebook, no need for a user space verifier.

Thanks,

Lukas

#102 lore raw
Quoted 10 lines · Lukas Wunner
Quoted 7 lines · Jason Gunthorpe

This is why I'm insistent the starting point for resmue is a very strong same-device check that prevents attackers from replacing the device with something that wouldn't pass remote verification.

If you don't do this and instead try to revalidate the certificate chains the kernel can be tricked into accepting a different device on resume and that will completely destroy the entire security model.

Finding a different device on resume is par for the course for hotplug.

And? The point is we must detect and reject this as a matter of security.

Quoted 9 lines
Quoted 4 lines

As Dan and I keep saying you should focus on enabling userspace verifier as the very first modest step and then come with proposals to add additional things like resume and perhaps a kernel-internal verifier.

There is nothing to "add". Seamless re-verification on resume and error recovery is already implemented in my patches. I don't see the point of throwing that out the window and start from scratch just because you think it doesn't have priority.

The objection is it doesn't implement a sufficient security model, and I'm strongly arguing you have built the wrong thing.

The suggested path is not about priority, but to follow the path of consense where we all agree a userspace verifier is a required component, so we should start with the parts we all agree on and incrementally work on the dis-agreed parts.

Saying I already built it so I should get to merge it is not constructive.

Quoted 3 lines

I'm coming from a very different direction, I want this to seamlessly integrate with all the infrastructure we already have in the PCI core (hotplug, suspend/resume, error recovery, ...), so I made sure it does.

Well, this is security work. Linking a bunch of stuff together without having a clear threat model and desired seurity properties is not the right approach at all.

The kernel needs to enforce security properties and present a consistent threat model, and what you have done is inconsisent and weaker than the models we do want to build.

It also seems all your use cases fit within the broder models, so I don't see why we should enshrine a bad security model in the kernel, as UAPI at this point.

Quoted 2 lines

I don't share the view that CMA is merely a building block for TDISP. It's useful on its own.

I agree it is useful on its own, but when used without TDISP we still want to have the same basic threat model and overall security posture, it is not an excusse to just weaken everything.

We are looking at building CMA based, non-TDISP systems that need all the stuff I'm talking about.

Quoted 5 lines

I also believe that the vast majority of users will simply need this to ensure the devices they attach to their chromebooks, phones etc are authentic (seems important given the reports of counterfeit hard drives). A .cma keyring is good enough for Grandma's chromebook, no need for a user space verifier.

A chromebook has no issue to run a userspace verifier, and overall the kernel community prefers to put things to userspace whenever possible.

Given a userspace solution will have to exist I think you have a fairly high bar to cross to propose duplicating it into the kernel, which is why it should come after the userspace path is setup so it can be properly explained and justified.

Jason

#103 lore raw
In reply to Lukas Wunner
Quoted 18 lines · Lukas Wunner
Quoted 6 lines · Jason Gunthorpe
Quoted 3 lines

It has turned out to be super convenient to expose the 8 slots with certificate chains in sysfs for direct examination with openssl and similar tools, without having to go through netlink.

Honestly, I'm reluctant to add permanent sysfs uAPI just for temporary debugging. Put it in debugfs.

Exposure of the certificates in the SPDM slots is not for debugging, it's just super convenient for day-to-day use.

Quoted 3 lines

Having to find/remember some baroque openssl command line with a million options is not reasonable for a production kind of environment.

Personally I find something like the following neither baroque nor unreasonable:

Quoted 8 lines

# What's the certificate chain in slot0?
openssl storeutl -text /sys/bus/pci/devices/0000:03:00.0/certificates/slot0

# Fingerprint of root cert in slot0, does it match what vendor claims? openssl x509 -fingerprint -in /sys/bus/pci/devices/0000:03:00.0/certificates/slot0

# Looks good, let's trust it:
keyctl padd asymmetric "" %:.cma < /sys/bus/pci/devices/0000:03:00.0/certificates/slot0

As much fun as it is to abuse sysfs, please, let's not do this there. You just did something that could have changed the device between storing, checking and then trusting it as the device is never guaranteed to remain the same across multiple calls to sysfs (i.e. yanked out and another added.)

So let's not design in a security issue from the start please :)

thanks,

greg k-h

maintainer
#104 lore raw
Quoted 7 lines · Greg KH
Quoted 6 lines · Lukas Wunner

# What's the certificate chain in slot0?
openssl storeutl -text /sys/bus/pci/devices/0000:03:00.0/certificates/slot0

# Fingerprint of root cert in slot0, does it match what vendor claims? openssl x509 -fingerprint -in /sys/bus/pci/devices/0000:03:00.0/certificates/slot0

Quoted 8 lines
Quoted 2 lines

# Looks good, let's trust it:
keyctl padd asymmetric "" %:.cma < /sys/bus/pci/devices/0000:03:00.0/certificates/slot0

As much fun as it is to abuse sysfs, please, let's not do this there. You just did something that could have changed the device between storing, checking and then trusting it as the device is never guaranteed to remain the same across multiple calls to sysfs (i.e. yanked out and another added.)

No, the kernel caches the certificate chains read from the SPDM slots and what is exposed in sysfs is that cached copy. So all three commands above pertain to the same certificate chain.

Quoted 1 line

So let's not design in a security issue from the start please :)

The alleged security issue does not exist.

Thanks,

Lukas

#105 lore raw
Quoted 17 lines · Lukas Wunner
Quoted 7 lines · Greg KH
Quoted 6 lines · Lukas Wunner

# What's the certificate chain in slot0?
openssl storeutl -text /sys/bus/pci/devices/0000:03:00.0/certificates/slot0

# Fingerprint of root cert in slot0, does it match what vendor claims? openssl x509 -fingerprint -in /sys/bus/pci/devices/0000:03:00.0/certificates/slot0

Quoted 8 lines
Quoted 2 lines

# Looks good, let's trust it:
keyctl padd asymmetric "" %:.cma < /sys/bus/pci/devices/0000:03:00.0/certificates/slot0

As much fun as it is to abuse sysfs, please, let's not do this there. You just did something that could have changed the device between storing, checking and then trusting it as the device is never guaranteed to remain the same across multiple calls to sysfs (i.e. yanked out and another added.)

Quoted 3 lines

No, the kernel caches the certificate chains read from the SPDM slots and what is exposed in sysfs is that cached copy. So all three commands above pertain to the same certificate chain.

So if a device is removed and a different one added between steps 2 and three above, with the same pci number, all is ok?

Remember, PCI device ids are not unique, they can come and go and be reordered and reused at any point in time. They are fully dynamic and should NEVER be used as a unique identifier except for a specific moment in time.

In other words, you are expecting that device id to always refer to the same device across all 3 operations, which is never guaranteed.

Quoted 3 lines
Quoted 1 line

So let's not design in a security issue from the start please :)

The alleged security issue does not exist.

How is this not the classic definition of a TOCTOU (time-of-check to time-of-use) problem?

thanks,

greg k-h

maintainer
#106 lore raw
Quoted 15 lines · Greg KH
Quoted 11 lines · Lukas Wunner
Quoted 10 lines · Greg KH
Quoted 8 lines · Lukas Wunner

# What's the certificate chain in slot0?
openssl storeutl -text /sys/bus/pci/devices/0000:03:00.0/certificates/slot0

# Fingerprint of root cert in slot0, does it match what vendor claims? openssl x509 -fingerprint -in /sys/bus/pci/devices/0000:03:00.0/certificates/slot0

# Looks good, let's trust it:
keyctl padd asymmetric "" %:.cma < /sys/bus/pci/devices/0000:03:00.0/certificates/slot0

So if a device is removed and a different one added between steps 2 and three above, with the same pci number, all is ok?

Quoted 7 lines

Remember, PCI device ids are not unique, they can come and go and be reordered and reused at any point in time. They are fully dynamic and should NEVER be used as a unique identifier except for a specific moment in time.

In other words, you are expecting that device id to always refer to the same device across all 3 operations, which is never guaranteed.

If the user chooses to replace the device between steps 2 and 3, they get to keep the pieces.

Thanks,

Lukas

#107 lore raw
Quoted 21 lines · Lukas Wunner
Quoted 20 lines · Greg KH
Quoted 11 lines · Lukas Wunner
Quoted 10 lines · Greg KH
Quoted 8 lines · Lukas Wunner

# What's the certificate chain in slot0?
openssl storeutl -text /sys/bus/pci/devices/0000:03:00.0/certificates/slot0

# Fingerprint of root cert in slot0, does it match what vendor claims? openssl x509 -fingerprint -in /sys/bus/pci/devices/0000:03:00.0/certificates/slot0

# Looks good, let's trust it:
keyctl padd asymmetric "" %:.cma < /sys/bus/pci/devices/0000:03:00.0/certificates/slot0

So if a device is removed and a different one added between steps 2 and three above, with the same pci number, all is ok?

Remember, PCI device ids are not unique, they can come and go and be reordered and reused at any point in time. They are fully dynamic and should NEVER be used as a unique identifier except for a specific moment in time.

Quoted 5 lines
Quoted 2 lines

In other words, you are expecting that device id to always refer to the same device across all 3 operations, which is never guaranteed.

If the user chooses to replace the device between steps 2 and 3, they get to keep the pieces.

So you are saying that is ok to do:
- test if we trust the fingerprint
- trust the device of a fingerprint we didn't check

Then why do step 1 here at all?

Perhaps sysfs just isn't the best api here :)

thanks,

greg k-h

#108 lore raw
In reply to Lukas Wunner
Quoted 8 lines · Lukas Wunner
Quoted 2 lines · Jason Gunthorpe

And not sure we should be dumping any certs in sysfs if the plan for the other stuff is netlink, it should be consistent I think.

It has turned out to be super convenient to expose the 8 slots with certificate chains in sysfs for direct examination with openssl and similar tools, without having to go through netlink.

Quoted 9 lines

Originally the plan was to make the certificates/slot[0..7] files also writable and the kernel would implicitly perform a SET_CERTIFICATE SPDM exchange with the device when writing to those files. Unfortunately with SPDM 1.3 the spec editors made things more complicated, *cough* sorry *more flexible* with additional CertModel and KeyPairID attributes. That additional complexity makes it less suitable for sysfs, hence for *provisioning* netlink is indeed the better choice. But just for *reading* the certificates in the slots, sysfs exposure is very useful.

Useful, but not required, right? The consistency argument from Jason is growing on me. Merely validating the certificate chain is not sufficient to establish trust in the device configuration. So, if a $pci_auth_tool is needed in the flow at all is there a practical difference between:

	$pci_auth_tool cert --device $dev --slot $slot | openssl

...and:

cat /sys/bus/pci/devices/$dev/certificates/$slot | openssl

maintainer
#109 lore raw
In reply to [email protected]
Quoted 7 lines · On Wed, Feb 18, 2026 at 03:40:10PM -0800, [email protected] wrote:

However, I notice that Aneesh needs x509 certificate parsing for his TSM driver [1], I think TDX would benefit from the same to offload needing to specify the wall-clock time to the module [2] for cert verification, and SEV-TIO (already upstream) is currently missing any facility for the host to attest the device.

[1]: http://lore.kernel.org/[email protected]

There's a newer version:

https://lore.kernel.org/all/[email protected]/

This would allow upstreaming at least the three X.509 patches at the beginning of my CMA series (and Alistair's rSPDM series) and thus reduce the patch count a little bit.

However I don't know how far along Aneesh's CCA work is.

Note that David Howells' introduction of ML-DSA in v7.0 moves around a lot of the X.509 code so the three X.509 patches for CMA will no longer apply cleanly:

https://lore.kernel.org/all/[email protected]/

I'll rebase my development branch after v7.0-rc1 is out and Aneesh can then pick up the latest version from it:

https://github.com/l1k/linux/commits/doe

Thanks,

Lukas

#110 lore raw
Quoted 7 lines · Lukas Wunner
Quoted 6 lines · On Wed, Feb 18, 2026 at 03:40:10PM -0800, [email protected] wrote:

However, I notice that Aneesh needs x509 certificate parsing for his TSM driver [1], I think TDX would benefit from the same to offload needing to specify the wall-clock time to the module [2] for cert verification, and SEV-TIO (already upstream) is currently missing any facility for the host to attest the device.

Quoted 5 lines
Quoted 1 line

[1]: http://lore.kernel.org/[email protected]

There's a newer version:

https://lore.kernel.org/all/[email protected]/

As I understand this still has a dependency on a new base ARM CCA posting. So the nearest term solution is to just wire up the existing certificate blobs retrieved by the upstream SEV-TIO driver.

Quoted 3 lines

This would allow upstreaming at least the three X.509 patches at the beginning of my CMA series (and Alistair's rSPDM series) and thus reduce the patch count a little bit.

ARM CCA only needs to parse the certificates to confirm to the TSM which public-key to use. I am struggling to find another near term need for the kernel to parse the certificate.

Quoted 6 lines

However I don't know how far along Aneesh's CCA work is.

Note that David Howells' introduction of ML-DSA in v7.0 moves around a lot of the X.509 code so the three X.509 patches for CMA will no longer apply cleanly:

Quoted 6 lines

https://lore.kernel.org/all/[email protected]/

I'll rebase my development branch after v7.0-rc1 is out and Aneesh can then pick up the latest version from it:

https://github.com/l1k/linux/commits/doe

Sounds good, but I want to focus on the blob dump interface for the existing upstream SPDM requester, SEV-TIO, and then go from there.

#111 lore raw
In reply to Jason Gunthorpe
Quoted 15 lines · On Tue, 17 Feb 2026 19:56:04 -0400 Jason Gunthorpe <[email protected]> wrote:
Quoted 14 lines · On Wed, Feb 11, 2026 at 01:29:07PM +1000, [email protected] wrote:

From: Alistair Francis <[email protected]>

Security Protocols and Data Models (SPDM) [1] is used for authentication, attestation and key exchange. SPDM is generally used over a range of transports, such as PCIe, MCTP/SMBus/I3C, ATA, SCSI, NVMe or TCP.

Quoted 1 line

From the kernels perspective SPDM is used to authenticate and attest devices.

In this threat model a device is considered untrusted until it can be verified by the kernel and userspace using SPDM. As such SPDM data is untrusted data that can be mallicious.

The SPDM specification is also complex, with the 1.2.1 spec being almost 200 pages and the 1.3.0 spec being almost 250 pages long.

Quoted 6 lines
Quoted 3 lines

As such we have the kernel parsing untrusted responses from a complex specification, which sounds like a possible exploit vector. This is the type of place where Rust excels!

I was arguing for exactly this at a recent conference, so I'm glad to see it. It is a great meaningful usecase for rust in the kernel.

FWIW I'm fully on board with this as well. More than happy to see my C code Lukas has been carrying go away in favor of the Rust :) It'll also finally make me learn more than a trivial level of Rust.

Jonathan