From 701bbe40158d011297da96f9797101f00e58ab9e Mon Sep 17 00:00:00 2001 From: Wilfred Mallawa Date: Thu, 21 Nov 2024 14:21:48 +1000 Subject: [PATCH 1/3] io_buffers: add page aligned allocation NVMe userspace API required page aligned allocated buffers, ensure we have support for this. Signed-off-by: Wilfred Mallawa --- Cargo.lock | 11 +++++ Cargo.toml | 5 ++- src/io_buffers.rs | 111 +++++++++++++++++++++++++++------------------- 3 files changed, 80 insertions(+), 47 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2f5215d..0108c3a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,6 +21,7 @@ dependencies = [ "minicbor-derive", "nix 0.29.0", "once_cell", + "page_size", "serialport", "sha2", "tokio", @@ -938,6 +939,16 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "page_size" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "parking_lot" version = "0.12.5" diff --git a/Cargo.toml b/Cargo.toml index 03116c7..24d3547 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,9 @@ std = [ "futures", "nix", "inquire", + "page_size", ] +page_size = ["dep:page_size"] [lib] name = "libspdm" @@ -61,6 +63,7 @@ colored = { version = "2.1", optional = true } which = { version = "6.0", optional = true } asn1-rs = { version = "0.6", optional = true } x509-parser = { version = "0.15", optional = true } +page_size = { version = "0.6", optional = true } minicbor = { version = "0.25", features = [ "half", "alloc", @@ -72,7 +75,7 @@ minicbor-derive = { version = "0.15", features = [ ], optional = true } tokio = { version = "1.49", features = ["full"], optional = true } futures = { version = "0.3", optional = true } -nix = { version = "0.29.0", features = ["user"], optional = true } +nix = { version = "0.29", features = ["user"], optional = true } libmctp = { version = "0.2" } inquire = { version = "0.7.5", optional = true} diff --git a/src/io_buffers.rs b/src/io_buffers.rs index a58dba7..612c298 100644 --- a/src/io_buffers.rs +++ b/src/io_buffers.rs @@ -1,44 +1,57 @@ +use nix::errno::Errno; use once_cell::sync::Lazy; +use std::alloc::dealloc; +use std::alloc::{Layout, alloc_zeroed}; use std::ffi::c_void; +use std::ptr::NonNull; use std::sync::Mutex; -static SEND_BUFFER: Lazy>>> = Lazy::new(|| Mutex::new(None)); -static RECEIVE_BUFFER: Lazy>>> = Lazy::new(|| Mutex::new(None)); +type IoBuffer = Option<(Vec, Layout)>; +static SEND_BUFFER: Lazy> = Lazy::new(|| Mutex::new(None)); +static RECEIVE_BUFFER: Lazy> = Lazy::new(|| Mutex::new(None)); pub fn libspdm_setup_io_buffers( context: *mut c_void, - send_len: usize, - recv_len: usize, + send_recv_len: usize, + libsdpm_buff_len: usize, ) -> Result<(), ()> { - let result = std::panic::catch_unwind(|| { - let buffer_send = vec![0; send_len]; - let buffer_receive = vec![0; recv_len]; - (buffer_send, buffer_receive) - }); - - match result { - Ok(buffers) => { - *(SEND_BUFFER.lock().unwrap()) = Some(buffers.0); - *(RECEIVE_BUFFER.lock().unwrap()) = Some(buffers.1); + // NVMe requires page aligned buffers + if !send_recv_len.is_multiple_of(page_size::get()) { + error!("Requested SEND/RECV buffer size is not system page size aligned"); + return Err(()); + } - unsafe { - libspdm::libspdm_rs::libspdm_register_device_buffer_func( - context, - send_len as u32, - recv_len as u32, - Some(acquire_sender_buffer), - Some(release_sender_buffer), - Some(acquire_receiver_buffer), - Some(release_receiver_buffer), - ) - } - Ok(()) - } - Err(_) => { - error!("Failed to allocate transport buffers"); - Err(()) - } + fn page_aligned_buffer(size: usize) -> Result<(Vec, Layout), Errno> { + let layout = Layout::from_size_align(size, page_size::get()).map_err(|_| Errno::EINVAL)?; + let ptr = unsafe { alloc_zeroed(layout) }; + let ptr = NonNull::new(ptr).ok_or(Errno::ENOMEM)?; + let vec = unsafe { Vec::from_raw_parts(ptr.as_ptr(), 0, size) }; + Ok((vec, layout)) } + + let buffer_send = page_aligned_buffer(send_recv_len).map_err(|e| { + error!("Failed to allocate SEND buffer: {e}"); + })?; + let buffer_recv = page_aligned_buffer(send_recv_len).map_err(|e| { + error!("Failed to allocate RECV buffer: {e}"); + })?; + + *(SEND_BUFFER.lock().unwrap()) = Some((buffer_send.0, buffer_send.1)); + *(RECEIVE_BUFFER.lock().unwrap()) = Some((buffer_recv.0, buffer_recv.1)); + + unsafe { + libspdm::libspdm_rs::libspdm_register_device_buffer_func( + context, + libsdpm_buff_len as u32, + libsdpm_buff_len as u32, + Some(acquire_sender_buffer), + Some(release_sender_buffer), + Some(acquire_receiver_buffer), + Some(release_receiver_buffer), + ) + }; + + Ok(()) } /// # Summary @@ -59,12 +72,11 @@ pub unsafe extern "C" fn acquire_sender_buffer( _context: *mut c_void, msg_buf_ptr: *mut *mut c_void, ) -> u32 { - if let Some(ref buf) = *SEND_BUFFER.lock().unwrap() { - let buf_ptr = buf.as_ptr() as *mut c_void; + if let Some(ref mem) = *SEND_BUFFER.lock().unwrap() { + let buf_ptr = mem.0.as_ptr() as *mut c_void; unsafe { *msg_buf_ptr = buf_ptr }; return 0; } - error!("Sender buffer is lost or not initialized"); 1 } @@ -87,8 +99,8 @@ pub unsafe extern "C" fn acquire_receiver_buffer( _context: *mut c_void, msg_buf_ptr: *mut *mut c_void, ) -> u32 { - if let Some(ref buf) = *RECEIVE_BUFFER.lock().unwrap() { - let buf_ptr = buf.as_ptr() as *mut c_void; + if let Some(ref mem) = *RECEIVE_BUFFER.lock().unwrap() { + let buf_ptr = mem.0.as_ptr() as *mut c_void; unsafe { *msg_buf_ptr = buf_ptr }; return 0; } @@ -114,17 +126,24 @@ pub unsafe extern "C" fn release_sender_buffer(_context: *mut c_void, _msg_buf_p /// Drop the IO buffers out of scope, this should release the underlying /// memory. pub unsafe fn libspdm_drop_io_buffers() { - let mut send_buf = SEND_BUFFER.lock().unwrap(); - if send_buf.is_some() { - *send_buf = None; - } else { - warn!("Send buffer is lost or not initialized"); + let free = |buf: &mut IoBuffer, err: &str| { + if let Some(mut mem) = buf.take() { + let ptr = mem.0.as_mut_ptr(); + let layout = mem.1; + std::mem::forget(mem.0); + unsafe { dealloc(ptr, layout) }; + } else { + error!("{}", err); + } + }; + + { + let mut send_buf = SEND_BUFFER.lock().unwrap(); + free(&mut send_buf, "Send buffer is lost or not initialized"); } - let mut recv_buf = RECEIVE_BUFFER.lock().unwrap(); - if recv_buf.is_some() { - *recv_buf = None; - } else { - warn!("Receive buffer is lost or not initialized"); + { + let mut recv_buf = RECEIVE_BUFFER.lock().unwrap(); + free(&mut recv_buf, "Receive buffer is lost or not initialized"); } } From bce0482fcaf1d1a054c85f48965988d0d7326f41 Mon Sep 17 00:00:00 2001 From: Wilfred Mallawa Date: Thu, 13 Jun 2024 11:27:46 +1000 Subject: [PATCH 2/3] storage: add storage standard protocols common Signed-off-by: Wilfred Mallawa --- src/storage_standards.rs | 92 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 src/storage_standards.rs diff --git a/src/storage_standards.rs b/src/storage_standards.rs new file mode 100644 index 0000000..e86e01f --- /dev/null +++ b/src/storage_standards.rs @@ -0,0 +1,92 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright (C) 2024, Western Digital Corporation or its affiliates. + +//! Contains all of the handlers for creating SPDM requests. + +/// SCSI SPDM Related ADDITIONAL SENSE CODE (ASQ) +#[allow(dead_code)] +#[derive(Debug)] +pub enum ScsiAsc { + InvalidFieldInCdb = 0x24, // ASCQ = 0x00 +} + +/// Defines the SPDM return status (upper byte) and error (lower byte) +/// for ATA as defined in DSP0284. +#[allow(dead_code)] +#[derive(Debug)] +pub enum AtaStatusErr { + Success = 0x5000, + InvalidCommand = 0x5104, +} + +/// NVME Completion Queue Command Completion Status +#[allow(dead_code)] +#[derive(Debug)] +pub enum NvmeCmdStatus { + NvmeSuccess = 0x0000, + NvmeInvalidFieldInCmd = 0x0002, + NvmeDoNotRetry = 0x4000, +} + +/// Spdm Storage Operations as defined in DMTF DSP0286 +#[derive(Debug, PartialEq)] +pub enum SpdmOperationCodes { + SpdmStorageDiscovery = 0x01, + SpdmStoragePendingInfo = 0x02, + SpdmStorageMessage = 0x05, + SpdmStorageSecMessage = 0x06, +} + +impl TryFrom for SpdmOperationCodes { + type Error = (); + fn try_from(value: u8) -> Result { + match value { + 0x01 => Ok(SpdmOperationCodes::SpdmStorageDiscovery), + 0x02 => Ok(SpdmOperationCodes::SpdmStoragePendingInfo), + 0x05 => Ok(SpdmOperationCodes::SpdmStorageMessage), + 0x06 => Ok(SpdmOperationCodes::SpdmStorageSecMessage), + _ => Err(()), + } + } +} + +impl From for u8 { + fn from(op: SpdmOperationCodes) -> Self { + match op { + SpdmOperationCodes::SpdmStorageDiscovery => 0x01, + SpdmOperationCodes::SpdmStoragePendingInfo => 0x02, + SpdmOperationCodes::SpdmStorageMessage => 0x05, + SpdmOperationCodes::SpdmStorageSecMessage => 0x06, + } + } +} + +/// Relevant Security Protocols as specified in Working Draft SCSI Primary +/// Commands - 6 (SPC-6) +#[derive(Debug, PartialEq)] +pub enum SpcSecurityProtocols { + SecurityProtocolInformation, + DmtfSpdm, +} + +impl From for u8 { + fn from(c: SpcSecurityProtocols) -> Self { + match c { + SpcSecurityProtocols::SecurityProtocolInformation => 0x00, + SpcSecurityProtocols::DmtfSpdm => 0xE8, + } + } +} + +impl TryFrom for SpcSecurityProtocols { + type Error = (); + fn try_from(v: u8) -> Result { + match v { + 0x00 => Ok(SpcSecurityProtocols::SecurityProtocolInformation), + 0xE8 => Ok(SpcSecurityProtocols::DmtfSpdm), + _ => Err(()), + } + } +} + From 7a03a581043e1438c1654d3096642f0f68193c54 Mon Sep 17 00:00:00 2001 From: Wilfred Mallawa Date: Mon, 4 Nov 2024 14:49:44 +1000 Subject: [PATCH 3/3] transport: storage: add nvme support Using the DSP0286, this patch adds support for communicating with an NVMe device with the NVME admin SECURITY SEND/RECEIVE commands for SPDM. `libnvme` is used to interact with the device specified. Signed-off-by: Wilfred Mallawa --- .github/workflows/ci.yml | 2 +- Cargo.toml | 3 +- README.md | 12 +- build.rs | 2 + src/libspdm/spdm.rs | 14 + src/main.rs | 36 ++ src/nvme.rs | 701 +++++++++++++++++++++++++++++++++++++++ src/qemu_server.rs | 1 + src/storage_standards.rs | 39 ++- wrapper.h | 3 + 10 files changed, 788 insertions(+), 25 deletions(-) create mode 100644 src/nvme.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9c1b209..a1b2e8c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: - name: Install dependencies run: | sudo apt-get update; \ - sudo apt-get install -y cmake libclang-dev libpci-dev libssl-dev python3-dev gem; \ + sudo apt-get install -y cmake libclang-dev libpci-dev libssl-dev python3-dev gem libnvme-dev; \ sudo gem install cbor-diag; - name: Build libspdm diff --git a/Cargo.toml b/Cargo.toml index 24d3547..34e7bac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,6 @@ std = [ "inquire", "page_size", ] -page_size = ["dep:page_size"] [lib] name = "libspdm" @@ -75,7 +74,7 @@ minicbor-derive = { version = "0.15", features = [ ], optional = true } tokio = { version = "1.49", features = ["full"], optional = true } futures = { version = "0.3", optional = true } -nix = { version = "0.29", features = ["user"], optional = true } +nix = { version = "0.29", features = ["user", "fs", "ioctl"], optional = true } libmctp = { version = "0.2" } inquire = { version = "0.7.5", optional = true} diff --git a/README.md b/README.md index cd1b890..eb7fb8e 100644 --- a/README.md +++ b/README.md @@ -57,11 +57,11 @@ Note: `dnf` commands are for Fedora, and `apt` is used for Debian/Ubuntu based distributions. ```shell -$ sudo dnf install cmake clang-libs clang-devel pciutils-devel openssl openssl-devel python3-devel systemd-devel +$ sudo dnf install cmake clang-libs clang-devel pciutils-devel openssl openssl-devel python3-devel systemd-devel libnvme or -$ sudo apt install cmake clang libclang-dev pciutils libpci-dev openssl libssl-dev libsystemd-dev python3-dev pkg-config +$ sudo apt install cmake clang libclang-dev pciutils libpci-dev openssl libssl-dev libsystemd-dev python3-dev pkg-config libnvme ``` ### Ruby @@ -296,6 +296,14 @@ invoked as below: ```shell ./target/debug/spdm_utils --pcie-vid --pcie-devid --doe-pci-cfg request get-digests ``` +### SPDM for NVMe over the SPDM Storage Transport + +SPDM-utils supports the SPDM over storage transport as defined by the DMTF DSP0286. +For example, the following command can be used to interact with an NVMe device. + +```shell +$ ./target/debug/spdm_utils --blk-dev-path /dev/nvme0 --nvme --no-session request get-version,get-capabilities +``` ## Retrieving Certificates diff --git a/build.rs b/build.rs index c4b537d..b5073b9 100644 --- a/build.rs +++ b/build.rs @@ -22,6 +22,7 @@ fn main() { println!("cargo:rustc-link-arg=-Wl,--start-group"); println!("cargo:rustc-link-arg=-lpci"); + println!("cargo:rustc-link-arg=-lnvme"); println!("cargo:rustc-link-arg=-lmemlib"); println!("cargo:rustc-link-arg=-lmalloclib"); @@ -41,6 +42,7 @@ fn main() { println!("cargo:rustc-link-arg=-lspdm_crypt_ext_lib"); println!("cargo:rustc-link-arg=-lspdm_transport_pcidoe_lib"); println!("cargo:rustc-link-arg=-lspdm_transport_mctp_lib"); + println!("cargo:rustc-link-arg=-lspdm_transport_storage_lib"); // Link SPDM Test Libraries let mut builder = if cfg!(feature = "libspdm_tests") { diff --git a/src/libspdm/spdm.rs b/src/libspdm/spdm.rs index dc5087a..edf7062 100644 --- a/src/libspdm/spdm.rs +++ b/src/libspdm/spdm.rs @@ -277,6 +277,8 @@ pub enum TransportLayer { Doe, /// DMTF Management Component Transport Protocol Mctp, + /// SCSI Security Protocol In/Out commands + Storage, } #[cfg(feature = "no_std")] @@ -506,6 +508,18 @@ pub unsafe fn setup_transport_layer( ) }; } + TransportLayer::Storage => { + unsafe { + libspdm_register_transport_layer_func( + context, + libspdm_max_spdm_msg_size, + LIBSPDM_STORAGE_TRANSPORT_HEADER_SIZE, + LIBSPDM_STORAGE_TRANSPORT_TAIL_SIZE, + Some(libspdm_transport_storage_encode_message), + Some(libspdm_transport_storage_decode_message), + ) + }; + } } let libspdm_scratch_buffer_size = diff --git a/src/main.rs b/src/main.rs index 15eb3a7..1731be8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,6 +22,7 @@ use tokio::task; extern crate log; use env_logger::Env; use libspdm::{responder, responder::CertModel, spdm}; +use nix::errno::Errno; use once_cell::sync::Lazy; pub static SOCKET_PATH: &str = "SPDM-Utils-loopback-socket"; @@ -29,10 +30,12 @@ pub static SOCKET_PATH: &str = "SPDM-Utils-loopback-socket"; mod cli_helpers; mod doe_pci_cfg; mod io_buffers; +mod nvme; mod qemu_server; mod request; mod socket_client; mod socket_server; +mod storage_standards; mod tcg_concise_evidence_binding; mod test_suite; mod usb_i2c; @@ -43,6 +46,18 @@ struct Args { #[command(subcommand)] command: Commands, + /// Use NVMe commands for the target device + #[arg(short, long, requires_ifs([("true", "blk_dev_path")]))] + nvme: bool, + + /// NVME NameSpace Identifier + #[arg(long, default_value_t = 1)] + nsid: u32, + + /// Path to the block device + #[arg(long)] + blk_dev_path: Option, + /// Use the Linux PCIe extended configuration backend /// This is generally run on the Linux host machine #[arg(short, long)] @@ -664,6 +679,10 @@ async fn main() -> Result<(), ()> { let mut count = 0; + cli.nvme.then(|| { + count += 1; + }); + cli.doe_pci_cfg.then(|| { count += 1; }); @@ -728,12 +747,21 @@ async fn main() -> Result<(), ()> { } usb_i2c::register_device(cntx_ptr, cli.usb_i2c_dev, cli.usb_i2c_baud)?; + } else if cli.nvme { + if let Err(e) = nvme::nvme_get_sec_info(&cli.blk_dev_path.clone().unwrap(), cli.nsid) { + if e == Errno::ENOTSUP { + error!("SPDM is not supported by this NVMe device"); + } + return Err(()); + } + nvme::register_device(cntx_ptr, &cli.blk_dev_path.clone().unwrap(), cli.nsid).unwrap(); } else if cli.qemu_server { if let Commands::Request { .. } = cli.command { error!("QEMU Server does not support running an SPDM requester"); return Err(()); } if let Some(proto) = cli.spdm_transport_protocol { + info!("Using {:?} transport for QEMU", proto); qemu_server::register_device(cntx_ptr, cli.qemu_port, proto)?; } else { qemu_server::register_device(cntx_ptr, cli.qemu_port, spdm::TransportLayer::Doe)?; @@ -745,6 +773,7 @@ async fn main() -> Result<(), ()> { unsafe { if let Some(proto) = cli.spdm_transport_protocol { + info!("Using {:?} transport", proto); spdm::setup_transport_layer(cntx_ptr, proto, spdm::LIBSPDM_MAX_SPDM_MSG_SIZE)?; } else if cli.usb_i2c { spdm::setup_transport_layer( @@ -752,6 +781,13 @@ async fn main() -> Result<(), ()> { spdm::TransportLayer::Mctp, spdm::LIBSPDM_MAX_SPDM_MSG_SIZE, )?; + } else if cli.nvme { + spdm::setup_transport_layer( + cntx_ptr, + spdm::TransportLayer::Storage, + spdm::LIBSPDM_MAX_SPDM_MSG_SIZE, + ) + .unwrap(); } else { spdm::setup_transport_layer( cntx_ptr, diff --git a/src/nvme.rs b/src/nvme.rs new file mode 100644 index 0000000..2308efa --- /dev/null +++ b/src/nvme.rs @@ -0,0 +1,701 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright (C) 2024, Western Digital Corporation or its affiliates. + +//! This file provides support for SPDM interfacing to a NVMe device +//! +//! SAFETY: This file includes a lot of unsafe Rust. +//! If libspdm behaves in a manor we don't expect this will be very bad, +//! so we are trusting libspdm here. +//! + +use crate::*; +use core::ffi::c_void; +use core::ptr; +use libspdm::spdm::LIBSPDM_MAX_SPDM_MSG_SIZE; +use nix::errno::Errno; +use nix::fcntl::{OFlag, open}; +use nix::sys::stat::{FileStat, Mode, SFlag, fstat}; +use nix::unistd::close; +use std::slice::from_raw_parts; +use std::sync::Mutex; +use storage_standards::{SpcSecurityProtocols, SpdmStorageOperationCodes}; + +/// Limit maximum transfers to 4k, if we allocate bigger IO buffers, we then need +/// to rely on kernel support mmap(2) with MAP_HUGETLB. To allocate aligned +/// and contiguous buffers. +const NVME_STORAGE_MSG_MAX_SIZE: usize = 4096; +const LIBSPDM_MAX_TRANSPORT_MSG_LEN: usize = NVME_STORAGE_MSG_MAX_SIZE; + +static NVME_DEV: Lazy>> = Lazy::new(|| Mutex::new(None)); +/// Transfer lengths must be DWORD Aligned. +const LIBNVME_RECV_ARG_MIN_AL_LEN: usize = 32; + +/// # Summary +/// +/// # Parameter +/// +/// * `_context`: The SPDM context +/// * `message_size`: Number of elements in `message_ptr` to send +/// * `message_ptr`: A pointer to the data buffer to be sent +/// * `_timeout`: Transaction timeout (Unsupported) +/// +/// # Returns +/// +/// (0) on success +/// +/// # Panics +/// +/// Panics if the buffers passed in are invalid or for any other point of +/// failure. +#[unsafe(no_mangle)] +unsafe extern "C" fn nvme_send_message( + _context: *mut c_void, + message_size: usize, + message_ptr: *const c_void, + timeout_us: u64, +) -> u32 { + debug!("Sending NVMe Security Send: SPDM Message"); + let message = message_ptr as *const u8; + let dptr = unsafe { from_raw_parts(message, message_size) }.to_vec(); + + match &mut *NVME_DEV.lock().unwrap() { + Some(dev) => { + // Setup a security receive command for security protocol discovery + let mut cmd = match NvmeSecSendCmds::new(dptr, dev.nsid) { + Ok(cmd) => cmd, + Err(why) => { + panic!("Failed to generate SPDM Message Command: {:?}", why) + } + }; + + cmd.debug_log().unwrap(); + let nvme_sec_send_args = cmd.get_args_mut().unwrap(); + if let Err(why) = dev.nvme_sec_send(nvme_sec_send_args, timeout_us) { + panic!("Failed to security send: {:?}", why); + } + } + None => unreachable!("NVMe device lost"), + } + 0 +} + +/// # Summary +/// +/// +/// # Parameter +/// +/// * `_context`: The SPDM context +/// * `message_size`: Returns the number of bytes received in this transaction +/// * `message_ptr`: A pointer to a data buffer of a minimum size of +/// `LIBSPDM_MAX_TRANSPORT_MSG_LEN` to capture the received bytes. +/// * `_timeout`: Transaction timeout (Unsupported) +/// +/// # Returns +/// +/// (0) on success +/// +/// # Panics +/// +/// Panics if the buffers passed in are invalid or for any other point of +/// failure. +#[unsafe(no_mangle)] +unsafe extern "C" fn nvme_receive_message( + context: *mut c_void, + message_size: *mut usize, + message_ptr: *mut *mut c_void, + timeout_us: u64, +) -> u32 { + debug!("Sending NVMe Security Receive: SPDM Message"); + match &mut *NVME_DEV.lock().unwrap() { + Some(dev) => { + let message = unsafe { *message_ptr as *mut u8 }; + // Setup a security receive command for security protocol discovery + let mut cmd = match NvmeSecRecvCmds::gen_libspdm_encoded_sec_recv_args( + context, + dev.nsid, + message, + unsafe { *message_size }, + ) { + Ok(cmd) => cmd, + Err(why) => { + panic!("Failed to generate SPDM Message Command: {:?}", why) + } + }; + + debug!("NVME Storage Message Cmd: {:?}", cmd); + + if let Err(why) = dev.nvme_sec_recv(&mut cmd, timeout_us) { + panic!("Failed to security receive: {:?}", why); + } + // Allow libspdm to do top-bottom SPDM message parsing, we don't know the + // number of valid bytes in the reception buffer. + unsafe { *message_size = LIBSPDM_MAX_TRANSPORT_MSG_LEN }; + } + None => unreachable!("NVMe device lost"), + } + 0 +} + +/// # Summary +/// +/// Setup device IO buffers and register the transport/IO functions to the +/// given libspdm @context. +/// +/// # Parameter +/// +/// * `context`: The SPDM context +/// +/// # Returns +/// +/// Ok(()) on success +/// +/// # Panics +/// +/// Panics if `SEND_BUFFER/RECEIVE_BUFFER` is occupied +pub fn register_device(context: *mut c_void, dev_path: &String, nsid: u32) -> Result<(), ()> { + if !LIBSPDM_MAX_TRANSPORT_MSG_LEN.is_multiple_of(page_size::get()) { + error!("Requested SEND/RECV buffer size is not target page size aligned"); + return Err(()); + } + + if LIBSPDM_MAX_TRANSPORT_MSG_LEN > LIBSPDM_MAX_SPDM_MSG_SIZE as usize { + error!("NVMe Transport buffers bigger than libspdm max supported"); + return Err(()); + } + + let dev = NvmeDev::new(dev_path, nsid, OFlag::O_EXCL | OFlag::O_RDWR) + .expect("Failed to establish a connection to the NVMe Controller"); + *NVME_DEV.lock().unwrap() = Some(dev); + unsafe { + libspdm_register_device_io_func( + context, + Some(nvme_send_message), + Some(nvme_receive_message), + ); + io_buffers::libspdm_setup_io_buffers( + context, + LIBSPDM_MAX_TRANSPORT_MSG_LEN, + LIBSPDM_MAX_TRANSPORT_MSG_LEN, + )?; + } + + Ok(()) +} + +#[derive(Debug)] +#[allow(dead_code)] +struct NvmeDev { + fd: i32, + nsid: u32, + flags: OFlag, + stat: Option, +} + +impl NvmeDev { + pub fn new(path: &String, nsid: u32, flags: OFlag) -> Result { + let path = Path::new(path); + + info!("Getting device info for: {:?}", path); + + match open(path, flags, Mode::S_IRUSR) { + Ok(fd) => { + let mut dev = NvmeDev { + fd, + nsid, + flags, + stat: None, + }; + + match fstat(fd) { + Ok(fs) => { + let st_mode = fs.st_mode; + if ((SFlag::S_IFMT.bits() & st_mode) != SFlag::S_IFCHR.bits()) + && ((SFlag::S_IFMT.bits() & st_mode) != SFlag::S_IFBLK.bits()) + { + error!("{:?} is not a block or character device", path); + close(fd)?; + return Err(Errno::ENODEV); + } + dev.stat = Some(fs); + } + Err(e) => { + error!("Failed to do file stat for {:?}: {:?}", path, e); + close(fd)?; + return Err(e); + } + } + + Ok(dev) + } + Err(e) => { + error!("Failed to open file {:?}: {:?}", path, e); + Err(e) + } + } + } + + /// # Summary + /// + /// The Security Send command transfers the security protocol data to the + /// controller. The data structure transferred to the controller as a part + /// of this command contains security protocol specific commands to be + /// performed by the controller. + /// + /// # Parameter + /// + /// * `cmd`: An initialized @nvme_security_send_args structure containing + /// all required security protocol data. + /// * `timeout`: timeout to wait for in micro-seconds + /// + /// # Returns + /// + /// Ok(()) on success + /// Err(error_code, nvme_cmd_completion_status): + /// -1 with errno set, or if the response was received, + /// nvme_cmd_completion_status set to indicate the failure. + pub fn nvme_sec_send( + &mut self, + cmd: &mut nvme_security_send_args, + timeout_us: u64, + ) -> Result<(), i32> { + cmd.fd = self.fd; + // In nvme_security_send_args, timeout is represented in milliseconds + // https://github.com/linux-nvme/libnvme/blob/5585f06a4f849a1b43b1a04f387d2f9b5829744f/src/nvme/api-types.h#L313 + cmd.timeout = if timeout_us == 0 { + NVME_DEFAULT_IOCTL_TIMEOUT + } else if let Ok(v) = u32::try_from(timeout_us / 1_000) { + v + } else { + NVME_DEFAULT_IOCTL_TIMEOUT + }; + // Call into libnvme + let rc = unsafe { nvme_security_send(cmd as *mut nvme_security_send_args) }; + if rc != 0 { + error!("Security send: rc: {:} errno: {:}", rc, Errno::last()); + return Err(rc); + } + debug!("Security Send Success"); + Ok(()) + } + + /// # Summary + /// + /// The Security Receive command is used to obtain a security response from + /// the controller. This maybe used without a prior security send for actions + /// such as storage security discovery (Mandatory support by specification). + /// However, to obtain an SPDM response, it must follow a security send that + /// issues an SPDM request to the controller. + /// + /// # Parameter + /// + /// * `cmd`: An initialized @nvme_security_receive_args structure + /// containing all required security protocol data. + /// * `timeout_us`: Timeout in micro-seconds + /// + /// # Returns + /// + /// Ok(()) on success + /// Err(error_code, nvme_cmd_completion_status): + /// -1 with errno set, or if the response was received, + /// nvme_cmd_completion_status set to indicate the failure. + pub fn nvme_sec_recv( + &mut self, + cmd: &mut nvme_security_receive_args, + timeout_us: u64, + ) -> Result<(), i32> { + cmd.fd = self.fd; + if timeout_us == 0 || u32::try_from(timeout_us / 1_000).is_err() { + cmd.timeout = NVME_DEFAULT_IOCTL_TIMEOUT; + } else { + // In nvme_security_send_args, timeout is represented in milliseconds + // https://github.com/linux-nvme/libnvme/blob/5585f06a4f849a1b43b1a04f387d2f9b5829744f/src/nvme/api-types.h#L313 + cmd.timeout = u32::try_from(timeout_us / 1_000).unwrap(); + } + // Call into libnvme + let rc = unsafe { nvme_security_receive(cmd as *mut nvme_security_receive_args) }; + if rc != 0 { + error!("Security receive: CQE: {:} errno: {:}", rc, Errno::last()); + return Err(rc); + } + debug!("Security Receive Success"); + Ok(()) + } +} + +impl Drop for NvmeDev { + /// # Summary + /// + /// Close the underlying file-descriptor when this instance goes out of scope + /// + /// # Panics + /// + /// If the device file-descriptor was not set. + fn drop(&mut self) { + // This fd is valid as `self` does not exist on any failures + // to `open()` the NVMe device. + close(self.fd).expect("failed to NVMe close device file-descriptor"); + debug!("NVMe device file-descriptor successfully closed"); + } +} + +struct NvmeSecSendCmds { + data: Option>, + args: Option, +} +struct NvmeSecRecvCmds; + +impl NvmeSecSendCmds { + pub fn new(data: Vec, nsid: u32) -> Result { + let len = data.len(); + let mut data = data; + let args = NvmeSecSendCmds::gen_libspdm_encoded_sec_send_args(&mut data, len, nsid)?; + Ok(Self { + data: Some(data), + args: Some(args), + }) + } + + pub fn get_args_mut(&mut self) -> Option<&mut nvme_security_send_args> { + self.args.as_mut() + } + + pub fn get_args(&self) -> Option<&nvme_security_send_args> { + self.args.as_ref() + } + pub fn get_data(&self) -> Option<&Vec> { + self.data.as_ref() + } + + pub fn debug_log(&self) -> Result<(), ()> { + debug!("SPDM Request: {:x?}", self.get_data().ok_or(())?); + let nvme_sec_send_args = self.get_args().ok_or(())?; + debug!("NVME Storage Message Cmd: {:?}", nvme_sec_send_args); + Ok(()) + } + + /// # Summary + /// + /// Generates an @nvme_security_send_args suitable for an `SPDM Storage + /// Message`. @dptr shall be the message buffer that contains the SPDM + /// request data (with transport encoding from libspdm). + /// + /// # Parameter + /// + /// * `dptr`: A vector containing the SPDM request message (crosses FFI, + /// i.e must be heap allocated). + /// * `msg_len`: Length of the SPDM message. + /// * `nsid`: NVMe namespace identifier. + /// + /// # Returns + /// + /// Returns an initialised @nvme_security_send_args suitable for use for an + /// `NVMe Security Receive` command for an SPDM request. + /// + /// Returns Err(ENOMSG), if @dptr is empty + pub fn gen_libspdm_encoded_sec_send_args( + dptr: &mut Vec, + msg_len: usize, + nsid: u32, + ) -> Result { + if dptr.is_empty() || dptr.len() < LIBSPDM_STORAGE_TRANSPORT_HEADER_SIZE as usize { + // Nothing to send + return Err(Errno::ENOMSG); + } + + let trans_hdr = + unsafe { *(dptr.as_mut_ptr() as *mut libspdm_storage_transport_virtual_header_t) }; + assert_eq!( + trans_hdr.security_protocol, + u8::from(SpcSecurityProtocols::DmtfSpdm) + ); + + let spsp0: u8 = (trans_hdr.security_protocol_specific & 0xFF) as u8; + let spsp1: u8 = (trans_hdr.security_protocol_specific >> 8) as u8; + + // Strip the transport header, only send the SPDM message. + // The transport relevant SPDM data is passed through the storage API. + let transfer_len = msg_len + .checked_sub(LIBSPDM_STORAGE_TRANSPORT_HEADER_SIZE as usize) + .filter(|&len| len != 0) + .ok_or(Errno::EINVAL)?; + + if transfer_len == 0 { + return Err(Errno::ENOMSG); + } + // Setup a security send command for an SPDM message + let args = nvme_security_send_args { + args_size: core::ffi::c_int::try_from(std::mem::size_of::()) + .unwrap(), + fd: 0, // Set at transport level + nsid, + nssf: 0, //RSVD + spsp0, + spsp1, + secp: trans_hdr.security_protocol, + tl: u32::try_from(transfer_len).unwrap(), + data_len: u32::try_from(transfer_len).unwrap(), + data: unsafe { + dptr.as_mut_ptr() + .add(LIBSPDM_STORAGE_TRANSPORT_HEADER_SIZE as usize) + as *mut c_void + }, + timeout: 0, // Set at transport + result: ptr::null_mut(), + }; + + Ok(args) + } +} + +impl NvmeSecRecvCmds { + /// # Summary + /// + /// Generates an @nvme_security_receive_args suitable for an `SPDM Storage + /// Message` by using `libspdm` for transport encoding. This is suitable for + /// use in the libspdm message receive handler. + /// + /// @dptr shall be the message buffer into to which an SPDM response shall + /// be copied to, from the NVMe controller. As an SPDM Response maybe of + /// arbitrary size, a vector of capacity `LIBSPDM_MAX_TRANSPORT_MSG_LEN` is + /// enforced, to accommodate the worst case scenario. + /// + /// # Note + /// + /// A mutable reference to @dptr is stored within the returned struct, thus, + /// the underlying memory pointed to by @dptr must not be free(d) until + /// after the data is received. Ex: after invoking `nvme_security_receive()`. + /// + /// # Parameter + /// + /// * `dptr`: A vector containing the SPDM request message (crosses FFI, + /// i.e must be heap allocated). + /// * `nsid`: NVMe namespace identifier. + /// * `message`: libspdm message buffer + /// * `message_size`: libspdm message buffer size + /// + /// # Returns + /// + /// Returns an initialised @nvme_security_receive_args suitable for use for an + /// `NVMe Security Receive` command for an SPDM request. + /// + /// Returns Err(ENOMEM) if @dptr does not mean size requirements. + pub fn gen_libspdm_encoded_sec_recv_args( + context: *mut c_void, + nsid: u32, + message: *mut u8, + message_size: usize, + ) -> Result { + // Setup a security receive command for an SPDM message + let mut args = nvme_security_receive_args { + args_size: + core::ffi::c_int::try_from(std::mem::size_of::()) + .unwrap(), + fd: 0, // Set at transport level + nsid, + nssf: 0, //RSVD + spsp0: 0, + spsp1: 0, // RSVD + secp: 0, + al: LIBSPDM_MAX_TRANSPORT_MSG_LEN as u32, + data_len: 0, + data: message as *mut c_void, + timeout: 0, // Set at transport + result: ptr::null_mut(), + }; + + let mut transport_message_len = message_size; + let mut transport_message = ptr::null_mut(); + + unsafe { + let context = context as *mut libspdm_context_t; + assert!( + libspdm_transport_storage_encode_message( + context as *mut c_void, + ptr::null(), + false, + true, + message_size - LIBSPDM_STORAGE_TRANSPORT_HEADER_SIZE as usize, + message as *mut _ as *mut c_void, + &mut transport_message_len, + &mut transport_message as *mut *mut c_void, + ) == 0 + ); + } + + assert!(transport_message_len >= LIBSPDM_STORAGE_TRANSPORT_HEADER_SIZE as usize); + let trans_hdr = unsafe { + *(transport_message as *const u8 as *mut libspdm_storage_transport_virtual_header_t) + }; + assert_eq!( + trans_hdr.security_protocol, + u8::from(SpcSecurityProtocols::DmtfSpdm) + ); + + // Update based on libspdm encoded data + args.secp = trans_hdr.security_protocol; + let spsp = u16::from_le(trans_hdr.security_protocol_specific); + args.spsp0 = (spsp & 0xFF) as u8; + args.spsp1 = (spsp >> 8) as u8; + + assert!(args.spsp0 != 0); + assert!(args.spsp1 == 0); + assert!(LIBSPDM_MAX_TRANSPORT_MSG_LEN.is_multiple_of(page_size::get())); + args.data_len = LIBSPDM_MAX_TRANSPORT_MSG_LEN as u32; + + Ok(args) + } + + pub fn gen_libspdm_encoded_discovery_request_args( + dptr: &mut Vec, + nsid: u32, + ) -> Result { + if dptr.len() < LIBSPDM_STORAGE_TRANSPORT_HEADER_SIZE as usize { + return Err(Errno::ENOMEM); + } + + if dptr.len() < LIBNVME_RECV_ARG_MIN_AL_LEN { + // Quirk: The dptr allocation length must be atleast 32 bytes, + // seeems to be an api limitation (?). The cmd will timeout otherwise. + return Err(Errno::ENOMEM); + } + + let conn_id: u8 = 0; + // Setup a security send command for an SPDM message + let mut args = nvme_security_receive_args { + args_size: + core::ffi::c_int::try_from(std::mem::size_of::()) + .unwrap(), + fd: 0, // Set at transport level + nsid, + nssf: 0, //RSVD + spsp0: 0, + spsp1: 0, // RSVD + secp: 0, + al: 0, + data_len: 0, + data: dptr.as_mut_ptr() as *mut c_void, + timeout: 0, // Set at transport + result: ptr::null_mut(), + }; + + let mut transport_message_len: usize = dptr.len(); + let mut allocation_len: usize = 0; + + let rc = unsafe { + libspdm_transport_storage_encode_management_cmd( + u8::try_from(LIBSPDM_STORAGE_CMD_DIRECTION_IF_RECV).unwrap(), + SpdmStorageOperationCodes::Discovery as u8, + conn_id, + &mut transport_message_len, + &mut allocation_len, + dptr.as_mut_ptr() as *mut c_void, + ) + }; + + if !spdm::LibspdmReturnStatus::libspdm_status_is_success(rc) { + panic!("libspdm failed to encode transport: {:x}", rc); + } + + let trans_hdr = + unsafe { *(dptr.as_mut_ptr() as *mut libspdm_storage_transport_virtual_header_t) }; + + // Update based on libspdm encoded data + args.secp = trans_hdr.security_protocol; + let spsp = u16::from_le(trans_hdr.security_protocol_specific); + args.spsp0 = (spsp & 0xFF) as u8; + args.spsp1 = (spsp >> 8) as u8; + + assert!(args.spsp0 != 0); + assert!(args.spsp1 == 0); + assert!(transport_message_len <= dptr.len()); + assert!(transport_message_len != 0); + assert!(transport_message_len != 0); + + args.al = std::cmp::max(allocation_len as u32, LIBNVME_RECV_ARG_MIN_AL_LEN as u32); + args.data_len = std::cmp::max( + u32::try_from(transport_message_len).unwrap(), + LIBNVME_RECV_ARG_MIN_AL_LEN as u32, + ); + + Ok(args) + } +} + +/// # Summary +/// +/// Fetch supported security protocols from the NVMe device. This should be used +/// prior to `register_device()` to ensure that the NVMe controller supports +/// SPDM over storage. +/// +/// # Note +/// +/// This function operates independantely, that is, when it returns it closes +/// the `fd` associated with the controller. +/// +/// # Parameter +/// +/// * `path: path to the device +/// * `nsid`: name-space identifier +/// +/// # Returns +/// +/// Ok(()), on success +/// Err(Errno::ENOTSUP), is DMTF SPDM is not supported +pub fn nvme_get_sec_info(path: &String, nsid: u32) -> Result<(), Errno> { + let mut dev = NvmeDev::new(path, nsid, OFlag::O_EXCL | OFlag::O_RDONLY)?; + let mut dptr = vec![0; 64]; + + let mut sec_recv_args = + NvmeSecRecvCmds::gen_libspdm_encoded_discovery_request_args(&mut dptr, nsid)?; + + debug!( + "Security Receive Command Discovery Request: {:?}", + sec_recv_args + ); + + // Receive the discovery response + if dev.nvme_sec_recv(&mut sec_recv_args, 0).is_err() { + return Err(Errno::EIO); + } + + info!("--- SPDM Storage Discovery Start ---"); + + let data_len = u16::from_be_bytes(dptr[..2].try_into().unwrap()); + let storage_binding_version = u16::from_be_bytes(dptr[2..4].try_into().unwrap()); + let max_connection_id = dptr[4] & 0b11; + let supported_operations = dptr[8]; + + info!(" Available bytes in SPDM Storage Data: {:}", data_len); + info!(" Storage Binding Version: 0x{:x}", storage_binding_version); + info!(" Max num connections: {:}", max_connection_id); + if max_connection_id == 0 { + info!(" - A value of 0 indicates 1 connection"); + } + info!(" Supported Opertations:"); + + if supported_operations & (1 << SpdmStorageOperationCodes::Discovery as u8) != 0 { + info!(" StorageDiscovery - Supported"); + } else { + error!(" Device does not support mandatory StorageDiscovery"); + return Err(Errno::ENOTSUP); + } + + if supported_operations & (1 << SpdmStorageOperationCodes::Message as u8) != 0 { + info!(" StorageMessage - Supported"); + } else { + error!(" Device does not support mandatory StorageMessage"); + return Err(Errno::ENOTSUP); + } + + if supported_operations & (1 << SpdmStorageOperationCodes::PendingInfo as u8) != 0 { + info!(" StoragePendingInfo - Supported"); + } + if supported_operations & (1 << SpdmStorageOperationCodes::SecMessage as u8) != 0 { + info!(" StorageSecMessage - Supported"); + } + + info!("--- SPDM Storage Discovery Response End ---"); + + Ok(()) +} diff --git a/src/qemu_server.rs b/src/qemu_server.rs index 793268a..6c5aebf 100644 --- a/src/qemu_server.rs +++ b/src/qemu_server.rs @@ -407,6 +407,7 @@ pub fn register_device( Some(qemu_receive_message_mctp), ); } + TransportLayer::Storage => todo!(), } io_buffers::libspdm_setup_io_buffers( context, diff --git a/src/storage_standards.rs b/src/storage_standards.rs index e86e01f..e225e24 100644 --- a/src/storage_standards.rs +++ b/src/storage_standards.rs @@ -24,40 +24,40 @@ pub enum AtaStatusErr { #[allow(dead_code)] #[derive(Debug)] pub enum NvmeCmdStatus { - NvmeSuccess = 0x0000, - NvmeInvalidFieldInCmd = 0x0002, - NvmeDoNotRetry = 0x4000, + Success = 0x0000, + InvalidFieldInCmd = 0x0002, + DoNotRetry = 0x4000, } /// Spdm Storage Operations as defined in DMTF DSP0286 #[derive(Debug, PartialEq)] -pub enum SpdmOperationCodes { - SpdmStorageDiscovery = 0x01, - SpdmStoragePendingInfo = 0x02, - SpdmStorageMessage = 0x05, - SpdmStorageSecMessage = 0x06, +pub enum SpdmStorageOperationCodes { + Discovery = 0x01, + PendingInfo = 0x02, + Message = 0x05, + SecMessage = 0x06, } -impl TryFrom for SpdmOperationCodes { +impl TryFrom for SpdmStorageOperationCodes { type Error = (); fn try_from(value: u8) -> Result { match value { - 0x01 => Ok(SpdmOperationCodes::SpdmStorageDiscovery), - 0x02 => Ok(SpdmOperationCodes::SpdmStoragePendingInfo), - 0x05 => Ok(SpdmOperationCodes::SpdmStorageMessage), - 0x06 => Ok(SpdmOperationCodes::SpdmStorageSecMessage), + 0x01 => Ok(SpdmStorageOperationCodes::Discovery), + 0x02 => Ok(SpdmStorageOperationCodes::PendingInfo), + 0x05 => Ok(SpdmStorageOperationCodes::Message), + 0x06 => Ok(SpdmStorageOperationCodes::SecMessage), _ => Err(()), } } } -impl From for u8 { - fn from(op: SpdmOperationCodes) -> Self { +impl From for u8 { + fn from(op: SpdmStorageOperationCodes) -> Self { match op { - SpdmOperationCodes::SpdmStorageDiscovery => 0x01, - SpdmOperationCodes::SpdmStoragePendingInfo => 0x02, - SpdmOperationCodes::SpdmStorageMessage => 0x05, - SpdmOperationCodes::SpdmStorageSecMessage => 0x06, + SpdmStorageOperationCodes::Discovery => 0x01, + SpdmStorageOperationCodes::PendingInfo => 0x02, + SpdmStorageOperationCodes::Message => 0x05, + SpdmStorageOperationCodes::SecMessage => 0x06, } } } @@ -89,4 +89,3 @@ impl TryFrom for SpcSecurityProtocols { } } } - diff --git a/wrapper.h b/wrapper.h index 2cc6f6c..23988c1 100644 --- a/wrapper.h +++ b/wrapper.h @@ -4,6 +4,7 @@ #ifdef RUST_STD #include +#include #endif #include @@ -15,6 +16,7 @@ #include #include #include +#include #ifdef LIBSPDM_TESTS #include #endif @@ -23,3 +25,4 @@ #include #include #include +#include