lib: Rust implementation of SPDM
Subsystems (9)
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 based on Lukas' C implementation [2], but has been refacted during the first few RFCs. I have included some of the relevent patchesfrom Lukas' C SPDM implementation in this series where they are required.
This is a standalone series and doesn't depend on Lukas' implementation.
The goal of this series is to get the smallest possible SPDM implementation upstream. That will provide building blocks for us to continue working on.
As such we don't yet provide evidence or certificates to userspace, allow userspace to provide a nonce, support PQC or more advanced SPDM features. This is enough to communicate with a device and return "authenticated" to userspace.
Note that RFC v3 did provide evidence and certificates to userspace and allowed a custom nonce. Showing that it's possible. I also have patches that build apon [4] to do this via a TSM driver, again showing it's possible with the current approach.
We just don't support it yet and for TSM I need [4] upstream first.
This series is different to Lukas' original approach and the approach taken in the previous RFCs and instead adds the PCI-CMA support as a TSM driver. This was described by Dan in [3] and [5]. The advantage here is that for PCIe we can leverage the TSM work for a lot of the features and provide userspace a consistient interface between PCI TSM and CMA.
This series also doesn't check the certificate chain against the kernel keyring and will instead leave that to userspace once [4] is merged.
Other transport mode (such as ATA, SCSI, NVMe and MCTP) will therefore need slightly different approaches, as TSM doesn't apply. The library can support this though, it will just need some netlink and sysfs wrappers added as applicable. This way each transport can support SPDM in the way it sees fit.
The entire tree can be seen here:
https://github.com/alistair23/linux/tree/alistair/spdm-rust-tsm
I'm testing this 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 ' ls /sys/devices/pci0000:00/0000:00:03.0/ ls /sys/devices/pci0000:00/0000:00:03.0/tsm/ cat /sys/devices/pci0000:00/0000:00:03.0/authenticated echo tsm0 > /sys/devices/pci0000:00/0000:00:03.0/tsm/connect cat /sys/devices/pci0000:00/0000:00:03.0/authenticated ```
1: https://www.dmtf.org/standards/spdm
2: https://lore.kernel.org/all/[email protected]/
3: http://lore.kernel.org/[email protected]
4: https://lore.kernel.org/all/[email protected]/
5: https://lore.kernel.org/lkml/[email protected]/
v3:
- Rebase on 7.3-rc1
v2:
- Tidy up the PCI/TSM function naming and is_pci_tsm_host()
- A large number of changes (mostly to the Rust code) based on Sashiko reviews
- This includes a few local runs of Sashiko
- This has fixed a few undefined behviour bugs in the Rust code
- Rebase on v4 of Benno's validate patches (patch 1, 2, 3)
v1:
- Add CMA as a TSM driver
- Initial support for SPDM 1.4
- Cleanup a range of comments and concerns from RFC
- Remove kernel keyring checks
RFC v3:
- Use netlink to send information to userspace
- Don't autogenerate Rust helpers
RFC v2:
- Drop support for Rust and C implementations
- Include patches from Lukas to reduce series deps
- Large code cleanups based on more testing
- Support for authentication
Alistair Francis (14):
rust: add bindings for hash.h
rust: error: impl From<FromBytesWithNulError> for Kernel Error
lib: rspdm: Initial commit of Rust SPDM
PCI/TSM: Rename pf0 to host
PCI/TSM: Support connecting to PCIe CMA devices PCI/CMA: Add a PCI TSM CMA driver using 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
lib: rspdm: Support SPDM certificate validation
rust: allow extracting the buffer from a CString
lib: rspdm: Support SPDM challenge
Benno Lossin (3):
rust: transmute: add cast_slice[_mut] functions
rust: create basic untrusted data API
rust: validate: add Validate trait
Lukas Wunner (4):
X.509: Make certificate parser public
X.509: Parse Subject Alternative Name in certificates X.509: Move certificate length retrieval into new helper PCI/CMA: Validate Subject Alternative Name in certificates
MAINTAINERS | 13 + crypto/asymmetric_keys/x509_cert_parser.c | 9 + crypto/asymmetric_keys/x509_loader.c | 38 +- crypto/asymmetric_keys/x509_parser.h | 42 +- drivers/crypto/ccp/sev-dev-tio.h | 4 +- drivers/crypto/ccp/sev-dev-tsm.c | 12 +- drivers/pci/Kconfig | 14 + drivers/pci/Makefile | 5 + drivers/pci/cma.asn1 | 41 + drivers/pci/cma.c | 275 +++++ drivers/pci/doe.c | 6 +- drivers/pci/tsm.c | 115 +- include/keys/asymmetric-type.h | 2 + include/keys/x509-parser.h | 57 + include/linux/oid_registry.h | 3 + include/linux/pci-doe.h | 4 + include/linux/pci-tsm.h | 30 +- include/linux/pci.h | 1 + include/linux/spdm.h | 37 + lib/Kconfig | 15 + lib/Makefile | 2 + lib/rspdm/Makefile | 10 + lib/rspdm/consts.rs | 184 +++ lib/rspdm/lib.rs | 167 +++ lib/rspdm/state.rs | 1230 +++++++++++++++++++++ lib/rspdm/validator.rs | 554 ++++++++++ rust/bindings/bindings_helper.h | 5 + rust/helpers/hash.c | 18 + rust/helpers/helpers.c | 1 + rust/kernel/error.rs | 18 +- rust/kernel/lib.rs | 1 + rust/kernel/str.rs | 13 +- rust/kernel/transmute.rs | 59 + rust/kernel/validate.rs | 216 ++++ 34 files changed, 3071 insertions(+), 130 deletions(-) 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 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 create mode 100644 rust/helpers/hash.c create mode 100644 rust/kernel/validate.rs
Signature · 1 line
2.55.0
PATCH v3 1/21 rust: transmute: add `cast_slice[_mut]` functions
From: Benno Lossin <[email protected]>
Add functions to make casting slices only one unsafe block.
Message-ID: <[email protected]>
--- rust/kernel/transmute.rs | 59 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+)
rust/kernel/transmute.rs +59 -0
@@ -3,6 +3,7 @@//! Traits for transmuting types. use core::mem::size_of; +use core::slice;/// Types for which any bit pattern is valid. ///
@@ -227,3 +228,61 @@ macro_rules! impl_asbytes { {<T: AsBytes>} [T], {<T: AsBytes, const N: usize>} [T; N], } + +/// Casts the type of a slice to another. +/// +/// Also see [`cast_slice_mut`]. +/// +/// # Examples +/// +/// ```rust +/// # use kernel::transmute::cast_slice; +/// #[repr(transparent)] +/// #[derive(Debug)] +/// struct Container<T>(T); +/// +/// let array = [0u32; 42]; +/// let slice = &array; +/// // SAFETY: `Container<u32>` transparently wraps a `u32`. +/// let container_slice = unsafe { cast_slice::<u32, Container<u32>>(slice) }; +/// pr_info!("{container_slice:?}"); +/// ``` +/// +/// # Safety +/// +/// - `T` and `U` must have the same layout. +pub unsafe fn cast_slice <T ,U >(slice: &[T ]) -> &[U ] { +// CAST: by the safety requirements, `T` and `U` have the same layout. +let ptr = slice.as_ptr ().cast ::<U >(); +// SAFETY: `ptr` and `len` come from the same slice reference. +unsafe { slice::from_raw_parts (ptr, slice.len ()) } +} + +/// Casts the type of a slice to another. +/// +/// Also see [`cast_slice`]. +/// +/// # Examples +/// +/// ```rust +/// # use kernel::transmute::cast_slice_mut; +/// #[repr(transparent)] +/// #[derive(Debug)] +/// struct Container<T>(T); +/// +/// let mut array = [0u32; 42]; +/// let slice = &mut array; +/// // SAFETY: `Container<u32>` transparently wraps a `u32`. +/// let container_slice = unsafe { cast_slice_mut::<u32, Container<u32>>(slice) }; +/// pr_info!("{container_slice:?}"); +/// ``` +/// +/// # Safety +/// +/// - `T` and `U` must have the same layout. +pub unsafe fn cast_slice_mut <T ,U >(slice: &mut [T ]) -> &mut [U ] { +// CAST: by the safety requirements, `T` and `U` have the same layout. +let ptr = slice.as_mut_ptr ().cast ::<U >(); +// SAFETY: `ptr` and `len` come from the same slice reference. +unsafe { slice::from_raw_parts_mut (ptr, slice.len ()) } +}
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v3 01/21] rust: transmute: add `cast_slice[_mut]` functions
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: - [High] The `# Safety` contract for `cast_slice` and `cast_slice_mut` is incomplete, omitting critical…
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] The # Safety contract for cast_slice and cast_slice_mut is incomplete, omitting critical requirements regarding bit-pattern validity and interior mutability.
- [Low] The small abstraction functions cast_slice and cast_slice_mut are missing #[inline] attributes.
- [Low] Import formatting does not follow the kernel vertical import style.
--
commit d8c311b266bb9a64cb5ebd99f708e41b6412ae21
Author: Benno Lossin <[email protected]>
rust: transmute: add cast_slice[_mut] functions
This commit introduces functions to enable casting slices with only a single unsafe block.
Message-ID: <[email protected]>
rust/kernel/transmute.rs line 6
Quoted 9 lines
rust/kernel/transmute.rs +1
-0@@ -3,6 +3,7 @@//! Traits for transmuting types. use core::mem::size_of; +use core::slice;
[Severity: Low]
Is this import formatting intentional? The subsystem coding guidelines specify following the kernel vertical import style, which would group this with the existing core import as use core::{mem::size_of, slice};.
[ ... ]
rust/kernel/transmute.rs macro_rules! impl_asbytes {
Quoted 3 lines
rust/kernel/transmute.rs +3
-0+/// # Safety +/// +/// - T and U must have the same layout.
[Severity: High]
Is this safety contract complete? It appears to omit requirements regarding bit-pattern validity and interior mutability. For example, if a caller uses types where values of T are not valid bit-patterns for U, could this lead to undefined behavior despite adhering to the stated layout constraint?
Quoted 1 line
+pub unsafe fn cast_slice<T, U>(slice: &[T]) -> &[U] {
[Severity: Low]
Does this function need an inline attribute? As a small abstraction wrapper forwarding to standard library slice functions within the kernel crate, the guidelines indicate it should be annotated with #[inline] to ensure optimal code generation across crate boundaries.
[ ... ]
rust/kernel/transmute.rs macro_rules! impl_asbytes {
Quoted 3 lines
rust/kernel/transmute.rs +3
-0+/// # Safety +/// +/// - T and U must have the same layout.
[Severity: High]
Does the mutable variant's safety contract also need to specify bit-pattern validity in both directions? If a caller writes a valid U that is an invalid bit-pattern for T, it seems this could cause undefined behavior when the slice reverts to a mutable slice of T. Should it also explicitly forbid introducing interior mutability?
Quoted 1 line
+pub unsafe fn cast_slice_mut<T, U>(slice: &mut [T]) -> &mut [U] {
[Severity: Low]
As with cast_slice, should this thin abstraction wrapper also include the #[inline] attribute?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
PATCH v3 2/21 rust: create basic untrusted data API
In reply to [email protected]From: Benno Lossin <[email protected]>
When the kernel receives external data (e.g. from userspace), it usually is a very bad idea to directly use the data for logic decision in the kernel. For this reason, such data should be explicitly marked and validated before making decision based on its value.
The Untrusted<T> wrapper type marks a value of type T as untrusted. The particular meaning of "untrusted" highly depends on the type T. For example T = u8 ensures that the value of the byte cannot be retrieved. However, T = [u8] still allows to access the length of the slice. Similarly, T = KVec<U> allows modifications.
Message-ID: <[email protected]>
--- rust/kernel/lib.rs | 1 + rust/kernel/validate.rs | 148 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 149 insertions(+) create mode 100644 rust/kernel/validate.rs
rust/kernel/lib.rs +1 -0
@@ -143,6 +143,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 +148 -0
148 lines not on this page · raw message
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v3 02/21] rust: create basic untrusted data API
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [High] Undefined Behavior due to missing layout guarantees when transmuting `Vec<T, A>` to…
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Undefined Behavior due to missing layout guarantees when transmuting Vec<T, A> to Vec<Untrusted<T>, A>.
- [Low] Small abstraction functions in the kernel crate are missing #[inline] annotations.
--
commit a2ef24201e92da02db41c39bbc750b20f5e2ce5b
Author: Benno Lossin <[email protected]>
rust: create basic untrusted data API
This commit introduces an Untrusted<T> wrapper type to explicitly mark and validate external data before use. The API provides Deref and DerefMut implementations to allow working with untrusted slices and vectors.
Quoted 5 lines
rust/kernel/validate.rs new file +0
-0
[ ... ]
Quoted 1 line
+impl<T: ?Sized> Untrusted<T> {
[ ... ]
rust/kernel/validate.rs line 109
Quoted 6 lines
rust/kernel/validate.rs new file +6
-0+pub fn new (value:T ) ->Self +where +T :Sized , + { +Self (value) + }
[Severity: Low]
Could this small abstraction function be annotated with #[inline]?
The Rust Subsystem coding guidelines suggest that small functions in abstractions like the kernel crate should be marked as inline to ensure optimal code generation.
rust/kernel/validate.rs line 119
Quoted 10 lines
rust/kernel/validate.rs new file +10
-0+} + +impl <T >Deref for Untrusted <[T ]> { +type Target = [Untrusted <T >]; + +fn deref (&self) -> &Self ::Target { +// SAFETY: `Untrusted<T>` transparently wraps `T`. +unsafe {cast_slice (&self.0 ) } + } +}
[Severity: Low]
Should this small trait method also have an #[inline] annotation?
rust/kernel/validate.rs line 126
Quoted 7 lines
rust/kernel/validate.rs new file +7
-0+ +impl <T >DerefMut for Untrusted <[T ]> { +fn deref_mut (&mut self) -> &mut Self ::Target { +// SAFETY: `Untrusted<T>` transparently wraps `T`. +unsafe {cast_slice_mut (&mut self.0 ) } + } +}
[Severity: Low]
Would an #[inline] annotation be appropriate for this deref_mut implementation as well?
rust/kernel/validate.rs line 138
Quoted 12 lines
rust/kernel/validate.rs new file +12
-0+ +impl <T ,A :Allocator >Deref for Untrusted <Vec <T ,A >> { +type Target =Vec <Untrusted <T >,A >; + +fn deref (&self) -> &Self ::Target { +let ptr: *const Untrusted <Vec <T ,A >> = self; +// CAST: `Untrusted<T>` transparently wraps `T`. +let ptr: *const Vec <Untrusted <T >,A > = ptr.cast (); +// SAFETY: `ptr` is derived from the reference `self`. +unsafe { &*ptr } + } +}
[Severity: High]
Is it possible to add an #[inline] annotation here?
Additionally, does this pointer cast rely on layout guarantees that the compiler does not provide?
Vec is a #[repr(Rust)] type, which does not guarantee stable or identical layouts across different generic monomorphizations. Even though Untrusted<T> is #[repr(transparent)] over T, Vec<T, A> and Vec<Untrusted<T>, A> are distinct monomorphizations.
If the compiler orders the internal fields differently, transmuting between these types could lead to memory corruption. The standard way to soundly convert a vector's element type is by deconstructing it via into_raw_parts and reconstructing it with from_raw_parts, but this cannot be done for references.
Are there alternative ways to expose this untrusted vector data safely?
rust/kernel/validate.rs line 148
Quoted 10 lines
rust/kernel/validate.rs new file +10
-0+ +impl <T ,A :Allocator >DerefMut for Untrusted <Vec <T ,A >> { +fn deref_mut (&mut self) -> &mut Self ::Target { +let ptr: *mut Untrusted <Vec <T ,A >> = self; +// CAST: `Untrusted<T>` transparently wraps `T`. +let ptr: *mut Vec <Untrusted <T >,A > = ptr.cast (); +// SAFETY: `ptr` is derived from the reference `self`. +unsafe { &mut *ptr } + } +}
[Severity: High]
Could this method be annotated with #[inline] as well?
Similar to the deref implementation above, can this pointer cast invoke undefined behavior by transmuting between two distinct #[repr(Rust)] types?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
PATCH v3 3/21 rust: validate: add `Validate` trait
In reply to [email protected]From: Benno Lossin <[email protected]>
Introduce the Validate<Input> trait and functions to validate Untrusted<T> using said trait. This allows one to access the inner value of Untrusted<T> via validate{,_ref,_mut} functions which subsequently delegate the validation to user-implemented Validate trait.
The Validate trait is the only entry point for validation code, making it easy to spot where data is being validated.
The reason for restricting the types that can be inputs to Validate::validate is to be able to have the validate... functions on Untrusted. This is also the reason for the suggestions in the Usage in API Design section in the commit that introduced Untrusted<T>.
Message-ID: <[email protected]>
--- rust/kernel/validate.rs | 70 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-)
rust/kernel/validate.rs +69 -1
@@ -11,6 +11,9 @@//! APIs that write back into userspace usually allow writing untrusted bytes directly, allowing //! direct copying of untrusted user data back into userspace without validation. //! +//! The only way to access untrusted data is to [`Validate::validate`] it. This is facilitated by +//! the [`Validate`] trait. +//! //! # Rationale //! //! When reading data from an untrusted source, it must be validated before it can be used for
@@ -52,7 +55,7 @@/// untrusted, as it would otherwise violate normal Rust rules. For this reason, one can easily /// convert that reference to `&[Untrusted<u8>]`. Another such example is `Untrusted<KVec<T>>`, it /// derefs to `KVec<Untrusted<T>>`. Raw bytes however do not behave in this way, `Untrusted<u8>` is -/// totally opaque. +/// totally opaque and one can only access its value by calling [`Untrusted::validate()`]. /// /// # Usage in API Design ///
@@ -107,6 +110,30 @@ pub fn new(value: T) -> Self {Self (value) } + +/// Validate the underlying untrusted data. +/// +/// See the [`Validate`] trait for more information. +pub fn validate <V :Validate <Self >>(self) ->Result <V ,V ::Err > +where +T :Sized , + { +V ::validate (self.0 ) + } + +/// Validate the underlying untrusted data. +/// +/// See the [`Validate`] trait for more information. +pub fn validate_ref <'a,V :Validate <&'aSelf >>(&'a self) ->Result <V ,V ::Err > { +V ::validate (&self.0 ) + } + +/// Validate the underlying untrusted data. +/// +/// See the [`Validate`] trait for more information. +pub fn validate_mut <'a,V :Validate <&'amut Self >>(&'amut self) ->Result <V ,V ::Err > { +V ::validate (&mut self.0 ) + } }impl <T >Deref for Untrusted <[T ]> {
@@ -146,3 +173,44 @@ fn deref_mut(&mut self) -> &mut Self::Target {unsafe { &mut *ptr } } } + +/// Marks valid input for the [`Validate`] trait. +pub trait ValidateInput : private::Sealed { +/// Type of the inner data. +type Inner : ?Sized ; +} + +impl <T : ?Sized >ValidateInput for Untrusted <T > { +type Inner =T ; +} + +impl <'a,T : ?Sized >ValidateInput for &'aUntrusted <T > { +type Inner = &'aT ; +} + +impl <'a,T : ?Sized >ValidateInput for &'amut Untrusted <T > { +type Inner = &'amut T ; +} + +mod private { +use super ::Untrusted ; + +pub trait Sealed {} + +impl <T : ?Sized >Sealed for Untrusted <T > {} +impl <'a,T : ?Sized >Sealed for &'aUntrusted <T > {} +impl <'a,T : ?Sized >Sealed for &'amut Untrusted <T > {} +} + +/// Validate [`Untrusted`] data. +/// +/// Care must be taken when implementing this trait, as unprotected access to unvalidated data is +/// given to the [`Validate::validate`] function. The implementer must ensure that the data is only +/// used for logic after successful validation. +pub trait Validate <Input :ValidateInput >:Sized { +/// Validation error. +type Err ; + +/// Validate the raw input. +fn validate (raw:Input ::Inner ) ->Result <Self ,Self ::Err >; +}
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v3 03/21] rust: validate: add `Validate` trait
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [Low] The small wrapper functions validate, validate_ref, and validate_mut in the Untrusted type are missing the #[inline] attribute, violating subsystem guidelines.
Pre-existing issues:
- [High] This problem wasn't introduced by this patch, but transmuting references between Vec<T, A> and Vec<Untrusted<T>, A> via pointer cast is Undefined Behavior because Vec lacks a guaranteed memory layout. --
commit 6a4bc1cc90cb4c5dd96a67a8f893828d2d0c3473
Author: Benno Lossin <[email protected]>
rust: validate: add Validate trait
Introduce the Validate<Input> trait and functions to validate Untrusted<T> using said trait.
Quoted 22 lines
rust/kernel/validate.rs +14
-0@@ -107,6 +110,30 @@ pub fn new(value: T) -> Self {Self (value) } + +/// Validate the underlying untrusted data. +/// +/// See the [`Validate`] trait for more information. +pub fn validate <V :Validate <Self >>(self) ->Result <V ,V ::Err > +where +T :Sized , + { +V ::validate (self.0 ) + } + +/// Validate the underlying untrusted data. +/// +/// See the [`Validate`] trait for more information.
rust/kernel/validate.rs pub fn new(value: T) -> Self · line 136
Quoted 10 lines
rust/kernel/validate.rs +10
-0+pub fn validate_ref <'a,V :Validate <&'aSelf >>(&'a self) ->Result <V ,V ::Err > { +V ::validate (&self.0 ) + } + +/// Validate the underlying untrusted data. +/// +/// See the [`Validate`] trait for more information. +pub fn validate_mut <'a,V :Validate <&'amut Self >>(&'amut self) ->Result <V ,V ::Err > { +V ::validate (&mut self.0 ) + }
[Severity: Low]
Should these small wrapper functions be annotated with #[inline]?
[ ... ]
Quoted 20 lines
rust/kernel/validate.rs +0
-0impl <T ,A :Allocator >Deref for Untrusted <Vec <T ,A >> {type Target =Vec <Untrusted <T >,A >;fn deref(&self) -> &Self::Target { let ptr: *const Untrusted<Vec<T, A>> = self; // CAST: `Untrusted<T>` transparently wraps `T`. let ptr: *const Vec<Untrusted<T>, A> = ptr.cast(); // SAFETY: `ptr` is derived from the reference `self`. unsafe { &*ptr } }}
impl<T, A: Allocator> DerefMut for Untrusted<Vec<T, A>> { fn deref_mut(&mut self) -> &mut Self::Target { let ptr: *mut Untrusted<Vec<T, A>> = self; // CAST: `Untrusted<T>` transparently wraps `T`. let ptr: *mut Vec<Untrusted<T>, A> = ptr.cast(); // SAFETY: `ptr` is derived from the reference `self`. unsafe { &mut *ptr } }
Quoted 1 line
}
[Severity: High]
This isn't a bug introduced by this patch, but does casting pointers between Vec<T, A> and Vec<Untrusted<T>, A> cause Undefined Behavior?
Since Vec<T, A> uses the default #[repr(rust)] representation, it does not guarantee stable field ordering. The Rust Reference explicitly states that pointer-casting between two generic types with the default representation is Undefined Behavior, even if their type arguments share identical memory layouts.
If the compiler orders fields differently between Vec<T, A> and Vec<Untrusted<T>, A>, could dereferencing the casted pointer or swapping values corrupt memory?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=3
PATCH v3 4/21 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.
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.55.0
[email protected]
AI reviewRe: [PATCH v3 04/21] X.509: Make certificate parser public
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 12 lines
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 structx509_certificateafter extracting the structpublic_keyandpublic_key_signaturefrom it. The Subject Alternative Name is thus inaccessible when using those functions.
Quoted 7 lines
Afford CMA-SPDM access to the Subject Alternative Name by making struct
x509_certificatepublic, together with the functions for parsing an X.509 certificate into such a struct and freeing such a struct.No functional change intended.
Signed-off-by Lukas Wunner <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=4
PATCH v3 5/21 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.55.0
[email protected]
AI reviewRe: [PATCH v3 05/21] X.509: Parse Subject Alternative Name in certificates
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 10 lines
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.
Signed-off-by Lukas Wunner <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=5
PATCH v3 6/21 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.55.0
[email protected]
AI reviewRe: [PATCH v3 06/21] X.509: Move certificate length retrieval into new helper
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 9 lines
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.
Quoted 6 lines
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.
Signed-off-by Lukas Wunner <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=6
PATCH v3 7/21 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 | 1 + rust/helpers/hash.c | 18 ++++++++++++++++++ rust/helpers/helpers.c | 1 + 3 files changed, 20 insertions(+) create mode 100644 rust/helpers/hash.c
rust/bindings/bindings_helper.h +1 -0
@@ -30,6 +30,7 @@#include <linux/acpi.h> #include <linux/gpu_buddy.h> +#include <crypto/hash.h> #include <drm/drm_device.h> #include <drm/drm_drv.h> #include <drm/drm_file.h>
rust/helpers/hash.c new file +18 -0
@@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <crypto/hash.h> + +__rust_helper unsigned int rust_helper_crypto_shash_descsize(struct crypto_shash *tfm) +{ +return crypto_shash_descsize (tfm); +} + +__rust_helper unsigned int rust_helper_crypto_shash_digestsize(struct crypto_shash *tfm) +{ +return crypto_shash_digestsize (tfm); +} + +__rust_helper void rust_helper_crypto_free_shash(struct crypto_shash *tfm) +{ +crypto_free_shash (tfm); +}
rust/helpers/helpers.c +1 -0
@@ -66,6 +66,7 @@#include "fs.c" #include "fwctl.c" #include "gpu.c" +#include "hash.c" #include "interrupt.c" #include "io.c" #include "irq.c"
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v3 07/21] rust: add bindings for hash.h
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 6 lines
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]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=7
PATCH v3 8/21 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 as we will use it in a future patch.
As this is now generally available we can remove it from the test case as well.
--- rust/kernel/error.rs | 18 +++++++++++++++--- rust/kernel/str.rs | 7 ------- 2 files changed, 15 insertions(+), 10 deletions(-)
rust/kernel/error.rs +15 -3
@@ -12,9 +12,14 @@ str::CStr, }; -use core::num::NonZeroI32 ; -use core::num::TryFromIntError ; -use core::str::Utf8Error ; +use core::{ + ffi::FromBytesWithNulError , + num::{ +NonZeroI32 , +TryFromIntError ,// + }, + str::Utf8Error ,// +};/// Contains the C-compatible error codes. #[rustfmt::skip]
@@ -358,6 +363,13 @@ fn from(e: core::convert::Infallible) -> Error { } } +impl From <FromBytesWithNulError >for Error { +#[inline] +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.
rust/kernel/str.rs +0 -7
@@ -433,13 +433,6 @@ macro_rules! c_str {mod tests {use super ::*; -impl From <core::ffi::FromBytesWithNulError >for Error { -#[inline] -fn from (_: core::ffi::FromBytesWithNulError ) ->Error { -EINVAL - } - } -macro_rules! format { ($($f:tt)*) => ({CString ::try_from_fmt (fmt! ($($f)*))?.to_str ()?
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v3 08/21] rust: error: impl From<FromBytesWithNulError> for Kernel Error
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 9 lines
From: Alistair Francis <[email protected]>
Implement From<FromBytesWithNulError> for the Kernel Error type as we will use it in a future patch.
As this is now generally available we can remove it from the test case as well.
Signed-off-by Alistair Francis <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=8
PATCH v3 9/21 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.
--- MAINTAINERS | 12 ++ include/linux/spdm.h | 37 +++++ lib/Kconfig | 15 ++ lib/Makefile | 2 + lib/rspdm/Makefile | 10 ++ lib/rspdm/consts.rs | 92 +++++++++++++ lib/rspdm/lib.rs | 89 ++++++++++++ lib/rspdm/state.rs | 237 ++++++++++++++++++++++++++++++++ lib/rspdm/validator.rs | 92 +++++++++++++ rust/bindings/bindings_helper.h | 1 + 10 files changed, 587 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
@@ -24753,6 +24753,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 +37 -0
@@ -0,0 +1,37 @@ +/* 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 int (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, +spdm_validate *validate); + +int spdm_authenticate (struct spdm_state *spdm_state); + +void spdm_destroy (struct spdm_state *spdm_state); + +#endif
lib/Kconfig +15 -0
@@ -588,6 +588,21 @@ config LWQ_TEST help Run boot-time test of light-weight queuing. +config RSPDM + bool "Rust SPDM" + depends on RUST + select ASYMMETRIC_KEY_TYPE + select CRYPTO + 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
@@ -297,6 +297,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 +92 -0
92 lines not on this page · raw message
lib/rspdm/lib.rs new file +89 -0
89 lines not on this page · raw message
lib/rspdm/state.rs new file +237 -0
237 lines not on this page · raw message
lib/rspdm/validator.rs new file +92 -0
92 lines not on this page · raw message
rust/bindings/bindings_helper.h +1 -0
@@ -89,6 +89,7 @@#include <linux/security.h> #include <linux/serdev.h> #include <linux/slab.h> +#include <linux/spdm.h> #include <linux/sys_soc.h> #include <linux/task_work.h> #include <linux/tracepoint.h>
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v3 09/21] lib: rspdm: Initial commit of Rust SPDM
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Low] RSPDM uses `select` for config options without inheriting their dependencies, leading to unmet…
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] RSPDM uses select for config options without inheriting their dependencies, leading to unmet dependency warnings. --
commit 752bdb39dced6672e12844f92653f4013c5aea48
Author: Alistair Francis <[email protected]>
lib: rspdm: Initial commit of Rust SPDM
This is the initial commit of the Rust SPDM library.
Quoted 4 lines
lib/Kconfig +0
-0
[ ... ]
lib/Kconfig config LWQ_TEST · line 596
Quoted 10 lines
lib/Kconfig +6
-0@@ -588,6 +588,21 @@ config LWQ_TEST help Run boot-time test of light-weight queuing. +config RSPDM + bool "Rust SPDM" + depends on RUST + select ASYMMETRIC_KEY_TYPE + select CRYPTO + select X509_CERTIFICATE_PARSER
[Severity: Low]
Will selecting these config options without inheriting their dependencies cause Kconfig unmet dependency warnings?
ASYMMETRIC_KEY_TYPE depends on KEYS, and X509_CERTIFICATE_PARSER depends on ASYMMETRIC_PUBLIC_KEY_SUBTYPE. If a user enables RSPDM without having KEYS enabled, Kconfig will issue an unmet direct dependency warning and potentially cause silent build failures.
Should this config add "depends on KEYS" and either depend on or select ASYMMETRIC_PUBLIC_KEY_SUBTYPE?
Quoted 4 lines
lib/Kconfig +4
-0+ help + The Rust implementation of the Security Protocol and Data Model (SPDM) + allows for device authentication, measurement, key exchange and + encrypted sessions.
[ ... ]
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=9
Re: [PATCH v3 09/21] lib: rspdm: Initial commit of Rust SPDM
In reply to [email protected]Quoted 5 lines · On Tue, 1 Sep 2026 11:03:35 +1000 [email protected] wrote:
From: Alistair Francis <[email protected]>
This is the initial commit of the Rust SPDM library.
Signed-off-by Alistair Francis <[email protected]>
Maybe want to consider updating the copyright given you changed some stuff since 2024 :)
Subject to tags from rust folk, this looks fine to me. Note I still haven't found the time to actually learn rust properly so this is me as a c coder reading and mostly figuring out what is going on.
PATCH v3 10/21 PCI/TSM: Rename pf0 to host
In reply to [email protected]From: Alistair Francis <[email protected]>
Rename pci_tsm_pf0 to pci_tsm_host (and rename variables and function names from pf0 to host) as part of converting pci_tsm_host to be any device that knows how to speak any of CMA, IDE, or TDISP.
This commit just renames the functions and provides no functional change. That will happen in the next commit.
--- drivers/crypto/ccp/sev-dev-tio.h | 4 +- drivers/crypto/ccp/sev-dev-tsm.c | 12 ++--- drivers/pci/tsm.c | 84 ++++++++++++++++---------------- include/linux/pci-tsm.h | 18 ++++--- 4 files changed, 61 insertions(+), 57 deletions(-)
drivers/crypto/ccp/sev-dev-tio.h +2 -2
@@ -53,9 +53,9 @@ struct tsm_dsm_tio {struct pci_ide *ide[TIO_IDE_MAX_TC ]; }; -/* Describes TSM structure for PF0 pointed by pci_dev->tsm */ +/* Describes TSM structure for the link host pointed by pci_dev->tsm */ struct tio_dsm { -struct pci_tsm_pf0 tsm; +struct pci_tsm_host tsm;struct tsm_dsm_tio data;struct sev_device *sev; };
drivers/crypto/ccp/sev-dev-tsm.c +6 -6
@@ -205,7 +205,7 @@ static int stream_alloc(struct pci_dev *pdev, struct pci_ide **ide,return 0 ; } -static struct pci_tsm *tio_pf0_probe (struct pci_dev *pdev,struct sev_device *sev) +static struct pci_tsm *tio_host_probe (struct pci_dev *pdev,struct sev_device *sev) { struct tio_dsm *dsm __free(kfree) = kzalloc_obj(*dsm);int rc;
@@ -213,7 +213,7 @@ static struct pci_tsm *tio_pf0_probe(struct pci_dev *pdev, struct sev_device *seif (!dsm)return NULL ; - rc =pci_tsm_pf0_constructor (pdev, &dsm->tsm, sev->tsmdev); + rc =pci_tsm_host_constructor (pdev, &dsm->tsm, sev->tsmdev);if (rc)return NULL ;
@@ -226,8 +226,8 @@ static struct pci_tsm *dsm_probe(struct tsm_dev *tsmdev, struct pci_dev *pdev) {struct sev_device *sev =tsm_dev_to_sev (tsmdev); -if (is_pci_tsm_pf0 (pdev)) -return tio_pf0_probe (pdev, sev); +if (is_pci_tsm_host (pdev)) +return tio_host_probe (pdev, sev);return NULL ; }
@@ -237,10 +237,10 @@ static void dsm_remove(struct pci_tsm *tsm)pci_dbg (pdev,"TSM disabled\n" ); -if (is_pci_tsm_pf0 (pdev)) { +if (is_pci_tsm_host (pdev)) { struct tio_dsm *dsm = container_of(tsm, struct tio_dsm, tsm.base_tsm); -pci_tsm_pf0_destructor (&dsm->tsm); +pci_tsm_host_destructor (&dsm->tsm);kfree (dsm); } }
drivers/pci/tsm.c +42 -42
250 lines not on this page · raw message
include/linux/pci-tsm.h +11 -7
@@ -40,7 +40,7 @@ struct pci_tsm_ops {* pci_tsm_rwsem held for write to sync with TSM unregistration and * mutual exclusion of @connect and @disconnect. @connect and * @disconnect additionally run under the DSM lock (struct -* pci_tsm_pf0::lock) as well as @probe and @remove of the subfunctions. +* pci_tsm_host::lock) as well as @probe and @remove of the subfunctions. * @bind, @unbind, and @guest_req run under pci_tsm_rwsem held for read * and the DSM lock. */
@@ -115,19 +115,23 @@ struct pci_tsm { };/** -* struct pci_tsm_pf0 - Physical Function 0 TDISP link context +* struct pci_tsm_host - TSM host link context (CMA, IDE, or TDISP) * @base_tsm: generic core "tsm" context * @lock: mutual exclustion for pci_tsm_ops invocation * @doe_mb: PCIe Data Object Exchange mailbox +* +* The host of a TSM link is the device that knows how to speak +* CMA, IDE, or TDISP. For TDISP / IDE that is a Physical Function 0. +* For CMA-only it is a CMA DOE mailbox. */ -struct pci_tsm_pf0 { +struct pci_tsm_host {struct pci_tsm base_tsm;struct mutex lock;struct pci_doe_mb *doe_mb; };/* physical function0 and capable of 'connect' */ -static inline bool is_pci_tsm_pf0 (struct pci_dev *pdev) +static inline bool is_pci_tsm_host (struct pci_dev *pdev) {if (!pdev)return false;
@@ -204,9 +208,9 @@ int pci_tsm_register(struct tsm_dev *tsm_dev);void pci_tsm_unregister (struct tsm_dev *tsm_dev);int pci_tsm_link_constructor (struct pci_dev *pdev,struct pci_tsm *tsm,struct tsm_dev *tsm_dev); -int pci_tsm_pf0_constructor (struct pci_dev *pdev,struct pci_tsm_pf0 *tsm, -struct tsm_dev *tsm_dev); -void pci_tsm_pf0_destructor (struct pci_tsm_pf0 *tsm); +int pci_tsm_host_constructor (struct pci_dev *pdev,struct pci_tsm_host *tsm, +struct tsm_dev *tsm_dev); +void pci_tsm_host_destructor (struct pci_tsm_host *tsm);int pci_tsm_doe_transfer (struct pci_dev *pdev,u8 type,const void *req,size_t req_sz,void *resp,size_t resp_sz);int pci_tsm_bind (struct pci_dev *pdev,struct kvm *kvm,u32 tdi_id);
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v3 10/21] PCI/TSM: Rename pf0 to host
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 11 lines
From: Alistair Francis <[email protected]>
Rename
pci_tsm_pf0topci_tsm_host(and rename variables and function names from pf0 to host) as part of convertingpci_tsm_hostto be any device that knows how to speak any of CMA, IDE, or TDISP.This commit just renames the functions and provides no functional change. That will happen in the next commit.
Signed-off-by Alistair Francis <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=10
Re: [PATCH v3 10/21] PCI/TSM: Rename pf0 to host
In reply to [email protected]Quoted 6 lines · On Tue, 1 Sep 2026 11:03:36 +1000 [email protected] wrote:
From: Alistair Francis <[email protected]>
Rename
pci_tsm_pf0topci_tsm_host(and rename variables and function names from pf0 to host) as part of convertingpci_tsm_hostto be any device that knows how to speak any of CMA, IDE, or TDISP.
I'm not against the change, but I would like at least one example of 'what' other type of device you plan to use this for.
vf or something else?
Quoted 12 lines
This commit just renames the functions and provides no functional change. That will happen in the next commit.
Signed-off-by Alistair Francis <[email protected]>--- drivers/crypto/ccp/sev-dev-tio.h | 4 +- drivers/crypto/ccp/sev-dev-tsm.c | 12 ++--- drivers/pci/tsm.c | 84 ++++++++++++++++---------------- include/linux/pci-tsm.h | 18 ++++--- 4 files changed, 61 insertions(+), 57 deletions(-)
Re: [PATCH v3 10/21] PCI/TSM: Rename pf0 to host
Quoted 11 lines · Jonathan Cameron
Quoted 7 lines · On Tue, 1 Sep 2026 11:03:36 +1000 [email protected] wrote:
From: Alistair Francis <[email protected]>
Rename
pci_tsm_pf0topci_tsm_host(and rename variables and function
names from pf0 to host) as part of convertingpci_tsm_hostto be any device that knows how to speak any of CMA, IDE, or TDISP.
Quoted 4 lines
I'm not against the change, but I would like at least one example of 'what' other type of device you plan to use this for.
vf or something else?
It's based on the reply here: https://lkml.org/lkml/2026/6/18/1741
I don't know PCIe that well, so honestly I'm not sure.
My use case is a root complex integrated endpoint
Alistair
Quoted 14 lines
Quoted 14 lines
This commit just renames the functions and provides no functional change. That will happen in the next commit.
Signed-off-by Alistair Francis <[email protected]>---
drivers/crypto/ccp/sev-dev-tio.h | 4 +-drivers/crypto/ccp/sev-dev-tsm.c| 12 ++--- drivers/pci/tsm.c | 84 ++++++++++++++++------------
----
include/linux/pci-tsm.h | 18 ++++---4 files changed, 61 insertions(+), 57 deletions(-)
Re: [PATCH v3 10/21] PCI/TSM: Rename pf0 to host
Quoted 12 lines · On Fri, 11 Sep 2026 15:00:59 +1000 Alistair <[email protected]> wrote:
Quoted 11 lines · Jonathan Cameron
Quoted 7 lines · On Tue, 1 Sep 2026 11:03:36 +1000 [email protected] wrote:
From: Alistair Francis <[email protected]>
Rename
pci_tsm_pf0topci_tsm_host(and rename variables and function
names from pf0 to host) as part of convertingpci_tsm_hostto be any device that knows how to speak any of CMA, IDE, or TDISP.
Quoted 8 lines
Quoted 4 lines
I'm not against the change, but I would like at least one example of 'what' other type of device you plan to use this for.
vf or something else?
It's based on the reply here: https://lkml.org/lkml/2026/6/18/1741
I don't know PCIe that well, so honestly I'm not sure.
Yeah, that's about both VFs and other physical functions. Those can exist on both real EPs and RCiEPs.
Quoted 2 lines
My use case is a root complex integrated endpoint
Any more info to share on that? Why isn't it attested etc as part of the host given it's integrated? Or is SPDM a path to something other than simple attestation here? Fine to do it this way, just seems like overkill so I'm curious what the application is.
Jonathan
Re: [PATCH v3 10/21] PCI/TSM: Rename pf0 to host
Quoted 25 lines · Jonathan Cameron
Quoted 16 lines · On Fri, 11 Sep 2026 15:00:59 +1000 Alistair <[email protected]> wrote:
Quoted 15 lines · Jonathan Cameron
On Tue, 1 Sep 2026 11:03:36 +1000
[email protected] wrote:Quoted 7 lines
From: Alistair Francis <[email protected]>
Rename
pci_tsm_pf0topci_tsm_host(and rename variables and function
names from pf0 to host) as part of convertingpci_tsm_hostto be any device that knows how to speak any of CMA, IDE, or TDISP.I'm not against the change, but I would like at least one example of
'what' other type of device you plan to use this for.Quoted 5 lines
Quoted 1 line
vf or something else?
It's based on the reply here: https://lkml.org/lkml/2026/6/18/1741
I don't know PCIe that well, so honestly I'm not sure.
Quoted 8 lines
Yeah, that's about both VFs and other physical functions. Those can exist on both real EPs and RCiEPs.
Quoted 2 lines
My use case is a root complex integrated endpoint
Any more info to share on that? Why isn't it attested etc as part of the host given it's integrated? Or is SPDM a path to
Oh, sorry. It's not integrated in the sense of on the board. It's just a PCIe attached NVMe device. You can see the example for testing with QEMU in the cover letter.
Quoted 3 lines
something other than simple attestation here? Fine to do it this way, just seems like overkill so I'm curious what the application is.
The next step is to expand this to support other transports, like the storage ones.
Alistair
Quoted 2 lines
Jonathan
PATCH v3 11/21 PCI/TSM: Support connecting to PCIe CMA devices
In reply to [email protected]From: Alistair Francis <[email protected]>
In the next patch we are going to add a PCIe CMA TSM driver, as such we need to ensure that is_pci_tsm_host() will allow us to connect to CMA capable devices. These devices don't necessarily has DEVCAP_TEE or IDE support.
To avoid calling pci_find_doe_mailbox() everytime is_pci_tsm_host() is called we can cache the CMA support in struct pci_dev and just check against that.
--- drivers/pci/doe.c | 3 +++ drivers/pci/tsm.c | 31 +++++++++++++++++++++++++------ include/linux/pci-tsm.h | 12 +++++++++++- include/linux/pci.h | 1 + 4 files changed, 40 insertions(+), 7 deletions(-)
drivers/pci/doe.c +3 -0
@@ -870,6 +870,9 @@ void pci_doe_init(struct pci_dev *pdev)pci_doe_destroy_mb (doe_mb); } } + + pdev->doe_cma =pci_find_doe_mailbox (pdev,PCI_VENDOR_ID_PCI_SIG , +PCI_DOE_FEATURE_CMA ); }void pci_doe_destroy (struct pci_dev *pdev)
drivers/pci/tsm.c +25 -6
@@ -88,8 +88,8 @@ static void pci_tsm_walk_fns(struct pci_dev *pdev,if (!pf)continue ; -/* on entry function 0 has already run @cb */ -if (i >0 ) +/* the caller is responsible for running @cb on the host itself */ +if (pf != pdev)cb (pf, data);/* walk virtual functions of each pf */
@@ -145,8 +145,8 @@ static void pci_tsm_walk_fns_reverse(struct pci_dev *pdev,cb (vf, data); } -/* on exit, caller will run @cb on function 0 */ -if (i >0 ) +/* the caller is responsible for running @cb on the host itself */ +if (pf != pdev)cb (pf, data); } }
@@ -287,6 +287,16 @@ static DEVICE_ATTR_RW(connect);static int remove_fn (struct pci_dev *pdev,void *data) { +struct pci_dev *host = data; + +/* +* Only teardown the security context of functions that belong to the +* DSM being disconnected, leaving any sibling DSM in the same slot +* untouched. +*/ +if (!pdev->tsm || pdev->tsm->dsm_dev != host) +return 0 ; +tsm_remove (pdev->tsm);link_sysfs_disable (pdev);return 0 ;
@@ -299,6 +309,7 @@ static int remove_fn(struct pci_dev *pdev, void *data)*/ static int __pci_tsm_unbind (struct pci_dev *pdev,void *data) { +struct pci_dev *host = data;struct pci_tdi *tdi;struct pci_tsm_host *tsm_host;
@@ -307,6 +318,14 @@ static int __pci_tsm_unbind(struct pci_dev *pdev, void *data)if (!pdev->tsm)return 0 ; +/* +* When walking a DSM's dependent functions skip any that belong to a +* different DSM in the same slot. A NULL @host is a direct unbind of +* @pdev itself. +*/ +if (host && pdev->tsm->dsm_dev != host) +return 0 ; + tsm_host =to_pci_tsm_host (pdev->tsm);guard (mutex)(&tsm_host->lock);
@@ -437,7 +456,7 @@ EXPORT_SYMBOL_GPL(pci_tsm_guest_req);static void pci_tsm_unbind_all (struct pci_dev *pdev) { -pci_tsm_walk_fns_reverse (pdev, __pci_tsm_unbind,NULL ); +pci_tsm_walk_fns_reverse (pdev, __pci_tsm_unbind, pdev);__pci_tsm_unbind (pdev,NULL ); }
@@ -456,7 +475,7 @@ static void __pci_tsm_disconnect(struct pci_dev *pdev)* teardown */ guard (mutex)(&tsm_host->lock); -pci_tsm_walk_fns_reverse (pdev, remove_fn,NULL ); +pci_tsm_walk_fns_reverse (pdev, remove_fn, pdev); ops->disconnect (pdev); }
include/linux/pci-tsm.h +11 -1
@@ -3,6 +3,7 @@#define __PCI_TSM_H#include <linux/mutex.h> #include <linux/pci.h> +#include <linux/pci-doe.h> #include <linux/sockptr.h> struct pci_tsm ;
@@ -130,7 +131,7 @@ struct pci_tsm_host {struct pci_doe_mb *doe_mb; }; -/* physical function0 and capable of 'connect' */ +/* device is a TSM host and capable of 'connect' */ static inline bool is_pci_tsm_host (struct pci_dev *pdev) {if (!pdev)
@@ -142,6 +143,15 @@ static inline bool is_pci_tsm_host(struct pci_dev *pdev)if (pdev->is_virtfn)return false; +/* +* Report capable if CMA is supported, which can be supported on any PCIe +* device. +*/ +#ifdef CONFIG_PCI_DOE +if (pdev->doe_cma) +return true; +#endif +/* * Allow for a Device Security Manager (DSM) associated with function0 * of an Endpoint to coordinate TDISP requests for other functions
include/linux/pci.h +1 -0
@@ -565,6 +565,7 @@ struct pci_dev {#endif #ifdef CONFIG_PCI_DOE struct xarray doe_mbs;/* Data Object Exchange mailboxes */ +bool doe_cma;/* A CMA/SPDM DOE mailbox is present */ #endif #ifdef CONFIG_PCI_NPEM struct npem *npem;/* Native PCIe Enclosure Management */
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v3 11/21] PCI/TSM: Support connecting to PCIe CMA devices
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
New issues:
- [High] Unconditional overwrite of pdev->tsm in probe_fn() causes memory leak and state corruption for independent sibling CMA hosts.
- [High] Expanding is_pci_tsm_host() to return true for dependent functions with CMA breaks DSM resolution, causing type confusion and preventing context cleanup.
Pre-existing issues:
- [High] AB-BA lock inversion between pci_bus_sem and tsm_host->lock in __pci_tsm_disconnect for upstream switch ports. --
commit 1f366330a43aef4dbb8b7e338fe088b357fa13a0
Author: Alistair Francis <[email protected]>
PCI/TSM: Support connecting to PCIe CMA devices
This patch adds support for connecting to PCIe CMA devices by modifying is_pci_tsm_host() to allow CMA capable devices. It caches the CMA support in struct pci_dev to avoid repeated mailbox lookups.
Quoted 4 lines
drivers/pci/tsm.c +0
-0
[ ... ]
drivers/pci/tsm.c static void __pci_tsm_disconnect(struct pci_dev *pdev) · line 478
Quoted 6 lines
drivers/pci/tsm.c +1
-1@@ -456,7 +475,7 @@ static void __pci_tsm_disconnect(struct pci_dev *pdev)* teardown */ guard (mutex)(&tsm_host->lock); -pci_tsm_walk_fns_reverse (pdev, remove_fn,NULL ); +pci_tsm_walk_fns_reverse (pdev, remove_fn, pdev);
[Severity: High]
This is a pre-existing issue, but does __pci_tsm_disconnect() create an AB-BA lock inversion between pci_bus_sem and tsm_host->lock?
First, pci_tsm_unbind_all() calls pci_tsm_walk_fns_reverse(). For upstream switch ports, this invokes pci_walk_bus_reverse(), acquiring pci_bus_sem for read. Inside the walk, the callback __pci_tsm_unbind() acquires tsm_host->lock, establishing a pci_bus_sem -> tsm_host->lock ordering.
Immediately after, __pci_tsm_disconnect() explicitly acquires tsm_host->lock via guard(mutex), and then calls pci_tsm_walk_fns_reverse() (which invokes pci_walk_bus_reverse()), attempting to acquire pci_bus_sem for read.
This reverses the acquisition order to tsm_host->lock -> pci_bus_sem, which could lead to a deadlock when racing with bus modifications.
Quoted 7 lines
drivers/pci/tsm.c +0
-0ops->disconnect (pdev); }include/linux/pci-tsm.h +0
-0
[ ... ]
include/linux/pci-tsm.h static inline bool is_pci_tsm_host(struct pci_dev *pdev) · line 154
Quoted 13 lines
include/linux/pci-tsm.h +9
-0@@ -142,6 +143,15 @@ static inline bool is_pci_tsm_host(struct pci_dev *pdev)if (pdev->is_virtfn)return false; +/* +* Report capable if CMA is supported, which can be supported on any PCIe +* device. +*/ +#ifdef CONFIG_PCI_DOE +if (pdev->doe_cma) +return true; +#endif +
[Severity: High]
Does expanding is_pci_tsm_host() to return true for dependent functions with CMA introduce a memory leak and state corruption?
If userspace connects a dependent function (like PF1) as a CMA host first, pci_tsm_connect() allocates PF1->tsm. If userspace subsequently connects a TEE host (DSM) on the same device (e.g. PF0), pci_tsm_connect(PF0) walks dependent functions and calls probe_fn(PF1, PF0).
In drivers/pci/tsm.c:probe_fn(), it unconditionally overwrites pdev->tsm:
pdev->tsm = ops->probe(dsm_dev->tsm->tsm_dev, pdev);
Could this overwrite the existing CMA TSM context without checking or freeing it, leaking the memory and corrupting the state?
[Severity: High]
Additionally, does returning true for dependent functions with CMA break DSM resolution in find_dsm_dev()?
When the parent TEE DSM (PF0) calls probe_fn(PF1), the allocation path calls find_dsm_dev(PF1). Since is_pci_tsm_host(PF1) now evaluates to true, find_dsm_dev() incorrectly returns PF1 instead of the actual DSM (PF0).
This causes PF1 to self-identify as its own host, meaning PF1->tsm->dsm_dev == PF1.
Later, when the parent DSM (PF0) is disconnected, remove_fn() has:
if (!pdev->tsm || pdev->tsm->dsm_dev != host) return 0;
This will skip PF1 because its dsm_dev no longer matches the host, causing the TEE context to leak. Furthermore, upon device removal, __pci_tsm_destroy(PF1) would incorrectly treat PF1 as a host and execute the host-only teardown path (pci_tsm_disconnect), causing type confusion.
Quoted 3 lines
include/linux/pci-tsm.h +0
-0/* * Allow for a Device Security Manager (DSM) associated with function0 * of an Endpoint to coordinate TDISP requests for other functions
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=11
PATCH v3 12/21 PCI/CMA: Add a PCI TSM CMA driver using SPDM
In reply to [email protected]From: Alistair Francis <[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, add support for authentication via a CMA TSM driver.
This was previously discusd here:
http://lore.kernel.org/[email protected]
By utilising a TSM driver we get a lot of the TSM driver probe policies "for free". Currently there is no mechanism to provide evidence to userspace, as the TSM system doesn't support that at the moment. That can be added later when support by TSM.
Credits: Jonathan wrote the original proof-of-concept for a CMA implementation. Lukas reworked that for upstream. Wilfred contributed fixes for issues discovered during testing. Alistair reworked it as a TSM driver.
--- MAINTAINERS | 1 + drivers/pci/Kconfig | 14 ++++ drivers/pci/Makefile | 2 + drivers/pci/cma.c | 154 ++++++++++++++++++++++++++++++++++++++++ drivers/pci/doe.c | 3 - include/linux/pci-doe.h | 4 ++ 6 files changed, 175 insertions(+), 3 deletions(-) create mode 100644 drivers/pci/cma.c
MAINTAINERS +1 -0
@@ -24762,6 +24762,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 +14 -0
@@ -124,6 +124,20 @@ config PCI_ATS config PCI_IDE bool +config PCI_CMA + bool "Component Measurement and Authentication (CMA-SPDM)" + depends on RSPDM + select CRYPTO_ECDSA + select CRYPTO_RSA + select CRYPTO_SHA256 + select CRYPTO_SHA512 + select PCI_DOE + select PCI_TSM + 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_TSM bool "PCI TSM: Device security protocol support" select PCI_IDE
drivers/pci/Makefile +2 -0
@@ -41,6 +41,8 @@ obj-$(CONFIG_PCI_NPEM) += npem.o obj-$(CONFIG_PCIE_TPH) += tph.o obj-$(CONFIG_CARDBUS) += setup-cardbus.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 +154 -0
154 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 *
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);
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v3 12/21] PCI/CMA: Add a PCI TSM CMA driver using SPDM
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] The CMA driver's probe callback unconditionally treats every probed device as a TSM host, which…
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The CMA driver's probe callback unconditionally treats every probed device as a TSM host, which triggers spurious kernel warnings for sub-functions like VFs. --
commit e6e460ef636dca826aac1d314b1cb072ffd45217
Author: Alistair Francis <[email protected]>
PCI/CMA: Add a PCI TSM CMA driver using SPDM
This commit adds a Component Measurement and Authentication (CMA) driver using SPDM. It utilizes a PCI DOE mailbox as a transport to authenticate devices during enumeration and integrates with the TSM subsystem.
Quoted 5 lines
drivers/pci/cma.c new file +0
-0
[ ... ]
drivers/pci/cma.c line 57
Quoted 11 lines
drivers/pci/cma.c new file +11
-0+static struct pci_tsm *pci_cma_tsm_probe (struct tsm_dev *tsm_dev, +struct pci_dev *pdev) +{ +struct pci_cma_tsm *cma; +int rc; + + cma =kzalloc (sizeof (*cma),GFP_KERNEL ); +if (!cma) +return NULL ; + + rc =pci_tsm_host_constructor (pdev, &cma->host, tsm_dev);
[Severity: Medium]
Will this unconditional call trigger spurious warnings for sub-functions like VFs?
When VFs are instantiated on an already-connected TSM host, pci_tsm_init() finds the host DSM and calls the probe function for the VF. Here in pci_cma_tsm_probe() from drivers/pci/cma.c, we unconditionally call pci_tsm_host_constructor() on every probed device.
Because VFs lack CMA mailboxes, this triggers spurious "TSM init failure, no CMA mailbox" warnings in the kernel log for every VF probed.
Would it make sense to add an is_pci_tsm_host(pdev) check here before calling pci_tsm_host_constructor(), to ensure we only attempt to initialize it when appropriate?
Quoted 4 lines
drivers/pci/cma.c new file +4
-0+if (rc) { +kfree (cma); +return NULL ; + }
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=12
Re: [PATCH v3 12/21] PCI/CMA: Add a PCI TSM CMA driver using SPDM
In reply to [email protected]Quoted 25 lines · On Tue, 1 Sep 2026 11:03:38 +1000 [email protected] wrote:
From: Alistair Francis <[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, add support for authentication via a CMA TSM driver.
This was previously discusd here:
http://lore.kernel.org/[email protected]By utilising a TSM driver we get a lot of the TSM driver probe policies "for free". Currently there is no mechanism to provide evidence to userspace, as the TSM system doesn't support that at the moment. That can be added later when support by TSM.
Credits: Jonathan wrote the original proof-of-concept for a CMA implementation. Lukas reworked that for upstream. Wilfred contributed fixes for issues discovered during testing. Alistair reworked it as a TSM driver.
Quoted 6 lines
Signed-off-by Jonathan Cameron <[email protected]>Co-developed-by Wilfred Mallawa <[email protected]>Signed-off-by Wilfred Mallawa <[email protected]>Co-developed-by Lukas Wunner <[email protected]>Signed-off-by Lukas Wunner <[email protected]>Signed-off-by Alistair Francis <[email protected]>
A few things inline. I have no idea if I'm poking holes in my own code or if none of that is left :)
Quoted 6 lines
drivers/pci/cma.c new file +0
-0
...
drivers/pci/cma.c line 91
Quoted 9 lines
drivers/pci/cma.c new file +9
-0+static int pci_cma_tsm_connect (struct pci_dev *pdev) +{ +struct pci_cma_tsm *cma =cma_tsm_from_tsm (pdev->tsm); +int rc; + +/* +* The DOE mailbox lives in the device's config space, so the +* device must be runtime-resumed for the duration of the SPDM +* exchange.
Oddly short wrap. Comments go to 80
/*
* The DOE mailbox lives in the device's config space, so the device
* must be runtime-resumed for the duration of the SPDM exchange.
drivers/pci/cma.c line 93
Quoted 2 lines
drivers/pci/cma.c new file +2
-0+*/ + rc =pm_runtime_get_sync (&pdev->dev);
rc = pm_runtime_resume_and_get(&pdev->dev) if (rc < 0) return rc;
See comments in docs for pm_runtime_get_sync() for info on this. Basically it doesn't mess up reference counting so you don't need to fix it in the error path.
Mind you, I haven't looked ahead but we have ACQUIRE macros for runtime PM that might apply nicely here and remove need to release anything at all.
PM_RUNTIME_ACQUIRE(&pdev->dev, pm); if (PM_RUNTIME_ACQUIRE_ERR(&pm)) return -ENXIO;
drivers/pci/cma.c line 99
Quoted 6 lines
drivers/pci/cma.c new file +6
-0+if (rc <0 ) { +pm_runtime_put_noidle (&pdev->dev); +return rc; + } + + rc =spdm_authenticate (cma->spdm);
If you can use ACQUIRE stuff
return spdm_authenticate(cma->spdm);
Quoted 15 lines
drivers/pci/cma.c new file +14
-0+ +pm_runtime_put_sync (&pdev->dev); +return rc; +} + +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, + .bind = pci_cma_tsm_bind, + .unbind = pci_cma_tsm_unbind, + .guest_req = pci_cma_tsm_guest_req,
drivers/pci/cma.c line 147
Quoted 10 lines
drivers/pci/cma.c new file +10
-0+ }, +}; + +static struct tsm_dev *pci_cma_tsm_dev; + +static int __init pci_cma_tsm_init (void ) +{ +struct tsm_dev *tsm_dev; + + tsm_dev =tsm_register (NULL , (struct pci_tsm_ops *)&pci_cma_tsm_ops);
So this is casting away the const. Why does tsm_register not take a const?
Can we change that as seems unlikely it will actually modify it? If there is any chance of a modification in future then we need to drop the const marking on the structure above.
Quoted 7 lines
drivers/pci/cma.c new file +7
-0+if (IS_ERR (tsm_dev)) +return PTR_ERR (tsm_dev); + + pci_cma_tsm_dev = tsm_dev; +return 0 ; +} +late_initcall (pci_cma_tsm_init);
PATCH v3 13/21 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.
[ Changed by AF:
- Fixup a few issues caught by Sashiko
]
--- drivers/pci/Makefile | 5 +- drivers/pci/cma.asn1 | 41 ++++++++++++ drivers/pci/cma.c | 123 ++++++++++++++++++++++++++++++++++- include/linux/oid_registry.h | 3 + 4 files changed, 170 insertions(+), 2 deletions(-) create mode 100644 drivers/pci/cma.asn1
drivers/pci/Makefile +4 -1
@@ -41,7 +41,10 @@ obj-$(CONFIG_PCI_NPEM) += npem.o obj-$(CONFIG_PCIE_TPH) += tph.o obj-$(CONFIG_CARDBUS) += setup-cardbus.o -obj-$(CONFIG_PCI_CMA) += cma.o +obj-$(CONFIG_PCI_CMA) += pci-cma.o +pci-cma-y := 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
143 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.55.0
[email protected]
AI reviewRe: [PATCH v3 13/21] PCI/CMA: Validate Subject Alternative Name in certificates
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 13 lines
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").
Quoted 8 lines
The Subject Alternative Name is signed, hence constitutes a signed copy of a Config Space portion. It's the same concept as web certificates
[ ... ][ Changed by AF:
- Fixup a few issues caught by Sashiko
]Signed-off-by Alistair Francis <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=13
PATCH v3 14/21 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 | 16 ++++++++-- lib/rspdm/lib.rs | 54 +++++++++++++++++++++++++++------ lib/rspdm/state.rs | 67 ++++++++++++++++++++++++++++++++++++++-- lib/rspdm/validator.rs | 69 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 192 insertions(+), 14 deletions(-)
lib/rspdm/consts.rs +14 -2
@@ -7,16 +7,24 @@//! Rust implementation of the DMTF Security Protocol and Data Model (SPDM) //! <https://www.dmtf.org/dsp/DSP0274> +use crate ::validator::GetVersionRsp ; +use core::mem;use kernel::error::{code::EINVAL ,Error };// 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 ; +#[allow(dead_code)] +pub (crate )const SPDM_VER_13 :u8 =0x13 ; +pub (crate )const SPDM_VER_14 :u8 =0x14 ;pub (crate )const SPDM_MIN_VER :u8 =SPDM_VER_10 ; +pub (crate )const SPDM_MAX_VER :u8 =SPDM_VER_14 ; -#[allow(dead_code)] pub (crate )const SPDM_REQ :u8 =0x80 ; -#[allow(dead_code)] pub (crate )const SPDM_ERROR :u8 =0x7f ;#[derive( Clone , Copy )]
@@ -90,3 +98,7 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {write! (f,"{:#x}" , *selfas u8 ) } } + +pub (crate )const SPDM_GET_VERSION :u8 =0x84 ; +pub (crate )const SPDM_GET_VERSION_LEN :usize = + mem::size_of ::<GetVersionRsp >() + (u8::MAX as usize ) * mem::size_of ::<u16 >();
lib/rspdm/lib.rs +44 -10
78 lines not on this page · raw message
lib/rspdm/state.rs +65 -2
105 lines not on this page · raw message
lib/rspdm/validator.rs +69 -0
84 lines not on this page · raw message
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v3 14/21] lib: rspdm: Support SPDM get_version
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Low] The newly added `kernel::sync` import violates the kernel vertical import style guidelines. --
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The newly added kernel::sync import violates the kernel vertical import style guidelines. --
commit 2c0be141f2603418ed69b0153c6ee75043525d1f
Author: Alistair Francis <[email protected]>
lib: rspdm: Support SPDM get_version
Support the GET_VERSION SPDM command.
[ ... ]
lib/rspdm/lib.rs line 24
Quoted 12 lines
lib/rspdm/lib.rs +2
-0@@ -18,8 +18,10 @@ c_int, c_void, // }; +use core::pin::Pin ;use core::ptr;use kernel::prelude::*; +use kernel::sync::{new_mutex,Mutex };
[Severity: Low]
Should the kernel::sync import in lib/rspdm/lib.rs be reformatted to follow the kernel vertical import style guidelines rather than grouping multiple items on a single line?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=14
Re: [PATCH v3 14/21] lib: rspdm: Support SPDM get_version
In reply to [email protected]Quoted 5 lines · On Tue, 1 Sep 2026 11:03:40 +1000 [email protected] wrote:
From: Alistair Francis <[email protected]>
Support the
GET_VERSIONSPDM command.Signed-off-by Alistair Francis <[email protected]>
From a an SPDM stand point looks good to me.
Quoted 4 lines
lib/rspdm/lib.rs +0
-0
...
Quoted 8 lines
lib/rspdm/lib.rs +4
-0@@ -234,4 +242,59 @@ pub(crate) fn spdm_exchange(Ok (length) } + +/// Negotiate a supported SPDM version and store the information +/// in the `SpdmState`. +pub (crate )fn get_version (&mut self) ->Result <(),Error > {
...
lib/rspdm/lib.rs
Quoted 8 lines
lib/rspdm/lib.rs +8
-0+ +for iin 0 ..entry_countas usize { +let off = entries_offset + i * core::mem::size_of ::<u16 >(); +let entry = u16::from_le_bytes ([response_vec[off], response_vec[off +1 ]]); +let alpha_version = (entry &0xF )as u8 ; +let version = (entry >>8 )as u8 ; + +if alpha_version >0 {
Can we do != 0 as that matches the spec text?
Quoted 17 lines
lib/rspdm/lib.rs +16
-0+pr_warn! ("Alpha version {alpha_version} is not specifically supported\n" ); + } + +if version >= self.version && version <=SPDM_MAX_VER { + self.version = version; + foundver =true ; + } + } + +if !foundver { +pr_err! ("No common supported version\n" ); +return Err (EPROTO ); + } + +Ok (()) + } }
PATCH v3 15/21 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 | 19 +++++++- lib/rspdm/lib.rs | 4 ++ lib/rspdm/state.rs | 78 ++++++++++++++++++++++++++++- lib/rspdm/validator.rs | 108 ++++++++++++++++++++++++++++++++++++++++- 4 files changed, 205 insertions(+), 4 deletions(-)
lib/rspdm/consts.rs +17 -2
@@ -9,13 +9,12 @@use crate ::validator::GetVersionRsp ;use core::mem; +use kernel::bits::bit_u32;use kernel::error::{code::EINVAL ,Error };// 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 ;#[allow(dead_code)] pub (crate )const SPDM_VER_13 :u8 =0x13 ;
@@ -102,3 +101,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 ::<GetVersionRsp >() + (u8::MAX as usize ) * mem::size_of ::<u16 >(); + +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 =bit_u32 (1 ); +pub (crate )const SPDM_CHAL_CAP :u32 =bit_u32 (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
@@ -102,6 +102,10 @@ pub extern "C" fn spdm_authenticate(state_ptr: *mut spdm_state) -> c_int {return e.to_errno ()as c_int ; } +if let Err (e) = state.get_capabilities () { +return e.to_errno ()as c_int ; + } + -(EPROTONOSUPPORT as i32 ) }
lib/rspdm/state.rs +77 -1
109 lines not on this page · raw message
lib/rspdm/validator.rs +107 -1
118 lines not on this page · raw message
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v3 15/21] lib: rspdm: Support SPDM get_capabilities
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 5 lines
From: Alistair Francis <[email protected]>
Support the
GET_CAPABILITIESSPDM command.Signed-off-by Alistair Francis <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=15
Re: [PATCH v3 15/21] lib: rspdm: Support SPDM get_capabilities
In reply to [email protected]Quoted 5 lines · On Tue, 1 Sep 2026 11:03:41 +1000 [email protected] wrote:
From: Alistair Francis <[email protected]>
Support the
GET_CAPABILITIESSPDM command.Signed-off-by Alistair Francis <[email protected]>
PATCH v3 16/21 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 | 56 +++++++++- lib/rspdm/lib.rs | 9 +- lib/rspdm/state.rs | 243 ++++++++++++++++++++++++++++++++++++++++- lib/rspdm/validator.rs | 110 ++++++++++++++++++- 4 files changed, 412 insertions(+), 6 deletions(-)
lib/rspdm/consts.rs +55 -1
@@ -9,7 +9,10 @@use crate ::validator::GetVersionRsp ;use core::mem; -use kernel::bits::bit_u32; +use kernel::bits::{ + bit_u32, + bit_u8,// +};use kernel::error::{code::EINVAL ,Error };// SPDM versions supported by this implementation
@@ -114,6 +117,57 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {pub (crate )const SPDM_CERT_CAP :u32 =bit_u32 (1 );pub (crate )const SPDM_CHAL_CAP :u32 =bit_u32 (2 ); +pub (crate )const SPDM_KEY_EX_CAP :u32 =bit_u32 (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 =bit_u8 (0 ); + +pub (crate )const SPDM_ASYM_RSASSA_2048 :u32 =bit_u32 (0 ); +pub (crate )const _SPDM_ASYM_RSAPSS_2048:u32 =bit_u32 (1 ); +pub (crate )const SPDM_ASYM_RSASSA_3072 :u32 =bit_u32 (2 ); +pub (crate )const _SPDM_ASYM_RSAPSS_3072:u32 =bit_u32 (3 ); +pub (crate )const SPDM_ASYM_ECDSA_ECC_NIST_P256 :u32 =bit_u32 (4 ); +pub (crate )const SPDM_ASYM_RSASSA_4096 :u32 =bit_u32 (5 ); +pub (crate )const _SPDM_ASYM_RSAPSS_4096:u32 =bit_u32 (6 ); +pub (crate )const SPDM_ASYM_ECDSA_ECC_NIST_P384 :u32 =bit_u32 (7 ); +pub (crate )const SPDM_ASYM_ECDSA_ECC_NIST_P521 :u32 =bit_u32 (8 ); +pub (crate )const _SPDM_ASYM_SM2_ECC_SM2_P256:u32 =bit_u32 (9 ); +pub (crate )const _SPDM_ASYM_EDDSA_ED25519:u32 =bit_u32 (10 ); +pub (crate )const _SPDM_ASYM_EDDSA_ED448:u32 =bit_u32 (11 ); + +pub (crate )const SPDM_HASH_SHA_256 :u32 =bit_u32 (0 ); +pub (crate )const SPDM_HASH_SHA_384 :u32 =bit_u32 (1 ); +pub (crate )const SPDM_HASH_SHA_512 :u32 =bit_u32 (2 ); + +// If the crypto support isn't enabled don't offer the algorithms +// to the responder +#[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 ; + +pub (crate )const SPDM_OPAQUE_DATA_FMT_GENERAL :u8 =bit_u8 (1 );
lib/rspdm/lib.rs +7 -2
@@ -94,7 +94,7 @@ pub extern "C" fn spdm_authenticate(state_ptr: *mut spdm_state) -> c_int {// exclusive `&mut SpdmState` lives entirely inside the lock guard, so // concurrent FFI callers serialize on the mutex and can never form // aliased `&mut SpdmState` references. -let mutex: &Mutex <SpdmState > =unsafe { &*(state_ptras *const Mutex <SpdmState >) }; +let mutex: &Mutex <SpdmState <'_>> =unsafe { &*(state_ptras *const Mutex <SpdmState <'_>>) };let mut state = mutex.lock ();
@@ -106,6 +106,10 @@ pub extern "C" fn spdm_authenticate(state_ptr: *mut spdm_state) -> c_int {return e.to_errno ()as c_int ; } +if let Err (e) = state.negotiate_algs () { +return e.to_errno ()as c_int ; + } + -(EPROTONOSUPPORT as i32 ) }
@@ -117,11 +121,12 @@ pub extern "C" fn spdm_destroy(state_ptr: *mut spdm_state) {if state_ptr.is_null () {return ; } +// SAFETY: `state_ptr` was returned from `spdm_create()`, which leaked a // `Pin<KBox<Mutex<SpdmState>>>` via `KBox::into_raw`. The caller // guarantees the state is no longer in use. Reconstructing the pinned // box and dropping it runs `Drop` for the `Mutex` and `SpdmState` and // frees the allocation. -let b =unsafe {KBox ::from_raw (state_ptras *mut Mutex <SpdmState >) }; +let b =unsafe {KBox ::from_raw (state_ptras *mut Mutex <SpdmState <'_>>) };drop (unsafe {Pin ::new_unchecked (b) }); }
lib/rspdm/state.rs +241 -2
293 lines not on this page · raw message
lib/rspdm/validator.rs +109 -1
129 lines not on this page · raw message
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v3 16/21] lib: rspdm: Support SPDM negotiate_algorithms
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [High] Memory provenance truncation when storing a dynamically sized C struct as a statically sized Rust…
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Memory provenance truncation when storing a dynamically sized C struct as a statically sized Rust reference.
- [Low] Algorithm match arms become dead code under valid Kconfig combinations.
--
commit 8baafb6a67bc3beb2ad7bb9b93fd0f34551c04c9
Author: Alistair Francis <[email protected]>
lib: rspdm: Support SPDM negotiate_algorithms
This commit adds support for the SPDM NEGOTIATE_ALGORITHMS command. It extends SpdmState to negotiate and store cryptographic algorithms, allocating the corresponding synchronous hash handle and context.
Quoted 4 lines
lib/rspdm/state.rs +0
-0
[ ... ]
lib/rspdm/state.rs pub(crate) fn get_capabilities(&mut self) -> Result<(), Error> { · line 461
Quoted 11 lines
lib/rspdm/state.rs +7
-0@@ -373,4 +452,164 @@ pub(crate) fn get_capabilities(&mut self) -> Result<(), Error> {Ok (()) } + +fn update_response_algs (&mut self) ->Result <(),Error > { +match self.base_asym_alg { +SPDM_ASYM_RSASSA_2048 => { + self.sig_len =256 ; + self.base_asym_enc =CStr ::from_bytes_with_nul (b"pkcs1\0" )?; + }
[Severity: Low]
Does this match arm become dead code when CONFIG_CRYPTO_RSA is disabled?
If CONFIG_CRYPTO_RSA is disabled, the SPDM_ASYM_RSA constant evaluates to 0. Consequently, negotiate_algs() will reject RSA algorithms because they are not present in SPDM_ASYM_ALGOS, making this match arm structurally unreachable.
Should these match arms be conditionally compiled so they don't produce dead code under valid Kconfig combinations?
[ ... ]
Quoted 9 lines
lib/rspdm/state.rs +9
-0+let desc_vec:KVec <u8 > =KVec ::from_elem (0u8 , desc_len,GFP_KERNEL )?; +// Consume the desc_vec to make sure it isn't dropped, untill we +// manually drop it later +let (desc_buf, _length, _capacity) = desc_vec.into_raw_parts (); + +// SAFETY: We are casting the allocation to be a shash_desc +let desc =unsafe { + core::mem::transmute ::<*mut c_void , &mut bindings::shash_desc >(desc_bufas *mut c_void ) + };
lib/rspdm/state.rs pub(crate) fn get_capabilities(&mut self) -> Result<(), Error> { · line 554
Quoted 10 lines
lib/rspdm/state.rs +10
-0+ desc.tfm = self.shash; + + self.desc =Some (desc); + +/* Used frequently to compute offsets, so cache H */ + self.hash_len =unsafe { bindings::crypto_shash_digestsize (self.shash)as usize }; + +if let Some (desc) = &mut self.desc { +// SAFETY: `self.desc` is a valid and initalised `shash_desc` sized buffer +unsafe {to_result (bindings::crypto_shash_init (*desc)) }
[Severity: High]
Does transmuting the dynamically sized buffer to a static Rust reference here cause memory provenance truncation?
When the dynamically sized buffer (which includes space for both the header and a flexible array context) is transmuted into a statically sized Rust reference &mut bindings::shash_desc, the Rust compiler strictly limits the memory provenance of this reference to the header's size.
When this restricted reference is coerced back to a pointer and passed to C functions like crypto_shash_init(), C writes into the trailing context array. This access is out-of-bounds with respect to the Rust reference's provenance, which triggers Undefined Behavior.
A similar issue occurs during Drop when the restricted reference is transmuted back to a pointer to free the full capacity. Checking the end of the patch series confirms this remains unfixed.
Would it be better to retain a raw pointer (*mut bindings::shash_desc) instead of materializing a provenance-restricted Rust reference?
Quoted 4 lines
lib/rspdm/state.rs +4
-0+ }else { +Err (ENOMEM ) + } + }
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=16
Re: [PATCH v3 16/21] lib: rspdm: Support SPDM negotiate_algorithms
In reply to [email protected]Quoted 11 lines · On 01/09/26 06:33, [email protected] wrote:
From: Alistair Francis <[email protected]>
Support the
NEGOTIATE_ALGORITHMSSPDM command.Signed-off-by Alistair Francis <[email protected]>--- lib/rspdm/consts.rs | 56 +++++++++- lib/rspdm/lib.rs | 9 +- lib/rspdm/state.rs | 243 ++++++++++++++++++++++++++++++++++++++++- lib/rspdm/validator.rs | 110 ++++++++++++++++++- 4 files changed, 412 insertions(+), 6 deletions(-)
[...]
Quoted 33 lines
lib/rspdm/state.rs +33
-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 ; + } + +let req_sz = core::mem::size_of ::<NegotiateAlgsReq >(); +let rsp_sz = core::mem::size_of ::<NegotiateAlgsRsp >(); + + request.length = (req_szas u16 ).to_le (); + +// 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 ::from_elem (0u8 , rsp_sz,GFP_KERNEL )?; + +let rc = self.spdm_exchange (request_buf, response_vec.as_mut_slice ())?as usize ; + +// The transport must report a length within the buffer we provided. +if rc > response_vec.len () { +pr_err! ("Overflowed capabilities response\n" ); +return Err (EIO ); + } + response_vec.truncate (rc); + +let response: &NegotiateAlgsRsp =Untrusted ::new (response_vec.as_slice ()).validate ()?; + + self.base_asym_alg = u32::from_le (response.base_asym_sel); + self.base_hash_alg = u32::from_le (response.base_hash_sel); + self.meas_hash_alg = u32::from_le (response.measurement_hash_algo);
lib/rspdm/state.rs pub(crate) fn get_capabilities(&mut self) -> Result<(), Error> { · line 601
Quoted 10 lines
lib/rspdm/state.rs +10
-0+ +if self.base_asym_alg &SPDM_ASYM_ALGOS ==0 || self.base_hash_alg &SPDM_HASH_ALGOS ==0 { +pr_err! ("No common supported algorithms\n" ); +return Err (EPROTO ); + } + +// /* 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 + || self.meas_hash_alg.count_ones() != 1
Hi Alistair,
As per the SPDM spec v1.4.0 Table 25 (ALGORITHMS response message format), description of the field 'MeasurementHashAlgo' states "If the Responder supports measurements (MEAS_CAP=01b or MEAS_CAP=10b in its CAPABILITIES response) and if MeasurementSpecificationSel is non-zero, then exactly one bit in this bit field shall be set. Otherwise, the Responder shall set this field to 0".
Currently, the code unconditionally checks for exactly one bit. If a responder does not support measurements, it will legitimately set this field to 0, which causes the current check to incorrectly fail.
A change somewhat similar to the diff shown below might help here:
return Err(EPROTO);
}+ let meas_hash_valid = if self.rsp_caps & SPDM_MEAS_CAP != 0
+ && response.measurement_specification_sel != 0
+ {
+ self.meas_hash_alg.count_ones() == 1
+ } else {
+ self.meas_hash_alg == 0
+ };
+
// /* 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
- || self.meas_hash_alg.count_ones() != 1
+ || !meas_hash_valid
|| response.ext_asym_sel_count != 0
|| response.ext_hash_sel_count != 0
|| response.param1 > request.param1Regards,
Aksh Garg
Quoted 13 lines
lib/rspdm/state.rs +13
-0+ || 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" ); +return Err (EPROTO ); + } + + self.update_response_algs()?; + +Ok (()) + }
Re: [PATCH v3 16/21] lib: rspdm: Support SPDM negotiate_algorithms
In reply to [email protected]Quoted 5 lines · On Tue, 1 Sep 2026 11:03:42 +1000 [email protected] wrote:
From: Alistair Francis <[email protected]>
Support the
NEGOTIATE_ALGORITHMSSPDM command.Signed-off-by Alistair Francis <[email protected]>
A question on types inline. Otherwise with the thing Aksh called out this looks fine to me.
lib/rspdm/lib.rs pub extern "C" fn spdm_destroy(state_ptr: *mut spdm_state) { · line 124
Quoted 10 lines
lib/rspdm/lib.rs +1
-0@@ -117,11 +121,12 @@ pub extern "C" fn spdm_destroy(state_ptr: *mut spdm_state) {if state_ptr.is_null () {return ; } +
Move that to the early commit.
Quoted 13 lines
lib/rspdm/lib.rs +1
-1// SAFETY: `state_ptr` was returned from `spdm_create()`, which leaked a // `Pin<KBox<Mutex<SpdmState>>>` via `KBox::into_raw`. The caller // guarantees the state is no longer in use. Reconstructing the pinned // box and dropping it runs `Drop` for the `Mutex` and `SpdmState` and // frees the allocation. -let b =unsafe {KBox ::from_raw (state_ptras *mut Mutex <SpdmState >) }; +let b =unsafe {KBox ::from_raw (state_ptras *mut Mutex <SpdmState <'_>>) };drop (unsafe {Pin ::new_unchecked (b) }); }lib/rspdm/state.rs +0
-0
...
Quoted 45 lines
lib/rspdm/state.rs +40
-0@@ -373,4 +452,164 @@ pub(crate) fn get_capabilities(&mut self) -> Result<(), Error> {Ok (()) } + +fn update_response_algs (&mut self) ->Result <(),Error > { +match self.base_asym_alg { +SPDM_ASYM_RSASSA_2048 => { + self.sig_len =256 ; + self.base_asym_enc =CStr ::from_bytes_with_nul (b"pkcs1\0" )?; + } +SPDM_ASYM_RSASSA_3072 => { + self.sig_len =384 ; + self.base_asym_enc =CStr ::from_bytes_with_nul (b"pkcs1\0" )?; + } +SPDM_ASYM_RSASSA_4096 => { + self.sig_len =512 ; + self.base_asym_enc =CStr ::from_bytes_with_nul (b"pkcs1\0" )?; + } +SPDM_ASYM_ECDSA_ECC_NIST_P256 => { + self.sig_len =64 ; + self.base_asym_enc =CStr ::from_bytes_with_nul (b"p1363\0" )?; + } +SPDM_ASYM_ECDSA_ECC_NIST_P384 => { + self.sig_len =96 ; + self.base_asym_enc =CStr ::from_bytes_with_nul (b"p1363\0" )?; + } +SPDM_ASYM_ECDSA_ECC_NIST_P521 => { + self.sig_len =132 ; + self.base_asym_enc =CStr ::from_bytes_with_nul (b"p1363\0" )?; + } + _ => { +pr_err! ("Unknown asym algorithm\n" ); +return Err (EINVAL ); + } + } + +match self.base_hash_alg { +SPDM_HASH_SHA_256 => { + self.base_hash_alg_name =CStr ::from_bytes_with_nul (b"sha256\0" )?; + } +SPDM_HASH_SHA_384 => { + self.base_hash_alg_name =CStr ::from_bytes_with_nul (b"sha384\0" )?; + }
lib/rspdm/state.rs pub(crate) fn get_capabilities(&mut self) -> Result<(), Error> { · line 504
Quoted 10 lines
lib/rspdm/state.rs +10
-0+ SPDM_HASH_SHA_512 => { + self.base_hash_alg_name =CStr ::from_bytes_with_nul (b"sha512\0" )?; + } + _ => { +pr_err! ("Unknown hash algorithm\n" ); +return Err (EINVAL ); + } + } + +// This is freed in when `SpdmState` is dropped, but this call
freed when
Quoted 7 lines
lib/rspdm/state.rs +7
-0+// can happen multiple times. +if self.shash != core::ptr::null_mut () { +if let Some (desc) = self.desc.take () { +// SAFETY: `self.shash` is a valid handle +let desc_len = core::mem::size_of ::<bindings::shash_desc >() + +unsafe { bindings::crypto_shash_descsize (self.shash) }as usize ; +
...
Quoted 12 lines
lib/rspdm/validator.rs +2
-1@@ -9,7 +9,8 @@use crate ::bindings::{ __IncompleteArrayField, - __le16,// + __le16, + __le32,// };
lib/rspdm/validator.rs
Quoted 10 lines
lib/rspdm/validator.rs +9
-0+ +#[repr( C , packed)] +pub (crate )struct NegotiateAlgsRsp { +pub (crate ) version:u8 , +pub (crate ) code:u8 , +pub (crate ) param1:u8 , +pub (crate ) param2:u8 , + +pub (crate ) length:u16 ,
Why do we treat this one as native endian but the ext_asym below as explicitly little endian?
Quoted 36 lines
lib/rspdm/validator.rs +36
-0+ 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, + 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) resp_alg_struct: __IncompleteArrayField<RegAlg>, +} + +impl <'a>Validate <Untrusted <&'a [u8 ]>>for &'aNegotiateAlgsRsp { +type Err =Error ; + +fn validate (unvalidated: &[u8 ]) ->Result <Self ,Self ::Err > { +if unvalidated.len () < mem::size_of ::<NegotiateAlgsRsp >() { +return Err (EINVAL ); + } + +let ptr = unvalidated.as_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: &NegotiateAlgsRsp =unsafe { &*ptr }; + +Ok (rsp) + } +}
Re: [PATCH v3 16/21] lib: rspdm: Support SPDM negotiate_algorithms
Quoted 16 lines · Jonathan Cameron
Quoted 5 lines · On Tue, 1 Sep 2026 11:03:42 +1000 [email protected] wrote:
From: Alistair Francis <[email protected]>
Support the
NEGOTIATE_ALGORITHMSSPDM command.Signed-off-by Alistair Francis <[email protected]>A question on types inline. Otherwise with the thing Aksh called out this looks fine to me.
Quoted 4 lines
lib/rspdm/lib.rs +0
-0
Quoted 8 lines
lib/rspdm/lib.rs pub extern "C" fn spdm_destroy(state_ptr: · line 125
Quoted 6 lines
lib/rspdm/lib.rs +1
-0@@ -117,11 +121,12 @@ pub extern "C" fn spdm_destroy(state_ptr: *mut spdm_state) { if state_ptr.is_null() { return; } +Move that to the early commit.
It's not that easy, even with an underscore Rust complains about it being unused
error[E0392]: lifetime parameter '_a is never used
--> lib/rspdm/state.rs:46:29
|
46 | pub(crate) struct SpdmState<'_a> {
| ^^^ unused lifetime parameter
| = help: consider removing '_a, referring to it in a field, or
using a marker such as PhantomData
error: aborting due to 1 previous error
I could use PhantomData to hide the error, but that doesn't feel great when it's just a temp for a few patches.
1:
https://doc.rust-lang.org/nightly/core/marker/struct.PhantomData.html
Quoted 115 lines
Quoted 20 lines
lib/rspdm/lib.rs +0
-0// SAFETY: `state_ptr` was returned from `spdm_create()`,which leaked a
//Pin<KBox<Mutex<SpdmState>>>viaKBox::into_raw. The caller
// guarantees the state is no longer in use. Reconstructing the pinned
// box and dropping it runsDropfor theMutexandSpdmStateand
// frees the allocation.
- let b = unsafe {KBox::from_raw(state_ptr as *mut Mutex<SpdmState>)};
+ let b = unsafe {KBox::from_raw(state_ptr as *mut Mutex<SpdmState<'_>>)};
drop(unsafe { Pin::new_unchecked(b) });
}lib/rspdm/state.rs +0
-0...
Quoted 58 lines
lib/rspdm/state.rs +0
-0@@ -373,4 +452,164 @@ pub(crate) fn get_capabilities(&mut self) -> Result<(), Error> {
Ok(())
}
+
+ fnupdate_response_algs(&mut self)-> Result<(), Error> { + match self.base_asym_alg {
+SPDM_ASYM_RSASSA_2048=> {
+ self.sig_len = 256;
+ self.base_asym_enc =CStr::from_bytes_with_nul(b"pkcs1\0")?;
+ }
+SPDM_ASYM_RSASSA_3072=> {
+ self.sig_len = 384;
+ self.base_asym_enc =CStr::from_bytes_with_nul(b"pkcs1\0")?;
+ }
+SPDM_ASYM_RSASSA_4096=> {
+ self.sig_len = 512;
+ self.base_asym_enc =CStr::from_bytes_with_nul(b"pkcs1\0")?;
+ }
+SPDM_ASYM_ECDSA_ECC_NIST_P256=> {
+ self.sig_len = 64;
+ self.base_asym_enc =CStr::from_bytes_with_nul(b"p1363\0")?;
+ }
+SPDM_ASYM_ECDSA_ECC_NIST_P384=> {
+ self.sig_len = 96;
+ self.base_asym_enc =CStr::from_bytes_with_nul(b"p1363\0")?;
+ }
+SPDM_ASYM_ECDSA_ECC_NIST_P521=> {
+ self.sig_len = 132;
+ self.base_asym_enc =CStr::from_bytes_with_nul(b"p1363\0")?;
+ }
+ _ => {
+ pr_err!("Unknown asym algorithm\n"); + return Err(EINVAL);
+ }
+ }
+
+ match self.base_hash_alg {
+SPDM_HASH_SHA_256=> {
+ self.base_hash_alg_name =CStr::from_bytes_with_nul(b"sha256\0")?;
+ }
+SPDM_HASH_SHA_384=> {
+ self.base_hash_alg_name =CStr::from_bytes_with_nul(b"sha384\0")?;
+ }
+SPDM_HASH_SHA_512=> {
+ self.base_hash_alg_name =CStr::from_bytes_with_nul(b"sha512\0")?;
+ }Quoted 8 lines
+ _ => {
+ pr_err!("Unknown hash algorithm\n");
+ return Err(EINVAL);
+ }
+ }
+
+ // This is freed in whenSpdmStateis dropped, but this callfreed when
Quoted 9 lines
+ // can happen multiple times.
+ if self.shash !=core::ptr::null_mut(){ + if let Some(desc) =self.desc.take(){ + // SAFETY:self.shashis a valid handle + letdesc_len= core::mem::size_of::<bindings::shash_desc>()
+ + unsafe {bindings::crypto_shash_descsize(self.shash)} as usize; +...
Quoted 12 lines
lib/rspdm/validator.rs +0
-0@@ -9,7 +9,8 @@
use crate::bindings::{
__IncompleteArrayField,
-__le16, //
+__le16,
+__le32, //
};
Quoted 12 lines
lib/rspdm/validator.rs fn validate(unvalidated: &mut KVec<u8>) -> Result<Self, Self::Err> { · line 339
Quoted 9 lines
lib/rspdm/validator.rs +9
-0+ +#[repr( C , packed)] +pub (crate )struct NegotiateAlgsRsp { + pub(crate) version: u8, + pub(crate) code: u8, + pub(crate) param1: u8, + pub(crate) param2: u8, + + pub(crate) length: u16,Why do we treat this one as native endian but the
ext_asymbelow as explicitly little endian?
Just a bug, fixed!
Alistair
Quoted 40 lines
Quoted 40 lines
+ pub(crate)
measurement_specification_sel: u8, + pub(crate)other_params_sel: u8,lib/rspdm/validator.rs +22
-0+ + 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, + 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) resp_alg_struct: __IncompleteArrayField<RegAlg>, +} + +impl <'a>Validate <Untrusted <&'a [u8 ]>>for &'aNegotiateAlgsRsp { + type Err = Error; + + fn validate(unvalidated: &[u8]) -> Result<Self, Self::Err> { + if unvalidated.len() < mem::size_of::<NegotiateAlgsRsp>(){
+ return Err(EINVAL);
+ }
+
+ let ptr =unvalidated.as_ptr();
+ // CAST:NegotiateAlgsRsponly contains integers and hasrepr(C).
+ let ptr = ptr.cast::<NegotiateAlgsRsp>();
+ // SAFETY:ptrcame from a reference and the cast above is valid.
+ let rsp: &NegotiateAlgsRsp = unsafe { &*ptr };lib/rspdm/validator.rs +4
-0+ + Ok(rsp) + } +}
PATCH v3 17/21 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 | 7 ++-- lib/rspdm/lib.rs | 4 +++ lib/rspdm/state.rs | 80 +++++++++++++++++++++++++++++++++++++++++- lib/rspdm/validator.rs | 53 ++++++++++++++++++++++++++++ 4 files changed, 141 insertions(+), 3 deletions(-)
lib/rspdm/consts.rs +5 -2
@@ -19,10 +19,11 @@pub (crate )const SPDM_VER_10 :u8 =0x10 ;pub (crate )const SPDM_VER_11 :u8 =0x11 ;pub (crate )const SPDM_VER_12 :u8 =0x12 ; -#[allow(dead_code)] pub (crate )const SPDM_VER_13 :u8 =0x13 ;pub (crate )const SPDM_VER_14 :u8 =0x14 ; +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_14 ;
@@ -106,7 +107,7 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { mem::size_of ::<GetVersionRsp >() + (u8::MAX as usize ) * mem::size_of ::<u16 >();pub (crate )const SPDM_GET_CAPABILITIES :u8 =0xe1 ; -pub (crate )const SPDM_MIN_DATA_TRANSFER_SIZE :u32 =42 ; +pub (crate )const SPDM_MIN_DATA_TRANSFER_SIZE :u32 =42 ;// SPDM 1.2.0 margin no 226 // SPDM cryptographic timeout of this implementation: // Assume calculations may take up to 1 sec on a busy machine, which equals
@@ -143,6 +144,8 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {pub (crate )const SPDM_HASH_SHA_384 :u32 =bit_u32 (1 );pub (crate )const SPDM_HASH_SHA_512 :u32 =bit_u32 (2 ); +pub (crate )const SPDM_GET_DIGESTS :u8 =0x81 ; +// If the crypto support isn't enabled don't offer the algorithms // to the responder #[cfg( CONFIG_CRYPTO_RSA )]
lib/rspdm/lib.rs +4 -0
@@ -110,6 +110,10 @@ pub extern "C" fn spdm_authenticate(state_ptr: *mut spdm_state) -> c_int {return e.to_errno ()as c_int ; } +if let Err (e) = state.get_digests () { +return e.to_errno ()as c_int ; + } + -(EPROTONOSUPPORT as i32 ) }
lib/rspdm/state.rs +79 -1
132 lines not on this page · raw message
lib/rspdm/validator.rs +53 -0
62 lines not on this page · raw message
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v3 17/21] lib: rspdm: Support SPDM get_digests
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: - [High] Buffer under-allocation for SPDM 1.3+ GET_DIGESTS responses leading to transport failure…
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Buffer under-allocation for SPDM 1.3+ GET_DIGESTS responses leading to transport failure, transcript truncation, and EIO crashes.
- [Medium] The GET_DIGESTS implementation validates the payload length but completely discards the returned certificate digests.
- [Low] An undersized/truncated GET_DIGESTS response incorrectly logs an 'Overflow' error.
--
commit 7b16689f564246befd0f0c43fa0d4515876746e6
Author: Alistair Francis <[email protected]>
lib: rspdm: Support SPDM get_digests
This commit adds support for the GET_DIGESTS SPDM command.
Quoted 4 lines
lib/rspdm/state.rs +0
-0
[ ... ]
lib/rspdm/state.rs pub(crate) fn negotiate_algs(&mut self) -> Result<(), Error> { · line 639
Quoted 11 lines
lib/rspdm/state.rs +7
-0@@ -612,4 +630,64 @@ 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;
[Severity: High]
Does this calculation fail to account for SPDM 1.3+ fields like KeyPairID, CertificateInfo, and KeyUsageMask?
For SPDM 1.3+ compliant responders, up to 32 bytes of additional fields can be returned. Because param2 is a bitmask of provisioned slots rather than a count, any device using slots >= 4 will produce a response size far larger than the allocated buffer, which could trigger an EIO crash.
Even for lower slots, this calculation omits the 1.3 fields from the transcript, which could cause a permanent signature verification failure in challenge authentication due to transcript desynchronization.
Quoted 6 lines
lib/rspdm/state.rs +6
-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 ::from_elem (0u8 , rsp_sz,GFP_KERNEL )?; +
lib/rspdm/state.rs pub(crate) fn negotiate_algs(&mut self) -> Result<(), Error> { · line 655
Quoted 10 lines
lib/rspdm/state.rs +10
-0+let len = self.spdm_exchange (request_buf, response_vec.as_mut_slice ())?as usize ; + +// The transport must report a length within the buffer we provided. +if len > response_vec.len () { +pr_err! ("Overflowed digests response\n" ); +return Err (EIO ); + } + response_vec.truncate (len); + +let response: &GetDigestsRsp =Untrusted ::new (response_vec.as_slice ()).validate ()?;
[Severity: Medium]
Does this completely discard the returned certificate digests?
The SPDM specification mandates that requesters compare the hash of the certificate chain with the digest returned in get_digests. There doesn't seem to be any storage for these digests in SpdmState, meaning this cryptographic binding between the get_digests response and the certificate chain downloaded in get_certificate might never be verified.
lib/rspdm/state.rs pub(crate) fn negotiate_algs(&mut self) -> Result<(), Error> { · line 661
Quoted 6 lines
lib/rspdm/state.rs +6
-0+ +if len + < core::mem::size_of ::<GetDigestsRsp >() + + response.param2.count_ones ()as usize * self.hash_len + { +pr_err! ("Overflowed digests response\n" );
[Severity: Low]
Should this error message indicate an underflow or truncation instead?
This condition triggers when the transport returns fewer bytes than expected for the number of provisioned slots. Logging "Overflowed" might be misleading when debugging transport truncation or packet loss.
Quoted 2 lines
lib/rspdm/state.rs +2
-0+return Err (EIO ); + }
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=17
Re: [PATCH v3 17/21] lib: rspdm: Support SPDM get_digests
Quoted 34 lines · On Tue, 01 Sep 2026 01:20:39 +0000 [email protected] wrote:
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Buffer under-allocation for SPDM 1.3+GET_DIGESTSresponses leading to transport failure, transcript truncation, and EIO crashes.
- [Medium] TheGET_DIGESTSimplementation validates the payload length but completely discards the returned certificate digests.
- [Low] An undersized/truncatedGET_DIGESTSresponse incorrectly logs an 'Overflow' error.
--commit
7b16689f564246befd0f0c43fa0d4515876746e6
Author: Alistair Francis <[email protected]>lib: rspdm: Support SPDM
get_digestsThis commit adds support for the
GET_DIGESTSSPDM command.Quoted 4 lines
lib/rspdm/state.rs +0
-0[ ... ]
lib/rspdm/state.rs pub(crate) fn negotiate_algs(&mut self) -> Result<(), Error> { · line 639
Quoted 11 lines
lib/rspdm/state.rs +7
-0@@ -612,4 +630,64 @@ 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;[Severity: High]
Does this calculation fail to account for SPDM 1.3+ fields like KeyPairID, CertificateInfo, and KeyUsageMask?
Quoted 8 lines
For SPDM 1.3+ compliant responders, up to 32 bytes of additional fields can be returned. Because param2 is a bitmask of provisioned slots rather than a count, any device using slots >= 4 will produce a response size far larger than the allocated buffer, which could trigger an EIO crash.
Even for lower slots, this calculation omits the 1.3 fields from the transcript, which could cause a permanent signature verification failure in challenge authentication due to transcript desynchronization.
Nope. Feature turned off by requester not asking for it (yet!)
Quoted 21 lines
Quoted 7 lines
lib/rspdm/state.rs +6
-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 ::from_elem (0u8 , rsp_sz,GFP_KERNEL )?; +lib/rspdm/state.rs pub(crate) fn negotiate_algs(&mut self) -> Result<(), Error> { · line 655
Quoted 10 lines
lib/rspdm/state.rs +10
-0+let len = self.spdm_exchange (request_buf, response_vec.as_mut_slice ())?as usize ; + +// The transport must report a length within the buffer we provided. +if len > response_vec.len () { +pr_err! ("Overflowed digests response\n" ); +return Err (EIO ); + } + response_vec.truncate (len); + +let response: &GetDigestsRsp =Untrusted ::new (response_vec.as_slice ()).validate ()?;[Severity: Medium]
Does this completely discard the returned certificate digests?
Quoted 5 lines
The SPDM specification mandates that requesters compare the hash of the certificate chain with the digest returned in
get_digests. There doesn't seem to be any storage for these digests in SpdmState, meaning this cryptographic binding between theget_digestsresponse and the certificate chain downloaded inget_certificatemight never be verified.
This one is curious. I can't find a reference to the spec requiring that. Digests are a short cut, I think you can always ignore them and just use the certs themselves. I couldn't find any text requiring the certs if we ignore the digests but maybe it is hiding in there somewhere.
Re: [PATCH v3 17/21] lib: rspdm: Support SPDM get_digests
Quoted 76 lines · Jonathan Cameron
Quoted 34 lines · On Tue, 01 Sep 2026 01:20:39 +0000 [email protected] wrote:
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Buffer under-allocation for SPDM 1.3+GET_DIGESTSresponses leading to transport failure, transcript truncation, and EIO crashes.
- [Medium] TheGET_DIGESTSimplementation validates the payload length but completely discards the returned certificate digests.
- [Low] An undersized/truncatedGET_DIGESTSresponse incorrectly logs an 'Overflow' error.
--commit
7b16689f564246befd0f0c43fa0d4515876746e6
Author: Alistair Francis <[email protected]>lib: rspdm: Support SPDM
get_digestsThis commit adds support for the
GET_DIGESTSSPDM command.Quoted 4 lines
lib/rspdm/state.rs +0
-0[ ... ]
lib/rspdm/state.rs pub(crate) fn negotiate_algs(&mut self) -> Result<(), Error> { · line 639
Quoted 11 lines
lib/rspdm/state.rs +7
-0@@ -612,4 +630,64 @@ 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;[Severity: High]
Does this calculation fail to account for SPDM 1.3+ fields like KeyPairID, CertificateInfo, and KeyUsageMask?Quoted 8 lines
For SPDM 1.3+ compliant responders, up to 32 bytes of additional fields can be returned. Because param2 is a bitmask of provisioned slots rather than a count, any device using slots >= 4 will produce a response size far larger than the allocated buffer, which could trigger an EIO crash.
Even for lower slots, this calculation omits the 1.3 fields from the transcript, which could cause a permanent signature verification failure in challenge authentication due to transcript desynchronization.
Nope. Feature turned off by requester not asking for it (yet!)
Quoted 21 lines
Quoted 7 lines
lib/rspdm/state.rs +6
-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 ::from_elem (0u8 , rsp_sz,GFP_KERNEL )?; +lib/rspdm/state.rs pub(crate) fn negotiate_algs(&mut self) -> Result<(), Error> { · line 655
Quoted 10 lines
lib/rspdm/state.rs +10
-0+let len = self.spdm_exchange (request_buf, response_vec.as_mut_slice ())?as usize ; + +// The transport must report a length within the buffer we provided. +if len > response_vec.len () { +pr_err! ("Overflowed digests response\n" ); +return Err (EIO ); + } + response_vec.truncate (len); + +let response: &GetDigestsRsp =Untrusted ::new (response_vec.as_slice ()).validate ()?;[Severity: Medium]
Does this completely discard the returned certificate digests?Quoted 5 lines
The SPDM specification mandates that requesters compare the hash of the certificate chain with the digest returned in
get_digests. There doesn't seem to be any storage for these digests in SpdmState, meaning this cryptographic binding between theget_digestsresponse and the certificate chain downloaded inget_certificatemight never be verified.
Quoted 4 lines
This one is curious. I can't find a reference to the spec requiring that. Digests are a short cut, I think you can always ignore them and just use the certs themselves. I couldn't find any text requiring the certs if we ignore the digests but maybe it is hiding in there somewhere.
It's not in the spec, it's just a hallucination
Alistair
Re: [PATCH v3 17/21] lib: rspdm: Support SPDM get_digests
In reply to [email protected]Quoted 5 lines · On Tue, 1 Sep 2026 11:03:43 +1000 [email protected] wrote:
From: Alistair Francis <[email protected]>
Support the
GET_DIGESTSSPDM command.Signed-off-by Alistair Francis <[email protected]>
Subject to my current standard - "as a non rust person"
PATCH v3 18/21 lib: rspdm: Support SPDM get_certificate
In reply to [email protected]From: Alistair Francis <[email protected]>
Support the GET_CERTIFICATE SPDM command.
The kernel will send a GET_CERTIFICATE request to the the responder and then iterate over all of the certificates returned.
Certificate validation happens in the next commit.
--- lib/rspdm/consts.rs | 2 + lib/rspdm/lib.rs | 15 ++++ lib/rspdm/state.rs | 156 +++++++++++++++++++++++++++++++++++++++++ lib/rspdm/validator.rs | 65 +++++++++++++++++ 4 files changed, 238 insertions(+)
lib/rspdm/consts.rs +2 -0
@@ -146,6 +146,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 ; +// If the crypto support isn't enabled don't offer the algorithms // to the responder #[cfg( CONFIG_CRYPTO_RSA )]
lib/rspdm/lib.rs +15 -0
@@ -114,6 +114,21 @@ pub extern "C" fn spdm_authenticate(state_ptr: *mut spdm_state) -> c_int {return e.to_errno ()as c_int ; } +if state.provisioned_slots ==0 { +return -(bindings::EIO 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); + } + -(EPROTONOSUPPORT as i32 ) }
lib/rspdm/state.rs +156 -0
172 lines not on this page · raw message
lib/rspdm/validator.rs +65 -0
74 lines not on this page · raw message
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v3 18/21] lib: rspdm: Support SPDM get_certificate
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: - [High] `request.length` is byte-swapped to little-endian but then immediately used as a native `usize`…
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] request.length is byte-swapped to little-endian but then immediately used as a native usize for buffer resizing, leading to incorrect sizing on big-endian architectures.
- [High] Potential integer underflow and kernel panic if self.transport_sz is less than the GetCertificateRsp header size (8 bytes).
- [Medium] Inconsistent masking of SPDM reserved bits in param1 causes spurious authentication failures.
--
commit 3d050ea39968a6dc6cb0a982af8f1746df8a12b6
Author: Alistair Francis <[email protected]>
lib: rspdm: Support SPDM get_certificate
Support the GET_CERTIFICATE SPDM command.
The kernel will send a GET_CERTIFICATE request to the the responder and then iterate over all of the certificates returned.
Quoted 4 lines
lib/rspdm/state.rs +0
-0
[ ... ]
Quoted 1 line
lib/rspdm/state.rs +0
-0@@ -690,4 +703,147 @@ pub(crate) fn get_digests(&mut self) -> Result<(), Error> {
[ ... ]
lib/rspdm/state.rs pub(crate) fn get_digests(&mut self) -> Result<(), Error> { · line 748
Quoted 11 lines
lib/rspdm/state.rs +11
-0+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 >()as u32 + u16::MAX as u32 ) + .min (self.transport_sz)as usize ; + + request.offset =0 ; + request.length = ((rsp_sz - core::mem::size_of ::<GetCertificateRsp >())as u16 ).to_le ();
[Severity: High]
Can this subtraction underflow and cause a kernel panic?
If a malicious or misconfigured device advertises a data transfer size during the capabilities exchange that is smaller than 8 bytes, self.transport_sz will be smaller than core::mem::size_of::<GetCertificateRsp>().
This would cause rsp_sz - core::mem::size_of::<GetCertificateRsp>() to underflow and panic.
lib/rspdm/state.rs pub(crate) fn get_digests(&mut self) -> Result<(), Error> { · line 757
Quoted 9 lines
lib/rspdm/state.rs +9
-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 ::from_elem (0u8 , rsp_sz,GFP_KERNEL )?; + +let response = self.get_cert_exchange (request_buf, &mut response_vec)?; + +if response.param1 != slot {
[Severity: Medium]
Will this initial slot verification erroneously reject valid responses?
Unlike the check inside the while loop below which uses (response.param1 & 0xF) != slot, this check strictly compares param1 without masking reserved bits.
If a compliant external device sets bits 4-7 in param1 during a certificate response, this will cause spurious authentication failures.
Quoted 6 lines
lib/rspdm/state.rs +6
-0+pr_err! ("Invalid slot response\n" ); +return Err (EPROTO ); + } + +let portion_length = response.portion_length; +let rem_length = response.remainder_length;
[ ... ]
lib/rspdm/state.rs pub(crate) fn get_digests(&mut self) -> Result<(), Error> { · line 781
Quoted 4 lines
lib/rspdm/state.rs +4
-0+while remainder_length >0 { + request.offset = offset.to_le (); + request.length = + ((remainder_length.min (rsp_sz - core::mem::size_of ::<GetCertificateRsp >()))as u16 )
[Severity: High]
Does this underflow the same way as the first request if rsp_sz is less than core::mem::size_of::<GetCertificateRsp>()?
lib/rspdm/state.rs pub(crate) fn get_digests(&mut self) -> Result<(), Error> { · line 788
Quoted 7 lines
lib/rspdm/state.rs +7
-0+ .to_le(); + +let request_buf = +unsafe {from_raw_parts_mut (&mut requestas *mut _ as *mut u8 , req_sz) }; + + response_vec.resize ( + request.length as usize + core::mem::size_of::<GetCertificateRsp>(),
[Severity: High]
Does this correctly resize the buffer on big-endian architectures?
Since request.length was just byte-swapped to little-endian using .to_le() above, reading it directly as a native usize on a big-endian system will produce an incorrectly large or small size.
This could lead to ENOMEM, out-of-bounds logic failures, or panics.
Quoted 3 lines
lib/rspdm/state.rs +3
-0+ 0, + GFP_KERNEL, + )?;
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=18
PATCH v3 19/21 lib: rspdm: Support SPDM certificate validation
In reply to [email protected]From: Alistair Francis <[email protected]>
Support validating the SPDM certificate chain. This only performs basic sanity checks on the chain before we continue on. This does not ensure that the root CA is trusted, we leave that for userspace to check and enforce. Instead we just make sure that the chain is correct, uses supported signatures and that it isn't blacklisted in the kernel.
We then store the first leaf certificate for use later.
--- lib/rspdm/lib.rs | 12 +++ lib/rspdm/state.rs | 146 +++++++++++++++++++++++++++++++- rust/bindings/bindings_helper.h | 2 + 3 files changed, 159 insertions(+), 1 deletion(-)
lib/rspdm/lib.rs +12 -0
@@ -129,6 +129,18 @@ pub extern "C" fn spdm_authenticate(state_ptr: *mut spdm_state) -> c_int { 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) { +pr_err! ("Certificate in slot {slot} failed to verify: {e:?}\n" ); +return e.to_errno ()as c_int ; + } + + provisioned_slots &= !(1 << slot); + } + -(EPROTONOSUPPORT as i32 ) }
lib/rspdm/state.rs +145 -1
172 lines not on this page · raw message
rust/bindings/bindings_helper.h +2 -0
@@ -38,6 +38,8 @@#include <drm/drm_gem_shmem_helper.h> #include <drm/drm_gpuvm.h> #include <drm/drm_ioctl.h> +#include <keys/asymmetric-type.h> +#include <keys/x509-parser.h> #include <kunit/test.h> #include <linux/auxiliary_bus.h> #include <linux/bitmap.h>
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v3 19/21] lib: rspdm: Support SPDM certificate validation
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] The SPDM implementation incorrectly assumes all certificate slots share the same leaf key…
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The SPDM implementation incorrectly assumes all certificate slots share the same leaf key, violating the SPDM specification and restricting support for multi-slot responders. --
commit b0152a815fb6a558a0d32bf4c0f287cb4d4ee1a3
Author: Alistair Francis <[email protected]>
lib: rspdm: Support SPDM certificate validation
Support validating the SPDM certificate chain. This performs basic sanity checks on the chain, ensures supported signatures are used, checks against the blacklist, and stores the first leaf certificate.
Quoted 4 lines
lib/rspdm/state.rs +0
-0
[ ... ]
Quoted 1 line
lib/rspdm/state.rs +0
-0@@ -846,4 +858,136 @@ pub(crate) fn get_certificate(&mut self, slot: u8) -> Result<(), Error> {
[ ... ]
Quoted 7 lines
lib/rspdm/state.rs +7
-0+if let Some (prev) = prev_cert { +if let Some (validate) = self.validate { +// SAFETY: Call the `validate` function provided. +let rc =unsafe {validate (self.dev, slot, prev) }; +if let Err (e) =to_result (rc) { +// SAFETY: `prev_cert` is the previously parsed +// certificate from a prior loop iteration.
lib/rspdm/state.rs pub(crate) fn get_certificate(&mut self, slot: u8) -> Result<(), Error> { · line 979
Quoted 10 lines
lib/rspdm/state.rs +10
-0+unsafe { bindings::x509_free_certificate (prev) }; +return Err (e); + } + } + +// The leaf key is the same for all slots, so just store the first one. +if self.leaf_key.is_none () { +// SAFETY: `prev_cert` is the previously parsed +// certificate from a prior loop iteration. + self.leaf_key =unsafe {Some ((*prev).pub_) };
[Severity: Medium]
Is it safe to assume all certificate slots share the same leaf key here?
According to the SPDM specification (DSP0274), devices are allowed up to 8 slots, and they can be provisioned with distinct certificate chains and cryptographic keys (e.g., one RSA chain and one ECDSA chain).
When an SPDM responder with multiple provisioned slots connects, this logic skips storing the leaf keys for all slots after the first one. If the responder negotiates an algorithm that directs a challenge to a slot other than the first one, won't authentication use the incorrect leaf key from the first slot and cause signature verification to fail?
Quoted 14 lines
lib/rspdm/state.rs +13
-0+// SAFETY: `prev_cert` is the previously parsed +// certificate from a prior loop iteration. We are setting +// the `pub` key to null so it isn't freed below +unsafe { (*prev).pub_ = core::ptr::null_mut () }; + } + +// SAFETY: `prev_cert` is the previously parsed +// certificate from a prior loop iteration. +unsafe { bindings::x509_free_certificate (prev) }; + } + +Ok (()) + } }
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=19
Re: [PATCH v3 19/21] lib: rspdm: Support SPDM certificate validation
In reply to [email protected]Quoted 11 lines · On Tue, 1 Sep 2026 11:03:45 +1000 [email protected] wrote:
From: Alistair Francis <[email protected]>
Support validating the SPDM certificate chain. This only performs basic sanity checks on the chain before we continue on. This does not ensure that the root CA is trusted, we leave that for userspace to check and enforce. Instead we just make sure that the chain is correct, uses supported signatures and that it isn't blacklisted in the kernel.
We then store the first leaf certificate for use later.
Signed-off-by Alistair Francis <[email protected]>
One small thing inline
Quoted 9 lines
lib/rspdm/state.rs +4
-0@@ -846,4 +858,136 @@ 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 ();
...
lib/rspdm/state.rs pub(crate) fn get_certificate(&mut self, slot: u8) -> Result<(), Error> { · line 975
Quoted 13 lines
lib/rspdm/state.rs +13
-0+if let Some (prev) = prev_cert { +if let Some (validate) = self.validate { +// SAFETY: Call the `validate` function provided. +let rc =unsafe {validate (self.dev, slot, prev) }; +if let Err (e) =to_result (rc) { +// SAFETY: `prev_cert` is the previously parsed +// certificate from a prior loop iteration. +unsafe { bindings::x509_free_certificate (prev) }; +return Err (e); + } + } + +// The leaf key is the same for all slots, so just store the first one.
I'd be tempted to call out MULT_KEY_CAP == 0 for this comment.
Quoted 18 lines
lib/rspdm/state.rs +17
-0+if self.leaf_key.is_none () { +// SAFETY: `prev_cert` is the previously parsed +// certificate from a prior loop iteration. + self.leaf_key =unsafe {Some ((*prev).pub_) }; +// SAFETY: `prev_cert` is the previously parsed +// certificate from a prior loop iteration. We are setting +// the `pub` key to null so it isn't freed below +unsafe { (*prev).pub_ = core::ptr::null_mut () }; + } + +// SAFETY: `prev_cert` is the previously parsed +// certificate from a prior loop iteration. +unsafe { bindings::x509_free_certificate (prev) }; + } + +Ok (()) + } }
PATCH v3 20/21 rust: allow extracting the buffer from a CString
In reply to [email protected]From: Alistair Francis <[email protected]>
The kernel CString is a wrapper around 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 | 6 ++++++ 1 file changed, 6 insertions(+)
rust/kernel/str.rs +6 -0
@@ -870,6 +870,12 @@ 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`] +#[inline] +pub fn into_vec (self) ->KVec <u8 > { + self.buf + } }impl Deref for CString {
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v3 20/21] rust: allow extracting the buffer from a CString
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] The `into_vec` method returns a `KVec<u8>` that includes the NUL terminator, deviating from…
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The into_vec method returns a KVec<u8> that includes the NUL terminator, deviating from standard Rust std::ffi::CString conventions and failing to indicate this in its name, creating a high risk of subtle logic errors. --
commit c0d8b032f9c0888c9ca7f0f193ec4c7fb49a4747
Author: Alistair Francis <[email protected]>
rust: allow extracting the buffer from a CString
This commit adds an into_vec() method to the kernel's CString type. It allows callers to consume the CString and extract the underlying KVec buffer.
Quoted 5 lines
rust/kernel/str.rs +0
-0
rust/kernel/str.rs pub fn try_from_fmt(args: fmt::Arguments<'_>) -> Result<Self, Error> { · line 878
Quoted 9 lines
rust/kernel/str.rs +6
-0@@ -870,6 +870,12 @@ 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`] +#[inline] +pub fn into_vec (self) ->KVec <u8 > { + self.buf + }
[Severity: Medium]
Will this return a vector that includes the NUL terminator?
In standard Rust, std::ffi::CString::into_bytes() strips the NUL byte, and there is a separate into_bytes_with_nul() method to retain it. Because this method is named into_vec() and the documentation doesn't specify how the NUL byte is handled, could callers unknowingly process an extra NUL byte if they assume standard behavior?
Could this lead to a regression with incorrect payload lengths if the array is passed to external subsystems expecting a standard string length?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=20
PATCH v3 21/21 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 | 10 +- lib/rspdm/state.rs | 239 +++++++++++++++++++++++++++++++- lib/rspdm/validator.rs | 61 ++++++++ rust/bindings/bindings_helper.h | 1 + 5 files changed, 313 insertions(+), 4 deletions(-)
lib/rspdm/consts.rs +6 -0
@@ -148,6 +148,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 ; +// If the crypto support isn't enabled don't offer the algorithms // to the responder #[cfg( CONFIG_CRYPTO_RSA )]
@@ -176,3 +178,7 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {pub (crate )const SPDM_HASH_ALGOS :u32 =SPDM_HASH_SHA2_256 |SPDM_HASH_SHA2_384_512 ;pub (crate )const SPDM_OPAQUE_DATA_FMT_GENERAL :u8 =bit_u8 (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 -3
@@ -11,8 +11,7 @@//! from other subsytems. use crate ::bindings::{ - spdm_state, -EPROTONOSUPPORT ,// + spdm_state,// };use core::ffi::{ c_int,
@@ -141,7 +140,12 @@ pub extern "C" fn spdm_authenticate(state_ptr: *mut spdm_state) -> c_int { provisioned_slots &= !(1 << slot); } - -(EPROTONOSUPPORT as i32 ) +let provisioned_slots = state.provisioned_slots.trailing_zeros (); +if let Err (e) = state.challenge (provisioned_slotsas u8 ) { +return e.to_errno ()as c_int ; + } + +0 }/// spdm_destroy() - Destroy SPDM session
lib/rspdm/state.rs +238 -1
349 lines not on this page · raw message
lib/rspdm/validator.rs +61 -0
70 lines not on this page · raw message
rust/bindings/bindings_helper.h +1 -0
@@ -31,6 +31,7 @@#include <linux/acpi.h> #include <linux/gpu_buddy.h> #include <crypto/hash.h> +#include <crypto/public_key.h> #include <drm/drm_device.h> #include <drm/drm_drv.h> #include <drm/drm_file.h>
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v3 21/21] lib: rspdm: Support SPDM challenge
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
New issues:
- [Medium] ChallengeRsp struct definition misrepresents the SPDM packet format by declaring variable-sized fields in the middle of a repr(C, packed) struct.
- [Medium] Missing upper bound check on transport response length (rc) in challenge function.
Pre-existing issues:
- [High] Stale leaf_key retention across multiple SPDM authentications causes persistent authentication failures. --
commit fe7e9708b9e2dfbb56d50564eed90c6c3454e54e
Author: Alistair Francis <[email protected]>
lib: rspdm: Support SPDM challenge
This commit adds support for the CHALLENGE SPDM command in the SPDM responder implementation.
Quoted 4 lines
lib/rspdm/state.rs +0
-0
[ ... ]
Quoted 1 line
+ fn
verify_signature(&mut self, signature: &mut [u8])-> Result<(), Error> {
[ ... ]
lib/rspdm/state.rs pub(crate) fn validate_cert_chain(&mut self, slot: u8) -> Result<(), Error> { · line 1141
Quoted 6 lines
lib/rspdm/state.rs +6
-0+if let Some (leaf_key) = self.leaf_key { +unsafe {to_result (bindings::public_key_verify_signature (leaf_key, &sig)) } + }else { +return Err (EPROTO ); + } + }
[Severity: High]
This is a pre-existing issue, but this code in verify_signature() relies on the cached leaf_key which can lead to persistent authentication failures.
If a device is re-authenticated and its certificates have changed, spdm_authenticate() will fetch the new certificates, but the existing code in validate_cert_chain() only sets leaf_key if it is missing.
Will verify_signature() then attempt to verify the new signature using the stale, previously cached leaf_key, resulting in an inevitable verification failure during the challenge sequence?
[ ... ]
Quoted 1 line
+ pub(crate) fn
challenge(&mut self, slot: u8)-> Result<(), Error> {
[ ... ]
lib/rspdm/state.rs pub(crate) fn validate_cert_chain(&mut self, slot: u8) -> Result<(), Error> { · line 1188
Quoted 8 lines
lib/rspdm/state.rs +8
-0+let rc = self.spdm_exchange (request_buf, response_vec.as_mut_slice ())?as usize ; + +// The transport must report a length within the buffer we provided. +if rc < core::mem::size_of ::<ChallengeRsp >() { +pr_err! ("Truncated challenge response\n" ); +return Err (EIO ); + } + response_vec.truncate (rc);
[Severity: Medium]
Does SpdmState::challenge() miss the upper bound check on the transport response length?
The comment notes that the transport must report a length within the buffer provided. If a buggy transport driver returns an rc greater than response_vec.len(), this check will pass and response_vec.truncate(rc) will not bound the vector correctly, allowing the function to process potentially uninitialized or out-of-bounds data later in the parsing process.
Should this also ensure that rc is less than or equal to response_vec.len()?
Quoted 4 lines
lib/rspdm/validator.rs +0
-0
[ ... ]
lib/rspdm/validator.rs fn validate(unvalidated: &[u8]) -> Result<Self, Self::Err> { · line 528
Quoted 9 lines
lib/rspdm/validator.rs +9
-0+#[repr( C , packed)] +pub (crate )struct ChallengeRsp { +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 ],
[Severity: Medium]
Does the ChallengeRsp struct definition in lib/rspdm/validator.rs misrepresent the actual wire format of the SPDM protocol by placing a variable-sized field before a fixed-size field?
Because __IncompleteArrayField<u8> acts as a zero-sized type, nonce and all subsequent fields will be assigned static offsets that do not account for the actual length of cert_chain_hash. Any future code attempting to access these fields directly from the struct would read from the wrong offsets.
Does the struct layout need to be restructured to accurately reflect the variable length nature of the SPDM challenge response?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=21
Re: [PATCH v3 21/21] lib: rspdm: Support SPDM challenge
In reply to [email protected]Quoted 5 lines · On Tue, 1 Sep 2026 11:03:47 +1000 [email protected] wrote:
From: Alistair Francis <[email protected]>
Support the CHALLENGE SPDM command.
Signed-off-by Alistair Francis <[email protected]>
Hi Alistair
I probably need to come at this with fresh eyes as I struggled to align the generation of what was being signed with the spec. Anyhow out of time for today and unfortunately I'm not sure when I'll get back to this.
Jonathan
Quoted 15 lines
lib/rspdm/state.rs +6
-0@@ -990,4 +1051,180 @@ 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 { +// No measurement summary hash requested (MSHLength == 0) +let mut length = + core::mem::size_of ::<SpdmHeader >() + self.hash_len + nonce_len + opaque_len +2 ; +
lib/rspdm/state.rs pub(crate) fn validate_cert_chain(&mut self, slot: u8) -> Result<(), Error> { · line 1069
Quoted 10 lines
lib/rspdm/state.rs +10
-0+if self.version >=SPDM_VER_13 { + length +=8 ; + } + + length + self.sig_len + } + +fn verify_signature (&mut self, signature: &mut [u8 ]) ->Result <(),Error > { +let mut sig = bindings::public_key_signature::default (); +let mut mhash:KVec <u8 > =KVec ::new ();
Can we move this down nearer where it is used?
Quoted 21 lines
lib/rspdm/state.rs +21
-0+ + sig.s = signatureas *mut _ as *mut u8 ; + sig.s_size = self.sig_lenas u32 ; + sig.encoding = self.base_asym_enc.as_ptr ()as *const u8 ; + sig.hash_algo = self.base_hash_alg_name.as_ptr ()as *const u8 ; + +let mut m:KVec <u8 > =KVec ::new (); + m.extend_with (SPDM_COMBINED_PREFIX_SZ + self.hash_len,0 ,GFP_KERNEL )?; + +if let Some (desc) = &mut self.desc { + desc.tfm = self.shash; + +unsafe { +to_result (bindings::crypto_shash_digest ( + *desc, + self.transcript.as_ptr (), + (self.transcript.len () - self.sig_len)as u32 , + m[SPDM_COMBINED_PREFIX_SZ ..].as_mut_ptr (), + ))?; + }; + }else {
lib/rspdm/state.rs pub(crate) fn validate_cert_chain(&mut self, slot: u8) -> Result<(), Error> { · line 1100
Quoted 10 lines
lib/rspdm/state.rs +10
-0+return Err (EPROTO ); + } + +if self.version <=SPDM_VER_11 { + sig.m = m[SPDM_COMBINED_PREFIX_SZ ..].as_mut_ptr (); + }else { +let major = self.version >>4 ; +let minor = self.version &0xF ; + +let prefix =CString ::try_from_fmt (fmt! ("dmtf-spdm-v{major:x}.{minor:x}.*dmtf-spdm-v{major:x}.{minor:x}.*dmtf-spdm-v{major:x}.{minor:x}.*dmtf-spdm-v{major:x}.{minor:x}.*" ))?;
Sometimes I think they added this bit just to make sure all implementations break the coding style and generally look ugly.
Quoted 16 lines
lib/rspdm/state.rs +15
-0+let mut buf = prefix.into_vec (); +let zero_pad_len =SPDM_COMBINED_PREFIX_SZ -SPDM_PREFIX_SZ -SPDM_CONTEXT .len () -1 ; + + buf.extend_with (zero_pad_len,0 ,GFP_KERNEL )?; + buf.extend_from_slice (SPDM_CONTEXT .as_bytes (),GFP_KERNEL )?; + +if buf.len () !=SPDM_COMBINED_PREFIX_SZ { +pr_err! ("combined_spdm_prefix calculation is incorrect" ); +return Err (EPROTO ); + } + + m[..SPDM_COMBINED_PREFIX_SZ ].copy_from_slice (&buf); + + mhash.extend_with (self.hash_len,0 ,GFP_KERNEL )?; +
lib/rspdm/state.rs pub(crate) fn validate_cert_chain(&mut self, slot: u8) -> Result<(), Error> { · line 1125
Quoted 10 lines
lib/rspdm/state.rs +10
-0+if let Some (desc) = &mut self.desc { + desc.tfm = self.shash; + +unsafe { +to_result (bindings::crypto_shash_digest ( + *desc, + m.as_ptr (), + m.len ()as u32 , + mhash.as_mut_ptr (), + ))?;
Could do with a few comments or spec references. It's a challenge to align the spec with the code. I've read this a couple of times and can't figure out why we have a hash of something we already hashed. I may well have forgotten a spec detail though.
Quoted 76 lines
lib/rspdm/state.rs +76
-0+ }; + }else { +return Err (EPROTO ); + } + + sig.m = mhash.as_mut_ptr (); + } + + sig.m_size = self.hash_lenas u32 ; + +if let Some (leaf_key) = self.leaf_key { +unsafe {to_result (bindings::public_key_verify_signature (leaf_key, &sig)) } + }else { +return Err (EPROTO ); + } + } + +pub (crate )fn challenge (&mut self, slot:u8 ) ->Result <(),Error > { +let mut request =ChallengeReq ::default (); + request.version = self.version; + request.param1 = slot; + +let nonce_len = request.nonce.len (); + +if self.next_nonce.len () >0 { +let request_nonce_len = request.nonce.len (); + +if self.next_nonce.len () == request_nonce_len { + request + .nonce + .copy_from_slice (&self.next_nonce[..request_nonce_len]); + }else { +return Err (EINVAL ); + } + + self.next_nonce.clear (); + }else { +unsafe { + bindings::get_random_bytes (&mut request.nonceas *mut _ as *mut c_void , nonce_len) + }; + } + +let req_sz =if self.version <=SPDM_VER_12 { +offset_of! (ChallengeReq , context) + }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 ::from_elem (0u8 , rsp_sz,GFP_KERNEL )?; + +let rc = self.spdm_exchange (request_buf, response_vec.as_mut_slice ())?as usize ; + +// The transport must report a length within the buffer we provided. +if rc < core::mem::size_of ::<ChallengeRsp >() { +pr_err! ("Truncated challenge response\n" ); +return Err (EIO ); + } + response_vec.truncate (rc); + +let _response: &ChallengeRsp =Untrusted ::new (response_vec.as_slice ()).validate ()?; + +// MSHLength is 0 as no measurement summary hash requested +let opaque_len_offset = core::mem::size_of ::<SpdmHeader >() + self.hash_len + nonce_len; + +if opaque_len_offset +2 > response_vec.len () { +return Err (EIO ); + } + +let opaque_len = u16::from_le_bytes ( + response_vec[opaque_len_offset..(opaque_len_offset +2 )] + .try_into()
lib/rspdm/state.rs pub(crate) fn validate_cert_chain(&mut self, slot: u8) -> Result<(), Error> { · line 1211
Quoted 10 lines
lib/rspdm/state.rs +10
-0+ .unwrap_or([0, 0]), + ); + +let rsp_sz = self.challenge_rsp_len (nonce_len, opaque_lenas usize ); + +if rsp_sz > response_vec.len () { +pr_err! ("Truncated challenge response\n" ); +return Err (EIO ); + } +
We should sanity check the context matches that in requester. Probably just a zero check given we aren't setting it in request.
Quoted 19 lines
lib/rspdm/state.rs +18
-0+ self.transcript + .extend_from_slice (&response_vec[..rsp_sz],GFP_KERNEL )?; + +/* Verify signature at end of transcript against leaf key */ +let sig_start = rsp_sz - self.sig_len; +let signature = &mut response_vec[sig_start..rsp_sz]; + +match self.verify_signature (signature) { +Ok (()) => { +pr_info! ("Authenticated with certificate slot {slot}\n" ); +Ok (()) + } +Err (e) => { +pr_err! ("Cannot verify challenge_auth signature: {e:?}\n" ); +Err (EPROTO ) + } + } + }}
Re: [PATCH v3 21/21] lib: rspdm: Support SPDM challenge
Quoted 9 lines · Jonathan Cameron
Quoted 5 lines · On Tue, 1 Sep 2026 11:03:47 +1000 [email protected] wrote:
From: Alistair Francis <[email protected]>
Support the CHALLENGE SPDM command.
Signed-off-by Alistair Francis <[email protected]>
Quoted 7 lines
Hi Alistair
I probably need to come at this with fresh eyes as I struggled to align
the generation of what was being signed with the spec. Anyhow out of time for today and unfortunately I'm not sure when I'll get back to this.
Thanks so much for your review!
I'm including fixes for all of your comments to my series.
I'm adding comments for the crypto as it is confusing (and some of it is outside of SPDM and part of how RSA/ECDSA signature verification works).
I'm hoping to get some Rust people to look at it over the new few weeks, so your SPDM eyes are very helpful!
Alistair
Quoted 239 lines
Jonathan
Quoted 26 lines
lib/rspdm/state.rs +0
-0@@ -990,4 +1051,180 @@ 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 {
+ // No measurement summary hash requested (MSHLength == 0) + let mut length =
+ core::mem::size_of::<SpdmHeader>() + self.hash_len +nonce_len+opaque_len+ 2;lib/rspdm/state.rs +9
-0+ + if self.version >= SPDM_VER_13 { + length += 8; + } + + length + self.sig_len + } + + fn verify_signature(&mut self, signature: &mut [u8]) ->Quoted 3 lines
Result<(), Error> {
+ let mut sig =bindings::public_key_signature::default(); + let mut mhash: KVec<u8> =KVec::new();Can we move this down nearer where it is used?
Quoted 33 lines
+
+ sig.s = signature as *mut _ as *mut u8;
+ sig.s_size = self.sig_len as u32;
+ sig.encoding =self.base_asym_enc.as_ptr()as *const u8; + sig.hash_algo =self.base_hash_alg_name.as_ptr()as *const u8;
+
+ let mut m: KVec<u8> =KVec::new();
+m.extend_with(SPDM_COMBINED_PREFIX_SZ + self.hash_len, 0, GFP_KERNEL)?;lib/rspdm/state.rs +23
-0+ + if let Some(desc) = &mut self.desc { + desc.tfm = self.shash; + + unsafe { + to_result(bindings::crypto_shash_digest( + *desc, + self.transcript.as_ptr(), + (self.transcript.len() - self.sig_len) as u32, + m[SPDM_COMBINED_PREFIX_SZ..].as_mut_ptr(), + ))?; + }; + } else { + return Err(EPROTO); + } + + if self.version <= SPDM_VER_11 { + sig.m = m[SPDM_COMBINED_PREFIX_SZ..].as_mut_ptr(); + } else { + let major = self.version >> 4; + let minor = self.version & 0xF; + + let prefix = CString::try_from_fmt(fmt!("dmtf-spdm-Quoted 2 lines
v{major:x}.{minor:x}.*dmtf-spdm-v{major:x}.{minor:x}.*dmtf-spdm- v{major:x}.{minor:x}.*dmtf-spdm-v{major:x}.{minor:x}.*"))?;
Sometimes I think they added this bit just to make sure all implementations break
the coding style and generally look ugly.Quoted 19 lines
+ let mut buf =
prefix.into_vec();
+ letzero_pad_len=SPDM_COMBINED_PREFIX_SZ-SPDM_PREFIX_SZ-SPDM_CONTEXT.len()- 1;
+
+buf.extend_with(zero_pad_len, 0, GFP_KERNEL)?; +buf.extend_from_slice(SPDM_CONTEXT.as_bytes(), GFP_KERNEL)?;
+
+ ifbuf.len()!=SPDM_COMBINED_PREFIX_SZ{ + pr_err!("combined_spdm_prefix calculation is incorrect");
+ return Err(EPROTO);
+ }lib/rspdm/state.rs +5
-0+ + m[..SPDM_COMBINED_PREFIX_SZ].copy_from_slice(&buf); + + mhash.extend_with(self.hash_len, 0, GFP_KERNEL)?; +lib/rspdm/state.rs pub(crate) fn validate_cert_chain(&mut self, slot: u8) -> Result<(), Error> { · line 1125
Quoted 10 lines
lib/rspdm/state.rs +10
-0+ if let Some(desc) = &mut self.desc { + desc.tfm = self.shash; + + unsafe { + to_result(bindings::crypto_shash_digest( + *desc, + m.as_ptr(), + m.len() as u32, + mhash.as_mut_ptr(), + ))?;Could do with a few comments or spec references. It's a challenge to align the spec with the code. I've read this a couple of times and can't figure out why we have a hash of something we already hashed. I may well have forgotten a spec detail though.
Quoted 94 lines
+ };
+ } else {
+ return Err(EPROTO);
+ }lib/rspdm/state.rs +8
-0+ + sig.m = mhash.as_mut_ptr(); + } + + sig.m_size = self.hash_len as u32; + + if let Some(leaf_key) = self.leaf_key { + unsafe {
to_result(bindings::public_key_verify_signature(leaf_key, &sig))} + } else {
+ return Err(EPROTO);
+ }
+ }
+
+ pub(crate) fnchallenge(&mut self, slot: u8)-> Result<(), Error> {
+ let mut request =ChallengeReq::default();
+ request.version = self.version;
+ request.param1 = slot;lib/rspdm/state.rs +10
-0+ + let nonce_len = request.nonce.len(); + + if self.next_nonce.len() > 0 { + let request_nonce_len = request.nonce.len(); + + if self.next_nonce.len() == request_nonce_len { + request + .nonce +.copy_from_slice(&self.next_nonce[..request_nonce_len]); + } else {
+ return Err(EINVAL);
+ }
+
+self.next_nonce.clear();
+ } else {
+ unsafe {
+bindings::get_random_bytes(&mut request.nonce as *mut _ as *mut c_void, nonce_len)
+ };
+ }lib/rspdm/state.rs +8
-0+ + let req_sz = if self.version <= SPDM_VER_12 { + offset_of!(ChallengeReq, context) + } else { + core::mem::size_of::<ChallengeReq>() + }; + + let rsp_sz = self.challenge_rsp_len(nonce_len,
SPDM_MAX_OPAQUE_DATA);
+
+ // 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, req_sz)};
+
+ let mutresponse_vec: KVec<u8> =KVec::from_elem(0u8, rsp_sz, GFP_KERNEL)?;
+
+ let rc =self.spdm_exchange(request_buf, response_vec.as_mut_slice())? as usize;
+
+ // The transport must report a length within the buffer we provided.
+ if rc < core::mem::size_of::<ChallengeRsp>() { + pr_err!("Truncated challenge response\n"); + return Err(EIO);
+ }
+response_vec.truncate(rc);
+
+ let_response: &ChallengeRsp = Untrusted::new(response_vec.as_slice()).validate()?;lib/rspdm/state.rs +11
-0+ + // MSHLength is 0 as no measurement summary hash requested + let opaque_len_offset = core::mem::size_of::<SpdmHeader>() + self.hash_len + nonce_len; + + if opaque_len_offset + 2 > response_vec.len() { + return Err(EIO); + } + + let opaque_len = u16::from_le_bytes( + response_vec[opaque_len_offset..(opaque_len_offset +2)]
+ .try_into()
+ .unwrap_or([0, 0]),
+ );
+
+ letrsp_sz=self.challenge_rsp_len(nonce_len, opaque_len as usize);lib/rspdm/state.rs pub(crate) fn validate_cert_chain(&mut self, slot: u8) -> Result<(), Error> { · line 1211
Quoted 6 lines
lib/rspdm/state.rs +6
-0+ + if rsp_sz > response_vec.len() { + pr_err!("Truncated challenge response\n"); + return Err(EIO); + } +We should sanity check the context matches that in requester. Probably just a zero check given we aren't setting it in request.
Quoted 23 lines
+ self.transcript
+ .extend_from_slice(&response_vec[..rsp_sz],GFP_KERNEL)?;
+
+ /* Verify signature at end of transcript against leaf key */
+ letsig_start=rsp_sz- self.sig_len;
+ let signature = &mut response_vec[sig_start..rsp_sz]; +
+ matchself.verify_signature(signature){
+ Ok(()) => {
+ pr_info!("Authenticated with certificate slot {slot}\n");
+ Ok(())
+ }
+ Err(e) => {
+ pr_err!("Cannot verifychallenge_authsignature:
{e:?}\n");
+ Err(EPROTO)
+ }
+ }
+ }
}
Quoted 18 lines
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=reliableno_timer_checkrcupdate.rcu_expedited=1 swiotlb=0 '
Looking at this because of discussion about RCiEPs.
For the test I'd do something more realistic and throw in a root port and put your device on that rather than as currently where I think it'll is presented as a Root Complex Integrated Endpoint.
You don't want a particularly complex setup for this but those ones in the qemu tests tend to provide syntax to copy.
I like the HMAT bios table tests
https://elixir.bootlin.com/qemu/v11.1.1/source/tests/qtest/bios-tables-test.c#L2058
And that suggests you want something like:
-device pcie-root-port,chassis=1,id=pci.1"
-device nvme,drive=mynvme,serial=deadbeef,spdm_port=2323,spdm_trans=doe,bus=pci.1"
Then you'll have a normal looking direct connected NVMe pcie express devices.
Shouldn't make any real difference in practice beyond paths changing and it smelling more normal for something you might want to attest!