-
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_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 <[email protected]>
- Loading branch information
Showing
4 changed files
with
361 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,170 @@ | ||
// 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; | ||
|
||
/// 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, | ||
} |
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,184 @@ | ||
// 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::address::{Address, VirtAddr}; | ||
use crate::greq::driver::{SnpGuestRequestClass, 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, | ||
req_class: SnpGuestRequestClass, | ||
) -> 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, | ||
req_class, | ||
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, | ||
SnpGuestRequestClass::Regular, | ||
) | ||
} | ||
|
||
/// 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, | ||
SnpGuestRequestClass::Extended, | ||
)?; | ||
|
||
// 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()); | ||
} | ||
} |
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