* [RFC v3 01/27] rust: add untrusted data abstraction
2026-02-11 3:29 [RFC v3 00/27] lib: Rust implementation of SPDM alistair23
@ 2026-02-11 3:29 ` alistair23
2026-02-11 3:29 ` [RFC v3 02/27] X.509: Make certificate parser public alistair23
` (27 subsequent siblings)
28 siblings, 0 replies; 99+ messages in thread
From: alistair23 @ 2026-02-11 3:29 UTC (permalink / raw)
To: bhelgaas, lukas, rust-for-linux, akpm, linux-pci,
Jonathan.Cameron, linux-cxl, linux-kernel
Cc: alex.gaynor, benno.lossin, boqun.feng, a.hindborg, gary,
bjorn3_gh, tmgross, alistair23, ojeda, wilfred.mallawa, aliceryhl,
Simona Vetter
From: Benno Lossin <benno.lossin@proton.me>
When reading data from userspace, hardware or other external untrusted
sources, the data must be validated before it is used for logic within
the kernel. This abstraction provides a generic newtype wrapper
`Untrusted`; it prevents direct access to the inner type. The only way
to use the underlying data is to call `.validate()` on such a value.
Doing so utilizes the new `Validate` trait that is responsible for all
of the validation logic. This trait gives access to the inner value of
`Untrusted` by means of another newtype wrapper `Unvalidated`. In
contrast to `Untrusted`, `Unvalidated` allows direct access and
additionally provides several helper functions for slices.
Having these two different newtype wrappers is an idea from Simona
Vetter. It has several benefits: it fully prevents safe access to the
underlying value of `Untrusted` without going through the `Validate`
API. Additionally, it allows one to grep for validation logic by simply
looking for `Unvalidated<`.
Any API that reads data from an untrusted source should return
`Untrusted<T>` where `T` is the type of the underlying untrusted data.
This generic allows other abstractions to return their custom type
wrapped by `Untrusted`, signaling to the caller that the data must be
validated before use. This allows those abstractions to be used both in
a trusted and untrusted manner, increasing their generality.
Additionally, using the arbitrary self types feature, APIs can be
designed to explicitly read untrusted data:
impl MyCustomDataSource {
pub fn read(self: &Untrusted<Self>) -> &Untrusted<[u8]>;
}
Cc: Simona Vetter <simona.vetter@ffwll.ch>
Signed-off-by: Benno Lossin <benno.lossin@proton.me>
Message-ID: <20240925205244.873020-2-benno.lossin@proton.me>
---
rust/kernel/lib.rs | 1 +
rust/kernel/validate.rs | 605 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 606 insertions(+)
create mode 100644 rust/kernel/validate.rs
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index 696f62f85eb5..915499704405 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -154,6 +154,7 @@
pub mod uaccess;
#[cfg(CONFIG_USB = "y")]
pub mod usb;
+pub mod validate;
pub mod workqueue;
pub mod xarray;
diff --git a/rust/kernel/validate.rs b/rust/kernel/validate.rs
new file mode 100644
index 000000000000..ae0aa20e27b4
--- /dev/null
+++ b/rust/kernel/validate.rs
@@ -0,0 +1,605 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Types for handling and validating untrusted data.
+//!
+//! # Overview
+//!
+//! Untrusted data is marked using the [`Untrusted<T>`] type. See [Rationale](#rationale) for the
+//! reasons to mark untrusted data throughout the kernel. It is a totally opaque wrapper, it is not
+//! possible to read the data inside; but it is possible to [`Untrusted::write`] into it.
+//!
+//! The only way to "access" the data inside an [`Untrusted<T>`] is to [`Untrusted::validate`] it;
+//! turning it into a different form using the [`Validate`] trait. That trait receives the data in
+//! the form of [`Unvalidated<T>`], which in contrast to [`Untrusted<T>`], allows access to the
+//! underlying data. It additionally provides several utility functions to simplify validation.
+//!
+//! # Rationale
+//!
+//! When reading data from an untrusted source, it must be validated before it can be used for
+//! logic. For example, this is a very bad idea:
+//!
+//! ```
+//! # fn read_bytes_from_network() -> Box<[u8]> {
+//! # Box::new([1, 0], kernel::alloc::flags::GFP_KERNEL).unwrap()
+//! # }
+//! let bytes: Box<[u8]> = read_bytes_from_network();
+//! let data_index = bytes[0];
+//! let data = bytes[usize::from(data_index)];
+//! ```
+//!
+//! While this will not lead to a memory violation (because the array index checks the bounds), it
+//! might result in a kernel panic. For this reason, all untrusted data must be wrapped in
+//! [`Untrusted<T>`]. This type only allows validating the data or passing it along, since copying
+//! data from one userspace buffer into another is allowed for untrusted data.
+
+use crate::prelude::Init;
+use core::{
+ mem::MaybeUninit,
+ ops::{Index, IndexMut},
+ ptr, slice,
+};
+
+/// Untrusted data of type `T`.
+///
+/// When reading data from userspace, hardware or other external untrusted sources, the data must
+/// be validated before it is used for logic within the kernel. To do so, the [`validate()`]
+/// function exists and uses the [`Validate`] trait.
+///
+/// Also see the [module] description.
+///
+/// [`validate()`]: Self::validate
+/// [module]: self
+#[repr(transparent)]
+pub struct Untrusted<T: ?Sized>(Unvalidated<T>);
+
+impl<T: ?Sized> Untrusted<T> {
+ /// Marks the given value as untrusted.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use kernel::validate::Untrusted;
+ ///
+ /// # mod bindings { pub(crate) unsafe fn read_foo_info() -> [u8; 4] { todo!() } };
+ /// fn read_foo_info() -> Untrusted<[u8; 4]> {
+ /// // SAFETY: just an FFI call without preconditions.
+ /// Untrusted::new(unsafe { bindings::read_foo_info() })
+ /// }
+ /// ```
+ pub fn new(value: T) -> Self
+ where
+ T: Sized,
+ {
+ Self(Unvalidated::new(value))
+ }
+
+ /// Marks the value behind the reference as untrusted.
+ ///
+ /// # Examples
+ ///
+ /// In this imaginary example there exists the `foo_hardware` struct on the C side, as well as
+ /// a `foo_hardware_read` function that reads some data directly from the hardware.
+ /// ```
+ /// use kernel::{error, types::Opaque, validate::Untrusted};
+ /// use core::ptr;
+ ///
+ /// # #[allow(non_camel_case_types)]
+ /// # mod bindings {
+ /// # pub(crate) struct foo_hardware;
+ /// # pub(crate) unsafe fn foo_hardware_read(_foo: *mut foo_hardware, _len: &mut usize) -> *mut u8 {
+ /// # todo!()
+ /// # }
+ /// # }
+ /// struct Foo(Opaque<bindings::foo_hardware>);
+ ///
+ /// impl Foo {
+ /// fn read(&mut self, mut len: usize) -> Result<&Untrusted<[u8]>> {
+ /// // SAFETY: just an FFI call without preconditions.
+ /// let data: *mut u8 = unsafe { bindings::foo_hardware_read(self.0.get(), &mut len) };
+ /// let data = error::from_err_ptr(data)?;
+ /// let data = ptr::slice_from_raw_parts(data, len);
+ /// // SAFETY: `data` returned by `foo_hardware_read` is valid for reads as long as the
+ /// // `foo_hardware` object exists. That function updated the
+ /// let data = unsafe { &*data };
+ /// Ok(Untrusted::new_ref(data))
+ /// }
+ /// }
+ /// ```
+ pub fn new_ref(value: &T) -> &Self {
+ let ptr: *const T = value;
+ // CAST: `Self` and `Unvalidated` are `repr(transparent)` and contain a `T`.
+ let ptr = ptr as *const Self;
+ // SAFETY: `ptr` came from a shared reference valid for `'a`.
+ unsafe { &*ptr }
+ }
+
+ /// Marks the value behind the reference as untrusted.
+ ///
+ /// # Examples
+ ///
+ /// In this imaginary example there exists the `foo_hardware` struct on the C side, as well as
+ /// a `foo_hardware_read` function that reads some data directly from the hardware.
+ /// ```
+ /// use kernel::{error, types::Opaque, validate::Untrusted};
+ /// use core::ptr;
+ ///
+ /// # #[allow(non_camel_case_types)]
+ /// # mod bindings {
+ /// # pub(crate) struct foo_hardware;
+ /// # pub(crate) unsafe fn foo_hardware_read(_foo: *mut foo_hardware, _len: &mut usize) -> *mut u8 {
+ /// # todo!()
+ /// # }
+ /// # }
+ /// struct Foo(Opaque<bindings::foo_hardware>);
+ ///
+ /// impl Foo {
+ /// fn read(&mut self, mut len: usize) -> Result<&mut Untrusted<[u8]>> {
+ /// // SAFETY: just an FFI call without preconditions.
+ /// let data: *mut u8 = unsafe { bindings::foo_hardware_read(self.0.get(), &mut len) };
+ /// let data = error::from_err_ptr(data)?;
+ /// let data = ptr::slice_from_raw_parts_mut(data, len);
+ /// // SAFETY: `data` returned by `foo_hardware_read` is valid for reads as long as the
+ /// // `foo_hardware` object exists. That function updated the
+ /// let data = unsafe { &mut *data };
+ /// Ok(Untrusted::new_mut(data))
+ /// }
+ /// }
+ /// ```
+ pub fn new_mut(value: &mut T) -> &mut Self {
+ let ptr: *mut T = value;
+ // CAST: `Self` and `Unvalidated` are `repr(transparent)` and contain a `T`.
+ let ptr = ptr as *mut Self;
+ // SAFETY: `ptr` came from a mutable reference valid for `'a`.
+ unsafe { &mut *ptr }
+ }
+
+ /// Validates and parses the untrusted data.
+ ///
+ /// See the [`Validate`] trait on how to implement it.
+ pub fn validate<'a, V: Validate<&'a Unvalidated<T>>>(&'a self) -> Result<V, V::Err> {
+ V::validate(&self.0)
+ }
+
+ /// Validates and parses the untrusted data.
+ ///
+ /// See the [`Validate`] trait on how to implement it.
+ pub fn validate_mut<'a, V: Validate<&'a mut Unvalidated<T>>>(
+ &'a mut self,
+ ) -> Result<V, V::Err> {
+ V::validate(&mut self.0)
+ }
+
+ /// Sets the underlying untrusted value.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use kernel::validate::Untrusted;
+ ///
+ /// let mut untrusted = Untrusted::new(42);
+ /// untrusted.write(24);
+ /// ```
+ pub fn write(&mut self, value: impl Init<T>) {
+ let ptr: *mut T = &mut self.0 .0;
+ // SAFETY: `ptr` came from a mutable reference and the value is overwritten before it is
+ // read.
+ unsafe { ptr::drop_in_place(ptr) };
+ // SAFETY: `ptr` came from a mutable reference and the initializer cannot error.
+ match unsafe { value.__init(ptr) } {
+ Ok(()) => {}
+ Err(_) => unreachable!(),
+ }
+ }
+
+ /// Turns a slice of untrusted values into an untrusted slice of values.
+ pub fn transpose_slice(slice: &[Untrusted<T>]) -> &Untrusted<[T]>
+ where
+ T: Sized,
+ {
+ let ptr = slice.as_ptr().cast::<T>();
+ // SAFETY: `ptr` and `len` come from the same slice reference.
+ let slice = unsafe { slice::from_raw_parts(ptr, slice.len()) };
+ Untrusted::new_ref(slice)
+ }
+
+ /// Turns a slice of uninitialized, untrusted values into an untrusted slice of uninitialized
+ /// values.
+ pub fn transpose_slice_uninit(
+ slice: &[MaybeUninit<Untrusted<T>>],
+ ) -> &Untrusted<[MaybeUninit<T>]>
+ where
+ T: Sized,
+ {
+ let ptr = slice.as_ptr().cast::<MaybeUninit<T>>();
+ // SAFETY: `ptr` and `len` come from the same mutable slice reference.
+ let slice = unsafe { slice::from_raw_parts(ptr, slice.len()) };
+ Untrusted::new_ref(slice)
+ }
+
+ /// Turns a slice of uninitialized, untrusted values into an untrusted slice of uninitialized
+ /// values.
+ pub fn transpose_slice_uninit_mut(
+ slice: &mut [MaybeUninit<Untrusted<T>>],
+ ) -> &mut Untrusted<[MaybeUninit<T>]>
+ where
+ T: Sized,
+ {
+ // CAST: `MaybeUninit<T>` and `MaybeUninit<Untrusted<T>>` have the same layout.
+ let ptr = slice.as_mut_ptr().cast::<MaybeUninit<T>>();
+ // SAFETY: `ptr` and `len` come from the same mutable slice reference.
+ let slice = unsafe { slice::from_raw_parts_mut(ptr, slice.len()) };
+ Untrusted::new_mut(slice)
+ }
+}
+
+impl<T> Untrusted<MaybeUninit<T>> {
+ /// Sets the underlying untrusted value.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use kernel::validate::Untrusted;
+ ///
+ /// let mut untrusted = Untrusted::new(42);
+ /// untrusted.write(24);
+ /// ```
+ pub fn write_uninit<E>(&mut self, value: impl Init<T, E>) -> Result<&mut Untrusted<T>, E> {
+ let ptr: *mut MaybeUninit<T> = &mut self.0 .0;
+ // CAST: `MaybeUninit<T>` is `repr(transparent)`.
+ let ptr = ptr.cast::<T>();
+ // SAFETY: `ptr` came from a reference and if `Err` is returned, the underlying memory is
+ // considered uninitialized.
+ unsafe { value.__init(ptr) }.map(|()| {
+ let this = self.0.raw_mut();
+ // SAFETY: we initialized the memory above.
+ Untrusted::new_mut(unsafe { this.assume_init_mut() })
+ })
+ }
+}
+
+impl<T> Untrusted<[MaybeUninit<T>]> {
+ /// Sets the underlying untrusted value.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use kernel::validate::Untrusted;
+ ///
+ /// let mut untrusted = Untrusted::new(42);
+ /// untrusted.write(24);
+ /// ```
+ pub fn write_uninit_slice<E>(
+ &mut self,
+ value: impl Init<[T], E>,
+ ) -> Result<&mut Untrusted<[T]>, E> {
+ let ptr: *mut [MaybeUninit<T>] = &mut self.0 .0;
+ // CAST: `MaybeUninit<T>` is `repr(transparent)`.
+ let ptr = ptr as *mut [T];
+ // SAFETY: `ptr` came from a reference and if `Err` is returned, the underlying memory is
+ // considered uninitialized.
+ unsafe { value.__init(ptr) }.map(|()| {
+ let this = self.0.raw_mut().as_mut_ptr();
+ // CAST: `MaybeUninit<T>` is `repr(transparent)`.
+ let this = this.cast::<T>();
+ // SAFETY: `this` and `len` came from the same slice reference.
+ let this = unsafe { slice::from_raw_parts_mut(this, self.0.len()) };
+ Untrusted::new_mut(this)
+ })
+ }
+}
+
+/// Marks types that can be used as input to [`Validate::validate`].
+pub trait ValidateInput: private::Sealed + Sized {}
+
+mod private {
+ pub trait Sealed {}
+}
+
+impl<'a, T: ?Sized> private::Sealed for &'a Unvalidated<T> {}
+impl<'a, T: ?Sized> ValidateInput for &'a Unvalidated<T> {}
+
+impl<'a, T: ?Sized> private::Sealed for &'a mut Unvalidated<T> {}
+impl<'a, T: ?Sized> ValidateInput for &'a mut Unvalidated<T> {}
+
+/// Validates untrusted data.
+///
+/// # Examples
+///
+/// The simplest way to validate data is to just implement `Validate<&Unvalidated<[u8]>>` for the
+/// type that you wish to validate:
+///
+/// ```
+/// use kernel::{
+/// error::{code::EINVAL, Error},
+/// str::{CStr, CString},
+/// validate::{Unvalidated, Validate},
+/// };
+///
+/// struct Data {
+/// flags: u8,
+/// name: CString,
+/// }
+///
+/// impl Validate<&Unvalidated<[u8]>> for Data {
+/// type Err = Error;
+///
+/// fn validate(unvalidated: &Unvalidated<[u8]>) -> Result<Self, Self::Err> {
+/// let raw = unvalidated.raw();
+/// let (&flags, name) = raw.split_first().ok_or(EINVAL)?;
+/// let name = CStr::from_bytes_with_nul(name)?.to_cstring()?;
+/// Ok(Data { flags, name })
+/// }
+/// }
+/// ```
+///
+/// This approach copies the data and requires allocation. If you want to avoid the allocation and
+/// copying the data, you can borrow from the input like this:
+///
+/// ```
+/// use kernel::{
+/// error::{code::EINVAL, Error},
+/// str::CStr,
+/// validate::{Unvalidated, Validate},
+/// };
+///
+/// struct Data<'a> {
+/// flags: u8,
+/// name: &'a CStr,
+/// }
+///
+/// impl<'a> Validate<&'a Unvalidated<[u8]>> for Data<'a> {
+/// type Err = Error;
+///
+/// fn validate(unvalidated: &'a Unvalidated<[u8]>) -> Result<Self, Self::Err> {
+/// let raw = unvalidated.raw();
+/// let (&flags, name) = raw.split_first().ok_or(EINVAL)?;
+/// let name = CStr::from_bytes_with_nul(name)?;
+/// Ok(Data { flags, name })
+/// }
+/// }
+/// ```
+///
+/// If you need to in-place validate your data, you currently need to resort to `unsafe`:
+///
+/// ```
+/// use kernel::{
+/// error::{code::EINVAL, Error},
+/// str::CStr,
+/// validate::{Unvalidated, Validate},
+/// };
+/// use core::mem;
+///
+/// // Important: use `repr(C)`, this ensures a linear layout of this type.
+/// #[repr(C)]
+/// struct Data {
+/// version: u8,
+/// flags: u8,
+/// _reserved: [u8; 2],
+/// count: u64,
+/// // lots of other fields...
+/// }
+///
+/// impl Validate<&Unvalidated<[u8]>> for &Data {
+/// type Err = Error;
+///
+/// fn validate(unvalidated: &Unvalidated<[u8]>) -> Result<Self, Self::Err> {
+/// let raw = unvalidated.raw();
+/// if raw.len() < mem::size_of::<Data>() {
+/// return Err(EINVAL);
+/// }
+/// // can only handle version 0
+/// if raw[0] != 0 {
+/// return Err(EINVAL);
+/// }
+/// // version 0 only uses the lower 4 bits of flags
+/// if raw[1] & 0xf0 != 0 {
+/// return Err(EINVAL);
+/// }
+/// let ptr = raw.as_ptr();
+/// // CAST: `Data` only contains integers and has `repr(C)`.
+/// let ptr = ptr.cast::<Data>();
+/// // SAFETY: `ptr` came from a reference and the cast above is valid.
+/// Ok(unsafe { &*ptr })
+/// }
+/// }
+/// ```
+///
+/// To be able to modify the parsed data, while still supporting zero-copy, you can implement
+/// `Validate<&mut Unvalidated<[u8]>>`:
+///
+/// ```
+/// use kernel::{
+/// error::{code::EINVAL, Error},
+/// str::CStr,
+/// validate::{Unvalidated, Validate},
+/// };
+/// use core::mem;
+///
+/// // Important: use `repr(C)`, this ensures a linear layout of this type.
+/// #[repr(C)]
+/// struct Data {
+/// version: u8,
+/// flags: u8,
+/// _reserved: [u8; 2],
+/// count: u64,
+/// // lots of other fields...
+/// }
+///
+/// impl Validate<&mut Unvalidated<[u8]>> for &Data {
+/// type Err = Error;
+///
+/// fn validate(unvalidated: &mut Unvalidated<[u8]>) -> Result<Self, Self::Err> {
+/// let raw = unvalidated.raw_mut();
+/// if raw.len() < mem::size_of::<Data>() {
+/// return Err(EINVAL);
+/// }
+/// match raw[0] {
+/// 0 => {},
+/// 1 => {
+/// // version 1 implicitly sets the first bit.
+/// raw[1] |= 1;
+/// },
+/// // can only handle version 0 and 1
+/// _ => return Err(EINVAL),
+/// }
+/// // version 0 and 1 only use the lower 4 bits of flags
+/// if raw[1] & 0xf0 != 0 {
+/// return Err(EINVAL);
+/// }
+/// if raw[1] == 0 {}
+/// let ptr = raw.as_ptr();
+/// // CAST: `Data` only contains integers and has `repr(C)`.
+/// let ptr = ptr.cast::<Data>();
+/// // SAFETY: `ptr` came from a reference and the cast above is valid.
+/// Ok(unsafe { &*ptr })
+/// }
+/// }
+/// ```
+pub trait Validate<I: ValidateInput>: Sized {
+ /// Validation error.
+ type Err;
+
+ /// Validate the given untrusted data and parse it into the output type.
+ fn validate(unvalidated: I) -> Result<Self, Self::Err>;
+}
+
+/// Unvalidated data of type `T`.
+#[repr(transparent)]
+pub struct Unvalidated<T: ?Sized>(T);
+
+impl<T: ?Sized> Unvalidated<T> {
+ fn new(value: T) -> Self
+ where
+ T: Sized,
+ {
+ Self(value)
+ }
+
+ fn new_ref(value: &T) -> &Self {
+ let ptr: *const T = value;
+ // CAST: `Self` is `repr(transparent)` and contains a `T`.
+ let ptr = ptr as *const Self;
+ // SAFETY: `ptr` came from a mutable reference valid for `'a`.
+ unsafe { &*ptr }
+ }
+
+ fn new_mut(value: &mut T) -> &mut Self {
+ let ptr: *mut T = value;
+ // CAST: `Self` is `repr(transparent)` and contains a `T`.
+ let ptr = ptr as *mut Self;
+ // SAFETY: `ptr` came from a mutable reference valid for `'a`.
+ unsafe { &mut *ptr }
+ }
+
+ /// Validates and parses the untrusted data.
+ ///
+ /// See the [`Validate`] trait on how to implement it.
+ pub fn validate_ref<'a, V: Validate<&'a Unvalidated<T>>>(&'a self) -> Result<V, V::Err> {
+ V::validate(self)
+ }
+
+ /// Validates and parses the untrusted data.
+ ///
+ /// See the [`Validate`] trait on how to implement it.
+ pub fn validate_mut<'a, V: Validate<&'a mut Unvalidated<T>>>(
+ &'a mut self,
+ ) -> Result<V, V::Err> {
+ V::validate(self)
+ }
+
+ /// Gives immutable access to the underlying value.
+ pub fn raw(&self) -> &T {
+ &self.0
+ }
+
+ /// Gives mutable access to the underlying value.
+ pub fn raw_mut(&mut self) -> &mut T {
+ &mut self.0
+ }
+}
+
+impl<T, I> Index<I> for Unvalidated<[T]>
+where
+ I: slice::SliceIndex<[T]>,
+{
+ type Output = Unvalidated<I::Output>;
+
+ fn index(&self, index: I) -> &Self::Output {
+ Unvalidated::new_ref(self.0.index(index))
+ }
+}
+
+impl<T, I> IndexMut<I> for Unvalidated<[T]>
+where
+ I: slice::SliceIndex<[T]>,
+{
+ fn index_mut(&mut self, index: I) -> &mut Self::Output {
+ Unvalidated::new_mut(self.0.index_mut(index))
+ }
+}
+
+/// Immutable unvalidated slice iterator.
+pub struct Iter<'a, T>(slice::Iter<'a, T>);
+
+/// Mutable unvalidated slice iterator.
+pub struct IterMut<'a, T>(slice::IterMut<'a, T>);
+
+impl<'a, T> Iterator for Iter<'a, T> {
+ type Item = &'a Unvalidated<T>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ self.0.next().map(Unvalidated::new_ref)
+ }
+}
+
+impl<'a, T> IntoIterator for &'a Unvalidated<[T]> {
+ type Item = &'a Unvalidated<T>;
+ type IntoIter = Iter<'a, T>;
+
+ fn into_iter(self) -> Self::IntoIter {
+ Iter(self.0.iter())
+ }
+}
+
+impl<'a, T> Iterator for IterMut<'a, T> {
+ type Item = &'a mut Unvalidated<T>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ self.0.next().map(Unvalidated::new_mut)
+ }
+}
+
+impl<'a, T> IntoIterator for &'a mut Unvalidated<[T]> {
+ type Item = &'a mut Unvalidated<T>;
+ type IntoIter = IterMut<'a, T>;
+
+ fn into_iter(self) -> Self::IntoIter {
+ IterMut(self.0.iter_mut())
+ }
+}
+
+impl<T> Unvalidated<[T]> {
+ /// Returns the number of elements in the underlying slice.
+ pub fn len(&self) -> usize {
+ self.0.len()
+ }
+
+ /// Returns true if the underlying slice has a length of 0.
+ pub fn is_empty(&self) -> bool {
+ self.0.is_empty()
+ }
+
+ /// Iterates over all items and validates each of them individually.
+ pub fn validate_iter<'a, V: Validate<&'a Unvalidated<T>>>(
+ &'a self,
+ ) -> impl Iterator<Item = Result<V, V::Err>> + 'a {
+ self.into_iter().map(|item| V::validate(item))
+ }
+
+ /// Iterates over all items and validates each of them individually.
+ pub fn validate_iter_mut<'a, V: Validate<&'a mut Unvalidated<T>>>(
+ &'a mut self,
+ ) -> impl Iterator<Item = Result<V, V::Err>> + 'a {
+ self.into_iter().map(|item| V::validate(item))
+ }
+}
--
2.52.0
^ permalink raw reply related [flat|nested] 99+ messages in thread* [RFC v3 02/27] X.509: Make certificate parser public
2026-02-11 3:29 [RFC v3 00/27] lib: Rust implementation of SPDM alistair23
2026-02-11 3:29 ` [RFC v3 01/27] rust: add untrusted data abstraction alistair23
@ 2026-02-11 3:29 ` alistair23
2026-02-11 3:29 ` [RFC v3 03/27] X.509: Parse Subject Alternative Name in certificates alistair23
` (26 subsequent siblings)
28 siblings, 0 replies; 99+ messages in thread
From: alistair23 @ 2026-02-11 3:29 UTC (permalink / raw)
To: bhelgaas, lukas, rust-for-linux, akpm, linux-pci,
Jonathan.Cameron, linux-cxl, linux-kernel
Cc: alex.gaynor, benno.lossin, boqun.feng, a.hindborg, gary,
bjorn3_gh, tmgross, alistair23, ojeda, wilfred.mallawa, aliceryhl,
Dan Williams, Alistair Francis, Ilpo Järvinen
From: Lukas Wunner <lukas@wunner.de>
The upcoming support for PCI device authentication with CMA-SPDM
(PCIe r6.1 sec 6.31) requires validating the Subject Alternative Name
in X.509 certificates.
High-level functions for X.509 parsing such as key_create_or_update()
throw away the internal, low-level struct x509_certificate after
extracting the struct public_key and public_key_signature from it.
The Subject Alternative Name is thus inaccessible when using those
functions.
Afford CMA-SPDM access to the Subject Alternative Name by making struct
x509_certificate public, together with the functions for parsing an
X.509 certificate into such a struct and freeing such a struct.
The private header file x509_parser.h previously included <linux/time.h>
for the definition of time64_t. That definition was since moved to
<linux/time64.h> by commit 361a3bf00582 ("time64: Add time64.h header
and define struct timespec64"), so adjust the #include directive as part
of the move to the new public header file <keys/x509-parser.h>.
No functional change intended.
Signed-off-by: Lukas Wunner <lukas@wunner.de>
Reviewed-by: Dan Williams <dan.j.williams@intel.com>
Reviewed-by: Alistair Francis <alistair.francis@wdc.com>
Reviewed-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
---
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
diff --git a/crypto/asymmetric_keys/x509_parser.h b/crypto/asymmetric_keys/x509_parser.h
index b7aeebdddb36..39f1521b773d 100644
--- a/crypto/asymmetric_keys/x509_parser.h
+++ b/crypto/asymmetric_keys/x509_parser.h
@@ -5,51 +5,11 @@
* Written by David Howells (dhowells@redhat.com)
*/
-#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);
diff --git a/include/keys/x509-parser.h b/include/keys/x509-parser.h
new file mode 100644
index 000000000000..8b68e720693a
--- /dev/null
+++ b/include/keys/x509-parser.h
@@ -0,0 +1,55 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* X.509 certificate parser
+ *
+ * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ */
+
+#ifndef _KEYS_X509_PARSER_H
+#define _KEYS_X509_PARSER_H
+
+#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;
+};
+
+struct x509_certificate *x509_cert_parse(const void *data, size_t datalen);
+void x509_free_certificate(struct x509_certificate *cert);
+
+DEFINE_FREE(x509_free_certificate, struct x509_certificate *,
+ if (!IS_ERR(_T)) x509_free_certificate(_T))
+
+#endif /* _KEYS_X509_PARSER_H */
--
2.52.0
^ permalink raw reply related [flat|nested] 99+ messages in thread* [RFC v3 03/27] X.509: Parse Subject Alternative Name in certificates
2026-02-11 3:29 [RFC v3 00/27] lib: Rust implementation of SPDM alistair23
2026-02-11 3:29 ` [RFC v3 01/27] rust: add untrusted data abstraction alistair23
2026-02-11 3:29 ` [RFC v3 02/27] X.509: Make certificate parser public alistair23
@ 2026-02-11 3:29 ` alistair23
2026-02-11 3:29 ` [RFC v3 04/27] X.509: Move certificate length retrieval into new helper alistair23
` (25 subsequent siblings)
28 siblings, 0 replies; 99+ messages in thread
From: alistair23 @ 2026-02-11 3:29 UTC (permalink / raw)
To: bhelgaas, lukas, rust-for-linux, akpm, linux-pci,
Jonathan.Cameron, linux-cxl, linux-kernel
Cc: alex.gaynor, benno.lossin, boqun.feng, a.hindborg, gary,
bjorn3_gh, tmgross, alistair23, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis, Ilpo Järvinen, Dan Williams
From: Lukas Wunner <lukas@wunner.de>
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 <lukas@wunner.de>
Reviewed-by: Wilfred Mallawa <wilfred.mallawa@wdc.com>
Reviewed-by: Alistair Francis <alistair.francis@wdc.com>
Reviewed-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
Acked-by: Dan Williams <dan.j.williams@intel.com>
---
crypto/asymmetric_keys/x509_cert_parser.c | 9 +++++++++
include/keys/x509-parser.h | 2 ++
2 files changed, 11 insertions(+)
diff --git a/crypto/asymmetric_keys/x509_cert_parser.c b/crypto/asymmetric_keys/x509_cert_parser.c
index 2fe094f5caf3..363acd87dba1 100644
--- a/crypto/asymmetric_keys/x509_cert_parser.c
+++ b/crypto/asymmetric_keys/x509_cert_parser.c
@@ -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
diff --git a/include/keys/x509-parser.h b/include/keys/x509-parser.h
index 8b68e720693a..4e6a05a8c7a6 100644
--- a/include/keys/x509-parser.h
+++ b/include/keys/x509-parser.h
@@ -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;
--
2.52.0
^ permalink raw reply related [flat|nested] 99+ messages in thread* [RFC v3 04/27] X.509: Move certificate length retrieval into new helper
2026-02-11 3:29 [RFC v3 00/27] lib: Rust implementation of SPDM alistair23
` (2 preceding siblings ...)
2026-02-11 3:29 ` [RFC v3 03/27] X.509: Parse Subject Alternative Name in certificates alistair23
@ 2026-02-11 3:29 ` alistair23
2026-02-11 3:29 ` [RFC v3 05/27] certs: Create blacklist keyring earlier alistair23
` (24 subsequent siblings)
28 siblings, 0 replies; 99+ messages in thread
From: alistair23 @ 2026-02-11 3:29 UTC (permalink / raw)
To: bhelgaas, lukas, rust-for-linux, akpm, linux-pci,
Jonathan.Cameron, linux-cxl, linux-kernel
Cc: alex.gaynor, benno.lossin, boqun.feng, a.hindborg, gary,
bjorn3_gh, tmgross, alistair23, ojeda, wilfred.mallawa, aliceryhl,
Dan Williams, Alistair Francis
From: Lukas Wunner <lukas@wunner.de>
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.
Signed-off-by: Lukas Wunner <lukas@wunner.de>
Reviewed-by: Dan Williams <dan.j.williams@intel.com>
Reviewed-by: Alistair Francis <alistair.francis@wdc.com>
Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
---
crypto/asymmetric_keys/x509_loader.c | 38 +++++++++++++++++++---------
include/keys/asymmetric-type.h | 2 ++
2 files changed, 28 insertions(+), 12 deletions(-)
diff --git a/crypto/asymmetric_keys/x509_loader.c b/crypto/asymmetric_keys/x509_loader.c
index a41741326998..25ff027fad1d 100644
--- a/crypto/asymmetric_keys/x509_loader.c
+++ b/crypto/asymmetric_keys/x509_loader.c
@@ -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),
diff --git a/include/keys/asymmetric-type.h b/include/keys/asymmetric-type.h
index 1b91c8f98688..301efa952e26 100644
--- a/include/keys/asymmetric-type.h
+++ b/include/keys/asymmetric-type.h
@@ -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);
--
2.52.0
^ permalink raw reply related [flat|nested] 99+ messages in thread* [RFC v3 05/27] certs: Create blacklist keyring earlier
2026-02-11 3:29 [RFC v3 00/27] lib: Rust implementation of SPDM alistair23
` (3 preceding siblings ...)
2026-02-11 3:29 ` [RFC v3 04/27] X.509: Move certificate length retrieval into new helper alistair23
@ 2026-02-11 3:29 ` alistair23
2026-02-11 3:29 ` [RFC v3 06/27] rust: add bindings for hash.h alistair23
` (23 subsequent siblings)
28 siblings, 0 replies; 99+ messages in thread
From: alistair23 @ 2026-02-11 3:29 UTC (permalink / raw)
To: bhelgaas, lukas, rust-for-linux, akpm, linux-pci,
Jonathan.Cameron, linux-cxl, linux-kernel
Cc: alex.gaynor, benno.lossin, boqun.feng, a.hindborg, gary,
bjorn3_gh, tmgross, alistair23, ojeda, wilfred.mallawa, aliceryhl,
Dan Williams, Alistair Francis, Ilpo Järvinen
From: Lukas Wunner <lukas@wunner.de>
The upcoming support for PCI device authentication with CMA-SPDM
(PCIe r6.2 sec 6.31) requires parsing X.509 certificates upon
device enumeration, which happens in a subsys_initcall().
Parsing X.509 certificates accesses the blacklist keyring:
x509_cert_parse()
x509_get_sig_params()
is_hash_blacklisted()
keyring_search()
So far the keyring is created much later in a device_initcall(). Avoid
a NULL pointer dereference on access to the keyring by creating it one
initcall level earlier than PCI device enumeration, i.e. in an
arch_initcall().
Signed-off-by: Lukas Wunner <lukas@wunner.de>
Reviewed-by: Dan Williams <dan.j.williams@intel.com>
Reviewed-by: Wilfred Mallawa <wilfred.mallawa@wdc.com>
Reviewed-by: Alistair Francis <alistair.francis@wdc.com>
Reviewed-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
---
certs/blacklist.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/certs/blacklist.c b/certs/blacklist.c
index 675dd7a8f07a..34185415d451 100644
--- a/certs/blacklist.c
+++ b/certs/blacklist.c
@@ -311,7 +311,7 @@ static int restrict_link_for_blacklist(struct key *dest_keyring,
* Initialise the blacklist
*
* The blacklist_init() function is registered as an initcall via
- * device_initcall(). As a result if the blacklist_init() function fails for
+ * arch_initcall(). As a result if the blacklist_init() function fails for
* any reason the kernel continues to execute. While cleanly returning -ENODEV
* could be acceptable for some non-critical kernel parts, if the blacklist
* keyring fails to load it defeats the certificate/key based deny list for
@@ -356,7 +356,7 @@ static int __init blacklist_init(void)
/*
* Must be initialised before we try and load the keys into the keyring.
*/
-device_initcall(blacklist_init);
+arch_initcall(blacklist_init);
#ifdef CONFIG_SYSTEM_REVOCATION_LIST
/*
--
2.52.0
^ permalink raw reply related [flat|nested] 99+ messages in thread* [RFC v3 06/27] rust: add bindings for hash.h
2026-02-11 3:29 [RFC v3 00/27] lib: Rust implementation of SPDM alistair23
` (4 preceding siblings ...)
2026-02-11 3:29 ` [RFC v3 05/27] certs: Create blacklist keyring earlier alistair23
@ 2026-02-11 3:29 ` alistair23
2026-02-19 14:48 ` Gary Guo
2026-03-02 16:18 ` Jonathan Cameron
2026-02-11 3:29 ` [RFC v3 07/27] rust: error: impl From<FromBytesWithNulError> for Kernel Error alistair23
` (22 subsequent siblings)
28 siblings, 2 replies; 99+ messages in thread
From: alistair23 @ 2026-02-11 3:29 UTC (permalink / raw)
To: bhelgaas, lukas, rust-for-linux, akpm, linux-pci,
Jonathan.Cameron, linux-cxl, linux-kernel
Cc: alex.gaynor, benno.lossin, boqun.feng, a.hindborg, gary,
bjorn3_gh, tmgross, alistair23, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis
From: Alistair Francis <alistair.francis@wdc.com>
Make the functions crypto_shash_descsize(), crypto_shash_digestsize()
and crypto_free_shash() available to Rust.
Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
---
rust/bindings/bindings_helper.h | 2 ++
rust/helpers/hash.c | 18 ++++++++++++++++++
rust/helpers/helpers.c | 1 +
3 files changed, 21 insertions(+)
create mode 100644 rust/helpers/hash.c
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index a067038b4b42..0075c4b62c29 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -34,6 +34,7 @@
#include <drm/drm_file.h>
#include <drm/drm_gem.h>
#include <drm/drm_ioctl.h>
+#include <crypto/hash.h>
#include <kunit/test.h>
#include <linux/auxiliary_bus.h>
#include <linux/bitmap.h>
@@ -60,6 +61,7 @@
#include <linux/fs.h>
#include <linux/i2c.h>
#include <linux/ioport.h>
+#include <linux/hash.h>
#include <linux/jiffies.h>
#include <linux/jump_label.h>
#include <linux/mdio.h>
diff --git a/rust/helpers/hash.c b/rust/helpers/hash.c
new file mode 100644
index 000000000000..8ddb84668841
--- /dev/null
+++ b/rust/helpers/hash.c
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <crypto/hash.h>
+
+unsigned int rust_helper_crypto_shash_descsize(struct crypto_shash *tfm)
+{
+ return crypto_shash_descsize(tfm);
+}
+
+unsigned int rust_helper_crypto_shash_digestsize(struct crypto_shash *tfm)
+{
+ return crypto_shash_digestsize(tfm);
+}
+
+void rust_helper_crypto_free_shash(struct crypto_shash *tfm)
+{
+ crypto_free_shash(tfm);
+}
diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
index a3c42e51f00a..4b08b4f1d3a3 100644
--- a/rust/helpers/helpers.c
+++ b/rust/helpers/helpers.c
@@ -30,6 +30,7 @@
#include "dma.c"
#include "drm.c"
#include "err.c"
+#include "hash.c"
#include "irq.c"
#include "fs.c"
#include "io.c"
--
2.52.0
^ permalink raw reply related [flat|nested] 99+ messages in thread* Re: [RFC v3 06/27] rust: add bindings for hash.h
2026-02-11 3:29 ` [RFC v3 06/27] rust: add bindings for hash.h alistair23
@ 2026-02-19 14:48 ` Gary Guo
2026-03-02 16:18 ` Jonathan Cameron
1 sibling, 0 replies; 99+ messages in thread
From: Gary Guo @ 2026-02-19 14:48 UTC (permalink / raw)
To: alistair23
Cc: bhelgaas, lukas, rust-for-linux, akpm, linux-pci,
Jonathan.Cameron, linux-cxl, linux-kernel, alex.gaynor,
benno.lossin, boqun.feng, a.hindborg, bjorn3_gh, tmgross, ojeda,
wilfred.mallawa, aliceryhl, Alistair Francis
On 2026-02-11 03:29, alistair23@gmail.com wrote:
> From: Alistair Francis <alistair.francis@wdc.com>
>
> Make the functions crypto_shash_descsize(), crypto_shash_digestsize()
> and crypto_free_shash() available to Rust.
>
> Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
> ---
> rust/bindings/bindings_helper.h | 2 ++
> rust/helpers/hash.c | 18 ++++++++++++++++++
> rust/helpers/helpers.c | 1 +
> 3 files changed, 21 insertions(+)
> create mode 100644 rust/helpers/hash.c
>
> diff --git a/rust/bindings/bindings_helper.h
> b/rust/bindings/bindings_helper.h
> index a067038b4b42..0075c4b62c29 100644
> --- a/rust/bindings/bindings_helper.h
> +++ b/rust/bindings/bindings_helper.h
> @@ -34,6 +34,7 @@
> #include <drm/drm_file.h>
> #include <drm/drm_gem.h>
> #include <drm/drm_ioctl.h>
> +#include <crypto/hash.h>
> #include <kunit/test.h>
> #include <linux/auxiliary_bus.h>
> #include <linux/bitmap.h>
> @@ -60,6 +61,7 @@
> #include <linux/fs.h>
> #include <linux/i2c.h>
> #include <linux/ioport.h>
> +#include <linux/hash.h>
> #include <linux/jiffies.h>
> #include <linux/jump_label.h>
> #include <linux/mdio.h>
> diff --git a/rust/helpers/hash.c b/rust/helpers/hash.c
> new file mode 100644
> index 000000000000..8ddb84668841
> --- /dev/null
> +++ b/rust/helpers/hash.c
> @@ -0,0 +1,18 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +#include <crypto/hash.h>
> +
> +unsigned int rust_helper_crypto_shash_descsize(struct crypto_shash
> *tfm)
Please prefix all helpers with __rust_helper.
Same for patch 26.
Best,
Gary
> +{
> + return crypto_shash_descsize(tfm);
> +}
> +
> +unsigned int rust_helper_crypto_shash_digestsize(struct crypto_shash
> *tfm)
> +{
> + return crypto_shash_digestsize(tfm);
> +}
> +
> +void rust_helper_crypto_free_shash(struct crypto_shash *tfm)
> +{
> + crypto_free_shash(tfm);
> +}
> diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
> index a3c42e51f00a..4b08b4f1d3a3 100644
> --- a/rust/helpers/helpers.c
> +++ b/rust/helpers/helpers.c
> @@ -30,6 +30,7 @@
> #include "dma.c"
> #include "drm.c"
> #include "err.c"
> +#include "hash.c"
> #include "irq.c"
> #include "fs.c"
> #include "io.c"
^ permalink raw reply [flat|nested] 99+ messages in thread* Re: [RFC v3 06/27] rust: add bindings for hash.h
2026-02-11 3:29 ` [RFC v3 06/27] rust: add bindings for hash.h alistair23
2026-02-19 14:48 ` Gary Guo
@ 2026-03-02 16:18 ` Jonathan Cameron
1 sibling, 0 replies; 99+ messages in thread
From: Jonathan Cameron @ 2026-03-02 16:18 UTC (permalink / raw)
To: alistair23
Cc: bhelgaas, lukas, rust-for-linux, akpm, linux-pci, linux-cxl,
linux-kernel, alex.gaynor, benno.lossin, boqun.feng, a.hindborg,
gary, bjorn3_gh, tmgross, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis
On Wed, 11 Feb 2026 13:29:13 +1000
alistair23@gmail.com wrote:
> From: Alistair Francis <alistair.francis@wdc.com>
>
> Make the functions crypto_shash_descsize(), crypto_shash_digestsize()
> and crypto_free_shash() available to Rust.
>
> Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
> ---
> rust/bindings/bindings_helper.h | 2 ++
> rust/helpers/hash.c | 18 ++++++++++++++++++
> rust/helpers/helpers.c | 1 +
> 3 files changed, 21 insertions(+)
> create mode 100644 rust/helpers/hash.c
>
> diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
> index a067038b4b42..0075c4b62c29 100644
> --- a/rust/bindings/bindings_helper.h
> +++ b/rust/bindings/bindings_helper.h
> @@ -34,6 +34,7 @@
> #include <drm/drm_file.h>
> #include <drm/drm_gem.h>
> #include <drm/drm_ioctl.h>
> +#include <crypto/hash.h>
I'm not sure on ordering conventions but this feels odd..
Maybe before the drm inclues?
> #include <kunit/test.h>
> #include <linux/auxiliary_bus.h>
> #include <linux/bitmap.h>
> @@ -60,6 +61,7 @@
^ permalink raw reply [flat|nested] 99+ messages in thread
* [RFC v3 07/27] rust: error: impl From<FromBytesWithNulError> for Kernel Error
2026-02-11 3:29 [RFC v3 00/27] lib: Rust implementation of SPDM alistair23
` (5 preceding siblings ...)
2026-02-11 3:29 ` [RFC v3 06/27] rust: add bindings for hash.h alistair23
@ 2026-02-11 3:29 ` alistair23
2026-02-19 14:49 ` Gary Guo
2026-02-11 3:29 ` [RFC v3 08/27] lib: rspdm: Initial commit of Rust SPDM alistair23
` (21 subsequent siblings)
28 siblings, 1 reply; 99+ messages in thread
From: alistair23 @ 2026-02-11 3:29 UTC (permalink / raw)
To: bhelgaas, lukas, rust-for-linux, akpm, linux-pci,
Jonathan.Cameron, linux-cxl, linux-kernel
Cc: alex.gaynor, benno.lossin, boqun.feng, a.hindborg, gary,
bjorn3_gh, tmgross, alistair23, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis
From: Alistair Francis <alistair.francis@wdc.com>
Implement From<FromBytesWithNulError> for the Kernel Error type
Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
---
rust/kernel/error.rs | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/rust/kernel/error.rs b/rust/kernel/error.rs
index 258b12afdcba..569d9d032ab3 100644
--- a/rust/kernel/error.rs
+++ b/rust/kernel/error.rs
@@ -12,6 +12,7 @@
str::CStr,
};
+use core::ffi::FromBytesWithNulError;
use core::num::NonZeroI32;
use core::num::TryFromIntError;
use core::str::Utf8Error;
@@ -251,6 +252,12 @@ fn from(e: core::convert::Infallible) -> Error {
}
}
+impl From<FromBytesWithNulError> for Error {
+ fn from(_: FromBytesWithNulError) -> Error {
+ code::EINVAL
+ }
+}
+
/// A [`Result`] with an [`Error`] error type.
///
/// To be used as the return type for functions that may fail.
--
2.52.0
^ permalink raw reply related [flat|nested] 99+ messages in thread* Re: [RFC v3 07/27] rust: error: impl From<FromBytesWithNulError> for Kernel Error
2026-02-11 3:29 ` [RFC v3 07/27] rust: error: impl From<FromBytesWithNulError> for Kernel Error alistair23
@ 2026-02-19 14:49 ` Gary Guo
2026-03-13 2:20 ` Alistair Francis
0 siblings, 1 reply; 99+ messages in thread
From: Gary Guo @ 2026-02-19 14:49 UTC (permalink / raw)
To: alistair23
Cc: bhelgaas, lukas, rust-for-linux, akpm, linux-pci,
Jonathan.Cameron, linux-cxl, linux-kernel, alex.gaynor,
benno.lossin, boqun.feng, a.hindborg, bjorn3_gh, tmgross, ojeda,
wilfred.mallawa, aliceryhl, Alistair Francis
On 2026-02-11 03:29, alistair23@gmail.com wrote:
> From: Alistair Francis <alistair.francis@wdc.com>
>
> Implement From<FromBytesWithNulError> for the Kernel Error type
>
> Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
> ---
> rust/kernel/error.rs | 7 +++++++
> 1 file changed, 7 insertions(+)
>
> diff --git a/rust/kernel/error.rs b/rust/kernel/error.rs
> index 258b12afdcba..569d9d032ab3 100644
> --- a/rust/kernel/error.rs
> +++ b/rust/kernel/error.rs
> @@ -12,6 +12,7 @@
> str::CStr,
> };
>
> +use core::ffi::FromBytesWithNulError;
> use core::num::NonZeroI32;
> use core::num::TryFromIntError;
> use core::str::Utf8Error;
> @@ -251,6 +252,12 @@ fn from(e: core::convert::Infallible) -> Error {
> }
> }
>
> +impl From<FromBytesWithNulError> for Error {
> + fn from(_: FromBytesWithNulError) -> Error {
> + code::EINVAL
> + }
> +}
Are we sure that `FromBytesWithNulError` maps cleanly to the `EINVAL`
error code?
Anyhow, please add `#[inline]` for such simple functions.
Best,
Gary
> +
> /// A [`Result`] with an [`Error`] error type.
> ///
> /// To be used as the return type for functions that may fail.
^ permalink raw reply [flat|nested] 99+ messages in thread* Re: [RFC v3 07/27] rust: error: impl From<FromBytesWithNulError> for Kernel Error
2026-02-19 14:49 ` Gary Guo
@ 2026-03-13 2:20 ` Alistair Francis
2026-03-13 10:35 ` Alice Ryhl
0 siblings, 1 reply; 99+ messages in thread
From: Alistair Francis @ 2026-03-13 2:20 UTC (permalink / raw)
To: Gary Guo
Cc: bhelgaas, lukas, rust-for-linux, akpm, linux-pci,
Jonathan.Cameron, linux-cxl, linux-kernel, alex.gaynor,
benno.lossin, boqun.feng, a.hindborg, bjorn3_gh, tmgross, ojeda,
wilfred.mallawa, aliceryhl, Alistair Francis
On Fri, Feb 20, 2026 at 12:49 AM Gary Guo <gary@garyguo.net> wrote:
>
> On 2026-02-11 03:29, alistair23@gmail.com wrote:
> > From: Alistair Francis <alistair.francis@wdc.com>
> >
> > Implement From<FromBytesWithNulError> for the Kernel Error type
> >
> > Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
> > ---
> > rust/kernel/error.rs | 7 +++++++
> > 1 file changed, 7 insertions(+)
> >
> > diff --git a/rust/kernel/error.rs b/rust/kernel/error.rs
> > index 258b12afdcba..569d9d032ab3 100644
> > --- a/rust/kernel/error.rs
> > +++ b/rust/kernel/error.rs
> > @@ -12,6 +12,7 @@
> > str::CStr,
> > };
> >
> > +use core::ffi::FromBytesWithNulError;
> > use core::num::NonZeroI32;
> > use core::num::TryFromIntError;
> > use core::str::Utf8Error;
> > @@ -251,6 +252,12 @@ fn from(e: core::convert::Infallible) -> Error {
> > }
> > }
> >
> > +impl From<FromBytesWithNulError> for Error {
> > + fn from(_: FromBytesWithNulError) -> Error {
> > + code::EINVAL
> > + }
> > +}
>
> Are we sure that `FromBytesWithNulError` maps cleanly to the `EINVAL`
> error code?
`FromBytesWithNulError` means "An error indicating that a nul byte was
not in the expected position." [1]
To me that maps with `EINVAL`
1: https://doc.rust-lang.org/std/ffi/enum.FromBytesWithNulError.html
>
> Anyhow, please add `#[inline]` for such simple functions.
I can, just noting that none of the others have `#[inline]`
Alistair
>
> Best,
> Gary
>
> > +
> > /// A [`Result`] with an [`Error`] error type.
> > ///
> > /// To be used as the return type for functions that may fail.
^ permalink raw reply [flat|nested] 99+ messages in thread* Re: [RFC v3 07/27] rust: error: impl From<FromBytesWithNulError> for Kernel Error
2026-03-13 2:20 ` Alistair Francis
@ 2026-03-13 10:35 ` Alice Ryhl
0 siblings, 0 replies; 99+ messages in thread
From: Alice Ryhl @ 2026-03-13 10:35 UTC (permalink / raw)
To: Alistair Francis
Cc: Gary Guo, bhelgaas, lukas, rust-for-linux, akpm, linux-pci,
Jonathan.Cameron, linux-cxl, linux-kernel, alex.gaynor,
benno.lossin, boqun.feng, a.hindborg, bjorn3_gh, tmgross, ojeda,
wilfred.mallawa, Alistair Francis
On Fri, Mar 13, 2026 at 3:20 AM Alistair Francis <alistair23@gmail.com> wrote:
>
> On Fri, Feb 20, 2026 at 12:49 AM Gary Guo <gary@garyguo.net> wrote:
> >
> > On 2026-02-11 03:29, alistair23@gmail.com wrote:
> > > From: Alistair Francis <alistair.francis@wdc.com>
> > >
> > > Implement From<FromBytesWithNulError> for the Kernel Error type
> > >
> > > Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
> > > ---
> > > rust/kernel/error.rs | 7 +++++++
> > > 1 file changed, 7 insertions(+)
> > >
> > > diff --git a/rust/kernel/error.rs b/rust/kernel/error.rs
> > > index 258b12afdcba..569d9d032ab3 100644
> > > --- a/rust/kernel/error.rs
> > > +++ b/rust/kernel/error.rs
> > > @@ -12,6 +12,7 @@
> > > str::CStr,
> > > };
> > >
> > > +use core::ffi::FromBytesWithNulError;
> > > use core::num::NonZeroI32;
> > > use core::num::TryFromIntError;
> > > use core::str::Utf8Error;
> > > @@ -251,6 +252,12 @@ fn from(e: core::convert::Infallible) -> Error {
> > > }
> > > }
> > >
> > > +impl From<FromBytesWithNulError> for Error {
> > > + fn from(_: FromBytesWithNulError) -> Error {
> > > + code::EINVAL
> > > + }
> > > +}
> >
> > Are we sure that `FromBytesWithNulError` maps cleanly to the `EINVAL`
> > error code?
>
> `FromBytesWithNulError` means "An error indicating that a nul byte was
> not in the expected position." [1]
>
> To me that maps with `EINVAL`
>
> 1: https://doc.rust-lang.org/std/ffi/enum.FromBytesWithNulError.html
>
> >
> > Anyhow, please add `#[inline]` for such simple functions.
>
> I can, just noting that none of the others have `#[inline]`
I think add #[inline] to these could be a nice easy issue for new
contributors. If you want, you can open an issue on our github.
Alice
^ permalink raw reply [flat|nested] 99+ messages in thread
* [RFC v3 08/27] lib: rspdm: Initial commit of Rust SPDM
2026-02-11 3:29 [RFC v3 00/27] lib: Rust implementation of SPDM alistair23
` (6 preceding siblings ...)
2026-02-11 3:29 ` [RFC v3 07/27] rust: error: impl From<FromBytesWithNulError> for Kernel Error alistair23
@ 2026-02-11 3:29 ` alistair23
2026-03-02 17:09 ` Jonathan Cameron
2026-02-11 3:29 ` [RFC v3 09/27] PCI/CMA: Authenticate devices on enumeration alistair23
` (20 subsequent siblings)
28 siblings, 1 reply; 99+ messages in thread
From: alistair23 @ 2026-02-11 3:29 UTC (permalink / raw)
To: bhelgaas, lukas, rust-for-linux, akpm, linux-pci,
Jonathan.Cameron, linux-cxl, linux-kernel
Cc: alex.gaynor, benno.lossin, boqun.feng, a.hindborg, gary,
bjorn3_gh, tmgross, alistair23, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis
From: Alistair Francis <alistair@alistair23.me>
This is the initial commit of the Rust SPDM library. It is based on and
compatible with the C SPDM library in the kernel (lib/spdm).
Signed-off-by: Alistair Francis <alistair@alistair23.me>
---
MAINTAINERS | 12 ++
include/linux/spdm.h | 39 ++++++
lib/Kconfig | 17 +++
lib/Makefile | 2 +
lib/rspdm/Makefile | 10 ++
lib/rspdm/consts.rs | 117 +++++++++++++++++
lib/rspdm/lib.rs | 119 +++++++++++++++++
lib/rspdm/state.rs | 220 ++++++++++++++++++++++++++++++++
lib/rspdm/validator.rs | 66 ++++++++++
rust/bindings/bindings_helper.h | 2 +
rust/kernel/error.rs | 3 +
11 files changed, 607 insertions(+)
create mode 100644 include/linux/spdm.h
create mode 100644 lib/rspdm/Makefile
create mode 100644 lib/rspdm/consts.rs
create mode 100644 lib/rspdm/lib.rs
create mode 100644 lib/rspdm/state.rs
create mode 100644 lib/rspdm/validator.rs
diff --git a/MAINTAINERS b/MAINTAINERS
index 149deedafe2c..a5c4ec16081c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -23693,6 +23693,18 @@ M: Security Officers <security@kernel.org>
S: Supported
F: Documentation/process/security-bugs.rst
+SECURITY PROTOCOL AND DATA MODEL (SPDM)
+M: Jonathan Cameron <jic23@kernel.org>
+M: Lukas Wunner <lukas@wunner.de>
+M: Alistair Francis <alistair@alistair23.me>
+L: linux-coco@lists.linux.dev
+L: linux-cxl@vger.kernel.org
+L: linux-pci@vger.kernel.org
+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 <paul@paul-moore.com>
M: James Morris <jmorris@namei.org>
diff --git a/include/linux/spdm.h b/include/linux/spdm.h
new file mode 100644
index 000000000000..9835a3202a0e
--- /dev/null
+++ b/include/linux/spdm.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * DMTF Security Protocol and Data Model (SPDM)
+ * https://www.dmtf.org/dsp/DSP0274
+ *
+ * Copyright (C) 2021-22 Huawei
+ * Jonathan Cameron <Jonathan.Cameron@huawei.com>
+ *
+ * Copyright (C) 2022-24 Intel Corporation
+ */
+
+#ifndef _SPDM_H_
+#define _SPDM_H_
+
+#include <linux/types.h>
+
+struct key;
+struct device;
+struct spdm_state;
+struct x509_certificate;
+
+typedef ssize_t (spdm_transport)(void *priv, struct device *dev,
+ const void *request, size_t request_sz,
+ void *response, size_t response_sz);
+
+typedef int (spdm_validate)(struct device *dev, u8 slot,
+ struct x509_certificate *leaf_cert);
+
+struct spdm_state *spdm_create(struct device *dev, spdm_transport *transport,
+ void *transport_priv, u32 transport_sz,
+ struct key *keyring, spdm_validate *validate);
+
+int spdm_authenticate(struct spdm_state *spdm_state);
+
+void spdm_destroy(struct spdm_state *spdm_state);
+
+extern const struct attribute_group spdm_attr_group;
+
+#endif
diff --git a/lib/Kconfig b/lib/Kconfig
index 2923924bea78..c21f9f9a5221 100644
--- a/lib/Kconfig
+++ b/lib/Kconfig
@@ -604,6 +604,23 @@ config LWQ_TEST
help
Run boot-time test of light-weight queuing.
+config RSPDM
+ bool "Rust SPDM"
+ select RUST
+ select CRYPTO
+ select KEYS
+ select ASYMMETRIC_KEY_TYPE
+ select ASYMMETRIC_PUBLIC_KEY_SUBTYPE
+ select X509_CERTIFICATE_PARSER
+ help
+ The Rust implementation of the Security Protocol and Data Model (SPDM)
+ allows for device authentication, measurement, key exchange and
+ encrypted sessions.
+
+ Crypto algorithms negotiated with SPDM are limited to those enabled
+ in .config. Users of SPDM therefore need to also select
+ any algorithms they deem mandatory.
+
endmenu
config GENERIC_IOREMAP
diff --git a/lib/Makefile b/lib/Makefile
index 22d8742bba57..2f530471e8dc 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -286,6 +286,8 @@ obj-$(CONFIG_PERCPU_TEST) += percpu_test.o
obj-$(CONFIG_ASN1) += asn1_decoder.o
obj-$(CONFIG_ASN1_ENCODER) += asn1_encoder.o
+obj-$(CONFIG_RSPDM) += rspdm/
+
obj-$(CONFIG_FONT_SUPPORT) += fonts/
#
diff --git a/lib/rspdm/Makefile b/lib/rspdm/Makefile
new file mode 100644
index 000000000000..1f62ee2a882d
--- /dev/null
+++ b/lib/rspdm/Makefile
@@ -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
diff --git a/lib/rspdm/consts.rs b/lib/rspdm/consts.rs
new file mode 100644
index 000000000000..40ce60eba2f3
--- /dev/null
+++ b/lib/rspdm/consts.rs
@@ -0,0 +1,117 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Copyright (C) 2024 Western Digital
+
+//! Constants used by the library
+//!
+//! Rust implementation of the DMTF Security Protocol and Data Model (SPDM)
+//! <https://www.dmtf.org/dsp/DSP0274>
+
+pub(crate) const SPDM_REQ: u8 = 0x80;
+pub(crate) const SPDM_ERROR: u8 = 0x7f;
+
+#[expect(dead_code)]
+#[derive(Clone, Copy)]
+pub(crate) enum SpdmErrorCode {
+ InvalidRequest = 0x01,
+ InvalidSession = 0x02,
+ Busy = 0x03,
+ UnexpectedRequest = 0x04,
+ Unspecified = 0x05,
+ DecryptError = 0x06,
+ UnsupportedRequest = 0x07,
+ RequestInFlight = 0x08,
+ InvalidResponseCode = 0x09,
+ SessionLimitExceeded = 0x0a,
+ SessionRequired = 0x0b,
+ ResetRequired = 0x0c,
+ ResponseTooLarge = 0x0d,
+ RequestTooLarge = 0x0e,
+ LargeResponse = 0x0f,
+ MessageLost = 0x10,
+ InvalidPolicy = 0x11,
+ VersionMismatch = 0x41,
+ ResponseNotReady = 0x42,
+ RequestResynch = 0x43,
+ OperationFailed = 0x44,
+ NoPendingRequests = 0x45,
+ VendorDefinedError = 0xff,
+}
+
+impl core::fmt::LowerHex for SpdmErrorCode {
+ /// A debug print format for the SpdmSessionInfo struct
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ match self {
+ SpdmErrorCode::InvalidRequest => {
+ writeln!(f, "0x01")?;
+ }
+ SpdmErrorCode::InvalidSession => {
+ writeln!(f, "0x02")?;
+ }
+ SpdmErrorCode::Busy => {
+ writeln!(f, "0x03")?;
+ }
+ SpdmErrorCode::UnexpectedRequest => {
+ writeln!(f, "0x04")?;
+ }
+ SpdmErrorCode::Unspecified => {
+ writeln!(f, "0x05")?;
+ }
+ SpdmErrorCode::DecryptError => {
+ writeln!(f, "0x06")?;
+ }
+ SpdmErrorCode::UnsupportedRequest => {
+ writeln!(f, "0x07")?;
+ }
+ SpdmErrorCode::RequestInFlight => {
+ writeln!(f, "0x08")?;
+ }
+ SpdmErrorCode::InvalidResponseCode => {
+ writeln!(f, "0x09")?;
+ }
+ SpdmErrorCode::SessionLimitExceeded => {
+ writeln!(f, "0x0a")?;
+ }
+ SpdmErrorCode::SessionRequired => {
+ writeln!(f, "0x0b")?;
+ }
+ SpdmErrorCode::ResetRequired => {
+ writeln!(f, "0x0c")?;
+ }
+ SpdmErrorCode::ResponseTooLarge => {
+ writeln!(f, "0x0d")?;
+ }
+ SpdmErrorCode::RequestTooLarge => {
+ writeln!(f, "0x0e")?;
+ }
+ SpdmErrorCode::LargeResponse => {
+ writeln!(f, "0x0f")?;
+ }
+ SpdmErrorCode::MessageLost => {
+ writeln!(f, "0x10")?;
+ }
+ SpdmErrorCode::InvalidPolicy => {
+ writeln!(f, "0x11")?;
+ }
+ SpdmErrorCode::VersionMismatch => {
+ writeln!(f, "0x41")?;
+ }
+ SpdmErrorCode::ResponseNotReady => {
+ writeln!(f, "0x42")?;
+ }
+ SpdmErrorCode::RequestResynch => {
+ writeln!(f, "0x43")?;
+ }
+ SpdmErrorCode::OperationFailed => {
+ writeln!(f, "0x44")?;
+ }
+ SpdmErrorCode::NoPendingRequests => {
+ writeln!(f, "0x45")?;
+ }
+ SpdmErrorCode::VendorDefinedError => {
+ writeln!(f, "0xff")?;
+ }
+ }
+ Ok(())
+ }
+}
diff --git a/lib/rspdm/lib.rs b/lib/rspdm/lib.rs
new file mode 100644
index 000000000000..2bb716140e0a
--- /dev/null
+++ b/lib/rspdm/lib.rs
@@ -0,0 +1,119 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Copyright (C) 2024 Western Digital
+
+//! Top level library for SPDM
+//!
+//! Rust implementation of the DMTF Security Protocol and Data Model (SPDM)
+//! <https://www.dmtf.org/dsp/DSP0274>
+//!
+//! Top level library, including C compatible public functions to be called
+//! from other subsytems.
+//!
+//! This mimics the C SPDM implementation in the kernel
+
+use core::ffi::{c_int, c_void};
+use core::ptr;
+use core::slice::from_raw_parts_mut;
+use kernel::prelude::*;
+use kernel::{alloc::flags, bindings};
+
+use crate::state::SpdmState;
+
+const __LOG_PREFIX: &[u8] = b"spdm\0";
+
+mod consts;
+mod state;
+mod validator;
+
+/// spdm_create() - Allocate SPDM session
+///
+/// `dev`: Responder device
+/// `transport`: Transport function to perform one message exchange
+/// `transport_priv`: Transport private data
+/// `transport_sz`: Maximum message size the transport is capable of (in bytes)
+/// `keyring`: Trusted root certificates
+/// `validate`: Function to validate additional leaf certificate requirements
+/// (optional, may be %NULL)
+///
+/// Return a pointer to the allocated SPDM session state or NULL on error.
+#[no_mangle]
+pub unsafe extern "C" fn spdm_create(
+ dev: *mut bindings::device,
+ transport: bindings::spdm_transport,
+ transport_priv: *mut c_void,
+ transport_sz: u32,
+ keyring: *mut bindings::key,
+ validate: bindings::spdm_validate,
+) -> *mut SpdmState {
+ match KBox::new(
+ SpdmState::new(
+ dev,
+ transport,
+ transport_priv,
+ transport_sz,
+ keyring,
+ validate,
+ ),
+ flags::GFP_KERNEL,
+ ) {
+ Ok(ret) => KBox::into_raw(ret) as *mut SpdmState,
+ Err(_) => ptr::null_mut(),
+ }
+}
+
+/// spdm_exchange() - Perform SPDM message exchange with device
+///
+/// @spdm_state: SPDM session state
+/// @req: Request message
+/// @req_sz: Size of @req
+/// @rsp: Response message
+/// @rsp_sz: Size of @rsp
+///
+/// Send the request @req to the device via the @transport in @spdm_state and
+/// receive the response into @rsp, respecting the maximum buffer size @rsp_sz.
+/// The request version is automatically populated.
+///
+/// Return response size on success or a negative errno. Response size may be
+/// less than @rsp_sz and the caller is responsible for checking that. It may
+/// also be more than expected (though never more than @rsp_sz), e.g. if the
+/// transport receives only dword-sized chunks.
+#[no_mangle]
+pub unsafe extern "C" fn spdm_exchange(
+ state: &'static mut SpdmState,
+ req: *mut c_void,
+ req_sz: usize,
+ rsp: *mut c_void,
+ rsp_sz: usize,
+) -> isize {
+ let request_buf: &mut [u8] = unsafe { from_raw_parts_mut(req as *mut u8, req_sz) };
+ let response_buf: &mut [u8] = unsafe { from_raw_parts_mut(rsp as *mut u8, rsp_sz) };
+
+ match state.spdm_exchange(request_buf, response_buf) {
+ Ok(ret) => ret as isize,
+ Err(e) => e.to_errno() as isize,
+ }
+}
+
+/// spdm_authenticate() - Authenticate device
+///
+/// @spdm_state: SPDM session state
+///
+/// Authenticate a device through a sequence of GET_VERSION, GET_CAPABILITIES,
+/// NEGOTIATE_ALGORITHMS, GET_DIGESTS, GET_CERTIFICATE and CHALLENGE exchanges.
+///
+/// Perform internal locking to serialize multiple concurrent invocations.
+/// Can be called repeatedly for reauthentication.
+///
+/// Return 0 on success or a negative errno. In particular, -EPROTONOSUPPORT
+/// indicates authentication is not supported by the device.
+#[no_mangle]
+pub unsafe extern "C" fn spdm_authenticate(_state: &'static mut SpdmState) -> c_int {
+ 0
+}
+
+/// spdm_destroy() - Destroy SPDM session
+///
+/// @spdm_state: SPDM session state
+#[no_mangle]
+pub unsafe extern "C" fn spdm_destroy(_state: &'static mut SpdmState) {}
diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
new file mode 100644
index 000000000000..68861f30e3fa
--- /dev/null
+++ b/lib/rspdm/state.rs
@@ -0,0 +1,220 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Copyright (C) 2024 Western Digital
+
+//! The `SpdmState` struct and implementation.
+//!
+//! Rust implementation of the DMTF Security Protocol and Data Model (SPDM)
+//! <https://www.dmtf.org/dsp/DSP0274>
+
+use core::ffi::c_void;
+use kernel::prelude::*;
+use kernel::{
+ bindings,
+ error::{code::EINVAL, to_result, Error},
+ validate::Untrusted,
+};
+
+use crate::consts::{SpdmErrorCode, SPDM_ERROR, SPDM_REQ};
+use crate::validator::{SpdmErrorRsp, SpdmHeader};
+
+/// The current SPDM session state for a device. Based on the
+/// C `struct spdm_state`.
+///
+/// `dev`: Responder device. Used for error reporting and passed to @transport.
+/// `transport`: Transport function to perform one message exchange.
+/// `transport_priv`: Transport private data.
+/// `transport_sz`: Maximum message size the transport is capable of (in bytes).
+/// Used as DataTransferSize in GET_CAPABILITIES exchange.
+/// `keyring`: Keyring against which to check the first certificate in
+/// responder's certificate chain.
+/// `validate`: Function to validate additional leaf certificate requirements.
+///
+/// `version`: Maximum common supported version of requester and responder.
+/// Negotiated during GET_VERSION exchange.
+#[expect(dead_code)]
+pub struct SpdmState {
+ pub(crate) dev: *mut bindings::device,
+ pub(crate) transport: bindings::spdm_transport,
+ pub(crate) transport_priv: *mut c_void,
+ pub(crate) transport_sz: u32,
+ pub(crate) keyring: *mut bindings::key,
+ pub(crate) validate: bindings::spdm_validate,
+
+ // Negotiated state
+ pub(crate) version: u8,
+}
+
+impl SpdmState {
+ pub(crate) fn new(
+ dev: *mut bindings::device,
+ transport: bindings::spdm_transport,
+ transport_priv: *mut c_void,
+ transport_sz: u32,
+ keyring: *mut bindings::key,
+ validate: bindings::spdm_validate,
+ ) -> Self {
+ SpdmState {
+ dev,
+ transport,
+ transport_priv,
+ transport_sz,
+ keyring,
+ validate,
+ version: 0x10,
+ }
+ }
+
+ fn spdm_err(&self, rsp: &SpdmErrorRsp) -> Result<(), Error> {
+ match rsp.error_code {
+ SpdmErrorCode::InvalidRequest => {
+ pr_err!("Invalid request\n");
+ Err(EINVAL)
+ }
+ SpdmErrorCode::InvalidSession => {
+ if rsp.version == 0x11 {
+ pr_err!("Invalid session {:#x}\n", rsp.error_data);
+ Err(EINVAL)
+ } else {
+ pr_err!("Undefined error {:#x}\n", rsp.error_code);
+ Err(EINVAL)
+ }
+ }
+ SpdmErrorCode::Busy => {
+ pr_err!("Busy\n");
+ Err(EBUSY)
+ }
+ SpdmErrorCode::UnexpectedRequest => {
+ pr_err!("Unexpected request\n");
+ Err(EINVAL)
+ }
+ SpdmErrorCode::Unspecified => {
+ pr_err!("Unspecified error\n");
+ Err(EINVAL)
+ }
+ SpdmErrorCode::DecryptError => {
+ pr_err!("Decrypt error\n");
+ Err(EIO)
+ }
+ SpdmErrorCode::UnsupportedRequest => {
+ pr_err!("Unsupported request {:#x}\n", rsp.error_data);
+ Err(EINVAL)
+ }
+ SpdmErrorCode::RequestInFlight => {
+ pr_err!("Request in flight\n");
+ Err(EINVAL)
+ }
+ SpdmErrorCode::InvalidResponseCode => {
+ pr_err!("Invalid response code\n");
+ Err(EINVAL)
+ }
+ SpdmErrorCode::SessionLimitExceeded => {
+ pr_err!("Session limit exceeded\n");
+ Err(EBUSY)
+ }
+ SpdmErrorCode::SessionRequired => {
+ pr_err!("Session required\n");
+ Err(EINVAL)
+ }
+ SpdmErrorCode::ResetRequired => {
+ pr_err!("Reset required\n");
+ Err(ECONNRESET)
+ }
+ SpdmErrorCode::ResponseTooLarge => {
+ pr_err!("Response too large\n");
+ Err(EINVAL)
+ }
+ SpdmErrorCode::RequestTooLarge => {
+ pr_err!("Request too large\n");
+ Err(EINVAL)
+ }
+ SpdmErrorCode::LargeResponse => {
+ pr_err!("Large response\n");
+ Err(EMSGSIZE)
+ }
+ SpdmErrorCode::MessageLost => {
+ pr_err!("Message lost\n");
+ Err(EIO)
+ }
+ SpdmErrorCode::InvalidPolicy => {
+ pr_err!("Invalid policy\n");
+ Err(EINVAL)
+ }
+ SpdmErrorCode::VersionMismatch => {
+ pr_err!("Version mismatch\n");
+ Err(EINVAL)
+ }
+ SpdmErrorCode::ResponseNotReady => {
+ pr_err!("Response not ready\n");
+ Err(EINPROGRESS)
+ }
+ SpdmErrorCode::RequestResynch => {
+ pr_err!("Request resynchronization\n");
+ Err(ECONNRESET)
+ }
+ SpdmErrorCode::OperationFailed => {
+ pr_err!("Operation failed\n");
+ Err(EINVAL)
+ }
+ SpdmErrorCode::NoPendingRequests => Err(ENOENT),
+ SpdmErrorCode::VendorDefinedError => {
+ pr_err!("Vendor defined error\n");
+ Err(EINVAL)
+ }
+ }
+ }
+
+ /// Start a SPDM exchange
+ ///
+ /// The data in `request_buf` is sent to the device and the response is
+ /// stored in `response_buf`.
+ pub(crate) fn spdm_exchange(
+ &self,
+ request_buf: &mut [u8],
+ response_buf: &mut [u8],
+ ) -> Result<i32, Error> {
+ let header_size = core::mem::size_of::<SpdmHeader>();
+ let request: &mut SpdmHeader = Untrusted::new_mut(request_buf).validate_mut()?;
+ let response: &SpdmHeader = Untrusted::new_ref(response_buf).validate()?;
+
+ let transport_function = self.transport.ok_or(EINVAL)?;
+ // SAFETY: `transport_function` is provided by the new(), we are
+ // calling the function.
+ let length = unsafe {
+ transport_function(
+ self.transport_priv,
+ self.dev,
+ request_buf.as_ptr() as *const c_void,
+ request_buf.len(),
+ response_buf.as_mut_ptr() as *mut c_void,
+ response_buf.len(),
+ ) as i32
+ };
+ to_result(length)?;
+
+ if (length as usize) < header_size {
+ return Ok(length); // Truncated response is handled by callers
+ }
+ if response.code == SPDM_ERROR {
+ if length as usize >= core::mem::size_of::<SpdmErrorRsp>() {
+ // SAFETY: The response buffer will be at least as large as
+ // `SpdmErrorRsp` so we can cast the buffer to `SpdmErrorRsp` which
+ // is a packed struct.
+ self.spdm_err(unsafe { &*(response_buf.as_ptr() as *const SpdmErrorRsp) })?;
+ } else {
+ return Err(EINVAL);
+ }
+ }
+
+ if response.code != request.code & !SPDM_REQ {
+ pr_err!(
+ "Response code {:#x} does not match request code {:#x}\n",
+ response.code,
+ request.code
+ );
+ to_result(-(bindings::EPROTO as i32))?;
+ }
+
+ Ok(length)
+ }
+}
diff --git a/lib/rspdm/validator.rs b/lib/rspdm/validator.rs
new file mode 100644
index 000000000000..a0a3a2f46952
--- /dev/null
+++ b/lib/rspdm/validator.rs
@@ -0,0 +1,66 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Copyright (C) 2024 Western Digital
+
+//! Related structs and their Validate implementations.
+//!
+//! Rust implementation of the DMTF Security Protocol and Data Model (SPDM)
+//! <https://www.dmtf.org/dsp/DSP0274>
+
+use crate::consts::SpdmErrorCode;
+use core::mem;
+use kernel::prelude::*;
+use kernel::{
+ error::{code::EINVAL, Error},
+ validate::{Unvalidated, Validate},
+};
+
+#[repr(C, packed)]
+pub(crate) struct SpdmHeader {
+ pub(crate) version: u8,
+ pub(crate) code: u8, /* RequestResponseCode */
+ pub(crate) param1: u8,
+ pub(crate) param2: u8,
+}
+
+impl Validate<&Unvalidated<[u8]>> for &SpdmHeader {
+ type Err = Error;
+
+ fn validate(unvalidated: &Unvalidated<[u8]>) -> Result<Self, Self::Err> {
+ let raw = unvalidated.raw();
+ if raw.len() < mem::size_of::<SpdmHeader>() {
+ return Err(EINVAL);
+ }
+
+ let ptr = raw.as_ptr();
+ // CAST: `SpdmHeader` only contains integers and has `repr(C)`.
+ let ptr = ptr.cast::<SpdmHeader>();
+ // SAFETY: `ptr` came from a reference and the cast above is valid.
+ Ok(unsafe { &*ptr })
+ }
+}
+
+impl Validate<&mut Unvalidated<[u8]>> for &mut SpdmHeader {
+ type Err = Error;
+
+ fn validate(unvalidated: &mut Unvalidated<[u8]>) -> Result<Self, Self::Err> {
+ let raw = unvalidated.raw_mut();
+ if raw.len() < mem::size_of::<SpdmHeader>() {
+ return Err(EINVAL);
+ }
+
+ let ptr = raw.as_mut_ptr();
+ // CAST: `SpdmHeader` only contains integers and has `repr(C)`.
+ let ptr = ptr.cast::<SpdmHeader>();
+ // SAFETY: `ptr` came from a reference and the cast above is valid.
+ Ok(unsafe { &mut *ptr })
+ }
+}
+
+#[repr(C, packed)]
+pub(crate) struct SpdmErrorRsp {
+ pub(crate) version: u8,
+ pub(crate) code: u8,
+ pub(crate) error_code: SpdmErrorCode,
+ pub(crate) error_data: u8,
+}
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 0075c4b62c29..5043eee2a8d6 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -84,7 +84,9 @@
#include <linux/slab.h>
#include <linux/task_work.h>
#include <linux/tracepoint.h>
+#include <linux/spdm.h>
#include <linux/usb.h>
+#include <linux/uaccess.h>
#include <linux/wait.h>
#include <linux/workqueue.h>
#include <linux/xarray.h>
diff --git a/rust/kernel/error.rs b/rust/kernel/error.rs
index 569d9d032ab3..c9f57082e481 100644
--- a/rust/kernel/error.rs
+++ b/rust/kernel/error.rs
@@ -88,6 +88,9 @@ macro_rules! declare_err {
declare_err!(EIOCBQUEUED, "iocb queued, will get completion event.");
declare_err!(ERECALLCONFLICT, "Conflict with recalled state.");
declare_err!(ENOGRACE, "NFS file lock reclaim refused.");
+ declare_err!(ECONNRESET, "Connection reset by peer.");
+ declare_err!(EMSGSIZE, "Message too long.");
+ declare_err!(EINPROGRESS, "Operation now in progress.");
}
/// Generic integer kernel error.
--
2.52.0
^ permalink raw reply related [flat|nested] 99+ messages in thread* Re: [RFC v3 08/27] lib: rspdm: Initial commit of Rust SPDM
2026-02-11 3:29 ` [RFC v3 08/27] lib: rspdm: Initial commit of Rust SPDM alistair23
@ 2026-03-02 17:09 ` Jonathan Cameron
2026-03-13 3:44 ` Alistair Francis
0 siblings, 1 reply; 99+ messages in thread
From: Jonathan Cameron @ 2026-03-02 17:09 UTC (permalink / raw)
To: alistair23
Cc: bhelgaas, lukas, rust-for-linux, akpm, linux-pci, linux-cxl,
linux-kernel, alex.gaynor, benno.lossin, boqun.feng, a.hindborg,
gary, bjorn3_gh, tmgross, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis
On Wed, 11 Feb 2026 13:29:15 +1000
alistair23@gmail.com wrote:
> From: Alistair Francis <alistair@alistair23.me>
>
> This is the initial commit of the Rust SPDM library. It is based on and
> compatible with the C SPDM library in the kernel (lib/spdm).
>
> Signed-off-by: Alistair Francis <alistair@alistair23.me>
The comments that follow are based on my very limited rust knowledge.
Hence may be garbage.
> ---
> MAINTAINERS | 12 ++
> include/linux/spdm.h | 39 ++++++
> lib/Kconfig | 17 +++
> lib/Makefile | 2 +
> lib/rspdm/Makefile | 10 ++
> lib/rspdm/consts.rs | 117 +++++++++++++++++
> lib/rspdm/lib.rs | 119 +++++++++++++++++
> lib/rspdm/state.rs | 220 ++++++++++++++++++++++++++++++++
> lib/rspdm/validator.rs | 66 ++++++++++
> rust/bindings/bindings_helper.h | 2 +
> rust/kernel/error.rs | 3 +
> 11 files changed, 607 insertions(+)
> create mode 100644 include/linux/spdm.h
> create mode 100644 lib/rspdm/Makefile
> create mode 100644 lib/rspdm/consts.rs
> create mode 100644 lib/rspdm/lib.rs
> create mode 100644 lib/rspdm/state.rs
> create mode 100644 lib/rspdm/validator.rs
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 149deedafe2c..a5c4ec16081c 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -23693,6 +23693,18 @@ M: Security Officers <security@kernel.org>
> S: Supported
> F: Documentation/process/security-bugs.rst
>
> +SECURITY PROTOCOL AND DATA MODEL (SPDM)
> +M: Jonathan Cameron <jic23@kernel.org>
Evil way to get me to learn rust. Ah well, I guess I can't put it
off for ever.
> +M: Lukas Wunner <lukas@wunner.de>
> +M: Alistair Francis <alistair@alistair23.me>
> +L: linux-coco@lists.linux.dev
> +L: linux-cxl@vger.kernel.org
> +L: linux-pci@vger.kernel.org
> +S: Maintained
> +T: git git://git.kernel.org/pub/scm/linux/kernel/git/devsec/spdm.git
> +F: include/linux/spdm.h
> +F: lib/rspdm/
> diff --git a/lib/rspdm/consts.rs b/lib/rspdm/consts.rs
> new file mode 100644
> index 000000000000..40ce60eba2f3
> --- /dev/null
> +++ b/lib/rspdm/consts.rs
> @@ -0,0 +1,117 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +// Copyright (C) 2024 Western Digital
> +
> +//! Constants used by the library
> +//!
> +//! Rust implementation of the DMTF Security Protocol and Data Model (SPDM)
> +//! <https://www.dmtf.org/dsp/DSP0274>
> +
> +pub(crate) const SPDM_REQ: u8 = 0x80;
> +pub(crate) const SPDM_ERROR: u8 = 0x7f;
> +
> +#[expect(dead_code)]
> +#[derive(Clone, Copy)]
> +pub(crate) enum SpdmErrorCode {
> + InvalidRequest = 0x01,
> + InvalidSession = 0x02,
This is reserved by the time we reach 1.2.1 and disappeared somewhere
in the middle of 1.1.0 (present) and 1.1.2 (reserved)
Probably good to leave some breadcrumbs for what spec versions could use
this error code. That will reduce confusion for future readers.
You have this info in the parsing code, but I'd like it here as well.
> + Busy = 0x03,
> + UnexpectedRequest = 0x04,
> + Unspecified = 0x05,
> + DecryptError = 0x06,
> + UnsupportedRequest = 0x07,
> + RequestInFlight = 0x08,
> + InvalidResponseCode = 0x09,
> + SessionLimitExceeded = 0x0a,
> + SessionRequired = 0x0b,
> + ResetRequired = 0x0c,
> + ResponseTooLarge = 0x0d,
> + RequestTooLarge = 0x0e,
> + LargeResponse = 0x0f,
> + MessageLost = 0x10,
> + InvalidPolicy = 0x11,
> + VersionMismatch = 0x41,
> + ResponseNotReady = 0x42,
> + RequestResynch = 0x43,
> + OperationFailed = 0x44,
> + NoPendingRequests = 0x45,
> + VendorDefinedError = 0xff,
> +}
> +
> +impl core::fmt::LowerHex for SpdmErrorCode {
> + /// A debug print format for the SpdmSessionInfo struct
> + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
> + match self {
> + SpdmErrorCode::InvalidRequest => {
> + writeln!(f, "0x01")?;
No way to get a string from an enum value in rust?
Having to check these against the enum above is a bit tedious.
> + }
> + }
> + Ok(())
> + }
> +}
> diff --git a/lib/rspdm/lib.rs b/lib/rspdm/lib.rs
> new file mode 100644
> index 000000000000..2bb716140e0a
> --- /dev/null
> +++ b/lib/rspdm/lib.rs
> @@ -0,0 +1,119 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +// Copyright (C) 2024 Western Digital
> +
> +//! Top level library for SPDM
> +//!
> +//! Rust implementation of the DMTF Security Protocol and Data Model (SPDM)
> +//! <https://www.dmtf.org/dsp/DSP0274>
> +//!
> +//! Top level library, including C compatible public functions to be called
> +//! from other subsytems.
> +//!
> +//! This mimics the C SPDM implementation in the kernel
The one that never gets merged if this goes according to plan ;)
> diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
> new file mode 100644
> index 000000000000..68861f30e3fa
> --- /dev/null
> +++ b/lib/rspdm/state.rs
> @@ -0,0 +1,220 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +// Copyright (C) 2024 Western Digital
> +
> +//! The `SpdmState` struct and implementation.
> +//!
> +//! Rust implementation of the DMTF Security Protocol and Data Model (SPDM)
> +//! <https://www.dmtf.org/dsp/DSP0274>
> +
> +use core::ffi::c_void;
> +use kernel::prelude::*;
> +use kernel::{
> + bindings,
> + error::{code::EINVAL, to_result, Error},
> + validate::Untrusted,
> +};
> +
> +use crate::consts::{SpdmErrorCode, SPDM_ERROR, SPDM_REQ};
> +use crate::validator::{SpdmErrorRsp, SpdmHeader};
> +
> +/// The current SPDM session state for a device. Based on the
> +/// C `struct spdm_state`.
> +///
> +/// `dev`: Responder device. Used for error reporting and passed to @transport.
> +/// `transport`: Transport function to perform one message exchange.
> +/// `transport_priv`: Transport private data.
> +/// `transport_sz`: Maximum message size the transport is capable of (in bytes).
> +/// Used as DataTransferSize in GET_CAPABILITIES exchange.
> +/// `keyring`: Keyring against which to check the first certificate in
> +/// responder's certificate chain.
Given the discussions, seems this will go away (for now anyway).
> +/// `validate`: Function to validate additional leaf certificate requirements.
> +///
> +/// `version`: Maximum common supported version of requester and responder.
> +/// Negotiated during GET_VERSION exchange.
> +#[expect(dead_code)]
> +pub struct SpdmState {
> + pub(crate) dev: *mut bindings::device,
> + pub(crate) transport: bindings::spdm_transport,
> + pub(crate) transport_priv: *mut c_void,
> + pub(crate) transport_sz: u32,
> + pub(crate) keyring: *mut bindings::key,
> + pub(crate) validate: bindings::spdm_validate,
> +
> + // Negotiated state
> + pub(crate) version: u8,
> +}
> +
> +impl SpdmState {
> + pub(crate) fn new(
> + dev: *mut bindings::device,
> + transport: bindings::spdm_transport,
> + transport_priv: *mut c_void,
> + transport_sz: u32,
> + keyring: *mut bindings::key,
> + validate: bindings::spdm_validate,
> + ) -> Self {
> + SpdmState {
> + dev,
> + transport,
> + transport_priv,
> + transport_sz,
> + keyring,
> + validate,
> + version: 0x10,
> + }
> + }
> +
> + /// Start a SPDM exchange
> + ///
> + /// The data in `request_buf` is sent to the device and the response is
> + /// stored in `response_buf`.
> + pub(crate) fn spdm_exchange(
> + &self,
> + request_buf: &mut [u8],
> + response_buf: &mut [u8],
> + ) -> Result<i32, Error> {
> + let header_size = core::mem::size_of::<SpdmHeader>();
> + let request: &mut SpdmHeader = Untrusted::new_mut(request_buf).validate_mut()?;
Why are we treating the request, which doesn't come from the device as untrusted?
Just for convenience on checking we formatted it right at the upper levels or is
the idea that might ultimately be coming from userspace?
> + let response: &SpdmHeader = Untrusted::new_ref(response_buf).validate()?;
> +
> + let transport_function = self.transport.ok_or(EINVAL)?;
> + // SAFETY: `transport_function` is provided by the new(), we are
> + // calling the function.
> + let length = unsafe {
> + transport_function(
> + self.transport_priv,
> + self.dev,
> + request_buf.as_ptr() as *const c_void,
> + request_buf.len(),
> + response_buf.as_mut_ptr() as *mut c_void,
> + response_buf.len(),
> + ) as i32
> + };
> + to_result(length)?;
> +
> + if (length as usize) < header_size {
> + return Ok(length); // Truncated response is handled by callers
> + }
> + if response.code == SPDM_ERROR {
> + if length as usize >= core::mem::size_of::<SpdmErrorRsp>() {
> + // SAFETY: The response buffer will be at least as large as
> + // `SpdmErrorRsp` so we can cast the buffer to `SpdmErrorRsp` which
> + // is a packed struct.
> + self.spdm_err(unsafe { &*(response_buf.as_ptr() as *const SpdmErrorRsp) })?;
> + } else {
> + return Err(EINVAL);
> + }
> + }
> +
> + if response.code != request.code & !SPDM_REQ {
> + pr_err!(
> + "Response code {:#x} does not match request code {:#x}\n",
> + response.code,
> + request.code
> + );
> + to_result(-(bindings::EPROTO as i32))?;
> + }
> +
> + Ok(length)
> + }
> +}
> diff --git a/lib/rspdm/validator.rs b/lib/rspdm/validator.rs
> new file mode 100644
> index 000000000000..a0a3a2f46952
> --- /dev/null
> +++ b/lib/rspdm/validator.rs
> @@ -0,0 +1,66 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +// Copyright (C) 2024 Western Digital
> +
> +//! Related structs and their Validate implementations.
> +//!
> +//! Rust implementation of the DMTF Security Protocol and Data Model (SPDM)
> +//! <https://www.dmtf.org/dsp/DSP0274>
> +
> +use crate::consts::SpdmErrorCode;
> +use core::mem;
> +use kernel::prelude::*;
> +use kernel::{
> + error::{code::EINVAL, Error},
> + validate::{Unvalidated, Validate},
> +};
> +
> +#[repr(C, packed)]
> +pub(crate) struct SpdmHeader {
> + pub(crate) version: u8,
> + pub(crate) code: u8, /* RequestResponseCode */
> + pub(crate) param1: u8,
> + pub(crate) param2: u8,
> +}
> +
> +impl Validate<&Unvalidated<[u8]>> for &SpdmHeader {
> + type Err = Error;
> +
> + fn validate(unvalidated: &Unvalidated<[u8]>) -> Result<Self, Self::Err> {
> + let raw = unvalidated.raw();
> + if raw.len() < mem::size_of::<SpdmHeader>() {
> + return Err(EINVAL);
> + }
> +
> + let ptr = raw.as_ptr();
> + // CAST: `SpdmHeader` only contains integers and has `repr(C)`.
> + let ptr = ptr.cast::<SpdmHeader>();
> + // SAFETY: `ptr` came from a reference and the cast above is valid.
> + Ok(unsafe { &*ptr })
> + }
> +}
> +
> +impl Validate<&mut Unvalidated<[u8]>> for &mut SpdmHeader {
> + type Err = Error;
> +
> + fn validate(unvalidated: &mut Unvalidated<[u8]>) -> Result<Self, Self::Err> {
> + let raw = unvalidated.raw_mut();
> + if raw.len() < mem::size_of::<SpdmHeader>() {
> + return Err(EINVAL);
> + }
> +
> + let ptr = raw.as_mut_ptr();
> + // CAST: `SpdmHeader` only contains integers and has `repr(C)`.
> + let ptr = ptr.cast::<SpdmHeader>();
> + // SAFETY: `ptr` came from a reference and the cast above is valid.
> + Ok(unsafe { &mut *ptr })
> + }
> +}
> +
> +#[repr(C, packed)]
> +pub(crate) struct SpdmErrorRsp {
> + pub(crate) version: u8,
> + pub(crate) code: u8,
Maybe document here that this will always be SPDM_ERROR 0x7F
> + pub(crate) error_code: SpdmErrorCode,
> + pub(crate) error_data: u8,
> +}
> diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
> index 0075c4b62c29..5043eee2a8d6 100644
> --- a/rust/bindings/bindings_helper.h
> +++ b/rust/bindings/bindings_helper.h
> @@ -84,7 +84,9 @@
> #include <linux/slab.h>
> #include <linux/task_work.h>
> #include <linux/tracepoint.h>
> +#include <linux/spdm.h>
Smells like this is alphabetical order. So move it up a bit?
> #include <linux/usb.h>
> +#include <linux/uaccess.h>
> #include <linux/wait.h>
> #include <linux/workqueue.h>
> #include <linux/xarray.h>
^ permalink raw reply [flat|nested] 99+ messages in thread* Re: [RFC v3 08/27] lib: rspdm: Initial commit of Rust SPDM
2026-03-02 17:09 ` Jonathan Cameron
@ 2026-03-13 3:44 ` Alistair Francis
0 siblings, 0 replies; 99+ messages in thread
From: Alistair Francis @ 2026-03-13 3:44 UTC (permalink / raw)
To: Jonathan Cameron
Cc: bhelgaas, lukas, rust-for-linux, akpm, linux-pci, linux-cxl,
linux-kernel, alex.gaynor, benno.lossin, boqun.feng, a.hindborg,
gary, bjorn3_gh, tmgross, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis
On Tue, Mar 3, 2026 at 3:09 AM Jonathan Cameron
<jonathan.cameron@huawei.com> wrote:
>
> On Wed, 11 Feb 2026 13:29:15 +1000
> alistair23@gmail.com wrote:
>
> > From: Alistair Francis <alistair@alistair23.me>
> >
> > This is the initial commit of the Rust SPDM library. It is based on and
> > compatible with the C SPDM library in the kernel (lib/spdm).
> >
> > Signed-off-by: Alistair Francis <alistair@alistair23.me>
> The comments that follow are based on my very limited rust knowledge.
> Hence may be garbage.
>
> > ---
> > MAINTAINERS | 12 ++
> > include/linux/spdm.h | 39 ++++++
> > lib/Kconfig | 17 +++
> > lib/Makefile | 2 +
> > lib/rspdm/Makefile | 10 ++
> > lib/rspdm/consts.rs | 117 +++++++++++++++++
> > lib/rspdm/lib.rs | 119 +++++++++++++++++
> > lib/rspdm/state.rs | 220 ++++++++++++++++++++++++++++++++
> > lib/rspdm/validator.rs | 66 ++++++++++
> > rust/bindings/bindings_helper.h | 2 +
> > rust/kernel/error.rs | 3 +
> > 11 files changed, 607 insertions(+)
> > create mode 100644 include/linux/spdm.h
> > create mode 100644 lib/rspdm/Makefile
> > create mode 100644 lib/rspdm/consts.rs
> > create mode 100644 lib/rspdm/lib.rs
> > create mode 100644 lib/rspdm/state.rs
> > create mode 100644 lib/rspdm/validator.rs
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 149deedafe2c..a5c4ec16081c 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -23693,6 +23693,18 @@ M: Security Officers <security@kernel.org>
> > S: Supported
> > F: Documentation/process/security-bugs.rst
> >
> > +SECURITY PROTOCOL AND DATA MODEL (SPDM)
> > +M: Jonathan Cameron <jic23@kernel.org>
>
> Evil way to get me to learn rust. Ah well, I guess I can't put it
> off for ever.
It's a great excuse!
>
> > +M: Lukas Wunner <lukas@wunner.de>
> > +M: Alistair Francis <alistair@alistair23.me>
> > +L: linux-coco@lists.linux.dev
> > +L: linux-cxl@vger.kernel.org
> > +L: linux-pci@vger.kernel.org
> > +S: Maintained
> > +T: git git://git.kernel.org/pub/scm/linux/kernel/git/devsec/spdm.git
> > +F: include/linux/spdm.h
> > +F: lib/rspdm/
>
>
> > diff --git a/lib/rspdm/consts.rs b/lib/rspdm/consts.rs
> > new file mode 100644
> > index 000000000000..40ce60eba2f3
> > --- /dev/null
> > +++ b/lib/rspdm/consts.rs
> > @@ -0,0 +1,117 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +
> > +// Copyright (C) 2024 Western Digital
> > +
> > +//! Constants used by the library
> > +//!
> > +//! Rust implementation of the DMTF Security Protocol and Data Model (SPDM)
> > +//! <https://www.dmtf.org/dsp/DSP0274>
> > +
> > +pub(crate) const SPDM_REQ: u8 = 0x80;
> > +pub(crate) const SPDM_ERROR: u8 = 0x7f;
> > +
> > +#[expect(dead_code)]
> > +#[derive(Clone, Copy)]
> > +pub(crate) enum SpdmErrorCode {
> > + InvalidRequest = 0x01,
> > + InvalidSession = 0x02,
>
> This is reserved by the time we reach 1.2.1 and disappeared somewhere
> in the middle of 1.1.0 (present) and 1.1.2 (reserved)
> Probably good to leave some breadcrumbs for what spec versions could use
> this error code. That will reduce confusion for future readers.
> You have this info in the parsing code, but I'd like it here as well.
>
> > + Busy = 0x03,
> > + UnexpectedRequest = 0x04,
> > + Unspecified = 0x05,
> > + DecryptError = 0x06,
> > + UnsupportedRequest = 0x07,
> > + RequestInFlight = 0x08,
> > + InvalidResponseCode = 0x09,
> > + SessionLimitExceeded = 0x0a,
> > + SessionRequired = 0x0b,
> > + ResetRequired = 0x0c,
> > + ResponseTooLarge = 0x0d,
> > + RequestTooLarge = 0x0e,
> > + LargeResponse = 0x0f,
> > + MessageLost = 0x10,
> > + InvalidPolicy = 0x11,
> > + VersionMismatch = 0x41,
> > + ResponseNotReady = 0x42,
> > + RequestResynch = 0x43,
> > + OperationFailed = 0x44,
> > + NoPendingRequests = 0x45,
> > + VendorDefinedError = 0xff,
> > +}
> > +
> > +impl core::fmt::LowerHex for SpdmErrorCode {
> > + /// A debug print format for the SpdmSessionInfo struct
> > + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
> > + match self {
> > + SpdmErrorCode::InvalidRequest => {
> > + writeln!(f, "0x01")?;
>
> No way to get a string from an enum value in rust?
There is actually, this is unnecessary
> Having to check these against the enum above is a bit tedious.
>
> > + }
>
> > + }
> > + Ok(())
> > + }
> > +}
> > diff --git a/lib/rspdm/lib.rs b/lib/rspdm/lib.rs
> > new file mode 100644
> > index 000000000000..2bb716140e0a
> > --- /dev/null
> > +++ b/lib/rspdm/lib.rs
> > @@ -0,0 +1,119 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +
> > +// Copyright (C) 2024 Western Digital
> > +
> > +//! Top level library for SPDM
> > +//!
> > +//! Rust implementation of the DMTF Security Protocol and Data Model (SPDM)
> > +//! <https://www.dmtf.org/dsp/DSP0274>
> > +//!
> > +//! Top level library, including C compatible public functions to be called
> > +//! from other subsytems.
> > +//!
> > +//! This mimics the C SPDM implementation in the kernel
>
> The one that never gets merged if this goes according to plan ;)
>
>
>
> > diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
> > new file mode 100644
> > index 000000000000..68861f30e3fa
> > --- /dev/null
> > +++ b/lib/rspdm/state.rs
> > @@ -0,0 +1,220 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +
> > +// Copyright (C) 2024 Western Digital
> > +
> > +//! The `SpdmState` struct and implementation.
> > +//!
> > +//! Rust implementation of the DMTF Security Protocol and Data Model (SPDM)
> > +//! <https://www.dmtf.org/dsp/DSP0274>
> > +
> > +use core::ffi::c_void;
> > +use kernel::prelude::*;
> > +use kernel::{
> > + bindings,
> > + error::{code::EINVAL, to_result, Error},
> > + validate::Untrusted,
> > +};
> > +
> > +use crate::consts::{SpdmErrorCode, SPDM_ERROR, SPDM_REQ};
> > +use crate::validator::{SpdmErrorRsp, SpdmHeader};
> > +
> > +/// The current SPDM session state for a device. Based on the
> > +/// C `struct spdm_state`.
> > +///
> > +/// `dev`: Responder device. Used for error reporting and passed to @transport.
> > +/// `transport`: Transport function to perform one message exchange.
> > +/// `transport_priv`: Transport private data.
> > +/// `transport_sz`: Maximum message size the transport is capable of (in bytes).
> > +/// Used as DataTransferSize in GET_CAPABILITIES exchange.
> > +/// `keyring`: Keyring against which to check the first certificate in
> > +/// responder's certificate chain.
>
> Given the discussions, seems this will go away (for now anyway).
>
> > +/// `validate`: Function to validate additional leaf certificate requirements.
> > +///
> > +/// `version`: Maximum common supported version of requester and responder.
> > +/// Negotiated during GET_VERSION exchange.
> > +#[expect(dead_code)]
> > +pub struct SpdmState {
> > + pub(crate) dev: *mut bindings::device,
> > + pub(crate) transport: bindings::spdm_transport,
> > + pub(crate) transport_priv: *mut c_void,
> > + pub(crate) transport_sz: u32,
> > + pub(crate) keyring: *mut bindings::key,
> > + pub(crate) validate: bindings::spdm_validate,
> > +
> > + // Negotiated state
> > + pub(crate) version: u8,
> > +}
> > +
> > +impl SpdmState {
> > + pub(crate) fn new(
> > + dev: *mut bindings::device,
> > + transport: bindings::spdm_transport,
> > + transport_priv: *mut c_void,
> > + transport_sz: u32,
> > + keyring: *mut bindings::key,
> > + validate: bindings::spdm_validate,
> > + ) -> Self {
> > + SpdmState {
> > + dev,
> > + transport,
> > + transport_priv,
> > + transport_sz,
> > + keyring,
> > + validate,
> > + version: 0x10,
> > + }
> > + }
>
> > +
> > + /// Start a SPDM exchange
> > + ///
> > + /// The data in `request_buf` is sent to the device and the response is
> > + /// stored in `response_buf`.
> > + pub(crate) fn spdm_exchange(
> > + &self,
> > + request_buf: &mut [u8],
> > + response_buf: &mut [u8],
> > + ) -> Result<i32, Error> {
> > + let header_size = core::mem::size_of::<SpdmHeader>();
> > + let request: &mut SpdmHeader = Untrusted::new_mut(request_buf).validate_mut()?;
>
> Why are we treating the request, which doesn't come from the device as untrusted?
> Just for convenience on checking we formatted it right at the upper levels or is
> the idea that might ultimately be coming from userspace?
Just for convenience, it's just a easy way to convert it to a
`SpdmHeader` and perform some simple sanity checks. Your right it's
not actually untrusted
Alistair
>
> > + let response: &SpdmHeader = Untrusted::new_ref(response_buf).validate()?;
> > +
> > + let transport_function = self.transport.ok_or(EINVAL)?;
> > + // SAFETY: `transport_function` is provided by the new(), we are
> > + // calling the function.
> > + let length = unsafe {
> > + transport_function(
> > + self.transport_priv,
> > + self.dev,
> > + request_buf.as_ptr() as *const c_void,
> > + request_buf.len(),
> > + response_buf.as_mut_ptr() as *mut c_void,
> > + response_buf.len(),
> > + ) as i32
> > + };
> > + to_result(length)?;
> > +
> > + if (length as usize) < header_size {
> > + return Ok(length); // Truncated response is handled by callers
> > + }
> > + if response.code == SPDM_ERROR {
> > + if length as usize >= core::mem::size_of::<SpdmErrorRsp>() {
> > + // SAFETY: The response buffer will be at least as large as
> > + // `SpdmErrorRsp` so we can cast the buffer to `SpdmErrorRsp` which
> > + // is a packed struct.
> > + self.spdm_err(unsafe { &*(response_buf.as_ptr() as *const SpdmErrorRsp) })?;
> > + } else {
> > + return Err(EINVAL);
> > + }
> > + }
> > +
> > + if response.code != request.code & !SPDM_REQ {
> > + pr_err!(
> > + "Response code {:#x} does not match request code {:#x}\n",
> > + response.code,
> > + request.code
> > + );
> > + to_result(-(bindings::EPROTO as i32))?;
> > + }
> > +
> > + Ok(length)
> > + }
> > +}
> > diff --git a/lib/rspdm/validator.rs b/lib/rspdm/validator.rs
> > new file mode 100644
> > index 000000000000..a0a3a2f46952
> > --- /dev/null
> > +++ b/lib/rspdm/validator.rs
> > @@ -0,0 +1,66 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +
> > +// Copyright (C) 2024 Western Digital
> > +
> > +//! Related structs and their Validate implementations.
> > +//!
> > +//! Rust implementation of the DMTF Security Protocol and Data Model (SPDM)
> > +//! <https://www.dmtf.org/dsp/DSP0274>
> > +
> > +use crate::consts::SpdmErrorCode;
> > +use core::mem;
> > +use kernel::prelude::*;
> > +use kernel::{
> > + error::{code::EINVAL, Error},
> > + validate::{Unvalidated, Validate},
> > +};
> > +
> > +#[repr(C, packed)]
> > +pub(crate) struct SpdmHeader {
> > + pub(crate) version: u8,
> > + pub(crate) code: u8, /* RequestResponseCode */
> > + pub(crate) param1: u8,
> > + pub(crate) param2: u8,
> > +}
> > +
> > +impl Validate<&Unvalidated<[u8]>> for &SpdmHeader {
> > + type Err = Error;
> > +
> > + fn validate(unvalidated: &Unvalidated<[u8]>) -> Result<Self, Self::Err> {
> > + let raw = unvalidated.raw();
> > + if raw.len() < mem::size_of::<SpdmHeader>() {
> > + return Err(EINVAL);
> > + }
> > +
> > + let ptr = raw.as_ptr();
> > + // CAST: `SpdmHeader` only contains integers and has `repr(C)`.
> > + let ptr = ptr.cast::<SpdmHeader>();
> > + // SAFETY: `ptr` came from a reference and the cast above is valid.
> > + Ok(unsafe { &*ptr })
> > + }
> > +}
> > +
> > +impl Validate<&mut Unvalidated<[u8]>> for &mut SpdmHeader {
> > + type Err = Error;
> > +
> > + fn validate(unvalidated: &mut Unvalidated<[u8]>) -> Result<Self, Self::Err> {
> > + let raw = unvalidated.raw_mut();
> > + if raw.len() < mem::size_of::<SpdmHeader>() {
> > + return Err(EINVAL);
> > + }
> > +
> > + let ptr = raw.as_mut_ptr();
> > + // CAST: `SpdmHeader` only contains integers and has `repr(C)`.
> > + let ptr = ptr.cast::<SpdmHeader>();
> > + // SAFETY: `ptr` came from a reference and the cast above is valid.
> > + Ok(unsafe { &mut *ptr })
> > + }
> > +}
> > +
> > +#[repr(C, packed)]
> > +pub(crate) struct SpdmErrorRsp {
> > + pub(crate) version: u8,
> > + pub(crate) code: u8,
>
> Maybe document here that this will always be SPDM_ERROR 0x7F
>
>
> > + pub(crate) error_code: SpdmErrorCode,
> > + pub(crate) error_data: u8,
> > +}
> > diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
> > index 0075c4b62c29..5043eee2a8d6 100644
> > --- a/rust/bindings/bindings_helper.h
> > +++ b/rust/bindings/bindings_helper.h
> > @@ -84,7 +84,9 @@
> > #include <linux/slab.h>
> > #include <linux/task_work.h>
> > #include <linux/tracepoint.h>
> > +#include <linux/spdm.h>
>
> Smells like this is alphabetical order. So move it up a bit?
>
> > #include <linux/usb.h>
> > +#include <linux/uaccess.h>
> > #include <linux/wait.h>
> > #include <linux/workqueue.h>
> > #include <linux/xarray.h>
>
^ permalink raw reply [flat|nested] 99+ messages in thread
* [RFC v3 09/27] PCI/CMA: Authenticate devices on enumeration
2026-02-11 3:29 [RFC v3 00/27] lib: Rust implementation of SPDM alistair23
` (7 preceding siblings ...)
2026-02-11 3:29 ` [RFC v3 08/27] lib: rspdm: Initial commit of Rust SPDM alistair23
@ 2026-02-11 3:29 ` alistair23
2026-02-16 4:25 ` Aksh Garg
2026-02-11 3:29 ` [RFC v3 10/27] PCI/CMA: Validate Subject Alternative Name in certificates alistair23
` (19 subsequent siblings)
28 siblings, 1 reply; 99+ messages in thread
From: alistair23 @ 2026-02-11 3:29 UTC (permalink / raw)
To: bhelgaas, lukas, rust-for-linux, akpm, linux-pci,
Jonathan.Cameron, linux-cxl, linux-kernel
Cc: alex.gaynor, benno.lossin, boqun.feng, a.hindborg, gary,
bjorn3_gh, tmgross, alistair23, ojeda, wilfred.mallawa, aliceryhl
From: Jonathan Cameron <Jonathan.Cameron@huawei.com>
Component Measurement and Authentication (CMA, PCIe r6.2 sec 6.31)
allows for measurement and authentication of PCIe devices. It is
based on the Security Protocol and Data Model specification (SPDM,
https://www.dmtf.org/dsp/DSP0274).
CMA-SPDM in turn forms the basis for Integrity and Data Encryption
(IDE, PCIe r6.2 sec 6.33) because the key material used by IDE is
transmitted over a CMA-SPDM session.
As a first step, authenticate CMA-capable devices on enumeration.
A subsequent commit will expose the result in sysfs.
When allocating SPDM session state with spdm_create(), the maximum SPDM
message length needs to be passed. Make the PCI_DOE_MAX_LENGTH macro
public and calculate the maximum payload length from it.
Credits: Jonathan wrote a proof-of-concept of this CMA implementation.
Lukas reworked it for upstream. Wilfred contributed fixes for issues
discovered during testing.
Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
Co-developed-by: Wilfred Mallawa <wilfred.mallawa@wdc.com>
Signed-off-by: Wilfred Mallawa <wilfred.mallawa@wdc.com>
Co-developed-by: Lukas Wunner <lukas@wunner.de>
Signed-off-by: Lukas Wunner <lukas@wunner.de>
---
MAINTAINERS | 1 +
drivers/pci/Kconfig | 16 +++++++
drivers/pci/Makefile | 2 +
drivers/pci/cma.c | 101 ++++++++++++++++++++++++++++++++++++++++
drivers/pci/doe.c | 3 --
drivers/pci/pci.h | 8 ++++
drivers/pci/probe.c | 1 +
drivers/pci/remove.c | 1 +
include/linux/pci-doe.h | 4 ++
include/linux/pci.h | 4 ++
10 files changed, 138 insertions(+), 3 deletions(-)
create mode 100644 drivers/pci/cma.c
diff --git a/MAINTAINERS b/MAINTAINERS
index a5c4ec16081c..58898260fde8 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -23702,6 +23702,7 @@ L: linux-cxl@vger.kernel.org
L: linux-pci@vger.kernel.org
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/
diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig
index e3f848ffb52a..7ea403799d78 100644
--- a/drivers/pci/Kconfig
+++ b/drivers/pci/Kconfig
@@ -125,6 +125,22 @@ config PCI_ATS
config PCI_IDE
bool
+config PCI_CMA
+ bool "Component Measurement and Authentication (CMA-SPDM)"
+ select CRYPTO_ECDSA
+ select CRYPTO_RSA
+ select CRYPTO_SHA256
+ select CRYPTO_SHA512
+ select PCI_DOE
+ select RSPDM
+ help
+ Authenticate devices on enumeration per PCIe r6.2 sec 6.31.
+ A PCI DOE mailbox is used as transport for DMTF SPDM based
+ authentication, measurement and secure channel establishment.
+
+config PCI_DOE
+ bool
+
config PCI_TSM
bool "PCI TSM: Device security protocol support"
select PCI_IDE
diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile
index e10cfe5a280b..f026f5dbb938 100644
--- a/drivers/pci/Makefile
+++ b/drivers/pci/Makefile
@@ -40,6 +40,8 @@ obj-$(CONFIG_PCI_DYNAMIC_OF_NODES) += of_property.o
obj-$(CONFIG_PCI_NPEM) += npem.o
obj-$(CONFIG_PCIE_TPH) += tph.o
+obj-$(CONFIG_PCI_CMA) += cma.o
+
# Endpoint library must be initialized before its users
obj-$(CONFIG_PCI_ENDPOINT) += endpoint/
diff --git a/drivers/pci/cma.c b/drivers/pci/cma.c
new file mode 100644
index 000000000000..7463cd1179f0
--- /dev/null
+++ b/drivers/pci/cma.c
@@ -0,0 +1,101 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Component Measurement and Authentication (CMA-SPDM, PCIe r6.2 sec 6.31)
+ *
+ * Copyright (C) 2021 Huawei
+ * Jonathan Cameron <Jonathan.Cameron@huawei.com>
+ *
+ * Copyright (C) 2022-24 Intel Corporation
+ */
+
+#define dev_fmt(fmt) "CMA: " fmt
+
+#include <linux/pci.h>
+#include <linux/pci-doe.h>
+#include <linux/pm_runtime.h>
+#include <linux/spdm.h>
+
+#include "pci.h"
+
+/* Keyring that userspace can poke certs into */
+static struct key *pci_cma_keyring;
+
+#define PCI_DOE_FEATURE_CMA 1
+
+static ssize_t pci_doe_transport(void *priv, struct device *dev,
+ const void *request, size_t request_sz,
+ void *response, size_t response_sz)
+{
+ struct pci_doe_mb *doe = priv;
+ ssize_t rc;
+
+ /*
+ * CMA-SPDM operation in non-D0 states is optional (PCIe r6.2
+ * sec 6.31.3). The spec does not define a way to determine
+ * if it's supported, so resume to D0 unconditionally.
+ */
+ rc = pm_runtime_resume_and_get(dev);
+ if (rc)
+ return rc;
+
+ rc = pci_doe(doe, PCI_VENDOR_ID_PCI_SIG, PCI_DOE_FEATURE_CMA,
+ request, request_sz, response, response_sz);
+
+ pm_runtime_put(dev);
+
+ return rc;
+}
+
+void pci_cma_init(struct pci_dev *pdev)
+{
+ struct pci_doe_mb *doe;
+
+ if (IS_ERR(pci_cma_keyring))
+ return;
+
+ if (!pci_is_pcie(pdev))
+ return;
+
+ doe = pci_find_doe_mailbox(pdev, PCI_VENDOR_ID_PCI_SIG,
+ PCI_DOE_FEATURE_CMA);
+ if (!doe)
+ return;
+
+ pdev->spdm_state = spdm_create(&pdev->dev, pci_doe_transport, doe,
+ PCI_DOE_MAX_PAYLOAD, pci_cma_keyring,
+ NULL);
+ if (!pdev->spdm_state)
+ return;
+
+ /*
+ * Keep spdm_state allocated even if initial authentication fails
+ * to allow for provisioning of certificates and reauthentication.
+ */
+ spdm_authenticate(pdev->spdm_state);
+}
+
+void pci_cma_destroy(struct pci_dev *pdev)
+{
+ if (!pdev->spdm_state)
+ return;
+
+ spdm_destroy(pdev->spdm_state);
+}
+
+__init static int pci_cma_keyring_init(void)
+{
+ pci_cma_keyring = keyring_alloc(".cma", KUIDT_INIT(0), KGIDT_INIT(0),
+ current_cred(),
+ (KEY_POS_ALL & ~KEY_POS_SETATTR) |
+ KEY_USR_VIEW | KEY_USR_READ |
+ KEY_USR_WRITE | KEY_USR_SEARCH,
+ KEY_ALLOC_NOT_IN_QUOTA |
+ KEY_ALLOC_SET_KEEP, NULL, NULL);
+ if (IS_ERR(pci_cma_keyring)) {
+ pr_err("PCI: Could not allocate .cma keyring\n");
+ return PTR_ERR(pci_cma_keyring);
+ }
+
+ return 0;
+}
+arch_initcall(pci_cma_keyring_init);
diff --git a/drivers/pci/doe.c b/drivers/pci/doe.c
index 62be9c8dbc52..344e01496a8e 100644
--- a/drivers/pci/doe.c
+++ b/drivers/pci/doe.c
@@ -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
*
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index 0e67014aa001..1ab1a39e8df3 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -599,6 +599,14 @@ static inline void pci_doe_destroy(struct pci_dev *pdev) { }
static inline void pci_doe_disconnected(struct pci_dev *pdev) { }
#endif
+#ifdef CONFIG_PCI_CMA
+void pci_cma_init(struct pci_dev *pdev);
+void pci_cma_destroy(struct pci_dev *pdev);
+#else
+static inline void pci_cma_init(struct pci_dev *pdev) { }
+static inline void pci_cma_destroy(struct pci_dev *pdev) { }
+#endif
+
#ifdef CONFIG_PCI_NPEM
void pci_npem_create(struct pci_dev *dev);
void pci_npem_remove(struct pci_dev *dev);
diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c
index 41183aed8f5d..665532a07cfc 100644
--- a/drivers/pci/probe.c
+++ b/drivers/pci/probe.c
@@ -2706,6 +2706,7 @@ static void pci_init_capabilities(struct pci_dev *dev)
pci_rebar_init(dev); /* Resizable BAR */
pci_dev3_init(dev); /* Device 3 capabilities */
pci_ide_init(dev); /* Link Integrity and Data Encryption */
+ pci_cma_init(dev); /* Component Measurement & Auth */
pcie_report_downtraining(dev);
pci_init_reset_methods(dev);
diff --git a/drivers/pci/remove.c b/drivers/pci/remove.c
index 417a9ea59117..b98d96919089 100644
--- a/drivers/pci/remove.c
+++ b/drivers/pci/remove.c
@@ -69,6 +69,7 @@ static void pci_destroy_dev(struct pci_dev *dev)
list_del(&dev->bus_list);
up_write(&pci_bus_sem);
+ pci_cma_destroy(dev);
pci_doe_destroy(dev);
pci_ide_destroy(dev);
pcie_aspm_exit_link_state(dev);
diff --git a/include/linux/pci-doe.h b/include/linux/pci-doe.h
index bd4346a7c4e7..7540396336de 100644
--- a/include/linux/pci-doe.h
+++ b/include/linux/pci-doe.h
@@ -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);
diff --git a/include/linux/pci.h b/include/linux/pci.h
index 9357e9b00e1c..7384846ade19 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -39,6 +39,7 @@
#include <linux/io.h>
#include <linux/resource_ext.h>
#include <linux/msi_api.h>
+#include <linux/spdm.h>
#include <uapi/linux/pci.h>
#include <linux/pci_ids.h>
@@ -542,6 +543,9 @@ struct pci_dev {
#ifdef CONFIG_PCI_DOE
struct xarray doe_mbs; /* Data Object Exchange mailboxes */
#endif
+#ifdef CONFIG_PCI_CMA
+ struct spdm_state *spdm_state; /* Security Protocol and Data Model */
+#endif
#ifdef CONFIG_PCI_NPEM
struct npem *npem; /* Native PCIe Enclosure Management */
#endif
--
2.52.0
^ permalink raw reply related [flat|nested] 99+ messages in thread* Re: [RFC v3 09/27] PCI/CMA: Authenticate devices on enumeration
2026-02-11 3:29 ` [RFC v3 09/27] PCI/CMA: Authenticate devices on enumeration alistair23
@ 2026-02-16 4:25 ` Aksh Garg
0 siblings, 0 replies; 99+ messages in thread
From: Aksh Garg @ 2026-02-16 4:25 UTC (permalink / raw)
To: alistair23, bhelgaas, lukas, rust-for-linux, akpm, linux-pci,
Jonathan.Cameron, linux-cxl, linux-kernel
Cc: alex.gaynor, benno.lossin, boqun.feng, a.hindborg, gary,
bjorn3_gh, tmgross, ojeda, wilfred.mallawa, aliceryhl
On 11/02/26 08:59, alistair23@gmail.com wrote:
> diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig
> index e3f848ffb52a..7ea403799d78 100644
> --- a/drivers/pci/Kconfig
> +++ b/drivers/pci/Kconfig
> @@ -125,6 +125,22 @@ config PCI_ATS
> config PCI_IDE
> bool
>
> +config PCI_CMA
> + bool "Component Measurement and Authentication (CMA-SPDM)"
> + select CRYPTO_ECDSA
> + select CRYPTO_RSA
> + select CRYPTO_SHA256
> + select CRYPTO_SHA512
> + select PCI_DOE
> + select RSPDM
> + help
> + Authenticate devices on enumeration per PCIe r6.2 sec 6.31.
> + A PCI DOE mailbox is used as transport for DMTF SPDM based
> + authentication, measurement and secure channel establishment.
> +
> +config PCI_DOE
> + bool
> +
config PCI_DOE is already in this Kconfig file just below config PCI_TSM
> config PCI_TSM
> bool "PCI TSM: Device security protocol support"
> select PCI_IDE
> diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile
> index e10cfe5a280b..f026f5dbb938 100644
> --- a/drivers/pci/Makefile
> +++ b/drivers/pci/Makefile
> @@ -40,6 +40,8 @@ obj-$(CONFIG_PCI_DYNAMIC_OF_NODES) += of_property.o
> obj-$(CONFIG_PCI_NPEM) += npem.o
> obj-$(CONFIG_PCIE_TPH) += tph.o
>
> +obj-$(CONFIG_PCI_CMA) += cma.o
> +
> # Endpoint library must be initialized before its users
> obj-$(CONFIG_PCI_ENDPOINT) += endpoint/
>
> diff --git a/drivers/pci/cma.c b/drivers/pci/cma.c
> new file mode 100644
> index 000000000000..7463cd1179f0
> --- /dev/null
> +++ b/drivers/pci/cma.c
> @@ -0,0 +1,101 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Component Measurement and Authentication (CMA-SPDM, PCIe r6.2 sec 6.31)
> + *
> + * Copyright (C) 2021 Huawei
> + * Jonathan Cameron <Jonathan.Cameron@huawei.com>
> + *
> + * Copyright (C) 2022-24 Intel Corporation
> + */
> +
> +#define dev_fmt(fmt) "CMA: " fmt
> +
> +#include <linux/pci.h>
> +#include <linux/pci-doe.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/spdm.h>
> +
> +#include "pci.h"
> +
> +/* Keyring that userspace can poke certs into */
> +static struct key *pci_cma_keyring;
> +
> +#define PCI_DOE_FEATURE_CMA 1
This macro is already present in <linux/pci-doe.h>, which have been
included in this file.
Regards,
Aksh Garg
^ permalink raw reply [flat|nested] 99+ messages in thread
* [RFC v3 10/27] PCI/CMA: Validate Subject Alternative Name in certificates
2026-02-11 3:29 [RFC v3 00/27] lib: Rust implementation of SPDM alistair23
` (8 preceding siblings ...)
2026-02-11 3:29 ` [RFC v3 09/27] PCI/CMA: Authenticate devices on enumeration alistair23
@ 2026-02-11 3:29 ` alistair23
2026-02-11 3:29 ` [RFC v3 11/27] PCI/CMA: Reauthenticate devices on reset and resume alistair23
` (18 subsequent siblings)
28 siblings, 0 replies; 99+ messages in thread
From: alistair23 @ 2026-02-11 3:29 UTC (permalink / raw)
To: bhelgaas, lukas, rust-for-linux, akpm, linux-pci,
Jonathan.Cameron, linux-cxl, linux-kernel
Cc: alex.gaynor, benno.lossin, boqun.feng, a.hindborg, gary,
bjorn3_gh, tmgross, alistair23, ojeda, wilfred.mallawa, aliceryhl
From: Lukas Wunner <lukas@wunner.de>
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.
Signed-off-by: Lukas Wunner <lukas@wunner.de>
Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com> # except ASN.1
---
drivers/pci/Makefile | 4 +-
drivers/pci/cma.asn1 | 41 ++++++++++++
drivers/pci/cma.c | 123 ++++++++++++++++++++++++++++++++++-
include/linux/oid_registry.h | 3 +
4 files changed, 169 insertions(+), 2 deletions(-)
create mode 100644 drivers/pci/cma.asn1
diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile
index f026f5dbb938..2fc161e15670 100644
--- a/drivers/pci/Makefile
+++ b/drivers/pci/Makefile
@@ -40,7 +40,9 @@ obj-$(CONFIG_PCI_DYNAMIC_OF_NODES) += of_property.o
obj-$(CONFIG_PCI_NPEM) += npem.o
obj-$(CONFIG_PCIE_TPH) += tph.o
-obj-$(CONFIG_PCI_CMA) += cma.o
+obj-$(CONFIG_PCI_CMA) += cma.o cma.asn1.o
+$(obj)/cma.o: $(obj)/cma.asn1.h
+$(obj)/cma.asn1.o: $(obj)/cma.asn1.c $(obj)/cma.asn1.h
# Endpoint library must be initialized before its users
obj-$(CONFIG_PCI_ENDPOINT) += endpoint/
diff --git a/drivers/pci/cma.asn1 b/drivers/pci/cma.asn1
new file mode 100644
index 000000000000..da41421d4085
--- /dev/null
+++ b/drivers/pci/cma.asn1
@@ -0,0 +1,41 @@
+-- SPDX-License-Identifier: BSD-3-Clause
+--
+-- Component Measurement and Authentication (CMA-SPDM, PCIe r6.1 sec 6.31.3)
+-- X.509 Subject Alternative Name (RFC 5280 sec 4.2.1.6)
+--
+-- Copyright (C) 2008 IETF Trust and the persons identified as authors
+-- of the code
+--
+-- https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.6
+--
+-- The ASN.1 module in RFC 5280 appendix A.1 uses EXPLICIT TAGS whereas the one
+-- in appendix A.2 uses IMPLICIT TAGS. The kernel's simplified asn1_compiler.c
+-- always uses EXPLICIT TAGS, hence this ASN.1 module differs from RFC 5280 in
+-- that it adds IMPLICIT to definitions from appendix A.2 (such as GeneralName)
+-- and omits EXPLICIT in those definitions.
+
+SubjectAltName ::= GeneralNames
+
+GeneralNames ::= SEQUENCE OF GeneralName
+
+GeneralName ::= CHOICE {
+ otherName [0] IMPLICIT OtherName,
+ rfc822Name [1] IMPLICIT IA5String,
+ dNSName [2] IMPLICIT IA5String,
+ x400Address [3] ANY,
+ directoryName [4] ANY,
+ ediPartyName [5] IMPLICIT EDIPartyName,
+ uniformResourceIdentifier [6] IMPLICIT IA5String,
+ iPAddress [7] IMPLICIT OCTET STRING,
+ registeredID [8] IMPLICIT OBJECT IDENTIFIER
+ }
+
+OtherName ::= SEQUENCE {
+ type-id OBJECT IDENTIFIER ({ pci_cma_note_oid }),
+ value [0] ANY ({ pci_cma_note_san })
+ }
+
+EDIPartyName ::= SEQUENCE {
+ nameAssigner [0] ANY OPTIONAL,
+ partyName [1] ANY
+ }
diff --git a/drivers/pci/cma.c b/drivers/pci/cma.c
index 7463cd1179f0..e974d489c7a2 100644
--- a/drivers/pci/cma.c
+++ b/drivers/pci/cma.c
@@ -10,16 +10,137 @@
#define dev_fmt(fmt) "CMA: " fmt
+#include <keys/x509-parser.h>
+#include <linux/asn1_decoder.h>
+#include <linux/oid_registry.h>
#include <linux/pci.h>
#include <linux/pci-doe.h>
#include <linux/pm_runtime.h>
#include <linux/spdm.h>
+#include "cma.asn1.h"
#include "pci.h"
/* Keyring that userspace can poke certs into */
static struct key *pci_cma_keyring;
+/*
+ * The spdm_requester.c library calls pci_cma_validate() to check requirements
+ * for Leaf Certificates per PCIe r6.1 sec 6.31.3.
+ *
+ * pci_cma_validate() parses the Subject Alternative Name using the ASN.1
+ * module cma.asn1, which calls pci_cma_note_oid() and pci_cma_note_san()
+ * to compare an OtherName against the expected name.
+ *
+ * The expected name is constructed beforehand by pci_cma_construct_san().
+ *
+ * PCIe r6.2 drops the Subject Alternative Name spec language, even though
+ * it continues to require "the leaf certificate to include the information
+ * typically used by system software for device driver binding". Use the
+ * Subject Alternative Name per PCIe r6.1 for lack of a replacement and
+ * because it is the de facto standard among existing products.
+ */
+#define CMA_NAME_MAX sizeof("Vendor=1234:Device=1234:CC=123456:" \
+ "REV=12:SSVID=1234:SSID=1234:1234567890123456")
+
+struct pci_cma_x509_context {
+ struct pci_dev *pdev;
+ u8 slot;
+ enum OID last_oid;
+ char expected_name[CMA_NAME_MAX];
+ unsigned int expected_len;
+ unsigned int found:1;
+};
+
+int pci_cma_note_oid(void *context, size_t hdrlen, unsigned char tag,
+ const void *value, size_t vlen)
+{
+ struct pci_cma_x509_context *ctx = context;
+
+ ctx->last_oid = look_up_OID(value, vlen);
+
+ return 0;
+}
+
+int pci_cma_note_san(void *context, size_t hdrlen, unsigned char tag,
+ const void *value, size_t vlen)
+{
+ struct pci_cma_x509_context *ctx = context;
+
+ /* These aren't the drOIDs we're looking for. */
+ if (ctx->last_oid != OID_CMA)
+ return 0;
+
+ if (tag != ASN1_UTF8STR ||
+ vlen != ctx->expected_len ||
+ memcmp(value, ctx->expected_name, vlen) != 0) {
+ pci_err(ctx->pdev, "Leaf certificate of slot %u "
+ "has invalid Subject Alternative Name\n", ctx->slot);
+ return -EINVAL;
+ }
+
+ ctx->found = true;
+
+ return 0;
+}
+
+static unsigned int pci_cma_construct_san(struct pci_dev *pdev, char *name)
+{
+ unsigned int len;
+ u64 serial;
+
+ len = snprintf(name, CMA_NAME_MAX,
+ "Vendor=%04hx:Device=%04hx:CC=%06x:REV=%02hhx",
+ pdev->vendor, pdev->device, pdev->class, pdev->revision);
+
+ if (pdev->hdr_type == PCI_HEADER_TYPE_NORMAL)
+ len += snprintf(name + len, CMA_NAME_MAX - len,
+ ":SSVID=%04hx:SSID=%04hx",
+ pdev->subsystem_vendor, pdev->subsystem_device);
+
+ serial = pci_get_dsn(pdev);
+ if (serial)
+ len += snprintf(name + len, CMA_NAME_MAX - len,
+ ":%016llx", serial);
+
+ return len;
+}
+
+static int pci_cma_validate(struct device *dev, u8 slot,
+ struct x509_certificate *leaf_cert)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ struct pci_cma_x509_context ctx;
+ int ret;
+
+ if (!leaf_cert->raw_san) {
+ pci_err(pdev, "Leaf certificate of slot %u "
+ "has no Subject Alternative Name\n", slot);
+ return -EINVAL;
+ }
+
+ ctx.pdev = pdev;
+ ctx.slot = slot;
+ ctx.found = false;
+ ctx.expected_len = pci_cma_construct_san(pdev, ctx.expected_name);
+
+ ret = asn1_ber_decoder(&cma_decoder, &ctx, leaf_cert->raw_san,
+ leaf_cert->raw_san_size);
+ if (ret == -EBADMSG || ret == -EMSGSIZE)
+ pci_err(pdev, "Leaf certificate of slot %u "
+ "has malformed Subject Alternative Name\n", slot);
+ if (ret < 0)
+ return ret;
+
+ if (!ctx.found) {
+ pci_err(pdev, "Leaf certificate of slot %u "
+ "has no OtherName with CMA OID\n", slot);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
#define PCI_DOE_FEATURE_CMA 1
static ssize_t pci_doe_transport(void *priv, struct device *dev,
@@ -63,7 +184,7 @@ void pci_cma_init(struct pci_dev *pdev)
pdev->spdm_state = spdm_create(&pdev->dev, pci_doe_transport, doe,
PCI_DOE_MAX_PAYLOAD, pci_cma_keyring,
- NULL);
+ pci_cma_validate);
if (!pdev->spdm_state)
return;
diff --git a/include/linux/oid_registry.h b/include/linux/oid_registry.h
index ebce402854de..113f4e802ec4 100644
--- a/include/linux/oid_registry.h
+++ b/include/linux/oid_registry.h
@@ -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
};
--
2.52.0
^ permalink raw reply related [flat|nested] 99+ messages in thread* [RFC v3 11/27] PCI/CMA: Reauthenticate devices on reset and resume
2026-02-11 3:29 [RFC v3 00/27] lib: Rust implementation of SPDM alistair23
` (9 preceding siblings ...)
2026-02-11 3:29 ` [RFC v3 10/27] PCI/CMA: Validate Subject Alternative Name in certificates alistair23
@ 2026-02-11 3:29 ` alistair23
2026-02-11 3:29 ` [RFC v3 12/27] lib: rspdm: Support SPDM get_version alistair23
` (17 subsequent siblings)
28 siblings, 0 replies; 99+ messages in thread
From: alistair23 @ 2026-02-11 3:29 UTC (permalink / raw)
To: bhelgaas, lukas, rust-for-linux, akpm, linux-pci,
Jonathan.Cameron, linux-cxl, linux-kernel
Cc: alex.gaynor, benno.lossin, boqun.feng, a.hindborg, gary,
bjorn3_gh, tmgross, alistair23, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis
From: Lukas Wunner <lukas@wunner.de>
CMA-SPDM state is lost when a device undergoes a Conventional Reset.
(But not a Function Level Reset, PCIe r6.2 sec 6.6.2.) A D3cold to D0
transition implies a Conventional Reset (PCIe r6.2 sec 5.8).
Thus, reauthenticate devices on resume from D3cold and on recovery from
a Secondary Bus Reset or DPC-induced Hot Reset.
The requirement to reauthenticate devices on resume from system sleep
(and in the future reestablish IDE encryption) is the reason why SPDM
needs to be in-kernel: During ->resume_noirq, which is the first phase
after system sleep, the PCI core walks down the hierarchy, puts each
device in D0, restores its config space and invokes the driver's
->resume_noirq callback. The driver is afforded the right to access the
device already during this phase.
To retain this usage model in the face of authentication and encryption,
CMA-SPDM reauthentication and IDE reestablishment must happen during the
->resume_noirq phase, before the driver's first access to the device.
The driver is thus afforded seamless authenticated and encrypted access
until the last moment before suspend and from the first moment after
resume.
During the ->resume_noirq phase, device interrupts are not yet enabled.
It is thus impossible to defer CMA-SPDM reauthentication to a user space
component on an attached disk or on the network, making an in-kernel
SPDM implementation mandatory.
The same catch-22 exists on recovery from a Conventional Reset: A user
space SPDM implementation might live on a device which underwent reset,
rendering its execution impossible.
Signed-off-by: Lukas Wunner <lukas@wunner.de>
Reviewed-by: Alistair Francis <alistair.francis@wdc.com>
---
drivers/pci/cma.c | 15 +++++++++++++++
drivers/pci/pci-driver.c | 1 +
drivers/pci/pci.c | 12 ++++++++++--
drivers/pci/pci.h | 2 ++
drivers/pci/pcie/err.c | 3 +++
5 files changed, 31 insertions(+), 2 deletions(-)
diff --git a/drivers/pci/cma.c b/drivers/pci/cma.c
index e974d489c7a2..f2c435b04b92 100644
--- a/drivers/pci/cma.c
+++ b/drivers/pci/cma.c
@@ -195,6 +195,21 @@ void pci_cma_init(struct pci_dev *pdev)
spdm_authenticate(pdev->spdm_state);
}
+/**
+ * pci_cma_reauthenticate() - Perform CMA-SPDM authentication again
+ * @pdev: Device to reauthenticate
+ *
+ * Can be called by drivers after device identity has mutated,
+ * e.g. after downloading firmware to an FPGA device.
+ */
+void pci_cma_reauthenticate(struct pci_dev *pdev)
+{
+ if (!pdev->spdm_state)
+ return;
+
+ spdm_authenticate(pdev->spdm_state);
+}
+
void pci_cma_destroy(struct pci_dev *pdev)
{
if (!pdev->spdm_state)
diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c
index a9590601835a..d1a574e21b0b 100644
--- a/drivers/pci/pci-driver.c
+++ b/drivers/pci/pci-driver.c
@@ -589,6 +589,7 @@ static void pci_pm_default_resume_early(struct pci_dev *pci_dev)
pci_pm_power_up_and_verify_state(pci_dev);
pci_restore_state(pci_dev);
pci_pme_restore(pci_dev);
+ pci_cma_reauthenticate(pci_dev);
}
static void pci_pm_bridge_power_up_actions(struct pci_dev *pci_dev)
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index 13dbb405dc31..7f03e07150a0 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -4887,8 +4887,16 @@ static int pci_reset_bus_function(struct pci_dev *dev, bool probe)
rc = pci_dev_reset_slot_function(dev, probe);
if (rc != -ENOTTY)
- return rc;
- return pci_parent_bus_reset(dev, probe);
+ goto done;
+
+ rc = pci_parent_bus_reset(dev, probe);
+
+done:
+ /* CMA-SPDM state is lost upon a Conventional Reset */
+ if (!probe)
+ pci_cma_reauthenticate(dev);
+
+ return rc;
}
static int cxl_reset_bus_function(struct pci_dev *dev, bool probe)
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index 1ab1a39e8df3..f87a4b7c92b8 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -602,9 +602,11 @@ static inline void pci_doe_disconnected(struct pci_dev *pdev) { }
#ifdef CONFIG_PCI_CMA
void pci_cma_init(struct pci_dev *pdev);
void pci_cma_destroy(struct pci_dev *pdev);
+void pci_cma_reauthenticate(struct pci_dev *pdev);
#else
static inline void pci_cma_init(struct pci_dev *pdev) { }
static inline void pci_cma_destroy(struct pci_dev *pdev) { }
+static inline void pci_cma_reauthenticate(struct pci_dev *pdev) { }
#endif
#ifdef CONFIG_PCI_NPEM
diff --git a/drivers/pci/pcie/err.c b/drivers/pci/pcie/err.c
index bebe4bc111d7..700663c38e31 100644
--- a/drivers/pci/pcie/err.c
+++ b/drivers/pci/pcie/err.c
@@ -151,6 +151,9 @@ static int report_slot_reset(struct pci_dev *dev, void *data)
pci_ers_result_t vote, *result = data;
const struct pci_error_handlers *err_handler;
+ /* CMA-SPDM state is lost upon a Conventional Reset */
+ pci_cma_reauthenticate(dev);
+
device_lock(&dev->dev);
pdrv = dev->driver;
if (!pci_dev_set_io_state(dev, pci_channel_io_normal) ||
--
2.52.0
^ permalink raw reply related [flat|nested] 99+ messages in thread* [RFC v3 12/27] lib: rspdm: Support SPDM get_version
2026-02-11 3:29 [RFC v3 00/27] lib: Rust implementation of SPDM alistair23
` (10 preceding siblings ...)
2026-02-11 3:29 ` [RFC v3 11/27] PCI/CMA: Reauthenticate devices on reset and resume alistair23
@ 2026-02-11 3:29 ` alistair23
2026-02-11 4:00 ` Wilfred Mallawa
2026-03-03 11:36 ` Jonathan Cameron
2026-02-11 3:29 ` [RFC v3 13/27] lib: rspdm: Support SPDM get_capabilities alistair23
` (16 subsequent siblings)
28 siblings, 2 replies; 99+ messages in thread
From: alistair23 @ 2026-02-11 3:29 UTC (permalink / raw)
To: bhelgaas, lukas, rust-for-linux, akpm, linux-pci,
Jonathan.Cameron, linux-cxl, linux-kernel
Cc: alex.gaynor, benno.lossin, boqun.feng, a.hindborg, gary,
bjorn3_gh, tmgross, alistair23, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis
From: Alistair Francis <alistair@alistair23.me>
Support the GET_VERSION SPDM command.
Signed-off-by: Alistair Francis <alistair@alistair23.me>
---
lib/rspdm/consts.rs | 17 +++++++++++
lib/rspdm/lib.rs | 6 +++-
lib/rspdm/state.rs | 58 ++++++++++++++++++++++++++++++++++--
lib/rspdm/validator.rs | 67 ++++++++++++++++++++++++++++++++++++++++++
4 files changed, 144 insertions(+), 4 deletions(-)
diff --git a/lib/rspdm/consts.rs b/lib/rspdm/consts.rs
index 40ce60eba2f3..38f48f0863e2 100644
--- a/lib/rspdm/consts.rs
+++ b/lib/rspdm/consts.rs
@@ -7,6 +7,20 @@
//! Rust implementation of the DMTF Security Protocol and Data Model (SPDM)
//! <https://www.dmtf.org/dsp/DSP0274>
+use crate::validator::SpdmHeader;
+use core::mem;
+
+/* SPDM versions supported by this implementation */
+pub(crate) const SPDM_VER_10: u8 = 0x10;
+#[allow(dead_code)]
+pub(crate) const SPDM_VER_11: u8 = 0x11;
+#[allow(dead_code)]
+pub(crate) const SPDM_VER_12: u8 = 0x12;
+pub(crate) const SPDM_VER_13: u8 = 0x13;
+
+pub(crate) const SPDM_MIN_VER: u8 = SPDM_VER_10;
+pub(crate) const SPDM_MAX_VER: u8 = SPDM_VER_13;
+
pub(crate) const SPDM_REQ: u8 = 0x80;
pub(crate) const SPDM_ERROR: u8 = 0x7f;
@@ -115,3 +129,6 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
Ok(())
}
}
+
+pub(crate) const SPDM_GET_VERSION: u8 = 0x84;
+pub(crate) const SPDM_GET_VERSION_LEN: usize = mem::size_of::<SpdmHeader>() + 255;
diff --git a/lib/rspdm/lib.rs b/lib/rspdm/lib.rs
index 2bb716140e0a..08abcbb21247 100644
--- a/lib/rspdm/lib.rs
+++ b/lib/rspdm/lib.rs
@@ -108,7 +108,11 @@
/// Return 0 on success or a negative errno. In particular, -EPROTONOSUPPORT
/// indicates authentication is not supported by the device.
#[no_mangle]
-pub unsafe extern "C" fn spdm_authenticate(_state: &'static mut SpdmState) -> c_int {
+pub unsafe extern "C" fn spdm_authenticate(state: &'static mut SpdmState) -> c_int {
+ if let Err(e) = state.get_version() {
+ return e.to_errno() as c_int;
+ }
+
0
}
diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
index 68861f30e3fa..3ad53ec05044 100644
--- a/lib/rspdm/state.rs
+++ b/lib/rspdm/state.rs
@@ -8,6 +8,7 @@
//! <https://www.dmtf.org/dsp/DSP0274>
use core::ffi::c_void;
+use core::slice::from_raw_parts_mut;
use kernel::prelude::*;
use kernel::{
bindings,
@@ -15,8 +16,10 @@
validate::Untrusted,
};
-use crate::consts::{SpdmErrorCode, SPDM_ERROR, SPDM_REQ};
-use crate::validator::{SpdmErrorRsp, SpdmHeader};
+use crate::consts::{
+ SpdmErrorCode, SPDM_ERROR, SPDM_GET_VERSION_LEN, SPDM_MAX_VER, SPDM_MIN_VER, SPDM_REQ,
+};
+use crate::validator::{GetVersionReq, GetVersionRsp, SpdmErrorRsp, SpdmHeader};
/// The current SPDM session state for a device. Based on the
/// C `struct spdm_state`.
@@ -61,7 +64,7 @@ pub(crate) fn new(
transport_sz,
keyring,
validate,
- version: 0x10,
+ version: SPDM_MIN_VER,
}
}
@@ -217,4 +220,53 @@ pub(crate) fn spdm_exchange(
Ok(length)
}
+
+ /// Negoiate a supported SPDM version and store the information
+ /// in the `SpdmState`.
+ pub(crate) fn get_version(&mut self) -> Result<(), Error> {
+ let mut request = GetVersionReq::default();
+ request.version = self.version;
+
+ // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
+ let request_buf = unsafe {
+ from_raw_parts_mut(
+ &mut request as *mut _ as *mut u8,
+ core::mem::size_of::<GetVersionReq>(),
+ )
+ };
+
+ let mut response_vec: KVec<u8> = KVec::with_capacity(SPDM_GET_VERSION_LEN, GFP_KERNEL)?;
+ // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
+ let response_buf =
+ unsafe { from_raw_parts_mut(response_vec.as_mut_ptr(), SPDM_GET_VERSION_LEN) };
+
+ let rc = self.spdm_exchange(request_buf, response_buf)?;
+
+ // SAFETY: `rc` is the length of data read, which will be smaller
+ // then the capacity of the vector
+ unsafe { response_vec.inc_len(rc as usize) };
+
+ let response: &mut GetVersionRsp = Untrusted::new_mut(&mut response_vec).validate_mut()?;
+
+ let mut foundver = false;
+ for i in 0..response.version_number_entry_count {
+ // Creating a reference on a packed struct will result in
+ // undefined behaviour, so we operate on the raw data directly
+ let unaligned = core::ptr::addr_of_mut!(response.version_number_entries) as *mut u16;
+ let addr = unaligned.wrapping_add(i as usize);
+ let version = (unsafe { core::ptr::read_unaligned::<u16>(addr) } >> 8) as u8;
+
+ if version >= self.version && version <= SPDM_MAX_VER {
+ self.version = version;
+ foundver = true;
+ }
+ }
+
+ if !foundver {
+ pr_err!("No common supported version\n");
+ to_result(-(bindings::EPROTO as i32))?;
+ }
+
+ Ok(())
+ }
}
diff --git a/lib/rspdm/validator.rs b/lib/rspdm/validator.rs
index a0a3a2f46952..f69be6aa6280 100644
--- a/lib/rspdm/validator.rs
+++ b/lib/rspdm/validator.rs
@@ -7,6 +7,7 @@
//! Rust implementation of the DMTF Security Protocol and Data Model (SPDM)
//! <https://www.dmtf.org/dsp/DSP0274>
+use crate::bindings::{__IncompleteArrayField, __le16};
use crate::consts::SpdmErrorCode;
use core::mem;
use kernel::prelude::*;
@@ -15,6 +16,8 @@
validate::{Unvalidated, Validate},
};
+use crate::consts::SPDM_GET_VERSION;
+
#[repr(C, packed)]
pub(crate) struct SpdmHeader {
pub(crate) version: u8,
@@ -64,3 +67,67 @@ pub(crate) struct SpdmErrorRsp {
pub(crate) error_code: SpdmErrorCode,
pub(crate) error_data: u8,
}
+
+#[repr(C, packed)]
+pub(crate) struct GetVersionReq {
+ pub(crate) version: u8,
+ pub(crate) code: u8,
+ pub(crate) param1: u8,
+ pub(crate) param2: u8,
+}
+
+impl Default for GetVersionReq {
+ fn default() -> Self {
+ GetVersionReq {
+ version: 0,
+ code: SPDM_GET_VERSION,
+ param1: 0,
+ param2: 0,
+ }
+ }
+}
+
+#[repr(C, packed)]
+pub(crate) struct GetVersionRsp {
+ pub(crate) version: u8,
+ pub(crate) code: u8,
+ param1: u8,
+ param2: u8,
+ reserved: u8,
+ pub(crate) version_number_entry_count: u8,
+ pub(crate) version_number_entries: __IncompleteArrayField<__le16>,
+}
+
+impl Validate<&mut Unvalidated<KVec<u8>>> for &mut GetVersionRsp {
+ type Err = Error;
+
+ fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> {
+ let raw = unvalidated.raw_mut();
+ if raw.len() < mem::size_of::<GetVersionRsp>() {
+ return Err(EINVAL);
+ }
+
+ let version_number_entries = *(raw.get(5).ok_or(ENOMEM))? as usize;
+ let total_expected_size = version_number_entries * 2 + 6;
+ if raw.len() < total_expected_size {
+ return Err(EINVAL);
+ }
+
+ let ptr = raw.as_mut_ptr();
+ // CAST: `GetVersionRsp` only contains integers and has `repr(C)`.
+ let ptr = ptr.cast::<GetVersionRsp>();
+ // SAFETY: `ptr` came from a reference and the cast above is valid.
+ let rsp: &mut GetVersionRsp = unsafe { &mut *ptr };
+
+ // Creating a reference on a packed struct will result in
+ // undefined behaviour, so we operate on the raw data directly
+ let unaligned = core::ptr::addr_of_mut!(rsp.version_number_entries) as *mut u16;
+ for version_offset in 0..version_number_entries {
+ let addr = unaligned.wrapping_add(version_offset);
+ let version = unsafe { core::ptr::read_unaligned::<u16>(addr) };
+ unsafe { core::ptr::write_unaligned::<u16>(addr, version.to_le()) }
+ }
+
+ Ok(rsp)
+ }
+}
--
2.52.0
^ permalink raw reply related [flat|nested] 99+ messages in thread* Re: [RFC v3 12/27] lib: rspdm: Support SPDM get_version
2026-02-11 3:29 ` [RFC v3 12/27] lib: rspdm: Support SPDM get_version alistair23
@ 2026-02-11 4:00 ` Wilfred Mallawa
2026-03-03 11:36 ` Jonathan Cameron
1 sibling, 0 replies; 99+ messages in thread
From: Wilfred Mallawa @ 2026-02-11 4:00 UTC (permalink / raw)
To: alistair23@gmail.com, akpm@linux-foundation.org,
linux-cxl@vger.kernel.org, Jonathan.Cameron@huawei.com,
linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org,
linux-pci@vger.kernel.org, lukas@wunner.de, bhelgaas@google.com
Cc: tmgross@umich.edu, benno.lossin@proton.me, alistair@alistair23.me,
gary@garyguo.net, a.hindborg@kernel.org, bjorn3_gh@protonmail.com,
boqun.feng@gmail.com, alex.gaynor@gmail.com, aliceryhl@google.com,
ojeda@kernel.org
This is cool! some minor comments inline.
>
> /// The current SPDM session state for a device. Based on the
> /// C `struct spdm_state`.
> @@ -61,7 +64,7 @@ pub(crate) fn new(
> transport_sz,
> keyring,
> validate,
> - version: 0x10,
> + version: SPDM_MIN_VER,
> }
> }
>
> @@ -217,4 +220,53 @@ pub(crate) fn spdm_exchange(
>
> Ok(length)
> }
> +
> + /// Negoiate a supported SPDM version and store the information
typo: s/Negoiate/Negotiate
> + /// in the `SpdmState`.
> + pub(crate) fn get_version(&mut self) -> Result<(), Error> {
> + let mut request = GetVersionReq::default();
> + request.version = self.version;
> +
> + // SAFETY: `request` is repr(C) and packed, so we can
> convert it to a slice
> + let request_buf = unsafe {
> + from_raw_parts_mut(
> + &mut request as *mut _ as *mut u8,
> + core::mem::size_of::<GetVersionReq>(),
> + )
> + };
> +
> + let mut response_vec: KVec<u8> =
> KVec::with_capacity(SPDM_GET_VERSION_LEN, GFP_KERNEL)?;
> + // SAFETY: `request` is repr(C) and packed, so we can
> convert it to a slice
The SAFETY comment here refers to `request` when setting up the
response buffer, seems unneeded?
Wilfred
^ permalink raw reply [flat|nested] 99+ messages in thread* Re: [RFC v3 12/27] lib: rspdm: Support SPDM get_version
2026-02-11 3:29 ` [RFC v3 12/27] lib: rspdm: Support SPDM get_version alistair23
2026-02-11 4:00 ` Wilfred Mallawa
@ 2026-03-03 11:36 ` Jonathan Cameron
2026-03-13 5:35 ` Alistair Francis
1 sibling, 1 reply; 99+ messages in thread
From: Jonathan Cameron @ 2026-03-03 11:36 UTC (permalink / raw)
To: alistair23
Cc: bhelgaas, lukas, rust-for-linux, akpm, linux-pci, linux-cxl,
linux-kernel, alex.gaynor, benno.lossin, boqun.feng, a.hindborg,
gary, bjorn3_gh, tmgross, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis
On Wed, 11 Feb 2026 13:29:19 +1000
alistair23@gmail.com wrote:
> From: Alistair Francis <alistair@alistair23.me>
>
> Support the GET_VERSION SPDM command.
>
> Signed-off-by: Alistair Francis <alistair@alistair23.me>
Hi Alistair,
Various minor comments inline. Biggest one is what I think
is some confusing le16 handling that to me is in the wrong place.
Jonathan
> ---
> lib/rspdm/consts.rs | 17 +++++++++++
> lib/rspdm/lib.rs | 6 +++-
> lib/rspdm/state.rs | 58 ++++++++++++++++++++++++++++++++++--
> lib/rspdm/validator.rs | 67 ++++++++++++++++++++++++++++++++++++++++++
> 4 files changed, 144 insertions(+), 4 deletions(-)
>
> diff --git a/lib/rspdm/consts.rs b/lib/rspdm/consts.rs
> index 40ce60eba2f3..38f48f0863e2 100644
> --- a/lib/rspdm/consts.rs
> +++ b/lib/rspdm/consts.rs
> @@ -115,3 +129,6 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
> Ok(())
> }
> }
> +
> +pub(crate) const SPDM_GET_VERSION: u8 = 0x84;
> +pub(crate) const SPDM_GET_VERSION_LEN: usize = mem::size_of::<SpdmHeader>() + 255;
I'd either add a comment on where the 255 comes from. Also do we have a U8_MAX definition
we can use? Or some way to get it from code?
> diff --git a/lib/rspdm/lib.rs b/lib/rspdm/lib.rs
> index 2bb716140e0a..08abcbb21247 100644
> --- a/lib/rspdm/lib.rs
> +++ b/lib/rspdm/lib.rs
> @@ -108,7 +108,11 @@
> /// Return 0 on success or a negative errno. In particular, -EPROTONOSUPPORT
> /// indicates authentication is not supported by the device.
> #[no_mangle]
> -pub unsafe extern "C" fn spdm_authenticate(_state: &'static mut SpdmState) -> c_int {
> +pub unsafe extern "C" fn spdm_authenticate(state: &'static mut SpdmState) -> c_int {
Is there more to this rename than it appears? Can it get pushed back to earlier patch?
> + if let Err(e) = state.get_version() {
> + return e.to_errno() as c_int;
> + }
> +
> 0
> }
>
> diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
> index 68861f30e3fa..3ad53ec05044 100644
> --- a/lib/rspdm/state.rs
> +++ b/lib/rspdm/state.rs
> @@ -8,6 +8,7 @@
> //! <https://www.dmtf.org/dsp/DSP0274>
>
> use core::ffi::c_void;
> +use core::slice::from_raw_parts_mut;
> use kernel::prelude::*;
> use kernel::{
> bindings,
> @@ -15,8 +16,10 @@
> validate::Untrusted,
> };
>
> -use crate::consts::{SpdmErrorCode, SPDM_ERROR, SPDM_REQ};
> -use crate::validator::{SpdmErrorRsp, SpdmHeader};
> +use crate::consts::{
> + SpdmErrorCode, SPDM_ERROR, SPDM_GET_VERSION_LEN, SPDM_MAX_VER, SPDM_MIN_VER, SPDM_REQ,
Is the Rust linter fussy about these being on one line? Feels like we'd
get less churn over time if we formatted this as one per line.
> +};
> +use crate::validator::{GetVersionReq, GetVersionRsp, SpdmErrorRsp, SpdmHeader};
>
> /// The current SPDM session state for a device. Based on the
> /// C `struct spdm_state`.
> @@ -61,7 +64,7 @@ pub(crate) fn new(
> transport_sz,
> keyring,
> validate,
> - version: 0x10,
> + version: SPDM_MIN_VER,
Pull that def back to earlier patch to keep the churn confined.
> }
> }
>
> @@ -217,4 +220,53 @@ pub(crate) fn spdm_exchange(
>
> Ok(length)
> }
> +
> + /// Negoiate a supported SPDM version and store the information
> + /// in the `SpdmState`.
> + pub(crate) fn get_version(&mut self) -> Result<(), Error> {
> + let mut request = GetVersionReq::default();
> + request.version = self.version;
> +
> + // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
> + let request_buf = unsafe {
> + from_raw_parts_mut(
> + &mut request as *mut _ as *mut u8,
> + core::mem::size_of::<GetVersionReq>(),
> + )
> + };
> +
> + let mut response_vec: KVec<u8> = KVec::with_capacity(SPDM_GET_VERSION_LEN, GFP_KERNEL)?;
> + // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
> + let response_buf =
> + unsafe { from_raw_parts_mut(response_vec.as_mut_ptr(), SPDM_GET_VERSION_LEN) };
> +
> + let rc = self.spdm_exchange(request_buf, response_buf)?;
> +
> + // SAFETY: `rc` is the length of data read, which will be smaller
> + // then the capacity of the vector
than?
> + unsafe { response_vec.inc_len(rc as usize) };
> +
> + let response: &mut GetVersionRsp = Untrusted::new_mut(&mut response_vec).validate_mut()?;
> +
> + let mut foundver = false;
> + for i in 0..response.version_number_entry_count {
> + // Creating a reference on a packed struct will result in
> + // undefined behaviour, so we operate on the raw data directly
> + let unaligned = core::ptr::addr_of_mut!(response.version_number_entries) as *mut u16;
Maybe pull that out of the loop like you do below?
> + let addr = unaligned.wrapping_add(i as usize);
> + let version = (unsafe { core::ptr::read_unaligned::<u16>(addr) } >> 8) as u8;
Given the endian conversion below, this is correct, but I think leaving it as the __le16 until
here is better. We could also just pull out the correct byte and ignore the other one.
It's a spec quirk that they decided to have it defined as a __le16 rather than u8[2] as
the fields are confined one byte or the other.
I wonder if we should also reject any alpha versions? I.e. check the bottom 4 bits
are 0.
> +
> + if version >= self.version && version <= SPDM_MAX_VER {
> + self.version = version;
> + foundver = true;
> + }
> + }
> +
> + if !foundver {
> + pr_err!("No common supported version\n");
> + to_result(-(bindings::EPROTO as i32))?;
> + }
> +
> + Ok(())
> + }
> }
> diff --git a/lib/rspdm/validator.rs b/lib/rspdm/validator.rs
> index a0a3a2f46952..f69be6aa6280 100644
> --- a/lib/rspdm/validator.rs
> +++ b/lib/rspdm/validator.rs
> @@ -7,6 +7,7 @@
> //! Rust implementation of the DMTF Security Protocol and Data Model (SPDM)
> //! <https://www.dmtf.org/dsp/DSP0274>
>
> +use crate::bindings::{__IncompleteArrayField, __le16};
> use crate::consts::SpdmErrorCode;
> use core::mem;
> use kernel::prelude::*;
> @@ -15,6 +16,8 @@
> validate::{Unvalidated, Validate},
> };
>
> +use crate::consts::SPDM_GET_VERSION;
> +
> #[repr(C, packed)]
> pub(crate) struct SpdmHeader {
> pub(crate) version: u8,
> @@ -64,3 +67,67 @@ pub(crate) struct SpdmErrorRsp {
> pub(crate) error_code: SpdmErrorCode,
> pub(crate) error_data: u8,
> }
> +
> +#[repr(C, packed)]
Why packed?
> +pub(crate) struct GetVersionReq {
> + pub(crate) version: u8,
> + pub(crate) code: u8,
> + pub(crate) param1: u8,
> + pub(crate) param2: u8,
> +}
> +
> +impl Default for GetVersionReq {
> + fn default() -> Self {
> + GetVersionReq {
> + version: 0,
Given this only takes one value we could default it to 0x10
(even though it's written above to keep the state machine in sync).
> + code: SPDM_GET_VERSION,
> + param1: 0,
> + param2: 0,
> + }
> + }
> +}
> +
> +#[repr(C, packed)]
> +pub(crate) struct GetVersionRsp {
> + pub(crate) version: u8,
> + pub(crate) code: u8,
> + param1: u8,
> + param2: u8,
> + reserved: u8,
> + pub(crate) version_number_entry_count: u8,
> + pub(crate) version_number_entries: __IncompleteArrayField<__le16>,
> +}
> +
> +impl Validate<&mut Unvalidated<KVec<u8>>> for &mut GetVersionRsp {
> + type Err = Error;
> +
> + fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> {
> + let raw = unvalidated.raw_mut();
> + if raw.len() < mem::size_of::<GetVersionRsp>() {
> + return Err(EINVAL);
> + }
I guess it probably belongs at the header validation point rather that here,
but can we also check the version matches what was requested. Possibly an
additional check here that it is 0x10 (as this one is a special case where
only that value is correct).
> +
> + let version_number_entries = *(raw.get(5).ok_or(ENOMEM))? as usize;
> + let total_expected_size = version_number_entries * 2 + 6;
Instead of 6 can we use mem::size_of::<GetVersionRsp>()?
Ideally something similar for the 2 as well even if it's a bit verbose.
(if I was lazy in the C code you get to make it better here :)
> + if raw.len() < total_expected_size {
> + return Err(EINVAL);
> + }
> +
> + let ptr = raw.as_mut_ptr();
> + // CAST: `GetVersionRsp` only contains integers and has `repr(C)`.
> + let ptr = ptr.cast::<GetVersionRsp>();
> + // SAFETY: `ptr` came from a reference and the cast above is valid.
> + let rsp: &mut GetVersionRsp = unsafe { &mut *ptr };
> +
> + // Creating a reference on a packed struct will result in
Silly question, but why is it packed? We might need to do this for some
structures in SPDM but do we have transports that mess up the alignment enough
that where the messages themselves don't need to be packed we have to mark
the structures so anyway? Probably good to document this somewhere.
> + // undefined behaviour, so we operate on the raw data directly
> + let unaligned = core::ptr::addr_of_mut!(rsp.version_number_entries) as *mut u16;
> + for version_offset in 0..version_number_entries {
> + let addr = unaligned.wrapping_add(version_offset);
> + let version = unsafe { core::ptr::read_unaligned::<u16>(addr) };
> + unsafe { core::ptr::write_unaligned::<u16>(addr, version.to_le()) }
I'd like to see a comment on why this seems to be doing an endian swap if we
are on big endian. Looking back at the c code, it has an endian swap but
only as part of the search for a version to use (finding the largest both
device and and kernel support).
Maybe I'm reading it wrong but isn't this putting cpu endian data into a structure
element that is of type __le16?
> + }
> +
> + Ok(rsp)
> + }
> +}
^ permalink raw reply [flat|nested] 99+ messages in thread* Re: [RFC v3 12/27] lib: rspdm: Support SPDM get_version
2026-03-03 11:36 ` Jonathan Cameron
@ 2026-03-13 5:35 ` Alistair Francis
2026-03-13 5:53 ` Miguel Ojeda
2026-03-16 17:16 ` Jonathan Cameron
0 siblings, 2 replies; 99+ messages in thread
From: Alistair Francis @ 2026-03-13 5:35 UTC (permalink / raw)
To: Jonathan Cameron
Cc: bhelgaas, lukas, rust-for-linux, akpm, linux-pci, linux-cxl,
linux-kernel, alex.gaynor, benno.lossin, boqun.feng, a.hindborg,
gary, bjorn3_gh, tmgross, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis
On Tue, Mar 3, 2026 at 9:36 PM Jonathan Cameron
<jonathan.cameron@huawei.com> wrote:
>
> On Wed, 11 Feb 2026 13:29:19 +1000
> alistair23@gmail.com wrote:
>
> > From: Alistair Francis <alistair@alistair23.me>
> >
> > Support the GET_VERSION SPDM command.
> >
> > Signed-off-by: Alistair Francis <alistair@alistair23.me>
> Hi Alistair,
>
> Various minor comments inline. Biggest one is what I think
> is some confusing le16 handling that to me is in the wrong place.
>
> Jonathan
>
> > ---
> > lib/rspdm/consts.rs | 17 +++++++++++
> > lib/rspdm/lib.rs | 6 +++-
> > lib/rspdm/state.rs | 58 ++++++++++++++++++++++++++++++++++--
> > lib/rspdm/validator.rs | 67 ++++++++++++++++++++++++++++++++++++++++++
> > 4 files changed, 144 insertions(+), 4 deletions(-)
> >
> > diff --git a/lib/rspdm/consts.rs b/lib/rspdm/consts.rs
> > index 40ce60eba2f3..38f48f0863e2 100644
> > --- a/lib/rspdm/consts.rs
> > +++ b/lib/rspdm/consts.rs
>
> > @@ -115,3 +129,6 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
> > Ok(())
> > }
> > }
> > +
> > +pub(crate) const SPDM_GET_VERSION: u8 = 0x84;
> > +pub(crate) const SPDM_GET_VERSION_LEN: usize = mem::size_of::<SpdmHeader>() + 255;
>
> I'd either add a comment on where the 255 comes from. Also do we have a U8_MAX definition
> we can use? Or some way to get it from code?
Fixed, can just use `u8::MAX`
>
> > diff --git a/lib/rspdm/lib.rs b/lib/rspdm/lib.rs
> > index 2bb716140e0a..08abcbb21247 100644
> > --- a/lib/rspdm/lib.rs
> > +++ b/lib/rspdm/lib.rs
> > @@ -108,7 +108,11 @@
> > /// Return 0 on success or a negative errno. In particular, -EPROTONOSUPPORT
> > /// indicates authentication is not supported by the device.
> > #[no_mangle]
> > -pub unsafe extern "C" fn spdm_authenticate(_state: &'static mut SpdmState) -> c_int {
> > +pub unsafe extern "C" fn spdm_authenticate(state: &'static mut SpdmState) -> c_int {
>
> Is there more to this rename than it appears? Can it get pushed back to earlier patch?
Rust will complain about `state` being unused, by adding a `_` to the
front we don't get the warning in the previous patch. So in this patch
I rename it back to `state` as it's now used.
This is a public function that matches the C implementation, hence the
arguments being fixed in the previous patch.
>
> > + if let Err(e) = state.get_version() {
> > + return e.to_errno() as c_int;
> > + }
> > +
> > 0
> > }
> >
> > diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
> > index 68861f30e3fa..3ad53ec05044 100644
> > --- a/lib/rspdm/state.rs
> > +++ b/lib/rspdm/state.rs
> > @@ -8,6 +8,7 @@
> > //! <https://www.dmtf.org/dsp/DSP0274>
> >
> > use core::ffi::c_void;
> > +use core::slice::from_raw_parts_mut;
> > use kernel::prelude::*;
> > use kernel::{
> > bindings,
> > @@ -15,8 +16,10 @@
> > validate::Untrusted,
> > };
> >
> > -use crate::consts::{SpdmErrorCode, SPDM_ERROR, SPDM_REQ};
> > -use crate::validator::{SpdmErrorRsp, SpdmHeader};
> > +use crate::consts::{
> > + SpdmErrorCode, SPDM_ERROR, SPDM_GET_VERSION_LEN, SPDM_MAX_VER, SPDM_MIN_VER, SPDM_REQ,
>
> Is the Rust linter fussy about these being on one line? Feels like we'd
> get less churn over time if we formatted this as one per line.
This is just how the linter formats it when I run ` LLVM=1 make
rustfmt`. I don't really want to be manually formatting things
>
> > +};
> > +use crate::validator::{GetVersionReq, GetVersionRsp, SpdmErrorRsp, SpdmHeader};
> >
> > /// The current SPDM session state for a device. Based on the
> > /// C `struct spdm_state`.
> > @@ -61,7 +64,7 @@ pub(crate) fn new(
> > transport_sz,
> > keyring,
> > validate,
> > - version: 0x10,
> > + version: SPDM_MIN_VER,
>
> Pull that def back to earlier patch to keep the churn confined.
Fixed
>
> > }
> > }
> >
> > @@ -217,4 +220,53 @@ pub(crate) fn spdm_exchange(
> >
> > Ok(length)
> > }
> > +
> > + /// Negoiate a supported SPDM version and store the information
> > + /// in the `SpdmState`.
> > + pub(crate) fn get_version(&mut self) -> Result<(), Error> {
> > + let mut request = GetVersionReq::default();
> > + request.version = self.version;
> > +
> > + // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
> > + let request_buf = unsafe {
> > + from_raw_parts_mut(
> > + &mut request as *mut _ as *mut u8,
> > + core::mem::size_of::<GetVersionReq>(),
> > + )
> > + };
> > +
> > + let mut response_vec: KVec<u8> = KVec::with_capacity(SPDM_GET_VERSION_LEN, GFP_KERNEL)?;
> > + // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
> > + let response_buf =
> > + unsafe { from_raw_parts_mut(response_vec.as_mut_ptr(), SPDM_GET_VERSION_LEN) };
> > +
> > + let rc = self.spdm_exchange(request_buf, response_buf)?;
> > +
> > + // SAFETY: `rc` is the length of data read, which will be smaller
> > + // then the capacity of the vector
>
> than?
Fixed
>
> > + unsafe { response_vec.inc_len(rc as usize) };
> > +
> > + let response: &mut GetVersionRsp = Untrusted::new_mut(&mut response_vec).validate_mut()?;
> > +
> > + let mut foundver = false;
> > + for i in 0..response.version_number_entry_count {
> > + // Creating a reference on a packed struct will result in
> > + // undefined behaviour, so we operate on the raw data directly
> > + let unaligned = core::ptr::addr_of_mut!(response.version_number_entries) as *mut u16;
>
> Maybe pull that out of the loop like you do below?
Fixed
>
> > + let addr = unaligned.wrapping_add(i as usize);
> > + let version = (unsafe { core::ptr::read_unaligned::<u16>(addr) } >> 8) as u8;
> Given the endian conversion below, this is correct, but I think leaving it as the __le16 until
> here is better. We could also just pull out the correct byte and ignore the other one.
Hmm... I'm not sure I follow.
The SPDM spec describes this as a u16, so I'm reading the entire value
and just taking the upper bits for the Major/Minor version
So I'm not sure what you think I should do differently?
> It's a spec quirk that they decided to have it defined as a __le16 rather than u8[2] as
> the fields are confined one byte or the other.
>
> I wonder if we should also reject any alpha versions? I.e. check the bottom 4 bits
> are 0.
Hmmm... That seems a bit unnecessary. Maybe a warning though?
>
> > +
> > + if version >= self.version && version <= SPDM_MAX_VER {
> > + self.version = version;
> > + foundver = true;
> > + }
> > + }
> > +
> > + if !foundver {
> > + pr_err!("No common supported version\n");
> > + to_result(-(bindings::EPROTO as i32))?;
> > + }
> > +
> > + Ok(())
> > + }
> > }
> > diff --git a/lib/rspdm/validator.rs b/lib/rspdm/validator.rs
> > index a0a3a2f46952..f69be6aa6280 100644
> > --- a/lib/rspdm/validator.rs
> > +++ b/lib/rspdm/validator.rs
> > @@ -7,6 +7,7 @@
> > //! Rust implementation of the DMTF Security Protocol and Data Model (SPDM)
> > //! <https://www.dmtf.org/dsp/DSP0274>
> >
> > +use crate::bindings::{__IncompleteArrayField, __le16};
> > use crate::consts::SpdmErrorCode;
> > use core::mem;
> > use kernel::prelude::*;
> > @@ -15,6 +16,8 @@
> > validate::{Unvalidated, Validate},
> > };
> >
> > +use crate::consts::SPDM_GET_VERSION;
> > +
> > #[repr(C, packed)]
> > pub(crate) struct SpdmHeader {
> > pub(crate) version: u8,
> > @@ -64,3 +67,67 @@ pub(crate) struct SpdmErrorRsp {
> > pub(crate) error_code: SpdmErrorCode,
> > pub(crate) error_data: u8,
> > }
> > +
> > +#[repr(C, packed)]
>
> Why packed?
We cast it from a struct to a slice (array), so we need it to be
packed to avoid gaps in the slice (array)
>
> > +pub(crate) struct GetVersionReq {
> > + pub(crate) version: u8,
> > + pub(crate) code: u8,
> > + pub(crate) param1: u8,
> > + pub(crate) param2: u8,
> > +}
> > +
> > +impl Default for GetVersionReq {
> > + fn default() -> Self {
> > + GetVersionReq {
> > + version: 0,
>
> Given this only takes one value we could default it to 0x10
> (even though it's written above to keep the state machine in sync).
We could, I do find that less clear though. Explicit writing it as
part of `get_version()` to me is really clear what is happening
>
> > + code: SPDM_GET_VERSION,
> > + param1: 0,
> > + param2: 0,
> > + }
> > + }
> > +}
> > +
> > +#[repr(C, packed)]
> > +pub(crate) struct GetVersionRsp {
> > + pub(crate) version: u8,
> > + pub(crate) code: u8,
> > + param1: u8,
> > + param2: u8,
> > + reserved: u8,
> > + pub(crate) version_number_entry_count: u8,
> > + pub(crate) version_number_entries: __IncompleteArrayField<__le16>,
> > +}
> > +
> > +impl Validate<&mut Unvalidated<KVec<u8>>> for &mut GetVersionRsp {
> > + type Err = Error;
> > +
> > + fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> {
> > + let raw = unvalidated.raw_mut();
> > + if raw.len() < mem::size_of::<GetVersionRsp>() {
> > + return Err(EINVAL);
> > + }
>
> I guess it probably belongs at the header validation point rather that here,
> but can we also check the version matches what was requested. Possibly an
> additional check here that it is 0x10 (as this one is a special case where
> only that value is correct).
Good point, I have added a check here
>
> > +
> > + let version_number_entries = *(raw.get(5).ok_or(ENOMEM))? as usize;
> > + let total_expected_size = version_number_entries * 2 + 6;
>
> Instead of 6 can we use mem::size_of::<GetVersionRsp>()?
Yep! Good idea
> Ideally something similar for the 2 as well even if it's a bit verbose.
> (if I was lazy in the C code you get to make it better here :)
let total_expected_size = version_number_entries *
mem::size_of::<__le16>() + mem::size_of::<GetVersionRsp>();
>
>
> > + if raw.len() < total_expected_size {
> > + return Err(EINVAL);
> > + }
> > +
> > + let ptr = raw.as_mut_ptr();
> > + // CAST: `GetVersionRsp` only contains integers and has `repr(C)`.
> > + let ptr = ptr.cast::<GetVersionRsp>();
> > + // SAFETY: `ptr` came from a reference and the cast above is valid.
> > + let rsp: &mut GetVersionRsp = unsafe { &mut *ptr };
> > +
> > + // Creating a reference on a packed struct will result in
>
> Silly question, but why is it packed? We might need to do this for some
> structures in SPDM but do we have transports that mess up the alignment enough
> that where the messages themselves don't need to be packed we have to mark
> the structures so anyway? Probably good to document this somewhere.
Like above, the Rust structs contain the information which we cast to
the slice (u8 array). So we want it to be packed to avoid gaps in the
buffer.
>
> > + // undefined behaviour, so we operate on the raw data directly
> > + let unaligned = core::ptr::addr_of_mut!(rsp.version_number_entries) as *mut u16;
> > + for version_offset in 0..version_number_entries {
> > + let addr = unaligned.wrapping_add(version_offset);
> > + let version = unsafe { core::ptr::read_unaligned::<u16>(addr) };
> > + unsafe { core::ptr::write_unaligned::<u16>(addr, version.to_le()) }
>
> I'd like to see a comment on why this seems to be doing an endian swap if we
> are on big endian. Looking back at the c code, it has an endian swap but
> only as part of the search for a version to use (finding the largest both
> device and and kernel support).
>
> Maybe I'm reading it wrong but isn't this putting cpu endian data into a structure
> element that is of type __le16?
On second thought I don't think this is required at all. It's
converting the LE data from the SPDM response to LE. Which is just
unnecessary no-op (LE) or a bug (BE) depending on the CPU endianness.
Alistair
>
> > + }
> > +
> > + Ok(rsp)
> > + }
> > +}
>
^ permalink raw reply [flat|nested] 99+ messages in thread* Re: [RFC v3 12/27] lib: rspdm: Support SPDM get_version
2026-03-13 5:35 ` Alistair Francis
@ 2026-03-13 5:53 ` Miguel Ojeda
2026-03-13 5:55 ` Miguel Ojeda
2026-03-16 17:16 ` Jonathan Cameron
1 sibling, 1 reply; 99+ messages in thread
From: Miguel Ojeda @ 2026-03-13 5:53 UTC (permalink / raw)
To: Alistair Francis
Cc: Jonathan Cameron, bhelgaas, lukas, rust-for-linux, akpm,
linux-pci, linux-cxl, linux-kernel, alex.gaynor, benno.lossin,
boqun.feng, a.hindborg, gary, bjorn3_gh, tmgross, ojeda,
wilfred.mallawa, aliceryhl, Alistair Francis
On Fri, Mar 13, 2026 at 6:36 AM Alistair Francis <alistair23@gmail.com> wrote:
>
> This is a public function that matches the C implementation, hence the
> arguments being fixed in the previous patch.
Since you have it in a C header and in `bindings_helper.h`, then you
should be able to use `#[export]`, which will do a typecheck that the
signatures match (it also does the `no_mangle`, so you can replace
it):
https://rust.docs.kernel.org/macros/attr.export.html
> This is just how the linter formats it when I run ` LLVM=1 make
> rustfmt`. I don't really want to be manually formatting things
Please use the "kernel vertical style" for imports (it is done
automatically by `rustfmt` too by adding the `//`):
https://docs.kernel.org/rust/coding-guidelines.html#imports
We have `size_of`, `align_of` and so on in the prelude, so you can use
those directly:
https://rust.docs.kernel.org/kernel/prelude/index.html
I hope that helps!
Cheers,
Miguel
^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [RFC v3 12/27] lib: rspdm: Support SPDM get_version
2026-03-13 5:53 ` Miguel Ojeda
@ 2026-03-13 5:55 ` Miguel Ojeda
0 siblings, 0 replies; 99+ messages in thread
From: Miguel Ojeda @ 2026-03-13 5:55 UTC (permalink / raw)
To: Alistair Francis
Cc: Jonathan Cameron, bhelgaas, lukas, rust-for-linux, akpm,
linux-pci, linux-cxl, linux-kernel, alex.gaynor, benno.lossin,
boqun.feng, a.hindborg, gary, bjorn3_gh, tmgross, ojeda,
wilfred.mallawa, aliceryhl, Alistair Francis
On Fri, Mar 13, 2026 at 6:53 AM Miguel Ojeda
<miguel.ojeda.sandonis@gmail.com> wrote:
>
> We have `size_of`, `align_of` and so on in the prelude, so you can use
> those directly:
>
> https://rust.docs.kernel.org/kernel/prelude/index.html
Sorry, I cut the quotes too much -- this was supposed to reply to the
`total_expected_size` part of the conversation.
Cheers,
Miguel
^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [RFC v3 12/27] lib: rspdm: Support SPDM get_version
2026-03-13 5:35 ` Alistair Francis
2026-03-13 5:53 ` Miguel Ojeda
@ 2026-03-16 17:16 ` Jonathan Cameron
1 sibling, 0 replies; 99+ messages in thread
From: Jonathan Cameron @ 2026-03-16 17:16 UTC (permalink / raw)
To: Alistair Francis
Cc: bhelgaas, lukas, rust-for-linux, akpm, linux-pci, linux-cxl,
linux-kernel, alex.gaynor, benno.lossin, boqun.feng, a.hindborg,
gary, bjorn3_gh, tmgross, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis
>
> >
> > > + let addr = unaligned.wrapping_add(i as usize);
> > > + let version = (unsafe { core::ptr::read_unaligned::<u16>(addr) } >> 8) as u8;
> > Given the endian conversion below, this is correct, but I think leaving it as the __le16 until
> > here is better. We could also just pull out the correct byte and ignore the other one.
>
> Hmm... I'm not sure I follow.
>
> The SPDM spec describes this as a u16, so I'm reading the entire value
> and just taking the upper bits for the Major/Minor version
Miss read by me I think... Or tied up with endian conversion I think you
say you are getting rid of below.
>
> So I'm not sure what you think I should do differently?
>
> > It's a spec quirk that they decided to have it defined as a __le16 rather than u8[2] as
> > the fields are confined one byte or the other.
> >
> > I wonder if we should also reject any alpha versions? I.e. check the bottom 4 bits
> > are 0.
>
> Hmmm... That seems a bit unnecessary. Maybe a warning though?
Warning works for me.
>
> >
> > > +
> > > + if version >= self.version && version <= SPDM_MAX_VER {
> > > + self.version = version;
> > > + foundver = true;
> > > + }
> > > + }
> > > +
> > > + if !foundver {
> > > + pr_err!("No common supported version\n");
> > > + to_result(-(bindings::EPROTO as i32))?;
> > > + }
> > > +
> > > + Ok(())
> > > + }
> > > }
> > > diff --git a/lib/rspdm/validator.rs b/lib/rspdm/validator.rs
> > > index a0a3a2f46952..f69be6aa6280 100644
> > > --- a/lib/rspdm/validator.rs
> > > +++ b/lib/rspdm/validator.rs
> > > @@ -7,6 +7,7 @@
> > > //! Rust implementation of the DMTF Security Protocol and Data Model (SPDM)
> > > //! <https://www.dmtf.org/dsp/DSP0274>
> > >
> > > +use crate::bindings::{__IncompleteArrayField, __le16};
> > > use crate::consts::SpdmErrorCode;
> > > use core::mem;
> > > use kernel::prelude::*;
> > > @@ -15,6 +16,8 @@
> > > validate::{Unvalidated, Validate},
> > > };
> > >
> > > +use crate::consts::SPDM_GET_VERSION;
> > > +
> > > #[repr(C, packed)]
> > > pub(crate) struct SpdmHeader {
> > > pub(crate) version: u8,
> > > @@ -64,3 +67,67 @@ pub(crate) struct SpdmErrorRsp {
> > > pub(crate) error_code: SpdmErrorCode,
> > > pub(crate) error_data: u8,
> > > }
> > > +
> > > +#[repr(C, packed)]
> >
> > Why packed?
>
> We cast it from a struct to a slice (array), so we need it to be
> packed to avoid gaps in the slice (array)
Ah ok. I'm learning slowly...
>
> >
> > > +pub(crate) struct GetVersionReq {
> > > + pub(crate) version: u8,
> > > + pub(crate) code: u8,
> > > + pub(crate) param1: u8,
> > > + pub(crate) param2: u8,
> > > +}
> > > +
> > > + // undefined behaviour, so we operate on the raw data directly
> > > + let unaligned = core::ptr::addr_of_mut!(rsp.version_number_entries) as *mut u16;
> > > + for version_offset in 0..version_number_entries {
> > > + let addr = unaligned.wrapping_add(version_offset);
> > > + let version = unsafe { core::ptr::read_unaligned::<u16>(addr) };
> > > + unsafe { core::ptr::write_unaligned::<u16>(addr, version.to_le()) }
> >
> > I'd like to see a comment on why this seems to be doing an endian swap if we
> > are on big endian. Looking back at the c code, it has an endian swap but
> > only as part of the search for a version to use (finding the largest both
> > device and and kernel support).
> >
> > Maybe I'm reading it wrong but isn't this putting cpu endian data into a structure
> > element that is of type __le16?
>
> On second thought I don't think this is required at all. It's
> converting the LE data from the SPDM response to LE. Which is just
> unnecessary no-op (LE) or a bug (BE) depending on the CPU endianness.
I think that makes sense but I'll take a close look at v4.
J
>
> Alistair
>
> >
> > > + }
> > > +
> > > + Ok(rsp)
> > > + }
> > > +}
> >
^ permalink raw reply [flat|nested] 99+ messages in thread
* [RFC v3 13/27] lib: rspdm: Support SPDM get_capabilities
2026-02-11 3:29 [RFC v3 00/27] lib: Rust implementation of SPDM alistair23
` (11 preceding siblings ...)
2026-02-11 3:29 ` [RFC v3 12/27] lib: rspdm: Support SPDM get_version alistair23
@ 2026-02-11 3:29 ` alistair23
2026-02-11 4:08 ` Wilfred Mallawa
2026-03-03 12:09 ` Jonathan Cameron
2026-02-11 3:29 ` [RFC v3 14/27] lib: rspdm: Support SPDM negotiate_algorithms alistair23
` (15 subsequent siblings)
28 siblings, 2 replies; 99+ messages in thread
From: alistair23 @ 2026-02-11 3:29 UTC (permalink / raw)
To: bhelgaas, lukas, rust-for-linux, akpm, linux-pci,
Jonathan.Cameron, linux-cxl, linux-kernel
Cc: alex.gaynor, benno.lossin, boqun.feng, a.hindborg, gary,
bjorn3_gh, tmgross, alistair23, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis
From: Alistair Francis <alistair@alistair23.me>
Support the GET_CAPABILITIES SPDM command.
Signed-off-by: Alistair Francis <alistair@alistair23.me>
---
lib/rspdm/consts.rs | 18 ++++++++--
lib/rspdm/lib.rs | 4 +++
lib/rspdm/state.rs | 68 +++++++++++++++++++++++++++++++++++--
lib/rspdm/validator.rs | 76 +++++++++++++++++++++++++++++++++++++++++-
4 files changed, 161 insertions(+), 5 deletions(-)
diff --git a/lib/rspdm/consts.rs b/lib/rspdm/consts.rs
index 38f48f0863e2..a6a66e2af1b5 100644
--- a/lib/rspdm/consts.rs
+++ b/lib/rspdm/consts.rs
@@ -12,9 +12,7 @@
/* SPDM versions supported by this implementation */
pub(crate) const SPDM_VER_10: u8 = 0x10;
-#[allow(dead_code)]
pub(crate) const SPDM_VER_11: u8 = 0x11;
-#[allow(dead_code)]
pub(crate) const SPDM_VER_12: u8 = 0x12;
pub(crate) const SPDM_VER_13: u8 = 0x13;
@@ -132,3 +130,19 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
pub(crate) const SPDM_GET_VERSION: u8 = 0x84;
pub(crate) const SPDM_GET_VERSION_LEN: usize = mem::size_of::<SpdmHeader>() + 255;
+
+pub(crate) const SPDM_GET_CAPABILITIES: u8 = 0xe1;
+pub(crate) const SPDM_MIN_DATA_TRANSFER_SIZE: u32 = 42;
+
+// SPDM cryptographic timeout of this implementation:
+// Assume calculations may take up to 1 sec on a busy machine, which equals
+// roughly 1 << 20. That's within the limits mandated for responders by CMA
+// (1 << 23 usec, PCIe r6.2 sec 6.31.3) and DOE (1 sec, PCIe r6.2 sec 6.30.2).
+// Used in GET_CAPABILITIES exchange.
+pub(crate) const SPDM_CTEXPONENT: u8 = 20;
+
+pub(crate) const SPDM_CERT_CAP: u32 = 1 << 1;
+pub(crate) const SPDM_CHAL_CAP: u32 = 1 << 2;
+
+pub(crate) const SPDM_REQ_CAPS: u32 = SPDM_CERT_CAP | SPDM_CHAL_CAP;
+pub(crate) const SPDM_RSP_MIN_CAPS: u32 = SPDM_CERT_CAP | SPDM_CHAL_CAP;
diff --git a/lib/rspdm/lib.rs b/lib/rspdm/lib.rs
index 08abcbb21247..5f6653ada59d 100644
--- a/lib/rspdm/lib.rs
+++ b/lib/rspdm/lib.rs
@@ -113,6 +113,10 @@
return e.to_errno() as c_int;
}
+ if let Err(e) = state.get_capabilities() {
+ return e.to_errno() as c_int;
+ }
+
0
}
diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
index 3ad53ec05044..0efad7f341cd 100644
--- a/lib/rspdm/state.rs
+++ b/lib/rspdm/state.rs
@@ -17,9 +17,12 @@
};
use crate::consts::{
- SpdmErrorCode, SPDM_ERROR, SPDM_GET_VERSION_LEN, SPDM_MAX_VER, SPDM_MIN_VER, SPDM_REQ,
+ SpdmErrorCode, SPDM_ERROR, SPDM_GET_VERSION_LEN, SPDM_MAX_VER, SPDM_MIN_DATA_TRANSFER_SIZE,
+ SPDM_MIN_VER, SPDM_REQ, SPDM_RSP_MIN_CAPS, SPDM_VER_10, SPDM_VER_11, SPDM_VER_12,
+};
+use crate::validator::{
+ GetCapabilitiesReq, GetCapabilitiesRsp, GetVersionReq, GetVersionRsp, SpdmErrorRsp, SpdmHeader,
};
-use crate::validator::{GetVersionReq, GetVersionRsp, SpdmErrorRsp, SpdmHeader};
/// The current SPDM session state for a device. Based on the
/// C `struct spdm_state`.
@@ -35,6 +38,8 @@
///
/// `version`: Maximum common supported version of requester and responder.
/// Negotiated during GET_VERSION exchange.
+/// @rsp_caps: Cached capabilities of responder.
+/// Received during GET_CAPABILITIES exchange.
#[expect(dead_code)]
pub struct SpdmState {
pub(crate) dev: *mut bindings::device,
@@ -46,6 +51,7 @@ pub struct SpdmState {
// Negotiated state
pub(crate) version: u8,
+ pub(crate) rsp_caps: u32,
}
impl SpdmState {
@@ -65,6 +71,7 @@ pub(crate) fn new(
keyring,
validate,
version: SPDM_MIN_VER,
+ rsp_caps: 0,
}
}
@@ -269,4 +276,61 @@ pub(crate) fn get_version(&mut self) -> Result<(), Error> {
Ok(())
}
+
+ /// Obtain the supported capabilities from an SPDM session and store the
+ /// information in the `SpdmState`.
+ pub(crate) fn get_capabilities(&mut self) -> Result<(), Error> {
+ let mut request = GetCapabilitiesReq::default();
+ request.version = self.version;
+
+ let (req_sz, rsp_sz) = match self.version {
+ SPDM_VER_10 => (4, 8),
+ SPDM_VER_11 => (8, 8),
+ _ => {
+ request.data_transfer_size = self.transport_sz.to_le();
+ request.max_spdm_msg_size = request.data_transfer_size;
+
+ (
+ core::mem::size_of::<GetCapabilitiesReq>(),
+ core::mem::size_of::<GetCapabilitiesRsp>(),
+ )
+ }
+ };
+
+ // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
+ let request_buf = unsafe { from_raw_parts_mut(&mut request as *mut _ as *mut u8, req_sz) };
+
+ let mut response_vec: KVec<u8> = KVec::with_capacity(rsp_sz, GFP_KERNEL)?;
+ // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
+ let response_buf = unsafe { from_raw_parts_mut(response_vec.as_mut_ptr(), rsp_sz) };
+
+ let rc = self.spdm_exchange(request_buf, response_buf)?;
+
+ if rc < (rsp_sz as i32) {
+ pr_err!("Truncated capabilities response\n");
+ to_result(-(bindings::EIO as i32))?;
+ }
+
+ // SAFETY: `rc` is the length of data read, which will be smaller
+ // then the capacity of the vector
+ unsafe { response_vec.inc_len(rc as usize) };
+
+ let response: &mut GetCapabilitiesRsp =
+ Untrusted::new_mut(&mut response_vec).validate_mut()?;
+
+ self.rsp_caps = u32::from_le(response.flags);
+ if (self.rsp_caps & SPDM_RSP_MIN_CAPS) != SPDM_RSP_MIN_CAPS {
+ to_result(-(bindings::EPROTONOSUPPORT as i32))?;
+ }
+
+ if self.version >= SPDM_VER_12 {
+ if response.data_transfer_size < SPDM_MIN_DATA_TRANSFER_SIZE {
+ pr_err!("Malformed capabilities response\n");
+ to_result(-(bindings::EPROTO as i32))?;
+ }
+ self.transport_sz = self.transport_sz.min(response.data_transfer_size);
+ }
+
+ Ok(())
+ }
}
diff --git a/lib/rspdm/validator.rs b/lib/rspdm/validator.rs
index f69be6aa6280..cd792c46767a 100644
--- a/lib/rspdm/validator.rs
+++ b/lib/rspdm/validator.rs
@@ -16,7 +16,7 @@
validate::{Unvalidated, Validate},
};
-use crate::consts::SPDM_GET_VERSION;
+use crate::consts::{SPDM_CTEXPONENT, SPDM_GET_CAPABILITIES, SPDM_GET_VERSION, SPDM_REQ_CAPS};
#[repr(C, packed)]
pub(crate) struct SpdmHeader {
@@ -131,3 +131,77 @@ fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err>
Ok(rsp)
}
}
+
+#[repr(C, packed)]
+pub(crate) struct GetCapabilitiesReq {
+ pub(crate) version: u8,
+ pub(crate) code: u8,
+ pub(crate) param1: u8,
+ pub(crate) param2: u8,
+
+ reserved1: u8,
+ pub(crate) ctexponent: u8,
+ reserved2: u16,
+
+ pub(crate) flags: u32,
+
+ /* End of SPDM 1.1 structure */
+ pub(crate) data_transfer_size: u32,
+ pub(crate) max_spdm_msg_size: u32,
+}
+
+impl Default for GetCapabilitiesReq {
+ fn default() -> Self {
+ GetCapabilitiesReq {
+ version: 0,
+ code: SPDM_GET_CAPABILITIES,
+ param1: 0,
+ param2: 0,
+ reserved1: 0,
+ ctexponent: SPDM_CTEXPONENT,
+ reserved2: 0,
+ flags: (SPDM_REQ_CAPS as u32).to_le(),
+ data_transfer_size: 0,
+ max_spdm_msg_size: 0,
+ }
+ }
+}
+
+#[repr(C, packed)]
+pub(crate) struct GetCapabilitiesRsp {
+ pub(crate) version: u8,
+ pub(crate) code: u8,
+ pub(crate) param1: u8,
+ pub(crate) param2: u8,
+
+ reserved1: u8,
+ pub(crate) ctexponent: u8,
+ reserved2: u16,
+
+ pub(crate) flags: u32,
+
+ /* End of SPDM 1.1 structure */
+ pub(crate) data_transfer_size: u32,
+ pub(crate) max_spdm_msg_size: u32,
+
+ pub(crate) supported_algorithms: __IncompleteArrayField<__le16>,
+}
+
+impl Validate<&mut Unvalidated<KVec<u8>>> for &mut GetCapabilitiesRsp {
+ type Err = Error;
+
+ fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> {
+ let raw = unvalidated.raw_mut();
+ if raw.len() < mem::size_of::<GetCapabilitiesRsp>() {
+ return Err(EINVAL);
+ }
+
+ let ptr = raw.as_mut_ptr();
+ // CAST: `GetCapabilitiesRsp` only contains integers and has `repr(C)`.
+ let ptr = ptr.cast::<GetCapabilitiesRsp>();
+ // SAFETY: `ptr` came from a reference and the cast above is valid.
+ let rsp: &mut GetCapabilitiesRsp = unsafe { &mut *ptr };
+
+ Ok(rsp)
+ }
+}
--
2.52.0
^ permalink raw reply related [flat|nested] 99+ messages in thread* Re: [RFC v3 13/27] lib: rspdm: Support SPDM get_capabilities
2026-02-11 3:29 ` [RFC v3 13/27] lib: rspdm: Support SPDM get_capabilities alistair23
@ 2026-02-11 4:08 ` Wilfred Mallawa
2026-03-03 12:09 ` Jonathan Cameron
1 sibling, 0 replies; 99+ messages in thread
From: Wilfred Mallawa @ 2026-02-11 4:08 UTC (permalink / raw)
To: alistair23@gmail.com, akpm@linux-foundation.org,
linux-cxl@vger.kernel.org, Jonathan.Cameron@huawei.com,
linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org,
linux-pci@vger.kernel.org, lukas@wunner.de, bhelgaas@google.com
Cc: tmgross@umich.edu, benno.lossin@proton.me, alistair@alistair23.me,
gary@garyguo.net, a.hindborg@kernel.org, bjorn3_gh@protonmail.com,
boqun.feng@gmail.com, alex.gaynor@gmail.com, aliceryhl@google.com,
ojeda@kernel.org
[snip]
> +
> + /// Obtain the supported capabilities from an SPDM session and
> store the
> + /// information in the `SpdmState`.
> + pub(crate) fn get_capabilities(&mut self) -> Result<(), Error> {
> + let mut request = GetCapabilitiesReq::default();
> + request.version = self.version;
> +
> + let (req_sz, rsp_sz) = match self.version {
> + SPDM_VER_10 => (4, 8),
> + SPDM_VER_11 => (8, 8),
> + _ => {
> + request.data_transfer_size =
> self.transport_sz.to_le();
> + request.max_spdm_msg_size =
> request.data_transfer_size;
> +
> + (
> + core::mem::size_of::<GetCapabilitiesReq>(),
> + core::mem::size_of::<GetCapabilitiesRsp>(),
> + )
> + }
> + };
> +
> + // SAFETY: `request` is repr(C) and packed, so we can
> convert it to a slice
> + let request_buf = unsafe { from_raw_parts_mut(&mut request
> as *mut _ as *mut u8, req_sz) };
> +
> + let mut response_vec: KVec<u8> = KVec::with_capacity(rsp_sz,
> GFP_KERNEL)?;
> + // SAFETY: `request` is repr(C) and packed, so we can
> convert it to a slice
Same here about the SAFETY comment.
> + let response_buf = unsafe {
> from_raw_parts_mut(response_vec.as_mut_ptr(), rsp_sz) };
> +
> + let rc = self.spdm_exchange(request_buf, response_buf)?;
> +
> + if rc < (rsp_sz as i32) {
> + pr_err!("Truncated capabilities response\n");
> + to_result(-(bindings::EIO as i32))?;
> + }
> +
> + // SAFETY: `rc` is the length of data read, which will be
> smaller
> + // then the capacity of the vector
> + unsafe { response_vec.inc_len(rc as usize) };
> +
> + let response: &mut GetCapabilitiesRsp =
> + Untrusted::new_mut(&mut response_vec).validate_mut()?;
> +
> + self.rsp_caps = u32::from_le(response.flags);
> + if (self.rsp_caps & SPDM_RSP_MIN_CAPS) != SPDM_RSP_MIN_CAPS
> {
Might be useful for debugging to have a pr_err() here.
Wilfred
^ permalink raw reply [flat|nested] 99+ messages in thread* Re: [RFC v3 13/27] lib: rspdm: Support SPDM get_capabilities
2026-02-11 3:29 ` [RFC v3 13/27] lib: rspdm: Support SPDM get_capabilities alistair23
2026-02-11 4:08 ` Wilfred Mallawa
@ 2026-03-03 12:09 ` Jonathan Cameron
2026-03-03 18:07 ` Miguel Ojeda
2026-03-20 4:32 ` Alistair Francis
1 sibling, 2 replies; 99+ messages in thread
From: Jonathan Cameron @ 2026-03-03 12:09 UTC (permalink / raw)
To: alistair23
Cc: bhelgaas, lukas, rust-for-linux, akpm, linux-pci, linux-cxl,
linux-kernel, alex.gaynor, benno.lossin, boqun.feng, a.hindborg,
gary, bjorn3_gh, tmgross, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis
On Wed, 11 Feb 2026 13:29:20 +1000
alistair23@gmail.com wrote:
> From: Alistair Francis <alistair@alistair23.me>
>
> Support the GET_CAPABILITIES SPDM command.
>
> Signed-off-by: Alistair Francis <alistair@alistair23.me>
A few comments inline.
Looks in pretty good shape to me but definitely needs review
by those more familiar with rust than me!
> ---
> lib/rspdm/consts.rs | 18 ++++++++--
> lib/rspdm/lib.rs | 4 +++
> lib/rspdm/state.rs | 68 +++++++++++++++++++++++++++++++++++--
> lib/rspdm/validator.rs | 76 +++++++++++++++++++++++++++++++++++++++++-
> 4 files changed, 161 insertions(+), 5 deletions(-)
>
> diff --git a/lib/rspdm/consts.rs b/lib/rspdm/consts.rs
> index 38f48f0863e2..a6a66e2af1b5 100644
> --- a/lib/rspdm/consts.rs
> +++ b/lib/rspdm/consts.rs
> @@ -12,9 +12,7 @@
>
> /* SPDM versions supported by this implementation */
> pub(crate) const SPDM_VER_10: u8 = 0x10;
> -#[allow(dead_code)]
> pub(crate) const SPDM_VER_11: u8 = 0x11;
> -#[allow(dead_code)]
> pub(crate) const SPDM_VER_12: u8 = 0x12;
> pub(crate) const SPDM_VER_13: u8 = 0x13;
>
> @@ -132,3 +130,19 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
>
> pub(crate) const SPDM_GET_VERSION: u8 = 0x84;
> pub(crate) const SPDM_GET_VERSION_LEN: usize = mem::size_of::<SpdmHeader>() + 255;
> +
> +pub(crate) const SPDM_GET_CAPABILITIES: u8 = 0xe1;
> +pub(crate) const SPDM_MIN_DATA_TRANSFER_SIZE: u32 = 42;
Spec reference? C code refers to /* SPDM 1.2.0 margin no 226 */
It's 269 in 1.3.1
> +
> +// SPDM cryptographic timeout of this implementation:
> +// Assume calculations may take up to 1 sec on a busy machine, which equals
> +// roughly 1 << 20. That's within the limits mandated for responders by CMA
> +// (1 << 23 usec, PCIe r6.2 sec 6.31.3) and DOE (1 sec, PCIe r6.2 sec 6.30.2).
> +// Used in GET_CAPABILITIES exchange.
> +pub(crate) const SPDM_CTEXPONENT: u8 = 20;
> +
> +pub(crate) const SPDM_CERT_CAP: u32 = 1 << 1;
Can we use bit_u32() or similar here?
We've tried to move to BIT() for similar defines in the C code, so I'm
looking for something along those lines. Provides a form of documentation
that they are single bit fields.
> +pub(crate) const SPDM_CHAL_CAP: u32 = 1 << 2;
> +
> +pub(crate) const SPDM_REQ_CAPS: u32 = SPDM_CERT_CAP | SPDM_CHAL_CAP;
> +pub(crate) const SPDM_RSP_MIN_CAPS: u32 = SPDM_CERT_CAP | SPDM_CHAL_CAP;
> diff --git a/lib/rspdm/lib.rs b/lib/rspdm/lib.rs
> index 08abcbb21247..5f6653ada59d 100644
> --- a/lib/rspdm/lib.rs
> +++ b/lib/rspdm/lib.rs
> @@ -113,6 +113,10 @@
> return e.to_errno() as c_int;
> }
>
> + if let Err(e) = state.get_capabilities() {
> + return e.to_errno() as c_int;
> + }
> +
> 0
> }
>
> diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
> index 3ad53ec05044..0efad7f341cd 100644
> --- a/lib/rspdm/state.rs
> +++ b/lib/rspdm/state.rs
> @@ -17,9 +17,12 @@
> };
>
> use crate::consts::{
> - SpdmErrorCode, SPDM_ERROR, SPDM_GET_VERSION_LEN, SPDM_MAX_VER, SPDM_MIN_VER, SPDM_REQ,
> + SpdmErrorCode, SPDM_ERROR, SPDM_GET_VERSION_LEN, SPDM_MAX_VER, SPDM_MIN_DATA_TRANSFER_SIZE,
> + SPDM_MIN_VER, SPDM_REQ, SPDM_RSP_MIN_CAPS, SPDM_VER_10, SPDM_VER_11, SPDM_VER_12,
> +};
This is why I was getting bit grumpy in previous about long lines.
Fair enough if this is only option, but it's not nice for a review process
that relies on patches!
> +use crate::validator::{
> + GetCapabilitiesReq, GetCapabilitiesRsp, GetVersionReq, GetVersionRsp, SpdmErrorRsp, SpdmHeader,
> };
> -use crate::validator::{GetVersionReq, GetVersionRsp, SpdmErrorRsp, SpdmHeader};
>
> /// The current SPDM session state for a device. Based on the
> /// C `struct spdm_state`.
> @@ -35,6 +38,8 @@
> ///
> /// `version`: Maximum common supported version of requester and responder.
> /// Negotiated during GET_VERSION exchange.
> +/// @rsp_caps: Cached capabilities of responder.
> +/// Received during GET_CAPABILITIES exchange.
> #[expect(dead_code)]
> pub struct SpdmState {
> pub(crate) dev: *mut bindings::device,
> @@ -46,6 +51,7 @@ pub struct SpdmState {
>
> // Negotiated state
> pub(crate) version: u8,
> + pub(crate) rsp_caps: u32,
> }
>
> impl SpdmState {
> @@ -65,6 +71,7 @@ pub(crate) fn new(
> keyring,
> validate,
> version: SPDM_MIN_VER,
> + rsp_caps: 0,
> }
> }
>
> @@ -269,4 +276,61 @@ pub(crate) fn get_version(&mut self) -> Result<(), Error> {
>
> Ok(())
> }
> +
> + /// Obtain the supported capabilities from an SPDM session and store the
> + /// information in the `SpdmState`.
> + pub(crate) fn get_capabilities(&mut self) -> Result<(), Error> {
> + let mut request = GetCapabilitiesReq::default();
> + request.version = self.version;
> +
> + let (req_sz, rsp_sz) = match self.version {
> + SPDM_VER_10 => (4, 8),
> + SPDM_VER_11 => (8, 8),
> + _ => {
> + request.data_transfer_size = self.transport_sz.to_le();
> + request.max_spdm_msg_size = request.data_transfer_size;
> +
> + (
> + core::mem::size_of::<GetCapabilitiesReq>(),
> + core::mem::size_of::<GetCapabilitiesRsp>(),
> + )
> + }
> + };
> +
> + // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
> + let request_buf = unsafe { from_raw_parts_mut(&mut request as *mut _ as *mut u8, req_sz) };
> +
> + let mut response_vec: KVec<u8> = KVec::with_capacity(rsp_sz, GFP_KERNEL)?;
> + // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
> + let response_buf = unsafe { from_raw_parts_mut(response_vec.as_mut_ptr(), rsp_sz) };
> +
> + let rc = self.spdm_exchange(request_buf, response_buf)?;
> +
> + if rc < (rsp_sz as i32) {
> + pr_err!("Truncated capabilities response\n");
> + to_result(-(bindings::EIO as i32))?;
> + }
> +
> + // SAFETY: `rc` is the length of data read, which will be smaller
> + // then the capacity of the vector
Fairly sure it's exactly the size of the of vector in this case.
Bit (0) of param1 isn't set sop the algorithms block is never included.
> + unsafe { response_vec.inc_len(rc as usize) };
> +
> + let response: &mut GetCapabilitiesRsp =
> + Untrusted::new_mut(&mut response_vec).validate_mut()?;
> +
> + self.rsp_caps = u32::from_le(response.flags);
Isn't response packed? So why don't we need the read_unaligned stuff in this
case but did in the previous patch?
> + if (self.rsp_caps & SPDM_RSP_MIN_CAPS) != SPDM_RSP_MIN_CAPS {
> + to_result(-(bindings::EPROTONOSUPPORT as i32))?;
> + }
> +
> + if self.version >= SPDM_VER_12 {
> + if response.data_transfer_size < SPDM_MIN_DATA_TRANSFER_SIZE {
> + pr_err!("Malformed capabilities response\n");
> + to_result(-(bindings::EPROTO as i32))?;
> + }
> + self.transport_sz = self.transport_sz.min(response.data_transfer_size);
> + }
> +
> + Ok(())
> + }
> }
> diff --git a/lib/rspdm/validator.rs b/lib/rspdm/validator.rs
> index f69be6aa6280..cd792c46767a 100644
> --- a/lib/rspdm/validator.rs
> +++ b/lib/rspdm/validator.rs
> @@ -16,7 +16,7 @@
> validate::{Unvalidated, Validate},
> };
>
> -use crate::consts::SPDM_GET_VERSION;
> +use crate::consts::{SPDM_CTEXPONENT, SPDM_GET_CAPABILITIES, SPDM_GET_VERSION, SPDM_REQ_CAPS};
>
> #[repr(C, packed)]
> pub(crate) struct SpdmHeader {
> @@ -131,3 +131,77 @@ fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err>
> Ok(rsp)
> }
> }
> +
> +#[repr(C, packed)]
> +pub(crate) struct GetCapabilitiesReq {
> + pub(crate) version: u8,
> + pub(crate) code: u8,
> + pub(crate) param1: u8,
> + pub(crate) param2: u8,
> +
> + reserved1: u8,
> + pub(crate) ctexponent: u8,
> + reserved2: u16,
> +
> + pub(crate) flags: u32,
> +
> + /* End of SPDM 1.1 structure */
> + pub(crate) data_transfer_size: u32,
> + pub(crate) max_spdm_msg_size: u32,
> +}
> +
> +impl Default for GetCapabilitiesReq {
> + fn default() -> Self {
> + GetCapabilitiesReq {
> + version: 0,
> + code: SPDM_GET_CAPABILITIES,
> + param1: 0,
Maybe add some comment on impacts of bit 0.
> + param2: 0,
> + reserved1: 0,
> + ctexponent: SPDM_CTEXPONENT,
> + reserved2: 0,
> + flags: (SPDM_REQ_CAPS as u32).to_le(),
> + data_transfer_size: 0,
> + max_spdm_msg_size: 0,
> + }
> + }
> +}
> +
> +#[repr(C, packed)]
> +pub(crate) struct GetCapabilitiesRsp {
> + pub(crate) version: u8,
> + pub(crate) code: u8,
> + pub(crate) param1: u8,
> + pub(crate) param2: u8,
param 2 is reserved in 1.3.1 Maybe not pub(crate)?
If it is not reserved in some particular version perhaps add a comment.
> +
> + reserved1: u8,
> + pub(crate) ctexponent: u8,
> + reserved2: u16,
> +
> + pub(crate) flags: u32,
> +
> + /* End of SPDM 1.1 structure */
> + pub(crate) data_transfer_size: u32,
> + pub(crate) max_spdm_msg_size: u32,
> +
> + pub(crate) supported_algorithms: __IncompleteArrayField<__le16>,
> +}
> +
> +impl Validate<&mut Unvalidated<KVec<u8>>> for &mut GetCapabilitiesRsp {
> + type Err = Error;
> +
> + fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> {
> + let raw = unvalidated.raw_mut();
> + if raw.len() < mem::size_of::<GetCapabilitiesRsp>() {
So we fail if 1.1? If that's the case I think we should fail when doing
the version establishment in the previous patch.
The c code had some size mangling to deal with this difference.
> + return Err(EINVAL);
> + }
> +
> + let ptr = raw.as_mut_ptr();
> + // CAST: `GetCapabilitiesRsp` only contains integers and has `repr(C)`.
> + let ptr = ptr.cast::<GetCapabilitiesRsp>();
> + // SAFETY: `ptr` came from a reference and the cast above is valid.
> + let rsp: &mut GetCapabilitiesRsp = unsafe { &mut *ptr };
> +
> + Ok(rsp)
> + }
> +}
^ permalink raw reply [flat|nested] 99+ messages in thread* Re: [RFC v3 13/27] lib: rspdm: Support SPDM get_capabilities
2026-03-03 12:09 ` Jonathan Cameron
@ 2026-03-03 18:07 ` Miguel Ojeda
2026-03-20 4:32 ` Alistair Francis
1 sibling, 0 replies; 99+ messages in thread
From: Miguel Ojeda @ 2026-03-03 18:07 UTC (permalink / raw)
To: Jonathan Cameron
Cc: alistair23, bhelgaas, lukas, rust-for-linux, akpm, linux-pci,
linux-cxl, linux-kernel, alex.gaynor, benno.lossin, boqun.feng,
a.hindborg, gary, bjorn3_gh, tmgross, ojeda, wilfred.mallawa,
aliceryhl, Alistair Francis
On Tue, Mar 3, 2026 at 1:09 PM Jonathan Cameron
<jonathan.cameron@huawei.com> wrote:
>
> Can we use bit_u32() or similar here?
> We've tried to move to BIT() for similar defines in the C code, so I'm
> looking for something along those lines. Provides a form of documentation
> that they are single bit fields.
We have these so far:
https://rust.docs.kernel.org/kernel/bits/
Out-of-tree long ago we had also a `bit<T>` generic const function
(https://github.com/Rust-for-Linux/linux/blob/rust/rust/kernel/types.rs#L317-L349).
I think we also discussed a `bit!` macro at some point.
> This is why I was getting bit grumpy in previous about long lines.
> Fair enough if this is only option, but it's not nice for a review process
> that relies on patches!
This should be using the kernel imports style, which should make a
case like this more readable:
https://docs.kernel.org/rust/coding-guidelines.html#imports
I hope that helps!
Cheers,
Miguel
^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [RFC v3 13/27] lib: rspdm: Support SPDM get_capabilities
2026-03-03 12:09 ` Jonathan Cameron
2026-03-03 18:07 ` Miguel Ojeda
@ 2026-03-20 4:32 ` Alistair Francis
1 sibling, 0 replies; 99+ messages in thread
From: Alistair Francis @ 2026-03-20 4:32 UTC (permalink / raw)
To: Jonathan Cameron
Cc: bhelgaas, lukas, rust-for-linux, akpm, linux-pci, linux-cxl,
linux-kernel, alex.gaynor, benno.lossin, boqun.feng, a.hindborg,
gary, bjorn3_gh, tmgross, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis
On Tue, Mar 3, 2026 at 10:09 PM Jonathan Cameron
<jonathan.cameron@huawei.com> wrote:
>
> On Wed, 11 Feb 2026 13:29:20 +1000
> alistair23@gmail.com wrote:
>
> > From: Alistair Francis <alistair@alistair23.me>
> >
> > Support the GET_CAPABILITIES SPDM command.
> >
> > Signed-off-by: Alistair Francis <alistair@alistair23.me>
> A few comments inline.
> Looks in pretty good shape to me but definitely needs review
> by those more familiar with rust than me!
>
> > ---
> > lib/rspdm/consts.rs | 18 ++++++++--
> > lib/rspdm/lib.rs | 4 +++
> > lib/rspdm/state.rs | 68 +++++++++++++++++++++++++++++++++++--
> > lib/rspdm/validator.rs | 76 +++++++++++++++++++++++++++++++++++++++++-
> > 4 files changed, 161 insertions(+), 5 deletions(-)
> >
> > diff --git a/lib/rspdm/consts.rs b/lib/rspdm/consts.rs
> > index 38f48f0863e2..a6a66e2af1b5 100644
> > --- a/lib/rspdm/consts.rs
> > +++ b/lib/rspdm/consts.rs
> > @@ -12,9 +12,7 @@
> >
> > /* SPDM versions supported by this implementation */
> > pub(crate) const SPDM_VER_10: u8 = 0x10;
> > -#[allow(dead_code)]
> > pub(crate) const SPDM_VER_11: u8 = 0x11;
> > -#[allow(dead_code)]
> > pub(crate) const SPDM_VER_12: u8 = 0x12;
> > pub(crate) const SPDM_VER_13: u8 = 0x13;
> >
> > @@ -132,3 +130,19 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
> >
> > pub(crate) const SPDM_GET_VERSION: u8 = 0x84;
> > pub(crate) const SPDM_GET_VERSION_LEN: usize = mem::size_of::<SpdmHeader>() + 255;
> > +
> > +pub(crate) const SPDM_GET_CAPABILITIES: u8 = 0xe1;
> > +pub(crate) const SPDM_MIN_DATA_TRANSFER_SIZE: u32 = 42;
> Spec reference? C code refers to /* SPDM 1.2.0 margin no 226 */
> It's 269 in 1.3.1
>
> > +
> > +// SPDM cryptographic timeout of this implementation:
> > +// Assume calculations may take up to 1 sec on a busy machine, which equals
> > +// roughly 1 << 20. That's within the limits mandated for responders by CMA
> > +// (1 << 23 usec, PCIe r6.2 sec 6.31.3) and DOE (1 sec, PCIe r6.2 sec 6.30.2).
> > +// Used in GET_CAPABILITIES exchange.
> > +pub(crate) const SPDM_CTEXPONENT: u8 = 20;
> > +
> > +pub(crate) const SPDM_CERT_CAP: u32 = 1 << 1;
>
> Can we use bit_u32() or similar here?
Yep, fixed
> We've tried to move to BIT() for similar defines in the C code, so I'm
> looking for something along those lines. Provides a form of documentation
> that they are single bit fields.
>
>
> > +pub(crate) const SPDM_CHAL_CAP: u32 = 1 << 2;
> > +
> > +pub(crate) const SPDM_REQ_CAPS: u32 = SPDM_CERT_CAP | SPDM_CHAL_CAP;
> > +pub(crate) const SPDM_RSP_MIN_CAPS: u32 = SPDM_CERT_CAP | SPDM_CHAL_CAP;
> > diff --git a/lib/rspdm/lib.rs b/lib/rspdm/lib.rs
> > index 08abcbb21247..5f6653ada59d 100644
> > --- a/lib/rspdm/lib.rs
> > +++ b/lib/rspdm/lib.rs
> > @@ -113,6 +113,10 @@
> > return e.to_errno() as c_int;
> > }
> >
> > + if let Err(e) = state.get_capabilities() {
> > + return e.to_errno() as c_int;
> > + }
> > +
> > 0
> > }
> >
> > diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
> > index 3ad53ec05044..0efad7f341cd 100644
> > --- a/lib/rspdm/state.rs
> > +++ b/lib/rspdm/state.rs
> > @@ -17,9 +17,12 @@
> > };
> >
> > use crate::consts::{
> > - SpdmErrorCode, SPDM_ERROR, SPDM_GET_VERSION_LEN, SPDM_MAX_VER, SPDM_MIN_VER, SPDM_REQ,
> > + SpdmErrorCode, SPDM_ERROR, SPDM_GET_VERSION_LEN, SPDM_MAX_VER, SPDM_MIN_DATA_TRANSFER_SIZE,
> > + SPDM_MIN_VER, SPDM_REQ, SPDM_RSP_MIN_CAPS, SPDM_VER_10, SPDM_VER_11, SPDM_VER_12,
> > +};
>
> This is why I was getting bit grumpy in previous about long lines.
> Fair enough if this is only option, but it's not nice for a review process
> that relies on patches!
Also fixed
>
>
> > +use crate::validator::{
> > + GetCapabilitiesReq, GetCapabilitiesRsp, GetVersionReq, GetVersionRsp, SpdmErrorRsp, SpdmHeader,
> > };
> > -use crate::validator::{GetVersionReq, GetVersionRsp, SpdmErrorRsp, SpdmHeader};
> >
> > /// The current SPDM session state for a device. Based on the
> > /// C `struct spdm_state`.
> > @@ -35,6 +38,8 @@
> > ///
> > /// `version`: Maximum common supported version of requester and responder.
> > /// Negotiated during GET_VERSION exchange.
> > +/// @rsp_caps: Cached capabilities of responder.
> > +/// Received during GET_CAPABILITIES exchange.
> > #[expect(dead_code)]
> > pub struct SpdmState {
> > pub(crate) dev: *mut bindings::device,
> > @@ -46,6 +51,7 @@ pub struct SpdmState {
> >
> > // Negotiated state
> > pub(crate) version: u8,
> > + pub(crate) rsp_caps: u32,
> > }
> >
> > impl SpdmState {
> > @@ -65,6 +71,7 @@ pub(crate) fn new(
> > keyring,
> > validate,
> > version: SPDM_MIN_VER,
> > + rsp_caps: 0,
> > }
> > }
> >
> > @@ -269,4 +276,61 @@ pub(crate) fn get_version(&mut self) -> Result<(), Error> {
> >
> > Ok(())
> > }
> > +
> > + /// Obtain the supported capabilities from an SPDM session and store the
> > + /// information in the `SpdmState`.
> > + pub(crate) fn get_capabilities(&mut self) -> Result<(), Error> {
> > + let mut request = GetCapabilitiesReq::default();
> > + request.version = self.version;
> > +
> > + let (req_sz, rsp_sz) = match self.version {
> > + SPDM_VER_10 => (4, 8),
> > + SPDM_VER_11 => (8, 8),
> > + _ => {
> > + request.data_transfer_size = self.transport_sz.to_le();
> > + request.max_spdm_msg_size = request.data_transfer_size;
> > +
> > + (
> > + core::mem::size_of::<GetCapabilitiesReq>(),
> > + core::mem::size_of::<GetCapabilitiesRsp>(),
> > + )
> > + }
> > + };
> > +
> > + // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
> > + let request_buf = unsafe { from_raw_parts_mut(&mut request as *mut _ as *mut u8, req_sz) };
> > +
> > + let mut response_vec: KVec<u8> = KVec::with_capacity(rsp_sz, GFP_KERNEL)?;
> > + // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
> > + let response_buf = unsafe { from_raw_parts_mut(response_vec.as_mut_ptr(), rsp_sz) };
> > +
> > + let rc = self.spdm_exchange(request_buf, response_buf)?;
> > +
> > + if rc < (rsp_sz as i32) {
> > + pr_err!("Truncated capabilities response\n");
> > + to_result(-(bindings::EIO as i32))?;
> > + }
> > +
> > + // SAFETY: `rc` is the length of data read, which will be smaller
> > + // then the capacity of the vector
>
> Fairly sure it's exactly the size of the of vector in this case.
> Bit (0) of param1 isn't set sop the algorithms block is never included.
It is, my point was that it won't overflow. I'll adjust this to be more clear
>
> > + unsafe { response_vec.inc_len(rc as usize) };
> > +
> > + let response: &mut GetCapabilitiesRsp =
> > + Untrusted::new_mut(&mut response_vec).validate_mut()?;
> > +
> > + self.rsp_caps = u32::from_le(response.flags);
>
> Isn't response packed? So why don't we need the read_unaligned stuff in this
> case but did in the previous patch?
We need it in the previous patch as we are using a pointer to iterate
over the dynamic length version_number_entries array, that doesn't
apply here
>
> > + if (self.rsp_caps & SPDM_RSP_MIN_CAPS) != SPDM_RSP_MIN_CAPS {
> > + to_result(-(bindings::EPROTONOSUPPORT as i32))?;
> > + }
> > +
> > + if self.version >= SPDM_VER_12 {
> > + if response.data_transfer_size < SPDM_MIN_DATA_TRANSFER_SIZE {
> > + pr_err!("Malformed capabilities response\n");
> > + to_result(-(bindings::EPROTO as i32))?;
> > + }
> > + self.transport_sz = self.transport_sz.min(response.data_transfer_size);
> > + }
> > +
> > + Ok(())
> > + }
> > }
> > diff --git a/lib/rspdm/validator.rs b/lib/rspdm/validator.rs
> > index f69be6aa6280..cd792c46767a 100644
> > --- a/lib/rspdm/validator.rs
> > +++ b/lib/rspdm/validator.rs
> > @@ -16,7 +16,7 @@
> > validate::{Unvalidated, Validate},
> > };
> >
> > -use crate::consts::SPDM_GET_VERSION;
> > +use crate::consts::{SPDM_CTEXPONENT, SPDM_GET_CAPABILITIES, SPDM_GET_VERSION, SPDM_REQ_CAPS};
> >
> > #[repr(C, packed)]
> > pub(crate) struct SpdmHeader {
> > @@ -131,3 +131,77 @@ fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err>
> > Ok(rsp)
> > }
> > }
> > +
> > +#[repr(C, packed)]
> > +pub(crate) struct GetCapabilitiesReq {
> > + pub(crate) version: u8,
> > + pub(crate) code: u8,
> > + pub(crate) param1: u8,
> > + pub(crate) param2: u8,
> > +
> > + reserved1: u8,
> > + pub(crate) ctexponent: u8,
> > + reserved2: u16,
> > +
> > + pub(crate) flags: u32,
> > +
> > + /* End of SPDM 1.1 structure */
> > + pub(crate) data_transfer_size: u32,
> > + pub(crate) max_spdm_msg_size: u32,
> > +}
> > +
> > +impl Default for GetCapabilitiesReq {
> > + fn default() -> Self {
> > + GetCapabilitiesReq {
> > + version: 0,
> > + code: SPDM_GET_CAPABILITIES,
> > + param1: 0,
>
> Maybe add some comment on impacts of bit 0.
>
> > + param2: 0,
> > + reserved1: 0,
> > + ctexponent: SPDM_CTEXPONENT,
> > + reserved2: 0,
> > + flags: (SPDM_REQ_CAPS as u32).to_le(),
> > + data_transfer_size: 0,
> > + max_spdm_msg_size: 0,
> > + }
> > + }
> > +}
> > +
> > +#[repr(C, packed)]
> > +pub(crate) struct GetCapabilitiesRsp {
> > + pub(crate) version: u8,
> > + pub(crate) code: u8,
> > + pub(crate) param1: u8,
> > + pub(crate) param2: u8,
>
> param 2 is reserved in 1.3.1 Maybe not pub(crate)?
> If it is not reserved in some particular version perhaps add a comment.
>
> > +
> > + reserved1: u8,
> > + pub(crate) ctexponent: u8,
> > + reserved2: u16,
> > +
> > + pub(crate) flags: u32,
> > +
> > + /* End of SPDM 1.1 structure */
> > + pub(crate) data_transfer_size: u32,
> > + pub(crate) max_spdm_msg_size: u32,
> > +
> > + pub(crate) supported_algorithms: __IncompleteArrayField<__le16>,
> > +}
> > +
> > +impl Validate<&mut Unvalidated<KVec<u8>>> for &mut GetCapabilitiesRsp {
> > + type Err = Error;
> > +
> > + fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> {
> > + let raw = unvalidated.raw_mut();
> > + if raw.len() < mem::size_of::<GetCapabilitiesRsp>() {
>
> So we fail if 1.1? If that's the case I think we should fail when doing
> the version establishment in the previous patch.
Good catch, we shouldn't fail. I'll fix this
Alistair
>
> The c code had some size mangling to deal with this difference.
>
> > + return Err(EINVAL);
> > + }
> > +
> > + let ptr = raw.as_mut_ptr();
> > + // CAST: `GetCapabilitiesRsp` only contains integers and has `repr(C)`.
> > + let ptr = ptr.cast::<GetCapabilitiesRsp>();
> > + // SAFETY: `ptr` came from a reference and the cast above is valid.
> > + let rsp: &mut GetCapabilitiesRsp = unsafe { &mut *ptr };
> > +
> > + Ok(rsp)
> > + }
> > +}
>
^ permalink raw reply [flat|nested] 99+ messages in thread
* [RFC v3 14/27] lib: rspdm: Support SPDM negotiate_algorithms
2026-02-11 3:29 [RFC v3 00/27] lib: Rust implementation of SPDM alistair23
` (12 preceding siblings ...)
2026-02-11 3:29 ` [RFC v3 13/27] lib: rspdm: Support SPDM get_capabilities alistair23
@ 2026-02-11 3:29 ` alistair23
2026-03-03 13:46 ` Jonathan Cameron
2026-02-11 3:29 ` [RFC v3 15/27] lib: rspdm: Support SPDM get_digests alistair23
` (14 subsequent siblings)
28 siblings, 1 reply; 99+ messages in thread
From: alistair23 @ 2026-02-11 3:29 UTC (permalink / raw)
To: bhelgaas, lukas, rust-for-linux, akpm, linux-pci,
Jonathan.Cameron, linux-cxl, linux-kernel
Cc: alex.gaynor, benno.lossin, boqun.feng, a.hindborg, gary,
bjorn3_gh, tmgross, alistair23, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis
From: Alistair Francis <alistair@alistair23.me>
Support the NEGOTIATE_ALGORITHMS SPDM command.
Signed-off-by: Alistair Francis <alistair@alistair23.me>
---
lib/rspdm/consts.rs | 53 ++++++++++
lib/rspdm/lib.rs | 16 ++-
lib/rspdm/state.rs | 214 ++++++++++++++++++++++++++++++++++++++++-
lib/rspdm/validator.rs | 115 +++++++++++++++++++++-
4 files changed, 391 insertions(+), 7 deletions(-)
diff --git a/lib/rspdm/consts.rs b/lib/rspdm/consts.rs
index a6a66e2af1b5..e8a05fd4299b 100644
--- a/lib/rspdm/consts.rs
+++ b/lib/rspdm/consts.rs
@@ -143,6 +143,59 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
pub(crate) const SPDM_CERT_CAP: u32 = 1 << 1;
pub(crate) const SPDM_CHAL_CAP: u32 = 1 << 2;
+pub(crate) const SPDM_MEAS_CAP_MASK: u32 = 3 << 3;
+pub(crate) const SPDM_KEY_EX_CAP: u32 = 1 << 9;
pub(crate) const SPDM_REQ_CAPS: u32 = SPDM_CERT_CAP | SPDM_CHAL_CAP;
pub(crate) const SPDM_RSP_MIN_CAPS: u32 = SPDM_CERT_CAP | SPDM_CHAL_CAP;
+
+pub(crate) const SPDM_NEGOTIATE_ALGS: u8 = 0xe3;
+
+pub(crate) const SPDM_MEAS_SPEC_DMTF: u8 = 1 << 0;
+
+pub(crate) const SPDM_ASYM_RSASSA_2048: u32 = 1 << 0;
+pub(crate) const _SPDM_ASYM_RSAPSS_2048: u32 = 1 << 1;
+pub(crate) const SPDM_ASYM_RSASSA_3072: u32 = 1 << 2;
+pub(crate) const _SPDM_ASYM_RSAPSS_3072: u32 = 1 << 3;
+pub(crate) const SPDM_ASYM_ECDSA_ECC_NIST_P256: u32 = 1 << 4;
+pub(crate) const SPDM_ASYM_RSASSA_4096: u32 = 1 << 5;
+pub(crate) const _SPDM_ASYM_RSAPSS_4096: u32 = 1 << 6;
+pub(crate) const SPDM_ASYM_ECDSA_ECC_NIST_P384: u32 = 1 << 7;
+pub(crate) const SPDM_ASYM_ECDSA_ECC_NIST_P521: u32 = 1 << 8;
+pub(crate) const _SPDM_ASYM_SM2_ECC_SM2_P256: u32 = 1 << 9;
+pub(crate) const _SPDM_ASYM_EDDSA_ED25519: u32 = 1 << 10;
+pub(crate) const _SPDM_ASYM_EDDSA_ED448: u32 = 1 << 11;
+
+pub(crate) const SPDM_HASH_SHA_256: u32 = 1 << 0;
+pub(crate) const SPDM_HASH_SHA_384: u32 = 1 << 1;
+pub(crate) const SPDM_HASH_SHA_512: u32 = 1 << 2;
+
+#[cfg(CONFIG_CRYPTO_RSA)]
+pub(crate) const SPDM_ASYM_RSA: u32 =
+ SPDM_ASYM_RSASSA_2048 | SPDM_ASYM_RSASSA_3072 | SPDM_ASYM_RSASSA_4096;
+#[cfg(not(CONFIG_CRYPTO_RSA))]
+pub(crate) const SPDM_ASYM_RSA: u32 = 0;
+
+#[cfg(CONFIG_CRYPTO_ECDSA)]
+pub(crate) const SPDM_ASYM_ECDSA: u32 =
+ SPDM_ASYM_ECDSA_ECC_NIST_P256 | SPDM_ASYM_ECDSA_ECC_NIST_P384 | SPDM_ASYM_ECDSA_ECC_NIST_P521;
+#[cfg(not(CONFIG_CRYPTO_ECDSA))]
+pub(crate) const SPDM_ASYM_ECDSA: u32 = 0;
+
+#[cfg(CONFIG_CRYPTO_SHA256)]
+pub(crate) const SPDM_HASH_SHA2_256: u32 = SPDM_HASH_SHA_256;
+#[cfg(not(CONFIG_CRYPTO_SHA256))]
+pub(crate) const SPDM_HASH_SHA2_256: u32 = 0;
+
+#[cfg(CONFIG_CRYPTO_SHA512)]
+pub(crate) const SPDM_HASH_SHA2_384_512: u32 = SPDM_HASH_SHA_384 | SPDM_HASH_SHA_512;
+#[cfg(not(CONFIG_CRYPTO_SHA512))]
+pub(crate) const SPDM_HASH_SHA2_384_512: u32 = 0;
+
+pub(crate) const SPDM_ASYM_ALGOS: u32 = SPDM_ASYM_RSA | SPDM_ASYM_ECDSA;
+pub(crate) const SPDM_HASH_ALGOS: u32 = SPDM_HASH_SHA2_256 | SPDM_HASH_SHA2_384_512;
+
+/* Maximum number of ReqAlgStructs sent by this implementation */
+// pub(crate) const SPDM_MAX_REQ_ALG_STRUCT: usize = 4;
+
+pub(crate) const SPDM_OPAQUE_DATA_FMT_GENERAL: u8 = 1 << 1;
diff --git a/lib/rspdm/lib.rs b/lib/rspdm/lib.rs
index 5f6653ada59d..33f706a55e09 100644
--- a/lib/rspdm/lib.rs
+++ b/lib/rspdm/lib.rs
@@ -117,6 +117,10 @@
return e.to_errno() as c_int;
}
+ if let Err(e) = state.negotiate_algs() {
+ return e.to_errno() as c_int;
+ }
+
0
}
@@ -124,4 +128,14 @@
///
/// @spdm_state: SPDM session state
#[no_mangle]
-pub unsafe extern "C" fn spdm_destroy(_state: &'static mut SpdmState) {}
+pub unsafe extern "C" fn spdm_destroy(state: &'static mut SpdmState) {
+ if let Some(desc) = &mut state.desc {
+ unsafe {
+ bindings::kfree(*desc as *mut _ as *mut c_void);
+ }
+ }
+
+ unsafe {
+ bindings::crypto_free_shash(state.shash);
+ }
+}
diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
index 0efad7f341cd..d0b10f27cd9c 100644
--- a/lib/rspdm/state.rs
+++ b/lib/rspdm/state.rs
@@ -12,16 +12,22 @@
use kernel::prelude::*;
use kernel::{
bindings,
- error::{code::EINVAL, to_result, Error},
+ error::{code::EINVAL, from_err_ptr, to_result, Error},
+ str::CStr,
validate::Untrusted,
};
use crate::consts::{
- SpdmErrorCode, SPDM_ERROR, SPDM_GET_VERSION_LEN, SPDM_MAX_VER, SPDM_MIN_DATA_TRANSFER_SIZE,
- SPDM_MIN_VER, SPDM_REQ, SPDM_RSP_MIN_CAPS, SPDM_VER_10, SPDM_VER_11, SPDM_VER_12,
+ SpdmErrorCode, SPDM_ASYM_ALGOS, SPDM_ASYM_ECDSA_ECC_NIST_P256, SPDM_ASYM_ECDSA_ECC_NIST_P384,
+ SPDM_ASYM_ECDSA_ECC_NIST_P521, SPDM_ASYM_RSASSA_2048, SPDM_ASYM_RSASSA_3072,
+ SPDM_ASYM_RSASSA_4096, SPDM_ERROR, SPDM_GET_VERSION_LEN, SPDM_HASH_ALGOS, SPDM_HASH_SHA_256,
+ SPDM_HASH_SHA_384, SPDM_HASH_SHA_512, SPDM_KEY_EX_CAP, SPDM_MAX_VER, SPDM_MEAS_CAP_MASK,
+ SPDM_MEAS_SPEC_DMTF, SPDM_MIN_DATA_TRANSFER_SIZE, SPDM_MIN_VER, SPDM_OPAQUE_DATA_FMT_GENERAL,
+ SPDM_REQ, SPDM_RSP_MIN_CAPS, SPDM_VER_10, SPDM_VER_11, SPDM_VER_12,
};
use crate::validator::{
- GetCapabilitiesReq, GetCapabilitiesRsp, GetVersionReq, GetVersionRsp, SpdmErrorRsp, SpdmHeader,
+ GetCapabilitiesReq, GetCapabilitiesRsp, GetVersionReq, GetVersionRsp, NegotiateAlgsReq,
+ NegotiateAlgsRsp, RegAlg, SpdmErrorRsp, SpdmHeader,
};
/// The current SPDM session state for a device. Based on the
@@ -40,6 +46,28 @@
/// Negotiated during GET_VERSION exchange.
/// @rsp_caps: Cached capabilities of responder.
/// Received during GET_CAPABILITIES exchange.
+/// @base_asym_alg: Asymmetric key algorithm for signature verification of
+/// CHALLENGE_AUTH and MEASUREMENTS messages.
+/// Selected by responder during NEGOTIATE_ALGORITHMS exchange.
+/// @base_hash_alg: Hash algorithm for signature verification of
+/// CHALLENGE_AUTH and MEASUREMENTS messages.
+/// Selected by responder during NEGOTIATE_ALGORITHMS exchange.
+/// @meas_hash_alg: Hash algorithm for measurement blocks.
+/// Selected by responder during NEGOTIATE_ALGORITHMS exchange.
+/// @base_asym_enc: Human-readable name of @base_asym_alg's signature encoding.
+/// Passed to crypto subsystem when calling verify_signature().
+/// @sig_len: Signature length of @base_asym_alg (in bytes).
+/// S or SigLen in SPDM specification.
+/// @base_hash_alg_name: Human-readable name of @base_hash_alg.
+/// Passed to crypto subsystem when calling crypto_alloc_shash() and
+/// verify_signature().
+/// @base_hash_alg_name: Human-readable name of @base_hash_alg.
+/// Passed to crypto subsystem when calling crypto_alloc_shash() and
+/// verify_signature().
+/// @shash: Synchronous hash handle for @base_hash_alg computation.
+/// @desc: Synchronous hash context for @base_hash_alg computation.
+/// @hash_len: Hash length of @base_hash_alg (in bytes).
+/// H in SPDM specification.
#[expect(dead_code)]
pub struct SpdmState {
pub(crate) dev: *mut bindings::device,
@@ -52,6 +80,19 @@ pub struct SpdmState {
// Negotiated state
pub(crate) version: u8,
pub(crate) rsp_caps: u32,
+ pub(crate) base_asym_alg: u32,
+ pub(crate) base_hash_alg: u32,
+ pub(crate) meas_hash_alg: u32,
+
+ /* Signature algorithm */
+ base_asym_enc: &'static CStr,
+ sig_len: usize,
+
+ /* Hash algorithm */
+ base_hash_alg_name: &'static CStr,
+ pub(crate) shash: *mut bindings::crypto_shash,
+ pub(crate) desc: Option<&'static mut bindings::shash_desc>,
+ pub(crate) hash_len: usize,
}
impl SpdmState {
@@ -72,6 +113,15 @@ pub(crate) fn new(
validate,
version: SPDM_MIN_VER,
rsp_caps: 0,
+ base_asym_alg: 0,
+ base_hash_alg: 0,
+ meas_hash_alg: 0,
+ base_asym_enc: unsafe { CStr::from_bytes_with_nul_unchecked(b"\0") },
+ sig_len: 0,
+ base_hash_alg_name: unsafe { CStr::from_bytes_with_nul_unchecked(b"\0") },
+ shash: core::ptr::null_mut(),
+ desc: None,
+ hash_len: 0,
}
}
@@ -333,4 +383,160 @@ 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")?;
+ }
+ 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);
+ }
+ }
+
+ /*
+ * shash and desc allocations are reused for subsequent measurement
+ * retrieval, hence are not freed until spdm_reset().
+ */
+ self.shash =
+ unsafe { bindings::crypto_alloc_shash(self.base_hash_alg_name.as_char_ptr(), 0, 0) };
+ from_err_ptr(self.shash)?;
+
+ let desc_len = core::mem::size_of::<bindings::shash_desc>()
+ + unsafe { bindings::crypto_shash_descsize(self.shash) } as usize;
+
+ let mut desc_vec: KVec<u8> = KVec::with_capacity(desc_len, GFP_KERNEL)?;
+ // SAFETY: `desc_vec` is `desc_len` long
+ let desc_buf = unsafe { from_raw_parts_mut(desc_vec.as_mut_ptr(), desc_len) };
+
+ let desc = unsafe {
+ core::mem::transmute::<*mut c_void, &mut bindings::shash_desc>(
+ desc_buf.as_mut_ptr() as *mut c_void
+ )
+ };
+ 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 {
+ unsafe { to_result(bindings::crypto_shash_init(*desc)) }
+ } else {
+ Err(ENOMEM)
+ }
+ }
+
+ pub(crate) fn negotiate_algs(&mut self) -> Result<(), Error> {
+ let mut request = NegotiateAlgsReq::default();
+ request.version = self.version;
+
+ if self.version >= SPDM_VER_12 && (self.rsp_caps & SPDM_KEY_EX_CAP) == SPDM_KEY_EX_CAP {
+ request.other_params_support = SPDM_OPAQUE_DATA_FMT_GENERAL;
+ }
+
+ // TODO support more algs
+ let reg_alg_entries = 0;
+
+ let req_sz = core::mem::size_of::<NegotiateAlgsReq>()
+ + core::mem::size_of::<RegAlg>() * reg_alg_entries;
+ let rsp_sz = core::mem::size_of::<NegotiateAlgsRsp>()
+ + core::mem::size_of::<RegAlg>() * reg_alg_entries;
+
+ request.length = req_sz as u16;
+ request.param1 = reg_alg_entries as u8;
+
+ // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
+ let request_buf = unsafe { from_raw_parts_mut(&mut request as *mut _ as *mut u8, req_sz) };
+
+ let mut response_vec: KVec<u8> = KVec::with_capacity(rsp_sz, GFP_KERNEL)?;
+ // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
+ let response_buf = unsafe { from_raw_parts_mut(response_vec.as_mut_ptr(), rsp_sz) };
+
+ let rc = self.spdm_exchange(request_buf, response_buf)?;
+
+ if rc < (rsp_sz as i32) {
+ pr_err!("Truncated capabilities response\n");
+ to_result(-(bindings::EIO as i32))?;
+ }
+
+ // SAFETY: `rc` is the length of data read, which will be smaller
+ // then the capacity of the vector
+ unsafe { response_vec.inc_len(rc as usize) };
+
+ let response: &mut NegotiateAlgsRsp =
+ Untrusted::new_mut(&mut response_vec).validate_mut()?;
+
+ self.base_asym_alg = response.base_asym_sel;
+ self.base_hash_alg = response.base_hash_sel;
+ self.meas_hash_alg = response.measurement_hash_algo;
+
+ if self.base_asym_alg & SPDM_ASYM_ALGOS == 0 || self.base_hash_alg & SPDM_HASH_ALGOS == 0 {
+ pr_err!("No common supported algorithms\n");
+ to_result(-(bindings::EPROTO as i32))?;
+ }
+
+ // /* Responder shall select exactly 1 alg (SPDM 1.0.0 table 14) */
+ if self.base_asym_alg.count_ones() != 1
+ || self.base_hash_alg.count_ones() != 1
+ || response.ext_asym_sel_count != 0
+ || response.ext_hash_sel_count != 0
+ || response.param1 > request.param1
+ || response.other_params_sel != request.other_params_support
+ {
+ pr_err!("Malformed algorithms response\n");
+ to_result(-(bindings::EPROTO as i32))?;
+ }
+
+ if self.rsp_caps & SPDM_MEAS_CAP_MASK == SPDM_MEAS_CAP_MASK
+ && (self.meas_hash_alg.count_ones() != 1
+ || response.measurement_specification_sel != SPDM_MEAS_SPEC_DMTF)
+ {
+ pr_err!("Malformed algorithms response\n");
+ to_result(-(bindings::EPROTO as i32))?;
+ }
+
+ self.update_response_algs()?;
+
+ Ok(())
+ }
}
diff --git a/lib/rspdm/validator.rs b/lib/rspdm/validator.rs
index cd792c46767a..036a077c71c3 100644
--- a/lib/rspdm/validator.rs
+++ b/lib/rspdm/validator.rs
@@ -7,7 +7,7 @@
//! Rust implementation of the DMTF Security Protocol and Data Model (SPDM)
//! <https://www.dmtf.org/dsp/DSP0274>
-use crate::bindings::{__IncompleteArrayField, __le16};
+use crate::bindings::{__IncompleteArrayField, __le16, __le32};
use crate::consts::SpdmErrorCode;
use core::mem;
use kernel::prelude::*;
@@ -16,7 +16,10 @@
validate::{Unvalidated, Validate},
};
-use crate::consts::{SPDM_CTEXPONENT, SPDM_GET_CAPABILITIES, SPDM_GET_VERSION, SPDM_REQ_CAPS};
+use crate::consts::{
+ SPDM_ASYM_ALGOS, SPDM_CTEXPONENT, SPDM_GET_CAPABILITIES, SPDM_GET_VERSION, SPDM_HASH_ALGOS,
+ SPDM_MEAS_SPEC_DMTF, SPDM_NEGOTIATE_ALGS, SPDM_REQ_CAPS,
+};
#[repr(C, packed)]
pub(crate) struct SpdmHeader {
@@ -205,3 +208,111 @@ fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err>
Ok(rsp)
}
}
+
+#[repr(C, packed)]
+pub(crate) struct RegAlg {
+ pub(crate) alg_type: u8,
+ pub(crate) alg_count: u8,
+ pub(crate) alg_supported: u16,
+ pub(crate) alg_external: __IncompleteArrayField<__le32>,
+}
+
+#[repr(C, packed)]
+pub(crate) struct NegotiateAlgsReq {
+ pub(crate) version: u8,
+ pub(crate) code: u8,
+ pub(crate) param1: u8,
+ pub(crate) param2: u8,
+
+ pub(crate) length: u16,
+ pub(crate) measurement_specification: u8,
+ pub(crate) other_params_support: u8,
+
+ pub(crate) base_asym_algo: u32,
+ pub(crate) base_hash_algo: u32,
+
+ reserved1: [u8; 12],
+
+ pub(crate) ext_asym_count: u8,
+ pub(crate) ext_hash_count: u8,
+ reserved2: u8,
+ pub(crate) mel_specification: u8,
+
+ pub(crate) ext_asym: __IncompleteArrayField<__le32>,
+ pub(crate) ext_hash: __IncompleteArrayField<__le32>,
+ pub(crate) req_alg_struct: __IncompleteArrayField<RegAlg>,
+}
+
+impl Default for NegotiateAlgsReq {
+ fn default() -> Self {
+ NegotiateAlgsReq {
+ version: 0,
+ code: SPDM_NEGOTIATE_ALGS,
+ param1: 0,
+ param2: 0,
+ length: 0,
+ measurement_specification: SPDM_MEAS_SPEC_DMTF,
+ other_params_support: 0,
+ base_asym_algo: SPDM_ASYM_ALGOS.to_le(),
+ base_hash_algo: SPDM_HASH_ALGOS.to_le(),
+ reserved1: [0u8; 12],
+ ext_asym_count: 0,
+ ext_hash_count: 0,
+ reserved2: 0,
+ mel_specification: 0,
+ ext_asym: __IncompleteArrayField::new(),
+ ext_hash: __IncompleteArrayField::new(),
+ req_alg_struct: __IncompleteArrayField::new(),
+ }
+ }
+}
+
+#[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,
+ 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) req_alg_struct: __IncompleteArrayField<RegAlg>,
+}
+
+impl Validate<&mut Unvalidated<KVec<u8>>> for &mut NegotiateAlgsRsp {
+ type Err = Error;
+
+ fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> {
+ let raw = unvalidated.raw_mut();
+ if raw.len() < mem::size_of::<NegotiateAlgsRsp>() {
+ return Err(EINVAL);
+ }
+
+ let ptr = raw.as_mut_ptr();
+ // CAST: `NegotiateAlgsRsp` only contains integers and has `repr(C)`.
+ let ptr = ptr.cast::<NegotiateAlgsRsp>();
+ // SAFETY: `ptr` came from a reference and the cast above is valid.
+ let rsp: &mut NegotiateAlgsRsp = unsafe { &mut *ptr };
+
+ rsp.base_asym_sel = rsp.base_asym_sel.to_le();
+ rsp.base_hash_sel = rsp.base_hash_sel.to_le();
+ rsp.measurement_hash_algo = rsp.measurement_hash_algo.to_le();
+
+ Ok(rsp)
+ }
+}
--
2.52.0
^ permalink raw reply related [flat|nested] 99+ messages in thread* Re: [RFC v3 14/27] lib: rspdm: Support SPDM negotiate_algorithms
2026-02-11 3:29 ` [RFC v3 14/27] lib: rspdm: Support SPDM negotiate_algorithms alistair23
@ 2026-03-03 13:46 ` Jonathan Cameron
0 siblings, 0 replies; 99+ messages in thread
From: Jonathan Cameron @ 2026-03-03 13:46 UTC (permalink / raw)
To: alistair23
Cc: bhelgaas, lukas, rust-for-linux, akpm, linux-pci, linux-cxl,
linux-kernel, alex.gaynor, benno.lossin, boqun.feng, a.hindborg,
gary, bjorn3_gh, tmgross, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis
On Wed, 11 Feb 2026 13:29:21 +1000
alistair23@gmail.com wrote:
> From: Alistair Francis <alistair@alistair23.me>
>
> Support the NEGOTIATE_ALGORITHMS SPDM command.
>
> Signed-off-by: Alistair Francis <alistair@alistair23.me>
Hi Alistair,
A few comments inline.
Thanks,
Jonathan
> ---
> lib/rspdm/consts.rs | 53 ++++++++++
> lib/rspdm/lib.rs | 16 ++-
> lib/rspdm/state.rs | 214 ++++++++++++++++++++++++++++++++++++++++-
> lib/rspdm/validator.rs | 115 +++++++++++++++++++++-
> 4 files changed, 391 insertions(+), 7 deletions(-)
>
> diff --git a/lib/rspdm/consts.rs b/lib/rspdm/consts.rs
> index a6a66e2af1b5..e8a05fd4299b 100644
> --- a/lib/rspdm/consts.rs
> +++ b/lib/rspdm/consts.rs
> +#[cfg(CONFIG_CRYPTO_RSA)]
> +pub(crate) const SPDM_ASYM_RSA: u32 =
> + SPDM_ASYM_RSASSA_2048 | SPDM_ASYM_RSASSA_3072 | SPDM_ASYM_RSASSA_4096;
> +#[cfg(not(CONFIG_CRYPTO_RSA))]
> +pub(crate) const SPDM_ASYM_RSA: u32 = 0;
Maybe add a comment on why setting these to 0 makes sense when
the support isn't built in because how how they are used.
> diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
> index 0efad7f341cd..d0b10f27cd9c 100644
> --- a/lib/rspdm/state.rs
> +++ b/lib/rspdm/state.rs
...
>
> /// The current SPDM session state for a device. Based on the
> @@ -40,6 +46,28 @@
> /// Negotiated during GET_VERSION exchange.
> /// @rsp_caps: Cached capabilities of responder.
> /// Received during GET_CAPABILITIES exchange.
> +/// @base_asym_alg: Asymmetric key algorithm for signature verification of
> +/// CHALLENGE_AUTH and MEASUREMENTS messages.
> +/// Selected by responder during NEGOTIATE_ALGORITHMS exchange.
> +/// @base_hash_alg: Hash algorithm for signature verification of
> +/// CHALLENGE_AUTH and MEASUREMENTS messages.
> +/// Selected by responder during NEGOTIATE_ALGORITHMS exchange.
> +/// @meas_hash_alg: Hash algorithm for measurement blocks.
> +/// Selected by responder during NEGOTIATE_ALGORITHMS exchange.
> +/// @base_asym_enc: Human-readable name of @base_asym_alg's signature encoding.
> +/// Passed to crypto subsystem when calling verify_signature().
> +/// @sig_len: Signature length of @base_asym_alg (in bytes).
> +/// S or SigLen in SPDM specification.
> +/// @base_hash_alg_name: Human-readable name of @base_hash_alg.
> +/// Passed to crypto subsystem when calling crypto_alloc_shash() and
> +/// verify_signature().
> +/// @base_hash_alg_name: Human-readable name of @base_hash_alg.
Duplicate
> +/// Passed to crypto subsystem when calling crypto_alloc_shash() and
> +/// verify_signature().
> +/// @shash: Synchronous hash handle for @base_hash_alg computation.
> +/// @desc: Synchronous hash context for @base_hash_alg computation.
> +/// @hash_len: Hash length of @base_hash_alg (in bytes).
> +/// H in SPDM specification.
> #[expect(dead_code)]
> pub struct SpdmState {
> pub(crate) dev: *mut bindings::device,
> @@ -52,6 +80,19 @@ pub struct SpdmState {
> // Negotiated state
> pub(crate) version: u8,
> pub(crate) rsp_caps: u32,
> + pub(crate) base_asym_alg: u32,
> + pub(crate) base_hash_alg: u32,
> + pub(crate) meas_hash_alg: u32,
> +
> + /* Signature algorithm */
> + base_asym_enc: &'static CStr,
> + sig_len: usize,
> +
> + /* Hash algorithm */
> + base_hash_alg_name: &'static CStr,
> + pub(crate) shash: *mut bindings::crypto_shash,
> + pub(crate) desc: Option<&'static mut bindings::shash_desc>,
> + pub(crate) hash_len: usize,
> }
> @@ -333,4 +383,160 @@ pub(crate) fn get_capabilities(&mut self) -> Result<(), Error> {
>
> Ok(())
> }
> +
> + pub(crate) fn negotiate_algs(&mut self) -> Result<(), Error> {
> + let mut request = NegotiateAlgsReq::default();
> + request.version = self.version;
> +
> + if self.version >= SPDM_VER_12 && (self.rsp_caps & SPDM_KEY_EX_CAP) == SPDM_KEY_EX_CAP {
> + request.other_params_support = SPDM_OPAQUE_DATA_FMT_GENERAL;
> + }
> +
> + // TODO support more algs
Meh. Why?
:)
> + let reg_alg_entries = 0;
> +
> + let req_sz = core::mem::size_of::<NegotiateAlgsReq>()
> + + core::mem::size_of::<RegAlg>() * reg_alg_entries;
If we are going to include the space for reg_alg_entries, then I'd like to also
have ExtAsymCount and ExtHashCount.
Or just don't include any of them at this stage. Picking one thing we haven't
implemented feels a little misleading.
Not that the c code was consistent on this as it handles responses with
reg_alg_entries != 0 but not requests (a thing that can't ever happen!)
I'll blame a younger more foolish engineer ;)
> + let rsp_sz = core::mem::size_of::<NegotiateAlgsRsp>()
> + + core::mem::size_of::<RegAlg>() * reg_alg_entries;
> +
> + request.length = req_sz as u16;
> + request.param1 = reg_alg_entries as u8;
> +
> + // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
> + let request_buf = unsafe { from_raw_parts_mut(&mut request as *mut _ as *mut u8, req_sz) };
> +
> + let mut response_vec: KVec<u8> = KVec::with_capacity(rsp_sz, GFP_KERNEL)?;
> + // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
> + let response_buf = unsafe { from_raw_parts_mut(response_vec.as_mut_ptr(), rsp_sz) };
> +
> + let rc = self.spdm_exchange(request_buf, response_buf)?;
> +
> + if rc < (rsp_sz as i32) {
So this is fun. We need an exact match for now, but longer term response is absolutely
allowed to be smaller if the requester has asked for things the responder doesn't support.
> + pr_err!("Truncated capabilities response\n");
> + to_result(-(bindings::EIO as i32))?;
> + }
> +
> + // SAFETY: `rc` is the length of data read, which will be smaller
> + // then the capacity of the vector
I think it's exactly the length at this point. But it may be less in future.
> + unsafe { response_vec.inc_len(rc as usize) };
> +
> + let response: &mut NegotiateAlgsRsp =
> + Untrusted::new_mut(&mut response_vec).validate_mut()?;
> +
> + self.base_asym_alg = response.base_asym_sel;
> + self.base_hash_alg = response.base_hash_sel;
> + self.meas_hash_alg = response.measurement_hash_algo;
> +
> + if self.base_asym_alg & SPDM_ASYM_ALGOS == 0 || self.base_hash_alg & SPDM_HASH_ALGOS == 0 {
> + pr_err!("No common supported algorithms\n");
> + to_result(-(bindings::EPROTO as i32))?;
> + }
> +
> + // /* Responder shall select exactly 1 alg (SPDM 1.0.0 table 14) */
> + if self.base_asym_alg.count_ones() != 1
> + || self.base_hash_alg.count_ones() != 1
> + || response.ext_asym_sel_count != 0
> + || response.ext_hash_sel_count != 0
> + || response.param1 > request.param1
> + || response.other_params_sel != request.other_params_support
> + {
> + pr_err!("Malformed algorithms response\n");
> + to_result(-(bindings::EPROTO as i32))?;
> + }
> +
> + if self.rsp_caps & SPDM_MEAS_CAP_MASK == SPDM_MEAS_CAP_MASK
> + && (self.meas_hash_alg.count_ones() != 1
> + || response.measurement_specification_sel != SPDM_MEAS_SPEC_DMTF)
> + {
> + pr_err!("Malformed algorithms response\n");
> + to_result(-(bindings::EPROTO as i32))?;
> + }
> +
> + self.update_response_algs()?;
> +
> + Ok(())
> + }
> }
> diff --git a/lib/rspdm/validator.rs b/lib/rspdm/validator.rs
> index cd792c46767a..036a077c71c3 100644
> --- a/lib/rspdm/validator.rs
> +++ b/lib/rspdm/validator.rs
> #[repr(C, packed)]
> pub(crate) struct SpdmHeader {
> @@ -205,3 +208,111 @@ fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err>
> Ok(rsp)
> }
> }
> +
> +#[repr(C, packed)]
> +pub(crate) struct RegAlg {
> + pub(crate) alg_type: u8,
> + pub(crate) alg_count: u8,
> + pub(crate) alg_supported: u16,
> + pub(crate) alg_external: __IncompleteArrayField<__le32>,
> +}
> +
> +#[repr(C, packed)]
> +pub(crate) struct NegotiateAlgsReq {
> + pub(crate) version: u8,
> + pub(crate) code: u8,
> + pub(crate) param1: u8,
> + pub(crate) param2: u8,
> +
> + pub(crate) length: u16,
> + pub(crate) measurement_specification: u8,
> + pub(crate) other_params_support: u8,
> +
> + pub(crate) base_asym_algo: u32,
> + pub(crate) base_hash_algo: u32,
> +
> + reserved1: [u8; 12],
Maybe it's worth always using arrays of u8 for reserved values just
for consistency. In previous patch you had a u16 for example.
> +
> + pub(crate) ext_asym_count: u8,
> + pub(crate) ext_hash_count: u8,
> + reserved2: u8,
> + pub(crate) mel_specification: u8,
> +
> + pub(crate) ext_asym: __IncompleteArrayField<__le32>,
> + pub(crate) ext_hash: __IncompleteArrayField<__le32>,
> + pub(crate) req_alg_struct: __IncompleteArrayField<RegAlg>,
> +}
> +
> +impl Default for NegotiateAlgsReq {
> + fn default() -> Self {
> + NegotiateAlgsReq {
> + version: 0,
> + code: SPDM_NEGOTIATE_ALGS,
> + param1: 0,
I would call out the meaning of 0 here in some fashion.
maybe just a comment to say it's the size of the req_alg_struct.
> + param2: 0,
> + length: 0,
Maybe should set length default to match what is configure for various
counts? So 32 I think.
> + measurement_specification: SPDM_MEAS_SPEC_DMTF,
> + other_params_support: 0,
> + base_asym_algo: SPDM_ASYM_ALGOS.to_le(),
> + base_hash_algo: SPDM_HASH_ALGOS.to_le(),
> + reserved1: [0u8; 12],
> + ext_asym_count: 0,
> + ext_hash_count: 0,
> + reserved2: 0,
> + mel_specification: 0,
> + ext_asym: __IncompleteArrayField::new(),
> + ext_hash: __IncompleteArrayField::new(),
> + req_alg_struct: __IncompleteArrayField::new(),
> + }
> + }
> +}
> +
> +#[repr(C, packed)]
> +pub(crate) struct NegotiateAlgsRsp {
> + pub(crate) version: u8,
> + pub(crate) code: u8,
> + pub(crate) param1: u8,
As above, perhaps a comment to indicate this has the size of resp_alg_Struct
> + pub(crate) param2: u8,
Reserved so maybe drop the pub(create) as you've done for other response fields
that are always reserved.
> +
> + pub(crate) length: u16,
> + pub(crate) measurement_specification_sel: u8,
> + pub(crate) other_params_sel: u8,
> +
> + pub(crate) measurement_hash_algo: u32,
> + pub(crate) base_asym_sel: u32,
> + pub(crate) base_hash_sel: u32,
> +
> + reserved1: [u8; 11],
> +
> + pub(crate) mel_specification_sel: u8,
Maybe add comments to indicate when fields were added? I don't think this
structure changed length which makes it a bit easier to handle.
> + pub(crate) ext_asym_sel_count: u8,
> + pub(crate) ext_hash_sel_count: u8,
> + reserved2: [u8; 2],
> +
> + pub(crate) ext_asym: __IncompleteArrayField<__le32>,
> + pub(crate) ext_hash: __IncompleteArrayField<__le32>,
> + pub(crate) req_alg_struct: __IncompleteArrayField<RegAlg>,
resp_alg_struct
> +}
> +
> +impl Validate<&mut Unvalidated<KVec<u8>>> for &mut NegotiateAlgsRsp {
> + type Err = Error;
> +
> + fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> {
> + let raw = unvalidated.raw_mut();
> + if raw.len() < mem::size_of::<NegotiateAlgsRsp>() {
> + return Err(EINVAL);
> + }
> +
> + let ptr = raw.as_mut_ptr();
> + // CAST: `NegotiateAlgsRsp` only contains integers and has `repr(C)`.
> + let ptr = ptr.cast::<NegotiateAlgsRsp>();
> + // SAFETY: `ptr` came from a reference and the cast above is valid.
> + let rsp: &mut NegotiateAlgsRsp = unsafe { &mut *ptr };
> +
> + rsp.base_asym_sel = rsp.base_asym_sel.to_le();
> + rsp.base_hash_sel = rsp.base_hash_sel.to_le();
> + rsp.measurement_hash_algo = rsp.measurement_hash_algo.to_le();
> +
> + Ok(rsp)
> + }
> +}
^ permalink raw reply [flat|nested] 99+ messages in thread
* [RFC v3 15/27] lib: rspdm: Support SPDM get_digests
2026-02-11 3:29 [RFC v3 00/27] lib: Rust implementation of SPDM alistair23
` (13 preceding siblings ...)
2026-02-11 3:29 ` [RFC v3 14/27] lib: rspdm: Support SPDM negotiate_algorithms alistair23
@ 2026-02-11 3:29 ` alistair23
2026-03-03 14:29 ` Jonathan Cameron
2026-02-11 3:29 ` [RFC v3 16/27] lib: rspdm: Support SPDM get_certificate alistair23
` (13 subsequent siblings)
28 siblings, 1 reply; 99+ messages in thread
From: alistair23 @ 2026-02-11 3:29 UTC (permalink / raw)
To: bhelgaas, lukas, rust-for-linux, akpm, linux-pci,
Jonathan.Cameron, linux-cxl, linux-kernel
Cc: alex.gaynor, benno.lossin, boqun.feng, a.hindborg, gary,
bjorn3_gh, tmgross, alistair23, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis
From: Alistair Francis <alistair@alistair23.me>
Support the GET_DIGESTS SPDM command.
Signed-off-by: Alistair Francis <alistair@alistair23.me>
---
lib/rspdm/consts.rs | 4 ++
lib/rspdm/lib.rs | 4 ++
lib/rspdm/state.rs | 87 ++++++++++++++++++++++++++++++++++++++++--
lib/rspdm/validator.rs | 52 ++++++++++++++++++++++++-
4 files changed, 142 insertions(+), 5 deletions(-)
diff --git a/lib/rspdm/consts.rs b/lib/rspdm/consts.rs
index e8a05fd4299b..4d39ca2cb584 100644
--- a/lib/rspdm/consts.rs
+++ b/lib/rspdm/consts.rs
@@ -16,6 +16,8 @@
pub(crate) const SPDM_VER_12: u8 = 0x12;
pub(crate) const SPDM_VER_13: u8 = 0x13;
+pub(crate) const SPDM_SLOTS: usize = 8;
+
pub(crate) const SPDM_MIN_VER: u8 = SPDM_VER_10;
pub(crate) const SPDM_MAX_VER: u8 = SPDM_VER_13;
@@ -170,6 +172,8 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
pub(crate) const SPDM_HASH_SHA_384: u32 = 1 << 1;
pub(crate) const SPDM_HASH_SHA_512: u32 = 1 << 2;
+pub(crate) const SPDM_GET_DIGESTS: u8 = 0x81;
+
#[cfg(CONFIG_CRYPTO_RSA)]
pub(crate) const SPDM_ASYM_RSA: u32 =
SPDM_ASYM_RSASSA_2048 | SPDM_ASYM_RSASSA_3072 | SPDM_ASYM_RSASSA_4096;
diff --git a/lib/rspdm/lib.rs b/lib/rspdm/lib.rs
index 33f706a55e09..a7af86d1dc0b 100644
--- a/lib/rspdm/lib.rs
+++ b/lib/rspdm/lib.rs
@@ -121,6 +121,10 @@
return e.to_errno() as c_int;
}
+ if let Err(e) = state.get_digests() {
+ return e.to_errno() as c_int;
+ }
+
0
}
diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
index d0b10f27cd9c..2606b825c494 100644
--- a/lib/rspdm/state.rs
+++ b/lib/rspdm/state.rs
@@ -23,11 +23,11 @@
SPDM_ASYM_RSASSA_4096, SPDM_ERROR, SPDM_GET_VERSION_LEN, SPDM_HASH_ALGOS, SPDM_HASH_SHA_256,
SPDM_HASH_SHA_384, SPDM_HASH_SHA_512, SPDM_KEY_EX_CAP, SPDM_MAX_VER, SPDM_MEAS_CAP_MASK,
SPDM_MEAS_SPEC_DMTF, SPDM_MIN_DATA_TRANSFER_SIZE, SPDM_MIN_VER, SPDM_OPAQUE_DATA_FMT_GENERAL,
- SPDM_REQ, SPDM_RSP_MIN_CAPS, SPDM_VER_10, SPDM_VER_11, SPDM_VER_12,
+ SPDM_REQ, SPDM_RSP_MIN_CAPS, SPDM_SLOTS, SPDM_VER_10, SPDM_VER_11, SPDM_VER_12,
};
use crate::validator::{
- GetCapabilitiesReq, GetCapabilitiesRsp, GetVersionReq, GetVersionRsp, NegotiateAlgsReq,
- NegotiateAlgsRsp, RegAlg, SpdmErrorRsp, SpdmHeader,
+ GetCapabilitiesReq, GetCapabilitiesRsp, GetDigestsReq, GetDigestsRsp, GetVersionReq,
+ GetVersionRsp, NegotiateAlgsReq, NegotiateAlgsRsp, RegAlg, SpdmErrorRsp, SpdmHeader,
};
/// The current SPDM session state for a device. Based on the
@@ -54,6 +54,10 @@
/// Selected by responder during NEGOTIATE_ALGORITHMS exchange.
/// @meas_hash_alg: Hash algorithm for measurement blocks.
/// Selected by responder during NEGOTIATE_ALGORITHMS exchange.
+/// @supported_slots: Bitmask of responder's supported certificate slots.
+/// Received during GET_DIGESTS exchange (from SPDM 1.3).
+/// @provisioned_slots: Bitmask of responder's provisioned certificate slots.
+/// Received during GET_DIGESTS exchange.
/// @base_asym_enc: Human-readable name of @base_asym_alg's signature encoding.
/// Passed to crypto subsystem when calling verify_signature().
/// @sig_len: Signature length of @base_asym_alg (in bytes).
@@ -68,6 +72,9 @@
/// @desc: Synchronous hash context for @base_hash_alg computation.
/// @hash_len: Hash length of @base_hash_alg (in bytes).
/// H in SPDM specification.
+/// @slot: Certificate chain in each of the 8 slots. NULL pointer if a slot is
+/// not populated. Prefixed by the 4 + H header per SPDM 1.0.0 table 15.
+/// @slot_sz: Certificate chain size (in bytes).
#[expect(dead_code)]
pub struct SpdmState {
pub(crate) dev: *mut bindings::device,
@@ -83,6 +90,8 @@ pub struct SpdmState {
pub(crate) base_asym_alg: u32,
pub(crate) base_hash_alg: u32,
pub(crate) meas_hash_alg: u32,
+ pub(crate) supported_slots: u8,
+ pub(crate) provisioned_slots: u8,
/* Signature algorithm */
base_asym_enc: &'static CStr,
@@ -93,6 +102,9 @@ pub struct SpdmState {
pub(crate) shash: *mut bindings::crypto_shash,
pub(crate) desc: Option<&'static mut bindings::shash_desc>,
pub(crate) hash_len: usize,
+
+ // Certificates
+ pub(crate) certs: [KVec<u8>; SPDM_SLOTS],
}
impl SpdmState {
@@ -116,12 +128,15 @@ pub(crate) fn new(
base_asym_alg: 0,
base_hash_alg: 0,
meas_hash_alg: 0,
+ supported_slots: 0,
+ provisioned_slots: 0,
base_asym_enc: unsafe { CStr::from_bytes_with_nul_unchecked(b"\0") },
sig_len: 0,
base_hash_alg_name: unsafe { CStr::from_bytes_with_nul_unchecked(b"\0") },
shash: core::ptr::null_mut(),
desc: None,
hash_len: 0,
+ certs: [const { KVec::new() }; SPDM_SLOTS],
}
}
@@ -539,4 +554,70 @@ pub(crate) fn negotiate_algs(&mut self) -> Result<(), Error> {
Ok(())
}
+
+ pub(crate) fn get_digests(&mut self) -> Result<(), Error> {
+ let mut request = GetDigestsReq::default();
+ request.version = self.version;
+
+ let req_sz = core::mem::size_of::<GetDigestsReq>();
+ let rsp_sz = core::mem::size_of::<GetDigestsRsp>() + SPDM_SLOTS * self.hash_len;
+
+ // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
+ let request_buf = unsafe { from_raw_parts_mut(&mut request as *mut _ as *mut u8, req_sz) };
+
+ let mut response_vec: KVec<u8> = KVec::with_capacity(rsp_sz, GFP_KERNEL)?;
+ // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
+ let response_buf = unsafe { from_raw_parts_mut(response_vec.as_mut_ptr(), rsp_sz) };
+
+ let rc = self.spdm_exchange(request_buf, response_buf)?;
+
+ if rc < (core::mem::size_of::<GetDigestsRsp>() as i32) {
+ pr_err!("Truncated digests response\n");
+ to_result(-(bindings::EIO as i32))?;
+ }
+
+ // SAFETY: `rc` is the length of data read, which will be smaller
+ // then the capacity of the vector
+ unsafe { response_vec.inc_len(rc as usize) };
+
+ let response: &mut GetDigestsRsp = Untrusted::new_mut(&mut response_vec).validate_mut()?;
+
+ if rc
+ < (core::mem::size_of::<GetDigestsReq>()
+ + response.param2.count_ones() as usize * self.hash_len) as i32
+ {
+ pr_err!("Truncated digests response\n");
+ to_result(-(bindings::EIO as i32))?;
+ }
+
+ let mut deprovisioned_slots = self.provisioned_slots & !response.param2;
+ while (deprovisioned_slots.trailing_zeros() as usize) < SPDM_SLOTS {
+ let slot = deprovisioned_slots.trailing_zeros() as usize;
+ self.certs[slot].clear();
+ deprovisioned_slots &= !(1 << slot);
+ }
+
+ self.provisioned_slots = response.param2;
+ if self.provisioned_slots == 0 {
+ pr_err!("No certificates provisioned\n");
+ to_result(-(bindings::EPROTO as i32))?;
+ }
+
+ if self.version >= 0x13 && (response.param2 & !response.param1 != 0) {
+ pr_err!("Malformed digests response\n");
+ to_result(-(bindings::EPROTO as i32))?;
+ }
+
+ let supported_slots = if self.version >= 0x13 {
+ response.param1
+ } else {
+ 0xFF
+ };
+
+ if self.supported_slots != supported_slots {
+ self.supported_slots = supported_slots;
+ }
+
+ Ok(())
+ }
}
diff --git a/lib/rspdm/validator.rs b/lib/rspdm/validator.rs
index 036a077c71c3..2150a23997db 100644
--- a/lib/rspdm/validator.rs
+++ b/lib/rspdm/validator.rs
@@ -17,8 +17,8 @@
};
use crate::consts::{
- SPDM_ASYM_ALGOS, SPDM_CTEXPONENT, SPDM_GET_CAPABILITIES, SPDM_GET_VERSION, SPDM_HASH_ALGOS,
- SPDM_MEAS_SPEC_DMTF, SPDM_NEGOTIATE_ALGS, SPDM_REQ_CAPS,
+ SPDM_ASYM_ALGOS, SPDM_CTEXPONENT, SPDM_GET_CAPABILITIES, SPDM_GET_DIGESTS, SPDM_GET_VERSION,
+ SPDM_HASH_ALGOS, SPDM_MEAS_SPEC_DMTF, SPDM_NEGOTIATE_ALGS, SPDM_REQ_CAPS,
};
#[repr(C, packed)]
@@ -316,3 +316,51 @@ fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err>
Ok(rsp)
}
}
+
+#[repr(C, packed)]
+pub(crate) struct GetDigestsReq {
+ pub(crate) version: u8,
+ pub(crate) code: u8,
+ pub(crate) param1: u8,
+ pub(crate) param2: u8,
+}
+
+impl Default for GetDigestsReq {
+ fn default() -> Self {
+ GetDigestsReq {
+ version: 0,
+ code: SPDM_GET_DIGESTS,
+ param1: 0,
+ param2: 0,
+ }
+ }
+}
+
+#[repr(C, packed)]
+pub(crate) struct GetDigestsRsp {
+ pub(crate) version: u8,
+ pub(crate) code: u8,
+ pub(crate) param1: u8,
+ pub(crate) param2: u8,
+
+ pub(crate) digests: __IncompleteArrayField<u8>,
+}
+
+impl Validate<&mut Unvalidated<KVec<u8>>> for &mut GetDigestsRsp {
+ type Err = Error;
+
+ fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> {
+ let raw = unvalidated.raw_mut();
+ if raw.len() < mem::size_of::<GetDigestsRsp>() {
+ return Err(EINVAL);
+ }
+
+ let ptr = raw.as_mut_ptr();
+ // CAST: `GetDigestsRsp` only contains integers and has `repr(C)`.
+ let ptr = ptr.cast::<GetDigestsRsp>();
+ // SAFETY: `ptr` came from a reference and the cast above is valid.
+ let rsp: &mut GetDigestsRsp = unsafe { &mut *ptr };
+
+ Ok(rsp)
+ }
+}
--
2.52.0
^ permalink raw reply related [flat|nested] 99+ messages in thread* Re: [RFC v3 15/27] lib: rspdm: Support SPDM get_digests
2026-02-11 3:29 ` [RFC v3 15/27] lib: rspdm: Support SPDM get_digests alistair23
@ 2026-03-03 14:29 ` Jonathan Cameron
0 siblings, 0 replies; 99+ messages in thread
From: Jonathan Cameron @ 2026-03-03 14:29 UTC (permalink / raw)
To: alistair23
Cc: bhelgaas, lukas, rust-for-linux, akpm, linux-pci, linux-cxl,
linux-kernel, alex.gaynor, benno.lossin, boqun.feng, a.hindborg,
gary, bjorn3_gh, tmgross, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis
On Wed, 11 Feb 2026 13:29:22 +1000
alistair23@gmail.com wrote:
> From: Alistair Francis <alistair@alistair23.me>
>
> Support the GET_DIGESTS SPDM command.
>
> Signed-off-by: Alistair Francis <alistair@alistair23.me>
A few things inline.
Thanks,
J
> diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
> index d0b10f27cd9c..2606b825c494 100644
> --- a/lib/rspdm/state.rs
> +++ b/lib/rspdm/state.rs
> /// The current SPDM session state for a device. Based on the
> @@ -54,6 +54,10 @@
> /// Selected by responder during NEGOTIATE_ALGORITHMS exchange.
> /// @meas_hash_alg: Hash algorithm for measurement blocks.
> /// Selected by responder during NEGOTIATE_ALGORITHMS exchange.
> +/// @supported_slots: Bitmask of responder's supported certificate slots.
> +/// Received during GET_DIGESTS exchange (from SPDM 1.3).
> +/// @provisioned_slots: Bitmask of responder's provisioned certificate slots.
> +/// Received during GET_DIGESTS exchange.
> /// @base_asym_enc: Human-readable name of @base_asym_alg's signature encoding.
> /// Passed to crypto subsystem when calling verify_signature().
> /// @sig_len: Signature length of @base_asym_alg (in bytes).
> @@ -68,6 +72,9 @@
> /// @desc: Synchronous hash context for @base_hash_alg computation.
> /// @hash_len: Hash length of @base_hash_alg (in bytes).
> /// H in SPDM specification.
> +/// @slot: Certificate chain in each of the 8 slots. NULL pointer if a slot is
> +/// not populated. Prefixed by the 4 + H header per SPDM 1.0.0 table 15.
> +/// @slot_sz: Certificate chain size (in bytes).
That's not matching the code..
> #[expect(dead_code)]
> pub struct SpdmState {
> pub(crate) dev: *mut bindings::device,
> @@ -83,6 +90,8 @@ pub struct SpdmState {
> pub(crate) base_asym_alg: u32,
> pub(crate) base_hash_alg: u32,
> pub(crate) meas_hash_alg: u32,
> + pub(crate) supported_slots: u8,
> + pub(crate) provisioned_slots: u8,
>
> /* Signature algorithm */
> base_asym_enc: &'static CStr,
> @@ -93,6 +102,9 @@ pub struct SpdmState {
> pub(crate) shash: *mut bindings::crypto_shash,
> pub(crate) desc: Option<&'static mut bindings::shash_desc>,
> pub(crate) hash_len: usize,
> +
> + // Certificates
> + pub(crate) certs: [KVec<u8>; SPDM_SLOTS],
> }
> @@ -539,4 +554,70 @@ pub(crate) fn negotiate_algs(&mut self) -> Result<(), Error> {
>
> Ok(())
> }
> +
> + pub(crate) fn get_digests(&mut self) -> Result<(), Error> {
> + let mut request = GetDigestsReq::default();
> + request.version = self.version;
> +
> + let req_sz = core::mem::size_of::<GetDigestsReq>();
> + let rsp_sz = core::mem::size_of::<GetDigestsRsp>() + SPDM_SLOTS * self.hash_len;
> +
> + // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
> + let request_buf = unsafe { from_raw_parts_mut(&mut request as *mut _ as *mut u8, req_sz) };
> +
> + let mut response_vec: KVec<u8> = KVec::with_capacity(rsp_sz, GFP_KERNEL)?;
> + // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
> + let response_buf = unsafe { from_raw_parts_mut(response_vec.as_mut_ptr(), rsp_sz) };
> +
> + let rc = self.spdm_exchange(request_buf, response_buf)?;
> +
> + if rc < (core::mem::size_of::<GetDigestsRsp>() as i32) {
> + pr_err!("Truncated digests response\n");
> + to_result(-(bindings::EIO as i32))?;
> + }
> +
> + // SAFETY: `rc` is the length of data read, which will be smaller
> + // then the capacity of the vector
> + unsafe { response_vec.inc_len(rc as usize) };
> +
> + let response: &mut GetDigestsRsp = Untrusted::new_mut(&mut response_vec).validate_mut()?;
> +
> + if rc
Perhaps another local variable to represent what this is better than the rc name does?
> + < (core::mem::size_of::<GetDigestsReq>()
> + + response.param2.count_ones() as usize * self.hash_len) as i32
> + {
> + pr_err!("Truncated digests response\n");
> + to_result(-(bindings::EIO as i32))?;
> + }
> +
> + let mut deprovisioned_slots = self.provisioned_slots & !response.param2;
> + while (deprovisioned_slots.trailing_zeros() as usize) < SPDM_SLOTS {
> + let slot = deprovisioned_slots.trailing_zeros() as usize;
> + self.certs[slot].clear();
> + deprovisioned_slots &= !(1 << slot);
> + }
> +
> + self.provisioned_slots = response.param2;
> + if self.provisioned_slots == 0 {
> + pr_err!("No certificates provisioned\n");
> + to_result(-(bindings::EPROTO as i32))?;
> + }
> +
> + if self.version >= 0x13 && (response.param2 & !response.param1 != 0) {
Should we use the define for the version?
> + pr_err!("Malformed digests response\n");
> + to_result(-(bindings::EPROTO as i32))?;
> + }
> +
> + let supported_slots = if self.version >= 0x13 {
> + response.param1
> + } else {
> + 0xFF
> + };
> +
> + if self.supported_slots != supported_slots {
> + self.supported_slots = supported_slots;
Why not set it unconditionally? Is this expected to have
side effects?
> + }
> +
> + Ok(())
> + }
> }
> diff --git a/lib/rspdm/validator.rs b/lib/rspdm/validator.rs
> index 036a077c71c3..2150a23997db 100644
> --- a/lib/rspdm/validator.rs
> +++ b/lib/rspdm/validator.rs
> +
> +#[repr(C, packed)]
> +pub(crate) struct GetDigestsRsp {
> + pub(crate) version: u8,
> + pub(crate) code: u8,
> + pub(crate) param1: u8,
> + pub(crate) param2: u8,
> +
> + pub(crate) digests: __IncompleteArrayField<u8>,
Maybe include KeyPairIDs, certificatinfo, and KeyUsageMask
in this structure definition (1.3.1 has them)
Or at very least a comment to say they are a job for another day.
None of those exist yet as we didn't ask for MULTI_KEY_CAP.
> +}
> +
> +impl Validate<&mut Unvalidated<KVec<u8>>> for &mut GetDigestsRsp {
> + type Err = Error;
> +
> + fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> {
> + let raw = unvalidated.raw_mut();
> + if raw.len() < mem::size_of::<GetDigestsRsp>() {
> + return Err(EINVAL);
> + }
> +
> + let ptr = raw.as_mut_ptr();
> + // CAST: `GetDigestsRsp` only contains integers and has `repr(C)`.
> + let ptr = ptr.cast::<GetDigestsRsp>();
> + // SAFETY: `ptr` came from a reference and the cast above is valid.
> + let rsp: &mut GetDigestsRsp = unsafe { &mut *ptr };
> +
> + Ok(rsp)
> + }
> +}
^ permalink raw reply [flat|nested] 99+ messages in thread
* [RFC v3 16/27] lib: rspdm: Support SPDM get_certificate
2026-02-11 3:29 [RFC v3 00/27] lib: Rust implementation of SPDM alistair23
` (14 preceding siblings ...)
2026-02-11 3:29 ` [RFC v3 15/27] lib: rspdm: Support SPDM get_digests alistair23
@ 2026-02-11 3:29 ` alistair23
2026-03-03 14:51 ` Jonathan Cameron
2026-02-11 3:29 ` [RFC v3 17/27] crypto: asymmetric_keys - Load certificate parsing early in boot alistair23
` (12 subsequent siblings)
28 siblings, 1 reply; 99+ messages in thread
From: alistair23 @ 2026-02-11 3:29 UTC (permalink / raw)
To: bhelgaas, lukas, rust-for-linux, akpm, linux-pci,
Jonathan.Cameron, linux-cxl, linux-kernel
Cc: alex.gaynor, benno.lossin, boqun.feng, a.hindborg, gary,
bjorn3_gh, tmgross, alistair23, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis
From: Alistair Francis <alistair@alistair23.me>
Support the GET_CERTIFICATE SPDM command.
Signed-off-by: Alistair Francis <alistair@alistair23.me>
---
lib/rspdm/consts.rs | 2 +
lib/rspdm/lib.rs | 11 ++++
lib/rspdm/state.rs | 127 ++++++++++++++++++++++++++++++++++++++++-
lib/rspdm/validator.rs | 64 ++++++++++++++++++++-
4 files changed, 200 insertions(+), 4 deletions(-)
diff --git a/lib/rspdm/consts.rs b/lib/rspdm/consts.rs
index 4d39ca2cb584..eaf2132da290 100644
--- a/lib/rspdm/consts.rs
+++ b/lib/rspdm/consts.rs
@@ -174,6 +174,8 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
pub(crate) const SPDM_GET_DIGESTS: u8 = 0x81;
+pub(crate) const SPDM_GET_CERTIFICATE: u8 = 0x82;
+
#[cfg(CONFIG_CRYPTO_RSA)]
pub(crate) const SPDM_ASYM_RSA: u32 =
SPDM_ASYM_RSASSA_2048 | SPDM_ASYM_RSASSA_3072 | SPDM_ASYM_RSASSA_4096;
diff --git a/lib/rspdm/lib.rs b/lib/rspdm/lib.rs
index a7af86d1dc0b..b065b3f70356 100644
--- a/lib/rspdm/lib.rs
+++ b/lib/rspdm/lib.rs
@@ -125,6 +125,17 @@
return e.to_errno() as c_int;
}
+ let mut provisioned_slots = state.provisioned_slots;
+ while (provisioned_slots as usize) > 0 {
+ let slot = provisioned_slots.trailing_zeros() as u8;
+
+ if let Err(e) = state.get_certificate(slot) {
+ return e.to_errno() as c_int;
+ }
+
+ provisioned_slots &= !(1 << slot);
+ }
+
0
}
diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
index 2606b825c494..1e5656144611 100644
--- a/lib/rspdm/state.rs
+++ b/lib/rspdm/state.rs
@@ -26,8 +26,9 @@
SPDM_REQ, SPDM_RSP_MIN_CAPS, SPDM_SLOTS, SPDM_VER_10, SPDM_VER_11, SPDM_VER_12,
};
use crate::validator::{
- GetCapabilitiesReq, GetCapabilitiesRsp, GetDigestsReq, GetDigestsRsp, GetVersionReq,
- GetVersionRsp, NegotiateAlgsReq, NegotiateAlgsRsp, RegAlg, SpdmErrorRsp, SpdmHeader,
+ GetCapabilitiesReq, GetCapabilitiesRsp, GetCertificateReq, GetCertificateRsp, GetDigestsReq,
+ GetDigestsRsp, GetVersionReq, GetVersionRsp, NegotiateAlgsReq, NegotiateAlgsRsp, RegAlg,
+ SpdmErrorRsp, SpdmHeader,
};
/// The current SPDM session state for a device. Based on the
@@ -107,6 +108,14 @@ pub struct SpdmState {
pub(crate) certs: [KVec<u8>; SPDM_SLOTS],
}
+#[repr(C, packed)]
+pub(crate) struct SpdmCertChain {
+ length: u16,
+ _reserved: [u8; 2],
+ root_hash: bindings::__IncompleteArrayField<u8>,
+ certificates: bindings::__IncompleteArrayField<u8>,
+}
+
impl SpdmState {
pub(crate) fn new(
dev: *mut bindings::device,
@@ -620,4 +629,118 @@ pub(crate) fn get_digests(&mut self) -> Result<(), Error> {
Ok(())
}
+
+ fn get_cert_exchange(
+ &mut self,
+ request_buf: &mut [u8],
+ response_vec: &mut KVec<u8>,
+ rsp_sz: usize,
+ ) -> Result<&mut GetCertificateRsp, Error> {
+ // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
+ let response_buf = unsafe { from_raw_parts_mut(response_vec.as_mut_ptr(), rsp_sz) };
+
+ let rc = self.spdm_exchange(request_buf, response_buf)?;
+
+ if rc < (core::mem::size_of::<GetCertificateReq>() as i32) {
+ pr_err!("Truncated certificate response\n");
+ to_result(-(bindings::EIO as i32))?;
+ }
+
+ // SAFETY: `rc` is the length of data read, which will be smaller
+ // then the capacity of the vector
+ unsafe { response_vec.inc_len(rc as usize) };
+
+ let response: &mut GetCertificateRsp = Untrusted::new_mut(response_vec).validate_mut()?;
+
+ if rc
+ < (core::mem::size_of::<GetCertificateRsp>() + response.portion_length as usize) as i32
+ {
+ pr_err!("Truncated certificate response\n");
+ to_result(-(bindings::EIO as i32))?;
+ }
+
+ Ok(response)
+ }
+
+ pub(crate) fn get_certificate(&mut self, slot: u8) -> Result<(), Error> {
+ let mut request = GetCertificateReq::default();
+ request.version = self.version;
+ request.param1 = slot;
+
+ let req_sz = core::mem::size_of::<GetCertificateReq>();
+ let rsp_sz = ((core::mem::size_of::<GetCertificateRsp>() + 0xffff) as u32)
+ .min(self.transport_sz) as usize;
+
+ request.offset = 0;
+ request.length = (rsp_sz - core::mem::size_of::<GetCertificateRsp>()).to_le() as u16;
+
+ // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
+ let request_buf = unsafe { from_raw_parts_mut(&mut request as *mut _ as *mut u8, req_sz) };
+
+ let mut response_vec: KVec<u8> = KVec::with_capacity(rsp_sz, GFP_KERNEL)?;
+
+ let response = self.get_cert_exchange(request_buf, &mut response_vec, rsp_sz)?;
+
+ let total_cert_len =
+ ((response.portion_length + response.remainder_length) & 0xFFFF) as usize;
+
+ let mut certs_buf: KVec<u8> = KVec::new();
+
+ certs_buf.extend_from_slice(
+ &response_vec[8..(8 + response.portion_length as usize)],
+ GFP_KERNEL,
+ )?;
+
+ let mut offset: usize = response.portion_length as usize;
+ let mut remainder_length = response.remainder_length as usize;
+
+ while remainder_length > 0 {
+ request.offset = offset.to_le() as u16;
+ request.length = (remainder_length
+ .min(rsp_sz - core::mem::size_of::<GetCertificateRsp>()))
+ .to_le() as u16;
+
+ let request_buf =
+ unsafe { from_raw_parts_mut(&mut request as *mut _ as *mut u8, req_sz) };
+
+ let response = self.get_cert_exchange(request_buf, &mut response_vec, rsp_sz)?;
+
+ if response.portion_length == 0
+ || (response.param1 & 0xF) != slot
+ || offset as u16 + response.portion_length + response.remainder_length
+ != total_cert_len as u16
+ {
+ pr_err!("Malformed certificate response\n");
+ to_result(-(bindings::EPROTO as i32))?;
+ }
+
+ certs_buf.extend_from_slice(
+ &response_vec[8..(8 + response.portion_length as usize)],
+ GFP_KERNEL,
+ )?;
+ offset += response.portion_length as usize;
+ remainder_length = response.remainder_length as usize;
+ }
+
+ let header_length = core::mem::size_of::<SpdmCertChain>() + self.hash_len;
+
+ let ptr = certs_buf.as_mut_ptr();
+ // SAFETY: `SpdmCertChain` is repr(C) and packed, so we can convert it from a slice
+ let ptr = ptr.cast::<SpdmCertChain>();
+ // SAFETY: `ptr` came from a reference and the cast above is valid.
+ let certs: &mut SpdmCertChain = unsafe { &mut *ptr };
+
+ if total_cert_len < header_length
+ || total_cert_len != usize::from_le(certs.length as usize)
+ || total_cert_len != certs_buf.len()
+ {
+ pr_err!("Malformed certificate chain in slot {slot}\n");
+ to_result(-(bindings::EPROTO as i32))?;
+ }
+
+ self.certs[slot as usize].clear();
+ self.certs[slot as usize].extend_from_slice(&certs_buf, GFP_KERNEL)?;
+
+ Ok(())
+ }
}
diff --git a/lib/rspdm/validator.rs b/lib/rspdm/validator.rs
index 2150a23997db..a8bc3378676f 100644
--- a/lib/rspdm/validator.rs
+++ b/lib/rspdm/validator.rs
@@ -17,8 +17,9 @@
};
use crate::consts::{
- SPDM_ASYM_ALGOS, SPDM_CTEXPONENT, SPDM_GET_CAPABILITIES, SPDM_GET_DIGESTS, SPDM_GET_VERSION,
- SPDM_HASH_ALGOS, SPDM_MEAS_SPEC_DMTF, SPDM_NEGOTIATE_ALGS, SPDM_REQ_CAPS,
+ SPDM_ASYM_ALGOS, SPDM_CTEXPONENT, SPDM_GET_CAPABILITIES, SPDM_GET_CERTIFICATE,
+ SPDM_GET_DIGESTS, SPDM_GET_VERSION, SPDM_HASH_ALGOS, SPDM_MEAS_SPEC_DMTF, SPDM_NEGOTIATE_ALGS,
+ SPDM_REQ_CAPS,
};
#[repr(C, packed)]
@@ -364,3 +365,62 @@ fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err>
Ok(rsp)
}
}
+
+#[repr(C, packed)]
+pub(crate) struct GetCertificateReq {
+ pub(crate) version: u8,
+ pub(crate) code: u8,
+ pub(crate) param1: u8,
+ pub(crate) param2: u8,
+
+ pub(crate) offset: u16,
+ pub(crate) length: u16,
+}
+
+impl Default for GetCertificateReq {
+ fn default() -> Self {
+ GetCertificateReq {
+ version: 0,
+ code: SPDM_GET_CERTIFICATE,
+ param1: 0,
+ param2: 0,
+ offset: 0,
+ length: 0,
+ }
+ }
+}
+
+#[repr(C, packed)]
+pub(crate) struct GetCertificateRsp {
+ pub(crate) version: u8,
+ pub(crate) code: u8,
+ pub(crate) param1: u8,
+ pub(crate) param2: u8,
+
+ pub(crate) portion_length: u16,
+ pub(crate) remainder_length: u16,
+
+ pub(crate) cert_chain: __IncompleteArrayField<u8>,
+}
+
+impl Validate<&mut Unvalidated<KVec<u8>>> for &mut GetCertificateRsp {
+ type Err = Error;
+
+ fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> {
+ let raw = unvalidated.raw_mut();
+ if raw.len() < mem::size_of::<GetCertificateRsp>() {
+ return Err(EINVAL);
+ }
+
+ let ptr = raw.as_mut_ptr();
+ // CAST: `GetCertificateRsp` only contains integers and has `repr(C)`.
+ let ptr = ptr.cast::<GetCertificateRsp>();
+ // SAFETY: `ptr` came from a reference and the cast above is valid.
+ let rsp: &mut GetCertificateRsp = unsafe { &mut *ptr };
+
+ rsp.portion_length = rsp.portion_length.to_le();
+ rsp.remainder_length = rsp.remainder_length.to_le();
+
+ Ok(rsp)
+ }
+}
--
2.52.0
^ permalink raw reply related [flat|nested] 99+ messages in thread* Re: [RFC v3 16/27] lib: rspdm: Support SPDM get_certificate
2026-02-11 3:29 ` [RFC v3 16/27] lib: rspdm: Support SPDM get_certificate alistair23
@ 2026-03-03 14:51 ` Jonathan Cameron
0 siblings, 0 replies; 99+ messages in thread
From: Jonathan Cameron @ 2026-03-03 14:51 UTC (permalink / raw)
To: alistair23
Cc: bhelgaas, lukas, rust-for-linux, akpm, linux-pci, linux-cxl,
linux-kernel, alex.gaynor, benno.lossin, boqun.feng, a.hindborg,
gary, bjorn3_gh, tmgross, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis
On Wed, 11 Feb 2026 13:29:23 +1000
alistair23@gmail.com wrote:
> From: Alistair Francis <alistair@alistair23.me>
>
> Support the GET_CERTIFICATE SPDM command.
>
> Signed-off-by: Alistair Francis <alistair@alistair23.me>
Minor things inline. The endian handling in general needs
some care + possibly some tests.
> diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
> index 2606b825c494..1e5656144611 100644
> --- a/lib/rspdm/state.rs
> +++ b/lib/rspdm/state.rs
> @@ -26,8 +26,9 @@
> SPDM_REQ, SPDM_RSP_MIN_CAPS, SPDM_SLOTS, SPDM_VER_10, SPDM_VER_11, SPDM_VER_12,
> };
> impl SpdmState {
> pub(crate) fn new(
> dev: *mut bindings::device,
> @@ -620,4 +629,118 @@ pub(crate) fn get_digests(&mut self) -> Result<(), Error> {
>
> Ok(())
> }
> +
> + fn get_cert_exchange(
> + &mut self,
> + request_buf: &mut [u8],
> + response_vec: &mut KVec<u8>,
> + rsp_sz: usize,
> + ) -> Result<&mut GetCertificateRsp, Error> {
> + // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
> + let response_buf = unsafe { from_raw_parts_mut(response_vec.as_mut_ptr(), rsp_sz) };
> +
> + let rc = self.spdm_exchange(request_buf, response_buf)?;
> +
> + if rc < (core::mem::size_of::<GetCertificateReq>() as i32) {
> + pr_err!("Truncated certificate response\n");
> + to_result(-(bindings::EIO as i32))?;
> + }
> +
> + // SAFETY: `rc` is the length of data read, which will be smaller
> + // then the capacity of the vector
> + unsafe { response_vec.inc_len(rc as usize) };
> +
> + let response: &mut GetCertificateRsp = Untrusted::new_mut(response_vec).validate_mut()?;
> +
> + if rc
> + < (core::mem::size_of::<GetCertificateRsp>() + response.portion_length as usize) as i32
As below, I'd keep the type matching the spec and have the little endian to cpu conversion out here.
> + {
> + pr_err!("Truncated certificate response\n");
> + to_result(-(bindings::EIO as i32))?;
> + }
> +
> + Ok(response)
> + }
> +
> + pub(crate) fn get_certificate(&mut self, slot: u8) -> Result<(), Error> {
> + let mut request = GetCertificateReq::default();
> + request.version = self.version;
> + request.param1 = slot;
> +
> + let req_sz = core::mem::size_of::<GetCertificateReq>();
> + let rsp_sz = ((core::mem::size_of::<GetCertificateRsp>() + 0xffff) as u32)
Similar to earlier comment, do we have U16_MAX or similar available?
> + .min(self.transport_sz) as usize;
> +
> + request.offset = 0;
That's the default, so worth setting here?
> + request.length = (rsp_sz - core::mem::size_of::<GetCertificateRsp>()).to_le() as u16;
Why store it in a u16 if it is le16?
> +
> + // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
> + let request_buf = unsafe { from_raw_parts_mut(&mut request as *mut _ as *mut u8, req_sz) };
> +
> + let mut response_vec: KVec<u8> = KVec::with_capacity(rsp_sz, GFP_KERNEL)?;
> +
> + let response = self.get_cert_exchange(request_buf, &mut response_vec, rsp_sz)?;
> +
> + let total_cert_len =
> + ((response.portion_length + response.remainder_length) & 0xFFFF) as usize;
> +
> + let mut certs_buf: KVec<u8> = KVec::new();
> +
> + certs_buf.extend_from_slice(
> + &response_vec[8..(8 + response.portion_length as usize)],
> + GFP_KERNEL,
> + )?;
> +
> + let mut offset: usize = response.portion_length as usize;
> + let mut remainder_length = response.remainder_length as usize;
> +
> + while remainder_length > 0 {
> + request.offset = offset.to_le() as u16;
Similar to other places, why not just make the type __le16
and avoid need to cast.
> + request.length = (remainder_length
> + .min(rsp_sz - core::mem::size_of::<GetCertificateRsp>()))
> + .to_le() as u16;
Likewise.
> +
> + let request_buf =
> + unsafe { from_raw_parts_mut(&mut request as *mut _ as *mut u8, req_sz) };
> +
> + let response = self.get_cert_exchange(request_buf, &mut response_vec, rsp_sz)?;
> +
> + if response.portion_length == 0
> + || (response.param1 & 0xF) != slot
> + || offset as u16 + response.portion_length + response.remainder_length
> + != total_cert_len as u16
> + {
> + pr_err!("Malformed certificate response\n");
> + to_result(-(bindings::EPROTO as i32))?;
> + }
> +
> + certs_buf.extend_from_slice(
> + &response_vec[8..(8 + response.portion_length as usize)],
> + GFP_KERNEL,
> + )?;
> + offset += response.portion_length as usize;
> + remainder_length = response.remainder_length as usize;
> + }
> +
> + let header_length = core::mem::size_of::<SpdmCertChain>() + self.hash_len;
> +
> + let ptr = certs_buf.as_mut_ptr();
> + // SAFETY: `SpdmCertChain` is repr(C) and packed, so we can convert it from a slice
> + let ptr = ptr.cast::<SpdmCertChain>();
> + // SAFETY: `ptr` came from a reference and the cast above is valid.
> + let certs: &mut SpdmCertChain = unsafe { &mut *ptr };
> +
> + if total_cert_len < header_length
> + || total_cert_len != usize::from_le(certs.length as usize)
That's a confusing bit of casting as you are interpretting an __le16 as a usize
before doing the endian conversion? Seems unlikely to get what you want
on a big endian machine.
> + || total_cert_len != certs_buf.len()
> + {
> + pr_err!("Malformed certificate chain in slot {slot}\n");
> + to_result(-(bindings::EPROTO as i32))?;
> + }
> +
> + self.certs[slot as usize].clear();
> + self.certs[slot as usize].extend_from_slice(&certs_buf, GFP_KERNEL)?;
> +
> + Ok(())
> + }
> }
> diff --git a/lib/rspdm/validator.rs b/lib/rspdm/validator.rs
> index 2150a23997db..a8bc3378676f 100644
> --- a/lib/rspdm/validator.rs
> +++ b/lib/rspdm/validator.rs
> @@ -17,8 +17,9 @@
> };
> #[repr(C, packed)]
> @@ -364,3 +365,62 @@ fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err>
> Ok(rsp)
> }
> }
> +#[repr(C, packed)]
> +pub(crate) struct GetCertificateRsp {
> + pub(crate) version: u8,
> + pub(crate) code: u8,
> + pub(crate) param1: u8,
> + pub(crate) param2: u8,
> +
> + pub(crate) portion_length: u16,
> + pub(crate) remainder_length: u16,
> +
> + pub(crate) cert_chain: __IncompleteArrayField<u8>,
> +}
> +
> +impl Validate<&mut Unvalidated<KVec<u8>>> for &mut GetCertificateRsp {
> + type Err = Error;
> +
> + fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> {
> + let raw = unvalidated.raw_mut();
> + if raw.len() < mem::size_of::<GetCertificateRsp>() {
> + return Err(EINVAL);
> + }
> +
> + let ptr = raw.as_mut_ptr();
> + // CAST: `GetCertificateRsp` only contains integers and has `repr(C)`.
> + let ptr = ptr.cast::<GetCertificateRsp>();
> + // SAFETY: `ptr` came from a reference and the cast above is valid.
> + let rsp: &mut GetCertificateRsp = unsafe { &mut *ptr };
> +
> + rsp.portion_length = rsp.portion_length.to_le();
> + rsp.remainder_length = rsp.remainder_length.to_le();
Why to_le()? I can understand from_le() but then I'm a bit dubious about the
types. My gut feeling is that the validate code should leave these in little
endian and we should convert them only at time of use.
> +
> + Ok(rsp)
> + }
> +}
^ permalink raw reply [flat|nested] 99+ messages in thread
* [RFC v3 17/27] crypto: asymmetric_keys - Load certificate parsing early in boot
2026-02-11 3:29 [RFC v3 00/27] lib: Rust implementation of SPDM alistair23
` (15 preceding siblings ...)
2026-02-11 3:29 ` [RFC v3 16/27] lib: rspdm: Support SPDM get_certificate alistair23
@ 2026-02-11 3:29 ` alistair23
2026-02-11 3:29 ` [RFC v3 18/27] KEYS: Load keyring and certificates " alistair23
` (11 subsequent siblings)
28 siblings, 0 replies; 99+ messages in thread
From: alistair23 @ 2026-02-11 3:29 UTC (permalink / raw)
To: bhelgaas, lukas, rust-for-linux, akpm, linux-pci,
Jonathan.Cameron, linux-cxl, linux-kernel
Cc: alex.gaynor, benno.lossin, boqun.feng, a.hindborg, gary,
bjorn3_gh, tmgross, alistair23, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis
From: Alistair Francis <alistair@alistair23.me>
Work is ongoing to support PCIe device attestation and authentication.
As part of this a PCIe device will provide a X.509 certificate chain
via the SPDM protocol to the kernel.
Linux should verify the chain before enabling the device, which means we
need the certificate store ready before arch initilisation (where PCIe
init happens). Move the certificate and keyring init to postcore to
ensure it's loaded before PCIe devices.
This patch enables X.509 certificate parsing and asymmetric key support
early in the boot process so that it can be used by the key store and
SPDM to verify the certificate chain provided by a PCIe device
via SPDM before we enable it.
Signed-off-by: Alistair Francis <alistair@alistair23.me>
---
crypto/asymmetric_keys/asymmetric_type.c | 2 +-
crypto/asymmetric_keys/x509_public_key.c | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/crypto/asymmetric_keys/asymmetric_type.c b/crypto/asymmetric_keys/asymmetric_type.c
index 2326743310b1..3f209299ee3a 100644
--- a/crypto/asymmetric_keys/asymmetric_type.c
+++ b/crypto/asymmetric_keys/asymmetric_type.c
@@ -677,5 +677,5 @@ static void __exit asymmetric_key_cleanup(void)
unregister_key_type(&key_type_asymmetric);
}
-module_init(asymmetric_key_init);
+postcore_initcall(asymmetric_key_init);
module_exit(asymmetric_key_cleanup);
diff --git a/crypto/asymmetric_keys/x509_public_key.c b/crypto/asymmetric_keys/x509_public_key.c
index 27b4fea37845..2b308345bc6f 100644
--- a/crypto/asymmetric_keys/x509_public_key.c
+++ b/crypto/asymmetric_keys/x509_public_key.c
@@ -258,7 +258,7 @@ static void __exit x509_key_exit(void)
unregister_asymmetric_key_parser(&x509_key_parser);
}
-module_init(x509_key_init);
+postcore_initcall(x509_key_init);
module_exit(x509_key_exit);
MODULE_DESCRIPTION("X.509 certificate parser");
--
2.52.0
^ permalink raw reply related [flat|nested] 99+ messages in thread* [RFC v3 18/27] KEYS: Load keyring and certificates early in boot
2026-02-11 3:29 [RFC v3 00/27] lib: Rust implementation of SPDM alistair23
` (16 preceding siblings ...)
2026-02-11 3:29 ` [RFC v3 17/27] crypto: asymmetric_keys - Load certificate parsing early in boot alistair23
@ 2026-02-11 3:29 ` alistair23
2026-02-11 3:29 ` [RFC v3 19/27] PCI/CMA: Support built in X.509 certificates alistair23
` (10 subsequent siblings)
28 siblings, 0 replies; 99+ messages in thread
From: alistair23 @ 2026-02-11 3:29 UTC (permalink / raw)
To: bhelgaas, lukas, rust-for-linux, akpm, linux-pci,
Jonathan.Cameron, linux-cxl, linux-kernel
Cc: alex.gaynor, benno.lossin, boqun.feng, a.hindborg, gary,
bjorn3_gh, tmgross, alistair23, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis
From: Alistair Francis <alistair@alistair23.me>
Work is ongoing to support PCIe device attestation and authentication.
As part of this a PCIe device will provide a certificate chain via the
SPDM protocol to the kernel.
Linux should verify the chain before enabling the device, which means we
need the certificate store ready before arch initilisation (where PCIe
init happens). Move the certificate and keyring init to postcore to
ensure it's loaded before PCIe devices.
This allows us to verify the certificate chain provided by a PCIe device
via SPDM before we enable it.
Signed-off-by: Alistair Francis <alistair@alistair23.me>
---
certs/system_keyring.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/certs/system_keyring.c b/certs/system_keyring.c
index 9de610bf1f4b..f3d8ea4f70b4 100644
--- a/certs/system_keyring.c
+++ b/certs/system_keyring.c
@@ -260,7 +260,7 @@ static __init int system_trusted_keyring_init(void)
/*
* Must be initialised before we try and load the keys into the keyring.
*/
-device_initcall(system_trusted_keyring_init);
+postcore_initcall(system_trusted_keyring_init);
__init int load_module_cert(struct key *keyring)
{
@@ -293,7 +293,7 @@ static __init int load_system_certificate_list(void)
return x509_load_certificate_list(p, size, builtin_trusted_keys);
}
-late_initcall(load_system_certificate_list);
+postcore_initcall(load_system_certificate_list);
#ifdef CONFIG_SYSTEM_DATA_VERIFICATION
--
2.52.0
^ permalink raw reply related [flat|nested] 99+ messages in thread* [RFC v3 19/27] PCI/CMA: Support built in X.509 certificates
2026-02-11 3:29 [RFC v3 00/27] lib: Rust implementation of SPDM alistair23
` (17 preceding siblings ...)
2026-02-11 3:29 ` [RFC v3 18/27] KEYS: Load keyring and certificates " alistair23
@ 2026-02-11 3:29 ` alistair23
2026-02-11 3:29 ` [RFC v3 20/27] crypto: sha: Load early in boot alistair23
` (9 subsequent siblings)
28 siblings, 0 replies; 99+ messages in thread
From: alistair23 @ 2026-02-11 3:29 UTC (permalink / raw)
To: bhelgaas, lukas, rust-for-linux, akpm, linux-pci,
Jonathan.Cameron, linux-cxl, linux-kernel
Cc: alex.gaynor, benno.lossin, boqun.feng, a.hindborg, gary,
bjorn3_gh, tmgross, alistair23, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis
From: Alistair Francis <alistair@alistair23.me>
Support building the X.509 certificates into the CMA certificate store.
This allows certificates to be built into the kernel which can be used
to authenticate PCIe devices via SPDM.
Signed-off-by: Alistair Francis <alistair@alistair23.me>
---
certs/system_keyring.c | 4 ----
drivers/pci/cma.c | 28 ++++++++++++++++++++++++++++
include/keys/system_keyring.h | 4 ++++
3 files changed, 32 insertions(+), 4 deletions(-)
diff --git a/certs/system_keyring.c b/certs/system_keyring.c
index f3d8ea4f70b4..adfc24139133 100644
--- a/certs/system_keyring.c
+++ b/certs/system_keyring.c
@@ -28,10 +28,6 @@ static struct key *machine_trusted_keys;
static struct key *platform_trusted_keys;
#endif
-extern __initconst const u8 system_certificate_list[];
-extern __initconst const unsigned long system_certificate_list_size;
-extern __initconst const unsigned long module_cert_size;
-
/**
* restrict_link_by_builtin_trusted - Restrict keyring addition by built-in CA
* @dest_keyring: Keyring being linked to.
diff --git a/drivers/pci/cma.c b/drivers/pci/cma.c
index f2c435b04b92..8d64008594e2 100644
--- a/drivers/pci/cma.c
+++ b/drivers/pci/cma.c
@@ -10,6 +10,7 @@
#define dev_fmt(fmt) "CMA: " fmt
+#include <keys/system_keyring.h>
#include <keys/x509-parser.h>
#include <linux/asn1_decoder.h>
#include <linux/oid_registry.h>
@@ -218,8 +219,31 @@ void pci_cma_destroy(struct pci_dev *pdev)
spdm_destroy(pdev->spdm_state);
}
+/*
+ * Load the compiled-in list of X.509 certificates.
+ */
+static int load_system_certificate_list(void)
+{
+ const u8 *p;
+ unsigned long size;
+
+ pr_notice("Loading compiled-in X.509 certificates for CMA\n");
+
+#ifdef CONFIG_MODULE_SIG
+ p = system_certificate_list;
+ size = system_certificate_list_size;
+#else
+ p = system_certificate_list + module_cert_size;
+ size = system_certificate_list_size - module_cert_size;
+#endif
+
+ return x509_load_certificate_list(p, size, pci_cma_keyring);
+}
+
__init static int pci_cma_keyring_init(void)
{
+ int rc;
+
pci_cma_keyring = keyring_alloc(".cma", KUIDT_INIT(0), KGIDT_INIT(0),
current_cred(),
(KEY_POS_ALL & ~KEY_POS_SETATTR) |
@@ -232,6 +256,10 @@ __init static int pci_cma_keyring_init(void)
return PTR_ERR(pci_cma_keyring);
}
+ rc = load_system_certificate_list();
+ if (rc)
+ return rc;
+
return 0;
}
arch_initcall(pci_cma_keyring_init);
diff --git a/include/keys/system_keyring.h b/include/keys/system_keyring.h
index a6c2897bcc63..35a33412e175 100644
--- a/include/keys/system_keyring.h
+++ b/include/keys/system_keyring.h
@@ -130,4 +130,8 @@ static inline void set_platform_trusted_keys(struct key *keyring)
}
#endif
+extern __initconst const u8 system_certificate_list[];
+extern __initconst const unsigned long system_certificate_list_size;
+extern __initconst const unsigned long module_cert_size;
+
#endif /* _KEYS_SYSTEM_KEYRING_H */
--
2.52.0
^ permalink raw reply related [flat|nested] 99+ messages in thread* [RFC v3 20/27] crypto: sha: Load early in boot
2026-02-11 3:29 [RFC v3 00/27] lib: Rust implementation of SPDM alistair23
` (18 preceding siblings ...)
2026-02-11 3:29 ` [RFC v3 19/27] PCI/CMA: Support built in X.509 certificates alistair23
@ 2026-02-11 3:29 ` alistair23
2026-03-03 14:52 ` Jonathan Cameron
2026-02-11 3:29 ` [RFC v3 21/27] crypto: ecdsa: " alistair23
` (8 subsequent siblings)
28 siblings, 1 reply; 99+ messages in thread
From: alistair23 @ 2026-02-11 3:29 UTC (permalink / raw)
To: bhelgaas, lukas, rust-for-linux, akpm, linux-pci,
Jonathan.Cameron, linux-cxl, linux-kernel
Cc: alex.gaynor, benno.lossin, boqun.feng, a.hindborg, gary,
bjorn3_gh, tmgross, alistair23, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis
From: Alistair Francis <alistair.francis@wdc.com>
Work is ongoing to support PCIe device attestation and authentication.
As part of this probing a PCIe device will require generating hashes via
the SPDM protocol to the kernel.
Linux should verify the device before enabling the device, which means we
need the crypto functions to be ready before arch initilisation (where PCIe
init happens). Move the crypto init to postcore to
ensure it's loaded before PCIe devices.
This allows us to verify the certificate chain provided by a PCIe device
via SPDM before we enable it.
Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
---
crypto/sha256.c | 2 +-
crypto/sha3.c | 2 +-
crypto/sha512.c | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/crypto/sha256.c b/crypto/sha256.c
index fb81defe084c..0ebe96f89733 100644
--- a/crypto/sha256.c
+++ b/crypto/sha256.c
@@ -398,7 +398,7 @@ static int __init crypto_sha256_mod_init(void)
{
return crypto_register_shashes(algs, ARRAY_SIZE(algs));
}
-module_init(crypto_sha256_mod_init);
+postcore_initcall(crypto_sha256_mod_init);
static void __exit crypto_sha256_mod_exit(void)
{
diff --git a/crypto/sha3.c b/crypto/sha3.c
index 8f364979ec89..d37ce694b50a 100644
--- a/crypto/sha3.c
+++ b/crypto/sha3.c
@@ -145,7 +145,7 @@ static int __init crypto_sha3_mod_init(void)
{
return crypto_register_shashes(algs, ARRAY_SIZE(algs));
}
-module_init(crypto_sha3_mod_init);
+postcore_initcall(crypto_sha3_mod_init);
static void __exit crypto_sha3_mod_exit(void)
{
diff --git a/crypto/sha512.c b/crypto/sha512.c
index d320fe53913f..6effa6043b55 100644
--- a/crypto/sha512.c
+++ b/crypto/sha512.c
@@ -404,7 +404,7 @@ static int __init crypto_sha512_mod_init(void)
{
return crypto_register_shashes(algs, ARRAY_SIZE(algs));
}
-module_init(crypto_sha512_mod_init);
+postcore_initcall(crypto_sha512_mod_init);
static void __exit crypto_sha512_mod_exit(void)
{
--
2.52.0
^ permalink raw reply related [flat|nested] 99+ messages in thread* Re: [RFC v3 20/27] crypto: sha: Load early in boot
2026-02-11 3:29 ` [RFC v3 20/27] crypto: sha: Load early in boot alistair23
@ 2026-03-03 14:52 ` Jonathan Cameron
0 siblings, 0 replies; 99+ messages in thread
From: Jonathan Cameron @ 2026-03-03 14:52 UTC (permalink / raw)
To: alistair23
Cc: bhelgaas, lukas, rust-for-linux, akpm, linux-pci, linux-cxl,
linux-kernel, alex.gaynor, benno.lossin, boqun.feng, a.hindborg,
gary, bjorn3_gh, tmgross, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis
On Wed, 11 Feb 2026 13:29:27 +1000
alistair23@gmail.com wrote:
> From: Alistair Francis <alistair.francis@wdc.com>
>
> Work is ongoing to support PCIe device attestation and authentication.
> As part of this probing a PCIe device will require generating hashes via
> the SPDM protocol to the kernel.
>
> Linux should verify the device before enabling the device, which means we
> need the crypto functions to be ready before arch initilisation (where PCIe
> init happens). Move the crypto init to postcore to
> ensure it's loaded before PCIe devices.
>
> This allows us to verify the certificate chain provided by a PCIe device
> via SPDM before we enable it.
>
> Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>
^ permalink raw reply [flat|nested] 99+ messages in thread
* [RFC v3 21/27] crypto: ecdsa: Load early in boot
2026-02-11 3:29 [RFC v3 00/27] lib: Rust implementation of SPDM alistair23
` (19 preceding siblings ...)
2026-02-11 3:29 ` [RFC v3 20/27] crypto: sha: Load early in boot alistair23
@ 2026-02-11 3:29 ` alistair23
2026-03-03 14:54 ` Jonathan Cameron
2026-02-11 3:29 ` [RFC v3 22/27] lib: rspdm: Support SPDM certificate validation alistair23
` (7 subsequent siblings)
28 siblings, 1 reply; 99+ messages in thread
From: alistair23 @ 2026-02-11 3:29 UTC (permalink / raw)
To: bhelgaas, lukas, rust-for-linux, akpm, linux-pci,
Jonathan.Cameron, linux-cxl, linux-kernel
Cc: alex.gaynor, benno.lossin, boqun.feng, a.hindborg, gary,
bjorn3_gh, tmgross, alistair23, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis
From: Alistair Francis <alistair.francis@wdc.com>
Work is ongoing to support PCIe device attestation and authentication.
As part of this probing a PCIe device will require signing via
the SPDM protocol to the kernel.
Linux should verify the device before enabling the device, which means we
need the crypto functions to be ready before arch initilisation (where PCIe
init happens). Move the crypto init to postcore to
ensure it's loaded before PCIe devices.
This allows us to verify the certificate chain provided by a PCIe device
via SPDM before we enable it.
Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
---
crypto/ecdsa.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/crypto/ecdsa.c b/crypto/ecdsa.c
index ce8e4364842f..b225911c5266 100644
--- a/crypto/ecdsa.c
+++ b/crypto/ecdsa.c
@@ -334,7 +334,7 @@ static void __exit ecdsa_exit(void)
crypto_unregister_sig(&ecdsa_nist_p521);
}
-module_init(ecdsa_init);
+postcore_initcall(ecdsa_init);
module_exit(ecdsa_exit);
MODULE_LICENSE("GPL");
--
2.52.0
^ permalink raw reply related [flat|nested] 99+ messages in thread* Re: [RFC v3 21/27] crypto: ecdsa: Load early in boot
2026-02-11 3:29 ` [RFC v3 21/27] crypto: ecdsa: " alistair23
@ 2026-03-03 14:54 ` Jonathan Cameron
0 siblings, 0 replies; 99+ messages in thread
From: Jonathan Cameron @ 2026-03-03 14:54 UTC (permalink / raw)
To: alistair23
Cc: bhelgaas, lukas, rust-for-linux, akpm, linux-pci, linux-cxl,
linux-kernel, alex.gaynor, benno.lossin, boqun.feng, a.hindborg,
gary, bjorn3_gh, tmgross, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis
On Wed, 11 Feb 2026 13:29:28 +1000
alistair23@gmail.com wrote:
> From: Alistair Francis <alistair.francis@wdc.com>
>
> Work is ongoing to support PCIe device attestation and authentication.
> As part of this probing a PCIe device will require signing via
> the SPDM protocol to the kernel.
>
> Linux should verify the device before enabling the device, which means we
> need the crypto functions to be ready before arch initilisation (where PCIe
> init happens). Move the crypto init to postcore to
> ensure it's loaded before PCIe devices.
>
> This allows us to verify the certificate chain provided by a PCIe device
> via SPDM before we enable it.
>
> Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
Whilst others may disagree, I think it still makes sense to check
signatures against the provided public leaf cert even if we aren't
checking the cert chain in kernel. That will need this.
Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>
> ---
> crypto/ecdsa.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/crypto/ecdsa.c b/crypto/ecdsa.c
> index ce8e4364842f..b225911c5266 100644
> --- a/crypto/ecdsa.c
> +++ b/crypto/ecdsa.c
> @@ -334,7 +334,7 @@ static void __exit ecdsa_exit(void)
> crypto_unregister_sig(&ecdsa_nist_p521);
> }
>
> -module_init(ecdsa_init);
> +postcore_initcall(ecdsa_init);
> module_exit(ecdsa_exit);
>
> MODULE_LICENSE("GPL");
^ permalink raw reply [flat|nested] 99+ messages in thread
* [RFC v3 22/27] lib: rspdm: Support SPDM certificate validation
2026-02-11 3:29 [RFC v3 00/27] lib: Rust implementation of SPDM alistair23
` (20 preceding siblings ...)
2026-02-11 3:29 ` [RFC v3 21/27] crypto: ecdsa: " alistair23
@ 2026-02-11 3:29 ` alistair23
2026-03-03 15:00 ` Jonathan Cameron
2026-02-11 3:29 ` [RFC v3 23/27] rust: allow extracting the buffer from a CString alistair23
` (6 subsequent siblings)
28 siblings, 1 reply; 99+ messages in thread
From: alistair23 @ 2026-02-11 3:29 UTC (permalink / raw)
To: bhelgaas, lukas, rust-for-linux, akpm, linux-pci,
Jonathan.Cameron, linux-cxl, linux-kernel
Cc: alex.gaynor, benno.lossin, boqun.feng, a.hindborg, gary,
bjorn3_gh, tmgross, alistair23, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis
From: Alistair Francis <alistair@alistair23.me>
Support validating the SPDM certificate chain.
Signed-off-by: Alistair Francis <alistair@alistair23.me>
---
lib/rspdm/lib.rs | 17 ++++++
lib/rspdm/state.rs | 93 ++++++++++++++++++++++++++++++++-
rust/bindings/bindings_helper.h | 2 +
3 files changed, 111 insertions(+), 1 deletion(-)
diff --git a/lib/rspdm/lib.rs b/lib/rspdm/lib.rs
index b065b3f70356..c016306116a3 100644
--- a/lib/rspdm/lib.rs
+++ b/lib/rspdm/lib.rs
@@ -136,6 +136,17 @@
provisioned_slots &= !(1 << slot);
}
+ let mut provisioned_slots = state.provisioned_slots;
+ while (provisioned_slots as usize) > 0 {
+ let slot = provisioned_slots.trailing_zeros() as u8;
+
+ if let Err(e) = state.validate_cert_chain(slot) {
+ return e.to_errno() as c_int;
+ }
+
+ provisioned_slots &= !(1 << slot);
+ }
+
0
}
@@ -144,6 +155,12 @@
/// @spdm_state: SPDM session state
#[no_mangle]
pub unsafe extern "C" fn spdm_destroy(state: &'static mut SpdmState) {
+ if let Some(leaf_key) = &mut state.leaf_key {
+ unsafe {
+ bindings::public_key_free(*leaf_key);
+ }
+ }
+
if let Some(desc) = &mut state.desc {
unsafe {
bindings::kfree(*desc as *mut _ as *mut c_void);
diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
index 1e5656144611..728b920beace 100644
--- a/lib/rspdm/state.rs
+++ b/lib/rspdm/state.rs
@@ -76,7 +76,8 @@
/// @slot: Certificate chain in each of the 8 slots. NULL pointer if a slot is
/// not populated. Prefixed by the 4 + H header per SPDM 1.0.0 table 15.
/// @slot_sz: Certificate chain size (in bytes).
-#[expect(dead_code)]
+/// @leaf_key: Public key portion of leaf certificate against which to check
+/// responder's signatures.
pub struct SpdmState {
pub(crate) dev: *mut bindings::device,
pub(crate) transport: bindings::spdm_transport,
@@ -106,6 +107,7 @@ pub struct SpdmState {
// Certificates
pub(crate) certs: [KVec<u8>; SPDM_SLOTS],
+ pub(crate) leaf_key: Option<*mut bindings::public_key>,
}
#[repr(C, packed)]
@@ -146,6 +148,7 @@ pub(crate) fn new(
desc: None,
hash_len: 0,
certs: [const { KVec::new() }; SPDM_SLOTS],
+ leaf_key: None,
}
}
@@ -743,4 +746,92 @@ pub(crate) fn get_certificate(&mut self, slot: u8) -> Result<(), Error> {
Ok(())
}
+
+ pub(crate) fn validate_cert_chain(&mut self, slot: u8) -> Result<(), Error> {
+ let cert_chain_buf = &self.certs[slot as usize];
+ let cert_chain_len = cert_chain_buf.len();
+ let header_len = 4 + self.hash_len;
+
+ let mut offset = header_len;
+ let mut prev_cert: Option<*mut bindings::x509_certificate> = None;
+
+ while offset < cert_chain_len {
+ let cert_len = unsafe {
+ bindings::x509_get_certificate_length(
+ &cert_chain_buf[offset..] as *const _ as *const u8,
+ cert_chain_len - offset,
+ )
+ };
+
+ if cert_len < 0 {
+ pr_err!("Invalid certificate length\n");
+ to_result(cert_len as i32)?;
+ }
+
+ let _is_leaf_cert = if offset + cert_len as usize == cert_chain_len {
+ true
+ } else {
+ false
+ };
+
+ let cert_ptr = unsafe {
+ from_err_ptr(bindings::x509_cert_parse(
+ &cert_chain_buf[offset..] as *const _ as *const c_void,
+ cert_len as usize,
+ ))?
+ };
+ let cert = unsafe { *cert_ptr };
+
+ if cert.unsupported_sig || cert.blacklisted {
+ to_result(-(bindings::EKEYREJECTED as i32))?;
+ }
+
+ if let Some(prev) = prev_cert {
+ // Check against previous certificate
+ let rc = unsafe { bindings::public_key_verify_signature((*prev).pub_, cert.sig) };
+
+ if rc < 0 {
+ pr_err!("Signature validation error\n");
+ to_result(rc)?;
+ }
+ } else {
+ // Check aginst root keyring
+ let key = unsafe {
+ from_err_ptr(bindings::find_asymmetric_key(
+ self.keyring,
+ (*cert.sig).auth_ids[0],
+ (*cert.sig).auth_ids[1],
+ (*cert.sig).auth_ids[2],
+ false,
+ ))?
+ };
+
+ let rc = unsafe { bindings::verify_signature(key, cert.sig) };
+ unsafe { bindings::key_put(key) };
+
+ if rc < 0 {
+ pr_err!("Root signature validation error\n");
+ to_result(rc)?;
+ }
+ }
+
+ if let Some(prev) = prev_cert {
+ unsafe { bindings::x509_free_certificate(prev) };
+ }
+
+ prev_cert = Some(cert_ptr);
+ offset += cert_len as usize;
+ }
+
+ if let Some(prev) = prev_cert {
+ if let Some(validate) = self.validate {
+ let rc = unsafe { validate(self.dev, slot, prev) };
+ to_result(rc)?;
+ }
+
+ self.leaf_key = unsafe { Some((*prev).pub_) };
+ }
+
+ Ok(())
+ }
}
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 5043eee2a8d6..52ad3e98e036 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -64,6 +64,8 @@
#include <linux/hash.h>
#include <linux/jiffies.h>
#include <linux/jump_label.h>
+#include <keys/asymmetric-type.h>
+#include <keys/x509-parser.h>
#include <linux/mdio.h>
#include <linux/mm.h>
#include <linux/miscdevice.h>
--
2.52.0
^ permalink raw reply related [flat|nested] 99+ messages in thread* Re: [RFC v3 22/27] lib: rspdm: Support SPDM certificate validation
2026-02-11 3:29 ` [RFC v3 22/27] lib: rspdm: Support SPDM certificate validation alistair23
@ 2026-03-03 15:00 ` Jonathan Cameron
0 siblings, 0 replies; 99+ messages in thread
From: Jonathan Cameron @ 2026-03-03 15:00 UTC (permalink / raw)
To: alistair23
Cc: bhelgaas, lukas, rust-for-linux, akpm, linux-pci, linux-cxl,
linux-kernel, alex.gaynor, benno.lossin, boqun.feng, a.hindborg,
gary, bjorn3_gh, tmgross, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis
On Wed, 11 Feb 2026 13:29:29 +1000
alistair23@gmail.com wrote:
> From: Alistair Francis <alistair@alistair23.me>
>
> Support validating the SPDM certificate chain.
>
> Signed-off-by: Alistair Francis <alistair@alistair23.me>
Only minor thing inline + observation on the code that I think we want to remove
for now wrt to checking the root cert.
> diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
> index 1e5656144611..728b920beace 100644
> --- a/lib/rspdm/state.rs
> +++ b/lib/rspdm/state.rs
> @@ -743,4 +746,92 @@ pub(crate) fn get_certificate(&mut self, slot: u8) -> Result<(), Error> {
>
> Ok(())
> }
> +
> + pub(crate) fn validate_cert_chain(&mut self, slot: u8) -> Result<(), Error> {
> + let cert_chain_buf = &self.certs[slot as usize];
> + let cert_chain_len = cert_chain_buf.len();
> + let header_len = 4 + self.hash_len;
> +
> + let mut offset = header_len;
> + let mut prev_cert: Option<*mut bindings::x509_certificate> = None;
> +
> + while offset < cert_chain_len {
> + let cert_len = unsafe {
> + bindings::x509_get_certificate_length(
> + &cert_chain_buf[offset..] as *const _ as *const u8,
> + cert_chain_len - offset,
> + )
> + };
> +
> + if cert_len < 0 {
> + pr_err!("Invalid certificate length\n");
> + to_result(cert_len as i32)?;
> + }
> +
> + let _is_leaf_cert = if offset + cert_len as usize == cert_chain_len {
> + true
> + } else {
> + false
> + };
Same as the following?
let _is_leaf_cert = offset + cert_len as usize == cert_chain_len;
> +
> + let cert_ptr = unsafe {
> + from_err_ptr(bindings::x509_cert_parse(
> + &cert_chain_buf[offset..] as *const _ as *const c_void,
> + cert_len as usize,
> + ))?
> + };
> + let cert = unsafe { *cert_ptr };
> +
> + if cert.unsupported_sig || cert.blacklisted {
> + to_result(-(bindings::EKEYREJECTED as i32))?;
> + }
> +
> + if let Some(prev) = prev_cert {
> + // Check against previous certificate
> + let rc = unsafe { bindings::public_key_verify_signature((*prev).pub_, cert.sig) };
> +
> + if rc < 0 {
> + pr_err!("Signature validation error\n");
> + to_result(rc)?;
> + }
> + } else {
> + // Check aginst root keyring
So this is the check that Dan and Jason are proposing we drop (for now).
Works for me as easy to bring back later and lets us move forward
in the meantime. As long as userspace gets nothing to indicate
that we have in any way checked this root cert we are making no false claims.
> + let key = unsafe {
> + from_err_ptr(bindings::find_asymmetric_key(
> + self.keyring,
> + (*cert.sig).auth_ids[0],
> + (*cert.sig).auth_ids[1],
> + (*cert.sig).auth_ids[2],
> + false,
> + ))?
> + };
> +
> + let rc = unsafe { bindings::verify_signature(key, cert.sig) };
> + unsafe { bindings::key_put(key) };
> +
> + if rc < 0 {
> + pr_err!("Root signature validation error\n");
> + to_result(rc)?;
> + }
> + }
> +
> + if let Some(prev) = prev_cert {
> + unsafe { bindings::x509_free_certificate(prev) };
> + }
> +
> + prev_cert = Some(cert_ptr);
> + offset += cert_len as usize;
> + }
> +
> + if let Some(prev) = prev_cert {
> + if let Some(validate) = self.validate {
> + let rc = unsafe { validate(self.dev, slot, prev) };
> + to_result(rc)?;
> + }
> +
> + self.leaf_key = unsafe { Some((*prev).pub_) };
> + }
> +
> + Ok(())
> + }
> }
^ permalink raw reply [flat|nested] 99+ messages in thread
* [RFC v3 23/27] rust: allow extracting the buffer from a CString
2026-02-11 3:29 [RFC v3 00/27] lib: Rust implementation of SPDM alistair23
` (21 preceding siblings ...)
2026-02-11 3:29 ` [RFC v3 22/27] lib: rspdm: Support SPDM certificate validation alistair23
@ 2026-02-11 3:29 ` alistair23
2026-02-19 14:50 ` Gary Guo
2026-02-11 3:29 ` [RFC v3 24/27] lib: rspdm: Support SPDM challenge alistair23
` (5 subsequent siblings)
28 siblings, 1 reply; 99+ messages in thread
From: alistair23 @ 2026-02-11 3:29 UTC (permalink / raw)
To: bhelgaas, lukas, rust-for-linux, akpm, linux-pci,
Jonathan.Cameron, linux-cxl, linux-kernel
Cc: alex.gaynor, benno.lossin, boqun.feng, a.hindborg, gary,
bjorn3_gh, tmgross, alistair23, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis
From: Alistair Francis <alistair@alistair23.me>
The kernel CString is a wrapper aroud a KVec. This patch allows
retrieving the underlying buffer and consuming the CString. This allows
users to create a CString from a string and then retrieve the underlying
buffer.
Signed-off-by: Alistair Francis <alistair@alistair23.me>
---
rust/kernel/str.rs | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/rust/kernel/str.rs b/rust/kernel/str.rs
index fa87779d2253..17426db83c9f 100644
--- a/rust/kernel/str.rs
+++ b/rust/kernel/str.rs
@@ -844,6 +844,11 @@ pub fn try_from_fmt(args: fmt::Arguments<'_>) -> Result<Self, Error> {
// exist in the buffer.
Ok(Self { buf })
}
+
+ /// Return the internal buffer while consuming the original [`CString`]
+ pub fn into_vec(self) -> KVec<u8> {
+ self.buf
+ }
}
impl Deref for CString {
--
2.52.0
^ permalink raw reply related [flat|nested] 99+ messages in thread* Re: [RFC v3 23/27] rust: allow extracting the buffer from a CString
2026-02-11 3:29 ` [RFC v3 23/27] rust: allow extracting the buffer from a CString alistair23
@ 2026-02-19 14:50 ` Gary Guo
0 siblings, 0 replies; 99+ messages in thread
From: Gary Guo @ 2026-02-19 14:50 UTC (permalink / raw)
To: alistair23
Cc: bhelgaas, lukas, rust-for-linux, akpm, linux-pci,
Jonathan.Cameron, linux-cxl, linux-kernel, alex.gaynor,
benno.lossin, boqun.feng, a.hindborg, bjorn3_gh, tmgross, ojeda,
wilfred.mallawa, aliceryhl, Alistair Francis
On 2026-02-11 03:29, alistair23@gmail.com wrote:
> From: Alistair Francis <alistair@alistair23.me>
>
> The kernel CString is a wrapper aroud a KVec. This patch allows
> retrieving the underlying buffer and consuming the CString. This allows
> users to create a CString from a string and then retrieve the
> underlying
> buffer.
>
> Signed-off-by: Alistair Francis <alistair@alistair23.me>
> ---
> rust/kernel/str.rs | 5 +++++
> 1 file changed, 5 insertions(+)
>
> diff --git a/rust/kernel/str.rs b/rust/kernel/str.rs
> index fa87779d2253..17426db83c9f 100644
> --- a/rust/kernel/str.rs
> +++ b/rust/kernel/str.rs
> @@ -844,6 +844,11 @@ pub fn try_from_fmt(args: fmt::Arguments<'_>) ->
> Result<Self, Error> {
> // exist in the buffer.
> Ok(Self { buf })
> }
> +
> + /// Return the internal buffer while consuming the original
> [`CString`]
> + pub fn into_vec(self) -> KVec<u8> {
> + self.buf
> + }
`#[inline]` here, too.
Feel free to add my R-b after adding it.
Best,
Gary
> }
>
> impl Deref for CString {
^ permalink raw reply [flat|nested] 99+ messages in thread
* [RFC v3 24/27] lib: rspdm: Support SPDM challenge
2026-02-11 3:29 [RFC v3 00/27] lib: Rust implementation of SPDM alistair23
` (22 preceding siblings ...)
2026-02-11 3:29 ` [RFC v3 23/27] rust: allow extracting the buffer from a CString alistair23
@ 2026-02-11 3:29 ` alistair23
2026-03-03 16:54 ` Jonathan Cameron
2026-02-11 3:29 ` [RFC v3 25/27] PCI/CMA: Expose in sysfs whether devices are authenticated alistair23
` (4 subsequent siblings)
28 siblings, 1 reply; 99+ messages in thread
From: alistair23 @ 2026-02-11 3:29 UTC (permalink / raw)
To: bhelgaas, lukas, rust-for-linux, akpm, linux-pci,
Jonathan.Cameron, linux-cxl, linux-kernel
Cc: alex.gaynor, benno.lossin, boqun.feng, a.hindborg, gary,
bjorn3_gh, tmgross, alistair23, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis
From: Alistair Francis <alistair@alistair23.me>
Support the CHALLENGE SPDM command.
Signed-off-by: Alistair Francis <alistair@alistair23.me>
---
lib/rspdm/consts.rs | 6 +
lib/rspdm/lib.rs | 8 +-
lib/rspdm/state.rs | 218 +++++++++++++++++++++++++++++++-
lib/rspdm/validator.rs | 65 +++++++++-
rust/bindings/bindings_helper.h | 1 +
5 files changed, 289 insertions(+), 9 deletions(-)
diff --git a/lib/rspdm/consts.rs b/lib/rspdm/consts.rs
index eaf2132da290..904d8272a1d0 100644
--- a/lib/rspdm/consts.rs
+++ b/lib/rspdm/consts.rs
@@ -176,6 +176,8 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
pub(crate) const SPDM_GET_CERTIFICATE: u8 = 0x82;
+pub(crate) const SPDM_CHALLENGE: u8 = 0x83;
+
#[cfg(CONFIG_CRYPTO_RSA)]
pub(crate) const SPDM_ASYM_RSA: u32 =
SPDM_ASYM_RSASSA_2048 | SPDM_ASYM_RSASSA_3072 | SPDM_ASYM_RSASSA_4096;
@@ -205,3 +207,7 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
// pub(crate) const SPDM_MAX_REQ_ALG_STRUCT: usize = 4;
pub(crate) const SPDM_OPAQUE_DATA_FMT_GENERAL: u8 = 1 << 1;
+
+pub(crate) const SPDM_PREFIX_SZ: usize = 64;
+pub(crate) const SPDM_COMBINED_PREFIX_SZ: usize = 100;
+pub(crate) const SPDM_MAX_OPAQUE_DATA: usize = 1024;
diff --git a/lib/rspdm/lib.rs b/lib/rspdm/lib.rs
index c016306116a3..05fa471bb1e2 100644
--- a/lib/rspdm/lib.rs
+++ b/lib/rspdm/lib.rs
@@ -136,17 +136,23 @@
provisioned_slots &= !(1 << slot);
}
+ let mut verify = true;
let mut provisioned_slots = state.provisioned_slots;
while (provisioned_slots as usize) > 0 {
let slot = provisioned_slots.trailing_zeros() as u8;
if let Err(e) = state.validate_cert_chain(slot) {
- return e.to_errno() as c_int;
+ pr_debug!("Certificate in slot {slot} failed to verify: {e:?}");
+ verify = false;
}
provisioned_slots &= !(1 << slot);
}
+ if let Err(e) = state.challenge(state.provisioned_slots.trailing_zeros() as u8, verify) {
+ return e.to_errno() as c_int;
+ }
+
0
}
diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
index 728b920beace..a4d803af48fe 100644
--- a/lib/rspdm/state.rs
+++ b/lib/rspdm/state.rs
@@ -14,23 +14,27 @@
bindings,
error::{code::EINVAL, from_err_ptr, to_result, Error},
str::CStr,
+ str::CString,
validate::Untrusted,
};
use crate::consts::{
SpdmErrorCode, SPDM_ASYM_ALGOS, SPDM_ASYM_ECDSA_ECC_NIST_P256, SPDM_ASYM_ECDSA_ECC_NIST_P384,
SPDM_ASYM_ECDSA_ECC_NIST_P521, SPDM_ASYM_RSASSA_2048, SPDM_ASYM_RSASSA_3072,
- SPDM_ASYM_RSASSA_4096, SPDM_ERROR, SPDM_GET_VERSION_LEN, SPDM_HASH_ALGOS, SPDM_HASH_SHA_256,
- SPDM_HASH_SHA_384, SPDM_HASH_SHA_512, SPDM_KEY_EX_CAP, SPDM_MAX_VER, SPDM_MEAS_CAP_MASK,
- SPDM_MEAS_SPEC_DMTF, SPDM_MIN_DATA_TRANSFER_SIZE, SPDM_MIN_VER, SPDM_OPAQUE_DATA_FMT_GENERAL,
+ SPDM_ASYM_RSASSA_4096, SPDM_COMBINED_PREFIX_SZ, SPDM_ERROR, SPDM_GET_VERSION_LEN,
+ SPDM_HASH_ALGOS, SPDM_HASH_SHA_256, SPDM_HASH_SHA_384, SPDM_HASH_SHA_512, SPDM_KEY_EX_CAP,
+ SPDM_MAX_OPAQUE_DATA, SPDM_MAX_VER, SPDM_MEAS_CAP_MASK, SPDM_MEAS_SPEC_DMTF,
+ SPDM_MIN_DATA_TRANSFER_SIZE, SPDM_MIN_VER, SPDM_OPAQUE_DATA_FMT_GENERAL, SPDM_PREFIX_SZ,
SPDM_REQ, SPDM_RSP_MIN_CAPS, SPDM_SLOTS, SPDM_VER_10, SPDM_VER_11, SPDM_VER_12,
};
use crate::validator::{
- GetCapabilitiesReq, GetCapabilitiesRsp, GetCertificateReq, GetCertificateRsp, GetDigestsReq,
- GetDigestsRsp, GetVersionReq, GetVersionRsp, NegotiateAlgsReq, NegotiateAlgsRsp, RegAlg,
- SpdmErrorRsp, SpdmHeader,
+ ChallengeReq, ChallengeRsp, GetCapabilitiesReq, GetCapabilitiesRsp, GetCertificateReq,
+ GetCertificateRsp, GetDigestsReq, GetDigestsRsp, GetVersionReq, GetVersionRsp,
+ NegotiateAlgsReq, NegotiateAlgsRsp, RegAlg, SpdmErrorRsp, SpdmHeader,
};
+const SPDM_CONTEXT: &str = "responder-challenge_auth signing";
+
/// The current SPDM session state for a device. Based on the
/// C `struct spdm_state`.
///
@@ -78,6 +82,14 @@
/// @slot_sz: Certificate chain size (in bytes).
/// @leaf_key: Public key portion of leaf certificate against which to check
/// responder's signatures.
+/// @transcript: Concatenation of all SPDM messages exchanged during an
+/// authentication or measurement sequence. Used to verify the signature,
+/// as it is computed over the hashed transcript.
+/// @next_nonce: Requester nonce to be used for the next authentication
+/// sequence. Populated from user space through sysfs.
+/// If user space does not provide a nonce, the kernel uses a random one.
+///
+/// `authenticated`: Whether device was authenticated successfully.
pub struct SpdmState {
pub(crate) dev: *mut bindings::device,
pub(crate) transport: bindings::spdm_transport,
@@ -105,9 +117,15 @@ pub struct SpdmState {
pub(crate) desc: Option<&'static mut bindings::shash_desc>,
pub(crate) hash_len: usize,
+ pub(crate) authenticated: bool,
+
// Certificates
pub(crate) certs: [KVec<u8>; SPDM_SLOTS],
pub(crate) leaf_key: Option<*mut bindings::public_key>,
+
+ transcript: VVec<u8>,
+
+ next_nonce: Option<&'static mut [u8]>,
}
#[repr(C, packed)]
@@ -147,8 +165,11 @@ pub(crate) fn new(
shash: core::ptr::null_mut(),
desc: None,
hash_len: 0,
+ authenticated: false,
certs: [const { KVec::new() }; SPDM_SLOTS],
leaf_key: None,
+ transcript: VVec::new(),
+ next_nonce: None,
}
}
@@ -256,7 +277,7 @@ fn spdm_err(&self, rsp: &SpdmErrorRsp) -> Result<(), Error> {
/// The data in `request_buf` is sent to the device and the response is
/// stored in `response_buf`.
pub(crate) fn spdm_exchange(
- &self,
+ &mut self,
request_buf: &mut [u8],
response_buf: &mut [u8],
) -> Result<i32, Error> {
@@ -264,6 +285,8 @@ pub(crate) fn spdm_exchange(
let request: &mut SpdmHeader = Untrusted::new_mut(request_buf).validate_mut()?;
let response: &SpdmHeader = Untrusted::new_ref(response_buf).validate()?;
+ self.transcript.extend_from_slice(request_buf, GFP_KERNEL)?;
+
let transport_function = self.transport.ok_or(EINVAL)?;
// SAFETY: `transport_function` is provided by the new(), we are
// calling the function.
@@ -331,6 +354,12 @@ pub(crate) fn get_version(&mut self) -> Result<(), Error> {
unsafe { response_vec.inc_len(rc as usize) };
let response: &mut GetVersionRsp = Untrusted::new_mut(&mut response_vec).validate_mut()?;
+ let rsp_sz = core::mem::size_of::<SpdmHeader>()
+ + 2
+ + response.version_number_entry_count as usize * 2;
+
+ self.transcript
+ .extend_from_slice(&response_vec[..rsp_sz], GFP_KERNEL)?;
let mut foundver = false;
for i in 0..response.version_number_entry_count {
@@ -395,6 +424,9 @@ pub(crate) fn get_capabilities(&mut self) -> Result<(), Error> {
let response: &mut GetCapabilitiesRsp =
Untrusted::new_mut(&mut response_vec).validate_mut()?;
+ self.transcript
+ .extend_from_slice(&response_vec[..rsp_sz], GFP_KERNEL)?;
+
self.rsp_caps = u32::from_le(response.flags);
if (self.rsp_caps & SPDM_RSP_MIN_CAPS) != SPDM_RSP_MIN_CAPS {
to_result(-(bindings::EPROTONOSUPPORT as i32))?;
@@ -533,6 +565,9 @@ pub(crate) fn negotiate_algs(&mut self) -> Result<(), Error> {
let response: &mut NegotiateAlgsRsp =
Untrusted::new_mut(&mut response_vec).validate_mut()?;
+ self.transcript
+ .extend_from_slice(&response_vec, GFP_KERNEL)?;
+
self.base_asym_alg = response.base_asym_sel;
self.base_hash_alg = response.base_hash_sel;
self.meas_hash_alg = response.measurement_hash_algo;
@@ -593,6 +628,10 @@ pub(crate) fn get_digests(&mut self) -> Result<(), Error> {
unsafe { response_vec.inc_len(rc as usize) };
let response: &mut GetDigestsRsp = Untrusted::new_mut(&mut response_vec).validate_mut()?;
+ let rsp_sz = core::mem::size_of::<SpdmHeader>() + response.param2 as usize * self.hash_len;
+
+ self.transcript
+ .extend_from_slice(&response_vec[..rsp_sz], GFP_KERNEL)?;
if rc
< (core::mem::size_of::<GetDigestsReq>()
@@ -654,6 +693,10 @@ fn get_cert_exchange(
unsafe { response_vec.inc_len(rc as usize) };
let response: &mut GetCertificateRsp = Untrusted::new_mut(response_vec).validate_mut()?;
+ let rsp_sz = core::mem::size_of::<SpdmHeader>() + 4 + response.portion_length as usize;
+
+ self.transcript
+ .extend_from_slice(&response_vec[..rsp_sz], GFP_KERNEL)?;
if rc
< (core::mem::size_of::<GetCertificateRsp>() + response.portion_length as usize) as i32
@@ -834,4 +877,165 @@ pub(crate) fn validate_cert_chain(&mut self, slot: u8) -> Result<(), Error> {
Ok(())
}
+
+ pub(crate) fn challenge_rsp_len(&mut self, nonce_len: usize, opaque_len: usize) -> usize {
+ let mut length =
+ core::mem::size_of::<SpdmHeader>() + self.hash_len + nonce_len + opaque_len + 2;
+
+ if self.version >= 0x13 {
+ length += 8;
+ }
+
+ length + self.sig_len
+ }
+
+ fn verify_signature(&mut self, response_vec: &mut [u8]) -> Result<(), Error> {
+ let sig_start = response_vec.len() - self.sig_len;
+ let mut sig = bindings::public_key_signature::default();
+ let mut mhash: KVec<u8> = KVec::new();
+
+ sig.s = &mut response_vec[sig_start..] as *mut _ as *mut u8;
+ sig.s_size = self.sig_len as u32;
+ 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 {
+ to_result(-(bindings::EPROTO as i32))?;
+ }
+
+ if self.version <= 0x11 {
+ sig.m = m[SPDM_COMBINED_PREFIX_SZ..].as_mut_ptr();
+ } else {
+ let major = self.version >> 4;
+ let minor = self.version & 0xF;
+
+ let output = 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}.*"))?;
+ let mut buf = output.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)?;
+
+ m[..SPDM_COMBINED_PREFIX_SZ].copy_from_slice(&buf);
+
+ mhash.extend_with(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,
+ m.as_ptr(),
+ m.len() as u32,
+ mhash.as_mut_ptr(),
+ ))?;
+ };
+ } else {
+ to_result(-(bindings::EPROTO as i32))?;
+ }
+
+ 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 {
+ to_result(-(bindings::EPROTO as i32))
+ }
+ }
+
+ pub(crate) fn challenge(&mut self, slot: u8, verify: bool) -> Result<(), Error> {
+ let mut request = ChallengeReq::default();
+ request.version = self.version;
+ request.param1 = slot;
+
+ let nonce_len = request.nonce.len();
+
+ if let Some(nonce) = &self.next_nonce {
+ request.nonce.copy_from_slice(&nonce);
+ self.next_nonce = None;
+ } else {
+ unsafe {
+ bindings::get_random_bytes(&mut request.nonce as *mut _ as *mut c_void, nonce_len)
+ };
+ }
+
+ let req_sz = if self.version <= 0x12 {
+ core::mem::size_of::<ChallengeReq>() - 8
+ } else {
+ core::mem::size_of::<ChallengeReq>()
+ };
+
+ let rsp_sz = self.challenge_rsp_len(nonce_len, SPDM_MAX_OPAQUE_DATA);
+
+ // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
+ let request_buf = unsafe { from_raw_parts_mut(&mut request as *mut _ as *mut u8, req_sz) };
+
+ let mut response_vec: KVec<u8> = KVec::with_capacity(rsp_sz, GFP_KERNEL)?;
+ // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
+ let response_buf = unsafe { from_raw_parts_mut(response_vec.as_mut_ptr(), rsp_sz) };
+
+ let rc = self.spdm_exchange(request_buf, response_buf)?;
+
+ if rc < (core::mem::size_of::<ChallengeRsp>() as i32) {
+ pr_err!("Truncated challenge response\n");
+ to_result(-(bindings::EIO as i32))?;
+ }
+
+ // SAFETY: `rc` is the length of data read, which will be smaller
+ // then the capacity of the vector
+ unsafe { response_vec.inc_len(rc as usize) };
+
+ let _response: &mut ChallengeRsp = Untrusted::new_mut(&mut response_vec).validate_mut()?;
+
+ let opaque_len_offset = core::mem::size_of::<SpdmHeader>() + self.hash_len + nonce_len;
+ let opaque_len = u16::from_le_bytes(
+ response_vec[opaque_len_offset..(opaque_len_offset + 2)]
+ .try_into()
+ .unwrap_or([0, 0]),
+ );
+
+ let rsp_sz = self.challenge_rsp_len(nonce_len, opaque_len as usize);
+
+ if rc < rsp_sz as i32 {
+ pr_err!("Truncated challenge response\n");
+ to_result(-(bindings::EIO as i32))?;
+ }
+
+ self.transcript
+ .extend_from_slice(&response_vec[..rsp_sz], GFP_KERNEL)?;
+
+ if verify {
+ /* Verify signature at end of transcript against leaf key */
+ match self.verify_signature(&mut response_vec[..rsp_sz]) {
+ Ok(()) => {
+ pr_info!("Authenticated with certificate slot {slot}");
+ self.authenticated = true;
+ }
+ Err(e) => {
+ pr_err!("Cannot verify challenge_auth signature: {e:?}");
+ self.authenticated = false;
+ }
+ };
+ }
+
+ Ok(())
+ }
}
diff --git a/lib/rspdm/validator.rs b/lib/rspdm/validator.rs
index a8bc3378676f..f8a5337841f0 100644
--- a/lib/rspdm/validator.rs
+++ b/lib/rspdm/validator.rs
@@ -17,7 +17,7 @@
};
use crate::consts::{
- SPDM_ASYM_ALGOS, SPDM_CTEXPONENT, SPDM_GET_CAPABILITIES, SPDM_GET_CERTIFICATE,
+ SPDM_ASYM_ALGOS, SPDM_CHALLENGE, SPDM_CTEXPONENT, SPDM_GET_CAPABILITIES, SPDM_GET_CERTIFICATE,
SPDM_GET_DIGESTS, SPDM_GET_VERSION, SPDM_HASH_ALGOS, SPDM_MEAS_SPEC_DMTF, SPDM_NEGOTIATE_ALGS,
SPDM_REQ_CAPS,
};
@@ -424,3 +424,66 @@ fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err>
Ok(rsp)
}
}
+
+#[repr(C, packed)]
+pub(crate) struct ChallengeReq {
+ pub(crate) version: u8,
+ pub(crate) code: u8,
+ pub(crate) param1: u8,
+ pub(crate) param2: u8,
+
+ pub(crate) nonce: [u8; 32],
+ pub(crate) context: [u8; 8],
+}
+
+impl Default for ChallengeReq {
+ fn default() -> Self {
+ ChallengeReq {
+ version: 0,
+ code: SPDM_CHALLENGE,
+ param1: 0,
+ param2: 0,
+ nonce: [0; 32],
+ context: [0; 8],
+ }
+ }
+}
+
+#[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],
+ pub(crate) message_summary_hash: __IncompleteArrayField<u8>,
+
+ pub(crate) opaque_data_len: u16,
+ pub(crate) opaque_data: __IncompleteArrayField<u8>,
+
+ pub(crate) context: [u8; 8],
+ pub(crate) signature: __IncompleteArrayField<u8>,
+}
+
+impl Validate<&mut Unvalidated<KVec<u8>>> for &mut ChallengeRsp {
+ type Err = Error;
+
+ fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> {
+ let raw = unvalidated.raw_mut();
+ if raw.len() < mem::size_of::<ChallengeRsp>() {
+ return Err(EINVAL);
+ }
+
+ let ptr = raw.as_mut_ptr();
+ // CAST: `ChallengeRsp` only contains integers and has `repr(C)`.
+ let ptr = ptr.cast::<ChallengeRsp>();
+ // SAFETY: `ptr` came from a reference and the cast above is valid.
+ let rsp: &mut ChallengeRsp = unsafe { &mut *ptr };
+
+ // rsp.opaque_data_len = rsp.opaque_data_len.to_le();
+
+ Ok(rsp)
+ }
+}
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 52ad3e98e036..35e4378fb9dc 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -35,6 +35,7 @@
#include <drm/drm_gem.h>
#include <drm/drm_ioctl.h>
#include <crypto/hash.h>
+#include <crypto/public_key.h>
#include <kunit/test.h>
#include <linux/auxiliary_bus.h>
#include <linux/bitmap.h>
--
2.52.0
^ permalink raw reply related [flat|nested] 99+ messages in thread* Re: [RFC v3 24/27] lib: rspdm: Support SPDM challenge
2026-02-11 3:29 ` [RFC v3 24/27] lib: rspdm: Support SPDM challenge alistair23
@ 2026-03-03 16:54 ` Jonathan Cameron
0 siblings, 0 replies; 99+ messages in thread
From: Jonathan Cameron @ 2026-03-03 16:54 UTC (permalink / raw)
To: alistair23
Cc: bhelgaas, lukas, rust-for-linux, akpm, linux-pci, linux-cxl,
linux-kernel, alex.gaynor, benno.lossin, boqun.feng, a.hindborg,
gary, bjorn3_gh, tmgross, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis
On Wed, 11 Feb 2026 13:29:31 +1000
alistair23@gmail.com wrote:
> From: Alistair Francis <alistair@alistair23.me>
>
> Support the CHALLENGE SPDM command.
>
> Signed-off-by: Alistair Francis <alistair@alistair23.me>
Nicely broken out. I was wondering when the transcript for the
hash might show up, but you sensibly kept that delight for only
being done when you need it. Might be worth talking a bit more
about that in the patch description!
Minor comments inline.
J
> diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
> index 728b920beace..a4d803af48fe 100644
> --- a/lib/rspdm/state.rs
> +++ b/lib/rspdm/state.rs
...
> @@ -834,4 +877,165 @@ pub(crate) fn validate_cert_chain(&mut self, slot: u8) -> Result<(), Error> {
>
> Ok(())
> }
> +
> + pub(crate) fn challenge_rsp_len(&mut self, nonce_len: usize, opaque_len: usize) -> usize {
> + let mut length =
> + core::mem::size_of::<SpdmHeader>() + self.hash_len + nonce_len + opaque_len + 2;
As below, perhaps add a comment at least that MSHLength == 0
> +
> + if self.version >= 0x13 {
> + length += 8;
> + }
> +
> + length + self.sig_len
> + }
> +
> + fn verify_signature(&mut self, response_vec: &mut [u8]) -> Result<(), Error> {
> + let sig_start = response_vec.len() - self.sig_len;
> + let mut sig = bindings::public_key_signature::default();
> + let mut mhash: KVec<u8> = KVec::new();
> +
> + sig.s = &mut response_vec[sig_start..] as *mut _ as *mut u8;
> + sig.s_size = self.sig_len as u32;
Perhaps it makes sense to extract the signature at the caller and only pass
that in here rather than the whole response_vec.
> + sig.encoding = self.base_asym_enc.as_ptr() as *const u8;
> + sig.hash_algo = self.base_hash_alg_name.as_ptr() as *const u8;
> +
...
> + pub(crate) fn challenge(&mut self, slot: u8, verify: bool) -> Result<(), Error> {
> + let mut request = ChallengeReq::default();
> + request.version = self.version;
> + request.param1 = slot;
> +
> + let nonce_len = request.nonce.len();
> +
> + if let Some(nonce) = &self.next_nonce {
> + request.nonce.copy_from_slice(&nonce);
> + self.next_nonce = None;
> + } else {
> + unsafe {
> + bindings::get_random_bytes(&mut request.nonce as *mut _ as *mut c_void, nonce_len)
> + };
> + }
> +
> + let req_sz = if self.version <= 0x12 {
> + core::mem::size_of::<ChallengeReq>() - 8
No means to do offset_of type stuff in Rust? Would make the sizing explicitly reflect the
structure.
> + } else {
> + core::mem::size_of::<ChallengeReq>()
> + };
> +
> + let rsp_sz = self.challenge_rsp_len(nonce_len, SPDM_MAX_OPAQUE_DATA);
> +
> + // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
> + let request_buf = unsafe { from_raw_parts_mut(&mut request as *mut _ as *mut u8, req_sz) };
> +
> + let mut response_vec: KVec<u8> = KVec::with_capacity(rsp_sz, GFP_KERNEL)?;
> + // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
> + let response_buf = unsafe { from_raw_parts_mut(response_vec.as_mut_ptr(), rsp_sz) };
> +
> + let rc = self.spdm_exchange(request_buf, response_buf)?;
> +
> + if rc < (core::mem::size_of::<ChallengeRsp>() as i32) {
> + pr_err!("Truncated challenge response\n");
> + to_result(-(bindings::EIO as i32))?;
> + }
> +
> + // SAFETY: `rc` is the length of data read, which will be smaller
> + // then the capacity of the vector
> + unsafe { response_vec.inc_len(rc as usize) };
> +
> + let _response: &mut ChallengeRsp = Untrusted::new_mut(&mut response_vec).validate_mut()?;
> +
> + let opaque_len_offset = core::mem::size_of::<SpdmHeader>() + self.hash_len + nonce_len;
Might be worth adding something to reflect that this also includes the MSHLength but that is 0 as
we didn't ask for a measurement summary hash. Would make it a tiny bit easier to correlate
with the spec.
> + let opaque_len = u16::from_le_bytes(
> + response_vec[opaque_len_offset..(opaque_len_offset + 2)]
> + .try_into()
> + .unwrap_or([0, 0]),
> + );
> +
> + let rsp_sz = self.challenge_rsp_len(nonce_len, opaque_len as usize);
> +
> + if rc < rsp_sz as i32 {
> + pr_err!("Truncated challenge response\n");
> + to_result(-(bindings::EIO as i32))?;
> + }
> +
> + self.transcript
> + .extend_from_slice(&response_vec[..rsp_sz], GFP_KERNEL)?;
> +
> + if verify {
> + /* Verify signature at end of transcript against leaf key */
> + match self.verify_signature(&mut response_vec[..rsp_sz]) {
> + Ok(()) => {
> + pr_info!("Authenticated with certificate slot {slot}");
> + self.authenticated = true;
> + }
> + Err(e) => {
> + pr_err!("Cannot verify challenge_auth signature: {e:?}");
> + self.authenticated = false;
> + }
> + };
> + }
> +
> + Ok(())
> + }
> }
> diff --git a/lib/rspdm/validator.rs b/lib/rspdm/validator.rs
> index a8bc3378676f..f8a5337841f0 100644
> --- a/lib/rspdm/validator.rs
> +++ b/lib/rspdm/validator.rs
> +
> +#[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],
> + pub(crate) message_summary_hash: __IncompleteArrayField<u8>,
> +
> + pub(crate) opaque_data_len: u16,
Similar to other places, I'd use a __le16 and convert at place of use
only.
> + pub(crate) opaque_data: __IncompleteArrayField<u8>,
> +
> + pub(crate) context: [u8; 8],
> + pub(crate) signature: __IncompleteArrayField<u8>,
> +}
> +
> +impl Validate<&mut Unvalidated<KVec<u8>>> for &mut ChallengeRsp {
> + type Err = Error;
> +
> + fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> {
> + let raw = unvalidated.raw_mut();
> + if raw.len() < mem::size_of::<ChallengeRsp>() {
> + return Err(EINVAL);
> + }
> +
> + let ptr = raw.as_mut_ptr();
> + // CAST: `ChallengeRsp` only contains integers and has `repr(C)`.
> + let ptr = ptr.cast::<ChallengeRsp>();
> + // SAFETY: `ptr` came from a reference and the cast above is valid.
> + let rsp: &mut ChallengeRsp = unsafe { &mut *ptr };
> +
> + // rsp.opaque_data_len = rsp.opaque_data_len.to_le();
Not sure why this is commented out. But as above, I'd leave it alone anyway.
> +
> + Ok(rsp)
> + }
> +}
^ permalink raw reply [flat|nested] 99+ messages in thread
* [RFC v3 25/27] PCI/CMA: Expose in sysfs whether devices are authenticated
2026-02-11 3:29 [RFC v3 00/27] lib: Rust implementation of SPDM alistair23
` (23 preceding siblings ...)
2026-02-11 3:29 ` [RFC v3 24/27] lib: rspdm: Support SPDM challenge alistair23
@ 2026-02-11 3:29 ` alistair23
2026-02-11 3:29 ` [RFC v3 26/27] rust: add bindings for hash_info alistair23
` (3 subsequent siblings)
28 siblings, 0 replies; 99+ messages in thread
From: alistair23 @ 2026-02-11 3:29 UTC (permalink / raw)
To: bhelgaas, lukas, rust-for-linux, akpm, linux-pci,
Jonathan.Cameron, linux-cxl, linux-kernel
Cc: alex.gaynor, benno.lossin, boqun.feng, a.hindborg, gary,
bjorn3_gh, tmgross, alistair23, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis
From: Alistair Francis <alistair.francis@wdc.com>
From: Lukas Wunner <lukas@wunner.de>
The PCI core has just been amended to authenticate CMA-capable devices
on enumeration and store the result in an "authenticated" bit in struct
pci_dev->spdm_state.
Expose the bit to user space through an eponymous sysfs attribute.
Allow user space to trigger reauthentication (e.g. after it has updated
the CMA keyring) by writing to the sysfs attribute.
Implement the attribute in the SPDM library so that other bus types
besides PCI may take advantage of it. They just need to add
spdm_attr_group to the attribute groups of their devices and amend the
dev_to_spdm_state() helper which retrieves the spdm_state for a given
device.
The helper may return an ERR_PTR if it couldn't be determined whether
SPDM is supported by the device. The sysfs attribute is visible in that
case but returns an error on access. This prevents downgrade attacks
where an attacker disturbs memory allocation or DOE communication
in order to create the appearance that SPDM is unsupported.
Subject to further discussion, a future commit might add a user-defined
policy to forbid driver binding to devices which failed authentication,
similar to the "authorized" attribute for USB.
Alternatively, authentication success might be signaled to user space
through a uevent, whereupon it may bind a (blacklisted) driver.
A uevent signaling authentication failure might similarly cause user
space to unbind or outright remove the potentially malicious device.
Traffic from devices which failed authentication could also be filtered
through ACS I/O Request Blocking Enable (PCIe r6.2 sec 7.7.11.3) or
through Link Disable (PCIe r6.2 sec 7.5.3.7). Unlike an IOMMU, that
will not only protect the host, but also prevent malicious peer-to-peer
traffic to other devices.
Signed-off-by: Lukas Wunner <lukas@wunner.de>
[ Changes by AF:
- Drop lib/spdm changes and replace with Rust implementation
]
Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
---
Documentation/ABI/testing/sysfs-devices-spdm | 31 +++++++
MAINTAINERS | 3 +-
drivers/pci/cma.c | 12 ++-
drivers/pci/doe.c | 2 +
drivers/pci/pci-sysfs.c | 3 +
drivers/pci/pci.h | 5 +
include/linux/pci.h | 12 +++
lib/rspdm/Makefile | 1 +
lib/rspdm/lib.rs | 1 +
lib/rspdm/req-sysfs.c | 98 ++++++++++++++++++++
lib/rspdm/spdm.h | 17 ++++
lib/rspdm/sysfs.rs | 38 ++++++++
12 files changed, 218 insertions(+), 5 deletions(-)
create mode 100644 Documentation/ABI/testing/sysfs-devices-spdm
create mode 100644 lib/rspdm/req-sysfs.c
create mode 100644 lib/rspdm/spdm.h
create mode 100644 lib/rspdm/sysfs.rs
diff --git a/Documentation/ABI/testing/sysfs-devices-spdm b/Documentation/ABI/testing/sysfs-devices-spdm
new file mode 100644
index 000000000000..018acde950ff
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-devices-spdm
@@ -0,0 +1,31 @@
+What: /sys/devices/.../authenticated
+Date: June 2025
+Contact: Lukas Wunner <lukas@wunner.de>
+Description:
+ This file contains 1 if the device authenticated successfully
+ with SPDM (Security Protocol and Data Model). It contains 0 if
+ the device failed authentication (and may thus be malicious).
+
+ Writing "re" to this file causes reauthentication.
+ That may be opportune after updating the device keyring.
+ The device keyring of the PCI bus is named ".cma"
+ (Component Measurement and Authentication).
+
+ Reauthentication may also be necessary after device identity
+ has mutated, e.g. after downloading firmware to an FPGA device.
+
+ The file is not visible if authentication is unsupported
+ by the device.
+
+ If the kernel could not determine whether authentication is
+ supported because memory was low or communication with the
+ device was not working, the file is visible but accessing it
+ fails with error code ENOTTY.
+
+ This prevents downgrade attacks where an attacker consumes
+ memory or disturbs communication in order to create the
+ appearance that a device does not support authentication.
+
+ The reason why authentication support could not be determined
+ is apparent from "dmesg". To re-probe authentication support
+ of PCI devices, exercise the "remove" and "rescan" attributes.
diff --git a/MAINTAINERS b/MAINTAINERS
index 58898260fde8..eae4722e40bf 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -23702,7 +23702,8 @@ L: linux-cxl@vger.kernel.org
L: linux-pci@vger.kernel.org
S: Maintained
T: git git://git.kernel.org/pub/scm/linux/kernel/git/devsec/spdm.git
-F: drivers/pci/cma.c
+F: Documentation/ABI/testing/sysfs-devices-spdm
+F: drivers/pci/cma*
F: include/linux/spdm.h
F: lib/rspdm/
diff --git a/drivers/pci/cma.c b/drivers/pci/cma.c
index 8d64008594e2..55b8fe6d3121 100644
--- a/drivers/pci/cma.c
+++ b/drivers/pci/cma.c
@@ -172,8 +172,10 @@ void pci_cma_init(struct pci_dev *pdev)
{
struct pci_doe_mb *doe;
- if (IS_ERR(pci_cma_keyring))
+ if (IS_ERR(pci_cma_keyring)) {
+ pdev->spdm_state = ERR_PTR(-ENOTTY);
return;
+ }
if (!pci_is_pcie(pdev))
return;
@@ -186,8 +188,10 @@ void pci_cma_init(struct pci_dev *pdev)
pdev->spdm_state = spdm_create(&pdev->dev, pci_doe_transport, doe,
PCI_DOE_MAX_PAYLOAD, pci_cma_keyring,
pci_cma_validate);
- if (!pdev->spdm_state)
+ if (!pdev->spdm_state) {
+ pdev->spdm_state = ERR_PTR(-ENOTTY);
return;
+ }
/*
* Keep spdm_state allocated even if initial authentication fails
@@ -205,7 +209,7 @@ void pci_cma_init(struct pci_dev *pdev)
*/
void pci_cma_reauthenticate(struct pci_dev *pdev)
{
- if (!pdev->spdm_state)
+ if (IS_ERR_OR_NULL(pdev->spdm_state))
return;
spdm_authenticate(pdev->spdm_state);
@@ -213,7 +217,7 @@ void pci_cma_reauthenticate(struct pci_dev *pdev)
void pci_cma_destroy(struct pci_dev *pdev)
{
- if (!pdev->spdm_state)
+ if (IS_ERR_OR_NULL(pdev->spdm_state))
return;
spdm_destroy(pdev->spdm_state);
diff --git a/drivers/pci/doe.c b/drivers/pci/doe.c
index 344e01496a8e..96ca77ecba90 100644
--- a/drivers/pci/doe.c
+++ b/drivers/pci/doe.c
@@ -857,6 +857,7 @@ void pci_doe_init(struct pci_dev *pdev)
if (IS_ERR(doe_mb)) {
pci_err(pdev, "[%x] failed to create mailbox: %ld\n",
offset, PTR_ERR(doe_mb));
+ pci_cma_disable(pdev);
continue;
}
@@ -865,6 +866,7 @@ void pci_doe_init(struct pci_dev *pdev)
pci_err(pdev, "[%x] failed to insert mailbox: %d\n",
offset, rc);
pci_doe_destroy_mb(doe_mb);
+ pci_cma_disable(pdev);
}
}
}
diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c
index c2df915ad2d2..b2b2096f8162 100644
--- a/drivers/pci/pci-sysfs.c
+++ b/drivers/pci/pci-sysfs.c
@@ -1853,6 +1853,9 @@ const struct attribute_group *pci_dev_attr_groups[] = {
#ifdef CONFIG_PCIEASPM
&aspm_ctrl_attr_group,
#endif
+#ifdef CONFIG_PCI_CMA
+ &spdm_attr_group,
+#endif
#ifdef CONFIG_PCI_DOE
&pci_doe_sysfs_group,
#endif
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index f87a4b7c92b8..80bd998e77e4 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -603,10 +603,15 @@ static inline void pci_doe_disconnected(struct pci_dev *pdev) { }
void pci_cma_init(struct pci_dev *pdev);
void pci_cma_destroy(struct pci_dev *pdev);
void pci_cma_reauthenticate(struct pci_dev *pdev);
+static inline void pci_cma_disable(struct pci_dev *pdev)
+{
+ pdev->spdm_state = ERR_PTR(-ENOTTY);
+}
#else
static inline void pci_cma_init(struct pci_dev *pdev) { }
static inline void pci_cma_destroy(struct pci_dev *pdev) { }
static inline void pci_cma_reauthenticate(struct pci_dev *pdev) { }
+static inline void pci_cma_disable(struct pci_dev *pdev) { }
#endif
#ifdef CONFIG_PCI_NPEM
diff --git a/include/linux/pci.h b/include/linux/pci.h
index 7384846ade19..44e7bd6f2076 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -2827,6 +2827,18 @@ static inline bool pci_is_thunderbolt_attached(struct pci_dev *pdev)
void pci_uevent_ers(struct pci_dev *pdev, enum pci_ers_result err_type);
#endif
+#ifdef CONFIG_PCI_CMA
+static inline struct spdm_state *pci_dev_to_spdm_state(struct pci_dev *pdev)
+{
+ return pdev->spdm_state;
+}
+#else
+static inline struct spdm_state *pci_dev_to_spdm_state(struct pci_dev *pdev)
+{
+ return NULL;
+}
+#endif
+
#include <linux/dma-mapping.h>
#define pci_emerg(pdev, fmt, arg...) dev_emerg(&(pdev)->dev, fmt, ##arg)
diff --git a/lib/rspdm/Makefile b/lib/rspdm/Makefile
index 1f62ee2a882d..f15b1437196b 100644
--- a/lib/rspdm/Makefile
+++ b/lib/rspdm/Makefile
@@ -8,3 +8,4 @@
obj-$(CONFIG_RSPDM) += spdm.o
spdm-y := lib.o
+spdm-$(CONFIG_SYSFS) += req-sysfs.o
diff --git a/lib/rspdm/lib.rs b/lib/rspdm/lib.rs
index 05fa471bb1e2..c0e3e039c14f 100644
--- a/lib/rspdm/lib.rs
+++ b/lib/rspdm/lib.rs
@@ -24,6 +24,7 @@
mod consts;
mod state;
+pub mod sysfs;
mod validator;
/// spdm_create() - Allocate SPDM session
diff --git a/lib/rspdm/req-sysfs.c b/lib/rspdm/req-sysfs.c
new file mode 100644
index 000000000000..7971be291627
--- /dev/null
+++ b/lib/rspdm/req-sysfs.c
@@ -0,0 +1,98 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Rust implementation of the DMTF Security Protocol and Data Model (SPDM)
+ * https://www.dmtf.org/dsp/DSP0274
+ *
+ * Requester role: sysfs interface
+ *
+ * Copyright (C) 2023-24 Intel Corporation
+ * Copyright (C) 2024 Western Digital
+ */
+
+#include <linux/pci.h>
+#include "spdm.h"
+
+int rust_authenticated_show(void *spdm_state, char *buf);
+
+/**
+ * dev_to_spdm_state() - Retrieve SPDM session state for given device
+ *
+ * @dev: Responder device
+ *
+ * Returns a pointer to the device's SPDM session state,
+ * %NULL if the device doesn't have one or
+ * %ERR_PTR if it couldn't be determined whether SPDM is supported.
+ *
+ * In the %ERR_PTR case, attributes are visible but return an error on access.
+ * This prevents downgrade attacks where an attacker disturbs memory allocation
+ * or communication with the device in order to create the appearance that SPDM
+ * is unsupported. E.g. with PCI devices, the attacker may foil CMA or DOE
+ * initialization by simply hogging memory.
+ */
+static void *dev_to_spdm_state(struct device *dev)
+{
+ if (dev_is_pci(dev))
+ return pci_dev_to_spdm_state(to_pci_dev(dev));
+
+ /* Insert mappers for further bus types here. */
+
+ return NULL;
+}
+
+/* authenticated attribute */
+
+static umode_t spdm_attrs_are_visible(struct kobject *kobj,
+ struct attribute *a, int n)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ void *spdm_state = dev_to_spdm_state(dev);
+
+ if (IS_ERR_OR_NULL(spdm_state))
+ return SYSFS_GROUP_INVISIBLE;
+
+ return a->mode;
+}
+
+static ssize_t authenticated_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ void *spdm_state = dev_to_spdm_state(dev);
+ int rc;
+
+ if (IS_ERR_OR_NULL(spdm_state))
+ return PTR_ERR(spdm_state);
+
+ if (sysfs_streq(buf, "re")) {
+ rc = spdm_chall(spdm_state);
+ if (rc)
+ return rc;
+ } else {
+ return -EINVAL;
+ }
+
+ return count;
+}
+
+static ssize_t authenticated_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ void *spdm_state = dev_to_spdm_state(dev);
+
+ if (IS_ERR_OR_NULL(spdm_state))
+ return PTR_ERR(spdm_state);
+
+ return rust_authenticated_show(spdm_state, buf);
+}
+static DEVICE_ATTR_RW(authenticated);
+
+static struct attribute *spdm_attrs[] = {
+ &dev_attr_authenticated.attr,
+ NULL
+};
+
+const struct attribute_group spdm_attr_group = {
+ .attrs = spdm_attrs,
+ .is_visible = spdm_attrs_are_visible,
+};
diff --git a/lib/rspdm/spdm.h b/lib/rspdm/spdm.h
new file mode 100644
index 000000000000..43ef56a073c0
--- /dev/null
+++ b/lib/rspdm/spdm.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * DMTF Security Protocol and Data Model (SPDM)
+ * https://www.dmtf.org/dsp/DSP0274
+ *
+ * Copyright (C) 2021-22 Huawei
+ * Jonathan Cameron <Jonathan.Cameron@huawei.com>
+ *
+ * Copyright (C) 2022-25 Intel Corporation
+ */
+
+#ifndef _LIB_SPDM_H_
+#define _LIB_SPDM_H_
+
+int spdm_chall(struct spdm_state *spdm_state);
+
+#endif /* _LIB_SPDM_H_ */
diff --git a/lib/rspdm/sysfs.rs b/lib/rspdm/sysfs.rs
new file mode 100644
index 000000000000..d0e7f6b3de40
--- /dev/null
+++ b/lib/rspdm/sysfs.rs
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Copyright (C) 2024 Western Digital
+
+//! Rust sysfs helper functions
+//!
+//! Rust implementation of the DMTF Security Protocol and Data Model (SPDM)
+//! <https://www.dmtf.org/dsp/DSP0274>
+
+use crate::SpdmState;
+use kernel::prelude::*;
+use kernel::{bindings, str::CString};
+
+/// Helper function for the sysfs `authenticated_show()`.
+#[no_mangle]
+pub extern "C" fn rust_authenticated_show(spdm_state: *mut SpdmState, buf: *mut u8) -> isize {
+ // SAFETY: The opaque pointer will be directly from the `spdm_create()`
+ // function, so we can safely reconstruct it.
+ let state = unsafe { KBox::from_raw(spdm_state) };
+
+ let fmt = match CString::try_from_fmt(fmt!("{}\n", state.authenticated)) {
+ Ok(f) => f,
+ Err(_e) => return 0,
+ };
+
+ // SAFETY: Calling a kernel C function with valid arguments
+ unsafe { bindings::sysfs_emit(buf, fmt.as_char_ptr()) as isize }
+}
+
+/// Helper function to trigger a SPDM challenge
+#[no_mangle]
+pub unsafe extern "C" fn spdm_chall(state: &'static mut SpdmState) -> c_int {
+ if let Err(e) = state.challenge(state.provisioned_slots.trailing_zeros() as u8, false) {
+ return e.to_errno() as c_int;
+ }
+
+ 0
+}
--
2.52.0
^ permalink raw reply related [flat|nested] 99+ messages in thread* [RFC v3 26/27] rust: add bindings for hash_info
2026-02-11 3:29 [RFC v3 00/27] lib: Rust implementation of SPDM alistair23
` (24 preceding siblings ...)
2026-02-11 3:29 ` [RFC v3 25/27] PCI/CMA: Expose in sysfs whether devices are authenticated alistair23
@ 2026-02-11 3:29 ` alistair23
2026-02-11 3:29 ` [RFC v3 27/27] rspdm: Multicast received signatures via netlink alistair23
` (2 subsequent siblings)
28 siblings, 0 replies; 99+ messages in thread
From: alistair23 @ 2026-02-11 3:29 UTC (permalink / raw)
To: bhelgaas, lukas, rust-for-linux, akpm, linux-pci,
Jonathan.Cameron, linux-cxl, linux-kernel
Cc: alex.gaynor, benno.lossin, boqun.feng, a.hindborg, gary,
bjorn3_gh, tmgross, alistair23, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis
From: Alistair Francis <alistair.francis@wdc.com>
C code can get the hash_digest_size by indexing into the global
hash_digest_size array. We can't do that in the Rust code, so let's add
a Rust helper function to allow us to get the hash_digest_size.
Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
---
rust/helpers/hash_info.c | 8 ++++++++
rust/helpers/helpers.c | 1 +
2 files changed, 9 insertions(+)
create mode 100644 rust/helpers/hash_info.c
diff --git a/rust/helpers/hash_info.c b/rust/helpers/hash_info.c
new file mode 100644
index 000000000000..d2cbc08b57d1
--- /dev/null
+++ b/rust/helpers/hash_info.c
@@ -0,0 +1,8 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <crypto/hash_info.h>
+
+int rust_helper_get_hash_digest_size(uint32_t offset)
+{
+ return hash_digest_size[offset];
+}
diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
index 4b08b4f1d3a3..34db9eb5e43e 100644
--- a/rust/helpers/helpers.c
+++ b/rust/helpers/helpers.c
@@ -31,6 +31,7 @@
#include "drm.c"
#include "err.c"
#include "hash.c"
+#include "hash_info.c"
#include "irq.c"
#include "fs.c"
#include "io.c"
--
2.52.0
^ permalink raw reply related [flat|nested] 99+ messages in thread* [RFC v3 27/27] rspdm: Multicast received signatures via netlink
2026-02-11 3:29 [RFC v3 00/27] lib: Rust implementation of SPDM alistair23
` (25 preceding siblings ...)
2026-02-11 3:29 ` [RFC v3 26/27] rust: add bindings for hash_info alistair23
@ 2026-02-11 3:29 ` alistair23
2026-02-19 10:19 ` Lukas Wunner
2026-02-12 5:56 ` [RFC v3 00/27] lib: Rust implementation of SPDM dan.j.williams
2026-02-17 23:56 ` Jason Gunthorpe
28 siblings, 1 reply; 99+ messages in thread
From: alistair23 @ 2026-02-11 3:29 UTC (permalink / raw)
To: bhelgaas, lukas, rust-for-linux, akpm, linux-pci,
Jonathan.Cameron, linux-cxl, linux-kernel
Cc: alex.gaynor, benno.lossin, boqun.feng, a.hindborg, gary,
bjorn3_gh, tmgross, alistair23, ojeda, wilfred.mallawa, aliceryhl,
James Bottomley, Jérôme Glisse, Jason Gunthorpe,
Alistair Francis
From: Lukas Wunner <lukas@wunner.de>
This is based on Lukas's patch from [1]. This exposes all of the SPDM
information to userspace via netlink. This includes the certificate
chain and communication transcript.
1: https://github.com/l1k/linux/commit/fe90b5700ee9bc595a21c030192eac4060eaeae1
Signed-off-by: Lukas Wunner <lukas@wunner.de>
Cc: James Bottomley <James.Bottomley@HansenPartnership.com>
Cc: Jérôme Glisse <jglisse@google.com>
Cc: Jason Gunthorpe <jgg@nvidia.com>
[ Change by AF:
- Fixup yaml spec issues
- Include certificate chain
- Port to support Rust SPDM
]
Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
---
Documentation/netlink/specs/spdm.yaml | 136 ++++++++++++++++++
include/uapi/linux/spdm_netlink.h | 49 +++++++
lib/rspdm/Makefile | 1 +
lib/rspdm/netlink-autogen.c | 33 +++++
lib/rspdm/netlink-autogen.h | 22 +++
lib/rspdm/req-netlink.c | 197 ++++++++++++++++++++++++++
lib/rspdm/spdm.h | 28 ++++
lib/rspdm/state.rs | 52 +++++++
rust/bindings/bindings_helper.h | 4 +
9 files changed, 522 insertions(+)
create mode 100644 Documentation/netlink/specs/spdm.yaml
create mode 100644 include/uapi/linux/spdm_netlink.h
create mode 100644 lib/rspdm/netlink-autogen.c
create mode 100644 lib/rspdm/netlink-autogen.h
create mode 100644 lib/rspdm/req-netlink.c
diff --git a/Documentation/netlink/specs/spdm.yaml b/Documentation/netlink/specs/spdm.yaml
new file mode 100644
index 000000000000..1eb349bdc0d1
--- /dev/null
+++ b/Documentation/netlink/specs/spdm.yaml
@@ -0,0 +1,136 @@
+# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
+
+name: spdm
+
+doc: |
+ DMTF Security Protocol and Data Model (SPDM)
+ https://www.dmtf.org/dsp/DSP0274
+
+protocol: genetlink
+
+uapi-header: linux/spdm_netlink.h
+
+definitions:
+ -
+ type: enum
+ name: spdm-reqrsp-code
+ doc: SPDM request or response code of a signed message (SPDM 1.0.0 table 4)
+ entries:
+ -
+ name: challenge-auth
+ -
+ name: endpoint-info
+ -
+ name: measurements
+ -
+ name: key-exchange-rsp
+ -
+ name: finish
+ -
+ type: enum
+ name: hash-algo
+ doc: SPDM-supported hash algorithm (SPDM 1.0.0 table 13)
+ entries:
+ -
+ name: sha256
+ -
+ name: sha384
+ -
+ name: sha512
+ -
+ name: sha3-256
+ -
+ name: sha3-384
+ -
+ name: sha3-512
+ header: uapi/linux/hash_info.h
+
+attribute-sets:
+ -
+ name: sig
+ doc: |
+ Signature received from a device, together with all ancillary data
+ needed for re-verification.
+
+ Meant for remote attestation services which do not trust the kernel
+ to have verified the signature correctly or which want to apply
+ policy constraints of their own.
+ attributes:
+ -
+ name: device
+ doc: |
+ Path under sysfs of the device generating the signature.
+ type: string
+ -
+ name: rsp-code
+ doc: |
+ SPDM response code of the message containing the signature,
+ to determine what kind of event caused signature generation.
+ Equivalent to the "context" string of SPDM 1.2.0 sec 15,
+ but represented numerically for easier parsing.
+ type: u8
+ enum: spdm-reqrsp-code
+ -
+ name: slot
+ doc: |
+ Certificate slot used for signature generation. Note that
+ if the slot has since been provisioned with a different
+ certificate chain, re-verification of the signature will fail.
+ type: u8
+ -
+ name: hash-algo
+ doc: |
+ Hash algorithm used for signature generation.
+ type: u16
+ enum: hash-algo
+ -
+ name: sig-offset
+ doc: |
+ Offset of signature in @transcript. The signature is located
+ at the end of @transcript, hence its size equals @transcript
+ size minus this offset.
+ type: u32
+ -
+ name: req-nonce-offset
+ doc: |
+ Offset of 32 byte nonce chosen by requester in @transcript.
+ Allows remote attestation services to verify freshness
+ (uniqueness) and entropy adequacy of the nonce.
+ type: u32
+ -
+ name: rsp-nonce-offset
+ doc: |
+ Offset of 32 byte nonce chosen by responder in @transcript.
+ Allows remote attestation services to verify freshness
+ (uniqueness) and entropy adequacy of the nonce.
+ type: u32
+ -
+ name: combined-spdm-prefix
+ doc: |
+ Only included in the message with SPDM version 1.2.0 or newer.
+ type: binary
+ checks:
+ exact-len: 100
+ -
+ name: certificate-chain
+ type: binary
+ -
+ name: transcript
+ type: binary
+ multi-attr: true
+
+operations:
+ list:
+ -
+ name: sig
+ doc: Signature event
+ attribute-set: sig
+ event:
+ attributes:
+ - sig
+ mcgrp: sig
+
+mcast-groups:
+ list:
+ -
+ name: sig
diff --git a/include/uapi/linux/spdm_netlink.h b/include/uapi/linux/spdm_netlink.h
new file mode 100644
index 000000000000..a7fa183757db
--- /dev/null
+++ b/include/uapi/linux/spdm_netlink.h
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
+/* Do not edit directly, auto-generated from: */
+/* Documentation/netlink/specs/spdm.yaml */
+/* YNL-GEN uapi header */
+/* To regenerate run: tools/net/ynl/ynl-regen.sh */
+
+#ifndef _UAPI_LINUX_SPDM_NETLINK_H
+#define _UAPI_LINUX_SPDM_NETLINK_H
+
+#define SPDM_FAMILY_NAME "spdm"
+#define SPDM_FAMILY_VERSION 1
+
+/*
+ * SPDM request or response code of a signed message (SPDM 1.0.0 table 4)
+ */
+enum spdm_spdm_reqrsp_code {
+ SPDM_SPDM_REQRSP_CODE_CHALLENGE_AUTH,
+ SPDM_SPDM_REQRSP_CODE_ENDPOINT_INFO,
+ SPDM_SPDM_REQRSP_CODE_MEASUREMENTS,
+ SPDM_SPDM_REQRSP_CODE_KEY_EXCHANGE_RSP,
+ SPDM_SPDM_REQRSP_CODE_FINISH,
+};
+
+enum {
+ SPDM_A_SIG_DEVICE = 1,
+ SPDM_A_SIG_RSP_CODE,
+ SPDM_A_SIG_SLOT,
+ SPDM_A_SIG_HASH_ALGO,
+ SPDM_A_SIG_SIG_OFFSET,
+ SPDM_A_SIG_REQ_NONCE_OFFSET,
+ SPDM_A_SIG_RSP_NONCE_OFFSET,
+ SPDM_A_SIG_COMBINED_SPDM_PREFIX,
+ SPDM_A_SIG_CERTIFICATE_CHAIN,
+ SPDM_A_SIG_TRANSCRIPT,
+
+ __SPDM_A_SIG_MAX,
+ SPDM_A_SIG_MAX = (__SPDM_A_SIG_MAX - 1)
+};
+
+enum {
+ SPDM_CMD_SIG = 1,
+
+ __SPDM_CMD_MAX,
+ SPDM_CMD_MAX = (__SPDM_CMD_MAX - 1)
+};
+
+#define SPDM_MCGRP_SIG "sig"
+
+#endif /* _UAPI_LINUX_SPDM_NETLINK_H */
diff --git a/lib/rspdm/Makefile b/lib/rspdm/Makefile
index f15b1437196b..2f29d0a62c1e 100644
--- a/lib/rspdm/Makefile
+++ b/lib/rspdm/Makefile
@@ -8,4 +8,5 @@
obj-$(CONFIG_RSPDM) += spdm.o
spdm-y := lib.o
+spdm-$(CONFIG_NET) += req-netlink.o netlink-autogen.o
spdm-$(CONFIG_SYSFS) += req-sysfs.o
diff --git a/lib/rspdm/netlink-autogen.c b/lib/rspdm/netlink-autogen.c
new file mode 100644
index 000000000000..4dc950133514
--- /dev/null
+++ b/lib/rspdm/netlink-autogen.c
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
+/* Do not edit directly, auto-generated from: */
+/* Documentation/netlink/specs/spdm.yaml */
+/* YNL-GEN kernel source */
+/* To regenerate run: tools/net/ynl/ynl-regen.sh */
+
+#include <net/netlink.h>
+#include <net/genetlink.h>
+
+#include "netlink-autogen.h"
+
+#include <uapi/linux/spdm_netlink.h>
+#include <uapi/linux/hash_info.h>
+
+/* Ops table for spdm */
+static const struct genl_split_ops spdm_nl_ops[] = {
+};
+
+static const struct genl_multicast_group spdm_nl_mcgrps[] = {
+ [SPDM_NLGRP_SIG] = { "sig", },
+};
+
+struct genl_family spdm_nl_family __ro_after_init = {
+ .name = SPDM_FAMILY_NAME,
+ .version = SPDM_FAMILY_VERSION,
+ .netnsok = true,
+ .parallel_ops = true,
+ .module = THIS_MODULE,
+ .split_ops = spdm_nl_ops,
+ .n_split_ops = ARRAY_SIZE(spdm_nl_ops),
+ .mcgrps = spdm_nl_mcgrps,
+ .n_mcgrps = ARRAY_SIZE(spdm_nl_mcgrps),
+};
diff --git a/lib/rspdm/netlink-autogen.h b/lib/rspdm/netlink-autogen.h
new file mode 100644
index 000000000000..2797d194604f
--- /dev/null
+++ b/lib/rspdm/netlink-autogen.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
+/* Do not edit directly, auto-generated from: */
+/* Documentation/netlink/specs/spdm.yaml */
+/* YNL-GEN kernel header */
+/* To regenerate run: tools/net/ynl/ynl-regen.sh */
+
+#ifndef _LINUX_SPDM_GEN_H
+#define _LINUX_SPDM_GEN_H
+
+#include <net/netlink.h>
+#include <net/genetlink.h>
+
+#include <uapi/linux/spdm_netlink.h>
+#include <uapi/linux/hash_info.h>
+
+enum {
+ SPDM_NLGRP_SIG,
+};
+
+extern struct genl_family spdm_nl_family;
+
+#endif /* _LINUX_SPDM_GEN_H */
diff --git a/lib/rspdm/req-netlink.c b/lib/rspdm/req-netlink.c
new file mode 100644
index 000000000000..65db5ec6a16c
--- /dev/null
+++ b/lib/rspdm/req-netlink.c
@@ -0,0 +1,197 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * DMTF Security Protocol and Data Model (SPDM)
+ * https://www.dmtf.org/dsp/DSP0274
+ *
+ * Requester role: netlink interface
+ *
+ * Copyright (C) 2025 Intel Corporation
+ * Copyright (C) 2026 WD Corporation
+ */
+
+#include "netlink-autogen.h"
+
+#include <linux/bitfield.h>
+#include <linux/mutex.h>
+#include <linux/spdm.h>
+
+#include <crypto/hash_info.h>
+
+#include "spdm.h"
+
+#define SPDM_NONCE_SZ 32 /* SPDM 1.0.0 table 20 */
+#define SPDM_PREFIX_SZ 64 /* SPDM 1.2.0 margin no 803 */
+#define SPDM_COMBINED_PREFIX_SZ 100 /* SPDM 1.2.0 margin no 806 */
+#define SPDM_MAX_OPAQUE_DATA 1024 /* SPDM 1.0.0 table 21 */
+
+static void spdm_create_combined_prefix(u8 version, const char *spdm_context,
+ void *buf)
+{
+ u8 major = FIELD_GET(0xf0, version);
+ u8 minor = FIELD_GET(0x0f, version);
+ size_t len = strlen(spdm_context);
+ int rc, zero_pad;
+
+ rc = snprintf(buf, SPDM_PREFIX_SZ + 1,
+ "dmtf-spdm-v%hhx.%hhx.*dmtf-spdm-v%hhx.%hhx.*dmtf-spdm-v%hhx.%hhx.*dmtf-spdm-v%hhx.%hhx.*",
+ major, minor, major, minor, major, minor, major, minor);
+ WARN_ON(rc != SPDM_PREFIX_SZ);
+
+ zero_pad = SPDM_COMBINED_PREFIX_SZ - SPDM_PREFIX_SZ - 1 - len;
+ WARN_ON(zero_pad < 0);
+
+ memset(buf + SPDM_PREFIX_SZ + 1, 0, zero_pad);
+ memcpy(buf + SPDM_PREFIX_SZ + 1 + zero_pad, spdm_context, len);
+}
+
+int spdm_netlink_sig_event(struct device *dev,
+ u8 version,
+ const void *transcript,
+ size_t transcript_len,
+ const void *cert_chain,
+ size_t cert_chain_len,
+ enum hash_algo base_hash_alg,
+ size_t sig_len,
+ int rsp_code, u8 slot,
+ size_t req_nonce_off, size_t rsp_nonce_off,
+ const char *spdm_context)
+{
+ unsigned int seq, msg_sz, nr_msgs, nr_pages, nr_frags;
+ struct sk_buff *msg;
+ struct nlattr *nla;
+ void *hdr;
+ const void *ptr;
+ int rc, i;
+
+ if (!genl_has_listeners(&spdm_nl_family, &init_net, SPDM_NLGRP_SIG))
+ return 0;
+
+ char *devpath __free(kfree) = kobject_get_path(&dev->kobj,
+ GFP_KERNEL);
+ if (!devpath)
+ return -ENOMEM;
+
+ nr_pages = transcript_len / PAGE_SIZE;
+ nr_msgs = DIV_ROUND_UP(nr_pages, MAX_SKB_FRAGS);
+
+ /* Calculate exact size to avoid reallocation by netlink_trim() */
+ msg_sz = nlmsg_total_size(genlmsg_msg_size(
+ nla_total_size(strlen(devpath)) +
+ nla_total_size(sizeof(u8)) +
+ nla_total_size(sizeof(u8)) +
+ nla_total_size(sizeof(u16)) +
+ nla_total_size(sizeof(u32)) +
+ nla_total_size(sizeof(u32)) +
+ nla_total_size(sizeof(u32)) +
+ nla_total_size(SPDM_COMBINED_PREFIX_SZ) +
+ nla_total_size(cert_chain_len) +
+ nla_total_size(0)));
+
+ msg = genlmsg_new(msg_sz, GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+
+ hdr = genlmsg_put(msg, 0, 0, &spdm_nl_family,
+ nr_msgs > 1 ? NLM_F_MULTI : 0, SPDM_CMD_SIG);
+ if (!hdr) {
+ rc = -EMSGSIZE;
+ goto err_free_msg;
+ }
+
+ if (nla_put_string(msg, SPDM_A_SIG_DEVICE, devpath) ||
+ nla_put_u8(msg, SPDM_A_SIG_RSP_CODE, rsp_code) ||
+ nla_put_u8(msg, SPDM_A_SIG_SLOT, slot) ||
+ nla_put_u16(msg, SPDM_A_SIG_HASH_ALGO,
+ base_hash_alg) ||
+ nla_put_u32(msg, SPDM_A_SIG_SIG_OFFSET,
+ transcript_len - sig_len) ||
+ nla_put_u32(msg, SPDM_A_SIG_REQ_NONCE_OFFSET, req_nonce_off) ||
+ nla_put_u32(msg, SPDM_A_SIG_RSP_NONCE_OFFSET, rsp_nonce_off)) {
+ rc = -EMSGSIZE;
+ goto err_cancel_msg;
+ }
+
+ if (version >= 0x12) {
+ nla = nla_reserve(msg, SPDM_A_SIG_COMBINED_SPDM_PREFIX,
+ SPDM_COMBINED_PREFIX_SZ);
+ if (!nla) {
+ rc = -EMSGSIZE;
+ goto err_cancel_msg;
+ }
+
+ spdm_create_combined_prefix(version, spdm_context,
+ nla_data(nla));
+ }
+
+ if (cert_chain_len >= 0)
+ nla_put(msg, SPDM_A_SIG_CERTIFICATE_CHAIN, cert_chain_len, cert_chain);
+
+ ptr = transcript;
+
+ /* Loop over Netlink messages - break condition is in loop body */
+ for (seq = 1; ; seq++) {
+ nla = nla_reserve(msg, SPDM_A_SIG_TRANSCRIPT, 0);
+ if (!nla) {
+ rc = -EMSGSIZE;
+ goto err_cancel_msg;
+ }
+
+ nr_frags = min(nr_pages, MAX_SKB_FRAGS);
+ nla->nla_len = nr_frags * PAGE_SIZE;
+ nr_pages -= nr_frags;
+
+ /* Loop over fragments of this Netlink message */
+ for (i = 0; i < nr_frags; i++) {
+ struct page *page = vmalloc_to_page(ptr);
+ size_t sz = min(transcript_len, PAGE_SIZE);
+
+ skb_add_rx_frag(msg, i, page, 0, sz, sz);
+ ptr += PAGE_SIZE;
+ transcript_len -= PAGE_SIZE;
+ }
+
+ genlmsg_end(msg, hdr);
+ rc = genlmsg_multicast(&spdm_nl_family, msg, 0,
+ SPDM_NLGRP_SIG, GFP_KERNEL);
+ if (rc)
+ return rc;
+
+ if (nr_pages == 0) /* End of loop - entire transcript sent */
+ break;
+
+ /* Start new message for remainder of transcript */
+ msg_sz = nlmsg_total_size(genlmsg_msg_size(nla_total_size(0)));
+
+ msg = genlmsg_new(msg_sz, GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+
+ hdr = genlmsg_put(msg, 0, seq, &spdm_nl_family,
+ NLM_F_MULTI, SPDM_CMD_SIG);
+ if (!hdr) {
+ rc = -EMSGSIZE;
+ goto err_free_msg;
+ }
+ }
+
+ return 0;
+
+err_cancel_msg:
+ nlmsg_cancel(msg, hdr);
+err_free_msg:
+ nlmsg_free(msg);
+ return rc;
+}
+
+static int __init spdm_netlink_init(void)
+{
+ return genl_register_family(&spdm_nl_family);
+}
+
+static void __exit spdm_netlink_exit(void)
+{
+ genl_unregister_family(&spdm_nl_family);
+}
+
+arch_initcall(spdm_netlink_init);
+module_exit(spdm_netlink_exit);
diff --git a/lib/rspdm/spdm.h b/lib/rspdm/spdm.h
index 43ef56a073c0..36bc1b47a796 100644
--- a/lib/rspdm/spdm.h
+++ b/lib/rspdm/spdm.h
@@ -12,6 +12,34 @@
#ifndef _LIB_SPDM_H_
#define _LIB_SPDM_H_
+#include <uapi/linux/hash_info.h>
+
+#ifdef CONFIG_NET
+int spdm_netlink_sig_event(struct device *dev,
+ u8 version,
+ const void *transcript,
+ size_t transcript_len,
+ const void *cert_chain,
+ size_t cert_chain_len,
+ enum hash_algo base_hash_alg,
+ size_t sig_len,
+ int rsp_code, u8 slot,
+ size_t req_nonce_off, size_t rsp_nonce_off,
+ const char *spdm_context);
+#else
+static inline int spdm_netlink_sig_event(struct device *dev,
+ u8 version,
+ const void *transcript,
+ size_t transcript_len,
+ const void *cert_chain,
+ size_t cert_chain_len,
+ enum hash_algo base_hash_alg,
+ size_t sig_len,
+ int rsp_code, u8 slot,
+ size_t req_nonce_off, size_t rsp_nonce_off,
+ const char *spdm_context) { return 0; }
+#endif
+
int spdm_chall(struct spdm_state *spdm_state);
#endif /* _LIB_SPDM_H_ */
diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
index a4d803af48fe..8b18e415d4d5 100644
--- a/lib/rspdm/state.rs
+++ b/lib/rspdm/state.rs
@@ -13,6 +13,7 @@
use kernel::{
bindings,
error::{code::EINVAL, from_err_ptr, to_result, Error},
+ page::PAGE_SIZE,
str::CStr,
str::CString,
validate::Untrusted,
@@ -1036,6 +1037,57 @@ pub(crate) fn challenge(&mut self, slot: u8, verify: bool) -> Result<(), Error>
};
}
+ let spdm_context = b"responder-challenge_auth signing\0";
+
+ let hash_digest_size = if self.base_hash_alg < bindings::hash_algo_HASH_ALGO__LAST {
+ // SAFETY: `base_hash_alg` is a valid offset into `hash_digest_size`
+ (unsafe { bindings::get_hash_digest_size(self.base_hash_alg) }) as usize
+ } else {
+ to_result(-(bindings::EIO as i32))?;
+ 0
+ };
+
+ let req_nonce_off = self.transcript.len() + core::mem::offset_of!(ChallengeReq, nonce);
+ let rsp_nonce_off =
+ self.transcript.len() + core::mem::size_of::<ChallengeRsp>() + hash_digest_size;
+
+ // This is the actual transcript length
+ let transcript_len = self.transcript.len();
+
+ // This is how much extra capacity we need to page align the transcript buffer
+ let extra_cap = PAGE_SIZE - transcript_len.rem_euclid(PAGE_SIZE);
+ // Ensure we have the capacity
+ self.transcript.reserve(extra_cap, GFP_KERNEL)?;
+
+ // We know the buffer is this long and this value will be PAGE_SIZE aligned
+ let transcript_buf_len = transcript_len + extra_cap;
+
+ let cert_chain_len = self.certs[slot as usize].len();
+
+ let cert_chain = if cert_chain_len > 0 {
+ self.certs[slot as usize].as_ptr() as *const c_void
+ } else {
+ core::ptr::null_mut()
+ };
+
+ unsafe {
+ bindings::spdm_netlink_sig_event(
+ self.dev,
+ self.version,
+ self.transcript.as_ptr() as *const c_void,
+ transcript_buf_len,
+ cert_chain,
+ cert_chain_len,
+ self.base_hash_alg,
+ self.sig_len,
+ 0x03,
+ slot,
+ req_nonce_off,
+ rsp_nonce_off,
+ spdm_context as *const _ as *const u8,
+ );
+ }
+
Ok(())
}
}
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 35e4378fb9dc..64326e5f2490 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -156,3 +156,7 @@ const vm_flags_t RUST_CONST_HELPER_VM_NOHUGEPAGE = VM_NOHUGEPAGE;
#include "../../drivers/android/binder/rust_binder_events.h"
#include "../../drivers/android/binder/page_range_helper.h"
#endif
+
+#if IS_ENABLED(CONFIG_RSPDM)
+#include "../../lib/rspdm/spdm.h"
+#endif
--
2.52.0
^ permalink raw reply related [flat|nested] 99+ messages in thread* Re: [RFC v3 27/27] rspdm: Multicast received signatures via netlink
2026-02-11 3:29 ` [RFC v3 27/27] rspdm: Multicast received signatures via netlink alistair23
@ 2026-02-19 10:19 ` Lukas Wunner
0 siblings, 0 replies; 99+ messages in thread
From: Lukas Wunner @ 2026-02-19 10:19 UTC (permalink / raw)
To: alistair23
Cc: bhelgaas, rust-for-linux, akpm, linux-pci, Jonathan.Cameron,
linux-cxl, linux-kernel, alex.gaynor, benno.lossin, boqun.feng,
a.hindborg, gary, bjorn3_gh, tmgross, ojeda, wilfred.mallawa,
aliceryhl, James Bottomley, Jérôme Glisse,
Jason Gunthorpe, Alistair Francis
On Wed, Feb 11, 2026 at 01:29:34PM +1000, alistair23@gmail.com wrote:
> This is based on Lukas's patch from [1]. This exposes all of the SPDM
> information to userspace via netlink. This includes the certificate
> chain and communication transcript.
>
> 1: https://github.com/l1k/linux/commit/fe90b5700ee9bc595a21c030192eac4060eaeae1
Just a heads-up, the above-linked commit is from Dec 11, but I spent
the calm days around Christmas and New Year testing and improving the
SPDM netlink code. I ended up squashing quite a few bugs and refactoring
things for clarity. Latest version is on my development branch:
https://github.com/l1k/linux/commits/doe
The branch contains a separate commit introducing a nla_put_blob()
helper to add a zero-copy blob attribute to an skb. And the SPDM
netlink code uses that helper instead of open coding its contents.
The SPDM netlink code can be tested with:
tools/net/ynl/pyynl/cli.py \
--spec Documentation/netlink/specs/spdm.yaml \
--subscribe sig --sleep 10
Just reauthenticate the device and the signature with ancillary
data gets dumped via netlink.
The commit message and kernel-doc of the SPDM netlink commit is
still a WIP because right after the calm of the holidays I got
swamped (again) with AER work that my employeer prioritizes.
But the code is now well-tested.
Thanks,
Lukas
^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [RFC v3 00/27] lib: Rust implementation of SPDM
2026-02-11 3:29 [RFC v3 00/27] lib: Rust implementation of SPDM alistair23
` (26 preceding siblings ...)
2026-02-11 3:29 ` [RFC v3 27/27] rspdm: Multicast received signatures via netlink alistair23
@ 2026-02-12 5:56 ` dan.j.williams
2026-02-18 2:12 ` Alistair Francis
2026-02-17 23:56 ` Jason Gunthorpe
28 siblings, 1 reply; 99+ messages in thread
From: dan.j.williams @ 2026-02-12 5:56 UTC (permalink / raw)
To: alistair23, bhelgaas, lukas, rust-for-linux, akpm, linux-pci,
Jonathan.Cameron, linux-cxl, linux-kernel
Cc: alex.gaynor, benno.lossin, boqun.feng, a.hindborg, gary,
bjorn3_gh, tmgross, alistair23, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis
alistair23@ wrote:
> From: Alistair Francis <alistair.francis@wdc.com>
Hi Alistair, quite a bit to digest here and details to dig into. Before
getting into that, I will say that at a broad strokes level, no immune
response to the core proposal of depending on a Rust SPDM library and
forgoing a C SPDM library.
Most of that allergy relief comes from how this organizes the C to
Rust interactions. The core SPDM implementation calls out to C for the
presentation layer (Netlink) or is invoked by sysfs. That makes it
amenable for sharing those presentation mechanics.
Specifically, my primary concern is integration and refactoring
opportunities with the PCI TSM implementation [1]. The PCI TSM case
should use the same uABI transport for requesting + conveying device
certificate chain, SPDM transcript, and device measurements as PCI CMA.
Note that in the TSM case the SPDM implementation is in platform
firmware and will bypass this library. The TSM SPDM session is mutually
exclusive with the CMA SPDM session.
[1]: http://lore.kernel.org/69339e215b09f_1e0210057@dwillia2-mobl4.notmuch
> Security Protocols and Data Models (SPDM) [1] is used for authentication,
> attestation and key exchange. SPDM is generally used over a range of
> transports, such as PCIe, MCTP/SMBus/I3C, ATA, SCSI, NVMe or TCP.
>
> From the kernels perspective SPDM is used to authenticate and attest devices.
> In this threat model a device is considered untrusted until it can be verified
> by the kernel and userspace using SPDM. As such SPDM data is untrusted data
> that can be mallicious.
>
> The SPDM specification is also complex, with the 1.2.1 spec being almost 200
> pages and the 1.3.0 spec being almost 250 pages long.
>
> As such we have the kernel parsing untrusted responses from a complex
> specification, which sounds like a possible exploit vector. This is the type
> of place where Rust excels!
>
> This series implements a SPDM requester in Rust.
>
> This is very similar to Lukas' implementation [2]. This series includes patches
> and files from Lukas' C SPDM implementation, which isn't in mainline.
>
> This is a standalone series and doesn't depend on Lukas' implementation.
So, I *am* allergic to how this series references Lukas' work by
pointing to random points in his personal git tree. I trust that was
done for RFC purposes, but it would have helped to call that out in the
changelog and set expectations about the ideal path to coordinate with
that work.
> To help with maintaining compatibility it's designed in a way to match Lukas'
> design and the state struct stores the same information, although in a Rust
> struct instead of the original C one.
>
> This series exposes the data to userspace via netlink, with a single sysfs
> atrribute to allow reauthentication.
>
> All of the patches are included in the RFC, as it depends on some patches
> that aren't upstream yet.
>
> Now that Rust is no longer experimental I have picked this back up. If the
> community is generally on board with a Rust implementation I can work on
> sending a non-RFC version and push towards getting that merged.
As long as this stays explicitly designed to minimize exposure to
"refactor across language boundary" events (as initially seems to be the
case), then it seems workable.
> The entire tree can be seen here: https://github.com/alistair23/linux/tree/alistair/spdm-rust
>
> I'm testing the netlink data by running the following
>
> ```shell
> cargo run -- --qemu-server response
>
> qemu-system-x86_64 \
> -nic none \
> -object rng-random,filename=/dev/urandom,id=rng0 \
> -device virtio-rng-pci,rng=rng0 \
> -drive file=deploy/images/qemux86-64/core-image-pcie-qemux86-64.rootfs.ext4,if=virtio,format=raw \
> -usb -device usb-tablet -usb -device usb-kbd \
> -cpu Skylake-Client \
> -machine q35,i8042=off \
> -smp 4 -m 2G \
> -drive file=blknvme,if=none,id=mynvme,format=raw \
> -device nvme,drive=mynvme,serial=deadbeef,spdm_port=2323,spdm_trans=doe \
> -snapshot \
> -serial mon:stdio -serial null -nographic \
> -kernel deploy/images/qemux86-64/bzImage \
> -append 'root=/dev/vda rw console=ttyS0 console=ttyS1 oprofile.timer=1 tsc=reliable no_timer_check rcupdate.rcu_expedited=1 swiotlb=0 '
>
> spdm_utils identify &
> sleep 1
> echo re > /sys/devices/pci0000:00/0000:00:03.0/authenticated
So this is where it will collide with TSM that also emits an
authenticated attribute. See Documentation/ABI/testing/sysfs-bus-pci.
The rough plan Lukas and I worked out is that switching between TSM and
CMA based authentication would use sysfs visibility to coordinate. I.e.
TSM to CMA conversion hides the TSM "authenticated" attribute and
unhides the CMA attribute of the same name.
The most significant unsolved point of contention between TSM and CMA is
the policy on when authentication is mandated and the driver probe
policy. The proposed model for enforcing device security for
Confidential Computing is make it completely amenable to userspace
policy. Draft details here [2] to be refreshed "soon" when I send out
the next version of that.
[2]: http://lore.kernel.org/20250827035259.1356758-6-dan.j.williams@intel.com
To be clear I am ok if there is an incremental option to have auto_cma
and/or auto_tsm that arranges for authentication or link encryption to
happen without asking. I take issue with auto_cma being the only hard
coded option.
^ permalink raw reply [flat|nested] 99+ messages in thread* Re: [RFC v3 00/27] lib: Rust implementation of SPDM
2026-02-12 5:56 ` [RFC v3 00/27] lib: Rust implementation of SPDM dan.j.williams
@ 2026-02-18 2:12 ` Alistair Francis
0 siblings, 0 replies; 99+ messages in thread
From: Alistair Francis @ 2026-02-18 2:12 UTC (permalink / raw)
To: dan.j.williams
Cc: bhelgaas, lukas, rust-for-linux, akpm, linux-pci,
Jonathan.Cameron, linux-cxl, linux-kernel, alex.gaynor,
benno.lossin, boqun.feng, a.hindborg, gary, bjorn3_gh, tmgross,
ojeda, wilfred.mallawa, aliceryhl, Alistair Francis
On Thu, Feb 12, 2026 at 3:56 PM <dan.j.williams@intel.com> wrote:
>
> alistair23@ wrote:
> > From: Alistair Francis <alistair.francis@wdc.com>
>
> Hi Alistair, quite a bit to digest here and details to dig into. Before
> getting into that, I will say that at a broad strokes level, no immune
> response to the core proposal of depending on a Rust SPDM library and
> forgoing a C SPDM library.
>
> Most of that allergy relief comes from how this organizes the C to
> Rust interactions. The core SPDM implementation calls out to C for the
> presentation layer (Netlink) or is invoked by sysfs. That makes it
> amenable for sharing those presentation mechanics.
Great! That was my goal.
Also, with netlink some of the C code is autogenerated, so a Rust
version would be tricky to do well.
>
> Specifically, my primary concern is integration and refactoring
> opportunities with the PCI TSM implementation [1]. The PCI TSM case
> should use the same uABI transport for requesting + conveying device
> certificate chain, SPDM transcript, and device measurements as PCI CMA.
I do think broadly that they can use the same uABI. I'm sure there
will be refactoring required though.
> Note that in the TSM case the SPDM implementation is in platform
> firmware and will bypass this library. The TSM SPDM session is mutually
> exclusive with the CMA SPDM session.
As long as we can get the details from the platform firmware (which I
assume is possible) then we can present them to userspace via the same
netlink ABI.
>
> [1]: http://lore.kernel.org/69339e215b09f_1e0210057@dwillia2-mobl4.notmuch
>
> > Security Protocols and Data Models (SPDM) [1] is used for authentication,
> > attestation and key exchange. SPDM is generally used over a range of
> > transports, such as PCIe, MCTP/SMBus/I3C, ATA, SCSI, NVMe or TCP.
> >
> > From the kernels perspective SPDM is used to authenticate and attest devices.
> > In this threat model a device is considered untrusted until it can be verified
> > by the kernel and userspace using SPDM. As such SPDM data is untrusted data
> > that can be mallicious.
> >
> > The SPDM specification is also complex, with the 1.2.1 spec being almost 200
> > pages and the 1.3.0 spec being almost 250 pages long.
> >
> > As such we have the kernel parsing untrusted responses from a complex
> > specification, which sounds like a possible exploit vector. This is the type
> > of place where Rust excels!
> >
> > This series implements a SPDM requester in Rust.
> >
> > This is very similar to Lukas' implementation [2]. This series includes patches
> > and files from Lukas' C SPDM implementation, which isn't in mainline.
> >
> > This is a standalone series and doesn't depend on Lukas' implementation.
>
> So, I *am* allergic to how this series references Lukas' work by
> pointing to random points in his personal git tree. I trust that was
> done for RFC purposes, but it would have helped to call that out in the
> changelog and set expectations about the ideal path to coordinate with
> that work.
I was trying to convey that a lot of this work was done by Lukas, I
didn't want to appear to be claiming otherwise or stepping on toes,
hence the links to his git tree.
As for an ideal path forward, what I think would be really useful is
if Lukas' prep patches get merged. Lukas sent them in his original
series and they have been reviewed there. I have picked them up here
to help with testing, but they could go upstream today and set the
ground work for either a C or Rust implementation.
That would reduce the size of any future series as well.
From there it would be worth thinking about a C or Rust implementation
and maybe just focus on one and work towards that.
>
> > To help with maintaining compatibility it's designed in a way to match Lukas'
> > design and the state struct stores the same information, although in a Rust
> > struct instead of the original C one.
> >
> > This series exposes the data to userspace via netlink, with a single sysfs
> > atrribute to allow reauthentication.
> >
> > All of the patches are included in the RFC, as it depends on some patches
> > that aren't upstream yet.
> >
> > Now that Rust is no longer experimental I have picked this back up. If the
> > community is generally on board with a Rust implementation I can work on
> > sending a non-RFC version and push towards getting that merged.
>
> As long as this stays explicitly designed to minimize exposure to
> "refactor across language boundary" events (as initially seems to be the
> case), then it seems workable.
>
> > The entire tree can be seen here: https://github.com/alistair23/linux/tree/alistair/spdm-rust
> >
> > I'm testing the netlink data by running the following
> >
> > ```shell
> > cargo run -- --qemu-server response
> >
> > qemu-system-x86_64 \
> > -nic none \
> > -object rng-random,filename=/dev/urandom,id=rng0 \
> > -device virtio-rng-pci,rng=rng0 \
> > -drive file=deploy/images/qemux86-64/core-image-pcie-qemux86-64.rootfs.ext4,if=virtio,format=raw \
> > -usb -device usb-tablet -usb -device usb-kbd \
> > -cpu Skylake-Client \
> > -machine q35,i8042=off \
> > -smp 4 -m 2G \
> > -drive file=blknvme,if=none,id=mynvme,format=raw \
> > -device nvme,drive=mynvme,serial=deadbeef,spdm_port=2323,spdm_trans=doe \
> > -snapshot \
> > -serial mon:stdio -serial null -nographic \
> > -kernel deploy/images/qemux86-64/bzImage \
> > -append 'root=/dev/vda rw console=ttyS0 console=ttyS1 oprofile.timer=1 tsc=reliable no_timer_check rcupdate.rcu_expedited=1 swiotlb=0 '
> >
> > spdm_utils identify &
> > sleep 1
> > echo re > /sys/devices/pci0000:00/0000:00:03.0/authenticated
>
> So this is where it will collide with TSM that also emits an
> authenticated attribute. See Documentation/ABI/testing/sysfs-bus-pci.
>
> The rough plan Lukas and I worked out is that switching between TSM and
> CMA based authentication would use sysfs visibility to coordinate. I.e.
> TSM to CMA conversion hides the TSM "authenticated" attribute and
> unhides the CMA attribute of the same name.
That sounds fine to me
>
> The most significant unsolved point of contention between TSM and CMA is
> the policy on when authentication is mandated and the driver probe
> policy. The proposed model for enforcing device security for
> Confidential Computing is make it completely amenable to userspace
> policy. Draft details here [2] to be refreshed "soon" when I send out
> the next version of that.
>
> [2]: http://lore.kernel.org/20250827035259.1356758-6-dan.j.williams@intel.com
>
> To be clear I am ok if there is an incremental option to have auto_cma
> and/or auto_tsm that arranges for authentication or link encryption to
> happen without asking. I take issue with auto_cma being the only hard
> coded option.
Ok, so we would need a way for users to select TSM or CMA and specify
policy information?
Alistair
^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [RFC v3 00/27] lib: Rust implementation of SPDM
2026-02-11 3:29 [RFC v3 00/27] lib: Rust implementation of SPDM alistair23
` (27 preceding siblings ...)
2026-02-12 5:56 ` [RFC v3 00/27] lib: Rust implementation of SPDM dan.j.williams
@ 2026-02-17 23:56 ` Jason Gunthorpe
2026-02-18 2:17 ` Alistair Francis
2026-02-19 11:24 ` Jonathan Cameron
28 siblings, 2 replies; 99+ messages in thread
From: Jason Gunthorpe @ 2026-02-17 23:56 UTC (permalink / raw)
To: alistair23
Cc: bhelgaas, lukas, rust-for-linux, akpm, linux-pci,
Jonathan.Cameron, linux-cxl, linux-kernel, alex.gaynor,
benno.lossin, boqun.feng, a.hindborg, gary, bjorn3_gh, tmgross,
ojeda, wilfred.mallawa, aliceryhl, Alistair Francis
On Wed, Feb 11, 2026 at 01:29:07PM +1000, alistair23@gmail.com wrote:
> From: Alistair Francis <alistair.francis@wdc.com>
>
> 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!
I was arguing for exactly this at a recent conference, so I'm glad to
see it. It is a great meaningful usecase for rust in the kernel.
IIRC the netlink was my suggestion too, it really needs a careful
look on its own. It is much better than sysfs, but comes with its own
pitfalls.
You might want to try to break this up into two parts, one just dumps
a large text file in debugfs where there are not uAPI rules. This
would let the rust work proceed.
And another to introduce a proper uAPI for the data.
It will be easier to get the right people interested in both parts if
it is split up I think, given the size.
Jason
^ permalink raw reply [flat|nested] 99+ messages in thread* Re: [RFC v3 00/27] lib: Rust implementation of SPDM
2026-02-17 23:56 ` Jason Gunthorpe
@ 2026-02-18 2:17 ` Alistair Francis
2026-02-18 23:40 ` dan.j.williams
2026-02-19 11:24 ` Jonathan Cameron
1 sibling, 1 reply; 99+ messages in thread
From: Alistair Francis @ 2026-02-18 2:17 UTC (permalink / raw)
To: Jason Gunthorpe
Cc: bhelgaas, lukas, rust-for-linux, akpm, linux-pci,
Jonathan.Cameron, linux-cxl, linux-kernel, alex.gaynor,
benno.lossin, boqun.feng, a.hindborg, gary, bjorn3_gh, tmgross,
ojeda, wilfred.mallawa, aliceryhl, Alistair Francis
On Wed, Feb 18, 2026 at 9:56 AM Jason Gunthorpe <jgg@nvidia.com> wrote:
>
> On Wed, Feb 11, 2026 at 01:29:07PM +1000, alistair23@gmail.com wrote:
> > From: Alistair Francis <alistair.francis@wdc.com>
> >
> > 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!
>
> I was arguing for exactly this at a recent conference, so I'm glad to
> see it. It is a great meaningful usecase for rust in the kernel.
Woo, someone is on board!
>
> IIRC the netlink was my suggestion too, it really needs a careful
> look on its own. It is much better than sysfs, but comes with its own
> pitfalls.
It was!
>
> You might want to try to break this up into two parts, one just dumps
> a large text file in debugfs where there are not uAPI rules. This
> would let the rust work proceed.
I'm currently thinking of splitting this in 3 parts. One for the
ground work patches (crypto), one for SPDM (without netlink) and then
a final one for netlink support.
I feel that this RFC proves netlink can work, which is important. For
SPDM the authenticated syfs attribute is enough to provide usefulness
to userspace to merge that while sending the netlink later.
That way hopefully everything is a little more self contained, but
still has users and value to justify being merged.
That's a good idea about debugfs though, I'll keep that in mind
Alistair
>
> And another to introduce a proper uAPI for the data.
>
> It will be easier to get the right people interested in both parts if
> it is split up I think, given the size.
>
> Jason
^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [RFC v3 00/27] lib: Rust implementation of SPDM
2026-02-18 2:17 ` Alistair Francis
@ 2026-02-18 23:40 ` dan.j.williams
2026-02-19 0:56 ` Jason Gunthorpe
2026-02-19 9:13 ` Lukas Wunner
0 siblings, 2 replies; 99+ messages in thread
From: dan.j.williams @ 2026-02-18 23:40 UTC (permalink / raw)
To: Alistair Francis, Jason Gunthorpe
Cc: bhelgaas, lukas, rust-for-linux, akpm, linux-pci,
Jonathan.Cameron, linux-cxl, linux-kernel, alex.gaynor,
benno.lossin, boqun.feng, a.hindborg, gary, bjorn3_gh, tmgross,
ojeda, wilfred.mallawa, aliceryhl, Alistair Francis, aneesh.kumar,
yilun.xu, aik
[ add Aneesh, Alexey, and Yilun ]
Alistair Francis wrote:
[..]
> I'm currently thinking of splitting this in 3 parts. One for the
> ground work patches (crypto), one for SPDM (without netlink) and then
> a final one for netlink support.
>
> I feel that this RFC proves netlink can work, which is important. For
> SPDM the authenticated syfs attribute is enough to provide usefulness
> to userspace to merge that while sending the netlink later.
>
> That way hopefully everything is a little more self contained, but
> still has users and value to justify being merged.
The split sounds good. I chatted with Lukas a bit this morning he
rightly pointed out that the crypto pre-work does not really stand on
its own without a user.
However, I notice that Aneesh needs x509 certificate parsing for his TSM
driver [1], I think TDX would benefit from the same to offload needing
to specify the wall-clock time to the module [2] for cert verification,
and SEV-TIO (already upstream) is currently missing any facility for the
host to attest the device.
[1]: http://lore.kernel.org/20250728135216.48084-17-aneesh.kumar@kernel.org
[2]: http://lore.kernel.org/20251117022311.2443900-9-yilun.xu@linux.intel.com
So one proposal to get the x509 pre-work upstream is to extend the TSM
core (drivers/pci/tsm.c) to export the certificates in sysfs, and update
the existing "authenticated" attribute to reflect the result of cert
chain validation.
This provides immediate value to the TSM "connect" flow and is smaller
than depending on the SPDM library as the first consumer. It also feels
like useful refactoring to split 'struct spdm_state' into context for a
session initiator (SPDM library) and context for the artifacts (certs +
measurements produced by either SPDM library or a TSM).
^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [RFC v3 00/27] lib: Rust implementation of SPDM
2026-02-18 23:40 ` dan.j.williams
@ 2026-02-19 0:56 ` Jason Gunthorpe
2026-02-19 5:05 ` dan.j.williams
2026-02-19 9:34 ` Lukas Wunner
2026-02-19 9:13 ` Lukas Wunner
1 sibling, 2 replies; 99+ messages in thread
From: Jason Gunthorpe @ 2026-02-19 0:56 UTC (permalink / raw)
To: dan.j.williams
Cc: Alistair Francis, bhelgaas, lukas, rust-for-linux, akpm,
linux-pci, Jonathan.Cameron, linux-cxl, linux-kernel, alex.gaynor,
benno.lossin, boqun.feng, a.hindborg, gary, bjorn3_gh, tmgross,
ojeda, wilfred.mallawa, aliceryhl, Alistair Francis, aneesh.kumar,
yilun.xu, aik
On Wed, Feb 18, 2026 at 03:40:10PM -0800, dan.j.williams@intel.com wrote:
> So one proposal to get the x509 pre-work upstream is to extend the TSM
> core (drivers/pci/tsm.c) to export the certificates in sysfs, and update
> the existing "authenticated" attribute to reflect the result of cert
> chain validation.
Why do we want the validate the cert chain in the kernel? That sounds
like something the verifier should do?
And not sure we should be dumping any certs in sysfs if the plan for
the other stuff is netlink, it should be consistent I think.
Though it is a good idea to do something with the TSM too..
Jason
^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [RFC v3 00/27] lib: Rust implementation of SPDM
2026-02-19 0:56 ` Jason Gunthorpe
@ 2026-02-19 5:05 ` dan.j.williams
2026-02-19 12:41 ` Jason Gunthorpe
2026-02-19 9:34 ` Lukas Wunner
1 sibling, 1 reply; 99+ messages in thread
From: dan.j.williams @ 2026-02-19 5:05 UTC (permalink / raw)
To: Jason Gunthorpe, dan.j.williams
Cc: Alistair Francis, bhelgaas, lukas, rust-for-linux, akpm,
linux-pci, Jonathan.Cameron, linux-cxl, linux-kernel, alex.gaynor,
benno.lossin, boqun.feng, a.hindborg, gary, bjorn3_gh, tmgross,
ojeda, wilfred.mallawa, aliceryhl, Alistair Francis, aneesh.kumar,
yilun.xu, aik
Jason Gunthorpe wrote:
> On Wed, Feb 18, 2026 at 03:40:10PM -0800, dan.j.williams@intel.com wrote:
>
> > So one proposal to get the x509 pre-work upstream is to extend the TSM
> > core (drivers/pci/tsm.c) to export the certificates in sysfs, and update
> > the existing "authenticated" attribute to reflect the result of cert
> > chain validation.
>
> Why do we want the validate the cert chain in the kernel? That sounds
> like something the verifier should do?
This is more for the CMA case where Lukas was imagining automatic
revalidation of device certificates coming out of resume before
userspace is present. If someone wanted to use a TSM for device-auth +
link encryption outside of TDISP and Confidential VMs, then it could use
the same mechanism.
However, error handling / automatic reset recovery is out of scope for
the first phase of the TDISP enabling. Also all the early TDISP use
cases seem focused on datacenter where there is no need for the VMM to
authenticate the device. I am ok to defer the "authenticate while
userspace is unavailable" scheme to keep this simple.
> And not sure we should be dumping any certs in sysfs if the plan for
> the other stuff is netlink, it should be consistent I think.
Lukas was only putting the dynamic / transactional pieces in netlink.
Specifically device signature events (multicast) and device measurement
collection with a nonce.
The static cert chain blobs can certainly also be in netlink... but no
real driving need like there was for the other flows. I am also
encouraged by Lukas's work to handle large blobs over netlink [1], but
no real need to add that as a dependency to this simple mission of "just
enough of a real user to land the crypto prep patches".
[1]: https://github.com/l1k/linux/commit/af9b939fc30b
^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [RFC v3 00/27] lib: Rust implementation of SPDM
2026-02-19 5:05 ` dan.j.williams
@ 2026-02-19 12:41 ` Jason Gunthorpe
2026-02-19 14:15 ` Lukas Wunner
0 siblings, 1 reply; 99+ messages in thread
From: Jason Gunthorpe @ 2026-02-19 12:41 UTC (permalink / raw)
To: dan.j.williams
Cc: Alistair Francis, bhelgaas, lukas, rust-for-linux, akpm,
linux-pci, Jonathan.Cameron, linux-cxl, linux-kernel, alex.gaynor,
benno.lossin, boqun.feng, a.hindborg, gary, bjorn3_gh, tmgross,
ojeda, wilfred.mallawa, aliceryhl, Alistair Francis, aneesh.kumar,
yilun.xu, aik
On Wed, Feb 18, 2026 at 09:05:28PM -0800, dan.j.williams@intel.com wrote:
> Jason Gunthorpe wrote:
> > On Wed, Feb 18, 2026 at 03:40:10PM -0800, dan.j.williams@intel.com wrote:
> >
> > > So one proposal to get the x509 pre-work upstream is to extend the TSM
> > > core (drivers/pci/tsm.c) to export the certificates in sysfs, and update
> > > the existing "authenticated" attribute to reflect the result of cert
> > > chain validation.
> >
> > Why do we want the validate the cert chain in the kernel? That sounds
> > like something the verifier should do?
>
> This is more for the CMA case where Lukas was imagining automatic
> revalidation of device certificates coming out of resume before
> userspace is present. If someone wanted to use a TSM for device-auth +
> link encryption outside of TDISP and Confidential VMs, then it could use
> the same mechanism.
I think we should have one flow for this based on what we talked about
for TDSIP.
We are thinking about many interesting models, and some of them
include running an external verifier on this no-VM case well. Kernel
auto acceptance is not desirable for the same reasons it is not
desirable in a TVM.
If we do some automatic re-accept for RAS/resume it should be strongly
tied to some target pre-set by the userspace acceptance process - ie
"the device must present exactly this specific cert chain and nothing
else", and probably more too since we may want to exclude swapping out
device FW versions or similar.
Not sure how that fits into an sysfs file.
> > And not sure we should be dumping any certs in sysfs if the plan for
> > the other stuff is netlink, it should be consistent I think.
>
> Lukas was only putting the dynamic / transactional pieces in netlink.
> Specifically device signature events (multicast) and device measurement
> collection with a nonce.
>
> The static cert chain blobs can certainly also be in netlink... but no
> real driving need like there was for the other flows. I am also
> encouraged by Lukas's work to handle large blobs over netlink [1], but
> no real need to add that as a dependency to this simple mission of "just
> enough of a real user to land the crypto prep patches".
It could, but also it seems like it just makes it more complicated to
force the verifying agent to use a combination of netlink and sysfs.
Jason
^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [RFC v3 00/27] lib: Rust implementation of SPDM
2026-02-19 12:41 ` Jason Gunthorpe
@ 2026-02-19 14:15 ` Lukas Wunner
2026-02-19 14:31 ` Jason Gunthorpe
2026-02-19 14:40 ` Greg KH
0 siblings, 2 replies; 99+ messages in thread
From: Lukas Wunner @ 2026-02-19 14:15 UTC (permalink / raw)
To: Jason Gunthorpe
Cc: dan.j.williams, Alistair Francis, bhelgaas, rust-for-linux, akpm,
linux-pci, Jonathan.Cameron, linux-cxl, linux-kernel, alex.gaynor,
benno.lossin, boqun.feng, a.hindborg, gary, bjorn3_gh, tmgross,
ojeda, wilfred.mallawa, aliceryhl, Alistair Francis, aneesh.kumar,
yilun.xu, aik
On Thu, Feb 19, 2026 at 08:41:19AM -0400, Jason Gunthorpe wrote:
> Kernel auto acceptance is not desirable for the same reasons it is not
> desirable in a TVM.
>
> If we do some automatic re-accept for RAS/resume it should be strongly
> tied to some target pre-set by the userspace acceptance process - ie
> "the device must present exactly this specific cert chain and nothing
> else", and probably more too since we may want to exclude swapping out
> device FW versions or similar.
The way this works in my series (and I presume Alistair's) is that
trusted root certificates for devices need to be added to the .cma
keyring.
This can be done from user space using keyctl(1) or some other utility
that can talk to the kernel's existing keyctl ABI.
It is also possible to link the .builtin_trusted_keys or .platform
keyrings into the .cma keyring and thereby declare anything trusted
that is used for UEFI Secure Boot. Likewise blacklisted certificates
added to the .blacklisted keyring are rejected for SPDM device
authentication. These are existing, well-established roots of trust
in the kernel that CMA simply inherits. I think it is reasonable
to base auto-acceptance on these existing mechanisms. No need to
reinvent the wheel.
If you want to trust exactly one specific End Entity certificate,
you could provision an SPDM slot with that certificate, add it to
the .cma keyring and clean out all the other 7 SPDM certificate slots
on the device.
> > It has turned out to be super convenient to expose the 8 slots with
> > certificate chains in sysfs for direct examination with openssl and
> > similar tools, without having to go through netlink.
>
> Honestly, I'm reluctant to add permanent sysfs uAPI just for temporary
> debugging. Put it in debugfs.
Exposure of the certificates in the SPDM slots is not for debugging,
it's just super convenient for day-to-day use.
> Having to find/remember some baroque openssl command line with a
> million options is not reasonable for a production kind of
> environment.
Personally I find something like the following neither baroque nor
unreasonable:
# What's the certificate chain in slot0?
openssl storeutl -text /sys/bus/pci/devices/0000:03:00.0/certificates/slot0
# Fingerprint of root cert in slot0, does it match what vendor claims?
openssl x509 -fingerprint -in /sys/bus/pci/devices/0000:03:00.0/certificates/slot0
# Looks good, let's trust it:
keyctl padd asymmetric "" %:.cma < /sys/bus/pci/devices/0000:03:00.0/certificates/slot0
> There should be a tool suite that runs on top of this stuff and
> presents a sensible user experiance, with man pages and so on.
Device authentication is currently trickling down from server to
client to embedded/edge devices and you don't want to require users
to install a tool suite on space-constrained embedded devices where
busybox + openssl is otherwise enough, doubly so with skyrocketing
eMMC prices.
Thanks,
Lukas
^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [RFC v3 00/27] lib: Rust implementation of SPDM
2026-02-19 14:15 ` Lukas Wunner
@ 2026-02-19 14:31 ` Jason Gunthorpe
2026-02-19 15:07 ` Lukas Wunner
2026-02-19 14:40 ` Greg KH
1 sibling, 1 reply; 99+ messages in thread
From: Jason Gunthorpe @ 2026-02-19 14:31 UTC (permalink / raw)
To: Lukas Wunner
Cc: dan.j.williams, Alistair Francis, bhelgaas, rust-for-linux, akpm,
linux-pci, Jonathan.Cameron, linux-cxl, linux-kernel, alex.gaynor,
benno.lossin, boqun.feng, a.hindborg, gary, bjorn3_gh, tmgross,
ojeda, wilfred.mallawa, aliceryhl, Alistair Francis, aneesh.kumar,
yilun.xu, aik
On Thu, Feb 19, 2026 at 03:15:34PM +0100, Lukas Wunner wrote:
> On Thu, Feb 19, 2026 at 08:41:19AM -0400, Jason Gunthorpe wrote:
> > Kernel auto acceptance is not desirable for the same reasons it is not
> > desirable in a TVM.
> >
> > If we do some automatic re-accept for RAS/resume it should be strongly
> > tied to some target pre-set by the userspace acceptance process - ie
> > "the device must present exactly this specific cert chain and nothing
> > else", and probably more too since we may want to exclude swapping out
> > device FW versions or similar.
>
> The way this works in my series (and I presume Alistair's) is that
> trusted root certificates for devices need to be added to the .cma
> keyring.
>
> This can be done from user space using keyctl(1) or some other utility
> that can talk to the kernel's existing keyctl ABI.
I really don't like this from a verification perspective. We don't
want the kernel checking signatures, that is the verifier's job.
And a general keyring based proeprty is not at all the same as 'this
device must present exactly the same certification and attesation
after resume'
> authentication. These are existing, well-established roots of trust
> in the kernel that CMA simply inherits. I think it is reasonable
> to base auto-acceptance on these existing mechanisms. No need to
> reinvent the wheel.
It depends what you are building. We've been focused on external
verification so this is not at all desirable.
Something else, maybe some kind of internal verification for embedded
is a quite different story. The two cases still need to compose
sensibly within the kernel though.
> > Having to find/remember some baroque openssl command line with a
> > million options is not reasonable for a production kind of
> > environment.
>
> Personally I find something like the following neither baroque nor
> unreasonable:
>
> # What's the certificate chain in slot0?
> openssl storeutl -text /sys/bus/pci/devices/0000:03:00.0/certificates/slot0
>
> # Fingerprint of root cert in slot0, does it match what vendor claims?
> openssl x509 -fingerprint -in /sys/bus/pci/devices/0000:03:00.0/certificates/slot0
>
> # Looks good, let's trust it:
> keyctl padd asymmetric "" %:.cma < /sys/bus/pci/devices/0000:03:00.0/certificates/slot0
That's exactly the baroque I'm talking about, no server admin is going
to want to grapple with that..
> Device authentication is currently trickling down from server to
> client to embedded/edge devices and you don't want to require users
> to install a tool suite on space-constrained embedded devices where
> busybox + openssl is otherwise enough, doubly so with skyrocketing
> eMMC prices.
Maybe, but is this a real thing where someone would run busybox and
want some minimal self-verification?
Jason
^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [RFC v3 00/27] lib: Rust implementation of SPDM
2026-02-19 14:31 ` Jason Gunthorpe
@ 2026-02-19 15:07 ` Lukas Wunner
2026-02-19 17:39 ` Jason Gunthorpe
0 siblings, 1 reply; 99+ messages in thread
From: Lukas Wunner @ 2026-02-19 15:07 UTC (permalink / raw)
To: Jason Gunthorpe
Cc: dan.j.williams, Alistair Francis, bhelgaas, rust-for-linux, akpm,
linux-pci, Jonathan.Cameron, linux-cxl, linux-kernel, alex.gaynor,
benno.lossin, boqun.feng, a.hindborg, gary, bjorn3_gh, tmgross,
ojeda, wilfred.mallawa, aliceryhl, Alistair Francis, aneesh.kumar,
yilun.xu, aik
On Thu, Feb 19, 2026 at 10:31:29AM -0400, Jason Gunthorpe wrote:
> On Thu, Feb 19, 2026 at 03:15:34PM +0100, Lukas Wunner wrote:
> > The way this works in my series (and I presume Alistair's) is that
> > trusted root certificates for devices need to be added to the .cma
> > keyring.
> >
> > This can be done from user space using keyctl(1) or some other utility
> > that can talk to the kernel's existing keyctl ABI.
>
> I really don't like this from a verification perspective. We don't
> want the kernel checking signatures, that is the verifier's job.
On resume from system sleep, the device is put into D0 already in the
->resume_noirq() phase and drivers are free to access it already at
that point. However a verifier in user space cannot be queried
at that point because user space is still frozen.
Likewise after recovery from DPC or AER, the device has been reset
and needs to be reauthenticated, yet user space may be unavailable
because the device that has been reset may contain the root partition
or may be the NIC that you need to query your remote attestation service.
There is no way around some form of in-kernel device authentication
to accommodate such use cases.
> And a general keyring based proeprty is not at all the same as 'this
> device must present exactly the same certification and attesation
> after resume'
Well please be constructive and propose something better.
> > authentication. These are existing, well-established roots of trust
> > in the kernel that CMA simply inherits. I think it is reasonable
> > to base auto-acceptance on these existing mechanisms. No need to
> > reinvent the wheel.
>
> It depends what you are building. We've been focused on external
> verification so this is not at all desirable.
No problem at all. The kernel will merely use the .cma keyring for
its own notion of an authenticated device.
However if there is no trusted root cert in the .cma keyring,
the kernel will still multicast the signature received from the
device via netlink, so your user space tool can ask the remote
attestation service and if it responds affirmatively, you trust
the device.
So you can either use the .cma keyring for in-kernel authentication
or you can use your user space utility.
But you can't rely on user space if you want seamless re-authentication
after a system sleep transition or error recovery.
We can discuss a way for user space to force the kernel into
considering a device authenticated. E.g. writing "force" to
the "authenticated" attribute may tell the kernel that it's
a trustworthy device irrespective of the .cma keyring.
So you'd perform remote attestation and if successful,
tell the kernel to consider the device trusted.
> > # What's the certificate chain in slot0?
> > openssl storeutl -text /sys/bus/pci/devices/0000:03:00.0/certificates/slot0
> >
> > # Fingerprint of root cert in slot0, does it match what vendor claims?
> > openssl x509 -fingerprint -in /sys/bus/pci/devices/0000:03:00.0/certificates/slot0
> >
> > # Looks good, let's trust it:
> > keyctl padd asymmetric "" %:.cma < /sys/bus/pci/devices/0000:03:00.0/certificates/slot0
>
> That's exactly the baroque I'm talking about, no server admin is going
> to want to grapple with that..
I used to be an admin for 2 decades and my experience is that
openssl usage has just become muscle memory, but YMMV. :)
Thanks,
Lukas
^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [RFC v3 00/27] lib: Rust implementation of SPDM
2026-02-19 15:07 ` Lukas Wunner
@ 2026-02-19 17:39 ` Jason Gunthorpe
2026-02-19 20:07 ` dan.j.williams
2026-02-20 8:30 ` Lukas Wunner
0 siblings, 2 replies; 99+ messages in thread
From: Jason Gunthorpe @ 2026-02-19 17:39 UTC (permalink / raw)
To: Lukas Wunner
Cc: dan.j.williams, Alistair Francis, bhelgaas, rust-for-linux, akpm,
linux-pci, Jonathan.Cameron, linux-cxl, linux-kernel, alex.gaynor,
benno.lossin, boqun.feng, a.hindborg, gary, bjorn3_gh, tmgross,
ojeda, wilfred.mallawa, aliceryhl, Alistair Francis, aneesh.kumar,
yilun.xu, aik
On Thu, Feb 19, 2026 at 04:07:58PM +0100, Lukas Wunner wrote:
> On Thu, Feb 19, 2026 at 10:31:29AM -0400, Jason Gunthorpe wrote:
> > On Thu, Feb 19, 2026 at 03:15:34PM +0100, Lukas Wunner wrote:
> > > The way this works in my series (and I presume Alistair's) is that
> > > trusted root certificates for devices need to be added to the .cma
> > > keyring.
> > >
> > > This can be done from user space using keyctl(1) or some other utility
> > > that can talk to the kernel's existing keyctl ABI.
> >
> > I really don't like this from a verification perspective. We don't
> > want the kernel checking signatures, that is the verifier's job.
>
> On resume from system sleep, the device is put into D0 already in the
> ->resume_noirq() phase and drivers are free to access it already at
> that point. However a verifier in user space cannot be queried
> at that point because user space is still frozen.
>
> Likewise after recovery from DPC or AER, the device has been reset
> and needs to be reauthenticated, yet user space may be unavailable
> because the device that has been reset may contain the root partition
> or may be the NIC that you need to query your remote attestation service.
>
> There is no way around some form of in-kernel device authentication
> to accommodate such use cases.
I'm arguing there are two very different steps here that must be kept
separate. Verification is done when the device is first seen and the
kernel is told it is OK to use the device.
A same-device check is performed if an already verified and accepted
device resumes or RAS's in some way.
same-device does not mean run a kernel verification against the kernel
keyring, as a second verification could be tricked into accepting
something that has changed and defeat the userspace verifier.
Instead the implementation should capture information when the device
is accepted by the verifier and on resume/RAS it should compare the
device against that captured information and determine if it is still
the same device.
The key north star must be that the userspace verifier alone decides
if the device is acceptable and if the kernel is configured to
auto-re-accept the device later on RAS/resume it must not permit a
device that is different from what userspace approved. In other words
it is not a verification on resume, it is just a kernel side
confirmation the device hasn't been changed.
Hence no keyring should be involved in resume.
I understand the tempatation to just run a kernel verification twice,
and it is OK if your use cases are limited to a kernel verifier, but
it doesn't compose very well with a userspace verfier at all and gives
us two very different frameworks.
> > And a general keyring based proeprty is not at all the same as 'this
> > device must present exactly the same certification and attesation
> > after resume'
>
> Well please be constructive and propose something better.
I have said what I'd like in a few ways now.
> We can discuss a way for user space to force the kernel into
> considering a device authenticated. E.g. writing "force" to
> the "authenticated" attribute may tell the kernel that it's
> a trustworthy device irrespective of the .cma keyring.
> So you'd perform remote attestation and if successful,
> tell the kernel to consider the device trusted.
We definitely need to ensure we build something where userspace is in
fully in charge.
I don't have an objection to an optional, useful for embedded, kernel
side verifier that uses the cma ring.
What I'm focusing on is that it be coherent with the whole complex
verifier flows we need, not just its own seperate different thing. So
replacing the userspace verifier with a kernel verifier is no
issue. Making it so you HAVE to use the kernel verifier to get certain
functionality like RAS is objectionable.
> > > # What's the certificate chain in slot0?
> > > openssl storeutl -text /sys/bus/pci/devices/0000:03:00.0/certificates/slot0
> > >
> > > # Fingerprint of root cert in slot0, does it match what vendor claims?
> > > openssl x509 -fingerprint -in /sys/bus/pci/devices/0000:03:00.0/certificates/slot0
> > >
> > > # Looks good, let's trust it:
> > > keyctl padd asymmetric "" %:.cma < /sys/bus/pci/devices/0000:03:00.0/certificates/slot0
> >
> > That's exactly the baroque I'm talking about, no server admin is going
> > to want to grapple with that..
>
> I used to be an admin for 2 decades and my experience is that
> openssl usage has just become muscle memory, but YMMV. :)
This is also not a very realistic scenario, there is no point to copy
a certificate from a device into the cma key ring. If you want to
trust the device as is without any verification then just accept it as
is with a trivial userspace "verifier".
If you want a kernel side verifier the realistic scenario is to bake
the permitted certificates into the image or kernel and/or have
busybox load them into the keyring at boot.
Jaason
^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [RFC v3 00/27] lib: Rust implementation of SPDM
2026-02-19 17:39 ` Jason Gunthorpe
@ 2026-02-19 20:07 ` dan.j.williams
2026-02-20 8:30 ` Lukas Wunner
1 sibling, 0 replies; 99+ messages in thread
From: dan.j.williams @ 2026-02-19 20:07 UTC (permalink / raw)
To: Jason Gunthorpe, Lukas Wunner
Cc: dan.j.williams, Alistair Francis, bhelgaas, rust-for-linux, akpm,
linux-pci, Jonathan.Cameron, linux-cxl, linux-kernel, alex.gaynor,
benno.lossin, boqun.feng, a.hindborg, gary, bjorn3_gh, tmgross,
ojeda, wilfred.mallawa, aliceryhl, Alistair Francis, aneesh.kumar,
yilun.xu, aik
Jason Gunthorpe wrote:
> On Thu, Feb 19, 2026 at 04:07:58PM +0100, Lukas Wunner wrote:
> > On Thu, Feb 19, 2026 at 10:31:29AM -0400, Jason Gunthorpe wrote:
> > > On Thu, Feb 19, 2026 at 03:15:34PM +0100, Lukas Wunner wrote:
> > > > The way this works in my series (and I presume Alistair's) is that
> > > > trusted root certificates for devices need to be added to the .cma
> > > > keyring.
> > > >
> > > > This can be done from user space using keyctl(1) or some other utility
> > > > that can talk to the kernel's existing keyctl ABI.
> > >
> > > I really don't like this from a verification perspective. We don't
> > > want the kernel checking signatures, that is the verifier's job.
> >
> > On resume from system sleep, the device is put into D0 already in the
> > ->resume_noirq() phase and drivers are free to access it already at
> > that point. However a verifier in user space cannot be queried
> > at that point because user space is still frozen.
> >
> > Likewise after recovery from DPC or AER, the device has been reset
> > and needs to be reauthenticated, yet user space may be unavailable
> > because the device that has been reset may contain the root partition
> > or may be the NIC that you need to query your remote attestation service.
> >
> > There is no way around some form of in-kernel device authentication
> > to accommodate such use cases.
>
> I'm arguing there are two very different steps here that must be kept
> separate. Verification is done when the device is first seen and the
> kernel is told it is OK to use the device.
>
> A same-device check is performed if an already verified and accepted
> device resumes or RAS's in some way.
>
> same-device does not mean run a kernel verification against the kernel
> keyring, as a second verification could be tricked into accepting
> something that has changed and defeat the userspace verifier.
>
> Instead the implementation should capture information when the device
> is accepted by the verifier and on resume/RAS it should compare the
> device against that captured information and determine if it is still
> the same device.
>
> The key north star must be that the userspace verifier alone decides
> if the device is acceptable and if the kernel is configured to
> auto-re-accept the device later on RAS/resume it must not permit a
> device that is different from what userspace approved. In other words
> it is not a verification on resume, it is just a kernel side
> confirmation the device hasn't been changed.
>
> Hence no keyring should be involved in resume.
I am also struggling to see a role for the .cma keyring as long as the
kernel eventually has a method to cache cert-chain, measurements, and
for TDISP, interface report digests. Support for a recovery flow is not
the first dragon to slay though as just establishing device trust in the
first instance without RAS concerns is a significant amount of work.
Linux should not over index on native bare-metal CMA because that
mechanism only tells you that the SPDM session with device firmware
(DSM) is authenticated, it does nothing to ensure that the kernel's view
of the device's MMIO is and remains associated with that DSM. Better
than nothing, yes, but it certainly assumes a less sophisticated threat
model than TDISP.
So the current 'authenticated' PCI sysfs attribute can simply indicate
"SPDM collateral (cert chain + measurements) available", and leave all
the decisions about what do with that collateral to userspace. For cases
where the full lock + accept TDISP flow is not available the only policy
knob that userspace has is to decline to attach a driver.
Once we have that userspace can optionally tell the kernel to cache
digests for automatic re-accept / keep driver bound, or userspace can
plan to do another round trip with the verifier for recovery if the
device bounces out of the TCB.
My current thought is take and adapt the netlink interface to retrieve
cert chains, change the certificate slot for the authentication attempt,
and retrieve device measurements. None of that requires the x509 parser.
With that in place native CMA SPDM can be modeled as just another TSM
driver that only addresses a subset of the TDISP threat model.
There are 2 flows depending on whether the TSM driver suports the
comprehensive security of the "lock+accept" model or not:
---
# $tsmN is a class device registered by one of amd-tio, intel-tdx,
# arm-cca, or when this spdm library is available a kernel-pci-cma TSM
# driver
# The "lock+accept" model is not available for any of the current tsm
# drivers on bare metal, only the "connect" flow. The connect flow gets
# you an SPDM secure session at a minimum and optionally IDE as well. It
# is a less comprehensive security model than TDISP
echo $tsmN > /sys/bus/pci/devices/$pci_dev/tsm/connect
# Alternatively, when a TDISP interface is assigned, the TSM driver
# publishes "lock+accept" attributes. This provides the comprehensive
# security model that closes "DSM is and remains associated to device
# MMIO" TOCTOU problem.
echo $tsmN > /sys/bus/pci/devices/$pci_dev/tsm/lock
# In either of the above cases the
# /sys/bus/pci/devices/$pci_dev/authenticated attribute toggles to 1 and
# userspace is able to use PCI netlink to gather evidence with nonces
...collect (with netlink) / validate evidence...
# When verifier is satisfied bind the driver, or in the Confidential
# Computing / TDISP case, first "accept" the device so that it is
# allowed to access private memory
echo 1 > /sys/bus/pci/devices/$pci_dev/tsm/accept
echo $pci_dev > /sys/bus/pci/drivers/$driver/bind
---
^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [RFC v3 00/27] lib: Rust implementation of SPDM
2026-02-19 17:39 ` Jason Gunthorpe
2026-02-19 20:07 ` dan.j.williams
@ 2026-02-20 8:30 ` Lukas Wunner
2026-02-20 14:10 ` Jason Gunthorpe
1 sibling, 1 reply; 99+ messages in thread
From: Lukas Wunner @ 2026-02-20 8:30 UTC (permalink / raw)
To: Jason Gunthorpe
Cc: dan.j.williams, Alistair Francis, bhelgaas, rust-for-linux, akpm,
linux-pci, Jonathan.Cameron, linux-cxl, linux-kernel, alex.gaynor,
benno.lossin, boqun.feng, a.hindborg, gary, bjorn3_gh, tmgross,
ojeda, wilfred.mallawa, aliceryhl, Alistair Francis, aneesh.kumar,
yilun.xu, aik
On Thu, Feb 19, 2026 at 01:39:37PM -0400, Jason Gunthorpe wrote:
> I'm arguing there are two very different steps here that must be kept
> separate. Verification is done when the device is first seen and the
> kernel is told it is OK to use the device.
>
> A same-device check is performed if an already verified and accepted
> device resumes or RAS's in some way.
[...]
> Hence no keyring should be involved in resume.
No, that doesn't work. You cannot rely on information that the device
presents. The information may be spoofed. The only secure way to
ascertain that the device you're talking to is what it purports to be
is to ask it for a signature and verify that the signature was made
with a device certificate which is signed by a root certificate
that you trust. And the way to trust that root certificate is to
add it to a keyring.
There is no way around that. What you have in mind could be achieved
by generating a secret token after first authentication and then using
that secret token after RAS / resume for re-authentication. But I just
looked through the SPDM spec and it doesn't support that kind of thing.
A userspace verifier is incompatible with authentication after RAS
and resume. It's a technical limitation that you'll have to live with,
I'm sorry. If you could come up with, say, an eBPF verifier that
user space loads into the kernel, that might be a workable solution.
> I don't have an objection to an optional, useful for embedded, kernel
> side verifier that uses the cma ring.
Good. My patches merely aim to be a spec-compliant implementation of
PCIe r7.0 sec 6.31 (Component Measurement and Authentication/SPDM).
That spec section does not talk about user space verifiers.
Moreover PCIe r6.1 page 115 states "CMA requires SPDM Version 1.0 or
above", so the baseline that we need to support for spec compliance
is the oldest spec version with the least features. So even if a
future spec version introduced a feature to establish a shared secret
on first authentication and use that for subsequent authentication,
we'd still be constrained by the SPDM 1.0 feature set for compliance
with older devices.
> > > > # What's the certificate chain in slot0?
> > > > openssl storeutl -text /sys/bus/pci/devices/0000:03:00.0/certificates/slot0
> > > >
> > > > # Fingerprint of root cert in slot0, does it match what vendor claims?
> > > > openssl x509 -fingerprint -in /sys/bus/pci/devices/0000:03:00.0/certificates/slot0
> > > >
> > > > # Looks good, let's trust it:
> > > > keyctl padd asymmetric "" %:.cma < /sys/bus/pci/devices/0000:03:00.0/certificates/slot0
>
> This is also not a very realistic scenario, there is no point to copy
> a certificate from a device into the cma key ring. If you want to
> trust the device as is without any verification then just accept it as
> is with a trivial userspace "verifier".
No, in the above example the device is not trusted "as is without any
verification". The verification that happens above is a manual
comparison of the fingerprint with one provided by the vendor.
Thanks,
Lukas
^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [RFC v3 00/27] lib: Rust implementation of SPDM
2026-02-20 8:30 ` Lukas Wunner
@ 2026-02-20 14:10 ` Jason Gunthorpe
2026-02-21 18:46 ` Lukas Wunner
0 siblings, 1 reply; 99+ messages in thread
From: Jason Gunthorpe @ 2026-02-20 14:10 UTC (permalink / raw)
To: Lukas Wunner
Cc: dan.j.williams, Alistair Francis, bhelgaas, rust-for-linux, akpm,
linux-pci, Jonathan.Cameron, linux-cxl, linux-kernel, alex.gaynor,
benno.lossin, boqun.feng, a.hindborg, gary, bjorn3_gh, tmgross,
ojeda, wilfred.mallawa, aliceryhl, Alistair Francis, aneesh.kumar,
yilun.xu, aik
On Fri, Feb 20, 2026 at 09:30:50AM +0100, Lukas Wunner wrote:
> On Thu, Feb 19, 2026 at 01:39:37PM -0400, Jason Gunthorpe wrote:
> > I'm arguing there are two very different steps here that must be kept
> > separate. Verification is done when the device is first seen and the
> > kernel is told it is OK to use the device.
> >
> > A same-device check is performed if an already verified and accepted
> > device resumes or RAS's in some way.
> [...]
> > Hence no keyring should be involved in resume.
>
> No, that doesn't work. You cannot rely on information that the device
> presents. The information may be spoofed. The only secure way to
> ascertain that the device you're talking to is what it purports to be
> is to ask it for a signature and verify that the signature was made
> with a device certificate which is signed by a root certificate
> that you trust.
Yes, and in the most general case the nonce/etc for this must come
through userspace from an external verifier agent.
> And the way to trust that root certificate is to
> add it to a keyring.
Not quite, the way to have a *kernel verifier* is to have a kernel
verifier use the cma keyring in this way. If you use a userspace
verifier the kernel keyring has no role.
> There is no way around that. What you have in mind could be achieved
> by generating a secret token after first authentication and then using
> that secret token after RAS / resume for re-authentication. But I just
> looked through the SPDM spec and it doesn't support that kind of
> thing.
The kernel just records the information the device presented and
checks it is the same. Yes on the same-device check it will have to
make a new nonce and push it through, but it is only checking the
results against the previously cached information for this device, not
a general keyring.
> A userspace verifier is incompatible with authentication after RAS
> and resume. It's a technical limitation that you'll have to live with,
> I'm sorry. If you could come up with, say, an eBPF verifier that
> user space loads into the kernel, that might be a workable solution.
You are ignoring the proposal - the idea is to NOT VERIFY on resume,
but to only do a same-device check.
A same-device check should have no policy components that require a
verifier. It gives the device a new fresh nonce, obtains a new
signature, checks the signature works with the previously cached
public key(s).
IOW the resume/RAS acceptance criteria is that the second nonce was
signed with the same private key(s) that the first nonce was signed
with.
In many devices this will be entirely sufficient. It should be common
that each physical device has a unique private key with a certificate
chain to the manufactures secured certificate. Since the private key
is globally unique proving the device has it during resume is the
majority of a same-device check.
From there the kernel can do additional checks, sanity check the
presented certificate list is exactly the same, check the attestation
report hasn't changed to ensure the device hasn't been replaced with
an insecure dowrev or something. (possibly the driver will have to
provide some helper to same-device check its attestation report)
No need for spec changes, no need for a key ring, very different flow
from verification.
The kernel should simply monitor the signature requests from the
verifier, internally confirm the reply is correct and record all the
public keys that were successfully used on this struct device.
> That spec section does not talk about user space verifiers.
Linux will have its own sw model, the spec is just the protocol
definition. In the CC world everyone just knows the verifier needs to
be external.. How else could it even work?
> > This is also not a very realistic scenario, there is no point to copy
> > a certificate from a device into the cma key ring. If you want to
> > trust the device as is without any verification then just accept it as
> > is with a trivial userspace "verifier".
>
> No, in the above example the device is not trusted "as is without any
> verification". The verification that happens above is a manual
> comparison of the fingerprint with one provided by the vendor.
"manual comparision" is some debugging flow, like I pointed out at the
start.
Jason
^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [RFC v3 00/27] lib: Rust implementation of SPDM
2026-02-20 14:10 ` Jason Gunthorpe
@ 2026-02-21 18:46 ` Lukas Wunner
2026-02-21 23:29 ` dan.j.williams
2026-02-24 14:16 ` Jason Gunthorpe
0 siblings, 2 replies; 99+ messages in thread
From: Lukas Wunner @ 2026-02-21 18:46 UTC (permalink / raw)
To: Jason Gunthorpe
Cc: dan.j.williams, Alistair Francis, bhelgaas, rust-for-linux, akpm,
linux-pci, Jonathan.Cameron, linux-cxl, linux-kernel, alex.gaynor,
benno.lossin, boqun.feng, a.hindborg, gary, bjorn3_gh, tmgross,
ojeda, wilfred.mallawa, aliceryhl, Alistair Francis, aneesh.kumar,
yilun.xu, aik, Mathieu Poirier, Thomas Fossati
On Fri, Feb 20, 2026 at 10:10:57AM -0400, Jason Gunthorpe wrote:
> IOW the resume/RAS acceptance criteria is that the second nonce was
> signed with the same private key(s) that the first nonce was signed
> with.
What you seem to have in mind is essentially a "trust on first use"
model where trust is given to a specific device certificate
(i.e. leaf certificate), not to a root certificate.
Whereas the SPDM spec (and by extension CMA, i.e. PCIe r7.0 sec 6.31)
envisions a model where each of the 8 slots may contain a certificate
*chain* (comprising a root certificate, optional intermediate certificates
and a leaf certificate). In this model, trust is given to root
certificates. These could be vendors, but it's also possible that
e.g. a CSP operates its own CA and provisions one of the 8 slots with
a custom certificate chain anchored in its own CA.
Your "trust on first use" model could be made to fit into the
SPDM model by adding the leaf certificate (= device certificate)
to the .cma keyring and provisioning one of the certificate slots
with just the leaf certificate (and nothing else in the cert chain).
The verifier in user space could add the leaf certificate to the keyring
and provision a slot with it after having verified the device successfully.
That would make verification in user space compatible with RAS & resume,
at least the certificate validation part.
An alternative solution would be to have the verifier in user space
operate its own mini CA. The root certificate of that mini CA would be
added to the .cma keyring. Upon successful verification, the verifier
would create a short-lived certificate for the device which is signed
by its mini CA. The verifier would provision one of the 8 cert slots
of the device with this custom short-lived certificate and thereby
allow the kernel to re-authenticate the device without reliance on
user space until the short-lived certificate expires. User space
would have to monitor expiration of those certificates and re-new
them to ensure continued re-authentication.
I'm adding Mathieu and Thomas to cc as they have been exploring ways
to take advantage of the .cma keyring for Confidential Computing
use cases.
> Linux will have its own sw model, the spec is just the protocol
> definition. In the CC world everyone just knows the verifier needs to
> be external.. How else could it even work?
There are products out there which support CMA but not TDISP.
In other words, the CC world isn't everything. The modest goal
of this series is to allow authentication of devices in compliance
with PCIe r7.0 sec 6.31 and the SPDM spec. I understand there are
features and authentication modes which are important for the
Confidential Computing world, but CoCo needs to fit into the
spec-defined mechanisms.
Thanks,
Lukas
^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [RFC v3 00/27] lib: Rust implementation of SPDM
2026-02-21 18:46 ` Lukas Wunner
@ 2026-02-21 23:29 ` dan.j.williams
2026-02-23 17:15 ` Jonathan Cameron
2026-02-24 14:16 ` Jason Gunthorpe
1 sibling, 1 reply; 99+ messages in thread
From: dan.j.williams @ 2026-02-21 23:29 UTC (permalink / raw)
To: Lukas Wunner, Jason Gunthorpe
Cc: dan.j.williams, Alistair Francis, bhelgaas, rust-for-linux, akpm,
linux-pci, Jonathan.Cameron, linux-cxl, linux-kernel, alex.gaynor,
benno.lossin, boqun.feng, a.hindborg, gary, bjorn3_gh, tmgross,
ojeda, wilfred.mallawa, aliceryhl, Alistair Francis, aneesh.kumar,
yilun.xu, aik, Mathieu Poirier, Thomas Fossati
Lukas Wunner wrote:
> On Fri, Feb 20, 2026 at 10:10:57AM -0400, Jason Gunthorpe wrote:
> > IOW the resume/RAS acceptance criteria is that the second nonce was
> > signed with the same private key(s) that the first nonce was signed
> > with.
[..]
> > Linux will have its own sw model, the spec is just the protocol
> > definition. In the CC world everyone just knows the verifier needs to
> > be external.. How else could it even work?
>
> There are products out there which support CMA but not TDISP.
> In other words, the CC world isn't everything. The modest goal
> of this series is to allow authentication of devices in compliance
> with PCIe r7.0 sec 6.31 and the SPDM spec. I understand there are
> features and authentication modes which are important for the
> Confidential Computing world, but CoCo needs to fit into the
> spec-defined mechanisms.
The TDISP proposal from Jason and I bears repeating because it is a
superset of what a CMA-only solution needs, and security guarantees it
provides. I also submit that "identity revalidation over reset/resume"
is not a *primary* concern. It is certainly *a* concern that needs to be
part of the ongoing discussion to avoid painting ourselves into a
corner, but certainly a complexity that is incremental to the base
enabling. Recall CMA is only a building block to trusting the rest of
the device interface outside of the SPDM session.
Userspace is in charge of all trust and verification decisions. A TSM
driver, whether that driver is in-kernel-SPDM-library, or platform TSM,
establishes a session with a device with a given certificate slot. The
session establishment makes cert-chain+transcript available to userspace
and caches the public-key. If userspace does not like that result, it
opts to not bind a driver to that device, or retries with a different
cert slot.
If later we want to support a "same device" capability in scenarios
where a userspace round trip is infeasible then that is incremental ABI.
That ABI would allow userspace to cache golden cert-chain+measurements.
The resume path can revalidate that identity description with a fresh
nonce and the cached public key.
For TDISP the violence of dropping the device out of the TCB likely
needs more sophistication than golden measurement revalidation. For CMA
mere trust in the root cert is not sufficient for many of the
adversarial device threat models, so the kernel need not carry that
responsibility.
Aneesh and I are putting together some POC patches along these lines.
^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [RFC v3 00/27] lib: Rust implementation of SPDM
2026-02-21 23:29 ` dan.j.williams
@ 2026-02-23 17:15 ` Jonathan Cameron
2026-02-23 19:11 ` dan.j.williams
0 siblings, 1 reply; 99+ messages in thread
From: Jonathan Cameron @ 2026-02-23 17:15 UTC (permalink / raw)
To: dan.j.williams
Cc: Lukas Wunner, Jason Gunthorpe, Alistair Francis, bhelgaas,
rust-for-linux, akpm, linux-pci, linux-cxl, linux-kernel,
alex.gaynor, benno.lossin, boqun.feng, a.hindborg, gary,
bjorn3_gh, tmgross, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis, aneesh.kumar, yilun.xu, aik, Mathieu Poirier,
Thomas Fossati
On Sat, 21 Feb 2026 15:29:55 -0800
dan.j.williams@intel.com wrote:
> Lukas Wunner wrote:
> > On Fri, Feb 20, 2026 at 10:10:57AM -0400, Jason Gunthorpe wrote:
> > > IOW the resume/RAS acceptance criteria is that the second nonce was
> > > signed with the same private key(s) that the first nonce was signed
> > > with.
> [..]
> > > Linux will have its own sw model, the spec is just the protocol
> > > definition. In the CC world everyone just knows the verifier needs to
> > > be external.. How else could it even work?
> >
> > There are products out there which support CMA but not TDISP.
> > In other words, the CC world isn't everything. The modest goal
> > of this series is to allow authentication of devices in compliance
> > with PCIe r7.0 sec 6.31 and the SPDM spec. I understand there are
> > features and authentication modes which are important for the
> > Confidential Computing world, but CoCo needs to fit into the
> > spec-defined mechanisms.
>
> The TDISP proposal from Jason and I bears repeating because it is a
> superset of what a CMA-only solution needs, and security guarantees it
> provides. I also submit that "identity revalidation over reset/resume"
> is not a *primary* concern. It is certainly *a* concern that needs to be
> part of the ongoing discussion to avoid painting ourselves into a
> corner, but certainly a complexity that is incremental to the base
> enabling. Recall CMA is only a building block to trusting the rest of
> the device interface outside of the SPDM session.
>
> Userspace is in charge of all trust and verification decisions. A TSM
> driver, whether that driver is in-kernel-SPDM-library, or platform TSM,
> establishes a session with a device with a given certificate slot. The
> session establishment makes cert-chain+transcript available to userspace
> and caches the public-key. If userspace does not like that result, it
> opts to not bind a driver to that device, or retries with a different
> cert slot.
>
> If later we want to support a "same device" capability in scenarios
> where a userspace round trip is infeasible then that is incremental ABI.
> That ABI would allow userspace to cache golden cert-chain+measurements.
> The resume path can revalidate that identity description with a fresh
> nonce and the cached public key.
From a simple case of 'what is here' in this set, the only bit I'm seeing
change in order to implement what I think you and Jason are describing is we
don't bother checking the cert chain in kernel for the first time: We
provide that to userspace to decide if it's good. If I understand
correctly, this will be at the end of the full sequence once we've pushed
through a nonce and gotten signatures + measurements. Same as checking a
TSM provided transcript. That was sort of happening anyway if we consider
the .cma keyring as just providing a short cut filter if we didn't trust
the device provided root cert.
User space got the transcripts before it had to make any decision on
binding and could do anything it liked with them.
For that caching the public key bit, I'm not clear on whether you intend
to do that from kernel side (which I think I'd prefer) or have user space
squirt that key back in again? If we are doing it in kernel we can
at least always verify the transcript is self consistent and refuse to
give anything to user space if it's not consistent (other than debug material).
By self consistent I mean we verify the signature against the device provided
cert (might as well verify whole chain as that's trivial given we have to partly
parse them anyway to find the cert). I don't think it maters hugely if
we do this in kernel beyond advantage of removing a few foot guns and
simplifying the userpace interface to "I'm fine with the provided transcript
for use when I'm not here next time" write. Disadvantage is we use the
cert parsing code (which is there anyway) and parse it all twice - once
in kernel and probably again in userspace or at some remote verifier.
Measurement verification (in kernel) is potentially a trickier bit of ABI
design as the space of what might be in there and what matters for a given
device is complex to say the least. Only a small part of that is spec
defined.
I can see there may be some use cases where we relax things beyond this
(potentially including .cma keyring and root cert only)
Jonathan
>
> For TDISP the violence of dropping the device out of the TCB likely
> needs more sophistication than golden measurement revalidation. For CMA
> mere trust in the root cert is not sufficient for many of the
> adversarial device threat models, so the kernel need not carry that
> responsibility.
>
> Aneesh and I are putting together some POC patches along these lines.
>
>
^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [RFC v3 00/27] lib: Rust implementation of SPDM
2026-02-23 17:15 ` Jonathan Cameron
@ 2026-02-23 19:11 ` dan.j.williams
2026-02-24 14:33 ` Jason Gunthorpe
2026-03-05 4:17 ` dan.j.williams
0 siblings, 2 replies; 99+ messages in thread
From: dan.j.williams @ 2026-02-23 19:11 UTC (permalink / raw)
To: Jonathan Cameron, dan.j.williams
Cc: Lukas Wunner, Jason Gunthorpe, Alistair Francis, bhelgaas,
rust-for-linux, akpm, linux-pci, linux-cxl, linux-kernel,
alex.gaynor, benno.lossin, boqun.feng, a.hindborg, gary,
bjorn3_gh, tmgross, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis, aneesh.kumar, yilun.xu, aik, Mathieu Poirier,
Thomas Fossati
Jonathan Cameron wrote:
[..]
> From a simple case of 'what is here' in this set, the only bit I'm seeing
> change in order to implement what I think you and Jason are describing is we
> don't bother checking the cert chain in kernel for the first time: We
> provide that to userspace to decide if it's good. If I understand
> correctly, this will be at the end of the full sequence once we've pushed
> through a nonce and gotten signatures + measurements. Same as checking a
> TSM provided transcript. That was sort of happening anyway if we consider
> the .cma keyring as just providing a short cut filter if we didn't trust
> the device provided root cert.
> User space got the transcripts before it had to make any decision on
> binding and could do anything it liked with them.
Exactly, the kernel checking the cert is not sufficient to establish
trust in the device interface (Link + MMIO). If userspace is making a
driver-bind or TDISP accept decision, it needs
certs+measurements+interface-report and does not benefit from the kernel
also validating the certificate.
> For that caching the public key bit, I'm not clear on whether you intend
> to do that from kernel side (which I think I'd prefer) or have user space
> squirt that key back in again? If we are doing it in kernel we can
> at least always verify the transcript is self consistent and refuse to
> give anything to user space if it's not consistent (other than debug material).
> By self consistent I mean we verify the signature against the device provided
> cert (might as well verify whole chain as that's trivial given we have to partly
> parse them anyway to find the cert). I don't think it maters hugely if
> we do this in kernel beyond advantage of removing a few foot guns and
> simplifying the userpace interface to "I'm fine with the provided transcript
> for use when I'm not here next time" write. Disadvantage is we use the
> cert parsing code (which is there anyway) and parse it all twice - once
> in kernel and probably again in userspace or at some remote verifier.
Right, the proposal is cache the public-key from pci_tsm_ops::connect()
and at least require that the resulting transcript from that session
establishment is properly self-signed. No point in continuing with a TSM
implementation that is not self-consistent.
> Measurement verification (in kernel) is potentially a trickier bit of ABI
> design as the space of what might be in there and what matters for a given
> device is complex to say the least. Only a small part of that is spec
> defined.
>
> I can see there may be some use cases where we relax things beyond this
> (potentially including .cma keyring and root cert only)
So I expect there will be an extended tail of problems to solve from
same device and same measurements checks, to full recovery into the TCB.
A .cma keyring may be a part of that eventually, but the "as simple as
possible, but no simpler" starting point is userspace device policy
wrapped around self-signed evidence.
If the device interface is adversarial, mere trust in the SPDM session
is insufficient to protect against the type of attacks that re-checking
the certificate only after reset/resume is meant to mitigate.
^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [RFC v3 00/27] lib: Rust implementation of SPDM
2026-02-23 19:11 ` dan.j.williams
@ 2026-02-24 14:33 ` Jason Gunthorpe
2026-03-05 4:17 ` dan.j.williams
1 sibling, 0 replies; 99+ messages in thread
From: Jason Gunthorpe @ 2026-02-24 14:33 UTC (permalink / raw)
To: dan.j.williams
Cc: Jonathan Cameron, Lukas Wunner, Alistair Francis, bhelgaas,
rust-for-linux, akpm, linux-pci, linux-cxl, linux-kernel,
alex.gaynor, benno.lossin, boqun.feng, a.hindborg, gary,
bjorn3_gh, tmgross, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis, aneesh.kumar, yilun.xu, aik, Mathieu Poirier,
Thomas Fossati
On Mon, Feb 23, 2026 at 11:11:23AM -0800, dan.j.williams@intel.com wrote:
> Jonathan Cameron wrote:
> [..]
> > From a simple case of 'what is here' in this set, the only bit I'm seeing
> > change in order to implement what I think you and Jason are describing is we
> > don't bother checking the cert chain in kernel for the first time: We
> > provide that to userspace to decide if it's good. If I understand
> > correctly, this will be at the end of the full sequence once we've pushed
> > through a nonce and gotten signatures + measurements. Same as checking a
> > TSM provided transcript. That was sort of happening anyway if we consider
> > the .cma keyring as just providing a short cut filter if we didn't trust
> > the device provided root cert.
> > User space got the transcripts before it had to make any decision on
> > binding and could do anything it liked with them.
>
> Exactly, the kernel checking the cert is not sufficient to establish
> trust in the device interface (Link + MMIO). If userspace is making a
> driver-bind or TDISP accept decision, it needs
> certs+measurements+interface-report and does not benefit from the kernel
> also validating the certificate.
Right, and from that position you have to ask *why* would the kernel
ever check the certs, what use case is that supporting, and I can't
come up with any beyond some kernel-internal simple verifier. So why
do we have a cma keyring at all?
I'm willing to be convinced there is some compelling embedded reason
why putting the verifier in the kernel instead of userspcae saves a
meaningful amount of flash - but I don't think we should start there
on the kernel side. First kernel steps should be to enable the
userspace verifier flow, which serves all use cases and offers the
highest policy flexibility.
Resume/RAS is handled by a much stronger same device check, not a weaker
kernel verifier with a root of trust in a keyring.
If somebody eventually wants a weaker check on resume then come with
that explanation and justification and lets talk about it later. I
would probably offer an eBPF policy hook not a CMA keyring though...
> > For that caching the public key bit, I'm not clear on whether you intend
> > to do that from kernel side (which I think I'd prefer) or have user space
> > squirt that key back in again? If we are doing it in kernel we can
> > at least always verify the transcript is self consistent and refuse to
> > give anything to user space if it's not consistent (other than debug material).
> > By self consistent I mean we verify the signature against the device provided
> > cert (might as well verify whole chain as that's trivial given we have to partly
> > parse them anyway to find the cert). I don't think it maters hugely if
> > we do this in kernel beyond advantage of removing a few foot guns and
> > simplifying the userpace interface to "I'm fine with the provided transcript
> > for use when I'm not here next time" write. Disadvantage is we use the
> > cert parsing code (which is there anyway) and parse it all twice - once
> > in kernel and probably again in userspace or at some remote verifier.
>
> Right, the proposal is cache the public-key from pci_tsm_ops::connect()
> and at least require that the resulting transcript from that session
> establishment is properly self-signed. No point in continuing with a TSM
> implementation that is not self-consistent.
I don't have a strong feeling either way, but agree having the kernel
do it automatically and magically seems overall better if it is
possible.
What I want to achieve is a very strong robust 'same device check'
such that if the same device check passes then the userspace verifier
would accept the device again as nothing it could be sensitive to
would change.
As I said in the other email I expect the private keys to be unique to
the physical device so checking the leaf certificates is most of the
way to a same device check. You then need to confirm the FW
configuration of the device is identical.
> > Measurement verification (in kernel) is potentially a trickier bit of ABI
> > design as the space of what might be in there and what matters for a given
> > device is complex to say the least. Only a small part of that is spec
> > defined.
Yes, I suggested we may have the device driver contribute this. From a
kernel perspective we can set out what 'same device check' means and
the drivers have to implement this by processing thier own device
specific attestation.
It is not the same as trying to do "measurement verificaiton" where
you may have a broad policy of what versions and configurations are
acceptable. Here we want simple "measurement has not changed", which I
think is reasonable to do in the kernel.
I gather these attestation reports are complex to parse so it would
make sense to do all that in rust...
> > I can see there may be some use cases where we relax things beyond this
> > (potentially including .cma keyring and root cert only)
>
> So I expect there will be an extended tail of problems to solve from
> same device and same measurements checks, to full recovery into the TCB.
> A .cma keyring may be a part of that eventually, but the "as simple as
> possible, but no simpler" starting point is userspace device policy
> wrapped around self-signed evidence.
I'm also willing to entertain a cma keyring, but only with a strong
justification and fitting into this overall security model after we
establish the initial basic flows in their most secure versions.
> If the device interface is adversarial, mere trust in the SPDM session
> is insufficient to protect against the type of attacks that re-checking
> the certificate only after reset/resume is meant to mitigate.
Right, if you want to exclude physical attacks checking certificates
only is clearly not sufficient.
Jason
^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [RFC v3 00/27] lib: Rust implementation of SPDM
2026-02-23 19:11 ` dan.j.williams
2026-02-24 14:33 ` Jason Gunthorpe
@ 2026-03-05 4:17 ` dan.j.williams
2026-03-05 12:48 ` Jason Gunthorpe
1 sibling, 1 reply; 99+ messages in thread
From: dan.j.williams @ 2026-03-05 4:17 UTC (permalink / raw)
To: dan.j.williams, Jonathan Cameron, dan.j.williams
Cc: Lukas Wunner, Jason Gunthorpe, Alistair Francis, bhelgaas,
rust-for-linux, akpm, linux-pci, linux-cxl, linux-kernel,
alex.gaynor, benno.lossin, boqun.feng, a.hindborg, gary,
bjorn3_gh, tmgross, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis, aneesh.kumar, yilun.xu, aik, Mathieu Poirier,
Thomas Fossati
dan.j.williams@ wrote:
[..]
> > For that caching the public key bit, I'm not clear on whether you intend
> > to do that from kernel side (which I think I'd prefer) or have user space
> > squirt that key back in again? If we are doing it in kernel we can
> > at least always verify the transcript is self consistent and refuse to
> > give anything to user space if it's not consistent (other than debug material).
> > By self consistent I mean we verify the signature against the device provided
> > cert (might as well verify whole chain as that's trivial given we have to partly
> > parse them anyway to find the cert). I don't think it maters hugely if
> > we do this in kernel beyond advantage of removing a few foot guns and
> > simplifying the userpace interface to "I'm fine with the provided transcript
> > for use when I'm not here next time" write. Disadvantage is we use the
> > cert parsing code (which is there anyway) and parse it all twice - once
> > in kernel and probably again in userspace or at some remote verifier.
>
> Right, the proposal is cache the public-key from pci_tsm_ops::connect()
> and at least require that the resulting transcript from that session
> establishment is properly self-signed. No point in continuing with a TSM
> implementation that is not self-consistent.
So I ended up dropping this bit of the proposal because there is no need
for the kernel to be involved in any decision about the validity and
sufficiency of device evidence. Userspace has everything it needs to
make that initial determination. "Authenticated" simply means "evidence
ready".
Automatic device recovery into the TCB is a separate concern that needs
to be prepared to handle more than just "is this device able to generate
a fresh signature with the same cert chain that userspace saw before".
Yes, that is a minimal requirement but not sufficient for many cases.
For example cases that want to validate measurements, interface reports,
or opt-out of recovery because SPDM session loss is fatal.
I had a chat with Lukas about what I think that looks like this morning
and offered to summarize that in this thread. Note, this description is
assuming the drivers/pci/tsm/evidence.c implementation started here [1].
[1]: http://lore.kernel.org/20260303000207.1836586-9-dan.j.williams@intel.com
Authenticate a device
=====================
Look in /sys/class/tsm to find a tsmN device which will be either an
instance associated with native kernel PCI CMA/SPDM or a platform tsm
like the one provided by AMD SEV-TIO, ARM CCA, Intel TDX, etc...
echo tsmN > /sys/bus/pci/devices/$device/tsm/connect
Once that succeeds the PCI/TSM evidence netlink interface is available
to dump any signatures created during that session establishment.
After userspace is happy with that evidence it can bind a driver.
If running in a confidential VM where the TSM driver is capable of
securing more than just an SPDM session the interface is:
echo tsmN > /sys/bus/pci/devices/$device/tsm/lock
Similar evidence can be collected, and when userspace is happy with it
it can accept the device:
echo 1 > /sys/bus/pci/devices/$device/tsm/accept
...and bind a driver.
Auto-recover device (future work)
=================================
By default, devices fall out of the TCB on recovery events for the TDISP
case and need userspace to rerun the lock and accept flow. For the
native SPDM CMA case the default is that the kernel continues to operate
the device post recovery and only userspace polling could detect device
replacement.
To go beyond those defaults the kernel needs userpsace to tell it how to
re-validate the device. I think that can be as simple as a netlink
message to store hashes of cert chains or measurements and then use
those in a new challenge / response with the device with a kernel
decided nonce. It can also be more sophisiticated than that. The
point is that it will be use case dependendent, policy driven, and
separate from the "connect" or "lock" flow.
The equivalent behavior to what is present in this SPDM proposal is
extend drivers/pci/tsm/evidence.c to add a netlink operation that tells
the kernel to cache the public-key and challenge the device regenerate a
valid signature. Then plumb all the recovery paths to call a new
'struct pci_tsm_ops::revalidate()' operation in all the same places
where this patch set wants to reauthenticate. Later when something more
sophisticated than "challenge the device to create a signature" comes
along it can reuse those revalidate() hooks.
Auto-authenticate devices (future work)
=======================================
One of the nice aspects of the .cma keyring proposal is that it would be
amenable to CONFIG_SYSTEM_EXTRA_CERTIFICATE. Give the kernel the
certificate to validate device signatures without needing to ask
userspace. That still comes with the caveat that it is not sufficient
for the cases that care about more than mere signature verification. I
am not opposed to that being built just that it is not a policy that is
implied by the device "authenticated" state. It is policy that is
incremental to the base which makes no assumptions about kernel
verification.
^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [RFC v3 00/27] lib: Rust implementation of SPDM
2026-03-05 4:17 ` dan.j.williams
@ 2026-03-05 12:48 ` Jason Gunthorpe
2026-03-05 19:49 ` dan.j.williams
0 siblings, 1 reply; 99+ messages in thread
From: Jason Gunthorpe @ 2026-03-05 12:48 UTC (permalink / raw)
To: dan.j.williams
Cc: Jonathan Cameron, Lukas Wunner, Alistair Francis, bhelgaas,
rust-for-linux, akpm, linux-pci, linux-cxl, linux-kernel,
alex.gaynor, benno.lossin, boqun.feng, a.hindborg, gary,
bjorn3_gh, tmgross, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis, aneesh.kumar, yilun.xu, aik, Mathieu Poirier,
Thomas Fossati
On Wed, Mar 04, 2026 at 08:17:24PM -0800, dan.j.williams@intel.com wrote:
> So I ended up dropping this bit of the proposal because there is no need
> for the kernel to be involved in any decision about the validity and
> sufficiency of device evidence. Userspace has everything it needs to
> make that initial determination. "Authenticated" simply means "evidence
> ready".
Right, this caching was only for the automatic device recovery flow.
I think the kernel still probably needs to check the signed nonce
against the device's public key as part of the SPDM handshake, but it
doesn't need to do any validation of the public key.
> Automatic device recovery into the TCB is a separate concern that needs
> to be prepared to handle more than just "is this device able to generate
> a fresh signature with the same cert chain that userspace saw before".
> Yes, that is a minimal requirement but not sufficient for many cases.
> For example cases that want to validate measurements, interface reports,
> or opt-out of recovery because SPDM session loss is fatal.
Yeah, but it is a pretty good starting point :)
> Authenticate a device
> =====================
> Look in /sys/class/tsm to find a tsmN device which will be either an
> instance associated with native kernel PCI CMA/SPDM or a platform tsm
> like the one provided by AMD SEV-TIO, ARM CCA, Intel TDX, etc...
>
> echo tsmN > /sys/bus/pci/devices/$device/tsm/connect
>
> Once that succeeds the PCI/TSM evidence netlink interface is available
> to dump any signatures created during that session establishment.
>
> After userspace is happy with that evidence it can bind a driver.
>
> If running in a confidential VM where the TSM driver is capable of
> securing more than just an SPDM session the interface is:
>
> echo tsmN > /sys/bus/pci/devices/$device/tsm/lock
>
> Similar evidence can be collected, and when userspace is happy with it
> it can accept the device:
>
> echo 1 > /sys/bus/pci/devices/$device/tsm/accept
>
> ...and bind a driver.
Makes sense to me, userspace can figure out what flow to use.
> Auto-recover device (future work)
> =================================
> By default, devices fall out of the TCB on recovery events for the TDISP
> case and need userspace to rerun the lock and accept flow. For the
> native SPDM CMA case the default is that the kernel continues to operate
> the device post recovery and only userspace polling could detect device
> replacement.
Even with SPDM the kernel should know if the SPDM session has to be
restarted right? It could squirt out a netlink multicast message, or a
uevent on these events, so the polling is not baked into the
architecture?
> To go beyond those defaults the kernel needs userpsace to tell it how to
> re-validate the device. I think that can be as simple as a netlink
> message to store hashes of cert chains or measurements and then use
> those in a new challenge / response with the device with a kernel
> decided nonce.
Yeah, I see several reasonable options:
1) Above, kernel is informed of hashes and does an exact content
compare
2) The driver operating the device has a built in same-device policy
and deep parses the evidence for equivilance after the private key
is validated, carefully avoiding volatile evidence bytes.
3) User provides a BPF program and it runs on the evidence
4) Standards bodies define a generic "same device check" algorithm for
the evidence and core kernel just does this for compatible devices
> The equivalent behavior to what is present in this SPDM proposal is
> extend drivers/pci/tsm/evidence.c to add a netlink operation that tells
> the kernel to cache the public-key and challenge the device regenerate a
> valid signature. Then plumb all the recovery paths to call a new
> 'struct pci_tsm_ops::revalidate()' operation in all the same places
> where this patch set wants to reauthenticate. Later when something more
> sophisticated than "challenge the device to create a signature" comes
> along it can reuse those revalidate() hooks.
That's a nice simple starting point.
> Auto-authenticate devices (future work)
> =======================================
> One of the nice aspects of the .cma keyring proposal is that it would be
> amenable to CONFIG_SYSTEM_EXTRA_CERTIFICATE. Give the kernel the
> certificate to validate device signatures without needing to ask
> userspace. That still comes with the caveat that it is not sufficient
> for the cases that care about more than mere signature verification. I
> am not opposed to that being built just that it is not a policy that is
> implied by the device "authenticated" state. It is policy that is
> incremental to the base which makes no assumptions about kernel
> verification.
I have no specific objection to this, and we have many example of
kernel having built-in policy with an option for userspace to take
control. But I'd like to hear a solid reason for a use case that
requires it. I don't really buy the argument that userspace is too
much flash consumption if all the implementation is doing is checking
a few certs using an already present TLS library.
Jason
^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [RFC v3 00/27] lib: Rust implementation of SPDM
2026-03-05 12:48 ` Jason Gunthorpe
@ 2026-03-05 19:49 ` dan.j.williams
2026-03-09 11:39 ` Jonathan Cameron
0 siblings, 1 reply; 99+ messages in thread
From: dan.j.williams @ 2026-03-05 19:49 UTC (permalink / raw)
To: Jason Gunthorpe, dan.j.williams
Cc: Jonathan Cameron, Lukas Wunner, Alistair Francis, bhelgaas,
rust-for-linux, akpm, linux-pci, linux-cxl, linux-kernel,
alex.gaynor, benno.lossin, boqun.feng, a.hindborg, gary,
bjorn3_gh, tmgross, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis, aneesh.kumar, yilun.xu, aik, Mathieu Poirier,
Thomas Fossati
Jason Gunthorpe wrote:
> On Wed, Mar 04, 2026 at 08:17:24PM -0800, dan.j.williams@intel.com wrote:
>
> > So I ended up dropping this bit of the proposal because there is no need
> > for the kernel to be involved in any decision about the validity and
> > sufficiency of device evidence. Userspace has everything it needs to
> > make that initial determination. "Authenticated" simply means "evidence
> > ready".
>
> Right, this caching was only for the automatic device recovery flow.
>
> I think the kernel still probably needs to check the signed nonce
> against the device's public key as part of the SPDM handshake, but it
> doesn't need to do any validation of the public key.
Yes, and I think this is a subtle detail that I was failing to grok /
communicate previously. Of course the native SPDM implementation needs
to be able to perform a challenge response to establish the session. All
of the platform TSMs also internally carry out that protocol. The
difference with platform TSMs being that the kernel only sees the
resulting evidence.
So when / if the PCI/TSM evidence implementation grows kernel-internal
revalidate support it will be growing something like pdev->tsm->pubkey
that is installed / cached "after the fact" for platform TSMs. For the
native SPDM driver, pdev->tsm->pubkey can be installed at 'struct
pci_tsm_ops::connect()' time because it already has it parsed for its
own internal purposes.
> > Automatic device recovery into the TCB is a separate concern that needs
> > to be prepared to handle more than just "is this device able to generate
> > a fresh signature with the same cert chain that userspace saw before".
> > Yes, that is a minimal requirement but not sufficient for many cases.
> > For example cases that want to validate measurements, interface reports,
> > or opt-out of recovery because SPDM session loss is fatal.
>
> Yeah, but it is a pretty good starting point :)
>
> > Authenticate a device
> > =====================
> > Look in /sys/class/tsm to find a tsmN device which will be either an
> > instance associated with native kernel PCI CMA/SPDM or a platform tsm
> > like the one provided by AMD SEV-TIO, ARM CCA, Intel TDX, etc...
> >
> > echo tsmN > /sys/bus/pci/devices/$device/tsm/connect
> >
> > Once that succeeds the PCI/TSM evidence netlink interface is available
> > to dump any signatures created during that session establishment.
> >
> > After userspace is happy with that evidence it can bind a driver.
> >
> > If running in a confidential VM where the TSM driver is capable of
> > securing more than just an SPDM session the interface is:
> >
> > echo tsmN > /sys/bus/pci/devices/$device/tsm/lock
> >
> > Similar evidence can be collected, and when userspace is happy with it
> > it can accept the device:
> >
> > echo 1 > /sys/bus/pci/devices/$device/tsm/accept
> >
> > ...and bind a driver.
>
> Makes sense to me, userspace can figure out what flow to use.
>
> > Auto-recover device (future work)
> > =================================
> > By default, devices fall out of the TCB on recovery events for the TDISP
> > case and need userspace to rerun the lock and accept flow. For the
> > native SPDM CMA case the default is that the kernel continues to operate
> > the device post recovery and only userspace polling could detect device
> > replacement.
>
> Even with SPDM the kernel should know if the SPDM session has to be
> restarted right? It could squirt out a netlink multicast message, or a
> uevent on these events, so the polling is not baked into the
> architecture?
Right, "SPDM session lost" is a reasonable notification to put into the
interface and "future work" bucket. The polling comment was with respect
to the near term limitation of this minimally viable first step.
> > To go beyond those defaults the kernel needs userpsace to tell it how to
> > re-validate the device. I think that can be as simple as a netlink
> > message to store hashes of cert chains or measurements and then use
> > those in a new challenge / response with the device with a kernel
> > decided nonce.
>
> Yeah, I see several reasonable options:
+1 to these.
> 1) Above, kernel is informed of hashes and does an exact content
> compare
>
> 2) The driver operating the device has a built in same-device policy
> and deep parses the evidence for equivilance after the private key
> is validated, carefully avoiding volatile evidence bytes.
Yes, Lukas was also pointing out that the existing driver reset/resume
handlers could signal "revalidated" as well if the PCI layer had no
opinion.
...but the point is that it is a menu of options, not a static policy.
> 3) User provides a BPF program and it runs on the evidence
>
> 4) Standards bodies define a generic "same device check" algorithm for
> the evidence and core kernel just does this for compatible devices
>
> > The equivalent behavior to what is present in this SPDM proposal is
> > extend drivers/pci/tsm/evidence.c to add a netlink operation that tells
> > the kernel to cache the public-key and challenge the device regenerate a
> > valid signature. Then plumb all the recovery paths to call a new
> > 'struct pci_tsm_ops::revalidate()' operation in all the same places
> > where this patch set wants to reauthenticate. Later when something more
> > sophisticated than "challenge the device to create a signature" comes
> > along it can reuse those revalidate() hooks.
>
> That's a nice simple starting point.
Appreciate it. Still want to close the loop with Lukas to make sure
everything he wants to do for native SPDM can fit in this scheme, but I
am feeling increasingly confident.
^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [RFC v3 00/27] lib: Rust implementation of SPDM
2026-03-05 19:49 ` dan.j.williams
@ 2026-03-09 11:39 ` Jonathan Cameron
2026-03-09 12:31 ` Jason Gunthorpe
0 siblings, 1 reply; 99+ messages in thread
From: Jonathan Cameron @ 2026-03-09 11:39 UTC (permalink / raw)
To: dan.j.williams
Cc: Jason Gunthorpe, Lukas Wunner, Alistair Francis, bhelgaas,
rust-for-linux, akpm, linux-pci, linux-cxl, linux-kernel,
alex.gaynor, benno.lossin, boqun.feng, a.hindborg, gary,
bjorn3_gh, tmgross, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis, aneesh.kumar, yilun.xu, aik, Mathieu Poirier,
Thomas Fossati
On Thu, 5 Mar 2026 11:49:27 -0800
dan.j.williams@intel.com wrote:
> Jason Gunthorpe wrote:
> > On Wed, Mar 04, 2026 at 08:17:24PM -0800, dan.j.williams@intel.com wrote:
> >
> > > So I ended up dropping this bit of the proposal because there is no need
> > > for the kernel to be involved in any decision about the validity and
> > > sufficiency of device evidence. Userspace has everything it needs to
> > > make that initial determination. "Authenticated" simply means "evidence
> > > ready".
> >
> > Right, this caching was only for the automatic device recovery flow.
> >
> > I think the kernel still probably needs to check the signed nonce
> > against the device's public key as part of the SPDM handshake, but it
> > doesn't need to do any validation of the public key.
>
> Yes, and I think this is a subtle detail that I was failing to grok /
> communicate previously. Of course the native SPDM implementation needs
> to be able to perform a challenge response to establish the session. All
> of the platform TSMs also internally carry out that protocol. The
> difference with platform TSMs being that the kernel only sees the
> resulting evidence.
I had a half written email on this but you two already covered much of it
and discussion is heading where I was going anyway :)
For native / CMA:
I agree that the kernel 'should' check the signature in the final
challenge/auth message so as to validate the whole flow was valid.
Fine to leave cert checking to userspace - that was earlier in
the flow anyway. I'd also like it to do sanity checks on the cert
chain (for internal consistency and the few requirements PCI puts
on the leaf cert), some of which it needs to do to pull the public
key out of the leaf anyway. I think that's all cheap stuff to
detect failures early.
As far as I can tell, the signature check isn't strictly necessary
as userspace has all the info to do that signature check on the
transcript. Bit of nasty parsing code but not particularly bad and
likely to be in tooling anyway for debug purposes. (I'm assuming that's
why there is a 'probably' above :)
We only _need_ the kernel to check the signature (in challenge/auth)
for the (future) recovery flow.
As Alastair's rust demonstrates though it's low cost in terms
of complexity to ensure internal consistency. I'm assuming
the various TSMs are doing similar?
Anyhow, all that really says is I'd like the internal consistency of
the SPDM session checking in now. Leave checking we actually trust
the cert + measurements to user space (and all the stuff below
on recovery comes later).
>
> So when / if the PCI/TSM evidence implementation grows kernel-internal
> revalidate support it will be growing something like pdev->tsm->pubkey
> that is installed / cached "after the fact" for platform TSMs. For the
> native SPDM driver, pdev->tsm->pubkey can be installed at 'struct
> pci_tsm_ops::connect()' time because it already has it parsed for its
> own internal purposes.
Agreed it'll be something along those lines.
>
> > > Automatic device recovery into the TCB is a separate concern that needs
> > > to be prepared to handle more than just "is this device able to generate
> > > a fresh signature with the same cert chain that userspace saw before".
> > > Yes, that is a minimal requirement but not sufficient for many cases.
> > > For example cases that want to validate measurements, interface reports,
> > > or opt-out of recovery because SPDM session loss is fatal.
> >
> > Yeah, but it is a pretty good starting point :)
> >
> > > Authenticate a device
> > > =====================
> > > Look in /sys/class/tsm to find a tsmN device which will be either an
> > > instance associated with native kernel PCI CMA/SPDM or a platform tsm
> > > like the one provided by AMD SEV-TIO, ARM CCA, Intel TDX, etc...
> > >
> > > echo tsmN > /sys/bus/pci/devices/$device/tsm/connect
> > >
> > > Once that succeeds the PCI/TSM evidence netlink interface is available
> > > to dump any signatures created during that session establishment.
> > >
> > > After userspace is happy with that evidence it can bind a driver.
> > >
> > > If running in a confidential VM where the TSM driver is capable of
> > > securing more than just an SPDM session the interface is:
> > >
> > > echo tsmN > /sys/bus/pci/devices/$device/tsm/lock
> > >
> > > Similar evidence can be collected, and when userspace is happy with it
> > > it can accept the device:
> > >
> > > echo 1 > /sys/bus/pci/devices/$device/tsm/accept
> > >
> > > ...and bind a driver.
> >
> > Makes sense to me, userspace can figure out what flow to use.
> >
> > > Auto-recover device (future work)
> > > =================================
> > > By default, devices fall out of the TCB on recovery events for the TDISP
> > > case and need userspace to rerun the lock and accept flow. For the
> > > native SPDM CMA case the default is that the kernel continues to operate
> > > the device post recovery and only userspace polling could detect device
> > > replacement.
> >
> > Even with SPDM the kernel should know if the SPDM session has to be
> > restarted right? It could squirt out a netlink multicast message, or a
> > uevent on these events, so the polling is not baked into the
> > architecture?
>
> Right, "SPDM session lost" is a reasonable notification to put into the
> interface and "future work" bucket. The polling comment was with respect
> to the near term limitation of this minimally viable first step.
>
> > > To go beyond those defaults the kernel needs userpsace to tell it how to
> > > re-validate the device. I think that can be as simple as a netlink
> > > message to store hashes of cert chains or measurements and then use
> > > those in a new challenge / response with the device with a kernel
> > > decided nonce.
> >
> > Yeah, I see several reasonable options:
>
> +1 to these.
>
> > 1) Above, kernel is informed of hashes and does an exact content
> > compare
> >
> > 2) The driver operating the device has a built in same-device policy
> > and deep parses the evidence for equivilance after the private key
> > is validated, carefully avoiding volatile evidence bytes.
>
> Yes, Lukas was also pointing out that the existing driver reset/resume
> handlers could signal "revalidated" as well if the PCI layer had no
> opinion.
>
> ...but the point is that it is a menu of options, not a static policy.
>
> > 3) User provides a BPF program and it runs on the evidence
> >
> > 4) Standards bodies define a generic "same device check" algorithm for
> > the evidence and core kernel just does this for compatible devices
> >
> > > The equivalent behavior to what is present in this SPDM proposal is
> > > extend drivers/pci/tsm/evidence.c to add a netlink operation that tells
> > > the kernel to cache the public-key and challenge the device regenerate a
> > > valid signature. Then plumb all the recovery paths to call a new
> > > 'struct pci_tsm_ops::revalidate()' operation in all the same places
> > > where this patch set wants to reauthenticate. Later when something more
> > > sophisticated than "challenge the device to create a signature" comes
> > > along it can reuse those revalidate() hooks.
> >
> > That's a nice simple starting point.
>
> Appreciate it. Still want to close the loop with Lukas to make sure
> everything he wants to do for native SPDM can fit in this scheme, but I
> am feeling increasingly confident.
So far all sounds good to me.
Jonathan
^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [RFC v3 00/27] lib: Rust implementation of SPDM
2026-03-09 11:39 ` Jonathan Cameron
@ 2026-03-09 12:31 ` Jason Gunthorpe
2026-03-09 15:33 ` Jonathan Cameron
0 siblings, 1 reply; 99+ messages in thread
From: Jason Gunthorpe @ 2026-03-09 12:31 UTC (permalink / raw)
To: Jonathan Cameron
Cc: dan.j.williams, Lukas Wunner, Alistair Francis, bhelgaas,
rust-for-linux, akpm, linux-pci, linux-cxl, linux-kernel,
alex.gaynor, benno.lossin, boqun.feng, a.hindborg, gary,
bjorn3_gh, tmgross, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis, aneesh.kumar, yilun.xu, aik, Mathieu Poirier,
Thomas Fossati
On Mon, Mar 09, 2026 at 11:39:41AM +0000, Jonathan Cameron wrote:
> Anyhow, all that really says is I'd like the internal consistency of
> the SPDM session checking in now. Leave checking we actually trust
> the cert + measurements to user space (and all the stuff below
> on recovery comes later).
I think kernel doing internal consistency (non policy) checks is fine
We just need to be careful to construct things so it is compatible
with an external verifier. Even in this bare SPDM mode we still want
to hook it into external verification.
I don't know alot, but I understand this requires the nonce (?) to
come from userspace? I suppose the flow is the usual crypto something
like
- Kernel negotiates a DH session/CSPRNG with the peer, generates
symmetric keys from the CSPRNG
- Kernel forwards a nonce challenge and peer signs it, somehow mixing in
CSPRNG data to bind to the DH session
- Kernel checks the signature against the public key and confirms the
CSPRNG and nonce are correct (only kernel can do this since the
CSPRNG must not leak out of the kernel)
- Kernel forwards the signature from the peer to userspace and
userspace re-checks the signature against the public key and
validates the nonce is the one it issued, and checks that the
public key is acceptable. Ignores the CSPRNG data.
- Presumably in a real TSM there will be a merkle tree signed by the
TSM's platform key that binds together all the TVM measurements and
the SPDM activity so the verifier can see a complete picture?
ie that the verifier pushed nonce A authenticated SPDM session B
which is part of PCI device C that is part of TVM D.
Should kernel be extending TPM PCRs or something in this bare SPDM
mode to provide something similar using the TPM to produce
platform evidence?
Of course all of this should try to align with the way TSMs are
working so we have as uniform as possible uAPI for evidence transfer.
Though obviously a kernel SPDM has to be distinguisable from any other
TSM from a verifier POV.
Jason
^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [RFC v3 00/27] lib: Rust implementation of SPDM
2026-03-09 12:31 ` Jason Gunthorpe
@ 2026-03-09 15:33 ` Jonathan Cameron
2026-03-09 15:59 ` Jason Gunthorpe
0 siblings, 1 reply; 99+ messages in thread
From: Jonathan Cameron @ 2026-03-09 15:33 UTC (permalink / raw)
To: Jason Gunthorpe
Cc: dan.j.williams, Lukas Wunner, Alistair Francis, bhelgaas,
rust-for-linux, akpm, linux-pci, linux-cxl, linux-kernel,
alex.gaynor, benno.lossin, boqun.feng, a.hindborg, gary,
bjorn3_gh, tmgross, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis, aneesh.kumar, yilun.xu, aik, Mathieu Poirier,
Thomas Fossati
On Mon, 9 Mar 2026 09:31:48 -0300
Jason Gunthorpe <jgg@nvidia.com> wrote:
> On Mon, Mar 09, 2026 at 11:39:41AM +0000, Jonathan Cameron wrote:
>
> > Anyhow, all that really says is I'd like the internal consistency of
> > the SPDM session checking in now. Leave checking we actually trust
> > the cert + measurements to user space (and all the stuff below
> > on recovery comes later).
>
> I think kernel doing internal consistency (non policy) checks is fine
>
> We just need to be careful to construct things so it is compatible
> with an external verifier. Even in this bare SPDM mode we still want
> to hook it into external verification.
>
> I don't know alot, but I understand this requires the nonce (?) to
> come from userspace?
Going out of my area of expertise... So take the following with a pinch
of salt.
Yes to userspace provided nonce. My understanding is that can come
from the verifier that wants to ensure freshness.
I'm not sure exactly what our security model is in the native CMA case,
so what software we can trust on the host. I.e. does the DH session actually
need to be between the kernel and the peer?
> I suppose the flow is the usual crypto something
> like
> - Kernel negotiates a DH session/CSPRNG with the peer, generates
> symmetric keys from the CSPRNG
> - Kernel forwards a nonce challenge and peer signs it, somehow mixing in
> CSPRNG data to bind to the DH session
We have a small amount of context (8 bytes) that we can put anything as
part of challenge/auth (alongside the nonce) It will part of the signed
response. Would that work for something from with the CSPRNG,
mixed so that you can't go from that context to the CSPRNG value?
> - Kernel checks the signature against the public key and confirms the
> CSPRNG and nonce are correct (only kernel can do this since the
> CSPRNG must not leak out of the kernel)
> - Kernel forwards the signature from the peer to userspace and
> userspace re-checks the signature against the public key and
> validates the nonce is the one it issued, and checks that the
> public key is acceptable. Ignores the CSPRNG data.
> - Presumably in a real TSM there will be a merkle tree signed by the
> TSM's platform key that binds together all the TVM measurements and
> the SPDM activity so the verifier can see a complete picture?
>
> ie that the verifier pushed nonce A authenticated SPDM session B
> which is part of PCI device C that is part of TVM D.
>
> Should kernel be extending TPM PCRs or something in this bare SPDM
> mode to provide something similar using the TPM to produce
> platform evidence?
>
> Of course all of this should try to align with the way TSMs are
> working so we have as uniform as possible uAPI for evidence transfer.
> Though obviously a kernel SPDM has to be distinguisable from any other
> TSM from a verifier POV.
Agreed. Very interesting to know what exactly is going in the TSM
SPDM exchanges as hopefully that will reflect best practice. If we
are really lucky they won't all do different things ;)
Jonathan
>
> Jason
^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [RFC v3 00/27] lib: Rust implementation of SPDM
2026-03-09 15:33 ` Jonathan Cameron
@ 2026-03-09 15:59 ` Jason Gunthorpe
2026-03-09 18:00 ` Jonathan Cameron
0 siblings, 1 reply; 99+ messages in thread
From: Jason Gunthorpe @ 2026-03-09 15:59 UTC (permalink / raw)
To: Jonathan Cameron
Cc: dan.j.williams, Lukas Wunner, Alistair Francis, bhelgaas,
rust-for-linux, akpm, linux-pci, linux-cxl, linux-kernel,
alex.gaynor, benno.lossin, boqun.feng, a.hindborg, gary,
bjorn3_gh, tmgross, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis, aneesh.kumar, yilun.xu, aik, Mathieu Poirier,
Thomas Fossati
On Mon, Mar 09, 2026 at 03:33:39PM +0000, Jonathan Cameron wrote:
> I'm not sure exactly what our security model is in the native CMA case,
> so what software we can trust on the host. I.e. does the DH session actually
> need to be between the kernel and the peer?
Yes, absolutely a DH session is required in all cases, it is the only
way to generate a PCI encryption shared secret and exclude a MIM.
For native the verifer should be able to measure the running kernel
using the TPM, and then confirm the measured kernel and the peer have
established a MIM free PCIe encryption, and finally measure the peer
device.
For instance, imagine an enhanced version of "Network-Bound Disk
Encryption" where the storage key is not released unless the server
also validates PCI properties (physically encrypted with no MIM, right
devices, etc)
> > I suppose the flow is the usual crypto something
> > like
> > - Kernel negotiates a DH session/CSPRNG with the peer, generates
> > symmetric keys from the CSPRNG
> > - Kernel forwards a nonce challenge and peer signs it, somehow mixing in
> > CSPRNG data to bind to the DH session
>
> We have a small amount of context (8 bytes) that we can put anything as
> part of challenge/auth (alongside the nonce) It will part of the signed
> response. Would that work for something from with the CSPRNG,
> mixed so that you can't go from that context to the CSPRNG value?
I assume SPDM is doing this already somehow otherwise there is no way
to setup the PCI encryption keys. The fundamental purpose of the
signature is to exclude a MIM by signing something that proves there
is no MIM, and that is usually a value that both sides independently
pull out of the CSPRNG. If they derive the same value then they have
the same CSPRNG and DH tells us nobody else can have it. But there are
other approaches too..
> > Of course all of this should try to align with the way TSMs are
> > working so we have as uniform as possible uAPI for evidence transfer.
> > Though obviously a kernel SPDM has to be distinguisable from any other
> > TSM from a verifier POV.
>
> Agreed. Very interesting to know what exactly is going in the TSM
> SPDM exchanges as hopefully that will reflect best practice. If we
> are really lucky they won't all do different things ;)
Yeah, and I don't really know the details, just have some general idea
how attestation and PCI link encryption should work in broad strokes.
But I know people who do, so if we can get a series that clearly lays
out the proposed kernel flow I can possibly get someone to compare
it..
The baseline I expect is a merkle tree signed by the root of trust
(TPM or platform TSM) that encompases everything down to the required
bits of the SPDM negotiation to prove no MIM.
Jason
^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [RFC v3 00/27] lib: Rust implementation of SPDM
2026-03-09 15:59 ` Jason Gunthorpe
@ 2026-03-09 18:00 ` Jonathan Cameron
2026-03-09 20:40 ` Jason Gunthorpe
0 siblings, 1 reply; 99+ messages in thread
From: Jonathan Cameron @ 2026-03-09 18:00 UTC (permalink / raw)
To: Jason Gunthorpe
Cc: dan.j.williams, Lukas Wunner, Alistair Francis, bhelgaas,
rust-for-linux, akpm, linux-pci, linux-cxl, linux-kernel,
alex.gaynor, benno.lossin, boqun.feng, a.hindborg, gary,
bjorn3_gh, tmgross, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis, aneesh.kumar, yilun.xu, aik, Mathieu Poirier,
Thomas Fossati
On Mon, 9 Mar 2026 12:59:09 -0300
Jason Gunthorpe <jgg@nvidia.com> wrote:
> On Mon, Mar 09, 2026 at 03:33:39PM +0000, Jonathan Cameron wrote:
>
> > I'm not sure exactly what our security model is in the native CMA case,
> > so what software we can trust on the host. I.e. does the DH session actually
> > need to be between the kernel and the peer?
>
> Yes, absolutely a DH session is required in all cases, it is the only
> way to generate a PCI encryption shared secret and exclude a MIM.
Ah. I was missing what you wanted with the DH part. For some reason wasn't
thinking about IDE (maybe because this patch set doesn't get you there for
native). Though as I understand it some of the native usecases for CMA aren't
using link encryption (different security model from CoCo).
Yes, if you want to avoid MIM you need to bring up IDE etc - the basic fact
that both ends can still talk to each other after enabling it given they have
to have the same keys and those went over the secure channel, is part of the
security around that. I think you can attest before that point and rely on
the link 'failing' if there was a man in the middle. The verifier would
want the evidence IDE was up before giving you any secrets.
Whether anyone actually implements root ports via standard DOE flows or
everyone does this a custom way at the host is an open question.
There are various mix and match situations where the Coco / TSM flows
mean we have an encrypted channel, but we are doing our own native
attestation (to a different DOE, say on a VF). Anyhow, others on this thread
have more info (I think) on the corner cases of how this is going to be
used than I do.
The secure channel establishment and key exchange comes later in the SPDM
flow than this patch set currently covers. This bit just gets you to the
point where you know you are ultimately talking to right device - you don't
know there isn't a MIM at this point.
Next up is KEY_EXCHANGE / KEY_EXCHANGE_RSP / FINISH / FINISH_RSP that will
exchange the public ephemeral keys and ultimately get us a secure channel.
Then we then switch to secured-CMA which is the PCIe spec and is basically the
format for wrapping up the session IDs etc + the encrypted channel.
Lukas' github has the next few bits required, but not sure he's all the
way there yet?
That then gets used for key exchange messages related to IDE via the PCI
spec defined IDE_KM protocol. (which is fairly simple compared to the other
steps - only about 8 pages :)).
So we may need to get a bit further before we can do the attestation
flow that would be equivalent to what CoCo needs. I'm thinking
we may need one more set of measurements to verify both ends think
IDE is up.
For now the host will know it didn't bring up IDE.
>
> For native the verifer should be able to measure the running kernel
> using the TPM, and then confirm the measured kernel and the peer have
> established a MIM free PCIe encryption, and finally measure the peer
> device.
>
> For instance, imagine an enhanced version of "Network-Bound Disk
> Encryption" where the storage key is not released unless the server
> also validates PCI properties (physically encrypted with no MIM, right
> devices, etc)
>
> > > I suppose the flow is the usual crypto something
> > > like
> > > - Kernel negotiates a DH session/CSPRNG with the peer, generates
> > > symmetric keys from the CSPRNG
> > > - Kernel forwards a nonce challenge and peer signs it, somehow mixing in
> > > CSPRNG data to bind to the DH session
> >
> > We have a small amount of context (8 bytes) that we can put anything as
> > part of challenge/auth (alongside the nonce) It will part of the signed
> > response. Would that work for something from with the CSPRNG,
> > mixed so that you can't go from that context to the CSPRNG value?
>
> I assume SPDM is doing this already somehow otherwise there is no way
> to setup the PCI encryption keys. The fundamental purpose of the
> signature is to exclude a MIM by signing something that proves there
> is no MIM, and that is usually a value that both sides independently
> pull out of the CSPRNG. If they derive the same value then they have
> the same CSPRNG and DH tells us nobody else can have it. But there are
> other approaches too..
For SPDM it's this or pre shared key stuff which requires some out of
band way path. So probably ignore that.
>
> > > Of course all of this should try to align with the way TSMs are
> > > working so we have as uniform as possible uAPI for evidence transfer.
> > > Though obviously a kernel SPDM has to be distinguisable from any other
> > > TSM from a verifier POV.
> >
> > Agreed. Very interesting to know what exactly is going in the TSM
> > SPDM exchanges as hopefully that will reflect best practice. If we
> > are really lucky they won't all do different things ;)
>
> Yeah, and I don't really know the details, just have some general idea
> how attestation and PCI link encryption should work in broad strokes.
>
> But I know people who do, so if we can get a series that clearly lays
> out the proposed kernel flow I can possibly get someone to compare
> it..
>
> The baseline I expect is a merkle tree signed by the root of trust
> (TPM or platform TSM) that encompases everything down to the required
> bits of the SPDM negotiation to prove no MIM.
Agreed, that's my basic undestanding as well and likewise, can get
some appropriate folk to check it out once we have the full thing described.
Jonathan
>
> Jason
>
^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [RFC v3 00/27] lib: Rust implementation of SPDM
2026-03-09 18:00 ` Jonathan Cameron
@ 2026-03-09 20:40 ` Jason Gunthorpe
2026-03-09 23:11 ` DanX Williams
0 siblings, 1 reply; 99+ messages in thread
From: Jason Gunthorpe @ 2026-03-09 20:40 UTC (permalink / raw)
To: Jonathan Cameron
Cc: dan.j.williams, Lukas Wunner, Alistair Francis, bhelgaas,
rust-for-linux, akpm, linux-pci, linux-cxl, linux-kernel,
alex.gaynor, benno.lossin, boqun.feng, a.hindborg, gary,
bjorn3_gh, tmgross, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis, aneesh.kumar, yilun.xu, aik, Mathieu Poirier,
Thomas Fossati
On Mon, Mar 09, 2026 at 06:00:54PM +0000, Jonathan Cameron wrote:
> On Mon, 9 Mar 2026 12:59:09 -0300
> Jason Gunthorpe <jgg@nvidia.com> wrote:
>
> > On Mon, Mar 09, 2026 at 03:33:39PM +0000, Jonathan Cameron wrote:
> >
> > > I'm not sure exactly what our security model is in the native CMA case,
> > > so what software we can trust on the host. I.e. does the DH session actually
> > > need to be between the kernel and the peer?
> >
> > Yes, absolutely a DH session is required in all cases, it is the only
> > way to generate a PCI encryption shared secret and exclude a MIM.
>
> Ah. I was missing what you wanted with the DH part. For some reason wasn't
> thinking about IDE (maybe because this patch set doesn't get you there for
> native). Though as I understand it some of the native usecases for CMA aren't
> using link encryption (different security model from CoCo).
Yeah, there are models where you could collect evidence and not have
any IDE setup where you have greater trust in physical security.
> Yes, if you want to avoid MIM you need to bring up IDE etc - the basic fact
> that both ends can still talk to each other after enabling it given they have
> to have the same keys and those went over the secure channel, is part of the
> security around that.
No.. With DH systems something can sit in the middle and
encrypt/decrypt and you can't detect that unless you sign something
derived from the DH the other side can validate.
> Whether anyone actually implements root ports via standard DOE flows or
> everyone does this a custom way at the host is an open question.
I'm expecting Linux will be able to setup Link IDE, either through a
platform TSM as you say, or through someone plugging in the IDE
registers into some Linux drivers.. I certainly don't want to close
that door by bad uAPI design.
> The secure channel establishment and key exchange comes later in the SPDM
> flow than this patch set currently covers. This bit just gets you to the
> point where you know you are ultimately talking to right device - you don't
> know there isn't a MIM at this point.
Hmm, like I said I don't really know the flow, but something has to
bind the DH into the evidence for it to be useful, if that comes after
(seems backwards, but OK) then as long as the evidence reporting and
controlling uAPI is happy with all these different flows OK..
Jason
^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [RFC v3 00/27] lib: Rust implementation of SPDM
2026-03-09 20:40 ` Jason Gunthorpe
@ 2026-03-09 23:11 ` DanX Williams
0 siblings, 0 replies; 99+ messages in thread
From: DanX Williams @ 2026-03-09 23:11 UTC (permalink / raw)
To: Jason Gunthorpe, Jonathan Cameron
Cc: dan.j.williams, Lukas Wunner, Alistair Francis, bhelgaas,
rust-for-linux, akpm, linux-pci, linux-cxl, linux-kernel,
alex.gaynor, benno.lossin, boqun.feng, a.hindborg, gary,
bjorn3_gh, tmgross, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis, aneesh.kumar, yilun.xu, aik, Mathieu Poirier,
Thomas Fossati
Jason Gunthorpe wrote:
[..]
> > Whether anyone actually implements root ports via standard DOE flows or
> > everyone does this a custom way at the host is an open question.
>
> I'm expecting Linux will be able to setup Link IDE, either through a
> platform TSM as you say, or through someone plugging in the IDE
> registers into some Linux drivers.. I certainly don't want to close
> that door by bad uAPI design.
Right now there is no extra uAPI for IDE. It is an implicit detail of
the given TSM whether the "connect" operation additionally establishes
IDE. The result of whether or not "connect" established
selective-stream-IDE with the device is conveyed in the arrival of
"stream" links in sysfs, see:
Documentation/ABI/testing/sysfs-devices-pci-host-bridge
You also asked:
> Yeah, and I don't really know the details, just have some general idea
> how attestation and PCI link encryption should work in broad strokes.
>
> But I know people who do, so if we can get a series that clearly lays
> out the proposed kernel flow I can possibly get someone to compare
> it..
tl;dr: can you point them at http://lore.kernel.org/20260303000207.1836586-1-dan.j.williams@intel.com
A couple notes that the host kernel is unable to establish IDE without a
platform TSM on all but Intel platforms (that I know of). At a minimum,
this is why I think native SPDM should behave as a TSM driver. Platform
TSM involvement for IDE is the predominant architecture in the
ecosystem.
As for link encryption and attestation it is all rooted in the launch
attestation of the VM. Once you trust that the TSM that claims to be
present is valid then you trust all of that TSMs ABIs to enforce
confidentiality and integrity.
Now, a TSM is free to decide, "I do not need PCI link encryption because
I have apriori knowledge that $device has a connection to the system
that meets confidentiality + integrity expectations". So link encryption
is present for discrete devices, but maybe not integrated devices.
Assuming VM launch attesation gets you trust in the guest TSM driver
responses, then the attestation flow to the kernel is mostly just
marshaling blobs and digests:
1/ Host collects a fresh copy of device measurements with a guest
provided nonce (response emitted by PCI/TSM netlink, nonce received
via guest-to-host communication, see AF_VSOCK comment in 2/).
2/ Host marshals cert chain, measurements (signed transcript with nonce
from 1/), and interface report blob to guest via an untrusted channel. I
am currently thinking just use a common transport like AF_VSOCK to get
those blobs into the guest and not have each implementation reinvent
that blob transfer wheel.
3/ Guest needs to validate that blobs are indeed the ones the TSM
expects. Each TSM has a private message protocol to request digests of
the blob contents for this purpose.
At no point is the guest offered explicit PCI link encryption details,
nor the host for that matter. I think some TSMs might include the key
exchange steps in the SPDM transcript. However, that happens within an
SPDM secure session, so host can not otherwise observe it. SPDM does
support mutual authentication so the device could in theory challenge
whether it is talking to a device-approved TSM.
The open question I generated typing this up, is that if a common
transport is used to get the blobs into guest userspace, that userspace
still needs to push the "interface report" blob into the guest kernel.
Kernel needs that to determine how to map private vs shared MMIO. I
still think I prefer that to each implementation having a set of
implementation specific message passing ioctls() to do the same.
^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [RFC v3 00/27] lib: Rust implementation of SPDM
2026-02-21 18:46 ` Lukas Wunner
2026-02-21 23:29 ` dan.j.williams
@ 2026-02-24 14:16 ` Jason Gunthorpe
2026-02-24 15:54 ` Lukas Wunner
1 sibling, 1 reply; 99+ messages in thread
From: Jason Gunthorpe @ 2026-02-24 14:16 UTC (permalink / raw)
To: Lukas Wunner
Cc: dan.j.williams, Alistair Francis, bhelgaas, rust-for-linux, akpm,
linux-pci, Jonathan.Cameron, linux-cxl, linux-kernel, alex.gaynor,
benno.lossin, boqun.feng, a.hindborg, gary, bjorn3_gh, tmgross,
ojeda, wilfred.mallawa, aliceryhl, Alistair Francis, aneesh.kumar,
yilun.xu, aik, Mathieu Poirier, Thomas Fossati
On Sat, Feb 21, 2026 at 07:46:09PM +0100, Lukas Wunner wrote:
> On Fri, Feb 20, 2026 at 10:10:57AM -0400, Jason Gunthorpe wrote:
> > IOW the resume/RAS acceptance criteria is that the second nonce was
> > signed with the same private key(s) that the first nonce was signed
> > with.
>
> What you seem to have in mind is essentially a "trust on first use"
> model where trust is given to a specific device certificate
> (i.e. leaf certificate), not to a root certificate.
Not really, please read my email again.
I said userspace does the verification, using all the certificate
chains and beyond. Then once verified the kernel only does a 'same
device' check that ensures the device hasn't changed from what was
originally verified.
Spec supports this just fine.
> certificates. These could be vendors, but it's also possible that
> e.g. a CSP operates its own CA and provisions one of the 8 slots with
> a custom certificate chain anchored in its own CA.
And the userspace verifier is free to check all of this.
> An alternative solution would be to have the verifier in user space
> operate its own mini CA. The root certificate of that mini CA would be
> added to the .cma keyring.
No! Why are you trying to massively over complicate this? The proposal
is very simple :(
> > Linux will have its own sw model, the spec is just the protocol
> > definition. In the CC world everyone just knows the verifier needs to
> > be external.. How else could it even work?
>
> There are products out there which support CMA but not TDISP.
Sure, but that doesn't mean anything for verification.
Most models I've seen for using this stuff are "cloud connected"
things where the cloud is going to measure and attest the end device
before giving it anything sensitive.
That's remote verification, and what you absolutely don't want is some
way for the attacker to pass remote verification, then replace the
device and somehow pass a much weaker local only verification and
attack the security.
This is why I'm insistent the starting point for resmue is a very
strong same-device check that prevents attackers from replacing the
device with something that wouldn't pass remote verification.
If you don't do this and instead try to revalidate the certificate
chains the kernel can be tricked into accepting a different device on
resume and that will completely destroy the entire security model.
> In other words, the CC world isn't everything. The modest goal
> of this series is to allow authentication of devices in compliance
> with PCIe r7.0 sec 6.31 and the SPDM spec.
As Dan and I keep saying you should focus on enabling userspace
verifier as the very first modest step and then come with proposals to
add additional things like resume and perhaps a kernel-internal
verifier.
I don't see a role for a cma keyring outside a kernel-internel
verifier (and I'm skeptical this is desirable in the first place since
a userspace implementation would not be such a burden)
Jason
^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [RFC v3 00/27] lib: Rust implementation of SPDM
2026-02-24 14:16 ` Jason Gunthorpe
@ 2026-02-24 15:54 ` Lukas Wunner
2026-02-25 14:50 ` Jason Gunthorpe
0 siblings, 1 reply; 99+ messages in thread
From: Lukas Wunner @ 2026-02-24 15:54 UTC (permalink / raw)
To: Jason Gunthorpe
Cc: dan.j.williams, Alistair Francis, bhelgaas, rust-for-linux, akpm,
linux-pci, Jonathan.Cameron, linux-cxl, linux-kernel, alex.gaynor,
benno.lossin, boqun.feng, a.hindborg, gary, bjorn3_gh, tmgross,
ojeda, wilfred.mallawa, aliceryhl, Alistair Francis, aneesh.kumar,
yilun.xu, aik, Mathieu Poirier, Thomas Fossati
On Tue, Feb 24, 2026 at 10:16:10AM -0400, Jason Gunthorpe wrote:
> This is why I'm insistent the starting point for resmue is a very
> strong same-device check that prevents attackers from replacing the
> device with something that wouldn't pass remote verification.
>
> If you don't do this and instead try to revalidate the certificate
> chains the kernel can be tricked into accepting a different device on
> resume and that will completely destroy the entire security model.
Finding a different device on resume is par for the course for hotplug.
> As Dan and I keep saying you should focus on enabling userspace
> verifier as the very first modest step and then come with proposals to
> add additional things like resume and perhaps a kernel-internal
> verifier.
There is nothing to "add". Seamless re-verification on resume and
error recovery is already implemented in my patches. I don't see
the point of throwing that out the window and start from scratch
just because you think it doesn't have priority.
> I don't see a role for a cma keyring outside a kernel-internel
> verifier
That sounds like we have minimal consensus.
I'm coming from a very different direction, I want this to seamlessly
integrate with all the infrastructure we already have in the PCI core
(hotplug, suspend/resume, error recovery, ...), so I made sure it does.
I don't share the view that CMA is merely a building block for TDISP.
It's useful on its own.
I also believe that the vast majority of users will simply need this
to ensure the devices they attach to their chromebooks, phones etc
are authentic (seems important given the reports of counterfeit
hard drives). A .cma keyring is good enough for Grandma's chromebook,
no need for a user space verifier.
Thanks,
Lukas
^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [RFC v3 00/27] lib: Rust implementation of SPDM
2026-02-24 15:54 ` Lukas Wunner
@ 2026-02-25 14:50 ` Jason Gunthorpe
0 siblings, 0 replies; 99+ messages in thread
From: Jason Gunthorpe @ 2026-02-25 14:50 UTC (permalink / raw)
To: Lukas Wunner
Cc: dan.j.williams, Alistair Francis, bhelgaas, rust-for-linux, akpm,
linux-pci, Jonathan.Cameron, linux-cxl, linux-kernel, alex.gaynor,
benno.lossin, boqun.feng, a.hindborg, gary, bjorn3_gh, tmgross,
ojeda, wilfred.mallawa, aliceryhl, Alistair Francis, aneesh.kumar,
yilun.xu, aik, Mathieu Poirier, Thomas Fossati
On Tue, Feb 24, 2026 at 04:54:15PM +0100, Lukas Wunner wrote:
> On Tue, Feb 24, 2026 at 10:16:10AM -0400, Jason Gunthorpe wrote:
> > This is why I'm insistent the starting point for resmue is a very
> > strong same-device check that prevents attackers from replacing the
> > device with something that wouldn't pass remote verification.
> >
> > If you don't do this and instead try to revalidate the certificate
> > chains the kernel can be tricked into accepting a different device on
> > resume and that will completely destroy the entire security model.
>
> Finding a different device on resume is par for the course for hotplug.
And? The point is we must detect and reject this as a matter of
security.
> > As Dan and I keep saying you should focus on enabling userspace
> > verifier as the very first modest step and then come with proposals to
> > add additional things like resume and perhaps a kernel-internal
> > verifier.
>
> There is nothing to "add". Seamless re-verification on resume and
> error recovery is already implemented in my patches. I don't see
> the point of throwing that out the window and start from scratch
> just because you think it doesn't have priority.
The objection is it doesn't implement a sufficient security model, and
I'm strongly arguing you have built the wrong thing.
The suggested path is not about priority, but to follow the path of
consense where we all agree a userspace verifier is a required
component, so we should start with the parts we all agree on and
incrementally work on the dis-agreed parts.
Saying I already built it so I should get to merge it is not
constructive.
> I'm coming from a very different direction, I want this to seamlessly
> integrate with all the infrastructure we already have in the PCI core
> (hotplug, suspend/resume, error recovery, ...), so I made sure it does.
Well, this is security work. Linking a bunch of stuff together without
having a clear threat model and desired seurity properties is not the
right approach at all.
The kernel needs to enforce security properties and present a
consistent threat model, and what you have done is inconsisent and
weaker than the models we do want to build.
It also seems all your use cases fit within the broder models, so I
don't see why we should enshrine a bad security model in the kernel,
as UAPI at this point.
> I don't share the view that CMA is merely a building block for TDISP.
> It's useful on its own.
I agree it is useful on its own, but when used without TDISP we still
want to have the same basic threat model and overall security posture,
it is not an excusse to just weaken everything.
We are looking at building CMA based, non-TDISP systems that need all
the stuff I'm talking about.
> I also believe that the vast majority of users will simply need this
> to ensure the devices they attach to their chromebooks, phones etc
> are authentic (seems important given the reports of counterfeit
> hard drives). A .cma keyring is good enough for Grandma's chromebook,
> no need for a user space verifier.
A chromebook has no issue to run a userspace verifier, and overall the
kernel community prefers to put things to userspace whenever possible.
Given a userspace solution will have to exist I think you have a
fairly high bar to cross to propose duplicating it into the kernel,
which is why it should come after the userspace path is setup so it
can be properly explained and justified.
Jason
^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [RFC v3 00/27] lib: Rust implementation of SPDM
2026-02-19 14:15 ` Lukas Wunner
2026-02-19 14:31 ` Jason Gunthorpe
@ 2026-02-19 14:40 ` Greg KH
2026-02-20 7:46 ` Lukas Wunner
1 sibling, 1 reply; 99+ messages in thread
From: Greg KH @ 2026-02-19 14:40 UTC (permalink / raw)
To: Lukas Wunner
Cc: Jason Gunthorpe, dan.j.williams, Alistair Francis, bhelgaas,
rust-for-linux, akpm, linux-pci, Jonathan.Cameron, linux-cxl,
linux-kernel, alex.gaynor, benno.lossin, boqun.feng, a.hindborg,
gary, bjorn3_gh, tmgross, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis, aneesh.kumar, yilun.xu, aik
On Thu, Feb 19, 2026 at 03:15:34PM +0100, Lukas Wunner wrote:
> On Thu, Feb 19, 2026 at 08:41:19AM -0400, Jason Gunthorpe wrote:
> > > It has turned out to be super convenient to expose the 8 slots with
> > > certificate chains in sysfs for direct examination with openssl and
> > > similar tools, without having to go through netlink.
> >
> > Honestly, I'm reluctant to add permanent sysfs uAPI just for temporary
> > debugging. Put it in debugfs.
>
> Exposure of the certificates in the SPDM slots is not for debugging,
> it's just super convenient for day-to-day use.
>
> > Having to find/remember some baroque openssl command line with a
> > million options is not reasonable for a production kind of
> > environment.
>
> Personally I find something like the following neither baroque nor
> unreasonable:
>
> # What's the certificate chain in slot0?
> openssl storeutl -text /sys/bus/pci/devices/0000:03:00.0/certificates/slot0
>
> # Fingerprint of root cert in slot0, does it match what vendor claims?
> openssl x509 -fingerprint -in /sys/bus/pci/devices/0000:03:00.0/certificates/slot0
>
> # Looks good, let's trust it:
> keyctl padd asymmetric "" %:.cma < /sys/bus/pci/devices/0000:03:00.0/certificates/slot0
As much fun as it is to abuse sysfs, please, let's not do this there.
You just did something that could have changed the device between
storing, checking and then trusting it as the device is never guaranteed
to remain the same across multiple calls to sysfs (i.e. yanked out and
another added.)
So let's not design in a security issue from the start please :)
thanks,
greg k-h
^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [RFC v3 00/27] lib: Rust implementation of SPDM
2026-02-19 14:40 ` Greg KH
@ 2026-02-20 7:46 ` Lukas Wunner
2026-02-20 9:14 ` Greg KH
0 siblings, 1 reply; 99+ messages in thread
From: Lukas Wunner @ 2026-02-20 7:46 UTC (permalink / raw)
To: Greg KH
Cc: Jason Gunthorpe, dan.j.williams, Alistair Francis, bhelgaas,
rust-for-linux, akpm, linux-pci, Jonathan.Cameron, linux-cxl,
linux-kernel, alex.gaynor, benno.lossin, boqun.feng, a.hindborg,
gary, bjorn3_gh, tmgross, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis, aneesh.kumar, yilun.xu, aik
On Thu, Feb 19, 2026 at 03:40:25PM +0100, Greg KH wrote:
> On Thu, Feb 19, 2026 at 03:15:34PM +0100, Lukas Wunner wrote:
> > # What's the certificate chain in slot0?
> > openssl storeutl -text /sys/bus/pci/devices/0000:03:00.0/certificates/slot0
> >
> > # Fingerprint of root cert in slot0, does it match what vendor claims?
> > openssl x509 -fingerprint -in /sys/bus/pci/devices/0000:03:00.0/certificates/slot0
> >
> > # Looks good, let's trust it:
> > keyctl padd asymmetric "" %:.cma < /sys/bus/pci/devices/0000:03:00.0/certificates/slot0
>
> As much fun as it is to abuse sysfs, please, let's not do this there.
> You just did something that could have changed the device between
> storing, checking and then trusting it as the device is never guaranteed
> to remain the same across multiple calls to sysfs (i.e. yanked out and
> another added.)
No, the kernel caches the certificate chains read from the SPDM slots
and what is exposed in sysfs is that cached copy. So all three commands
above pertain to the same certificate chain.
> So let's not design in a security issue from the start please :)
The alleged security issue does not exist.
Thanks,
Lukas
^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [RFC v3 00/27] lib: Rust implementation of SPDM
2026-02-20 7:46 ` Lukas Wunner
@ 2026-02-20 9:14 ` Greg KH
2026-02-20 11:45 ` Lukas Wunner
0 siblings, 1 reply; 99+ messages in thread
From: Greg KH @ 2026-02-20 9:14 UTC (permalink / raw)
To: Lukas Wunner
Cc: Jason Gunthorpe, dan.j.williams, Alistair Francis, bhelgaas,
rust-for-linux, akpm, linux-pci, Jonathan.Cameron, linux-cxl,
linux-kernel, alex.gaynor, benno.lossin, boqun.feng, a.hindborg,
gary, bjorn3_gh, tmgross, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis, aneesh.kumar, yilun.xu, aik
On Fri, Feb 20, 2026 at 08:46:21AM +0100, Lukas Wunner wrote:
> On Thu, Feb 19, 2026 at 03:40:25PM +0100, Greg KH wrote:
> > On Thu, Feb 19, 2026 at 03:15:34PM +0100, Lukas Wunner wrote:
> > > # What's the certificate chain in slot0?
> > > openssl storeutl -text /sys/bus/pci/devices/0000:03:00.0/certificates/slot0
> > >
> > > # Fingerprint of root cert in slot0, does it match what vendor claims?
> > > openssl x509 -fingerprint -in /sys/bus/pci/devices/0000:03:00.0/certificates/slot0
> > >
> > > # Looks good, let's trust it:
> > > keyctl padd asymmetric "" %:.cma < /sys/bus/pci/devices/0000:03:00.0/certificates/slot0
> >
> > As much fun as it is to abuse sysfs, please, let's not do this there.
> > You just did something that could have changed the device between
> > storing, checking and then trusting it as the device is never guaranteed
> > to remain the same across multiple calls to sysfs (i.e. yanked out and
> > another added.)
>
> No, the kernel caches the certificate chains read from the SPDM slots
> and what is exposed in sysfs is that cached copy. So all three commands
> above pertain to the same certificate chain.
So if a device is removed and a different one added between steps 2 and
three above, with the same pci number, all is ok?
Remember, PCI device ids are not unique, they can come and go and be
reordered and reused at any point in time. They are fully dynamic and
should NEVER be used as a unique identifier except for a specific moment
in time.
In other words, you are expecting that device id to always refer to the
same device across all 3 operations, which is never guaranteed.
> > So let's not design in a security issue from the start please :)
>
> The alleged security issue does not exist.
How is this not the classic definition of a TOCTOU (time-of-check to
time-of-use) problem?
thanks,
greg k-h
^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [RFC v3 00/27] lib: Rust implementation of SPDM
2026-02-20 9:14 ` Greg KH
@ 2026-02-20 11:45 ` Lukas Wunner
2026-02-20 11:57 ` Greg KH
0 siblings, 1 reply; 99+ messages in thread
From: Lukas Wunner @ 2026-02-20 11:45 UTC (permalink / raw)
To: Greg KH
Cc: Jason Gunthorpe, dan.j.williams, Alistair Francis, bhelgaas,
rust-for-linux, akpm, linux-pci, Jonathan.Cameron, linux-cxl,
linux-kernel, alex.gaynor, benno.lossin, boqun.feng, a.hindborg,
gary, bjorn3_gh, tmgross, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis, aneesh.kumar, yilun.xu, aik
On Fri, Feb 20, 2026 at 10:14:56AM +0100, Greg KH wrote:
> On Fri, Feb 20, 2026 at 08:46:21AM +0100, Lukas Wunner wrote:
> > On Thu, Feb 19, 2026 at 03:40:25PM +0100, Greg KH wrote:
> > > On Thu, Feb 19, 2026 at 03:15:34PM +0100, Lukas Wunner wrote:
> > > > # What's the certificate chain in slot0?
> > > > openssl storeutl -text /sys/bus/pci/devices/0000:03:00.0/certificates/slot0
> > > >
> > > > # Fingerprint of root cert in slot0, does it match what vendor claims?
> > > > openssl x509 -fingerprint -in /sys/bus/pci/devices/0000:03:00.0/certificates/slot0
> > > >
> > > > # Looks good, let's trust it:
> > > > keyctl padd asymmetric "" %:.cma < /sys/bus/pci/devices/0000:03:00.0/certificates/slot0
> > >
> So if a device is removed and a different one added between steps 2 and
> three above, with the same pci number, all is ok?
>
> Remember, PCI device ids are not unique, they can come and go and be
> reordered and reused at any point in time. They are fully dynamic and
> should NEVER be used as a unique identifier except for a specific moment
> in time.
>
> In other words, you are expecting that device id to always refer to the
> same device across all 3 operations, which is never guaranteed.
If the user chooses to replace the device between steps 2 and 3,
they get to keep the pieces.
Thanks,
Lukas
^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [RFC v3 00/27] lib: Rust implementation of SPDM
2026-02-20 11:45 ` Lukas Wunner
@ 2026-02-20 11:57 ` Greg KH
0 siblings, 0 replies; 99+ messages in thread
From: Greg KH @ 2026-02-20 11:57 UTC (permalink / raw)
To: Lukas Wunner
Cc: Jason Gunthorpe, dan.j.williams, Alistair Francis, bhelgaas,
rust-for-linux, akpm, linux-pci, Jonathan.Cameron, linux-cxl,
linux-kernel, alex.gaynor, benno.lossin, boqun.feng, a.hindborg,
gary, bjorn3_gh, tmgross, ojeda, wilfred.mallawa, aliceryhl,
Alistair Francis, aneesh.kumar, yilun.xu, aik
On Fri, Feb 20, 2026 at 12:45:19PM +0100, Lukas Wunner wrote:
> On Fri, Feb 20, 2026 at 10:14:56AM +0100, Greg KH wrote:
> > On Fri, Feb 20, 2026 at 08:46:21AM +0100, Lukas Wunner wrote:
> > > On Thu, Feb 19, 2026 at 03:40:25PM +0100, Greg KH wrote:
> > > > On Thu, Feb 19, 2026 at 03:15:34PM +0100, Lukas Wunner wrote:
> > > > > # What's the certificate chain in slot0?
> > > > > openssl storeutl -text /sys/bus/pci/devices/0000:03:00.0/certificates/slot0
> > > > >
> > > > > # Fingerprint of root cert in slot0, does it match what vendor claims?
> > > > > openssl x509 -fingerprint -in /sys/bus/pci/devices/0000:03:00.0/certificates/slot0
> > > > >
> > > > > # Looks good, let's trust it:
> > > > > keyctl padd asymmetric "" %:.cma < /sys/bus/pci/devices/0000:03:00.0/certificates/slot0
> > > >
> > So if a device is removed and a different one added between steps 2 and
> > three above, with the same pci number, all is ok?
> >
> > Remember, PCI device ids are not unique, they can come and go and be
> > reordered and reused at any point in time. They are fully dynamic and
> > should NEVER be used as a unique identifier except for a specific moment
> > in time.
> >
> > In other words, you are expecting that device id to always refer to the
> > same device across all 3 operations, which is never guaranteed.
>
> If the user chooses to replace the device between steps 2 and 3,
> they get to keep the pieces.
So you are saying that is ok to do:
- test if we trust the fingerprint
- trust the device of a fingerprint we didn't check
Then why do step 1 here at all?
Perhaps sysfs just isn't the best api here :)
thanks,
greg k-h
^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [RFC v3 00/27] lib: Rust implementation of SPDM
2026-02-19 0:56 ` Jason Gunthorpe
2026-02-19 5:05 ` dan.j.williams
@ 2026-02-19 9:34 ` Lukas Wunner
2026-02-19 12:43 ` Jason Gunthorpe
2026-02-19 18:48 ` dan.j.williams
1 sibling, 2 replies; 99+ messages in thread
From: Lukas Wunner @ 2026-02-19 9:34 UTC (permalink / raw)
To: Jason Gunthorpe
Cc: dan.j.williams, Alistair Francis, bhelgaas, rust-for-linux, akpm,
linux-pci, Jonathan.Cameron, linux-cxl, linux-kernel, alex.gaynor,
benno.lossin, boqun.feng, a.hindborg, gary, bjorn3_gh, tmgross,
ojeda, wilfred.mallawa, aliceryhl, Alistair Francis, aneesh.kumar,
yilun.xu, aik
On Wed, Feb 18, 2026 at 08:56:14PM -0400, Jason Gunthorpe wrote:
> And not sure we should be dumping any certs in sysfs if the plan for
> the other stuff is netlink, it should be consistent I think.
It has turned out to be super convenient to expose the 8 slots with
certificate chains in sysfs for direct examination with openssl and
similar tools, without having to go through netlink.
Originally the plan was to make the certificates/slot[0..7] files
also writable and the kernel would implicitly perform a SET_CERTIFICATE
SPDM exchange with the device when writing to those files.
Unfortunately with SPDM 1.3 the spec editors made things more
complicated, *cough* sorry *more flexible* with additional CertModel
and KeyPairID attributes. That additional complexity makes it less
suitable for sysfs, hence for *provisioning* netlink is indeed the
better choice. But just for *reading* the certificates in the slots,
sysfs exposure is very useful.
Thanks,
Lukas
^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [RFC v3 00/27] lib: Rust implementation of SPDM
2026-02-19 9:34 ` Lukas Wunner
@ 2026-02-19 12:43 ` Jason Gunthorpe
2026-02-19 18:48 ` dan.j.williams
1 sibling, 0 replies; 99+ messages in thread
From: Jason Gunthorpe @ 2026-02-19 12:43 UTC (permalink / raw)
To: Lukas Wunner
Cc: dan.j.williams, Alistair Francis, bhelgaas, rust-for-linux, akpm,
linux-pci, Jonathan.Cameron, linux-cxl, linux-kernel, alex.gaynor,
benno.lossin, boqun.feng, a.hindborg, gary, bjorn3_gh, tmgross,
ojeda, wilfred.mallawa, aliceryhl, Alistair Francis, aneesh.kumar,
yilun.xu, aik
On Thu, Feb 19, 2026 at 10:34:55AM +0100, Lukas Wunner wrote:
> On Wed, Feb 18, 2026 at 08:56:14PM -0400, Jason Gunthorpe wrote:
> > And not sure we should be dumping any certs in sysfs if the plan for
> > the other stuff is netlink, it should be consistent I think.
>
> It has turned out to be super convenient to expose the 8 slots with
> certificate chains in sysfs for direct examination with openssl and
> similar tools, without having to go through netlink.
Honestly, I'm reluctant to add permanent sysfs uAPI just for temporary
debugging. Put it in debugfs.
There should be a tool suite that runs on top of this stuff and
presents a sensible user experiance, with man pages and so on.
Having to find/remember some baroque openssl command line with a
million options is not reasonable for a production kind of
environment.
Jason
^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [RFC v3 00/27] lib: Rust implementation of SPDM
2026-02-19 9:34 ` Lukas Wunner
2026-02-19 12:43 ` Jason Gunthorpe
@ 2026-02-19 18:48 ` dan.j.williams
1 sibling, 0 replies; 99+ messages in thread
From: dan.j.williams @ 2026-02-19 18:48 UTC (permalink / raw)
To: Lukas Wunner, Jason Gunthorpe
Cc: dan.j.williams, Alistair Francis, bhelgaas, rust-for-linux, akpm,
linux-pci, Jonathan.Cameron, linux-cxl, linux-kernel, alex.gaynor,
benno.lossin, boqun.feng, a.hindborg, gary, bjorn3_gh, tmgross,
ojeda, wilfred.mallawa, aliceryhl, Alistair Francis, aneesh.kumar,
yilun.xu, aik
Lukas Wunner wrote:
> On Wed, Feb 18, 2026 at 08:56:14PM -0400, Jason Gunthorpe wrote:
> > And not sure we should be dumping any certs in sysfs if the plan for
> > the other stuff is netlink, it should be consistent I think.
>
> It has turned out to be super convenient to expose the 8 slots with
> certificate chains in sysfs for direct examination with openssl and
> similar tools, without having to go through netlink.
>
> Originally the plan was to make the certificates/slot[0..7] files
> also writable and the kernel would implicitly perform a SET_CERTIFICATE
> SPDM exchange with the device when writing to those files.
> Unfortunately with SPDM 1.3 the spec editors made things more
> complicated, *cough* sorry *more flexible* with additional CertModel
> and KeyPairID attributes. That additional complexity makes it less
> suitable for sysfs, hence for *provisioning* netlink is indeed the
> better choice. But just for *reading* the certificates in the slots,
> sysfs exposure is very useful.
Useful, but not required, right? The consistency argument from Jason is
growing on me. Merely validating the certificate chain is not sufficient
to establish trust in the device configuration. So, if a $pci_auth_tool
is needed in the flow at all is there a practical difference between:
$pci_auth_tool cert --device $dev --slot $slot | openssl
...and:
cat /sys/bus/pci/devices/$dev/certificates/$slot | openssl
^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [RFC v3 00/27] lib: Rust implementation of SPDM
2026-02-18 23:40 ` dan.j.williams
2026-02-19 0:56 ` Jason Gunthorpe
@ 2026-02-19 9:13 ` Lukas Wunner
2026-02-19 18:42 ` dan.j.williams
1 sibling, 1 reply; 99+ messages in thread
From: Lukas Wunner @ 2026-02-19 9:13 UTC (permalink / raw)
To: dan.j.williams
Cc: Alistair Francis, Jason Gunthorpe, bhelgaas, rust-for-linux, akpm,
linux-pci, Jonathan.Cameron, linux-cxl, linux-kernel, alex.gaynor,
benno.lossin, boqun.feng, a.hindborg, gary, bjorn3_gh, tmgross,
ojeda, wilfred.mallawa, aliceryhl, Alistair Francis, aneesh.kumar,
yilun.xu, aik
On Wed, Feb 18, 2026 at 03:40:10PM -0800, dan.j.williams@intel.com wrote:
> However, I notice that Aneesh needs x509 certificate parsing for his TSM
> driver [1], I think TDX would benefit from the same to offload needing
> to specify the wall-clock time to the module [2] for cert verification,
> and SEV-TIO (already upstream) is currently missing any facility for the
> host to attest the device.
>
> [1]: http://lore.kernel.org/20250728135216.48084-17-aneesh.kumar@kernel.org
There's a newer version:
https://lore.kernel.org/all/20251027095602.1154418-1-aneesh.kumar@kernel.org/
This would allow upstreaming at least the three X.509 patches at the
beginning of my CMA series (and Alistair's rSPDM series) and thus
reduce the patch count a little bit.
However I don't know how far along Aneesh's CCA work is.
Note that David Howells' introduction of ML-DSA in v7.0 moves around
a lot of the X.509 code so the three X.509 patches for CMA will no longer
apply cleanly:
https://lore.kernel.org/all/2977832.1770384806@warthog.procyon.org.uk/
I'll rebase my development branch after v7.0-rc1 is out and Aneesh can
then pick up the latest version from it:
https://github.com/l1k/linux/commits/doe
Thanks,
Lukas
^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [RFC v3 00/27] lib: Rust implementation of SPDM
2026-02-19 9:13 ` Lukas Wunner
@ 2026-02-19 18:42 ` dan.j.williams
0 siblings, 0 replies; 99+ messages in thread
From: dan.j.williams @ 2026-02-19 18:42 UTC (permalink / raw)
To: Lukas Wunner, dan.j.williams
Cc: Alistair Francis, Jason Gunthorpe, bhelgaas, rust-for-linux, akpm,
linux-pci, Jonathan.Cameron, linux-cxl, linux-kernel, alex.gaynor,
benno.lossin, boqun.feng, a.hindborg, gary, bjorn3_gh, tmgross,
ojeda, wilfred.mallawa, aliceryhl, Alistair Francis, aneesh.kumar,
yilun.xu, aik
Lukas Wunner wrote:
> On Wed, Feb 18, 2026 at 03:40:10PM -0800, dan.j.williams@intel.com wrote:
> > However, I notice that Aneesh needs x509 certificate parsing for his TSM
> > driver [1], I think TDX would benefit from the same to offload needing
> > to specify the wall-clock time to the module [2] for cert verification,
> > and SEV-TIO (already upstream) is currently missing any facility for the
> > host to attest the device.
> >
> > [1]: http://lore.kernel.org/20250728135216.48084-17-aneesh.kumar@kernel.org
>
> There's a newer version:
>
> https://lore.kernel.org/all/20251027095602.1154418-1-aneesh.kumar@kernel.org/
As I understand this still has a dependency on a new base ARM CCA
posting. So the nearest term solution is to just wire up the existing
certificate blobs retrieved by the upstream SEV-TIO driver.
> This would allow upstreaming at least the three X.509 patches at the
> beginning of my CMA series (and Alistair's rSPDM series) and thus
> reduce the patch count a little bit.
ARM CCA only needs to parse the certificates to confirm to the TSM which
public-key to use. I am struggling to find another near term need for
the kernel to parse the certificate.
> However I don't know how far along Aneesh's CCA work is.
>
> Note that David Howells' introduction of ML-DSA in v7.0 moves around
> a lot of the X.509 code so the three X.509 patches for CMA will no longer
> apply cleanly:
>
> https://lore.kernel.org/all/2977832.1770384806@warthog.procyon.org.uk/
>
> I'll rebase my development branch after v7.0-rc1 is out and Aneesh can
> then pick up the latest version from it:
>
> https://github.com/l1k/linux/commits/doe
Sounds good, but I want to focus on the blob dump interface for the
existing upstream SPDM requester, SEV-TIO, and then go from there.
^ permalink raw reply [flat|nested] 99+ messages in thread
* Re: [RFC v3 00/27] lib: Rust implementation of SPDM
2026-02-17 23:56 ` Jason Gunthorpe
2026-02-18 2:17 ` Alistair Francis
@ 2026-02-19 11:24 ` Jonathan Cameron
1 sibling, 0 replies; 99+ messages in thread
From: Jonathan Cameron @ 2026-02-19 11:24 UTC (permalink / raw)
To: Jason Gunthorpe
Cc: alistair23, bhelgaas, lukas, rust-for-linux, akpm, linux-pci,
linux-cxl, linux-kernel, alex.gaynor, benno.lossin, boqun.feng,
a.hindborg, gary, bjorn3_gh, tmgross, ojeda, wilfred.mallawa,
aliceryhl, Alistair Francis
On Tue, 17 Feb 2026 19:56:04 -0400
Jason Gunthorpe <jgg@nvidia.com> wrote:
> On Wed, Feb 11, 2026 at 01:29:07PM +1000, alistair23@gmail.com wrote:
> > From: Alistair Francis <alistair.francis@wdc.com>
> >
> > 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!
>
> I was arguing for exactly this at a recent conference, so I'm glad to
> see it. It is a great meaningful usecase for rust in the kernel.
FWIW I'm fully on board with this as well. More than happy to see my C
code Lukas has been carrying go away in favor of the Rust :)
It'll also finally make me learn more than a trivial level of Rust.
Jonathan
^ permalink raw reply [flat|nested] 99+ messages in thread