Linux PCI subsystem development
 help / color / mirror / Atom feed
* [PATCH 00/18] lib: Rust implementation of SPDM
@ 2026-05-08  3:16 alistair23
  2026-05-08  3:16 ` [PATCH 01/18] rust: add untrusted data abstraction alistair23
                   ` (17 more replies)
  0 siblings, 18 replies; 35+ messages in thread
From: alistair23 @ 2026-05-08  3:16 UTC (permalink / raw)
  To: alistair, linux-kernel, lukas, Jonathan.Cameron, bhelgaas,
	rust-for-linux, akpm, linux-cxl, djbw, linux-pci
  Cc: alex.gaynor, wilfred.mallawa, gary, bjorn3_gh, benno.lossin,
	aliceryhl, boqun.feng, a.hindborg, tmgross, ojeda, alistair23,
	Alistair Francis

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!

This series implements a SPDM requester in Rust.

This is based on Lukas' C implementation [2], but has been refacted during the
first few RFCs. I have included some of the relevent patchesfrom Lukas' C
SPDM implementation in this series where they are required.

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

The goal of this series is to get the smallest possible SPDM implementation
upstream. That will provide building blocks for us to continue working on.

As such we don't yet provide evidence or certificates to userspace, allow
userspace to provide a nonce, support PQC or more advanced SPDM features.
This is enough to communicate with a device and return "authenticated" to
userspace.

Note that RFC v3 did provide evidence and certificates to userspace and
allowed a custom nonce. Showing that it's possible. I also have patches
that build apon [4] to do this via a TSM driver, again showing it's
possible with the current approach.
We just don't support it yet and for TSM I need [4] upstream first.

This series is different to Lukas' original approach and the approach taken
in the previous RFCs and instead adds the PCI-CMA support as a TSM driver.
This was described by Dan in [3] and [5]. The advantage here is that for PCIe
we can leverage the TSM work for a lot of the features and provide userspace
a consistient interface between PCI TSM and CMA.

This series also doesn't check the certificate chain against the kernel
keyring and will instead leave that to userspace once [4] is merged.

Other transport mode (such as ATA, SCSI, NVMe and MCTP) will
therefore need slightly different approaches, as TSM doesn't apply.
The library can support this though, it will just need some netlink
and sysfs wrappers added as applicable. This way each transport can support
SPDM in the way it sees fit.

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

I'm testing this by running the following

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

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

ls /sys/devices/pci0000:00/0000:00:03.0/
ls /sys/devices/pci0000:00/0000:00:03.0/tsm/
cat  /sys/devices/pci0000:00/0000:00:03.0/authenticated
echo tsm0 > /sys/devices/pci0000:00/0000:00:03.0/tsm/connect
cat  /sys/devices/pci0000:00/0000:00:03.0/authenticated
```

1: https://www.dmtf.org/standards/spdm
2: https://lore.kernel.org/all/cover.1719771133.git.lukas@wunner.de/
3: http://lore.kernel.org/69976d7d39c60_2f4a1009@dwillia2-mobl4.notmuch
4: https://lore.kernel.org/all/69976d7d39c60_2f4a1009@dwillia2-mobl4.notmuch/
5: https://lore.kernel.org/lkml/69e19c80b892b_fe0831000@djbw-dev.notmuch/

v1:
 - Add CMA as a TSM driver
 - Initial support for SPDM 1.4
 - Cleanup a range of comments and concerns from RFC
 - Remove kernel keyring checks
RFC v3:
 - Use netlink to send information to userspace
 - Don't autogenerate Rust helpers
RFC v2:
 - Drop support for Rust and C implementations
 - Include patches from Lukas to reduce series deps
 - Large code cleanups based on more testing
 - Support for authentication

Alistair Francis (13):
  rust: add bindings for hash.h
  rust: error: impl From<FromBytesWithNulError> for Kernel Error
  lib: rspdm: Initial commit of Rust SPDM
  PCI/TSM: Support connecting to PCIe CMA devices
  PCI/CMA: Add a PCI TSM CMA driver using SPDM
  lib: rspdm: Support SPDM get_version
  lib: rspdm: Support SPDM get_capabilities
  lib: rspdm: Support SPDM negotiate_algorithms
  lib: rspdm: Support SPDM get_digests
  lib: rspdm: Support SPDM get_certificate
  lib: rspdm: Support SPDM certificate validation
  rust: allow extracting the buffer from a CString
  lib: rspdm: Support SPDM challenge

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

Lukas Wunner (4):
  X.509: Make certificate parser public
  X.509: Parse Subject Alternative Name in certificates
  X.509: Move certificate length retrieval into new helper
  PCI/CMA: Validate Subject Alternative Name in certificates

 MAINTAINERS                               |   13 +
 crypto/asymmetric_keys/x509_cert_parser.c |    9 +
 crypto/asymmetric_keys/x509_loader.c      |   38 +-
 crypto/asymmetric_keys/x509_parser.h      |   42 +-
 drivers/pci/Kconfig                       |   14 +
 drivers/pci/Makefile                      |    4 +
 drivers/pci/cma.asn1                      |   41 +
 drivers/pci/cma.c                         |  262 +++++
 drivers/pci/doe.c                         |    3 -
 include/keys/asymmetric-type.h            |    2 +
 include/keys/x509-parser.h                |   57 ++
 include/linux/oid_registry.h              |    3 +
 include/linux/pci-doe.h                   |    4 +
 include/linux/pci-tsm.h                   |   11 +-
 include/linux/spdm.h                      |   39 +
 lib/Kconfig                               |   17 +
 lib/Makefile                              |    2 +
 lib/rspdm/Makefile                        |   10 +
 lib/rspdm/consts.rs                       |  155 +++
 lib/rspdm/lib.rs                          |  180 ++++
 lib/rspdm/state.rs                        | 1070 +++++++++++++++++++++
 lib/rspdm/validator.rs                    |  524 ++++++++++
 rust/bindings/bindings_helper.h           |    7 +
 rust/helpers/hash.c                       |   18 +
 rust/helpers/helpers.c                    |    1 +
 rust/kernel/error.rs                      |   10 +
 rust/kernel/lib.rs                        |    1 +
 rust/kernel/str.rs                        |   13 +-
 rust/kernel/validate.rs                   |  605 ++++++++++++
 29 files changed, 3091 insertions(+), 64 deletions(-)
 create mode 100644 drivers/pci/cma.asn1
 create mode 100644 drivers/pci/cma.c
 create mode 100644 include/keys/x509-parser.h
 create mode 100644 include/linux/spdm.h
 create mode 100644 lib/rspdm/Makefile
 create mode 100644 lib/rspdm/consts.rs
 create mode 100644 lib/rspdm/lib.rs
 create mode 100644 lib/rspdm/state.rs
 create mode 100644 lib/rspdm/validator.rs
 create mode 100644 rust/helpers/hash.c
 create mode 100644 rust/kernel/validate.rs

-- 
2.52.0


^ permalink raw reply	[flat|nested] 35+ messages in thread

* [PATCH 01/18] rust: add untrusted data abstraction
  2026-05-08  3:16 [PATCH 00/18] lib: Rust implementation of SPDM alistair23
@ 2026-05-08  3:16 ` alistair23
  2026-05-08  3:52   ` sashiko-bot
  2026-05-08  5:17   ` Dirk Behme
  2026-05-08  3:16 ` [PATCH 02/18] X.509: Make certificate parser public alistair23
                   ` (16 subsequent siblings)
  17 siblings, 2 replies; 35+ messages in thread
From: alistair23 @ 2026-05-08  3:16 UTC (permalink / raw)
  To: alistair, linux-kernel, lukas, Jonathan.Cameron, bhelgaas,
	rust-for-linux, akpm, linux-cxl, djbw, linux-pci
  Cc: alex.gaynor, wilfred.mallawa, gary, bjorn3_gh, benno.lossin,
	aliceryhl, boqun.feng, a.hindborg, tmgross, ojeda, alistair23,
	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 b72b2fbe046d..fe580fde609f 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -133,6 +133,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] 35+ messages in thread

* [PATCH 02/18] X.509: Make certificate parser public
  2026-05-08  3:16 [PATCH 00/18] lib: Rust implementation of SPDM alistair23
  2026-05-08  3:16 ` [PATCH 01/18] rust: add untrusted data abstraction alistair23
@ 2026-05-08  3:16 ` alistair23
  2026-05-08  3:45   ` sashiko-bot
  2026-05-08  3:16 ` [PATCH 03/18] X.509: Parse Subject Alternative Name in certificates alistair23
                   ` (15 subsequent siblings)
  17 siblings, 1 reply; 35+ messages in thread
From: alistair23 @ 2026-05-08  3:16 UTC (permalink / raw)
  To: alistair, linux-kernel, lukas, Jonathan.Cameron, bhelgaas,
	rust-for-linux, akpm, linux-cxl, djbw, linux-pci
  Cc: alex.gaynor, wilfred.mallawa, gary, bjorn3_gh, benno.lossin,
	aliceryhl, boqun.feng, a.hindborg, tmgross, ojeda, alistair23,
	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] 35+ messages in thread

* [PATCH 03/18] X.509: Parse Subject Alternative Name in certificates
  2026-05-08  3:16 [PATCH 00/18] lib: Rust implementation of SPDM alistair23
  2026-05-08  3:16 ` [PATCH 01/18] rust: add untrusted data abstraction alistair23
  2026-05-08  3:16 ` [PATCH 02/18] X.509: Make certificate parser public alistair23
@ 2026-05-08  3:16 ` alistair23
  2026-05-08  3:16 ` [PATCH 04/18] X.509: Move certificate length retrieval into new helper alistair23
                   ` (14 subsequent siblings)
  17 siblings, 0 replies; 35+ messages in thread
From: alistair23 @ 2026-05-08  3:16 UTC (permalink / raw)
  To: alistair, linux-kernel, lukas, Jonathan.Cameron, bhelgaas,
	rust-for-linux, akpm, linux-cxl, djbw, linux-pci
  Cc: alex.gaynor, wilfred.mallawa, gary, bjorn3_gh, benno.lossin,
	aliceryhl, boqun.feng, a.hindborg, tmgross, ojeda, alistair23,
	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 bfd10f0195e0..c3ec2846695a 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] 35+ messages in thread

* [PATCH 04/18] X.509: Move certificate length retrieval into new helper
  2026-05-08  3:16 [PATCH 00/18] lib: Rust implementation of SPDM alistair23
                   ` (2 preceding siblings ...)
  2026-05-08  3:16 ` [PATCH 03/18] X.509: Parse Subject Alternative Name in certificates alistair23
@ 2026-05-08  3:16 ` alistair23
  2026-05-08  3:39   ` sashiko-bot
  2026-05-08  3:16 ` [PATCH 05/18] rust: add bindings for hash.h alistair23
                   ` (13 subsequent siblings)
  17 siblings, 1 reply; 35+ messages in thread
From: alistair23 @ 2026-05-08  3:16 UTC (permalink / raw)
  To: alistair, linux-kernel, lukas, Jonathan.Cameron, bhelgaas,
	rust-for-linux, akpm, linux-cxl, djbw, linux-pci
  Cc: alex.gaynor, wilfred.mallawa, gary, bjorn3_gh, benno.lossin,
	aliceryhl, boqun.feng, a.hindborg, tmgross, ojeda, alistair23,
	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] 35+ messages in thread

* [PATCH 05/18] rust: add bindings for hash.h
  2026-05-08  3:16 [PATCH 00/18] lib: Rust implementation of SPDM alistair23
                   ` (3 preceding siblings ...)
  2026-05-08  3:16 ` [PATCH 04/18] X.509: Move certificate length retrieval into new helper alistair23
@ 2026-05-08  3:16 ` alistair23
  2026-05-08  3:43   ` sashiko-bot
  2026-05-08  3:16 ` [PATCH 06/18] rust: error: impl From<FromBytesWithNulError> for Kernel Error alistair23
                   ` (12 subsequent siblings)
  17 siblings, 1 reply; 35+ messages in thread
From: alistair23 @ 2026-05-08  3:16 UTC (permalink / raw)
  To: alistair, linux-kernel, lukas, Jonathan.Cameron, bhelgaas,
	rust-for-linux, akpm, linux-cxl, djbw, linux-pci
  Cc: alex.gaynor, wilfred.mallawa, gary, bjorn3_gh, benno.lossin,
	aliceryhl, boqun.feng, a.hindborg, tmgross, ojeda, alistair23,
	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 446dbeaf0866..d73142078240 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -30,6 +30,7 @@
 
 #include <linux/acpi.h>
 #include <linux/gpu_buddy.h>
+#include <crypto/hash.h>
 #include <drm/drm_device.h>
 #include <drm/drm_drv.h>
 #include <drm/drm_file.h>
@@ -60,6 +61,7 @@
 #include <linux/file.h>
 #include <linux/firmware.h>
 #include <linux/fs.h>
+#include <linux/hash.h>
 #include <linux/i2c.h>
 #include <linux/interrupt.h>
 #include <linux/io-pgtable.h>
diff --git a/rust/helpers/hash.c b/rust/helpers/hash.c
new file mode 100644
index 000000000000..23a63618a370
--- /dev/null
+++ b/rust/helpers/hash.c
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <crypto/hash.h>
+
+__rust_helper unsigned int rust_helper_crypto_shash_descsize(struct crypto_shash *tfm)
+{
+	return crypto_shash_descsize(tfm);
+}
+
+__rust_helper unsigned int rust_helper_crypto_shash_digestsize(struct crypto_shash *tfm)
+{
+	return crypto_shash_digestsize(tfm);
+}
+
+__rust_helper void rust_helper_crypto_free_shash(struct crypto_shash *tfm)
+{
+	crypto_free_shash(tfm);
+}
diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
index 625921e27dfb..6372e14f8419 100644
--- a/rust/helpers/helpers.c
+++ b/rust/helpers/helpers.c
@@ -60,6 +60,7 @@
 #include "dma-resv.c"
 #include "drm.c"
 #include "err.c"
+#include "hash.c"
 #include "irq.c"
 #include "fs.c"
 #include "gpu.c"
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 06/18] rust: error: impl From<FromBytesWithNulError> for Kernel Error
  2026-05-08  3:16 [PATCH 00/18] lib: Rust implementation of SPDM alistair23
                   ` (4 preceding siblings ...)
  2026-05-08  3:16 ` [PATCH 05/18] rust: add bindings for hash.h alistair23
@ 2026-05-08  3:16 ` alistair23
  2026-05-08  3:51   ` sashiko-bot
  2026-05-08  3:16 ` [PATCH 07/18] lib: rspdm: Initial commit of Rust SPDM alistair23
                   ` (11 subsequent siblings)
  17 siblings, 1 reply; 35+ messages in thread
From: alistair23 @ 2026-05-08  3:16 UTC (permalink / raw)
  To: alistair, linux-kernel, lukas, Jonathan.Cameron, bhelgaas,
	rust-for-linux, akpm, linux-cxl, djbw, linux-pci
  Cc: alex.gaynor, wilfred.mallawa, gary, bjorn3_gh, benno.lossin,
	aliceryhl, boqun.feng, a.hindborg, tmgross, ojeda, alistair23,
	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 | 8 ++++++++
 rust/kernel/str.rs   | 7 -------
 2 files changed, 8 insertions(+), 7 deletions(-)

diff --git a/rust/kernel/error.rs b/rust/kernel/error.rs
index 05cf869ac090..5463addbfd95 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;
@@ -258,6 +259,13 @@ fn from(e: core::convert::Infallible) -> Error {
     }
 }
 
+impl From<FromBytesWithNulError> for Error {
+    #[inline]
+    fn from(_: FromBytesWithNulError) -> Error {
+        code::EINVAL
+    }
+}
+
 /// A [`Result`] with an [`Error`] error type.
 ///
 /// To be used as the return type for functions that may fail.
