diff --git a/src/greq/mod.rs b/src/greq/mod.rs index 8bbc905ef..56aca7547 100644 --- a/src/greq/mod.rs +++ b/src/greq/mod.rs @@ -4,5 +4,9 @@ // // Authors: Claudio Carvalho +//! `SNP_GUEST_REQUEST` mechanism to communicate with the PSP + pub mod driver; pub mod msg; +pub mod pld_report; +pub mod services; diff --git a/src/greq/pld_report.rs b/src/greq/pld_report.rs new file mode 100644 index 000000000..12f12f9b9 --- /dev/null +++ b/src/greq/pld_report.rs @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (C) 2023 IBM +// +// Authors: Claudio Carvalho + +//! `SNP_GUEST_REQUEST` command to request an attestation report. + +extern crate alloc; + +use crate::protocols::errors::SvsmReqError; + +/// Size of the `SnpReportRequest.user_data` +pub const USER_DATA_SIZE: usize = 64; + +/// MSG_REPORT_REQ payload format (AMD SEV-SNP spec. table 20) +#[repr(C, packed)] +#[derive(Clone, Copy, Debug)] +pub struct SnpReportRequest { + /// Guest-provided data to be included in the attestation report + /// REPORT_DATA (512 bits) + user_data: [u8; USER_DATA_SIZE], + /// The VMPL to put in the attestation report + vmpl: u32, + /// 31:2 - Reserved + /// 1:0 - KEY_SEL. Selects which key to use for derivation + /// 0: If VLEK is installed, sign with VLEK. Otherwise, sign with VCEK + /// 1: Sign with VCEK + /// 2: Sign with VLEK + /// 3: Reserved + flags: u32, + /// Reserved, must be zero + rsvd: [u8; 24], +} + +impl SnpReportRequest { + /// Check if the reserved field is clear + fn is_reserved_clear(&self) -> bool { + self.rsvd.into_iter().all(|e| e == 0) + } + + /// Validate the [SnpReportRequest] fields + pub fn validate(&self) -> Result<(), SvsmReqError> { + // Non-VMPL0 attestation reports can be requested by the guest kernel + // directly to the PSP. + if self.vmpl != 0 || !self.is_reserved_clear() { + return Err(SvsmReqError::invalid_parameter()); + } + + Ok(()) + } +} + +/// MSG_REPORT_RSP payload format (AMD SEV-SNP spec. table 23) +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct SnpReportResponse { + /// The status of the key derivation operation, see [SnpReportResponseStatus] + status: u32, + /// Size in bytes of the report + report_size: u32, + /// Reserved + _reserved: [u8; 24], + /// The attestation report generated by firmware + report: AttestationReport, +} + +/// Supported values for SnpReportResponse.status +#[repr(u32)] +#[derive(Clone, Copy, Debug)] +pub enum SnpReportResponseStatus { + Success = 0, + InvalidParameters = 0x16, + InvalidKeySelection = 0x27, +} + +impl SnpReportResponse { + /// Validate the [SnpReportResponse] fields + pub fn validate(&self) -> Result<(), SvsmReqError> { + if self.status != SnpReportResponseStatus::Success as u32 { + return Err(SvsmReqError::invalid_request()); + } + + const REPORT_SIZE: usize = core::mem::size_of::(); + assert!(u32::try_from(REPORT_SIZE).is_ok()); + + if self.report_size != REPORT_SIZE as u32 { + return Err(SvsmReqError::invalid_format()); + } + + Ok(()) + } +} + +/// The `TCB_VERSION` contains the security version numbers of each +/// component in the trusted computing base (TCB) of the SNP firmware. +/// (AMD SEV-SNP spec. table 3) +#[repr(C, packed)] +#[derive(Clone, Copy, Debug)] +struct TcbVersion { + /// Version of the Microcode, SNP firmware, PSP and boot loader + raw: u64, +} + +/// Format for an ECDSA P-384 with SHA-384 signature (AMD SEV-SNP spec. table 115) +#[repr(C, packed)] +#[derive(Clone, Copy, Debug)] +struct Signature { + /// R component of this signature + r: [u8; 72], + /// S component of this signature + s: [u8; 72], + /// Reserved + reserved: [u8; 368], +} + +/// ATTESTATION_REPORT format (AMD SEV-SNP spec. table 21) +#[repr(C, packed)] +#[derive(Clone, Copy, Debug)] +pub struct AttestationReport { + /// Version number of this attestation report + version: u32, + /// The guest SVN + guest_svn: u32, + /// The guest policy + policy: u64, + /// The family ID provided at launch + family_id: [u8; 16], + /// The image ID provided at launch + image_id: [u8; 16], + /// The request VMPL for the attestation report + vmpl: u32, + /// The signature algorithm used to sign this report + signature_algo: u32, + /// CurrentTcb + platform_version: TcbVersion, + /// Information about the platform + platform_info: u64, + /// Flags + flags: u32, + /// Reserved, must be zero + reserved0: u32, + /// Guest-provided data + report_data: [u8; 64], + /// The measurement calculated at launch + measurement: [u8; 48], + /// Data provided by the hypervisor at launch + host_data: [u8; 32], + /// SHA-384 digest of the ID public key that signed the ID block + /// provided in `SNP_LAUNCH_FINISH` + id_key_digest: [u8; 48], + /// SHA-384 digest of the Author public key that certified the ID key, + /// if provided in `SNP_LAUNCH_FINISH`. Zeroes if `AUTHOR_KEY_EN` is 1 + author_key_digest: [u8; 48], + /// Report ID of this guest + report_id: [u8; 32], + /// Report ID of this guest's migration agent + report_id_ma: [u8; 32], + /// Report TCB version used to derive the VCEK that signed this report + reported_tcb: TcbVersion, + /// Reserved + reserved1: [u8; 24], + /// If `MaskChipId` is set to 0, Identifier unique to the chip as + /// output by `GET_ID`. Otherwise, set to 0h + chip_id: [u8; 64], + /// Reserved and some more flags + reserved2: [u8; 192], + /// Signature of bytes 0h to 29Fh inclusive of this report + signature: Signature, +} diff --git a/src/greq/services.rs b/src/greq/services.rs new file mode 100644 index 000000000..c67cebd0f --- /dev/null +++ b/src/greq/services.rs @@ -0,0 +1,174 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (C) 2023 IBM +// +// Authors: Claudio Carvalho + +//! API to send `SNP_GUEST_REQUEST` commands to the PSP + +extern crate alloc; + +use crate::address::{Address, VirtAddr}; +use crate::greq::driver::SnpGuestRequestDriver; +use crate::greq::msg::SNP_MSG_REPORT_REQ; +use crate::greq::pld_report::{SnpReportRequest, SnpReportResponse}; +use crate::locking::{LockGuard, SpinLock}; +use crate::protocols::errors::SvsmReqError; + +use log; + +/// `SNP_GUEST_REQUEST` driver. +/// The PSP accepts only one `SNP_GUEST_REQUEST` command at a time +static GREQ_DRIVER: SpinLock = + SpinLock::new(SnpGuestRequestDriver::default()); + +/// Initialize the `SNP_GUEST_REQUEST` driver +pub fn greq_driver_init() { + if let Err(e) = GREQ_DRIVER.lock().init() { + log::error!("SNP_GUEST_REQUEST driver failed to initialize, e={:?}", e); + } +} + +fn get_report_common( + driver: &mut LockGuard, + buffer: VirtAddr, + buffer_size: usize, + extended: bool, +) -> Result { + const REPORT_REQUEST_SIZE: usize = core::mem::size_of::(); + const REPORT_RESPONSE_SIZE: usize = core::mem::size_of::(); + + if buffer.is_null() || REPORT_REQUEST_SIZE > buffer_size || REPORT_RESPONSE_SIZE > buffer_size { + return Err(SvsmReqError::invalid_parameter()); + } + + let request = buffer.as_ptr::(); + unsafe { (*request).validate()? }; + + // The SnpReportRequest structure size has to fit in the + // SnpGuestRequestMsgHdr.msg_size field, which is a u16. + assert!(u16::try_from(REPORT_REQUEST_SIZE).is_ok()); + + let buffer_len = REPORT_REQUEST_SIZE as u16; + + let response_len = driver.send_request( + SNP_MSG_REPORT_REQ, + extended, + buffer, + buffer_size, + buffer_len, + )?; + + if REPORT_RESPONSE_SIZE > response_len { + return Err(SvsmReqError::invalid_parameter()); + } + + let response = buffer.as_ptr::(); + unsafe { (*response).validate()? }; + + Ok(response_len) +} + +/// Request a VMPL0 attestation report to the PSP through the `MSG_REPORT_REQ` command. The VMPCK0 +/// is disabled if the function fails in a way that the VM state can be compromised. +/// +/// # Parameters +/// +/// * `buffer`: Buffer that contains the [SnpReportRequest] that will be used to request +/// the attestation report. The vmpl field inside of it must be zero. +/// This buffer is also used to store the [SnpReportResponse] received +/// from the PSP. +/// Must not be null. +/// +/// * `buffer_size`: Size of `buffer` in bytes. +/// Must be bigger than zero and must be large enough to hold the [SnpReportResponse] +/// received from the hypervisor. +/// +/// # Return codes +/// +/// * Success +/// * `usize`: Number of bytes written to `buffer`. It should match the [SnpReportResponse] size. +/// * Error +/// * `SvsmReqError::invalid_parameter()` +/// * `SvsmReqError::invalid_request()` +/// * `SvsmReqError::invalid_format()` +/// * Any other `SvsmReqError::RequestError()` or any `SvsmReqError::FatalError()` returned by functions +/// we call here but are owned by other SVSM sub-systems. +pub fn get_report(buffer: VirtAddr, buffer_size: usize) -> Result { + let mut driver: LockGuard = GREQ_DRIVER.lock(); + get_report_common(&mut driver, buffer, buffer_size, false) +} + +/// Request an extended VMPL0 attestation report to the PSP through the `MSG_REPORT_REQ` command; it's +/// extended because it also requests to the hypervisor the certificates required to verify attestation report. +/// The VMPCK0 is disabled if the function fails in a way that the VM state can be compromised. +/// +/// # Parameters +/// +/// * `buffer`: Buffer that contains the [SnpReportRequest] that will be used to request +/// the attestation report. The vmpl field inside of it must be zero. +/// This buffer is also used to store the [SnpReportResponse] received +/// from the PSP. +/// Must not be null. +/// +/// * `buffer_size`: Size of `buffer` in bytes. +/// Must be bigger than zero and must be large enough to hold the [SnpReportResponse] +/// received from the hypervisor. +/// +/// * `certs`: Buffer to store the SEV-SNP certificates received from the hypervisor. +/// Must not be null. +/// +/// * `certs_size`: Size of `certs` in bytes. Must be bigger than zero. +/// +/// # Return codes +/// +/// * Success +/// * `usize`: Number of bytes written to `buffer`. It should match the [SnpReportResponse] size. +/// * Error +/// * `SvsmReqError::FatalError(SvsmError::Ghcb(GhcbError::VmgexitError(certs_buffer_size, psp_rc)))`: +/// In addition to errors like "invalid parameter", you may also want to handle +/// this error, which indicates that `certs` is not large enough to hold the +/// certificates. The `certs_buffer_size` embedded in the error indicates the +/// number of bytes required. The `psp_rc` just indicates whether or not the +/// communication with the PSP worked fine, although that is not reflected +/// to `buffer`. +/// * Any other `SvsmReqError::RequestError()` or any `SvsmReqError::FatalError()` returned by functions +/// we call here but are owned by other SVSM sub-systems. +pub fn get_ext_report( + buffer: VirtAddr, + buffer_size: usize, + certs: VirtAddr, + certs_size: usize, +) -> Result { + if certs.is_null() || certs_size == 0 { + return Err(SvsmReqError::invalid_parameter()); + } + + let mut driver: LockGuard = GREQ_DRIVER.lock(); + + driver.extended_request_prepare(certs_size)?; + + let response_len = get_report_common(&mut driver, buffer, buffer_size, true)?; + + // The SEV-SNP certificates can be used to verify the attestation report. At this point, a zeroed + // ext_data buffer indicates that the certificates were not imported (yet?). + // The VM owner can import them from the host using the virtee/snphost project + if driver.is_extended_data_clear() { + log::warn!("SEV-SNP certificates not found. Make sure they were loaded from the host."); + } + driver.extended_data_ncopy_to(certs_size, certs, certs_size)?; + + Ok(response_len) +} + +#[cfg(test)] +mod tests { + use crate::greq::pld_report::SnpReportRequest; + + #[test] + fn u16_from_report_request_size() { + const REPORT_REQUEST_SIZE: usize = core::mem::size_of::(); + // In SnpGuestRequestMsgHdr, the size of SnpReportRequest has to fit in a u16 + assert!(u16::try_from(REPORT_REQUEST_SIZE).is_ok()); + } +} diff --git a/src/svsm.rs b/src/svsm.rs index b78fb5f64..ed5975eed 100644 --- a/src/svsm.rs +++ b/src/svsm.rs @@ -32,6 +32,7 @@ use svsm::elf; use svsm::error::SvsmError; use svsm::fs::{initialize_fs, populate_ram_fs}; use svsm::fw_cfg::FwCfg; +use svsm::greq::services::greq_driver_init; use svsm::kernel_launch::KernelLaunchInfo; use svsm::mm::alloc::{memory_info, print_memory_info, root_mem_init}; use svsm::mm::memory::init_memory_map; @@ -462,6 +463,8 @@ pub extern "C" fn svsm_main() { panic!("Failed to validate flash memory: {:#?}", e); } + greq_driver_init(); + prepare_fw_launch(&fw_meta).expect("Failed to setup guest VMSA"); virt_log_usage();