From 817c3171345af7d87383f896e4fc7c1740d59b6f Mon Sep 17 00:00:00 2001 From: Claudio Carvalho Date: Sun, 29 Oct 2023 15:46:35 +0200 Subject: [PATCH] greq: Add attestation report support Add get_regular_report() and get_extended_report(). They both call the SNP_GUEST_REQUEST driver to request a VMPL0 attestation report, the difference is that get_extended_report() also requests the SEV-SNP certificates needed to verify the attestation report. The get_extended_report() function will return an empty buffer if the SEV-SNP certificates where not imported yet, but they can be imported from the host using the github virtee/snphost project: $ snphost import For testing purposes, if you import PEM files that contain some random data, you should be able to see the same random data when you call get_extended_report() from the SVSM. Signed-off-by: Claudio Carvalho --- src/greq/mod.rs | 4 + src/greq/pld_report.rs | 192 +++++++++++++++++++++++++++++++++++++++++ src/greq/services.rs | 108 +++++++++++++++++++++++ 3 files changed, 304 insertions(+) create mode 100644 src/greq/pld_report.rs create mode 100644 src/greq/services.rs diff --git a/src/greq/mod.rs b/src/greq/mod.rs index 8bbc905ef0..56aca7547c 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 0000000000..fafa2ce099 --- /dev/null +++ b/src/greq/pld_report.rs @@ -0,0 +1,192 @@ +// 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 core::mem::size_of; + +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 { + /// Take a slice and return a reference for Self + pub fn try_from_as_ref(buffer: &[u8]) -> Result<&Self, SvsmReqError> { + let buffer = buffer + .get(..size_of::()) + .ok_or_else(SvsmReqError::invalid_parameter)?; + + let request = unsafe { &*buffer.as_ptr().cast::() }; + + if !request.is_reserved_clear() { + return Err(SvsmReqError::invalid_parameter()); + } + Ok(request) + } + + pub fn is_vmpl0(&self) -> bool { + self.vmpl == 0 + } + + /// Check if the reserved field is clear + fn is_reserved_clear(&self) -> bool { + self.rsvd.into_iter().all(|e| e == 0) + } +} + +/// MSG_REPORT_RSP payload format (AMD SEV-SNP spec. table 23) +#[repr(C, packed)] +#[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 { + pub fn try_from_as_ref(buffer: &[u8]) -> Result<&Self, SvsmReqError> { + let buffer = buffer + .get(..size_of::()) + .ok_or_else(SvsmReqError::invalid_parameter)?; + + let response = unsafe { &*buffer.as_ptr().cast::() }; + Ok(response) + } + + /// Validate the [SnpReportResponse] fields + /// + /// # Panic + /// + /// * The size of the struct [`AttestationReport`] must fit in a u32 + pub fn validate(&self) -> Result<(), SvsmReqError> { + if self.status != SnpReportResponseStatus::Success as u32 { + return Err(SvsmReqError::invalid_request()); + } + + const REPORT_SIZE: usize = 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 0000000000..1beebc2d4a --- /dev/null +++ b/src/greq/services.rs @@ -0,0 +1,108 @@ +// 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::{ + greq::{ + driver::{send_extended_guest_request, send_regular_guest_request}, + msg::SnpGuestRequestMsgType, + pld_report::{SnpReportRequest, SnpReportResponse}, + }, + protocols::errors::SvsmReqError, +}; +use core::mem::size_of; + +const REPORT_REQUEST_SIZE: usize = size_of::(); +const REPORT_RESPONSE_SIZE: usize = size_of::(); + +fn get_report(buffer: &mut [u8], certs: Option<&mut [u8]>) -> Result { + let request: &SnpReportRequest = SnpReportRequest::try_from_as_ref(buffer)?; + // Non-VMPL0 attestation reports can be requested by the guest kernel + // directly to the PSP. + if !request.is_vmpl0() { + return Err(SvsmReqError::invalid_parameter()); + } + let response_len = if certs.is_none() { + send_regular_guest_request( + SnpGuestRequestMsgType::ReportRequest, + buffer, + REPORT_REQUEST_SIZE, + )? + } else { + send_extended_guest_request( + SnpGuestRequestMsgType::ReportRequest, + buffer, + REPORT_REQUEST_SIZE, + certs.unwrap(), + )? + }; + if REPORT_RESPONSE_SIZE > response_len { + return Err(SvsmReqError::invalid_request()); + } + let response: &SnpReportResponse = SnpReportResponse::try_from_as_ref(buffer)?; + response.validate()?; + + Ok(response_len) +} + +/// Request a regular VMPL0 attestation report to the PSP. +/// +/// Use the `SNP_GUEST_REQUEST` driver to send the provided `MSG_REPORT_REQ` command to +/// the PSP. The VPML field of the command must be set to zero. +/// +/// The VMPCK0 is disabled for subsequent calls if this function fails in a way that +/// the VM state can be compromised. +/// +/// # Arguments +/// +/// * `buffer`: Buffer with the [`MSG_REPORT_REQ`](SnpReportRequest) command that will be +/// sent to the PSP. It must be large enough to hold the +/// [`MSG_REPORT_RESP`](SnpReportResponse) received from the PSP. +/// +/// # Returns +/// +/// * Success +/// * `usize`: Number of bytes written to `buffer`. It should match the +/// [`MSG_REPORT_RESP`](SnpReportResponse) size. +/// * Error +/// * [`SvsmReqError`] +pub fn get_regular_report(buffer: &mut [u8]) -> Result { + get_report(buffer, None) +} + +/// Request an extended VMPL0 attestation report to the PSP. +/// +/// We say that it is extended because it requests a VMPL0 attestation report +/// to the PSP (as in [`get_regular_report()`]) and also requests to the hypervisor +/// the certificates required to verify the attestation report. +/// +/// The VMPCK0 is disabled for subsequent calls if this function fails in a way that +/// the VM state can be compromised. +/// +/// # Arguments +/// +/// * `buffer`: Buffer with the [`MSG_REPORT_REQ`](SnpReportRequest) command that will be +/// sent to the PSP. It must be large enough to hold the +/// [`MSG_REPORT_RESP`](SnpReportResponse) received from the PSP. +/// * `certs`: Buffer to store the SEV-SNP certificates received from the hypervisor. +/// +/// # Return codes +/// +/// * Success +/// * `usize`: Number of bytes written to `buffer`. It should match +/// the [`MSG_REPORT_RESP`](SnpReportResponse) size. +/// * Error +/// * [`SvsmReqError`] +/// * `SvsmReqError::FatalError(SvsmError::Ghcb(GhcbError::VmgexitError(certs_buffer_size, psp_rc)))`: +/// * `certs` is not large enough to hold the certificates. +/// * `certs_buffer_size`: number of bytes required. +/// * `psp_rc`: PSP return code +pub fn get_extended_report(buffer: &mut [u8], certs: &mut [u8]) -> Result { + get_report(buffer, Some(certs)) +}