diff --git a/rust/kernel/str.rs b/rust/kernel/str.rs
index 8311d91549e1..17070dde0e35 100644
--- a/rust/kernel/str.rs
+++ b/rust/kernel/str.rs
@@ -419,13 +419,6 @@ macro_rules! c_str {
 mod tests {
     use super::*;
 
-    impl From<core::ffi::FromBytesWithNulError> for Error {
-        #[inline]
-        fn from(_: core::ffi::FromBytesWithNulError) -> Error {
-            EINVAL
-        }
-    }
-
     macro_rules! format {
         ($($f:tt)*) => ({
             CString::try_from_fmt(fmt!($($f)*))?.to_str()?
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 07/18] lib: rspdm: Initial commit of Rust SPDM
  2026-05-08  3:16 [PATCH 00/18] lib: Rust implementation of SPDM alistair23
                   ` (5 preceding siblings ...)
  2026-05-08  3:16 ` [PATCH 06/18] rust: error: impl From<FromBytesWithNulError> for Kernel Error alistair23
@ 2026-05-08  3:16 ` alistair23
  2026-05-08  3:41   ` sashiko-bot
  2026-05-08  3:17 ` [PATCH 08/18] PCI/TSM: Support connecting to PCIe CMA devices alistair23
                   ` (10 subsequent siblings)
  17 siblings, 1 reply; 35+ messages in thread
From: alistair23 @ 2026-05-08  3:16 UTC (permalink / raw)
  To: alistair, linux-kernel, lukas, Jonathan.Cameron, bhelgaas,
	rust-for-linux, akpm, linux-cxl, djbw, linux-pci
  Cc: alex.gaynor, wilfred.mallawa, gary, bjorn3_gh, benno.lossin,
	aliceryhl, boqun.feng, a.hindborg, tmgross, ojeda, alistair23

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             |  56 ++++++++
 lib/rspdm/lib.rs                | 116 ++++++++++++++++
 lib/rspdm/state.rs              | 235 ++++++++++++++++++++++++++++++++
 lib/rspdm/validator.rs          |  73 ++++++++++
 rust/bindings/bindings_helper.h |   2 +
 rust/kernel/error.rs            |   2 +
 11 files changed, 564 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 882214b0e7db..2e8ad57fec5d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -24164,6 +24164,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..e23e386ed97a
--- /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,
+			       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 00a9509636c1..5a248709132e 100644
--- a/lib/Kconfig
+++ b/lib/Kconfig
@@ -592,6 +592,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 f33a24bf1c19..ef2d33224d89 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -282,6 +282,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..2feddde67885
--- /dev/null
+++ b/lib/rspdm/consts.rs
@@ -0,0 +1,56 @@
+// 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>
+
+/* SPDM versions supported by this implementation */
+pub(crate) const SPDM_VER_10: u8 = 0x10;
+
+pub(crate) const SPDM_MIN_VER: u8 = SPDM_VER_10;
+
+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,
+    /// This was removed in version 1.2.0 and is now reserved
+    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,
+    RequestSessionTerminated = 0x46,
+    InvalidState = 0x47,
+    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 {
+        writeln!(f, "{:#x}", *self as u8)?;
+
+        Ok(())
+    }
+}
diff --git a/lib/rspdm/lib.rs b/lib/rspdm/lib.rs
new file mode 100644
index 000000000000..758d43fba5cb
--- /dev/null
+++ b/lib/rspdm/lib.rs
@@ -0,0 +1,116 @@
+// 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.
+
+use crate::bindings::spdm_state;
+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.
+#[export]
+pub unsafe extern "C" fn spdm_create(
+    dev: *mut bindings::device,
+    transport: bindings::spdm_transport,
+    transport_priv: *mut c_void,
+    transport_sz: u32,
+    validate: bindings::spdm_validate,
+) -> *mut spdm_state {
+    match KBox::new(
+        SpdmState::new(dev, transport, transport_priv, transport_sz, validate),
+        flags::GFP_KERNEL,
+    ) {
+        Ok(ret) => KBox::into_raw(ret) as *mut spdm_state,
+        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.
+#[export]
+pub unsafe extern "C" fn spdm_authenticate(_state_ptr: *mut spdm_state) -> c_int {
+    0
+}
+
+/// spdm_destroy() - Destroy SPDM session
+///
+/// @spdm_state: SPDM session state
+#[export]
+pub unsafe extern "C" fn spdm_destroy(_state_ptr: *mut spdm_state) {}
diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
new file mode 100644
index 000000000000..18e81f24c724
--- /dev/null
+++ b/lib/rspdm/state.rs
@@ -0,0 +1,235 @@
+// 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_MIN_VER,
+    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.
+/// `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) 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,
+        validate: bindings::spdm_validate,
+    ) -> Self {
+        SpdmState {
+            dev,
+            transport,
+            transport_priv,
+            transport_sz,
+            validate,
+            version: SPDM_MIN_VER,
+        }
+    }
+
+    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)
+            }
+            SpdmErrorCode::RequestSessionTerminated => {
+                pr_err!("Request session terminated\n");
+                Err(EINVAL)
+            }
+            SpdmErrorCode::InvalidState => {
+                pr_err!("Invalid State\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..58039f532b7d
--- /dev/null
+++ b/lib/rspdm/validator.rs
@@ -0,0 +1,73 @@
+// 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,
+    /// This will always be SPDM_ERROR (0x7F)
+    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 d73142078240..0231e4f87f20 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -87,10 +87,12 @@
 #include <linux/sched.h>
 #include <linux/security.h>
 #include <linux/slab.h>
+#include <linux/spdm.h>
 #include <linux/sys_soc.h>
 #include <linux/task_work.h>
 #include <linux/tracepoint.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 5463addbfd95..386110a1e19f 100644
--- a/rust/kernel/error.rs
+++ b/rust/kernel/error.rs
@@ -89,6 +89,8 @@ 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!(EINPROGRESS, "Operation now in progress.");
 }
 
 /// Generic integer kernel error.
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 08/18] PCI/TSM: Support connecting to PCIe CMA devices
  2026-05-08  3:16 [PATCH 00/18] lib: Rust implementation of SPDM alistair23
                   ` (6 preceding siblings ...)
  2026-05-08  3:16 ` [PATCH 07/18] lib: rspdm: Initial commit of Rust SPDM alistair23
@ 2026-05-08  3:17 ` alistair23
  2026-05-08  3:17 ` [PATCH 09/18] PCI/CMA: Add a PCI TSM CMA driver using SPDM alistair23
                   ` (9 subsequent siblings)
  17 siblings, 0 replies; 35+ messages in thread
From: alistair23 @ 2026-05-08  3:17 UTC (permalink / raw)
  To: alistair, linux-kernel, lukas, Jonathan.Cameron, bhelgaas,
	rust-for-linux, akpm, linux-cxl, djbw, linux-pci
  Cc: alex.gaynor, wilfred.mallawa, gary, bjorn3_gh, benno.lossin,
	aliceryhl, boqun.feng, a.hindborg, tmgross, ojeda, alistair23,
	Alistair Francis

From: Alistair Francis <alistair.francis@wdc.com>

In the next patch we are going to add a PCIe CMA TSM driver, as such we
need to ensure that is_pci_tsm_pf0() will allow us to connect to CMA
capable devices. These devices don't necessarily has DEVCAP_TEE or IDE
support.

As such for Root Complex Integrated Endpoint (PCI_EXP_TYPE_RC_END) we
also check for the CMA DOE feature.

Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
---
 include/linux/pci-tsm.h | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/include/linux/pci-tsm.h b/include/linux/pci-tsm.h
index a6435aba03f9..5059954e4853 100644
--- a/include/linux/pci-tsm.h
+++ b/include/linux/pci-tsm.h
@@ -3,6 +3,7 @@
 #define __PCI_TSM_H
 #include <linux/mutex.h>
 #include <linux/pci.h>
+#include <linux/pci-doe.h>
 #include <linux/sockptr.h>
 
 struct pci_tsm;
@@ -129,6 +130,8 @@ struct pci_tsm_pf0 {
 /* physical function0 and capable of 'connect' */
 static inline bool is_pci_tsm_pf0(struct pci_dev *pdev)
 {
+	struct pci_doe_mb *doe;
+
 	if (!pdev)
 		return false;
 
@@ -146,9 +149,15 @@ static inline bool is_pci_tsm_pf0(struct pci_dev *pdev)
 	 * switch.
 	 */
 	switch (pci_pcie_type(pdev)) {
+	case PCI_EXP_TYPE_RC_END:
+		doe = pci_find_doe_mailbox(pdev, PCI_VENDOR_ID_PCI_SIG,
+					   PCI_DOE_FEATURE_CMA);
+
+		if (doe)
+			break;
+		fallthrough;
 	case PCI_EXP_TYPE_ENDPOINT:
 	case PCI_EXP_TYPE_UPSTREAM:
-	case PCI_EXP_TYPE_RC_END:
 		if (pdev->ide_cap || (pdev->devcap & PCI_EXP_DEVCAP_TEE))
 			break;
 		fallthrough;
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 09/18] PCI/CMA: Add a PCI TSM CMA driver using SPDM
  2026-05-08  3:16 [PATCH 00/18] lib: Rust implementation of SPDM alistair23
                   ` (7 preceding siblings ...)
  2026-05-08  3:17 ` [PATCH 08/18] PCI/TSM: Support connecting to PCIe CMA devices alistair23
@ 2026-05-08  3:17 ` alistair23
  2026-05-08  5:02   ` sashiko-bot
  2026-05-08  3:17 ` [PATCH 10/18] PCI/CMA: Validate Subject Alternative Name in certificates alistair23
                   ` (8 subsequent siblings)
  17 siblings, 1 reply; 35+ messages in thread
From: alistair23 @ 2026-05-08  3:17 UTC (permalink / raw)
  To: alistair, linux-kernel, lukas, Jonathan.Cameron, bhelgaas,
	rust-for-linux, akpm, linux-cxl, djbw, linux-pci
  Cc: alex.gaynor, wilfred.mallawa, gary, bjorn3_gh, benno.lossin,
	aliceryhl, boqun.feng, a.hindborg, tmgross, ojeda, alistair23,
	Alistair Francis

From: Alistair Francis <alistair.francis@wdc.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, add support for authentication via a CMA TSM driver.

This was previously discusd here:
http://lore.kernel.org/69976d7d39c60_2f4a1009@dwillia2-mobl4.notmuch

By utilising a TSM driver we get a lot of the TSM driver probe policies
"for free". Currently there is no mechanism to provide evidence to
userspace, as the TSM system doesn't support that at the moment. That
can be added later when support by TSM.

Credits: Jonathan wrote the original proof-of-concept for a CMA implementation.
Lukas reworked that for upstream. Wilfred contributed fixes for issues
discovered during testing. Alistair reworked it as a TSM driver.

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>
Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
---
 MAINTAINERS             |   1 +
 drivers/pci/Kconfig     |  14 ++++
 drivers/pci/Makefile    |   2 +
 drivers/pci/cma.c       | 141 ++++++++++++++++++++++++++++++++++++++++
 drivers/pci/doe.c       |   3 -
 include/linux/pci-doe.h |   4 ++
 6 files changed, 162 insertions(+), 3 deletions(-)
 create mode 100644 drivers/pci/cma.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 2e8ad57fec5d..c99fc6ae2d03 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -24173,6 +24173,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 33c88432b728..dcf4170381f2 100644
--- a/drivers/pci/Kconfig
+++ b/drivers/pci/Kconfig
@@ -124,6 +124,20 @@ 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 PCI_TSM
+	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_TSM
 	bool "PCI TSM: Device security protocol support"
 	select PCI_IDE
diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile
index 41ebc3b9a518..16abfd0e17e1 100644
--- a/drivers/pci/Makefile
+++ b/drivers/pci/Makefile
@@ -41,6 +41,8 @@ obj-$(CONFIG_PCI_NPEM)		+= npem.o
 obj-$(CONFIG_PCIE_TPH)		+= tph.o
 obj-$(CONFIG_CARDBUS)		+= setup-cardbus.o
 
+obj-$(CONFIG_PCI_CMA)		+= cma.o
+
 # Endpoint library must be initialized before its users
 obj-$(CONFIG_PCI_ENDPOINT)	+= endpoint/
 
diff --git a/drivers/pci/cma.c b/drivers/pci/cma.c
new file mode 100644
index 000000000000..998fde6366fb
--- /dev/null
+++ b/drivers/pci/cma.c
@@ -0,0 +1,141 @@
+// 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
+ * Copyright (C) 2026 Western Digital
+ * 	Alistair Francis <alistair.francis@wdc.com>
+ */
+
+#define dev_fmt(fmt) "CMA: " fmt
+
+#include <linux/pci.h>
+#include <linux/pci-doe.h>
+#include <linux/pci-tsm.h>
+#include <linux/slab.h>
+#include <linux/spdm.h>
+#include <linux/tsm.h>
+
+#include "pci.h"
+
+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;
+
+	rc = pci_doe(doe, PCI_VENDOR_ID_PCI_SIG, PCI_DOE_FEATURE_CMA,
+		     request, request_sz, response, response_sz);
+
+	return rc;
+}
+
+/**
+ * struct pci_cma_tsm - CMA SPDM TSM driver context
+ * @pf0: base pci_tsm_pf0 context (must be first)
+ * @spdm: SPDM session for this device
+ */
+struct pci_cma_tsm {
+	struct pci_tsm_pf0 pf0;
+	struct spdm_state *spdm;
+};
+
+static struct pci_cma_tsm *cma_tsm_from_tsm(struct pci_tsm *tsm)
+{
+	struct pci_tsm_pf0 *pf0 = container_of(tsm, struct pci_tsm_pf0, base_tsm);
+
+	return container_of(pf0, struct pci_cma_tsm, pf0);
+}
+
+/**
+ * struct pci_cma_devsec - CMA SPDM devsec TSM context
+ * @spdm: SPDM session for this device
+ */
+struct pci_cma_devsec {
+	struct spdm_state *spdm;
+};
+
+static struct pci_tsm *pci_cma_tsm_probe(struct tsm_dev *tsm_dev,
+				      struct pci_dev *pdev)
+{
+	struct pci_doe_mb *doe;
+	struct pci_cma_tsm *cma;
+
+	doe = pci_find_doe_mailbox(pdev, PCI_VENDOR_ID_PCI_SIG,
+				   PCI_DOE_FEATURE_CMA);
+	if (!doe)
+		return NULL;
+
+	cma = kzalloc(sizeof(*cma), GFP_KERNEL);
+	if (!cma)
+		return NULL;
+
+	mutex_init(&cma->pf0.lock);
+	cma->pf0.doe_mb = doe;
+	cma->pf0.base_tsm.pdev = pdev;
+	cma->pf0.base_tsm.dsm_dev = pdev;
+	cma->pf0.base_tsm.tsm_dev = tsm_dev;
+
+	cma->spdm = spdm_create(&pdev->dev, pci_doe_transport, doe,
+				PCI_DOE_MAX_PAYLOAD, NULL);
+	if (!cma->spdm) {
+		mutex_destroy(&cma->pf0.lock);
+		kfree(cma);
+		return NULL;
+	}
+
+	return &cma->pf0.base_tsm;
+}
+
+static void pci_cma_tsm_remove(struct pci_tsm *tsm)
+{
+	struct pci_cma_tsm *cma = cma_tsm_from_tsm(tsm);
+
+	spdm_destroy(cma->spdm);
+	mutex_destroy(&cma->pf0.lock);
+	kfree(cma);
+}
+
+static int pci_cma_tsm_connect(struct pci_dev *pdev)
+{
+	struct pci_cma_tsm *cma = cma_tsm_from_tsm(pdev->tsm);
+	int rc;
+
+	rc = spdm_authenticate(cma->spdm);
+	if (rc)
+		return rc;
+
+	return 0;
+}
+
+static void pci_cma_tsm_disconnect(struct pci_dev *pdev)
+{
+	/* SPDM state is freed in pci_cma_tsm_remove() */
+}
+
+static const struct pci_tsm_ops pci_cma_tsm_ops = {
+	.link_ops = {
+		.probe		= pci_cma_tsm_probe,
+		.remove		= pci_cma_tsm_remove,
+		.connect	= pci_cma_tsm_connect,
+		.disconnect	= pci_cma_tsm_disconnect,
+	},
+};
+
+static struct tsm_dev *pci_cma_tsm_dev;
+
+static int __init pci_cma_tsm_init(void)
+{
+	struct tsm_dev *tsm_dev;
+
+	tsm_dev = tsm_register(NULL, (struct pci_tsm_ops *)&pci_cma_tsm_ops);
+	if (IS_ERR(tsm_dev))
+		return PTR_ERR(tsm_dev);
+
+	pci_cma_tsm_dev = tsm_dev;
+	return 0;
+}
+late_initcall(pci_cma_tsm_init);
diff --git a/drivers/pci/doe.c b/drivers/pci/doe.c
index 7b41da4ec11a..f236942660a3 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/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);
 
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 10/18] PCI/CMA: Validate Subject Alternative Name in certificates
  2026-05-08  3:16 [PATCH 00/18] lib: Rust implementation of SPDM alistair23
                   ` (8 preceding siblings ...)
  2026-05-08  3:17 ` [PATCH 09/18] PCI/CMA: Add a PCI TSM CMA driver using SPDM alistair23
@ 2026-05-08  3:17 ` alistair23
  2026-05-08  3:58   ` sashiko-bot
  2026-05-08  3:17 ` [PATCH 11/18] lib: rspdm: Support SPDM get_version alistair23
                   ` (7 subsequent siblings)
  17 siblings, 1 reply; 35+ messages in thread
From: alistair23 @ 2026-05-08  3:17 UTC (permalink / raw)
  To: alistair, linux-kernel, lukas, Jonathan.Cameron, bhelgaas,
	rust-for-linux, akpm, linux-cxl, djbw, linux-pci
  Cc: alex.gaynor, wilfred.mallawa, gary, bjorn3_gh, benno.lossin,
	aliceryhl, boqun.feng, a.hindborg, tmgross, ojeda, alistair23

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 16abfd0e17e1..15512512fce7 100644
--- a/drivers/pci/Makefile
+++ b/drivers/pci/Makefile
@@ -41,7 +41,9 @@ obj-$(CONFIG_PCI_NPEM)		+= npem.o
 obj-$(CONFIG_PCIE_TPH)		+= tph.o
 obj-$(CONFIG_CARDBUS)		+= setup-cardbus.o
 
-obj-$(CONFIG_PCI_CMA)		+= cma.o
+obj-$(CONFIG_PCI_CMA)		+= 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 998fde6366fb..ee186f361940 100644
--- a/drivers/pci/cma.c
+++ b/drivers/pci/cma.c
@@ -11,6 +11,9 @@
 
 #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/pci-tsm.h>
@@ -18,8 +21,126 @@
 #include <linux/spdm.h>
 #include <linux/tsm.h>
 
+#include "cma.asn1.h"
 #include "pci.h"
 
+/*
+ * 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;
+}
+
 static ssize_t pci_doe_transport(void *priv, struct device *dev,
 				 const void *request, size_t request_sz,
 				 void *response, size_t response_sz)
@@ -80,7 +201,7 @@ static struct pci_tsm *pci_cma_tsm_probe(struct tsm_dev *tsm_dev,
 	cma->pf0.base_tsm.tsm_dev = tsm_dev;
 
 	cma->spdm = spdm_create(&pdev->dev, pci_doe_transport, doe,
-				PCI_DOE_MAX_PAYLOAD, NULL);
+				PCI_DOE_MAX_PAYLOAD, pci_cma_validate);
 	if (!cma->spdm) {
 		mutex_destroy(&cma->pf0.lock);
 		kfree(cma);
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] 35+ messages in thread

* [PATCH 11/18] lib: rspdm: Support SPDM get_version
  2026-05-08  3:16 [PATCH 00/18] lib: Rust implementation of SPDM alistair23
                   ` (9 preceding siblings ...)
  2026-05-08  3:17 ` [PATCH 10/18] PCI/CMA: Validate Subject Alternative Name in certificates alistair23
@ 2026-05-08  3:17 ` alistair23
  2026-05-08  3:50   ` sashiko-bot
  2026-05-08  3:17 ` [PATCH 12/18] lib: rspdm: Support SPDM get_capabilities alistair23
                   ` (6 subsequent siblings)
  17 siblings, 1 reply; 35+ messages in thread
From: alistair23 @ 2026-05-08  3:17 UTC (permalink / raw)
  To: alistair, linux-kernel, lukas, Jonathan.Cameron, bhelgaas,
	rust-for-linux, akpm, linux-cxl, djbw, linux-pci
  Cc: alex.gaynor, wilfred.mallawa, gary, bjorn3_gh, benno.lossin,
	aliceryhl, boqun.feng, a.hindborg, tmgross, ojeda, alistair23

From: Alistair Francis <alistair@alistair23.me>

Support the GET_VERSION SPDM command.

Signed-off-by: Alistair Francis <alistair@alistair23.me>
---
 lib/rspdm/consts.rs    | 14 +++++++++
 lib/rspdm/lib.rs       |  8 ++++-
 lib/rspdm/state.rs     | 61 ++++++++++++++++++++++++++++++++++++
 lib/rspdm/validator.rs | 70 ++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 152 insertions(+), 1 deletion(-)

diff --git a/lib/rspdm/consts.rs b/lib/rspdm/consts.rs
index 2feddde67885..5482a0f6cee0 100644
--- a/lib/rspdm/consts.rs
+++ b/lib/rspdm/consts.rs
@@ -7,10 +7,21 @@
 //! 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;
+#[allow(dead_code)]
+pub(crate) const SPDM_VER_13: u8 = 0x13;
+pub(crate) const SPDM_VER_14: u8 = 0x14;
 
 pub(crate) const SPDM_MIN_VER: u8 = SPDM_VER_10;
+pub(crate) const SPDM_MAX_VER: u8 = SPDM_VER_14;
 
 pub(crate) const SPDM_REQ: u8 = 0x80;
 pub(crate) const SPDM_ERROR: u8 = 0x7f;
@@ -54,3 +65,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>() + u8::MAX as usize;
diff --git a/lib/rspdm/lib.rs b/lib/rspdm/lib.rs
index 758d43fba5cb..1cc6c33516fb 100644
--- a/lib/rspdm/lib.rs
+++ b/lib/rspdm/lib.rs
@@ -105,7 +105,13 @@
 /// Return 0 on success or a negative errno.  In particular, -EPROTONOSUPPORT
 /// indicates authentication is not supported by the device.
 #[export]
-pub unsafe extern "C" fn spdm_authenticate(_state_ptr: *mut spdm_state) -> c_int {
+pub unsafe extern "C" fn spdm_authenticate(state_ptr: *mut spdm_state) -> c_int {
+    let state: &mut SpdmState = unsafe { &mut (*(state_ptr as *mut SpdmState)) };
+
+    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 18e81f24c724..26b90942b298 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,
@@ -22,10 +23,14 @@
 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, //
 };
@@ -232,4 +237,60 @@ pub(crate) fn spdm_exchange(
 
         Ok(length)
     }
+
+    /// Negotiate a supported SPDM version and store the information
+    /// in the `SpdmState`.
+    pub(crate) fn get_version(&mut self) -> Result<(), Error> {
+        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: `response_vec` is SPDM_GET_VERSION_LEN length, initialised, aligned
+        // and won't be mutated
+        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
+        // than 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;
+        let unaligned = core::ptr::addr_of_mut!(response.version_number_entries) as *mut u16;
+
+        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 addr = unaligned.wrapping_add(i as usize);
+            let alpha_version = (unsafe { core::ptr::read_unaligned::<u16>(addr) } & 0xF) as u8;
+            let version = (unsafe { core::ptr::read_unaligned::<u16>(addr) } >> 8) as u8;
+
+            if alpha_version > 0 {
+                pr_warn!("Alpha version {alpha_version} is unsupported\n");
+            }
+
+            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 58039f532b7d..8f45bafd4d69 100644
--- a/lib/rspdm/validator.rs
+++ b/lib/rspdm/validator.rs
@@ -7,6 +7,10 @@
 //! 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::*;
@@ -21,6 +25,11 @@
     },
 };
 
+use crate::consts::{
+    SPDM_GET_VERSION,
+    SPDM_MIN_VER, //
+};
+
 #[repr(C, packed)]
 pub(crate) struct SpdmHeader {
     pub(crate) version: u8,
@@ -71,3 +80,64 @@ 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 = *(raw.get(0).ok_or(ENOMEM))? as usize;
+        if version != SPDM_MIN_VER.into() {
+            return Err(EINVAL);
+        }
+
+        let version_number_entries = *(raw.get(5).ok_or(ENOMEM))? as usize;
+        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 };
+
+        Ok(rsp)
+    }
+}
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 12/18] lib: rspdm: Support SPDM get_capabilities
  2026-05-08  3:16 [PATCH 00/18] lib: Rust implementation of SPDM alistair23
                   ` (10 preceding siblings ...)
  2026-05-08  3:17 ` [PATCH 11/18] lib: rspdm: Support SPDM get_version alistair23
@ 2026-05-08  3:17 ` alistair23
  2026-05-08  4:05   ` sashiko-bot
  2026-05-08  3:17 ` [PATCH 13/18] lib: rspdm: Support SPDM negotiate_algorithms alistair23
                   ` (5 subsequent siblings)
  17 siblings, 1 reply; 35+ messages in thread
From: alistair23 @ 2026-05-08  3:17 UTC (permalink / raw)
  To: alistair, linux-kernel, lukas, Jonathan.Cameron, bhelgaas,
	rust-for-linux, akpm, linux-cxl, djbw, linux-pci
  Cc: alex.gaynor, wilfred.mallawa, gary, bjorn3_gh, benno.lossin,
	aliceryhl, boqun.feng, a.hindborg, tmgross, ojeda, alistair23

From: Alistair Francis <alistair@alistair23.me>

Support the GET_CAPABILITIES SPDM command.

Signed-off-by: Alistair Francis <alistair@alistair23.me>
---
 lib/rspdm/consts.rs    | 19 ++++++++-
 lib/rspdm/lib.rs       |  4 ++
 lib/rspdm/state.rs     | 76 ++++++++++++++++++++++++++++++++-
 lib/rspdm/validator.rs | 97 +++++++++++++++++++++++++++++++++++++++++-
 4 files changed, 192 insertions(+), 4 deletions(-)

diff --git a/lib/rspdm/consts.rs b/lib/rspdm/consts.rs
index 5482a0f6cee0..ef7d2d1d8e6e 100644
--- a/lib/rspdm/consts.rs
+++ b/lib/rspdm/consts.rs
@@ -9,12 +9,11 @@
 
 use crate::validator::SpdmHeader;
 use core::mem;
+use kernel::bits::bit_u32;
 
 /* SPDM versions supported by this implementation */
 pub(crate) const SPDM_VER_10: u8 = 0x10;
-#[allow(dead_code)]
 pub(crate) const SPDM_VER_11: u8 = 0x11;
-#[allow(dead_code)]
 pub(crate) const SPDM_VER_12: u8 = 0x12;
 #[allow(dead_code)]
 pub(crate) const SPDM_VER_13: u8 = 0x13;
@@ -68,3 +67,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>() + u8::MAX as usize;
+
+pub(crate) const SPDM_GET_CAPABILITIES: u8 = 0xe1;
+pub(crate) const SPDM_MIN_DATA_TRANSFER_SIZE: u32 = 42;
+
+// SPDM cryptographic timeout of this implementation:
+// Assume calculations may take up to 1 sec on a busy machine, which equals
+// roughly 1 << 20.  That's within the limits mandated for responders by CMA
+// (1 << 23 usec, PCIe r6.2 sec 6.31.3) and DOE (1 sec, PCIe r6.2 sec 6.30.2).
+// Used in GET_CAPABILITIES exchange.
+pub(crate) const SPDM_CTEXPONENT: u8 = 20;
+
+pub(crate) const SPDM_CERT_CAP: u32 = bit_u32(1);
+pub(crate) const SPDM_CHAL_CAP: u32 = bit_u32(2);
+
+pub(crate) const SPDM_REQ_CAPS: u32 = SPDM_CERT_CAP | SPDM_CHAL_CAP;
+pub(crate) const SPDM_RSP_MIN_CAPS: u32 = SPDM_CERT_CAP | SPDM_CHAL_CAP;
diff --git a/lib/rspdm/lib.rs b/lib/rspdm/lib.rs
index 1cc6c33516fb..9628f258854c 100644
--- a/lib/rspdm/lib.rs
+++ b/lib/rspdm/lib.rs
@@ -112,6 +112,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 26b90942b298..e7119ffa9a69 100644
--- a/lib/rspdm/state.rs
+++ b/lib/rspdm/state.rs
@@ -25,10 +25,17 @@
     SPDM_ERROR,
     SPDM_GET_VERSION_LEN,
     SPDM_MAX_VER,
+    SPDM_MIN_DATA_TRANSFER_SIZE,
     SPDM_MIN_VER,
-    SPDM_REQ, //
+    SPDM_REQ,
+    SPDM_RSP_MIN_CAPS,
+    SPDM_VER_10,
+    SPDM_VER_11,
+    SPDM_VER_12, //
 };
 use crate::validator::{
+    GetCapabilitiesReq,
+    GetCapabilitiesRsp,
     GetVersionReq,
     GetVersionRsp,
     SpdmErrorRsp,
@@ -47,6 +54,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,
@@ -57,6 +66,7 @@ pub struct SpdmState {
 
     // Negotiated state
     pub(crate) version: u8,
+    pub(crate) rsp_caps: u32,
 }
 
 impl SpdmState {
@@ -74,6 +84,7 @@ pub(crate) fn new(
             transport_sz,
             validate,
             version: SPDM_MIN_VER,
+            rsp_caps: 0,
         }
     }
 
@@ -293,4 +304,67 @@ 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: `response_vec` is rsp_sz length, initialised, aligned
+        // and won't be mutated
+        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
+        // or the same as 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 {
+            pr_err!(
+                "{:#x} capabilities are supported, which don't meet required {:#x}",
+                self.rsp_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 8f45bafd4d69..7dc55882c880 100644
--- a/lib/rspdm/validator.rs
+++ b/lib/rspdm/validator.rs
@@ -26,8 +26,12 @@
 };
 
 use crate::consts::{
+    SPDM_CTEXPONENT,
+    SPDM_GET_CAPABILITIES,
     SPDM_GET_VERSION,
-    SPDM_MIN_VER, //
+    SPDM_MIN_VER,
+    SPDM_REQ_CAPS, //
+    SPDM_VER_11,
 };
 
 #[repr(C, packed)]
@@ -141,3 +145,94 @@ 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: [u8; 2],
+
+    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; 2],
+            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,
+    param2: u8,
+
+    reserved1: u8,
+    pub(crate) ctexponent: u8,
+    reserved2: [u8; 2],
+
+    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>() {
+            let version = *(raw.get(0).ok_or(ENOMEM))?;
+            let version_1_1_len = mem::size_of::<GetCapabilitiesRsp>()
+                - mem::size_of::<__IncompleteArrayField<__le16>>()
+                - mem::size_of::<u32>()
+                - mem::size_of::<u32>();
+
+            if version == SPDM_VER_11 && raw.len() == version_1_1_len {
+                // Version 1.1 of the spec doesn't include all of the fields
+                // So let's extend the KVec with 0s so we can cast the
+                // vector to GetCapabilitiesRsp
+
+                for _i in version_1_1_len..mem::size_of::<GetCapabilitiesRsp>() {
+                    raw.push(0, GFP_KERNEL)?;
+                }
+            } else {
+                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] 35+ messages in thread

* [PATCH 13/18] lib: rspdm: Support SPDM negotiate_algorithms
  2026-05-08  3:16 [PATCH 00/18] lib: Rust implementation of SPDM alistair23
                   ` (11 preceding siblings ...)
  2026-05-08  3:17 ` [PATCH 12/18] lib: rspdm: Support SPDM get_capabilities alistair23
@ 2026-05-08  3:17 ` alistair23
  2026-05-08  4:05   ` sashiko-bot
  2026-05-08  3:17 ` [PATCH 14/18] lib: rspdm: Support SPDM get_digests alistair23
                   ` (4 subsequent siblings)
  17 siblings, 1 reply; 35+ messages in thread
From: alistair23 @ 2026-05-08  3:17 UTC (permalink / raw)
  To: alistair, linux-kernel, lukas, Jonathan.Cameron, bhelgaas,
	rust-for-linux, akpm, linux-cxl, djbw, linux-pci
  Cc: alex.gaynor, wilfred.mallawa, gary, bjorn3_gh, benno.lossin,
	aliceryhl, boqun.feng, a.hindborg, tmgross, ojeda, alistair23

From: Alistair Francis <alistair@alistair23.me>

Support the NEGOTIATE_ALGORITHMS SPDM command.

Signed-off-by: Alistair Francis <alistair@alistair23.me>
---
 lib/rspdm/consts.rs    |  65 ++++++++++++-
 lib/rspdm/lib.rs       |  18 +++-
 lib/rspdm/state.rs     | 211 +++++++++++++++++++++++++++++++++++++++++
 lib/rspdm/validator.rs | 115 +++++++++++++++++++++-
 4 files changed, 404 insertions(+), 5 deletions(-)

diff --git a/lib/rspdm/consts.rs b/lib/rspdm/consts.rs
index ef7d2d1d8e6e..e4652b18eb2a 100644
--- a/lib/rspdm/consts.rs
+++ b/lib/rspdm/consts.rs
@@ -9,7 +9,11 @@
 
 use crate::validator::SpdmHeader;
 use core::mem;
-use kernel::bits::bit_u32;
+use kernel::bits::{
+    bit_u32,
+    bit_u8,
+    genmask_u32, //
+};
 
 /* SPDM versions supported by this implementation */
 pub(crate) const SPDM_VER_10: u8 = 0x10;
@@ -73,13 +77,68 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
 
 // 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).
+// roughly bit_u32(20.  That's within the limits mandated for responders by CMA
+// (bit_u32(23 usec, PCIe r6.2 sec 6.31.3) and DOE (1 sec, PCIe r6.2 sec 6.30.2).
 // Used in GET_CAPABILITIES exchange.
 pub(crate) const SPDM_CTEXPONENT: u8 = 20;
 
 pub(crate) const SPDM_CERT_CAP: u32 = bit_u32(1);
 pub(crate) const SPDM_CHAL_CAP: u32 = bit_u32(2);
+pub(crate) const SPDM_MEAS_CAP_MASK: u32 = genmask_u32(3..=4);
+pub(crate) const SPDM_KEY_EX_CAP: u32 = bit_u32(9);
 
 pub(crate) const SPDM_REQ_CAPS: u32 = SPDM_CERT_CAP | SPDM_CHAL_CAP;
 pub(crate) const SPDM_RSP_MIN_CAPS: u32 = SPDM_CERT_CAP | SPDM_CHAL_CAP;
+
+pub(crate) const SPDM_NEGOTIATE_ALGS: u8 = 0xe3;
+
+pub(crate) const SPDM_MEAS_SPEC_DMTF: u8 = bit_u8(0);
+
+pub(crate) const SPDM_ASYM_RSASSA_2048: u32 = bit_u32(0);
+pub(crate) const _SPDM_ASYM_RSAPSS_2048: u32 = bit_u32(1);
+pub(crate) const SPDM_ASYM_RSASSA_3072: u32 = bit_u32(2);
+pub(crate) const _SPDM_ASYM_RSAPSS_3072: u32 = bit_u32(3);
+pub(crate) const SPDM_ASYM_ECDSA_ECC_NIST_P256: u32 = bit_u32(4);
+pub(crate) const SPDM_ASYM_RSASSA_4096: u32 = bit_u32(5);
+pub(crate) const _SPDM_ASYM_RSAPSS_4096: u32 = bit_u32(6);
+pub(crate) const SPDM_ASYM_ECDSA_ECC_NIST_P384: u32 = bit_u32(7);
+pub(crate) const SPDM_ASYM_ECDSA_ECC_NIST_P521: u32 = bit_u32(8);
+pub(crate) const _SPDM_ASYM_SM2_ECC_SM2_P256: u32 = bit_u32(9);
+pub(crate) const _SPDM_ASYM_EDDSA_ED25519: u32 = bit_u32(10);
+pub(crate) const _SPDM_ASYM_EDDSA_ED448: u32 = bit_u32(11);
+
+pub(crate) const SPDM_HASH_SHA_256: u32 = bit_u32(0);
+pub(crate) const SPDM_HASH_SHA_384: u32 = bit_u32(1);
+pub(crate) const SPDM_HASH_SHA_512: u32 = bit_u32(2);
+
+// If the crypto support isn't enabled don't offer the algorithms
+// to the responder
+#[cfg(CONFIG_CRYPTO_RSA)]
+pub(crate) const SPDM_ASYM_RSA: u32 =
+    SPDM_ASYM_RSASSA_2048 | SPDM_ASYM_RSASSA_3072 | SPDM_ASYM_RSASSA_4096;
+#[cfg(not(CONFIG_CRYPTO_RSA))]
+pub(crate) const SPDM_ASYM_RSA: u32 = 0;
+
+#[cfg(CONFIG_CRYPTO_ECDSA)]
+pub(crate) const SPDM_ASYM_ECDSA: u32 =
+    SPDM_ASYM_ECDSA_ECC_NIST_P256 | SPDM_ASYM_ECDSA_ECC_NIST_P384 | SPDM_ASYM_ECDSA_ECC_NIST_P521;
+#[cfg(not(CONFIG_CRYPTO_ECDSA))]
+pub(crate) const SPDM_ASYM_ECDSA: u32 = 0;
+
+#[cfg(CONFIG_CRYPTO_SHA256)]
+pub(crate) const SPDM_HASH_SHA2_256: u32 = SPDM_HASH_SHA_256;
+#[cfg(not(CONFIG_CRYPTO_SHA256))]
+pub(crate) const SPDM_HASH_SHA2_256: u32 = 0;
+
+#[cfg(CONFIG_CRYPTO_SHA512)]
+pub(crate) const SPDM_HASH_SHA2_384_512: u32 = SPDM_HASH_SHA_384 | SPDM_HASH_SHA_512;
+#[cfg(not(CONFIG_CRYPTO_SHA512))]
+pub(crate) const SPDM_HASH_SHA2_384_512: u32 = 0;
+
+pub(crate) const SPDM_ASYM_ALGOS: u32 = SPDM_ASYM_RSA | SPDM_ASYM_ECDSA;
+pub(crate) const SPDM_HASH_ALGOS: u32 = SPDM_HASH_SHA2_256 | SPDM_HASH_SHA2_384_512;
+
+/* 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 = bit_u8(1);
diff --git a/lib/rspdm/lib.rs b/lib/rspdm/lib.rs
index 9628f258854c..72886a5dfd69 100644
--- a/lib/rspdm/lib.rs
+++ b/lib/rspdm/lib.rs
@@ -116,6 +116,10 @@
         return e.to_errno() as c_int;
     }
 
+    if let Err(e) = state.negotiate_algs() {
+        return e.to_errno() as c_int;
+    }
+
     0
 }
 
@@ -123,4 +127,16 @@
 ///
 /// @spdm_state: SPDM session state
 #[export]
-pub unsafe extern "C" fn spdm_destroy(_state_ptr: *mut spdm_state) {}
+pub unsafe extern "C" fn spdm_destroy(state_ptr: *mut spdm_state) {
+    let state: &mut SpdmState = unsafe { &mut (*(state_ptr as *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 e7119ffa9a69..34676744e509 100644
--- a/lib/rspdm/state.rs
+++ b/lib/rspdm/state.rs
@@ -14,19 +14,36 @@
     bindings,
     error::{
         code::EINVAL,
+        from_err_ptr,
         to_result,
         Error, //
     },
+    str::CStr,
     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_REQ,
     SPDM_RSP_MIN_CAPS,
     SPDM_VER_10,
@@ -38,6 +55,8 @@
     GetCapabilitiesRsp,
     GetVersionReq,
     GetVersionRsp,
+    NegotiateAlgsReq,
+    NegotiateAlgsRsp,
     SpdmErrorRsp,
     SpdmHeader, //
 };
@@ -56,6 +75,25 @@
 ///  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().
+/// @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,
@@ -67,6 +105,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 {
@@ -85,6 +136,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,
         }
     }
 
@@ -367,4 +427,155 @@ 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;
+        }
+
+        let req_sz = core::mem::size_of::<NegotiateAlgsReq>();
+        let rsp_sz = core::mem::size_of::<NegotiateAlgsRsp>();
+
+        request.length = req_sz 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)?;
+        // SAFETY: `response_vec` is rsp_sz length, initialised, aligned
+        // and won't be mutated
+        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 7dc55882c880..9d738133399d 100644
--- a/lib/rspdm/validator.rs
+++ b/lib/rspdm/validator.rs
@@ -9,7 +9,8 @@
 
 use crate::bindings::{
     __IncompleteArrayField,
-    __le16, //
+    __le16,
+    __le32, //
 };
 use crate::consts::SpdmErrorCode;
 use core::mem;
@@ -26,10 +27,14 @@
 };
 
 use crate::consts::{
+    SPDM_ASYM_ALGOS,
     SPDM_CTEXPONENT,
     SPDM_GET_CAPABILITIES,
     SPDM_GET_VERSION,
+    SPDM_HASH_ALGOS,
+    SPDM_MEAS_SPEC_DMTF,
     SPDM_MIN_VER,
+    SPDM_NEGOTIATE_ALGS,
     SPDM_REQ_CAPS, //
     SPDM_VER_11,
 };
@@ -236,3 +241,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, // size of resp_alg_struct
+    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) resp_alg_struct: __IncompleteArrayField<RegAlg>,
+}
+
+impl Default for NegotiateAlgsReq {
+    fn default() -> Self {
+        NegotiateAlgsReq {
+            version: 0,
+            code: SPDM_NEGOTIATE_ALGS,
+            param1: 0, // Size of resp_alg_struct
+            param2: 0,
+            length: 32,
+            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(),
+            resp_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) resp_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] 35+ messages in thread

* [PATCH 14/18] lib: rspdm: Support SPDM get_digests
  2026-05-08  3:16 [PATCH 00/18] lib: Rust implementation of SPDM alistair23
                   ` (12 preceding siblings ...)
  2026-05-08  3:17 ` [PATCH 13/18] lib: rspdm: Support SPDM negotiate_algorithms alistair23
@ 2026-05-08  3:17 ` alistair23
  2026-05-08  4:06   ` sashiko-bot
  2026-05-08  3:17 ` [PATCH 15/18] lib: rspdm: Support SPDM get_certificate alistair23
                   ` (3 subsequent siblings)
  17 siblings, 1 reply; 35+ messages in thread
From: alistair23 @ 2026-05-08  3:17 UTC (permalink / raw)
  To: alistair, linux-kernel, lukas, Jonathan.Cameron, bhelgaas,
	rust-for-linux, akpm, linux-cxl, djbw, linux-pci
  Cc: alex.gaynor, wilfred.mallawa, gary, bjorn3_gh, benno.lossin,
	aliceryhl, boqun.feng, a.hindborg, tmgross, ojeda, alistair23

From: Alistair Francis <alistair@alistair23.me>

Support the GET_DIGESTS SPDM command.

Signed-off-by: Alistair Francis <alistair@alistair23.me>
---
 lib/rspdm/consts.rs    |  7 +++-
 lib/rspdm/lib.rs       |  4 ++
 lib/rspdm/state.rs     | 85 +++++++++++++++++++++++++++++++++++++++++-
 lib/rspdm/validator.rs | 54 +++++++++++++++++++++++++++
 4 files changed, 147 insertions(+), 3 deletions(-)

diff --git a/lib/rspdm/consts.rs b/lib/rspdm/consts.rs
index e4652b18eb2a..092205dab74d 100644
--- a/lib/rspdm/consts.rs
+++ b/lib/rspdm/consts.rs
@@ -19,10 +19,11 @@
 pub(crate) const SPDM_VER_10: u8 = 0x10;
 pub(crate) const SPDM_VER_11: u8 = 0x11;
 pub(crate) const SPDM_VER_12: u8 = 0x12;
-#[allow(dead_code)]
 pub(crate) const SPDM_VER_13: u8 = 0x13;
 pub(crate) const SPDM_VER_14: u8 = 0x14;
 
+pub(crate) const SPDM_SLOTS: usize = 8;
+
 pub(crate) const SPDM_MIN_VER: u8 = SPDM_VER_10;
 pub(crate) const SPDM_MAX_VER: u8 = SPDM_VER_14;
 
@@ -73,7 +74,7 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
 pub(crate) const SPDM_GET_VERSION_LEN: usize = mem::size_of::<SpdmHeader>() + u8::MAX as usize;
 
 pub(crate) const SPDM_GET_CAPABILITIES: u8 = 0xe1;
-pub(crate) const SPDM_MIN_DATA_TRANSFER_SIZE: u32 = 42;
+pub(crate) const SPDM_MIN_DATA_TRANSFER_SIZE: u32 = 42; // SPDM 1.2.0 margin no 226
 
 // SPDM cryptographic timeout of this implementation:
 // Assume calculations may take up to 1 sec on a busy machine, which equals
@@ -111,6 +112,8 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
 pub(crate) const SPDM_HASH_SHA_384: u32 = bit_u32(1);
 pub(crate) const SPDM_HASH_SHA_512: u32 = bit_u32(2);
 
+pub(crate) const SPDM_GET_DIGESTS: u8 = 0x81;
+
 // If the crypto support isn't enabled don't offer the algorithms
 // to the responder
 #[cfg(CONFIG_CRYPTO_RSA)]
diff --git a/lib/rspdm/lib.rs b/lib/rspdm/lib.rs
index 72886a5dfd69..e42cfdd35524 100644
--- a/lib/rspdm/lib.rs
+++ b/lib/rspdm/lib.rs
@@ -120,6 +120,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 34676744e509..bcb1cc955c4c 100644
--- a/lib/rspdm/state.rs
+++ b/lib/rspdm/state.rs
@@ -46,13 +46,17 @@
     SPDM_OPAQUE_DATA_FMT_GENERAL,
     SPDM_REQ,
     SPDM_RSP_MIN_CAPS,
+    SPDM_SLOTS,
     SPDM_VER_10,
     SPDM_VER_11,
-    SPDM_VER_12, //
+    SPDM_VER_12,
+    SPDM_VER_13, //
 };
 use crate::validator::{
     GetCapabilitiesReq,
     GetCapabilitiesRsp,
+    GetDigestsReq,
+    GetDigestsRsp,
     GetVersionReq,
     GetVersionRsp,
     NegotiateAlgsReq,
@@ -83,6 +87,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).
@@ -94,6 +102,8 @@
 /// @desc: Synchronous hash context for @base_hash_alg computation.
 /// @hash_len: Hash length of @base_hash_alg (in bytes).
 ///  H in SPDM specification.
+/// @certs: Certificate chain in each of the 8 slots. Empty KVec if a slot is
+///  not populated. Prefixed by the 4 + H header per SPDM 1.0.0 table 15.
 #[expect(dead_code)]
 pub struct SpdmState {
     pub(crate) dev: *mut bindings::device,
@@ -108,6 +118,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,
@@ -118,6 +130,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 {
@@ -139,12 +154,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],
         }
     }
 
@@ -578,4 +596,69 @@ 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: `response_vec` is rsp_sz length, initialised, aligned
+        // and won't be mutated
+        let response_buf = unsafe { from_raw_parts_mut(response_vec.as_mut_ptr(), rsp_sz) };
+
+        let len = self.spdm_exchange(request_buf, response_buf)?;
+
+        if len < (core::mem::size_of::<GetDigestsRsp>() as i32) {
+            pr_err!("Truncated digests response\n");
+            to_result(-(bindings::EIO as i32))?;
+        }
+
+        // SAFETY: `len` is the length of data read, which will be smaller
+        // then the capacity of the vector
+        unsafe { response_vec.inc_len(len as usize) };
+
+        let response: &mut GetDigestsRsp = Untrusted::new_mut(&mut response_vec).validate_mut()?;
+
+        if len
+            < (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 >= SPDM_VER_13 && (response.param2 & !response.param1 != 0) {
+            pr_err!("Malformed digests response\n");
+            to_result(-(bindings::EPROTO as i32))?;
+        }
+
+        let supported_slots = if self.version >= SPDM_VER_13 {
+            response.param1
+        } else {
+            0xFF
+        };
+
+        self.supported_slots = supported_slots;
+
+        Ok(())
+    }
 }
diff --git a/lib/rspdm/validator.rs b/lib/rspdm/validator.rs
index 9d738133399d..1e5ee8a7582b 100644
--- a/lib/rspdm/validator.rs
+++ b/lib/rspdm/validator.rs
@@ -30,6 +30,7 @@
     SPDM_ASYM_ALGOS,
     SPDM_CTEXPONENT,
     SPDM_GET_CAPABILITIES,
+    SPDM_GET_DIGESTS,
     SPDM_GET_VERSION,
     SPDM_HASH_ALGOS,
     SPDM_MEAS_SPEC_DMTF,
@@ -349,3 +350,56 @@ 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>,
+    // KeyPairIDs, added in 1.3
+
+    // CertificatInfo, added in 1.3
+
+    // KeyUsageMask, added in 1.3
+}
+
+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] 35+ messages in thread

* [PATCH 15/18] lib: rspdm: Support SPDM get_certificate
  2026-05-08  3:16 [PATCH 00/18] lib: Rust implementation of SPDM alistair23
                   ` (13 preceding siblings ...)
  2026-05-08  3:17 ` [PATCH 14/18] lib: rspdm: Support SPDM get_digests alistair23
@ 2026-05-08  3:17 ` alistair23
  2026-05-08  4:23   ` sashiko-bot
  2026-05-08  3:17 ` [PATCH 16/18] lib: rspdm: Support SPDM certificate validation alistair23
                   ` (2 subsequent siblings)
  17 siblings, 1 reply; 35+ messages in thread
From: alistair23 @ 2026-05-08  3:17 UTC (permalink / raw)
  To: alistair, linux-kernel, lukas, Jonathan.Cameron, bhelgaas,
	rust-for-linux, akpm, linux-cxl, djbw, linux-pci
  Cc: alex.gaynor, wilfred.mallawa, gary, bjorn3_gh, benno.lossin,
	aliceryhl, boqun.feng, a.hindborg, tmgross, ojeda, alistair23

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     | 125 +++++++++++++++++++++++++++++++++++++++++
 lib/rspdm/validator.rs |  57 +++++++++++++++++++
 4 files changed, 195 insertions(+)

diff --git a/lib/rspdm/consts.rs b/lib/rspdm/consts.rs
index 092205dab74d..302bc0285478 100644
--- a/lib/rspdm/consts.rs
+++ b/lib/rspdm/consts.rs
@@ -114,6 +114,8 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
 
 pub(crate) const SPDM_GET_DIGESTS: u8 = 0x81;
 
+pub(crate) const SPDM_GET_CERTIFICATE: u8 = 0x82;
+
 // If the crypto support isn't enabled don't offer the algorithms
 // to the responder
 #[cfg(CONFIG_CRYPTO_RSA)]
diff --git a/lib/rspdm/lib.rs b/lib/rspdm/lib.rs
index e42cfdd35524..d5aee761003a 100644
--- a/lib/rspdm/lib.rs
+++ b/lib/rspdm/lib.rs
@@ -124,6 +124,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 bcb1cc955c4c..69b6f67a6ef5 100644
--- a/lib/rspdm/state.rs
+++ b/lib/rspdm/state.rs
@@ -55,6 +55,8 @@
 use crate::validator::{
     GetCapabilitiesReq,
     GetCapabilitiesRsp,
+    GetCertificateReq,
+    GetCertificateRsp,
     GetDigestsReq,
     GetDigestsRsp,
     GetVersionReq,
@@ -135,6 +137,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,
@@ -661,4 +671,119 @@ 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: `response_vec` is rsp_sz length, initialised, aligned
+        // and won't be mutated
+        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>() as u32 + u16::MAX as u32)
+            .min(self.transport_sz) as usize;
+
+        request.offset = 0;
+        request.length = (rsp_sz - core::mem::size_of::<GetCertificateRsp>()) as u16;
+
+        // 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>())) as u16)
+                    .to_le();
+
+            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 as u16 != certs.length
+            || 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 1e5ee8a7582b..8b44a056b335 100644
--- a/lib/rspdm/validator.rs
+++ b/lib/rspdm/validator.rs
@@ -30,6 +30,7 @@
     SPDM_ASYM_ALGOS,
     SPDM_CTEXPONENT,
     SPDM_GET_CAPABILITIES,
+    SPDM_GET_CERTIFICATE,
     SPDM_GET_DIGESTS,
     SPDM_GET_VERSION,
     SPDM_HASH_ALGOS,
@@ -403,3 +404,59 @@ 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 };
+
+        Ok(rsp)
+    }
+}
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 16/18] lib: rspdm: Support SPDM certificate validation
  2026-05-08  3:16 [PATCH 00/18] lib: Rust implementation of SPDM alistair23
                   ` (14 preceding siblings ...)
  2026-05-08  3:17 ` [PATCH 15/18] lib: rspdm: Support SPDM get_certificate alistair23
@ 2026-05-08  3:17 ` alistair23
  2026-05-08  4:25   ` sashiko-bot
  2026-05-08  3:17 ` [PATCH 17/18] rust: allow extracting the buffer from a CString alistair23
  2026-05-08  3:17 ` [PATCH 18/18] lib: rspdm: Support SPDM challenge alistair23
  17 siblings, 1 reply; 35+ messages in thread
From: alistair23 @ 2026-05-08  3:17 UTC (permalink / raw)
  To: alistair, linux-kernel, lukas, Jonathan.Cameron, bhelgaas,
	rust-for-linux, akpm, linux-cxl, djbw, linux-pci
  Cc: alex.gaynor, wilfred.mallawa, gary, bjorn3_gh, benno.lossin,
	aliceryhl, boqun.feng, a.hindborg, tmgross, ojeda, alistair23

From: Alistair Francis <alistair@alistair23.me>

Support validating the SPDM certificate chain. This only performs basic
sanity checks on the chain before we continue on. This does not ensure
that the root CA is trusted, we leave that for userspace to check and
enforce. Instead we just make sure that the chain is correct, uses
supported signatures and that it isn't blacklisted in the kernel.

Signed-off-by: Alistair Francis <alistair@alistair23.me>
---
 lib/rspdm/lib.rs                | 17 ++++++++
 lib/rspdm/state.rs              | 69 ++++++++++++++++++++++++++++++++-
 rust/bindings/bindings_helper.h |  2 +
 3 files changed, 87 insertions(+), 1 deletion(-)

diff --git a/lib/rspdm/lib.rs b/lib/rspdm/lib.rs
index d5aee761003a..d6421b2fab7d 100644
--- a/lib/rspdm/lib.rs
+++ b/lib/rspdm/lib.rs
@@ -135,6 +135,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
 }
 
@@ -145,6 +156,12 @@
 pub unsafe extern "C" fn spdm_destroy(state_ptr: *mut spdm_state) {
     let state: &mut SpdmState = unsafe { &mut (*(state_ptr as *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 69b6f67a6ef5..4c8ee553bb69 100644
--- a/lib/rspdm/state.rs
+++ b/lib/rspdm/state.rs
@@ -106,7 +106,8 @@
 ///  H in SPDM specification.
 /// @certs: Certificate chain in each of the 8 slots. Empty KVec if a slot is
 ///  not populated. Prefixed by the 4 + H header per SPDM 1.0.0 table 15.
-#[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,
@@ -135,6 +136,7 @@ pub struct SpdmState {
 
     // Certificates
     pub(crate) certs: [KVec<u8>; SPDM_SLOTS],
+    pub(crate) leaf_key: Option<*mut bindings::public_key>,
 }
 
 #[repr(C, packed)]
@@ -173,6 +175,7 @@ pub(crate) fn new(
             desc: None,
             hash_len: 0,
             certs: [const { KVec::new() }; SPDM_SLOTS],
+            leaf_key: None,
         }
     }
 
@@ -786,4 +789,68 @@ 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 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 {
+                pr_err!("Certificate was rejected\n");
+                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)?;
+                }
+            }
+
+            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 0231e4f87f20..4e1519b2382d 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -69,6 +69,8 @@
 #include <linux/iosys-map.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] 35+ messages in thread

* [PATCH 17/18] rust: allow extracting the buffer from a CString
  2026-05-08  3:16 [PATCH 00/18] lib: Rust implementation of SPDM alistair23
                   ` (15 preceding siblings ...)
  2026-05-08  3:17 ` [PATCH 16/18] lib: rspdm: Support SPDM certificate validation alistair23
@ 2026-05-08  3:17 ` alistair23
  2026-05-08  3:17 ` [PATCH 18/18] lib: rspdm: Support SPDM challenge alistair23
  17 siblings, 0 replies; 35+ messages in thread
From: alistair23 @ 2026-05-08  3:17 UTC (permalink / raw)
  To: alistair, linux-kernel, lukas, Jonathan.Cameron, bhelgaas,
	rust-for-linux, akpm, linux-cxl, djbw, linux-pci
  Cc: alex.gaynor, wilfred.mallawa, gary, bjorn3_gh, benno.lossin,
	aliceryhl, boqun.feng, a.hindborg, tmgross, ojeda, alistair23

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>
Reviewed-by: Gary Guo <gary@garyguo.net>
---
 rust/kernel/str.rs | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/rust/kernel/str.rs b/rust/kernel/str.rs
index 17070dde0e35..764dd5c695f6 100644
--- a/rust/kernel/str.rs
+++ b/rust/kernel/str.rs
@@ -856,6 +856,12 @@ pub fn try_from_fmt(args: fmt::Arguments<'_>) -> Result<Self, Error> {
         // exist in the buffer.
         Ok(Self { buf })
     }
+
+    /// Return the internal buffer while consuming the original [`CString`]
+    #[inline]
+    pub fn into_vec(self) -> KVec<u8> {
+        self.buf
+    }
 }
 
 impl Deref for CString {
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 18/18] lib: rspdm: Support SPDM challenge
  2026-05-08  3:16 [PATCH 00/18] lib: Rust implementation of SPDM alistair23
                   ` (16 preceding siblings ...)
  2026-05-08  3:17 ` [PATCH 17/18] rust: allow extracting the buffer from a CString alistair23
@ 2026-05-08  3:17 ` alistair23
  2026-05-08  4:19   ` sashiko-bot
  17 siblings, 1 reply; 35+ messages in thread
From: alistair23 @ 2026-05-08  3:17 UTC (permalink / raw)
  To: alistair, linux-kernel, lukas, Jonathan.Cameron, bhelgaas,
	rust-for-linux, akpm, linux-cxl, djbw, linux-pci
  Cc: alex.gaynor, wilfred.mallawa, gary, bjorn3_gh, benno.lossin,
	aliceryhl, boqun.feng, a.hindborg, tmgross, ojeda, alistair23

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              | 216 +++++++++++++++++++++++++++++++-
 lib/rspdm/validator.rs          |  62 +++++++++
 rust/bindings/bindings_helper.h |   1 +
 5 files changed, 291 insertions(+), 2 deletions(-)

diff --git a/lib/rspdm/consts.rs b/lib/rspdm/consts.rs
index 302bc0285478..9d41928da0c6 100644
--- a/lib/rspdm/consts.rs
+++ b/lib/rspdm/consts.rs
@@ -116,6 +116,8 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
 
 pub(crate) const SPDM_GET_CERTIFICATE: u8 = 0x82;
 
+pub(crate) const SPDM_CHALLENGE: u8 = 0x83;
+
 // If the crypto support isn't enabled don't offer the algorithms
 // to the responder
 #[cfg(CONFIG_CRYPTO_RSA)]
@@ -147,3 +149,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 = bit_u8(1);
+
+pub(crate) const SPDM_PREFIX_SZ: usize = 64;
+pub(crate) const SPDM_COMBINED_PREFIX_SZ: usize = 100;
+pub(crate) const SPDM_MAX_OPAQUE_DATA: usize = 1024;
diff --git a/lib/rspdm/lib.rs b/lib/rspdm/lib.rs
index d6421b2fab7d..7fcf5a2d3071 100644
--- a/lib/rspdm/lib.rs
+++ b/lib/rspdm/lib.rs
@@ -135,17 +135,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_err!("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 4c8ee553bb69..3cf7236af7b2 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::mem::offset_of;
 use core::slice::from_raw_parts_mut;
 use kernel::prelude::*;
 use kernel::{
@@ -19,6 +20,7 @@
         Error, //
     },
     str::CStr,
+    str::CString,
     validate::Untrusted,
 };
 
@@ -31,6 +33,7 @@
     SPDM_ASYM_RSASSA_2048,
     SPDM_ASYM_RSASSA_3072,
     SPDM_ASYM_RSASSA_4096,
+    SPDM_COMBINED_PREFIX_SZ,
     SPDM_ERROR,
     SPDM_GET_VERSION_LEN,
     SPDM_HASH_ALGOS,
@@ -38,12 +41,14 @@
     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,
@@ -53,6 +58,8 @@
     SPDM_VER_13, //
 };
 use crate::validator::{
+    ChallengeReq,
+    ChallengeRsp,
     GetCapabilitiesReq,
     GetCapabilitiesRsp,
     GetCertificateReq,
@@ -67,6 +74,8 @@
     SpdmHeader, //
 };
 
+const SPDM_CONTEXT: &str = "responder-challenge_auth signing";
+
 /// The current SPDM session state for a device. Based on the
 /// C `struct spdm_state`.
 ///
@@ -108,6 +117,14 @@
 ///  not populated. Prefixed by the 4 + H header per SPDM 1.0.0 table 15.
 /// @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,
@@ -134,9 +151,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>,
+
+    pub(crate) next_nonce: KVec<u8>,
 }
 
 #[repr(C, packed)]
@@ -174,8 +197,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: KVec::new(),
         }
     }
 
@@ -291,7 +317,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> {
@@ -299,6 +325,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.
@@ -367,6 +395,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;
         let unaligned = core::ptr::addr_of_mut!(response.version_number_entries) as *mut u16;
@@ -438,6 +472,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 {
             pr_err!(
@@ -576,6 +613,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;
@@ -637,6 +677,10 @@ pub(crate) fn get_digests(&mut self) -> Result<(), Error> {
         unsafe { response_vec.inc_len(len 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 len
             < (core::mem::size_of::<GetDigestsReq>()
@@ -697,6 +741,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
@@ -853,4 +901,170 @@ pub(crate) fn validate_cert_chain(&mut self, slot: u8) -> Result<(), Error> {
 
         Ok(())
     }
+
+    pub(crate) fn challenge_rsp_len(&mut self, nonce_len: usize, opaque_len: usize) -> usize {
+        // No measurement summary hash requested (MSHLength == 0)
+        let mut length =
+            core::mem::size_of::<SpdmHeader>() + self.hash_len + nonce_len + opaque_len + 2;
+
+        if self.version >= 0x13 {
+            length += 8;
+        }
+
+        length + self.sig_len
+    }
+
+    fn verify_signature(&mut self, signature: &mut [u8]) -> Result<(), Error> {
+        let mut sig = bindings::public_key_signature::default();
+        let mut mhash: KVec<u8> = KVec::new();
+
+        sig.s = signature as *mut _ as *mut u8;
+        sig.s_size = self.sig_len as u32;
+        sig.encoding = self.base_asym_enc.as_ptr() as *const u8;
+        sig.hash_algo = self.base_hash_alg_name.as_ptr() as *const u8;
+
+        let mut m: KVec<u8> = KVec::new();
+        m.extend_with(SPDM_COMBINED_PREFIX_SZ + self.hash_len, 0, GFP_KERNEL)?;
+
+        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 self.next_nonce.len() > 0 {
+            request.nonce.copy_from_slice(&self.next_nonce);
+            self.next_nonce.clear();
+        } else {
+            unsafe {
+                bindings::get_random_bytes(&mut request.nonce as *mut _ as *mut c_void, nonce_len)
+            };
+        }
+
+        let req_sz = if self.version <= 0x12 {
+            offset_of!(ChallengeReq, context)
+        } else {
+            core::mem::size_of::<ChallengeReq>()
+        };
+
+        let rsp_sz = self.challenge_rsp_len(nonce_len, SPDM_MAX_OPAQUE_DATA);
+
+        // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
+        let request_buf = unsafe { from_raw_parts_mut(&mut request as *mut _ as *mut u8, req_sz) };
+
+        let mut response_vec: KVec<u8> = KVec::with_capacity(rsp_sz, GFP_KERNEL)?;
+        // SAFETY: `response_vec` is rsp_sz length, initialised, aligned
+        // and won't be mutated
+        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()?;
+
+        // MSHLength is 0 as no measurement summary hash requested
+        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 */
+            let sig_start = response_vec.len() - self.sig_len;
+            let signature = &mut response_vec[sig_start..rsp_sz];
+
+            match self.verify_signature(signature) {
+                Ok(()) => {
+                    pr_info!("Authenticated with certificate slot {slot}");
+                    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 8b44a056b335..1975eca81be3 100644
--- a/lib/rspdm/validator.rs
+++ b/lib/rspdm/validator.rs
@@ -28,6 +28,7 @@
 
 use crate::consts::{
     SPDM_ASYM_ALGOS,
+    SPDM_CHALLENGE,
     SPDM_CTEXPONENT,
     SPDM_GET_CAPABILITIES,
     SPDM_GET_CERTIFICATE,
@@ -460,3 +461,64 @@ 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 };
+
+        Ok(rsp)
+    }
+}
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 4e1519b2382d..ed2377be1b44 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -31,6 +31,7 @@
 #include <linux/acpi.h>
 #include <linux/gpu_buddy.h>
 #include <crypto/hash.h>
+#include <crypto/public_key.h>
 #include <drm/drm_device.h>
 #include <drm/drm_drv.h>
 #include <drm/drm_file.h>
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* Re: [PATCH 04/18] X.509: Move certificate length retrieval into new helper
  2026-05-08  3:16 ` [PATCH 04/18] X.509: Move certificate length retrieval into new helper alistair23
@ 2026-05-08  3:39   ` sashiko-bot
  0 siblings, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-05-08  3:39 UTC (permalink / raw)
  To: alistair23; +Cc: linux-pci, ojeda

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] The exported helper `x509_get_certificate_length()` uses a flawed logical condition (`&&` instead of `||`) that fails to properly validate ASN.1 headers, leading to parser differentials, incorrect length calculations, and functional failures.
--

commit 0f604cd0b6dd899bd029f10862a9b7010589e559
Author: Lukas Wunner <lukas@wunner.de>

X.509: Move certificate length retrieval into new helper

This commit extracts the certificate length parsing logic from
x509_load_certificate_list() into a newly exported helper,
x509_get_certificate_length(), to be reused by the upcoming in-kernel
SPDM library.

> diff --git a/crypto/asymmetric_keys/x509_loader.c b/crypto/asymmetric_keys/x509_loader.c
> index a417413269989..25ff027fad1d3 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;

Does this logical condition properly enforce the expected ASN.1 header?

If an untrusted device supplies a valid certificate smaller than 256 bytes
using a 1-byte length encoding (for example p[0] = 0x30, p[1] = 0x81), this
check evaluates to false because p[0] is equal to 0x30. This allows the
short certificate to bypass the check.

> +
> +	plen = (p[2] << 8) | p[3];
> +	plen += 4;

If a short certificate bypasses the check above, does this calculate an
incorrect length?

For a 1-byte length encoding, p[2] would be the actual length and p[3]
would be the first byte of the payload, resulting in a potentially very
large calculated length.

If this bogus length exceeds the buffer size, it would return an error and
cause functional failures. If the length is within the buffer size, could
it advance the offset incorrectly and cause parser desynchronization by
skipping over subsequent certificates or payload data?

> +	if (plen > buflen)
> +		return -EINVAL;
> +
> +	return plen;
> +}
> +EXPORT_SYMBOL_GPL(x509_get_certificate_length);

[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260508031710.514574-1-alistair.francis@wdc.com?part=4

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 07/18] lib: rspdm: Initial commit of Rust SPDM
  2026-05-08  3:16 ` [PATCH 07/18] lib: rspdm: Initial commit of Rust SPDM alistair23
@ 2026-05-08  3:41   ` sashiko-bot
  0 siblings, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-05-08  3:41 UTC (permalink / raw)
  To: alistair23; +Cc: linux-pci, ojeda

Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
- [Critical] `spdm_exchange` parses the response buffer before the transport function populates it, violating aliasing rules and causing incorrect behavior.
- [Critical] Casting untrusted device data to a packed struct containing a Rust enum causes Undefined Behavior.
- [High] `spdm_destroy` is an empty function, leading to a permanent memory leak of the SPDM state.
- [High] `spdm_exchange` unsoundly requires a `&'static mut SpdmState` parameter across the FFI boundary.
- [Medium] `config RSPDM` uses `select RUST` without inheriting its dependencies.
- [Low] `spdm_exchange` is exported to C but missing a declaration in the header file.
- [Low] The `LowerHex` implementation for `SpdmErrorCode` injects unexpected newlines into logs.
--

commit e8e3509aa659e22c45fe6865916c641e09098dfb
Author: Alistair Francis <alistair@alistair23.me>

lib: rspdm: Initial commit of Rust SPDM

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).

> diff --git a/include/linux/spdm.h b/include/linux/spdm.h
> new file mode 100644
> index 0000000000000..e23e386ed97ab
> --- /dev/null
> +++ b/include/linux/spdm.h
[ ... ]
> +struct spdm_state *spdm_create(struct device *dev, spdm_transport *transport,
> +			       void *transport_priv, u32 transport_sz,
> +			       spdm_validate *validate);
> +
> +int spdm_authenticate(struct spdm_state *spdm_state);
> +
> +void spdm_destroy(struct spdm_state *spdm_state);

Is spdm_exchange() intended to be a part of the public C API? The Rust
implementation exports it, but a declaration appears to be missing from this
header file.

> +
> +extern const struct attribute_group spdm_attr_group;
> +
> +#endif
> diff --git a/lib/Kconfig b/lib/Kconfig
> index 00a9509636c18..5a248709132e2 100644
> --- a/lib/Kconfig
> +++ b/lib/Kconfig
[ ... ]
> @@ -592,6 +592,23 @@ config LWQ_TEST
>  	help
>            Run boot-time test of light-weight queuing.
>  
> +config RSPDM
> +	bool "Rust SPDM"
> +	select RUST

Does this need to use "depends on RUST" instead of "select RUST"? According
to the Kconfig subsystem guidelines, selecting a symbol without satisfying
its dependencies (such as HAVE_RUST) can cause unmet dependency warnings and
potential build failures.

> +	select CRYPTO
> +	select KEYS
[ ... ]
> diff --git a/lib/rspdm/consts.rs b/lib/rspdm/consts.rs
> new file mode 100644
> index 0000000000000..2feddde678858
> --- /dev/null
> +++ b/lib/rspdm/consts.rs
[ ... ]
> +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,

Does this enum need a #[repr(u8)] attribute? It is embedded in the packed
SpdmErrorRsp struct and cast from untrusted device data. Without a specified
representation, its memory layout is compiler-defined, which might lead to
unexpected behavior when casting raw bytes.

[ ... ]
> +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 {
> +        writeln!(f, "{:#x}", *self as u8)?;

This isn't a bug, but using writeln! here appends a newline character. When
formatted inside macros like pr_err! that already include a newline, this
results in two consecutive newlines in the kernel log. Would it be better
to use write! instead?

> +
> +        Ok(())
> +    }
> +}
> diff --git a/lib/rspdm/lib.rs b/lib/rspdm/lib.rs
> new file mode 100644
> index 0000000000000..758d43fba5cba
> --- /dev/null
> +++ b/lib/rspdm/lib.rs
[ ... ]
> +#[no_mangle]
> +pub unsafe extern "C" fn spdm_exchange(
> +    state: &'static mut SpdmState,

Could this signature cause lifetime issues? The state reference is marked
as &'static mut, which asserts to the compiler that the object lives for
the entire duration of the program and has exclusive global access. When
the C side frees this state via spdm_destroy(), the 'static guarantee is
violated. Should this take a raw pointer or an elided lifetime reference?

> +    req: *mut c_void,
> +    req_sz: usize,
> +    rsp: *mut c_void,
> +    rsp_sz: usize,
> +) -> isize {
[ ... ]
> +/// spdm_destroy() - Destroy SPDM session
> +///
> +/// @spdm_state: SPDM session state
> +#[export]
> +pub unsafe extern "C" fn spdm_destroy(_state_ptr: *mut spdm_state) {}

Does this code leak the SPDM state allocation? spdm_create() allocates the
memory on the heap using KBox::new() and transfers ownership to C, but this
function is empty and does not drop the allocation.

> diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
> new file mode 100644
> index 0000000000000..18e81f24c7249
> --- /dev/null
> +++ b/lib/rspdm/state.rs
[ ... ]
> +    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()?;

Can this result in undefined behavior due to aliasing rules? The code
creates an immutable reference to the response_buf here, but later passes
a mutable raw pointer to the C transport function below while this reference
is still alive.

Also, does this validate uninitialized or stale memory instead of the newly
received response? The transport function has not been called yet.

> +
> +        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) })?;

