lib: Rust implementation of SPDM
Subsystems (9)
v4 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 done using the Validate and Untrusted traits. The validate() function has been updated updated to allow context which we use as part of the validation.
The validation has also swapped from a C style pointer cast to using a valid Rust struct and copying the data from the original buffer to the new struct. This obvious has some overhead, but as SPDM packets are relativly small, and not used as part of a constant hot path this seems fine. The big advantage is that we can validate everyting at once, convert the endianess when creating the struct and we get left with valid (not repr[c, packed]) Rust structs.
This makes the code a lot cleaner and more robust. It also removes a large number of unsafes and using get() for the arrays removes a large number of possible panics. Thanks for everyone at Kangrejos for the help with this!
I have kept Jonathon's reviews of the SPDM code, as the changes since the last vesion are mostly just mechanical, convertting the repr[c, packed] structs to copy structs.
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 pcie-root-port,chassis=1,id=pci.1 \ -device nvme,drive=mynvme,serial=deadbeef,spdm_port=2323,spdm_trans=doe,bus=pci.1 \ -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 ' echo tsm0 > /sys/bus/pci/devices/0000:01:00.0/tsm/connect ```
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]/
v4:
- Based on discussions at Kangrejos the following changes were made
- Add context to the validate functions
- Convert the validate() functions to be complete Rust structs and copy the data, instead of C style pointer casts
- Use ForeignOwnable trait for SpdmState
- Use get() for array accesses
- Address review comments
v3:
- Rebase on 7.3-rc1
v2:
- Tidy up the PCI/TSM function naming and is_pci_tsm_host()
- A large number of changes (mostly to the Rust code) based on Sashiko reviews
- This includes a few local runs of Sashiko
- This has fixed a few undefined behviour bugs in the Rust code
- Rebase on v4 of Benno's validate patches (patch 1, 2, 3)
v1:
- Add CMA as a TSM driver
- Initial support for SPDM 1.4
- Cleanup a range of comments and concerns from RFC
- Remove kernel keyring checks
RFC v3:
- Use netlink to send information to userspace
- Don't autogenerate Rust helpers
RFC v2:
- Drop support for Rust and C implementations
- Include patches from Lukas to reduce series deps
- Large code cleanups based on more testing
- Support for authentication
Alistair Francis (15):
virt: coco: change tsm_register() to use a const struct
rust: add bindings for hash.h
rust: error: impl From<FromBytesWithNulError> for Kernel Error
lib: rspdm: Initial commit of Rust SPDM
PCI/TSM: Rename pf0 to host
PCI/TSM: Support connecting to PCIe CMA devices PCI/CMA: Add a PCI TSM CMA driver using SPDM
lib: rspdm: Support SPDM get_version
lib: rspdm: Support SPDM get_capabilities
lib: rspdm: Support SPDM negotiate_algorithms
lib: rspdm: Support SPDM get_digests
lib: rspdm: Support SPDM get_certificate
lib: rspdm: Support SPDM certificate validation
rust: allow extracting the buffer from a CString
lib: rspdm: Support SPDM challenge
Benno Lossin (3):
rust: transmute: add cast_slice[_mut] functions
rust: create basic untrusted data API
rust: validate: add Validate trait
Lukas Wunner (4):
X.509: Make certificate parser public
X.509: Parse Subject Alternative Name in certificates X.509: Move certificate length retrieval into new helper PCI/CMA: Validate Subject Alternative Name in certificates
MAINTAINERS | 13 + crypto/asymmetric_keys/x509_cert_parser.c | 9 + crypto/asymmetric_keys/x509_loader.c | 38 +- crypto/asymmetric_keys/x509_parser.h | 42 +- drivers/crypto/ccp/sev-dev-tio.h | 4 +- drivers/crypto/ccp/sev-dev-tsm.c | 14 +- drivers/pci/Kconfig | 14 + drivers/pci/Makefile | 5 + drivers/pci/cma.asn1 | 41 + drivers/pci/cma.c | 271 +++++ drivers/pci/doe.c | 6 +- drivers/pci/tsm.c | 122 ++- drivers/virt/coco/tsm-core.c | 4 +- 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 | 48 +- include/linux/pci.h | 1 + include/linux/spdm.h | 37 + include/linux/tsm.h | 2 +- lib/Kconfig | 17 + lib/Makefile | 2 + lib/rspdm/Makefile | 10 + lib/rspdm/consts.rs | 186 ++++ lib/rspdm/lib.rs | 160 +++ lib/rspdm/state.rs | 1121 +++++++++++++++++++++ lib/rspdm/validator.rs | 1091 ++++++++++++++++++++ rust/bindings/bindings_helper.h | 5 + rust/helpers/hash.c | 18 + rust/helpers/helpers.c | 1 + rust/kernel/error.rs | 18 +- rust/kernel/lib.rs | 1 + rust/kernel/str.rs | 13 +- rust/kernel/transmute.rs | 71 +- rust/kernel/validate.rs | 229 +++++ 36 files changed, 3545 insertions(+), 135 deletions(-) create mode 100644 drivers/pci/cma.asn1 create mode 100644 drivers/pci/cma.c create mode 100644 include/keys/x509-parser.h create mode 100644 include/linux/spdm.h create mode 100644 lib/rspdm/Makefile create mode 100644 lib/rspdm/consts.rs create mode 100644 lib/rspdm/lib.rs create mode 100644 lib/rspdm/state.rs create mode 100644 lib/rspdm/validator.rs create mode 100644 rust/helpers/hash.c create mode 100644 rust/kernel/validate.rs
Signature · 1 line
2.55.0
PATCH v4 1/22 virt: coco: change tsm_register() to use a const struct
From: Alistair Francis <[email protected]>
The tsm_register() function takes a struct pci_tsm_ops *ops argument. This eventually is stored in struct tsm_dev as a const, so we should just change the tsm_register() register function argument to be a const.
--- drivers/crypto/ccp/sev-dev-tsm.c | 2 +- drivers/virt/coco/tsm-core.c | 4 ++-- include/linux/tsm.h | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-)
drivers/crypto/ccp/sev-dev-tsm.c +1 -1
@@ -339,7 +339,7 @@ static void dsm_disconnect(struct pci_dev *pdev)streams_teardown (dev_data->ide); } -static struct pci_tsm_ops sev_tsm_ops = { +static const struct pci_tsm_ops sev_tsm_ops = { .probe = dsm_probe, .remove = dsm_remove, .connect = dsm_connect,
drivers/virt/coco/tsm-core.c +2 -2
@@ -57,7 +57,7 @@ static struct tsm_dev *alloc_tsm_dev(struct device *parent) }static struct tsm_dev *tsm_register_pci_or_reset (struct tsm_dev *tsm_dev, -struct pci_tsm_ops *pci_ops) +const struct pci_tsm_ops *pci_ops) {int rc;
@@ -78,7 +78,7 @@ static struct tsm_dev *tsm_register_pci_or_reset(struct tsm_dev *tsm_dev,return tsm_dev; } -struct tsm_dev *tsm_register (struct device *parent,struct pci_tsm_ops *pci_ops) +struct tsm_dev *tsm_register (struct device *parent,const struct pci_tsm_ops *pci_ops) { struct tsm_dev *tsm_dev __free(put_tsm_dev) = alloc_tsm_dev(parent);struct device *dev;
include/linux/tsm.h +1 -1
@@ -120,7 +120,7 @@ DEFINE_FREE(put_tsm_dev, struct tsm_dev *,int tsm_report_register (const struct tsm_report_ops *ops,void *priv);int tsm_report_unregister (const struct tsm_report_ops *ops); -struct tsm_dev *tsm_register (struct device *parent,struct pci_tsm_ops *ops); +struct tsm_dev *tsm_register (struct device *parent,const struct pci_tsm_ops *ops);void tsm_unregister (struct tsm_dev *tsm_dev);struct tsm_dev *find_tsm_dev (int id);#endif /* __TSM_H */
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v4 01/22] virt: coco: change tsm_register() to use a const struct
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 7 lines
From: Alistair Francis <[email protected]>
The
tsm_register()function takes a structpci_tsm_ops*ops argument. This eventually is stored in structtsm_devas a const, so we should just change thetsm_register()register function argument to be a const.Signed-off-by Alistair Francis <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
PATCH v4 2/22 rust: transmute: add `cast_slice[_mut]` functions
In reply to [email protected]From: Benno Lossin <[email protected]>
Add functions to make casting slices only one unsafe block.
Message-ID: <[email protected]>
--- rust/kernel/transmute.rs | 71 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-)
rust/kernel/transmute.rs +70 -1
@@ -2,7 +2,10 @@//! Traits for transmuting types. -use core::mem::size_of; +use core::{ + mem::size_of, + slice,// +};/// Types for which any bit pattern is valid. ///
@@ -227,3 +230,69 @@ macro_rules! impl_asbytes { {<T: AsBytes>} [T], {<T: AsBytes, const N: usize>} [T; N], } + +/// Casts the type of a slice to another. +/// +/// Also see [`cast_slice_mut`]. +/// +/// # Examples +/// +/// ```rust +/// # use kernel::transmute::cast_slice; +/// #[repr(transparent)] +/// #[derive(Debug)] +/// struct Container<T>(T); +/// +/// let array = [0u32; 42]; +/// let slice = &array; +/// // SAFETY: `Container<u32>` transparently wraps a `u32`. +/// let container_slice = unsafe { cast_slice::<u32, Container<u32>>(slice) }; +/// pr_info!("{container_slice:?}"); +/// ``` +/// +/// # Safety +/// +/// - `T` and `U` must have the same layout. +/// - Every bit pattern that is valid for `T` must also be valid for `U`. +/// - Neither `T` nor `U` may have interior mutability. +#[inline] +pub unsafe fn cast_slice <T ,U >(slice: &[T ]) -> &[U ] { +// CAST: by the safety requirements, `T` and `U` have the same layout. +let ptr = slice.as_ptr ().cast ::<U >(); +// SAFETY: `ptr` and `len` come from the same slice reference. +unsafe { slice::from_raw_parts (ptr, slice.len ()) } +} + +/// Casts the type of a slice to another. +/// +/// Also see [`cast_slice`]. +/// +/// # Examples +/// +/// ```rust +/// # use kernel::transmute::cast_slice_mut; +/// #[repr(transparent)] +/// #[derive(Debug)] +/// struct Container<T>(T); +/// +/// let mut array = [0u32; 42]; +/// let slice = &mut array; +/// // SAFETY: `Container<u32>` transparently wraps a `u32`. +/// let container_slice = unsafe { cast_slice_mut::<u32, Container<u32>>(slice) }; +/// pr_info!("{container_slice:?}"); +/// ``` +/// +/// # Safety +/// +/// - `T` and `U` must have the same layout. +/// - Every bit pattern that is valid for `T` must also be valid for `U`, and +/// vice versa, so that values written through the returned slice are again +/// valid for `T`. +/// - Neither `T` nor `U` may have interior mutability. +#[inline] +pub unsafe fn cast_slice_mut <T ,U >(slice: &mut [T ]) -> &mut [U ] { +// CAST: by the safety requirements, `T` and `U` have the same layout. +let ptr = slice.as_mut_ptr ().cast ::<U >(); +// SAFETY: `ptr` and `len` come from the same slice reference. +unsafe { slice::from_raw_parts_mut (ptr, slice.len ()) } +}
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v4 02/22] rust: transmute: add `cast_slice[_mut]` functions
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 7 lines
From: Benno Lossin <[email protected]>
Add functions to make casting slices only one
unsafeblock.Signed-off-by Benno Lossin <[email protected]>Message-ID: <[email protected]>
Signed-off-by Alistair Francis <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
Alexandre Courbot
reviewerRe: [PATCH v4 02/22] rust: transmute: add `cast_slice[_mut]` functions
In reply to [email protected]Quoted 10 lines · On Mon Sep 28, 2026 at 10:11 AM JST, alistair23 wrote:
From: Benno Lossin <[email protected]>
Add functions to make casting slices only one
unsafeblock.Signed-off-by Benno Lossin <[email protected]>Message-ID: <[email protected]>
Signed-off-by Alistair Francis <[email protected]>--- rust/kernel/transmute.rs | 71 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-)
transmute.rs has been made obsolete by the introduction of zerocopy, so I dont think the timing is right to extend it, especially since the new methods allow to basically cast any slice into any other without any constraint. Thankfully I don't think we need this at all: the only users for these new methods are the Deref implementations in patch 3, and I believe these can be implemented better (I'll comment there).
So basically I think this patch can be dropped entirely.
PATCH v4 3/22 rust: create basic untrusted data API
In reply to [email protected]From: Benno Lossin <[email protected]>
When the kernel receives external data (e.g. from userspace), it usually is a very bad idea to directly use the data for logic decision in the kernel. For this reason, such data should be explicitly marked and validated before making decision based on its value.
The Untrusted<T> wrapper type marks a value of type T as untrusted. The particular meaning of "untrusted" highly depends on the type T. For example T = u8 ensures that the value of the byte cannot be retrieved. However, T = [u8] still allows to access the length of the slice. Similarly, T = KVec<U> allows modifications.
Message-ID: <[email protected]>
--- rust/kernel/lib.rs | 1 + rust/kernel/validate.rs | 147 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 148 insertions(+) create mode 100644 rust/kernel/validate.rs
rust/kernel/lib.rs +1 -0
@@ -143,6 +143,7 @@pub mod uaccess;#[cfg( CONFIG_USB = "y" )] pub mod usb; +pub mod validate;pub mod workqueue;pub mod xarray;
rust/kernel/validate.rs new file +147 -0
147 lines not on this page · raw message
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v4 03/22] rust: create basic untrusted data API
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: - [High] rust: validate: Untrusted Deref implementations violate cast_slice safety contract - [Low] rust…
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] rust: validate: Untrusted Deref implementations violate cast_slice safety contract
- [Low] rust: validate: typo in module documentation
- [Low] rust: validate: imports do not follow vertical style
--
commit faffe058082273ce8374694a0c1cc53da4578ae7
Author: Benno Lossin <[email protected]>
rust: create basic untrusted data API
This commit introduces the Untrusted<T> wrapper type to explicitly mark data originating from untrusted sources, such as userspace. It allows validating the data before making logical decisions based on its value.
Quoted 9 lines
rust/kernel/validate.rs new file +3
-0@@ -0,0 +1,147 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Untrusted data API.
[ ... ]
Quoted 1 line
+use core::ops::{Deref, DerefMut};
[Severity: Low]
Does this import follow the vertical import style? The subsystem guidelines request following the vertical style for imports.
Quoted 2 lines
rust/kernel/validate.rs new file +2
-0+ +use crate ::{
[ ... ]
Quoted 1 line
+/// The reason for this is that
&mut Untrusted<[u8]>can beconverted into&mut [Untrusted<u8>]
[Severity: Low]
There appears to be a typo here ("beconverted" instead of "be converted").
Quoted 1 line
+/// very easily, but the converse is not possible.
[ ... ]
rust/kernel/validate.rs line 119
Quoted 7 lines
rust/kernel/validate.rs new file +7
-0+impl <T >Deref for Untrusted <[T ]> { +type Target = [Untrusted <T >]; + +#[inline] +fn deref (&self) -> &Self ::Target { +// SAFETY: `Untrusted<T>` transparently wraps `T`. +unsafe {cast_slice (&self.0 ) }
[Severity: High]
Does this violate the safety contract of cast_slice? The documented safety precondition of kernel::transmute::cast_slice requires that neither T nor U have interior mutability. However, Untrusted::new and this Deref implementation don't place any trait bounds on T preventing interior mutability, which could allow safely constructing and dereferencing something like Untrusted<Vec<Cell<u8>>>.
rust/kernel/validate.rs line 127
Quoted 8 lines
rust/kernel/validate.rs new file +8
-0+ } +} + +impl <T >DerefMut for Untrusted <[T ]> { +#[inline] +fn deref_mut (&mut self) -> &mut Self ::Target { +// SAFETY: `Untrusted<T>` transparently wraps `T`. +unsafe {cast_slice_mut (&mut self.0 ) }
[Severity: High]
Similarly, does this violate the safety contract of cast_slice_mut by lacking a check against interior mutability?
rust/kernel/validate.rs line 137
Quoted 10 lines
rust/kernel/validate.rs new file +10
-0+ } +} + +impl <T ,A :Allocator >Deref for Untrusted <Vec <T ,A >> { +type Target = [Untrusted <T >]; + +#[inline] +fn deref (&self) -> &Self ::Target { +// SAFETY: `Untrusted<T>` transparently wraps `T`. +unsafe {cast_slice (self.0 .as_slice ()) }
[Severity: High]
Does this violate the interior mutability ban for cast_slice?
rust/kernel/validate.rs line 145
Quoted 8 lines
rust/kernel/validate.rs new file +8
-0+ } +} + +impl <T ,A :Allocator >DerefMut for Untrusted <Vec <T ,A >> { +#[inline] +fn deref_mut (&mut self) -> &mut Self ::Target { +// SAFETY: `Untrusted<T>` transparently wraps `T`. +unsafe {cast_slice_mut (self.0 .as_mut_slice ()) }
[Severity: High]
Does this violate the interior mutability ban for cast_slice_mut?
Quoted 2 lines
rust/kernel/validate.rs new file +2
-0+ } +}
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=3
Alexandre Courbot
reviewerRe: [PATCH v4 03/22] rust: create basic untrusted data API
In reply to [email protected]On Mon Sep 28, 2026 at 10:11 AM JST, alistair23 wrote:
<...>
Quoted 16 lines
rust/kernel/validate.rs new file +16
-0+/// Untrusted data of type `T`. +/// +/// Data coming from userspace is considered untrusted and should be marked by this type. +/// +/// The particular meaning of [`Untrusted<T>`] depends heavily on the type `T`. For example, +/// `&Untrusted<[u8]>` is a reference to an untrusted slice. But the length is not considered +/// untrusted, as it would otherwise violate normal Rust rules. For this reason, one can easily +/// convert that reference to `&[Untrusted<u8>]`. Another such example is `Untrusted<KVec<T>>`, it +/// derefs to `[Untrusted<T>]`. Raw bytes however do not behave in this way, `Untrusted<u8>` is +/// totally opaque. +/// +/// # Usage in API Design +/// +/// The exact location where to put [`Untrusted`] depends on the kind of API. When asking for an +/// untrusted input value, or buffer to write to, always move the [`Untrusted`] wrapper as far +/// inwards as possible:
rust/kernel/validate.rs line 71
Quoted 10 lines
rust/kernel/validate.rs new file +10
-0+/// +/// ```ignore +/// // use this +/// pub fn read_from_userspace(buf: &mut [Untrusted<u8>]) { todo!() } +/// +/// // and not this +/// pub fn read_from_userspace(buf: &mut Untrusted<[u8]>) { todo!() } +/// ``` +/// +/// The reason for this is that `&mut Untrusted<[u8]>` can beconverted into `&mut [Untrusted<u8>]`
nit: missing space: "can be converted"
Quoted 29 lines
rust/kernel/validate.rs new file +29
-0+/// very easily, but the converse is not possible. +/// +/// For the same reason, when returning untrusted data by-value, one should move the [`Untrusted`] +/// wrapper as far outward as possible: +/// +/// ```ignore +/// // use this +/// pub fn read_all_from_userspace() -> Untrusted<KVec<u8>> { todo!() } +/// +/// // and not this +/// pub fn read_all_from_userspace() -> KVec<Untrusted<u8>> { todo!() } +/// ``` +/// +/// Here too the reason is that `KVec<Untrusted<u8>>` is more restrictive compared to +/// `Untrusted<KVec<u8>>`. +#[repr(transparent)] +pub struct Untrusted <T : ?Sized >(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.
rust/kernel/validate.rs line 110
Quoted 10 lines
rust/kernel/validate.rs new file +10
-0+/// Untrusted::new(unsafe { bindings::read_foo_info() }) +/// } +/// ``` +#[inline] +pub fn new (value:T ) ->Self +where +T :Sized , + { +Self (value) + }
This constructor is probably useful (although it should probably be a From<T> implementation?), but is also moves value, which might perform a copy on the stack that we will want to avoid if T is big.
So in addition to it, can we define constructors that work on references:
pub fn from_ref(r: &T) -> &Self;
pub fn from_mut(r: &mut T) -> &mut Self;So we can pass around data to be validated without performing a copy.
While we are at it, we can also add slice variants:
pub const fn from_slice(s: &[T]) -> &[Self];
pub const fn from_mut_slice(s: &mut [T]) -> &mut [Self];They will also be useful for the Deref implementations below.
rust/kernel/validate.rs line 119
Quoted 9 lines
rust/kernel/validate.rs new file +9
-0+} + +impl <T >Deref for Untrusted <[T ]> { +type Target = [Untrusted <T >]; + +#[inline] +fn deref (&self) -> &Self ::Target { +// SAFETY: `Untrusted<T>` transparently wraps `T`. +unsafe {cast_slice (&self.0 ) }
With from_slice above this can become:
Untrusted::from_slice(&self.0)
rust/kernel/validate.rs line 127
Quoted 8 lines
rust/kernel/validate.rs new file +8
-0+ } +} + +impl <T >DerefMut for Untrusted <[T ]> { +#[inline] +fn deref_mut (&mut self) -> &mut Self ::Target { +// SAFETY: `Untrusted<T>` transparently wraps `T`. +unsafe {cast_slice_mut (&mut self.0 ) }
And this becomes:
Untrusted::from_mut_slice(&mut self.0)
Quoted 10 lines
rust/kernel/validate.rs new file +10
-0+ } +} + +impl <T ,A :Allocator >Deref for Untrusted <Vec <T ,A >> { +type Target = [Untrusted <T >]; + +#[inline] +fn deref (&self) -> &Self ::Target { +// SAFETY: `Untrusted<T>` transparently wraps `T`. +unsafe {cast_slice (self.0 .as_slice ()) }
rust/kernel/validate.rs line 147
Quoted 10 lines
rust/kernel/validate.rs new file +10
-0+ } +} + +impl <T ,A :Allocator >DerefMut for Untrusted <Vec <T ,A >> { +#[inline] +fn deref_mut (&mut self) -> &mut Self ::Target { +// SAFETY: `Untrusted<T>` transparently wraps `T`. +unsafe {cast_slice_mut (self.0 .as_mut_slice ()) } + } +}
These can also be implemented identically to the ones above.
PATCH v4 4/22 rust: validate: add `Validate` trait
In reply to [email protected]From: Benno Lossin <[email protected]>
Introduce the Validate<Input> trait and functions to validate Untrusted<T> using said trait. This allows one to access the inner value of Untrusted<T> via validate{,_ref,_mut} functions which subsequently delegate the validation to user-implemented Validate trait.
The Validate trait is the only entry point for validation code, making it easy to spot where data is being validated.
The reason for restricting the types that can be inputs to Validate::validate is to be able to have the validate... functions on Untrusted. This is also the reason for the suggestions in the Usage in API Design section in the commit that introduced Untrusted<T>.
The Validate::validate() function allows additional context to be passed, which can be used as part of the validation process. For example comparing data against already negotiated information.
Message-ID: <[email protected]>
--- rust/kernel/validate.rs | 84 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 83 insertions(+), 1 deletion(-)
rust/kernel/validate.rs +83 -1
@@ -11,6 +11,9 @@//! APIs that write back into userspace usually allow writing untrusted bytes directly, allowing //! direct copying of untrusted user data back into userspace without validation. //! +//! The only way to access untrusted data is to [`Validate::validate`] it. This is facilitated by +//! the [`Validate`] trait. +//! //! # Rationale //! //! When reading data from an untrusted source, it must be validated before it can be used for
@@ -52,7 +55,7 @@/// untrusted, as it would otherwise violate normal Rust rules. For this reason, one can easily /// convert that reference to `&[Untrusted<u8>]`. Another such example is `Untrusted<KVec<T>>`, it /// derefs to `[Untrusted<T>]`. Raw bytes however do not behave in this way, `Untrusted<u8>` is -/// totally opaque. +/// totally opaque and one can only access its value by calling [`Untrusted::validate()`]. /// /// # Usage in API Design ///
@@ -108,6 +111,39 @@ pub fn new(value: T) -> Self {Self (value) } + +/// Validate the underlying untrusted data. +/// +/// See the [`Validate`] trait for more information. +#[inline] +pub fn validate <'c,V :Validate <'c,Self >>(self, context:V ::Context ) ->Result <V ,V ::Err > +where +T :Sized , + { +V ::validate (self.0 , context) + } + +/// Validate the underlying untrusted data. +/// +/// See the [`Validate`] trait for more information. +#[inline] +pub fn validate_ref <'a, 'c,V :Validate <'c, &'aSelf >>( + &'a self, + context:V ::Context , + ) ->Result <V ,V ::Err > { +V ::validate (&self.0 , context) + } + +/// Validate the underlying untrusted data. +/// +/// See the [`Validate`] trait for more information. +#[inline] +pub fn validate_mut <'a, 'c,V :Validate <'c, &'amut Self >>( + &'amut self, + context:V ::Context , + ) ->Result <V ,V ::Err > { +V ::validate (&mut self.0 , context) + } }impl <T >Deref for Untrusted <[T ]> {
@@ -145,3 +181,49 @@ fn deref_mut(&mut self) -> &mut Self::Target {unsafe {cast_slice_mut (self.0 .as_mut_slice ()) } } } + +/// Marks valid input for the [`Validate`] trait. +pub trait ValidateInput : private::Sealed { +/// Type of the inner data. +type Inner : ?Sized ; +} + +impl <T : ?Sized >ValidateInput for Untrusted <T > { +type Inner =T ; +} + +impl <'a,T : ?Sized >ValidateInput for &'aUntrusted <T > { +type Inner = &'aT ; +} + +impl <'a,T : ?Sized >ValidateInput for &'amut Untrusted <T > { +type Inner = &'amut T ; +} + +mod private { +use super ::Untrusted ; + +pub trait Sealed {} + +impl <T : ?Sized >Sealed for Untrusted <T > {} +impl <'a,T : ?Sized >Sealed for &'aUntrusted <T > {} +impl <'a,T : ?Sized >Sealed for &'amut Untrusted <T > {} +} + +/// Validate [`Untrusted`] data. +/// +/// Care must be taken when implementing this trait, as unprotected access to unvalidated data is +/// given to the [`Validate::validate`] function. The implementer must ensure that the data is only +/// used for logic after successful validation. +pub trait Validate <'c,Input :ValidateInput >:Sized { +/// Validation error. +type Err ; + +/// Additional context passed to [`Validate::validate`]. +/// +/// The `'c` lifetime is the lifetime of the borrowed context (`&'c Foo`). +type Context ; + +/// Validate the raw input. +fn validate (raw:Input ::Inner , context:Self ::Context ) ->Result <Self ,Self ::Err >; +}
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v4 04/22] rust: validate: add `Validate` trait
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 17 lines
From: Benno Lossin <[email protected]>
Introduce the
Validate<Input>trait and functions to validateUntrusted<T>using said trait. This allows one to access the inner value ofUntrusted<T>viavalidate{,_ref,_mut}functions which subsequently delegate the validation to user-implementedValidatetrait.The
Validatetrait is the only entry point for validation code, making it easy to spot where data is being validated.The reason for restricting the types that can be inputs to
Validate::validateis to be able to have thevalidate...functions onUntrusted. This is also the reason for the suggestions in theUsage in API Designsection in the commit that introducedUntrusted<T>.
Quoted 7 lines
The
Validate::validate()function allows additional context to be passed, which can be used as part of the validation process. For example comparing data against already negotiated information.Signed-off-by Benno Lossin <[email protected]>Message-ID: <[email protected]>
Signed-off-by Alistair Francis <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=4
PATCH v4 5/22 X.509: Make certificate parser public
In reply to [email protected]From: Lukas Wunner <[email protected]>
The upcoming support for PCI device authentication with CMA-SPDM (PCIe r6.1 sec 6.31) requires validating the Subject Alternative Name in X.509 certificates.
High-level functions for X.509 parsing such as key_create_or_update() throw away the internal, low-level struct x509_certificate after extracting the struct public_key and public_key_signature from it. The Subject Alternative Name is thus inaccessible when using those functions.
Afford CMA-SPDM access to the Subject Alternative Name by making struct x509_certificate public, together with the functions for parsing an X.509 certificate into such a struct and freeing such a struct.
No functional change intended.
--- crypto/asymmetric_keys/x509_parser.h | 42 +-------------------- include/keys/x509-parser.h | 55 ++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 41 deletions(-) create mode 100644 include/keys/x509-parser.h
crypto/asymmetric_keys/x509_parser.h +1 -41
@@ -5,51 +5,11 @@* Written by David Howells ([email protected]) */ -#include <linux/cleanup.h> -#include <linux/time.h> -#include <crypto/public_key.h> -#include <keys/asymmetric-type.h> -#include <crypto/sha2.h> - -struct x509_certificate { -struct x509_certificate *next; -struct x509_certificate *signer;/* Certificate that signed this one */ -struct public_key *pub;/* Public key details */ -struct public_key_signature *sig;/* Signature parameters */ -u8 sha256[SHA256_DIGEST_SIZE ];/* Hash for blacklist purposes */ -char *issuer;/* Name of certificate issuer */ -char *subject;/* Name of certificate subject */ -struct asymmetric_key_id *id;/* Issuer + Serial number */ -struct asymmetric_key_id *skid;/* Subject + subjectKeyId (optional) */ -time64_t valid_from; -time64_t valid_to; -const void *tbs;/* Signed data */ -unsigned tbs_size;/* Size of signed data */ -unsigned raw_sig_size;/* Size of signature */ -const void *raw_sig;/* Signature data */ -const void *raw_serial;/* Raw serial number in ASN.1 */ -unsigned raw_serial_size; -unsigned raw_issuer_size; -const void *raw_issuer;/* Raw issuer name in ASN.1 */ -const void *raw_subject;/* Raw subject name in ASN.1 */ -unsigned raw_subject_size; -unsigned raw_skid_size; -const void *raw_skid;/* Raw subjectKeyId in ASN.1 */ -unsigned index; -bool seen;/* Infinite recursion prevention */ -bool verified; -bool self_signed;/* T if self-signed (check unsupported_sig too) */ -bool unsupported_sig;/* T if signature uses unsupported crypto */ -bool blacklisted; -}; +#include <keys/x509-parser.h> /* * x509_cert_parser.c */ -extern void x509_free_certificate (struct x509_certificate *cert); -DEFINE_FREE(x509_free_certificate, struct x509_certificate *, - if (!IS_ERR(_T)) x509_free_certificate(_T)) -extern struct x509_certificate *x509_cert_parse (const void *data,size_t datalen);extern int x509_decode_time (time64_t *_t,size_t hdrlen,unsigned char tag,const unsigned char *value,size_t vlen);
include/keys/x509-parser.h new file +55 -0
55 lines not on this page · raw message
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v4 05/22] X.509: Make certificate parser public
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 12 lines
From: Lukas Wunner <[email protected]>
The upcoming support for PCI device authentication with CMA-SPDM (PCIe r6.1 sec 6.31) requires validating the Subject Alternative Name in X.509 certificates.
High-level functions for X.509 parsing such as
key_create_or_update()throw away the internal, low-level structx509_certificateafter extracting the structpublic_keyandpublic_key_signaturefrom it. The Subject Alternative Name is thus inaccessible when using those functions.
Quoted 7 lines
Afford CMA-SPDM access to the Subject Alternative Name by making struct
x509_certificatepublic, together with the functions for parsing an X.509 certificate into such a struct and freeing such a struct.No functional change intended.
Signed-off-by Lukas Wunner <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=5
PATCH v4 6/22 X.509: Parse Subject Alternative Name in certificates
In reply to [email protected]From: Lukas Wunner <[email protected]>
The upcoming support for PCI device authentication with CMA-SPDM (PCIe r6.1 sec 6.31) requires validating the Subject Alternative Name in X.509 certificates.
Store a pointer to the Subject Alternative Name upon parsing for consumption by CMA-SPDM.
--- crypto/asymmetric_keys/x509_cert_parser.c | 9 +++++++++ include/keys/x509-parser.h | 2 ++ 2 files changed, 11 insertions(+)
crypto/asymmetric_keys/x509_cert_parser.c +9 -0
@@ -596,6 +596,15 @@ int x509_process_extension(void *context, size_t hdrlen,return 0 ; } +if (ctx->last_oid == OID_subjectAltName) { +if (ctx->cert->raw_san) +return -EBADMSG ; + + ctx->cert->raw_san = v; + ctx->cert->raw_san_size = vlen; +return 0 ; + } +if (ctx->last_oid == OID_keyUsage) {/* * Get hold of the keyUsage bit string
include/keys/x509-parser.h +2 -0
@@ -38,6 +38,8 @@ struct x509_certificate {unsigned raw_subject_size;unsigned raw_skid_size;const void *raw_skid;/* Raw subjectKeyId in ASN.1 */ +const void *raw_san;/* Raw subjectAltName in ASN.1 */ +unsigned raw_san_size;unsigned index;bool seen;/* Infinite recursion prevention */ bool verified;
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v4 06/22] X.509: Parse Subject Alternative Name in certificates
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 10 lines
From: Lukas Wunner <[email protected]>
The upcoming support for PCI device authentication with CMA-SPDM (PCIe r6.1 sec 6.31) requires validating the Subject Alternative Name in X.509 certificates.
Store a pointer to the Subject Alternative Name upon parsing for consumption by CMA-SPDM.
Signed-off-by Lukas Wunner <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=6
PATCH v4 7/22 X.509: Move certificate length retrieval into new helper
In reply to [email protected]From: Lukas Wunner <[email protected]>
The upcoming in-kernel SPDM library (Security Protocol and Data Model,
https://www.dmtf.org/dsp/DSP0274) needs to retrieve the length from ASN.1 DER-encoded X.509 certificates.
Such code already exists in x509_load_certificate_list(), so move it into a new helper for reuse by SPDM.
Export the helper so that SPDM can be tristate. (Some upcoming users of the SPDM libray may be modular, such as SCSI and ATA.)
No functional change intended.
--- crypto/asymmetric_keys/x509_loader.c | 38 +++++++++++++++++++--------- include/keys/asymmetric-type.h | 2 ++ 2 files changed, 28 insertions(+), 12 deletions(-)
crypto/asymmetric_keys/x509_loader.c +26 -12
@@ -4,28 +4,42 @@#include <linux/key.h> #include <keys/asymmetric-type.h> +ssize_t x509_get_certificate_length (const u8 *p,unsigned long buflen) +{ +ssize_t plen; + +/* Each cert begins with an ASN.1 SEQUENCE tag and must be more +* than 256 bytes in size. +*/ +if (buflen <4 ) +return -EINVAL ; + +if (p[0 ] !=0x30 || + p[1 ] !=0x82 ) +return -EINVAL ; + + plen = (p[2 ] <<8 ) | p[3 ]; + plen +=4 ; +if (plen > buflen) +return -EINVAL ; + +return plen; +} +EXPORT_SYMBOL_GPL (x509_get_certificate_length); +int x509_load_certificate_list (const u8 cert_list[],const unsigned long list_size,const struct key *keyring) {key_ref_t key;const u8 *p, *end; -size_t plen; +ssize_t plen; p = cert_list; end = p + list_size;while (p < end) { -/* Each cert begins with an ASN.1 SEQUENCE tag and must be more -* than 256 bytes in size. -*/ -if (end - p <4 ) - goto dodgy_cert; -if (p[0 ] !=0x30 || - p[1 ] !=0x82 ) - goto dodgy_cert; - plen = (p[2 ] <<8 ) | p[3 ]; - plen +=4 ; -if (plen > end - p) + plen =x509_get_certificate_length (p, end - p); +if (plen <0 ) goto dodgy_cert; key = key_create_or_update(make_key_ref(keyring, 1),
include/keys/asymmetric-type.h +2 -0
@@ -84,6 +84,8 @@ extern struct key *find_asymmetric_key(struct key *keyring,const struct asymmetric_key_id *id_2, bool partial); +ssize_t x509_get_certificate_length (const u8 *p,unsigned long buflen); +int x509_load_certificate_list (const u8 cert_list[],const unsigned long list_size,const struct key *keyring);
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v4 07/22] X.509: Move certificate length retrieval into new helper
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 9 lines
From: Lukas Wunner <[email protected]>
The upcoming in-kernel SPDM library (Security Protocol and Data Model,
https://www.dmtf.org/dsp/DSP0274) needs to retrieve the length from ASN.1 DER-encoded X.509 certificates.Such code already exists in
x509_load_certificate_list(), so move it into a new helper for reuse by SPDM.
Quoted 6 lines
Export the helper so that SPDM can be tristate. (Some upcoming users of the SPDM libray may be modular, such as SCSI and ATA.)
No functional change intended.
Signed-off-by Lukas Wunner <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=7
PATCH v4 8/22 rust: add bindings for hash.h
In reply to [email protected]From: Alistair Francis <[email protected]>
Make the functions crypto_shash_descsize(), crypto_shash_digestsize() and crypto_free_shash() available to Rust.
--- rust/bindings/bindings_helper.h | 1 + rust/helpers/hash.c | 18 ++++++++++++++++++ rust/helpers/helpers.c | 1 + 3 files changed, 20 insertions(+) create mode 100644 rust/helpers/hash.c
rust/bindings/bindings_helper.h +1 -0
@@ -30,6 +30,7 @@#include <linux/acpi.h> #include <linux/gpu_buddy.h> +#include <crypto/hash.h> #include <drm/drm_device.h> #include <drm/drm_drv.h> #include <drm/drm_file.h>
rust/helpers/hash.c new file +18 -0
@@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <crypto/hash.h> + +__rust_helper unsigned int rust_helper_crypto_shash_descsize(struct crypto_shash *tfm) +{ +return crypto_shash_descsize (tfm); +} + +__rust_helper unsigned int rust_helper_crypto_shash_digestsize(struct crypto_shash *tfm) +{ +return crypto_shash_digestsize (tfm); +} + +__rust_helper void rust_helper_crypto_free_shash(struct crypto_shash *tfm) +{ +crypto_free_shash (tfm); +}
rust/helpers/helpers.c +1 -0
@@ -66,6 +66,7 @@#include "fs.c" #include "fwctl.c" #include "gpu.c" +#include "hash.c" #include "interrupt.c" #include "io.c" #include "irq.c"
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v4 08/22] rust: add bindings for hash.h
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [Low] rust: linkage failure for hash helpers when CONFIG_CRYPTO=n - [Low] rust: bindings_helper.h include…
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Low] rust: linkage failure for hash helpers when CONFIG_CRYPTO=n
- [Low] rust: bindings_helper.h include violates alphabetical sorting
--
commit d3a13bbc1d2175dfb4494c82add6ef5923646f93
Author: Alistair Francis <[email protected]>
rust: add bindings for hash.h
Make the functions crypto_shash_descsize(), crypto_shash_digestsize() and crypto_free_shash() available to Rust.
rust/bindings/bindings_helper.h line 33
Quoted 9 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>
[Severity: Low]
Is this include placed in the correct alphabetical order?
This file requires alphabetical sorting, but <crypto/hash.h> was placed after <linux/gpu_buddy.h> instead of before the <drm/...> and <linux/...> headers.
Quoted 2 lines
rust/bindings/bindings_helper.h +0
-0#include <drm/drm_device.h> #include <drm/drm_drv.h>
[ ... ]
Quoted 6 lines
rust/helpers/hash.c new file +0
-0@@ -0,0 +1,18 @@
[ ... ]
rust/helpers/hash.c line 17
Quoted 3 lines
rust/helpers/hash.c new file +3
-0+__rust_helper void rust_helper_crypto_free_shash(struct crypto_shash *tfm) +{ +crypto_free_shash (tfm);
[Severity: Low]
Will this cause a linkage failure when CONFIG_CRYPTO is disabled?
This unguarded call to crypto_free_shash() resolves to the external symbol crypto_destroy_tfm(), which is not compiled when CONFIG_CRYPTO=n.
rust/helpers/helpers.c line 69
Quoted 10 lines
rust/helpers/hash.c new file +1
-0+}rust/helpers/helpers.c +1
-0@@ -66,6 +66,7 @@#include "fs.c" #include "fwctl.c" #include "gpu.c" +#include "hash.c"
[Severity: Low]
Does this unconditionally compile the hash helpers into the rust_helpers object?
Because hash.c is included here without any configuration guards, the crypto_free_shash() usage above is compiled even when the crypto subsystem is disabled, leading to the undefined reference link error.
Quoted 3 lines
rust/helpers/helpers.c +0
-0#include "interrupt.c" #include "io.c" #include "irq.c"
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=8
PATCH v4 9/22 rust: error: impl From<FromBytesWithNulError> for Kernel Error
In reply to [email protected]From: Alistair Francis <[email protected]>
Implement From<FromBytesWithNulError> for the Kernel Error type as we will use it in a future patch.
As this is now generally available we can remove it from the test case as well.
--- rust/kernel/error.rs | 18 +++++++++++++++--- rust/kernel/str.rs | 7 ------- 2 files changed, 15 insertions(+), 10 deletions(-)
rust/kernel/error.rs +15 -3
@@ -12,9 +12,14 @@ str::CStr, }; -use core::num::NonZeroI32 ; -use core::num::TryFromIntError ; -use core::str::Utf8Error ; +use core::{ + ffi::FromBytesWithNulError , + num::{ +NonZeroI32 , +TryFromIntError ,// + }, + str::Utf8Error ,// +};/// Contains the C-compatible error codes. #[rustfmt::skip]
@@ -358,6 +363,13 @@ fn from(e: core::convert::Infallible) -> Error { } } +impl From <FromBytesWithNulError >for Error { +#[inline] +fn from (_:FromBytesWithNulError ) ->Error { + code::EINVAL + } +} +/// A [`Result`] with an [`Error`] error type. /// /// To be used as the return type for functions that may fail.
rust/kernel/str.rs +0 -7
@@ -433,13 +433,6 @@ macro_rules! c_str {mod tests {use super ::*; -impl From <core::ffi::FromBytesWithNulError >for Error { -#[inline] -fn from (_: core::ffi::FromBytesWithNulError ) ->Error { -EINVAL - } - } -macro_rules! format { ($($f:tt)*) => ({CString ::try_from_fmt (fmt! ($($f)*))?.to_str ()?
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v4 09/22] rust: error: impl From<FromBytesWithNulError> for Kernel Error
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 9 lines
From: Alistair Francis <[email protected]>
Implement From<FromBytesWithNulError> for the Kernel Error type as we will use it in a future patch.
As this is now generally available we can remove it from the test case as well.
Signed-off-by Alistair Francis <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=9
PATCH v4 10/22 lib: rspdm: Initial commit of Rust SPDM
In reply to [email protected]From: Alistair Francis <[email protected]>
This is the initial commit of the Rust SPDM library.
--- MAINTAINERS | 12 ++ include/linux/spdm.h | 37 +++++ lib/Kconfig | 17 +++ lib/Makefile | 2 + lib/rspdm/Makefile | 10 ++ lib/rspdm/consts.rs | 92 ++++++++++++ lib/rspdm/lib.rs | 96 +++++++++++++ lib/rspdm/state.rs | 241 ++++++++++++++++++++++++++++++++ lib/rspdm/validator.rs | 93 ++++++++++++ rust/bindings/bindings_helper.h | 1 + 10 files changed, 601 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
@@ -24748,6 +24748,18 @@ M: Security Officers <[email protected]> S: Supported F: Documentation/process/security-bugs.rst +SECURITY PROTOCOL AND DATA MODEL (SPDM) +M: Jonathan Cameron <[email protected]> +M: Lukas Wunner <[email protected]> +M: Alistair Francis <[email protected]> +L: [email protected] +L: [email protected] +L: [email protected] +S: Maintained +T: git git://git.kernel.org/pub/scm/linux/kernel/git/devsec/spdm.git +F: include/linux/spdm.h +F: lib/rspdm/ + SECURITY SUBSYSTEM M: Paul Moore <[email protected]> M: James Morris <[email protected]>
include/linux/spdm.h new file +37 -0
@@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* +* DMTF Security Protocol and Data Model (SPDM) +* https://www.dmtf.org/dsp/DSP0274 +* +* Copyright (C) 2021-22 Huawei +* Jonathan Cameron <[email protected]> +* +* Copyright (C) 2022-24 Intel Corporation +*/ + +#ifndef _SPDM_H_ +#define _SPDM_H_ + +#include <linux/types.h> + +struct key ; +struct device ; +struct spdm_state ; +struct x509_certificate ; + +typedef int (spdm_transport )(void *priv,struct device *dev, +const void *request,size_t request_sz, +void *response,size_t response_sz); + +typedef int (spdm_validate )(struct device *dev,u8 slot, +struct x509_certificate *leaf_cert); + +struct spdm_state *spdm_create (struct device *dev,spdm_transport *transport, +void *transport_priv,u32 transport_sz, +spdm_validate *validate); + +int spdm_authenticate (struct spdm_state *spdm_state); + +void spdm_destroy (struct spdm_state *spdm_state); + +#endif
lib/Kconfig +17 -0
@@ -588,6 +588,23 @@ config LWQ_TEST help Run boot-time test of light-weight queuing. +config RSPDM + bool "Rust SPDM" + depends on RUST + select KEYS + select CRYPTO + 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
@@ -297,6 +297,8 @@ obj-$(CONFIG_PERCPU_TEST) += percpu_test.o obj-$(CONFIG_ASN1) += asn1_decoder.o obj-$(CONFIG_ASN1_ENCODER) += asn1_encoder.o +obj-$(CONFIG_RSPDM) += rspdm/ + obj-$(CONFIG_FONT_SUPPORT) += fonts/ #
lib/rspdm/Makefile new file +10 -0
@@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Rust implementation of the DMTF Security Protocol and Data Model (SPDM) +# https://www.dmtf.org/dsp/DSP0274 +# +# Copyright (C) 2026 Western Digital + +obj-$(CONFIG_RSPDM) += spdm.o + +spdm-y := lib.o
lib/rspdm/consts.rs new file +92 -0
92 lines not on this page · raw message
lib/rspdm/lib.rs new file +96 -0
96 lines not on this page · raw message
lib/rspdm/state.rs new file +241 -0
241 lines not on this page · raw message
lib/rspdm/validator.rs new file +93 -0
93 lines not on this page · raw message
rust/bindings/bindings_helper.h +1 -0
@@ -89,6 +89,7 @@#include <linux/security.h> #include <linux/serdev.h> #include <linux/slab.h> +#include <linux/spdm.h> #include <linux/sys_soc.h> #include <linux/task_work.h> #include <linux/tracepoint.h>
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v4 10/22] lib: rspdm: Initial commit of Rust SPDM
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [High] rust: ForeignOwnable type mismatch in spdm_destroy() - [Low] rspdm: needless mutable request_buf…
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] rust: ForeignOwnable type mismatch in spdm_destroy()
- [Low] rspdm: needless mutable request_buf in spdm_exchange()
--
commit 3c4db293fc6c18056667fde06984c7043ab7fc64
Author: Alistair Francis <[email protected]>
lib: rspdm: Initial commit of Rust SPDM
This commit adds the initial Rust implementation of the Security Protocol and Data Model (SPDM) library. It introduces session allocation, authentication stubs, and session destruction functions exposed via a C API.
Quoted 4 lines
lib/rspdm/lib.rs new file +0
-0
[ ... ]
Quoted 17 lines
lib/rspdm/lib.rs new file +17
-0+pub extern "C" fn spdm_create ( + dev: *mut bindings::device , + transport: bindings::spdm_transport , + transport_priv: *mut c_void , + transport_sz:u32 , + validate: bindings::spdm_validate , +) -> *mut spdm_state { +// Wrap the `SpdmState` in a `Mutex` so that concurrent FFI callers (for +// example, two threads racing on `spdm_authenticate()` for the same +// device) serialize on the lock and never form aliased `&mut SpdmState` +// references. +let state =SpdmState ::new (dev, transport, transport_priv, transport_sz, validate); +match KBox ::pin_init (new_mutex! (state), flags::GFP_KERNEL ) { +Ok (b) => b.into_foreign ()as *mut spdm_state , +Err (_) => ptr::null_mut (), + } +}
[ ... ]
lib/rspdm/lib.rs line 93
Quoted 8 lines
lib/rspdm/lib.rs new file +8
-0+pub extern "C" fn spdm_destroy (state_ptr: *mut spdm_state ) { +if state_ptr.is_null () { +return ; + } + +// SAFETY: `state_ptr` was returned from `spdm_create()` which used `into_foreign()` +// to create the pointer. +let mutex:KBox <Mutex <SpdmState >> =unsafe {KBox ::from_foreign (state_ptras *mut c_void ) };
[Severity: High]
Does this break the ForeignOwnable safety contract?
The spdm_create() function converts a Pin<KBox<Mutex<SpdmState>>> into a raw pointer via into_foreign(), but here in spdm_destroy(), the pointer is reconstructed as an unpinned KBox<Mutex<SpdmState>>.
Reconstructing a different type from a raw pointer breaks the strict type symmetry required by the ForeignOwnable trait and violates Pin invariants. Should this be reconstructed as a pinned box to match its original allocation?
Quoted 3 lines
lib/rspdm/lib.rs new file +3
-0+ +drop (mutex); +}
[ ... ]
Quoted 4 lines
lib/rspdm/state.rs new file +0
-0
[ ... ]
lib/rspdm/state.rs line 194
Quoted 3 lines
lib/rspdm/state.rs new file +3
-0+pub (crate )fn spdm_exchange ( + &self, + request_buf: &mut [u8],
[Severity: Low]
Is it necessary to require a mutable slice for the request buffer here?
The function only appears to use its read-only pointer to pass to the transport layer:
lib/rspdm/state.rs:spdm_exchange() {
...
let length = unsafe {
transport_function(
...
request_buf.as_ptr() as *const c_void,
...
...
}This enforces mutable access to a buffer that is never modified, creating unnecessary constraints on callers by forcing them to hold or provide mutable references to read-only buffers. Could this be changed to &[u8]?
Quoted 2 lines
lib/rspdm/state.rs new file +2
-0+ response_buf: &mut [u8], + ) -> Result<i32, Error> {
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=10
Alexandre Courbot
reviewerRe: [PATCH v4 10/22] lib: rspdm: Initial commit of Rust SPDM
In reply to [email protected]Quoted 3 lines · On Mon Sep 28, 2026 at 10:11 AM JST, alistair23 wrote:
From: Alistair Francis <[email protected]>
This is the initial commit of the Rust SPDM library.
This commit message deserves to be a bit more verbose. :)
<...>
Quoted 6 lines
lib/rspdm/consts.rs new file +0
-0
lib/rspdm/consts.rs line 10
Quoted 10 lines
lib/rspdm/consts.rs new file +10
-0@@ -0,0 +1,92 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2026 Western Digital + +//! Constants used by the library +//! +//! Rust implementation of the DMTF Security Protocol and Data Model (SPDM) +//! <https://www.dmtf.org/dsp/DSP0274> + +use kernel::error::{code::EINVAL ,Error };
This can just be use kernel::prelude::*;.
Quoted 4 lines
lib/rspdm/consts.rs new file +4
-0+ +// SPDM versions supported by this implementation +pub (crate )const SPDM_VER_10 :u8 =0x10 ; +
lib/rspdm/consts.rs line 24
Quoted 10 lines
lib/rspdm/consts.rs new file +10
-0+pub (crate )const SPDM_MIN_VER :u8 =SPDM_VER_10 ; + +#[allow(dead_code)] +pub (crate )const SPDM_REQ :u8 =0x80 ; +#[allow(dead_code)] +pub (crate )const SPDM_ERROR :u8 =0x7f ; + +#[derive( Clone , Copy )] +#[repr( u8 )] +pub (crate )enum SpdmErrorCode {
This is in consts.rs but is not a const... Actually everything in this module looks like it should be in lib.rs, and by doing so you could also remove the pub(crate) visibility.
<...>
Quoted 44 lines
lib/rspdm/lib.rs new file +38
-0@@ -0,0 +1,96 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2026 Western Digital + +//! Top level library for SPDM +//! +//! Rust implementation of the DMTF Security Protocol and Data Model (SPDM) +//! <https://www.dmtf.org/dsp/DSP0274> +//! +//! Top level library, including C compatible public functions to be called +//! from other subsytems. + +use crate ::bindings::{ + spdm_state, +EPROTONOSUPPORT ,// +}; +use core::ffi::{ + c_int, + c_void,// +}; +use core::ptr; +use kernel::prelude::*; +use kernel::{ + alloc::flags, + bindings, + new_mutex, + sync::Mutex , + types::ForeignOwnable ,// +}; + +use crate ::state::SpdmState ; + +const __LOG_PREFIX: &[u8 ] =b"spdm\0" ; + +mod consts; +mod state; +mod validator; +
lib/rspdm/lib.rs line 48
Quoted 10 lines
lib/rspdm/lib.rs new file +10
-0+/// spdm_create() - Allocate SPDM session +/// +/// `dev`: Responder device +/// `transport`: Transport function to perform one message exchange +/// `transport_priv`: Transport private data +/// `transport_sz`: Maximum message size the transport is capable of (in bytes) +/// `validate`: Function to validate additional leaf certificate requirements +/// (optional, may be %NULL) +/// +/// Return a pointer to the allocated SPDM session state or NULL on error.
The commenting style is a bit all over the place in this series, sometimes using the Rust style, sometimes the C kernel style, sometimes apparently something else.
For Rust items, please follow the Rustdoc style. These functions though are the C FFI interface though, so they are useful to the C callers and as such I think their documentation belongs to the interface file (i.e. include/linux/spdm.h).
<...>
Quoted 44 lines
lib/rspdm/state.rs new file +38
-0@@ -0,0 +1,241 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2026 Western Digital + +//! The `SpdmState` struct and implementation. +//! +//! Rust implementation of the DMTF Security Protocol and Data Model (SPDM) +//! <https://www.dmtf.org/dsp/DSP0274> + +use core::ffi::c_void; +use kernel::prelude::*; +use kernel::{ + bindings, + error::{ + code::EINVAL , + to_result, +Error ,// + }, + validate::Untrusted , +}; + +use crate ::consts::{ +SpdmErrorCode , +SPDM_ERROR , +SPDM_MIN_VER , +SPDM_REQ ,// +}; +use crate ::validator::{ +SpdmErrorRsp , +SpdmHeader ,// +}; + +/// The current SPDM session state for a device. +/// +/// Concurrent access is serialised by wrapping the whole struct in a +/// `Mutex<SpdmState>` at the FFI boundary, so `spdm_authenticate()` callers +/// run one at a time and the locked `&mut SpdmState` is the only way to +/// reach the inner fields.
lib/rspdm/state.rs line 48
Quoted 10 lines
lib/rspdm/state.rs new file +10
-0+/// +/// `dev`: Responder device. Used for error reporting and passed to @transport. +/// `transport`: Transport function to perform one message exchange. +/// `transport_priv`: Transport private data. +/// `transport_sz`: Maximum message size the transport is capable of (in bytes). +/// Used as DataTransferSize in GET_CAPABILITIES exchange. +/// `validate`: Function to validate additional leaf certificate requirements. +/// +/// `version`: Maximum common supported version of requester and responder. +/// Negotiated during GET_VERSION exchange.
Field documentation should be their own doccomments before each individual field (see [1] for an example and general good practices when writing Rust doccomments).
lib/rspdm/state.rs line 51
Quoted 3 lines
lib/rspdm/state.rs new file +3
-0+#[expect(dead_code)] +pub (crate )struct SpdmState { +pub (crate ) dev: *mut bindings::device ,
This makes me very nervous. I see this is passed to C callbacks. We need to maintain the guarantee that the device still exists, so an ARef<Device>, from which you obtain a pointer to pass to the C callbacks, should be the minimum here. Does the device also need to be bound? Looking at drivers/pci/cma.c it seems to be. Then this needs to be a &Device<Bound>, with a lifetime to SpdmState so callers only use it while the device is bound.
Just storing a C pointer and passing it whenever a function needs it cancels the purpose of using Rust and make the safety requirements of the callback you pass it to impossible to guarantee, as nothing enforces that the device still exists or is in a valid state when they are invoked.
lib/rspdm/state.rs line 53
Quoted 2 lines
lib/rspdm/state.rs new file +2
-0+ pub(crate) transport: bindings::spdm_transport, + pub(crate) transport_priv: *mut c_void,
Here I also suspect we want to define some kind of ownership model for the private data instead of hoping that the pointer is still valid when we use it.
Quoted 17 lines
lib/rspdm/state.rs new file +17
-0+ pub(crate) transport_sz: u32, + pub(crate) validate: bindings::spdm_validate, + +// Negotiated state + pub(crate) version: u8, +} + +impl SpdmState { +pub (crate )fn new ( + dev: *mut bindings::device , + transport: bindings::spdm_transport , + transport_priv: *mut c_void , + transport_sz:u32 , + validate: bindings::spdm_validate , + ) ->Self { +SpdmState { + dev,
lib/rspdm/state.rs line 80
Quoted 10 lines
lib/rspdm/state.rs new file +10
-0+ transport, + transport_priv, + transport_sz, + validate, + version: SPDM_MIN_VER, + } + } + +#[allow(dead_code)] +fn spdm_err (&self, rsp: &SpdmErrorRsp ) ->Result <(),Error > {
&self is never used - this should probably be a method of SpdmErrorRsp.
Quoted 11 lines
lib/rspdm/state.rs new file +11
-0+match rsp.error_code { +SpdmErrorCode ::InvalidRequest => { +pr_err! ("Invalid request\n" ); +Err (EINVAL ) + } +SpdmErrorCode ::InvalidSession => { +if rsp.version ==0x11 { +pr_err! ("Invalid session {:#x}\n" , rsp.error_data); +Err (EINVAL ) + }else { +pr_err! ("Undefined error {:#x}\n" , rsp.error_code);
lib/rspdm/state.rs line 101
Quoted 10 lines
lib/rspdm/state.rs new file +10
-0+Err (EINVAL ) + } + } + SpdmErrorCode::Busy => { + pr_err!("Busy\n"); + Err(EBUSY) + } + SpdmErrorCode::UnexpectedRequest => { +pr_err! ("Unexpected request\n" ); +Err (EINVAL )
These blocks are getting repetitive. Instead you can implement Debug on SpdmErrorCode and From<SpdmErrorCode> for Error, and get rid of this method entirely as callers will be able to print the error directly and have it automatically converted to the kernel error type.
<...>
lib/rspdm/state.rs line 194
Quoted 8 lines
lib/rspdm/state.rs new file +8
-0+/// Start a SPDM exchange +/// +/// The data in `request_buf` is sent to the device and the response is +/// stored in `response_buf`. +#[allow(dead_code)] +pub (crate )fn spdm_exchange ( + &self, + request_buf: &mut [u8],
This shouldn't need to be mutable?
lib/rspdm/state.rs line 121
Quoted 12 lines
lib/rspdm/state.rs new file +12
-0+impl <'a, 'c>Validate <'c,Untrusted <&'a [u8 ]>>for SpdmHeader { +type Err =Error ; + +type Context = &'cSpdmState ; + +fn validate (unvalidated: &[u8 ], _context: &'cSpdmState ) ->Result <Self ,Self ::Err > { +Ok (SpdmHeader { + version: *unvalidated.get (0 ).ok_or (EIO )?, + code: *unvalidated.get (1 ).ok_or (EIO )?, + param1: *unvalidated.get (2 ).ok_or (EIO )?, + param2: *unvalidated.get (3 ).ok_or (EIO )?, + })
Here you will definitely want to leverage zerocopy's FromBytes trait.
Quoted 16 lines
lib/rspdm/state.rs new file +16
-0+ } +} + +#[expect(dead_code)] +pub (crate )struct SpdmErrorRsp { +pub (crate ) version:u8 , +/// This will always be SPDM_ERROR (0x7F) +pub (crate ) code:u8 , +pub (crate ) error_code:SpdmErrorCode , +pub (crate ) error_data:u8 , +} + +impl <'a, 'c>Validate <'c,Untrusted <&'a [u8 ]>>for SpdmErrorRsp { +type Err =Error ; + +type Context = &'cSpdmState ;
lib/rspdm/state.rs line 147
Quoted 10 lines
lib/rspdm/state.rs new file +10
-0+ +fn validate (unvalidated: &[u8 ], _context: &'cSpdmState ) ->Result <Self ,Self ::Err > { +Ok (SpdmErrorRsp { + version: *unvalidated.get (0 ).ok_or (EIO )?, + code: *unvalidated.get (1 ).ok_or (EIO )?, +// `try_from` rejects unknown `SpdmErrorCode` discriminants. + error_code:SpdmErrorCode ::try_from (*unvalidated.get (2 ).ok_or (EIO )?)?, + error_data: *unvalidated.get (3 ).ok_or (EIO )?, + }) + }
Same here.
Re: [PATCH v4 10/22] lib: rspdm: Initial commit of Rust SPDM
Quoted 174 lines · Alexandre Courbot
Quoted 3 lines · On Mon Sep 28, 2026 at 10:11 AM JST, alistair23 wrote:
From: Alistair Francis <[email protected]>
This is the initial commit of the Rust SPDM library.
This commit message deserves to be a bit more verbose. :)
<...>
Quoted 6 lines
lib/rspdm/consts.rs new file +0
-0lib/rspdm/consts.rs line 10
Quoted 10 lines
lib/rspdm/consts.rs new file +10
-0@@ -0,0 +1,92 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2026 Western Digital + +//! Constants used by the library +//! +//! Rust implementation of the DMTF Security Protocol and Data Model (SPDM) +//! <https://www.dmtf.org/dsp/DSP0274> + +use kernel::error::{code::EINVAL ,Error };This can just be
use kernel::prelude::*;.Quoted 4 lines
lib/rspdm/consts.rs new file +4
-0+ +// SPDM versions supported by this implementation +pub (crate )const SPDM_VER_10 :u8 =0x10 ; +lib/rspdm/consts.rs line 24
Quoted 10 lines
lib/rspdm/consts.rs new file +10
-0+pub (crate )const SPDM_MIN_VER :u8 =SPDM_VER_10 ; + +#[allow(dead_code)] +pub (crate )const SPDM_REQ :u8 =0x80 ; +#[allow(dead_code)] +pub (crate )const SPDM_ERROR :u8 =0x7f ; + +#[derive( Clone , Copy )] +#[repr( u8 )] +pub (crate )enum SpdmErrorCode {This is in
consts.rsbut is not a const... Actually everything in this module looks like it should be inlib.rs, and by doing so you could also remove thepub(crate)visibility.<...>
Quoted 44 lines
lib/rspdm/lib.rs new file +38
-0@@ -0,0 +1,96 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2026 Western Digital + +//! Top level library for SPDM +//! +//! Rust implementation of the DMTF Security Protocol and Data Model (SPDM) +//! <https://www.dmtf.org/dsp/DSP0274> +//! +//! Top level library, including C compatible public functions to be called +//! from other subsytems. + +use crate ::bindings::{ + spdm_state, +EPROTONOSUPPORT ,// +}; +use core::ffi::{ + c_int, + c_void,// +}; +use core::ptr; +use kernel::prelude::*; +use kernel::{ + alloc::flags, + bindings, + new_mutex, + sync::Mutex , + types::ForeignOwnable ,// +}; + +use crate ::state::SpdmState ; + +const __LOG_PREFIX: &[u8 ] =b"spdm\0" ; + +mod consts; +mod state; +mod validator; +lib/rspdm/lib.rs line 48
Quoted 10 lines
lib/rspdm/lib.rs new file +10
-0+/// spdm_create() - Allocate SPDM session +/// +/// `dev`: Responder device +/// `transport`: Transport function to perform one message exchange +/// `transport_priv`: Transport private data +/// `transport_sz`: Maximum message size the transport is capable of (in bytes) +/// `validate`: Function to validate additional leaf certificate requirements +/// (optional, may be %NULL) +/// +/// Return a pointer to the allocated SPDM session state or NULL on error. The commenting style is a bit all over the place in this series, sometimes using the Rust style, sometimes the C kernel style, sometimes apparently something else.
For Rust items, please follow the Rustdoc style. These functions though are the C FFI interface though, so they are useful to the C callers and as such I think their documentation belongs to the interface file (i.e.
include/linux/spdm.h).<...>
Quoted 44 lines
lib/rspdm/state.rs new file +38
-0@@ -0,0 +1,241 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2026 Western Digital + +//! The `SpdmState` struct and implementation. +//! +//! Rust implementation of the DMTF Security Protocol and Data Model (SPDM) +//! <https://www.dmtf.org/dsp/DSP0274> + +use core::ffi::c_void; +use kernel::prelude::*; +use kernel::{ + bindings, + error::{ + code::EINVAL , + to_result, +Error ,// + }, + validate::Untrusted , +}; + +use crate ::consts::{ +SpdmErrorCode , +SPDM_ERROR , +SPDM_MIN_VER , +SPDM_REQ ,// +}; +use crate ::validator::{ +SpdmErrorRsp , +SpdmHeader ,// +}; + +/// The current SPDM session state for a device. +/// +/// Concurrent access is serialised by wrapping the whole struct in a +/// `Mutex<SpdmState>` at the FFI boundary, so `spdm_authenticate()` callers +/// run one at a time and the locked `&mut SpdmState` is the only way to +/// reach the inner fields. lib/rspdm/state.rs line 48
Quoted 10 lines
lib/rspdm/state.rs new file +10
-0+/// +/// `dev`: Responder device. Used for error reporting and passed to @transport. +/// `transport`: Transport function to perform one message exchange. +/// `transport_priv`: Transport private data. +/// `transport_sz`: Maximum message size the transport is capable of (in bytes). +/// Used as DataTransferSize in GET_CAPABILITIES exchange. +/// `validate`: Function to validate additional leaf certificate requirements. +/// +/// `version`: Maximum common supported version of requester and responder. +/// Negotiated during GET_VERSION exchange. Field documentation should be their own doccomments before each individual field (see [1] for an example and general good practices when writing Rust doccomments).
Quoted 11 lines
lib/rspdm/state.rs line 51
Quoted 3 lines
lib/rspdm/state.rs new file +3
-0+#[expect(dead_code)] +pub (crate )struct SpdmState { +pub (crate ) dev: *mut bindings::device ,This makes me very nervous. I see this is passed to C callbacks. We need to maintain the guarantee that the device still exists, so an
ARef<Device>, from which you obtain a pointer to pass to the C callbacks, should be the minimum here. Does the device also need to be bound? Looking atdrivers/pci/cma.cit seems to be. Then this needs to be a&Device<Bound>, with a lifetime toSpdmStateso callers only use it while the device is bound.
Good point, I have converted this to use a &Device<Bound>
Quoted 7 lines
Just storing a C pointer and passing it whenever a function needs it cancels the purpose of using Rust and make the safety requirements of the callback you pass it to impossible to guarantee, as nothing enforces that the device still exists or is in a valid state when they are invoked.
Quoted 6 lines
lib/rspdm/state.rs line 53
Quoted 2 lines
lib/rspdm/state.rs new file +2
-0+ pub(crate) transport: bindings::spdm_transport, + pub(crate) transport_priv: *mut c_void,Here I also suspect we want to define some kind of ownership model for the private data instead of hoping that the pointer is still valid when we use it.
Any ideas here? It's just a pointer that we pass back to C when calling the transport_function(), I'm not clear on what we could do to ensure it's still valid.
Quoted 69 lines
Quoted 18 lines
lib/rspdm/state.rs new file +17
-0+ pub(crate) transport_sz: u32, + pub(crate) validate: bindings::spdm_validate, + +// Negotiated state + pub(crate) version: u8, +} + +impl SpdmState { +pub (crate )fn new ( + dev: *mut bindings::device , + transport: bindings::spdm_transport , + transport_priv: *mut c_void , + transport_sz:u32 , + validate: bindings::spdm_validate , + ) ->Self { +SpdmState { + dev,lib/rspdm/state.rs line 80
Quoted 10 lines
lib/rspdm/state.rs new file +10
-0+ transport, + transport_priv, + transport_sz, + validate, + version: SPDM_MIN_VER, + } + } + +#[allow(dead_code)] +fn spdm_err (&self, rsp: &SpdmErrorRsp ) ->Result <(),Error > {
&selfis never used - this should probably be a method ofSpdmErrorRsp.Quoted 11 lines
lib/rspdm/state.rs new file +11
-0+match rsp.error_code { +SpdmErrorCode ::InvalidRequest => { +pr_err! ("Invalid request\n" ); +Err (EINVAL ) + } +SpdmErrorCode ::InvalidSession => { +if rsp.version ==0x11 { +pr_err! ("Invalid session {:#x}\n" , rsp.error_data); +Err (EINVAL ) + }else { +pr_err! ("Undefined error {:#x}\n" , rsp.error_code);lib/rspdm/state.rs line 101
Quoted 10 lines
lib/rspdm/state.rs new file +10
-0+Err (EINVAL ) + } + } + SpdmErrorCode::Busy => { + pr_err!("Busy\n"); + Err(EBUSY) + } + SpdmErrorCode::UnexpectedRequest => { +pr_err! ("Unexpected request\n" ); +Err (EINVAL )These blocks are getting repetitive. Instead you can implement
DebugonSpdmErrorCodeandFrom<SpdmErrorCode> for Error, and get rid of this method entirely as callers will be able to print the error directly and have it automatically converted to the kernel error type.<...>
lib/rspdm/state.rs line 194
Quoted 8 lines
lib/rspdm/state.rs new file +8
-0+/// Start a SPDM exchange +/// +/// The data in `request_buf` is sent to the device and the response is +/// stored in `response_buf`. +#[allow(dead_code)] +pub (crate )fn spdm_exchange ( + &self, + request_buf: &mut [u8],
Quoted 1 line
This shouldn't need to be mutable?
Nope, it doesn't need to be. That's just leftover from earlier versions.
Quoted 15 lines
lib/rspdm/state.rs line 121
Quoted 13 lines
lib/rspdm/state.rs new file +12
-0+impl <'a, 'c>Validate <'c,Untrusted <&'a [u8 ]>>for SpdmHeader { +type Err =Error ; + +type Context = &'cSpdmState ; + +fn validate (unvalidated: &[u8 ], _context: &'cSpdmState ) ->Result <Self ,Self ::Err > { +Ok (SpdmHeader { + version: *unvalidated.get (0 ).ok_or (EIO )?, + code: *unvalidated.get (1 ).ok_or (EIO )?, + param1: *unvalidated.get (2 ).ok_or (EIO )?, + param2: *unvalidated.get (3 ).ok_or (EIO )?, + })Here you will definitely want to leverage zerocopy's
FromBytestrait.
I was hoping not too actually.
The SPDM responses are a bit complex. For example NegotiateAlgsRsp has multiple dynamically sized arrays, and the fields change based on the version.
Using zerocopy means we need packed structs and even then I'm not clear on how we can handle the KVec<u32> or even the KVec<RespAlgStruct> nicley.
The current approach has the obvious downside of copying the data, but as the data is pretty small in the scheme of things and not part of a hot path, I think the tradeoff of copying the data with much cleaner and easier to understand code is worth it.
Maybe in the future zerocopy could be a performance improvement, but I think right now it's a large complexity overhead in this case.
Note that obviously SpdmHeader is not complex, but I think they should all follow the same pattern.
Alistair
Quoted 29 lines
Quoted 17 lines
lib/rspdm/state.rs new file +16
-0+ } +} + +#[expect(dead_code)] +pub (crate )struct SpdmErrorRsp { +pub (crate ) version:u8 , +/// This will always be SPDM_ERROR (0x7F) +pub (crate ) code:u8 , +pub (crate ) error_code:SpdmErrorCode , +pub (crate ) error_data:u8 , +} + +impl <'a, 'c>Validate <'c,Untrusted <&'a [u8 ]>>for SpdmErrorRsp { +type Err =Error ; + +type Context = &'cSpdmState ;lib/rspdm/state.rs line 147
Quoted 10 lines
lib/rspdm/state.rs new file +10
-0+ +fn validate (unvalidated: &[u8 ], _context: &'cSpdmState ) ->Result <Self ,Self ::Err > { +Ok (SpdmErrorRsp { + version: *unvalidated.get (0 ).ok_or (EIO )?, + code: *unvalidated.get (1 ).ok_or (EIO )?, +// `try_from` rejects unknown `SpdmErrorCode` discriminants. + error_code:SpdmErrorCode ::try_from (*unvalidated.get (2 ).ok_or (EIO )?)?, + error_data: *unvalidated.get (3 ).ok_or (EIO )?, + }) + }Same here.
Alexandre Courbot
reviewerRe: [PATCH v4 10/22] lib: rspdm: Initial commit of Rust SPDM
On Thu Oct 15, 2026 at 12:06 PM JST, Alistair Francis wrote:
<...>
Quoted 35 lines
Quoted 23 lines
Quoted 6 lines
lib/rspdm/state.rs new file +6
-0+/// The current SPDM session state for a device. +/// +/// Concurrent access is serialised by wrapping the whole struct in a +/// `Mutex<SpdmState>` at the FFI boundary, so `spdm_authenticate()` callers +/// run one at a time and the locked `&mut SpdmState` is the only way to +/// reach the inner fields. lib/rspdm/state.rs line 48
Quoted 10 lines
lib/rspdm/state.rs new file +10
-0+/// +/// `dev`: Responder device. Used for error reporting and passed to @transport. +/// `transport`: Transport function to perform one message exchange. +/// `transport_priv`: Transport private data. +/// `transport_sz`: Maximum message size the transport is capable of (in bytes). +/// Used as DataTransferSize in GET_CAPABILITIES exchange. +/// `validate`: Function to validate additional leaf certificate requirements. +/// +/// `version`: Maximum common supported version of requester and responder. +/// Negotiated during GET_VERSION exchange. Field documentation should be their own doccomments before each individual field (see [1] for an example and general good practices when writing Rust doccomments).
Quoted 11 lines
lib/rspdm/state.rs line 51
Quoted 3 lines
lib/rspdm/state.rs new file +3
-0+#[expect(dead_code)] +pub (crate )struct SpdmState { +pub (crate ) dev: *mut bindings::device ,This makes me very nervous. I see this is passed to C callbacks. We need to maintain the guarantee that the device still exists, so an
ARef<Device>, from which you obtain a pointer to pass to the C callbacks, should be the minimum here. Does the device also need to be bound? Looking atdrivers/pci/cma.cit seems to be. Then this needs to be a&Device<Bound>, with a lifetime toSpdmStateso callers only use it while the device is bound.
Quoted 1 line
Good point, I have converted this to use a
&Device<Bound>
Another related point of feedback is that this code seems to be designed to interact with C drivers exclusively - i.e. I don't see a publicly exported Rust API.
That's probably what has guided the current design of storing pointers, as that's what the C functions expect; and the C side is also responsible for taking care of lifetime and bound device status, so in that context using a &Device<Bound> will be of little help as it will need to be forged by trusting that the calling C code knows what it is doing.
I think it would be helpful on the contrary to design things around a Rust API, on top of which the C FFI is built. That way you would have to work with properly bound references, and have a proof that the model is viable before exposing it to C. Also I guess Rust drivers will want to use this at some point, so better design with that in mind from the get-go.
Also related, what is the reason for having this code in lib? Why not make this a regular kernel module?
Quoted 11 lines
Quoted 11 lines
Just storing a C pointer and passing it whenever a function needs it cancels the purpose of using Rust and make the safety requirements of the callback you pass it to impossible to guarantee, as nothing enforces that the device still exists or is in a valid state when they are invoked.
Quoted 2 lines
lib/rspdm/state.rs new file +2
-0+ pub(crate) transport: bindings::spdm_transport, + pub(crate) transport_priv: *mut c_void,
Quoted 7 lines
Quoted 3 lines
Here I also suspect we want to define some kind of ownership model for the private data instead of hoping that the pointer is still valid when we use it.
Any ideas here? It's just a pointer that we pass back to C when calling the
transport_function(), I'm not clear on what we could do to ensure it's still valid.
I suspect something close to what fwctl does? Basically SpdmState would store a reference to a generic type, which outlives the SPDM driver bound time. That's also why having the Rust API first is helpful, as it will provide the lifetime that needs to be outlived.
Looking at the example in cma.c, the private data appears to be host.doe_mb, which exists before spdm_create is called and is still there after spdm_destroy, so that seems to be the correct model, i.e. there should be no need to transfer full ownership of the private data to the SPDM driver - a reference should be sufficient.
Actually thinking some more about it seems like the fwctl Rust bindings could be a good model to follow. They are simple and adopt a similar pattern of a sub-component of a larger driver that never outlives its parent.
<...>
Quoted 31 lines
Quoted 15 lines
lib/rspdm/validator.rs line 66
Quoted 13 lines
lib/rspdm/validator.rs new file +12
-0+impl <'a, 'c>Validate <'c,Untrusted <&'a [u8 ]>>for SpdmHeader { +type Err =Error ; + +type Context = &'cSpdmState ; + +fn validate (unvalidated: &[u8 ], _context: &'cSpdmState ) ->Result <Self ,Self ::Err > { +Ok (SpdmHeader { + version: *unvalidated.get (0 ).ok_or (EIO )?, + code: *unvalidated.get (1 ).ok_or (EIO )?, + param1: *unvalidated.get (2 ).ok_or (EIO )?, + param2: *unvalidated.get (3 ).ok_or (EIO )?, + })Here you will definitely want to leverage zerocopy's
FromBytestrait.I was hoping not too actually.
The SPDM responses are a bit complex. For example NegotiateAlgsRsp has multiple dynamically sized arrays, and the fields change based on the version.
Using zerocopy means we need packed structs and even then I'm not clear on how we can handle the KVec<u32> or even the KVec<RespAlgStruct> nicley.
The current approach has the obvious downside of copying the data, but as the data is pretty small in the scheme of things and not part of a hot path, I think the tradeoff of copying the data with much cleaner and easier to understand code is worth it.
Quoted 5 lines
Maybe in the future zerocopy could be a performance improvement, but I think right now it's a large complexity overhead in this case.
Note that obviously SpdmHeader is not complex, but I think they should all follow the same pattern.
For more complex structures sure, but in the case of SpdmHeader zerocopy looks like it would work nicely? It also supports performing a copy (see read_from_bytes and read_from_prefix).
Re: [PATCH v4 10/22] lib: rspdm: Initial commit of Rust SPDM
Quoted 38 lines · Alexandre Courbot
On Thu Oct 15, 2026 at 12:06 PM JST, Alistair Francis wrote:
<...>Quoted 35 lines
Quoted 34 lines
Quoted 6 lines
lib/rspdm/state.rs new file +6
-0+/// The current SPDM session state for a device. +/// +/// Concurrent access is serialised by wrapping the whole struct in a +/// `Mutex<SpdmState>` at the FFI boundary, so `spdm_authenticate()` callers +/// run one at a time and the locked `&mut SpdmState` is the only way to +/// reach the inner fields. lib/rspdm/state.rs line 48
Quoted 10 lines
lib/rspdm/state.rs new file +10
-0+/// +/// `dev`: Responder device. Used for error reporting and passed to @transport. +/// `transport`: Transport function to perform one message exchange. +/// `transport_priv`: Transport private data. +/// `transport_sz`: Maximum message size the transport is capable of (in bytes). +/// Used as DataTransferSize in GET_CAPABILITIES exchange. +/// `validate`: Function to validate additional leaf certificate requirements. +/// +/// `version`: Maximum common supported version of requester and responder. +/// Negotiated during GET_VERSION exchange. Field documentation should be their own doccomments before each individual field (see [1] for an example and general good practices when writing Rust doccomments).
lib/rspdm/state.rs line 51
Quoted 3 lines
lib/rspdm/state.rs new file +3
-0+#[expect(dead_code)] +pub (crate )struct SpdmState { +pub (crate ) dev: *mut bindings::device ,This makes me very nervous. I see this is passed to C callbacks. We need to maintain the guarantee that the device still exists, so an
ARef<Device>, from which you obtain a pointer to pass to the C callbacks, should be the minimum here. Does the device also need to be bound? Looking atdrivers/pci/cma.cit seems to be. Then this needs to be a&Device<Bound>, with a lifetime toSpdmStateso callers only use it while the device is bound.
Quoted 5 lines
Quoted 1 line
Good point, I have converted this to use a
&Device<Bound>Another related point of feedback is that this code seems to be designed to interact with C drivers exclusively - i.e. I don't see a publicly exported Rust API.
There isn't a public Rust API, but SpdmState and its functions are more or less the Rust API.
Quoted 8 lines
That's probably what has guided the current design of storing pointers, as that's what the C functions expect; and the C side is also responsible for taking care of lifetime and bound device status, so in that context using a
&Device<Bound>will be of little help as it will need to be forged by trusting that the calling C code knows what it is doing.
Quoted 6 lines
I think it would be helpful on the contrary to design things around a Rust API, on top of which the C FFI is built. That way you would have to work with properly bound references, and have a proof that the model is viable before exposing it to C. Also I guess Rust drivers will want to use this at some point, so better design with that in mind from the get-go.
That was kind of the goal with SpdmState. The issue is all of the drivers calling this (PCIe, NVMe, SATA and SCSI) are C drivers and won't be Rust for the foreseeable future. So there aren't any Rust users.
I have improved things a lot with v5, let me know if you still want a Rust API. It just feels a bit silly to add one that will probably never be used.
Quoted 3 lines
Also related, what is the reason for having this code in
lib? Why not make this a regular kernel module?
That's just where the C implementation was as well, so I followed that. It felt right, as it's a helper library. But I'm happy to move it if that's prefered?
Quoted 31 lines
Quoted 12 lines
Quoted 12 lines
Just storing a C pointer and passing it whenever a function needs it cancels the purpose of using Rust and make the safety requirements of the callback you pass it to impossible to guarantee, as nothing enforces that the device still exists or is in a valid state when they are invoked.
Quoted 2 lines
lib/rspdm/state.rs new file +2
-0+ pub(crate) transport: bindings::spdm_transport, + pub(crate) transport_priv: *mut c_void,Quoted 7 lines
Quoted 3 lines
Here I also suspect we want to define some kind of ownership model for the private data instead of hoping that the pointer is still valid when we use it.
Any ideas here? It's just a pointer that we pass back to C when calling the
transport_function(), I'm not clear on what we could do to ensure it's still valid.I suspect something close to what
fwctldoes? BasicallySpdmStatewould store a reference to a generic type, which outlives the SPDM driver bound time. That's also why having the Rust API first is helpful, as it will provide the lifetime that needs to be outlived.Looking at the example in
cma.c, the private data appears to behost.doe_mb, which exists beforespdm_createis called and is still there afterspdm_destroy, so that seems to be the correct model, i.e. there should be no need to transfer full ownership of the private data to the SPDM driver - a reference should be sufficient.
Quoted 4 lines
Actually thinking some more about it seems like the
fwctlRust bindings could be a good model to follow. They are simple and adopt a similar pattern of a sub-component of a larger driver that never outlives its parent.
Ok, I think I have done something along the lines of this in v5
Quoted 36 lines
<...>
Quoted 34 lines
Quoted 15 lines
lib/rspdm/validator.rs line 66
Quoted 13 lines
lib/rspdm/validator.rs new file +12
-0+impl <'a, 'c>Validate <'c,Untrusted <&'a [u8 ]>>for SpdmHeader { +type Err =Error ; + +type Context = &'cSpdmState ; + +fn validate (unvalidated: &[u8 ], _context: &'cSpdmState ) ->Result <Self ,Self ::Err > { +Ok (SpdmHeader { + version: *unvalidated.get (0 ).ok_or (EIO )?, + code: *unvalidated.get (1 ).ok_or (EIO )?, + param1: *unvalidated.get (2 ).ok_or (EIO )?, + param2: *unvalidated.get (3 ).ok_or (EIO )?, + })Here you will definitely want to leverage zerocopy's
FromBytestrait.I was hoping not too actually.
The SPDM responses are a bit complex. For example NegotiateAlgsRsp has multiple dynamically sized arrays, and the fields change based on the version.
Using zerocopy means we need packed structs and even then I'm not clear on how we can handle the KVec<u32> or even the KVec<RespAlgStruct> nicley.
The current approach has the obvious downside of copying the data, but as the data is pretty small in the scheme of things and not part of a hot path, I think the tradeoff of copying the data with much cleaner and easier to understand code is worth it.
Maybe in the future zerocopy could be a performance improvement, but I think right now it's a large complexity overhead in this case.
Quoted 6 lines
Quoted 2 lines
Note that obviously SpdmHeader is not complex, but I think they should all follow the same pattern.
For more complex structures sure, but in the case of
SpdmHeaderzerocopy looks like it would work nicely? It also supports performing a copy (seeread_from_bytesandread_from_prefix).
You convinced me :)
Fixed in v5
Alistair
Re: [PATCH v4 10/22] lib: rspdm: Initial commit of Rust SPDM
Quoted 9 lines · Alistair Francis
That was kind of the goal with
SpdmState. The issue is all of the drivers calling this (PCIe, NVMe, SATA and SCSI) are C drivers and won't be Rust for the foreseeable future. So there aren't any Rust users.I have improved things a lot with v5, let me know if you still want a Rust API. It just feels a bit silly to add one that will probably never be used.
I mean, it is true that we don't want dead code, but in general we would also want more Rust callers in the future -- so I guess it depends on that "probably" you mention, i.e. how likely it is. So could you please elaborate? i.e. why is it that you don't expect future Rust callers? Would the NVMe Rust driver want to use this eventually if merged? (Cc'ing Andreas)
Cheers,
Miguel
Alexandre Courbot
reviewerRe: [PATCH v4 10/22] lib: rspdm: Initial commit of Rust SPDM
Quoted 11 lines · Miguel Ojeda
Quoted 9 lines · Alistair Francis
That was kind of the goal with
SpdmState. The issue is all of the drivers calling this (PCIe, NVMe, SATA and SCSI) are C drivers and won't be Rust for the foreseeable future. So there aren't any Rust users.I have improved things a lot with v5, let me know if you still want a Rust API. It just feels a bit silly to add one that will probably never be used.
Quoted 6 lines
I mean, it is true that we don't want dead code, but in general we would also want more Rust callers in the future -- so I guess it depends on that "probably" you mention, i.e. how likely it is. So could you please elaborate? i.e. why is it that you don't expect future Rust callers? Would the NVMe Rust driver want to use this eventually if merged? (Cc'ing Andreas)
Yeah the "probably never be used" part sounds a bit strong to me, unless there is a reason I missed for why no Rust driver would ever want to use SPDM.
Regardless of that, I think it still makes sense to have a layered API design where the (necessarily unsafe) C API builds on top of a safe Rust API that enforces the invariants and other rules you want to uphold. That makes the invariants that C callers need to uphold manually clear, and avoids having C-isms in the Rust code (like we had with the crypto APIs calls in v4). Not to mention my favorite: it makes things easier to review. :)
Re: [PATCH v4 10/22] lib: rspdm: Initial commit of Rust SPDM
Quoted 13 lines · Alexandre Courbot
Quoted 11 lines · Miguel Ojeda
Quoted 9 lines · Alistair Francis
That was kind of the goal with
SpdmState. The issue is all of the drivers calling this (PCIe, NVMe, SATA and SCSI) are C drivers and won't be Rust for the foreseeable future. So there aren't any Rust users.I have improved things a lot with v5, let me know if you still want a Rust API. It just feels a bit silly to add one that will probably never be used.
Quoted 6 lines
Quoted 6 lines
I mean, it is true that we don't want dead code, but in general we would also want more Rust callers in the future -- so I guess it depends on that "probably" you mention, i.e. how likely it is. So could you please elaborate? i.e. why is it that you don't expect future Rust callers? Would the NVMe Rust driver want to use this eventually if merged? (Cc'ing Andreas)
You guys might have a better understanding them me.
For SPDM to work over a transport that transport needs a DMTF spec. So right now SPDM will only work over PCIe, MCTP, NVMe/SCSI/ATA or TCP.
From my (limited) understanding of the kernel's MCTP subsystem, SPDM isn't going to happen in the kernel. I don't think there is a need for TCP support either.
So that just leaves us with PCIe and the storage bindings. Both PCIe and the storage transports have old well established C support in the kernel. So those would need a Rust implementation to use the Rust API for SPDM.
The NVMe Rust driver could use this! But that would have to be accepted upstream first. From my outside view I thought that was still a ways off.
Quoted 4 lines
Yeah the "probably never be used" part sounds a bit strong to me, unless there is a reason I missed for why no Rust driver would ever want to use SPDM.
The "probably never be used" comes from having a Rust re-implementation of PCIe or a storage driver.
Quoted 8 lines
Regardless of that, I think it still makes sense to have a layered API design where the (necessarily unsafe) C API builds on top of a safe Rust API that enforces the invariants and other rules you want to uphold. That makes the invariants that C callers need to uphold manually clear, and avoids having C-isms in the Rust code (like we had with the crypto APIs calls in v4). Not to mention my favorite: it makes things easier to review. :)
Ok, I'll add a Rust API then
Alistair
PATCH v4 11/22 PCI/TSM: Rename pf0 to host
In reply to [email protected]From: Alistair Francis <[email protected]>
Rename pci_tsm_pf0 to pci_tsm_host (and rename variables and function names from pf0 to host) as part of converting pci_tsm_host to be any device that knows how to speak any of CMA, IDE, or TDISP.
This commit just renames the functions and provides no functional change. That will happen in the next commit.
--- drivers/crypto/ccp/sev-dev-tio.h | 4 +- drivers/crypto/ccp/sev-dev-tsm.c | 12 ++--- drivers/pci/tsm.c | 84 ++++++++++++++++---------------- include/linux/pci-tsm.h | 18 ++++--- 4 files changed, 61 insertions(+), 57 deletions(-)
drivers/crypto/ccp/sev-dev-tio.h +2 -2
@@ -53,9 +53,9 @@ struct tsm_dsm_tio {struct pci_ide *ide[TIO_IDE_MAX_TC ]; }; -/* Describes TSM structure for PF0 pointed by pci_dev->tsm */ +/* Describes TSM structure for the link host pointed by pci_dev->tsm */ struct tio_dsm { -struct pci_tsm_pf0 tsm; +struct pci_tsm_host tsm;struct tsm_dsm_tio data;struct sev_device *sev; };
drivers/crypto/ccp/sev-dev-tsm.c +6 -6
@@ -205,7 +205,7 @@ static int stream_alloc(struct pci_dev *pdev, struct pci_ide **ide,return 0 ; } -static struct pci_tsm *tio_pf0_probe (struct pci_dev *pdev,struct sev_device *sev) +static struct pci_tsm *tio_host_probe (struct pci_dev *pdev,struct sev_device *sev) { struct tio_dsm *dsm __free(kfree) = kzalloc_obj(*dsm);int rc;
@@ -213,7 +213,7 @@ static struct pci_tsm *tio_pf0_probe(struct pci_dev *pdev, struct sev_device *seif (!dsm)return NULL ; - rc =pci_tsm_pf0_constructor (pdev, &dsm->tsm, sev->tsmdev); + rc =pci_tsm_host_constructor (pdev, &dsm->tsm, sev->tsmdev);if (rc)return NULL ;
@@ -226,8 +226,8 @@ static struct pci_tsm *dsm_probe(struct tsm_dev *tsmdev, struct pci_dev *pdev) {struct sev_device *sev =tsm_dev_to_sev (tsmdev); -if (is_pci_tsm_pf0 (pdev)) -return tio_pf0_probe (pdev, sev); +if (is_pci_tsm_host (pdev)) +return tio_host_probe (pdev, sev);return NULL ; }
@@ -237,10 +237,10 @@ static void dsm_remove(struct pci_tsm *tsm)pci_dbg (pdev,"TSM disabled\n" ); -if (is_pci_tsm_pf0 (pdev)) { +if (is_pci_tsm_host (pdev)) { struct tio_dsm *dsm = container_of(tsm, struct tio_dsm, tsm.base_tsm); -pci_tsm_pf0_destructor (&dsm->tsm); +pci_tsm_host_destructor (&dsm->tsm);kfree (dsm); } }
drivers/pci/tsm.c +42 -42
250 lines not on this page · raw message
include/linux/pci-tsm.h +11 -7
@@ -40,7 +40,7 @@ struct pci_tsm_ops {* pci_tsm_rwsem held for write to sync with TSM unregistration and * mutual exclusion of @connect and @disconnect. @connect and * @disconnect additionally run under the DSM lock (struct -* pci_tsm_pf0::lock) as well as @probe and @remove of the subfunctions. +* pci_tsm_host::lock) as well as @probe and @remove of the subfunctions. * @bind, @unbind, and @guest_req run under pci_tsm_rwsem held for read * and the DSM lock. */
@@ -115,19 +115,23 @@ struct pci_tsm { };/** -* struct pci_tsm_pf0 - Physical Function 0 TDISP link context +* struct pci_tsm_host - TSM host link context (CMA, IDE, or TDISP) * @base_tsm: generic core "tsm" context * @lock: mutual exclustion for pci_tsm_ops invocation * @doe_mb: PCIe Data Object Exchange mailbox +* +* The host of a TSM link is the device that knows how to speak +* CMA, IDE, or TDISP. For TDISP / IDE that is a Physical Function 0. +* For CMA-only it is a CMA DOE mailbox. */ -struct pci_tsm_pf0 { +struct pci_tsm_host {struct pci_tsm base_tsm;struct mutex lock;struct pci_doe_mb *doe_mb; };/* physical function0 and capable of 'connect' */ -static inline bool is_pci_tsm_pf0 (struct pci_dev *pdev) +static inline bool is_pci_tsm_host (struct pci_dev *pdev) {if (!pdev)return false;
@@ -204,9 +208,9 @@ int pci_tsm_register(struct tsm_dev *tsm_dev);void pci_tsm_unregister (struct tsm_dev *tsm_dev);int pci_tsm_link_constructor (struct pci_dev *pdev,struct pci_tsm *tsm,struct tsm_dev *tsm_dev); -int pci_tsm_pf0_constructor (struct pci_dev *pdev,struct pci_tsm_pf0 *tsm, -struct tsm_dev *tsm_dev); -void pci_tsm_pf0_destructor (struct pci_tsm_pf0 *tsm); +int pci_tsm_host_constructor (struct pci_dev *pdev,struct pci_tsm_host *tsm, +struct tsm_dev *tsm_dev); +void pci_tsm_host_destructor (struct pci_tsm_host *tsm);int pci_tsm_doe_transfer (struct pci_dev *pdev,u8 type,const void *req,size_t req_sz,void *resp,size_t resp_sz);int pci_tsm_bind (struct pci_dev *pdev,struct kvm *kvm,u32 tdi_id);
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v4 11/22] PCI/TSM: Rename pf0 to host
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 11 lines
From: Alistair Francis <[email protected]>
Rename
pci_tsm_pf0topci_tsm_host(and rename variables and function names from pf0 to host) as part of convertingpci_tsm_hostto be any device that knows how to speak any of CMA, IDE, or TDISP.This commit just renames the functions and provides no functional change. That will happen in the next commit.
Signed-off-by Alistair Francis <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=11
PATCH v4 12/22 PCI/TSM: Support connecting to PCIe CMA devices
In reply to [email protected]From: Alistair Francis <[email protected]>
In the next patch we are going to add a PCIe CMA TSM driver, as such we need to ensure that is_pci_tsm_host() will allow us to connect to CMA capable devices. These devices don't necessarily has DEVCAP_TEE or IDE support.
To avoid calling pci_find_doe_mailbox() everytime is_pci_tsm_host() is called we can cache the CMA support in struct pci_dev and just check against that.
--- drivers/crypto/ccp/sev-dev-tsm.c | 4 ++-- drivers/pci/doe.c | 3 +++ drivers/pci/tsm.c | 38 +++++++++++++++++++++++++++----- include/linux/pci-tsm.h | 32 +++++++++++++++++++++++++-- include/linux/pci.h | 1 + 5 files changed, 68 insertions(+), 10 deletions(-)
drivers/crypto/ccp/sev-dev-tsm.c +2 -2
@@ -226,7 +226,7 @@ static struct pci_tsm *dsm_probe(struct tsm_dev *tsmdev, struct pci_dev *pdev) {struct sev_device *sev =tsm_dev_to_sev (tsmdev); -if (is_pci_tsm_host (pdev)) +if (is_pci_tsm_dsm (pdev))return tio_host_probe (pdev, sev);return NULL ; }
@@ -237,7 +237,7 @@ static void dsm_remove(struct pci_tsm *tsm)pci_dbg (pdev,"TSM disabled\n" ); -if (is_pci_tsm_host (pdev)) { +if (is_pci_tsm_dsm (pdev)) { struct tio_dsm *dsm = container_of(tsm, struct tio_dsm, tsm.base_tsm);pci_tsm_host_destructor (&dsm->tsm);
drivers/pci/doe.c +3 -0
@@ -870,6 +870,9 @@ void pci_doe_init(struct pci_dev *pdev)pci_doe_destroy_mb (doe_mb); } } + + pdev->doe_cma =pci_find_doe_mailbox (pdev,PCI_VENDOR_ID_PCI_SIG , +PCI_DOE_FEATURE_CMA ); }void pci_doe_destroy (struct pci_dev *pdev)
drivers/pci/tsm.c +32 -6
@@ -88,8 +88,8 @@ static void pci_tsm_walk_fns(struct pci_dev *pdev,if (!pf)continue ; -/* on entry function 0 has already run @cb */ -if (i >0 ) +/* the caller is responsible for running @cb on the host itself */ +if (pf != pdev)cb (pf, data);/* walk virtual functions of each pf */
@@ -145,8 +145,8 @@ static void pci_tsm_walk_fns_reverse(struct pci_dev *pdev,cb (vf, data); } -/* on exit, caller will run @cb on function 0 */ -if (i >0 ) +/* the caller is responsible for running @cb on the host itself */ +if (pf != pdev)cb (pf, data); } }
@@ -175,6 +175,9 @@ static int probe_fn(struct pci_dev *pdev, void *dsm)struct pci_dev *dsm_dev = dsm;const struct pci_tsm_ops *ops =to_pci_tsm_ops (dsm_dev->tsm); +if (pdev->tsm) +return 0 ; + pdev->tsm = ops->probe (dsm_dev->tsm->tsm_dev, pdev);pci_dbg (pdev,"setup TSM context: DSM: %s status: %s\n" ,pci_name (dsm_dev), pdev->tsm ?"success" :"failed" );
@@ -287,6 +290,19 @@ static DEVICE_ATTR_RW(connect);static int remove_fn (struct pci_dev *pdev,void *data) { +struct pci_dev *host = data; +struct pci_tsm_host *tsm_host; + +/* +* Only teardown the security context of functions that belong to the +* DSM being disconnected, leaving any sibling DSM in the same slot +* untouched. +*/ +if (!pdev->tsm || pdev->tsm->dsm_dev != host) +return 0 ; + + tsm_host =to_pci_tsm_host (pdev->tsm); +guard (mutex)(&tsm_host->lock);tsm_remove (pdev->tsm);link_sysfs_disable (pdev);return 0 ;
@@ -299,6 +315,7 @@ static int remove_fn(struct pci_dev *pdev, void *data)*/ static int __pci_tsm_unbind (struct pci_dev *pdev,void *data) { +struct pci_dev *host = data;struct pci_tdi *tdi;struct pci_tsm_host *tsm_host;
@@ -307,6 +324,14 @@ static int __pci_tsm_unbind(struct pci_dev *pdev, void *data)if (!pdev->tsm)return 0 ; +/* +* When walking a DSM's dependent functions skip any that belong to a +* different DSM in the same slot. A NULL @host is a direct unbind of +* @pdev itself. +*/ +if (host && pdev->tsm->dsm_dev != host) +return 0 ; + tsm_host =to_pci_tsm_host (pdev->tsm);guard (mutex)(&tsm_host->lock);
@@ -437,7 +462,7 @@ EXPORT_SYMBOL_GPL(pci_tsm_guest_req);static void pci_tsm_unbind_all (struct pci_dev *pdev) { -pci_tsm_walk_fns_reverse (pdev, __pci_tsm_unbind,NULL ); +pci_tsm_walk_fns_reverse (pdev, __pci_tsm_unbind, pdev);__pci_tsm_unbind (pdev,NULL ); }
@@ -451,12 +476,13 @@ static void __pci_tsm_disconnect(struct pci_dev *pdev)pci_tsm_unbind_all (pdev); +pci_tsm_walk_fns_reverse (pdev, remove_fn, pdev); +/* * disconnect() is uninterruptible as it may be called for device * teardown */ guard (mutex)(&tsm_host->lock); -pci_tsm_walk_fns_reverse (pdev, remove_fn,NULL ); ops->disconnect (pdev); }
include/linux/pci-tsm.h +30 -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 ;
@@ -130,8 +131,11 @@ struct pci_tsm_host {struct pci_doe_mb *doe_mb; }; -/* physical function0 and capable of 'connect' */ -static inline bool is_pci_tsm_host (struct pci_dev *pdev) + +/* device is a Device Security Manager (DSM), similar to +* is_pci_tsm_host() but doesnt' support CMA +*/ +static inline bool is_pci_tsm_dsm (struct pci_dev *pdev) {if (!pdev)return false;
@@ -163,6 +167,30 @@ static inline bool is_pci_tsm_host(struct pci_dev *pdev)return PCI_FUNC (pdev->devfn) ==0 ; } +/* device is a TSM host and capable of 'connect' */ +static inline bool is_pci_tsm_host (struct pci_dev *pdev) +{ +if (!pdev) +return false; + +if (!pci_is_pcie (pdev)) +return false; + +if (pdev->is_virtfn) +return false; + +/* +* Report capable if CMA is supported, which can be supported on any PCIe +* device. +*/ +#ifdef CONFIG_PCI_DOE +if (pdev->doe_cma) +return true; +#endif + +return is_pci_tsm_dsm (pdev); +} +/** * enum pci_tsm_req_scope - Scope of guest requests to be validated by TSM *
include/linux/pci.h +1 -0
@@ -565,6 +565,7 @@ struct pci_dev {#endif #ifdef CONFIG_PCI_DOE struct xarray doe_mbs;/* Data Object Exchange mailboxes */ +bool doe_cma;/* A CMA/SPDM DOE mailbox is present */ #endif #ifdef CONFIG_PCI_NPEM struct npem *npem;/* Native PCIe Enclosure Management */
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v4 12/22] PCI/TSM: Support connecting to PCIe CMA devices
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 7 lines
From: Alistair Francis <[email protected]>
In the next patch we are going to add a PCIe CMA TSM driver, as such we need to ensure that
is_pci_tsm_host()will allow us to connect to CMA capable devices. These devices don't necessarily hasDEVCAP_TEEor IDE support.
Quoted 5 lines
To avoid calling
pci_find_doe_mailbox()everytimeis_pci_tsm_host()is called we can cache the CMA support in structpci_devand just check against that.Signed-off-by Alistair Francis <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=12
PATCH v4 13/22 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 | 150 ++++++++++++++++++++++++++++++++++++++++ drivers/pci/doe.c | 3 - include/linux/pci-doe.h | 4 ++ 6 files changed, 171 insertions(+), 3 deletions(-) create mode 100644 drivers/pci/cma.c
MAINTAINERS +1 -0
@@ -24757,6 +24757,7 @@ L: [email protected] L: [email protected] S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/devsec/spdm.git +F: drivers/pci/cma.c F: include/linux/spdm.h F: lib/rspdm/
drivers/pci/Kconfig +14 -0
@@ -124,6 +124,20 @@ config PCI_ATS config PCI_IDE bool +config PCI_CMA + bool "Component Measurement and Authentication (CMA-SPDM)" + depends on RSPDM + select CRYPTO_ECDSA + select CRYPTO_RSA + select CRYPTO_SHA256 + select CRYPTO_SHA512 + select PCI_DOE + select PCI_TSM + help + Authenticate devices on enumeration per PCIe r6.2 sec 6.31. + A PCI DOE mailbox is used as transport for DMTF SPDM based + authentication, measurement and secure channel establishment. + config PCI_TSM bool "PCI TSM: Device security protocol support" select PCI_IDE
drivers/pci/Makefile +2 -0
@@ -41,6 +41,8 @@ obj-$(CONFIG_PCI_NPEM) += npem.o obj-$(CONFIG_PCIE_TPH) += tph.o obj-$(CONFIG_CARDBUS) += setup-cardbus.o +obj-$(CONFIG_PCI_CMA) += cma.o + # Endpoint library must be initialized before its users obj-$(CONFIG_PCI_ENDPOINT) += endpoint/
drivers/pci/cma.c new file +150 -0
150 lines not on this page · raw message
drivers/pci/doe.c +0 -3
@@ -31,9 +31,6 @@#define PCI_DOE_FLAG_CANCEL 0#define PCI_DOE_FLAG_DEAD 1 -/* Max data object length is 2^18 dwords */ -#define PCI_DOE_MAX_LENGTH (1 << 18) -/** * struct pci_doe_mb - State for a single DOE mailbox *
include/linux/pci-doe.h +4 -0
@@ -19,6 +19,10 @@ struct pci_doe_mb;#define PCI_DOE_FEATURE_CMA 1#define PCI_DOE_FEATURE_SSESSION 2 +/* Max data object length is 2^18 dwords (including 2 dwords for header) */ +#define PCI_DOE_MAX_LENGTH (1 << 18) +#define PCI_DOE_MAX_PAYLOAD ((PCI_DOE_MAX_LENGTH - 2) * sizeof(u32)) +struct pci_doe_mb *pci_find_doe_mailbox (struct pci_dev *pdev,u16 vendor,u8 type);
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v4 13/22] PCI/CMA: Add a PCI TSM CMA driver using SPDM
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 13 lines
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.
Quoted 8 lines
This was previously discusd here:
http://lore.kernel.org/[email protected][ ... ]Co-developed-by Wilfred Mallawa <[email protected]>Signed-off-by Wilfred Mallawa <[email protected]>Co-developed-by Lukas Wunner <[email protected]>Signed-off-by Lukas Wunner <[email protected]>Signed-off-by Alistair Francis <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=13
PATCH v4 14/22 PCI/CMA: Validate Subject Alternative Name in certificates
In reply to [email protected]From: Lukas Wunner <[email protected]>
PCIe r6.1 sec 6.31.3 stipulates requirements for Leaf Certificates presented by devices, in particular the presence of a Subject Alternative Name which encodes the Vendor ID, Device ID, Device Serial Number, etc.
This prevents a mismatch between the device identity in Config Space and the certificate. A device cannot misappropriate a certificate from a different device without also spoofing Config Space. As a corollary, it cannot dupe an arbitrary driver into binding to it. Only drivers which bind to the device identity in the Subject Alternative Name work (PCIe r6.1 sec 6.31 "Implementation Note: Overview of Threat Model").
The Subject Alternative Name is signed, hence constitutes a signed copy of a Config Space portion. It's the same concept as web certificates which contain a set of domain names in the Subject Alternative Name for identity verification.
Parse the Subject Alternative Name using a small ASN.1 module and validate its contents. The theory of operation is explained in a comment at the top of the newly inserted code.
This functionality is introduced in a separate commit on top of basic CMA-SPDM support to split the code into digestible, reviewable chunks.
The CMA OID added here is taken from the official OID Repository (it's not documented in the PCIe Base Spec):
https://oid-rep.orange-labs.fr/get/2.23.147
Side notes:
* PCIe r6.2 removes the spec language on the Subject Alternative Name. It still "requires the leaf certificate to include the information typically used by system software for device driver binding", but no longer specifies how that information is encoded into the certificate.
According to the editor of the PCIe Base Spec and the author of the CMA 1.1 ECN (which caused this change), FPGA cards which mutate their device identity at runtime (due to a firmware update) were thought as unable to satisfy the previous spec language. The Protocol Working Group could not agree on a better solution and therefore dropped the spec language entirely. They acknowledge that the requirement is now under-spec'd. Because products already exist which adhere to the Subject Alternative Name requirement per PCIe r6.1 sec 6.31.3, they recommended to "push through" and use it as the de facto standard.
The FPGA concerns are easily overcome by reauthenticating the device after a firmware update, either via sysfs or pci_cma_reauthenticate() (added by a subsequent commit).
* PCIe r6.1 sec 6.31.3 strongly recommends to verify that "the information provided in the Subject Alternative Name entry is signed by the vendor indicated by the Vendor ID." In other words, the root certificate on pci_cma_keyring which signs the device's certificate chain must have been created for a particular Vendor ID.
Unfortunately the spec neglects to define how the Vendor ID shall be encoded into the root certificate. So the recommendation cannot be implemented at this point and it is thus possible that a vendor signs device certificates of a different vendor.
* Instead of a Subject Alternative Name, Leaf Certificates may include "a Reference Integrity Manifest, e.g., see Trusted Computing Group" or "a pointer to a location where such a Reference Integrity Manifest can be obtained" (PCIe r6.1 sec 6.31.3).
A Reference Integrity Manifest contains "golden" measurements which can be compared to actual measurements retrieved from a device. It serves a different purpose than the Subject Alternative Name, hence it is unclear why the spec says only either of them is necessary. It is also unclear how a Reference Integrity Manifest shall be encoded into a certificate.
Hence ignore the Reference Integrity Manifest requirement.
[ Changed by AF:
- Fixup a few issues caught by Sashiko
]
--- drivers/pci/Makefile | 5 +- drivers/pci/cma.asn1 | 41 ++++++++++++ drivers/pci/cma.c | 123 ++++++++++++++++++++++++++++++++++- include/linux/oid_registry.h | 3 + 4 files changed, 170 insertions(+), 2 deletions(-) create mode 100644 drivers/pci/cma.asn1
drivers/pci/Makefile +4 -1
@@ -41,7 +41,10 @@ obj-$(CONFIG_PCI_NPEM) += npem.o obj-$(CONFIG_PCIE_TPH) += tph.o obj-$(CONFIG_CARDBUS) += setup-cardbus.o -obj-$(CONFIG_PCI_CMA) += cma.o +obj-$(CONFIG_PCI_CMA) += pci-cma.o +pci-cma-y := cma.o cma.asn1.o +$(obj)/cma.o: $(obj)/cma.asn1.h +$(obj)/cma.asn1.o: $(obj)/cma.asn1.c $(obj)/cma.asn1.h # Endpoint library must be initialized before its users obj-$(CONFIG_PCI_ENDPOINT) += endpoint/
drivers/pci/cma.asn1 new file +41 -0
41 lines not on this page · raw message
drivers/pci/cma.c +122 -1
143 lines not on this page · raw message
include/linux/oid_registry.h +3 -0
@@ -150,6 +150,9 @@ enum OID { OID_id_ml_dsa_65,/* 2.16.840.1.101.3.4.3.18 */ OID_id_ml_dsa_87,/* 2.16.840.1.101.3.4.3.19 */ +/* PCI */ +OID_CMA ,/* 2.23.147 */ + OID__NR };
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v4 14/22] PCI/CMA: Validate Subject Alternative Name in certificates
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [High] pci: case-sensitive SAN validation rejects uppercase hexadecimal device identities - [High] pci…
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] pci: case-sensitive SAN validation rejects uppercase hexadecimal device identities
- [High] pci: ASN.1 decoder aborts prematurely on certificates with multiple CMA SANs --
commit 8216d5120f9a26d7f0a6ec14495fd951b1a7513d
Author: Lukas Wunner <[email protected]>
PCI/CMA: Validate Subject Alternative Name in certificates
This patch introduces validation for the Subject Alternative Name (SAN) in PCIe leaf certificates per PCIe r6.1 specifications. It uses an ASN.1 decoder module to parse and authenticate the identity encoding within the certificate's SAN against the device's actual configuration space.
Quoted 4 lines
drivers/pci/cma.c +0
-0
[ ... ]
drivers/pci/cma.c line 78
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 ) {
[Severity: High]
Will this strict memcmp() in pci_cma_note_san() fail if a device presents a compliant SAN string using uppercase hexadecimal characters?
drivers/pci/cma.c line 81
Quoted 3 lines
drivers/pci/cma.c +3
-0+pci_err (ctx->pdev,"Leaf certificate of slot %u " +"has invalid Subject Alternative Name\n" , ctx->slot); +return -EINVAL ;
[Severity: High]
Could returning -EINVAL here prematurely abort the ASN.1 decoding sequence for certificates with multiple CMA SANs?
If a multi-function device shares a single leaf certificate containing multiple Subject Alternative Name entries, aborting on the first mismatch prevents the decoder from checking subsequent SANs that might legitimately match the current PCIe function.
Quoted 6 lines
drivers/pci/cma.c +6
-0+ } + + ctx->found = true; + +return 0 ; +}
[ ... ]
drivers/pci/cma.c line 96
Quoted 8 lines
drivers/pci/cma.c +8
-0+static unsigned int pci_cma_construct_san (struct pci_dev *pdev,char *name) +{ +unsigned int len; +u64 serial; + + len =scnprintf (name,CMA_NAME_MAX , +"Vendor=%04hx:Device=%04hx:CC=%06x:REV=%02hhx" , + pdev->vendor, pdev->device, pdev->class, pdev->revision);
[Severity: High]
Is it possible that using lowercase formatting (%04hx) here inadvertently causes the validation in pci_cma_note_san() to reject valid certificates that encode their identity using uppercase hexadecimal characters?
Quoted 5 lines
drivers/pci/cma.c +5
-0+ +if (pdev->hdr_type ==PCI_HEADER_TYPE_NORMAL ) + len +=scnprintf (name + len,CMA_NAME_MAX - len, +":SSVID=%04hx:SSID=%04hx" , + pdev->subsystem_vendor, pdev->subsystem_device);
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=14
PATCH v4 15/22 lib: rspdm: Support SPDM get_version
In reply to [email protected]From: Alistair Francis <[email protected]>
Support the GET_VERSION SPDM command.
--- lib/rspdm/consts.rs | 16 +++++++- lib/rspdm/lib.rs | 23 ++++++++++- lib/rspdm/state.rs | 56 ++++++++++++++++++++++++++- lib/rspdm/validator.rs | 88 +++++++++++++++++++++++++++++++++++++++++- 4 files changed, 176 insertions(+), 7 deletions(-)
lib/rspdm/consts.rs +14 -2
@@ -7,16 +7,24 @@//! Rust implementation of the DMTF Security Protocol and Data Model (SPDM) //! <https://www.dmtf.org/dsp/DSP0274> +use crate ::validator::SpdmHeader ; +use core::mem;use kernel::error::{code::EINVAL ,Error };// SPDM versions supported by this implementation pub (crate )const SPDM_VER_10 :u8 =0x10 ; +#[expect(dead_code)] +pub (crate )const SPDM_VER_11 :u8 =0x11 ; +#[expect(dead_code)] +pub (crate )const SPDM_VER_12 :u8 =0x12 ; +#[expect(dead_code)] +pub (crate )const SPDM_VER_13 :u8 =0x13 ; +pub (crate )const SPDM_VER_14 :u8 =0x14 ;pub (crate )const SPDM_MIN_VER :u8 =SPDM_VER_10 ; +pub (crate )const SPDM_MAX_VER :u8 =SPDM_VER_14 ; -#[allow(dead_code)] pub (crate )const SPDM_REQ :u8 =0x80 ; -#[allow(dead_code)] pub (crate )const SPDM_ERROR :u8 =0x7f ;#[derive( Clone , Copy )]
@@ -90,3 +98,7 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {write! (f,"{:#x}" , *selfas u8 ) } } + +pub (crate )const SPDM_GET_VERSION :u8 =0x84 ; +pub (crate )const SPDM_GET_VERSION_LEN :usize = + mem::size_of ::<SpdmHeader >() +2 + (u8::MAX as usize ) * mem::size_of ::<u16 >();
lib/rspdm/lib.rs +22 -1
@@ -75,7 +75,28 @@ pub extern "C" fn spdm_create(/// Return 0 on success or a negative errno. In particular, -EPROTONOSUPPORT /// indicates authentication is not supported by the device. #[export] -pub extern "C" fn spdm_authenticate (_state_ptr: *mut spdm_state ) ->c_int { +pub extern "C" fn spdm_authenticate (state_ptr: *mut spdm_state ) ->c_int { +if state_ptr.is_null () { +return -(bindings::EINVAL as c_int ); + } + +// SAFETY: `state_ptr` was returned from `spdm_create()` which used `into_foreign()` +// to create the pointer, and it remains valid until `spdm_destroy()` is called. +// We only borrow here (rather than `from_foreign()`) so that ownership stays +// with the foreign (C) caller. +// The exclusive `&mut SpdmState` lives entirely inside the lock guard, so +// concurrent FFI callers serialize on the mutex and can never form +// aliased `&mut SpdmState` references. +let mutex =unsafe { + <Pin <KBox <Mutex <SpdmState >>>as ForeignOwnable >::borrow (state_ptras *mut c_void ) + }; + +let mut state = mutex.lock (); + +if let Err (e) = state.get_version () { +return e.to_errno ()as c_int ; + } + -(EPROTONOSUPPORT as i32 ) }
lib/rspdm/state.rs +54 -2
88 lines not on this page · raw message
lib/rspdm/validator.rs +86 -2
115 lines not on this page · raw message
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v4 15/22] lib: rspdm: Support SPDM get_version
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Low] Duplicate documentation paragraph for SpdmState --
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] Duplicate documentation paragraph for SpdmState
--
commit 9b95322ccfb4a7875669dc07aadddb7266aa7fc7
Author: Alistair Francis <[email protected]>
lib: rspdm: Support SPDM get_version
This commit introduces support for the GET_VERSION SPDM command in the Rust SPDM implementation. It implements version parsing and validates responses during authentication.
Quoted 4 lines
lib/rspdm/state.rs +0
-0
[ ... ]
lib/rspdm/state.rs line 48
Quoted 9 lines
lib/rspdm/state.rs +5
-0@@ -37,6 +41,11 @@/// run one at a time and the locked `&mut SpdmState` is the only way to /// reach the inner fields. /// +/// Concurrent access is serialized by wrapping the whole struct in a +/// `Mutex<SpdmState>` at the FFI boundary, so `spdm_authenticate()` callers +/// run one at a time and the locked `&mut SpdmState` is the only way to +/// reach the inner fields. +///
[Severity: Low]
Does this documentation paragraph need to be duplicated?
It looks like this newly added text is an exact duplicate of the preceding paragraph explaining the concurrent access model of SpdmState.
Quoted 3 lines
lib/rspdm/state.rs +0
-0/// `dev`: Responder device. Used for error reporting and passed to @transport. /// `transport`: Transport function to perform one message exchange. /// `transport_priv`: Transport private data.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=15
PATCH v4 16/22 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 | 23 ++- lib/rspdm/lib.rs | 4 + lib/rspdm/state.rs | 70 +++++++++- lib/rspdm/validator.rs | 309 ++++++++++++++++++++++++++++++++++++++++- 4 files changed, 401 insertions(+), 5 deletions(-)
lib/rspdm/consts.rs +21 -2
@@ -9,13 +9,15 @@use crate ::validator::SpdmHeader ;use core::mem; +use kernel::bits::{ + bit_u32, + bit_u8,// +};use kernel::error::{code::EINVAL ,Error };// SPDM versions supported by this implementation pub (crate )const SPDM_VER_10 :u8 =0x10 ; -#[expect(dead_code)] pub (crate )const SPDM_VER_11 :u8 =0x11 ; -#[expect(dead_code)] pub (crate )const SPDM_VER_12 :u8 =0x12 ;#[expect(dead_code)] pub (crate )const SPDM_VER_13 :u8 =0x13 ;
@@ -102,3 +104,20 @@ 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 >() +2 + (u8::MAX as usize ) * mem::size_of ::<u16 >(); + +pub (crate )const SPDM_GET_CAPABILITIES :u8 =0xe1 ; +pub (crate )const SPDM_CAP_SUPPORTED_ALGORITHMS :u8 =bit_u8 (0 ); +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 +// 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
@@ -97,6 +97,10 @@ pub extern "C" fn spdm_authenticate(state_ptr: *mut spdm_state) -> c_int {return e.to_errno ()as c_int ; } +if let Err (e) = state.get_capabilities () { +return e.to_errno ()as c_int ; + } + -(EPROTONOSUPPORT as i32 ) }
lib/rspdm/state.rs +68 -2
103 lines not on this page · raw message
lib/rspdm/validator.rs +308 -1
319 lines not on this page · raw message
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v4 16/22] lib: rspdm: Support SPDM get_capabilities
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [Medium] rspdm: spec violation and connection failure parsing SPDM 1.3 capabilities on SPDM 1.2 - [Low]…
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] rspdm: spec violation and connection failure parsing SPDM 1.3 capabilities on SPDM 1.2
- [Low] rspdm: unreachable data_transfer_size boundary error path
--
commit a4f1214f54c265aa579b2a02f78463b717992311
Author: Alistair Francis <[email protected]>
lib: rspdm: Support SPDM get_capabilities
This commit adds support for the GET_CAPABILITIES SPDM command.
Quoted 4 lines
lib/rspdm/state.rs +0
-0
[ ... ]
Quoted 1 line
lib/rspdm/state.rs +0
-0@@ -290,4 +301,59 @@ pub(crate) fn get_version(&mut self) -> Result<(), Error> {
[ ... ]
Quoted 1 line
+ pub(crate) fn
get_capabilities(&mut self)-> Result<(), Error> {
[ ... ]
Quoted 12 lines
lib/rspdm/state.rs +12
-0+let response:GetCapabilitiesRsp = +Untrusted ::new (response_vec.as_slice ()).validate (&*self)?; + + self.rsp_caps = response.flags; +if (self.rsp_caps &SPDM_RSP_MIN_CAPS ) !=SPDM_RSP_MIN_CAPS { +pr_err! ( +"{:#x} capabilities are supported, which don't meet required {:#x}\n" , + self.rsp_caps, +SPDM_RSP_MIN_CAPS + ); + self.rsp_caps =0 ; +return Err (EPROTONOSUPPORT );
lib/rspdm/state.rs pub(crate) fn get_version(&mut self) -> Result<(), Error> { · line 352
Quoted 10 lines
lib/rspdm/state.rs +10
-0+ } + +if self.version >=SPDM_VER_12 { +if response.data_transfer_size <42 { +pr_err! ( +"Invalid minimum transport size {}, must be at least 42\n" , + response.data_transfer_size + ); +return Err (EPROTONOSUPPORT ); + }
[Severity: Low]
Is this error path reachable?
It looks like the earlier validation in GetCapabilitiesRsp::validate already enforces data_transfer_size >= SPDM_MIN_DATA_TRANSFER_SIZE (which is 42) and returns EPROTO.
This seems to make the EPROTONOSUPPORT path in SpdmState::get_capabilities dead code.
Quoted 11 lines
lib/rspdm/state.rs +6
-0+ + self.transport_sz = self.transport_sz.min (response.data_transfer_size); + } + +Ok (()) + } }lib/rspdm/validator.rs +0
-0
[ ... ]
Quoted 1 line
lib/rspdm/validator.rs +0
-0@@ -175,3 +183,302 @@ fn validate(unvalidated: &[u8], context: &'c SpdmState) -> Result<Self, Self::Er
[ ... ]
Quoted 6 lines
lib/rspdm/validator.rs +6
-0+impl <'a, 'c>Validate <'c,Untrusted <&'a [u8 ]>>for GetCapabilitiesRsp { +type Err =Error ; + +type Context = &'cSpdmState ; + +fn validate (unvalidated: &[u8 ], context: &'cSpdmState ) ->Result <Self ,Self ::Err > {
[ ... ]
Quoted 2 lines
lib/rspdm/validator.rs +2
-0+let (data_transfer_size, max_spdm_msg_size, supported_algorithms, length) = +if context.version <=SPDM_VER_11 {
[ ... ]
Quoted 1 line
+ } else {
[ ... ]
lib/rspdm/validator.rs fn validate(unvalidated: &[u8], context: &'c SpdmState) -> Result<Self, Self::Er · line 459
Quoted 7 lines
lib/rspdm/validator.rs +7
-0+let supported_algorithms =if header.param1 &SPDM_CAP_SUPPORTED_ALGORITHMS !=0 { +Some (SupportedAlgorithms ::from_bytes ( + unvalidated.get (GET_CAPABILITIES_RSP_SZ ..).ok_or (EIO )?, + )?) + }else { +None + };
[Severity: Medium]
Can this logic incorrectly parse an SPDM 1.2 payload?
If an SPDM 1.2 responder has bit 0 of Param1 set (either by mistake or due to uninitialized reserved fields), wouldn't this lack of a context.version >= SPDM_VER_13 guard cause the kernel to incorrectly interpret this SPDM 1.3 field?
It seems this would attempt to parse SupportedAlgorithms from the end of the 20-byte packet, read an empty slice, and abort the handshake with EIO.
Quoted 13 lines
lib/rspdm/validator.rs +13
-0+ +let length =GET_CAPABILITIES_RSP_SZ + + supported_algorithms + .as_ref () + .map_or (0 , |s| s.lengthas usize ); + + ( + data_transfer_size, + max_spdm_msg_size, + supported_algorithms, + length, + ) + };
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=16
PATCH v4 17/22 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 | 52 +++++++++ lib/rspdm/lib.rs | 8 +- lib/rspdm/state.rs | 250 ++++++++++++++++++++++++++++++++++++++++- lib/rspdm/validator.rs | 231 +++++++++++++++++++++++++++++++++++-- 4 files changed, 528 insertions(+), 13 deletions(-)
lib/rspdm/consts.rs +52 -0
@@ -118,6 +118,58 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {pub (crate )const SPDM_CERT_CAP :u32 =bit_u32 (1 );pub (crate )const SPDM_CHAL_CAP :u32 =bit_u32 (2 ); +pub (crate )const SPDM_KEY_EX_CAP :u32 =bit_u32 (9 ); +pub (crate )const SPDM_MEAS_RESP_CAP :u32 =bit_u32 (3 ) |bit_u32 (4 );pub (crate )const SPDM_REQ_CAPS :u32 =SPDM_CERT_CAP |SPDM_CHAL_CAP ;pub (crate )const SPDM_RSP_MIN_CAPS :u32 =SPDM_CERT_CAP |SPDM_CHAL_CAP ; + +pub (crate )const SPDM_NEGOTIATE_ALGS :u8 =0xe3 ; + +pub (crate )const SPDM_MEAS_SPEC_DMTF :u8 =bit_u8 (0 ); + +pub (crate )const SPDM_ASYM_RSASSA_2048 :u32 =bit_u32 (0 ); +pub (crate )const _SPDM_ASYM_RSAPSS_2048:u32 =bit_u32 (1 ); +pub (crate )const SPDM_ASYM_RSASSA_3072 :u32 =bit_u32 (2 ); +pub (crate )const _SPDM_ASYM_RSAPSS_3072:u32 =bit_u32 (3 ); +pub (crate )const SPDM_ASYM_ECDSA_ECC_NIST_P256 :u32 =bit_u32 (4 ); +pub (crate )const SPDM_ASYM_RSASSA_4096 :u32 =bit_u32 (5 ); +pub (crate )const _SPDM_ASYM_RSAPSS_4096:u32 =bit_u32 (6 ); +pub (crate )const SPDM_ASYM_ECDSA_ECC_NIST_P384 :u32 =bit_u32 (7 ); +pub (crate )const SPDM_ASYM_ECDSA_ECC_NIST_P521 :u32 =bit_u32 (8 ); +pub (crate )const _SPDM_ASYM_SM2_ECC_SM2_P256:u32 =bit_u32 (9 ); +pub (crate )const _SPDM_ASYM_EDDSA_ED25519:u32 =bit_u32 (10 ); +pub (crate )const _SPDM_ASYM_EDDSA_ED448:u32 =bit_u32 (11 ); + +pub (crate )const SPDM_HASH_SHA_256 :u32 =bit_u32 (0 ); +pub (crate )const SPDM_HASH_SHA_384 :u32 =bit_u32 (1 ); +pub (crate )const SPDM_HASH_SHA_512 :u32 =bit_u32 (2 ); + +// If the crypto support isn't enabled don't offer the algorithms +// to the responder +#[cfg( CONFIG_CRYPTO_RSA )] +pub (crate )const SPDM_ASYM_RSA :u32 = +SPDM_ASYM_RSASSA_2048 |SPDM_ASYM_RSASSA_3072 |SPDM_ASYM_RSASSA_4096 ; +#[cfg(not( CONFIG_CRYPTO_RSA ))] +pub (crate )const SPDM_ASYM_RSA :u32 =0 ; + +#[cfg( CONFIG_CRYPTO_ECDSA )] +pub (crate )const SPDM_ASYM_ECDSA :u32 = +SPDM_ASYM_ECDSA_ECC_NIST_P256 |SPDM_ASYM_ECDSA_ECC_NIST_P384 |SPDM_ASYM_ECDSA_ECC_NIST_P521 ; +#[cfg(not( CONFIG_CRYPTO_ECDSA ))] +pub (crate )const SPDM_ASYM_ECDSA :u32 =0 ; + +#[cfg( CONFIG_CRYPTO_SHA256 )] +pub (crate )const SPDM_HASH_SHA2_256 :u32 =SPDM_HASH_SHA_256 ; +#[cfg(not( CONFIG_CRYPTO_SHA256 ))] +pub (crate )const SPDM_HASH_SHA2_256 :u32 =0 ; + +#[cfg( CONFIG_CRYPTO_SHA512 )] +pub (crate )const SPDM_HASH_SHA2_384_512 :u32 =SPDM_HASH_SHA_384 |SPDM_HASH_SHA_512 ; +#[cfg(not( CONFIG_CRYPTO_SHA512 ))] +pub (crate )const SPDM_HASH_SHA2_384_512 :u32 =0 ; + +pub (crate )const SPDM_ASYM_ALGOS :u32 =SPDM_ASYM_RSA |SPDM_ASYM_ECDSA ; +pub (crate )const SPDM_HASH_ALGOS :u32 =SPDM_HASH_SHA2_256 |SPDM_HASH_SHA2_384_512 ; + +pub (crate )const SPDM_OPAQUE_DATA_FMT_GENERAL :u8 =bit_u8 (1 );
lib/rspdm/lib.rs +6 -2
@@ -88,7 +88,7 @@ pub extern "C" fn spdm_authenticate(state_ptr: *mut spdm_state) -> c_int {// concurrent FFI callers serialize on the mutex and can never form // aliased `&mut SpdmState` references. let mutex =unsafe { - <Pin <KBox <Mutex <SpdmState >>>as ForeignOwnable >::borrow (state_ptras *mut c_void ) + <Pin <KBox <Mutex <SpdmState <'_>>>>as ForeignOwnable >::borrow (state_ptras *mut c_void ) };let mut state = mutex.lock ();
@@ -101,6 +101,10 @@ pub extern "C" fn spdm_authenticate(state_ptr: *mut spdm_state) -> c_int {return e.to_errno ()as c_int ; } +if let Err (e) = state.negotiate_algs () { +return e.to_errno ()as c_int ; + } + -(EPROTONOSUPPORT as i32 ) }
@@ -115,7 +119,7 @@ pub extern "C" fn spdm_destroy(state_ptr: *mut spdm_state) {// SAFETY: `state_ptr` was returned from `spdm_create()` which used `into_foreign()` // to create the pointer. -let mutex:KBox <Mutex <SpdmState >> =unsafe {KBox ::from_foreign (state_ptras *mut c_void ) }; +let mutex:KBox <Mutex <SpdmState <'_>>> =unsafe {KBox ::from_foreign (state_ptras *mut c_void ) };drop (mutex); }
lib/rspdm/state.rs +247 -3
301 lines not on this page · raw message
lib/rspdm/validator.rs +223 -8
274 lines not on this page · raw message
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v4 17/22] lib: rspdm: Support SPDM negotiate_algorithms
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 5 lines
From: Alistair Francis <[email protected]>
Support the
NEGOTIATE_ALGORITHMSSPDM command.Signed-off-by Alistair Francis <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=17
PATCH v4 18/22 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 | 5 ++- lib/rspdm/lib.rs | 4 ++ lib/rspdm/state.rs | 56 +++++++++++++++++++++++- lib/rspdm/validator.rs | 99 +++++++++++++++++++++++++++++++++++++++++- 4 files changed, 161 insertions(+), 3 deletions(-)
lib/rspdm/consts.rs +4 -1
@@ -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 ; -#[expect(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 ;
@@ -145,6 +146,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
@@ -105,6 +105,10 @@ pub extern "C" fn spdm_authenticate(state_ptr: *mut spdm_state) -> c_int {return e.to_errno ()as c_int ; } +if let Err (e) = state.get_digests () { +return e.to_errno ()as c_int ; + } + -(EPROTONOSUPPORT as i32 ) }
lib/rspdm/state.rs +55 -1
108 lines not on this page · raw message
lib/rspdm/validator.rs +98 -1
116 lines not on this page · raw message
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v4 18/22] lib: rspdm: Support SPDM get_digests
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] rspdm: GET_DIGESTS buffer truncation and invalid length breaks SPDM 1.3+ authentication --
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] rspdm: GET_DIGESTS buffer truncation and invalid length breaks SPDM 1.3+ authentication --
commit 302ede91b9559c0e7a23017a89574c6ce5ec60eb
Author: Alistair Francis <[email protected]>
lib: rspdm: Support SPDM get_digests
Support the get_digests Spdm command.
Quoted 6 lines
lib/rspdm/state.rs +0
-0
lib/rspdm/state.rs pub(crate) fn negotiate_algs(&mut self) -> Result<(), Error> { · line 626
Quoted 9 lines
lib/rspdm/state.rs +6
-0@@ -600,4 +618,40 @@ pub(crate) fn negotiate_algs(&mut self) -> Result<(), Error> {Ok (()) } + +pub (crate )fn get_digests (&mut self) ->Result <(),Error > { +let mut request =GetDigestsReq ::default (); + request.header.version = self.version; + +let rsp_sz = core::mem::size_of ::<SpdmHeader >() +SPDM_SLOTS * self.hash_len;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[Severity: High]
Does this allocation provide enough capacity for Spdm 1.3+ payloads?
For Spdm 1.3+, the response appends 4 bytes per provisioned slot for items like KeyPairID, CertificateInfo, and KeyUsageMask.
If the device utilizes multiple slots, it seems the extra bytes will exceed this capacity, causing transport errors when interacting with valid Spdm 1.3 or 1.4 responder devices.
Quoted 11 lines
lib/rspdm/state.rs +4
-0+ +let mut request_buf = request.to_bytes ()?; + +let mut response_vec:KVec <u8 > =KVec ::from_elem (0u8 , rsp_sz,GFP_KERNEL )?;lib/rspdm/validator.rs +0
-0
lib/rspdm/validator.rs fn validate(unvalidated: &[u8], context: &'c SpdmState<'c>) -> Result<Self, Self · line 793
Quoted 10 lines
lib/rspdm/validator.rs +9
-0@@ -697,3 +700,97 @@ fn validate(unvalidated: &[u8], context: &'c SpdmState<'c>) -> Result<Self, Self +if context.version >=SPDM_VER_13 && (header.param2 & !header.param1 !=0 ) { +pr_err! ("Malformed digests response\n" ); +return Err (EPROTO ); + } + +Ok (GetDigestsRsp { + header, + digests, + length: offset,
^^^^^^
[Severity: High]
Does returning only the offset for the length drop the Spdm 1.3+ suffix fields?
In GetDigestsRsp::validate, this length only covers the header and hashes, which appears to explicitly ignore the Spdm 1.3+ suffix fields completely.
Since this truncated response length is appended to the host transcript, could this silently corrupt the transcript computation and cause the challenge authentication signature verification to fail?
Quoted 3 lines
lib/rspdm/validator.rs +3
-0+ }) + } +}
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=18
PATCH v4 19/22 lib: rspdm: Support SPDM get_certificate
In reply to [email protected]From: Alistair Francis <[email protected]>
Support the GET_CERTIFICATE SPDM command.
The kernel will send a GET_CERTIFICATE request to the the responder and then iterate over all of the certificates returned.
Certificate validation happens in the next commit.
--- lib/rspdm/consts.rs | 2 + lib/rspdm/lib.rs | 15 ++++ lib/rspdm/state.rs | 110 ++++++++++++++++++++++++++++- lib/rspdm/validator.rs | 152 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 278 insertions(+), 1 deletion(-)
lib/rspdm/consts.rs +2 -0
@@ -148,6 +148,8 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {pub (crate )const SPDM_GET_DIGESTS :u8 =0x81 ; +pub (crate )const SPDM_GET_CERTIFICATE :u8 =0x82 ; +// If the crypto support isn't enabled don't offer the algorithms // to the responder #[cfg( CONFIG_CRYPTO_RSA )]
lib/rspdm/lib.rs +15 -0
@@ -109,6 +109,21 @@ pub extern "C" fn spdm_authenticate(state_ptr: *mut spdm_state) -> c_int {return e.to_errno ()as c_int ; } +if state.provisioned_slots ==0 { +return -(bindings::EIO as c_int ); + } + +let mut provisioned_slots = state.provisioned_slots; +while (provisioned_slotsas usize ) >0 { +let slot = provisioned_slots.trailing_zeros ()as u8 ; + +if let Err (e) = state.get_certificate (slot) { +return e.to_errno ()as c_int ; + } + + provisioned_slots &= !(1 << slot); + } + -(EPROTONOSUPPORT as i32 ) }
lib/rspdm/state.rs +109 -1
129 lines not on this page · raw message
lib/rspdm/validator.rs +152 -0
161 lines not on this page · raw message
Signature · 1 line
2.55.0
PATCH v4 20/22 lib: rspdm: Support SPDM certificate validation
From: Alistair Francis <[email protected]>
Support validating the SPDM certificate chain. This only performs basic sanity checks on the chain before we continue on. This does not ensure that the root CA is trusted, we leave that for userspace to check and enforce. Instead we just make sure that the chain is correct, uses supported signatures and that it isn't blacklisted in the kernel.
We then store the first leaf certificate for use later.
--- lib/rspdm/lib.rs | 12 +++ lib/rspdm/state.rs | 147 +++++++++++++++++++++++++++++++- rust/bindings/bindings_helper.h | 2 + 3 files changed, 160 insertions(+), 1 deletion(-)
lib/rspdm/lib.rs +12 -0
@@ -124,6 +124,18 @@ pub extern "C" fn spdm_authenticate(state_ptr: *mut spdm_state) -> c_int { provisioned_slots &= !(1 << slot); } +let mut provisioned_slots = state.provisioned_slots; +while (provisioned_slotsas usize ) >0 { +let slot = provisioned_slots.trailing_zeros ()as u8 ; + +if let Err (e) = state.validate_cert_chain (slot) { +pr_err! ("Certificate in slot {slot} failed to verify: {e:?}\n" ); +return e.to_errno ()as c_int ; + } + + provisioned_slots &= !(1 << slot); + } + -(EPROTONOSUPPORT as i32 ) }
lib/rspdm/state.rs +146 -1
173 lines not on this page · raw message
rust/bindings/bindings_helper.h +2 -0
@@ -38,6 +38,8 @@#include <drm/drm_gem_shmem_helper.h> #include <drm/drm_gpuvm.h> #include <drm/drm_ioctl.h> +#include <keys/asymmetric-type.h> +#include <keys/x509-parser.h> #include <kunit/test.h> #include <linux/auxiliary_bus.h> #include <linux/bitmap.h>
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v4 20/22] lib: rspdm: Support SPDM certificate validation
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 11 lines
From: Alistair Francis <[email protected]>
Support validating the SPDM certificate chain. This only performs basic sanity checks on the chain before we continue on. This does not ensure that the root CA is trusted, we leave that for userspace to check and enforce. Instead we just make sure that the chain is correct, uses supported signatures and that it isn't blacklisted in the kernel.
We then store the first leaf certificate for use later.
Signed-off-by Alistair Francis <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=20
PATCH v4 21/22 rust: allow extracting the buffer from a CString
In reply to [email protected]From: Alistair Francis <[email protected]>
The kernel CString is a wrapper around a KVec. This patch allows retrieving the underlying buffer and consuming the CString. This allows users to create a CString from a string and then retrieve the underlying buffer.
--- rust/kernel/str.rs | 6 ++++++ 1 file changed, 6 insertions(+)
rust/kernel/str.rs +6 -0
@@ -870,6 +870,12 @@ pub fn try_from_fmt(args: fmt::Arguments<'_>) -> Result<Self, Error> {// exist in the buffer. Ok (Self { buf }) } + +/// Return the internal buffer while consuming the original [`CString`] +#[inline] +pub fn into_vec (self) ->KVec <u8 > { + self.buf + } }impl Deref for CString {
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v4 21/22] rust: allow extracting the buffer from a CString
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Low] rust: str: CString::into_vec() ambiguously retains NUL terminator --
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] rust: str: CString::into_vec() ambiguously retains NUL terminator --
commit 2eeadc6d96d6cb1bbc75fa7b1a4d782fb1ed8cd5
Author: Alistair Francis <[email protected]>
rust: allow extracting the buffer from a CString
This patch introduces a method to consume a CString and retrieve its underlying buffer. It allows users to convert a string to a CString and then access the internal KVec directly.
Quoted 4 lines
rust/kernel/str.rs +0
-0
[ ... ]
rust/kernel/str.rs pub fn try_from_fmt(args: fmt::Arguments<'_>) -> Result<Self, Error> { · line 878
Quoted 10 lines
rust/kernel/str.rs +6
-0@@ -870,6 +870,12 @@ pub fn try_from_fmt(args: fmt::Arguments<'_>) -> Result<Self, Error> {// exist in the buffer. Ok (Self { buf }) } + +/// Return the internal buffer while consuming the original [`CString`] +#[inline] +pub fn into_vec (self) ->KVec <u8 > { + self.buf + }
[Severity: Low]
This isn't a bug, but could the name of this method cause confusion about whether the NUL terminator is retained in the returned vector?
In the standard library, std::ffi::CString uses into_bytes() to drop the terminator and into_bytes_with_nul() to retain it. Because into_vec() silently retains the trailing NUL byte, could developers assume it drops it, similar to standard string conversions?
Could this lead to regressions like off-by-one errors or embedded NUL bytes when the buffer is used for network payloads? Would it be clearer to use a naming convention that explicitly indicates the presence of the terminator?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=21
PATCH v4 22/22 lib: rspdm: Support SPDM challenge
In reply to [email protected]From: Alistair Francis <[email protected]>
Support the CHALLENGE SPDM command.
--- lib/rspdm/consts.rs | 6 + lib/rspdm/lib.rs | 10 +- lib/rspdm/state.rs | 215 +++++++++++++++++++++++++++++++- lib/rspdm/validator.rs | 153 ++++++++++++++++++++++- rust/bindings/bindings_helper.h | 1 + 5 files changed, 375 insertions(+), 10 deletions(-)
lib/rspdm/consts.rs +6 -0
@@ -150,6 +150,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 )]
@@ -178,3 +180,7 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {pub (crate )const SPDM_HASH_ALGOS :u32 =SPDM_HASH_SHA2_256 |SPDM_HASH_SHA2_384_512 ;pub (crate )const SPDM_OPAQUE_DATA_FMT_GENERAL :u8 =bit_u8 (1 ); + +pub (crate )const SPDM_PREFIX_SZ :usize =64 ; +pub (crate )const SPDM_COMBINED_PREFIX_SZ :usize =100 ; +pub (crate )const SPDM_MAX_OPAQUE_DATA :usize =1024 ;
lib/rspdm/lib.rs +7 -3
@@ -11,8 +11,7 @@//! from other subsytems. use crate ::bindings::{ - spdm_state, -EPROTONOSUPPORT ,// + spdm_state,// };use core::ffi::{ c_int,
@@ -136,7 +135,12 @@ pub extern "C" fn spdm_authenticate(state_ptr: *mut spdm_state) -> c_int { provisioned_slots &= !(1 << slot); } - -(EPROTONOSUPPORT as i32 ) +let provisioned_slots = state.provisioned_slots.trailing_zeros (); +if let Err (e) = state.challenge (provisioned_slotsas u8 ) { +return e.to_errno ()as c_int ; + } + +0 }/// spdm_destroy() - Destroy SPDM session
lib/rspdm/state.rs +213 -2
326 lines not on this page · raw message
lib/rspdm/validator.rs +148 -5
198 lines not on this page · raw message
rust/bindings/bindings_helper.h +1 -0
@@ -31,6 +31,7 @@#include <linux/acpi.h> #include <linux/gpu_buddy.h> #include <crypto/hash.h> +#include <crypto/public_key.h> #include <drm/drm_device.h> #include <drm/drm_drv.h> #include <drm/drm_file.h>
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v4 22/22] lib: rspdm: Support SPDM challenge
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] rspdm: missing CertChainHash verification in CHALLENGE_AUTH response --
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] rspdm: missing CertChainHash verification in CHALLENGE_AUTH response --
commit eee76c6b536b5164eade967b244ae4e6193db601
Author: Alistair Francis <[email protected]>
lib: rspdm: Support SPDM challenge
This commit adds support for the CHALLENGE SPDM command. It introduces the state handling and response validation for SPDM challenges to authenticate the responder.
Quoted 4 lines
lib/rspdm/state.rs +0
-0
[ ... ]
Quoted 1 line
lib/rspdm/state.rs +0
-0@@ -907,4 +957,165 @@ pub(crate) fn validate_cert_chain(&mut self, slot: u8) -> Result<(), Error> {
[ ... ]
Quoted 1 line
+ pub(crate) fn
challenge(&mut self, slot: u8)-> Result<(), Error> {
[ ... ]
lib/rspdm/state.rs pub(crate) fn validate_cert_chain(&mut self, slot: u8) -> Result<(), Error> { · line 1110
Quoted 12 lines
lib/rspdm/state.rs +12
-0+let mut response:ChallengeRsp = +Untrusted ::new (response_vec.as_slice ()).validate (&*self)?; + +if request.context != response.context { +pr_err! ("Mismatched challenge context response\n" ); +return Err (EIO ); + } + + self.transcript + .extend_from_slice (response_vec.get (..response.len ()).ok_or (EIO )?,GFP_KERNEL )?; + +match self.verify_signature (&mut response.signature) {
[Severity: Medium]
Does this code skip the verification of the certificate chain hash?
The SPDM specification (e.g., DSP0274 Section 11.2) explicitly mandates that the requester must verify that the response cert_chain_hash matches the hash of the certificate chain that the requester possesses for the specified slot.
Since response.cert_chain_hash is ignored here instead of being verified against the expected hash of self.certs[slot], could this allow protocol confusion or downgrade attacks if a responder uses a different certificate chain for the signature than the one the requester holds?
Quoted 16 lines
lib/rspdm/state.rs +10
-0+ Ok(()) => { +pr_info! ("Authenticated with certificate slot {slot}\n" ); +Ok (()) + } + Err(e) => { +pr_err! ("Cannot verify challenge_auth signature: {e:?}\n" ); +Err (EPROTO ) + } + } + } }lib/rspdm/validator.rs +0
-0
[ ... ]
Quoted 1 line
lib/rspdm/validator.rs +0
-0@@ -946,3 +943,149 @@ fn validate(unvalidated: &[u8], context: &'c SpdmState<'c>) -> Result<Self, Self
[ ... ]
Quoted 1 line
+impl<'a, 'c> Validate<'c, Untrusted<&'a [u8]>> for ChallengeRsp {
[ ... ]
lib/rspdm/validator.rs fn validate(unvalidated: &[u8], context: &'c SpdmState<'c>) -> Result<Self, Self · line 1035
Quoted 7 lines
lib/rspdm/validator.rs +7
-0+let mut cert_chain_hash =KVec ::new (); + cert_chain_hash.extend_from_slice ( + unvalidated + .get (mem::size_of ::<SpdmHeader >()..nonce_offset) + .ok_or (EIO )?, +GFP_KERNEL , + )?;
This isn't a bug in the extraction itself, but the cert_chain_hash is parsed from the response here, confirming it is available to the requester, yet it appears to be ultimately left unused in SpdmState::challenge().
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=22
[email protected]
AI reviewRe: [PATCH v4 19/22] lib: rspdm: Support SPDM get_certificate
In reply to [email protected]Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [High] rspdm: SPDM cert chain truncation and underflow in SpdmCertChain::validate - [Medium] rspdm…
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] rspdm: SPDM cert chain truncation and underflow in SpdmCertChain::validate
- [Medium] rspdm: integer underflow in get_certificate when transport_sz is small --
commit 4862644196f20f9c3e54555a8df7d0d01825e781
Author: Alistair Francis <[email protected]>
lib: rspdm: Support SPDM get_certificate
This commit introduces support for the GET_CERTIFICATE SPDM command. It sends requests to the responder and iterates over the returned certificates for later validation.
Quoted 4 lines
lib/rspdm/state.rs +0
-0
[ ... ]
Quoted 1 line
lib/rspdm/state.rs +0
-0@@ -654,4 +659,107 @@ pub(crate) fn get_digests(&mut self) -> Result<(), Error> {
[ ... ]
lib/rspdm/state.rs pub(crate) fn get_digests(&mut self) -> Result<(), Error> { · line 686
Quoted 10 lines
lib/rspdm/state.rs +10
-0+pub (crate )fn get_certificate (&mut self, slot:u8 ) ->Result <(),Error > { +let mut request =GetCertificateReq ::default (); + request.header.version = self.version; + request.header.param1 = slot; + +let rsp_sz = + (GET_CERTIFICATE_RSP_HDR_SZ as u32 + u16::MAX as u32 ).min (self.transport_sz)as usize ; + + request.offset =0 ; + request.length = (rsp_sz -GET_CERTIFICATE_RSP_HDR_SZ )as u16 ;
[Severity: Medium]
What happens if the caller provides a transport_sz that is less than 8?
Since GET_CERTIFICATE_RSP_HDR_SZ is 8, rsp_sz could be less than 8, causing this subtraction to underflow usize and panic.
Quoted 5 lines
lib/rspdm/state.rs +5
-0+ +let mut response_vec:KVec <u8 > =KVec ::from_elem (0u8 , rsp_sz,GFP_KERNEL )?; + +let mut request_buf = request.to_bytes ()?; +let response = self.get_cert_exchange (request_buf.as_mut_slice (), &mut response_vec)?;
[ ... ]
lib/rspdm/state.rs pub(crate) fn get_digests(&mut self) -> Result<(), Error> { · line 712
Quoted 6 lines
lib/rspdm/state.rs +6
-0+let mut offset:u16 = portion_length; +let mut remainder_length = rem_lengthas usize ; + +while remainder_length >0 { + request.offset = offset; + request.length = (remainder_length.min (rsp_sz -GET_CERTIFICATE_RSP_HDR_SZ ))as u16 ;
[Severity: Medium]
Could this subtraction also underflow under the same conditions where rsp_sz is smaller than GET_CERTIFICATE_RSP_HDR_SZ?
Quoted 2 lines
lib/rspdm/state.rs +2
-0+ +let mut request_buf = request.to_bytes ()?;
[ ... ]
Quoted 4 lines
lib/rspdm/validator.rs +0
-0
[ ... ]
Quoted 1 line
lib/rspdm/validator.rs +0
-0@@ -794,3 +795,154 @@ fn validate(unvalidated: &[u8], context: &'c SpdmState<'c>) -> Result<Self, Self
[ ... ]
Quoted 6 lines
lib/rspdm/validator.rs +6
-0+impl <'a, 'c>Validate <'c,Untrusted <&'a [u8 ]>>for SpdmCertChain { +type Err =Error ; + +type Context = &'cSpdmState <'c>; + +fn validate (unvalidated: &[u8 ], context: &'cSpdmState <'c>) ->Result <Self ,Self ::Err > {
[ ... ]
lib/rspdm/validator.rs fn validate(unvalidated: &[u8], context: &'c SpdmState<'c>) -> Result<Self, Self · line 868
Quoted 10 lines
lib/rspdm/validator.rs +10
-0+let root_hash_end =SPDM_CERT_CHAIN_HDR_SZ + context.hash_len; +let mut root_hash =KVec ::new (); + root_hash.extend_from_slice ( + unvalidated + .get (SPDM_CERT_CHAIN_HDR_SZ ..root_hash_end) + .ok_or (EIO )?, +GFP_KERNEL , + )?; + +let cert_chain_end = lengthas usize - root_hash_end;
[Severity: High]
Does this calculation safely handle payloads where length is smaller than root_hash_end?
It appears this subtraction will underflow and trigger a panic if the untrusted payload provides a short length.
lib/rspdm/validator.rs fn validate(unvalidated: &[u8], context: &'c SpdmState<'c>) -> Result<Self, Self · line 871
Quoted 3 lines
lib/rspdm/validator.rs +3
-0+let mut certificates =KVec ::new (); + certificates.extend_from_slice ( + unvalidated.get(root_hash_end..cert_chain_end).ok_or(EIO)?,
[Severity: High]
Is it intentional to use the subtracted value as the exclusive slice bound here?
Because cert_chain_end is calculated as a relative length but used as an absolute upper bound, it seems to unconditionally truncate the valid certificate stream by root_hash_end bytes.
Quoted 10 lines
lib/rspdm/validator.rs +10
-0+ GFP_KERNEL, + )?; + +Ok (SpdmCertChain { + length, + root_hash, + certificates, + }) + } +}
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=19
Alexandre Courbot
reviewerHi Alistair,
Quoted 11 lines · On Mon Sep 28, 2026 at 10:11 AM JST, alistair23 wrote:
From: Alistair Francis <[email protected]>
Security Protocols and Data Models (SPDM) [1] is used for authentication, attestation and key exchange. SPDM is generally used over a range of transports, such as PCIe, MCTP/SMBus/I3C, ATA, SCSI, NVMe or TCP.
From the kernels perspective SPDM is used to authenticate and attest devices. In this threat model a device is considered untrusted until it can be verified by the kernel and userspace using SPDM. As such SPDM data is untrusted data that can be mallicious.
Quoted 8 lines
The SPDM specification is also complex, with the 1.2.1 spec being almost 200 pages and the 1.3.0 spec being almost 250 pages long.
As such we have the kernel parsing untrusted responses from a complex specification, which sounds like a possible exploit vector. This is the type of place where Rust excels!
This series implements a SPDM requester in Rust.
A few high-level comments/questions about this series:
- Have you tried building it with CLIPPY=1 as per the checklist [1]? I am getting dozen of Clippy warnings about unused imports, unused constants, missing safety comments, and style issues. Series are expected to be Clippy-clean when submitted.
- Most of the C parts have received more attention than the Rust ones so far, if they are meaningful on their own can they be merged separately so this series can focus on the Rust bits and the C FFI?
- I see many unsafe calls to C crypto functions in e.g. patch 17, these will need to have their own Rust wrapper so they can be called safely.
I am not familiar with SPDM yet so I cannot comment on the protocol-specific parts, but I'll try to perform a review on the Rust bits. For this revision it won't be exhaustive as there are a few critical things to fix before considering the rest.
[1] https://rust-for-linux.com/contributing#submit-checklist-addendum
Quoted 18 lines · Alexandre Courbot
Hi Alistair,
Quoted 14 lines · On Mon Sep 28, 2026 at 10:11 AM JST, alistair23 wrote:
From: Alistair Francis <[email protected]>
Security Protocols and Data Models (SPDM) [1] is used for authentication, attestation and key exchange. SPDM is generally used over a range of transports, such as PCIe, MCTP/SMBus/I3C, ATA, SCSI, NVMe or TCP.
From the kernels perspective SPDM is used to authenticate and attest devices. In this threat model a device is considered untrusted until it can be verified by the kernel and userspace using SPDM. As such SPDM data is untrusted data that can be mallicious.
The SPDM specification is also complex, with the 1.2.1 spec being almost 200 pages and the 1.3.0 spec being almost 250 pages long.
Quoted 7 lines
Quoted 5 lines
As such we have the kernel parsing untrusted responses from a complex specification, which sounds like a possible exploit vector. This is the type of place where Rust excels!
This series implements a SPDM requester in Rust.
A few high-level comments/questions about this series:
Thanks for the review!
Quoted 5 lines
- Have you tried building it with
CLIPPY=1as per the checklist [1]? I am getting dozen of Clippy warnings about unused imports, unused constants, missing safety comments, and style issues. Series are expected to be Clippy-clean when submitted.
I have been, but apparently running
``` CLIPPY=1 LLVM=1 make -j32 ```
doesn't trigger clippy while
``` LLVM=1 make CLIPPY=1 -j32 ```
does. I have fixed the clippy issues
Quoted 3 lines
- Most of the C parts have received more attention than the Rust ones so far, if they are meaningful on their own can they be merged separately so this series can focus on the Rust bits and the C FFI?
Not really unfortunately. A few could be pulled out and merged today, but there isn't much movement there
Quoted 2 lines
- I see many unsafe calls to C crypto functions in e.g. patch 17, these will need to have their own Rust wrapper so they can be called safely.
Yeah, the crypto calls all use unsafe as there aren't bindings written.
This came up at Kangrejos and I thought the takeaway that this was ok as a starting point. My next step is to work on PQC and crypto bindings, but I was hoping to split it out of this series, as it's already long and touching a few subsystems.
Quoted 5 lines
I am not familiar with SPDM yet so I cannot comment on the protocol-specific parts, but I'll try to perform a review on the Rust bits. For this revision it won't be exhaustive as there are a few critical things to fix before considering the rest.
Thanks. I have fixed up your comments and have a few fixes I have found. I'll get a v5 out soon
Alistair
Quoted 2 lines
[1] https://rust-for-linux.com/contributing#submit-checklist-addendum
Alice Ryhl
reviewerQuoted 11 lines · On Mon, Sep 28, 2026 at 11:11:01AM +1000, [email protected] wrote:
From: Alistair Francis <[email protected]>
Security Protocols and Data Models (SPDM) [1] is used for authentication, attestation and key exchange. SPDM is generally used over a range of transports, such as PCIe, MCTP/SMBus/I3C, ATA, SCSI, NVMe or TCP.
From the kernels perspective SPDM is used to authenticate and attest devices. In this threat model a device is considered untrusted until it can be verified by the kernel and userspace using SPDM. As such SPDM data is untrusted data that can be mallicious.
Quoted 8 lines
The SPDM specification is also complex, with the 1.2.1 spec being almost 200 pages and the 1.3.0 spec being almost 250 pages long.
As such we have the kernel parsing untrusted responses from a complex specification, which sounds like a possible exploit vector. This is the type of place where Rust excels!
This series implements a SPDM requester in Rust.
So I looked a bit at this, and here are some comments. I will not repeat what Alexandre has already said.
First, I'd like to say that it's good to see that all wire-format parsing in lib/rspdm/validator.rs is written in completely safe Rust. That's exactly what we want to see for parsing in Rust. We want the code to have basically no unsafe code whatsoever so that we really get to rely on the bullet-proof guarantees about bugs being caught at compile time, or at least at runtime.
Unfortunately it looks like the same cannot be said for the state machine in lib/rspdm/state.rs, which has way too much unsafe code in its interaction with the rest of the kernel. There needs to be more intermediate Rust abstractions here. This file should not have *any* unsafe code, except maybe 1-2 blocks for using the C transport and validate function pointers.
I've also asked my LLM to take a look, please see below. I looked over the LLM report myself, and while it's more strongly worded than I would have said myself, its feedback seems more or less valid to me.
Alice
#### 1. Bugs & Memory Safety / Soundness Issues
- **UB in shash_desc allocation and deallocation in SpdmState::update_response_algs and SpdmState::free_desc (Patch 17)**:
- update_response_algs allocates a KVec<u8> via KVec::from_elem(0u8, desc_len, GFP_KERNEL), calls into_raw_parts(), discards _capacity, and stores desc_buf as *mut bindings::shash_desc.
- In free_desc, it reconstructs KVec::<u8>::from_raw_parts(self.desc as *mut u8, desc_len, desc_len), passing desc_len as the capacity even though KVec::from_elem does not guarantee capacity == len.
- Casting a *mut u8 from KVec<u8> to *mut bindings::shash_desc only relies on kmalloc minimum alignment by accident, rather than properly allocating a dynamically-sized type or wrapping crypto_shash in a safe kernel abstraction.
- **Mismatched ForeignOwnable types in lib/rspdm/lib.rs (Patch 10)**:
- spdm_create creates a Pin<KBox<Mutex<SpdmState<'_>>>> via KBox::pin_init and calls .into_foreign().
- spdm_authenticate borrows it as <Pin<KBox<Mutex<SpdmState<'_>>>> as ForeignOwnable>::borrow(...).
- spdm_destroy, however, calls KBox::<Mutex<SpdmState<'_>>>::from_foreign(...) (without Pin). It should use Pin::<KBox<Mutex<SpdmState<'_>>>>::from_foreign(...) to match into_foreign().
- **Unchecked transport return length in spdm_exchange and callers**:
- In spdm_exchange, Untrusted::new(&response_buf[..]).validate(&*self) validates the entire pre-allocated response_buf instead of &response_buf[..length as usize].
- Only get_version checks if rc > response_vec.len() before response_vec.truncate(rc). None of get_capabilities, negotiate_algs, get_digests, get_cert_exchange, or challenge check if rc > response_vec.len(); if a transport returns a size larger than the buffer, truncate(rc) is a no-op. Better yet, spdm_exchange itself should enforce (length as usize) <= response_buf.len() and return usize.
#### 2. Design & Abstraction Issues
- **Untrusted<T> / Validate Usage Defeats Its Own Purpose (Patches 3–4, 10, 15–22)**:
- The motivation for Untrusted<T> is that I/O boundaries (such as spdm_exchange) accept or return Untrusted data so the caller cannot touch the bytes without going through Validate.
- Instead, spdm_exchange takes a plain response_buf: &mut [u8], and every caller manually wraps the slice and validates it in a single expression:
```rust
let response: GetVersionRsp = Untrusted::new(response_vec.as_slice()).validate(&*self)?;
``` Wrapping a plain &[u8] in Untrusted::new(...) only to immediately call .validate(...) on the same line provides no type-level guarantee that untrusted buffers aren't read elsewhere (and in fact, response_vec is indexed directly right after validate() to extend self.transcript).
- Furthermore, adding type Context with a lifetime parameter 'c to Validate<'c, Input> makes the general Validate trait much heavier when a normal parsing method FromBytes / try_from_bytes(buf: &[u8], state: &SpdmState) would be simpler and more idiomatic.
- **Abuse of CString and CString::into_vec (Patches 21 & 22)**:
- Patch 21 adds CString::into_vec solely so verify_signature in Patch 22 can format the 64-byte ASCII spdm_prefix using CString::try_from_fmt(...).
- Because CString's internal KVec<u8> includes the trailing NUL terminator (\0), prefix.into_vec() actually returns **65 bytes** (64 ASCII bytes + 1 NUL byte), which then forces line 1013 to compensate for the extra NUL byte by subtracting 1 from zero_pad_len:
```rust
let zero_pad_len = SPDM_COMBINED_PREFIX_SZ - SPDM_PREFIX_SZ - SPDM_CONTEXT.len() - 1;
``` - Not only is CString::into_vec a footgun, verify_signature doesn't need CString or heap allocation at all: m is already allocated right above on line 981, or it can format directly into a fixed-size stack buffer [0u8; SPDM_COMBINED_PREFIX_SZ].
- **Direct unsafe C FFI Calls in Driver/Library Code (lib/rspdm/state.rs)**:
- lib/rspdm/state.rs directly manipulates raw C pointers (*mut bindings::device, *mut bindings::crypto_shash, *mut bindings::shash_desc, *mut bindings::x509_certificate, *mut bindings::public_key) and makes raw unsafe calls to crypto_alloc_shash, crypto_shash_digest, x509_cert_parse, public_key_verify_signature, and get_random_bytes.
- Per Rust-for-Linux conventions, C subsystem bindings (crypto_shash, x509_certificate, public_key, get_random_bytes) should be wrapped in safe abstractions under rust/kernel/, and dev should use device::Device rather than a raw *mut bindings::device with no lifetime or reference-count guarantee.
- Note also in validate_cert_chain: let cert = unsafe { *cert_ptr }; copies the entire C struct x509_certificate by value onto the stack just to read fields (cert.unsupported_sig, cert.blacklisted, cert.sig).
- **Useless lifetime 'a on SpdmState<'a> (state.rs:136)**:
- SpdmState<'a> has a lifetime parameter 'a only used for base_asym_enc: &'a CStr and base_hash_alg_name: &'a CStr, which only ever point to 'static string literals (c_str!("pkcs1"), c_str!("sha256"), etc.). Using &'static CStr (and c"..." instead of CStr::from_bytes_with_nul(b"...\0")?, which also eliminates the need for Patch 9!) removes the 'a lifetime parameter from SpdmState entirely.
============================
While lib/rspdm/validator.rs is a step up from earlier versions in that it contains **zero unsafe blocks** and uses .get(...) instead of [] indexing, the implementation is very unidiomatic, does a lot of wasted heap allocations, and still has bugs (including a kernel panic / underflow vector).
Here is a breakdown of the issues in validator.rs:
---
### 1. Kernel Panic / Integer Underflow & Range Bug in SpdmCertChain::validate
Even without unsafe, safe Rust in the kernel must not panic on untrusted input when CONFIG_RUST_OVERFLOW_CHECKS is enabled. Look at lines 849–872:
```rust
let length = u16::from_le_bytes(
unvalidated
.get(0..2)
.ok_or(EIO)?
.try_into()
.map_err(|_| EINVAL)?,
);
let root_hash_end = SPDM_CERT_CHAIN_HDR_SZ + context.hash_len;
...
let cert_chain_end = length as usize - root_hash_end;
let mut certificates = KVec::new();
certificates.extend_from_slice(
unvalidated.get(root_hash_end..cert_chain_end).ok_or(EIO)?,
GFP_KERNEL,
)?;
```- **Arithmetic underflow**: length comes directly from the first 2 bytes of the untrusted certificate chain before state.rs compares it to total_cert_len. If a responder sends length < 4 + hash_len, length as usize - root_hash_end underflows (panicking with overflow checks enabled, or wrapping to usize::MAX - ...).
- **Wrong slice end**: length in the SPDM Certificate Chain format (SPDM 1.0 Table 15) is the *total* length of the chain including root_hash_end (4 + H). Subtracting root_hash_end gives the *number of certificate bytes*, not the end index. Using it as the end index of root_hash_end..cert_chain_end slices [4+H .. length-(4+H)], dropping the last 4 + H bytes of the certificate chain (or returning Err(EIO) if the certificates are smaller than 4 + H bytes).
---
### 2. Allocating and Copying into KVecs Only to Immediately Drop Them (#[expect(dead_code)] Everywhere)
Almost every response struct in validator.rs (GetVersionRsp, RespAlgStruct, SupportedAlgorithms, GetCapabilitiesRsp, NegotiateAlgsRsp, GetDigestsRsp, SpdmCertChain, ChallengeRsp) is annotated with #[expect(dead_code)].
Why? Because validate() heap-allocates KVecs and copies payload data out of the response buffer into fields that **are never read anywhere in the crate**:
- **GetDigestsRsp::validate**: Allocates up to 8 KVec<u8>s (digests: [KVec<u8>; SPDM_SLOTS]) and copies every digest into the heap. Then SpdmState::get_digests only reads response.header.param1 and response.header.param2 and immediately drops response (freeing all 8 KVecs without ever looking at the digests).
- **SupportedAlgorithms::from_bytes**: Allocates ext_asym: KVec<u32>, ext_hash: KVec<u32>, and alg_struct: KVec<RespAlgStruct> (each containing 2 more KVecs). SpdmState::get_capabilities never accesses response.supported_algorithms at all.
- **NegotiateAlgsRsp::validate**: Loops over ext_asym_sel_count, ext_hash_sel_count, and header.param1 (RespAlgStruct), allocating KVecs for all of them. Then, as soon as validate() returns to SpdmState::negotiate_algs, state.rs checks:
```rust || response.ext_asym_sel_count != 0 || response.ext_hash_sel_count != 0 || response.header.param1 > request.header.param1 // request.header.param1 is always 0! ```
So any non-empty ext_asym, ext_hash, or resp_alg_struct is **always rejected** in state.rs—yet validator.rs has 45 lines of code to heap-allocate and populate those vectors from untrusted counts *before* state.rs rejects them!
- **SpdmCertChain::validate**: Heap-allocates root_hash: KVec<u8> and certificates: KVec<u8>. SpdmState::get_certificate only checks certs.length, drops certs, and stores the original unsplit certs_buf in self.certs[slot].
- **ChallengeRsp::validate**: Heap-allocates cert_chain_hash: KVec<u8> and opaque_data: KVec<u8>, neither of which is ever read or verified.
- **Even fixed-size requests heap-allocate**: SpdmHeader::to_bytes() (4 bytes), GetVersionReq::to_bytes() (4 bytes), GetDigestsReq::to_bytes() (4 bytes), GetCertificateReq::to_bytes() (8 bytes), NegotiateAlgsReq::to_bytes() (32 bytes), and ChallengeReq::to_bytes() (36 or 44 bytes) all allocate a KVec<u8> (without with_capacity, causing multiple reallocations as .push() / .extend_from_slice() are called) instead of returning a fixed-size byte array [u8; N] or implementing AsBytes.
Why copy slices out of unvalidated: &'a [u8] into newly allocated KVec<u8>s at all, when the parsed response struct can either borrow &'a [u8] sub-slices directly from the response buffer or validate in-place?
---
### 3. Magic Byte Offsets Instead of a Cursor/Reader or FromBytes
Because the author moved away from #[repr(C, packed)] structs in v4, they replaced struct definitions with hardcoded byte offsets. Look at SupportedAlgorithms::from_bytes and NegotiateAlgsRsp::validate:
```rust let alg_struct_count = *buf.get(0).ok_or(EIO)?; let length = read_le16(2)?; let measurement_specification = *buf.get(4).ok_or(EIO)?; let other_params_support = *buf.get(5).ok_or(EIO)?; let base_asym_algo = read_le32(6)?; let base_hash_algo = read_le32(10)?; let ext_asym_count = *buf.get(26).ok_or(EIO)?; let ext_hash_count = *buf.get(27).ok_or(EIO)?; let mel_specification = *buf.get(29).ok_or(EIO)?; let mut offset = 30; ```
- **Hard to audit against the spec**: Jumping from offset 10 to 26 (skipping 12 reserved bytes) or 27 to 29 with magic numbers makes it very easy to miscount offsets, and read_le16 / read_le32 closures are copy-pasted across multiple functions.
- **What would be much cleaner**:
1. For fixed-size layouts, use FromBytes::from_bytes_copy_prefix (or zerocopy) on #[repr(C)] structs with explicit reserved fields (_reserved: [u8; 12]) and little-endian integer wrappers, **or**
2. Use a small sequential reader struct wrapping &'a [u8] (e.g., r.read_u8()?, r.read_le16()?, r.read_le32()?, r.skip(12)?, r.read_slice(len)?). That eliminates manual offset arithmetic, cannot underflow/overflow offset, and reads top-to-bottom like the spec table.
---
### 4. Inconsistent Split of Responsibilities Between validator.rs and state.rs
The boundary between what is validated in Validate::validate (validator.rs) vs. what is checked in SpdmState (state.rs) is arbitrary:
- **Duplicated checks**: GetCapabilitiesRsp::validate checks if data_transfer_size < SPDM_MIN_DATA_TRANSFER_SIZE (validator.rs:456), and then SpdmState::get_capabilities checks if response.data_transfer_size < 42 (state.rs:508) again.
- **Misplaced checks**: GetDigestsRsp::validate checks header.param2 == 0 inside validator.rs, whereas NegotiateAlgsRsp and GetCertificateRsp leave basic structural checks (ext_asym_sel_count != 0, response.header.param1 & 0xF != slot, total_cert_len != cert_chain_length) to state.rs.
- **Inconsistent error codes**: Within validator.rs, out-of-bounds buffer slices return EIO, wrong response codes return EINVAL, and wrong versions or lengths return EPROTO. In spdm_exchange, if a truncated packet is < 4 bytes (header size), spdm_exchange returns Ok(length) and lets the caller's validate() fail with EIO.
---
### Summary
The move to safe code in validator.rs achieved the goal of removing unsafe pointer casts, but it was done in the most mechanical way possible: replacing struct field accesses with hardcoded .get(offset..offset + N) calls and copying every variable-length region into throwaway KVecs.
Replacing the manual offset indexing with either FromBytes / zerocopy (for fixed headers) + a tiny slice cursor (borrowing &'a [u8] slices instead of allocating KVecs) would probably cut validator.rs in half, remove ~15 unnecessary heap allocations per authentication, and fix the SpdmCertChain underflow/range bug.
Quoted 32 lines · Alice Ryhl
Quoted 11 lines · On Mon, Sep 28, 2026 at 11:11:01AM +1000, [email protected] wrote:
From: Alistair Francis <[email protected]>
Security Protocols and Data Models (SPDM) [1] is used for authentication, attestation and key exchange. SPDM is generally used over a range of transports, such as PCIe, MCTP/SMBus/I3C, ATA, SCSI, NVMe or TCP.
From the kernels perspective SPDM is used to authenticate and attest devices. In this threat model a device is considered untrusted until it can be verified by the kernel and userspace using SPDM. As such SPDM data is untrusted data that can be mallicious.
Quoted 8 lines
The SPDM specification is also complex, with the 1.2.1 spec being almost 200 pages and the 1.3.0 spec being almost 250 pages long.
As such we have the kernel parsing untrusted responses from a complex specification, which sounds like a possible exploit vector. This is the type of place where Rust excels!
This series implements a SPDM requester in Rust.
So I looked a bit at this, and here are some comments. I will not repeat what Alexandre has already said.
First, I'd like to say that it's good to see that all wire-format parsing in
lib/rspdm/validator.rsis written in completely safe Rust. That's exactly what we want to see for parsing in Rust. We want the code to have basically no unsafe code whatsoever so that we really get to rely on the bullet-proof guarantees about bugs being caught at compile time, or at least at runtime.
Quoted 6 lines
Unfortunately it looks like the same cannot be said for the state machine in
lib/rspdm/state.rs, which has way too much unsafe code in its interaction with the rest of the kernel. There needs to be more intermediate Rust abstractions here. This file should not have *any* unsafe code, except maybe 1-2 blocks for using the C transport and validate function pointers.
Yeah, it's the crypto functions. There are currently no bindings, so it's just directly calling the C functions.
Without the crypto functions it's just a single unsafe call to transport_function() in state.rs and a few in lib.rs to handl the C <-> FFI boundy.
I was trying to avoid adding crypto and X.509 bindings in this series, as that's a few more subsystems this ends up touching. We need to add PQC as well, so crypto bindings are next on the todo list once this is merged.
Is this going to stall on adding crypto bindings?
Quoted 4 lines
I've also asked my LLM to take a look, please see below. I looked over the LLM report myself, and while it's more strongly worded than I would have said myself, its feedback seems more or less valid to me.
Ha, it is pretty strongly worded. I think I addressed everything below
Quoted 6 lines
Alice
Quoted 6 lines
#### 1. Bugs & Memory Safety / Soundness Issues
- **UB in
shash_descallocation and deallocation inSpdmState::update_response_algsandSpdmState::free_desc(Patch 17)**:
-update_response_algsallocates aKVec<u8>viaKVec::from_elem(0u8, desc_len, GFP_KERNEL), callsinto_raw_parts(), discards_capacity, and storesdesc_buf as *mut bindings::shash_desc.
- Infree_desc, it reconstructsKVec::<u8>::from_raw_parts(self.desc as *mut u8, desc_len, desc_len), passingdesc_lenas the capacity even thoughKVec::from_elemdoes not guaranteecapacity == len.
- Casting a*mut u8fromKVec<u8>to*mut bindings::shash_desconly relies onkmallocminimum alignment by accident, rather than properly allocating a dynamically-sized type or wrappingcrypto_shashin a safe kernel abstraction.
Good point, fixed
Quoted 5 lines
- **Mismatched
ForeignOwnabletypes inlib/rspdm/lib.rs(Patch 10)**:
-spdm_createcreates aPin<KBox<Mutex<SpdmState<'_>>>>viaKBox::pin_initand calls.into_foreign().
-spdm_authenticateborrows it as<Pin<KBox<Mutex<SpdmState<'_>>>> as ForeignOwnable>::borrow(...).
-spdm_destroy, however, callsKBox::<Mutex<SpdmState<'_>>>::from_foreign(...)(withoutPin). It should usePin::<KBox<Mutex<SpdmState<'_>>>>::from_foreign(...)to matchinto_foreign().
Shasiko caught this one
Quoted 4 lines
- **Unchecked transport return length in
spdm_exchangeand callers**:
- Inspdm_exchange,Untrusted::new(&response_buf[..]).validate(&*self)validates the entire pre-allocatedresponse_bufinstead of&response_buf[..length as usize].
- Onlyget_versionchecksif rc > response_vec.len()beforeresponse_vec.truncate(rc). None ofget_capabilities,negotiate_algs,get_digests,get_cert_exchange, orchallengecheck ifrc > response_vec.len(); if a transport returns a size larger than the buffer,truncate(rc)is a no-op. Better yet,spdm_exchangeitself should enforce(length as usize) <= response_buf.len()and returnusize.
The next version handles this better, but I don't think this is an actual issue. If transport_function returns a wrong length we will just check the entire response_buf. It's not a soundness or safety issue, just a logic bug in transport_function. That's just up to transport_function to tell us how much data we received.
Quoted 11 lines
#### 2. Design & Abstraction Issues
- **
Untrusted<T>/ValidateUsage Defeats Its Own Purpose (Patches 3–4, 10, 15–22)**:
- The motivation forUntrusted<T>is that I/O boundaries (such asspdm_exchange) accept or returnUntrusteddata so the caller cannot touch the bytes without going throughValidate.
- Instead,spdm_exchangetakes a plainresponse_buf: &mut [u8], and every caller manually wraps the slice and validates it in a single expression:```rust let response: GetVersionRsp = Untrusted::new(response_vec.as_slice()).validate(&*self)?; ```Wrapping a plain
&[u8]inUntrusted::new(...)only to immediately call.validate(...)on the same line provides no type-level guarantee that untrusted buffers aren't read elsewhere (and in fact,response_vecis indexed directly right aftervalidate()to extendself.transcript).
- Furthermore, addingtype Contextwith a lifetime parameter'ctoValidate<'c, Input>makes the generalValidatetrait much heavier when a normal parsing methodFromBytes/try_from_bytes(buf: &[u8], state: &SpdmState)would be simpler and more idiomatic.
Yeah, I caught this already
Quoted 8 lines
- **Abuse of
CStringandCString::into_vec(Patches 21 & 22)**:
- Patch 21 addsCString::into_vecsolely soverify_signaturein Patch 22 can format the 64-byte ASCIIspdm_prefixusingCString::try_from_fmt(...).
- BecauseCString's internalKVec<u8>includes the trailing NUL terminator (\0),prefix.into_vec()actually returns **65 bytes** (64 ASCII bytes + 1 NUL byte), which then forces line 1013 to compensate for the extra NUL byte by subtracting1fromzero_pad_len:```rust let zero_pad_len = SPDM_COMBINED_PREFIX_SZ - SPDM_PREFIX_SZ - SPDM_CONTEXT.len() - 1; ```- Not only is
CString::into_veca footgun,verify_signaturedoesn't needCStringor heap allocation at all:mis already allocated right above on line 981, or it can format directly into a fixed-size stack buffer[0u8; SPDM_COMBINED_PREFIX_SZ].
Fixed
Quoted 5 lines
- **Direct
unsafeC FFI Calls in Driver/Library Code (lib/rspdm/state.rs)**:
-lib/rspdm/state.rsdirectly manipulates raw C pointers (*mut bindings::device,*mut bindings::crypto_shash,*mut bindings::shash_desc,*mut bindings::x509_certificate,*mut bindings::public_key) and makes rawunsafecalls tocrypto_alloc_shash,crypto_shash_digest,x509_cert_parse,public_key_verify_signature, andget_random_bytes.
- Per Rust-for-Linux conventions, C subsystem bindings (crypto_shash,x509_certificate,public_key,get_random_bytes) should be wrapped in safe abstractions underrust/kernel/, anddevshould usedevice::Devicerather than a raw*mut bindings::devicewith no lifetime or reference-count guarantee.
- Note also invalidate_cert_chain:let cert = unsafe { *cert_ptr };copies the entire Cstruct x509_certificateby value onto the stack just to read fields (cert.unsupported_sig,cert.blacklisted,cert.sig).
Fixed the device::Device part, crypto still doesn't have bindings (see above)
Quoted 3 lines
- **Useless lifetime
'aonSpdmState<'a>(state.rs:136)**:
-SpdmState<'a>has a lifetime parameter'aonly used forbase_asym_enc: &'a CStrandbase_hash_alg_name: &'a CStr, which only ever point to'staticstring literals (c_str!("pkcs1"),c_str!("sha256"), etc.). Using&'static CStr(andc"..."instead ofCStr::from_bytes_with_nul(b"...\0")?, which also eliminates the need for Patch 9!) removes the'alifetime parameter fromSpdmStateentirely.
Outdated with Device lifetimes, although good point about patch 9
Quoted 35 lines
============================
While
lib/rspdm/validator.rsis a step up from earlier versions in that it contains **zerounsafeblocks** and uses.get(...)instead of[]indexing, the implementation is very unidiomatic, does a lot of wasted heap allocations, and still has bugs (including a kernel panic / underflow vector).Here is a breakdown of the issues in
validator.rs:---
### 1. Kernel Panic / Integer Underflow & Range Bug in
SpdmCertChain::validate
Even withoutunsafe, safe Rust in the kernel must not panic on untrusted input whenCONFIG_RUST_OVERFLOW_CHECKSis enabled. Look at lines 849–872:```rust let length = u16::from_le_bytes( unvalidated .get(0..2) .ok_or(EIO)? .try_into() .map_err(|_| EINVAL)?, ); let root_hash_end = SPDM_CERT_CHAIN_HDR_SZ + context.hash_len; ... let cert_chain_end = length as usize - root_hash_end; let mut certificates = KVec::new(); certificates.extend_from_slice( unvalidated.get(root_hash_end..cert_chain_end).ok_or(EIO)?, GFP_KERNEL, )?; ```
Quoted 2 lines
- **Arithmetic underflow**:
lengthcomes directly from the first 2 bytes of the untrusted certificate chain beforestate.rscompares it tototal_cert_len. If a responder sendslength < 4 + hash_len,length as usize - root_hash_endunderflows (panicking with overflow checks enabled, or wrapping tousize::MAX - ...).
- **Wrong slice end**:lengthin the SPDM Certificate Chain format (SPDM 1.0 Table 15) is the *total* length of the chain includingroot_hash_end(4 + H). Subtractingroot_hash_endgives the *number of certificate bytes*, not the end index. Using it as the end index ofroot_hash_end..cert_chain_endslices[4+H .. length-(4+H)], dropping the last4 + Hbytes of the certificate chain (or returningErr(EIO)if the certificates are smaller than4 + Hbytes).
Shasiko caught this too
Quoted 15 lines
---
### 2. Allocating and Copying into
KVecs Only to Immediately Drop Them (#[expect(dead_code)]Everywhere)
Almost every response struct invalidator.rs(GetVersionRsp,RespAlgStruct,SupportedAlgorithms,GetCapabilitiesRsp,NegotiateAlgsRsp,GetDigestsRsp,SpdmCertChain,ChallengeRsp) is annotated with#[expect(dead_code)].Why? Because
validate()heap-allocatesKVecs and copies payload data out of the response buffer into fields that **are never read anywhere in the crate**:
- **GetDigestsRsp::validate**: Allocates up to 8KVec<u8>s (digests: [KVec<u8>; SPDM_SLOTS]) and copies every digest into the heap. ThenSpdmState::get_digestsonly readsresponse.header.param1andresponse.header.param2and immediately dropsresponse(freeing all 8KVecs without ever looking at the digests).
- **SupportedAlgorithms::from_bytes**: Allocatesext_asym: KVec<u32>,ext_hash: KVec<u32>, andalg_struct: KVec<RespAlgStruct>(each containing 2 moreKVecs).SpdmState::get_capabilitiesnever accessesresponse.supported_algorithmsat all.
- **NegotiateAlgsRsp::validate**: Loops overext_asym_sel_count,ext_hash_sel_count, andheader.param1(RespAlgStruct), allocatingKVecs for all of them. Then, as soon asvalidate()returns toSpdmState::negotiate_algs,state.rschecks:```rust || response.ext_asym_sel_count != 0 || response.ext_hash_sel_count != 0 || response.header.param1 > request.header.param1 // request.header.param1 is always 0! ```
Quoted 6 lines
So any non-empty
ext_asym,ext_hash, orresp_alg_structis **always rejected** instate.rs—yetvalidator.rshas 45 lines of code to heap-allocate and populate those vectors from untrusted counts *before*state.rsrejects them!
- **SpdmCertChain::validate**: Heap-allocatesroot_hash: KVec<u8>andcertificates: KVec<u8>.SpdmState::get_certificateonly checkscerts.length, dropscerts, and stores the original unsplitcerts_bufinself.certs[slot].
- **ChallengeRsp::validate**: Heap-allocatescert_chain_hash: KVec<u8>andopaque_data: KVec<u8>, neither of which is ever read or verified.
- **Even fixed-size requests heap-allocate**:SpdmHeader::to_bytes()(4 bytes),GetVersionReq::to_bytes()(4 bytes),GetDigestsReq::to_bytes()(4 bytes),GetCertificateReq::to_bytes()(8 bytes),NegotiateAlgsReq::to_bytes()(32 bytes), andChallengeReq::to_bytes()(36 or 44 bytes) all allocate aKVec<u8>(withoutwith_capacity, causing multiple reallocations as.push()/.extend_from_slice()are called) instead of returning a fixed-size byte array[u8; N]or implementingAsBytes.Why copy slices out of
unvalidated: &'a [u8]into newly allocatedKVec<u8>s at all, when the parsed response struct can either borrow&'a [u8]sub-slices directly from the response buffer or validate in-place?
I replied to Alexandre about this as well.
The SPDM responses are a bit complex. For example NegotiateAlgsRsp has multiple dynamically sized arrays, and the fields change based on the version.
The current approach has the obvious downside of copying the data, but the buffers are small in the scheme of things. Copying it allows us to use nice Rust structs (not packed), ensure the data is in the correct endianes and check and validate the entire message. It also means that the Rsp structs contain all of the information. As your LLM points out, it currently isn't all read, but this is a basic starting point.
In terms of the allocations, it is only a few bytes, so I don't think it matters that much.
In terms of unused elements, there are only a few unused, most of the elements aren't dead code
Maybe in the future zerocopy could be a performance improvement, but I think right now it's a large complexity overhead in this case.
Quoted 19 lines
---
### 3. Magic Byte Offsets Instead of a Cursor/Reader or
FromBytes
Because the author moved away from#[repr(C, packed)]structs in v4, they replaced struct definitions with hardcoded byte offsets. Look atSupportedAlgorithms::from_bytesandNegotiateAlgsRsp::validate:```rust let alg_struct_count = *buf.get(0).ok_or(EIO)?; let length = read_le16(2)?; let measurement_specification = *buf.get(4).ok_or(EIO)?; let other_params_support = *buf.get(5).ok_or(EIO)?; let base_asym_algo = read_le32(6)?; let base_hash_algo = read_le32(10)?; let ext_asym_count = *buf.get(26).ok_or(EIO)?; let ext_hash_count = *buf.get(27).ok_or(EIO)?; let mel_specification = *buf.get(29).ok_or(EIO)?; let mut offset = 30; ```
Quoted 3 lines
- **Hard to audit against the spec**: Jumping from offset
10to26(skipping 12 reserved bytes) or27to29with magic numbers makes it very easy to miscount offsets, andread_le16/read_le32closures are copy-pasted across multiple functions.
- **What would be much cleaner**:
1. For fixed-size layouts, useFromBytes::from_bytes_copy_prefix(orzerocopy) on#[repr(C)]structs with explicit reserved fields (_reserved: [u8; 12]) and little-endian integer wrappers, **or**
I don't think this would be cleaner, it actually sounds much less clean. Also, there aren't that many fixed size structs
Quoted 10 lines
2. Use a small sequential reader struct wrapping
&'a [u8](e.g.,r.read_u8()?,r.read_le16()?,r.read_le32()?,r.skip(12)?,r.read_slice(len)?). That eliminates manual offset arithmetic, cannot underflow/overflowoffset, and reads top-to-bottom like the spec table.---
### 4. Inconsistent Split of Responsibilities Between
validator.rsandstate.rs
The boundary between what is validated inValidate::validate(validator.rs) vs. what is checked inSpdmState(state.rs) is arbitrary:
- **Duplicated checks**:GetCapabilitiesRsp::validatechecksif data_transfer_size < SPDM_MIN_DATA_TRANSFER_SIZE(validator.rs:456), and thenSpdmState::get_capabilitieschecksif response.data_transfer_size < 42(state.rs:508) again.
- **Misplaced checks**:GetDigestsRsp::validatechecksheader.param2 == 0insidevalidator.rs, whereasNegotiateAlgsRspandGetCertificateRspleave basic structural checks (ext_asym_sel_count != 0,response.header.param1 & 0xF != slot,total_cert_len != cert_chain_length) tostate.rs.
- **Inconsistent error codes**: Withinvalidator.rs, out-of-bounds buffer slices returnEIO, wrong response codes returnEINVAL, and wrong versions or lengths returnEPROTO. Inspdm_exchange, if a truncated packet is< 4bytes (header size),spdm_exchangereturnsOk(length)and lets the caller'svalidate()fail withEIO.
Quoted 6 lines
---
### Summary
The move to safe code invalidator.rsachieved the goal of removingunsafepointer casts, but it was done in the most mechanical way possible: replacing struct field accesses with hardcoded.get(offset..offset + N)calls and copying every variable-length region into throwawayKVecs.Replacing the manual offset indexing with either
FromBytes/zerocopy(for fixed headers) + a tiny slice cursor (borrowing&'a [u8]slices instead of allocatingKVecs) would probably cutvalidator.rsin half, remove ~15 unnecessary heap allocations per authentication, and fix theSpdmCertChainunderflow/range bug.
That won't handle endian conversion though. A slice cursor is an interesting idea though, I might add that as a followup
Alistair
Quoted 7 lines · Alistair Francis
I was trying to avoid adding crypto and X.509 bindings in this series, as that's a few more subsystems this ends up touching. We need to add PQC as well, so crypto bindings are next on the todo list once this is merged.
Is this going to stall on adding crypto bindings?
The general rule is to add safe abstractions first, and their dependencies (i.e. other safe abstractions), then finally the users. That is, adding code that calls C bindings directly without a proper abstraction is generally not allowed.
Exceptions can be made, of course, but they need to be justified, i.e. there are cases where the kernel may really need some code to land, but one needs to argue why -- it is definitely not the default.
Cheers,
Miguel
Alexandre Courbot
reviewerQuoted 47 lines · Alistair Francis
Quoted 32 lines · Alice Ryhl
Quoted 11 lines · On Mon, Sep 28, 2026 at 11:11:01AM +1000, [email protected] wrote:
From: Alistair Francis <[email protected]>
Security Protocols and Data Models (SPDM) [1] is used for authentication, attestation and key exchange. SPDM is generally used over a range of transports, such as PCIe, MCTP/SMBus/I3C, ATA, SCSI, NVMe or TCP.
From the kernels perspective SPDM is used to authenticate and attest devices. In this threat model a device is considered untrusted until it can be verified by the kernel and userspace using SPDM. As such SPDM data is untrusted data that can be mallicious.
Quoted 8 lines
The SPDM specification is also complex, with the 1.2.1 spec being almost 200 pages and the 1.3.0 spec being almost 250 pages long.
As such we have the kernel parsing untrusted responses from a complex specification, which sounds like a possible exploit vector. This is the type of place where Rust excels!
This series implements a SPDM requester in Rust.
So I looked a bit at this, and here are some comments. I will not repeat what Alexandre has already said.
First, I'd like to say that it's good to see that all wire-format parsing in
lib/rspdm/validator.rsis written in completely safe Rust. That's exactly what we want to see for parsing in Rust. We want the code to have basically no unsafe code whatsoever so that we really get to rely on the bullet-proof guarantees about bugs being caught at compile time, or at least at runtime.Quoted 6 lines
Unfortunately it looks like the same cannot be said for the state machine in
lib/rspdm/state.rs, which has way too much unsafe code in its interaction with the rest of the kernel. There needs to be more intermediate Rust abstractions here. This file should not have *any* unsafe code, except maybe 1-2 blocks for using the C transport and validate function pointers.Yeah, it's the crypto functions. There are currently no bindings, so it's just directly calling the C functions.
Without the crypto functions it's just a single unsafe call to
transport_function()instate.rsand a few inlib.rsto handl the C <-> FFI boundy.
Quoted 6 lines
I was trying to avoid adding crypto and X.509 bindings in this series, as that's a few more subsystems this ends up touching. We need to add PQC as well, so crypto bindings are next on the todo list once this is merged.
Is this going to stall on adding crypto bindings?
So without going all the way to fully-fledged crypto bindings, you will spare yourself and your reviewers quite a bit of pain if you at least implement a safe and lightweight crypto wrapper that is local to SPDM and could eventually grow into official crypto bindings as a future effort. As it is the code is very hard to review: for instance crypto_free_shash is always called after free_desc, and looks like it could/should be part of it, but that's hard to assess as the crypto code is mixed with the rest and called here and there. I just cannot tell whether all these unsafe blocks are safe of not, which is a problem since the goal of writing this in Rust is to prevent exploits.
A self-contained crypto module exposing a safe interface would remove that burden, reduce the number of unsafe blocks, and make the series easier to review globally while not necessarily resulting in more code. You just need to store the relevant pointers into a new data structure, enforce the required invariants at construction time, and codify the actions that SPDM is already doing in the form of methods.
Quoted 67 lines · Alexandre Courbot
Quoted 47 lines · Alistair Francis
Quoted 32 lines · Alice Ryhl
Quoted 11 lines · On Mon, Sep 28, 2026 at 11:11:01AM +1000, [email protected] wrote:
From: Alistair Francis <[email protected]>
Security Protocols and Data Models (SPDM) [1] is used for authentication, attestation and key exchange. SPDM is generally used over a range of transports, such as PCIe, MCTP/SMBus/I3C, ATA, SCSI, NVMe or TCP.
From the kernels perspective SPDM is used to authenticate and attest devices. In this threat model a device is considered untrusted until it can be verified by the kernel and userspace using SPDM. As such SPDM data is untrusted data that can be mallicious.
Quoted 8 lines
The SPDM specification is also complex, with the 1.2.1 spec being almost 200 pages and the 1.3.0 spec being almost 250 pages long.
As such we have the kernel parsing untrusted responses from a complex specification, which sounds like a possible exploit vector. This is the type of place where Rust excels!
This series implements a SPDM requester in Rust.
So I looked a bit at this, and here are some comments. I will not repeat what Alexandre has already said.
First, I'd like to say that it's good to see that all wire-format parsing in
lib/rspdm/validator.rsis written in completely safe Rust. That's exactly what we want to see for parsing in Rust. We want the code to have basically no unsafe code whatsoever so that we really get to rely on the bullet-proof guarantees about bugs being caught at compile time, or at least at runtime.Quoted 6 lines
Unfortunately it looks like the same cannot be said for the state machine in
lib/rspdm/state.rs, which has way too much unsafe code in its interaction with the rest of the kernel. There needs to be more intermediate Rust abstractions here. This file should not have *any* unsafe code, except maybe 1-2 blocks for using the C transport and validate function pointers.Yeah, it's the crypto functions. There are currently no bindings, so it's just directly calling the C functions.
Without the crypto functions it's just a single unsafe call to
transport_function()instate.rsand a few inlib.rsto handl the C <-> FFI boundy.Quoted 6 lines
I was trying to avoid adding crypto and X.509 bindings in this series, as that's a few more subsystems this ends up touching. We need to add PQC as well, so crypto bindings are next on the todo list once this is merged.
Is this going to stall on adding crypto bindings?
So without going all the way to fully-fledged crypto bindings, you will spare yourself and your reviewers quite a bit of pain if you at least implement a safe and lightweight crypto wrapper that is local to SPDM and could eventually grow into official crypto bindings as a future effort. As it is the code is very hard to review: for instance
crypto_free_shashis always called afterfree_desc, and looks like it could/should be part of it, but that's hard to assess as the crypto code is mixed with the rest and called here and there. I just cannot tell whether all these unsafe blocks are safe of not, which is a problem since the goal of writing this in Rust is to prevent exploits.
Quoted 6 lines
A self-contained crypto module exposing a safe interface would remove that burden, reduce the number of
unsafeblocks, and make the series easier to review globally while not necessarily resulting in more code. You just need to store the relevant pointers into a new data structure, enforce the required invariants at construction time, and codify the actions that SPDM is already doing in the form of methods.
Oh! That is a great idea. I have added that locally and it's pretty nice. I'll tidy it up a bit more and then send a v5
Alistair
Quoted 3 lines · Alistair Francis
Oh! That is a great idea. I have added that locally and it's pretty nice. I'll tidy it up a bit more and then send a v5
Yeah, to clarify, abstractions do not need to be complete. In fact, they should only cover whatever you as a user need, since the kernel does not want dead code anyway.
In general, though, we also avoid adding them locally and we involve the subsystem maintainers etc.
Cheers,
Miguel