Skip to content

Commit

Permalink
greq: Add attestation report support
Browse files Browse the repository at this point in the history
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 <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_extended_report() from the SVSM.

Signed-off-by: Claudio Carvalho <cclaudio@linux.ibm.com>
  • Loading branch information
cclaudio committed Oct 29, 2023
1 parent 532079a commit 30c556e
Show file tree
Hide file tree
Showing 3 changed files with 314 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;
199 changes: 199 additions & 0 deletions src/greq/pld_report.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
// 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;

use core::{mem::size_of, slice::from_raw_parts};

/// 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> {
const REPORT_REQUEST_SIZE: usize = size_of::<SnpReportRequest>();

if REPORT_REQUEST_SIZE > buffer.len() {
return Err(SvsmReqError::invalid_parameter());
}

let request: &SnpReportRequest = unsafe {
&from_raw_parts(buffer.as_ptr() as *const _ as *const SnpReportRequest, 1)[0]
};
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)]
#[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> {
const REPORT_RESPONSE_SIZE: usize = size_of::<SnpReportResponse>();

if REPORT_RESPONSE_SIZE > buffer.len() {
return Err(SvsmReqError::invalid_request());
}

let response: &SnpReportResponse = unsafe {
&from_raw_parts(buffer.as_ptr() as *const _ as *const SnpReportResponse, 1)[0]
};
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::<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,
}
111 changes: 111 additions & 0 deletions src/greq/services.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// 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::{
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::<SnpReportRequest>();
const REPORT_RESPONSE_SIZE: usize = size_of::<SnpReportResponse>();

fn get_report(buffer: &mut [u8], certs: Option<&mut [u8]>) -> Result<usize, SvsmReqError> {
if REPORT_RESPONSE_SIZE > buffer.len() {
return Err(SvsmReqError::invalid_parameter());
};
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_parameter());
}
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<usize, SvsmReqError> {
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 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<usize, SvsmReqError> {
get_report(buffer, Some(certs))
}

0 comments on commit 30c556e

Please sign in to comment.