Can this raw pointer cast cause undefined behavior if the untrusted device
data contains an invalid enum variant? SpdmErrorRsp embeds the Rust enum
SpdmErrorCode, and interpreting unvalidated bytes as a Rust enum that does
not match a valid variant is undefined behavior.

> +            } else {
> +                return Err(EINVAL);
> +            }
> +        }

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260508031710.514574-1-alistair.francis@wdc.com?part=7

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 05/18] rust: add bindings for hash.h
  2026-05-08  3:16 ` [PATCH 05/18] rust: add bindings for hash.h alistair23
@ 2026-05-08  3:43   ` sashiko-bot
  0 siblings, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-05-08  3:43 UTC (permalink / raw)
  To: alistair23; +Cc: linux-pci, ojeda

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Low] Unintended exposure of <linux/hash.h> to Rust bindings.
- [Low] Alphabetical sorting rules are violated in bindings_helper.h and helpers.c.
--

commit d4762d094c0da15881ab9c843fc2316ba4792255
Author: Alistair Francis <alistair.francis@wdc.com>

rust: add bindings for hash.h

This commit exposes crypto_shash_descsize(), crypto_shash_digestsize(),
and crypto_free_shash() to Rust by adding them to the C helpers and
updating the bindings include lists.

> diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
> index 446dbeaf08668..d73142078240f 100644
> --- a/rust/bindings/bindings_helper.h
> +++ b/rust/bindings/bindings_helper.h
> @@ -30,6 +30,7 @@
>  
>  #include <linux/acpi.h>
>  #include <linux/gpu_buddy.h>
> +#include <crypto/hash.h>
>  #include <drm/drm_device.h>
>  #include <drm/drm_drv.h>

