Skip to content

Commit

Permalink
greq: Add attestation report support
Browse files Browse the repository at this point in the history
Add get_report() and get_ext_report(). Both of them call the SNP_GUEST_REQUEST
driver to request a VMPL0 attestation report, the difference is that
get_ext_report() also requests the SEV-SNP certificates needed to verify the
attestation report.

The github project virtee/snphost can be used to import the SEV-SNP
certificates into the platform, for more information see the project
documentation. From the host:

$ snphost import <PEM-files-directory>

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_ext_report()
from the SVSM.

Signed-off-by: Claudio Carvalho <cclaudio@linux.ibm.com>
  • Loading branch information
cclaudio committed Oct 11, 2023
1 parent af2dc3b commit 3162668
Show file tree
Hide file tree
Showing 4 changed files with 351 additions and 0 deletions.
4 changes: 4 additions & 0 deletions src/greq/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,9 @@
//
// Authors: Claudio Carvalho <cclaudio@linux.ibm.com>

//! `SNP_GUEST_REQUEST` mechanism to communicate with the PSP

pub mod driver;
pub mod msg;
pub mod pld_report;
pub mod services;
170 changes: 170 additions & 0 deletions src/greq/pld_report.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (C) 2023 IBM
//
// Authors: Claudio Carvalho <cclaudio@linux.ibm.com>

//! `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::<AttestationReport>();
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,
}
174 changes: 174 additions & 0 deletions src/greq/services.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (C) 2023 IBM
//
// Authors: Claudio Carvalho <cclaudio@linux.ibm.com>

//! 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<SnpGuestRequestDriver> =
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<SnpGuestRequestDriver>,
buffer: VirtAddr,
buffer_size: usize,
extended: bool,
) -> Result<usize, SvsmReqError> {
const REPORT_REQUEST_SIZE: usize = core::mem::size_of::<SnpReportRequest>();
const REPORT_RESPONSE_SIZE: usize = core::mem::size_of::<SnpReportResponse>();

if buffer.is_null() || REPORT_REQUEST_SIZE > buffer_size || REPORT_RESPONSE_SIZE > buffer_size {
return Err(SvsmReqError::invalid_parameter());
}

let request = buffer.as_ptr::<SnpReportRequest>();
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::<SnpReportResponse>();
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<usize, SvsmReqError> {
let mut driver: LockGuard<SnpGuestRequestDriver> = 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<usize, SvsmReqError> {
if certs.is_null() || certs_size == 0 {
return Err(SvsmReqError::invalid_parameter());
}

let mut driver: LockGuard<SnpGuestRequestDriver> = 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::<SnpReportRequest>();
// In SnpGuestRequestMsgHdr, the size of SnpReportRequest has to fit in a u16
assert!(u16::try_from(REPORT_REQUEST_SIZE).is_ok());
}
}
3 changes: 3 additions & 0 deletions src/svsm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down

0 comments on commit 3162668

Please sign in to comment.