This is the initial commit of the Rust SPDM library. It is based on and compatible with the C SPDM library in the kernel (lib/spdm). Signed-off-by: Alistair Francis <alistair@xxxxxxxxxxxxx> --- MAINTAINERS | 12 ++ include/linux/spdm.h | 39 ++++++ lib/Kconfig | 16 +++ lib/Makefile | 2 + lib/rspdm/Makefile | 10 ++ lib/rspdm/consts.rs | 39 ++++++ lib/rspdm/lib.rs | 119 +++++++++++++++++ lib/rspdm/state.rs | 225 ++++++++++++++++++++++++++++++++ lib/rspdm/validator.rs | 66 ++++++++++ rust/bindings/bindings_helper.h | 2 + rust/kernel/error.rs | 3 + 11 files changed, 533 insertions(+) create mode 100644 include/linux/spdm.h create mode 100644 lib/rspdm/Makefile create mode 100644 lib/rspdm/consts.rs create mode 100644 lib/rspdm/lib.rs create mode 100644 lib/rspdm/state.rs create mode 100644 lib/rspdm/validator.rs diff --git a/MAINTAINERS b/MAINTAINERS index 1b0cc181db74..0f37dbdcfd77 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -21376,6 +21376,18 @@ M: Security Officers <security@xxxxxxxxxx> S: Supported F: Documentation/process/security-bugs.rst +SECURITY PROTOCOL AND DATA MODEL (SPDM) +M: Jonathan Cameron <jic23@xxxxxxxxxx> +M: Lukas Wunner <lukas@xxxxxxxxx> +M: Alistair Francis <alistair@xxxxxxxxxxxxx> +L: linux-coco@xxxxxxxxxxxxxxx +L: linux-cxl@xxxxxxxxxxxxxxx +L: linux-pci@xxxxxxxxxxxxxxx +S: Maintained +T: git git://git.kernel.org/pub/scm/linux/kernel/git/devsec/spdm.git +F: include/linux/spdm.h +F: lib/rspdm/ + SECURITY SUBSYSTEM M: Paul Moore <paul@xxxxxxxxxxxxxx> M: James Morris <jmorris@xxxxxxxxx> diff --git a/include/linux/spdm.h b/include/linux/spdm.h new file mode 100644 index 000000000000..9835a3202a0e --- /dev/null +++ b/include/linux/spdm.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * DMTF Security Protocol and Data Model (SPDM) + * https://www.dmtf.org/dsp/DSP0274 + * + * Copyright (C) 2021-22 Huawei + * Jonathan Cameron <Jonathan.Cameron@xxxxxxxxxx> + * + * Copyright (C) 2022-24 Intel Corporation + */ + +#ifndef _SPDM_H_ +#define _SPDM_H_ + +#include <linux/types.h> + +struct key; +struct device; +struct spdm_state; +struct x509_certificate; + +typedef ssize_t (spdm_transport)(void *priv, struct device *dev, + const void *request, size_t request_sz, + void *response, size_t response_sz); + +typedef int (spdm_validate)(struct device *dev, u8 slot, + struct x509_certificate *leaf_cert); + +struct spdm_state *spdm_create(struct device *dev, spdm_transport *transport, + void *transport_priv, u32 transport_sz, + struct key *keyring, spdm_validate *validate); + +int spdm_authenticate(struct spdm_state *spdm_state); + +void spdm_destroy(struct spdm_state *spdm_state); + +extern const struct attribute_group spdm_attr_group; + +#endif diff --git a/lib/Kconfig b/lib/Kconfig index dccb61b7d698..df9c96a440c5 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -714,6 +714,22 @@ config LWQ_TEST help Run boot-time test of light-weight queuing. +config RSPDM + bool "Rust SPDM" + select CRYPTO + select KEYS + select ASYMMETRIC_KEY_TYPE + select ASYMMETRIC_PUBLIC_KEY_SUBTYPE + select X509_CERTIFICATE_PARSER + help + The Rust implementation of the Security Protocol and Data Model (SPDM) + allows for device authentication, measurement, key exchange and + encrypted sessions. + + Crypto algorithms negotiated with SPDM are limited to those enabled + in .config. Users of SPDM therefore need to also select + any algorithms they deem mandatory. + endmenu config GENERIC_IOREMAP diff --git a/lib/Makefile b/lib/Makefile index d5cfc7afbbb8..09e1cfe413ef 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -303,6 +303,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/ hostprogs := gen_crc32table diff --git a/lib/rspdm/Makefile b/lib/rspdm/Makefile new file mode 100644 index 000000000000..1f62ee2a882d --- /dev/null +++ b/lib/rspdm/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Rust implementation of the DMTF Security Protocol and Data Model (SPDM) +# https://www.dmtf.org/dsp/DSP0274 +# +# Copyright (C) 2024 Western Digital + +obj-$(CONFIG_RSPDM) += spdm.o + +spdm-y := lib.o diff --git a/lib/rspdm/consts.rs b/lib/rspdm/consts.rs new file mode 100644 index 000000000000..311e34c7fae7 --- /dev/null +++ b/lib/rspdm/consts.rs @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2024 Western Digital + +//! Constants used by the library +//! +//! Rust implementation of the DMTF Security Protocol and Data Model (SPDM) +//! <https://www.dmtf.org/dsp/DSP0274> + +pub(crate) const SPDM_REQ: u8 = 0x80; +pub(crate) const SPDM_ERROR: u8 = 0x7f; + +#[expect(dead_code)] +#[derive(Clone, Copy)] +pub(crate) enum SpdmErrorCode { + InvalidRequest = 0x01, + InvalidSession = 0x02, + Busy = 0x03, + UnexpectedRequest = 0x04, + Unspecified = 0x05, + DecryptError = 0x06, + UnsupportedRequest = 0x07, + RequestInFlight = 0x08, + InvalidResponseCode = 0x09, + SessionLimitExceeded = 0x0a, + SessionRequired = 0x0b, + ResetRequired = 0x0c, + ResponseTooLarge = 0x0d, + RequestTooLarge = 0x0e, + LargeResponse = 0x0f, + MessageLost = 0x10, + InvalidPolicy = 0x11, + VersionMismatch = 0x41, + ResponseNotReady = 0x42, + RequestResynch = 0x43, + OperationFailed = 0x44, + NoPendingRequests = 0x45, + VendorDefinedError = 0xff, +} diff --git a/lib/rspdm/lib.rs b/lib/rspdm/lib.rs new file mode 100644 index 000000000000..2bb716140e0a --- /dev/null +++ b/lib/rspdm/lib.rs @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2024 Western Digital + +//! Top level library for SPDM +//! +//! Rust implementation of the DMTF Security Protocol and Data Model (SPDM) +//! <https://www.dmtf.org/dsp/DSP0274> +//! +//! Top level library, including C compatible public functions to be called +//! from other subsytems. +//! +//! This mimics the C SPDM implementation in the kernel + +use core::ffi::{c_int, c_void}; +use core::ptr; +use core::slice::from_raw_parts_mut; +use kernel::prelude::*; +use kernel::{alloc::flags, bindings}; + +use crate::state::SpdmState; + +const __LOG_PREFIX: &[u8] = b"spdm\0"; + +mod consts; +mod state; +mod validator; + +/// spdm_create() - Allocate SPDM session +/// +/// `dev`: Responder device +/// `transport`: Transport function to perform one message exchange +/// `transport_priv`: Transport private data +/// `transport_sz`: Maximum message size the transport is capable of (in bytes) +/// `keyring`: Trusted root certificates +/// `validate`: Function to validate additional leaf certificate requirements +/// (optional, may be %NULL) +/// +/// Return a pointer to the allocated SPDM session state or NULL on error. +#[no_mangle] +pub unsafe extern "C" fn spdm_create( + dev: *mut bindings::device, + transport: bindings::spdm_transport, + transport_priv: *mut c_void, + transport_sz: u32, + keyring: *mut bindings::key, + validate: bindings::spdm_validate, +) -> *mut SpdmState { + match KBox::new( + SpdmState::new( + dev, + transport, + transport_priv, + transport_sz, + keyring, + validate, + ), + flags::GFP_KERNEL, + ) { + Ok(ret) => KBox::into_raw(ret) as *mut SpdmState, + Err(_) => ptr::null_mut(), + } +} + +/// spdm_exchange() - Perform SPDM message exchange with device +/// +/// @spdm_state: SPDM session state +/// @req: Request message +/// @req_sz: Size of @req +/// @rsp: Response message +/// @rsp_sz: Size of @rsp +/// +/// Send the request @req to the device via the @transport in @spdm_state and +/// receive the response into @rsp, respecting the maximum buffer size @rsp_sz. +/// The request version is automatically populated. +/// +/// Return response size on success or a negative errno. Response size may be +/// less than @rsp_sz and the caller is responsible for checking that. It may +/// also be more than expected (though never more than @rsp_sz), e.g. if the +/// transport receives only dword-sized chunks. +#[no_mangle] +pub unsafe extern "C" fn spdm_exchange( + state: &'static mut SpdmState, + req: *mut c_void, + req_sz: usize, + rsp: *mut c_void, + rsp_sz: usize, +) -> isize { + let request_buf: &mut [u8] = unsafe { from_raw_parts_mut(req as *mut u8, req_sz) }; + let response_buf: &mut [u8] = unsafe { from_raw_parts_mut(rsp as *mut u8, rsp_sz) }; + + match state.spdm_exchange(request_buf, response_buf) { + Ok(ret) => ret as isize, + Err(e) => e.to_errno() as isize, + } +} + +/// spdm_authenticate() - Authenticate device +/// +/// @spdm_state: SPDM session state +/// +/// Authenticate a device through a sequence of GET_VERSION, GET_CAPABILITIES, +/// NEGOTIATE_ALGORITHMS, GET_DIGESTS, GET_CERTIFICATE and CHALLENGE exchanges. +/// +/// Perform internal locking to serialize multiple concurrent invocations. +/// Can be called repeatedly for reauthentication. +/// +/// Return 0 on success or a negative errno. In particular, -EPROTONOSUPPORT +/// indicates authentication is not supported by the device. +#[no_mangle] +pub unsafe extern "C" fn spdm_authenticate(_state: &'static mut SpdmState) -> c_int { + 0 +} + +/// spdm_destroy() - Destroy SPDM session +/// +/// @spdm_state: SPDM session state +#[no_mangle] +pub unsafe extern "C" fn spdm_destroy(_state: &'static mut SpdmState) {} diff --git a/lib/rspdm/state.rs b/lib/rspdm/state.rs new file mode 100644 index 000000000000..9ebd87603454 --- /dev/null +++ b/lib/rspdm/state.rs @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2024 Western Digital + +//! The `SpdmState` struct and implementation. +//! +//! Rust implementation of the DMTF Security Protocol and Data Model (SPDM) +//! <https://www.dmtf.org/dsp/DSP0274> + +use core::ffi::c_void; +use kernel::prelude::*; +use kernel::{ + bindings, + error::{code::EINVAL, to_result, Error}, + validate::Untrusted, +}; + +use crate::consts::{SpdmErrorCode, SPDM_ERROR, SPDM_REQ}; +use crate::validator::{SpdmErrorRsp, SpdmHeader}; + +/// The current SPDM session state for a device. Based on the +/// C `struct spdm_state`. +/// +/// `dev`: Responder device. Used for error reporting and passed to @transport. +/// `transport`: Transport function to perform one message exchange. +/// `transport_priv`: Transport private data. +/// `transport_sz`: Maximum message size the transport is capable of (in bytes). +/// Used as DataTransferSize in GET_CAPABILITIES exchange. +/// `keyring`: Keyring against which to check the first certificate in +/// responder's certificate chain. +/// `validate`: Function to validate additional leaf certificate requirements. +/// +/// `version`: Maximum common supported version of requester and responder. +/// Negotiated during GET_VERSION exchange. +/// +/// `authenticated`: Whether device was authenticated successfully. +#[expect(dead_code)] +pub struct SpdmState { + pub(crate) dev: *mut bindings::device, + pub(crate) transport: bindings::spdm_transport, + pub(crate) transport_priv: *mut c_void, + pub(crate) transport_sz: u32, + pub(crate) keyring: *mut bindings::key, + pub(crate) validate: bindings::spdm_validate, + + // Negotiated state + pub(crate) version: u8, + + pub(crate) authenticated: bool, +} + +impl SpdmState { + pub(crate) fn new( + dev: *mut bindings::device, + transport: bindings::spdm_transport, + transport_priv: *mut c_void, + transport_sz: u32, + keyring: *mut bindings::key, + validate: bindings::spdm_validate, + ) -> Self { + SpdmState { + dev, + transport, + transport_priv, + transport_sz, + keyring, + validate, + version: 0x10, + authenticated: false, + } + } + + fn spdm_err(&self, rsp: &SpdmErrorRsp) -> Result<(), Error> { + match rsp.error_code { + SpdmErrorCode::InvalidRequest => { + pr_err!("Invalid request\n"); + Err(EINVAL) + } + SpdmErrorCode::InvalidSession => { + if rsp.version == 0x11 { + pr_err!("Invalid session {:#x}\n", rsp.error_data); + Err(EINVAL) + } else { + pr_err!("Undefined error {:#x}\n", rsp.error_code as u8); + Err(EINVAL) + } + } + SpdmErrorCode::Busy => { + pr_err!("Busy\n"); + Err(EBUSY) + } + SpdmErrorCode::UnexpectedRequest => { + pr_err!("Unexpected request\n"); + Err(EINVAL) + } + SpdmErrorCode::Unspecified => { + pr_err!("Unspecified error\n"); + Err(EINVAL) + } + SpdmErrorCode::DecryptError => { + pr_err!("Decrypt error\n"); + Err(EIO) + } + SpdmErrorCode::UnsupportedRequest => { + pr_err!("Unsupported request {:#x}\n", rsp.error_data); + Err(EINVAL) + } + SpdmErrorCode::RequestInFlight => { + pr_err!("Request in flight\n"); + Err(EINVAL) + } + SpdmErrorCode::InvalidResponseCode => { + pr_err!("Invalid response code\n"); + Err(EINVAL) + } + SpdmErrorCode::SessionLimitExceeded => { + pr_err!("Session limit exceeded\n"); + Err(EBUSY) + } + SpdmErrorCode::SessionRequired => { + pr_err!("Session required\n"); + Err(EINVAL) + } + SpdmErrorCode::ResetRequired => { + pr_err!("Reset required\n"); + Err(ECONNRESET) + } + SpdmErrorCode::ResponseTooLarge => { + pr_err!("Response too large\n"); + Err(EINVAL) + } + SpdmErrorCode::RequestTooLarge => { + pr_err!("Request too large\n"); + Err(EINVAL) + } + SpdmErrorCode::LargeResponse => { + pr_err!("Large response\n"); + Err(EMSGSIZE) + } + SpdmErrorCode::MessageLost => { + pr_err!("Message lost\n"); + Err(EIO) + } + SpdmErrorCode::InvalidPolicy => { + pr_err!("Invalid policy\n"); + Err(EINVAL) + } + SpdmErrorCode::VersionMismatch => { + pr_err!("Version mismatch\n"); + Err(EINVAL) + } + SpdmErrorCode::ResponseNotReady => { + pr_err!("Response not ready\n"); + Err(EINPROGRESS) + } + SpdmErrorCode::RequestResynch => { + pr_err!("Request resynchronization\n"); + Err(ECONNRESET) + } + SpdmErrorCode::OperationFailed => { + pr_err!("Operation failed\n"); + Err(EINVAL) + } + SpdmErrorCode::NoPendingRequests => Err(ENOENT), + SpdmErrorCode::VendorDefinedError => { + pr_err!("Vendor defined error\n"); + Err(EINVAL) + } + } + } + + /// Start a SPDM exchange + /// + /// The data in `request_buf` is sent to the device and the response is + /// stored in `response_buf`. + pub(crate) fn spdm_exchange( + &self, + request_buf: &mut [u8], + response_buf: &mut [u8], + ) -> Result<i32, Error> { + let header_size = core::mem::size_of::<SpdmHeader>(); + let request: &mut SpdmHeader = Untrusted::new_mut(request_buf).validate_mut()?; + let response: &SpdmHeader = Untrusted::new_ref(response_buf).validate()?; + + let transport_function = self.transport.ok_or(EINVAL)?; + // SAFETY: `transport_function` is provided by the new(), we are + // calling the function. + let length = unsafe { + transport_function( + self.transport_priv, + self.dev, + request_buf.as_ptr() as *const c_void, + request_buf.len(), + response_buf.as_mut_ptr() as *mut c_void, + response_buf.len(), + ) as i32 + }; + to_result(length)?; + + if (length as usize) < header_size { + return Ok(length); // Truncated response is handled by callers + } + if response.code == SPDM_ERROR { + if length as usize >= core::mem::size_of::<SpdmErrorRsp>() { + // SAFETY: The response buffer will be at at least as large as + // `SpdmErrorRsp` so we can cast the buffer to `SpdmErrorRsp` which + // is a packed struct. + self.spdm_err(unsafe { &*(response_buf.as_ptr() as *const SpdmErrorRsp) })?; + } else { + return Err(EINVAL); + } + } + + if response.code != request.code & !SPDM_REQ { + pr_err!( + "Response code {:#x} does not match request code {:#x}\n", + response.code, + request.code + ); + to_result(-(bindings::EPROTO as i32))?; + } + + Ok(length) + } +} diff --git a/lib/rspdm/validator.rs b/lib/rspdm/validator.rs new file mode 100644 index 000000000000..a0a3a2f46952 --- /dev/null +++ b/lib/rspdm/validator.rs @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Copyright (C) 2024 Western Digital + +//! Related structs and their Validate implementations. +//! +//! Rust implementation of the DMTF Security Protocol and Data Model (SPDM) +//! <https://www.dmtf.org/dsp/DSP0274> + +use crate::consts::SpdmErrorCode; +use core::mem; +use kernel::prelude::*; +use kernel::{ + error::{code::EINVAL, Error}, + validate::{Unvalidated, Validate}, +}; + +#[repr(C, packed)] +pub(crate) struct SpdmHeader { + pub(crate) version: u8, + pub(crate) code: u8, /* RequestResponseCode */ + pub(crate) param1: u8, + pub(crate) param2: u8, +} + +impl Validate<&Unvalidated<[u8]>> for &SpdmHeader { + type Err = Error; + + fn validate(unvalidated: &Unvalidated<[u8]>) -> Result<Self, Self::Err> { + let raw = unvalidated.raw(); + if raw.len() < mem::size_of::<SpdmHeader>() { + return Err(EINVAL); + } + + let ptr = raw.as_ptr(); + // CAST: `SpdmHeader` only contains integers and has `repr(C)`. + let ptr = ptr.cast::<SpdmHeader>(); + // SAFETY: `ptr` came from a reference and the cast above is valid. + Ok(unsafe { &*ptr }) + } +} + +impl Validate<&mut Unvalidated<[u8]>> for &mut SpdmHeader { + type Err = Error; + + fn validate(unvalidated: &mut Unvalidated<[u8]>) -> Result<Self, Self::Err> { + let raw = unvalidated.raw_mut(); + if raw.len() < mem::size_of::<SpdmHeader>() { + return Err(EINVAL); + } + + let ptr = raw.as_mut_ptr(); + // CAST: `SpdmHeader` only contains integers and has `repr(C)`. + let ptr = ptr.cast::<SpdmHeader>(); + // SAFETY: `ptr` came from a reference and the cast above is valid. + Ok(unsafe { &mut *ptr }) + } +} + +#[repr(C, packed)] +pub(crate) struct SpdmErrorRsp { + pub(crate) version: u8, + pub(crate) code: u8, + pub(crate) error_code: SpdmErrorCode, + pub(crate) error_data: u8, +} diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index f46cf3bb7069..42aa62f0c8f5 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -33,6 +33,8 @@ #include <linux/security.h> #include <linux/slab.h> #include <linux/tracepoint.h> +#include <linux/spdm.h> +#include <linux/uaccess.h> #include <linux/wait.h> #include <linux/workqueue.h> #include <trace/events/rust_sample.h> diff --git a/rust/kernel/error.rs b/rust/kernel/error.rs index f6ecf09cb65f..8ee8674ec485 100644 --- a/rust/kernel/error.rs +++ b/rust/kernel/error.rs @@ -83,6 +83,9 @@ macro_rules! declare_err { declare_err!(EIOCBQUEUED, "iocb queued, will get completion event."); declare_err!(ERECALLCONFLICT, "Conflict with recalled state."); declare_err!(ENOGRACE, "NFS file lock reclaim refused."); + declare_err!(ECONNRESET, "Connection reset by peer."); + declare_err!(EMSGSIZE, "Message too long."); + declare_err!(EINPROGRESS, "Operation now in progress."); } /// Generic integer kernel error. -- 2.48.1