This isn't a bug, but does this addition break the alphabetical sorting of
the includes?  It looks like <crypto/hash.h> is being inserted between
<linux/gpu_buddy.h> and <drm/drm_device.h>.

[ ... ]

> @@ -60,6 +61,7 @@
>  #include <linux/file.h>
>  #include <linux/firmware.h>
>  #include <linux/fs.h>
> +#include <linux/hash.h>
>  #include <linux/i2c.h>

The commit message mentions adding bindings for crypto/hash.h.  Is the
addition of linux/hash.h intended here?

It doesn't appear that utilities from linux/hash.h are being used, which
could unnecessarily increase bindgen execution time and pollute the
Rust namespace.

> diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
> index 625921e27dfbc..6372e14f8419c 100644
> --- a/rust/helpers/helpers.c
> +++ b/rust/helpers/helpers.c
> @@ -60,6 +60,7 @@
>  #include "dma-resv.c"
>  #include "drm.c"
>  #include "err.c"
> +#include "hash.c"
>  #include "irq.c"
>  #include "fs.c"
>  #include "gpu.c"

This isn't a bug, but does inserting hash.c here also bypass alphabetical
sorting?

While it is placed before irq.c, both fs.c and gpu.c appear further down in
the existing list, so placing hash.c after gpu.c might keep this block more
properly ordered.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260508031710.514574-1-alistair.francis@wdc.com?part=5

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 02/18] X.509: Make certificate parser public
  2026-05-08  3:16 ` [PATCH 02/18] X.509: Make certificate parser public alistair23
@ 2026-05-08  3:45   ` sashiko-bot
  0 siblings, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-05-08  3:45 UTC (permalink / raw)
  To: alistair23; +Cc: ojeda, linux-pci

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The commit message claims to adjust the `#include <linux/time.h>` directive to `#include <linux/time64.h>`, but the patch fails to do so.
--

