lib: Rust implementation of SPDM
Subsystems (10)
v5 cover lib: Rust implementation of SPDM
From: Alistair Francis <[email protected]>
Security Protocols and Data Models (SPDM) [1] is used for authentication, attestation and key exchange. SPDM is generally used over a range of transports, such as PCIe, MCTP/SMBus/I3C, ATA, SCSI, NVMe or TCP.
From the kernels perspective SPDM is used to authenticate and attest devices. In this threat model a device is considered untrusted until it can be verified by the kernel and userspace using SPDM. As such SPDM data is untrusted data that can be mallicious.
The SPDM specification is also complex, with the 1.2.1 spec being almost 200 pages and the 1.3.0 spec being almost 250 pages long.
As such we have the kernel parsing untrusted responses from a complex specification, which sounds like a possible exploit vector. This is the type of place where Rust excels!
This series implements a SPDM requester in Rust.
This is done using the Validate and Untrusted traits. The validate() function has been updated updated to allow context which we use as part of the validation.
The validation has also swapped from a C style pointer cast to using a valid Rust struct and copying the data from the original buffer to the new struct. This obvious has some overhead, but as SPDM packets are relativly small, and not used as part of a constant hot path this seems fine. The big advantage is that we can validate everyting at once, convert the endianess when creating the struct and we get left with valid (not repr[c, packed]) Rust structs.
This makes the code a lot cleaner and more robust. It also removes a large number of unsafes and using get() for the arrays removes a large number of possible panics. Thanks for everyone at Kangrejos for the help with this!
I have kept Jonathon's reviews of the SPDM code, as the changes since the last vesion are mostly just mechanical, convertting the repr[c, packed] structs to copy structs.
This is based on Lukas' C implementation [2], but has been refacted during the first few RFCs. I have included some of the relevent patchesfrom Lukas' C SPDM implementation in this series where they are required.
This is a standalone series and doesn't depend on Lukas' implementation.
The goal of this series is to get the smallest possible SPDM implementation upstream. That will provide building blocks for us to continue working on.
As such we don't yet provide evidence or certificates to userspace, allow userspace to provide a nonce, support PQC or more advanced SPDM features. This is enough to communicate with a device and return "authenticated" to userspace.
Note that RFC v3 did provide evidence and certificates to userspace and allowed a custom nonce. Showing that it's possible. I also have patches that build apon [4] to do this via a TSM driver, again showing it's possible with the current approach.
We just don't support it yet and for TSM I need [4] upstream first.
This series is different to Lukas' original approach and the approach taken in the previous RFCs and instead adds the PCI-CMA support as a TSM driver. This was described by Dan in [3] and [5]. The advantage here is that for PCIe we can leverage the TSM work for a lot of the features and provide userspace a consistient interface between PCI TSM and CMA.
This series also doesn't check the certificate chain against the kernel keyring and will instead leave that to userspace once [4] is merged.
Other transport mode (such as ATA, SCSI, NVMe and MCTP) will therefore need slightly different approaches, as TSM doesn't apply. The library can support this though, it will just need some netlink and sysfs wrappers added as applicable. This way each transport can support SPDM in the way it sees fit.
The entire tree can be seen here:
https://github.com/alistair23/linux/tree/alistair/spdm-rust-tsm
I'm testing this by running the following
```shell cargo run -- --qemu-server response qemu-system-x86_64 \ -nic none \ -object rng-random,filename=/dev/urandom,id=rng0 \ -device virtio-rng-pci,rng=rng0 \ -drive file=deploy/images/qemux86-64/core-image-pcie-qemux86-64.rootfs.ext4,if=virtio,format=raw \ -usb -device usb-tablet -usb -device usb-kbd \ -cpu Skylake-Client \ -machine q35,i8042=off \ -smp 4 -m 2G \ -drive file=blknvme,if=none,id=mynvme,format=raw \ -device pcie-root-port,chassis=1,id=pci.1 \ -device nvme,drive=mynvme,serial=deadbeef,spdm_port=2323,spdm_trans=doe,bus=pci.1 \ -snapshot \ -serial mon:stdio -serial null -nographic \ -kernel deploy/images/qemux86-64/bzImage \ -append 'root=/dev/vda rw console=ttyS0 console=ttyS1 oprofile.timer=1 tsc=reliable no_timer_check rcupdate.rcu_expedited=1 swiotlb=0 ' echo tsm0 > /sys/bus/pci/devices/0000:01:00.0/tsm/connect ```
1: https://www.dmtf.org/standards/spdm
2: https://lore.kernel.org/all/[email protected]/
3: http://lore.kernel.org/[email protected]
4: https://lore.kernel.org/all/[email protected]/
5: https://lore.kernel.org/lkml/[email protected]/
v5:
- Split out crypto into Rust kernel wrappers
- Use ZeroCopy for SpdmHeader
- Add the Transport trait and CTransport type
- Bind the device driver
- Address bugs and issues from reviews
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 (17):
virt: coco: change tsm_register() to use a const struct
rust: add bindings for hash.h
rust: device: Expose Device from_raw() publically
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
rust: kernel: Add crypto hash bindings
lib: rspdm: Support SPDM negotiate_algorithms
lib: rspdm: Support SPDM get_digests
lib: rspdm: Support SPDM get_certificate
rust: public_key: Add crypto public key bindings
rust: x509: Add X509 certificate bindings
lib: rspdm: Support SPDM certificate validation
lib: rspdm: Support SPDM challenge
Benno Lossin (2):
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 | 66 ++ include/linux/tsm.h | 2 +- lib/Kconfig | 17 + lib/Makefile | 2 + lib/rspdm/Makefile | 10 + lib/rspdm/consts.rs | 113 +++ lib/rspdm/lib.rs | 329 ++++++ lib/rspdm/state.rs | 784 ++++++++++++++ lib/rspdm/validator.rs | 1129 +++++++++++++++++++++ rust/bindings/bindings_helper.h | 5 + rust/helpers/hash.c | 22 + rust/helpers/helpers.c | 1 + rust/kernel/device.rs | 2 +- rust/kernel/hash.rs | 145 +++ rust/kernel/lib.rs | 7 + rust/kernel/public_key.rs | 69 ++ rust/kernel/validate.rs | 275 +++++ rust/kernel/x509.rs | 118 +++ 37 files changed, 3669 insertions(+), 125 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/hash.rs create mode 100644 rust/kernel/public_key.rs create mode 100644 rust/kernel/validate.rs create mode 100644 rust/kernel/x509.rs
Signature · 1 line
2.56.0
PATCH v5 1/23 virt: coco: change tsm_register() to use a const struct
From: Alistair Francis <[email protected]>
The tsm_register() function takes a struct pci_tsm_ops *ops argument. This eventually is stored in struct tsm_dev as a const, so we should just change the tsm_register() register function argument to be a const.
--- drivers/crypto/ccp/sev-dev-tsm.c | 2 +- drivers/virt/coco/tsm-core.c | 4 ++-- include/linux/tsm.h | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-)
drivers/crypto/ccp/sev-dev-tsm.c +1 -1
@@ -339,7 +339,7 @@ static void dsm_disconnect(struct pci_dev *pdev)streams_teardown (dev_data->ide); } -static struct pci_tsm_ops sev_tsm_ops = { +static const struct pci_tsm_ops sev_tsm_ops = { .probe = dsm_probe, .remove = dsm_remove, .connect = dsm_connect,
drivers/virt/coco/tsm-core.c +2 -2
@@ -57,7 +57,7 @@ static struct tsm_dev *alloc_tsm_dev(struct device *parent) }static struct tsm_dev *tsm_register_pci_or_reset (struct tsm_dev *tsm_dev, -struct pci_tsm_ops *pci_ops) +const struct pci_tsm_ops *pci_ops) {int rc;
@@ -78,7 +78,7 @@ static struct tsm_dev *tsm_register_pci_or_reset(struct tsm_dev *tsm_dev,return tsm_dev; } -struct tsm_dev *tsm_register (struct device *parent,struct pci_tsm_ops *pci_ops) +struct tsm_dev *tsm_register (struct device *parent,const struct pci_tsm_ops *pci_ops) { struct tsm_dev *tsm_dev __free(put_tsm_dev) = alloc_tsm_dev(parent);struct device *dev;
include/linux/tsm.h +1 -1
@@ -120,7 +120,7 @@ DEFINE_FREE(put_tsm_dev, struct tsm_dev *,int tsm_report_register (const struct tsm_report_ops *ops,void *priv);int tsm_report_unregister (const struct tsm_report_ops *ops); -struct tsm_dev *tsm_register (struct device *parent,struct pci_tsm_ops *ops); +struct tsm_dev *tsm_register (struct device *parent,const struct pci_tsm_ops *ops);void tsm_unregister (struct tsm_dev *tsm_dev);struct tsm_dev *find_tsm_dev (int id);#endif /* __TSM_H */
Signature · 1 line
2.56.0
[email protected]
AI reviewRe: [PATCH v5 01/23] virt: coco: change tsm_register() to use a const struct
Sashiko has reviewed this patch and found no issues.
Quoted 7 lines
From: Alistair Francis <[email protected]>
The
tsm_register()function takes a structpci_tsm_ops*ops argument. This eventually is stored in structtsm_devas a const, so we should just change thetsm_register()register function argument to be a const.Signed-off-by Alistair Francis <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
PATCH v5 2/23 rust: create basic untrusted data API
In reply to [email protected]From: Benno Lossin <[email protected]>
When the kernel receives external data (e.g. from userspace), it usually is a very bad idea to directly use the data for logic decision in the kernel. For this reason, such data should be explicitly marked and validated before making decision based on its value.
The Untrusted<T> wrapper type marks a value of type T as untrusted. The particular meaning of "untrusted" highly depends on the type T. For example T = u8 ensures that the value of the byte cannot be retrieved. However, T = [u8] still allows to access the length of the slice. Similarly, T = KVec<U> allows modifications.
Message-ID: <[email protected]>
--- rust/kernel/lib.rs | 1 + rust/kernel/validate.rs | 192 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 193 insertions(+) create mode 100644 rust/kernel/validate.rs
rust/kernel/lib.rs +1 -0
@@ -147,6 +147,7 @@pub mod uaccess;#[cfg( CONFIG_USB = "y" )] pub mod usb; +pub mod validate;pub mod workqueue;pub mod xarray;
rust/kernel/validate.rs new file +192 -0
192 lines not on this page · raw message
Signature · 1 line
2.56.0
[email protected]
AI reviewRe: [PATCH v5 02/23] rust: create basic untrusted data API
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] rust: validate: from_mut() and from_mut_slice() bypass Untrusted opacity --
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] rust: validate: from_mut() and from_mut_slice() bypass Untrusted opacity --
commit 46cfb0c607f570933dd68037f09805a7bea851ee
Author: Benno Lossin <[email protected]>
rust: create basic untrusted data API
This commit introduces the Untrusted<T> wrapper type in kernel::validate to explicitly mark external data as untrusted and prevent unvalidated data from being directly read or used for kernel logic decisions.
Quoted 6 lines
rust/kernel/validate.rs new file +0
-0@@ -0,0 +1,192 @@
[ ... ]
Quoted 27 lines
rust/kernel/validate.rs new file +27
-0+impl <T >Untrusted <T > { +/// Marks the data behind the given reference as untrusted. +/// +/// Unlike [`Untrusted::new`], this does not require moving the value. +#[inline] +pub const fn from_ref (value: &T ) -> &Self { +// SAFETY: `Untrusted<T>` transparently wraps `T`. The cast +// does not introduce interior mutability, as `Untrusted<T>` +// has interior mutability if and only if `T` has. +unsafe { &*from_ref (value).cast ::<Self >() } + } + +/// Marks the data behind the given mutable reference as untrusted. +/// +/// Unlike [`Untrusted::new`], this does not require moving the value. +#[inline] +pub const fn from_mut (value: &mut T ) -> &mut Self { +// SAFETY: Same reasoning as `from_ref()`. +unsafe { &mut *from_mut (value).cast ::<Self >() } + } + +/// Marks the given slice of data as untrusted. +#[inline] +pub const fn from_slice (value: &[T ]) -> &[Self ] { +// SAFETY: `Untrusted<T>` transparently wraps `T`. The cast +// does not introduce interior mutability either, as `Untrusted<T>` +// has interior mutability if and only if `T` has.
rust/kernel/validate.rs line 160
Quoted 10 lines
rust/kernel/validate.rs new file +10
-0+unsafe { slice::from_raw_parts (value.as_ptr ().cast ::<Self >(), value.len ()) } + } + +/// Marks the given mutable slice of data as untrusted. +#[inline] +pub const fn from_mut_slice (value: &mut [T ]) -> &mut [Self ] { +// SAFETY: Same reasoning as `from_slice()`. +unsafe { slice::from_raw_parts_mut (value.as_mut_ptr ().cast ::<Self >(), value.len ()) } + } +}
[Severity: Medium]
Do public Untrusted::from_mut() and Untrusted::from_mut_slice() allow safe code to bypass the opacity guarantee of Untrusted<T>?
Because mutable references are invariant in T, converting &mut T into &mut Untrusted<T> (or &mut [T] into &mut [Untrusted<T>]) allows callers to write unvalidated Untrusted<T> values directly into a plain, trusted T or [T] variable without validation:
let mut trusted: u8 = 0;
*Untrusted::from_mut(&mut trusted) = untrusted_byte;Similarly, a caller with a plain mutable slice buf: &mut [u8] can pass Untrusted::from_mut_slice(buf) to a function expecting &mut [Untrusted<u8>] (such as the read_from_userspace(buf: &mut [Untrusted<u8>]) pattern shown in the documentation) and then read the unvalidated bytes directly from buf after the call returns.
Since DerefMut on Untrusted<[T]> and Untrusted<Vec<T, A>> only converts an already-untrusted container into &mut [Untrusted<T>], should from_mut_slice() be private and from_mut() be removed or kept private?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
Benno Lossin
reviewerRe: [PATCH v5 02/23] rust: create basic untrusted data API
Quoted 56 lines · sashiko-bot
Quoted 27 lines
rust/kernel/validate.rs new file +27
-0+impl <T >Untrusted <T > { +/// Marks the data behind the given reference as untrusted. +/// +/// Unlike [`Untrusted::new`], this does not require moving the value. +#[inline] +pub const fn from_ref (value: &T ) -> &Self { +// SAFETY: `Untrusted<T>` transparently wraps `T`. The cast +// does not introduce interior mutability, as `Untrusted<T>` +// has interior mutability if and only if `T` has. +unsafe { &*from_ref (value).cast ::<Self >() } + } + +/// Marks the data behind the given mutable reference as untrusted. +/// +/// Unlike [`Untrusted::new`], this does not require moving the value. +#[inline] +pub const fn from_mut (value: &mut T ) -> &mut Self { +// SAFETY: Same reasoning as `from_ref()`. +unsafe { &mut *from_mut (value).cast ::<Self >() } + } + +/// Marks the given slice of data as untrusted. +#[inline] +pub const fn from_slice (value: &[T ]) -> &[Self ] { +// SAFETY: `Untrusted<T>` transparently wraps `T`. The cast +// does not introduce interior mutability either, as `Untrusted<T>` +// has interior mutability if and only if `T` has. rust/kernel/validate.rs line 160
Quoted 10 lines
rust/kernel/validate.rs new file +10
-0+unsafe { slice::from_raw_parts (value.as_ptr ().cast ::<Self >(), value.len ()) } + } + +/// Marks the given mutable slice of data as untrusted. +#[inline] +pub const fn from_mut_slice (value: &mut [T ]) -> &mut [Self ] { +// SAFETY: Same reasoning as `from_slice()`. +unsafe { slice::from_raw_parts_mut (value.as_mut_ptr ().cast ::<Self >(), value.len ()) } + } +}[Severity: Medium]
Do publicUntrusted::from_mut()andUntrusted::from_mut_slice()allow safe code to bypass the opacity guarantee of Untrusted<T>?Because mutable references are invariant in T, converting &mut T into &mut Untrusted<T> (or &mut [T] into &mut [Untrusted<T>]) allows callers to write unvalidated Untrusted<T> values directly into a plain, trusted T or [T] variable without validation:
let mut trusted: u8 = 0; *Untrusted::from_mut(&mut trusted) = untrusted_byte;Similarly, a caller with a plain mutable slice buf: &mut [u8] can pass
Untrusted::from_mut_slice(buf)to a function expecting &mut [Untrusted<u8>] (such as theread_from_userspace(buf: &mut [Untrusted<u8>])pattern shown in the documentation) and then read the unvalidated bytes directly from buf after the call returns.
Quoted 3 lines
Since DerefMut on Untrusted<[T]> and Untrusted<Vec<T, A>> only converts an already-untrusted container into &mut [Untrusted<T>], should
from_mut_slice()be private andfrom_mut()be removed or kept private?
this is correct. we should remove from_mut_slice. but we can provide Vec::from_trusted: fn(Vec<T>) -> Vec<Untrusted<T>>.
best,
Benno
PATCH v5 3/23 rust: validate: add `Validate` trait
In reply to [email protected]From: Benno Lossin <[email protected]>
Introduce the Validate<Input> trait and functions to validate Untrusted<T> using said trait. This allows one to access the inner value of Untrusted<T> via validate{,_ref,_mut} functions which subsequently delegate the validation to user-implemented Validate trait.
The Validate trait is the only entry point for validation code, making it easy to spot where data is being validated.
The reason for restricting the types that can be inputs to Validate::validate is to be able to have the validate... functions on Untrusted. This is also the reason for the suggestions in the Usage in API Design section in the commit that introduced Untrusted<T>.
The Validate::validate() function allows additional context to be passed, which can be used as part of the validation process. For example comparing data against already negotiated information.
Message-ID: <[email protected]>
--- rust/kernel/validate.rs | 85 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 84 insertions(+), 1 deletion(-)
rust/kernel/validate.rs +84 -1
@@ -11,6 +11,9 @@//! APIs that write back into userspace usually allow writing untrusted bytes directly, allowing //! direct copying of untrusted user data back into userspace without validation. //! +//! The only way to access untrusted data is to [`Validate::validate`] it. This is facilitated by +//! the [`Validate`] trait. +//! //! # Rationale //! //! When reading data from an untrusted source, it must be validated before it can be used for
@@ -56,7 +59,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 ///
@@ -119,6 +122,42 @@ pub fn new(value: T) -> Self {Self ::from (value) } + +/// Validate the underlying untrusted data. +/// +/// See the [`Validate`] trait for more information. +#[inline] +pub fn validate <'c,Context ,V :Validate <'c,Self ,Context >>( + self, + context: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,Context ,V :Validate <'c, &'aSelf ,Context >>( + &'a self, + context: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,Context ,V :Validate <'c, &'amut Self ,Context >>( + &'amut self, + context:Context , + ) ->Result <V ,V ::Err > { +V ::validate (&mut self.0 , context) + } }impl <T >Untrusted <T > {
@@ -190,3 +229,47 @@ fn deref_mut(&mut self) -> &mut Self::Target {Untrusted ::from_mut_slice (self.0 .as_mut_slice ()) } } + +/// Marks valid input for the [`Validate`] trait. +pub trait ValidateInput : private::Sealed { +/// Type of the inner data. +type Inner : ?Sized ; +} + +impl <T : ?Sized >ValidateInput for Untrusted <T > { +type Inner =T ; +} + +impl <'a,T : ?Sized >ValidateInput for &'aUntrusted <T > { +type Inner = &'aT ; +} + +impl <'a,T : ?Sized >ValidateInput for &'amut Untrusted <T > { +type Inner = &'amut T ; +} + +mod private { +use super ::Untrusted ; + +pub trait Sealed {} + +impl <T : ?Sized >Sealed for Untrusted <T > {} +impl <'a,T : ?Sized >Sealed for &'aUntrusted <T > {} +impl <'a,T : ?Sized >Sealed for &'amut Untrusted <T > {} +} + +/// Validate [`Untrusted`] data. +/// +/// Care must be taken when implementing this trait, as unprotected access to unvalidated data is +/// given to the [`Validate::validate`] function. The implementer must ensure that the data is only +/// used for logic after successful validation. +pub trait Validate <'c,Input :ValidateInput ,Context >:Sized { +/// Validation error. +type Err ; + +/// Validate the raw input. +/// +/// `Context` is additional context needed for validation. The `'c` lifetime is the lifetime of +/// the borrowed context (`&'c Foo`). +fn validate (raw:Input ::Inner , context:Context ) ->Result <Self ,Self ::Err >; +}
Signature · 1 line
2.56.0
[email protected]
AI reviewRe: [PATCH v5 03/23] rust: validate: add `Validate` trait
Sashiko has reviewed this patch and found no issues.
Quoted 17 lines
From: Benno Lossin <[email protected]>
Introduce the
Validate<Input>trait and functions to validateUntrusted<T>using said trait. This allows one to access the inner value ofUntrusted<T>viavalidate{,_ref,_mut}functions which subsequently delegate the validation to user-implementedValidatetrait.The
Validatetrait is the only entry point for validation code, making it easy to spot where data is being validated.The reason for restricting the types that can be inputs to
Validate::validateis to be able to have thevalidate...functions onUntrusted. This is also the reason for the suggestions in theUsage in API Designsection in the commit that introducedUntrusted<T>.
Quoted 7 lines
The
Validate::validate()function allows additional context to be passed, which can be used as part of the validation process. For example comparing data against already negotiated information.Signed-off-by Benno Lossin <[email protected]>Message-ID: <[email protected]>
Signed-off-by Alistair Francis <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=3
PATCH v5 4/23 X.509: Make certificate parser public
In reply to [email protected]From: Lukas Wunner <[email protected]>
The upcoming support for PCI device authentication with CMA-SPDM (PCIe r6.1 sec 6.31) requires validating the Subject Alternative Name in X.509 certificates.
High-level functions for X.509 parsing such as key_create_or_update() throw away the internal, low-level struct x509_certificate after extracting the struct public_key and public_key_signature from it. The Subject Alternative Name is thus inaccessible when using those functions.
Afford CMA-SPDM access to the Subject Alternative Name by making struct x509_certificate public, together with the functions for parsing an X.509 certificate into such a struct and freeing such a struct.
No functional change intended.
--- crypto/asymmetric_keys/x509_parser.h | 42 +-------------------- include/keys/x509-parser.h | 55 ++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 41 deletions(-) create mode 100644 include/keys/x509-parser.h
crypto/asymmetric_keys/x509_parser.h +1 -41
@@ -5,51 +5,11 @@* Written by David Howells ([email protected]) */ -#include <linux/cleanup.h> -#include <linux/time.h> -#include <crypto/public_key.h> -#include <keys/asymmetric-type.h> -#include <crypto/sha2.h> - -struct x509_certificate { -struct x509_certificate *next; -struct x509_certificate *signer;/* Certificate that signed this one */ -struct public_key *pub;/* Public key details */ -struct public_key_signature *sig;/* Signature parameters */ -u8 sha256[SHA256_DIGEST_SIZE ];/* Hash for blacklist purposes */ -char *issuer;/* Name of certificate issuer */ -char *subject;/* Name of certificate subject */ -struct asymmetric_key_id *id;/* Issuer + Serial number */ -struct asymmetric_key_id *skid;/* Subject + subjectKeyId (optional) */ -time64_t valid_from; -time64_t valid_to; -const void *tbs;/* Signed data */ -unsigned tbs_size;/* Size of signed data */ -unsigned raw_sig_size;/* Size of signature */ -const void *raw_sig;/* Signature data */ -const void *raw_serial;/* Raw serial number in ASN.1 */ -unsigned raw_serial_size; -unsigned raw_issuer_size; -const void *raw_issuer;/* Raw issuer name in ASN.1 */ -const void *raw_subject;/* Raw subject name in ASN.1 */ -unsigned raw_subject_size; -unsigned raw_skid_size; -const void *raw_skid;/* Raw subjectKeyId in ASN.1 */ -unsigned index; -bool seen;/* Infinite recursion prevention */ -bool verified; -bool self_signed;/* T if self-signed (check unsupported_sig too) */ -bool unsupported_sig;/* T if signature uses unsupported crypto */ -bool blacklisted; -}; +#include <keys/x509-parser.h> /* * x509_cert_parser.c */ -extern void x509_free_certificate (struct x509_certificate *cert); -DEFINE_FREE(x509_free_certificate, struct x509_certificate *, - if (!IS_ERR(_T)) x509_free_certificate(_T)) -extern struct x509_certificate *x509_cert_parse (const void *data,size_t datalen);extern int x509_decode_time (time64_t *_t,size_t hdrlen,unsigned char tag,const unsigned char *value,size_t vlen);
include/keys/x509-parser.h new file +55 -0
55 lines not on this page · raw message
Signature · 1 line
2.56.0
[email protected]
AI reviewRe: [PATCH v5 04/23] X.509: Make certificate parser public
Sashiko has reviewed this patch and found no issues.
Quoted 16 lines
From: Lukas Wunner <[email protected]>
The upcoming support for PCI device authentication with CMA-SPDM (PCIe r6.1 sec 6.31) requires validating the Subject Alternative Name in X.509 certificates.
High-level functions for X.509 parsing such as
key_create_or_update()throw away the internal, low-level structx509_certificateafter extracting the structpublic_keyandpublic_key_signaturefrom it. The Subject Alternative Name is thus inaccessible when using those functions.Afford CMA-SPDM access to the Subject Alternative Name by making struct
x509_certificatepublic, together with the functions for parsing an X.509 certificate into such a struct and freeing such a struct.
Quoted 8 lines
No functional change intended.
Signed-off-by Lukas Wunner <[email protected]>Reviewed-by Dan Williams <[email protected]>Reviewed-by Alistair Francis <[email protected]>Reviewed-by Ilpo Järvinen <[email protected]>Reviewed-by Jonathan Cameron <[email protected]>Signed-off-by Alistair Francis <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=4
PATCH v5 5/23 X.509: Parse Subject Alternative Name in certificates
In reply to [email protected]From: Lukas Wunner <[email protected]>
The upcoming support for PCI device authentication with CMA-SPDM (PCIe r6.1 sec 6.31) requires validating the Subject Alternative Name in X.509 certificates.
Store a pointer to the Subject Alternative Name upon parsing for consumption by CMA-SPDM.
--- crypto/asymmetric_keys/x509_cert_parser.c | 9 +++++++++ include/keys/x509-parser.h | 2 ++ 2 files changed, 11 insertions(+)
crypto/asymmetric_keys/x509_cert_parser.c +9 -0
@@ -596,6 +596,15 @@ int x509_process_extension(void *context, size_t hdrlen,return 0 ; } +if (ctx->last_oid == OID_subjectAltName) { +if (ctx->cert->raw_san) +return -EBADMSG ; + + ctx->cert->raw_san = v; + ctx->cert->raw_san_size = vlen; +return 0 ; + } +if (ctx->last_oid == OID_keyUsage) {/* * Get hold of the keyUsage bit string
include/keys/x509-parser.h +2 -0
@@ -38,6 +38,8 @@ struct x509_certificate {unsigned raw_subject_size;unsigned raw_skid_size;const void *raw_skid;/* Raw subjectKeyId in ASN.1 */ +const void *raw_san;/* Raw subjectAltName in ASN.1 */ +unsigned raw_san_size;unsigned index;bool seen;/* Infinite recursion prevention */ bool verified;
Signature · 1 line
2.56.0
[email protected]
AI reviewRe: [PATCH v5 05/23] X.509: Parse Subject Alternative Name in certificates
Sashiko has reviewed this patch and found no issues.
Quoted 9 lines
From: Lukas Wunner <[email protected]>
The upcoming support for PCI device authentication with CMA-SPDM (PCIe r6.1 sec 6.31) requires validating the Subject Alternative Name in X.509 certificates.
Store a pointer to the Subject Alternative Name upon parsing for consumption by CMA-SPDM.
Quoted 7 lines
Signed-off-by Lukas Wunner <[email protected]>Reviewed-by Wilfred Mallawa <[email protected]>Reviewed-by Alistair Francis <[email protected]>Reviewed-by Ilpo Järvinen <[email protected]>Reviewed-by Jonathan Cameron <[email protected]>Acked-by Dan Williams <[email protected]>Signed-off-by Alistair Francis <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=5
PATCH v5 6/23 X.509: Move certificate length retrieval into new helper
In reply to [email protected]From: Lukas Wunner <[email protected]>
The upcoming in-kernel SPDM library (Security Protocol and Data Model,
https://www.dmtf.org/dsp/DSP0274) needs to retrieve the length from ASN.1 DER-encoded X.509 certificates.
Such code already exists in x509_load_certificate_list(), so move it into a new helper for reuse by SPDM.
Export the helper so that SPDM can be tristate. (Some upcoming users of the SPDM libray may be modular, such as SCSI and ATA.)
No functional change intended.
--- crypto/asymmetric_keys/x509_loader.c | 38 +++++++++++++++++++--------- include/keys/asymmetric-type.h | 2 ++ 2 files changed, 28 insertions(+), 12 deletions(-)
crypto/asymmetric_keys/x509_loader.c +26 -12
@@ -4,28 +4,42 @@#include <linux/key.h> #include <keys/asymmetric-type.h> +ssize_t x509_get_certificate_length (const u8 *p,unsigned long buflen) +{ +ssize_t plen; + +/* Each cert begins with an ASN.1 SEQUENCE tag and must be more +* than 256 bytes in size. +*/ +if (buflen <4 ) +return -EINVAL ; + +if (p[0 ] !=0x30 || + p[1 ] !=0x82 ) +return -EINVAL ; + + plen = (p[2 ] <<8 ) | p[3 ]; + plen +=4 ; +if (plen > buflen) +return -EINVAL ; + +return plen; +} +EXPORT_SYMBOL_GPL (x509_get_certificate_length); +int x509_load_certificate_list (const u8 cert_list[],const unsigned long list_size,const struct key *keyring) {key_ref_t key;const u8 *p, *end; -size_t plen; +ssize_t plen; p = cert_list; end = p + list_size;while (p < end) { -/* Each cert begins with an ASN.1 SEQUENCE tag and must be more -* than 256 bytes in size. -*/ -if (end - p <4 ) - goto dodgy_cert; -if (p[0 ] !=0x30 || - p[1 ] !=0x82 ) - goto dodgy_cert; - plen = (p[2 ] <<8 ) | p[3 ]; - plen +=4 ; -if (plen > end - p) + plen =x509_get_certificate_length (p, end - p); +if (plen <0 ) goto dodgy_cert; key = key_create_or_update(make_key_ref(keyring, 1),
include/keys/asymmetric-type.h +2 -0
@@ -84,6 +84,8 @@ extern struct key *find_asymmetric_key(struct key *keyring,const struct asymmetric_key_id *id_2, bool partial); +ssize_t x509_get_certificate_length (const u8 *p,unsigned long buflen); +int x509_load_certificate_list (const u8 cert_list[],const unsigned long list_size,const struct key *keyring);
Signature · 1 line
2.56.0
[email protected]
AI reviewRe: [PATCH v5 06/23] X.509: Move certificate length retrieval into new helper
Sashiko has reviewed this patch and found no issues.
Quoted 12 lines
From: Lukas Wunner <[email protected]>
The upcoming in-kernel SPDM library (Security Protocol and Data Model,
https://www.dmtf.org/dsp/DSP0274) needs to retrieve the length from ASN.1 DER-encoded X.509 certificates.Such code already exists in
x509_load_certificate_list(), so move it into a new helper for reuse by SPDM.Export the helper so that SPDM can be tristate. (Some upcoming users of the SPDM libray may be modular, such as SCSI and ATA.)
Quoted 7 lines
No functional change intended.
Signed-off-by Lukas Wunner <[email protected]>Reviewed-by Dan Williams <[email protected]>Reviewed-by Alistair Francis <[email protected]>Reviewed-by Jonathan Cameron <[email protected]>Signed-off-by Alistair Francis <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=6
PATCH v5 7/23 rust: add bindings for hash.h
In reply to [email protected]From: Alistair Francis <[email protected]>
Make the functions crypto_shash_descsize(), crypto_shash_digestsize() and crypto_free_shash() available to Rust.
--- rust/bindings/bindings_helper.h | 1 + rust/helpers/hash.c | 22 ++++++++++++++++++++++ rust/helpers/helpers.c | 1 + 3 files changed, 24 insertions(+) create mode 100644 rust/helpers/hash.c
rust/bindings/bindings_helper.h +1 -0
@@ -28,6 +28,7 @@*/ #include <linux/hrtimer_types.h> +#include <crypto/hash.h> #include <linux/acpi.h> #include <linux/gpu_buddy.h> #include <drm/drm_device.h>
rust/helpers/hash.c new file +22 -0
@@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <crypto/hash.h> + +#ifdef CONFIG_CRYPTO + +__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); +} + +#endif /* CONFIG_CRYPTO */
rust/helpers/helpers.c +1 -0
@@ -67,6 +67,7 @@#include "fs.c" #include "fwctl.c" #include "gpu.c" +#include "hash.c" #include "interrupt.c" #include "io.c" #include "irq.c"
Signature · 1 line
2.56.0
[email protected]
AI reviewRe: [PATCH v5 07/23] rust: add bindings for hash.h
Sashiko has reviewed this patch and found no issues.
Quoted 6 lines
From: Alistair Francis <[email protected]>
Make the functions
crypto_shash_descsize(),crypto_shash_digestsize()andcrypto_free_shash()available to Rust.Signed-off-by Alistair Francis <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=7
PATCH v5 8/23 rust: device: Expose Device from_raw() publically
In reply to [email protected]From: Alistair Francis <[email protected]>
We are going to call the from_raw() function later as part of the SPDM library, so expose it publically.
--- rust/kernel/device.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
rust/kernel/device.rs +1 -1
@@ -262,7 +262,7 @@ pub unsafe fn drvdata_borrow<T>(&self) -> Pin<&T> {impl <Ctx :DeviceContext >Device <Ctx > {/// Obtain the raw `struct device *`. -pub (crate )fn as_raw (&self) -> *mut bindings::device { +pub fn as_raw (&self) -> *mut bindings::device { self.0 .get () }
Signature · 1 line
2.56.0
[email protected]
AI reviewRe: [PATCH v5 08/23] rust: device: Expose Device from_raw() publically
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [Medium] rust: device: commit message claims to expose from_raw() instead of as_raw() - [Low] rust…
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] rust: device: commit message claims to expose from_raw() instead of as_raw()
- [Low] rust: device: missing #[inline] annotation on public Device::as_raw() --
commit 4612538f4ce0b7f345e904370c4f1a9efe8adc51
Author: Alistair Francis <[email protected]>
rust: device: Expose Device from_raw() publically
This commit changes the visibility of Device::as_raw() in rust/kernel/device.rs from pub(crate) to pub so it can be called from outside the kernel crate.
Quoted 6 lines
rust: device: Expose Device
from_raw()publicallyWe are going to call the
from_raw()function later as part of the SPDM library, so expose it publically.Signed-off-by Alistair Francis <[email protected]>
[Severity: Medium]
Should the commit subject and message refer to Device::as_raw() instead of from_raw(), and fix the typo "publically" to "publicly"?
In rust/kernel/device.rs, Device::from_raw() was already public prior to this patch:
pub unsafe fn from_raw<'a>(ptr: *mut bindings::device) -> &'a Self {
// SAFETY: Guaranteed by the safety requirements of the function.
unsafe { &*ptr.cast() }
}Whereas the diff below actually exposes Device::as_raw() publicly so that callers in lib/rspdm/lib.rs and lib/rspdm/state.rs can pass the raw device pointer to C callbacks.
rust/kernel/device.rs pub unsafe fn drvdata_borrow<T>(&self) -> Pin<&T> { · line 265
Quoted 10 lines
rust/kernel/device.rs +1
-1@@ -262,7 +262,7 @@ pub unsafe fn drvdata_borrow<T>(&self) -> Pin<&T> {impl <Ctx :DeviceContext >Device <Ctx > {/// Obtain the raw `struct device *`. -pub (crate )fn as_raw (&self) -> *mut bindings::device { +pub fn as_raw (&self) -> *mut bindings::device {
[Severity: Low]
This isn't a bug, but since Device::as_raw() in rust/kernel/device.rs is a small one-line accessor in the kernel crate now exposed across crate boundaries, should it be annotated with #[inline]?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=8
PATCH v5 9/23 lib: rspdm: Initial commit of Rust SPDM
In reply to [email protected]From: Alistair Francis <[email protected]>
This is the initial commit of the Rust SPDM library.
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.
This initial commit setups the framework for SPDM support. This uses the Validate and Untrusted traits, which means we can treat the responses as Untrusted until we validate them.
The next few patches add SPDM functionality, which allows us to perform basic authentication.
1: https://www.dmtf.org/standards/spdm
--- MAINTAINERS | 12 ++ include/linux/spdm.h | 66 ++++++++ lib/Kconfig | 17 +++ lib/Makefile | 2 + lib/rspdm/Makefile | 10 ++ lib/rspdm/consts.rs | 18 +++ lib/rspdm/lib.rs | 263 ++++++++++++++++++++++++++++++++ lib/rspdm/state.rs | 145 ++++++++++++++++++ lib/rspdm/validator.rs | 92 +++++++++++ rust/bindings/bindings_helper.h | 1 + 10 files changed, 626 insertions(+) create mode 100644 include/linux/spdm.h create mode 100644 lib/rspdm/Makefile create mode 100644 lib/rspdm/consts.rs create mode 100644 lib/rspdm/lib.rs create mode 100644 lib/rspdm/state.rs create mode 100644 lib/rspdm/validator.rs
MAINTAINERS +12 -0
@@ -25290,6 +25290,18 @@ M: Security Officers <[email protected]> S: Supported F: Documentation/process/security-bugs.rst +SECURITY PROTOCOL AND DATA MODEL (SPDM) +M: Alistair Francis <[email protected]> +M: Lukas Wunner <[email protected]> +M: Jonathan Cameron <[email protected]> +L: [email protected] +L: [email protected] +L: [email protected] +S: Maintained +T: git git://git.kernel.org/pub/scm/linux/kernel/git/devsec/spdm.git +F: include/linux/spdm.h +F: lib/rspdm/ + SECURITY SUBSYSTEM M: Paul Moore <[email protected]> M: James Morris <[email protected]>
include/linux/spdm.h new file +66 -0
66 lines not on this page · raw message
lib/Kconfig +17 -0
@@ -588,6 +588,23 @@ config LWQ_TEST help Run boot-time test of light-weight queuing. +config RSPDM + bool "Rust SPDM" + depends on RUST + select KEYS + select CRYPTO + select ASYMMETRIC_KEY_TYPE + select ASYMMETRIC_PUBLIC_KEY_SUBTYPE + select X509_CERTIFICATE_PARSER + help + The Rust implementation of the Security Protocol and Data Model (SPDM) + allows for device authentication, measurement, key exchange and + encrypted sessions. + + Crypto algorithms negotiated with SPDM are limited to those enabled + in .config. Users of SPDM therefore need to also select + any algorithms they deem mandatory. + endmenu config GENERIC_IOREMAP
lib/Makefile +2 -0
@@ -297,6 +297,8 @@ obj-$(CONFIG_PERCPU_TEST) += percpu_test.o obj-$(CONFIG_ASN1) += asn1_decoder.o obj-$(CONFIG_ASN1_ENCODER) += asn1_encoder.o +obj-$(CONFIG_RSPDM) += rspdm/ + obj-$(CONFIG_FONT_SUPPORT) += fonts/ #
lib/rspdm/Makefile new file +10 -0
@@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Rust implementation of the DMTF Security Protocol and Data Model (SPDM) +# https://www.dmtf.org/dsp/DSP0274 +# +# Copyright (C) 2026 Western Digital + +obj-$(CONFIG_RSPDM) += spdm.o + +spdm-y := lib.o
lib/rspdm/consts.rs new file +18 -0
@@ -0,0 +1,18 @@ +// 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> + +// 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 ;
lib/rspdm/lib.rs new file +263 -0
263 lines not on this page · raw message
lib/rspdm/state.rs new file +145 -0
145 lines not on this page · raw message
lib/rspdm/validator.rs new file +92 -0
92 lines not on this page · raw message
rust/bindings/bindings_helper.h +1 -0
@@ -90,6 +90,7 @@#include <linux/security.h> #include <linux/serdev.h> #include <linux/slab.h> +#include <linux/spdm.h> #include <linux/sys_soc.h> #include <linux/task_work.h> #include <linux/tracepoint.h>
Signature · 1 line
2.56.0
[email protected]
AI reviewRe: [PATCH v5 09/23] lib: rspdm: Initial commit of Rust SPDM
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: - [High] rspdm: un-sliced request_buf in spdm_exchange() fails SpdmHeader validation - [Low] lib: rspdm…
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] rspdm: un-sliced request_buf in spdm_exchange() fails SpdmHeader validation
- [Low] lib: rspdm: missing unsafe qualifier on spdm_destroy() and spdm_authenticate()
- [Low] lib: rspdm: non-vertical import and core::ffi usage in lib.rs
--
commit 8332eb50f9f3f308b55adfa6b54fbbcfc9566854
Author: Alistair Francis <[email protected]>
lib: rspdm: Initial commit of Rust SPDM
This commit introduces the initial framework for the Rust Security Protocol and Data Model (SPDM) library, including C FFI entry points, session state management, and message header validation.
Quoted 32 lines
lib/rspdm/lib.rs new file +26
-0@@ -0,0 +1,263 @@ +// 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, + device::{
lib/rspdm/lib.rs line 36
Quoted 10 lines
lib/rspdm/lib.rs new file +10
-0+ Bound, + Device, // + }, + error::to_result, + new_mutex, + sync::Mutex, + types::ForeignOwnable, // +}; + +use crate ::state::{SpdmState ,Transport };
[Severity: Low]
This isn't a bug, but per Documentation/rust/coding-guidelines.rst, should c_int and c_void come from kernel::prelude::* rather than core::ffi, and should the crate::state import use the vertical import style?
[ ... ]
Quoted 17 lines
lib/rspdm/lib.rs new file +17
-0+/// Authenticates a device through a sequence of SPDM exchanges. +/// +/// See `include/linux/spdm.h` for the full C API documentation. +#[export] +pub extern "C" fn spdm_authenticate (_state_ptr: *mut spdm_state ) ->c_int { + -(EPROTONOSUPPORT as i32 ) +} + +/// Destroys an SPDM session. +/// +/// See `include/linux/spdm.h` for the full C API documentation. +#[export] +pub extern "C" fn spdm_destroy (state_ptr: *mut spdm_state ) { +if state_ptr.is_null () { +return ; + } +
lib/rspdm/lib.rs line 263
Quoted 10 lines
lib/rspdm/lib.rs new file +10
-0+// SAFETY: `state_ptr` was returned from `spdm_create()` which used +// `into_foreign()` to create the pointer. +let mutex =unsafe { + <Pin <KBox <Mutex <SpdmState <'_,CTransport >>>>as ForeignOwnable >::from_foreign ( + state_ptr.cast ::<c_void >(), + ) + }; + +drop (mutex); +}
[Severity: Low]
Should spdm_destroy() and spdm_authenticate() be marked unsafe extern "C" fn with # Safety documentation sections, matching spdm_create()?
In spdm_destroy(), when state_ptr is non-null, it is passed to ForeignOwnable::from_foreign(), which requires the caller to guarantee that the pointer was returned by a previous call to into_foreign() and is not reclaimed more than once:
rust/kernel/types.rs:ForeignOwnable::from_foreign() {
/// # Safety
///
/// The provided pointer must have been returned by a previous call to [`into_foreign`], and it
/// must not be passed to `from_foreign` more than once.
unsafe fn from_foreign(ptr: *mut c_void) -> Self;
}Similarly, spdm_authenticate() takes a raw *mut spdm_state pointer from C that later patches in this series pass to ForeignOwnable::borrow(). Without an unsafe qualifier on these functions, their internal SAFETY comments rely on caller preconditions that are not part of a safety contract.
Quoted 6 lines
lib/rspdm/state.rs new file +0
-0@@ -0,0 +1,145 @@
[ ... ]
lib/rspdm/state.rs line 107
Quoted 12 lines
lib/rspdm/state.rs new file +12
-0+/// Start a SPDM exchange +/// +/// The data in `request_buf` is sent to the device and the response +/// is returned as untrusted data to be validated by the caller. +#[allow(dead_code)] +pub (crate )fn spdm_exchange ( + &self, + request_buf: &[u8 ], +mut response_buf:KVec <u8 >, + ) ->Result <Untrusted <KVec <u8 >>,Error > { +let header_size = core::mem::size_of ::<SpdmHeader >(); +let request:SpdmHeader =Untrusted ::new (request_buf).validate (self)?;
[Severity: High]
Will SpdmState::spdm_exchange() fail with EPROTO for any SPDM request message that has a payload after the 4-byte SpdmHeader?
Here, the full request_buf slice is passed to SpdmHeader::validate() without slicing it to ..header_size (unlike response_buf.get(..header_size) below). In lib/rspdm/validator.rs, SpdmHeader::validate() calls SpdmHeader::read_from_bytes(unvalidated):
lib/rspdm/validator.rs:<SpdmHeader as Validate>::validate() {
fn validate(unvalidated: &[u8], _context: &'c SpdmState<'c, T>) -> Result<Self, Self::Err> {
SpdmHeader::read_from_bytes(unvalidated).or(Err(EPROTO))
}
}Because zerocopy's FromBytes::read_from_bytes() enforces exact length equality via Ref::sized_from():
rust/zerocopy/src/ref.rs:Ref::sized_from() {
if bytes.len() != mem::size_of::<T>() {
return Err(SizeError::new(bytes).into());
}
}any request_buf longer than 4 bytes is rejected with Err(EPROTO) before self.transport.exchange() is ever called. In the subsequent patches in this series (such as get_capabilities, negotiate_algorithms, get_certificate, and challenge), requests with payloads beyond the 4-byte header are passed to spdm_exchange(), causing spdm_authenticate() to fail unconditionally once it moves past the 4-byte GET_VERSION request.
Should request_buf be sliced to ..header_size before validating the request header, or should SpdmHeader::validate() read from the prefix of the slice?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=9
PATCH v5 10/23 PCI/TSM: Rename pf0 to host
In reply to [email protected]From: Alistair Francis <[email protected]>
Rename pci_tsm_pf0 to pci_tsm_host (and rename variables and function names from pf0 to host) as part of converting pci_tsm_host to be any device that knows how to speak any of CMA, IDE, or TDISP.
This commit just renames the functions and provides no functional change. That will happen in the next commit.
--- drivers/crypto/ccp/sev-dev-tio.h | 4 +- drivers/crypto/ccp/sev-dev-tsm.c | 12 ++--- drivers/pci/tsm.c | 84 ++++++++++++++++---------------- include/linux/pci-tsm.h | 18 ++++--- 4 files changed, 61 insertions(+), 57 deletions(-)
drivers/crypto/ccp/sev-dev-tio.h +2 -2
@@ -53,9 +53,9 @@ struct tsm_dsm_tio {struct pci_ide *ide[TIO_IDE_MAX_TC ]; }; -/* Describes TSM structure for PF0 pointed by pci_dev->tsm */ +/* Describes TSM structure for the link host pointed by pci_dev->tsm */ struct tio_dsm { -struct pci_tsm_pf0 tsm; +struct pci_tsm_host tsm;struct tsm_dsm_tio data;struct sev_device *sev; };
drivers/crypto/ccp/sev-dev-tsm.c +6 -6
@@ -205,7 +205,7 @@ static int stream_alloc(struct pci_dev *pdev, struct pci_ide **ide,return 0 ; } -static struct pci_tsm *tio_pf0_probe (struct pci_dev *pdev,struct sev_device *sev) +static struct pci_tsm *tio_host_probe (struct pci_dev *pdev,struct sev_device *sev) { struct tio_dsm *dsm __free(kfree) = kzalloc_obj(*dsm);int rc;
@@ -213,7 +213,7 @@ static struct pci_tsm *tio_pf0_probe(struct pci_dev *pdev, struct sev_device *seif (!dsm)return NULL ; - rc =pci_tsm_pf0_constructor (pdev, &dsm->tsm, sev->tsmdev); + rc =pci_tsm_host_constructor (pdev, &dsm->tsm, sev->tsmdev);if (rc)return NULL ;
@@ -226,8 +226,8 @@ static struct pci_tsm *dsm_probe(struct tsm_dev *tsmdev, struct pci_dev *pdev) {struct sev_device *sev =tsm_dev_to_sev (tsmdev); -if (is_pci_tsm_pf0 (pdev)) -return tio_pf0_probe (pdev, sev); +if (is_pci_tsm_host (pdev)) +return tio_host_probe (pdev, sev);return NULL ; }
@@ -237,10 +237,10 @@ static void dsm_remove(struct pci_tsm *tsm)pci_dbg (pdev,"TSM disabled\n" ); -if (is_pci_tsm_pf0 (pdev)) { +if (is_pci_tsm_host (pdev)) { struct tio_dsm *dsm = container_of(tsm, struct tio_dsm, tsm.base_tsm); -pci_tsm_pf0_destructor (&dsm->tsm); +pci_tsm_host_destructor (&dsm->tsm);kfree (dsm); } }
drivers/pci/tsm.c +42 -42
250 lines not on this page · raw message
include/linux/pci-tsm.h +11 -7
@@ -40,7 +40,7 @@ struct pci_tsm_ops {* pci_tsm_rwsem held for write to sync with TSM unregistration and * mutual exclusion of @connect and @disconnect. @connect and * @disconnect additionally run under the DSM lock (struct -* pci_tsm_pf0::lock) as well as @probe and @remove of the subfunctions. +* pci_tsm_host::lock) as well as @probe and @remove of the subfunctions. * @bind, @unbind, and @guest_req run under pci_tsm_rwsem held for read * and the DSM lock. */
@@ -115,19 +115,23 @@ struct pci_tsm { };/** -* struct pci_tsm_pf0 - Physical Function 0 TDISP link context +* struct pci_tsm_host - TSM host link context (CMA, IDE, or TDISP) * @base_tsm: generic core "tsm" context * @lock: mutual exclustion for pci_tsm_ops invocation * @doe_mb: PCIe Data Object Exchange mailbox +* +* The host of a TSM link is the device that knows how to speak +* CMA, IDE, or TDISP. For TDISP / IDE that is a Physical Function 0. +* For CMA-only it is a CMA DOE mailbox. */ -struct pci_tsm_pf0 { +struct pci_tsm_host {struct pci_tsm base_tsm;struct mutex lock;struct pci_doe_mb *doe_mb; };/* physical function0 and capable of 'connect' */ -static inline bool is_pci_tsm_pf0 (struct pci_dev *pdev) +static inline bool is_pci_tsm_host (struct pci_dev *pdev) {if (!pdev)return false;
@@ -204,9 +208,9 @@ int pci_tsm_register(struct tsm_dev *tsm_dev);void pci_tsm_unregister (struct tsm_dev *tsm_dev);int pci_tsm_link_constructor (struct pci_dev *pdev,struct pci_tsm *tsm,struct tsm_dev *tsm_dev); -int pci_tsm_pf0_constructor (struct pci_dev *pdev,struct pci_tsm_pf0 *tsm, -struct tsm_dev *tsm_dev); -void pci_tsm_pf0_destructor (struct pci_tsm_pf0 *tsm); +int pci_tsm_host_constructor (struct pci_dev *pdev,struct pci_tsm_host *tsm, +struct tsm_dev *tsm_dev); +void pci_tsm_host_destructor (struct pci_tsm_host *tsm);int pci_tsm_doe_transfer (struct pci_dev *pdev,u8 type,const void *req,size_t req_sz,void *resp,size_t resp_sz);int pci_tsm_bind (struct pci_dev *pdev,struct kvm *kvm,u32 tdi_id);
Signature · 1 line
2.56.0
[email protected]
AI reviewRe: [PATCH v5 10/23] PCI/TSM: Rename pf0 to host
Sashiko has reviewed this patch and found no issues.
Quoted 11 lines
From: Alistair Francis <[email protected]>
Rename
pci_tsm_pf0topci_tsm_host(and rename variables and function names from pf0 to host) as part of convertingpci_tsm_hostto be any device that knows how to speak any of CMA, IDE, or TDISP.This commit just renames the functions and provides no functional change. That will happen in the next commit.
Signed-off-by Alistair Francis <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=10
PATCH v5 11/23 PCI/TSM: Support connecting to PCIe CMA devices
In reply to [email protected]From: Alistair Francis <[email protected]>
In the next patch we are going to add a PCIe CMA TSM driver, as such we need to ensure that is_pci_tsm_host() will allow us to connect to CMA capable devices. These devices don't necessarily has DEVCAP_TEE or IDE support.
To avoid calling pci_find_doe_mailbox() everytime is_pci_tsm_host() is called we can cache the CMA support in struct pci_dev and just check against that.
--- drivers/crypto/ccp/sev-dev-tsm.c | 4 ++-- drivers/pci/doe.c | 3 +++ drivers/pci/tsm.c | 38 +++++++++++++++++++++++++++----- include/linux/pci-tsm.h | 32 +++++++++++++++++++++++++-- include/linux/pci.h | 1 + 5 files changed, 68 insertions(+), 10 deletions(-)
drivers/crypto/ccp/sev-dev-tsm.c +2 -2
@@ -226,7 +226,7 @@ static struct pci_tsm *dsm_probe(struct tsm_dev *tsmdev, struct pci_dev *pdev) {struct sev_device *sev =tsm_dev_to_sev (tsmdev); -if (is_pci_tsm_host (pdev)) +if (is_pci_tsm_dsm (pdev))return tio_host_probe (pdev, sev);return NULL ; }
@@ -237,7 +237,7 @@ static void dsm_remove(struct pci_tsm *tsm)pci_dbg (pdev,"TSM disabled\n" ); -if (is_pci_tsm_host (pdev)) { +if (is_pci_tsm_dsm (pdev)) { struct tio_dsm *dsm = container_of(tsm, struct tio_dsm, tsm.base_tsm);pci_tsm_host_destructor (&dsm->tsm);
drivers/pci/doe.c +3 -0
@@ -871,6 +871,9 @@ void pci_doe_init(struct pci_dev *pdev)pci_doe_destroy_mb (doe_mb); } } + + pdev->doe_cma =pci_find_doe_mailbox (pdev,PCI_VENDOR_ID_PCI_SIG , +PCI_DOE_FEATURE_CMA ); }void pci_doe_destroy (struct pci_dev *pdev)
drivers/pci/tsm.c +32 -6
@@ -88,8 +88,8 @@ static void pci_tsm_walk_fns(struct pci_dev *pdev,if (!pf)continue ; -/* on entry function 0 has already run @cb */ -if (i >0 ) +/* the caller is responsible for running @cb on the host itself */ +if (pf != pdev)cb (pf, data);/* walk virtual functions of each pf */
@@ -145,8 +145,8 @@ static void pci_tsm_walk_fns_reverse(struct pci_dev *pdev,cb (vf, data); } -/* on exit, caller will run @cb on function 0 */ -if (i >0 ) +/* the caller is responsible for running @cb on the host itself */ +if (pf != pdev)cb (pf, data); } }
@@ -175,6 +175,9 @@ static int probe_fn(struct pci_dev *pdev, void *dsm)struct pci_dev *dsm_dev = dsm;const struct pci_tsm_ops *ops =to_pci_tsm_ops (dsm_dev->tsm); +if (pdev->tsm) +return 0 ; + pdev->tsm = ops->probe (dsm_dev->tsm->tsm_dev, pdev);pci_dbg (pdev,"setup TSM context: DSM: %s status: %s\n" ,pci_name (dsm_dev), pdev->tsm ?"success" :"failed" );
@@ -287,6 +290,19 @@ static DEVICE_ATTR_RW(connect);static int remove_fn (struct pci_dev *pdev,void *data) { +struct pci_dev *host = data; +struct pci_tsm_host *tsm_host; + +/* +* Only teardown the security context of functions that belong to the +* DSM being disconnected, leaving any sibling DSM in the same slot +* untouched. +*/ +if (!pdev->tsm || pdev->tsm->dsm_dev != host) +return 0 ; + + tsm_host =to_pci_tsm_host (pdev->tsm); +guard (mutex)(&tsm_host->lock);tsm_remove (pdev->tsm);link_sysfs_disable (pdev);return 0 ;
@@ -299,6 +315,7 @@ static int remove_fn(struct pci_dev *pdev, void *data)*/ static int __pci_tsm_unbind (struct pci_dev *pdev,void *data) { +struct pci_dev *host = data;struct pci_tdi *tdi;struct pci_tsm_host *tsm_host;
@@ -307,6 +324,14 @@ static int __pci_tsm_unbind(struct pci_dev *pdev, void *data)if (!pdev->tsm)return 0 ; +/* +* When walking a DSM's dependent functions skip any that belong to a +* different DSM in the same slot. A NULL @host is a direct unbind of +* @pdev itself. +*/ +if (host && pdev->tsm->dsm_dev != host) +return 0 ; + tsm_host =to_pci_tsm_host (pdev->tsm);guard (mutex)(&tsm_host->lock);
@@ -437,7 +462,7 @@ EXPORT_SYMBOL_GPL(pci_tsm_guest_req);static void pci_tsm_unbind_all (struct pci_dev *pdev) { -pci_tsm_walk_fns_reverse (pdev, __pci_tsm_unbind,NULL ); +pci_tsm_walk_fns_reverse (pdev, __pci_tsm_unbind, pdev);__pci_tsm_unbind (pdev,NULL ); }
@@ -451,12 +476,13 @@ static void __pci_tsm_disconnect(struct pci_dev *pdev)pci_tsm_unbind_all (pdev); +pci_tsm_walk_fns_reverse (pdev, remove_fn, pdev); +/* * disconnect() is uninterruptible as it may be called for device * teardown */ guard (mutex)(&tsm_host->lock); -pci_tsm_walk_fns_reverse (pdev, remove_fn,NULL ); ops->disconnect (pdev); }
include/linux/pci-tsm.h +30 -2
@@ -3,6 +3,7 @@#define __PCI_TSM_H#include <linux/mutex.h> #include <linux/pci.h> +#include <linux/pci-doe.h> #include <linux/sockptr.h> struct pci_tsm ;
@@ -130,8 +131,11 @@ struct pci_tsm_host {struct pci_doe_mb *doe_mb; }; -/* physical function0 and capable of 'connect' */ -static inline bool is_pci_tsm_host (struct pci_dev *pdev) + +/* device is a Device Security Manager (DSM), similar to +* is_pci_tsm_host() but doesnt' support CMA +*/ +static inline bool is_pci_tsm_dsm (struct pci_dev *pdev) {if (!pdev)return false;
@@ -163,6 +167,30 @@ static inline bool is_pci_tsm_host(struct pci_dev *pdev)return PCI_FUNC (pdev->devfn) ==0 ; } +/* device is a TSM host and capable of 'connect' */ +static inline bool is_pci_tsm_host (struct pci_dev *pdev) +{ +if (!pdev) +return false; + +if (!pci_is_pcie (pdev)) +return false; + +if (pdev->is_virtfn) +return false; + +/* +* Report capable if CMA is supported, which can be supported on any PCIe +* device. +*/ +#ifdef CONFIG_PCI_DOE +if (pdev->doe_cma) +return true; +#endif + +return is_pci_tsm_dsm (pdev); +} +/** * enum pci_tsm_req_scope - Scope of guest requests to be validated by TSM *
include/linux/pci.h +1 -0
@@ -566,6 +566,7 @@ struct pci_dev {#endif #ifdef CONFIG_PCI_DOE struct xarray doe_mbs;/* Data Object Exchange mailboxes */ +bool doe_cma;/* A CMA/SPDM DOE mailbox is present */ #endif #ifdef CONFIG_PCI_NPEM struct npem *npem;/* Native PCIe Enclosure Management */
Signature · 1 line
2.56.0
[email protected]
AI reviewRe: [PATCH v5 11/23] PCI/TSM: Support connecting to PCIe CMA devices
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [High] PCI/TSM: probe_fn() probes sibling TSM hosts without calling connect() - [High] PCI/TSM…
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] PCI/TSM: probe_fn() probes sibling TSM hosts without calling connect()
- [High] PCI/TSM: find_dsm_dev() misidentifies CMA subfunctions as their own DSM --
commit 42f160df056909252113f2ee2453b7fc4a07f4ab
Author: Alistair Francis <[email protected]>
PCI/TSM: Support connecting to PCIe CMA devices
This commit caches PCIe CMA DOE mailbox support in struct pci_dev, splits the existing DSM check into is_pci_tsm_dsm(), and updates is_pci_tsm_host() and function walks in drivers/pci/tsm.c so PCIe CMA devices can connect to a TSM driver.
Quoted 15 lines
drivers/pci/tsm.c +2
-2@@ -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 */
[ ... ]
Quoted 20 lines
drivers/pci/tsm.c +9
-0@@ -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
drivers/pci/tsm.c static DEVICE_ATTR_RW(connect); · line 308
Quoted 10 lines
drivers/pci/tsm.c +7
-0+* 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 ;
[Severity: High]
When connecting a function that has PCI_EXP_DEVCAP_TEE set, can pci_tsm_connect() -> pci_tsm_walk_fns() -> probe_fn() end up probing unconnected sibling TSM hosts without calling ops->connect()?
In pci_tsm_connect(), pci_tsm_walk_fns() is invoked whenever has_tee(pdev) is true:
drivers/pci/tsm.c:pci_tsm_connect() {
...
if (has_tee(pdev))
pci_tsm_walk_fns(pdev, probe_fn, pdev);
return 0;
}Because is_pci_tsm_host() now returns true for any non-VF PCIe function with pdev->doe_cma set, and pci_tsm_walk_fns() visits every physical function in the slot where pf != pdev, connecting either PF0 to a CMA TSM driver or connecting a non-PF0 CMA host that has PCI_EXP_DEVCAP_TEE set will call probe_fn(pf, pdev) on all other physical functions in the slot (as well as downstream devices when pdev is an upstream port DSM).
In probe_fn(), only functions with pdev->tsm already set are skipped. For an unconnected sibling host (pf->tsm == NULL), ops->probe() is called and initializes a struct pci_tsm_host via:
pci_tsm_host_constructor()
-> pci_tsm_link_constructor()
-> find_dsm_dev(pf)Because is_pci_tsm_host(pf) is true, find_dsm_dev(pf) returns pf itself, so pf->tsm->dsm_dev is set to pf without ops->connect(pf) ever being called.
Wouldn't this leave the sibling function with a populated pf->tsm where:
1. authenticated_show() and connect_show() report the sibling function as authenticated and connected without an established SPDM session, and connect_store() rejects subsequent connection attempts with -EBUSY.
2. Disconnecting the original host skips the sibling in remove_fn() because pf->tsm->dsm_dev != host, leaking pf->tsm.
3. Later disconnecting or removing the sibling invokes __pci_tsm_disconnect() -> ops->disconnect() on a device where ops->connect() was never called.
Should pci_tsm_connect() only walk subordinate functions when the connected device is acting as a TDISP DSM, and should probe_fn() avoid probing independent TSM hosts as subordinate functions?
Quoted 4 lines
include/linux/pci-tsm.h +0
-0
[ ... ]
Quoted 17 lines
include/linux/pci-tsm.h +13
-0@@ -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; + +/*
include/linux/pci-tsm.h static inline bool is_pci_tsm_host(struct pci_dev *pdev) · line 192
Quoted 10 lines
include/linux/pci-tsm.h +10
-0+* 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); +}
[Severity: High]
How does find_dsm_dev() behave when a subordinate physical function (PF1..PF7) or a downstream endpoint has a CMA DOE mailbox (pdev->doe_cma == true), while PF0 or an upstream switch port is acting as the TDISP DSM?
In drivers/pci/tsm.c, find_dsm_dev() checks is_pci_tsm_host(pdev) first:
drivers/pci/tsm.c:find_dsm_dev() {
if (is_pci_tsm_host(pdev))
return pdev;
struct pci_dev *pf0 __free(pci_dev_put) = pf0_dev_get(pdev);
if (!pf0)
return NULL;
if (is_dsm(pf0))
return pf0;
...
}Because is_pci_tsm_host(pdev) now returns true for any non-VF PCIe function with pdev->doe_cma, find_dsm_dev(pdev) will unconditionally return pdev itself before checking whether PF0 or an upstream port has an active DSM context (is_dsm(pf0) or is_dsm(uport)):
1. In pci_tsm_init(pdev), dsm = find_dsm_dev(pdev) returns pdev instead of PF0 or uport, so if (!dsm->tsm) returns early and fails to attach the subfunction to the connected DSM.
2. When a TDISP DSM driver probes a subordinate function via probe_fn() -> ops->probe() -> pci_tsm_link_constructor(pdev, tsm, tsm_dev), where tsm is a base struct pci_tsm rather than a struct pci_tsm_host, find_dsm_dev(pdev) sets tsm->dsm_dev = pdev. That makes both is_dsm(pdev) and is_pci_tsm_host(pdev) true, causing to_pci_tsm_host() to perform an invalid container_of(host->tsm, struct pci_tsm_host, base_tsm) on a non-host struct pci_tsm and access out-of-bounds memory when acquiring &tsm_host->lock in remove_fn(), __pci_tsm_unbind(), pci_tsm_bind(), pci_tsm_guest_req(), and bound_show().
3. During DSM disconnect, remove_fn() and __pci_tsm_unbind() skip the subordinate function because pdev->tsm->dsm_dev != host, leaking its TSM context.
4. In __pci_tsm_destroy(), the check:
if (is_link_tsm(tsm_dev) && is_pci_tsm_host(pdev)) pci_tsm_disconnect(pdev); else pci_tsm_fn_exit(pdev);
will call pci_tsm_disconnect(pdev) -> ops->disconnect(pdev) instead of pci_tsm_fn_exit(pdev) on a subordinate function just because it has pdev->doe_cma set.
Should find_dsm_dev() and __pci_tsm_destroy() distinguish between a device that was actually initialized as a host/DSM (such as via is_dsm()) versus a subordinate function that happens to have a CMA DOE mailbox?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=11
PATCH v5 12/23 PCI/CMA: Add a PCI TSM CMA driver using SPDM
In reply to [email protected]From: Alistair Francis <[email protected]>
Component Measurement and Authentication (CMA, PCIe r6.2 sec 6.31) allows for measurement and authentication of PCIe devices. It is based on the Security Protocol and Data Model specification (SPDM,
https://www.dmtf.org/dsp/DSP0274).
CMA-SPDM in turn forms the basis for Integrity and Data Encryption (IDE, PCIe r6.2 sec 6.33) because the key material used by IDE is transmitted over a CMA-SPDM session.
As a first step, add support for authentication via a CMA TSM driver.
This was previously discusd here:
http://lore.kernel.org/[email protected]
By utilising a TSM driver we get a lot of the TSM driver probe policies "for free". Currently there is no mechanism to provide evidence to userspace, as the TSM system doesn't support that at the moment. That can be added later when support by TSM.
Credits: Jonathan wrote the original proof-of-concept for a CMA implementation. Lukas reworked that for upstream. Wilfred contributed fixes for issues discovered during testing. Alistair reworked it as a TSM driver.
--- MAINTAINERS | 1 + drivers/pci/Kconfig | 14 ++++ drivers/pci/Makefile | 2 + drivers/pci/cma.c | 150 ++++++++++++++++++++++++++++++++++++++++ drivers/pci/doe.c | 3 - include/linux/pci-doe.h | 4 ++ 6 files changed, 171 insertions(+), 3 deletions(-) create mode 100644 drivers/pci/cma.c
MAINTAINERS +1 -0
@@ -25299,6 +25299,7 @@ L: [email protected] L: [email protected] S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/devsec/spdm.git +F: drivers/pci/cma.c F: include/linux/spdm.h F: lib/rspdm/
drivers/pci/Kconfig +14 -0
@@ -124,6 +124,20 @@ config PCI_ATS config PCI_IDE bool +config PCI_CMA + bool "Component Measurement and Authentication (CMA-SPDM)" + depends on RSPDM + select CRYPTO_ECDSA + select CRYPTO_RSA + select CRYPTO_SHA256 + select CRYPTO_SHA512 + select PCI_DOE + select PCI_TSM + help + Authenticate devices on enumeration per PCIe r6.2 sec 6.31. + A PCI DOE mailbox is used as transport for DMTF SPDM based + authentication, measurement and secure channel establishment. + config PCI_TSM bool "PCI TSM: Device security protocol support" select PCI_IDE
drivers/pci/Makefile +2 -0
@@ -41,6 +41,8 @@ obj-$(CONFIG_PCI_NPEM) += npem.o obj-$(CONFIG_PCIE_TPH) += tph.o obj-$(CONFIG_CARDBUS) += setup-cardbus.o +obj-$(CONFIG_PCI_CMA) += cma.o + # Endpoint library must be initialized before its users obj-$(CONFIG_PCI_ENDPOINT) += endpoint/
drivers/pci/cma.c new file +150 -0
150 lines not on this page · raw message
drivers/pci/doe.c +0 -3
@@ -31,9 +31,6 @@#define PCI_DOE_FLAG_CANCEL 0#define PCI_DOE_FLAG_DEAD 1 -/* Max data object length is 2^18 dwords */ -#define PCI_DOE_MAX_LENGTH (1 << 18) -/** * struct pci_doe_mb - State for a single DOE mailbox *
include/linux/pci-doe.h +4 -0
@@ -19,6 +19,10 @@ struct pci_doe_mb;#define PCI_DOE_FEATURE_CMA 1#define PCI_DOE_FEATURE_SSESSION 2 +/* Max data object length is 2^18 dwords (including 2 dwords for header) */ +#define PCI_DOE_MAX_LENGTH (1 << 18) +#define PCI_DOE_MAX_PAYLOAD ((PCI_DOE_MAX_LENGTH - 2) * sizeof(u32)) +struct pci_doe_mb *pci_find_doe_mailbox (struct pci_dev *pdev,u16 vendor,u8 type);
Signature · 1 line
2.56.0
[email protected]
AI reviewRe: [PATCH v5 12/23] PCI/CMA: Add a PCI TSM CMA driver using SPDM
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] PCI/CMA: pci_cma_tsm_probe() initializes subfunctions without connect() --
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] PCI/CMA: pci_cma_tsm_probe() initializes subfunctions without connect() --
commit 422c44a59294e74ffe91f6512ceb7d64705e55f8
Author: Alistair Francis <[email protected]>
PCI/CMA: Add a PCI TSM CMA driver using SPDM
This commit adds a PCI TSM driver for Component Measurement and Authentication (CMA-SPDM) over a PCI DOE mailbox to support device authentication.
Quoted 5 lines
drivers/pci/cma.c new file +0
-0
[ ... ]
Quoted 19 lines
drivers/pci/cma.c new file +19
-0+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 ; + } +
drivers/pci/cma.c line 75
Quoted 10 lines
drivers/pci/cma.c new file +10
-0+ 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; +}
[Severity: High]
Can pci_cma_tsm_probe() be invoked via probe_fn() for subordinate or sibling PCI functions without pci_cma_tsm_connect() ever being called on them?
In drivers/pci/tsm.c, ops->probe() is called not only on the target host device in pci_tsm_connect(), but also via probe_fn() when walking functions in pci_tsm_connect() (when has_tee(pdev) is true) or when SR-IOV VFs are initialized in pci_tsm_init():
drivers/pci/tsm.c:probe_fn() {
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");
if (pdev->tsm)
link_sysfs_enable(pdev);
return 0;
}drivers/pci/tsm.c:pci_tsm_connect() {
...
if (has_tee(pdev))
pci_tsm_walk_fns(pdev, probe_fn, pdev);
return 0;
}Because pci_cma_tsm_probe() only checks pdev->doe_cma, any walked sibling PF, downstream device, or VF with doe_cma set will have cma->host and cma->spdm allocated and assigned to pdev->tsm, followed by link_sysfs_enable(), without pci_cma_tsm_connect() (and thus spdm_authenticate()) running on that function.
Doesn't this cause a few issues for those walked functions:
1. In drivers/pci/tsm.c, authenticated_show() delegates to connect_show(), which reports the TSM device name whenever pdev->tsm is non-NULL, falsely reporting the unauthenticated function as authenticated in sysfs.
2. Any subsequent attempt by userspace to authenticate that function via connect_store() will fail with -EBUSY because pdev->tsm is already populated.
3. For sibling PFs and downstream endpoints where is_pci_tsm_host() is true, pci_tsm_host_constructor() sets sub_pdev->tsm->dsm_dev = sub_pdev via find_dsm_dev(). When the parent device later disconnects in __pci_tsm_disconnect(), remove_fn() skips cleaning up sub_pdev:
drivers/pci/tsm.c:remove_fn() {
if (!pdev->tsm || pdev->tsm->dsm_dev != host)
return 0;
...
}leaving the unauthenticated sub_pdev->tsm and cma->spdm leaked and active.
4. If a VF (pdev->is_virtfn) has doe_cma set, calling pci_tsm_host_constructor() on it violates the is_pci_tsm_host() expectation for struct pci_tsm_host.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=12
PATCH v5 13/23 PCI/CMA: Validate Subject Alternative Name in certificates
In reply to [email protected]From: Lukas Wunner <[email protected]>
PCIe r6.1 sec 6.31.3 stipulates requirements for Leaf Certificates presented by devices, in particular the presence of a Subject Alternative Name which encodes the Vendor ID, Device ID, Device Serial Number, etc.
This prevents a mismatch between the device identity in Config Space and the certificate. A device cannot misappropriate a certificate from a different device without also spoofing Config Space. As a corollary, it cannot dupe an arbitrary driver into binding to it. Only drivers which bind to the device identity in the Subject Alternative Name work (PCIe r6.1 sec 6.31 "Implementation Note: Overview of Threat Model").
The Subject Alternative Name is signed, hence constitutes a signed copy of a Config Space portion. It's the same concept as web certificates which contain a set of domain names in the Subject Alternative Name for identity verification.
Parse the Subject Alternative Name using a small ASN.1 module and validate its contents. The theory of operation is explained in a comment at the top of the newly inserted code.
This functionality is introduced in a separate commit on top of basic CMA-SPDM support to split the code into digestible, reviewable chunks.
The CMA OID added here is taken from the official OID Repository (it's not documented in the PCIe Base Spec):
https://oid-rep.orange-labs.fr/get/2.23.147
Side notes:
* PCIe r6.2 removes the spec language on the Subject Alternative Name. It still "requires the leaf certificate to include the information typically used by system software for device driver binding", but no longer specifies how that information is encoded into the certificate.
According to the editor of the PCIe Base Spec and the author of the CMA 1.1 ECN (which caused this change), FPGA cards which mutate their device identity at runtime (due to a firmware update) were thought as unable to satisfy the previous spec language. The Protocol Working Group could not agree on a better solution and therefore dropped the spec language entirely. They acknowledge that the requirement is now under-spec'd. Because products already exist which adhere to the Subject Alternative Name requirement per PCIe r6.1 sec 6.31.3, they recommended to "push through" and use it as the de facto standard.
The FPGA concerns are easily overcome by reauthenticating the device after a firmware update, either via sysfs or pci_cma_reauthenticate() (added by a subsequent commit).
* PCIe r6.1 sec 6.31.3 strongly recommends to verify that "the information provided in the Subject Alternative Name entry is signed by the vendor indicated by the Vendor ID." In other words, the root certificate on pci_cma_keyring which signs the device's certificate chain must have been created for a particular Vendor ID.
Unfortunately the spec neglects to define how the Vendor ID shall be encoded into the root certificate. So the recommendation cannot be implemented at this point and it is thus possible that a vendor signs device certificates of a different vendor.
* Instead of a Subject Alternative Name, Leaf Certificates may include "a Reference Integrity Manifest, e.g., see Trusted Computing Group" or "a pointer to a location where such a Reference Integrity Manifest can be obtained" (PCIe r6.1 sec 6.31.3).
A Reference Integrity Manifest contains "golden" measurements which can be compared to actual measurements retrieved from a device. It serves a different purpose than the Subject Alternative Name, hence it is unclear why the spec says only either of them is necessary. It is also unclear how a Reference Integrity Manifest shall be encoded into a certificate.
Hence ignore the Reference Integrity Manifest requirement.
[ Changed by AF:
- Fixup a few issues caught by Sashiko
]
--- drivers/pci/Makefile | 5 +- drivers/pci/cma.asn1 | 41 ++++++++++++ drivers/pci/cma.c | 123 ++++++++++++++++++++++++++++++++++- include/linux/oid_registry.h | 3 + 4 files changed, 170 insertions(+), 2 deletions(-) create mode 100644 drivers/pci/cma.asn1
drivers/pci/Makefile +4 -1
@@ -41,7 +41,10 @@ obj-$(CONFIG_PCI_NPEM) += npem.o obj-$(CONFIG_PCIE_TPH) += tph.o obj-$(CONFIG_CARDBUS) += setup-cardbus.o -obj-$(CONFIG_PCI_CMA) += cma.o +obj-$(CONFIG_PCI_CMA) += pci-cma.o +pci-cma-y := cma.o cma.asn1.o +$(obj)/cma.o: $(obj)/cma.asn1.h +$(obj)/cma.asn1.o: $(obj)/cma.asn1.c $(obj)/cma.asn1.h # Endpoint library must be initialized before its users obj-$(CONFIG_PCI_ENDPOINT) += endpoint/
drivers/pci/cma.asn1 new file +41 -0
41 lines not on this page · raw message
drivers/pci/cma.c +122 -1
143 lines not on this page · raw message
include/linux/oid_registry.h +3 -0
@@ -150,6 +150,9 @@ enum OID { OID_id_ml_dsa_65,/* 2.16.840.1.101.3.4.3.18 */ OID_id_ml_dsa_87,/* 2.16.840.1.101.3.4.3.19 */ +/* PCI */ +OID_CMA ,/* 2.23.147 */ + OID__NR };
Signature · 1 line
2.56.0
[email protected]
AI reviewRe: [PATCH v5 13/23] PCI/CMA: Validate Subject Alternative Name in certificates
Sashiko has reviewed this patch and found no issues.
Quoted 13 lines
From: Lukas Wunner <[email protected]>
PCIe r6.1 sec 6.31.3 stipulates requirements for Leaf Certificates presented by devices, in particular the presence of a Subject Alternative Name which encodes the Vendor ID, Device ID, Device Serial Number, etc.
This prevents a mismatch between the device identity in Config Space and the certificate. A device cannot misappropriate a certificate from a different device without also spoofing Config Space. As a corollary, it cannot dupe an arbitrary driver into binding to it. Only drivers which bind to the device identity in the Subject Alternative Name work (PCIe r6.1 sec 6.31 "Implementation Note: Overview of Threat Model").
Quoted 8 lines
The Subject Alternative Name is signed, hence constitutes a signed copy of a Config Space portion. It's the same concept as web certificates
[ ... ][ Changed by AF:
- Fixup a few issues caught by Sashiko
]Signed-off-by Alistair Francis <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=13
PATCH v5 14/23 lib: rspdm: Support SPDM get_version
In reply to [email protected]From: Alistair Francis <[email protected]>
Support the GET_VERSION SPDM command.
--- lib/rspdm/consts.rs | 17 +++++++- lib/rspdm/lib.rs | 25 +++++++++++- lib/rspdm/state.rs | 43 +++++++++++++++++++- lib/rspdm/validator.rs | 90 +++++++++++++++++++++++++++++++++++++++++- 4 files changed, 169 insertions(+), 6 deletions(-)
lib/rspdm/consts.rs +15 -2
@@ -7,12 +7,25 @@//! Rust implementation of the DMTF Security Protocol and Data Model (SPDM) //! <https://www.dmtf.org/dsp/DSP0274> +use crate ::validator::SpdmHeader ; +use core::mem; +// SPDM versions supported by this implementation pub (crate )const SPDM_VER_10 :u8 =0x10 ; +#[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 ; + +pub (crate )const SPDM_GET_VERSION :u8 =0x84 ; +pub (crate )const SPDM_GET_VERSION_LEN :usize = + mem::size_of ::<SpdmHeader >() +2 + (u8::MAX as usize ) * mem::size_of ::<u16 >();
lib/rspdm/lib.rs +24 -1
@@ -238,7 +238,30 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {/// /// See `include/linux/spdm.h` for the full C API documentation. #[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 <'_,CTransport >>>>as ForeignOwnable >::borrow ( + state_ptr.cast ::<c_void >(), + ) + }; + +let mut state = mutex.lock (); + +if let Err (e) = state.get_version () { +return e.to_errno ()as c_int ; + } + -(EPROTONOSUPPORT as i32 ) }
lib/rspdm/state.rs +42 -1
63 lines not on this page · raw message
lib/rspdm/validator.rs +88 -2
116 lines not on this page · raw message
Signature · 1 line
2.56.0
[email protected]
AI reviewRe: [PATCH v5 14/23] lib: rspdm: Support SPDM get_version
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] rspdm: missing unsafe and Safety doc on spdm_authenticate() --
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] rspdm: missing unsafe and Safety doc on spdm_authenticate()
--
commit 80429ad18bdb5fc720a23fcc20975ddc687dd8ec
Author: Alistair Francis <[email protected]>
lib: rspdm: Support SPDM get_version
This commit adds support for the GET_VERSION SPDM command and calls state.get_version() from spdm_authenticate().
Quoted 16 lines
lib/rspdm/lib.rs +7
-1@@ -238,7 +238,30 @@ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {/// /// See `include/linux/spdm.h` for the full C API documentation. #[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.
lib/rspdm/lib.rs fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { · line 257
Quoted 10 lines
lib/rspdm/lib.rs +10
-0+// 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 <'_,CTransport >>>>as ForeignOwnable >::borrow ( + state_ptr.cast ::<c_void >(), + ) + };
[Severity: Medium]
Should spdm_authenticate() in lib/rspdm/lib.rs be declared as pub unsafe extern "C" fn with a # Safety documentation section, similar to spdm_create() in the same file?
Now that spdm_authenticate() passes a non-null state_ptr to ForeignOwnable::borrow() and locks the returned mutex, calling it with an invalid or dangling pointer (or concurrently with spdm_destroy()) causes undefined behavior. The SAFETY comment here relies on the caller guaranteeing that state_ptr was returned from spdm_create() and remains valid until spdm_destroy() is called, which requires the function to be marked unsafe.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=14
PATCH v5 15/23 lib: rspdm: Support SPDM get_capabilities
In reply to [email protected]From: Alistair Francis <[email protected]>
Support the GET_CAPABILITIES SPDM command.
--- lib/rspdm/consts.rs | 23 ++- lib/rspdm/lib.rs | 4 + lib/rspdm/state.rs | 60 +++++++- lib/rspdm/validator.rs | 319 ++++++++++++++++++++++++++++++++++++++++- 4 files changed, 401 insertions(+), 5 deletions(-)
lib/rspdm/consts.rs +21 -2
@@ -9,12 +9,14 @@use crate ::validator::SpdmHeader ;use core::mem; +use kernel::bits::{ + bit_u32, + bit_u8,// +};// 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 ;
@@ -29,3 +31,20 @@pub (crate )const SPDM_GET_VERSION :u8 =0x84 ;pub (crate )const SPDM_GET_VERSION_LEN :usize = mem::size_of ::<SpdmHeader >() +2 + (u8::MAX as usize ) * mem::size_of ::<u16 >(); + +pub (crate )const SPDM_GET_CAPABILITIES :u8 =0xe1 ; +pub (crate )const SPDM_CAP_SUPPORTED_ALGORITHMS :u8 =bit_u8 (0 ); +pub (crate )const SPDM_MIN_DATA_TRANSFER_SIZE :u32 =42 ;// SPDM 1.2.0 margin no 226 + +// SPDM cryptographic timeout of this implementation: +// Assume calculations may take up to 1 sec on a busy machine, which equals +// roughly 1 << 20. That's within the limits mandated for responders by CMA +// (1 << 23 usec, PCIe r6.2 sec 6.31.3) and DOE (1 sec, PCIe r6.2 sec 6.30.2). +// Used in GET_CAPABILITIES exchange. +pub (crate )const SPDM_CTEXPONENT :u8 =20 ; + +pub (crate )const SPDM_CERT_CAP :u32 =bit_u32 (1 ); +pub (crate )const SPDM_CHAL_CAP :u32 =bit_u32 (2 ); + +pub (crate )const SPDM_REQ_CAPS :u32 =SPDM_CERT_CAP |SPDM_CHAL_CAP ; +pub (crate )const SPDM_RSP_MIN_CAPS :u32 =SPDM_CERT_CAP |SPDM_CHAL_CAP ;
lib/rspdm/lib.rs +4 -0
@@ -262,6 +262,10 @@ pub extern "C" fn spdm_authenticate(state_ptr: *mut spdm_state) -> c_int {return e.to_errno ()as c_int ; } +if let Err (e) = state.get_capabilities () { +return e.to_errno ()as c_int ; + } + -(EPROTONOSUPPORT as i32 ) }
lib/rspdm/state.rs +58 -2
87 lines not on this page · raw message
lib/rspdm/validator.rs +318 -1
329 lines not on this page · raw message
Signature · 1 line
2.56.0
[email protected]
AI reviewRe: [PATCH v5 15/23] lib: rspdm: Support SPDM get_capabilities
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] rspdm: swapped fixed and extended alg_count nibbles in from_bytes() --
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] rspdm: swapped fixed and extended alg_count nibbles in from_bytes() --
commit 9a97b825dcf3405e60a1e8252ec1b1e885dfcb08
Author: Alistair Francis <[email protected]>
lib: rspdm: Support SPDM get_capabilities
Add support for the SPDM GET_CAPABILITIES command and response validation, including parsing the SPDM 1.3+ SupportedAlgorithms block.
Quoted 4 lines
lib/rspdm/validator.rs +0
-0
[ ... ]
Quoted 4 lines
lib/rspdm/validator.rs +0
-0@@ -176,3 +184,312 @@ fn validate(unvalidated: KVec<u8>, context: &'c SpdmState<'c, T>) -> Result<Self }) } }
[ ... ]
Quoted 3 lines
lib/rspdm/validator.rs +3
-0+impl SupportedAlgorithms { +#[allow(clippy::get_first)] +fn from_bytes (buf: &[u8 ]) ->Result <Self ,Error > {
[ ... ]
lib/rspdm/validator.rs fn validate(unvalidated: KVec<u8>, context: &'c SpdmState<'c, T>) -> Result<Self · line 324
Quoted 7 lines
lib/rspdm/validator.rs +7
-0+let mut alg_struct =KVec ::new (); +for _in 0 ..alg_struct_count { +let alg_type = *buf.get (offset).ok_or (EPROTO )?; +let alg_count = *buf.get (offset +1 ).ok_or (EPROTO )?; + +let fixed_alg_count = (alg_count &0xf )as usize ; +let ext_alg_count = (alg_count >>4 )as usize ;
[Severity: High]
Are the high and low nibbles of alg_count swapped here in SupportedAlgorithms::from_bytes()?
Per DSP0274, the AlgCount field in an AlgStructure entry encodes FixedAlgCount in bits [7:4] (alg_count >> 4) and ExtAlgCount in bits [3:0] (alg_count & 0xf).
When an SPDM 1.3+ responder sets SPDM_CAP_SUPPORTED_ALGORITHMS in header.param1 and includes AlgStructure entries in the CAPABILITIES response, spdm_authenticate() -> SpdmState::get_capabilities() -> GetCapabilitiesRsp::validate() -> SupportedAlgorithms::from_bytes() will compute the wrong sizes for alg_supported and alg_external (for example, treating a standard alg_count of 0x20 as 0 fixed bytes and 2 extended 32-bit algorithms instead of 2 fixed bytes and 0 extended algorithms), causing offset validation to fail with EPROTO.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=15
PATCH v5 16/23 rust: kernel: Add crypto hash bindings
In reply to [email protected]From: Alistair Francis <[email protected]>
Initial support for the kernel crypto hash bindings, which will be used by the SPDM library.
This is just a few functions that the Rust SPDM implementation will use in the next few commits.
--- rust/kernel/hash.rs | 145 ++++++++++++++++++++++++++++++++++++++++++++ rust/kernel/lib.rs | 2 + 2 files changed, 147 insertions(+) create mode 100644 rust/kernel/hash.rs
rust/kernel/hash.rs new file +145 -0
@@ -0,0 +1,145 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2026 Western Digital + +//! Wrappers around the Linux kernel crypto hash APIs. +//! +//! C headers: [`include/crypto/hash.h`](srctree/include/crypto/hash.h). +//! +//! Reference: <https://www.kernel.org/doc/html/latest/crypto/devel-algos.html#hashing-hash> + +use core::ptr::NonNull ; +use kernel::prelude::*; +use kernel::{ + bindings, + error::{ + from_err_ptr, + to_result,// + }, + str::CStr , +}; + +/// Synchronous hash transformation plus its descriptor buffer. +pub struct Shash { + shash:NonNull <bindings::crypto_shash >, + desc:NonNull <bindings::shash_desc >, + desc_len:usize , + desc_capacity:usize , + hash_len:usize , + name: &'staticCStr , +} + +impl Shash { +/// Allocates a hash transformation and initialises a descriptor for it. +pub fn new (name: &'staticCStr ) ->Result <Self > { +// SAFETY: `name.as_char_ptr()` is a valid NUL-terminated C string. +// `crypto_alloc_shash()` returns a valid pointer or an ERR_PTR. +let shash = +from_err_ptr (unsafe { bindings::crypto_alloc_shash (name.as_char_ptr (),0 ,0 ) })?; +let shash =NonNull ::new (shash).ok_or (ENOMEM )?; + +// SAFETY: `shash` is a valid `crypto_shash` handle. +let desc_size =unsafe { bindings::crypto_shash_descsize (shash.as_ptr ()) }as usize ; +// SAFETY: `shash` is a valid `crypto_shash` handle. +let hash_len =unsafe { bindings::crypto_shash_digestsize (shash.as_ptr ()) }as usize ; +let desc_len = core::mem::size_of ::<bindings::shash_desc >() + desc_size; + +let desc_vec =match KVec ::from_elem (0u8 , desc_len,GFP_KERNEL ) { +Ok (desc_vec) => desc_vec, +Err (e) => { +// SAFETY: `shash` was allocated above and must be freed if the +// descriptor allocation fails. +unsafe { + bindings::crypto_free_shash (shash.as_ptr ()); + } +return Err (e.into ()); + } + }; + +let (desc_buf, _desc_len, desc_capacity) = desc_vec.into_raw_parts (); +let desc =NonNull ::new (desc_buf.cast ::<bindings::shash_desc >()).ok_or (ENOMEM )?; + +// SAFETY: `desc` points to an allocation of `desc_len` bytes, which is +// exactly large enough for a `shash_desc` header and its trailing +// algorithm state. +unsafe { + (*desc.as_ptr ()).tfm = shash.as_ptr (); + } + +// SAFETY: `desc` is a valid `shash_desc` buffer with `tfm` initialised. +if let Err (e) =unsafe {to_result (bindings::crypto_shash_init (desc.as_ptr ())) } { +// SAFETY: `desc.as_ptr()`, `desc_len`, and `desc_capacity` are the +// exact parts returned by `into_raw_parts()` above. +let desc_vec =unsafe { +KVec ::<u8 >::from_raw_parts (desc.as_ptr ().cast ::<u8 >(), desc_len, desc_capacity) + }; +drop (desc_vec); + +// SAFETY: `shash` is a valid handle created above. +unsafe { + bindings::crypto_free_shash (shash.as_ptr ()); + } +return Err (e); + } + +Ok (Self { + shash, + desc, + desc_len, + desc_capacity, + hash_len, + name, + }) + } + +/// Return the name of the hash +pub fn name (&self) -> &'staticCStr { + self.name + } + +/// Return the name of the hash output +pub fn hash_len (&self) ->usize { + self.hash_len + } + +/// Computes a one-shot digest into `out`, which must be exactly the digest +/// size of the selected hash algorithm. +pub fn digest (&self, data: &[u8 ], out: &mut [u8 ]) ->Result { +if out.len () != self.hash_len { +return Err (EINVAL ); + } + +// SAFETY: `self.desc` was created by `Shash::new()` and points to a +// valid `shash_desc` with a valid `tfm`. `out` is checked above to be +// exactly `crypto_shash_digestsize()` bytes. +unsafe { +to_result (bindings::crypto_shash_digest ( + self.desc.as_ptr (), + data.as_ptr (), + data.len ()as u32 , + out.as_mut_ptr (), + )) + } + } +} + +impl Drop for Shash { +fn drop (&mut self) { +// SAFETY: `self.desc` was produced from a `KVec<u8>` allocation by +// `into_raw_parts()` in `Shash::new()`, and `desc_len` and +// `desc_capacity` are the exact values handed out at that time. +let desc_vec =unsafe { +KVec ::<u8 >::from_raw_parts ( + self.desc.as_ptr ().cast ::<u8 >(), + self.desc_len, + self.desc_capacity, + ) + }; +drop (desc_vec); + +// SAFETY: `self.shash` is a valid handle created by `crypto_alloc_shash()`. +unsafe { + bindings::crypto_free_shash (self.shash.as_ptr ()); + } + } +}
rust/kernel/lib.rs +2 -0
@@ -82,6 +82,8 @@pub mod fwctl;#[cfg( CONFIG_GPU_BUDDY = "y" )] pub mod gpu; +#[cfg( CONFIG_CRYPTO )] +pub mod hash;#[cfg( CONFIG_I2C = "y" )] pub mod i2c;pub mod id_pool;
Signature · 1 line
2.56.0
[email protected]
AI reviewRe: [PATCH v5 16/23] rust: kernel: Add crypto hash bindings
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: - [Medium] rust: kernel: hash module gated on CONFIG_CRYPTO breaks m/n builds - [Low] rust: kernel…
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Medium] rust: kernel: hash module gated on CONFIG_CRYPTO breaks m/n builds
- [Low] rust: kernel: unchecked usize to u32 cast in Shash::digest()
- [Low] rust: kernel: wrong doc comment and missing #[inline] in Shash
--
commit 43861816c7010fda266b93e8a13fd64b1fdc4ecd
Author: Alistair Francis <[email protected]>
rust: kernel: Add crypto hash bindings
This commit adds initial Rust abstractions in the kernel crate for synchronous crypto hash transformations (Shash) wrapping the C crypto_shash APIs.
Quoted 5 lines
rust/kernel/hash.rs new file +0
-0
[ ... ]
rust/kernel/hash.rs line 103
Quoted 9 lines
rust/kernel/hash.rs new file +9
-0+/// Return the name of the hash +pub fn name (&self) -> &'staticCStr { + self.name + } + +/// Return the name of the hash output +pub fn hash_len (&self) ->usize { + self.hash_len + }
[Severity: Low]
This isn't a bug, but the doc comment on Shash::hash_len() says "Return the name of the hash output" instead of the length of the hash output. Also, should Shash::name(), Shash::hash_len(), and Shash::digest() in rust/kernel/hash.rs be annotated with #[inline] as small kernel crate abstraction functions?
Quoted 9 lines
rust/kernel/hash.rs new file +9
-0+/// Computes a one-shot digest into `out`, which must be exactly the digest +/// size of the selected hash algorithm. +pub fn digest (&self, data: &[u8 ], out: &mut [u8 ]) ->Result { +if out.len () != self.hash_len { +return Err (EINVAL ); + } + +// SAFETY: `self.desc` was created by `Shash::new()` and points to a +// valid `shash_desc` with a valid `tfm`. `out` is checked above to be
rust/kernel/hash.rs line 123
Quoted 10 lines
rust/kernel/hash.rs new file +10
-0+// exactly `crypto_shash_digestsize()` bytes. +unsafe { +to_result (bindings::crypto_shash_digest ( + self.desc.as_ptr (), + data.as_ptr (), + data.len ()as u32 , + out.as_mut_ptr (), + )) + } + }
[Severity: Low]
In Shash::digest(), could passing a slice with data.len() > u32::MAX on a 64-bit platform silently truncate the input length via data.len() as u32 and compute a digest over only a prefix of data? Would u32::try_from(data.len())? avoid the unchecked narrowing cast?
[ ... ]
rust/kernel/lib.rs line 86
Quoted 10 lines
rust/kernel/lib.rs +2
-0@@ -82,6 +82,8 @@pub mod fwctl;#[cfg( CONFIG_GPU_BUDDY = "y" )] pub mod gpu; +#[cfg( CONFIG_CRYPTO )] +pub mod hash;
[Severity: Medium]
Will gating the built-in hash module in rust/kernel/lib.rs on #[cfg(CONFIG_CRYPTO)] break the build when CONFIG_CRYPTO=m, or when CONFIG_CRYPTO=y and CONFIG_CRYPTO_HASH2 is m or n?
Because CONFIG_CRYPTO is a tristate symbol, Kconfig emits --cfg=CONFIG_CRYPTO for both CONFIG_CRYPTO=y and CONFIG_CRYPTO=m, whereas in C, CONFIG_CRYPTO is only defined for built-in builds:
rust/helpers/hash.c:
#ifdef CONFIG_CRYPTO
__rust_helper unsigned int rust_helper_crypto_shash_descsize(struct crypto_shash *tfm)
When CONFIG_CRYPTO=m, Shash::new() and Shash::drop() in rust/kernel/hash.rs are compiled into built-in rust/kernel.o, but bindgen does not generate bindings::crypto_shash_descsize, bindings::crypto_shash_digestsize, or bindings::crypto_free_shash from rust/helpers/hash.c.
In addition, Shash::new() and Shash::digest() call crypto_alloc_shash(), crypto_shash_init(), and crypto_shash_digest(), which are compiled under CONFIG_CRYPTO_HASH2 rather than CONFIG_CRYPTO:
crypto/Makefile:
crypto_hash-y += ahash.o
crypto_hash-y += shash.o
obj-$(CONFIG_CRYPTO_HASH2) += crypto_hash.oIf CONFIG_CRYPTO=y and CONFIG_CRYPTO_HASH2 is n or m, vmlinux fails to link with undefined references to those shash symbols from rust/kernel.o.
Should rust/kernel/lib.rs use #[cfg(CONFIG_CRYPTO_HASH2 = "y")] (and rust/helpers/hash.c guard on CONFIG_CRYPTO_HASH2) to match the other built-in subsystem modules in rust/kernel/lib.rs?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=16
PATCH v5 17/23 lib: rspdm: Support SPDM negotiate_algorithms
In reply to [email protected]From: Alistair Francis <[email protected]>
Support the NEGOTIATE_ALGORITHMS SPDM command.
--- lib/rspdm/consts.rs | 52 ++++++++++ lib/rspdm/lib.rs | 4 + lib/rspdm/state.rs | 157 +++++++++++++++++++++++++++- lib/rspdm/validator.rs | 226 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 438 insertions(+), 1 deletion(-)
lib/rspdm/consts.rs +52 -0
@@ -45,6 +45,58 @@pub (crate )const SPDM_CERT_CAP :u32 =bit_u32 (1 );pub (crate )const SPDM_CHAL_CAP :u32 =bit_u32 (2 ); +pub (crate )const SPDM_KEY_EX_CAP :u32 =bit_u32 (9 ); +pub (crate )const SPDM_MEAS_RESP_CAP :u32 =bit_u32 (3 ) |bit_u32 (4 );pub (crate )const SPDM_REQ_CAPS :u32 =SPDM_CERT_CAP |SPDM_CHAL_CAP ;pub (crate )const SPDM_RSP_MIN_CAPS :u32 =SPDM_CERT_CAP |SPDM_CHAL_CAP ; + +pub (crate )const SPDM_NEGOTIATE_ALGS :u8 =0xe3 ; + +pub (crate )const SPDM_MEAS_SPEC_DMTF :u8 =bit_u8 (0 ); + +pub (crate )const SPDM_ASYM_RSASSA_2048 :u32 =bit_u32 (0 ); +pub (crate )const _SPDM_ASYM_RSAPSS_2048:u32 =bit_u32 (1 ); +pub (crate )const SPDM_ASYM_RSASSA_3072 :u32 =bit_u32 (2 ); +pub (crate )const _SPDM_ASYM_RSAPSS_3072:u32 =bit_u32 (3 ); +pub (crate )const SPDM_ASYM_ECDSA_ECC_NIST_P256 :u32 =bit_u32 (4 ); +pub (crate )const SPDM_ASYM_RSASSA_4096 :u32 =bit_u32 (5 ); +pub (crate )const _SPDM_ASYM_RSAPSS_4096:u32 =bit_u32 (6 ); +pub (crate )const SPDM_ASYM_ECDSA_ECC_NIST_P384 :u32 =bit_u32 (7 ); +pub (crate )const SPDM_ASYM_ECDSA_ECC_NIST_P521 :u32 =bit_u32 (8 ); +pub (crate )const _SPDM_ASYM_SM2_ECC_SM2_P256:u32 =bit_u32 (9 ); +pub (crate )const _SPDM_ASYM_EDDSA_ED25519:u32 =bit_u32 (10 ); +pub (crate )const _SPDM_ASYM_EDDSA_ED448:u32 =bit_u32 (11 ); + +pub (crate )const SPDM_HASH_SHA_256 :u32 =bit_u32 (0 ); +pub (crate )const SPDM_HASH_SHA_384 :u32 =bit_u32 (1 ); +pub (crate )const SPDM_HASH_SHA_512 :u32 =bit_u32 (2 ); + +// If the crypto support isn't enabled don't offer the algorithms +// to the responder +#[cfg( CONFIG_CRYPTO_RSA )] +pub (crate )const SPDM_ASYM_RSA :u32 = +SPDM_ASYM_RSASSA_2048 |SPDM_ASYM_RSASSA_3072 |SPDM_ASYM_RSASSA_4096 ; +#[cfg(not( CONFIG_CRYPTO_RSA ))] +pub (crate )const SPDM_ASYM_RSA :u32 =0 ; + +#[cfg( CONFIG_CRYPTO_ECDSA )] +pub (crate )const SPDM_ASYM_ECDSA :u32 = +SPDM_ASYM_ECDSA_ECC_NIST_P256 |SPDM_ASYM_ECDSA_ECC_NIST_P384 |SPDM_ASYM_ECDSA_ECC_NIST_P521 ; +#[cfg(not( CONFIG_CRYPTO_ECDSA ))] +pub (crate )const SPDM_ASYM_ECDSA :u32 =0 ; + +#[cfg( CONFIG_CRYPTO_SHA256 )] +pub (crate )const SPDM_HASH_SHA2_256 :u32 =SPDM_HASH_SHA_256 ; +#[cfg(not( CONFIG_CRYPTO_SHA256 ))] +pub (crate )const SPDM_HASH_SHA2_256 :u32 =0 ; + +#[cfg( CONFIG_CRYPTO_SHA512 )] +pub (crate )const SPDM_HASH_SHA2_384_512 :u32 =SPDM_HASH_SHA_384 |SPDM_HASH_SHA_512 ; +#[cfg(not( CONFIG_CRYPTO_SHA512 ))] +pub (crate )const SPDM_HASH_SHA2_384_512 :u32 =0 ; + +pub (crate )const SPDM_ASYM_ALGOS :u32 =SPDM_ASYM_RSA |SPDM_ASYM_ECDSA ; +pub (crate )const SPDM_HASH_ALGOS :u32 =SPDM_HASH_SHA2_256 |SPDM_HASH_SHA2_384_512 ; + +pub (crate )const SPDM_OPAQUE_DATA_FMT_GENERAL :u8 =bit_u8 (1 );
lib/rspdm/lib.rs +4 -0
@@ -266,6 +266,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 ) }
lib/rspdm/state.rs +156 -1
195 lines not on this page · raw message
lib/rspdm/validator.rs +226 -0
241 lines not on this page · raw message
Signature · 1 line
2.56.0
[email protected]
AI reviewRe: [PATCH v5 17/23] lib: rspdm: Support SPDM negotiate_algorithms
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider: - [High] rspdm: spdm_exchange() passes full request buffer to SpdmHeader::validate() - [Medium] rspdm…
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] rspdm: spdm_exchange() passes full request buffer to SpdmHeader::validate()
- [Medium] rspdm: missing minimum buffer length check in NegotiateAlgsRsp::validate()
- [Medium] rspdm: swapped alg_count nibbles in validate() and from_bytes()
- [Medium] rspdm: missing measurement_specification_sel check in negotiate_algs()
- [Low] rspdm: unguarded algorithm constants and imports break Kconfig builds --
commit f0e28cd8a1d08f541ce0684d3f1feb7d8f5fafa5
Author: Alistair Francis <[email protected]>
lib: rspdm: Support SPDM negotiate_algorithms
This commit adds support for the SPDM NEGOTIATE_ALGORITHMS request and response messages, allowing the requester and responder to negotiate asymmetric, hash, and measurement hash algorithms.
Quoted 58 lines
lib/rspdm/consts.rs +47
-0@@ -45,6 +45,58 @@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 ;
[ ... ]
Quoted 27 lines
lib/rspdm/state.rs +13
-0@@ -15,14 +15,30 @@ Device, // }, error::Error, + hash::Shash, + str::CStr, validate::Untrusted, };use crate ::consts::{ +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 ,
[ ... ]
Quoted 50 lines
lib/rspdm/state.rs +46
-0@@ -239,4 +285,113 @@ 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 =c"pkcs1" ; + } +#[cfg( CONFIG_CRYPTO_RSA )] +SPDM_ASYM_RSASSA_3072 => { + self.sig_len =384 ; + self.base_asym_enc =c"pkcs1" ; + } +#[cfg( CONFIG_CRYPTO_RSA )] +SPDM_ASYM_RSASSA_4096 => { + self.sig_len =512 ; + self.base_asym_enc =c"pkcs1" ; + } +#[cfg( CONFIG_CRYPTO_ECDSA )] +SPDM_ASYM_ECDSA_ECC_NIST_P256 => { + self.sig_len =64 ; + self.base_asym_enc =c"p1363" ; + } +#[cfg( CONFIG_CRYPTO_ECDSA )] +SPDM_ASYM_ECDSA_ECC_NIST_P384 => { + self.sig_len =96 ; + self.base_asym_enc =c"p1363" ; + } +#[cfg( CONFIG_CRYPTO_ECDSA )] +SPDM_ASYM_ECDSA_ECC_NIST_P521 => { + self.sig_len =132 ; + self.base_asym_enc =c"p1363" ; + } + _ => { +pr_err! ("Unknown asym algorithm\n" ); +return Err (EPROTO ); + } + } + +let hash_name =match self.base_hash_alg { +#[cfg( CONFIG_CRYPTO_SHA256 )] +SPDM_HASH_SHA_256 =>c"sha256" , +#[cfg( CONFIG_CRYPTO_SHA512 )] +SPDM_HASH_SHA_384 =>c"sha384" , +#[cfg( CONFIG_CRYPTO_SHA512 )] +SPDM_HASH_SHA_512 =>c"sha512" ,
lib/rspdm/state.rs pub(crate) fn get_capabilities(&mut self) -> Result<(), Error> { · line 343
Quoted 10 lines
lib/rspdm/state.rs +10
-0+ _ => { +pr_err! ("Unknown hash algorithm\n" ); +return Err (EPROTO ); + } + }; + + self.crypto =Some (Shash ::new (hash_name)?); + +Ok (()) + }
[Severity: Low]
Will this fail to build when CONFIG_RSPDM=y and any of CONFIG_CRYPTO_RSA, CONFIG_CRYPTO_ECDSA, CONFIG_CRYPTO_SHA256, or CONFIG_CRYPTO_SHA512 is not enabled?
The nine algorithm constants (SPDM_ASYM_RSASSA_2048, SPDM_ASYM_RSASSA_3072, SPDM_ASYM_RSASSA_4096, SPDM_ASYM_ECDSA_ECC_NIST_P256, SPDM_ASYM_ECDSA_ECC_NIST_P384, SPDM_ASYM_ECDSA_ECC_NIST_P521, SPDM_HASH_SHA_256, SPDM_HASH_SHA_384, and SPDM_HASH_SHA_512) are defined in lib/rspdm/consts.rs and imported in lib/rspdm/state.rs unconditionally, while their only uses in consts.rs and SpdmState::update_response_algs() are gated behind #[cfg(CONFIG_CRYPTO_*)] attributes.
Additionally, in SpdmState::update_response_algs(), if neither CONFIG_CRYPTO_RSA nor CONFIG_CRYPTO_ECDSA is enabled, or if neither CONFIG_CRYPTO_SHA256 nor CONFIG_CRYPTO_SHA512 is enabled, the corresponding match expression only retains the diverging wildcard arm that returns Err(EPROTO), making the statements following the match unreachable and Shash an unused import under -Dwarnings.
Quoted 11 lines
lib/rspdm/state.rs +11
-0+ +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 + u32::from (u16::MAX )).min (self.transport_sz)as usize ;
lib/rspdm/state.rs pub(crate) fn get_capabilities(&mut self) -> Result<(), Error> { · line 364
Quoted 10 lines
lib/rspdm/state.rs +10
-0+ + request.length =NegotiateAlgsReq ::WIRE_SIZE as u16 ; + +let mut request_buf = request.to_bytes ()?; + +let response_vec:KVec <u8 > =KVec ::from_elem (0u8 , rsp_sz,GFP_KERNEL )?; + +let response:NegotiateAlgsRsp = self + .spdm_exchange (request_buf.as_mut_slice (), response_vec)? + .validate (&*self)?;
[Severity: High]
Won't SpdmState::negotiate_algs() always fail with Err(EPROTO) inside SpdmState::spdm_exchange() before sending the request?
In SpdmState::negotiate_algs(), request.to_bytes() serializes a 32-byte NegotiateAlgsReq buffer and passes the full slice to SpdmState::spdm_exchange():
lib/rspdm/state.rs:SpdmState::spdm_exchange() {
let header_size = core::mem::size_of::<SpdmHeader>();
let request: SpdmHeader = Untrusted::new(request_buf).validate(self)?;
...
}Unlike the response validation below it, which slices response_buf.get(..header_size), SpdmState::spdm_exchange() passes the entire 32-byte request_buf to SpdmHeader::validate():
lib/rspdm/validator.rs:SpdmHeader::validate() {
SpdmHeader::read_from_bytes(unvalidated).or(Err(EPROTO))
}
Because zerocopy::FromBytes::read_from_bytes() requires the slice length to exactly equal mem::size_of::<SpdmHeader>() (4 bytes), any request message with a payload beyond the 4-byte header is rejected with Err(EPROTO). Should SpdmState::spdm_exchange() slice request_buf.get(..header_size) when validating the request header?
Quoted 7 lines
lib/rspdm/state.rs +7
-0+ + 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" );
lib/rspdm/state.rs pub(crate) fn get_capabilities(&mut self) -> Result<(), Error> { · line 381
Quoted 10 lines
lib/rspdm/state.rs +10
-0+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 + };
[Severity: Medium]
Can a responder bypass measurement specification validation here by returning an unrequested or multi-bit value in response.measurement_specification_sel?
In NegotiateAlgsReq::default(), the requester only offers SPDM_MEAS_SPEC_DMTF (bit 0). However, SpdmState::negotiate_algs() only checks whether response.measurement_specification_sel != 0 when SPDM_MEAS_RESP_CAP is set, and does not verify that response.measurement_specification_sel is 0 when SPDM_MEAS_RESP_CAP is not set.
If a responder sets an unsupported bit (such as 0x02), sets multiple bits (such as 0x03), or returns a non-zero measurement_specification_sel without advertising SPDM_MEAS_RESP_CAP (along with measurement_hash_algo == 0), negotiate_algs() will accept the response. Should this verify that response.measurement_specification_sel is either 0 or SPDM_MEAS_SPEC_DMTF (and 0 when SPDM_MEAS_RESP_CAP is absent)?
[ ... ]
Quoted 4 lines
lib/rspdm/validator.rs +0
-0
[ ... ]
Quoted 4 lines
lib/rspdm/validator.rs +0
-0@@ -493,3 +497,225 @@ fn validate(unvalidated: KVec<u8>, context: &'c SpdmState<'c, T>) -> Result<Self }) } }
[ ... ]
Quoted 6 lines
lib/rspdm/validator.rs +6
-0+impl <'c,T :Transport >Validate <'c,Untrusted <KVec <u8 >>, &'cSpdmState <'c,T >> +for NegotiateAlgsRsp +{ +type Err =Error ; + +fn validate (unvalidated:KVec <u8 >, context: &'cSpdmState <'c,T >) ->Result <Self ,Self ::Err > {
[ ... ]
Quoted 20 lines
lib/rspdm/validator.rs +20
-0+let mel_specification_sel = *unvalidated.get (31 ).ok_or (EPROTO )?; +let ext_asym_sel_count = *unvalidated.get (32 ).ok_or (EPROTO )?; +let ext_hash_sel_count = *unvalidated.get (33 ).ok_or (EPROTO )?; + +// We currently never request extended algorithms or algorithm structures +if header.param1 !=0 || ext_asym_sel_count !=0 || ext_hash_sel_count !=0 { +pr_err! ("Responder selected unsupported extended algorithms\n" ); +return Err (EPROTO ); + } + +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 {
lib/rspdm/validator.rs fn validate(unvalidated: KVec<u8>, context: &'c SpdmState<'c, T>) -> Result<Self · line 670
Quoted 10 lines
lib/rspdm/validator.rs +10
-0+ 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 (EPROTO )?; +let alg_count = *unvalidated.get (offset +1 ).ok_or (EPROTO )?; +let fixed_alg_count = (alg_count &0xf )as usize ; +let ext_alg_count = (alg_count >>4 )as usize ;
[Severity: Medium]
Are fixed_alg_count and ext_alg_count swapped when decoding alg_count here and in SupportedAlgorithms::from_bytes()?
Per the DMTF SPDM specification (DSP0274) for ReqAlgStruct and RespAlgStruct, the 1-byte AlgCount field encodes FixedAlgCount in bits [7:4] (alg_count >> 4) and ExtAlgCount in bits [3:0] (alg_count & 0xf). Both NegotiateAlgsRsp::validate() and SupportedAlgorithms::from_bytes() extract fixed_alg_count from the low nibble and ext_alg_count from the high nibble:
lib/rspdm/validator.rs:SupportedAlgorithms::from_bytes() {
...
for _ in 0..alg_struct_count {
let alg_type = *buf.get(offset).ok_or(EPROTO)?;
let alg_count = *buf.get(offset + 1).ok_or(EPROTO)?;
let fixed_alg_count = (alg_count & 0xf) as usize;
let ext_alg_count = (alg_count >> 4) as usize;
...
}For a standard 4-byte algorithm structure where alg_count is 0x20 (2 fixed algorithm bytes and 0 extended dwords), this reads 0 bytes of alg_supported and 8 bytes of alg_external, causing SupportedAlgorithms::from_bytes() to fail with Err(EPROTO) when validating SPDM 1.3+ capabilities responses. Also, in NegotiateAlgsRsp::validate(), the check just above rejects any non-zero header.param1, ext_asym_sel_count, or ext_hash_sel_count, which makes these three loops in NegotiateAlgsRsp::validate() unreachable.
Quoted 22 lines
lib/rspdm/validator.rs +22
-0+ offset +=2 ; + +let mut alg_supported =KVec ::new (); + alg_supported.extend_from_slice ( + unvalidated + .get (offset..offset + fixed_alg_count) + .ok_or (EPROTO )?, +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,
lib/rspdm/validator.rs fn validate(unvalidated: KVec<u8>, context: &'c SpdmState<'c, T>) -> Result<Self · line 702
Quoted 10 lines
lib/rspdm/validator.rs +10
-0+ alg_external, + }, + GFP_KERNEL, + )?; + } + +if resp_len != offsetas u16 { +pr_err! ("Incorrect response length reported\n" ); +return Err (EPROTO ); + }
[Severity: Medium]
What happens if a responder returns a truncated 34- or 35-byte buffer whose header resp_len field is set to 36 (NEGOTIATE_ALGS_RSP_SZ)?
In NegotiateAlgsRsp::validate(), the last byte accessed from unvalidated is at index 33 (ext_hash_sel_count), leaving the two reserved bytes at offsets 34 and 35 unread. Because the loops are skipped when counts are zero, offset remains NEGOTIATE_ALGS_RSP_SZ (36), and the check only compares the untrusted resp_len field from the packet against offset without verifying that unvalidated.len() >= offset.
If unvalidated is 34 or 35 bytes long with resp_len == 36, validate() succeeds and sets NegotiateAlgsRsp.length to 36 while raw_bytes is shorter than 36 bytes, causing NegotiateAlgsRsp::as_bytes() to fail with Err(EPROTO). Should NegotiateAlgsRsp::validate() check that unvalidated.len() >= offset?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=17
PATCH v5 18/23 lib: rspdm: Support SPDM get_digests
In reply to [email protected]From: Alistair Francis <[email protected]>
Support the GET_DIGESTS SPDM command.
--- lib/rspdm/consts.rs | 5 +- lib/rspdm/lib.rs | 4 ++ lib/rspdm/state.rs | 61 +++++++++++++++++++++++- lib/rspdm/validator.rs | 102 ++++++++++++++++++++++++++++++++++++++++- 4 files changed, 169 insertions(+), 3 deletions(-)
lib/rspdm/consts.rs +4 -1
@@ -18,10 +18,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 ;
@@ -72,6 +73,8 @@pub (crate )const SPDM_HASH_SHA_384 :u32 =bit_u32 (1 );pub (crate )const SPDM_HASH_SHA_512 :u32 =bit_u32 (2 ); +pub (crate )const SPDM_GET_DIGESTS :u8 =0x81 ; +// If the crypto support isn't enabled don't offer the algorithms // to the responder #[cfg( CONFIG_CRYPTO_RSA )]
lib/rspdm/lib.rs +4 -0
@@ -270,6 +270,10 @@ pub extern "C" fn spdm_authenticate(state_ptr: *mut spdm_state) -> c_int {return e.to_errno ()as c_int ; } +if let Err (e) = state.get_digests () { +return e.to_errno ()as c_int ; + } + -(EPROTONOSUPPORT as i32 ) }
lib/rspdm/state.rs +60 -1
101 lines not on this page · raw message
lib/rspdm/validator.rs +101 -1
119 lines not on this page · raw message
Signature · 1 line
2.56.0
[email protected]
AI reviewRe: [PATCH v5 18/23] lib: rspdm: Support SPDM get_digests
Sashiko has reviewed this patch and found no issues.
Quoted 5 lines
From: Alistair Francis <[email protected]>
Support the
GET_DIGESTSSPDM command.Signed-off-by Alistair Francis <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=18
PATCH v5 19/23 lib: rspdm: Support SPDM get_certificate
In reply to [email protected]From: Alistair Francis <[email protected]>
Support the GET_CERTIFICATE SPDM command.
The kernel will send a GET_CERTIFICATE request to the the responder and then iterate over all of the certificates returned.
Certificate validation happens in the next commit.
--- lib/rspdm/consts.rs | 2 + lib/rspdm/lib.rs | 15 ++++ lib/rspdm/state.rs | 100 ++++++++++++++++++++++++- lib/rspdm/validator.rs | 162 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 278 insertions(+), 1 deletion(-)
lib/rspdm/consts.rs +2 -0
@@ -75,6 +75,8 @@pub (crate )const SPDM_GET_DIGESTS :u8 =0x81 ; +pub (crate )const SPDM_GET_CERTIFICATE :u8 =0x82 ; +// If the crypto support isn't enabled don't offer the algorithms // to the responder #[cfg( CONFIG_CRYPTO_RSA )]
lib/rspdm/lib.rs +15 -0
@@ -274,6 +274,21 @@ pub extern "C" fn spdm_authenticate(state_ptr: *mut spdm_state) -> c_int {return e.to_errno ()as c_int ; } +if state.provisioned_slots ==0 { +return -(bindings::EIO as c_int ); + } + +let mut provisioned_slots = state.provisioned_slots; +while (provisioned_slotsas usize ) >0 { +let slot = provisioned_slots.trailing_zeros ()as u8 ; + +if let Err (e) = state.get_certificate (slot) { +return e.to_errno ()as c_int ; + } + + provisioned_slots &= !(1 << slot); + } + -(EPROTONOSUPPORT as i32 ) }
lib/rspdm/state.rs +99 -1
119 lines not on this page · raw message
lib/rspdm/validator.rs +162 -0
171 lines not on this page · raw message
Signature · 1 line
2.56.0
[email protected]
AI reviewRe: [PATCH v5 19/23] lib: rspdm: Support SPDM get_certificate
Sashiko has reviewed this patch and found no issues.
Quoted 10 lines
From: Alistair Francis <[email protected]>
Support the
GET_CERTIFICATESPDM command.The kernel will send a
GET_CERTIFICATErequest 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 <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=19
PATCH v5 20/23 rust: public_key: Add crypto public key bindings
In reply to [email protected]From: Alistair Francis <[email protected]>
Initial support for the kernel crypto public key (asymmetric-keys) bindings, which will be used by the SPDM library.
This is just a few functions that the Rust SPDM implementation will use in the next few commits.
--- rust/bindings/bindings_helper.h | 1 + rust/kernel/lib.rs | 2 + rust/kernel/public_key.rs | 69 +++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+) create mode 100644 rust/kernel/public_key.rs
rust/bindings/bindings_helper.h +1 -0
@@ -29,6 +29,7 @@#include <linux/hrtimer_types.h> #include <crypto/hash.h> +#include <crypto/public_key.h> #include <linux/acpi.h> #include <linux/gpu_buddy.h> #include <drm/drm_device.h>
rust/kernel/lib.rs +2 -0
@@ -122,6 +122,8 @@pub mod print;pub mod processor;pub mod ptr; +#[cfg( CONFIG_CRYPTO )] +pub mod public_key;#[cfg( CONFIG_RUST_PWM_ABSTRACTIONS )] pub mod pwm;pub mod ratelimit;
rust/kernel/public_key.rs new file +69 -0
69 lines not on this page · raw message
Signature · 1 line
2.56.0
[email protected]
AI reviewRe: [PATCH v5 20/23] rust: public_key: Add crypto public key bindings
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: - [Medium] rust: public_key: missing unsafe on PublicKey::from_raw() - [Low] rust: public_key: wrong…
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Medium] rust: public_key: missing unsafe on PublicKey::from_raw()
- [Low] rust: public_key: wrong Kconfig guard on public_key module in lib.rs
- [Low] rust: public_key: missing inline attributes and split imports
--
commit d3b51b7112cb9e69d8cc7083f31a2a45bf366492
Author: Alistair Francis <[email protected]>
rust: public_key: Add crypto public key bindings
This commit adds initial Rust bindings and a PublicKey wrapper around the kernel crypto public key (asymmetric-keys) APIs, including PublicKey::from_raw(), PublicKey::verify_signature(), and Drop cleanup via public_key_free().
rust/kernel/lib.rs line 126
Quoted 10 lines
rust/kernel/lib.rs +2
-0@@ -122,6 +122,8 @@pub mod print;pub mod processor;pub mod ptr; +#[cfg( CONFIG_CRYPTO )] +pub mod public_key;
[Severity: Low]
Will gating the public_key module on CONFIG_CRYPTO break the build when CONFIG_RUST=y and CONFIG_CRYPTO=y, while CONFIG_ASYMMETRIC_PUBLIC_KEY_SUBTYPE is n or m?
In rust/kernel/public_key.rs, PublicKey::verify_signature() calls bindings::public_key_verify_signature() and PublicKey::drop() calls bindings::public_key_free(). Both functions are defined in crypto/asymmetric_keys/public_key.c, which is built under the tristate symbol CONFIG_ASYMMETRIC_PUBLIC_KEY_SUBTYPE rather than CONFIG_CRYPTO:
crypto/asymmetric_keys/Makefile:
obj-$(CONFIG_ASYMMETRIC_PUBLIC_KEY_SUBTYPE) += public_key.o
When CONFIG_ASYMMETRIC_PUBLIC_KEY_SUBTYPE=n, include/crypto/public_key.h defines public_key_verify_signature() as a static inline stub:
include/crypto/public_key.h:
#if IS_REACHABLE(CONFIG_ASYMMETRIC_PUBLIC_KEY_SUBTYPE)
int public_key_verify_signature(const struct public_key *pkey,
const struct public_key_signature *sig);
#else
static inline
int public_key_verify_signature(const struct public_key *pkey,
const struct public_key_signature *sig)
{
return -EINVAL;
}
#endifWithout a Rust helper wrapper, bindgen does not generate bindings::public_key_verify_signature, and public_key_free() has no stub at all. Additionally, when CONFIG_ASYMMETRIC_PUBLIC_KEY_SUBTYPE=m, built-in rust/kernel.o cannot link against public_key_verify_signature() and public_key_free() in the loadable public_key.ko module.
Should this module instead be gated on #[cfg(CONFIG_ASYMMETRIC_PUBLIC_KEY_SUBTYPE = "y")]?
Quoted 24 lines
rust/kernel/public_key.rs new file +18
-0@@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2026 Western Digital + +//! Wrappers around the Linux kernel crypto public key APIs. +//! +//! C headers: [`include/crypto/public_key.h`](srctree/include/crypto/public_key.h). +//! +//! Reference: <https://www.kernel.org/doc/html/latest/crypto/asymmetric-keys.html> + +use core::ptr::NonNull ; +use kernel::prelude::*; +use kernel::{ + bindings, + error::to_result, + str::CStr ,// +}; +
rust/kernel/public_key.rs line 28
Quoted 10 lines
rust/kernel/public_key.rs new file +10
-0+/// Owned `struct public_key` wrapper. +pub struct PublicKey { + key:NonNull <bindings::public_key >, +} + +impl PublicKey { +/// Create a Rust `PublicKey` from a valid C `struct public_key` pointer. +pub fn from_raw (key:NonNull <bindings::public_key >) ->Self { +Self { key } + }
[Severity: Medium]
Should PublicKey::from_raw() be marked unsafe?
NonNull<bindings::public_key> is Copy and can be constructed or duplicated in safe Rust (for example via NonNull::dangling() or passing the same NonNull pointer twice). However, PublicKey::verify_signature() passes self.key.as_ptr() to bindings::public_key_verify_signature(), which dereferences the pointer and its internal fields, and PublicKey::drop() unconditionally passes self.key.as_ptr() to bindings::public_key_free(), which calls kfree_sensitive(key->key), kfree(key->params), and kfree(key).
Although Certificate::take_public_key() in a later commit in this series passes a valid pointer and clears cert->pub_, leaving PublicKey::from_raw() as a safe public function makes the abstraction unsound for safe callers. Could PublicKey document its type invariants in an Invariants section, and could PublicKey::from_raw() be made an unsafe fn with a Safety section and an invariant comment?
Quoted 31 lines
rust/kernel/public_key.rs new file +31
-0+ +/// Verify a signature using this public key. +pub fn verify_signature ( + &self, + signature: &[u8 ], + encoding: &'staticCStr , + hash_algo: &'staticCStr , + message: &[u8 ], + ) ->Result { +let sig = bindings::public_key_signature { + s: signature.as_ptr ().cast_mut (), + s_size: signature.len ()as u32 , + encoding: encoding.as_char_ptr ().cast ::<u8 >(), + hash_algo: hash_algo.as_char_ptr ().cast ::<u8 >(), + m: message.as_ptr ().cast_mut (), + m_size: message.len ()as u32 , + ..Default ::default () + }; + +// SAFETY: `self.key` is a valid public key handle, and `sig` points to +// valid signature/message data owned by the caller for the duration of +// this call. +unsafe { +to_result (bindings::public_key_verify_signature ( + self.key.as_ptr (), + &sig, + )) + } + } +} +
rust/kernel/public_key.rs line 69
Quoted 10 lines
rust/kernel/public_key.rs new file +10
-0+impl Drop for PublicKey { +fn drop (&mut self) { +// SAFETY: `self.key` was either returned by `x509_cert_parse()` or +// manually transferred from an `x509_certificate` and has not been +// freed yet. +unsafe { + bindings::public_key_free (self.key.as_ptr ()); + } + } +}
[Severity: Low]
This isn't a bug, but per the Rust coding guidelines for the kernel crate, could PublicKey::from_raw(), PublicKey::verify_signature(), and PublicKey::drop() be annotated with #[inline], and could the two use kernel::... blocks at the top of rust/kernel/public_key.rs be merged into a single vertical import block (noting that CStr is already exported by kernel::prelude::*)?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=20
PATCH v5 21/23 rust: x509: Add X509 certificate bindings
In reply to [email protected]From: Alistair Francis <[email protected]>
Initial support for the kernel x509 parser bindings, which will be used by the SPDM library.
This is just a few functions that the Rust SPDM implementation will use in the next few commits.
--- rust/bindings/bindings_helper.h | 2 + rust/kernel/lib.rs | 2 + rust/kernel/x509.rs | 118 ++++++++++++++++++++++++++++++++ 3 files changed, 122 insertions(+) create mode 100644 rust/kernel/x509.rs
rust/bindings/bindings_helper.h +2 -0
@@ -39,6 +39,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>
rust/kernel/lib.rs +2 -0
@@ -153,6 +153,8 @@pub mod usb;pub mod validate;pub mod workqueue; +#[cfg( CONFIG_CRYPTO )] +pub mod x509;pub mod xarray;#[doc(hidden)]
rust/kernel/x509.rs new file +118 -0
118 lines not on this page · raw message
Signature · 1 line
2.56.0
[email protected]
AI reviewRe: [PATCH v5 21/23] rust: x509: Add X509 certificate bindings
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: - [Medium] rust: x509: build and link failure when CONFIG_X509_CERTIFICATE_PARSER != y - [Medium] rust…
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Medium] rust: x509: build and link failure when CONFIG_X509_CERTIFICATE_PARSER != y
- [Medium] rust: bindings: build failure in bindings_helper.h when CONFIG_KEYS=n
- [Low] rust: x509: missing inline attributes, import style, and log typo --
commit bd0a2a1272842d031d7bfad2c6e145a2a0b663dc
Author: Alistair Francis <[email protected]>
rust: x509: Add X509 certificate bindings
This commit adds initial Rust bindings and a Certificate wrapper around the kernel X.509 certificate parser APIs for use by the Rust SPDM implementation.
rust/bindings/bindings_helper.h line 43
Quoted 10 lines
rust/bindings/bindings_helper.h +2
-0@@ -39,6 +39,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>
[Severity: Medium]
Does including keys/asymmetric-type.h and keys/x509-parser.h unconditionally break bindgen when CONFIG_KEYS is disabled?
In include/linux/key.h, struct key is only defined under #ifdef CONFIG_KEYS and is otherwise only forward-declared:
include/linux/key.h:
struct key;
struct net;
#ifdef CONFIG_KEYSHowever, include/keys/asymmetric-type.h defines inline functions outside any #ifdef CONFIG_KEYS guard that dereference const struct key *:
include/keys/asymmetric-type.h:
static inline
const struct asymmetric_key_ids *asymmetric_key_ids(const struct key *key)
{
return key->payload.data[asym_key_ids];
}When building with CONFIG_RUST=y and CONFIG_KEYS=n, clang fails during bindgen generation with an incomplete type error on struct key. Should these headers be guarded or should struct key handling in include/keys/asymmetric-type.h account for !CONFIG_KEYS?
rust/kernel/lib.rs line 157
Quoted 10 lines
rust/kernel/lib.rs +2
-0@@ -153,6 +153,8 @@pub mod usb;pub mod validate;pub mod workqueue; +#[cfg( CONFIG_CRYPTO )] +pub mod x509;
[Severity: Medium]
Should the x509 module be gated on #[cfg(CONFIG_X509_CERTIFICATE_PARSER = "y")] instead of #[cfg(CONFIG_CRYPTO)]?
Because rust/kernel.o is always built-in when CONFIG_RUST=y, non-generic methods in rust/kernel/x509.rs such as Certificate::parse(), Certificate::verify_signed(), and Certificate::drop() are compiled directly into vmlinux whenever CONFIG_CRYPTO is enabled.
In Certificate::parse() and Certificate::drop(), the code calls bindings::x509_get_certificate_length(), bindings::x509_cert_parse(), and bindings::x509_free_certificate(), which are only built under CONFIG_X509_CERTIFICATE_PARSER in crypto/asymmetric_keys/Makefile:
obj-$(CONFIG_X509_CERTIFICATE_PARSER) += x509_key_parser.o
x509_key_parser-y := \
x509.asn1.o \
x509_akid.asn1.o \
x509_cert_parser.o \
x509_loader.o \
x509_public_key.oSimilarly, Certificate::verify_signed() calls bindings::public_key_verify_signature(), which is built under CONFIG_ASYMMETRIC_PUBLIC_KEY_SUBTYPE (selected by CONFIG_X509_CERTIFICATE_PARSER) and falls back to a static inline stub in include/crypto/public_key.h when disabled, which bindgen does not expose without a rust_helper_* wrapper.
If CONFIG_CRYPTO is enabled while CONFIG_X509_CERTIFICATE_PARSER is n or m, won't this fail to compile or fail to link vmlinux with undefined references from rust/kernel.o?
Quoted 104 lines
rust/kernel/x509.rs new file +98
-0@@ -0,0 +1,118 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2026 Western Digital + +//! Wrappers around the Linux kernel X509 parser APIs. +//! +//! C headers: [`include/keys/x509-parser.h`](srctree/include/keys/x509-parser.h). + +use core::ffi::c_void; +use core::ptr::{self,NonNull }; +use kernel::prelude::*; +use kernel::{ + bindings, + error::{ + from_err_ptr, + to_result,// + }, + public_key::PublicKey , +}; + +/// Owned `struct x509_certificate` wrapper. +pub struct Certificate { + cert:NonNull <bindings::x509_certificate >, +} + +impl Certificate { +/// Parses a DER certificate from `data` and returns it with the length +/// of the certificate in bytes. +pub fn parse (data: &[u8 ]) ->Result <(Self ,usize )> { +// SAFETY: `data.as_ptr()` points to at least `data.len()` valid bytes. +let len =unsafe { bindings::x509_get_certificate_length (data.as_ptr (), data.len ()) }; +let len = usize::try_from (len).map_err (|_| { +pr_err! ("Invalid certificate length\n" ); +EPROTO + })?; + +if len > data.len () { +pr_err! ("Certificate length is larger then the buffer\n" ); +return Err (EPROTO ); + } + +// SAFETY: `data.as_ptr()` points to `data.len()` valid bytes and the +// caller has already checked that the DER certificate length fits in +// that buffer. +let cert =from_err_ptr (unsafe { + bindings::x509_cert_parse (data.as_ptr ().cast ::<c_void >(), len) + })?; +let cert =NonNull ::new (cert).ok_or (EINVAL )?; + +Ok ((Self { cert }, len)) + } + +/// Returns the raw pointer for the X.509 certificate. +/// This is what is returned from the C function x509_cert_parse() +pub fn raw (&self) -> *mut bindings::x509_certificate { + self.cert.as_ptr () + } + +/// Check that the certificate is not unsupported or blacklisted. +pub fn check_accepted (&self) ->Result { +// SAFETY: `self.cert` was returned by `x509_cert_parse()` and remains +// valid until dropped. +let cert =unsafe { &*self.cert.as_ptr () }; +if cert.unsupported_sig || cert.blacklisted { +pr_err! ("Certificate was rejected\n" ); +return Err (EKEYREJECTED ); + } + +Ok (()) + } + +/// Verify that `child`'s signature is signed by this cert's public/private key. +pub fn verify_signed (&self, child: &Self ) ->Result { +// SAFETY: both certificate pointers were produced by `x509_cert_parse()` +// and remain valid until dropped. +let (prev, child) =unsafe { (&*self.cert.as_ptr (), &*child.cert.as_ptr ()) }; + +let prev_pub =NonNull ::new (prev.pub_).ok_or (EINVAL )?; +let child_sig =NonNull ::new (child.sig).ok_or (EINVAL )?; + +// SAFETY: `prev_pub` and `child_sig` are valid for at least the duration +// of this call. +unsafe { +to_result (bindings::public_key_verify_signature ( + prev_pub.as_ptr (), + child_sig.as_ptr (), + )) + } + } + +/// Transfers the public key out of the certificate so the certificate can be +/// dropped without freeing the public key. +pub fn take_public_key (&mut self) ->Option <PublicKey > { +// SAFETY: `self.cert` is a valid certificate pointer. +let raw =unsafe { (*self.cert.as_ptr ()).pub_ }; +let key =NonNull ::new (raw)?; + +// SAFETY: `self.cert` is a valid certificate pointer. Clearing `pub_`
rust/kernel/x509.rs line 108
Quoted 10 lines
rust/kernel/x509.rs new file +10
-0+// transfers ownership of the public key to the returned `PublicKey`. +// The `pub_` field will not be freed by `x509_free_certificate()` in +// Drop +unsafe { + (*self.cert.as_ptr ()).pub_ = ptr::null_mut (); + } + +Some (PublicKey ::from_raw (key)) + } +}
[Severity: Low]
This isn't a bug, but there are a few minor style and coding guideline items in rust/kernel/x509.rs:
1. At the top of rust/kernel/x509.rs, core::ptr::{self, NonNull} should use vertical import formatting, and core::ffi::c_void is already provided by kernel::prelude::*.
2. In Certificate::parse(), the error message has a typo ("larger then" instead of "larger than").
3. Small abstraction methods in the kernel crate such as Certificate::raw(), Certificate::check_accepted(), Certificate::verify_signed(), and Certificate::take_public_key() should be annotated with #[inline].
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=21
PATCH v5 22/23 lib: rspdm: Support SPDM certificate validation
In reply to [email protected]From: Alistair Francis <[email protected]>
Support validating the SPDM certificate chain. This only performs basic sanity checks on the chain before we continue on. This does not ensure that the root CA is trusted, we leave that for userspace to check and enforce. Instead we just make sure that the chain is correct, uses supported signatures and that it isn't blacklisted in the kernel.
We then store the first leaf certificate for use later.
--- lib/rspdm/lib.rs | 12 ++++++++++ lib/rspdm/state.rs | 56 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 66 insertions(+), 2 deletions(-)
lib/rspdm/lib.rs +12 -0
@@ -289,6 +289,18 @@ pub extern "C" fn spdm_authenticate(state_ptr: *mut spdm_state) -> c_int { provisioned_slots &= !(1 << slot); } +let mut provisioned_slots = state.provisioned_slots; +while (provisioned_slotsas usize ) >0 { +let slot = provisioned_slots.trailing_zeros ()as u8 ; + +if let Err (e) = state.validate_cert_chain (slot) { +pr_err! ("Certificate in slot {slot} failed to verify: {e:?}\n" ); +return e.to_errno ()as c_int ; + } + + provisioned_slots &= !(1 << slot); + } + -(EPROTONOSUPPORT as i32 ) }
lib/rspdm/state.rs +54 -2
93 lines not on this page · raw message
Signature · 1 line
2.56.0
[email protected]
AI reviewRe: [PATCH v5 22/23] lib: rspdm: Support SPDM certificate validation
Sashiko has reviewed this patch and found no issues.
Quoted 11 lines
From: Alistair Francis <[email protected]>
Support validating the SPDM certificate chain. This only performs basic sanity checks on the chain before we continue on. This does not ensure that the root CA is trusted, we leave that for userspace to check and enforce. Instead we just make sure that the chain is correct, uses supported signatures and that it isn't blacklisted in the kernel.
We then store the first leaf certificate for use later.
Signed-off-by Alistair Francis <[email protected]>
Sashiko has reviewed this patch and found no issues.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=22
PATCH v5 23/23 lib: rspdm: Support SPDM challenge
In reply to [email protected]From: Alistair Francis <[email protected]>
Support the CHALLENGE SPDM command.
--- lib/rspdm/consts.rs | 6 ++ lib/rspdm/lib.rs | 10 ++- lib/rspdm/state.rs | 188 +++++++++++++++++++++++++++++++++++++++-- lib/rspdm/validator.rs | 156 ++++++++++++++++++++++++++++++++-- 4 files changed, 347 insertions(+), 13 deletions(-)
lib/rspdm/consts.rs +6 -0
@@ -77,6 +77,8 @@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 )]
@@ -105,3 +107,7 @@pub (crate )const SPDM_HASH_ALGOS :u32 =SPDM_HASH_SHA2_256 |SPDM_HASH_SHA2_384_512 ;pub (crate )const SPDM_OPAQUE_DATA_FMT_GENERAL :u8 =bit_u8 (1 ); + +pub (crate )const SPDM_PREFIX_SZ :usize =64 ; +pub (crate )const SPDM_COMBINED_PREFIX_SZ :usize =100 ; +pub (crate )const SPDM_MAX_OPAQUE_DATA :usize =1024 ;
lib/rspdm/lib.rs +7 -3
@@ -11,8 +11,7 @@//! from other subsytems. use crate ::bindings::{ - spdm_state, -EPROTONOSUPPORT ,// + spdm_state,// };use core::ffi::{ c_int,
@@ -301,7 +300,12 @@ pub extern "C" fn spdm_authenticate(state_ptr: *mut spdm_state) -> c_int { provisioned_slots &= !(1 << slot); } - -(EPROTONOSUPPORT as i32 ) +let provisioned_slots = state.provisioned_slots.trailing_zeros (); +if let Err (e) = state.challenge (provisioned_slotsas u8 ) { +return e.to_errno ()as c_int ; + } + +0 }/// Destroys an SPDM session.
lib/rspdm/state.rs +183 -5
306 lines not on this page · raw message
lib/rspdm/validator.rs +151 -5
201 lines not on this page · raw message
Signature · 1 line
2.56.0
[email protected]
AI reviewRe: [PATCH v5 23/23] lib: rspdm: Support SPDM challenge
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] lib: rspdm: unsliced request_buf in spdm_exchange() fails validation --
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] lib: rspdm: unsliced request_buf in spdm_exchange() fails validation --
commit b875413583bbf6bfc09ae38ff772d19acd98e3d0
Author: Alistair Francis <[email protected]>
lib: rspdm: Support SPDM challenge
This commit adds support for the SPDM CHALLENGE command, including message transcript tracking, signature verification against the responder leaf certificate, and challenge response validation.
Quoted 4 lines
lib/rspdm/state.rs +0
-0
[ ... ]
lib/rspdm/state.rs pub(crate) fn hash_len(&self) -> usize { · line 225
Quoted 12 lines
lib/rspdm/state.rs +2
-2@@ -197,12 +217,14 @@ pub(crate) fn hash_len(&self) -> usize {/// The data in `request_buf` is sent to the device and the response /// is returned as untrusted data to be validated by the caller. pub (crate )fn spdm_exchange ( - &self, + &mut self, request_buf: &[u8 ],mut response_buf:KVec <u8 >, ) ->Result <Untrusted <KVec <u8 >>,Error > {let header_size = core::mem::size_of ::<SpdmHeader >(); -let request:SpdmHeader =Untrusted ::new (request_buf).validate (self)?; +let request:SpdmHeader =Untrusted ::new (request_buf).validate (&*self)?;
[Severity: High]
Does validating the full request_buf as an SpdmHeader cause SpdmState::challenge() (and other requests with payloads beyond the 4-byte header) to fail with EPROTO?
In lib/rspdm/state.rs, spdm_authenticate() calls SpdmState::challenge(), which serializes a 36-byte (or 44-byte for SPDM 1.3+) ChallengeReq via ChallengeReq::to_bytes() and passes the buffer to SpdmState::spdm_exchange():
lib/rspdm/state.rs:SpdmState::challenge() {
...
let mut request_buf = request.to_bytes()?;
...
let mut response: ChallengeRsp = self
.spdm_exchange(request_buf.as_mut_slice(), response_vec)?
.validate(&*self)?;
}In SpdmState::spdm_exchange(), request_buf is passed unsliced to Untrusted::new(request_buf).validate(&*self), which invokes SpdmHeader::validate() in lib/rspdm/validator.rs:
lib/rspdm/validator.rs:SpdmHeader::validate() {
SpdmHeader::read_from_bytes(unvalidated).or(Err(EPROTO))
}
Because zerocopy::FromBytes::read_from_bytes() requires the input slice length to exactly equal size_of::<SpdmHeader>() (4 bytes) via Ref::sized_from() in rust/zerocopy/src/ref.rs, any request buffer larger than 4 bytes is rejected with EPROTO before the request can be added to the transcript or sent over the transport.
Should request_buf be sliced to ..header_size before validating the request header, matching how response_buf.get(..header_size) is validated later in SpdmState::spdm_exchange()?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=23