-
Notifications
You must be signed in to change notification settings - Fork 47
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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 <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 <[email protected]>
- Loading branch information
Showing
3 changed files
with
314 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,5 +4,9 @@ | |
// | ||
// Authors: Claudio Carvalho <[email protected]> | ||
|
||
//! `SNP_GUEST_REQUEST` mechanism to communicate with the PSP | ||
pub mod driver; | ||
pub mod msg; | ||
pub mod pld_report; | ||
pub mod services; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 <[email protected]> | ||
|
||
//! `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, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 <[email protected]> | ||
|
||
//! 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)) | ||
} |