commit b079c32d2fd414877e2d8a8b3e079a1f6c2dc220
Author: Lukas Wunner <lukas@wunner.de>

X.509: Make certificate parser public

This commit makes struct x509_certificate and its associated parsing functions
public to allow CMA-SPDM access to the Subject Alternative Name. It moves
these definitions from the private x509_parser.h to a new public header.

>     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>.

[ ... ]

> diff --git a/include/keys/x509-parser.h b/include/keys/x509-parser.h
> new file mode 100644
> index 0000000000000..8b68e720693a6
> --- /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>

Does this include directive match the intention stated in the commit message?

The commit message mentions adjusting the include to <linux/time64.h>, but it
appears this still includes <linux/time.h>.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260508031710.514574-1-alistair.francis@wdc.com?part=2

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 11/18] lib: rspdm: Support SPDM get_version
  2026-05-08  3:17 ` [PATCH 11/18] lib: rspdm: Support SPDM get_version alistair23
@ 2026-05-08  3:50   ` sashiko-bot
  0 siblings, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-05-08  3:50 UTC (permalink / raw)
  To: alistair23; +Cc: ojeda, linux-pci

Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [Critical] Missing capacity bounds check before increasing vector length leads to out-of-bounds heap memory read.
- [High] Creating a `&mut [u8]` slice from uninitialized heap memory triggers Undefined Behavior.
- [High] Endianness bug when parsing little-endian SPDM version numbers.
- [High] Out-of-bounds pointer provenance (UB) when accessing the `version_number_entries` flexible array member.
- [High] Data race and mutable aliasing due to missing internal locking in `spdm_authenticate`.
- [Medium] `SPDM_GET_VERSION_LEN` is too small to accommodate a maximum-sized valid GET_VERSION response.
--

