* [PATCH v4 00/22] lib: Rust implementation of SPDM
@ 2026-09-28 1:11 alistair23
2026-09-28 1:11 ` [PATCH v4 01/22] virt: coco: change tsm_register() to use a const struct alistair23
` (18 more replies)
0 siblings, 19 replies; 45+ messages in thread
From: alistair23 @ 2026-09-28 1:11 UTC (permalink / raw)
To: lukas, linux-cxl, akpm, Jonathan.Cameron, rust-for-linux,
linux-kernel, jic23, bhelgaas, alistair, djbw, linux-pci
Cc: wilfred.mallawa, a.hindborg, gregkh, alex.gaynor, alistair23,
aliceryhl, benno.lossin, boqun.feng, bjorn3_gh, tmgross, gary,
ojeda, Alistair Francis
From: Alistair Francis <alistair.francis@wdc.com>
Security Protocols and Data Models (SPDM) [1] is used for authentication,
attestation and key exchange. SPDM is generally used over a range of
transports, such as PCIe, MCTP/SMBus/I3C, ATA, SCSI, NVMe or TCP.
From the kernels perspective SPDM is used to authenticate and attest devices.
In this threat model a device is considered untrusted until it can be verified
by the kernel and userspace using SPDM. As such SPDM data is untrusted data
that can be mallicious.
The SPDM specification is also complex, with the 1.2.1 spec being almost 200
pages and the 1.3.0 spec being almost 250 pages long.
As such we have the kernel parsing untrusted responses from a complex
specification, which sounds like a possible exploit vector. This is the type
of place where Rust excels!
This series implements a SPDM requester in Rust.
This is 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 `unsafe`s and using `get()` for the arrays removes a large number
of possible `panic`s. 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/cover.1719771133.git.lukas@wunner.de/
3: http://lore.kernel.org/69976d7d39c60_2f4a1009@dwillia2-mobl4.notmuch
4: https://lore.kernel.org/all/69976d7d39c60_2f4a1009@dwillia2-mobl4.notmuch/
5: https://lore.kernel.org/lkml/69e19c80b892b_fe0831000@djbw-dev.notmuch/
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
--
2.55.0
^ permalink raw reply [flat|nested] 45+ messages in thread
* [PATCH v4 01/22] virt: coco: change tsm_register() to use a const struct
2026-09-28 1:11 [PATCH v4 00/22] lib: Rust implementation of SPDM alistair23
@ 2026-09-28 1:11 ` alistair23
2026-09-28 1:41 ` sashiko-bot
2026-09-28 1:11 ` [PATCH v4 02/22] rust: transmute: add `cast_slice[_mut]` functions alistair23
` (17 subsequent siblings)
18 siblings, 1 reply; 45+ messages in thread
From: alistair23 @ 2026-09-28 1:11 UTC (permalink / raw)
To: lukas, linux-cxl, akpm, Jonathan.Cameron, rust-for-linux,
linux-kernel, jic23, bhelgaas, alistair, djbw, linux-pci
Cc: wilfred.mallawa, a.hindborg, gregkh, alex.gaynor, alistair23,
aliceryhl, benno.lossin, boqun.feng, bjorn3_gh, tmgross, gary,
ojeda, Alistair Francis
From: Alistair Francis <alistair.francis@wdc.com>
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.
Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
---
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(-)
diff --git a/drivers/crypto/ccp/sev-dev-tsm.c b/drivers/crypto/ccp/sev-dev-tsm.c
index 46f2539d2d5a..48df8c579c74 100644
--- a/drivers/crypto/ccp/sev-dev-tsm.c
+++ b/drivers/crypto/ccp/sev-dev-tsm.c
@@ -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,
diff --git a/drivers/virt/coco/tsm-core.c b/drivers/virt/coco/tsm-core.c
index e784993353d8..6b735ac2a814 100644
--- a/drivers/virt/coco/tsm-core.c
+++ b/drivers/virt/coco/tsm-core.c
@@ -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;
diff --git a/include/linux/tsm.h b/include/linux/tsm.h
index 381c53244c83..6ac12de8c4d3 100644
--- a/include/linux/tsm.h
+++ b/include/linux/tsm.h
@@ -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 */
--
2.55.0
^ permalink raw reply related [flat|nested] 45+ messages in thread
* [PATCH v4 02/22] rust: transmute: add `cast_slice[_mut]` functions
2026-09-28 1:11 [PATCH v4 00/22] lib: Rust implementation of SPDM alistair23
2026-09-28 1:11 ` [PATCH v4 01/22] virt: coco: change tsm_register() to use a const struct alistair23
@ 2026-09-28 1:11 ` alistair23
2026-09-28 1:35 ` sashiko-bot
2026-09-28 1:11 ` [PATCH v4 03/22] rust: create basic untrusted data API alistair23
` (16 subsequent siblings)
18 siblings, 1 reply; 45+ messages in thread
From: alistair23 @ 2026-09-28 1:11 UTC (permalink / raw)
To: lukas, linux-cxl, akpm, Jonathan.Cameron, rust-for-linux,
linux-kernel, jic23, bhelgaas, alistair, djbw, linux-pci
Cc: wilfred.mallawa, a.hindborg, gregkh, alex.gaynor, alistair23,
aliceryhl, benno.lossin, boqun.feng, bjorn3_gh, tmgross, gary,
ojeda, Alistair Francis
From: Benno Lossin <benno.lossin@proton.me>
Add functions to make casting slices only one `unsafe` block.
Signed-off-by: Benno Lossin <benno.lossin@proton.me>
Message-ID: <20250814124424.516191-2-lossin@kernel.org>
Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
---
rust/kernel/transmute.rs | 71 +++++++++++++++++++++++++++++++++++++++-
1 file changed, 70 insertions(+), 1 deletion(-)
diff --git a/rust/kernel/transmute.rs b/rust/kernel/transmute.rs
index 654b5ede2fe2..68e96c635ed7 100644
--- a/rust/kernel/transmute.rs
+++ b/rust/kernel/transmute.rs
@@ -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()) }
+}
--
2.55.0
^ permalink raw reply related [flat|nested] 45+ messages in thread
* [PATCH v4 03/22] rust: create basic untrusted data API
2026-09-28 1:11 [PATCH v4 00/22] lib: Rust implementation of SPDM alistair23
2026-09-28 1:11 ` [PATCH v4 01/22] virt: coco: change tsm_register() to use a const struct alistair23
2026-09-28 1:11 ` [PATCH v4 02/22] rust: transmute: add `cast_slice[_mut]` functions alistair23
@ 2026-09-28 1:11 ` alistair23
2026-09-28 1:41 ` sashiko-bot
2026-09-28 1:11 ` [PATCH v4 04/22] rust: validate: add `Validate` trait alistair23
` (15 subsequent siblings)
18 siblings, 1 reply; 45+ messages in thread
From: alistair23 @ 2026-09-28 1:11 UTC (permalink / raw)
To: lukas, linux-cxl, akpm, Jonathan.Cameron, rust-for-linux,
linux-kernel, jic23, bhelgaas, alistair, djbw, linux-pci
Cc: wilfred.mallawa, a.hindborg, gregkh, alex.gaynor, alistair23,
aliceryhl, benno.lossin, boqun.feng, bjorn3_gh, tmgross, gary,
ojeda, Alistair Francis
From: Benno Lossin <benno.lossin@proton.me>
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.
Signed-off-by: Benno Lossin <benno.lossin@proton.me>
Message-ID: <20250814124424.516191-3-lossin@kernel.org>
Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
---
rust/kernel/lib.rs | 1 +
rust/kernel/validate.rs | 147 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 148 insertions(+)
create mode 100644 rust/kernel/validate.rs
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index 4d5c96ddc49c..239f8325b40a 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -143,6 +143,7 @@
pub mod uaccess;
#[cfg(CONFIG_USB = "y")]
pub mod usb;
+pub mod validate;
pub mod workqueue;
pub mod xarray;
diff --git a/rust/kernel/validate.rs b/rust/kernel/validate.rs
new file mode 100644
index 000000000000..ca2e5abda3e2
--- /dev/null
+++ b/rust/kernel/validate.rs
@@ -0,0 +1,147 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Untrusted data API.
+//!
+//! # Overview
+//!
+//! Untrusted data is marked using the [`Untrusted<T>`] type. See [Rationale](#rationale) for the
+//! reasons to mark untrusted data throughout the kernel. It is a totally opaque wrapper, it is not
+//! possible to read the data inside.
+//!
+//! APIs that write back into userspace usually allow writing untrusted bytes directly, allowing
+//! direct copying of untrusted user data back into userspace without validation.
+//!
+//! # Rationale
+//!
+//! When reading data from an untrusted source, it must be validated before it can be used for
+//! **logic**. For example, this is a very bad idea:
+//!
+//! ```
+//! # fn read_bytes_from_network() -> KBox<[u8]> {
+//! # Box::new([1, 0], kernel::alloc::flags::GFP_KERNEL).unwrap()
+//! # }
+//! let bytes: KBox<[u8]> = read_bytes_from_network();
+//! let data_index = bytes[0];
+//! let data = bytes[usize::from(data_index)];
+//! ```
+//!
+//! While this will not lead to a memory violation (because the array index checks the bounds), it
+//! might result in a kernel panic. For this reason, all untrusted data must be wrapped in
+//! [`Untrusted<T>`]. This type only allows validating the data or passing it along, since copying
+//! data from userspace back into userspace is allowed for untrusted data.
+
+use core::ops::{Deref, DerefMut};
+
+use crate::{
+ alloc::{
+ Allocator,
+ Vec, //
+ },
+ transmute::{
+ cast_slice,
+ cast_slice_mut, //
+ },
+};
+
+/// 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:
+///
+/// ```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>]`
+/// 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.
+ /// Untrusted::new(unsafe { bindings::read_foo_info() })
+ /// }
+ /// ```
+ #[inline]
+ pub fn new(value: T) -> Self
+ where
+ T: Sized,
+ {
+ Self(value)
+ }
+}
+
+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) }
+ }
+}
+
+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) }
+ }
+}
+
+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()) }
+ }
+}
+
+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()) }
+ }
+}
--
2.55.0
^ permalink raw reply related [flat|nested] 45+ messages in thread
* [PATCH v4 04/22] rust: validate: add `Validate` trait
2026-09-28 1:11 [PATCH v4 00/22] lib: Rust implementation of SPDM alistair23
` (2 preceding siblings ...)
2026-09-28 1:11 ` [PATCH v4 03/22] rust: create basic untrusted data API alistair23
@ 2026-09-28 1:11 ` alistair23
2026-09-28 1:41 ` sashiko-bot
2026-09-28 1:11 ` [PATCH v4 05/22] X.509: Make certificate parser public alistair23
` (14 subsequent siblings)
18 siblings, 1 reply; 45+ messages in thread
From: alistair23 @ 2026-09-28 1:11 UTC (permalink / raw)
To: lukas, linux-cxl, akpm, Jonathan.Cameron, rust-for-linux,
linux-kernel, jic23, bhelgaas, alistair, djbw, linux-pci
Cc: wilfred.mallawa, a.hindborg, gregkh, alex.gaynor, alistair23,
aliceryhl, benno.lossin, boqun.feng, bjorn3_gh, tmgross, gary,
ojeda, Alistair Francis
From: Benno Lossin <benno.lossin@proton.me>
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.
Signed-off-by: Benno Lossin <benno.lossin@proton.me>
Message-ID: <20250814124424.516191-4-lossin@kernel.org>
Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
---
rust/kernel/validate.rs | 84 ++++++++++++++++++++++++++++++++++++++++-
1 file changed, 83 insertions(+), 1 deletion(-)
diff --git a/rust/kernel/validate.rs b/rust/kernel/validate.rs
index ca2e5abda3e2..991eda67a50f 100644
--- a/rust/kernel/validate.rs
+++ b/rust/kernel/validate.rs
@@ -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, &'a Self>>(
+ &'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, &'a mut Self>>(
+ &'a mut 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 &'a Untrusted<T> {
+ type Inner = &'a T;
+}
+
+impl<'a, T: ?Sized> ValidateInput for &'a mut Untrusted<T> {
+ type Inner = &'a mut T;
+}
+
+mod private {
+ use super::Untrusted;
+
+ pub trait Sealed {}
+
+ impl<T: ?Sized> Sealed for Untrusted<T> {}
+ impl<'a, T: ?Sized> Sealed for &'a Untrusted<T> {}
+ impl<'a, T: ?Sized> Sealed for &'a mut 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>;
+}
--
2.55.0
^ permalink raw reply related [flat|nested] 45+ messages in thread
* [PATCH v4 05/22] X.509: Make certificate parser public
2026-09-28 1:11 [PATCH v4 00/22] lib: Rust implementation of SPDM alistair23
` (3 preceding siblings ...)
2026-09-28 1:11 ` [PATCH v4 04/22] rust: validate: add `Validate` trait alistair23
@ 2026-09-28 1:11 ` alistair23
2026-09-28 1:39 ` sashiko-bot
2026-09-28 1:11 ` [PATCH v4 06/22] X.509: Parse Subject Alternative Name in certificates alistair23
` (13 subsequent siblings)
18 siblings, 1 reply; 45+ messages in thread
From: alistair23 @ 2026-09-28 1:11 UTC (permalink / raw)
To: lukas, linux-cxl, akpm, Jonathan.Cameron, rust-for-linux,
linux-kernel, jic23, bhelgaas, alistair, djbw, linux-pci
Cc: wilfred.mallawa, a.hindborg, gregkh, alex.gaynor, alistair23,
aliceryhl, benno.lossin, boqun.feng, bjorn3_gh, tmgross, gary,
ojeda, Dan Williams, Alistair Francis, Ilpo Järvinen
From: Lukas Wunner <lukas@wunner.de>
The upcoming support for PCI device authentication with CMA-SPDM
(PCIe r6.1 sec 6.31) requires validating the Subject Alternative Name
in X.509 certificates.
High-level functions for X.509 parsing such as key_create_or_update()
throw away the internal, low-level struct x509_certificate after
extracting the struct public_key and public_key_signature from it.
The Subject Alternative Name is thus inaccessible when using those
functions.
Afford CMA-SPDM access to the Subject Alternative Name by making struct
x509_certificate public, together with the functions for parsing an
X.509 certificate into such a struct and freeing such a struct.
No functional change intended.
Signed-off-by: Lukas Wunner <lukas@wunner.de>
Reviewed-by: Dan Williams <dan.j.williams@intel.com>
Reviewed-by: Alistair Francis <alistair.francis@wdc.com>
Reviewed-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
---
crypto/asymmetric_keys/x509_parser.h | 42 +--------------------
include/keys/x509-parser.h | 55 ++++++++++++++++++++++++++++
2 files changed, 56 insertions(+), 41 deletions(-)
create mode 100644 include/keys/x509-parser.h
diff --git a/crypto/asymmetric_keys/x509_parser.h b/crypto/asymmetric_keys/x509_parser.h
index b7aeebdddb36..39f1521b773d 100644
--- a/crypto/asymmetric_keys/x509_parser.h
+++ b/crypto/asymmetric_keys/x509_parser.h
@@ -5,51 +5,11 @@
* Written by David Howells (dhowells@redhat.com)
*/
-#include <linux/cleanup.h>
-#include <linux/time.h>
-#include <crypto/public_key.h>
-#include <keys/asymmetric-type.h>
-#include <crypto/sha2.h>
-
-struct x509_certificate {
- struct x509_certificate *next;
- struct x509_certificate *signer; /* Certificate that signed this one */
- struct public_key *pub; /* Public key details */
- struct public_key_signature *sig; /* Signature parameters */
- u8 sha256[SHA256_DIGEST_SIZE]; /* Hash for blacklist purposes */
- char *issuer; /* Name of certificate issuer */
- char *subject; /* Name of certificate subject */
- struct asymmetric_key_id *id; /* Issuer + Serial number */
- struct asymmetric_key_id *skid; /* Subject + subjectKeyId (optional) */
- time64_t valid_from;
- time64_t valid_to;
- const void *tbs; /* Signed data */
- unsigned tbs_size; /* Size of signed data */
- unsigned raw_sig_size; /* Size of signature */
- const void *raw_sig; /* Signature data */
- const void *raw_serial; /* Raw serial number in ASN.1 */
- unsigned raw_serial_size;
- unsigned raw_issuer_size;
- const void *raw_issuer; /* Raw issuer name in ASN.1 */
- const void *raw_subject; /* Raw subject name in ASN.1 */
- unsigned raw_subject_size;
- unsigned raw_skid_size;
- const void *raw_skid; /* Raw subjectKeyId in ASN.1 */
- unsigned index;
- bool seen; /* Infinite recursion prevention */
- bool verified;
- bool self_signed; /* T if self-signed (check unsupported_sig too) */
- bool unsupported_sig; /* T if signature uses unsupported crypto */
- bool blacklisted;
-};
+#include <keys/x509-parser.h>
/*
* x509_cert_parser.c
*/
-extern void x509_free_certificate(struct x509_certificate *cert);
-DEFINE_FREE(x509_free_certificate, struct x509_certificate *,
- if (!IS_ERR(_T)) x509_free_certificate(_T))
-extern struct x509_certificate *x509_cert_parse(const void *data, size_t datalen);
extern int x509_decode_time(time64_t *_t, size_t hdrlen,
unsigned char tag,
const unsigned char *value, size_t vlen);
diff --git a/include/keys/x509-parser.h b/include/keys/x509-parser.h
new file mode 100644
index 000000000000..8b68e720693a
--- /dev/null
+++ b/include/keys/x509-parser.h
@@ -0,0 +1,55 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* X.509 certificate parser
+ *
+ * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ */
+
+#ifndef _KEYS_X509_PARSER_H
+#define _KEYS_X509_PARSER_H
+
+#include <linux/cleanup.h>
+#include <linux/time.h>
+#include <crypto/public_key.h>
+#include <keys/asymmetric-type.h>
+#include <crypto/sha2.h>
+
+struct x509_certificate {
+ struct x509_certificate *next;
+ struct x509_certificate *signer; /* Certificate that signed this one */
+ struct public_key *pub; /* Public key details */
+ struct public_key_signature *sig; /* Signature parameters */
+ u8 sha256[SHA256_DIGEST_SIZE]; /* Hash for blacklist purposes */
+ char *issuer; /* Name of certificate issuer */
+ char *subject; /* Name of certificate subject */
+ struct asymmetric_key_id *id; /* Issuer + Serial number */
+ struct asymmetric_key_id *skid; /* Subject + subjectKeyId (optional) */
+ time64_t valid_from;
+ time64_t valid_to;
+ const void *tbs; /* Signed data */
+ unsigned tbs_size; /* Size of signed data */
+ unsigned raw_sig_size; /* Size of signature */
+ const void *raw_sig; /* Signature data */
+ const void *raw_serial; /* Raw serial number in ASN.1 */
+ unsigned raw_serial_size;
+ unsigned raw_issuer_size;
+ const void *raw_issuer; /* Raw issuer name in ASN.1 */
+ const void *raw_subject; /* Raw subject name in ASN.1 */
+ unsigned raw_subject_size;
+ unsigned raw_skid_size;
+ const void *raw_skid; /* Raw subjectKeyId in ASN.1 */
+ unsigned index;
+ bool seen; /* Infinite recursion prevention */
+ bool verified;
+ bool self_signed; /* T if self-signed (check unsupported_sig too) */
+ bool unsupported_sig; /* T if signature uses unsupported crypto */
+ bool blacklisted;
+};
+
+struct x509_certificate *x509_cert_parse(const void *data, size_t datalen);
+void x509_free_certificate(struct x509_certificate *cert);
+
+DEFINE_FREE(x509_free_certificate, struct x509_certificate *,
+ if (!IS_ERR(_T)) x509_free_certificate(_T))
+
+#endif /* _KEYS_X509_PARSER_H */
--
2.55.0
^ permalink raw reply related [flat|nested] 45+ messages in thread
* [PATCH v4 06/22] X.509: Parse Subject Alternative Name in certificates
2026-09-28 1:11 [PATCH v4 00/22] lib: Rust implementation of SPDM alistair23
` (4 preceding siblings ...)
2026-09-28 1:11 ` [PATCH v4 05/22] X.509: Make certificate parser public alistair23
@ 2026-09-28 1:11 ` alistair23
2026-09-28 1:39 ` sashiko-bot
2026-09-28 1:11 ` [PATCH v4 07/22] X.509: Move certificate length retrieval into new helper alistair23
` (12 subsequent siblings)
18 siblings, 1 reply; 45+ messages in thread
From: alistair23 @ 2026-09-28 1:11 UTC (permalink / raw)
To: lukas, linux-cxl, akpm, Jonathan.Cameron, rust-for-linux,
linux-kernel, jic23, bhelgaas, alistair, djbw, linux-pci
Cc: wilfred.mallawa, a.hindborg, gregkh, alex.gaynor, alistair23,
aliceryhl, benno.lossin, boqun.feng, bjorn3_gh, tmgross, gary,
ojeda, Alistair Francis, Ilpo Järvinen, Dan Williams
From: Lukas Wunner <lukas@wunner.de>
The upcoming support for PCI device authentication with CMA-SPDM
(PCIe r6.1 sec 6.31) requires validating the Subject Alternative Name
in X.509 certificates.
Store a pointer to the Subject Alternative Name upon parsing for
consumption by CMA-SPDM.
Signed-off-by: Lukas Wunner <lukas@wunner.de>
Reviewed-by: Wilfred Mallawa <wilfred.mallawa@wdc.com>
Reviewed-by: Alistair Francis <alistair.francis@wdc.com>
Reviewed-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
Acked-by: Dan Williams <dan.j.williams@intel.com>
---
crypto/asymmetric_keys/x509_cert_parser.c | 9 +++++++++
include/keys/x509-parser.h | 2 ++
2 files changed, 11 insertions(+)
diff --git a/crypto/asymmetric_keys/x509_cert_parser.c b/crypto/asymmetric_keys/x509_cert_parser.c
index bfd10f0195e0..c3ec2846695a 100644
--- a/crypto/asymmetric_keys/x509_cert_parser.c
+++ b/crypto/asymmetric_keys/x509_cert_parser.c
@@ -596,6 +596,15 @@ int x509_process_extension(void *context, size_t hdrlen,
return 0;
}
+ if (ctx->last_oid == OID_subjectAltName) {
+ if (ctx->cert->raw_san)
+ return -EBADMSG;
+
+ ctx->cert->raw_san = v;
+ ctx->cert->raw_san_size = vlen;
+ return 0;
+ }
+
if (ctx->last_oid == OID_keyUsage) {
/*
* Get hold of the keyUsage bit string
diff --git a/include/keys/x509-parser.h b/include/keys/x509-parser.h
index 8b68e720693a..4e6a05a8c7a6 100644
--- a/include/keys/x509-parser.h
+++ b/include/keys/x509-parser.h
@@ -38,6 +38,8 @@ struct x509_certificate {
unsigned raw_subject_size;
unsigned raw_skid_size;
const void *raw_skid; /* Raw subjectKeyId in ASN.1 */
+ const void *raw_san; /* Raw subjectAltName in ASN.1 */
+ unsigned raw_san_size;
unsigned index;
bool seen; /* Infinite recursion prevention */
bool verified;
--
2.55.0
^ permalink raw reply related [flat|nested] 45+ messages in thread
* [PATCH v4 07/22] X.509: Move certificate length retrieval into new helper
2026-09-28 1:11 [PATCH v4 00/22] lib: Rust implementation of SPDM alistair23
` (5 preceding siblings ...)
2026-09-28 1:11 ` [PATCH v4 06/22] X.509: Parse Subject Alternative Name in certificates alistair23
@ 2026-09-28 1:11 ` alistair23
2026-09-28 1:36 ` sashiko-bot
2026-09-28 1:11 ` [PATCH v4 08/22] rust: add bindings for hash.h alistair23
` (11 subsequent siblings)
18 siblings, 1 reply; 45+ messages in thread
From: alistair23 @ 2026-09-28 1:11 UTC (permalink / raw)
To: lukas, linux-cxl, akpm, Jonathan.Cameron, rust-for-linux,
linux-kernel, jic23, bhelgaas, alistair, djbw, linux-pci
Cc: wilfred.mallawa, a.hindborg, gregkh, alex.gaynor, alistair23,
aliceryhl, benno.lossin, boqun.feng, bjorn3_gh, tmgross, gary,
ojeda, Dan Williams, Alistair Francis
From: Lukas Wunner <lukas@wunner.de>
The upcoming in-kernel SPDM library (Security Protocol and Data Model,
https://www.dmtf.org/dsp/DSP0274) needs to retrieve the length from
ASN.1 DER-encoded X.509 certificates.
Such code already exists in x509_load_certificate_list(), so move it
into a new helper for reuse by SPDM.
Export the helper so that SPDM can be tristate. (Some upcoming users of
the SPDM libray may be modular, such as SCSI and ATA.)
No functional change intended.
Signed-off-by: Lukas Wunner <lukas@wunner.de>
Reviewed-by: Dan Williams <dan.j.williams@intel.com>
Reviewed-by: Alistair Francis <alistair.francis@wdc.com>
Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
---
crypto/asymmetric_keys/x509_loader.c | 38 +++++++++++++++++++---------
include/keys/asymmetric-type.h | 2 ++
2 files changed, 28 insertions(+), 12 deletions(-)
diff --git a/crypto/asymmetric_keys/x509_loader.c b/crypto/asymmetric_keys/x509_loader.c
index 0d516c77cc26..174451b93eda 100644
--- a/crypto/asymmetric_keys/x509_loader.c
+++ b/crypto/asymmetric_keys/x509_loader.c
@@ -4,28 +4,42 @@
#include <linux/key.h>
#include <keys/asymmetric-type.h>
+ssize_t x509_get_certificate_length(const u8 *p, unsigned long buflen)
+{
+ ssize_t plen;
+
+ /* Each cert begins with an ASN.1 SEQUENCE tag and must be more
+ * than 256 bytes in size.
+ */
+ if (buflen < 4)
+ return -EINVAL;
+
+ if (p[0] != 0x30 ||
+ p[1] != 0x82)
+ return -EINVAL;
+
+ plen = (p[2] << 8) | p[3];
+ plen += 4;
+ if (plen > buflen)
+ return -EINVAL;
+
+ return plen;
+}
+EXPORT_SYMBOL_GPL(x509_get_certificate_length);
+
int x509_load_certificate_list(const u8 cert_list[],
const unsigned long list_size,
const struct key *keyring)
{
key_ref_t key;
const u8 *p, *end;
- size_t plen;
+ ssize_t plen;
p = cert_list;
end = p + list_size;
while (p < end) {
- /* Each cert begins with an ASN.1 SEQUENCE tag and must be more
- * than 256 bytes in size.
- */
- if (end - p < 4)
- goto dodgy_cert;
- if (p[0] != 0x30 ||
- p[1] != 0x82)
- goto dodgy_cert;
- plen = (p[2] << 8) | p[3];
- plen += 4;
- if (plen > end - p)
+ plen = x509_get_certificate_length(p, end - p);
+ if (plen < 0)
goto dodgy_cert;
key = key_create_or_update(make_key_ref(keyring, 1),
diff --git a/include/keys/asymmetric-type.h b/include/keys/asymmetric-type.h
index 1b91c8f98688..301efa952e26 100644
--- a/include/keys/asymmetric-type.h
+++ b/include/keys/asymmetric-type.h
@@ -84,6 +84,8 @@ extern struct key *find_asymmetric_key(struct key *keyring,
const struct asymmetric_key_id *id_2,
bool partial);
+ssize_t x509_get_certificate_length(const u8 *p, unsigned long buflen);
+
int x509_load_certificate_list(const u8 cert_list[], const unsigned long list_size,
const struct key *keyring);
--
2.55.0
^ permalink raw reply related [flat|nested] 45+ messages in thread
* [PATCH v4 08/22] rust: add bindings for hash.h
2026-09-28 1:11 [PATCH v4 00/22] lib: Rust implementation of SPDM alistair23
` (6 preceding siblings ...)
2026-09-28 1:11 ` [PATCH v4 07/22] X.509: Move certificate length retrieval into new helper alistair23
@ 2026-09-28 1:11 ` alistair23
2026-09-28 1:43 ` sashiko-bot
2026-09-28 1:11 ` [PATCH v4 09/22] rust: error: impl From<FromBytesWithNulError> for Kernel Error alistair23
` (10 subsequent siblings)
18 siblings, 1 reply; 45+ messages in thread
From: alistair23 @ 2026-09-28 1:11 UTC (permalink / raw)
To: lukas, linux-cxl, akpm, Jonathan.Cameron, rust-for-linux,
linux-kernel, jic23, bhelgaas, alistair, djbw, linux-pci
Cc: wilfred.mallawa, a.hindborg, gregkh, alex.gaynor, alistair23,
aliceryhl, benno.lossin, boqun.feng, bjorn3_gh, tmgross, gary,
ojeda, Alistair Francis
From: Alistair Francis <alistair.francis@wdc.com>
Make the functions crypto_shash_descsize(), crypto_shash_digestsize()
and crypto_free_shash() available to Rust.
Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
---
rust/bindings/bindings_helper.h | 1 +
rust/helpers/hash.c | 18 ++++++++++++++++++
rust/helpers/helpers.c | 1 +
3 files changed, 20 insertions(+)
create mode 100644 rust/helpers/hash.c
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 4b31aa7f432f..fd223b6c5aaf 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -30,6 +30,7 @@
#include <linux/acpi.h>
#include <linux/gpu_buddy.h>
+#include <crypto/hash.h>
#include <drm/drm_device.h>
#include <drm/drm_drv.h>
#include <drm/drm_file.h>
diff --git a/rust/helpers/hash.c b/rust/helpers/hash.c
new file mode 100644
index 000000000000..23a63618a370
--- /dev/null
+++ b/rust/helpers/hash.c
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <crypto/hash.h>
+
+__rust_helper unsigned int rust_helper_crypto_shash_descsize(struct crypto_shash *tfm)
+{
+ return crypto_shash_descsize(tfm);
+}
+
+__rust_helper unsigned int rust_helper_crypto_shash_digestsize(struct crypto_shash *tfm)
+{
+ return crypto_shash_digestsize(tfm);
+}
+
+__rust_helper void rust_helper_crypto_free_shash(struct crypto_shash *tfm)
+{
+ crypto_free_shash(tfm);
+}
diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
index 440fb7638e3c..09ec69f736b1 100644
--- a/rust/helpers/helpers.c
+++ b/rust/helpers/helpers.c
@@ -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"
--
2.55.0
^ permalink raw reply related [flat|nested] 45+ messages in thread
* [PATCH v4 09/22] rust: error: impl From<FromBytesWithNulError> for Kernel Error
2026-09-28 1:11 [PATCH v4 00/22] lib: Rust implementation of SPDM alistair23
` (7 preceding siblings ...)
2026-09-28 1:11 ` [PATCH v4 08/22] rust: add bindings for hash.h alistair23
@ 2026-09-28 1:11 ` alistair23
2026-09-28 1:36 ` sashiko-bot
2026-09-28 1:11 ` [PATCH v4 10/22] lib: rspdm: Initial commit of Rust SPDM alistair23
` (9 subsequent siblings)
18 siblings, 1 reply; 45+ messages in thread
From: alistair23 @ 2026-09-28 1:11 UTC (permalink / raw)
To: lukas, linux-cxl, akpm, Jonathan.Cameron, rust-for-linux,
linux-kernel, jic23, bhelgaas, alistair, djbw, linux-pci
Cc: wilfred.mallawa, a.hindborg, gregkh, alex.gaynor, alistair23,
aliceryhl, benno.lossin, boqun.feng, bjorn3_gh, tmgross, gary,
ojeda, Alistair Francis
From: Alistair Francis <alistair.francis@wdc.com>
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 <alistair.francis@wdc.com>
---
rust/kernel/error.rs | 18 +++++++++++++++---
rust/kernel/str.rs | 7 -------
2 files changed, 15 insertions(+), 10 deletions(-)
diff --git a/rust/kernel/error.rs b/rust/kernel/error.rs
index e52793f77196..84e6466f54c2 100644
--- a/rust/kernel/error.rs
+++ b/rust/kernel/error.rs
@@ -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.
diff --git a/rust/kernel/str.rs b/rust/kernel/str.rs
index b3caa9a1c898..a556788bcc5e 100644
--- a/rust/kernel/str.rs
+++ b/rust/kernel/str.rs
@@ -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()?
--
2.55.0
^ permalink raw reply related [flat|nested] 45+ messages in thread
* [PATCH v4 10/22] lib: rspdm: Initial commit of Rust SPDM
2026-09-28 1:11 [PATCH v4 00/22] lib: Rust implementation of SPDM alistair23
` (8 preceding siblings ...)
2026-09-28 1:11 ` [PATCH v4 09/22] rust: error: impl From<FromBytesWithNulError> for Kernel Error alistair23
@ 2026-09-28 1:11 ` alistair23
2026-09-28 1:45 ` sashiko-bot
2026-09-28 1:11 ` [PATCH v4 11/22] PCI/TSM: Rename pf0 to host alistair23
` (8 subsequent siblings)
18 siblings, 1 reply; 45+ messages in thread
From: alistair23 @ 2026-09-28 1:11 UTC (permalink / raw)
To: lukas, linux-cxl, akpm, Jonathan.Cameron, rust-for-linux,
linux-kernel, jic23, bhelgaas, alistair, djbw, linux-pci
Cc: wilfred.mallawa, a.hindborg, gregkh, alex.gaynor, alistair23,
aliceryhl, benno.lossin, boqun.feng, bjorn3_gh, tmgross, gary,
ojeda, Jonathan Cameron
From: Alistair Francis <alistair@alistair23.me>
This is the initial commit of the Rust SPDM library.
Signed-off-by: Alistair Francis <alistair@alistair23.me>
Reviewed-by: Jonathan Cameron <jonathan.cameron@oss.qualcomm.com>
---
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
diff --git a/MAINTAINERS b/MAINTAINERS
index cc6a2d0e0d38..424e25710ed2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -24748,6 +24748,18 @@ M: Security Officers <security@kernel.org>
S: Supported
F: Documentation/process/security-bugs.rst
+SECURITY PROTOCOL AND DATA MODEL (SPDM)
+M: Jonathan Cameron <jic23@kernel.org>
+M: Lukas Wunner <lukas@wunner.de>
+M: Alistair Francis <alistair@alistair23.me>
+L: linux-coco@lists.linux.dev
+L: linux-cxl@vger.kernel.org
+L: linux-pci@vger.kernel.org
+S: Maintained
+T: git git://git.kernel.org/pub/scm/linux/kernel/git/devsec/spdm.git
+F: include/linux/spdm.h
+F: lib/rspdm/
+
SECURITY SUBSYSTEM
M: Paul Moore <paul@paul-moore.com>
M: James Morris <jmorris@namei.org>
diff --git a/include/linux/spdm.h b/include/linux/spdm.h
new file mode 100644
index 000000000000..1f7207b584a8
--- /dev/null
+++ b/include/linux/spdm.h
@@ -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 <Jonathan.Cameron@huawei.com>
+ *
+ * Copyright (C) 2022-24 Intel Corporation
+ */
+
+#ifndef _SPDM_H_
+#define _SPDM_H_
+
+#include <linux/types.h>
+
+struct key;
+struct device;
+struct spdm_state;
+struct x509_certificate;
+
+typedef 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
diff --git a/lib/Kconfig b/lib/Kconfig
index 4e6b34c3346d..b84facfa88bc 100644
--- a/lib/Kconfig
+++ b/lib/Kconfig
@@ -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
diff --git a/lib/Makefile b/lib/Makefile
index dfab958327c5..22776b7e9416 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -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/
#
diff --git a/lib/rspdm/Makefile b/lib/rspdm/Makefile
new file mode 100644
index 000000000000..3f5cae33ccb8
--- /dev/null
+++ b/lib/rspdm/Makefile
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Rust implementation of the DMTF Security Protocol and Data Model (SPDM)
+# https://www.dmtf.org/dsp/DSP0274
+#
+# Copyright (C) 2026 Western Digital
+
+obj-$(CONFIG_RSPDM) += spdm.o
+
+spdm-y := lib.o
diff --git a/lib/rspdm/consts.rs b/lib/rspdm/consts.rs
new file mode 100644
index 000000000000..9709f51849d7
--- /dev/null
+++ b/lib/rspdm/consts.rs
@@ -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};
+
+// SPDM versions supported by this implementation
+pub(crate) const SPDM_VER_10: u8 = 0x10;
+
+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 {
+ InvalidRequest = 0x01,
+ /// This was removed in version 1.2.0 and is now reserved
+ InvalidSession = 0x02,
+ Busy = 0x03,
+ UnexpectedRequest = 0x04,
+ Unspecified = 0x05,
+ DecryptError = 0x06,
+ UnsupportedRequest = 0x07,
+ RequestInFlight = 0x08,
+ InvalidResponseCode = 0x09,
+ SessionLimitExceeded = 0x0a,
+ SessionRequired = 0x0b,
+ ResetRequired = 0x0c,
+ ResponseTooLarge = 0x0d,
+ RequestTooLarge = 0x0e,
+ LargeResponse = 0x0f,
+ MessageLost = 0x10,
+ InvalidPolicy = 0x11,
+ VersionMismatch = 0x41,
+ ResponseNotReady = 0x42,
+ RequestResynch = 0x43,
+ OperationFailed = 0x44,
+ NoPendingRequests = 0x45,
+ RequestSessionTerminated = 0x46,
+ InvalidState = 0x47,
+ VendorDefinedError = 0xff,
+}
+
+impl TryFrom<u8> for SpdmErrorCode {
+ type Error = Error;
+
+ fn try_from(value: u8) -> Result<Self, Self::Error> {
+ Ok(match value {
+ 0x01 => Self::InvalidRequest,
+ 0x02 => Self::InvalidSession,
+ 0x03 => Self::Busy,
+ 0x04 => Self::UnexpectedRequest,
+ 0x05 => Self::Unspecified,
+ 0x06 => Self::DecryptError,
+ 0x07 => Self::UnsupportedRequest,
+ 0x08 => Self::RequestInFlight,
+ 0x09 => Self::InvalidResponseCode,
+ 0x0a => Self::SessionLimitExceeded,
+ 0x0b => Self::SessionRequired,
+ 0x0c => Self::ResetRequired,
+ 0x0d => Self::ResponseTooLarge,
+ 0x0e => Self::RequestTooLarge,
+ 0x0f => Self::LargeResponse,
+ 0x10 => Self::MessageLost,
+ 0x11 => Self::InvalidPolicy,
+ 0x41 => Self::VersionMismatch,
+ 0x42 => Self::ResponseNotReady,
+ 0x43 => Self::RequestResynch,
+ 0x44 => Self::OperationFailed,
+ 0x45 => Self::NoPendingRequests,
+ 0x46 => Self::RequestSessionTerminated,
+ 0x47 => Self::InvalidState,
+ 0xff => Self::VendorDefinedError,
+ _ => return Err(EINVAL),
+ })
+ }
+}
+
+impl core::fmt::LowerHex for SpdmErrorCode {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ write!(f, "{:#x}", *self as u8)
+ }
+}
diff --git a/lib/rspdm/lib.rs b/lib/rspdm/lib.rs
new file mode 100644
index 000000000000..1010bd38df6d
--- /dev/null
+++ b/lib/rspdm/lib.rs
@@ -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;
+
+/// 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.
+#[export]
+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(),
+ }
+}
+
+/// spdm_authenticate() - Authenticate device
+///
+/// @spdm_state: SPDM session state
+///
+/// Authenticate a device through a sequence of GET_VERSION, GET_CAPABILITIES,
+/// NEGOTIATE_ALGORITHMS, GET_DIGESTS, GET_CERTIFICATE and CHALLENGE exchanges.
+///
+/// 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 {
+ -(EPROTONOSUPPORT as i32)
+}
+
+/// spdm_destroy() - Destroy SPDM session
+///
+/// @spdm_state: SPDM session state
+#[export]
+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_ptr as *mut c_void) };
+
+ drop(mutex);
+}
diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
new file mode 100644
index 000000000000..c894cf30bb87
--- /dev/null
+++ b/lib/rspdm/state.rs
@@ -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.
+///
+/// `dev`: Responder device. Used for error reporting and passed to @transport.
+/// `transport`: Transport function to perform one message exchange.
+/// `transport_priv`: Transport private data.
+/// `transport_sz`: Maximum message size the transport is capable of (in bytes).
+/// Used as DataTransferSize in GET_CAPABILITIES exchange.
+/// `validate`: Function to validate additional leaf certificate requirements.
+///
+/// `version`: Maximum common supported version of requester and responder.
+/// Negotiated during GET_VERSION exchange.
+#[expect(dead_code)]
+pub(crate) struct SpdmState {
+ pub(crate) dev: *mut bindings::device,
+ pub(crate) transport: bindings::spdm_transport,
+ pub(crate) transport_priv: *mut c_void,
+ pub(crate) transport_sz: u32,
+ pub(crate) validate: bindings::spdm_validate,
+
+ // Negotiated state
+ pub(crate) version: u8,
+}
+
+impl SpdmState {
+ pub(crate) fn new(
+ dev: *mut bindings::device,
+ transport: bindings::spdm_transport,
+ transport_priv: *mut c_void,
+ transport_sz: u32,
+ validate: bindings::spdm_validate,
+ ) -> Self {
+ SpdmState {
+ dev,
+ transport,
+ transport_priv,
+ transport_sz,
+ validate,
+ version: SPDM_MIN_VER,
+ }
+ }
+
+ #[allow(dead_code)]
+ fn spdm_err(&self, rsp: &SpdmErrorRsp) -> Result<(), Error> {
+ match rsp.error_code {
+ SpdmErrorCode::InvalidRequest => {
+ pr_err!("Invalid request\n");
+ Err(EINVAL)
+ }
+ SpdmErrorCode::InvalidSession => {
+ if rsp.version == 0x11 {
+ pr_err!("Invalid session {:#x}\n", rsp.error_data);
+ Err(EINVAL)
+ } else {
+ pr_err!("Undefined error {:#x}\n", rsp.error_code);
+ Err(EINVAL)
+ }
+ }
+ SpdmErrorCode::Busy => {
+ pr_err!("Busy\n");
+ Err(EBUSY)
+ }
+ SpdmErrorCode::UnexpectedRequest => {
+ pr_err!("Unexpected request\n");
+ Err(EINVAL)
+ }
+ SpdmErrorCode::Unspecified => {
+ pr_err!("Unspecified error\n");
+ Err(EINVAL)
+ }
+ SpdmErrorCode::DecryptError => {
+ pr_err!("Decrypt error\n");
+ Err(EIO)
+ }
+ SpdmErrorCode::UnsupportedRequest => {
+ pr_err!("Unsupported request {:#x}\n", rsp.error_data);
+ Err(EINVAL)
+ }
+ SpdmErrorCode::RequestInFlight => {
+ pr_err!("Request in flight\n");
+ Err(EINVAL)
+ }
+ SpdmErrorCode::InvalidResponseCode => {
+ pr_err!("Invalid response code\n");
+ Err(EINVAL)
+ }
+ SpdmErrorCode::SessionLimitExceeded => {
+ pr_err!("Session limit exceeded\n");
+ Err(EBUSY)
+ }
+ SpdmErrorCode::SessionRequired => {
+ pr_err!("Session required\n");
+ Err(EINVAL)
+ }
+ SpdmErrorCode::ResetRequired => {
+ pr_err!("Reset required\n");
+ Err(ECONNRESET)
+ }
+ SpdmErrorCode::ResponseTooLarge => {
+ pr_err!("Response too large\n");
+ Err(EINVAL)
+ }
+ SpdmErrorCode::RequestTooLarge => {
+ pr_err!("Request too large\n");
+ Err(EINVAL)
+ }
+ SpdmErrorCode::LargeResponse => {
+ pr_err!("Large response\n");
+ Err(EMSGSIZE)
+ }
+ SpdmErrorCode::MessageLost => {
+ pr_err!("Message lost\n");
+ Err(EIO)
+ }
+ SpdmErrorCode::InvalidPolicy => {
+ pr_err!("Invalid policy\n");
+ Err(EINVAL)
+ }
+ SpdmErrorCode::VersionMismatch => {
+ pr_err!("Version mismatch\n");
+ Err(EINVAL)
+ }
+ SpdmErrorCode::ResponseNotReady => {
+ pr_err!("Response not ready\n");
+ Err(EINPROGRESS)
+ }
+ SpdmErrorCode::RequestResynch => {
+ pr_err!("Request resynchronization\n");
+ Err(ECONNRESET)
+ }
+ SpdmErrorCode::OperationFailed => {
+ pr_err!("Operation failed\n");
+ Err(EINVAL)
+ }
+ SpdmErrorCode::NoPendingRequests => Err(ENOENT),
+ SpdmErrorCode::VendorDefinedError => {
+ pr_err!("Vendor defined error\n");
+ Err(EINVAL)
+ }
+ SpdmErrorCode::RequestSessionTerminated => {
+ pr_err!("Request session terminated\n");
+ Err(EINVAL)
+ }
+ SpdmErrorCode::InvalidState => {
+ pr_err!("Invalid State\n");
+ Err(EINVAL)
+ }
+ }
+ }
+
+ /// Start a SPDM exchange
+ ///
+ /// The data in `request_buf` is sent to the device and the response is
+ /// stored in `response_buf`.
+ #[allow(dead_code)]
+ pub(crate) fn spdm_exchange(
+ &self,
+ request_buf: &mut [u8],
+ response_buf: &mut [u8],
+ ) -> Result<i32, Error> {
+ let header_size = core::mem::size_of::<SpdmHeader>();
+ let request: SpdmHeader = Untrusted::new(&request_buf[..]).validate(&*self)?;
+
+ let transport_function = self.transport.ok_or(EINVAL)?;
+ // SAFETY: `transport_function` is provided by the new(), we are
+ // calling the function.
+ // We have a immutable reference to request_buf above, and pass
+ // another reference here.
+ // We don't have any references to the mutable response_buf
+ let length = unsafe {
+ transport_function(
+ self.transport_priv,
+ self.dev,
+ request_buf.as_ptr() as *const c_void,
+ request_buf.len(),
+ response_buf.as_mut_ptr() as *mut c_void,
+ response_buf.len(),
+ ) as i32
+ };
+ to_result(length)?;
+
+ if (length as usize) < header_size {
+ return Ok(length); // Truncated response is handled by callers
+ }
+
+ let response: SpdmHeader = Untrusted::new(&response_buf[..]).validate(&*self)?;
+
+ if response.code == SPDM_ERROR {
+ let error_rsp: SpdmErrorRsp =
+ Untrusted::new(&response_buf[..header_size as usize]).validate(&*self)?;
+ self.spdm_err(&error_rsp)?;
+ }
+
+ if response.code != request.code & !SPDM_REQ {
+ pr_err!(
+ "Response code {:#x} does not match request code {:#x}\n",
+ response.code,
+ request.code
+ );
+ return Err(EPROTO);
+ }
+
+ Ok(length)
+ }
+}
diff --git a/lib/rspdm/validator.rs b/lib/rspdm/validator.rs
new file mode 100644
index 000000000000..b14c066e6a51
--- /dev/null
+++ b/lib/rspdm/validator.rs
@@ -0,0 +1,93 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Copyright (C) 2026 Western Digital
+
+//! Related structs and their Validate implementations.
+//!
+//! Rust implementation of the DMTF Security Protocol and Data Model (SPDM)
+//! <https://www.dmtf.org/dsp/DSP0274>
+
+use crate::{
+ consts::SpdmErrorCode,
+ SpdmState, //
+};
+use kernel::prelude::*;
+use kernel::{
+ error::Error,
+ validate::{
+ Untrusted,
+ Validate, //
+ },
+};
+
+#[repr(C, packed)]
+pub(crate) struct SpdmHeader {
+ pub(crate) version: u8,
+ pub(crate) code: u8, /* RequestResponseCode */
+ pub(crate) param1: u8,
+ pub(crate) param2: u8,
+}
+
+impl SpdmHeader {
+ #[expect(dead_code)]
+ pub(crate) fn new(code: u8) -> Self {
+ SpdmHeader {
+ version: 0,
+ code,
+ param1: 0,
+ param2: 0,
+ }
+ }
+
+ #[expect(dead_code)]
+ pub(crate) fn to_bytes(&self) -> Result<KVec<u8>> {
+ let mut out = KVec::new();
+
+ out.extend_from_slice(
+ &[self.version, self.code, self.param1, self.param2],
+ GFP_KERNEL,
+ )?;
+
+ Ok(out)
+ }
+}
+
+impl<'a, 'c> Validate<'c, Untrusted<&'a [u8]>> for SpdmHeader {
+ type Err = Error;
+
+ type Context = &'c SpdmState;
+
+ fn validate(unvalidated: &[u8], _context: &'c SpdmState) -> 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)?,
+ })
+ }
+}
+
+#[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 = &'c SpdmState;
+
+ fn validate(unvalidated: &[u8], _context: &'c SpdmState) -> 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)?,
+ })
+ }
+}
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index fd223b6c5aaf..d2781c27794b 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -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>
--
2.55.0
^ permalink raw reply related [flat|nested] 45+ messages in thread
* [PATCH v4 11/22] PCI/TSM: Rename pf0 to host
2026-09-28 1:11 [PATCH v4 00/22] lib: Rust implementation of SPDM alistair23
` (9 preceding siblings ...)
2026-09-28 1:11 ` [PATCH v4 10/22] lib: rspdm: Initial commit of Rust SPDM alistair23
@ 2026-09-28 1:11 ` alistair23
2026-09-28 1:41 ` sashiko-bot
2026-09-28 1:11 ` [PATCH v4 12/22] PCI/TSM: Support connecting to PCIe CMA devices alistair23
` (7 subsequent siblings)
18 siblings, 1 reply; 45+ messages in thread
From: alistair23 @ 2026-09-28 1:11 UTC (permalink / raw)
To: lukas, linux-cxl, akpm, Jonathan.Cameron, rust-for-linux,
linux-kernel, jic23, bhelgaas, alistair, djbw, linux-pci
Cc: wilfred.mallawa, a.hindborg, gregkh, alex.gaynor, alistair23,
aliceryhl, benno.lossin, boqun.feng, bjorn3_gh, tmgross, gary,
ojeda, Alistair Francis
From: Alistair Francis <alistair.francis@wdc.com>
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.
Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
---
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(-)
diff --git a/drivers/crypto/ccp/sev-dev-tio.h b/drivers/crypto/ccp/sev-dev-tio.h
index 67512b3dbc53..78d598686487 100644
--- a/drivers/crypto/ccp/sev-dev-tio.h
+++ b/drivers/crypto/ccp/sev-dev-tio.h
@@ -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;
};
diff --git a/drivers/crypto/ccp/sev-dev-tsm.c b/drivers/crypto/ccp/sev-dev-tsm.c
index 48df8c579c74..1d2561628f43 100644
--- a/drivers/crypto/ccp/sev-dev-tsm.c
+++ b/drivers/crypto/ccp/sev-dev-tsm.c
@@ -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 *se
if (!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);
}
}
diff --git a/drivers/pci/tsm.c b/drivers/pci/tsm.c
index 5fdcd7f2e820..10c9c6696624 100644
--- a/drivers/pci/tsm.c
+++ b/drivers/pci/tsm.c
@@ -45,22 +45,22 @@ static inline bool has_tee(struct pci_dev *pdev)
return pdev->devcap & PCI_EXP_DEVCAP_TEE;
}
-/* 'struct pci_tsm_pf0' wraps 'struct pci_tsm' when ->dsm_dev == ->pdev (self) */
-static struct pci_tsm_pf0 *to_pci_tsm_pf0(struct pci_tsm *tsm)
+/* 'struct pci_tsm_host' wraps 'struct pci_tsm' when ->dsm_dev == ->pdev (self) */
+static struct pci_tsm_host *to_pci_tsm_host(struct pci_tsm *tsm)
{
/*
* All "link" TSM contexts reference the device that hosts the DSM
* interface for a set of devices. Walk to the DSM device and cast its
- * ->tsm context to a 'struct pci_tsm_pf0 *'.
+ * ->tsm context to a 'struct pci_tsm_host *'.
*/
- struct pci_dev *pf0 = tsm->dsm_dev;
+ struct pci_dev *host = tsm->dsm_dev;
- if (!is_pci_tsm_pf0(pf0) || !is_dsm(pf0)) {
+ if (!is_pci_tsm_host(host) || !is_dsm(host)) {
pci_WARN_ONCE(tsm->pdev, 1, "invalid context object\n");
return NULL;
}
- return container_of(pf0->tsm, struct pci_tsm_pf0, base_tsm);
+ return container_of(host->tsm, struct pci_tsm_host, base_tsm);
}
static void tsm_remove(struct pci_tsm *tsm)
@@ -186,7 +186,7 @@ static int probe_fn(struct pci_dev *pdev, void *dsm)
static int pci_tsm_connect(struct pci_dev *pdev, struct tsm_dev *tsm_dev)
{
int rc;
- struct pci_tsm_pf0 *tsm_pf0;
+ struct pci_tsm_host *tsm_host;
const struct pci_tsm_ops *ops = tsm_dev->pci_ops;
struct pci_tsm *pci_tsm __free(tsm_remove) = ops->probe(tsm_dev, pdev);
@@ -197,10 +197,10 @@ static int pci_tsm_connect(struct pci_dev *pdev, struct tsm_dev *tsm_dev)
return -ENXIO;
pdev->tsm = pci_tsm;
- tsm_pf0 = to_pci_tsm_pf0(pdev->tsm);
+ tsm_host = to_pci_tsm_host(pdev->tsm);
/* mutex_intr assumes connect() is always sysfs/user driven */
- ACQUIRE(mutex_intr, lock)(&tsm_pf0->lock);
+ ACQUIRE(mutex_intr, lock)(&tsm_host->lock);
if ((rc = ACQUIRE_ERR(mutex_intr, &lock)))
return rc;
@@ -300,15 +300,15 @@ static int remove_fn(struct pci_dev *pdev, void *data)
static int __pci_tsm_unbind(struct pci_dev *pdev, void *data)
{
struct pci_tdi *tdi;
- struct pci_tsm_pf0 *tsm_pf0;
+ struct pci_tsm_host *tsm_host;
lockdep_assert_held(&pci_tsm_rwsem);
if (!pdev->tsm)
return 0;
- tsm_pf0 = to_pci_tsm_pf0(pdev->tsm);
- guard(mutex)(&tsm_pf0->lock);
+ tsm_host = to_pci_tsm_host(pdev->tsm);
+ guard(mutex)(&tsm_host->lock);
tdi = pdev->tsm->tdi;
if (!tdi)
@@ -341,7 +341,7 @@ EXPORT_SYMBOL_GPL(pci_tsm_unbind);
*/
int pci_tsm_bind(struct pci_dev *pdev, struct kvm *kvm, u32 tdi_id)
{
- struct pci_tsm_pf0 *tsm_pf0;
+ struct pci_tsm_host *tsm_host;
struct pci_tdi *tdi;
if (!kvm)
@@ -355,8 +355,8 @@ int pci_tsm_bind(struct pci_dev *pdev, struct kvm *kvm, u32 tdi_id)
if (!is_link_tsm(pdev->tsm->tsm_dev))
return -ENXIO;
- tsm_pf0 = to_pci_tsm_pf0(pdev->tsm);
- guard(mutex)(&tsm_pf0->lock);
+ tsm_host = to_pci_tsm_host(pdev->tsm);
+ guard(mutex)(&tsm_host->lock);
/* Resolve races to bind a TDI */
if (pdev->tsm->tdi) {
@@ -404,7 +404,7 @@ ssize_t pci_tsm_guest_req(struct pci_dev *pdev, enum pci_tsm_req_scope scope,
sockptr_t req_in, size_t in_len, sockptr_t req_out,
size_t out_len, u64 *tsm_code)
{
- struct pci_tsm_pf0 *tsm_pf0;
+ struct pci_tsm_host *tsm_host;
struct pci_tdi *tdi;
int rc;
@@ -422,8 +422,8 @@ ssize_t pci_tsm_guest_req(struct pci_dev *pdev, enum pci_tsm_req_scope scope,
if (!is_link_tsm(pdev->tsm->tsm_dev))
return -ENXIO;
- tsm_pf0 = to_pci_tsm_pf0(pdev->tsm);
- ACQUIRE(mutex_intr, ops_lock)(&tsm_pf0->lock);
+ tsm_host = to_pci_tsm_host(pdev->tsm);
+ ACQUIRE(mutex_intr, ops_lock)(&tsm_host->lock);
if ((rc = ACQUIRE_ERR(mutex_intr, &ops_lock)))
return rc;
@@ -443,7 +443,7 @@ static void pci_tsm_unbind_all(struct pci_dev *pdev)
static void __pci_tsm_disconnect(struct pci_dev *pdev)
{
- struct pci_tsm_pf0 *tsm_pf0 = to_pci_tsm_pf0(pdev->tsm);
+ struct pci_tsm_host *tsm_host = to_pci_tsm_host(pdev->tsm);
const struct pci_tsm_ops *ops = to_pci_tsm_ops(pdev->tsm);
/* disconnect() mutually exclusive with subfunction pci_tsm_init() */
@@ -455,7 +455,7 @@ static void __pci_tsm_disconnect(struct pci_dev *pdev)
* disconnect() is uninterruptible as it may be called for device
* teardown
*/
- guard(mutex)(&tsm_pf0->lock);
+ guard(mutex)(&tsm_host->lock);
pci_tsm_walk_fns_reverse(pdev, remove_fn, NULL);
ops->disconnect(pdev);
}
@@ -494,7 +494,7 @@ static ssize_t bound_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct pci_dev *pdev = to_pci_dev(dev);
- struct pci_tsm_pf0 *tsm_pf0;
+ struct pci_tsm_host *tsm_host;
struct pci_tsm *tsm;
int rc;
@@ -505,9 +505,9 @@ static ssize_t bound_show(struct device *dev,
tsm = pdev->tsm;
if (!tsm)
return sysfs_emit(buf, "\n");
- tsm_pf0 = to_pci_tsm_pf0(tsm);
+ tsm_host = to_pci_tsm_host(tsm);
- ACQUIRE(mutex_intr, ops_lock)(&tsm_pf0->lock);
+ ACQUIRE(mutex_intr, ops_lock)(&tsm_host->lock);
if ((rc = ACQUIRE_ERR(mutex_intr, &ops_lock)))
return rc;
@@ -547,7 +547,7 @@ static bool pci_tsm_link_group_visible(struct kobject *kobj)
if (!pci_is_pcie(pdev))
return false;
- if (is_pci_tsm_pf0(pdev))
+ if (is_pci_tsm_host(pdev))
return true;
/*
@@ -572,14 +572,14 @@ static umode_t pci_tsm_attr_visible(struct kobject *kobj,
struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj));
if (attr == &dev_attr_bound.attr) {
- if (is_pci_tsm_pf0(pdev) && has_tee(pdev))
+ if (is_pci_tsm_host(pdev) && has_tee(pdev))
return attr->mode;
if (pdev->tsm && has_tee(pdev->tsm->dsm_dev))
return attr->mode;
}
if (attr == &dev_attr_dsm.attr) {
- if (is_pci_tsm_pf0(pdev))
+ if (is_pci_tsm_host(pdev))
return attr->mode;
if (pdev->tsm && has_tee(pdev->tsm->dsm_dev))
return attr->mode;
@@ -587,7 +587,7 @@ static umode_t pci_tsm_attr_visible(struct kobject *kobj,
if (attr == &dev_attr_connect.attr ||
attr == &dev_attr_disconnect.attr) {
- if (is_pci_tsm_pf0(pdev))
+ if (is_pci_tsm_host(pdev))
return attr->mode;
}
}
@@ -662,7 +662,7 @@ static struct pci_dev *find_dsm_dev(struct pci_dev *pdev)
struct device *grandparent;
struct pci_dev *uport;
- if (is_pci_tsm_pf0(pdev))
+ if (is_pci_tsm_host(pdev))
return pdev;
struct pci_dev *pf0 __free(pci_dev_put) = pf0_dev_get(pdev);
@@ -735,13 +735,13 @@ int pci_tsm_link_constructor(struct pci_dev *pdev, struct pci_tsm *tsm,
EXPORT_SYMBOL_GPL(pci_tsm_link_constructor);
/**
- * pci_tsm_pf0_constructor() - common 'struct pci_tsm_pf0' (DSM) initialization
- * @pdev: Physical Function 0 PCI device (as indicated by is_pci_tsm_pf0())
+ * pci_tsm_host_constructor() - common 'struct pci_tsm_host' (DSM) initialization
+ * @pdev: TSM host PCI device (as indicated by is_pci_tsm_host())
* @tsm: context to initialize
* @tsm_dev: Platform TEE Security Manager, initiator of security operations
*/
-int pci_tsm_pf0_constructor(struct pci_dev *pdev, struct pci_tsm_pf0 *tsm,
- struct tsm_dev *tsm_dev)
+int pci_tsm_host_constructor(struct pci_dev *pdev, struct pci_tsm_host *tsm,
+ struct tsm_dev *tsm_dev)
{
mutex_init(&tsm->lock);
tsm->doe_mb = pci_find_doe_mailbox(pdev, PCI_VENDOR_ID_PCI_SIG,
@@ -753,13 +753,13 @@ int pci_tsm_pf0_constructor(struct pci_dev *pdev, struct pci_tsm_pf0 *tsm,
return pci_tsm_link_constructor(pdev, &tsm->base_tsm, tsm_dev);
}
-EXPORT_SYMBOL_GPL(pci_tsm_pf0_constructor);
+EXPORT_SYMBOL_GPL(pci_tsm_host_constructor);
-void pci_tsm_pf0_destructor(struct pci_tsm_pf0 *pf0_tsm)
+void pci_tsm_host_destructor(struct pci_tsm_host *host_tsm)
{
- mutex_destroy(&pf0_tsm->lock);
+ mutex_destroy(&host_tsm->lock);
}
-EXPORT_SYMBOL_GPL(pci_tsm_pf0_destructor);
+EXPORT_SYMBOL_GPL(pci_tsm_host_destructor);
int pci_tsm_register(struct tsm_dev *tsm_dev)
{
@@ -780,7 +780,7 @@ int pci_tsm_register(struct tsm_dev *tsm_dev)
/* On first enable, update sysfs groups */
if (is_link_tsm(tsm_dev) && pci_tsm_link_count++ == 0) {
for_each_pci_dev(pdev)
- if (is_pci_tsm_pf0(pdev))
+ if (is_pci_tsm_host(pdev))
link_sysfs_enable(pdev);
} else if (is_devsec_tsm(tsm_dev)) {
pci_tsm_devsec_count++;
@@ -815,7 +815,7 @@ static void __pci_tsm_destroy(struct pci_dev *pdev, struct tsm_dev *tsm_dev)
* skipped if the device itself is being removed since sysfs goes away
* naturally at that point
*/
- if (is_link_tsm(tsm_dev) && is_pci_tsm_pf0(pdev) && !pci_tsm_link_count)
+ if (is_link_tsm(tsm_dev) && is_pci_tsm_host(pdev) && !pci_tsm_link_count)
link_sysfs_disable(pdev);
/* Nothing else to do if this device never attached to the departing TSM */
@@ -828,7 +828,7 @@ static void __pci_tsm_destroy(struct pci_dev *pdev, struct tsm_dev *tsm_dev)
else if (tsm_dev != tsm->tsm_dev)
return;
- if (is_link_tsm(tsm_dev) && is_pci_tsm_pf0(pdev))
+ if (is_link_tsm(tsm_dev) && is_pci_tsm_host(pdev))
pci_tsm_disconnect(pdev);
else
pci_tsm_fn_exit(pdev);
@@ -885,12 +885,12 @@ void pci_tsm_unregister(struct tsm_dev *tsm_dev)
int pci_tsm_doe_transfer(struct pci_dev *pdev, u8 type, const void *req,
size_t req_sz, void *resp, size_t resp_sz)
{
- struct pci_tsm_pf0 *tsm;
+ struct pci_tsm_host *tsm;
- if (!pdev->tsm || !is_pci_tsm_pf0(pdev))
+ if (!pdev->tsm || !is_pci_tsm_host(pdev))
return -ENXIO;
- tsm = to_pci_tsm_pf0(pdev->tsm);
+ tsm = to_pci_tsm_host(pdev->tsm);
if (!tsm->doe_mb)
return -ENXIO;
diff --git a/include/linux/pci-tsm.h b/include/linux/pci-tsm.h
index a6435aba03f9..950e2c36a4ca 100644
--- a/include/linux/pci-tsm.h
+++ b/include/linux/pci-tsm.h
@@ -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);
--
2.55.0
^ permalink raw reply related [flat|nested] 45+ messages in thread
* [PATCH v4 12/22] PCI/TSM: Support connecting to PCIe CMA devices
2026-09-28 1:11 [PATCH v4 00/22] lib: Rust implementation of SPDM alistair23
` (10 preceding siblings ...)
2026-09-28 1:11 ` [PATCH v4 11/22] PCI/TSM: Rename pf0 to host alistair23
@ 2026-09-28 1:11 ` alistair23
2026-09-28 1:43 ` sashiko-bot
2026-09-28 1:11 ` [PATCH v4 13/22] PCI/CMA: Add a PCI TSM CMA driver using SPDM alistair23
` (6 subsequent siblings)
18 siblings, 1 reply; 45+ messages in thread
From: alistair23 @ 2026-09-28 1:11 UTC (permalink / raw)
To: lukas, linux-cxl, akpm, Jonathan.Cameron, rust-for-linux,
linux-kernel, jic23, bhelgaas, alistair, djbw, linux-pci
Cc: wilfred.mallawa, a.hindborg, gregkh, alex.gaynor, alistair23,
aliceryhl, benno.lossin, boqun.feng, bjorn3_gh, tmgross, gary,
ojeda, Alistair Francis
From: Alistair Francis <alistair.francis@wdc.com>
In the next patch we are going to add a PCIe CMA TSM driver, as such we
need to ensure that is_pci_tsm_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.
Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
---
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(-)
diff --git a/drivers/crypto/ccp/sev-dev-tsm.c b/drivers/crypto/ccp/sev-dev-tsm.c
index 1d2561628f43..22f2855f397b 100644
--- a/drivers/crypto/ccp/sev-dev-tsm.c
+++ b/drivers/crypto/ccp/sev-dev-tsm.c
@@ -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);
diff --git a/drivers/pci/doe.c b/drivers/pci/doe.c
index ac95b1d2d999..6a59969bd52f 100644
--- a/drivers/pci/doe.c
+++ b/drivers/pci/doe.c
@@ -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)
diff --git a/drivers/pci/tsm.c b/drivers/pci/tsm.c
index 10c9c6696624..03a84630bd69 100644
--- a/drivers/pci/tsm.c
+++ b/drivers/pci/tsm.c
@@ -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);
}
diff --git a/include/linux/pci-tsm.h b/include/linux/pci-tsm.h
index 950e2c36a4ca..ddaec2168c7e 100644
--- a/include/linux/pci-tsm.h
+++ b/include/linux/pci-tsm.h
@@ -3,6 +3,7 @@
#define __PCI_TSM_H
#include <linux/mutex.h>
#include <linux/pci.h>
+#include <linux/pci-doe.h>
#include <linux/sockptr.h>
struct pci_tsm;
@@ -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
*
diff --git a/include/linux/pci.h b/include/linux/pci.h
index d31a8d107b1e..cb13b40952b4 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -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 */
--
2.55.0
^ permalink raw reply related [flat|nested] 45+ messages in thread
* [PATCH v4 13/22] PCI/CMA: Add a PCI TSM CMA driver using SPDM
2026-09-28 1:11 [PATCH v4 00/22] lib: Rust implementation of SPDM alistair23
` (11 preceding siblings ...)
2026-09-28 1:11 ` [PATCH v4 12/22] PCI/TSM: Support connecting to PCIe CMA devices alistair23
@ 2026-09-28 1:11 ` alistair23
2026-09-28 1:43 ` sashiko-bot
2026-09-28 1:11 ` [PATCH v4 14/22] PCI/CMA: Validate Subject Alternative Name in certificates alistair23
` (5 subsequent siblings)
18 siblings, 1 reply; 45+ messages in thread
From: alistair23 @ 2026-09-28 1:11 UTC (permalink / raw)
To: lukas, linux-cxl, akpm, Jonathan.Cameron, rust-for-linux,
linux-kernel, jic23, bhelgaas, alistair, djbw, linux-pci
Cc: wilfred.mallawa, a.hindborg, gregkh, alex.gaynor, alistair23,
aliceryhl, benno.lossin, boqun.feng, bjorn3_gh, tmgross, gary,
ojeda, Alistair Francis
From: Alistair Francis <alistair.francis@wdc.com>
Component Measurement and Authentication (CMA, PCIe r6.2 sec 6.31)
allows for measurement and authentication of PCIe devices. It is
based on the Security Protocol and Data Model specification (SPDM,
https://www.dmtf.org/dsp/DSP0274).
CMA-SPDM in turn forms the basis for Integrity and Data Encryption
(IDE, PCIe r6.2 sec 6.33) because the key material used by IDE is
transmitted over a CMA-SPDM session.
As a first step, add support for authentication via a CMA TSM driver.
This was previously discusd here:
http://lore.kernel.org/69976d7d39c60_2f4a1009@dwillia2-mobl4.notmuch
By utilising a TSM driver we get a lot of the TSM driver probe policies
"for free". Currently there is no mechanism to provide evidence to
userspace, as the TSM system doesn't support that at the moment. That
can be added later when support by TSM.
Credits: Jonathan wrote the original proof-of-concept for a CMA implementation.
Lukas reworked that for upstream. Wilfred contributed fixes for issues
discovered during testing. Alistair reworked it as a TSM driver.
Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
Co-developed-by: Wilfred Mallawa <wilfred.mallawa@wdc.com>
Signed-off-by: Wilfred Mallawa <wilfred.mallawa@wdc.com>
Co-developed-by: Lukas Wunner <lukas@wunner.de>
Signed-off-by: Lukas Wunner <lukas@wunner.de>
Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
---
MAINTAINERS | 1 +
drivers/pci/Kconfig | 14 ++++
drivers/pci/Makefile | 2 +
drivers/pci/cma.c | 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
diff --git a/MAINTAINERS b/MAINTAINERS
index 424e25710ed2..c52dab8c6044 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -24757,6 +24757,7 @@ L: linux-cxl@vger.kernel.org
L: linux-pci@vger.kernel.org
S: Maintained
T: git git://git.kernel.org/pub/scm/linux/kernel/git/devsec/spdm.git
+F: drivers/pci/cma.c
F: include/linux/spdm.h
F: lib/rspdm/
diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig
index 0c7408509ba2..0862ef69aa3f 100644
--- a/drivers/pci/Kconfig
+++ b/drivers/pci/Kconfig
@@ -124,6 +124,20 @@ config PCI_ATS
config PCI_IDE
bool
+config PCI_CMA
+ bool "Component Measurement and Authentication (CMA-SPDM)"
+ 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
diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile
index 41ebc3b9a518..16abfd0e17e1 100644
--- a/drivers/pci/Makefile
+++ b/drivers/pci/Makefile
@@ -41,6 +41,8 @@ obj-$(CONFIG_PCI_NPEM) += npem.o
obj-$(CONFIG_PCIE_TPH) += tph.o
obj-$(CONFIG_CARDBUS) += setup-cardbus.o
+obj-$(CONFIG_PCI_CMA) += cma.o
+
# Endpoint library must be initialized before its users
obj-$(CONFIG_PCI_ENDPOINT) += endpoint/
diff --git a/drivers/pci/cma.c b/drivers/pci/cma.c
new file mode 100644
index 000000000000..6b52b129574e
--- /dev/null
+++ b/drivers/pci/cma.c
@@ -0,0 +1,150 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Component Measurement and Authentication (CMA-SPDM, PCIe r6.2 sec 6.31)
+ *
+ * Copyright (C) 2021 Huawei
+ * Jonathan Cameron <Jonathan.Cameron@huawei.com>
+ * Copyright (C) 2022-24 Intel Corporation
+ * Copyright (C) 2026 Western Digital
+ * Alistair Francis <alistair.francis@wdc.com>
+ */
+
+#define dev_fmt(fmt) "CMA: " fmt
+
+#include <linux/err.h>
+#include <linux/pci.h>
+#include <linux/pci-doe.h>
+#include <linux/pci-tsm.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+#include <linux/spdm.h>
+#include <linux/tsm.h>
+
+#include "pci.h"
+
+static int pci_doe_transport(void *priv, struct device *dev,
+ const void *request, size_t request_sz,
+ void *response, size_t response_sz)
+{
+ struct pci_doe_mb *doe = priv;
+
+ return pci_doe(doe, PCI_VENDOR_ID_PCI_SIG, PCI_DOE_FEATURE_CMA,
+ request, request_sz, response, response_sz);
+}
+
+struct pci_cma_tsm {
+ struct pci_tsm_host host;
+ struct spdm_state *spdm;
+};
+
+static struct pci_cma_tsm *cma_tsm_from_tsm(struct pci_tsm *tsm)
+{
+ struct pci_tsm_host *host = container_of(tsm, struct pci_tsm_host, base_tsm);
+
+ return container_of(host, struct pci_cma_tsm, host);
+}
+
+static struct pci_tsm *pci_cma_tsm_probe(struct tsm_dev *tsm_dev,
+ struct pci_dev *pdev)
+{
+ struct pci_cma_tsm *cma;
+ int rc;
+
+ if (!pdev->doe_cma)
+ return NULL;
+
+ cma = kzalloc(sizeof(*cma), GFP_KERNEL);
+ if (!cma)
+ return NULL;
+
+ rc = pci_tsm_host_constructor(pdev, &cma->host, tsm_dev);
+ if (rc) {
+ kfree(cma);
+ return NULL;
+ }
+
+ cma->spdm = spdm_create(&pdev->dev, pci_doe_transport, cma->host.doe_mb,
+ PCI_DOE_MAX_PAYLOAD, NULL);
+ if (!cma->spdm) {
+ pci_tsm_host_destructor(&cma->host);
+ kfree(cma);
+ return NULL;
+ }
+
+ return &cma->host.base_tsm;
+}
+
+static void pci_cma_tsm_remove(struct pci_tsm *tsm)
+{
+ struct pci_cma_tsm *cma = cma_tsm_from_tsm(tsm);
+
+ spdm_destroy(cma->spdm);
+ pci_tsm_host_destructor(&cma->host);
+ kfree(cma);
+}
+
+static int pci_cma_tsm_connect(struct pci_dev *pdev)
+{
+ struct pci_cma_tsm *cma = cma_tsm_from_tsm(pdev->tsm);
+
+ /*
+ * The DOE mailbox lives in the device's config space, so the device
+ * must be runtime-resumed for the duration of the SPDM exchange.
+ */
+ PM_RUNTIME_ACQUIRE(&pdev->dev, pm);
+ if (PM_RUNTIME_ACQUIRE_ERR(&pm))
+ return -ENXIO;
+
+ return spdm_authenticate(cma->spdm);
+}
+
+static void pci_cma_tsm_disconnect(struct pci_dev *pdev)
+{
+ /* SPDM state is freed in pci_cma_tsm_remove() */
+}
+
+static struct pci_tdi *pci_cma_tsm_bind(struct pci_dev *pdev,
+ struct kvm *kvm, u32 tdi_id)
+{
+ return ERR_PTR(-EOPNOTSUPP);
+}
+
+static void pci_cma_tsm_unbind(struct pci_tdi *tdi)
+{
+}
+
+static ssize_t pci_cma_tsm_guest_req(struct pci_tdi *tdi,
+ enum pci_tsm_req_scope scope,
+ sockptr_t req_in, size_t in_len,
+ sockptr_t req_out, size_t out_len,
+ u64 *tsm_code)
+{
+ return -EOPNOTSUPP;
+}
+
+static const struct pci_tsm_ops pci_cma_tsm_ops = {
+ .link_ops = {
+ .probe = pci_cma_tsm_probe,
+ .remove = pci_cma_tsm_remove,
+ .connect = pci_cma_tsm_connect,
+ .disconnect = pci_cma_tsm_disconnect,
+ .bind = pci_cma_tsm_bind,
+ .unbind = pci_cma_tsm_unbind,
+ .guest_req = pci_cma_tsm_guest_req,
+ },
+};
+
+static struct tsm_dev *pci_cma_tsm_dev;
+
+static int __init pci_cma_tsm_init(void)
+{
+ struct tsm_dev *tsm_dev;
+
+ tsm_dev = tsm_register(NULL, &pci_cma_tsm_ops);
+ if (IS_ERR(tsm_dev))
+ return PTR_ERR(tsm_dev);
+
+ pci_cma_tsm_dev = tsm_dev;
+ return 0;
+}
+late_initcall(pci_cma_tsm_init);
diff --git a/drivers/pci/doe.c b/drivers/pci/doe.c
index 6a59969bd52f..1b3d6e74fbe8 100644
--- a/drivers/pci/doe.c
+++ b/drivers/pci/doe.c
@@ -31,9 +31,6 @@
#define PCI_DOE_FLAG_CANCEL 0
#define PCI_DOE_FLAG_DEAD 1
-/* Max data object length is 2^18 dwords */
-#define PCI_DOE_MAX_LENGTH (1 << 18)
-
/**
* struct pci_doe_mb - State for a single DOE mailbox
*
diff --git a/include/linux/pci-doe.h b/include/linux/pci-doe.h
index bd4346a7c4e7..7540396336de 100644
--- a/include/linux/pci-doe.h
+++ b/include/linux/pci-doe.h
@@ -19,6 +19,10 @@ struct pci_doe_mb;
#define PCI_DOE_FEATURE_CMA 1
#define PCI_DOE_FEATURE_SSESSION 2
+/* Max data object length is 2^18 dwords (including 2 dwords for header) */
+#define PCI_DOE_MAX_LENGTH (1 << 18)
+#define PCI_DOE_MAX_PAYLOAD ((PCI_DOE_MAX_LENGTH - 2) * sizeof(u32))
+
struct pci_doe_mb *pci_find_doe_mailbox(struct pci_dev *pdev, u16 vendor,
u8 type);
--
2.55.0
^ permalink raw reply related [flat|nested] 45+ messages in thread
* [PATCH v4 14/22] PCI/CMA: Validate Subject Alternative Name in certificates
2026-09-28 1:11 [PATCH v4 00/22] lib: Rust implementation of SPDM alistair23
` (12 preceding siblings ...)
2026-09-28 1:11 ` [PATCH v4 13/22] PCI/CMA: Add a PCI TSM CMA driver using SPDM alistair23
@ 2026-09-28 1:11 ` alistair23
2026-09-28 1:44 ` sashiko-bot
2026-09-28 1:11 ` [PATCH v4 15/22] lib: rspdm: Support SPDM get_version alistair23
` (4 subsequent siblings)
18 siblings, 1 reply; 45+ messages in thread
From: alistair23 @ 2026-09-28 1:11 UTC (permalink / raw)
To: lukas, linux-cxl, akpm, Jonathan.Cameron, rust-for-linux,
linux-kernel, jic23, bhelgaas, alistair, djbw, linux-pci
Cc: wilfred.mallawa, a.hindborg, gregkh, alex.gaynor, alistair23,
aliceryhl, benno.lossin, boqun.feng, bjorn3_gh, tmgross, gary,
ojeda, Alistair Francis
From: Lukas Wunner <lukas@wunner.de>
PCIe r6.1 sec 6.31.3 stipulates requirements for Leaf Certificates
presented by devices, in particular the presence of a Subject Alternative
Name which encodes the Vendor ID, Device ID, Device Serial Number, etc.
This prevents a mismatch between the device identity in Config Space and
the certificate. A device cannot misappropriate a certificate from a
different device without also spoofing Config Space. As a corollary,
it cannot dupe an arbitrary driver into binding to it. Only drivers
which bind to the device identity in the Subject Alternative Name work
(PCIe r6.1 sec 6.31 "Implementation Note: Overview of Threat Model").
The Subject Alternative Name is signed, hence constitutes a signed copy
of a Config Space portion. It's the same concept as web certificates
which contain a set of domain names in the Subject Alternative Name for
identity verification.
Parse the Subject Alternative Name using a small ASN.1 module and
validate its contents. The theory of operation is explained in a
comment at the top of the newly inserted code.
This functionality is introduced in a separate commit on top of basic
CMA-SPDM support to split the code into digestible, reviewable chunks.
The CMA OID added here is taken from the official OID Repository
(it's not documented in the PCIe Base Spec):
https://oid-rep.orange-labs.fr/get/2.23.147
Side notes:
* PCIe r6.2 removes the spec language on the Subject Alternative Name.
It still "requires the leaf certificate to include the information
typically used by system software for device driver binding", but no
longer specifies how that information is encoded into the certificate.
According to the editor of the PCIe Base Spec and the author of the
CMA 1.1 ECN (which caused this change), FPGA cards which mutate their
device identity at runtime (due to a firmware update) were thought as
unable to satisfy the previous spec language. The Protocol Working
Group could not agree on a better solution and therefore dropped the
spec language entirely. They acknowledge that the requirement is now
under-spec'd. Because products already exist which adhere to the
Subject Alternative Name requirement per PCIe r6.1 sec 6.31.3, they
recommended to "push through" and use it as the de facto standard.
The FPGA concerns are easily overcome by reauthenticating the device
after a firmware update, either via sysfs or pci_cma_reauthenticate()
(added by a subsequent commit).
* PCIe r6.1 sec 6.31.3 strongly recommends to verify that "the
information provided in the Subject Alternative Name entry is signed
by the vendor indicated by the Vendor ID." In other words, the root
certificate on pci_cma_keyring which signs the device's certificate
chain must have been created for a particular Vendor ID.
Unfortunately the spec neglects to define how the Vendor ID shall be
encoded into the root certificate. So the recommendation cannot be
implemented at this point and it is thus possible that a vendor signs
device certificates of a different vendor.
* Instead of a Subject Alternative Name, Leaf Certificates may include
"a Reference Integrity Manifest, e.g., see Trusted Computing Group" or
"a pointer to a location where such a Reference Integrity Manifest can
be obtained" (PCIe r6.1 sec 6.31.3).
A Reference Integrity Manifest contains "golden" measurements which
can be compared to actual measurements retrieved from a device.
It serves a different purpose than the Subject Alternative Name,
hence it is unclear why the spec says only either of them is necessary.
It is also unclear how a Reference Integrity Manifest shall be encoded
into a certificate.
Hence ignore the Reference Integrity Manifest requirement.
Signed-off-by: Lukas Wunner <lukas@wunner.de>
Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com> # except ASN.1
[ Changed by AF:
- Fixup a few issues caught by Sashiko
]
Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
---
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
diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile
index 16abfd0e17e1..882cbb108364 100644
--- a/drivers/pci/Makefile
+++ b/drivers/pci/Makefile
@@ -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/
diff --git a/drivers/pci/cma.asn1 b/drivers/pci/cma.asn1
new file mode 100644
index 000000000000..da41421d4085
--- /dev/null
+++ b/drivers/pci/cma.asn1
@@ -0,0 +1,41 @@
+-- SPDX-License-Identifier: BSD-3-Clause
+--
+-- Component Measurement and Authentication (CMA-SPDM, PCIe r6.1 sec 6.31.3)
+-- X.509 Subject Alternative Name (RFC 5280 sec 4.2.1.6)
+--
+-- Copyright (C) 2008 IETF Trust and the persons identified as authors
+-- of the code
+--
+-- https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.6
+--
+-- The ASN.1 module in RFC 5280 appendix A.1 uses EXPLICIT TAGS whereas the one
+-- in appendix A.2 uses IMPLICIT TAGS. The kernel's simplified asn1_compiler.c
+-- always uses EXPLICIT TAGS, hence this ASN.1 module differs from RFC 5280 in
+-- that it adds IMPLICIT to definitions from appendix A.2 (such as GeneralName)
+-- and omits EXPLICIT in those definitions.
+
+SubjectAltName ::= GeneralNames
+
+GeneralNames ::= SEQUENCE OF GeneralName
+
+GeneralName ::= CHOICE {
+ otherName [0] IMPLICIT OtherName,
+ rfc822Name [1] IMPLICIT IA5String,
+ dNSName [2] IMPLICIT IA5String,
+ x400Address [3] ANY,
+ directoryName [4] ANY,
+ ediPartyName [5] IMPLICIT EDIPartyName,
+ uniformResourceIdentifier [6] IMPLICIT IA5String,
+ iPAddress [7] IMPLICIT OCTET STRING,
+ registeredID [8] IMPLICIT OBJECT IDENTIFIER
+ }
+
+OtherName ::= SEQUENCE {
+ type-id OBJECT IDENTIFIER ({ pci_cma_note_oid }),
+ value [0] ANY ({ pci_cma_note_san })
+ }
+
+EDIPartyName ::= SEQUENCE {
+ nameAssigner [0] ANY OPTIONAL,
+ partyName [1] ANY
+ }
diff --git a/drivers/pci/cma.c b/drivers/pci/cma.c
index 6b52b129574e..6e4eb3b92113 100644
--- a/drivers/pci/cma.c
+++ b/drivers/pci/cma.c
@@ -12,6 +12,9 @@
#define dev_fmt(fmt) "CMA: " fmt
#include <linux/err.h>
+#include <keys/x509-parser.h>
+#include <linux/asn1_decoder.h>
+#include <linux/oid_registry.h>
#include <linux/pci.h>
#include <linux/pci-doe.h>
#include <linux/pci-tsm.h>
@@ -20,8 +23,126 @@
#include <linux/spdm.h>
#include <linux/tsm.h>
+#include "cma.asn1.h"
#include "pci.h"
+/*
+ * The spdm_requester.c library calls pci_cma_validate() to check requirements
+ * for Leaf Certificates per PCIe r6.1 sec 6.31.3.
+ *
+ * pci_cma_validate() parses the Subject Alternative Name using the ASN.1
+ * module cma.asn1, which calls pci_cma_note_oid() and pci_cma_note_san()
+ * to compare an OtherName against the expected name.
+ *
+ * The expected name is constructed beforehand by pci_cma_construct_san().
+ *
+ * PCIe r6.2 drops the Subject Alternative Name spec language, even though
+ * it continues to require "the leaf certificate to include the information
+ * typically used by system software for device driver binding". Use the
+ * Subject Alternative Name per PCIe r6.1 for lack of a replacement and
+ * because it is the de facto standard among existing products.
+ */
+#define CMA_NAME_MAX sizeof("Vendor=1234:Device=1234:CC=123456:" \
+ "REV=12:SSVID=1234:SSID=1234:1234567890123456")
+
+struct pci_cma_x509_context {
+ struct pci_dev *pdev;
+ u8 slot;
+ enum OID last_oid;
+ char expected_name[CMA_NAME_MAX];
+ unsigned int expected_len;
+ unsigned int found:1;
+};
+
+int pci_cma_note_oid(void *context, size_t hdrlen, unsigned char tag,
+ const void *value, size_t vlen)
+{
+ struct pci_cma_x509_context *ctx = context;
+
+ ctx->last_oid = look_up_OID(value, vlen);
+
+ return 0;
+}
+
+int pci_cma_note_san(void *context, size_t hdrlen, unsigned char tag,
+ const void *value, size_t vlen)
+{
+ struct pci_cma_x509_context *ctx = context;
+
+ /* These aren't the drOIDs we're looking for. */
+ if (ctx->last_oid != OID_CMA)
+ return 0;
+
+ if (tag != ASN1_UTF8STR ||
+ vlen != ctx->expected_len ||
+ memcmp(value, ctx->expected_name, vlen) != 0) {
+ pci_err(ctx->pdev, "Leaf certificate of slot %u "
+ "has invalid Subject Alternative Name\n", ctx->slot);
+ return -EINVAL;
+ }
+
+ ctx->found = true;
+
+ return 0;
+}
+
+static unsigned int pci_cma_construct_san(struct pci_dev *pdev, char *name)
+{
+ unsigned int len;
+ u64 serial;
+
+ len = scnprintf(name, CMA_NAME_MAX,
+ "Vendor=%04hx:Device=%04hx:CC=%06x:REV=%02hhx",
+ pdev->vendor, pdev->device, pdev->class, pdev->revision);
+
+ if (pdev->hdr_type == PCI_HEADER_TYPE_NORMAL)
+ len += scnprintf(name + len, CMA_NAME_MAX - len,
+ ":SSVID=%04hx:SSID=%04hx",
+ pdev->subsystem_vendor, pdev->subsystem_device);
+
+ serial = pci_get_dsn(pdev);
+ if (serial)
+ len += scnprintf(name + len, CMA_NAME_MAX - len,
+ ":%016llx", serial);
+
+ return len;
+}
+
+static int pci_cma_validate(struct device *dev, u8 slot,
+ struct x509_certificate *leaf_cert)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ struct pci_cma_x509_context ctx;
+ int ret;
+
+ if (!leaf_cert->raw_san) {
+ pci_err(pdev, "Leaf certificate of slot %u "
+ "has no Subject Alternative Name\n", slot);
+ return -EINVAL;
+ }
+
+ ctx.pdev = pdev;
+ ctx.slot = slot;
+ ctx.found = false;
+ ctx.expected_len = pci_cma_construct_san(pdev, ctx.expected_name);
+
+ ret = asn1_ber_decoder(&cma_decoder, &ctx, leaf_cert->raw_san,
+ leaf_cert->raw_san_size);
+ if (ret == -EBADMSG || ret == -EMSGSIZE)
+ pci_err(pdev, "Leaf certificate of slot %u "
+ "has malformed Subject Alternative Name\n", slot);
+ if (ret < 0)
+ return ret;
+
+ if (!ctx.found) {
+ pci_err(pdev, "Leaf certificate of slot %u "
+ "has no OtherName with CMA OID\n", slot);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
static int pci_doe_transport(void *priv, struct device *dev,
const void *request, size_t request_sz,
void *response, size_t response_sz)
@@ -64,7 +185,7 @@ static struct pci_tsm *pci_cma_tsm_probe(struct tsm_dev *tsm_dev,
}
cma->spdm = spdm_create(&pdev->dev, pci_doe_transport, cma->host.doe_mb,
- PCI_DOE_MAX_PAYLOAD, NULL);
+ PCI_DOE_MAX_PAYLOAD, pci_cma_validate);
if (!cma->spdm) {
pci_tsm_host_destructor(&cma->host);
kfree(cma);
diff --git a/include/linux/oid_registry.h b/include/linux/oid_registry.h
index ebce402854de..113f4e802ec4 100644
--- a/include/linux/oid_registry.h
+++ b/include/linux/oid_registry.h
@@ -150,6 +150,9 @@ enum OID {
OID_id_ml_dsa_65, /* 2.16.840.1.101.3.4.3.18 */
OID_id_ml_dsa_87, /* 2.16.840.1.101.3.4.3.19 */
+ /* PCI */
+ OID_CMA, /* 2.23.147 */
+
OID__NR
};
--
2.55.0
^ permalink raw reply related [flat|nested] 45+ messages in thread
* [PATCH v4 15/22] lib: rspdm: Support SPDM get_version
2026-09-28 1:11 [PATCH v4 00/22] lib: Rust implementation of SPDM alistair23
` (13 preceding siblings ...)
2026-09-28 1:11 ` [PATCH v4 14/22] PCI/CMA: Validate Subject Alternative Name in certificates alistair23
@ 2026-09-28 1:11 ` alistair23
2026-09-28 1:39 ` sashiko-bot
2026-09-28 1:11 ` [PATCH v4 16/22] lib: rspdm: Support SPDM get_capabilities alistair23
` (3 subsequent siblings)
18 siblings, 1 reply; 45+ messages in thread
From: alistair23 @ 2026-09-28 1:11 UTC (permalink / raw)
To: lukas, linux-cxl, akpm, Jonathan.Cameron, rust-for-linux,
linux-kernel, jic23, bhelgaas, alistair, djbw, linux-pci
Cc: wilfred.mallawa, a.hindborg, gregkh, alex.gaynor, alistair23,
aliceryhl, benno.lossin, boqun.feng, bjorn3_gh, tmgross, gary,
ojeda, Jonathan Cameron
From: Alistair Francis <alistair@alistair23.me>
Support the GET_VERSION SPDM command.
Signed-off-by: Alistair Francis <alistair@alistair23.me>
Reviewed-by: Jonathan Cameron <jonathan.cameron@oss.qualcomm.com>
---
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(-)
diff --git a/lib/rspdm/consts.rs b/lib/rspdm/consts.rs
index 9709f51849d7..cb0fc2b55206 100644
--- a/lib/rspdm/consts.rs
+++ b/lib/rspdm/consts.rs
@@ -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}", *self as 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>();
diff --git a/lib/rspdm/lib.rs b/lib/rspdm/lib.rs
index 1010bd38df6d..df3b97a05c41 100644
--- a/lib/rspdm/lib.rs
+++ b/lib/rspdm/lib.rs
@@ -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_ptr as *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)
}
diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
index c894cf30bb87..3bbe55d965f7 100644
--- a/lib/rspdm/state.rs
+++ b/lib/rspdm/state.rs
@@ -22,10 +22,14 @@
use crate::consts::{
SpdmErrorCode,
SPDM_ERROR,
+ SPDM_GET_VERSION_LEN,
+ SPDM_MAX_VER,
SPDM_MIN_VER,
SPDM_REQ, //
};
use crate::validator::{
+ GetVersionReq,
+ GetVersionRsp,
SpdmErrorRsp,
SpdmHeader, //
};
@@ -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.
+///
/// `dev`: Responder device. Used for error reporting and passed to @transport.
/// `transport`: Transport function to perform one message exchange.
/// `transport_priv`: Transport private data.
@@ -76,7 +85,6 @@ pub(crate) fn new(
}
}
- #[allow(dead_code)]
fn spdm_err(&self, rsp: &SpdmErrorRsp) -> Result<(), Error> {
match rsp.error_code {
SpdmErrorCode::InvalidRequest => {
@@ -188,7 +196,6 @@ fn spdm_err(&self, rsp: &SpdmErrorRsp) -> Result<(), Error> {
///
/// The data in `request_buf` is sent to the device and the response is
/// stored in `response_buf`.
- #[allow(dead_code)]
pub(crate) fn spdm_exchange(
&self,
request_buf: &mut [u8],
@@ -238,4 +245,49 @@ pub(crate) fn spdm_exchange(
Ok(length)
}
+
+ /// Negotiate a supported SPDM version and store the information
+ /// in the `SpdmState`.
+ pub(crate) fn get_version(&mut self) -> Result<(), Error> {
+ let mut request = GetVersionReq::default();
+ request.header.version = SPDM_MIN_VER;
+ self.version = SPDM_MIN_VER;
+
+ let mut request_buf = request.to_bytes()?;
+
+ let mut response_vec: KVec<u8> = KVec::from_elem(0u8, SPDM_GET_VERSION_LEN, GFP_KERNEL)?;
+
+ let rc =
+ self.spdm_exchange(request_buf.as_mut_slice(), response_vec.as_mut_slice())? as usize;
+
+ // The transport must report a length within the buffer we provided.
+ if rc > response_vec.len() {
+ return Err(EINVAL);
+ }
+ response_vec.truncate(rc);
+
+ let response: GetVersionRsp = Untrusted::new(response_vec.as_slice()).validate(&*self)?;
+
+ let mut foundver = false;
+ for &entry in response.version_number_entries.iter() {
+ let alpha_version = (entry & 0xF) as u8;
+ let version = (entry >> 8) as u8;
+
+ if alpha_version != 0 {
+ pr_warn!("Alpha version {alpha_version} is not specifically supported\n");
+ }
+
+ if version >= self.version && version <= SPDM_MAX_VER {
+ self.version = version;
+ foundver = true;
+ }
+ }
+
+ if !foundver {
+ pr_err!("No common supported version\n");
+ return Err(EPROTO);
+ }
+
+ Ok(())
+ }
}
diff --git a/lib/rspdm/validator.rs b/lib/rspdm/validator.rs
index b14c066e6a51..d0b6691365c0 100644
--- a/lib/rspdm/validator.rs
+++ b/lib/rspdm/validator.rs
@@ -11,6 +11,7 @@
consts::SpdmErrorCode,
SpdmState, //
};
+use core::mem;
use kernel::prelude::*;
use kernel::{
error::Error,
@@ -20,6 +21,11 @@
},
};
+use crate::consts::{
+ SPDM_GET_VERSION,
+ SPDM_MIN_VER, //
+};
+
#[repr(C, packed)]
pub(crate) struct SpdmHeader {
pub(crate) version: u8,
@@ -29,7 +35,6 @@ pub(crate) struct SpdmHeader {
}
impl SpdmHeader {
- #[expect(dead_code)]
pub(crate) fn new(code: u8) -> Self {
SpdmHeader {
version: 0,
@@ -39,7 +44,6 @@ pub(crate) fn new(code: u8) -> Self {
}
}
- #[expect(dead_code)]
pub(crate) fn to_bytes(&self) -> Result<KVec<u8>> {
let mut out = KVec::new();
@@ -91,3 +95,83 @@ fn validate(unvalidated: &[u8], _context: &'c SpdmState) -> Result<Self, Self::E
})
}
}
+
+pub(crate) struct GetVersionReq {
+ pub(crate) header: SpdmHeader,
+}
+
+impl GetVersionReq {
+ pub(crate) fn to_bytes(&self) -> Result<KVec<u8>> {
+ self.header.to_bytes()
+ }
+}
+
+impl Default for GetVersionReq {
+ fn default() -> Self {
+ GetVersionReq {
+ header: SpdmHeader::new(SPDM_GET_VERSION),
+ }
+ }
+}
+
+#[expect(dead_code)]
+pub(crate) struct GetVersionRsp {
+ pub(crate) header: SpdmHeader,
+
+ pub(crate) version_number_entry_count: u8,
+ pub(crate) version_number_entries: KVec<u16>,
+
+ /// Size of the response, not public
+ length: usize,
+}
+
+impl GetVersionRsp {
+ #[expect(dead_code)]
+ pub(crate) fn len(&self) -> usize {
+ self.length
+ }
+}
+
+impl<'a, 'c> Validate<'c, Untrusted<&'a [u8]>> for GetVersionRsp {
+ type Err = Error;
+
+ type Context = &'c SpdmState;
+
+ fn validate(unvalidated: &[u8], context: &'c SpdmState) -> Result<Self, Self::Err> {
+ let header: SpdmHeader =
+ Untrusted::new(unvalidated.get(0..4).ok_or(EIO)?).validate(context)?;
+
+ if header.code != SPDM_GET_VERSION - 0x80 {
+ return Err(EINVAL);
+ }
+
+ if header.version != SPDM_MIN_VER {
+ return Err(EINVAL);
+ }
+
+ let version_number_entry_count = *unvalidated.get(5).ok_or(EIO)?;
+
+ // Entries follow header (4) + reserved (1) + count (1) = 6 bytes.
+ let mut offset = mem::size_of::<SpdmHeader>() + 2;
+
+ let mut version_number_entries = KVec::new();
+ for _ in 0..version_number_entry_count {
+ let entry = u16::from_le_bytes(
+ unvalidated
+ .get(offset..offset + mem::size_of::<u16>())
+ .ok_or(EIO)?
+ .try_into()
+ .map_err(|_| EINVAL)?,
+ );
+ version_number_entries.push(entry, GFP_KERNEL)?;
+ offset += mem::size_of::<u16>();
+ }
+
+ Ok(GetVersionRsp {
+ header,
+ version_number_entry_count,
+ version_number_entries,
+ length: offset,
+ })
+ }
+}
--
2.55.0
^ permalink raw reply related [flat|nested] 45+ messages in thread
* [PATCH v4 16/22] lib: rspdm: Support SPDM get_capabilities
2026-09-28 1:11 [PATCH v4 00/22] lib: Rust implementation of SPDM alistair23
` (14 preceding siblings ...)
2026-09-28 1:11 ` [PATCH v4 15/22] lib: rspdm: Support SPDM get_version alistair23
@ 2026-09-28 1:11 ` alistair23
2026-09-28 1:41 ` sashiko-bot
2026-09-28 1:11 ` [PATCH v4 17/22] lib: rspdm: Support SPDM negotiate_algorithms alistair23
` (2 subsequent siblings)
18 siblings, 1 reply; 45+ messages in thread
From: alistair23 @ 2026-09-28 1:11 UTC (permalink / raw)
To: lukas, linux-cxl, akpm, Jonathan.Cameron, rust-for-linux,
linux-kernel, jic23, bhelgaas, alistair, djbw, linux-pci
Cc: wilfred.mallawa, a.hindborg, gregkh, alex.gaynor, alistair23,
aliceryhl, benno.lossin, boqun.feng, bjorn3_gh, tmgross, gary,
ojeda, Jonathan Cameron
From: Alistair Francis <alistair@alistair23.me>
Support the GET_CAPABILITIES SPDM command.
Signed-off-by: Alistair Francis <alistair@alistair23.me>
Reviewed-by: Jonathan Cameron <jonathan.cameron@oss.qualcomm.com>
---
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(-)
diff --git a/lib/rspdm/consts.rs b/lib/rspdm/consts.rs
index cb0fc2b55206..cbba95944ef9 100644
--- a/lib/rspdm/consts.rs
+++ b/lib/rspdm/consts.rs
@@ -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;
diff --git a/lib/rspdm/lib.rs b/lib/rspdm/lib.rs
index df3b97a05c41..1109a9334713 100644
--- a/lib/rspdm/lib.rs
+++ b/lib/rspdm/lib.rs
@@ -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)
}
diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
index 3bbe55d965f7..fb43260f4800 100644
--- a/lib/rspdm/state.rs
+++ b/lib/rspdm/state.rs
@@ -25,13 +25,20 @@
SPDM_GET_VERSION_LEN,
SPDM_MAX_VER,
SPDM_MIN_VER,
- SPDM_REQ, //
+ SPDM_REQ,
+ SPDM_RSP_MIN_CAPS,
+ SPDM_VER_10,
+ SPDM_VER_11,
+ SPDM_VER_12, //
};
use crate::validator::{
+ GetCapabilitiesReq,
+ GetCapabilitiesRsp,
GetVersionReq,
GetVersionRsp,
SpdmErrorRsp,
- SpdmHeader, //
+ SpdmHeader,
+ GET_CAPABILITIES_RSP_SZ, //
};
/// The current SPDM session state for a device.
@@ -55,6 +62,8 @@
///
/// `version`: Maximum common supported version of requester and responder.
/// Negotiated during GET_VERSION exchange.
+/// `rsp_caps`: Cached capabilities of responder.
+/// Received during GET_CAPABILITIES exchange.
#[expect(dead_code)]
pub(crate) struct SpdmState {
pub(crate) dev: *mut bindings::device,
@@ -65,6 +74,7 @@ pub(crate) struct SpdmState {
// Negotiated state
pub(crate) version: u8,
+ pub(crate) rsp_caps: u32,
}
impl SpdmState {
@@ -82,6 +92,7 @@ pub(crate) fn new(
transport_sz,
validate,
version: SPDM_MIN_VER,
+ rsp_caps: 0,
}
}
@@ -290,4 +301,59 @@ pub(crate) fn get_version(&mut self) -> Result<(), Error> {
Ok(())
}
+
+ /// Obtain the supported capabilities from an SPDM session and store the
+ /// information in the `SpdmState`.
+ pub(crate) fn get_capabilities(&mut self) -> Result<(), Error> {
+ let mut request = GetCapabilitiesReq::default();
+ request.header.version = self.version;
+
+ let rsp_sz = match self.version {
+ SPDM_VER_10 | SPDM_VER_11 => {
+ core::mem::size_of::<SpdmHeader>() + 4 + core::mem::size_of::<u32>()
+ }
+ _ => {
+ request.data_transfer_size = self.transport_sz;
+ request.max_spdm_msg_size = self.transport_sz;
+
+ (GET_CAPABILITIES_RSP_SZ as u32 + u16::MAX as u32).min(self.transport_sz) as usize
+ }
+ };
+
+ let mut request_buf = request.to_bytes()?;
+
+ let mut response_vec: KVec<u8> = KVec::from_elem(0u8, rsp_sz, GFP_KERNEL)?;
+
+ let rc =
+ self.spdm_exchange(request_buf.as_mut_slice(), response_vec.as_mut_slice())? as usize;
+ response_vec.truncate(rc);
+
+ 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);
+ }
+
+ 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);
+ }
+
+ self.transport_sz = self.transport_sz.min(response.data_transfer_size);
+ }
+
+ Ok(())
+ }
}
diff --git a/lib/rspdm/validator.rs b/lib/rspdm/validator.rs
index d0b6691365c0..ca837bd9cfcc 100644
--- a/lib/rspdm/validator.rs
+++ b/lib/rspdm/validator.rs
@@ -22,8 +22,16 @@
};
use crate::consts::{
+ SPDM_CAP_SUPPORTED_ALGORITHMS,
+ SPDM_CTEXPONENT,
+ SPDM_GET_CAPABILITIES,
SPDM_GET_VERSION,
- SPDM_MIN_VER, //
+ SPDM_MIN_DATA_TRANSFER_SIZE,
+ SPDM_MIN_VER,
+ SPDM_REQ_CAPS,
+ SPDM_VER_10,
+ SPDM_VER_11,
+ SPDM_VER_12, //
};
#[repr(C, packed)]
@@ -175,3 +183,302 @@ fn validate(unvalidated: &[u8], context: &'c SpdmState) -> Result<Self, Self::Er
})
}
}
+
+pub(crate) struct GetCapabilitiesReq {
+ pub(crate) header: SpdmHeader,
+
+ pub(crate) ctexponent: u8,
+
+ pub(crate) flags: u32,
+
+ /* End of SPDM 1.1 structure */
+ pub(crate) data_transfer_size: u32,
+ pub(crate) max_spdm_msg_size: u32,
+}
+
+impl GetCapabilitiesReq {
+ pub(crate) fn to_bytes(&self) -> Result<KVec<u8>> {
+ let mut out = self.header.to_bytes()?;
+
+ if self.header.version <= SPDM_VER_10 {
+ return Ok(out);
+ }
+
+ out.push(0u8, GFP_KERNEL)?;
+ out.push(self.ctexponent, GFP_KERNEL)?;
+
+ out.extend_from_slice(&[0u8; 2], GFP_KERNEL)?;
+ out.extend_from_slice(&self.flags.to_le_bytes(), GFP_KERNEL)?;
+
+ if self.header.version >= SPDM_VER_12 {
+ out.extend_from_slice(&self.data_transfer_size.to_le_bytes(), GFP_KERNEL)?;
+ out.extend_from_slice(&self.max_spdm_msg_size.to_le_bytes(), GFP_KERNEL)?;
+ }
+
+ Ok(out)
+ }
+}
+
+impl Default for GetCapabilitiesReq {
+ fn default() -> Self {
+ GetCapabilitiesReq {
+ header: SpdmHeader::new(SPDM_GET_CAPABILITIES),
+
+ ctexponent: SPDM_CTEXPONENT,
+ flags: SPDM_REQ_CAPS,
+ data_transfer_size: 0,
+ max_spdm_msg_size: 0,
+ }
+ }
+}
+
+/// Response AlgStructure field format
+#[expect(dead_code)]
+pub(crate) struct RespAlgStruct {
+ pub(crate) alg_type: u8,
+ pub(crate) alg_count: u8,
+ pub(crate) alg_supported: KVec<u8>,
+ pub(crate) alg_external: KVec<u32>,
+}
+
+pub(crate) const GET_CAPABILITIES_RSP_SZ: usize = mem::size_of::<SpdmHeader>() + 16;
+
+/// The GET_CAPABILITIES SupportedAlgorithms block (SPDM 1.3+).
+///
+/// Conforms to the NEGOTIATE_ALGORITHMS request message format,
+/// including all fields from Param1 through the end of the message inclusive.
+/// The `Length` field is equal to the total size of the block (`AlgSize`).
+#[expect(dead_code)]
+pub(crate) struct SupportedAlgorithms {
+ /// param1
+ pub(crate) alg_struct_count: u8,
+ pub(crate) length: u16,
+
+ pub(crate) measurement_specification: u8,
+ pub(crate) other_params_support: u8,
+
+ pub(crate) base_asym_algo: u32,
+ pub(crate) base_hash_algo: u32,
+
+ pub(crate) ext_asym_count: u8,
+ pub(crate) ext_hash_count: u8,
+
+ pub(crate) mel_specification: u8,
+
+ pub(crate) ext_asym: KVec<u32>,
+ pub(crate) ext_hash: KVec<u32>,
+ pub(crate) alg_struct: KVec<RespAlgStruct>,
+}
+
+impl SupportedAlgorithms {
+ fn from_bytes(buf: &[u8]) -> Result<Self, Error> {
+ let read_le16 = |off: usize| -> Result<u16, Error> {
+ Ok(u16::from_le_bytes(
+ buf.get(off..off + mem::size_of::<u16>())
+ .ok_or(EIO)?
+ .try_into()
+ .map_err(|_| EINVAL)?,
+ ))
+ };
+ let read_le32 = |off: usize| -> Result<u32, Error> {
+ Ok(u32::from_le_bytes(
+ buf.get(off..off + mem::size_of::<u32>())
+ .ok_or(EIO)?
+ .try_into()
+ .map_err(|_| EINVAL)?,
+ ))
+ };
+
+ 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;
+
+ let mut ext_asym = KVec::new();
+ for _ in 0..ext_asym_count {
+ ext_asym.push(read_le32(offset)?, GFP_KERNEL)?;
+ offset += mem::size_of::<u32>();
+ }
+
+ let mut ext_hash = KVec::new();
+ for _ in 0..ext_hash_count {
+ ext_hash.push(read_le32(offset)?, GFP_KERNEL)?;
+ offset += mem::size_of::<u32>();
+ }
+
+ let mut alg_struct = KVec::new();
+ for _ in 0..alg_struct_count {
+ let alg_type = *buf.get(offset).ok_or(EIO)?;
+ let alg_count = *buf.get(offset + 1).ok_or(EIO)?;
+
+ let fixed_alg_count = (alg_count & 0xf) as usize;
+ let ext_alg_count = (alg_count >> 4) as usize;
+
+ offset += 2;
+
+ let mut alg_supported = KVec::new();
+ alg_supported.extend_from_slice(
+ buf.get(offset..offset + fixed_alg_count).ok_or(EIO)?,
+ GFP_KERNEL,
+ )?;
+ offset += fixed_alg_count;
+
+ let mut alg_external = KVec::new();
+ for _ in 0..ext_alg_count {
+ alg_external.push(read_le32(offset)?, GFP_KERNEL)?;
+ offset += mem::size_of::<u32>();
+ }
+
+ alg_struct.push(
+ RespAlgStruct {
+ alg_type,
+ alg_count,
+ alg_supported,
+ alg_external,
+ },
+ GFP_KERNEL,
+ )?;
+ }
+
+ if length as usize != offset {
+ pr_err!("Malformed SupportedAlgorithms block\n");
+ return Err(EPROTO);
+ }
+
+ Ok(SupportedAlgorithms {
+ alg_struct_count,
+ length,
+ measurement_specification,
+ other_params_support,
+ base_asym_algo,
+ base_hash_algo,
+ ext_asym_count,
+ ext_hash_count,
+ mel_specification,
+ ext_asym,
+ ext_hash,
+ alg_struct,
+ })
+ }
+}
+
+#[expect(dead_code)]
+pub(crate) struct GetCapabilitiesRsp {
+ pub(crate) header: SpdmHeader,
+
+ pub(crate) ctexponent: u8,
+ pub(crate) flags: u32,
+
+ // End of SPDM 1.1 structure
+ pub(crate) data_transfer_size: u32,
+ pub(crate) max_spdm_msg_size: u32,
+
+ pub(crate) supported_algorithms: Option<SupportedAlgorithms>,
+
+ /// Size of the response, not public
+ length: usize,
+}
+
+impl GetCapabilitiesRsp {
+ #[expect(dead_code)]
+ pub(crate) fn len(&self) -> usize {
+ self.length
+ }
+}
+
+impl<'a, 'c> Validate<'c, Untrusted<&'a [u8]>> for GetCapabilitiesRsp {
+ type Err = Error;
+
+ type Context = &'c SpdmState;
+
+ fn validate(unvalidated: &[u8], context: &'c SpdmState) -> Result<Self, Self::Err> {
+ let header: SpdmHeader =
+ Untrusted::new(unvalidated.get(0..4).ok_or(EIO)?).validate(context)?;
+
+ if header.code != SPDM_GET_CAPABILITIES - 0x80 {
+ return Err(EINVAL);
+ }
+
+ if header.version != context.version {
+ pr_err!("Invalid version response\n");
+ return Err(EPROTO);
+ }
+
+ let ctexponent = *unvalidated.get(5).ok_or(EIO)?;
+ let flags = u32::from_le_bytes(
+ unvalidated
+ .get(8..12)
+ .ok_or(EIO)?
+ .try_into()
+ .map_err(|_| EINVAL)?,
+ );
+
+ // DataTransferSize and MaxSPDMmsgSize only exist in SPDM 1.2 and later.
+ let (data_transfer_size, max_spdm_msg_size, supported_algorithms, length) =
+ if context.version <= SPDM_VER_11 {
+ (
+ 0,
+ 0,
+ None,
+ mem::size_of::<SpdmHeader>() + 4 + mem::size_of::<u32>(),
+ )
+ } else {
+ let data_transfer_size = u32::from_le_bytes(
+ unvalidated
+ .get(12..16)
+ .ok_or(EIO)?
+ .try_into()
+ .map_err(|_| EINVAL)?,
+ );
+ let max_spdm_msg_size = u32::from_le_bytes(
+ unvalidated
+ .get(16..20)
+ .ok_or(EIO)?
+ .try_into()
+ .map_err(|_| EINVAL)?,
+ );
+
+ if data_transfer_size < SPDM_MIN_DATA_TRANSFER_SIZE {
+ pr_err!("Malformed capabilities response\n");
+ return Err(EPROTO);
+ }
+
+ 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
+ };
+
+ let length = GET_CAPABILITIES_RSP_SZ
+ + supported_algorithms
+ .as_ref()
+ .map_or(0, |s| s.length as usize);
+
+ (
+ data_transfer_size,
+ max_spdm_msg_size,
+ supported_algorithms,
+ length,
+ )
+ };
+
+ Ok(GetCapabilitiesRsp {
+ header,
+ ctexponent,
+ flags,
+ data_transfer_size,
+ max_spdm_msg_size,
+ supported_algorithms,
+ length,
+ })
+ }
+}
--
2.55.0
^ permalink raw reply related [flat|nested] 45+ messages in thread
* [PATCH v4 17/22] lib: rspdm: Support SPDM negotiate_algorithms
2026-09-28 1:11 [PATCH v4 00/22] lib: Rust implementation of SPDM alistair23
` (15 preceding siblings ...)
2026-09-28 1:11 ` [PATCH v4 16/22] lib: rspdm: Support SPDM get_capabilities alistair23
@ 2026-09-28 1:11 ` alistair23
2026-09-28 1:45 ` sashiko-bot
2026-09-28 1:11 ` [PATCH v4 18/22] lib: rspdm: Support SPDM get_digests alistair23
2026-09-28 1:31 ` [PATCH v4 19/22] lib: rspdm: Support SPDM get_certificate alistair23
18 siblings, 1 reply; 45+ messages in thread
From: alistair23 @ 2026-09-28 1:11 UTC (permalink / raw)
To: lukas, linux-cxl, akpm, Jonathan.Cameron, rust-for-linux,
linux-kernel, jic23, bhelgaas, alistair, djbw, linux-pci
Cc: wilfred.mallawa, a.hindborg, gregkh, alex.gaynor, alistair23,
aliceryhl, benno.lossin, boqun.feng, bjorn3_gh, tmgross, gary,
ojeda
From: Alistair Francis <alistair@alistair23.me>
Support the NEGOTIATE_ALGORITHMS SPDM command.
Signed-off-by: Alistair Francis <alistair@alistair23.me>
---
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(-)
diff --git a/lib/rspdm/consts.rs b/lib/rspdm/consts.rs
index cbba95944ef9..d51b6eb06f81 100644
--- a/lib/rspdm/consts.rs
+++ b/lib/rspdm/consts.rs
@@ -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);
diff --git a/lib/rspdm/lib.rs b/lib/rspdm/lib.rs
index 1109a9334713..63db5da8aa7a 100644
--- a/lib/rspdm/lib.rs
+++ b/lib/rspdm/lib.rs
@@ -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_ptr as *mut c_void)
+ <Pin<KBox<Mutex<SpdmState<'_>>>> as ForeignOwnable>::borrow(state_ptr as *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_ptr as *mut c_void) };
+ let mutex: KBox<Mutex<SpdmState<'_>>> = unsafe { KBox::from_foreign(state_ptr as *mut c_void) };
drop(mutex);
}
diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
index fb43260f4800..51055ea521d7 100644
--- a/lib/rspdm/state.rs
+++ b/lib/rspdm/state.rs
@@ -13,18 +13,34 @@
bindings,
error::{
code::EINVAL,
+ from_err_ptr,
to_result,
Error, //
},
+ str::CStr,
validate::Untrusted,
};
use crate::consts::{
SpdmErrorCode,
+ SPDM_ASYM_ALGOS,
+ SPDM_ASYM_ECDSA_ECC_NIST_P256,
+ SPDM_ASYM_ECDSA_ECC_NIST_P384,
+ SPDM_ASYM_ECDSA_ECC_NIST_P521,
+ SPDM_ASYM_RSASSA_2048,
+ SPDM_ASYM_RSASSA_3072,
+ SPDM_ASYM_RSASSA_4096,
SPDM_ERROR,
SPDM_GET_VERSION_LEN,
+ SPDM_HASH_ALGOS,
+ SPDM_HASH_SHA_256,
+ SPDM_HASH_SHA_384,
+ SPDM_HASH_SHA_512,
+ SPDM_KEY_EX_CAP,
SPDM_MAX_VER,
+ SPDM_MEAS_RESP_CAP,
SPDM_MIN_VER,
+ SPDM_OPAQUE_DATA_FMT_GENERAL,
SPDM_REQ,
SPDM_RSP_MIN_CAPS,
SPDM_VER_10,
@@ -36,9 +52,12 @@
GetCapabilitiesRsp,
GetVersionReq,
GetVersionRsp,
+ NegotiateAlgsReq,
+ NegotiateAlgsRsp,
SpdmErrorRsp,
SpdmHeader,
- GET_CAPABILITIES_RSP_SZ, //
+ GET_CAPABILITIES_RSP_SZ,
+ NEGOTIATE_ALGS_RSP_SZ, //
};
/// The current SPDM session state for a device.
@@ -64,8 +83,27 @@
/// Negotiated during GET_VERSION exchange.
/// `rsp_caps`: Cached capabilities of responder.
/// Received during GET_CAPABILITIES exchange.
+/// @base_asym_alg: Asymmetric key algorithm for signature verification of
+/// CHALLENGE_AUTH and MEASUREMENTS messages.
+/// Selected by responder during NEGOTIATE_ALGORITHMS exchange.
+/// @base_hash_alg: Hash algorithm for signature verification of
+/// CHALLENGE_AUTH and MEASUREMENTS messages.
+/// Selected by responder during NEGOTIATE_ALGORITHMS exchange.
+/// @meas_hash_alg: Hash algorithm for measurement blocks.
+/// Selected by responder during NEGOTIATE_ALGORITHMS exchange.
+/// @base_asym_enc: Human-readable name of @base_asym_alg's signature encoding.
+/// Passed to crypto subsystem when calling verify_signature().
+/// @sig_len: Signature length of @base_asym_alg (in bytes).
+/// S or SigLen in SPDM specification.
+/// @base_hash_alg_name: Human-readable name of @base_hash_alg.
+/// Passed to crypto subsystem when calling crypto_alloc_shash() and
+/// verify_signature().
+/// @shash: Synchronous hash handle for @base_hash_alg computation.
+/// @desc: Synchronous hash context for @base_hash_alg computation.
+/// @hash_len: Hash length of @base_hash_alg (in bytes).
+/// H in SPDM specification.
#[expect(dead_code)]
-pub(crate) struct SpdmState {
+pub(crate) struct SpdmState<'a> {
pub(crate) dev: *mut bindings::device,
pub(crate) transport: bindings::spdm_transport,
pub(crate) transport_priv: *mut c_void,
@@ -75,9 +113,32 @@ pub(crate) struct SpdmState {
// Negotiated state
pub(crate) version: u8,
pub(crate) rsp_caps: u32,
+ pub(crate) base_asym_alg: u32,
+ pub(crate) base_hash_alg: u32,
+ pub(crate) meas_hash_alg: u32,
+
+ /* Signature algorithm */
+ base_asym_enc: &'a CStr,
+ sig_len: usize,
+
+ /* Hash algorithm */
+ base_hash_alg_name: &'a CStr,
+ pub(crate) shash: *mut bindings::crypto_shash,
+ pub(crate) desc: *mut bindings::shash_desc,
+ pub(crate) hash_len: usize,
}
-impl SpdmState {
+impl Drop for SpdmState<'_> {
+ fn drop(&mut self) {
+ self.free_desc();
+
+ unsafe {
+ bindings::crypto_free_shash(self.shash);
+ }
+ }
+}
+
+impl SpdmState<'_> {
pub(crate) fn new(
dev: *mut bindings::device,
transport: bindings::spdm_transport,
@@ -93,6 +154,31 @@ pub(crate) fn new(
validate,
version: SPDM_MIN_VER,
rsp_caps: 0,
+ base_asym_alg: 0,
+ base_hash_alg: 0,
+ meas_hash_alg: 0,
+ base_asym_enc: unsafe { CStr::from_bytes_with_nul_unchecked(b"\0") },
+ sig_len: 0,
+ base_hash_alg_name: unsafe { CStr::from_bytes_with_nul_unchecked(b"\0") },
+ shash: core::ptr::null_mut(),
+ desc: core::ptr::null_mut(),
+ hash_len: 0,
+ }
+ }
+
+ /// Free the `shash_desc` buffer if one is allocated.
+ fn free_desc(&mut self) {
+ if !self.desc.is_null() {
+ // SAFETY: `self.shash` is a valid handle when `desc` is allocated.
+ let desc_len = core::mem::size_of::<bindings::shash_desc>()
+ + unsafe { bindings::crypto_shash_descsize(self.shash) } as usize;
+
+ // SAFETY: `desc` points to a KVec<u8> allocation of `desc_len`
+ // bytes handed out with `KVec::into_raw_parts()`.
+ let desc_vec =
+ unsafe { KVec::<u8>::from_raw_parts(self.desc as *mut u8, desc_len, desc_len) };
+ drop(desc_vec);
+ self.desc = core::ptr::null_mut();
}
}
@@ -356,4 +442,162 @@ pub(crate) fn get_capabilities(&mut self) -> Result<(), Error> {
Ok(())
}
+
+ fn update_response_algs(&mut self) -> Result<(), Error> {
+ match self.base_asym_alg {
+ #[cfg(CONFIG_CRYPTO_RSA)]
+ SPDM_ASYM_RSASSA_2048 => {
+ self.sig_len = 256;
+ self.base_asym_enc = CStr::from_bytes_with_nul(b"pkcs1\0")?;
+ }
+ #[cfg(CONFIG_CRYPTO_RSA)]
+ SPDM_ASYM_RSASSA_3072 => {
+ self.sig_len = 384;
+ self.base_asym_enc = CStr::from_bytes_with_nul(b"pkcs1\0")?;
+ }
+ #[cfg(CONFIG_CRYPTO_RSA)]
+ SPDM_ASYM_RSASSA_4096 => {
+ self.sig_len = 512;
+ self.base_asym_enc = CStr::from_bytes_with_nul(b"pkcs1\0")?;
+ }
+ #[cfg(CONFIG_CRYPTO_ECDSA)]
+ SPDM_ASYM_ECDSA_ECC_NIST_P256 => {
+ self.sig_len = 64;
+ self.base_asym_enc = CStr::from_bytes_with_nul(b"p1363\0")?;
+ }
+ #[cfg(CONFIG_CRYPTO_ECDSA)]
+ SPDM_ASYM_ECDSA_ECC_NIST_P384 => {
+ self.sig_len = 96;
+ self.base_asym_enc = CStr::from_bytes_with_nul(b"p1363\0")?;
+ }
+ #[cfg(CONFIG_CRYPTO_ECDSA)]
+ SPDM_ASYM_ECDSA_ECC_NIST_P521 => {
+ self.sig_len = 132;
+ self.base_asym_enc = CStr::from_bytes_with_nul(b"p1363\0")?;
+ }
+ _ => {
+ pr_err!("Unknown asym algorithm\n");
+ return Err(EINVAL);
+ }
+ }
+
+ match self.base_hash_alg {
+ #[cfg(CONFIG_CRYPTO_SHA256)]
+ SPDM_HASH_SHA_256 => {
+ self.base_hash_alg_name = CStr::from_bytes_with_nul(b"sha256\0")?;
+ }
+ #[cfg(CONFIG_CRYPTO_SHA512)]
+ SPDM_HASH_SHA_384 => {
+ self.base_hash_alg_name = CStr::from_bytes_with_nul(b"sha384\0")?;
+ }
+ #[cfg(CONFIG_CRYPTO_SHA512)]
+ SPDM_HASH_SHA_512 => {
+ self.base_hash_alg_name = CStr::from_bytes_with_nul(b"sha512\0")?;
+ }
+ _ => {
+ pr_err!("Unknown hash algorithm\n");
+ return Err(EINVAL);
+ }
+ }
+
+ // This is freed when `SpdmState` is dropped, but this call
+ // can happen multiple times.
+ if self.shash != core::ptr::null_mut() {
+ self.free_desc();
+
+ unsafe {
+ bindings::crypto_free_shash(self.shash);
+ }
+ }
+
+ self.shash =
+ unsafe { bindings::crypto_alloc_shash(self.base_hash_alg_name.as_char_ptr(), 0, 0) };
+ if let Err(e) = from_err_ptr(self.shash) {
+ self.shash = core::ptr::null_mut();
+ return Err(e);
+ }
+
+ // SAFETY: `self.shash` is a valid handle (verified above).
+ let desc_len = core::mem::size_of::<bindings::shash_desc>()
+ + unsafe { bindings::crypto_shash_descsize(self.shash) } as usize;
+
+ let desc_vec: KVec<u8> = KVec::from_elem(0u8, desc_len, GFP_KERNEL)?;
+ // Consume the desc_vec to make sure it isn't dropped, untill we
+ // manually drop it later
+ let (desc_buf, _length, _capacity) = desc_vec.into_raw_parts();
+
+ let desc = desc_buf as *mut bindings::shash_desc;
+
+ // SAFETY: `desc` points to an allocation of `desc_len` bytes, which is
+ // large enough for a `shash_desc` header and its trailing context.
+ unsafe { (*desc).tfm = self.shash };
+
+ self.desc = desc;
+
+ // Used frequently to compute offsets, so cache H
+ self.hash_len = unsafe { bindings::crypto_shash_digestsize(self.shash) as usize };
+
+ // SAFETY: `self.desc` points to a valid `shash_desc` sized buffer
+ // with `tfm` initialised above.
+ unsafe { to_result(bindings::crypto_shash_init(desc)) }
+ }
+
+ pub(crate) fn negotiate_algs(&mut self) -> Result<(), Error> {
+ let mut request = NegotiateAlgsReq::default();
+ request.header.version = self.version;
+
+ if self.version >= SPDM_VER_12 && (self.rsp_caps & SPDM_KEY_EX_CAP) == SPDM_KEY_EX_CAP {
+ request.other_params_support = SPDM_OPAQUE_DATA_FMT_GENERAL;
+ }
+
+ let rsp_sz =
+ (NEGOTIATE_ALGS_RSP_SZ as u32 + u16::MAX as u32).min(self.transport_sz) as usize;
+
+ request.length = NegotiateAlgsReq::WIRE_SIZE as u16;
+
+ let mut request_buf = request.to_bytes()?;
+
+ let mut response_vec: KVec<u8> = KVec::from_elem(0u8, rsp_sz, GFP_KERNEL)?;
+
+ let rc =
+ self.spdm_exchange(request_buf.as_mut_slice(), response_vec.as_mut_slice())? as usize;
+ response_vec.truncate(rc);
+
+ let response: NegotiateAlgsRsp =
+ Untrusted::new(response_vec.as_slice()).validate(&*self)?;
+
+ self.base_asym_alg = response.base_asym_sel;
+ self.base_hash_alg = response.base_hash_sel;
+ self.meas_hash_alg = response.measurement_hash_algo;
+
+ if self.base_asym_alg & SPDM_ASYM_ALGOS == 0 || self.base_hash_alg & SPDM_HASH_ALGOS == 0 {
+ pr_err!("No common supported algorithms\n");
+ return Err(EPROTO);
+ }
+
+ let meas_hash_valid = if self.rsp_caps & SPDM_MEAS_RESP_CAP != 0
+ && response.measurement_specification_sel != 0
+ {
+ self.meas_hash_alg.count_ones() == 1
+ } else {
+ self.meas_hash_alg == 0
+ };
+
+ // /* Responder shall select exactly 1 alg (SPDM 1.0.0 table 14) */
+ if self.base_asym_alg.count_ones() != 1
+ || self.base_hash_alg.count_ones() != 1
+ || !meas_hash_valid
+ || response.ext_asym_sel_count != 0
+ || response.ext_hash_sel_count != 0
+ || response.header.param1 > request.header.param1
+ || response.other_params_sel != request.other_params_support
+ {
+ pr_err!("Malformed algorithms response\n");
+ return Err(EPROTO);
+ }
+
+ self.update_response_algs()?;
+
+ Ok(())
+ }
}
diff --git a/lib/rspdm/validator.rs b/lib/rspdm/validator.rs
index ca837bd9cfcc..58f6a3d436a8 100644
--- a/lib/rspdm/validator.rs
+++ b/lib/rspdm/validator.rs
@@ -22,12 +22,16 @@
};
use crate::consts::{
+ SPDM_ASYM_ALGOS,
SPDM_CAP_SUPPORTED_ALGORITHMS,
SPDM_CTEXPONENT,
SPDM_GET_CAPABILITIES,
SPDM_GET_VERSION,
+ SPDM_HASH_ALGOS,
+ SPDM_MEAS_SPEC_DMTF,
SPDM_MIN_DATA_TRANSFER_SIZE,
SPDM_MIN_VER,
+ SPDM_NEGOTIATE_ALGS,
SPDM_REQ_CAPS,
SPDM_VER_10,
SPDM_VER_11,
@@ -67,9 +71,9 @@ pub(crate) fn to_bytes(&self) -> Result<KVec<u8>> {
impl<'a, 'c> Validate<'c, Untrusted<&'a [u8]>> for SpdmHeader {
type Err = Error;
- type Context = &'c SpdmState;
+ type Context = &'c SpdmState<'c>;
- fn validate(unvalidated: &[u8], _context: &'c SpdmState) -> Result<Self, Self::Err> {
+ fn validate(unvalidated: &[u8], _context: &'c SpdmState<'c>) -> Result<Self, Self::Err> {
Ok(SpdmHeader {
version: *unvalidated.get(0).ok_or(EIO)?,
code: *unvalidated.get(1).ok_or(EIO)?,
@@ -91,9 +95,9 @@ pub(crate) struct SpdmErrorRsp {
impl<'a, 'c> Validate<'c, Untrusted<&'a [u8]>> for SpdmErrorRsp {
type Err = Error;
- type Context = &'c SpdmState;
+ type Context = &'c SpdmState<'c>;
- fn validate(unvalidated: &[u8], _context: &'c SpdmState) -> Result<Self, Self::Err> {
+ fn validate(unvalidated: &[u8], _context: &'c SpdmState<'c>) -> Result<Self, Self::Err> {
Ok(SpdmErrorRsp {
version: *unvalidated.get(0).ok_or(EIO)?,
code: *unvalidated.get(1).ok_or(EIO)?,
@@ -143,9 +147,9 @@ pub(crate) fn len(&self) -> usize {
impl<'a, 'c> Validate<'c, Untrusted<&'a [u8]>> for GetVersionRsp {
type Err = Error;
- type Context = &'c SpdmState;
+ type Context = &'c SpdmState<'c>;
- fn validate(unvalidated: &[u8], context: &'c SpdmState) -> Result<Self, Self::Err> {
+ fn validate(unvalidated: &[u8], context: &'c SpdmState<'c>) -> Result<Self, Self::Err> {
let header: SpdmHeader =
Untrusted::new(unvalidated.get(0..4).ok_or(EIO)?).validate(context)?;
@@ -396,9 +400,9 @@ pub(crate) fn len(&self) -> usize {
impl<'a, 'c> Validate<'c, Untrusted<&'a [u8]>> for GetCapabilitiesRsp {
type Err = Error;
- type Context = &'c SpdmState;
+ type Context = &'c SpdmState<'c>;
- fn validate(unvalidated: &[u8], context: &'c SpdmState) -> Result<Self, Self::Err> {
+ fn validate(unvalidated: &[u8], context: &'c SpdmState<'c>) -> Result<Self, Self::Err> {
let header: SpdmHeader =
Untrusted::new(unvalidated.get(0..4).ok_or(EIO)?).validate(context)?;
@@ -482,3 +486,214 @@ fn validate(unvalidated: &[u8], context: &'c SpdmState) -> Result<Self, Self::Er
})
}
}
+
+pub(crate) struct NegotiateAlgsReq {
+ pub(crate) header: SpdmHeader,
+
+ pub(crate) length: u16,
+ pub(crate) measurement_specification: u8,
+ pub(crate) other_params_support: u8,
+
+ pub(crate) base_asym_algo: u32,
+ pub(crate) base_hash_algo: u32,
+
+ pub(crate) ext_asym_count: u8,
+ pub(crate) ext_hash_count: u8,
+ pub(crate) mel_specification: u8,
+ // ext_asym
+ // ext_hash
+ // resp_alg_struct
+}
+
+impl NegotiateAlgsReq {
+ pub(crate) const WIRE_SIZE: usize = mem::size_of::<SpdmHeader>() + 28;
+
+ pub(crate) fn to_bytes(&self) -> Result<KVec<u8>> {
+ let mut out = self.header.to_bytes()?;
+
+ out.extend_from_slice(&self.length.to_le_bytes(), GFP_KERNEL)?;
+ out.push(self.measurement_specification, GFP_KERNEL)?;
+ out.push(self.other_params_support, GFP_KERNEL)?;
+
+ out.extend_from_slice(&self.base_asym_algo.to_le_bytes(), GFP_KERNEL)?;
+ out.extend_from_slice(&self.base_hash_algo.to_le_bytes(), GFP_KERNEL)?;
+
+ out.extend_from_slice(&[0u8; 12], GFP_KERNEL)?;
+
+ out.push(self.ext_asym_count, GFP_KERNEL)?;
+ out.push(self.ext_hash_count, GFP_KERNEL)?;
+ out.push(0u8, GFP_KERNEL)?;
+ out.push(self.mel_specification, GFP_KERNEL)?;
+
+ Ok(out)
+ }
+}
+
+impl Default for NegotiateAlgsReq {
+ fn default() -> Self {
+ NegotiateAlgsReq {
+ header: SpdmHeader::new(SPDM_NEGOTIATE_ALGS),
+
+ length: 32,
+ measurement_specification: SPDM_MEAS_SPEC_DMTF,
+ other_params_support: 0,
+ base_asym_algo: SPDM_ASYM_ALGOS,
+ base_hash_algo: SPDM_HASH_ALGOS,
+ ext_asym_count: 0,
+ ext_hash_count: 0,
+ mel_specification: 0,
+ }
+ }
+}
+
+/// Size of everything up to the variable-length algorithm arrays (ExtAsymSel).
+pub(crate) const NEGOTIATE_ALGS_RSP_SZ: usize = mem::size_of::<SpdmHeader>() + 32;
+
+#[expect(dead_code)]
+pub(crate) struct NegotiateAlgsRsp {
+ pub(crate) header: SpdmHeader,
+
+ pub(crate) measurement_specification_sel: u8,
+ pub(crate) other_params_sel: u8,
+
+ pub(crate) measurement_hash_algo: u32,
+ pub(crate) base_asym_sel: u32,
+ pub(crate) base_hash_sel: u32,
+
+ pub(crate) mel_specification_sel: u8,
+ pub(crate) ext_asym_sel_count: u8,
+ pub(crate) ext_hash_sel_count: u8,
+
+ pub(crate) ext_asym: KVec<u32>,
+ pub(crate) ext_hash: KVec<u32>,
+ pub(crate) resp_alg_struct: KVec<RespAlgStruct>,
+
+ /// Size of the response, not public
+ length: usize,
+}
+
+impl NegotiateAlgsRsp {
+ #[expect(dead_code)]
+ pub(crate) fn len(&self) -> usize {
+ self.length
+ }
+}
+
+impl<'a, 'c> Validate<'c, Untrusted<&'a [u8]>> for NegotiateAlgsRsp {
+ type Err = Error;
+
+ type Context = &'c SpdmState<'c>;
+
+ fn validate(unvalidated: &[u8], context: &'c SpdmState<'c>) -> Result<Self, Self::Err> {
+ let header: SpdmHeader =
+ Untrusted::new(unvalidated.get(0..4).ok_or(EIO)?).validate(context)?;
+
+ if header.code != SPDM_NEGOTIATE_ALGS - 0x80 {
+ return Err(EINVAL);
+ }
+
+ if header.version != context.version {
+ pr_err!("Invalid version response\n");
+ return Err(EPROTO);
+ }
+
+ let resp_len = u16::from_le_bytes(
+ unvalidated
+ .get(4..4 + mem::size_of::<u16>())
+ .ok_or(EIO)?
+ .try_into()
+ .map_err(|_| EINVAL)?,
+ );
+
+ let measurement_specification_sel = *unvalidated.get(6).ok_or(EIO)?;
+ let other_params_sel = *unvalidated.get(7).ok_or(EIO)?;
+
+ // Helper to read a little-endian `u32`
+ let read_le32 = |offset: usize| -> Result<u32, Error> {
+ Ok(u32::from_le_bytes(
+ unvalidated
+ .get(offset..offset + mem::size_of::<u32>())
+ .ok_or(EIO)?
+ .try_into()
+ .map_err(|_| EINVAL)?,
+ ))
+ };
+
+ let measurement_hash_algo = read_le32(8)?;
+ let base_asym_sel = read_le32(12)?;
+ let base_hash_sel = read_le32(16)?;
+
+ let mel_specification_sel = *unvalidated.get(31).ok_or(EIO)?;
+ let ext_asym_sel_count = *unvalidated.get(32).ok_or(EIO)?;
+ let ext_hash_sel_count = *unvalidated.get(33).ok_or(EIO)?;
+
+ let mut offset = NEGOTIATE_ALGS_RSP_SZ;
+
+ let mut ext_asym = KVec::new();
+ for _ in 0..ext_asym_sel_count {
+ ext_asym.push(read_le32(offset)?, GFP_KERNEL)?;
+ offset += mem::size_of::<u32>();
+ }
+
+ let mut ext_hash = KVec::new();
+ for _ in 0..ext_hash_sel_count {
+ ext_hash.push(read_le32(offset)?, GFP_KERNEL)?;
+ offset += mem::size_of::<u32>();
+ }
+
+ let mut resp_alg_struct = KVec::new();
+ for _ in 0..header.param1 {
+ let alg_type = *unvalidated.get(offset).ok_or(EIO)?;
+ let alg_count = *unvalidated.get(offset + 1).ok_or(EIO)?;
+ let fixed_alg_count = (alg_count & 0xf) as usize;
+ let ext_alg_count = (alg_count >> 4) as usize;
+ offset += 2;
+
+ let mut alg_supported = KVec::new();
+ alg_supported.extend_from_slice(
+ unvalidated
+ .get(offset..offset + fixed_alg_count)
+ .ok_or(EIO)?,
+ GFP_KERNEL,
+ )?;
+ offset += fixed_alg_count;
+
+ let mut alg_external = KVec::new();
+ for _ in 0..ext_alg_count {
+ alg_external.push(read_le32(offset)?, GFP_KERNEL)?;
+ offset += mem::size_of::<u32>();
+ }
+
+ resp_alg_struct.push(
+ RespAlgStruct {
+ alg_type,
+ alg_count,
+ alg_supported,
+ alg_external,
+ },
+ GFP_KERNEL,
+ )?;
+ }
+
+ if resp_len != offset as u16 {
+ pr_err!("Incorrect response length reported\n");
+ return Err(EPROTO);
+ }
+
+ Ok(NegotiateAlgsRsp {
+ header,
+ measurement_specification_sel,
+ other_params_sel,
+ measurement_hash_algo,
+ base_asym_sel,
+ base_hash_sel,
+ mel_specification_sel,
+ ext_asym_sel_count,
+ ext_hash_sel_count,
+ ext_asym,
+ ext_hash,
+ resp_alg_struct,
+ length: offset,
+ })
+ }
+}
--
2.55.0
^ permalink raw reply related [flat|nested] 45+ messages in thread
* [PATCH v4 18/22] lib: rspdm: Support SPDM get_digests
2026-09-28 1:11 [PATCH v4 00/22] lib: Rust implementation of SPDM alistair23
` (16 preceding siblings ...)
2026-09-28 1:11 ` [PATCH v4 17/22] lib: rspdm: Support SPDM negotiate_algorithms alistair23
@ 2026-09-28 1:11 ` alistair23
2026-09-28 1:45 ` sashiko-bot
2026-09-28 1:31 ` [PATCH v4 19/22] lib: rspdm: Support SPDM get_certificate alistair23
18 siblings, 1 reply; 45+ messages in thread
From: alistair23 @ 2026-09-28 1:11 UTC (permalink / raw)
To: lukas, linux-cxl, akpm, Jonathan.Cameron, rust-for-linux,
linux-kernel, jic23, bhelgaas, alistair, djbw, linux-pci
Cc: wilfred.mallawa, a.hindborg, gregkh, alex.gaynor, alistair23,
aliceryhl, benno.lossin, boqun.feng, bjorn3_gh, tmgross, gary,
ojeda, Jonathan Cameron
From: Alistair Francis <alistair@alistair23.me>
Support the GET_DIGESTS SPDM command.
Signed-off-by: Alistair Francis <alistair@alistair23.me>
Reviewed-by: Jonathan Cameron <jonathan.cameron@oss.qualcomm.com>
---
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(-)
diff --git a/lib/rspdm/consts.rs b/lib/rspdm/consts.rs
index d51b6eb06f81..11e080815aa6 100644
--- a/lib/rspdm/consts.rs
+++ b/lib/rspdm/consts.rs
@@ -19,10 +19,11 @@
pub(crate) const SPDM_VER_10: u8 = 0x10;
pub(crate) const SPDM_VER_11: u8 = 0x11;
pub(crate) const SPDM_VER_12: u8 = 0x12;
-#[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)]
diff --git a/lib/rspdm/lib.rs b/lib/rspdm/lib.rs
index 63db5da8aa7a..92035b1070d1 100644
--- a/lib/rspdm/lib.rs
+++ b/lib/rspdm/lib.rs
@@ -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)
}
diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
index 51055ea521d7..502d99eb13a6 100644
--- a/lib/rspdm/state.rs
+++ b/lib/rspdm/state.rs
@@ -43,13 +43,17 @@
SPDM_OPAQUE_DATA_FMT_GENERAL,
SPDM_REQ,
SPDM_RSP_MIN_CAPS,
+ SPDM_SLOTS,
SPDM_VER_10,
SPDM_VER_11,
- SPDM_VER_12, //
+ SPDM_VER_12,
+ SPDM_VER_13, //
};
use crate::validator::{
GetCapabilitiesReq,
GetCapabilitiesRsp,
+ GetDigestsReq,
+ GetDigestsRsp,
GetVersionReq,
GetVersionRsp,
NegotiateAlgsReq,
@@ -91,6 +95,10 @@
/// Selected by responder during NEGOTIATE_ALGORITHMS exchange.
/// @meas_hash_alg: Hash algorithm for measurement blocks.
/// Selected by responder during NEGOTIATE_ALGORITHMS exchange.
+/// @supported_slots: Bitmask of responder's supported certificate slots.
+/// Received during GET_DIGESTS exchange (from SPDM 1.3).
+/// @provisioned_slots: Bitmask of responder's provisioned certificate slots.
+/// Received during GET_DIGESTS exchange.
/// @base_asym_enc: Human-readable name of @base_asym_alg's signature encoding.
/// Passed to crypto subsystem when calling verify_signature().
/// @sig_len: Signature length of @base_asym_alg (in bytes).
@@ -102,6 +110,8 @@
/// @desc: Synchronous hash context for @base_hash_alg computation.
/// @hash_len: Hash length of @base_hash_alg (in bytes).
/// H in SPDM specification.
+/// @certs: Certificate chain in each of the 8 slots. Empty KVec if a slot is
+/// not populated. Prefixed by the 4 + H header per SPDM 1.0.0 table 15.
#[expect(dead_code)]
pub(crate) struct SpdmState<'a> {
pub(crate) dev: *mut bindings::device,
@@ -116,6 +126,8 @@ pub(crate) struct SpdmState<'a> {
pub(crate) base_asym_alg: u32,
pub(crate) base_hash_alg: u32,
pub(crate) meas_hash_alg: u32,
+ pub(crate) supported_slots: u8,
+ pub(crate) provisioned_slots: u8,
/* Signature algorithm */
base_asym_enc: &'a CStr,
@@ -126,6 +138,9 @@ pub(crate) struct SpdmState<'a> {
pub(crate) shash: *mut bindings::crypto_shash,
pub(crate) desc: *mut bindings::shash_desc,
pub(crate) hash_len: usize,
+
+ // Certificates
+ pub(crate) certs: [KVec<u8>; SPDM_SLOTS],
}
impl Drop for SpdmState<'_> {
@@ -157,12 +172,15 @@ pub(crate) fn new(
base_asym_alg: 0,
base_hash_alg: 0,
meas_hash_alg: 0,
+ supported_slots: 0,
+ provisioned_slots: 0,
base_asym_enc: unsafe { CStr::from_bytes_with_nul_unchecked(b"\0") },
sig_len: 0,
base_hash_alg_name: unsafe { CStr::from_bytes_with_nul_unchecked(b"\0") },
shash: core::ptr::null_mut(),
desc: core::ptr::null_mut(),
hash_len: 0,
+ certs: [const { KVec::new() }; SPDM_SLOTS],
}
}
@@ -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;
+
+ let mut request_buf = request.to_bytes()?;
+
+ let mut response_vec: KVec<u8> = KVec::from_elem(0u8, rsp_sz, GFP_KERNEL)?;
+
+ let len =
+ self.spdm_exchange(request_buf.as_mut_slice(), response_vec.as_mut_slice())? as usize;
+ response_vec.truncate(len);
+
+ let response: GetDigestsRsp = Untrusted::new(response_vec.as_slice()).validate(&*self)?;
+
+ let mut deprovisioned_slots = self.provisioned_slots & !response.header.param2;
+ while (deprovisioned_slots.trailing_zeros() as usize) < SPDM_SLOTS {
+ let slot = deprovisioned_slots.trailing_zeros() as usize;
+ self.certs[slot].clear();
+ deprovisioned_slots &= !(1 << slot);
+ }
+
+ self.provisioned_slots = response.header.param2;
+
+ let supported_slots = if self.version >= SPDM_VER_13 {
+ response.header.param1
+ } else {
+ 0xFF
+ };
+
+ self.supported_slots = supported_slots;
+
+ Ok(())
+ }
}
diff --git a/lib/rspdm/validator.rs b/lib/rspdm/validator.rs
index 58f6a3d436a8..c058dde31440 100644
--- a/lib/rspdm/validator.rs
+++ b/lib/rspdm/validator.rs
@@ -26,6 +26,7 @@
SPDM_CAP_SUPPORTED_ALGORITHMS,
SPDM_CTEXPONENT,
SPDM_GET_CAPABILITIES,
+ SPDM_GET_DIGESTS,
SPDM_GET_VERSION,
SPDM_HASH_ALGOS,
SPDM_MEAS_SPEC_DMTF,
@@ -33,9 +34,11 @@
SPDM_MIN_VER,
SPDM_NEGOTIATE_ALGS,
SPDM_REQ_CAPS,
+ SPDM_SLOTS,
SPDM_VER_10,
SPDM_VER_11,
- SPDM_VER_12, //
+ SPDM_VER_12,
+ SPDM_VER_13, //
};
#[repr(C, packed)]
@@ -697,3 +700,97 @@ fn validate(unvalidated: &[u8], context: &'c SpdmState<'c>) -> Result<Self, Self
})
}
}
+
+pub(crate) struct GetDigestsReq {
+ pub(crate) header: SpdmHeader,
+}
+
+impl GetDigestsReq {
+ pub(crate) fn to_bytes(&self) -> Result<KVec<u8>> {
+ self.header.to_bytes()
+ }
+}
+
+impl Default for GetDigestsReq {
+ fn default() -> Self {
+ GetDigestsReq {
+ header: SpdmHeader::new(SPDM_GET_DIGESTS),
+ }
+ }
+}
+
+#[expect(dead_code)]
+pub(crate) struct GetDigestsRsp {
+ pub(crate) header: SpdmHeader,
+
+ pub(crate) digests: [KVec<u8>; SPDM_SLOTS],
+
+ // KeyPairIDs, added in 1.3
+
+ // CertificateInfo, added in 1.3
+
+ // KeyUsageMask, added in 1.3
+ /// Size of the response, not public
+ length: usize,
+}
+
+impl GetDigestsRsp {
+ #[expect(dead_code)]
+ pub(crate) fn len(&self) -> usize {
+ self.length
+ }
+}
+
+impl<'a, 'c> Validate<'c, Untrusted<&'a [u8]>> for GetDigestsRsp {
+ type Err = Error;
+
+ type Context = &'c SpdmState<'c>;
+
+ fn validate(unvalidated: &[u8], context: &'c SpdmState<'c>) -> Result<Self, Self::Err> {
+ let header: SpdmHeader =
+ Untrusted::new(unvalidated.get(0..4).ok_or(EIO)?).validate(context)?;
+
+ if header.code != SPDM_GET_DIGESTS - 0x80 {
+ return Err(EINVAL);
+ }
+
+ if header.version != context.version {
+ pr_err!("Invalid version response\n");
+ return Err(EPROTO);
+ }
+
+ if header.param2 == 0 {
+ pr_err!("No certificates provisioned\n");
+ return Err(EPROTO);
+ }
+
+ let mut digests: [KVec<u8>; SPDM_SLOTS] = [const { KVec::new() }; SPDM_SLOTS];
+ let mut offset = mem::size_of::<SpdmHeader>();
+ let mut slot_mask = header.param2;
+
+ while (slot_mask.trailing_zeros() as usize) < SPDM_SLOTS {
+ let slot = slot_mask.trailing_zeros() as usize;
+
+ digests[slot].extend_from_slice(
+ unvalidated
+ .get(offset..(offset + context.hash_len))
+ .ok_or(EIO)?,
+ GFP_KERNEL,
+ )?;
+ offset += context.hash_len;
+
+ slot_mask &= !(1 << slot);
+ }
+
+ 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,
+ })
+ }
+}
--
2.55.0
^ permalink raw reply related [flat|nested] 45+ messages in thread
* [PATCH v4 19/22] lib: rspdm: Support SPDM get_certificate
2026-09-28 1:11 [PATCH v4 00/22] lib: Rust implementation of SPDM alistair23
` (17 preceding siblings ...)
2026-09-28 1:11 ` [PATCH v4 18/22] lib: rspdm: Support SPDM get_digests alistair23
@ 2026-09-28 1:31 ` alistair23
2026-09-28 1:31 ` [PATCH v4 20/22] lib: rspdm: Support SPDM certificate validation alistair23
` (3 more replies)
18 siblings, 4 replies; 45+ messages in thread
From: alistair23 @ 2026-09-28 1:31 UTC (permalink / raw)
To: djbw, linux-pci, bhelgaas, linux-kernel, Jonathan.Cameron,
rust-for-linux, lukas, akpm, jic23, alistair, linux-cxl
Cc: aliceryhl, gregkh, benno.lossin, a.hindborg, boqun.feng, ojeda,
alistair23, bjorn3_gh, gary, tmgross, wilfred.mallawa,
alex.gaynor
From: Alistair Francis <alistair@alistair23.me>
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.
Signed-off-by: Alistair Francis <alistair@alistair23.me>
---
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(-)
diff --git a/lib/rspdm/consts.rs b/lib/rspdm/consts.rs
index 11e080815aa6..234534c7aa6d 100644
--- a/lib/rspdm/consts.rs
+++ b/lib/rspdm/consts.rs
@@ -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)]
diff --git a/lib/rspdm/lib.rs b/lib/rspdm/lib.rs
index 92035b1070d1..ee17f4818aeb 100644
--- a/lib/rspdm/lib.rs
+++ b/lib/rspdm/lib.rs
@@ -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_slots as usize) > 0 {
+ let slot = provisioned_slots.trailing_zeros() as u8;
+
+ if let Err(e) = state.get_certificate(slot) {
+ return e.to_errno() as c_int;
+ }
+
+ provisioned_slots &= !(1 << slot);
+ }
+
-(EPROTONOSUPPORT as i32)
}
diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
index 502d99eb13a6..780b9d459abf 100644
--- a/lib/rspdm/state.rs
+++ b/lib/rspdm/state.rs
@@ -52,16 +52,21 @@
use crate::validator::{
GetCapabilitiesReq,
GetCapabilitiesRsp,
+ GetCertificateReq,
+ GetCertificateRsp,
GetDigestsReq,
GetDigestsRsp,
GetVersionReq,
GetVersionRsp,
NegotiateAlgsReq,
NegotiateAlgsRsp,
+ SpdmCertChain,
SpdmErrorRsp,
SpdmHeader,
GET_CAPABILITIES_RSP_SZ,
- NEGOTIATE_ALGS_RSP_SZ, //
+ GET_CERTIFICATE_RSP_HDR_SZ,
+ NEGOTIATE_ALGS_RSP_SZ,
+ SPDM_CERT_CHAIN_HDR_SZ, //
};
/// The current SPDM session state for a device.
@@ -654,4 +659,107 @@ pub(crate) fn get_digests(&mut self) -> Result<(), Error> {
Ok(())
}
+
+ fn get_cert_exchange(
+ &mut self,
+ request_buf: &mut [u8],
+ response_vec: &mut KVec<u8>,
+ ) -> Result<GetCertificateRsp, Error> {
+ let len = self.spdm_exchange(request_buf, response_vec.as_mut_slice())? as usize;
+ response_vec.truncate(len);
+
+ let response: GetCertificateRsp =
+ Untrusted::new(response_vec.as_slice()).validate(&*self)?;
+
+ Ok(response)
+ }
+
+ 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;
+
+ 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)?;
+
+ if (response.header.param1 & 0xF) != slot {
+ pr_err!("Invalid slot response\n");
+ return Err(EPROTO);
+ }
+
+ let portion_length = response.portion_length;
+ let rem_length = response.remainder_length;
+
+ let total_cert_len = portion_length as usize + rem_length as usize;
+
+ let mut certs_buf: KVec<u8> = KVec::new();
+
+ certs_buf.extend_from_slice(&response.cert_chain, GFP_KERNEL)?;
+
+ let mut offset: u16 = portion_length;
+ let mut remainder_length = rem_length as usize;
+
+ while remainder_length > 0 {
+ request.offset = offset;
+ request.length = (remainder_length.min(rsp_sz - GET_CERTIFICATE_RSP_HDR_SZ)) as u16;
+
+ let mut request_buf = request.to_bytes()?;
+
+ response_vec.resize(
+ request.length as usize + GET_CERTIFICATE_RSP_HDR_SZ,
+ 0,
+ GFP_KERNEL,
+ )?;
+
+ let response = self.get_cert_exchange(request_buf.as_mut_slice(), &mut response_vec)?;
+
+ let portion_length = response.portion_length;
+ let rem_length = response.remainder_length;
+
+ if portion_length == 0
+ || (response.header.param1 & 0xF) != slot
+ || offset as usize + portion_length as usize + rem_length as usize != total_cert_len
+ {
+ pr_err!("Malformed certificate response\n");
+ return Err(EPROTO);
+ }
+
+ certs_buf.extend_from_slice(&response.cert_chain, GFP_KERNEL)?;
+ let (val, overflow) = offset.overflowing_add(portion_length);
+ if overflow {
+ pr_err!("portion_length response overflowed\n");
+ return Err(EPROTO);
+ }
+ offset = val;
+ remainder_length = rem_length as usize;
+ }
+
+ let header_length = SPDM_CERT_CHAIN_HDR_SZ + self.hash_len;
+
+ if total_cert_len < header_length || total_cert_len != certs_buf.len() {
+ pr_err!("Malformed certificate chain in slot {slot}\n");
+ return Err(EPROTO);
+ }
+
+ let certs: SpdmCertChain = Untrusted::new(certs_buf.as_slice()).validate(&*self)?;
+ let cert_chain_length = certs.length as usize;
+
+ if total_cert_len != cert_chain_length {
+ pr_err!("Malformed certificate chain in slot {slot}\n");
+ return Err(EPROTO);
+ }
+
+ self.certs[slot as usize].clear();
+ self.certs[slot as usize].extend_from_slice(&certs_buf, GFP_KERNEL)?;
+
+ Ok(())
+ }
}
diff --git a/lib/rspdm/validator.rs b/lib/rspdm/validator.rs
index c058dde31440..f4e9485179d0 100644
--- a/lib/rspdm/validator.rs
+++ b/lib/rspdm/validator.rs
@@ -26,6 +26,7 @@
SPDM_CAP_SUPPORTED_ALGORITHMS,
SPDM_CTEXPONENT,
SPDM_GET_CAPABILITIES,
+ SPDM_GET_CERTIFICATE,
SPDM_GET_DIGESTS,
SPDM_GET_VERSION,
SPDM_HASH_ALGOS,
@@ -794,3 +795,154 @@ fn validate(unvalidated: &[u8], context: &'c SpdmState<'c>) -> Result<Self, Self
})
}
}
+
+pub(crate) struct GetCertificateReq {
+ pub(crate) header: SpdmHeader,
+
+ pub(crate) offset: u16,
+ pub(crate) length: u16,
+}
+
+impl GetCertificateReq {
+ pub(crate) fn to_bytes(&self) -> Result<KVec<u8>> {
+ let mut out = self.header.to_bytes()?;
+
+ out.extend_from_slice(&self.offset.to_le_bytes(), GFP_KERNEL)?;
+ out.extend_from_slice(&self.length.to_le_bytes(), GFP_KERNEL)?;
+
+ Ok(out)
+ }
+}
+
+impl Default for GetCertificateReq {
+ fn default() -> Self {
+ GetCertificateReq {
+ header: SpdmHeader::new(SPDM_GET_CERTIFICATE),
+
+ offset: 0,
+ length: 0,
+ }
+ }
+}
+
+pub(crate) const GET_CERTIFICATE_RSP_HDR_SZ: usize = mem::size_of::<SpdmHeader>() + 4;
+
+pub(crate) const SPDM_CERT_CHAIN_HDR_SZ: usize = mem::size_of::<u16>() + 2;
+
+/// A parsed SPDM certificate chain
+#[expect(dead_code)]
+pub(crate) struct SpdmCertChain {
+ // `length` is a u16 (with 2 bytes reserved) for SPDM versions 1.3
+ // and lower and u32 for 1.4. We don't currently support `LargeOffset`
+ // and `LargeLength`, so let's pretend this is always a u16
+ pub(crate) length: u16,
+
+ pub(crate) root_hash: KVec<u8>,
+
+ pub(crate) certificates: KVec<u8>,
+}
+
+impl<'a, 'c> Validate<'c, Untrusted<&'a [u8]>> for SpdmCertChain {
+ type Err = Error;
+
+ type Context = &'c SpdmState<'c>;
+
+ fn validate(unvalidated: &[u8], context: &'c SpdmState<'c>) -> Result<Self, Self::Err> {
+ 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 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 = 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,
+ )?;
+
+ Ok(SpdmCertChain {
+ length,
+ root_hash,
+ certificates,
+ })
+ }
+}
+
+pub(crate) struct GetCertificateRsp {
+ pub(crate) header: SpdmHeader,
+
+ pub(crate) portion_length: u16,
+ pub(crate) remainder_length: u16,
+
+ pub(crate) cert_chain: KVec<u8>,
+
+ /// Size of the response, not public
+ length: usize,
+}
+
+impl GetCertificateRsp {
+ #[expect(dead_code)]
+ pub(crate) fn len(&self) -> usize {
+ self.length
+ }
+}
+
+impl<'a, 'c> Validate<'c, Untrusted<&'a [u8]>> for GetCertificateRsp {
+ type Err = Error;
+
+ type Context = &'c SpdmState<'c>;
+
+ fn validate(unvalidated: &[u8], context: &'c SpdmState<'c>) -> Result<Self, Self::Err> {
+ let header: SpdmHeader =
+ Untrusted::new(unvalidated.get(0..4).ok_or(EIO)?).validate(context)?;
+
+ if header.code != SPDM_GET_CERTIFICATE - 0x80 {
+ return Err(EINVAL);
+ }
+
+ if header.version != context.version {
+ pr_err!("Invalid version response\n");
+ return Err(EPROTO);
+ }
+
+ let portion_length = u16::from_le_bytes(
+ unvalidated
+ .get(4..6)
+ .ok_or(EIO)?
+ .try_into()
+ .map_err(|_| EINVAL)?,
+ );
+ let remainder_length = u16::from_le_bytes(
+ unvalidated
+ .get(6..8)
+ .ok_or(EIO)?
+ .try_into()
+ .map_err(|_| EINVAL)?,
+ );
+
+ let cert_chain_end = 8 + portion_length as usize;
+
+ let mut cert_chain = KVec::new();
+ cert_chain.extend_from_slice(unvalidated.get(8..cert_chain_end).ok_or(EIO)?, GFP_KERNEL)?;
+
+ Ok(GetCertificateRsp {
+ header,
+ portion_length,
+ remainder_length,
+ cert_chain,
+ length: cert_chain_end,
+ })
+ }
+}
--
2.55.0
^ permalink raw reply related [flat|nested] 45+ messages in thread
* [PATCH v4 20/22] lib: rspdm: Support SPDM certificate validation
2026-09-28 1:31 ` [PATCH v4 19/22] lib: rspdm: Support SPDM get_certificate alistair23
@ 2026-09-28 1:31 ` alistair23
2026-09-28 1:47 ` sashiko-bot
2026-09-28 1:31 ` [PATCH v4 21/22] rust: allow extracting the buffer from a CString alistair23
` (2 subsequent siblings)
3 siblings, 1 reply; 45+ messages in thread
From: alistair23 @ 2026-09-28 1:31 UTC (permalink / raw)
To: djbw, linux-pci, bhelgaas, linux-kernel, Jonathan.Cameron,
rust-for-linux, lukas, akpm, jic23, alistair, linux-cxl
Cc: aliceryhl, gregkh, benno.lossin, a.hindborg, boqun.feng, ojeda,
alistair23, bjorn3_gh, gary, tmgross, wilfred.mallawa,
alex.gaynor
From: Alistair Francis <alistair@alistair23.me>
Support validating the SPDM certificate chain. This only performs basic
sanity checks on the chain before we continue on. This does not ensure
that the root CA is trusted, we leave that for userspace to check and
enforce. Instead we just make sure that the chain is correct, uses
supported signatures and that it isn't blacklisted in the kernel.
We then store the first leaf certificate for use later.
Signed-off-by: Alistair Francis <alistair@alistair23.me>
---
lib/rspdm/lib.rs | 12 +++
lib/rspdm/state.rs | 147 +++++++++++++++++++++++++++++++-
rust/bindings/bindings_helper.h | 2 +
3 files changed, 160 insertions(+), 1 deletion(-)
diff --git a/lib/rspdm/lib.rs b/lib/rspdm/lib.rs
index ee17f4818aeb..50ff92741086 100644
--- a/lib/rspdm/lib.rs
+++ b/lib/rspdm/lib.rs
@@ -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_slots as 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)
}
diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
index 780b9d459abf..9f7f6d0ffac6 100644
--- a/lib/rspdm/state.rs
+++ b/lib/rspdm/state.rs
@@ -117,7 +117,8 @@
/// H in SPDM specification.
/// @certs: Certificate chain in each of the 8 slots. Empty KVec if a slot is
/// not populated. Prefixed by the 4 + H header per SPDM 1.0.0 table 15.
-#[expect(dead_code)]
+/// @leaf_key: Public key portion of leaf certificate against which to check
+/// responder's signatures.
pub(crate) struct SpdmState<'a> {
pub(crate) dev: *mut bindings::device,
pub(crate) transport: bindings::spdm_transport,
@@ -146,10 +147,20 @@ pub(crate) struct SpdmState<'a> {
// Certificates
pub(crate) certs: [KVec<u8>; SPDM_SLOTS],
+ pub(crate) leaf_key: Option<*mut bindings::public_key>,
}
impl Drop for SpdmState<'_> {
fn drop(&mut self) {
+ if let Some(leaf_key) = self.leaf_key.take() {
+ // SAFETY: `leaf_key` was extracted from a x509 certificate
+ // in `validate_cert_chain()` so it is valid to pass to
+ // `public_key_free()`.
+ unsafe {
+ bindings::public_key_free(leaf_key);
+ }
+ }
+
self.free_desc();
unsafe {
@@ -186,6 +197,7 @@ pub(crate) fn new(
desc: core::ptr::null_mut(),
hash_len: 0,
certs: [const { KVec::new() }; SPDM_SLOTS],
+ leaf_key: None,
}
}
@@ -762,4 +774,137 @@ pub(crate) fn get_certificate(&mut self, slot: u8) -> Result<(), Error> {
Ok(())
}
+
+ pub(crate) fn validate_cert_chain(&mut self, slot: u8) -> Result<(), Error> {
+ let cert_chain_buf = &self.certs[slot as usize];
+ let cert_chain_len = cert_chain_buf.len();
+ // We skip over the RootHash
+ let header_len = 4 + self.hash_len;
+
+ let mut offset = header_len;
+ let mut prev_cert: Option<*mut bindings::x509_certificate> = None;
+
+ if offset >= cert_chain_len {
+ return Err(EPROTO);
+ }
+
+ while offset < cert_chain_len {
+ // SAFETY: `cert_chain_buf[offset..]` is a non-empty slice of
+ // bytes valid for at least `cert_chain_len` bytes.
+ let cert_len = unsafe {
+ bindings::x509_get_certificate_length(
+ &cert_chain_buf[offset..] as *const _ as *const u8,
+ cert_chain_len - offset,
+ )
+ };
+
+ if cert_len < 0 {
+ pr_err!("Invalid certificate length\n");
+
+ if let Some(prev) = prev_cert {
+ // SAFETY: `prev_cert` is the previously parsed
+ // certificate from a prior loop iteration.
+ unsafe { bindings::x509_free_certificate(prev) };
+ }
+
+ to_result(cert_len as i32)?;
+ }
+
+ // SAFETY: `cert_chain_buf[offset..]` is a non-empty slice of
+ // bytes valid for at least `cert_len` bytes.
+ let cert_ptr = unsafe {
+ match from_err_ptr(bindings::x509_cert_parse(
+ &cert_chain_buf[offset..] as *const _ as *const c_void,
+ cert_len as usize,
+ )) {
+ Err(e) => {
+ if let Some(prev) = prev_cert {
+ // SAFETY: `prev_cert` is the previously parsed
+ // certificate from a prior loop iteration.
+ bindings::x509_free_certificate(prev);
+ }
+ return Err(e);
+ }
+ Ok(c) => c,
+ }
+ };
+ // SAFETY: Cast the `struct x509_certificate` to a Rust binding
+ let cert = unsafe { *cert_ptr };
+
+ if cert.unsupported_sig || cert.blacklisted {
+ pr_err!("Certificate was rejected\n");
+
+ if let Some(prev) = prev_cert {
+ // SAFETY: `prev_cert` is the previously parsed
+ // certificate from a prior loop iteration.
+ unsafe { bindings::x509_free_certificate(prev) };
+ }
+ // SAFETY: `cert_ptr` was just returned by
+ // `x509_cert_parse()`.
+ unsafe { bindings::x509_free_certificate(cert_ptr) };
+
+ return Err(EKEYREJECTED);
+ }
+
+ if let Some(prev) = prev_cert {
+ // SAFETY: `prev_cert` is the previously parsed
+ // certificate from a prior loop iteration.
+ let rc = unsafe { bindings::public_key_verify_signature((*prev).pub_, cert.sig) };
+
+ if rc < 0 {
+ pr_err!("Signature validation error\n");
+
+ // SAFETY: `prev_cert` is the previously parsed
+ // certificate from a prior loop iteration.
+ unsafe { bindings::x509_free_certificate(prev) };
+
+ // SAFETY: `cert_ptr` was just returned by
+ // `x509_cert_parse()`.
+ unsafe { bindings::x509_free_certificate(cert_ptr) };
+
+ to_result(rc)?;
+ }
+ }
+
+ if let Some(prev) = prev_cert {
+ // SAFETY: `prev_cert` is the previously parsed
+ // certificate from a prior loop iteration.
+ unsafe { bindings::x509_free_certificate(prev) };
+ }
+
+ prev_cert = Some(cert_ptr);
+ offset += cert_len as usize;
+ }
+
+ if let Some(prev) = prev_cert {
+ if let Some(validate) = self.validate {
+ // SAFETY: Call the `validate` function provided.
+ let rc = unsafe { validate(self.dev, slot, prev) };
+ if let Err(e) = to_result(rc) {
+ // SAFETY: `prev_cert` is the previously parsed
+ // certificate from a prior loop iteration.
+ unsafe { bindings::x509_free_certificate(prev) };
+ return Err(e);
+ }
+ }
+
+ // The leaf key is the same for all slots (MULT_KEY_CAP == 0),
+ // so just store the first one.
+ if self.leaf_key.is_none() {
+ // SAFETY: `prev_cert` is the previously parsed
+ // certificate from a prior loop iteration.
+ self.leaf_key = unsafe { Some((*prev).pub_) };
+ // SAFETY: `prev_cert` is the previously parsed
+ // certificate from a prior loop iteration. We are setting
+ // the `pub` key to null so it isn't freed below
+ unsafe { (*prev).pub_ = core::ptr::null_mut() };
+ }
+
+ // SAFETY: `prev_cert` is the previously parsed
+ // certificate from a prior loop iteration.
+ unsafe { bindings::x509_free_certificate(prev) };
+ }
+
+ Ok(())
+ }
}
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index d2781c27794b..5cc71552b524 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -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>
--
2.55.0
^ permalink raw reply related [flat|nested] 45+ messages in thread
* [PATCH v4 21/22] rust: allow extracting the buffer from a CString
2026-09-28 1:31 ` [PATCH v4 19/22] lib: rspdm: Support SPDM get_certificate alistair23
2026-09-28 1:31 ` [PATCH v4 20/22] lib: rspdm: Support SPDM certificate validation alistair23
@ 2026-09-28 1:31 ` alistair23
2026-09-28 1:41 ` sashiko-bot
2026-09-28 1:31 ` [PATCH v4 22/22] lib: rspdm: Support SPDM challenge alistair23
2026-09-28 1:43 ` [PATCH v4 19/22] lib: rspdm: Support SPDM get_certificate sashiko-bot
3 siblings, 1 reply; 45+ messages in thread
From: alistair23 @ 2026-09-28 1:31 UTC (permalink / raw)
To: djbw, linux-pci, bhelgaas, linux-kernel, Jonathan.Cameron,
rust-for-linux, lukas, akpm, jic23, alistair, linux-cxl
Cc: aliceryhl, gregkh, benno.lossin, a.hindborg, boqun.feng, ojeda,
alistair23, bjorn3_gh, gary, tmgross, wilfred.mallawa,
alex.gaynor
From: Alistair Francis <alistair@alistair23.me>
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.
Signed-off-by: Alistair Francis <alistair@alistair23.me>
Reviewed-by: Gary Guo <gary@garyguo.net>
---
rust/kernel/str.rs | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/rust/kernel/str.rs b/rust/kernel/str.rs
index a556788bcc5e..3fd8a218547f 100644
--- a/rust/kernel/str.rs
+++ b/rust/kernel/str.rs
@@ -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 {
--
2.55.0
^ permalink raw reply related [flat|nested] 45+ messages in thread
* [PATCH v4 22/22] lib: rspdm: Support SPDM challenge
2026-09-28 1:31 ` [PATCH v4 19/22] lib: rspdm: Support SPDM get_certificate alistair23
2026-09-28 1:31 ` [PATCH v4 20/22] lib: rspdm: Support SPDM certificate validation alistair23
2026-09-28 1:31 ` [PATCH v4 21/22] rust: allow extracting the buffer from a CString alistair23
@ 2026-09-28 1:31 ` alistair23
2026-09-28 1:46 ` sashiko-bot
2026-09-28 1:43 ` [PATCH v4 19/22] lib: rspdm: Support SPDM get_certificate sashiko-bot
3 siblings, 1 reply; 45+ messages in thread
From: alistair23 @ 2026-09-28 1:31 UTC (permalink / raw)
To: djbw, linux-pci, bhelgaas, linux-kernel, Jonathan.Cameron,
rust-for-linux, lukas, akpm, jic23, alistair, linux-cxl
Cc: aliceryhl, gregkh, benno.lossin, a.hindborg, boqun.feng, ojeda,
alistair23, bjorn3_gh, gary, tmgross, wilfred.mallawa,
alex.gaynor
From: Alistair Francis <alistair@alistair23.me>
Support the CHALLENGE SPDM command.
Signed-off-by: Alistair Francis <alistair@alistair23.me>
---
lib/rspdm/consts.rs | 6 +
lib/rspdm/lib.rs | 10 +-
lib/rspdm/state.rs | 215 +++++++++++++++++++++++++++++++-
lib/rspdm/validator.rs | 153 ++++++++++++++++++++++-
rust/bindings/bindings_helper.h | 1 +
5 files changed, 375 insertions(+), 10 deletions(-)
diff --git a/lib/rspdm/consts.rs b/lib/rspdm/consts.rs
index 234534c7aa6d..d4d743f260c6 100644
--- a/lib/rspdm/consts.rs
+++ b/lib/rspdm/consts.rs
@@ -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;
diff --git a/lib/rspdm/lib.rs b/lib/rspdm/lib.rs
index 50ff92741086..e555c565faae 100644
--- a/lib/rspdm/lib.rs
+++ b/lib/rspdm/lib.rs
@@ -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_slots as u8) {
+ return e.to_errno() as c_int;
+ }
+
+ 0
}
/// spdm_destroy() - Destroy SPDM session
diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
index 9f7f6d0ffac6..d6ece65a6fc0 100644
--- a/lib/rspdm/state.rs
+++ b/lib/rspdm/state.rs
@@ -18,6 +18,7 @@
Error, //
},
str::CStr,
+ str::CString,
validate::Untrusted,
};
@@ -30,6 +31,7 @@
SPDM_ASYM_RSASSA_2048,
SPDM_ASYM_RSASSA_3072,
SPDM_ASYM_RSASSA_4096,
+ SPDM_COMBINED_PREFIX_SZ,
SPDM_ERROR,
SPDM_GET_VERSION_LEN,
SPDM_HASH_ALGOS,
@@ -37,10 +39,12 @@
SPDM_HASH_SHA_384,
SPDM_HASH_SHA_512,
SPDM_KEY_EX_CAP,
+ SPDM_MAX_OPAQUE_DATA,
SPDM_MAX_VER,
SPDM_MEAS_RESP_CAP,
SPDM_MIN_VER,
SPDM_OPAQUE_DATA_FMT_GENERAL,
+ SPDM_PREFIX_SZ,
SPDM_REQ,
SPDM_RSP_MIN_CAPS,
SPDM_SLOTS,
@@ -50,6 +54,8 @@
SPDM_VER_13, //
};
use crate::validator::{
+ ChallengeReq,
+ ChallengeRsp,
GetCapabilitiesReq,
GetCapabilitiesRsp,
GetCertificateReq,
@@ -69,6 +75,8 @@
SPDM_CERT_CHAIN_HDR_SZ, //
};
+const SPDM_CONTEXT: &str = "responder-challenge_auth signing";
+
/// The current SPDM session state for a device.
///
/// Concurrent access is serialised by wrapping the whole struct in a
@@ -119,6 +127,12 @@
/// not populated. Prefixed by the 4 + H header per SPDM 1.0.0 table 15.
/// @leaf_key: Public key portion of leaf certificate against which to check
/// responder's signatures.
+/// @transcript: Concatenation of all SPDM messages exchanged during an
+/// authentication or measurement sequence. Used to verify the signature,
+/// as it is computed over the hashed transcript.
+/// @next_nonce: Requester nonce to be used for the next authentication
+/// sequence. Populated from user space through sysfs.
+/// If user space does not provide a nonce, the kernel uses a random one.
pub(crate) struct SpdmState<'a> {
pub(crate) dev: *mut bindings::device,
pub(crate) transport: bindings::spdm_transport,
@@ -137,7 +151,7 @@ pub(crate) struct SpdmState<'a> {
/* Signature algorithm */
base_asym_enc: &'a CStr,
- sig_len: usize,
+ pub(crate) sig_len: usize,
/* Hash algorithm */
base_hash_alg_name: &'a CStr,
@@ -148,6 +162,10 @@ pub(crate) struct SpdmState<'a> {
// Certificates
pub(crate) certs: [KVec<u8>; SPDM_SLOTS],
pub(crate) leaf_key: Option<*mut bindings::public_key>,
+
+ transcript: VVec<u8>,
+
+ pub(crate) next_nonce: KVec<u8>,
}
impl Drop for SpdmState<'_> {
@@ -198,6 +216,8 @@ pub(crate) fn new(
hash_len: 0,
certs: [const { KVec::new() }; SPDM_SLOTS],
leaf_key: None,
+ transcript: VVec::new(),
+ next_nonce: KVec::new(),
}
}
@@ -329,13 +349,15 @@ fn spdm_err(&self, rsp: &SpdmErrorRsp) -> Result<(), Error> {
/// The data in `request_buf` is sent to the device and the response is
/// stored in `response_buf`.
pub(crate) fn spdm_exchange(
- &self,
+ &mut self,
request_buf: &mut [u8],
response_buf: &mut [u8],
) -> Result<i32, Error> {
let header_size = core::mem::size_of::<SpdmHeader>();
let request: SpdmHeader = Untrusted::new(&request_buf[..]).validate(&*self)?;
+ self.transcript.extend_from_slice(request_buf, GFP_KERNEL)?;
+
let transport_function = self.transport.ok_or(EINVAL)?;
// SAFETY: `transport_function` is provided by the new(), we are
// calling the function.
@@ -385,6 +407,18 @@ pub(crate) fn get_version(&mut self) -> Result<(), Error> {
request.header.version = SPDM_MIN_VER;
self.version = SPDM_MIN_VER;
+ self.transcript.clear();
+
+ // Clear the leaf cert as we are re-authenticating
+ if let Some(leaf_key) = self.leaf_key.take() {
+ // SAFETY: `leaf_key` was extracted from a x509 certificate
+ // in `validate_cert_chain()` so it is valid to pass to
+ // `public_key_free()`.
+ unsafe {
+ bindings::public_key_free(leaf_key);
+ }
+ }
+
let mut request_buf = request.to_bytes()?;
let mut response_vec: KVec<u8> = KVec::from_elem(0u8, SPDM_GET_VERSION_LEN, GFP_KERNEL)?;
@@ -400,6 +434,9 @@ pub(crate) fn get_version(&mut self) -> Result<(), Error> {
let response: GetVersionRsp = Untrusted::new(response_vec.as_slice()).validate(&*self)?;
+ self.transcript
+ .extend_from_slice(response_vec.get(..response.len()).ok_or(EIO)?, GFP_KERNEL)?;
+
let mut foundver = false;
for &entry in response.version_number_entries.iter() {
let alpha_version = (entry & 0xF) as u8;
@@ -452,7 +489,11 @@ pub(crate) fn get_capabilities(&mut self) -> Result<(), Error> {
let response: GetCapabilitiesRsp =
Untrusted::new(response_vec.as_slice()).validate(&*self)?;
+ self.transcript
+ .extend_from_slice(response_vec.get(..response.len()).ok_or(EIO)?, GFP_KERNEL)?;
+
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",
@@ -601,6 +642,9 @@ pub(crate) fn negotiate_algs(&mut self) -> Result<(), Error> {
let response: NegotiateAlgsRsp =
Untrusted::new(response_vec.as_slice()).validate(&*self)?;
+ self.transcript
+ .extend_from_slice(response_vec.get(..response.len()).ok_or(EIO)?, GFP_KERNEL)?;
+
self.base_asym_alg = response.base_asym_sel;
self.base_hash_alg = response.base_hash_sel;
self.meas_hash_alg = response.measurement_hash_algo;
@@ -652,6 +696,9 @@ pub(crate) fn get_digests(&mut self) -> Result<(), Error> {
let response: GetDigestsRsp = Untrusted::new(response_vec.as_slice()).validate(&*self)?;
+ self.transcript
+ .extend_from_slice(response_vec.get(..response.len()).ok_or(EIO)?, GFP_KERNEL)?;
+
let mut deprovisioned_slots = self.provisioned_slots & !response.header.param2;
while (deprovisioned_slots.trailing_zeros() as usize) < SPDM_SLOTS {
let slot = deprovisioned_slots.trailing_zeros() as usize;
@@ -683,6 +730,9 @@ fn get_cert_exchange(
let response: GetCertificateRsp =
Untrusted::new(response_vec.as_slice()).validate(&*self)?;
+ self.transcript
+ .extend_from_slice(response_vec.get(..response.len()).ok_or(EIO)?, GFP_KERNEL)?;
+
Ok(response)
}
@@ -907,4 +957,165 @@ pub(crate) fn validate_cert_chain(&mut self, slot: u8) -> Result<(), Error> {
Ok(())
}
+
+ pub(crate) fn challenge_rsp_len(&mut self, nonce_len: usize, opaque_len: usize) -> usize {
+ // No measurement summary hash requested (MSHLength == 0)
+ let mut length =
+ core::mem::size_of::<SpdmHeader>() + self.hash_len + nonce_len + opaque_len + 2;
+
+ if self.version >= SPDM_VER_13 {
+ length += 8;
+ }
+
+ length + self.sig_len
+ }
+
+ fn verify_signature(&mut self, signature: &mut [u8]) -> Result<(), Error> {
+ let mut sig = bindings::public_key_signature::default();
+
+ sig.s = signature as *mut _ as *mut u8;
+ sig.s_size = self.sig_len as u32;
+ sig.encoding = self.base_asym_enc.as_ptr() as *const u8;
+ sig.hash_algo = self.base_hash_alg_name.as_ptr() as *const u8;
+
+ let mut m: KVec<u8> = KVec::new();
+ m.extend_with(SPDM_COMBINED_PREFIX_SZ + self.hash_len, 0, GFP_KERNEL)?;
+
+ if !self.desc.is_null() {
+ unsafe { (*self.desc).tfm = self.shash };
+
+ // Generate the `unverified_message_hash`, which is the the hash of
+ // the `unverified_data` (Chapter 16 - "Signature Verification")
+ unsafe {
+ to_result(bindings::crypto_shash_digest(
+ self.desc,
+ self.transcript.as_ptr(),
+ (self.transcript.len() - self.sig_len) as u32,
+ m[SPDM_COMBINED_PREFIX_SZ..].as_mut_ptr(),
+ ))?;
+ };
+ } else {
+ return Err(EPROTO);
+ }
+
+ // Must be in scope whenenver `sig` is used
+ let mut mhash: KVec<u8> = KVec::new();
+
+ if self.version <= SPDM_VER_11 {
+ sig.m = m[SPDM_COMBINED_PREFIX_SZ..].as_mut_ptr();
+ } else {
+ let major = self.version >> 4;
+ let minor = self.version & 0xF;
+
+ // Generate the `spdm_prefix` (Chapter 15 - "Signature generation")
+ let prefix = CString::try_from_fmt(fmt!("dmtf-spdm-v{major:x}.{minor:x}.*dmtf-spdm-v{major:x}.{minor:x}.*dmtf-spdm-v{major:x}.{minor:x}.*dmtf-spdm-v{major:x}.{minor:x}.*"))?;
+ let mut buf = prefix.into_vec();
+ let zero_pad_len = SPDM_COMBINED_PREFIX_SZ - SPDM_PREFIX_SZ - SPDM_CONTEXT.len() - 1;
+
+ // Copy in the `zero_pad` and `spdm_context` to form the `combined_spdm_prefix`
+ // (Chapter 15 - "Signature generation")
+ buf.extend_with(zero_pad_len, 0, GFP_KERNEL)?;
+ buf.extend_from_slice(SPDM_CONTEXT.as_bytes(), GFP_KERNEL)?;
+
+ // The `combined_spdm_prefix` must be 100 bytes
+ // (Chapter 15 - "Signature generation")
+ if buf.len() != SPDM_COMBINED_PREFIX_SZ {
+ pr_err!("combined_spdm_prefix calculation is incorrect");
+ return Err(EPROTO);
+ }
+
+ // `M` shall be the concatenation of the `combined_spdm_prefix`
+ // and `unverified_message_hash`. We use `M` for pub/priv
+ // signature verification below.
+ // (Chapter 16 - "Signature Verification")
+ m[..SPDM_COMBINED_PREFIX_SZ].copy_from_slice(&buf);
+
+ if !self.desc.is_null() {
+ unsafe { (*self.desc).tfm = self.shash };
+ // Generate a hash of `M` and use that for signature
+ // verification (RSA or ECDSA)
+ mhash.extend_with(self.hash_len, 0, GFP_KERNEL)?;
+
+ unsafe {
+ to_result(bindings::crypto_shash_digest(
+ self.desc,
+ m.as_ptr(),
+ m.len() as u32,
+ mhash.as_mut_ptr(),
+ ))?;
+ };
+ } else {
+ return Err(EPROTO);
+ }
+
+ sig.m = mhash.as_mut_ptr();
+ }
+
+ sig.m_size = self.hash_len as u32;
+
+ // Finally verify the generated signature against the public key
+ if let Some(leaf_key) = self.leaf_key {
+ unsafe { to_result(bindings::public_key_verify_signature(leaf_key, &sig)) }
+ } else {
+ return Err(EPROTO);
+ }
+ }
+
+ pub(crate) fn challenge(&mut self, slot: u8) -> Result<(), Error> {
+ let mut request = ChallengeReq::default();
+ request.header.version = self.version;
+ request.header.param1 = slot;
+
+ let nonce_len = request.nonce.len();
+
+ if self.next_nonce.len() > 0 {
+ let request_nonce_len = request.nonce.len();
+
+ if self.next_nonce.len() == request_nonce_len {
+ request
+ .nonce
+ .copy_from_slice(&self.next_nonce[..request_nonce_len]);
+ } else {
+ return Err(EINVAL);
+ }
+
+ self.next_nonce.clear();
+ } else {
+ unsafe {
+ bindings::get_random_bytes(&mut request.nonce as *mut _ as *mut c_void, nonce_len)
+ };
+ }
+
+ let rsp_sz = self.challenge_rsp_len(nonce_len, SPDM_MAX_OPAQUE_DATA);
+
+ let mut request_buf = request.to_bytes()?;
+
+ let mut response_vec: KVec<u8> = KVec::from_elem(0u8, rsp_sz, GFP_KERNEL)?;
+
+ let rc =
+ self.spdm_exchange(request_buf.as_mut_slice(), response_vec.as_mut_slice())? as usize;
+ response_vec.truncate(rc);
+
+ 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) {
+ Ok(()) => {
+ pr_info!("Authenticated with certificate slot {slot}\n");
+ Ok(())
+ }
+ Err(e) => {
+ pr_err!("Cannot verify challenge_auth signature: {e:?}\n");
+ Err(EPROTO)
+ }
+ }
+ }
}
diff --git a/lib/rspdm/validator.rs b/lib/rspdm/validator.rs
index f4e9485179d0..9992679f7c8a 100644
--- a/lib/rspdm/validator.rs
+++ b/lib/rspdm/validator.rs
@@ -24,12 +24,14 @@
use crate::consts::{
SPDM_ASYM_ALGOS,
SPDM_CAP_SUPPORTED_ALGORITHMS,
+ SPDM_CHALLENGE,
SPDM_CTEXPONENT,
SPDM_GET_CAPABILITIES,
SPDM_GET_CERTIFICATE,
SPDM_GET_DIGESTS,
SPDM_GET_VERSION,
SPDM_HASH_ALGOS,
+ SPDM_MAX_OPAQUE_DATA,
SPDM_MEAS_SPEC_DMTF,
SPDM_MIN_DATA_TRANSFER_SIZE,
SPDM_MIN_VER,
@@ -142,7 +144,6 @@ pub(crate) struct GetVersionRsp {
}
impl GetVersionRsp {
- #[expect(dead_code)]
pub(crate) fn len(&self) -> usize {
self.length
}
@@ -395,7 +396,6 @@ pub(crate) struct GetCapabilitiesRsp {
}
impl GetCapabilitiesRsp {
- #[expect(dead_code)]
pub(crate) fn len(&self) -> usize {
self.length
}
@@ -577,7 +577,6 @@ pub(crate) struct NegotiateAlgsRsp {
}
impl NegotiateAlgsRsp {
- #[expect(dead_code)]
pub(crate) fn len(&self) -> usize {
self.length
}
@@ -736,7 +735,6 @@ pub(crate) struct GetDigestsRsp {
}
impl GetDigestsRsp {
- #[expect(dead_code)]
pub(crate) fn len(&self) -> usize {
self.length
}
@@ -893,7 +891,6 @@ pub(crate) struct GetCertificateRsp {
}
impl GetCertificateRsp {
- #[expect(dead_code)]
pub(crate) fn len(&self) -> usize {
self.length
}
@@ -946,3 +943,149 @@ fn validate(unvalidated: &[u8], context: &'c SpdmState<'c>) -> Result<Self, Self
})
}
}
+
+pub(crate) struct ChallengeReq {
+ pub(crate) header: SpdmHeader,
+
+ pub(crate) nonce: [u8; 32],
+ pub(crate) context: [u8; 8],
+}
+
+impl ChallengeReq {
+ pub(crate) fn to_bytes(&self) -> Result<KVec<u8>> {
+ let mut out = self.header.to_bytes()?;
+
+ out.extend_from_slice(&self.nonce, GFP_KERNEL)?;
+
+ if self.header.version > SPDM_VER_12 {
+ out.extend_from_slice(&self.context, GFP_KERNEL)?;
+ }
+
+ Ok(out)
+ }
+}
+
+impl Default for ChallengeReq {
+ fn default() -> Self {
+ ChallengeReq {
+ header: SpdmHeader::new(SPDM_CHALLENGE),
+
+ nonce: [0; 32],
+ context: [0; 8],
+ }
+ }
+}
+
+#[expect(dead_code)]
+pub(crate) struct ChallengeRsp {
+ pub(crate) header: SpdmHeader,
+
+ pub(crate) cert_chain_hash: KVec<u8>,
+ pub(crate) nonce: [u8; 32],
+
+ pub(crate) opaque_data_len: u16,
+ pub(crate) opaque_data: KVec<u8>,
+
+ /// `RequesterContext`, only present in SPDM 1.3 and later, zeroed otherwise.
+ pub(crate) context: [u8; 8],
+
+ pub(crate) signature: KVec<u8>,
+
+ /// Size of the response, not public
+ length: usize,
+}
+
+impl ChallengeRsp {
+ pub(crate) fn len(&self) -> usize {
+ self.length
+ }
+}
+
+impl<'a, 'c> Validate<'c, Untrusted<&'a [u8]>> for ChallengeRsp {
+ type Err = Error;
+
+ type Context = &'c SpdmState<'c>;
+
+ fn validate(unvalidated: &[u8], context: &'c SpdmState<'c>) -> Result<Self, Self::Err> {
+ let header: SpdmHeader =
+ Untrusted::new(unvalidated.get(0..4).ok_or(EIO)?).validate(context)?;
+
+ if header.code != SPDM_CHALLENGE - 0x80 {
+ return Err(EINVAL);
+ }
+
+ if header.version != context.version {
+ pr_err!("Invalid version response\n");
+ return Err(EPROTO);
+ }
+
+ let hash_len = context.hash_len;
+
+ let nonce_offset = mem::size_of::<SpdmHeader>() + hash_len;
+ // MSHLength (H) is always zero as we don't support it
+ let opaque_len_offset = nonce_offset + 32;
+ let opaque_offset = opaque_len_offset + 2;
+
+ 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,
+ )?;
+
+ let mut nonce = [0u8; 32];
+ nonce.copy_from_slice(
+ unvalidated
+ .get(nonce_offset..nonce_offset + 32)
+ .ok_or(EIO)?,
+ );
+
+ let opaque_data_len = u16::from_le_bytes(
+ unvalidated
+ .get(opaque_len_offset..opaque_len_offset + 2)
+ .ok_or(EIO)?
+ .try_into()
+ .map_err(|_| EINVAL)?,
+ );
+
+ if opaque_data_len > SPDM_MAX_OPAQUE_DATA as u16 {
+ return Err(EINVAL);
+ }
+
+ let opaque_end = opaque_offset + opaque_data_len as usize;
+ let mut opaque_data = KVec::new();
+ opaque_data.extend_from_slice(
+ unvalidated.get(opaque_offset..opaque_end).ok_or(EIO)?,
+ GFP_KERNEL,
+ )?;
+
+ // `RequesterContext` (8 bytes) is only present in SPDM 1.3 and later.
+ let mut req_context = [0u8; 8];
+ let context_end;
+ if context.version >= SPDM_VER_13 {
+ context_end = opaque_end + 8;
+ req_context.copy_from_slice(unvalidated.get(opaque_end..context_end).ok_or(EIO)?);
+ } else {
+ context_end = opaque_end;
+ }
+
+ let signature_end = context_end + context.sig_len;
+ let mut signature = KVec::new();
+ signature.extend_from_slice(
+ unvalidated.get(context_end..signature_end).ok_or(EIO)?,
+ GFP_KERNEL,
+ )?;
+
+ Ok(ChallengeRsp {
+ header,
+ cert_chain_hash,
+ nonce,
+ opaque_data_len,
+ opaque_data,
+ context: req_context,
+ signature,
+ length: signature_end,
+ })
+ }
+}
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 5cc71552b524..f3d3f6e9f60a 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -31,6 +31,7 @@
#include <linux/acpi.h>
#include <linux/gpu_buddy.h>
#include <crypto/hash.h>
+#include <crypto/public_key.h>
#include <drm/drm_device.h>
#include <drm/drm_drv.h>
#include <drm/drm_file.h>
--
2.55.0
^ permalink raw reply related [flat|nested] 45+ messages in thread
* Re: [PATCH v4 02/22] rust: transmute: add `cast_slice[_mut]` functions
2026-09-28 1:11 ` [PATCH v4 02/22] rust: transmute: add `cast_slice[_mut]` functions alistair23
@ 2026-09-28 1:35 ` sashiko-bot
0 siblings, 0 replies; 45+ messages in thread
From: sashiko-bot @ 2026-09-28 1:35 UTC (permalink / raw)
To: alistair23; +Cc: linux-cxl, gary, ojeda, linux-pci
> From: Benno Lossin <benno.lossin@proton.me>
>
> Add functions to make casting slices only one `unsafe` block.
>
> Signed-off-by: Benno Lossin <benno.lossin@proton.me>
> Message-ID: <20250814124424.516191-2-lossin@kernel.org>
> Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260928011123.450800-1-alistair.francis@wdc.com?part=2
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v4 09/22] rust: error: impl From<FromBytesWithNulError> for Kernel Error
2026-09-28 1:11 ` [PATCH v4 09/22] rust: error: impl From<FromBytesWithNulError> for Kernel Error alistair23
@ 2026-09-28 1:36 ` sashiko-bot
0 siblings, 0 replies; 45+ messages in thread
From: sashiko-bot @ 2026-09-28 1:36 UTC (permalink / raw)
To: alistair23; +Cc: linux-cxl, ojeda, gary, linux-pci
> From: Alistair Francis <alistair.francis@wdc.com>
>
> 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 <alistair.francis@wdc.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260928011123.450800-1-alistair.francis@wdc.com?part=9
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v4 07/22] X.509: Move certificate length retrieval into new helper
2026-09-28 1:11 ` [PATCH v4 07/22] X.509: Move certificate length retrieval into new helper alistair23
@ 2026-09-28 1:36 ` sashiko-bot
0 siblings, 0 replies; 45+ messages in thread
From: sashiko-bot @ 2026-09-28 1:36 UTC (permalink / raw)
To: alistair23; +Cc: linux-cxl, gary, ojeda, linux-pci
> From: Lukas Wunner <lukas@wunner.de>
>
> The upcoming in-kernel SPDM library (Security Protocol and Data Model,
> https://www.dmtf.org/dsp/DSP0274) needs to retrieve the length from
> ASN.1 DER-encoded X.509 certificates.
>
> Such code already exists in x509_load_certificate_list(), so move it
> into a new helper for reuse by SPDM.
>
> Export the helper so that SPDM can be tristate. (Some upcoming users of
> the SPDM libray may be modular, such as SCSI and ATA.)
>
> No functional change intended.
>
> Signed-off-by: Lukas Wunner <lukas@wunner.de>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260928011123.450800-1-alistair.francis@wdc.com?part=7
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v4 06/22] X.509: Parse Subject Alternative Name in certificates
2026-09-28 1:11 ` [PATCH v4 06/22] X.509: Parse Subject Alternative Name in certificates alistair23
@ 2026-09-28 1:39 ` sashiko-bot
0 siblings, 0 replies; 45+ messages in thread
From: sashiko-bot @ 2026-09-28 1:39 UTC (permalink / raw)
To: alistair23; +Cc: ojeda, linux-pci, linux-cxl, gary
> From: Lukas Wunner <lukas@wunner.de>
>
> The upcoming support for PCI device authentication with CMA-SPDM
> (PCIe r6.1 sec 6.31) requires validating the Subject Alternative Name
> in X.509 certificates.
>
> Store a pointer to the Subject Alternative Name upon parsing for
> consumption by CMA-SPDM.
>
> Signed-off-by: Lukas Wunner <lukas@wunner.de>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260928011123.450800-1-alistair.francis@wdc.com?part=6
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v4 15/22] lib: rspdm: Support SPDM get_version
2026-09-28 1:11 ` [PATCH v4 15/22] lib: rspdm: Support SPDM get_version alistair23
@ 2026-09-28 1:39 ` sashiko-bot
0 siblings, 0 replies; 45+ messages in thread
From: sashiko-bot @ 2026-09-28 1:39 UTC (permalink / raw)
To: alistair23; +Cc: gary, linux-cxl, ojeda, linux-pci
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 <alistair@alistair23.me>
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.
> diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
> index c894cf30bb870..3bbe55d965f7a 100644
> --- a/lib/rspdm/state.rs
> +++ b/lib/rspdm/state.rs
[ ... ]
> @@ -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.
> /// `dev`: Responder device. Used for error reporting and passed to @transport.
> /// `transport`: Transport function to perform one message exchange.
> /// `transport_priv`: Transport private data.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260928011123.450800-1-alistair.francis@wdc.com?part=15
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v4 05/22] X.509: Make certificate parser public
2026-09-28 1:11 ` [PATCH v4 05/22] X.509: Make certificate parser public alistair23
@ 2026-09-28 1:39 ` sashiko-bot
0 siblings, 0 replies; 45+ messages in thread
From: sashiko-bot @ 2026-09-28 1:39 UTC (permalink / raw)
To: alistair23; +Cc: linux-cxl, linux-pci, gary, ojeda
> From: Lukas Wunner <lukas@wunner.de>
>
> The upcoming support for PCI device authentication with CMA-SPDM
> (PCIe r6.1 sec 6.31) requires validating the Subject Alternative Name
> in X.509 certificates.
>
> High-level functions for X.509 parsing such as key_create_or_update()
> throw away the internal, low-level struct x509_certificate after
> extracting the struct public_key and public_key_signature from it.
> The Subject Alternative Name is thus inaccessible when using those
> functions.
>
> Afford CMA-SPDM access to the Subject Alternative Name by making struct
> x509_certificate public, together with the functions for parsing an
> X.509 certificate into such a struct and freeing such a struct.
>
> No functional change intended.
>
> Signed-off-by: Lukas Wunner <lukas@wunner.de>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260928011123.450800-1-alistair.francis@wdc.com?part=5
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v4 16/22] lib: rspdm: Support SPDM get_capabilities
2026-09-28 1:11 ` [PATCH v4 16/22] lib: rspdm: Support SPDM get_capabilities alistair23
@ 2026-09-28 1:41 ` sashiko-bot
0 siblings, 0 replies; 45+ messages in thread
From: sashiko-bot @ 2026-09-28 1:41 UTC (permalink / raw)
To: alistair23; +Cc: linux-cxl, gary, ojeda, linux-pci
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 <alistair@alistair23.me>
lib: rspdm: Support SPDM get_capabilities
This commit adds support for the GET_CAPABILITIES SPDM command.
> diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
> index 3bbe55d965f7a..fb43260f48004 100644
> --- a/lib/rspdm/state.rs
> +++ b/lib/rspdm/state.rs
[ ... ]
> @@ -290,4 +301,59 @@ pub(crate) fn get_version(&mut self) -> Result<(), Error> {
[ ... ]
> + pub(crate) fn get_capabilities(&mut self) -> Result<(), Error> {
[ ... ]
> + 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);
> + }
> +
> + 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.
> +
> + self.transport_sz = self.transport_sz.min(response.data_transfer_size);
> + }
> +
> + Ok(())
> + }
> }
> diff --git a/lib/rspdm/validator.rs b/lib/rspdm/validator.rs
> index d0b6691365c0a..ca837bd9cfcc7 100644
> --- a/lib/rspdm/validator.rs
> +++ b/lib/rspdm/validator.rs
[ ... ]
> @@ -175,3 +183,302 @@ fn validate(unvalidated: &[u8], context: &'c SpdmState) -> Result<Self, Self::Er
[ ... ]
> +impl<'a, 'c> Validate<'c, Untrusted<&'a [u8]>> for GetCapabilitiesRsp {
> + type Err = Error;
> +
> + type Context = &'c SpdmState;
> +
> + fn validate(unvalidated: &[u8], context: &'c SpdmState) -> Result<Self, Self::Err> {
[ ... ]
> + let (data_transfer_size, max_spdm_msg_size, supported_algorithms, length) =
> + if context.version <= SPDM_VER_11 {
[ ... ]
> + } else {
[ ... ]
> + 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.
> +
> + let length = GET_CAPABILITIES_RSP_SZ
> + + supported_algorithms
> + .as_ref()
> + .map_or(0, |s| s.length as usize);
> +
> + (
> + data_transfer_size,
> + max_spdm_msg_size,
> + supported_algorithms,
> + length,
> + )
> + };
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260928011123.450800-1-alistair.francis@wdc.com?part=16
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v4 21/22] rust: allow extracting the buffer from a CString
2026-09-28 1:31 ` [PATCH v4 21/22] rust: allow extracting the buffer from a CString alistair23
@ 2026-09-28 1:41 ` sashiko-bot
0 siblings, 0 replies; 45+ messages in thread
From: sashiko-bot @ 2026-09-28 1:41 UTC (permalink / raw)
To: alistair23; +Cc: ojeda, linux-pci, linux-cxl, gary
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 <alistair@alistair23.me>
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.
> diff --git a/rust/kernel/str.rs b/rust/kernel/str.rs
> index a556788bcc5e9..3fd8a218547f4 100644
> --- a/rust/kernel/str.rs
> +++ b/rust/kernel/str.rs
[ ... ]
> @@ -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?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260928011123.450800-1-alistair.francis@wdc.com?part=21
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v4 04/22] rust: validate: add `Validate` trait
2026-09-28 1:11 ` [PATCH v4 04/22] rust: validate: add `Validate` trait alistair23
@ 2026-09-28 1:41 ` sashiko-bot
0 siblings, 0 replies; 45+ messages in thread
From: sashiko-bot @ 2026-09-28 1:41 UTC (permalink / raw)
To: alistair23; +Cc: linux-pci, ojeda, linux-cxl, gary
> From: Benno Lossin <benno.lossin@proton.me>
>
> 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.
>
> Signed-off-by: Benno Lossin <benno.lossin@proton.me>
> Message-ID: <20250814124424.516191-4-lossin@kernel.org>
> Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260928011123.450800-1-alistair.francis@wdc.com?part=4
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v4 03/22] rust: create basic untrusted data API
2026-09-28 1:11 ` [PATCH v4 03/22] rust: create basic untrusted data API alistair23
@ 2026-09-28 1:41 ` sashiko-bot
0 siblings, 0 replies; 45+ messages in thread
From: sashiko-bot @ 2026-09-28 1:41 UTC (permalink / raw)
To: alistair23; +Cc: linux-pci, gary, linux-cxl, ojeda
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 <lossin@kernel.org>
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.
Link: Message-ID: <20250814124424.516191-3-lossin@kernel.org>
> diff --git a/rust/kernel/validate.rs b/rust/kernel/validate.rs
> new file mode 100644
> index 0000000000000..ca2e5abda3e25
> --- /dev/null
> +++ b/rust/kernel/validate.rs
> @@ -0,0 +1,147 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +//! Untrusted data API.
[ ... ]
> +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.
> +
> +use crate::{
[ ... ]
> +/// 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").
> +/// very easily, but the converse is not possible.
[ ... ]
> +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>>>.
> + }
> +}
> +
> +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?
> + }
> +}
> +
> +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?
> + }
> +}
> +
> +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?
> + }
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260928011123.450800-1-alistair.francis@wdc.com?part=3
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v4 11/22] PCI/TSM: Rename pf0 to host
2026-09-28 1:11 ` [PATCH v4 11/22] PCI/TSM: Rename pf0 to host alistair23
@ 2026-09-28 1:41 ` sashiko-bot
0 siblings, 0 replies; 45+ messages in thread
From: sashiko-bot @ 2026-09-28 1:41 UTC (permalink / raw)
To: alistair23; +Cc: linux-cxl, linux-pci, gary, ojeda
> From: Alistair Francis <alistair.francis@wdc.com>
>
> 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.
>
> Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260928011123.450800-1-alistair.francis@wdc.com?part=11
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v4 01/22] virt: coco: change tsm_register() to use a const struct
2026-09-28 1:11 ` [PATCH v4 01/22] virt: coco: change tsm_register() to use a const struct alistair23
@ 2026-09-28 1:41 ` sashiko-bot
0 siblings, 0 replies; 45+ messages in thread
From: sashiko-bot @ 2026-09-28 1:41 UTC (permalink / raw)
To: alistair23; +Cc: linux-cxl, ojeda, gary, linux-pci
> From: Alistair Francis <alistair.francis@wdc.com>
>
> 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.
>
> Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260928011123.450800-1-alistair.francis@wdc.com?part=1
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v4 13/22] PCI/CMA: Add a PCI TSM CMA driver using SPDM
2026-09-28 1:11 ` [PATCH v4 13/22] PCI/CMA: Add a PCI TSM CMA driver using SPDM alistair23
@ 2026-09-28 1:43 ` sashiko-bot
0 siblings, 0 replies; 45+ messages in thread
From: sashiko-bot @ 2026-09-28 1:43 UTC (permalink / raw)
To: alistair23; +Cc: linux-cxl, linux-pci, ojeda, gary
> From: Alistair Francis <alistair.francis@wdc.com>
>
> Component Measurement and Authentication (CMA, PCIe r6.2 sec 6.31)
> allows for measurement and authentication of PCIe devices. It is
> based on the Security Protocol and Data Model specification (SPDM,
> https://www.dmtf.org/dsp/DSP0274).
>
> CMA-SPDM in turn forms the basis for Integrity and Data Encryption
> (IDE, PCIe r6.2 sec 6.33) because the key material used by IDE is
> transmitted over a CMA-SPDM session.
>
> As a first step, add support for authentication via a CMA TSM driver.
>
> This was previously discusd here:
> http://lore.kernel.org/69976d7d39c60_2f4a1009@dwillia2-mobl4.notmuch
> [ ... ]
> Co-developed-by: Wilfred Mallawa <wilfred.mallawa@wdc.com>
> Signed-off-by: Wilfred Mallawa <wilfred.mallawa@wdc.com>
> Co-developed-by: Lukas Wunner <lukas@wunner.de>
> Signed-off-by: Lukas Wunner <lukas@wunner.de>
> Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260928011123.450800-1-alistair.francis@wdc.com?part=13
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v4 08/22] rust: add bindings for hash.h
2026-09-28 1:11 ` [PATCH v4 08/22] rust: add bindings for hash.h alistair23
@ 2026-09-28 1:43 ` sashiko-bot
0 siblings, 0 replies; 45+ messages in thread
From: sashiko-bot @ 2026-09-28 1:43 UTC (permalink / raw)
To: alistair23; +Cc: linux-pci, linux-cxl, ojeda, gary
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 <alistair.francis@wdc.com>
rust: add bindings for hash.h
Make the functions crypto_shash_descsize(), crypto_shash_digestsize()
and crypto_free_shash() available to Rust.
> diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
> index 4b31aa7f432f5..fd223b6c5aafc 100644
> --- a/rust/bindings/bindings_helper.h
> +++ b/rust/bindings/bindings_helper.h
> @@ -30,6 +30,7 @@
>
> #include <linux/acpi.h>
> #include <linux/gpu_buddy.h>
> +#include <crypto/hash.h>
[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.
> #include <drm/drm_device.h>
> #include <drm/drm_drv.h>
[ ... ]
> diff --git a/rust/helpers/hash.c b/rust/helpers/hash.c
> new file mode 100644
> index 0000000000000..23a63618a3709
> --- /dev/null
> +++ b/rust/helpers/hash.c
> @@ -0,0 +1,18 @@
[ ... ]
> +__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.
> +}
> diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
> index 440fb7638e3cb..09ec69f736b19 100644
> --- a/rust/helpers/helpers.c
> +++ b/rust/helpers/helpers.c
> @@ -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.
> #include "interrupt.c"
> #include "io.c"
> #include "irq.c"
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260928011123.450800-1-alistair.francis@wdc.com?part=8
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v4 19/22] lib: rspdm: Support SPDM get_certificate
2026-09-28 1:31 ` [PATCH v4 19/22] lib: rspdm: Support SPDM get_certificate alistair23
` (2 preceding siblings ...)
2026-09-28 1:31 ` [PATCH v4 22/22] lib: rspdm: Support SPDM challenge alistair23
@ 2026-09-28 1:43 ` sashiko-bot
3 siblings, 0 replies; 45+ messages in thread
From: sashiko-bot @ 2026-09-28 1:43 UTC (permalink / raw)
To: alistair23; +Cc: gary, linux-cxl, ojeda, linux-pci
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 <alistair@alistair23.me>
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.
> diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
> index 502d99eb13a66..780b9d459abf9 100644
> --- a/lib/rspdm/state.rs
> +++ b/lib/rspdm/state.rs
[ ... ]
> @@ -654,4 +659,107 @@ pub(crate) fn get_digests(&mut self) -> Result<(), Error> {
[ ... ]
> + 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.
> +
> + 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)?;
[ ... ]
> + let mut offset: u16 = portion_length;
> + let mut remainder_length = rem_length as 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`?
> +
> + let mut request_buf = request.to_bytes()?;
[ ... ]
> diff --git a/lib/rspdm/validator.rs b/lib/rspdm/validator.rs
> index c058dde314401..f4e9485179d01 100644
> --- a/lib/rspdm/validator.rs
> +++ b/lib/rspdm/validator.rs
[ ... ]
> @@ -794,3 +795,154 @@ fn validate(unvalidated: &[u8], context: &'c SpdmState<'c>) -> Result<Self, Self
[ ... ]
> +impl<'a, 'c> Validate<'c, Untrusted<&'a [u8]>> for SpdmCertChain {
> + type Err = Error;
> +
> + type Context = &'c SpdmState<'c>;
> +
> + fn validate(unvalidated: &[u8], context: &'c SpdmState<'c>) -> Result<Self, Self::Err> {
[ ... ]
> + 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 = length as 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.
> + 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.
> + GFP_KERNEL,
> + )?;
> +
> + Ok(SpdmCertChain {
> + length,
> + root_hash,
> + certificates,
> + })
> + }
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260928011123.450800-1-alistair.francis@wdc.com?part=19
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v4 12/22] PCI/TSM: Support connecting to PCIe CMA devices
2026-09-28 1:11 ` [PATCH v4 12/22] PCI/TSM: Support connecting to PCIe CMA devices alistair23
@ 2026-09-28 1:43 ` sashiko-bot
0 siblings, 0 replies; 45+ messages in thread
From: sashiko-bot @ 2026-09-28 1:43 UTC (permalink / raw)
To: alistair23; +Cc: ojeda, gary, linux-cxl, linux-pci
> From: Alistair Francis <alistair.francis@wdc.com>
>
> In the next patch we are going to add a PCIe CMA TSM driver, as such we
> need to ensure that is_pci_tsm_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.
>
> Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260928011123.450800-1-alistair.francis@wdc.com?part=12
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v4 14/22] PCI/CMA: Validate Subject Alternative Name in certificates
2026-09-28 1:11 ` [PATCH v4 14/22] PCI/CMA: Validate Subject Alternative Name in certificates alistair23
@ 2026-09-28 1:44 ` sashiko-bot
0 siblings, 0 replies; 45+ messages in thread
From: sashiko-bot @ 2026-09-28 1:44 UTC (permalink / raw)
To: alistair23; +Cc: linux-cxl, linux-pci, gary, ojeda
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 <lukas@wunner.de>
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.
> diff --git a/drivers/pci/cma.c b/drivers/pci/cma.c
> index 6b52b129574ec..6e4eb3b921131 100644
> --- a/drivers/pci/cma.c
> +++ b/drivers/pci/cma.c
[ ... ]
> +int pci_cma_note_san(void *context, size_t hdrlen, unsigned char tag,
> + const void *value, size_t vlen)
> +{
> + struct pci_cma_x509_context *ctx = context;
> +
> + /* These aren't the drOIDs we're looking for. */
> + if (ctx->last_oid != OID_CMA)
> + return 0;
> +
> + if (tag != ASN1_UTF8STR ||
> + vlen != ctx->expected_len ||
> + memcmp(value, ctx->expected_name, vlen) != 0) {
[Severity: High]
Will this strict memcmp() in pci_cma_note_san() fail if a device presents
a compliant SAN string using uppercase hexadecimal characters?
> + 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.
> + }
> +
> + ctx->found = true;
> +
> + return 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?
> +
> + 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);
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260928011123.450800-1-alistair.francis@wdc.com?part=14
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v4 18/22] lib: rspdm: Support SPDM get_digests
2026-09-28 1:11 ` [PATCH v4 18/22] lib: rspdm: Support SPDM get_digests alistair23
@ 2026-09-28 1:45 ` sashiko-bot
0 siblings, 0 replies; 45+ messages in thread
From: sashiko-bot @ 2026-09-28 1:45 UTC (permalink / raw)
To: alistair23; +Cc: linux-cxl, gary, ojeda, linux-pci
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 <alistair@alistair23.me>
lib: rspdm: Support SPDM get_digests
Support the get_digests Spdm command.
> diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
> index 51055ea521d72..502d99eb13a66 100644
> --- a/lib/rspdm/state.rs
> +++ b/lib/rspdm/state.rs
> [ ... ]
> @@ -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.
> +
> + let mut request_buf = request.to_bytes()?;
> +
> + let mut response_vec: KVec<u8> = KVec::from_elem(0u8, rsp_sz, GFP_KERNEL)?;
> [ ... ]
> diff --git a/lib/rspdm/validator.rs b/lib/rspdm/validator.rs
> index 58f6a3d436a8b..c058dde314401 100644
> --- a/lib/rspdm/validator.rs
> +++ b/lib/rspdm/validator.rs
> [ ... ]
> @@ -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?
> + })
> + }
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260928011123.450800-1-alistair.francis@wdc.com?part=18
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v4 10/22] lib: rspdm: Initial commit of Rust SPDM
2026-09-28 1:11 ` [PATCH v4 10/22] lib: rspdm: Initial commit of Rust SPDM alistair23
@ 2026-09-28 1:45 ` sashiko-bot
0 siblings, 0 replies; 45+ messages in thread
From: sashiko-bot @ 2026-09-28 1:45 UTC (permalink / raw)
To: alistair23; +Cc: ojeda, linux-pci, gary, linux-cxl
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 <alistair@alistair23.me>
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.
> diff --git a/lib/rspdm/lib.rs b/lib/rspdm/lib.rs
> new file mode 100644
> --- /dev/null
> +++ b/lib/rspdm/lib.rs
[ ... ]
> +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(),
> + }
> +}
[ ... ]
> +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_ptr as *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?
> +
> + drop(mutex);
> +}
[ ... ]
> diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
> new file mode 100644
> --- /dev/null
> +++ b/lib/rspdm/state.rs
[ ... ]
> + 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]?
> + response_buf: &mut [u8],
> + ) -> Result<i32, Error> {
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260928011123.450800-1-alistair.francis@wdc.com?part=10
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v4 17/22] lib: rspdm: Support SPDM negotiate_algorithms
2026-09-28 1:11 ` [PATCH v4 17/22] lib: rspdm: Support SPDM negotiate_algorithms alistair23
@ 2026-09-28 1:45 ` sashiko-bot
0 siblings, 0 replies; 45+ messages in thread
From: sashiko-bot @ 2026-09-28 1:45 UTC (permalink / raw)
To: alistair23; +Cc: ojeda, linux-pci, linux-cxl, gary
> From: Alistair Francis <alistair@alistair23.me>
>
> Support the NEGOTIATE_ALGORITHMS SPDM command.
>
> Signed-off-by: Alistair Francis <alistair@alistair23.me>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260928011123.450800-1-alistair.francis@wdc.com?part=17
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v4 22/22] lib: rspdm: Support SPDM challenge
2026-09-28 1:31 ` [PATCH v4 22/22] lib: rspdm: Support SPDM challenge alistair23
@ 2026-09-28 1:46 ` sashiko-bot
0 siblings, 0 replies; 45+ messages in thread
From: sashiko-bot @ 2026-09-28 1:46 UTC (permalink / raw)
To: alistair23; +Cc: ojeda, gary, linux-cxl, linux-pci
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 <alistair@alistair23.me>
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.
> diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs
> index 9f7f6d0ffac65..d6ece65a6fc05 100644
> --- a/lib/rspdm/state.rs
> +++ b/lib/rspdm/state.rs
[ ... ]
> @@ -907,4 +957,165 @@ pub(crate) fn validate_cert_chain(&mut self, slot: u8) -> Result<(), Error> {
[ ... ]
> + pub(crate) fn challenge(&mut self, slot: u8) -> Result<(), Error> {
[ ... ]
> + 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?
> + Ok(()) => {
> + pr_info!("Authenticated with certificate slot {slot}\n");
> + Ok(())
> + }
> + Err(e) => {
> + pr_err!("Cannot verify challenge_auth signature: {e:?}\n");
> + Err(EPROTO)
> + }
> + }
> + }
> }
> diff --git a/lib/rspdm/validator.rs b/lib/rspdm/validator.rs
> index f4e9485179d01..9992679f7c8a3 100644
> --- a/lib/rspdm/validator.rs
> +++ b/lib/rspdm/validator.rs
[ ... ]
> @@ -946,3 +943,149 @@ fn validate(unvalidated: &[u8], context: &'c SpdmState<'c>) -> Result<Self, Self
[ ... ]
> +impl<'a, 'c> Validate<'c, Untrusted<&'a [u8]>> for ChallengeRsp {
[ ... ]
> + 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().
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260928011123.450800-1-alistair.francis@wdc.com?part=22
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v4 20/22] lib: rspdm: Support SPDM certificate validation
2026-09-28 1:31 ` [PATCH v4 20/22] lib: rspdm: Support SPDM certificate validation alistair23
@ 2026-09-28 1:47 ` sashiko-bot
0 siblings, 0 replies; 45+ messages in thread
From: sashiko-bot @ 2026-09-28 1:47 UTC (permalink / raw)
To: alistair23; +Cc: linux-cxl, ojeda, linux-pci, gary
> From: Alistair Francis <alistair@alistair23.me>
>
> Support validating the SPDM certificate chain. This only performs basic
> sanity checks on the chain before we continue on. This does not ensure
> that the root CA is trusted, we leave that for userspace to check and
> enforce. Instead we just make sure that the chain is correct, uses
> supported signatures and that it isn't blacklisted in the kernel.
>
> We then store the first leaf certificate for use later.
>
> Signed-off-by: Alistair Francis <alistair@alistair23.me>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260928011123.450800-1-alistair.francis@wdc.com?part=20
^ permalink raw reply [flat|nested] 45+ messages in thread
end of thread, other threads:[~2026-09-28 1:47 UTC | newest]
Thread overview: 45+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-28 1:11 [PATCH v4 00/22] lib: Rust implementation of SPDM alistair23
2026-09-28 1:11 ` [PATCH v4 01/22] virt: coco: change tsm_register() to use a const struct alistair23
2026-09-28 1:41 ` sashiko-bot
2026-09-28 1:11 ` [PATCH v4 02/22] rust: transmute: add `cast_slice[_mut]` functions alistair23
2026-09-28 1:35 ` sashiko-bot
2026-09-28 1:11 ` [PATCH v4 03/22] rust: create basic untrusted data API alistair23
2026-09-28 1:41 ` sashiko-bot
2026-09-28 1:11 ` [PATCH v4 04/22] rust: validate: add `Validate` trait alistair23
2026-09-28 1:41 ` sashiko-bot
2026-09-28 1:11 ` [PATCH v4 05/22] X.509: Make certificate parser public alistair23
2026-09-28 1:39 ` sashiko-bot
2026-09-28 1:11 ` [PATCH v4 06/22] X.509: Parse Subject Alternative Name in certificates alistair23
2026-09-28 1:39 ` sashiko-bot
2026-09-28 1:11 ` [PATCH v4 07/22] X.509: Move certificate length retrieval into new helper alistair23
2026-09-28 1:36 ` sashiko-bot
2026-09-28 1:11 ` [PATCH v4 08/22] rust: add bindings for hash.h alistair23
2026-09-28 1:43 ` sashiko-bot
2026-09-28 1:11 ` [PATCH v4 09/22] rust: error: impl From<FromBytesWithNulError> for Kernel Error alistair23
2026-09-28 1:36 ` sashiko-bot
2026-09-28 1:11 ` [PATCH v4 10/22] lib: rspdm: Initial commit of Rust SPDM alistair23
2026-09-28 1:45 ` sashiko-bot
2026-09-28 1:11 ` [PATCH v4 11/22] PCI/TSM: Rename pf0 to host alistair23
2026-09-28 1:41 ` sashiko-bot
2026-09-28 1:11 ` [PATCH v4 12/22] PCI/TSM: Support connecting to PCIe CMA devices alistair23
2026-09-28 1:43 ` sashiko-bot
2026-09-28 1:11 ` [PATCH v4 13/22] PCI/CMA: Add a PCI TSM CMA driver using SPDM alistair23
2026-09-28 1:43 ` sashiko-bot
2026-09-28 1:11 ` [PATCH v4 14/22] PCI/CMA: Validate Subject Alternative Name in certificates alistair23
2026-09-28 1:44 ` sashiko-bot
2026-09-28 1:11 ` [PATCH v4 15/22] lib: rspdm: Support SPDM get_version alistair23
2026-09-28 1:39 ` sashiko-bot
2026-09-28 1:11 ` [PATCH v4 16/22] lib: rspdm: Support SPDM get_capabilities alistair23
2026-09-28 1:41 ` sashiko-bot
2026-09-28 1:11 ` [PATCH v4 17/22] lib: rspdm: Support SPDM negotiate_algorithms alistair23
2026-09-28 1:45 ` sashiko-bot
2026-09-28 1:11 ` [PATCH v4 18/22] lib: rspdm: Support SPDM get_digests alistair23
2026-09-28 1:45 ` sashiko-bot
2026-09-28 1:31 ` [PATCH v4 19/22] lib: rspdm: Support SPDM get_certificate alistair23
2026-09-28 1:31 ` [PATCH v4 20/22] lib: rspdm: Support SPDM certificate validation alistair23
2026-09-28 1:47 ` sashiko-bot
2026-09-28 1:31 ` [PATCH v4 21/22] rust: allow extracting the buffer from a CString alistair23
2026-09-28 1:41 ` sashiko-bot
2026-09-28 1:31 ` [PATCH v4 22/22] lib: rspdm: Support SPDM challenge alistair23
2026-09-28 1:46 ` sashiko-bot
2026-09-28 1:43 ` [PATCH v4 19/22] lib: rspdm: Support SPDM get_certificate sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox