lib: Rust implementation of SPDM
Subsystems (7)
cover lib: Rust implementation of SPDM
From: Alistair Francis <[email protected]>
Security Protocols and Data Models (SPDM) [1] is used for authentication, attestation and key exchange. SPDM is generally used over a range of transports, such as PCIe, MCTP/SMBus/I3C, ATA, SCSI, NVMe or TCP.
From the kernels perspective SPDM is used to authenticate and attest devices. In this threat model a device is considered untrusted until it can be verified by the kernel and userspace using SPDM. As such SPDM data is untrusted data that can be mallicious.
The SPDM specification is also complex, with the 1.2.1 spec being almost 200 pages and the 1.3.0 spec being almost 250 pages long.
As such we have the kernel parsing untrusted responses from a complex specification, which sounds like a possible exploit vector. This is the type of place where Rust excels!
This series implements a SPDM requester in Rust.
This is based on Lukas' C implementation [2], but has been refacted during the first few RFCs. I have included some of the relevent patchesfrom Lukas' C SPDM implementation in this series where they are required.
This is a standalone series and doesn't depend on Lukas' implementation.
The goal of this series is to get the smallest possible SPDM implementation upstream. That will provide building blocks for us to continue working on.
As such we don't yet provide evidence or certificates to userspace, allow userspace to provide a nonce, support PQC or more advanced SPDM features. This is enough to communicate with a device and return "authenticated" to userspace.
Note that RFC v3 did provide evidence and certificates to userspace and allowed a custom nonce. Showing that it's possible. I also have patches that build apon [4] to do this via a TSM driver, again showing it's possible with the current approach.
We just don't support it yet and for TSM I need [4] upstream first.
This series is different to Lukas' original approach and the approach taken in the previous RFCs and instead adds the PCI-CMA support as a TSM driver. This was described by Dan in [3] and [5]. The advantage here is that for PCIe we can leverage the TSM work for a lot of the features and provide userspace a consistient interface between PCI TSM and CMA.
This series also doesn't check the certificate chain against the kernel keyring and will instead leave that to userspace once [4] is merged.
Other transport mode (such as ATA, SCSI, NVMe and MCTP) will therefore need slightly different approaches, as TSM doesn't apply. The library can support this though, it will just need some netlink and sysfs wrappers added as applicable. This way each transport can support SPDM in the way it sees fit.
The entire tree can be seen here:
https://github.com/alistair23/linux/tree/alistair/spdm-rust-tsm
I'm testing this by running the following
```shell cargo run -- --qemu-server response qemu-system-x86_64 \ -nic none \ -object rng-random,filename=/dev/urandom,id=rng0 \ -device virtio-rng-pci,rng=rng0 \ -drive file=deploy/images/qemux86-64/core-image-pcie-qemux86-64.rootfs.ext4,if=virtio,format=raw \ -usb -device usb-tablet -usb -device usb-kbd \ -cpu Skylake-Client \ -machine q35,i8042=off \ -smp 4 -m 2G \ -drive file=blknvme,if=none,id=mynvme,format=raw \ -device nvme,drive=mynvme,serial=deadbeef,spdm_port=2323,spdm_trans=doe \ -snapshot \ -serial mon:stdio -serial null -nographic \ -kernel deploy/images/qemux86-64/bzImage \ -append 'root=/dev/vda rw console=ttyS0 console=ttyS1 oprofile.timer=1 tsc=reliable no_timer_check rcupdate.rcu_expedited=1 swiotlb=0 ' ls /sys/devices/pci0000:00/0000:00:03.0/ ls /sys/devices/pci0000:00/0000:00:03.0/tsm/ cat /sys/devices/pci0000:00/0000:00:03.0/authenticated echo tsm0 > /sys/devices/pci0000:00/0000:00:03.0/tsm/connect cat /sys/devices/pci0000:00/0000:00:03.0/authenticated ```
1: https://www.dmtf.org/standards/spdm
2: https://lore.kernel.org/all/[email protected]/
3: http://lore.kernel.org/[email protected]
4: https://lore.kernel.org/all/[email protected]/
5: https://lore.kernel.org/lkml/[email protected]/
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
Signature · 1 line
2.52.0
PATCH 1/18 rust: add untrusted data abstraction
From: Benno Lossin <[email protected]>
When reading data from userspace, hardware or other external untrusted sources, the data must be validated before it is used for logic within the kernel. This abstraction provides a generic newtype wrapper Untrusted; it prevents direct access to the inner type. The only way to use the underlying data is to call .validate() on such a value.
Doing so utilizes the new Validate trait that is responsible for all of the validation logic. This trait gives access to the inner value of Untrusted by means of another newtype wrapper Unvalidated. In contrast to Untrusted, Unvalidated allows direct access and additionally provides several helper functions for slices.
Having these two different newtype wrappers is an idea from Simona Vetter. It has several benefits: it fully prevents safe access to the underlying value of Untrusted without going through the Validate API. Additionally, it allows one to grep for validation logic by simply looking for Unvalidated<.
Any API that reads data from an untrusted source should return Untrusted<T> where T is the type of the underlying untrusted data. This generic allows other abstractions to return their custom type wrapped by Untrusted, signaling to the caller that the data must be validated before use. This allows those abstractions to be used both in a trusted and untrusted manner, increasing their generality. Additionally, using the arbitrary self types feature, APIs can be designed to explicitly read untrusted data:
impl MyCustomDataSource {
pub fn read(self: &Untrusted<Self>) -> &Untrusted<[u8]>;
}Message-ID: <[email protected]>
--- rust/kernel/lib.rs | 1 + rust/kernel/validate.rs | 605 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 606 insertions(+) create mode 100644 rust/kernel/validate.rs
rust/kernel/lib.rs +1 -0
@@ -133,6 +133,7 @@pub mod uaccess;#[cfg( CONFIG_USB = "y" )] pub mod usb; +pub mod validate;pub mod workqueue;pub mod xarray;
rust/kernel/validate.rs new file +605 -0
605 lines not on this page · raw message
Signature · 1 line
2.52.0
[email protected]
AI reviewRe: [PATCH 01/18] rust: add untrusted data abstraction
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…
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 <[email protected]>
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: <[email protected]>
Quoted 7 lines
rust/kernel/validate.rs new file +1
-0@@ -0,0 +1,605 @@ +// SPDX-License-Identifier: GPL-2.0
[ ... ]
rust/kernel/validate.rs line 40
Quoted 6 lines
rust/kernel/validate.rs new file +6
-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?
[ ... ]
Quoted 10 lines
rust/kernel/validate.rs new file +10
-0+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]> {
rust/kernel/validate.rs line 74
Quoted 10 lines
rust/kernel/validate.rs new file +10
-0+/// // 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?
[ ... ]
rust/kernel/validate.rs line 148
Quoted 8 lines
rust/kernel/validate.rs new file +8
-0+/// // 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.
[ ... ]
rust/kernel/validate.rs line 215
Quoted 11 lines
rust/kernel/validate.rs new file +11
-0+/// 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?
Quoted 5 lines
rust/kernel/validate.rs new file +5
-0+Untrusted ::new_ref (slice) + } + +/// Turns a slice of uninitialized, untrusted values into an untrusted slice of uninitialized +/// values.
rust/kernel/validate.rs line 230
Quoted 10 lines
rust/kernel/validate.rs new file +10
-0+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()?
Quoted 6 lines
rust/kernel/validate.rs new file +6
-0+Untrusted ::new_mut (slice) + } +} + +impl <T >Untrusted <MaybeUninit <T >> { +/// Sets the underlying untrusted value.
rust/kernel/validate.rs line 246
Quoted 10 lines
rust/kernel/validate.rs new file +10
-0+/// +/// # 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.
[ ... ]
Quoted 16 lines
rust/kernel/validate.rs new file +16
-0+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 ;
rust/kernel/validate.rs line 285
Quoted 10 lines
rust/kernel/validate.rs new file +10
-0+// CAST: `MaybeUninit<T>` is `repr(transparent)`. +let ptr = ptras *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()?
[ ... ]
rust/kernel/validate.rs line 404
Quoted 7 lines
rust/kernel/validate.rs new file +7
-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 }) +/// } +/// }
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?
[ ... ]
rust/kernel/validate.rs line 450
Quoted 6 lines
rust/kernel/validate.rs new file +6
-0+/// // 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.
[ ... ]
rust/kernel/validate.rs line 484
Quoted 7 lines
rust/kernel/validate.rs new file +7
-0+fn new_ref (value: &T ) -> &Self { +let ptr: *const T = value; +// CAST: `Self` is `repr(transparent)` and contains a `T`. +let ptr = ptras *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?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
Re: [PATCH 01/18] rust: add untrusted data abstraction
In reply to [email protected]Hi Alistair,
Quoted 29 lines · On 08.05.2026 05:16, [email protected] wrote:
From: Benno Lossin <[email protected]>
When reading data from userspace, hardware or other external untrusted sources, the data must be validated before it is used for logic within the kernel. This abstraction provides a generic newtype wrapper
Untrusted; it prevents direct access to the inner type. The only way to use the underlying data is to call.validate()on such a value.Doing so utilizes the new
Validatetrait that is responsible for all of the validation logic. This trait gives access to the inner value ofUntrustedby means of another newtype wrapperUnvalidated. In contrast toUntrusted,Unvalidatedallows 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
Untrustedwithout going through theValidateAPI. Additionally, it allows one to grep for validation logic by simply looking forUnvalidated<.Any API that reads data from an untrusted source should return
Untrusted<T>whereTis the type of the underlying untrusted data. This generic allows other abstractions to return their custom type wrapped byUntrusted, 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:
Quoted 7 lines
impl MyCustomDataSource { pub fn read(self: &Untrusted<Self>) -> &Untrusted<[u8]>; }Cc Simona Vetter <[email protected]>Signed-off-by Benno Lossin <[email protected]>Message-ID: <[email protected]>
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/[email protected]/
?
While the latest version from Benno I found is a v4:
https://lore.kernel.org/rust-for-linux/[email protected]/
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/[email protected]/
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
Quoted 629 lines
--- rust/kernel/lib.rs | 1 + rust/kernel/validate.rs | 605 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 606 insertions(+)create mode 100644
rust/kernel/validate.rsrust/kernel/lib.rs +1
-0@@ -133,6 +133,7 @@pub mod uaccess;#[cfg( CONFIG_USB = "y" )] pub mod usb; +pub mod validate;pub mod workqueue;pub mod xarray;rust/kernel/validate.rs new file +605
-0@@ -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 = ptras *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 = ptras *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 <&'aUnvalidated <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 <&'amut Unvalidated <T >>>( + &'amut 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 = ptras *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 &'aUnvalidated <T > {} +impl <'a,T : ?Sized >ValidateInput for &'aUnvalidated <T > {} + +impl <'a,T : ?Sized > private::Sealed for &'amut Unvalidated <T > {} +impl <'a,T : ?Sized >ValidateInput for &'amut 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 = ptras *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 = ptras *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 <&'aUnvalidated <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 <&'amut Unvalidated <T >>>( + &'amut 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 = &'aUnvalidated <T >; + +fn next (&mut self) ->Option <Self ::Item > { + self.0 .next ().map (Unvalidated ::new_ref) + } +} + +impl <'a,T >IntoIterator for &'aUnvalidated <[T ]> { +type Item = &'aUnvalidated <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 = &'amut Unvalidated <T >; + +fn next (&mut self) ->Option <Self ::Item > { + self.0 .next ().map (Unvalidated ::new_mut) + } +} + +impl <'a,T >IntoIterator for &'amut Unvalidated <[T ]> { +type Item = &'amut 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 <&'aUnvalidated <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 <&'amut Unvalidated <T >>>( + &'amut self, + ) ->impl Iterator <Item =Result <V ,V ::Err >> + 'a { + self.into_iter ().map (|item|V ::validate (item)) + } +}
Re: [PATCH 01/18] rust: add untrusted data abstraction
Quoted 51 lines · Dirk Behme
Hi Alistair,
Quoted 29 lines · On 08.05.2026 05:16, [email protected] wrote:
From: Benno Lossin <[email protected]>
When reading data from userspace, hardware or other external untrusted sources, the data must be validated before it is used for logic within the kernel. This abstraction provides a generic newtype wrapper
Untrusted; it prevents direct access to the inner type. The only way to use the underlying data is to call.validate()on such a value.Doing so utilizes the new
Validatetrait that is responsible for all of the validation logic. This trait gives access to the inner value ofUntrustedby means of another newtype wrapperUnvalidated. In contrast toUntrusted,Unvalidatedallows 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
Untrustedwithout going through theValidateAPI. Additionally, it allows one to grep for validation logic by simply looking forUnvalidated<.Any API that reads data from an untrusted source should return
Untrusted<T>whereTis the type of the underlying untrusted data. This generic allows other abstractions to return their custom type wrapped byUntrusted, 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:Quoted 7 lines
impl MyCustomDataSource { pub fn read(self: &Untrusted<Self>) -> &Untrusted<[u8]>; }Cc Simona Vetter <[email protected]>Signed-off-by Benno Lossin <[email protected]>Message-ID: <[email protected]>
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:
Quoted 7 lines
?
While the latest version from Benno I found is a v4:
https://lore.kernel.org/rust-for-linux/[email protected]/
Is there any reason not to use that?
Nope, there isn't, I just didn't know it existed. I have updated to using the v4 submission
Quoted 4 lines
While at this, do you have any plan to add the
Validatetrait as well?
I am using the Validate trait, so yes :) I have pulled in patched 1, 2 and 3 from the v4 series.
Quoted 10 lines
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?
Thanks, I have fixed that up.
Alistair
PATCH 2/18 X.509: Make certificate parser public
In reply to [email protected]From: Lukas Wunner <[email protected]>
The upcoming support for PCI device authentication with CMA-SPDM (PCIe r6.1 sec 6.31) requires validating the Subject Alternative Name in X.509 certificates.
High-level functions for X.509 parsing such as key_create_or_update() throw away the internal, low-level struct x509_certificate after extracting the struct public_key and public_key_signature from it. The Subject Alternative Name is thus inaccessible when using those functions.
Afford CMA-SPDM access to the Subject Alternative Name by making struct x509_certificate public, together with the functions for parsing an X.509 certificate into such a struct and freeing such a struct.
The private header file x509_parser.h previously included <linux/time.h> for the definition of time64_t. That definition was since moved to <linux/time64.h> by commit 361a3bf00582 ("time64: Add time64.h header and define struct timespec64"), so adjust the #include directive as part of the move to the new public header file <keys/x509-parser.h>.
No functional change intended.
--- crypto/asymmetric_keys/x509_parser.h | 42 +-------------------- include/keys/x509-parser.h | 55 ++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 41 deletions(-) create mode 100644 include/keys/x509-parser.h
crypto/asymmetric_keys/x509_parser.h +1 -41
@@ -5,51 +5,11 @@* Written by David Howells ([email protected]) */ -#include <linux/cleanup.h> -#include <linux/time.h> -#include <crypto/public_key.h> -#include <keys/asymmetric-type.h> -#include <crypto/sha2.h> - -struct x509_certificate { -struct x509_certificate *next; -struct x509_certificate *signer;/* Certificate that signed this one */ -struct public_key *pub;/* Public key details */ -struct public_key_signature *sig;/* Signature parameters */ -u8 sha256[SHA256_DIGEST_SIZE ];/* Hash for blacklist purposes */ -char *issuer;/* Name of certificate issuer */ -char *subject;/* Name of certificate subject */ -struct asymmetric_key_id *id;/* Issuer + Serial number */ -struct asymmetric_key_id *skid;/* Subject + subjectKeyId (optional) */ -time64_t valid_from; -time64_t valid_to; -const void *tbs;/* Signed data */ -unsigned tbs_size;/* Size of signed data */ -unsigned raw_sig_size;/* Size of signature */ -const void *raw_sig;/* Signature data */ -const void *raw_serial;/* Raw serial number in ASN.1 */ -unsigned raw_serial_size; -unsigned raw_issuer_size; -const void *raw_issuer;/* Raw issuer name in ASN.1 */ -const void *raw_subject;/* Raw subject name in ASN.1 */ -unsigned raw_subject_size; -unsigned raw_skid_size; -const void *raw_skid;/* Raw subjectKeyId in ASN.1 */ -unsigned index; -bool seen;/* Infinite recursion prevention */ -bool verified; -bool self_signed;/* T if self-signed (check unsupported_sig too) */ -bool unsupported_sig;/* T if signature uses unsupported crypto */ -bool blacklisted; -}; +#include <keys/x509-parser.h> /* * x509_cert_parser.c */ -extern void x509_free_certificate (struct x509_certificate *cert); -DEFINE_FREE(x509_free_certificate, struct x509_certificate *, - if (!IS_ERR(_T)) x509_free_certificate(_T)) -extern struct x509_certificate *x509_cert_parse (const void *data,size_t datalen);extern int x509_decode_time (time64_t *_t,size_t hdrlen,unsigned char tag,const unsigned char *value,size_t vlen);
include/keys/x509-parser.h new file +55 -0
55 lines not on this page · raw message
Signature · 1 line
2.52.0
[email protected]
AI reviewRe: [PATCH 02/18] X.509: Make certificate parser public
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…
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 <[email protected]>
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.
Quoted 5 lines
The private header file
x509_parser.hpreviously included <linux/time.h> for the definition oftime64_t. That definition was since moved to <linux/time64.h> by commit361a3bf00582("time64: Addtime64.hheader and define struct timespec64"), so adjust the #include directive as part of the move to the new public header file <keys/x509-parser.h>.
[ ... ]
Quoted 8 lines
include/keys/x509-parser.h new file +2
-0@@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* X.509 certificate parser
include/keys/x509-parser.h line 12
Quoted 10 lines
include/keys/x509-parser.h new file +10
-0+* +* Copyright (C) 2012 Red Hat, Inc. All Rights Reserved. +* Written by David Howells ([email protected]) +*/ + +#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>.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
Lukas Wunner
maintainerRe: [PATCH 02/18] X.509: Make certificate parser public
Quoted 2 lines · On Fri, May 08, 2026 at 03:45:43AM +0000, [email protected] wrote:
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.
Observation is valid, but appears to be caused by an outdated commit on Alistair's development branch. The corresponding commit on my development branch does change the #include directive as advertised in the commit message:
PATCH 3/18 X.509: Parse Subject Alternative Name in certificates
In reply to [email protected]From: Lukas Wunner <[email protected]>
The upcoming support for PCI device authentication with CMA-SPDM (PCIe r6.1 sec 6.31) requires validating the Subject Alternative Name in X.509 certificates.
Store a pointer to the Subject Alternative Name upon parsing for consumption by CMA-SPDM.
--- crypto/asymmetric_keys/x509_cert_parser.c | 9 +++++++++ include/keys/x509-parser.h | 2 ++ 2 files changed, 11 insertions(+)
crypto/asymmetric_keys/x509_cert_parser.c +9 -0
@@ -596,6 +596,15 @@ int x509_process_extension(void *context, size_t hdrlen,return 0 ; } +if (ctx->last_oid == OID_subjectAltName) { +if (ctx->cert->raw_san) +return -EBADMSG ; + + ctx->cert->raw_san = v; + ctx->cert->raw_san_size = vlen; +return 0 ; + } +if (ctx->last_oid == OID_keyUsage) {/* * Get hold of the keyUsage bit string
include/keys/x509-parser.h +2 -0
@@ -38,6 +38,8 @@ struct x509_certificate {unsigned raw_subject_size;unsigned raw_skid_size;const void *raw_skid;/* Raw subjectKeyId in ASN.1 */ +const void *raw_san;/* Raw subjectAltName in ASN.1 */ +unsigned raw_san_size;unsigned index;bool seen;/* Infinite recursion prevention */ bool verified;
Signature · 1 line
2.52.0
PATCH 4/18 X.509: Move certificate length retrieval into new helper
In reply to [email protected]From: Lukas Wunner <[email protected]>
The upcoming in-kernel SPDM library (Security Protocol and Data Model,
https://www.dmtf.org/dsp/DSP0274) needs to retrieve the length from ASN.1 DER-encoded X.509 certificates.
Such code already exists in x509_load_certificate_list(), so move it into a new helper for reuse by SPDM.
Export the helper so that SPDM can be tristate. (Some upcoming users of the SPDM libray may be modular, such as SCSI and ATA.)
No functional change intended.
--- crypto/asymmetric_keys/x509_loader.c | 38 +++++++++++++++++++--------- include/keys/asymmetric-type.h | 2 ++ 2 files changed, 28 insertions(+), 12 deletions(-)
crypto/asymmetric_keys/x509_loader.c +26 -12
@@ -4,28 +4,42 @@#include <linux/key.h> #include <keys/asymmetric-type.h> +ssize_t x509_get_certificate_length (const u8 *p,unsigned long buflen) +{ +ssize_t plen; + +/* Each cert begins with an ASN.1 SEQUENCE tag and must be more +* than 256 bytes in size. +*/ +if (buflen <4 ) +return -EINVAL ; + +if (p[0 ] !=0x30 && + p[1 ] !=0x82 ) +return -EINVAL ; + + plen = (p[2 ] <<8 ) | p[3 ]; + plen +=4 ; +if (plen > buflen) +return -EINVAL ; + +return plen; +} +EXPORT_SYMBOL_GPL (x509_get_certificate_length); +int x509_load_certificate_list (const u8 cert_list[],const unsigned long list_size,const struct key *keyring) {key_ref_t key;const u8 *p, *end; -size_t plen; +ssize_t plen; p = cert_list; end = p + list_size;while (p < end) { -/* Each cert begins with an ASN.1 SEQUENCE tag and must be more -* than 256 bytes in size. -*/ -if (end - p <4 ) - goto dodgy_cert; -if (p[0 ] !=0x30 && - p[1 ] !=0x82 ) - goto dodgy_cert; - plen = (p[2 ] <<8 ) | p[3 ]; - plen +=4 ; -if (plen > end - p) + plen =x509_get_certificate_length (p, end - p); +if (plen <0 ) goto dodgy_cert; key = key_create_or_update(make_key_ref(keyring, 1),
include/keys/asymmetric-type.h +2 -0
@@ -84,6 +84,8 @@ extern struct key *find_asymmetric_key(struct key *keyring,const struct asymmetric_key_id *id_2, bool partial); +ssize_t x509_get_certificate_length (const u8 *p,unsigned long buflen); +int x509_load_certificate_list (const u8 cert_list[],const unsigned long list_size,const struct key *keyring);
Signature · 1 line
2.52.0
[email protected]
AI reviewRe: [PATCH 04/18] X.509: Move certificate length retrieval into new helper
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…
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 <[email protected]>
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.
Quoted 11 lines
crypto/asymmetric_keys/x509_loader.c +3
-0@@ -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;
crypto/asymmetric_keys/x509_loader.c line 19
Quoted 10 lines
crypto/asymmetric_keys/x509_loader.c +10
-0+ +/* 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.
crypto/asymmetric_keys/x509_loader.c line 22
Quoted 3 lines
crypto/asymmetric_keys/x509_loader.c +3
-0+ + 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?
Quoted 6 lines
crypto/asymmetric_keys/x509_loader.c +6
-0+if (plen > buflen) +return -EINVAL ; + +return plen; +} +EXPORT_SYMBOL_GPL (x509_get_certificate_length);
[ ... ]
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=4
Lukas Wunner
maintainerRe: [PATCH 04/18] X.509: Move certificate length retrieval into new helper
Quoted 2 lines · On Fri, May 08, 2026 at 03:39:17AM +0000, [email protected] wrote:
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] The exported helperx509_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.
Fixed by:
https://lore.kernel.org/r/782bc59d5939aa69b58cad42f71946f1c0a6dccb.1778741457.git.lukas@wunner.de/
PATCH 5/18 rust: add bindings for hash.h
In reply to [email protected]From: Alistair Francis <[email protected]>
Make the functions crypto_shash_descsize(), crypto_shash_digestsize() and crypto_free_shash() available to Rust.
--- rust/bindings/bindings_helper.h | 2 ++ rust/helpers/hash.c | 18 ++++++++++++++++++ rust/helpers/helpers.c | 1 + 3 files changed, 21 insertions(+) create mode 100644 rust/helpers/hash.c
rust/bindings/bindings_helper.h +2 -0
@@ -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>
rust/helpers/hash.c new file +18 -0
@@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <crypto/hash.h> + +__rust_helper unsigned int rust_helper_crypto_shash_descsize(struct crypto_shash *tfm) +{ +return crypto_shash_descsize (tfm); +} + +__rust_helper unsigned int rust_helper_crypto_shash_digestsize(struct crypto_shash *tfm) +{ +return crypto_shash_digestsize (tfm); +} + +__rust_helper void rust_helper_crypto_free_shash(struct crypto_shash *tfm) +{ +crypto_free_shash (tfm); +}
rust/helpers/helpers.c +1 -0
@@ -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"
Signature · 1 line
2.52.0
[email protected]
AI reviewRe: [PATCH 05/18] rust: add bindings for hash.h
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…
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 <[email protected]>
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.
rust/bindings/bindings_helper.h line 35
Quoted 11 lines
rust/bindings/bindings_helper.h +1
-0@@ -30,6 +30,7 @@#include <linux/acpi.h> #include <linux/gpu_buddy.h> +#include <crypto/hash.h> #include <drm/drm_device.h> #include <drm/drm_drv.h>
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>.
[ ... ]
rust/bindings/bindings_helper.h line 65
Quoted 6 lines
rust/bindings/bindings_helper.h +1
-0@@ -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.
rust/helpers/helpers.c line 66
Quoted 12 lines
rust/helpers/helpers.c +1
-0@@ -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.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=5
PATCH 6/18 rust: error: impl From<FromBytesWithNulError> for Kernel Error
In reply to [email protected]From: Alistair Francis <[email protected]>
Implement From<FromBytesWithNulError> for the Kernel Error type
--- rust/kernel/error.rs | 8 ++++++++ rust/kernel/str.rs | 7 ------- 2 files changed, 8 insertions(+), 7 deletions(-)
rust/kernel/error.rs +8 -0
@@ -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.
rust/kernel/str.rs +0 -7
@@ -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 ()?
Signature · 1 line
2.52.0
[email protected]
AI reviewRe: [PATCH 06/18] rust: error: impl From<FromBytesWithNulError> for Kernel Error
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…
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 <[email protected]>
rust: error: impl From<FromBytesWithNulError> for Kernel Error
Implement From<FromBytesWithNulError> for the Kernel Error type.
rust/kernel/error.rs line 18
Quoted 12 lines
rust/kernel/error.rs +1
-0@@ -12,6 +12,7 @@ str::CStr, }; +use core::ffi::FromBytesWithNulError ;use core::num::NonZeroI32 ;use core::num::TryFromIntError ;use core::str::Utf8Error ;
This isn't a bug, but should this newly added import follow the kernel vertical import style documented in the general coding guidelines?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=6
PATCH 7/18 lib: rspdm: Initial commit of Rust SPDM
In reply to [email protected]From: Alistair Francis <[email protected]>
This is the initial commit of the Rust SPDM library. It is based on and compatible with the C SPDM library in the kernel (lib/spdm).
--- MAINTAINERS | 12 ++ include/linux/spdm.h | 39 ++++++ lib/Kconfig | 17 +++ lib/Makefile | 2 + lib/rspdm/Makefile | 10 ++ lib/rspdm/consts.rs | 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
MAINTAINERS +12 -0
@@ -24164,6 +24164,18 @@ M: Security Officers <[email protected]> S: Supported F: Documentation/process/security-bugs.rst +SECURITY PROTOCOL AND DATA MODEL (SPDM) +M: Jonathan Cameron <[email protected]> +M: Lukas Wunner <[email protected]> +M: Alistair Francis <[email protected]> +L: [email protected] +L: [email protected] +L: [email protected] +S: Maintained +T: git git://git.kernel.org/pub/scm/linux/kernel/git/devsec/spdm.git +F: include/linux/spdm.h +F: lib/rspdm/ + SECURITY SUBSYSTEM M: Paul Moore <[email protected]> M: James Morris <[email protected]>
include/linux/spdm.h new file +39 -0
@@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* +* DMTF Security Protocol and Data Model (SPDM) +* https://www.dmtf.org/dsp/DSP0274 +* +* Copyright (C) 2021-22 Huawei +* Jonathan Cameron <[email protected]> +* +* Copyright (C) 2022-24 Intel Corporation +*/ + +#ifndef _SPDM_H_ +#define _SPDM_H_ + +#include <linux/types.h> + +struct key ; +struct device ; +struct spdm_state ; +struct x509_certificate ; + +typedef ssize_t (spdm_transport )(void *priv,struct device *dev, +const void *request,size_t request_sz, +void *response,size_t response_sz); + +typedef int (spdm_validate )(struct device *dev,u8 slot, +struct x509_certificate *leaf_cert); + +struct spdm_state *spdm_create (struct device *dev,spdm_transport *transport, +void *transport_priv,u32 transport_sz, +spdm_validate *validate); + +int spdm_authenticate (struct spdm_state *spdm_state); + +void spdm_destroy (struct spdm_state *spdm_state); + +extern const struct attribute_group spdm_attr_group; + +#endif
lib/Kconfig +17 -0
@@ -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
lib/Makefile +2 -0
@@ -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/ #
lib/rspdm/Makefile new file +10 -0
@@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Rust implementation of the DMTF Security Protocol and Data Model (SPDM) +# https://www.dmtf.org/dsp/DSP0274 +# +# Copyright (C) 2024 Western Digital + +obj-$(CONFIG_RSPDM) += spdm.o + +spdm-y := lib.o
lib/rspdm/consts.rs new file +56 -0
56 lines not on this page · raw message
lib/rspdm/lib.rs new file +116 -0
116 lines not on this page · raw message
lib/rspdm/state.rs new file +235 -0
235 lines not on this page · raw message
lib/rspdm/validator.rs new file +73 -0
73 lines not on this page · raw message
rust/bindings/bindings_helper.h +2 -0
@@ -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>
rust/kernel/error.rs +2 -0
@@ -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.
Signature · 1 line
2.52.0
[email protected]
AI reviewRe: [PATCH 07/18] lib: rspdm: Initial commit of Rust SPDM
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…
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 <[email protected]>
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).
Quoted 5 lines
include/linux/spdm.h new file +0
-0
[ ... ]
include/linux/spdm.h line 35
Quoted 7 lines
include/linux/spdm.h new file +7
-0+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.
Quoted 8 lines
include/linux/spdm.h new file +4
-0+ +extern const struct attribute_group spdm_attr_group; + +#endif lib/Kconfig +0
-0
[ ... ]
lib/Kconfig config LWQ_TEST · line 597
Quoted 7 lines
lib/Kconfig +3
-0@@ -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.
Quoted 2 lines
lib/Kconfig +2
-0+ select CRYPTO + select KEYS
[ ... ]
Quoted 5 lines
lib/rspdm/consts.rs new file +0
-0
[ ... ]
lib/rspdm/consts.rs line 21
Quoted 7 lines
lib/rspdm/consts.rs new file +7
-0+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.
[ ... ]
lib/rspdm/consts.rs line 52
Quoted 4 lines
lib/rspdm/consts.rs new file +4
-0+impl core::fmt::LowerHex for SpdmErrorCode { +/// A debug print format for the SpdmSessionInfo struct +fn fmt (&self, f: &mut core::fmt::Formatter <'_>) -> core::fmt::Result { +writeln! (f,"{:#x}" , *selfas 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?
Quoted 9 lines
lib/rspdm/consts.rs new file +4
-0+ +Ok (()) + } +}lib/rspdm/lib.rs new file +0
-0
[ ... ]
lib/rspdm/lib.rs line 80
Quoted 3 lines
lib/rspdm/lib.rs new file +3
-0+#[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?
Quoted 5 lines
lib/rspdm/lib.rs new file +5
-0+ req: *mut c_void, + req_sz: usize, + rsp: *mut c_void, + rsp_sz: usize, +) -> isize {
[ ... ]
lib/rspdm/lib.rs line 116
Quoted 5 lines
lib/rspdm/lib.rs new file +5
-0+/// 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.
Quoted 5 lines
lib/rspdm/state.rs new file +0
-0
[ ... ]
lib/rspdm/state.rs line 193
Quoted 8 lines
lib/rspdm/state.rs new file +8
-0+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.
Quoted 15 lines
lib/rspdm/state.rs new file +15
-0+ +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)?;
lib/rspdm/state.rs line 218
Quoted 10 lines
lib/rspdm/state.rs new file +10
-0+ +if (lengthas usize ) < header_size { +return Ok (length);// Truncated response is handled by callers + } +if response.code ==SPDM_ERROR { +if lengthas usize >= core::mem::size_of ::<SpdmErrorRsp >() { +// SAFETY: The response buffer will be at least as large as +// `SpdmErrorRsp` so we can cast the buffer to `SpdmErrorRsp` which +// is a packed struct. + self.spdm_err (unsafe { &*(response_buf.as_ptr ()as *const SpdmErrorRsp ) })?;
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.
Quoted 4 lines
lib/rspdm/state.rs new file +4
-0+ }else { +return Err (EINVAL ); + } + }
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=7
PATCH 8/18 PCI/TSM: Support connecting to PCIe CMA devices
In reply to [email protected]From: Alistair Francis <[email protected]>
In the next patch we are going to add a PCIe CMA TSM driver, as such we need to ensure that is_pci_tsm_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.
--- include/linux/pci-tsm.h | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-)
include/linux/pci-tsm.h +10 -1
@@ -3,6 +3,7 @@#define __PCI_TSM_H#include <linux/mutex.h> #include <linux/pci.h> +#include <linux/pci-doe.h> #include <linux/sockptr.h> struct pci_tsm ;
@@ -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;
Signature · 1 line
2.52.0
Re: [PATCH 08/18] PCI/TSM: Support connecting to PCIe CMA devices
Quoted 10 lines · On Fri, May 8, 2026 at 1:18 PM <[email protected]> wrote:
From: Alistair Francis <[email protected]>
In the next patch we are going to add a PCIe CMA TSM driver, as such we need to ensure that
is_pci_tsm_pf0()will allow us to connect to CMA capable devices. These devices don't necessarily hasDEVCAP_TEEor IDE support.As such for Root Complex Integrated Endpoint (
PCI_EXP_TYPE_RC_END) we also check for the CMA DOE feature.
@Dan Williams this is the patch I really need your thoughts on.
The current upstream pci_tsm code only works if IDE or TDISP is supported, which isn't true for CMA support.
This patch works around that, but the more I think about it the hackier it is. I have a local change that reverts this and updatestsm.c to work with a DSM (->dsm_dev is NULL), but that doesn't feel right either. Do you have a better idea or how to enable a CMA TSM driver?
Alistair
Quoted 47 lines
Signed-off-by Alistair Francis <[email protected]>--- include/linux/pci-tsm.h | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-)include/linux/pci-tsm.h +10
-2@@ -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
Re: [PATCH 08/18] PCI/TSM: Support connecting to PCIe CMA devices
Quoted 15 lines · Alistair Francis
Quoted 10 lines · On Fri, May 8, 2026 at 1:18 PM <[email protected]> wrote:
From: Alistair Francis <[email protected]>
In the next patch we are going to add a PCIe CMA TSM driver, as such we need to ensure that
is_pci_tsm_pf0()will allow us to connect to CMA capable devices. These devices don't necessarily hasDEVCAP_TEEor IDE support.As such for Root Complex Integrated Endpoint (
PCI_EXP_TYPE_RC_END) we also check for the CMA DOE feature.@Dan Williams this is the patch I really need your thoughts on.
Quoted 8 lines
The current upstream
pci_tsmcode only works if IDE or TDISP is supported, which isn't true for CMA support.This patch works around that, but the more I think about it the hackier it is. I have a local change that reverts this and updates
tsm.cto work with a DSM (->dsm_devis NULL), but that doesn't feel right either. Do you have a better idea or how to enable a CMA TSM driver?
Gentle ping on this. Any thoughts here?
Alistair
Quoted 2 lines
Alistair
Re: [PATCH 08/18] PCI/TSM: Support connecting to PCIe CMA devices
Quoted 19 lines · Alistair Francis
Quoted 18 lines · Alistair Francis
Quoted 10 lines · On Fri, May 8, 2026 at 1:18 PM <[email protected]> wrote:
From: Alistair Francis <[email protected]>
In the next patch we are going to add a PCIe CMA TSM driver, as such we need to ensure that
is_pci_tsm_pf0()will allow us to connect to CMA capable devices. These devices don't necessarily hasDEVCAP_TEEor IDE support.As such for Root Complex Integrated Endpoint (
PCI_EXP_TYPE_RC_END) we also check for the CMA DOE feature.@Dan Williams this is the patch I really need your thoughts on.
The current upstream
pci_tsmcode only works if IDE or TDISP is supported, which isn't true for CMA support.
Quoted 7 lines
Quoted 5 lines
This patch works around that, but the more I think about it the hackier it is. I have a local change that reverts this and updates
tsm.cto work with a DSM (->dsm_devis NULL), but that doesn't feel right either. Do you have a better idea or how to enable a CMA TSM driver?Gentle ping on this. Any thoughts here?
Apologies for not replying to this sooner.
A Device Security Manager (DSM) at a minimum provides device evidence validation and retrieval (CMA).
So my only comments on this 08/18 patch would be why is:
pci_find_doe_mailbox(pdev, PCI_VENDOR_ID_PCI_SIG, PCI_DOE_FEATURE_CMA);
...limited to root complex integrated endpoints? That capability could appear anywhere.
No need for ->dsm_dev to be NULL, it reflects the truth that the device being connected also hosts the DOE mailbox.
Perhaps the discomfort arises from the fact that CMA can appear on any function while IDE and TDISP are limited to physical function0? I agree that needs some fixups to convert the "pf0" naming to "host". Where a pci_tsm_host is any device that knows how to speak any of CMA, IDE, or TDISP.
In the case where a device has CMA on multiple functions, ->connect() hould be allowed. It only comes into conflict if function0 supports TDISP and has claimed all the sub-functions before ->connect() is attempted on a sub-function. That seems a rare corner case.
PATCH 9/18 PCI/CMA: Add a PCI TSM CMA driver using SPDM
In reply to [email protected]From: Alistair Francis <[email protected]>
Component Measurement and Authentication (CMA, PCIe r6.2 sec 6.31) allows for measurement and authentication of PCIe devices. It is based on the Security Protocol and Data Model specification (SPDM,
https://www.dmtf.org/dsp/DSP0274).
CMA-SPDM in turn forms the basis for Integrity and Data Encryption (IDE, PCIe r6.2 sec 6.33) because the key material used by IDE is transmitted over a CMA-SPDM session.
As a first step, add support for authentication via a CMA TSM driver.
This was previously discusd here:
http://lore.kernel.org/[email protected]
By utilising a TSM driver we get a lot of the TSM driver probe policies "for free". Currently there is no mechanism to provide evidence to userspace, as the TSM system doesn't support that at the moment. That can be added later when support by TSM.
Credits: Jonathan wrote the original proof-of-concept for a CMA implementation. Lukas reworked that for upstream. Wilfred contributed fixes for issues discovered during testing. Alistair reworked it as a TSM driver.
--- MAINTAINERS | 1 + drivers/pci/Kconfig | 14 ++++ drivers/pci/Makefile | 2 + drivers/pci/cma.c | 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
MAINTAINERS +1 -0
@@ -24173,6 +24173,7 @@ L: [email protected] L: [email protected] S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/devsec/spdm.git +F: drivers/pci/cma.c F: include/linux/spdm.h F: lib/rspdm/
drivers/pci/Kconfig +14 -0
@@ -124,6 +124,20 @@ config PCI_ATS config PCI_IDE bool +config PCI_CMA + bool "Component Measurement and Authentication (CMA-SPDM)" + 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
drivers/pci/Makefile +2 -0
@@ -41,6 +41,8 @@ obj-$(CONFIG_PCI_NPEM) += npem.o obj-$(CONFIG_PCIE_TPH) += tph.o obj-$(CONFIG_CARDBUS) += setup-cardbus.o +obj-$(CONFIG_PCI_CMA) += cma.o + # Endpoint library must be initialized before its users obj-$(CONFIG_PCI_ENDPOINT) += endpoint/
drivers/pci/cma.c new file +141 -0
141 lines not on this page · raw message
drivers/pci/doe.c +0 -3
@@ -31,9 +31,6 @@#define PCI_DOE_FLAG_CANCEL 0#define PCI_DOE_FLAG_DEAD 1 -/* Max data object length is 2^18 dwords */ -#define PCI_DOE_MAX_LENGTH (1 << 18) -/** * struct pci_doe_mb - State for a single DOE mailbox *
include/linux/pci-doe.h +4 -0
@@ -19,6 +19,10 @@ struct pci_doe_mb;#define PCI_DOE_FEATURE_CMA 1#define PCI_DOE_FEATURE_SSESSION 2 +/* Max data object length is 2^18 dwords (including 2 dwords for header) */ +#define PCI_DOE_MAX_LENGTH (1 << 18) +#define PCI_DOE_MAX_PAYLOAD ((PCI_DOE_MAX_LENGTH - 2) * sizeof(u32)) +struct pci_doe_mb *pci_find_doe_mailbox (struct pci_dev *pdev,u16 vendor,u8 type);
Signature · 1 line
2.52.0
[email protected]
AI reviewRe: [PATCH 09/18] PCI/CMA: Add a PCI TSM CMA driver using SPDM
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]…
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 <[email protected]>
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.
Quoted 7 lines
drivers/pci/Kconfig +0
-0@@ -124,6 +124,20 @@ config PCI_ATS config PCI_IDE bool
drivers/pci/Kconfig config PCI_ATS · line 135
Quoted 10 lines
drivers/pci/Kconfig +9
-0+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?
Quoted 5 lines
drivers/pci/cma.c new file +0
-0
[ ... ]
drivers/pci/cma.c line 59
Quoted 7 lines
drivers/pci/cma.c new file +7
-0+/** +* 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.
Quoted 9 lines
drivers/pci/cma.c new file +9
-0+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)
drivers/pci/cma.c line 79
Quoted 10 lines
drivers/pci/cma.c new file +10
-0+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.
Quoted 4 lines
drivers/pci/cma.c new file +4
-0+ cma->pf0.base_tsm.tsm_dev = tsm_dev; + + cma->spdm =spdm_create (&pdev->dev, pci_doe_transport, doe, +PCI_DOE_MAX_PAYLOAD ,NULL );
[ ... ]
drivers/pci/cma.c line 107
Quoted 6 lines
drivers/pci/cma.c new file +6
-0+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.
Quoted 5 lines
drivers/pci/cma.c new file +5
-0+if (rc) +return rc; + +return 0 ; +}
[ ... ]
drivers/pci/cma.c line 126
Quoted 8 lines
drivers/pci/cma.c new file +8
-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?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=9
PATCH 10/18 PCI/CMA: Validate Subject Alternative Name in certificates
In reply to [email protected]From: Lukas Wunner <[email protected]>
PCIe r6.1 sec 6.31.3 stipulates requirements for Leaf Certificates presented by devices, in particular the presence of a Subject Alternative Name which encodes the Vendor ID, Device ID, Device Serial Number, etc.
This prevents a mismatch between the device identity in Config Space and the certificate. A device cannot misappropriate a certificate from a different device without also spoofing Config Space. As a corollary, it cannot dupe an arbitrary driver into binding to it. Only drivers which bind to the device identity in the Subject Alternative Name work (PCIe r6.1 sec 6.31 "Implementation Note: Overview of Threat Model").
The Subject Alternative Name is signed, hence constitutes a signed copy of a Config Space portion. It's the same concept as web certificates which contain a set of domain names in the Subject Alternative Name for identity verification.
Parse the Subject Alternative Name using a small ASN.1 module and validate its contents. The theory of operation is explained in a comment at the top of the newly inserted code.
This functionality is introduced in a separate commit on top of basic CMA-SPDM support to split the code into digestible, reviewable chunks.
The CMA OID added here is taken from the official OID Repository (it's not documented in the PCIe Base Spec):
https://oid-rep.orange-labs.fr/get/2.23.147
Side notes:
* PCIe r6.2 removes the spec language on the Subject Alternative Name. It still "requires the leaf certificate to include the information typically used by system software for device driver binding", but no longer specifies how that information is encoded into the certificate.
According to the editor of the PCIe Base Spec and the author of the CMA 1.1 ECN (which caused this change), FPGA cards which mutate their device identity at runtime (due to a firmware update) were thought as unable to satisfy the previous spec language. The Protocol Working Group could not agree on a better solution and therefore dropped the spec language entirely. They acknowledge that the requirement is now under-spec'd. Because products already exist which adhere to the Subject Alternative Name requirement per PCIe r6.1 sec 6.31.3, they recommended to "push through" and use it as the de facto standard.
The FPGA concerns are easily overcome by reauthenticating the device after a firmware update, either via sysfs or pci_cma_reauthenticate() (added by a subsequent commit).
* PCIe r6.1 sec 6.31.3 strongly recommends to verify that "the information provided in the Subject Alternative Name entry is signed by the vendor indicated by the Vendor ID." In other words, the root certificate on pci_cma_keyring which signs the device's certificate chain must have been created for a particular Vendor ID.
Unfortunately the spec neglects to define how the Vendor ID shall be encoded into the root certificate. So the recommendation cannot be implemented at this point and it is thus possible that a vendor signs device certificates of a different vendor.
* Instead of a Subject Alternative Name, Leaf Certificates may include "a Reference Integrity Manifest, e.g., see Trusted Computing Group" or "a pointer to a location where such a Reference Integrity Manifest can be obtained" (PCIe r6.1 sec 6.31.3).
A Reference Integrity Manifest contains "golden" measurements which can be compared to actual measurements retrieved from a device. It serves a different purpose than the Subject Alternative Name, hence it is unclear why the spec says only either of them is necessary. It is also unclear how a Reference Integrity Manifest shall be encoded into a certificate.
Hence ignore the Reference Integrity Manifest requirement.
--- drivers/pci/Makefile | 4 +- drivers/pci/cma.asn1 | 41 ++++++++++++ drivers/pci/cma.c | 123 ++++++++++++++++++++++++++++++++++- include/linux/oid_registry.h | 3 + 4 files changed, 169 insertions(+), 2 deletions(-) create mode 100644 drivers/pci/cma.asn1
drivers/pci/Makefile +3 -1
@@ -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/
drivers/pci/cma.asn1 new file +41 -0
41 lines not on this page · raw message
drivers/pci/cma.c +122 -1
143 lines not on this page · raw message
include/linux/oid_registry.h +3 -0
@@ -150,6 +150,9 @@ enum OID { OID_id_ml_dsa_65,/* 2.16.840.1.101.3.4.3.18 */ OID_id_ml_dsa_87,/* 2.16.840.1.101.3.4.3.19 */ +/* PCI */ +OID_CMA ,/* 2.23.147 */ + OID__NR };
Signature · 1 line
2.52.0
[email protected]
AI reviewRe: [PATCH 10/18] PCI/CMA: Validate Subject Alternative Name in certificates
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…
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 <[email protected]>
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.
Quoted 4 lines
drivers/pci/Makefile +0
-0
[ ... ]
drivers/pci/Makefile obj-$(CONFIG_PCI_NPEM) += npem.o · line 46
Quoted 4 lines
drivers/pci/Makefile +3
-1-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
Quoted 4 lines
drivers/pci/cma.c +0
-0
[ ... ]
drivers/pci/cma.c line 76
Quoted 12 lines
drivers/pci/cma.c +12
-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 ) {
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?
Quoted 8 lines
drivers/pci/cma.c +8
-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 ;
drivers/pci/cma.c line 94
Quoted 10 lines
drivers/pci/cma.c +10
-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?
drivers/pci/cma.c line 99
Quoted 5 lines
drivers/pci/cma.c +5
-0+ +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?
drivers/pci/cma.c line 107
Quoted 8 lines
drivers/pci/cma.c +8
-0+ + 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?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=10
Re: [PATCH 10/18] PCI/CMA: Validate Subject Alternative Name in certificates
Quoted 9 lines · On Fri, May 8, 2026 at 1:58 PM <[email protected]> wrote:
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]Kbuildlinkage failure when compiled as a loadable module
- [Medium] Chainingsnprintfwithout 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 --
+ Lukas
Quoted 27 lines
commit
72435325efe665bb825f47731ca5c0081b7acc14
Author: Lukas Wunner <[email protected]>
Subject: PCI/CMA: Validate Subject Alternative Name in certificatesThis 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.
Quoted 4 lines
drivers/pci/Makefile +0
-0[ ... ]
drivers/pci/Makefile obj-$(CONFIG_PCI_NPEM) += npem.o · line 46
Quoted 4 lines
drivers/pci/Makefile +3
-1-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.hIf
CONFIG_PCI_CMAis configured as a loadable module, willKbuildcompile two separate modules (cma.ko and cma.asn1.ko)?Because the ASN.1 compiler does not export the
cma_decodersymbol, would the cma.ko module fail to link during modpost with an undefined symbol error?
Quoted 5 lines
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
This seems correct
Quoted 22 lines
Quoted 5 lines
drivers/pci/cma.c +0
-0[ ... ]
drivers/pci/cma.c line 76
Quoted 12 lines
drivers/pci/cma.c +12
-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 ) {Does this perform a strict case-sensitive comparison of the Subject Alternative Name?
Quoted 3 lines
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?
This is incorrect though, as the case is specified in the PCI spec
Quoted 15 lines
Quoted 15 lines
drivers/pci/cma.c +14
-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;
Quoted 8 lines
drivers/pci/cma.c line 94
Quoted 4 lines
drivers/pci/cma.c +4
-0+ + 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).
This I'm not clear on. Do we need to call pci_bus_read_dev_vendor_id() to get updated values?
Quoted 7 lines
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.
This is true
Quoted 4 lines
If the inputs ever exceed the 79-byte
CMA_NAME_MAXbuffer (for instance, if a quirk mutatespdev->classto exceed 6 hex characters), the string will be truncated but len will reflect the untruncated size.
That's true, but I don't see how the fixed length format string could exceed the size.
So I don't think this is an actual issue, but...
Quoted 4 lines
Could this cause the later
memcmp()check inpci_cma_note_san()to read out of bounds of thectx->expected_namestack buffer? Shouldscnprintf()be used here instead to track the actual length written?
scnprintf() should be used instead, just in case.
Quoted 26 lines
drivers/pci/cma.c line 99
Quoted 6 lines
drivers/pci/cma.c +5
-0+ +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?
drivers/pci/cma.c line 107
Quoted 8 lines
drivers/pci/cma.c +8
-0+ + 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.
Quoted 5 lines
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?
I see your point, but I don't think so.
Quoted 10 lines
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?
Really?
Alistair
Quoted 3 lines
--
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=10
Lukas Wunner
maintainerRe: [PATCH 10/18] PCI/CMA: Validate Subject Alternative Name in certificates
Quoted 14 lines · Alistair Francis
Quoted 13 lines · On Fri, May 8, 2026 at 1:58 PM <[email protected]> wrote:
drivers/pci/Makefile obj-$(CONFIG_PCI_NPEM) += npem.o · line 46
Quoted 5 lines
+++ b/drivers/pci/Makefile
drivers/pci/Makefile +3
-1-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.hIf
CONFIG_PCI_CMAis configured as a loadable module, willKbuildcompile two separate modules (cma.ko and cma.asn1.ko)?Because the ASN.1 compiler does not export the
cma_decodersymbol, would the cma.ko module fail to link during modpost with an undefined symbol error?
Quoted 7 lines
Quoted 5 lines
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.oThis seems correct
Hallucination alert:
CONFIG_PCI_CMA is bool, so cannot be configured as a loadable module.
Quoted 4 lines
Quoted 4 lines
Quoted 3 lines
drivers/pci/cma.c +3
-0+if (tag !=ASN1_UTF8STR || + vlen != ctx->expected_len || +memcmp (value, ctx->expected_name, vlen) !=0 ) {
Quoted 8 lines
Quoted 6 lines
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?This is incorrect though, as the case is specified in the PCI spec
Right. Another hallucination.
Quoted 10 lines
Quoted 7 lines
drivers/pci/cma.c line 94
Quoted 3 lines
drivers/pci/cma.c +3
-0+ 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).This I'm not clear on. Do we need to call
pci_bus_read_dev_vendor_id()to get updated values?
If the device mutates its device identity in Config Space at runtime, e.g. as a result of an FPGA update or hot replacement, it is necessary to re-enumerate the device either through the standard hotplug mechanism or manually via sysfs. Otherwise authentication simply fails because of the mismatch between (cached) Config Space and Subject Alternative Name.
I think this is the correct thing to do. The alternative would be that we'd just carry on using the device normally even though the mismatch tells us something's not right.
pciehp_device_replaced() already checks whether device identity in (unsigned) Config Space has changed after resume from system sleep and after error recovery and automatically triggers a hot unplug event. CMA will make this check even more useful because we can trigger hot unplug also when the device was previously authenticated but now no longer is, e.g. because of a Subject Alternative Name mismatch.
Quoted 3 lines
Quoted 3 lines
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?
I think it doesn't really matter whether we use cached data or read it from Config Space, we just want to detect a mismatch with the Subject Alternative Name.
Quoted 11 lines
Quoted 2 lines
Additionally,
snprintf()returns the number of characters that would have been written, not the actual bytes written.This is true
Quoted 4 lines
If the inputs ever exceed the 79-byte
CMA_NAME_MAXbuffer (for instance, if a quirk mutatespdev->classto exceed 6 hex characters), the string will be truncated but len will reflect the untruncated size.That's true, but I don't see how the fixed length format string could exceed the size.
Right.
Quoted 5 lines
Quoted 3 lines
Could this cause the later
memcmp()check inpci_cma_note_san()to read out of bounds of thectx->expected_namestack buffer? Shouldscnprintf()be used here instead to track the actual length written?
scnprintf()should be used instead, just in case.
The assumption that pdev->class may exceed 6 hex characters is a hallucination as the 24-bit size is prescribed by the spec.
Should the expected name ever change, the CMA_NAME_MAX macro at the top of cma.c needs to be amended. So I don't really see the need for scnprintf().
Quoted 7 lines
Quoted 7 lines
drivers/pci/cma.c line 99
Quoted 4 lines
drivers/pci/cma.c +4
-0+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.
Hallucination.
Quoted 11 lines
Quoted 11 lines
drivers/pci/cma.c line 107
Quoted 7 lines
drivers/pci/cma.c +7
-0+ 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.
Quoted 7 lines
Quoted 5 lines
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?I see your point, but I don't think so.
PCIe r7.0 sec 7.9.3.2 defines the Device Serial Number as an EUI-64.
In the corresponding IEEE spec on EUIs on page 11 in section "Unassigned and NULL EUI values", it says:
"Many applications have found it useful to define a distinct null
identifier, most often indicating the absence of a valid EUI-48 or EUI-64 value. As an example, a null value might be the power-on state for an integrated circuit register, until the hardware or firmware initializes the register with a valid EUI.
[...]
The all-zeros EUI-48 value (00-00-00-00-00-00) and EUI-64 value (00-00-00- 00-00-00-00-00), though assigned to an organization, have not been and will not be used by that assignee as an EUI. (They can be considered as assigned to the IEEE Registration Authority.)"
https://standards.ieee.org/content/dam/ieee-standards/standards/web/documents/tutorials/eui.pdf
PCIe r6.1 sec 6.31.3 says the Device Serial Number shall be included in the Subject Alternative Name if it is "implemented". It is debatable whether it can be considered implemented if the vendor neglected to initialize it to a valid number, but I'm inclined to consider that "not implemented". The vendor made a mistake and signed an invalid Device Serial Number. That can't be considered a valid certificate then.
Quoted 11 lines
Quoted 9 lines
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?
Really?
Again, it doesn't matter whether the data is cached or read from Config Space. We just want to detect a mismatch with the Subject Alternative Name.
Thanks,
Lukas
PATCH 11/18 lib: rspdm: Support SPDM get_version
In reply to [email protected]From: Alistair Francis <[email protected]>
Support the GET_VERSION SPDM command.
--- lib/rspdm/consts.rs | 14 +++++++++ lib/rspdm/lib.rs | 8 ++++- lib/rspdm/state.rs | 61 ++++++++++++++++++++++++++++++++++++ lib/rspdm/validator.rs | 70 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 152 insertions(+), 1 deletion(-)
lib/rspdm/consts.rs +14 -0
@@ -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 ;
lib/rspdm/lib.rs +7 -1
@@ -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_ptras *mut SpdmState )) }; + +if let Err (e) = state.get_version () { +return e.to_errno ()as c_int ; + } +0 }
lib/rspdm/state.rs +61 -0
81 lines not on this page · raw message
lib/rspdm/validator.rs +70 -0
85 lines not on this page · raw message
Signature · 1 line
2.52.0
[email protected]
AI reviewRe: [PATCH 11/18] lib: rspdm: Support SPDM get_version
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…
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 <[email protected]>
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.
Quoted 4 lines
lib/rspdm/consts.rs +0
-0
[ ... ]
lib/rspdm/consts.rs fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { · line 70
Quoted 7 lines
lib/rspdm/consts.rs +3
-0@@ -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.
Quoted 4 lines
lib/rspdm/lib.rs +0
-0
[ ... ]
lib/rspdm/lib.rs line 109
Quoted 7 lines
lib/rspdm/lib.rs +2
-1@@ -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_ptras *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?
Quoted 4 lines
lib/rspdm/state.rs +0
-0
[ ... ]
Quoted 1 line
lib/rspdm/state.rs +0
-0@@ -232,4 +237,60 @@ pub(crate) fn spdm_exchange(
[ ... ]
Quoted 2 lines
lib/rspdm/state.rs +2
-0+pub (crate )fn get_version (&mut self) ->Result <(),Error > { +let mut request =GetVersionReq ::default ();
[ ... ]
lib/rspdm/state.rs pub(crate) fn spdm_exchange( · line 259
Quoted 5 lines
lib/rspdm/state.rs +5
-0+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()?
lib/rspdm/state.rs pub(crate) fn spdm_exchange( · line 265
Quoted 5 lines
lib/rspdm/state.rs +5
-0+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 (rcas 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?
lib/rspdm/state.rs pub(crate) fn spdm_exchange( · line 270
Quoted 4 lines
lib/rspdm/state.rs +4
-0+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.
lib/rspdm/state.rs pub(crate) fn spdm_exchange( · line 277
Quoted 6 lines
lib/rspdm/state.rs +6
-0+for iin 0 ..response.version_number_entry_count { +// Creating a reference on a packed struct will result in +// undefined behaviour, so we operate on the raw data directly +let addr = unaligned.wrapping_add (ias 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.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=11
PATCH 12/18 lib: rspdm: Support SPDM get_capabilities
In reply to [email protected]From: Alistair Francis <[email protected]>
Support the GET_CAPABILITIES SPDM command.
--- lib/rspdm/consts.rs | 19 ++++++++- lib/rspdm/lib.rs | 4 ++ lib/rspdm/state.rs | 76 ++++++++++++++++++++++++++++++++- lib/rspdm/validator.rs | 97 +++++++++++++++++++++++++++++++++++++++++- 4 files changed, 192 insertions(+), 4 deletions(-)
lib/rspdm/consts.rs +17 -2
@@ -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 ;
lib/rspdm/lib.rs +4 -0
@@ -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 }
lib/rspdm/state.rs +75 -1
107 lines not on this page · raw message
lib/rspdm/validator.rs +96 -1
107 lines not on this page · raw message
Signature · 1 line
2.52.0
[email protected]
AI reviewRe: [PATCH 12/18] lib: rspdm: Support SPDM get_capabilities
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.…
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 <[email protected]>
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.
Quoted 4 lines
lib/rspdm/state.rs +0
-0
[ ... ]
Quoted 4 lines
lib/rspdm/state.rs +0
-0@@ -293,4 +304,67 @@ pub(crate) fn get_version(&mut self) -> Result<(), Error> {Ok (()) }
lib/rspdm/state.rs pub(crate) fn get_version(&mut self) -> Result<(), Error> { · line 316
Quoted 10 lines
lib/rspdm/state.rs +10
-0+ +/// 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?
Quoted 8 lines
lib/rspdm/state.rs +8
-0+ _ => { + 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 >(), + )
lib/rspdm/state.rs pub(crate) fn get_version(&mut self) -> Result<(), Error> { · line 334
Quoted 10 lines
lib/rspdm/state.rs +10
-0+ } + }; + +// SAFETY: `request` is repr(C) and packed, so we can convert it to a slice +let request_buf =unsafe {from_raw_parts_mut (&mut requestas *mut _ as *mut u8 , req_sz) }; + +let mut response_vec:KVec <u8 > =KVec ::with_capacity (rsp_sz,GFP_KERNEL )?; +// SAFETY: `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?
lib/rspdm/state.rs pub(crate) fn get_version(&mut self) -> Result<(), Error> { · line 345
Quoted 11 lines
lib/rspdm/state.rs +11
-0+ +let rc = self.spdm_exchange (request_buf, response_buf)?; + +if rc < (rsp_szas i32 ) { +pr_err! ("Truncated capabilities response\n" ); +to_result (-(bindings::EIO as i32 ))?; + } + +// SAFETY: `rc` is the length of data read, which will be smaller +// or the same as the capacity of the vector +unsafe { response_vec.inc_len (rcas 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?
Quoted 6 lines
lib/rspdm/state.rs +6
-0+ +let response: &mut GetCapabilitiesRsp = +Untrusted ::new_mut (&mut response_vec).validate_mut ()?; + + self.rsp_caps = u32::from_le (response.flags); +if (self.rsp_caps &SPDM_RSP_MIN_CAPS ) !=SPDM_RSP_MIN_CAPS {
lib/rspdm/state.rs pub(crate) fn get_version(&mut self) -> Result<(), Error> { · line 361
Quoted 10 lines
lib/rspdm/state.rs +10
-0+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?
Quoted 14 lines
lib/rspdm/state.rs +8
-0+pr_err! ("Malformed capabilities response\n" ); +to_result (-(bindings::EPROTO as i32 ))?; + } + self.transport_sz = self.transport_sz.min (response.data_transfer_size); + } + +Ok (()) + } }lib/rspdm/validator.rs +0
-0
[ ... ]
Quoted 4 lines
lib/rspdm/validator.rs +0
-0@@ -141,3 +145,94 @@ fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err>Ok (rsp) } }
[ ... ]
Quoted 14 lines
lib/rspdm/validator.rs +14
-0+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 {
lib/rspdm/validator.rs fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> · line 227
Quoted 10 lines
lib/rspdm/validator.rs +10
-0+// 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 _iin 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?
Quoted 11 lines
lib/rspdm/validator.rs +11
-0+ } + +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) + } +}
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=12
PATCH 13/18 lib: rspdm: Support SPDM negotiate_algorithms
In reply to [email protected]From: Alistair Francis <[email protected]>
Support the NEGOTIATE_ALGORITHMS SPDM command.
--- lib/rspdm/consts.rs | 65 ++++++++++++- lib/rspdm/lib.rs | 18 +++- lib/rspdm/state.rs | 211 +++++++++++++++++++++++++++++++++++++++++ lib/rspdm/validator.rs | 115 +++++++++++++++++++++- 4 files changed, 404 insertions(+), 5 deletions(-)
lib/rspdm/consts.rs +62 -3
@@ -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 );
lib/rspdm/lib.rs +17 -1
@@ -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_ptras *mut SpdmState )) }; + +if let Some (desc) = &mut state.desc { +unsafe { + bindings::kfree (*descas *mut _ as *mut c_void ); + } + } + +unsafe { + bindings::crypto_free_shash (state.shash); + } +}
lib/rspdm/state.rs +211 -0
258 lines not on this page · raw message
lib/rspdm/validator.rs +114 -1
134 lines not on this page · raw message
Signature · 1 line
2.52.0
[email protected]
AI reviewRe: [PATCH 13/18] lib: rspdm: Support SPDM negotiate_algorithms
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…
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 <[email protected]>
lib: rspdm: Support SPDM negotiate_algorithms
This commit adds support for the NEGOTIATE_ALGORITHMS SPDM command.
Quoted 4 lines
lib/rspdm/lib.rs +0
-0
[ ... ]
Quoted 4 lines
lib/rspdm/lib.rs +0
-0@@ -123,4 +127,16 @@/// /// @spdm_state: SPDM session state #[export]
lib/rspdm/lib.rs line 138
Quoted 10 lines
lib/rspdm/lib.rs +9
-1-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_ptras *mut SpdmState )) }; + +if let Some (desc) = &mut state.desc { +unsafe { + bindings::kfree (*descas *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.
lib/rspdm/lib.rs line 142
Quoted 4 lines
lib/rspdm/lib.rs +4
-0+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?
Quoted 4 lines
lib/rspdm/state.rs +0
-0
[ ... ]
Quoted 1 line
lib/rspdm/state.rs +0
-0@@ -367,4 +427,155 @@ pub(crate) fn get_capabilities(&mut self) -> Result<(), Error> {
[ ... ]
lib/rspdm/state.rs pub(crate) fn get_capabilities(&mut self) -> Result<(), Error> { · line 492
Quoted 3 lines
lib/rspdm/state.rs +3
-0+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.
lib/rspdm/state.rs pub(crate) fn get_capabilities(&mut self) -> Result<(), Error> { · line 501
Quoted 9 lines
lib/rspdm/state.rs +9
-0+ +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.
[ ... ]
lib/rspdm/state.rs pub(crate) fn get_capabilities(&mut self) -> Result<(), Error> { · line 524
Quoted 12 lines
lib/rspdm/state.rs +12
-0+pub (crate )fn negotiate_algs (&mut self) ->Result <(),Error > { +let mut request =NegotiateAlgsReq ::default (); + request.version = self.version; + +if self.version >=SPDM_VER_12 && (self.rsp_caps &SPDM_KEY_EX_CAP ) ==SPDM_KEY_EX_CAP { + request.other_params_support =SPDM_OPAQUE_DATA_FMT_GENERAL ; + } + +let req_sz = core::mem::size_of ::<NegotiateAlgsReq >(); +let rsp_sz = core::mem::size_of ::<NegotiateAlgsRsp >(); + + request.length = req_szas u16 ;
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.
lib/rspdm/state.rs pub(crate) fn get_capabilities(&mut self) -> Result<(), Error> { · line 532
Quoted 8 lines
lib/rspdm/state.rs +8
-0+ +// SAFETY: `request` is repr(C) and packed, so we can convert it to a slice +let request_buf =unsafe {from_raw_parts_mut (&mut requestas *mut _ as *mut u8 , req_sz) }; + +let mut response_vec:KVec <u8 > =KVec ::with_capacity (rsp_sz,GFP_KERNEL )?; +// SAFETY: `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.
lib/rspdm/state.rs pub(crate) fn get_capabilities(&mut self) -> Result<(), Error> { · line 543
Quoted 11 lines
lib/rspdm/state.rs +11
-0+ +let rc = self.spdm_exchange (request_buf, response_buf)?; + +if rc < (rsp_szas i32 ) { +pr_err! ("Truncated capabilities response\n" ); +to_result (-(bindings::EIO as i32 ))?; + } + +// SAFETY: `rc` is the length of data read, which will be smaller +// then the capacity of the vector +unsafe { response_vec.inc_len (rcas 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?
[ ... ]
lib/rspdm/state.rs pub(crate) fn get_capabilities(&mut self) -> Result<(), Error> { · line 575
Quoted 7 lines
lib/rspdm/state.rs +7
-0+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.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=13
PATCH 14/18 lib: rspdm: Support SPDM get_digests
In reply to [email protected]From: Alistair Francis <[email protected]>
Support the GET_DIGESTS SPDM command.
--- lib/rspdm/consts.rs | 7 +++- lib/rspdm/lib.rs | 4 ++ lib/rspdm/state.rs | 85 +++++++++++++++++++++++++++++++++++++++++- lib/rspdm/validator.rs | 54 +++++++++++++++++++++++++++ 4 files changed, 147 insertions(+), 3 deletions(-)
lib/rspdm/consts.rs +5 -2
@@ -19,10 +19,11 @@pub (crate )const SPDM_VER_10 :u8 =0x10 ;pub (crate )const SPDM_VER_11 :u8 =0x11 ;pub (crate )const SPDM_VER_12 :u8 =0x12 ; -#[allow(dead_code)] pub (crate )const SPDM_VER_13 :u8 =0x13 ;pub (crate )const SPDM_VER_14 :u8 =0x14 ; +pub (crate )const SPDM_SLOTS :usize =8 ; +pub (crate )const SPDM_MIN_VER :u8 =SPDM_VER_10 ;pub (crate )const SPDM_MAX_VER :u8 =SPDM_VER_14 ;
@@ -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 )]
lib/rspdm/lib.rs +4 -0
@@ -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 }
lib/rspdm/state.rs +84 -1
137 lines not on this page · raw message
lib/rspdm/validator.rs +54 -0
63 lines not on this page · raw message
Signature · 1 line
2.52.0
[email protected]
AI reviewRe: [PATCH 14/18] lib: rspdm: Support SPDM get_digests
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]…
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 <[email protected]>
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.
Quoted 5 lines
lib/rspdm/lib.rs +0
-0
lib/rspdm/lib.rs line 128
Quoted 9 lines
lib/rspdm/lib.rs +4
-0@@ -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?
Quoted 4 lines
lib/rspdm/state.rs +0
-0
[ ... ]
Quoted 4 lines
lib/rspdm/state.rs +4
-0+pub (crate )fn get_digests (&mut self) ->Result <(),Error > { +let mut request =GetDigestsReq ::default (); + request.version = self.version; +
lib/rspdm/state.rs pub(crate) fn negotiate_algs(&mut self) -> Result<(), Error> { · line 613
Quoted 10 lines
lib/rspdm/state.rs +10
-0+let req_sz = core::mem::size_of ::<GetDigestsReq >(); +let rsp_sz = core::mem::size_of ::<GetDigestsRsp >() +SPDM_SLOTS * self.hash_len; + +// SAFETY: `request` is repr(C) and packed, so we can convert it to a slice +let request_buf =unsafe {from_raw_parts_mut (&mut requestas *mut _ as *mut u8 , req_sz) }; + +let mut response_vec:KVec <u8 > =KVec ::with_capacity (rsp_sz,GFP_KERNEL )?; +// SAFETY: `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?
lib/rspdm/state.rs pub(crate) fn negotiate_algs(&mut self) -> Result<(), Error> { · line 624
Quoted 11 lines
lib/rspdm/state.rs +11
-0+ +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 (lenas 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.
lib/rspdm/state.rs pub(crate) fn negotiate_algs(&mut self) -> Result<(), Error> { · line 631
Quoted 7 lines
lib/rspdm/state.rs +7
-0+ +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.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=14
PATCH 15/18 lib: rspdm: Support SPDM get_certificate
In reply to [email protected]From: Alistair Francis <[email protected]>
Support the GET_CERTIFICATE SPDM command.
--- lib/rspdm/consts.rs | 2 + lib/rspdm/lib.rs | 11 ++++ lib/rspdm/state.rs | 125 +++++++++++++++++++++++++++++++++++++++++ lib/rspdm/validator.rs | 57 +++++++++++++++++++ 4 files changed, 195 insertions(+)
lib/rspdm/consts.rs +2 -0
@@ -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 )]
lib/rspdm/lib.rs +11 -0
@@ -124,6 +124,17 @@return e.to_errno ()as c_int ; } +let mut provisioned_slots = state.provisioned_slots; +while (provisioned_slotsas usize ) >0 { +let slot = provisioned_slots.trailing_zeros ()as u8 ; + +if let Err (e) = state.get_certificate (slot) { +return e.to_errno ()as c_int ; + } + + provisioned_slots &= !(1 << slot); + } +0 }
lib/rspdm/state.rs +125 -0
141 lines not on this page · raw message
lib/rspdm/validator.rs +57 -0
66 lines not on this page · raw message
Signature · 1 line
2.52.0
[email protected]
AI reviewRe: [PATCH 15/18] lib: rspdm: Support SPDM get_certificate
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…
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 <[email protected]>
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.
Quoted 4 lines
lib/rspdm/state.rs +0
-0
[ ... ]
Quoted 4 lines
lib/rspdm/state.rs +0
-0@@ -661,4 +671,119 @@ pub(crate) fn get_digests(&mut self) -> Result<(), Error> {Ok (()) }
lib/rspdm/state.rs pub(crate) fn get_digests(&mut self) -> Result<(), Error> { · line 683
Quoted 10 lines
lib/rspdm/state.rs +10
-0+ +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.
lib/rspdm/state.rs pub(crate) fn get_digests(&mut self) -> Result<(), Error> { · line 687
Quoted 4 lines
lib/rspdm/state.rs +4
-0+ +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?
lib/rspdm/state.rs pub(crate) fn get_digests(&mut self) -> Result<(), Error> { · line 694
Quoted 7 lines
lib/rspdm/state.rs +7
-0+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 (rcas 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?
[ ... ]
lib/rspdm/state.rs pub(crate) fn get_digests(&mut self) -> Result<(), Error> { · line 718
Quoted 11 lines
lib/rspdm/state.rs +11
-0+pub (crate )fn get_certificate (&mut self, slot:u8 ) ->Result <(),Error > { +let mut request =GetCertificateReq ::default (); + request.version = self.version; + request.param1 = slot; + +let req_sz = core::mem::size_of ::<GetCertificateReq >(); +let rsp_sz = (core::mem::size_of ::<GetCertificateRsp >()as u32 + u16::MAX as u32 ) + .min (self.transport_sz)as usize ; + + request.offset =0 ; + request.length = (rsp_sz - core::mem::size_of ::<GetCertificateRsp >())as u16 ;
Are we missing a conversion to little-endian for request.length here?
[ ... ]
lib/rspdm/state.rs pub(crate) fn get_digests(&mut self) -> Result<(), Error> { · line 728
Quoted 4 lines
lib/rspdm/state.rs +4
-0+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?
[ ... ]
lib/rspdm/state.rs pub(crate) fn get_digests(&mut self) -> Result<(), Error> { · line 741
Quoted 2 lines
lib/rspdm/state.rs +2
-0+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?
[ ... ]
lib/rspdm/state.rs pub(crate) fn get_digests(&mut self) -> Result<(), Error> { · line 754
Quoted 4 lines
lib/rspdm/state.rs +4
-0+ 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?
[ ... ]
lib/rspdm/state.rs pub(crate) fn get_digests(&mut self) -> Result<(), Error> { · line 779
Quoted 12 lines
lib/rspdm/state.rs +12
-0+let header_length = core::mem::size_of ::<SpdmCertChain >() + self.hash_len; + +let ptr = certs_buf.as_mut_ptr (); +// SAFETY: `SpdmCertChain` is repr(C) and packed, so we can convert it from a slice +let ptr = ptr.cast ::<SpdmCertChain >(); +// SAFETY: `ptr` came from a reference and the cast above is valid. +let certs: &mut SpdmCertChain =unsafe { &mut *ptr }; + +if total_cert_len < header_length + || total_cert_lenas 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?
Quoted 4 lines
lib/rspdm/validator.rs +0
-0
[ ... ]
Quoted 8 lines
lib/rspdm/validator.rs +8
-0+impl Validate <&mut Unvalidated <KVec <u8 >>>for &mut GetCertificateRsp { +type Err =Error ; + +fn validate (unvalidated: &mut Unvalidated <KVec <u8 >>) ->Result <Self ,Self ::Err > { +let raw = unvalidated.raw_mut (); +if raw.len () < mem::size_of ::<GetCertificateRsp >() { +return Err (EINVAL ); + }
lib/rspdm/validator.rs fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> · line 462
Quoted 10 lines
lib/rspdm/validator.rs +10
-0+ +let ptr = raw.as_mut_ptr (); +// CAST: `GetCertificateRsp` only contains integers and has `repr(C)`. +let ptr = ptr.cast ::<GetCertificateRsp >(); +// SAFETY: `ptr` came from a reference and the cast above is valid. +let rsp: &mut GetCertificateRsp =unsafe { &mut *ptr }; + +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?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=15
PATCH 16/18 lib: rspdm: Support SPDM certificate validation
In reply to [email protected]From: Alistair Francis <[email protected]>
Support validating the SPDM certificate chain. This only performs basic sanity checks on the chain before we continue on. This does not ensure that the root CA is trusted, we leave that for userspace to check and enforce. Instead we just make sure that the chain is correct, uses supported signatures and that it isn't blacklisted in the kernel.
--- lib/rspdm/lib.rs | 17 ++++++++ lib/rspdm/state.rs | 69 ++++++++++++++++++++++++++++++++- rust/bindings/bindings_helper.h | 2 + 3 files changed, 87 insertions(+), 1 deletion(-)
lib/rspdm/lib.rs +17 -0
@@ -135,6 +135,17 @@ provisioned_slots &= !(1 << slot); } +let mut provisioned_slots = state.provisioned_slots; +while (provisioned_slotsas usize ) >0 { +let slot = provisioned_slots.trailing_zeros ()as u8 ; + +if let Err (e) = state.validate_cert_chain (slot) { +return e.to_errno ()as c_int ; + } + + provisioned_slots &= !(1 << slot); + } +0 }
@@ -145,6 +156,12 @@pub unsafe extern "C" fn spdm_destroy (state_ptr: *mut spdm_state ) {let state: &mut SpdmState =unsafe { &mut (*(state_ptras *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 (*descas *mut _ as *mut c_void );
lib/rspdm/state.rs +68 -1
91 lines not on this page · raw message
rust/bindings/bindings_helper.h +2 -0
@@ -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>
Signature · 1 line
2.52.0
[email protected]
AI reviewRe: [PATCH 16/18] lib: rspdm: Support SPDM certificate validation
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…
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 <[email protected]>
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.
Quoted 4 lines
lib/rspdm/state.rs +0
-0
[ ... ]
Quoted 6 lines
lib/rspdm/state.rs +2
-0@@ -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 > {
[ ... ]
lib/rspdm/state.rs pub(crate) fn get_certificate(&mut self, slot: u8) -> Result<(), Error> { · line 825
Quoted 12 lines
lib/rspdm/state.rs +12
-0+let cert_ptr =unsafe { +from_err_ptr (bindings::x509_cert_parse ( + &cert_chain_buf[offset..]as *const _ as *const c_void , + cert_lenas usize , + ))? + }; +let cert =unsafe { *cert_ptr }; + +if cert.unsupported_sig || cert.blacklisted { +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.
Quoted 10 lines
lib/rspdm/state.rs +10
-0+ +if let Some (prev) = prev_cert { +// Check against previous certificate +let rc =unsafe { bindings::public_key_verify_signature ((*prev).pub_, cert.sig) }; + +if rc <0 { +pr_err! ("Signature validation error\n" ); +to_result (rc)?; + } + }
[ ... ]
lib/rspdm/state.rs pub(crate) fn get_certificate(&mut self, slot: u8) -> Result<(), Error> { · line 851
Quoted 7 lines
lib/rspdm/state.rs +7
-0+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.
Quoted 4 lines
lib/rspdm/state.rs +4
-0+ } + +Ok (()) + }
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=16
PATCH 17/18 rust: allow extracting the buffer from a CString
In reply to [email protected]From: Alistair Francis <[email protected]>
The kernel CString is a wrapper aroud a KVec. This patch allows retrieving the underlying buffer and consuming the CString. This allows users to create a CString from a string and then retrieve the underlying buffer.
--- rust/kernel/str.rs | 6 ++++++ 1 file changed, 6 insertions(+)
rust/kernel/str.rs +6 -0
@@ -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 {
Signature · 1 line
2.52.0
PATCH 18/18 lib: rspdm: Support SPDM challenge
In reply to [email protected]From: Alistair Francis <[email protected]>
Support the CHALLENGE SPDM command.
--- lib/rspdm/consts.rs | 6 + lib/rspdm/lib.rs | 8 +- lib/rspdm/state.rs | 216 +++++++++++++++++++++++++++++++- lib/rspdm/validator.rs | 62 +++++++++ rust/bindings/bindings_helper.h | 1 + 5 files changed, 291 insertions(+), 2 deletions(-)
lib/rspdm/consts.rs +6 -0
@@ -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 ;
lib/rspdm/lib.rs +7 -1
@@ -135,17 +135,23 @@ provisioned_slots &= !(1 << slot); } +let mut verify =true ;let mut provisioned_slots = state.provisioned_slots;while (provisioned_slotsas usize ) >0 {let slot = provisioned_slots.trailing_zeros ()as u8 ;if let Err (e) = state.validate_cert_chain (slot) { -return e.to_errno ()as c_int ; +pr_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 }
lib/rspdm/state.rs +215 -1
327 lines not on this page · raw message
lib/rspdm/validator.rs +62 -0
71 lines not on this page · raw message
rust/bindings/bindings_helper.h +1 -0
@@ -31,6 +31,7 @@#include <linux/acpi.h> #include <linux/gpu_buddy.h> #include <crypto/hash.h> +#include <crypto/public_key.h> #include <drm/drm_device.h> #include <drm/drm_drv.h> #include <drm/drm_file.h>
Signature · 1 line
2.52.0
[email protected]
AI reviewRe: [PATCH 18/18] lib: rspdm: Support SPDM challenge
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]…
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 <[email protected]>
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.
Quoted 11 lines
lib/rspdm/lib.rs +1
-0@@ -135,17 +135,23 @@ provisioned_slots &= !(1 << slot); } +let mut verify =true ;let mut provisioned_slots = state.provisioned_slots;while (provisioned_slotsas usize ) >0 {
lib/rspdm/lib.rs line 149
Quoted 10 lines
lib/rspdm/lib.rs +2
-1let 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.
lib/rspdm/lib.rs line 156
Quoted 6 lines
lib/rspdm/lib.rs +4
-0+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.
Quoted 4 lines
lib/rspdm/state.rs +0
-0
[ ... ]
lib/rspdm/state.rs pub(crate) fn get_version(&mut self) -> Result<(), Error> { · line 403
Quoted 10 lines
lib/rspdm/state.rs +6
-0@@ -367,6 +395,12 @@ pub(crate) fn get_version(&mut self) -> Result<(), Error> {unsafe { response_vec.inc_len (rcas 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_countas 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.
[ ... ]
lib/rspdm/state.rs pub(crate) fn get_digests(&mut self) -> Result<(), Error> { · line 683
Quoted 8 lines
lib/rspdm/state.rs +4
-0@@ -637,6 +677,10 @@ pub(crate) fn get_digests(&mut self) -> Result<(), Error> {unsafe { response_vec.inc_len (lenas usize ) };let response: &mut GetDigestsRsp =Untrusted ::new_mut (&mut response_vec).validate_mut ()?; +let rsp_sz = core::mem::size_of ::<SpdmHeader >() + response.param2as 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?
[ ... ]
lib/rspdm/state.rs fn get_cert_exchange( · line 747
Quoted 8 lines
lib/rspdm/state.rs +4
-0@@ -697,6 +741,10 @@ fn get_cert_exchange(unsafe { response_vec.inc_len (rcas usize ) };let response: &mut GetCertificateRsp =Untrusted ::new_mut (response_vec).validate_mut ()?; +let rsp_sz = core::mem::size_of ::<SpdmHeader >() +4 + response.portion_lengthas 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?
[ ... ]
lib/rspdm/state.rs pub(crate) fn validate_cert_chain(&mut self, slot: u8) -> Result<(), Error> { · line 996
Quoted 9 lines
lib/rspdm/state.rs +9
-0+pub (crate )fn challenge (&mut self, slot:u8 , verify:bool ) ->Result <(),Error > { +let mut request =ChallengeReq ::default (); + request.version = self.version; + request.param1 = slot; + +let nonce_len = request.nonce.len (); + +if 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.
Quoted 12 lines
lib/rspdm/state.rs +12
-0+ self.next_nonce.clear (); + }else { +unsafe { + bindings::get_random_bytes (&mut request.nonceas *mut _ as *mut c_void , nonce_len) + }; + } + +let req_sz =if self.version <=0x12 { +offset_of! (ChallengeReq , context) + }else { + core::mem::size_of ::<ChallengeReq >() + };
lib/rspdm/state.rs pub(crate) fn validate_cert_chain(&mut self, slot: u8) -> Result<(), Error> { · line 1018
Quoted 10 lines
lib/rspdm/state.rs +10
-0+ +let rsp_sz = self.challenge_rsp_len (nonce_len,SPDM_MAX_OPAQUE_DATA ); + +// SAFETY: `request` is repr(C) and packed, so we can convert it to a slice +let request_buf =unsafe {from_raw_parts_mut (&mut requestas *mut _ as *mut u8 , req_sz) }; + +let mut response_vec:KVec <u8 > =KVec ::with_capacity (rsp_sz,GFP_KERNEL )?; +// SAFETY: `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?
Quoted 10 lines
lib/rspdm/state.rs +10
-0+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 (rcas usize ) };
lib/rspdm/state.rs pub(crate) fn validate_cert_chain(&mut self, slot: u8) -> Result<(), Error> { · line 1039
Quoted 10 lines
lib/rspdm/state.rs +10
-0+ +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)?
Quoted 4 lines
lib/rspdm/state.rs +4
-0+let rsp_sz = self.challenge_rsp_len (nonce_len, opaque_lenas usize ); + +if rc < rsp_szas i32 { +pr_err! ("Truncated challenge response\n" );
lib/rspdm/state.rs pub(crate) fn validate_cert_chain(&mut self, slot: u8) -> Result<(), Error> { · line 1054
Quoted 10 lines
lib/rspdm/state.rs +10
-0+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.
Quoted 5 lines
lib/rspdm/state.rs +5
-0+match self.verify_signature (signature) { +Ok (()) => { +pr_info! ("Authenticated with certificate slot {slot}" ); + self.authenticated =true ; + }
lib/rspdm/state.rs pub(crate) fn validate_cert_chain(&mut self, slot: u8) -> Result<(), Error> { · line 1070
Quoted 10 lines
lib/rspdm/state.rs +9
-0+ 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.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=18
Re: [PATCH 18/18] lib: rspdm: Support SPDM challenge
In reply to [email protected]Quoted 6 lines · On 08.05.26 05:17, [email protected] wrote:
From: Alistair Francis <[email protected]>
Support the CHALLENGE SPDM command.
Signed-off-by Alistair Francis <[email protected]>---
...
Quoted 8 lines
lib/rspdm/validator.rs +8
-0+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 ); + }
lib/rspdm/validator.rs fn validate(unvalidated: &mut Unvalidated<KVec<u8>>) -> Result<Self, Self::Err> · line 524
Quoted 10 lines
lib/rspdm/validator.rs +10
-0+ +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) + } +}
Just as reference, there is some discussion about this in
https://lore.kernel.org/rust-for-linux/2026051610-flint-compound-810f@gregkh/
Dirk