commit 0a244ec3f0839ba962c85556e66ffad42451e041
Author: Alistair Francis <alistair@alistair23.me>

lib: rspdm: Support SPDM get_version

This commit adds support for the SPDM GET_VERSION command to negotiate
a supported SPDM version and store it in the SpdmState.

> diff --git a/lib/rspdm/consts.rs b/lib/rspdm/consts.rs
> index 2feddde678858..5482a0f6cee09 100644
> --- a/lib/rspdm/consts.rs
> +++ b/lib/rspdm/consts.rs
[ ... ]
> @@ -54,3 +65,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>() + u8::MAX as usize;

Is this length large enough to accommodate a maximum-sized valid GET_VERSION
response?

A valid GET_VERSION response payload contains up to 255 16-bit version
entries. The maximum possible valid response size would be 6 bytes (header
and count) plus 510 bytes (entries), totaling 516 bytes.

Allocating only 259 bytes means valid responses with more than 126 entries
might be truncated and rejected during length validation.


> diff --git a/lib/rspdm/lib.rs b/lib/rspdm/lib.rs
> index 758d43fba5cba..1cc6c33516fb2 100644
> --- a/lib/rspdm/lib.rs
> +++ b/lib/rspdm/lib.rs
[ ... ]
> @@ -105,7 +105,13 @@
>  /// Return 0 on success or a negative errno.  In particular, -EPROTONOSUPPORT
>  /// indicates authentication is not supported by the device.
>  #[export]
> -pub unsafe extern "C" fn spdm_authenticate(_state_ptr: *mut spdm_state) -> c_int {
> +pub unsafe extern "C" fn spdm_authenticate(state_ptr: *mut spdm_state) -> c_int {
> +    let state: &mut SpdmState = unsafe { &mut (*(state_ptr as *mut SpdmState)) };

Can concurrent FFI calls to this function on the same device state create
mutable aliased Rust references?

If multiple callers invoke this without external synchronization, creating
a mutable reference directly from the pointer could trigger data races
and undefined behavior. Does this need internal locking to serialize
concurrent invocations?


> diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
> index 18e81f24c7249..26b90942b298d 100644
> --- a/lib/rspdm/state.rs
> +++ b/lib/rspdm/state.rs
[ ... ]
> @@ -232,4 +237,60 @@ pub(crate) fn spdm_exchange(
[ ... ]
> +    pub(crate) fn get_version(&mut self) -> Result<(), Error> {
> +        let mut request = GetVersionReq::default();
[ ... ]
> +        let mut response_vec: KVec<u8> = KVec::with_capacity(SPDM_GET_VERSION_LEN, GFP_KERNEL)?;
> +        // SAFETY: `response_vec` is SPDM_GET_VERSION_LEN length, initialised, aligned
> +        // and won't be mutated
> +        let response_buf =
> +            unsafe { from_raw_parts_mut(response_vec.as_mut_ptr(), SPDM_GET_VERSION_LEN) };

Does creating a mutable slice from this vector trigger undefined behavior?

KVec::with_capacity() allocates memory but leaves the contents uninitialized.
Forming a reference or slice over uninitialized memory for types that expect
valid initialization (like u8) is immediate undefined behavior in Rust.

Should the buffer be explicitly initialized first, for example by using
KVec::from_elem()?


> +        let rc = self.spdm_exchange(request_buf, response_buf)?;
> +
> +        // SAFETY: `rc` is the length of data read, which will be smaller
> +        // than the capacity of the vector
> +        unsafe { response_vec.inc_len(rc as usize) };

Is it guaranteed that rc will always be smaller than the capacity of the
vector?

The length returned by spdm_exchange() originates from the hardware
transport function, which might report a payload length larger than the
requested buffer.

If a device returns a length larger than SPDM_GET_VERSION_LEN, this would
push the vector's length beyond its actual allocation, potentially leading
to an out-of-bounds heap memory read later on. Should there be a bounds
check before increasing the vector length?


> +        let response: &mut GetVersionRsp = Untrusted::new_mut(&mut response_vec).validate_mut()?;
> +
> +        let mut foundver = false;
> +        let unaligned = core::ptr::addr_of_mut!(response.version_number_entries) as *mut u16;

Does deriving this pointer from a strongly typed reference violate Rust's
strict aliasing rules?

The validate method returns a strongly typed &mut GetVersionRsp, which
restricts the memory provenance strictly to the struct's size (6 bytes,
since the trailing __IncompleteArrayField is a zero-sized type).

Using a raw pointer derived from this reference to read memory beyond
that 6-byte boundary is undefined behavior under Stacked Borrows.


> +        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 addr = unaligned.wrapping_add(i as usize);
> +            let alpha_version = (unsafe { core::ptr::read_unaligned::<u16>(addr) } & 0xF) as u8;
> +            let version = (unsafe { core::ptr::read_unaligned::<u16>(addr) } >> 8) as u8;

Will this correctly parse the version on big-endian architectures?

The protocol version numbers are specified as little-endian 16-bit integers
(__le16), but core::ptr::read_unaligned() reads the values natively.

On big-endian machines, reading a little-endian 0x1000 (SPDM version 1.0)
natively will incorrectly interpret the bytes backwards, yielding 0x0010.
This would extract a version of 0 and break the SPDM negotiation.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260508031710.514574-1-alistair.francis@wdc.com?part=11

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 06/18] rust: error: impl From<FromBytesWithNulError> for Kernel Error
  2026-05-08  3:16 ` [PATCH 06/18] rust: error: impl From<FromBytesWithNulError> for Kernel Error alistair23
@ 2026-05-08  3:51   ` sashiko-bot
  0 siblings, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-05-08  3:51 UTC (permalink / raw)
  To: alistair23; +Cc: linux-pci, ojeda

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The newly added import `use core::ffi::FromBytesWithNulError;` does not follow the required kernel vertical import style.
--

commit 125b12ec12dfbbb7b1bcebfd39f627c83e1aa2f3
Author: Alistair Francis <alistair.francis@wdc.com>

rust: error: impl From<FromBytesWithNulError> for Kernel Error

Implement From<FromBytesWithNulError> for the Kernel Error type.

> diff --git a/rust/kernel/error.rs b/rust/kernel/error.rs
> index 05cf869ac0908..5463addbfd957 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;

This isn't a bug, but should this newly added import follow the kernel
vertical import style documented in the general coding guidelines?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260508031710.514574-1-alistair.francis@wdc.com?part=6

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 01/18] rust: add untrusted data abstraction
  2026-05-08  3:16 ` [PATCH 01/18] rust: add untrusted data abstraction alistair23
@ 2026-05-08  3:52   ` sashiko-bot
  2026-05-08  5:17   ` Dirk Behme
  1 sibling, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-05-08  3:52 UTC (permalink / raw)
  To: alistair23; +Cc: linux-pci, ojeda

Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [High] Missing owned interface causes memory leaks for `Untrusted<MaybeUninit<T>>`.
- [High] Calling `.len()` after `.as_mut_ptr()` on the same slice triggers Undefined Behavior under Stacked Borrows.
- [Medium] Unaligned reference creation in `Validate` documentation examples triggers Undefined Behavior.
- [Low] Missing `#[inline]` annotations on abstraction functions.
- [Low] Nested imports violate the vertical import style guidelines.
- [Low] Incomplete sentences, wrong examples, and inaccurate safety comments in documentation.
--

commit 2a47d2c142208915fc2ad9542e62ff816065513b
Author: Benno Lossin <lossin@kernel.org>

rust: add untrusted data abstraction

This commit introduces the Untrusted and Unvalidated abstractions for handling
data from userspace or hardware. It uses the Validate trait to enforce
validation before data can be safely accessed.

Message-ID: <20240925205244.873020-2-benno.lossin@proton.me>

> diff --git a/rust/kernel/validate.rs b/rust/kernel/validate.rs
> new file mode 100644
> index 0000000000000..ae0aa20e27b47
> --- /dev/null
> +++ b/rust/kernel/validate.rs
> @@ -0,0 +1,605 @@
> +// SPDX-License-Identifier: GPL-2.0

[ ... ]

> +use crate::prelude::Init;
> +use core::{
> +    mem::MaybeUninit,
> +    ops::{Index, IndexMut},
> +    ptr, slice,
> +};

This isn't a bug, but should ptr and slice be on separate lines to follow the
vertical import style guidelines?

[ ... ]

> +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))
> +    }

Should small abstraction functions like new(), new_ref(), new_mut(), and
validate() be annotated with #[inline] per the Rust subsystem coding
guidelines?

[ ... ]

> +    ///         // 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 {

This isn't a bug, but it looks like the comment sentence "That function updated
the" is incomplete.

[ ... ]

> +    /// 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()) };

This isn't a bug, but the safety comment mentions a "mutable slice reference",
while the input slice is an immutable reference. Could this be corrected?

> +        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()) };

Could calling slice.len() after slice.as_mut_ptr() trigger undefined behavior
under Stacked Borrows?

Calling slice.len() creates a shared reference that invalidates the previously
derived mutable raw pointer.

Should the length be stored in a local variable before calling
slice.as_mut_ptr()?

> +        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> {

This isn't a bug, but the example initializes Untrusted::new(42) instead of
using MaybeUninit, and calls write() instead of write_uninit().

Additionally, does this interface leak owned resources?

There is no assume_init() or into_inner() method to consume the Untrusted
MaybeUninit type by value and return the initialized Untrusted type.

Because MaybeUninit does not drop the inner type when it goes out of scope,
it seems any initialized resource requiring a destructor will be permanently
leaked.

[ ... ]

> +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()) };

Similar to transpose_slice_uninit_mut(), does calling self.0.len()
invalidate the mutable pointer derived from self.0.raw_mut().as_mut_ptr()?

[ ... ]

> +///         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 })
> +///     }
> +/// }

Could dereferencing this pointer trigger undefined behavior due to unaligned
access?

A byte slice &[u8] only guarantees 1-byte alignment, so the resulting
pointer might not satisfy the 8-byte alignment required by the Data struct.

Should the example verify alignment before dereferencing?

[ ... ]

> +///         // 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();

This isn't a bug, but there appears to be an unused if block here.

[ ... ]

> +    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 }
> +    }

This isn't a bug, but the safety comment says the pointer came from a "mutable
reference", yet the value is an immutable reference. Could this be fixed?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260508031710.514574-1-alistair.francis@wdc.com?part=1

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 10/18] PCI/CMA: Validate Subject Alternative Name in certificates
  2026-05-08  3:17 ` [PATCH 10/18] PCI/CMA: Validate Subject Alternative Name in certificates alistair23
@ 2026-05-08  3:58   ` sashiko-bot
  0 siblings, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-05-08  3:58 UTC (permalink / raw)
  To: alistair23; +Cc: linux-pci, ojeda

Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [High] Valid devices with a DSN of 0 are incorrectly rejected
- [High] SSVID and SSID fields are incorrectly omitted for PCIe bridges
- [High] Torn read of device identity prevents reauthentication after runtime mutations
- [High] Kbuild linkage failure when compiled as a loadable module
- [Medium] Chaining `snprintf` without length checks risks logic errors and out-of-bounds stack reads
- [Medium] Strict case-sensitive comparison of the Subject Alternative Name risks rejecting valid certificates
--

commit 72435325efe665bb825f47731ca5c0081b7acc14
Author: Lukas Wunner <lukas@wunner.de>
Subject: PCI/CMA: Validate Subject Alternative Name in certificates

This commit introduces functionality to validate the Subject Alternative Name
(SAN) in PCIe Leaf Certificates. It parses the SAN using an ASN.1 decoder
and verifies that the identity encoded in the certificate matches the
device's identity in Config Space.

> diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile
> index 16abfd0e17e19..15512512fce7d 100644
> --- a/drivers/pci/Makefile
> +++ b/drivers/pci/Makefile
[ ... ]
> -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

If CONFIG_PCI_CMA is configured as a loadable module, will Kbuild compile
two separate modules (cma.ko and cma.asn1.ko)?

Because the ASN.1 compiler does not export the cma_decoder symbol, would
the cma.ko module fail to link during modpost with an undefined symbol
error?

Should these objects be bundled into a composite object instead? For
example:

cma_module-y := cma.o cma.asn1.o
obj-$(CONFIG_PCI_CMA) += cma_module.o

> diff --git a/drivers/pci/cma.c b/drivers/pci/cma.c
> index 998fde6366fb8..ee186f3619409 100644
> --- a/drivers/pci/cma.c
> +++ b/drivers/pci/cma.c
[ ... ]
> +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) {

Does this perform a strict case-sensitive comparison of the Subject
Alternative Name?

If a device vendor provisions the certificate using uppercase hexadecimal
letters (a common standard for hardware IDs), could this strict memcmp()
erroneously reject a valid device?

> +		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);

The commit message mentions handling devices that mutate their identity
at runtime, but this string is constructed using cached software state
from probe time (like pdev->vendor).

Since pci_get_dsn() reads the live hardware state, could a mutated device
result in an inconsistent string containing a stale vendor/device ID but
a new DSN? Will reauthentication fail without a full PCI rescan?

Additionally, snprintf() returns the number of characters that would have
been written, not the actual bytes written.

If the inputs ever exceed the 79-byte CMA_NAME_MAX buffer (for instance,
if a quirk mutates pdev->class to exceed 6 hex characters), the string
will be truncated but len will reflect the untruncated size.

Could this cause the later memcmp() check in pci_cma_note_san() to read
out of bounds of the ctx->expected_name stack buffer? Should scnprintf()
be used here instead to track the actual length written?

> +
> +	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);

PCIe Type 1 devices (bridges) can also implement the Subsystem Vendor ID
capability.

If a Type 1 bridge implements this capability and includes these fields in
its certificate as required by the specification, will this condition
unconditionally omit them and cause validation to fail?

> +
> +	serial = pci_get_dsn(pdev);
> +	if (serial)
> +		len += snprintf(name + len, CMA_NAME_MAX - len,
> +				":%016llx", serial);
> +
> +	return len;
> +}

Looking at pci_get_dsn(), it returns 0 both when the DSN capability is
missing and when the device's legitimate serial number is 0.

If a device implements the capability with a valid DSN of 0, will this
check evaluate to false, omit the field, and reject a valid device?

Would it be safer to explicitly check for the capability's presence via
pci_find_ext_capability() instead?

Also, pci_get_dsn() performs two separate 32-bit reads without a lock:

pci_read_config_dword(dev, pos, &dword);
dsn = (u64)dword;
pci_read_config_dword(dev, pos + 4, &dword);
dsn |= ((u64)dword) << 32;

Could a concurrent mutation of the device identity result in a torn
64-bit read during this check?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260508031710.514574-1-alistair.francis@wdc.com?part=10

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 12/18] lib: rspdm: Support SPDM get_capabilities
  2026-05-08  3:17 ` [PATCH 12/18] lib: rspdm: Support SPDM get_capabilities alistair23
@ 2026-05-08  4:05   ` sashiko-bot
  0 siblings, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-05-08  4:05 UTC (permalink / raw)
  To: alistair23; +Cc: ojeda, linux-pci

Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] Incorrect request and response buffer sizes for SPDM 1.0 and 1.1 GET_CAPABILITIES exchanges.
- [High] Missing little-endian to native-endian conversion for `response.data_transfer_size`.
- [High] `GetCapabilitiesRsp::validate` unconditionally rejects valid SPDM 1.0 responses.
- [High] Creating a mutable slice (`&mut [u8]`) from uninitialized memory causes Undefined Behavior.
- [Medium] Vector length bounds check does not prevent out-of-bounds capacity overflow if transport returns a larger length.
--

commit 1fee665b22607bae77947f31f9d54552e86f77d2
Author: Alistair Francis <alistair@alistair23.me>

lib: rspdm: Support SPDM get_capabilities

This commit adds support for the GET_CAPABILITIES SPDM command. It negotiates
responder capabilities and updates the SPDM state.

> diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
> index 26b90942b298d..e7119ffa9a691 100644
> --- a/lib/rspdm/state.rs
> +++ b/lib/rspdm/state.rs

[ ... ]

> @@ -293,4 +304,67 @@ 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),

Are these buffer sizes correct for SPDM 1.0 and 1.1?

According to the SPDM specification, both SPDM 1.0 and 1.1 responses include
a mandatory 4-byte flags field, which would make their true size 12 bytes.
Furthermore, the 1.1 request is also 12 bytes.

Additionally, since GetCapabilitiesRsp::validate() enforces a minimum length
of 12 bytes for SPDM 1.1, wouldn't an 8-byte response unconditionally fail
validation with EINVAL?

