lib: Rust implementation of SPDM
Subsystems (11)
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
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
@@ -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
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
@@ -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
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
@@ -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
@@ -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
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
@@ -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
@@ -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
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
@@ -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
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
@@ -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
@@ -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
@@ -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
Gary Guo
reviewerRe: [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()andcrypto_free_shash()available to Rust.Signed-off-by Alistair Francis <[email protected]>--- 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.crust/bindings/bindings_helper.h +2
-0@@ -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@@ -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@@ -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"
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()andcrypto_free_shash()available to Rust.Signed-off-by Alistair Francis <[email protected]>--- 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.crust/bindings/bindings_helper.h +0
-0
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 @@
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
@@ -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
Gary Guo
reviewerRe: [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
Signed-off-by Alistair Francis <[email protected]>--- rust/kernel/error.rs | 7 +++++++ 1 file changed, 7 insertions(+)rust/kernel/error.rs +1
-0@@ -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.
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
Signed-off-by Alistair Francis <[email protected]>--- rust/kernel/error.rs | 7 +++++++ 1 file changed, 7 insertions(+)rust/kernel/error.rs +1
-0@@ -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
FromBytesWithNulErrormaps cleanly to theEINVALerror 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,
GaryQuoted 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.
Alice Ryhl
reviewerRe: [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
Signed-off-by Alistair Francis <[email protected]>--- rust/kernel/error.rs | 7 +++++++ 1 file changed, 7 insertions(+)rust/kernel/error.rs +1
-0@@ -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
FromBytesWithNulErrormaps cleanly to theEINVALerror code?
FromBytesWithNulErrormeans "An error indicating that a nul byte was not in the expected position." [1]
Quoted 8 lines
To me that maps with
EINVAL1: 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
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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
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).
Signed-off-by Alistair Francis <[email protected]>
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.rsMAINTAINERS +0
-0
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@@ -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@@ -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@@ -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 (lengthas usize ) < header_size { +return Ok (length);// Truncated response is handled by callers + } +if response.code ==SPDM_ERROR { +if lengthas 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@@ -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@@ -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>
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).
Signed-off-by Alistair Francis <[email protected]>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.rsMAINTAINERS +0
-0@@ -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@@ -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@@ -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@@ -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 (lengthas usize ) < header_size { +return Ok (length);// Truncated response is handled by callers + } +if response.code ==SPDM_ERROR { +if lengthas 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@@ -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_ERROR0x7Frust/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@@ -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>
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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
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@@ -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
-0config PCI_TSM bool "PCI TSM: Device security protocol support" select PCI_IDEdrivers/pci/Makefile +2
-0@@ -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@@ -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
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
@@ -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
@@ -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
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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
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
@@ -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
@@ -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: &'staticmut SpdmState ) ->c_int { +pub unsafe extern "C" fn spdm_authenticate (state: &'staticmut 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
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 /// Cstruct 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) fnget_version(&mut self)-> Result<(), Error> { + let mut request =GetVersionReq::default();
+ request.version = self.version;
+
+ // SAFETY:requestis repr(C) and packed, so we can convert it to a slice
+ letrequest_buf= unsafe {
+ from_raw_parts_mut(
+ &mut request as *mut _ as *mut u8,
Quoted 8 lines
+ core::mem::size_of::<GetVersionReq>(), + )
+ };
+
+ let mutresponse_vec: KVec<u8> =KVec::with_capacity(SPDM_GET_VERSION_LEN, GFP_KERNEL)?; + // SAFETY:requestis 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
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_VERSIONSPDM command.Signed-off-by Alistair Francis <[email protected]>
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
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@@ -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: &'staticmut SpdmState ) ->c_int { +pub unsafe extern "C" fn spdm_authenticate (state: &'staticmut 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@@ -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 requestas *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 (rcas usize ) }; + +let response: &mut GetVersionRsp =Untrusted ::new_mut (&mut response_vec).validate_mut ()?; + +let mut foundver =false ; +for iin 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 (ias 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@@ -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_offsetin 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) + } +}
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_VERSIONSPDM command.Signed-off-by Alistair Francis <[email protected]>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
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_MAXdefinition 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@@ -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: &'staticmut SpdmState ) ->c_int { +pub unsafe extern "C" fn spdm_authenticate (state: &'staticmut 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@@ -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 requestas *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 (rcas usize ) }; + +let response: &mut GetVersionRsp =Untrusted ::new_mut (&mut response_vec).validate_mut ()?; + +let mut foundver =false ; +for iin 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 (ias 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
__le16until 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
__le16rather 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@@ -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_offsetin 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) + } +}
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
Re: [RFC v3 12/27] lib: rspdm: Support SPDM get_version
Quoted 5 lines · Miguel Ojeda
We have
size_of,align_ofand so on in the prelude, so you can use those directly:
Sorry, I cut the quotes too much -- this was supposed to reply to the total_expected_size part of the conversation.
Cheers,
Miguel
Re: [RFC v3 12/27] lib: rspdm: Support SPDM get_version
In reply to Alistair FrancisQuoted 5 lines
Quoted 5 lines
Quoted 5 lines
lib/rspdm/state.rs +2
-0+let addr = unaligned.wrapping_add (ias 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
__le16until 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
__le16rather 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@@ -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_offsetin 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) + } +}
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
@@ -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
@@ -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
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 theSpdmState.
+ pub(crate) fnget_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
+ letrequest_buf= unsafe {from_raw_parts_mut(&mut request as *mut _ as *mut u8, req_sz)};
+
+ let mutresponse_vec: KVec<u8> =KVec::with_capacity(rsp_sz, GFP_KERNEL)?;
+ // SAFETY:requestis 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 besmaller
+ // 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
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_CAPABILITIESSPDM command.Signed-off-by Alistair Francis <[email protected]>
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@@ -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@@ -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
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 requestas *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_szas 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 (rcas 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@@ -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) + } +}
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 toBIT()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
Re: [RFC v3 13/27] lib: rspdm: Support SPDM get_capabilities
In reply to Jonathan CameronQuoted 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_CAPABILITIESSPDM command.Signed-off-by Alistair Francis <[email protected]>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@@ -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@@ -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@@ -17,9 +17,12 @@ };
Quoted 9 lines
lib/rspdm/state.rs line 22
Quoted 5 lines
lib/rspdm/state.rs +3
-1use 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 requestas *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_szas 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 (rcas 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_unalignedstuff 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@@ -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) + } +}
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
@@ -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
@@ -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: &'staticmut SpdmState ) {} +pub unsafe extern "C" fn spdm_destroy (state: &'staticmut SpdmState ) { +if let Some (desc) = &mut state.desc { +unsafe { + bindings::kfree (*descas *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
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_ALGORITHMSSPDM command.Signed-off-by Alistair Francis <[email protected]>
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
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
...
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_szas u16 ; + request.param1 = reg_alg_entriesas 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 requestas *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_szas 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 (rcas 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#[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) + } +}
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
@@ -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
@@ -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
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_DIGESTSSPDM command.Signed-off-by Alistair Francis <[email protected]>
A few things inline.
Thanks,
J
Quoted 19 lines
lib/rspdm/state.rs +4
-0/// 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 requestas *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 (rcas 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
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) + } +}
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
@@ -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
@@ -125,6 +125,17 @@return e.to_errno ()as c_int ; } +let mut provisioned_slots = state.provisioned_slots; +while (provisioned_slotsas 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
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_CERTIFICATESPDM command.Signed-off-by Alistair Francis <[email protected]>
Minor things inline. The endian handling in general needs some care + possibly some tests.
Quoted 30 lines
lib/rspdm/state.rs +15
-0@@ -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 (rcas 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 requestas *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_lengthas usize )], +GFP_KERNEL , + )?; + +let mut offset:usize = response.portion_lengthas usize ; +let mut remainder_length = response.remainder_lengthas 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 requestas *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 + || offsetas u16 + response.portion_length + response.remainder_length + != total_cert_lenas u16 + { +pr_err! ("Malformed certificate response\n" ); +to_result (-(bindings::EPROTO as i32 ))?; + } + + certs_buf.extend_from_slice ( + &response_vec[8 ..(8 + response.portion_lengthas usize )], +GFP_KERNEL , + )?; + offset += response.portion_lengthas usize ; + remainder_length = response.remainder_lengthas 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[slotas usize ].extend_from_slice (&certs_buf,GFP_KERNEL )?; + +Ok (()) + } }lib/rspdm/validator.rs +20
-0@@ -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) + } +}
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_CERTIFICATESPDM command.Signed-off-by Alistair Francis <[email protected]>Minor things inline. The endian handling in general needs some care + possibly some tests.
Quoted 30 lines
lib/rspdm/state.rs +15
-0@@ -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 (rcas 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 i32As 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_MAXor 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 requestas *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_lengthas 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_lengthas usize ; +let mut remainder_length = response.remainder_lengthas usize ; + +while remainder_length >0 { + request.offset = offset.to_le ()as u16 ;Similar to other places, why not just make the type
__le16and 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 requestas *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 + || offsetas u16 + response.portion_length + response.remainder_length + != total_cert_lenas u16 + { +pr_err! ("Malformed certificate response\n" ); +to_result (-(bindings::EPROTO as i32 ))?; + } + + certs_buf.extend_from_slice ( + &response_vec[8 ..(8 + response.portion_lengthas usize )], +GFP_KERNEL , + )?; + offset += response.portion_lengthas usize ; + remainder_length = response.remainder_lengthas 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
__le16as 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[slotas usize ].extend_from_slice (&certs_buf,GFP_KERNEL )?; + +Ok (()) + } }lib/rspdm/validator.rs +26
-0@@ -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 understandfrom_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) + } +}
Gary Guo
reviewerRe: [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_CERTIFICATESPDM command.Signed-off-by Alistair Francis <[email protected]>Minor things inline. The endian handling in general needs some care + possibly some tests.
Quoted 30 lines
lib/rspdm/state.rs +15
-0@@ -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 (rcas 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 i32As 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_MAXor 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_ofgives 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__le16which 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 requestas *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_lengthas 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_lengthas usize ; +let mut remainder_length = response.remainder_lengthas usize ; + +while remainder_length >0 { + request.offset = offset.to_le ()as u16 ;Similar to other places, why not just make the type
__le16and 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 requestas *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 + || offsetas u16 + response.portion_length + response.remainder_length + != total_cert_lenas u16 + { +pr_err! ("Malformed certificate response\n" ); +to_result (-(bindings::EPROTO as i32 ))?; + } + + certs_buf.extend_from_slice ( + &response_vec[8 ..(8 + response.portion_lengthas usize )], +GFP_KERNEL , + )?; + offset += response.portion_lengthas usize ; + remainder_length = response.remainder_lengthas 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
__le16as 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[slotas usize ].extend_from_slice (&certs_buf,GFP_KERNEL )?; + +Ok (()) + } }lib/rspdm/validator.rs +26
-0@@ -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 understandfrom_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) + } +}
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
@@ -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
@@ -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
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
@@ -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
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
@@ -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
@@ -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
@@ -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
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
@@ -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
@@ -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
@@ -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
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.
Signed-off-by Alistair Francis <[email protected]>
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
@@ -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
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.
Signed-off-by Alistair Francis <[email protected]>
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@@ -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" );
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
@@ -136,6 +136,17 @@ provisioned_slots &= !(1 << slot); } +let mut provisioned_slots = state.provisioned_slots; +while (provisioned_slotsas 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: &'staticmut 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 (*descas *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
@@ -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
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.
Signed-off-by Alistair Francis <[email protected]>
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@@ -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[slotas 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_lenas i32 )?; + } + +let _is_leaf_cert =if offset + cert_lenas 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_lenas 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_lenas 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 (()) + } }
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.
Signed-off-by Alistair Francis <[email protected]>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@@ -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[slotas 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_lenas i32 )?; + } + +let _is_leaf_cert =if offset + cert_lenas 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_lenas 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_lenas 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 (()) + } }
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
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
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
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
@@ -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
Gary Guo
reviewerRe: [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.
Signed-off-by Alistair Francis <[email protected]>--- rust/kernel/str.rs | 5 +++++ 1 file changed, 5 insertions(+)rust/kernel/str.rs +2
-0@@ -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 {
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
@@ -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
@@ -136,17 +136,23 @@ provisioned_slots &= !(1 << slot); } +let mut verify =true ;let mut provisioned_slots = state.provisioned_slots;while (provisioned_slotsas 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
@@ -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
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.
Signed-off-by Alistair Francis <[email protected]>
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
...
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_lenas 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.nonceas *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 requestas *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 (rcas 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_lenas usize ); + +if rc < rsp_szas 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+ +#[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) + } +}
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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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: &'staticmut 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
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
@@ -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
@@ -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
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
[ 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
Lukas Wunner
maintainerRe: [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 10Just 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
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 responseqemu-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=reliableno_timer_checkrcupdate.rcu_expedited=1 swiotlb=0 '
Quoted 3 lines
spdm_utilsidentify &
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.
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 responseqemu-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=reliableno_timer_checkrcupdate.rcu_expedited=1 swiotlb=0 'Quoted 3 lines
spdm_utilsidentify &
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_cmaand/orauto_tsmthat arranges for authentication or link encryption to happen without asking. I take issue withauto_cmabeing the only hard coded option.
Ok, so we would need a way for users to select TSM or CMA and specify policy information?
Alistair
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 responseqemu-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=reliableno_timer_checkrcupdate.rcu_expedited=1 swiotlb=0 'Quoted 3 lines
spdm_utilsidentify &
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.
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_cmaand/orauto_tsmthat arranges for authentication or link encryption to happen without asking. I take issue withauto_cmabeing 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
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 responseqemu-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=reliableno_timer_checkrcupdate.rcu_expedited=1 swiotlb=0 'Quoted 3 lines
spdm_utilsidentify &
sleep 1
echo re > /sys/devices/pci0000:00/0000:00:03.0/authenticatedQuoted 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.
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_cmaand/orauto_tsmthat arranges for authentication or link encryption to happen without asking. I take issue withauto_cmabeing 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
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
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_cmaand/orauto_tsmthat arranges for authentication or link encryption to happen without asking. I take issue withauto_cmabeing 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.
Lukas Wunner
maintainerQuoted 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].
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
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
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
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
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_opsseem 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.
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].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 onhttps://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_opsseem 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
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
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
[ 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).
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
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".
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
Lukas Wunner
maintainerQuoted 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
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
Lukas Wunner
maintainerQuoted 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
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
Lukas Wunner
maintainerQuoted 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/slot0That'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
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/slot0That'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
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
---
Lukas Wunner
maintainerQuoted 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/slot0This 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
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
Lukas Wunner
maintainerQuoted 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
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.
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.
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.
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
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.
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/tsmto 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/connectOnce 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/lockSimilar 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.cto 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 'structpci_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 thoserevalidate()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 toCONFIG_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
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/tsmto 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/connectOnce 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/lockSimilar 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.cto 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 'structpci_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 thoserevalidate()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.
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->pubkeythat is installed / cached "after the fact" for platform TSMs. For the native SPDM driver,pdev->tsm->pubkeycan be installed at 'structpci_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/tsmto 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/connectOnce 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/lockSimilar 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.cto 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 'structpci_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 thoserevalidate()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
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
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
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 sessionWe 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
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 sessionWe 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
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
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.
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
Lukas Wunner
maintainerQuoted 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
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
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
Lukas Wunner
maintainerQuoted 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/slot0As 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
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/slot0As 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
Lukas Wunner
maintainerQuoted 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/slot0So 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
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/slot0So 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
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_CERTIFICATESPDM 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
Lukas Wunner
maintainerQuoted 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.
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
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
There's a newer version:
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:
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.
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