> +            _ => {
> +                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: `response_vec` is rsp_sz length, initialised, aligned
> +        // and won't be mutated
> +        let response_buf = unsafe { from_raw_parts_mut(response_vec.as_mut_ptr(), rsp_sz) };

Does creating a mutable slice from this vector trigger undefined behavior?

The safety comment mentions the memory is initialized, but
KVec::with_capacity() allocates uninitialized capacity. Creating a &mut [u8]
reference to uninitialized memory is undefined behavior in Rust.

Should this use something like KVec::resize() or MaybeUninit to ensure the
memory is initialized before slicing?

> +
> +        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
> +        // or the same as the capacity of the vector
> +        unsafe { response_vec.inc_len(rc as usize) };

The code checks if rc is truncated, but what if a buggy transport driver
returns an rc strictly larger than rsp_sz?

If that happens, response_vec.inc_len() would force the length beyond the
allocated capacity, leading to out-of-bounds memory accesses. Should there
be a check to ensure rc <= rsp_sz?

> +
> +        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 {
> +            pr_err!(
> +                "{:#x} capabilities are supported, which don't meet required {:#x}",
> +                self.rsp_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 {

Is an endianness conversion missing here?

The SPDM protocol transmits multi-byte fields in little-endian format, but
response.data_transfer_size is read directly here.

On big-endian architectures, could this be read as an artificially massive
integer and bypass the minimum size bounds check? Should this use
u32::from_le() like the flags field above it?

> +                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 8f45bafd4d691..7dc55882c8809 100644
> --- a/lib/rspdm/validator.rs
> +++ b/lib/rspdm/validator.rs

[ ... ]

> @@ -141,3 +145,94 @@ fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err>
>          Ok(rsp)
>      }
>  }

[ ... ]

> +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>() {
> +            let version = *(raw.get(0).ok_or(ENOMEM))?;
> +            let version_1_1_len = mem::size_of::<GetCapabilitiesRsp>()
> +                - mem::size_of::<__IncompleteArrayField<__le16>>()
> +                - mem::size_of::<u32>()
> +                - mem::size_of::<u32>();
> +
> +            if version == SPDM_VER_11 && raw.len() == version_1_1_len {
> +                // Version 1.1 of the spec doesn't include all of the fields
> +                // So let's extend the KVec with 0s so we can cast the
> +                // vector to GetCapabilitiesRsp
> +
> +                for _i in version_1_1_len..mem::size_of::<GetCapabilitiesRsp>() {
> +                    raw.push(0, GFP_KERNEL)?;
> +                }
> +            } else {
> +                return Err(EINVAL);
> +            }

Does this unintentionally reject valid SPDM 1.0 responses?

If the SPDM session negotiated SPDM 1.0, the response will carry the
SPDM_VER_10 version byte and have a shorter response length. Because this
strictly checks against SPDM_VER_11, won't SPDM 1.0 responses fall through
to the else branch and unconditionally return 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)
> +    }
> +}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260508031710.514574-1-alistair.francis@wdc.com?part=12

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 13/18] lib: rspdm: Support SPDM negotiate_algorithms
  2026-05-08  3:17 ` [PATCH 13/18] lib: rspdm: Support SPDM negotiate_algorithms alistair23
@ 2026-05-08  4:05   ` sashiko-bot
  0 siblings, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-05-08  4:05 UTC (permalink / raw)
  To: alistair23; +Cc: ojeda, linux-pci

Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
- [Critical] Use-After-Free and Double Free of `shash_desc` allocation.
- [High] Invalid pointer dereference and kernel panic when `crypto_alloc_shash` fails.
- [High] Logic error in measurement capability check bypasses algorithm validation.
- [High] Missing upper-bound check on `rc` allows vector length to exceed capacity.
- [High] Endianness bug in `request.length` field generation.
- [High] Creating `&mut [u8]` slices over uninitialized memory is Undefined Behavior.
- [High] Memory leak of the `SpdmState` struct in `spdm_destroy()`.
--

commit 76d36cb1cc14535a6da5b5475b1186acce60ab8a
Author: Alistair Francis <alistair@alistair23.me>

lib: rspdm: Support SPDM negotiate_algorithms

This commit adds support for the NEGOTIATE_ALGORITHMS SPDM command.

> diff --git a/lib/rspdm/lib.rs b/lib/rspdm/lib.rs
> index 9628f258854c6..72886a5dfd695 100644
> --- a/lib/rspdm/lib.rs
> +++ b/lib/rspdm/lib.rs
[ ... ]
> @@ -123,4 +127,16 @@
>  ///
>  /// @spdm_state: SPDM session state
>  #[export]
> -pub unsafe extern "C" fn spdm_destroy(_state_ptr: *mut spdm_state) {}
> +pub unsafe extern "C" fn spdm_destroy(state_ptr: *mut spdm_state) {
> +    let state: &mut SpdmState = unsafe { &mut (*(state_ptr as *mut SpdmState)) };
> +
> +    if let Some(desc) = &mut state.desc {
> +        unsafe {
> +            bindings::kfree(*desc as *mut _ as *mut c_void);
> +        }
> +    }
> +

Does this leak the SpdmState allocation itself? The internal fields are cleaned
up, but state_ptr itself does not appear to be freed here.

> +    unsafe {
> +        bindings::crypto_free_shash(state.shash);
> +    }
> +}

If crypto_alloc_shash() fails earlier, it can return an ERR_PTR. If that
happens, state.shash holds an error pointer. Since crypto_free_shash()
only checks for NULL, will this cause a kernel panic when trying to free
an ERR_PTR here?

> diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
> index e7119ffa9a691..34676744e5094 100644
> --- a/lib/rspdm/state.rs
> +++ b/lib/rspdm/state.rs
[ ... ]
> @@ -367,4 +427,155 @@ pub(crate) fn get_capabilities(&mut self) -> Result<(), Error> {
[ ... ]
> +        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) };

Is this creating a reference to uninitialized memory? KVec::with_capacity()
leaves the underlying memory uninitialized, and forming a slice over
uninitialized memory is undefined behavior in Rust.

> +
> +        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);

Can this cause a use-after-free and a later double-free? desc_vec is a local
variable that will be dropped at the end of this function, freeing its
backing buffer. self.desc then holds a dangling reference. Furthermore,
when spdm_destroy() cleans up the session, it calls bindings::kfree() on
this dangling pointer, which seems like it would corrupt the kernel
allocator.

[ ... ]
> +    pub(crate) fn negotiate_algs(&mut self) -> Result<(), Error> {
> +        let mut request = NegotiateAlgsReq::default();
> +        request.version = self.version;
> +
> +        if self.version >= SPDM_VER_12 && (self.rsp_caps & SPDM_KEY_EX_CAP) == SPDM_KEY_EX_CAP {
> +            request.other_params_support = SPDM_OPAQUE_DATA_FMT_GENERAL;
> +        }
> +
> +        let req_sz = core::mem::size_of::<NegotiateAlgsReq>();
> +        let rsp_sz = core::mem::size_of::<NegotiateAlgsRsp>();
> +
> +        request.length = req_sz as u16;

The SPDM protocol requires multi-byte fields to be transmitted in
little-endian format. Will this send a corrupted, big-endian length value
on big-endian architectures? The base_asym_algo field uses to_le(), so
perhaps this should as well.

> +
> +        // 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: `response_vec` is rsp_sz length, initialised, aligned
> +        // and won't be mutated
> +        let response_buf = unsafe { from_raw_parts_mut(response_vec.as_mut_ptr(), rsp_sz) };

Is this also creating a reference to uninitialized memory? The safety
comment notes it is initialized, but KVec::with_capacity() does not initialize
the allocated memory.

> +
> +        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) };

Does this code verify that rc is not larger than rsp_sz? If
spdm_exchange() returns a value greater than rsp_sz (for example, if a
device sends extra payload data), would this set the vector length beyond
its allocated capacity and lead to out-of-bounds memory accesses?

[ ... ]
> +        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))?;
> +        }

Does this condition correctly check for measurement support?
SPDM_MEAS_CAP_MASK is defined as bits 3 and 4 (11b). According to the SPDM
specification, 11b is a reserved value, while valid measurement support is
01b or 10b. By checking for strict equality with the mask, it looks like
this evaluates to false for legitimate devices and bypasses the algorithm
validation.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260508031710.514574-1-alistair.francis@wdc.com?part=13

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 14/18] lib: rspdm: Support SPDM get_digests
  2026-05-08  3:17 ` [PATCH 14/18] lib: rspdm: Support SPDM get_digests alistair23
@ 2026-05-08  4:06   ` sashiko-bot
  0 siblings, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-05-08  4:06 UTC (permalink / raw)
  To: alistair23; +Cc: ojeda, linux-pci

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] Creating a mutable slice `&mut [u8]` from uninitialized memory is Undefined Behavior.
- [High] Missing bounds check on the returned length before incrementing vector capacity.
- [High] `spdm_authenticate` lacks the documented internal locking, allowing concurrent accesses to mutate state without synchronization.
- [Medium] Incorrect structure size used in the response length validation.
--

commit ff12daae7b365f533b9a93bfd428874ab0cac519
Author: Alistair Francis <alistair@alistair23.me>

lib: rspdm: Support SPDM get_digests

This commit introduces support for the GET_DIGESTS SPDM command. It
adds the necessary constants, state tracking for certificate slots,
and the get_digests() function to perform the exchange.

> diff --git a/lib/rspdm/lib.rs b/lib/rspdm/lib.rs
> index 72886a5dfd695..e42cfdd35524c 100644
> --- a/lib/rspdm/lib.rs
> +++ b/lib/rspdm/lib.rs
> @@ -120,6 +120,10 @@
>          return e.to_errno() as c_int;
>      }
>  
> +    if let Err(e) = state.get_digests() {
> +        return e.to_errno() as c_int;
> +    }
> +
>      0
>  }

Does spdm_authenticate properly handle concurrent invocations?

The exported C wrapper spdm_authenticate casts the opaque pointer to an
exclusive mutable reference &mut SpdmState without acquiring any lock.
The documentation explicitly promises to perform internal locking to serialize
multiple concurrent invocations.

Because SpdmState does not contain a lock and none is acquired, concurrent
calls from C drivers could create multiple aliasing &mut references to the
exact same memory, causing undefined behavior and data races when mutating
shared state like self.provisioned_slots.

Could we add the necessary locking around the state object?

> diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
> index 34676744e5094..bcb1cc955c4c6 100644
> --- a/lib/rspdm/state.rs
> +++ b/lib/rspdm/state.rs

[ ... ]

> +    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: `response_vec` is rsp_sz length, initialised, aligned
> +        // and won't be mutated
> +        let response_buf = unsafe { from_raw_parts_mut(response_vec.as_mut_ptr(), rsp_sz) };

Is this slice creation safe?

KVec::with_capacity() allocates memory but leaves it uninitialized. The code
then calls from_raw_parts_mut() to create a &mut [u8] slice spanning the entire
capacity.

In Rust, creating a slice reference to uninitialized memory is immediate
undefined behavior, as it violates the validity invariant of the u8 type. The
SAFETY comment asserts the memory is initialized, but it actually isn't.

Could we initialize the memory first (e.g., passing __GFP_ZERO to the
allocator or explicitly zeroing it), or handle it using MaybeUninit?

> +
> +        let len = self.spdm_exchange(request_buf, response_buf)?;
> +
> +        if len < (core::mem::size_of::<GetDigestsRsp>() as i32) {
> +            pr_err!("Truncated digests response\n");
> +            to_result(-(bindings::EIO as i32))?;
> +        }
> +
> +        // SAFETY: `len` is the length of data read, which will be smaller
> +        // then the capacity of the vector
> +        unsafe { response_vec.inc_len(len as usize) };

Are we missing a bounds check on the returned length before incrementing the
vector's length?

The code retrieves len from self.spdm_exchange() and immediately passes it to
response_vec.inc_len(). The safety comment assumes that len will inherently be
smaller than or equal to the capacity.

If an external device or transport layer returns a length greater than rsp_sz,
the inc_len call will set the vector's length beyond its physical allocation,
leading to out-of-bounds memory accesses in subsequent operations.

> +
> +        let response: &mut GetDigestsRsp = Untrusted::new_mut(&mut response_vec).validate_mut()?;
> +
> +        if len
> +            < (core::mem::size_of::<GetDigestsReq>()
> +                + response.param2.count_ones() as usize * self.hash_len) as i32
> +        {

Is the correct structure size used in this response length validation?

The code dynamically calculates the limit using size_of::<GetDigestsReq>(),
which is the request structure rather than the response structure
GetDigestsRsp.

While both structures currently happen to be exactly 4 bytes in size, using
the request structure to validate a response payload is logically flawed. If
the response structure size is updated in the future, this bounds check will
fail to properly validate the minimum length.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260508031710.514574-1-alistair.francis@wdc.com?part=14

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 18/18] lib: rspdm: Support SPDM challenge
  2026-05-08  3:17 ` [PATCH 18/18] lib: rspdm: Support SPDM challenge alistair23
@ 2026-05-08  4:19   ` sashiko-bot
  0 siblings, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-05-08  4:19 UTC (permalink / raw)
  To: alistair23; +Cc: ojeda, linux-pci

Thank you for your contribution! Sashiko AI review found 8 potential issue(s) to consider:
- [Critical] Silent security bypass in `spdm_authenticate` when authentication fails.
- [High] Out-of-bounds slice access in `get_version`, `get_digests`, and `get_cert_exchange` due to unvalidated `rsp_sz`.
- [High] Out-of-bounds slice access for `opaque_len` in `challenge()` due to insufficient length validation.
- [High] Undefined Behavior and use of uninitialized memory for `response_buf` in `challenge()`.
- [High] Slice boundary panic when computing `sig_start` in `challenge()`.
- [High] `copy_from_slice` panics if `next_nonce` is not exactly 32 bytes.
- [Medium] Overwriting `leaf_key` during cert validation loop causes valid challenges to fail.
- [Medium] Logic error when no certificates are provisioned causing invalid SPDM command.
--

commit 5fe97b5ab6b0931dd96461eb23247bb2ee5006a7
Author: Alistair Francis <alistair@alistair23.me>

lib: rspdm: Support SPDM challenge

This commit adds support for the CHALLENGE SPDM command in the rspdm
library. It updates the state machine to perform challenges, verify
signatures, and record transcripts.

> diff --git a/lib/rspdm/lib.rs b/lib/rspdm/lib.rs
> index d6421b2fab7df..7fcf5a2d3071a 100644
> --- a/lib/rspdm/lib.rs
> +++ b/lib/rspdm/lib.rs
> @@ -135,17 +135,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_err!("Certificate in slot {slot} failed to verify: {e:?}");
> +            verify = false;
>          }
>  
>          provisioned_slots &= !(1 << slot);
>      }

By continuing the loop and setting verify to false, could this allow a
device with an invalid certificate to bypass authentication? Later in
challenge(), if verify is false, signature verification is skipped and it
returns Ok(()), causing spdm_authenticate() to return 0 (success).

When multiple slots are provisioned, this loop calls validate_cert_chain()
for each slot sequentially, which updates state.leaf_key. Could this cause
authentication to fail? The challenge is issued for the lowest slot (using
trailing_zeros() below), but state.leaf_key will contain the public key of
the highest provisioned slot.

> +    if let Err(e) = state.challenge(state.provisioned_slots.trailing_zeros() as u8, verify) {
> +        return e.to_errno() as c_int;
> +    }
> +
>      0
>  }

What happens if state.provisioned_slots is 0? The trailing_zeros() call
would return 32, which is an invalid slot number for SPDM, potentially
sending a malformed request to the device.

> diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
> index 4c8ee553bb699..3cf7236af7b24 100644
> --- a/lib/rspdm/state.rs
> +++ b/lib/rspdm/state.rs
[ ... ]
> @@ -367,6 +395,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)?;

Can this cause an out-of-bounds slice access? We calculate rsp_sz based on
the attacker-controlled entry count, but we don't appear to verify that the
actually received length (rc) is at least rsp_sz before creating the slice.

[ ... ]
> @@ -637,6 +677,10 @@ pub(crate) fn get_digests(&mut self) -> Result<(), Error> {
>          unsafe { response_vec.inc_len(len 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)?;

Similar to get_version(), is it possible for response.param2 to cause an
out-of-bounds read if the device returns a short packet?

[ ... ]
> @@ -697,6 +741,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)?;

Could an untrusted portion_length lead to an out-of-bounds slice access
here if the actual received length is smaller than rsp_sz?

[ ... ]
> +    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 self.next_nonce.len() > 0 {
> +            request.nonce.copy_from_slice(&self.next_nonce);

Can this panic if next_nonce is not exactly 32 bytes? Since next_nonce is
populated from user space sysfs and request.nonce is a fixed 32-byte array,
an incorrect length might cause copy_from_slice() to panic.

> +            self.next_nonce.clear();
> +        } else {
> +            unsafe {
> +                bindings::get_random_bytes(&mut request.nonce as *mut _ as *mut c_void, nonce_len)
> +            };
> +        }
> +
> +        let req_sz = if self.version <= 0x12 {
> +            offset_of!(ChallengeReq, context)
> +        } else {
> +            core::mem::size_of::<ChallengeReq>()
> +        };
> +
> +        let rsp_sz = self.challenge_rsp_len(nonce_len, SPDM_MAX_OPAQUE_DATA);
> +
> +        // SAFETY: `request` is repr(C) and packed, so we can convert it to a slice
> +        let request_buf = unsafe { from_raw_parts_mut(&mut request as *mut _ as *mut u8, req_sz) };
> +
> +        let mut response_vec: KVec<u8> = KVec::with_capacity(rsp_sz, GFP_KERNEL)?;
> +        // SAFETY: `response_vec` is rsp_sz length, initialised, aligned
> +        // and won't be mutated
> +        let response_buf = unsafe { from_raw_parts_mut(response_vec.as_mut_ptr(), rsp_sz) };

Does KVec::with_capacity() initialize the memory? If it only allocates
capacity without initializing, creating a mutable slice over this memory
could be undefined behavior. Also, if spdm_exchange() validates the buffer
before passing it to the transport, wouldn't it be reading uninitialized
memory?

> +        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()?;
> +
> +        // MSHLength is 0 as no measurement summary hash requested
> +        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]),
> +        );

Does the initial truncation check guarantee that response_vec is large enough
to read opaque_len? The check only verifies it is at least the size of
ChallengeRsp (which might only be 46 bytes), but opaque_len_offset includes
hash_len and nonce_len (which could be 68 bytes or more). Could a device
send a response that passes the truncation check but causes a panic when
slicing opaque_len_offset..(opaque_len_offset + 2)?

> +        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 */
> +            let sig_start = response_vec.len() - self.sig_len;
> +            let signature = &mut response_vec[sig_start..rsp_sz];

If the device sends more data than expected (where rc is greater than
rsp_sz), could sig_start become larger than rsp_sz? If response_vec.len()
was set to rc earlier via inc_len(), then sig_start could shift forward,
causing a panic when attempting to create the signature slice since the
start index would exceed the end index.

> +            match self.verify_signature(signature) {
> +                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(())
> +    }
>  }

If verify_signature() fails and we take the Err path, setting authenticated
to false, the function still returns Ok(()). Should this return an error
instead? As currently written, it appears spdm_authenticate() will return
success to the C caller even when a device provides an invalid signature.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260508031710.514574-1-alistair.francis@wdc.com?part=18

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 15/18] lib: rspdm: Support SPDM get_certificate
  2026-05-08  3:17 ` [PATCH 15/18] lib: rspdm: Support SPDM get_certificate alistair23
@ 2026-05-08  4:23   ` sashiko-bot
  0 siblings, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-05-08  4:23 UTC (permalink / raw)
  To: alistair23; +Cc: linux-pci, ojeda

Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
- [Critical] Unbounded lifetime in `Validate` implementation subverts borrow checker, causing strict aliasing UB.
- [High] Improper vector length accumulation via `inc_len` causes capacity overflow and memory corruption.
- [High] Creating an uninitialized memory slice causes Undefined Behavior.
- [High] Out-of-bounds memory reference creation before verifying `certs_buf` length.
- [High] Missing little-endian conversions for SPDM fields break the protocol on Big Endian architectures.
- [High] Raw `u16` arithmetic on untrusted network lengths can overflow and panic the kernel.
- [Medium] The validation of the certificate response size incorrectly checks against the request struct.
--

commit 2a3b232f8fe8f4917dbcbbbbc545cdee33becabb
Author: Alistair Francis <alistair@alistair23.me>

lib: rspdm: Support SPDM get_certificate

This commit adds support for the SPDM GET_CERTIFICATE command. It introduces
the get_certificate function and related state exchange logic to retrieve
and validate certificate chains.

> diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
> index bcb1cc955c4c6..69b6f67a6ef52 100644
> --- a/lib/rspdm/state.rs
> +++ b/lib/rspdm/state.rs
[ ... ]
> @@ -661,4 +671,119 @@ 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: `response_vec` is rsp_sz length, initialised, aligned
> +        // and won't be mutated
> +        let response_buf = unsafe { from_raw_parts_mut(response_vec.as_mut_ptr(), rsp_sz) };

Does this create a slice over uninitialized memory? Since response_vec was
allocated with KVec::with_capacity(rsp_sz, GFP_KERNEL), it hasn't been
initialized to valid u8 values.

> +
> +        let rc = self.spdm_exchange(request_buf, response_buf)?;
> +
> +        if rc < (core::mem::size_of::<GetCertificateReq>() as i32) {

Is it correct to validate the response size against GetCertificateReq here?

Should this check against GetCertificateRsp instead, in case the structures
diverge in size in the future?

> +            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) };

Since get_certificate() reuses response_vec in a while loop across multiple
exchanges without clearing it, does calling inc_len() here continuously
accumulate the length?

If spdm_exchange always writes to the start of the buffer, could this
accumulation eventually exceed the vector's allocated capacity?

[ ... ]
> +    pub(crate) fn get_certificate(&mut self, slot: u8) -> Result<(), Error> {
> +        let mut request = GetCertificateReq::default();
> +        request.version = self.version;
> +        request.param1 = slot;
> +
> +        let req_sz = core::mem::size_of::<GetCertificateReq>();
> +        let rsp_sz = (core::mem::size_of::<GetCertificateRsp>() as u32 + u16::MAX as u32)
> +            .min(self.transport_sz) as usize;
> +
> +        request.offset = 0;
> +        request.length = (rsp_sz - core::mem::size_of::<GetCertificateRsp>()) as u16;

Are we missing a conversion to little-endian for request.length here?

[ ... ]
> +        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;

Since portion_length and remainder_length are read directly from the
untrusted SPDM response, do they need to be converted from little-endian?

Could this addition overflow u16 and cause a panic if the responder provides
large lengths? Should they be cast to usize before adding?

[ ... ]
> +        while remainder_length > 0 {
> +            request.offset = offset.to_le() as u16;

Does offset.to_le() perform a byte swap on the 64-bit usize before
truncating it to u16? On big-endian systems, wouldn't this shift zeroes
into the lower 16 bits and always send an offset of 0?

Should this be (offset as u16).to_le() instead?

[ ... ]
> +            if response.portion_length == 0
> +                || (response.param1 & 0xF) != slot
> +                || offset as u16 + response.portion_length + response.remainder_length
> +                    != total_cert_len as u16

Could this arithmetic overflow u16 if a malicious responder supplies large
length values?

[ ... ]
> +        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 as u16 != certs.length
> +            || total_cert_len != certs_buf.len()
> +        {

Is it safe to create the mutable reference to SpdmCertChain before verifying
the length of certs_buf?

If the responder replies with a truncated payload, could this create a
reference to unallocated memory? Should the boundary checks happen before
creating the reference?

Also, does certs.length need to be converted from little-endian before
comparing it here?

> diff --git a/lib/rspdm/validator.rs b/lib/rspdm/validator.rs
> index 1e5ee8a7582b1..8b44a056b3354 100644
> --- a/lib/rspdm/validator.rs
> +++ b/lib/rspdm/validator.rs
[ ... ]
> +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 };
> +
> +        Ok(rsp)
> +    }
> +}

Does returning Self here decouple the returned reference's lifetime from the
unvalidated parameter's lifetime?

In get_cert_exchange(), could this allow the returned response reference
to outlive the borrow of the underlying vector, potentially causing
overlapping mutable and immutable references to the same memory allocation?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260508031710.514574-1-alistair.francis@wdc.com?part=15

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 16/18] lib: rspdm: Support SPDM certificate validation
  2026-05-08  3:17 ` [PATCH 16/18] lib: rspdm: Support SPDM certificate validation alistair23
@ 2026-05-08  4:25   ` sashiko-bot
  0 siblings, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-05-08  4:25 UTC (permalink / raw)
  To: alistair23; +Cc: ojeda, linux-pci

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] validate_cert_chain overwrites `leaf_key` when validating multiple provisioned slots, corrupting state and breaking authentication.
- [High] validate_cert_chain leaks dynamically allocated `x509_certificate` structures on all error paths.
- [High] validate_cert_chain leaks the leaf `x509_certificate` structure upon successful validation.
--

commit 3ec614986c351b34141bcbc9f49c6cce87d68be8
Author: Alistair Francis <alistair@alistair23.me>

    lib: rspdm: Support SPDM certificate validation

This commit adds support for validating the SPDM certificate chain. It performs
basic sanity checks to ensure the chain is correct, uses supported signatures,
and is not blacklisted in the kernel.

> diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
> index 69b6f67a6ef52..4c8ee553bb699 100644
> --- a/lib/rspdm/state.rs
> +++ b/lib/rspdm/state.rs

[ ... ]

> @@ -786,4 +789,68 @@ 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_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 {
> +                pr_err!("Certificate was rejected\n");
> +                to_result(-(bindings::EKEYREJECTED as i32))?;
> +            }

Is it possible this code leaks the x509_certificate structure on error paths?

If any validation step fails (such as cert.unsupported_sig or an error in
public_key_verify_signature), returning early with the ? operator will bypass
the x509_free_certificate cleanup for both the currently parsing certificate
(cert_ptr) and the previously parsed certificate (prev_cert).

Since they are stored as raw pointers, the drop semantics do not free them.

> +
> +            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)?;
> +                }
> +            }

[ ... ]

> +        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_) };

Does this overwrite self.leaf_key when validating multiple provisioned slots?

The caller in lib.rs iterates over all provisioned slots, calling
validate_cert_chain() for each:

    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);
    }

Since self.leaf_key is a single Option, this will overwrite the previous public
key pointer without freeing it.

If a device has multiple provisioned slots, could signature verification later
in challenge() fail because self.leaf_key holds the public key of the last
provisioned slot rather than the first?

Also, does this leak the leaf x509_certificate structure on successful
validation?

At the successful completion of the certificate chain loop, the final
certificate remains in prev_cert. The public key is extracted into
self.leaf_key, but bindings::x509_free_certificate(prev) is never called.

While spdm_destroy() later frees the public key via public_key_free(), the
x509_certificate structure itself and its internal fields appear to be leaked
permanently.

> +        }
> +
> +        Ok(())
> +    }

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260508031710.514574-1-alistair.francis@wdc.com?part=16

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 09/18] PCI/CMA: Add a PCI TSM CMA driver using SPDM
  2026-05-08  3:17 ` [PATCH 09/18] PCI/CMA: Add a PCI TSM CMA driver using SPDM alistair23
@ 2026-05-08  5:02   ` sashiko-bot
  0 siblings, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-05-08  5:02 UTC (permalink / raw)
  To: alistair23; +Cc: linux-pci, ojeda

Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] PCI_CMA unconditionally selects RSPDM, bypassing Rust architectural dependencies.
- [High] `pci_cma_tsm_probe()` manually initializes the TSM context and incorrectly sets `dsm_dev = pdev`, causing a guaranteed NULL pointer dereference for Virtual Functions (VFs).
- [High] Missing `bind`, `unbind`, and `guest_req` callbacks in `pci_cma_tsm_ops` lead to NULL function pointer dereferences in core TSM APIs.
- [High] Missing `pm_runtime_get_sync()` before accessing the DOE mailbox in `pci_cma_tsm_connect()`.
- [Low] The struct `pci_cma_devsec` is defined but never used.
--

commit e57964ed86001a515a5baf89472dd183655e1af8
Author: Alistair Francis <alistair.francis@wdc.com>

PCI/CMA: Add a PCI TSM CMA driver using SPDM

This commit adds support for component measurement and authentication 
(CMA-SPDM) per PCIe r6.2 sec 6.31. It introduces a TSM driver that uses 
a PCI DOE mailbox as a transport for SPDM-based authentication.

> diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig
> index 33c88432b7287..dcf4170381f24 100644
> --- a/drivers/pci/Kconfig
> +++ b/drivers/pci/Kconfig
> @@ -124,6 +124,20 @@ 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 PCI_TSM
> +	select RSPDM

Does selecting RSPDM here bypass Rust architectural dependencies?

Since RSPDM selects RUST, using select here forces RUST on regardless of
its dependencies like HAVE_RUST and RUST_IS_AVAILABLE.

Can this cause unmet dependency warnings and build failures due to missing
compiler toolchains on architectures without Rust support?

> diff --git a/drivers/pci/cma.c b/drivers/pci/cma.c
> new file mode 100644
> index 0000000000000..998fde6366fb8
> --- /dev/null
> +++ b/drivers/pci/cma.c

[ ... ]

> +/**
> + * struct pci_cma_devsec - CMA SPDM devsec TSM context
> + * @spdm: SPDM session for this device
> + */
> +struct pci_cma_devsec {
> +	struct spdm_state *spdm;
> +};

This isn't a bug, but struct pci_cma_devsec appears to be defined but
never used anywhere in the driver.

> +static struct pci_tsm *pci_cma_tsm_probe(struct tsm_dev *tsm_dev,
> +				      struct pci_dev *pdev)
> +{
> +	struct pci_doe_mb *doe;
> +	struct pci_cma_tsm *cma;
> +
> +	doe = pci_find_doe_mailbox(pdev, PCI_VENDOR_ID_PCI_SIG,
> +				   PCI_DOE_FEATURE_CMA);
> +	if (!doe)
> +		return NULL;
> +
> +	cma = kzalloc(sizeof(*cma), GFP_KERNEL);
> +	if (!cma)
> +		return NULL;
> +
> +	mutex_init(&cma->pf0.lock);
> +	cma->pf0.doe_mb = doe;
> +	cma->pf0.base_tsm.pdev = pdev;
> +	cma->pf0.base_tsm.dsm_dev = pdev;

Is it safe to bypass pci_tsm_pf0_constructor() and manually set
dsm_dev = pdev here?

During pci_tsm_connect(), the framework walks all dependent functions,
meaning pdev can be a Virtual Function (VF).

Later, when the core TSM subsystem calls to_pci_tsm_pf0(), it validates
the DSM device with is_pci_tsm_pf0(), which returns false for VFs.

This causes the helper to return NULL, leading to a NULL pointer dereference
when the core framework attempts to acquire the lock.

> +	cma->pf0.base_tsm.tsm_dev = tsm_dev;
> +
> +	cma->spdm = spdm_create(&pdev->dev, pci_doe_transport, doe,
> +				PCI_DOE_MAX_PAYLOAD, NULL);

[ ... ]

> +static int pci_cma_tsm_connect(struct pci_dev *pdev)
> +{
> +	struct pci_cma_tsm *cma = cma_tsm_from_tsm(pdev->tsm);
> +	int rc;
> +
> +	rc = spdm_authenticate(cma->spdm);

Does this sequence need a pm_runtime_get_sync() before attempting
authentication?

Since this is triggered via the sysfs connect attribute, it does not
automatically wake the device.

If the device is in a low-power state like D3hot, accessing its DOE mailbox
registers here could result in PCI aborts or return ~0.

> +	if (rc)
> +		return rc;
> +
> +	return 0;
> +}

[ ... ]

> +static const struct pci_tsm_ops pci_cma_tsm_ops = {
> +	.link_ops = {
> +		.probe		= pci_cma_tsm_probe,
> +		.remove		= pci_cma_tsm_remove,
> +		.connect	= pci_cma_tsm_connect,
> +		.disconnect	= pci_cma_tsm_disconnect,
> +	},
> +};

Are the missing bind, unbind, and guest_req callbacks intentional?

The core TSM framework APIs like pci_tsm_bind() and pci_tsm_unbind()
unconditionally call these function pointers without verifying if they
exist.

Could this cause an immediate kernel panic if userspace or VFIO attempts
to bind a CMA-authenticated device as a TDI?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260508031710.514574-1-alistair.francis@wdc.com?part=9

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 01/18] rust: add untrusted data abstraction
  2026-05-08  3:16 ` [PATCH 01/18] rust: add untrusted data abstraction alistair23
  2026-05-08  3:52   ` sashiko-bot
@ 2026-05-08  5:17   ` Dirk Behme
  1 sibling, 0 replies; 35+ messages in thread
From: Dirk Behme @ 2026-05-08  5:17 UTC (permalink / raw)
  To: alistair23, alistair, linux-kernel, lukas, Jonathan.Cameron,
	bhelgaas, rust-for-linux, akpm, linux-cxl, djbw, linux-pci
  Cc: alex.gaynor, wilfred.mallawa, gary, bjorn3_gh, benno.lossin,
	aliceryhl, boqun.feng, a.hindborg, tmgross, ojeda, Simona Vetter

Hi Alistair,

On 08.05.2026 05:16, alistair23@gmail.com wrote:
> 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>


Today, randomly, I was about to ask Benno if there are any plans to 
continue with his great untrusted work. Just to find that you are 
carrying this forward. Many thanks!

While trying to figure out some history of this patch, it looks to me 
that the version you are using is Benno's v2:

https://lore.kernel.org/rust-for-linux/20240925205244.873020-2-benno.lossin@proton.me/

?

While the latest version from Benno I found is a v4:

https://lore.kernel.org/rust-for-linux/20250814124424.516191-3-lossin@kernel.org/

Is there any reason not to use that?

While at this, do you have any plan to add the `Validate` trait as well?

https://lore.kernel.org/rust-for-linux/20250814124424.516191-4-lossin@kernel.org/

Thanks

Dirk

P.S.: Carrying this patch forward, please check if it needs an update 
regarding new "rules". E.g. it looks to me that there should be an 
update regarding the vertical style for imports?

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


> ---
>   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 b72b2fbe046d..fe580fde609f 100644
> --- a/rust/kernel/lib.rs
> +++ b/rust/kernel/lib.rs
> @@ -133,6 +133,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))
> +    }
> +}


^ permalink raw reply	[flat|nested] 35+ messages in thread

end of thread, other threads:[~2026-05-08  5:18 UTC | newest]

Thread overview: 35+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-08  3:16 [PATCH 00/18] lib: Rust implementation of SPDM alistair23
2026-05-08  3:16 ` [PATCH 01/18] rust: add untrusted data abstraction alistair23
2026-05-08  3:52   ` sashiko-bot
2026-05-08  5:17   ` Dirk Behme
2026-05-08  3:16 ` [PATCH 02/18] X.509: Make certificate parser public alistair23
2026-05-08  3:45   ` sashiko-bot
2026-05-08  3:16 ` [PATCH 03/18] X.509: Parse Subject Alternative Name in certificates alistair23
2026-05-08  3:16 ` [PATCH 04/18] X.509: Move certificate length retrieval into new helper alistair23
2026-05-08  3:39   ` sashiko-bot
2026-05-08  3:16 ` [PATCH 05/18] rust: add bindings for hash.h alistair23
2026-05-08  3:43   ` sashiko-bot
2026-05-08  3:16 ` [PATCH 06/18] rust: error: impl From<FromBytesWithNulError> for Kernel Error alistair23
2026-05-08  3:51   ` sashiko-bot
2026-05-08  3:16 ` [PATCH 07/18] lib: rspdm: Initial commit of Rust SPDM alistair23
2026-05-08  3:41   ` sashiko-bot
2026-05-08  3:17 ` [PATCH 08/18] PCI/TSM: Support connecting to PCIe CMA devices alistair23
2026-05-08  3:17 ` [PATCH 09/18] PCI/CMA: Add a PCI TSM CMA driver using SPDM alistair23
2026-05-08  5:02   ` sashiko-bot
2026-05-08  3:17 ` [PATCH 10/18] PCI/CMA: Validate Subject Alternative Name in certificates alistair23
2026-05-08  3:58   ` sashiko-bot
2026-05-08  3:17 ` [PATCH 11/18] lib: rspdm: Support SPDM get_version alistair23
2026-05-08  3:50   ` sashiko-bot
2026-05-08  3:17 ` [PATCH 12/18] lib: rspdm: Support SPDM get_capabilities alistair23
2026-05-08  4:05   ` sashiko-bot
2026-05-08  3:17 ` [PATCH 13/18] lib: rspdm: Support SPDM negotiate_algorithms alistair23
2026-05-08  4:05   ` sashiko-bot
2026-05-08  3:17 ` [PATCH 14/18] lib: rspdm: Support SPDM get_digests alistair23
2026-05-08  4:06   ` sashiko-bot
2026-05-08  3:17 ` [PATCH 15/18] lib: rspdm: Support SPDM get_certificate alistair23
2026-05-08  4:23   ` sashiko-bot
2026-05-08  3:17 ` [PATCH 16/18] lib: rspdm: Support SPDM certificate validation alistair23
2026-05-08  4:25   ` sashiko-bot
2026-05-08  3:17 ` [PATCH 17/18] rust: allow extracting the buffer from a CString alistair23
2026-05-08  3:17 ` [PATCH 18/18] lib: rspdm: Support SPDM challenge alistair23
2026-05-08  4:19   ` sashiko-bot

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox