From 14d0c4d29a41c2abcc53fdbee12e5288c2a62c16 Mon Sep 17 00:00:00 2001 From: Geoffrey Ndu Date: Fri, 15 Nov 2024 13:37:49 +0000 Subject: [PATCH 01/10] svsm: add SVSM VTPM Service Attestation vTPM service attestation is described in section 8.3 of "Secure VM Service Module for SEV-SNP Guests, Publication #58019 Revision: 1.00 Issue Date: July 2023". It certifies the Endorsement Key (EK) of the vTPM by providing the TPMT_PUBLIC structure of the EK. This is crucial for downstream projects like Keylime, as the SVSM vTPM lacks an EK certificate found in physical TPMs to anchor trust. The attestation is part of the SVSM Attestation Protocol and uses the SVSM_ATTEST_SINGLE_SERVICE call (see section 7 of the specifications). It is triggered by making an SVSM_ATTEST_SINGLE_SERVICE call with the GUID set to c476f1eb-0123-45a5-9641-b4e7dde5bfe3. The attestation code returns the VMPL0 attestation report and the vTPM Service Manifest Data Structure (TPMT_PUBLIC structure of the EK). The REPORT_DATA in the SNP attestation request is the SHA-512 digest of the input nonce and the vTPM Service Manifest Data Structure. The vTPM initialization function was modified to generate an RSA 2048-bit EK from the TPM's Endorsement Primary Seed (EPS) and cache the public key as a TPMT_PUBLIC structure. This cached EK public key can be retrieved later for vTPM service attestation. The EK is created with the TCG default EK template (see Table 4 of the "TCG EK Credential Profile For TPM Family 2.0; Level 0 Version 2.5 Revision 2.0"). Since the EK is derived from the EPS, it can be recreated upstream at any time. For example, the same EK can be recreated in an OS using the TSS2 command "tpm2_createek -c ek.ctx -G rsa -u ek.pub" and compared against the one returned by vTPM service attestation. vTPM service attestation as specified can only return one type of EK, so the implementation supports RSA 2048-bit EK as defined in Table 4 of the "TCG EK Credential Profile For TPM Family 2.0; Level 0 Version 2.5 Revision 2.0," which is the most common Trusted Computing Group(TCG) EK type. Resolves coconut-svsm/svsm#437, resolves coconut-svsm/svsm#361 Signed-off-by: Geoffrey Ndu --- Cargo.lock | 1 + Cargo.toml | 2 +- kernel/Cargo.toml | 1 + kernel/src/protocols/attestation.rs | 356 ++++++++++++++++++++++++++++ kernel/src/protocols/mod.rs | 2 + kernel/src/requests.rs | 2 + kernel/src/vtpm/mod.rs | 33 +++ kernel/src/vtpm/tcgtpm/mod.rs | 167 ++++++++++++- 8 files changed, 561 insertions(+), 3 deletions(-) create mode 100644 kernel/src/protocols/attestation.rs diff --git a/Cargo.lock b/Cargo.lock index 6804c2e18..62951e649 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -947,6 +947,7 @@ dependencies = [ "log", "packit", "rustc_version", + "sha2", "syscall", "test", "verify_external", diff --git a/Cargo.toml b/Cargo.toml index 1d3a7bf20..deca45202 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,7 +47,7 @@ intrusive-collections = "0.9.6" libfuzzer-sys = "0.4" log = "0.4.17" p384 = { version = "0.13.0" } -sha2 = "0.10.8" +sha2 = { version = "0.10.8", default-features = false} uuid = "1.6.1" # Add the derive feature by default because all crates use it. zerocopy = { version = "0.8.2", features = ["alloc", "derive"] } diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 33e328032..dcdf712c8 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -35,6 +35,7 @@ log = { workspace = true, features = ["max_level_info", "release_max_level_info" packit.workspace = true libtcgtpm = { workspace = true, optional = true } zerocopy.workspace = true +sha2 = { workspace = true, features = ["force-soft"] } builtin = { workspace = true, optional = true } builtin_macros = { workspace = true } diff --git a/kernel/src/protocols/attestation.rs b/kernel/src/protocols/attestation.rs new file mode 100644 index 000000000..1e99e9e6d --- /dev/null +++ b/kernel/src/protocols/attestation.rs @@ -0,0 +1,356 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2024 Hewlett Packard Enterprise Development LP +// +// Author: Geoffrey Ndu (gtn@hpe.com) + +//! Attestation protocol implementation + +extern crate alloc; + +use crate::protocols::{errors::SvsmReqError, RequestParams}; +use crate::{ + address::{Address, PhysAddr}, + mm::{valid_phys_address, PerCPUPageMappingGuard}, + types::PAGE_SIZE, +}; +#[cfg(all(feature = "vtpm", not(test)))] +use crate::{ + greq::{ + pld_report::{SnpReportResponse, USER_DATA_SIZE}, + services::get_regular_report, + }, + vtpm::vtpm_get_ekpub, +}; +use alloc::vec::Vec; +#[cfg(all(feature = "vtpm", not(test)))] +use core::slice::from_raw_parts_mut; +use core::{mem::size_of, slice::from_raw_parts}; +#[cfg(all(feature = "vtpm", not(test)))] +use sha2::{Digest, Sha512}; +const SVSM_ATTEST_SERVICES: u32 = 0; +const SVSM_ATTEST_SINGLE_SERVICE: u32 = 1; + +#[cfg(all(feature = "vtpm", not(test)))] +const SVSM_ATTEST_VTPM_GUID: u128 = u128::from_le_bytes([ + 0xeb, 0xf1, 0x76, 0xc4, 0x23, 0x01, 0xa5, 0x45, 0x96, 0x41, 0xb4, 0xe7, 0xdd, 0xe5, 0xbf, 0xe3, +]); + +// Attest Single Service Operation structure, as defined in +// Table 13 of Secure VM Service Module for SEV-SNP Guests +// 58019 Rev. 1.00 July 2023 +#[repr(C, packed)] +#[derive(Clone, Copy, Debug)] +pub struct AttestSingleServiceOp { + report_gpa: u64, + report_size: u32, + reserved_1: [u8; 4], + nonce_gpa: u64, + nonce_size: u16, + reserved_2: [u8; 6], + manifest_gpa: u64, + manifest_size: u32, + reserved_3: [u8; 4], + certificate_gpa: u64, + certificate_size: u32, + reserved_4: [u8; 4], + guid: [u8; 16], + manifest_ver: u32, + reserved_5: [u8; 4], +} + +impl AttestSingleServiceOp { + /// 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)?; + + // SAFETY: AttestSingleServiceOp has no invalid representations, as it is + // comprised entirely of integer types. It is repr(packed), so its + // required alignment is simply 1. We have checked the size, so this + // is entirely safe. + let ops = unsafe { &*buffer.as_ptr().cast::() }; + + if !ops.is_reserved_clear() || !ops.is_manifest_version_valid() { + return Err(SvsmReqError::invalid_parameter()); + } + + Ok(ops) + } + /// Checks if reserved fields are all set to zero + pub fn is_reserved_clear(&self) -> bool { + self.reserved_1.iter().all(|&x| x == 0) + && self.reserved_2.iter().all(|&x| x == 0) + && self.reserved_3.iter().all(|&x| x == 0) + && self.reserved_4.iter().all(|&x| x == 0) + && self.reserved_5.iter().all(|&x| x == 0) + } + + /// Returns the nonce + pub fn get_nonce(&self) -> Result, SvsmReqError> { + let (gpa, size) = self.get_nonce_gpa_and_size()?; + // get_nonce_gpa_and_size() already validated that gpa is page + // aligned, valid and does not cross page boundary. + let start = gpa.page_align(); + let offset = gpa.page_offset(); + + let guard = PerCPUPageMappingGuard::create_4k(start)?; + let vaddr = guard.virt_addr() + offset; + + // Check that the nonce length is not greater than 4k. + // If it is, return an error as something is wrong with the request + if size > PAGE_SIZE { + return Err(SvsmReqError::invalid_parameter()); + } + + // SAFETY: vaddr points to a new mapped page region. And get_nonce_gpa_and_size() already + // validated that gpa is page aligned, valid and does not cross. We also checked earlier + // that size is not greater than PAGE_SIZE, so we can safely read the nonce. + let buffer = unsafe { from_raw_parts(vaddr.as_mut_ptr::(), size) }; + let nonce = buffer.to_vec(); + + Ok(nonce) + } + + /// Returns the nonce GPA and size + /// Checks if gpa is page aligned, valid and does not cross page boundary. + pub fn get_nonce_gpa_and_size(&self) -> Result<(PhysAddr, usize), SvsmReqError> { + let gpa = PhysAddr::from(self.nonce_gpa); + if !gpa.is_aligned(8) || !valid_phys_address(gpa) || gpa.crosses_page(8) { + return Err(SvsmReqError::invalid_parameter()); + } + + //cast won't panic on amd64 as usize > u32 always + let size = self.nonce_size as usize; + + Ok((gpa, size)) + } + + /// Returns the manifest GPA and size + /// Checks if gpa is page aligned, valid and does not cross page boundary. + pub fn get_manifest_gpa_and_size(&self) -> Result<(PhysAddr, usize), SvsmReqError> { + let gpa = PhysAddr::from(self.manifest_gpa); + if !gpa.is_aligned(8) || !valid_phys_address(gpa) || gpa.crosses_page(8) { + return Err(SvsmReqError::invalid_parameter()); + } + + // Won't panic on amd64 as usize > u32 always + let size = self.manifest_size as usize; + + Ok((gpa, size)) + } + + /// Returns the guid + /// Checks if gpa is page aligned, valid and does not cross page boundary + pub fn get_report_gpa_and_size(&self) -> Result<(PhysAddr, usize), SvsmReqError> { + let gpa = PhysAddr::from(self.report_gpa); + if !gpa.is_aligned(8) || !valid_phys_address(gpa) || gpa.crosses_page(8) { + return Err(SvsmReqError::invalid_parameter()); + } + + // Won't panic on amd64 as usize > u32 always + let size = self.report_size as usize; + + Ok((gpa, size)) + } + + /// Returns the certificate GPA and size + /// Checks if gpa is page aligned, valid and does not cross page boundary + /// pub fn get_report_gpa_and_size(&self) -> Result<(PhysAddr, usize), SvsmReqError> { + /// Currently not implemented supported as no service attestation currently supports + /// returning certificates. Implement when needed by copying get_report_gpa_and_size() + /// and replacing report's gpa and size with certificate's. + /// } + + pub fn get_manifest_version(&self) -> u32 { + self.manifest_ver + } + + fn is_manifest_version_valid(&self) -> bool { + //Currently only manifest version 0 is supported + self.manifest_ver == 0 + } + pub fn get_guid(&self) -> u128 { + u128::from_le_bytes(self.guid) + } +} + +#[cfg(all(feature = "vtpm", not(test)))] +fn get_attestation_report(nonce: &[u8]) -> Result, SvsmReqError> { + //Construct attestation request message to send to SNP + let mut report_req = Vec::::with_capacity(size_of::()); + let mut buf = Vec::::with_capacity(USER_DATA_SIZE); + buf.fill(0); + + if nonce.len() > USER_DATA_SIZE { + // If the nonce is greater than the user data size, return an error as something is wrong. + return Err(SvsmReqError::invalid_parameter()); + } + // Copy user attestation request nonce to buffer + buf.extend_from_slice(nonce); + report_req.extend_from_slice(&buf[..nonce.len()]); + + // Set request VMPL to 0 + report_req.extend_from_slice(&0_u32.to_le_bytes()); + + // Set reserved bytes to zeros + report_req.extend_from_slice(&[0; 28]); + + // Make sure buffer is big enough to hold the report + report_req.resize(size_of::(), 0); + + //send request to snp + let response_size = get_regular_report(report_req.as_mut_slice()).unwrap(); + + // Per Table 24 of "SEV Secure Nested Paging Firmware ABI Specification, Revision 1.56", + // attestation report starts at byte offset 0x20 + if response_size < 0x20 { + // If the response size is less than 0x20, return an error as something is wrong with + // the report or SNP + return Err(SvsmReqError::unsupported_protocol()); + } + report_req.drain(0..0x20); + + Ok(report_req) +} + +#[cfg(all(feature = "vtpm", not(test)))] +fn attest_single_vtpm( + params: &mut RequestParams, + ops: &AttestSingleServiceOp, +) -> Result<(), SvsmReqError> { + let nonce = ops.get_nonce()?; + + // Get the cached EKpub from the VTPM. Returns an error if the EKpub is not cached. + let manifest = vtpm_get_ekpub()?; + + // Concatenate nonce and manifest and hash per page 29 of + // "Secure VM Service Module for SEV-SNP Guests 58019 Rev. 1.00". + let nonce_and_manifest = [&nonce[..], &manifest[..]].concat(); + let hash = Sha512::digest(&nonce_and_manifest); + + // Get attestation report from PSP with Sha512(nonce||manifest) as REPORT_DATA. + let report = get_attestation_report(hash.as_slice())?; + + // Get attestation report buffer's gPA from call's Attest Single Service Operation structure + let (report_gpa, _) = ops.get_report_gpa_and_size()?; + let report_start = report_gpa.page_align(); + let report_offset = report_gpa.page_offset(); + + let report_guard = PerCPUPageMappingGuard::create_4k(report_start)?; + let report_vaddr = report_guard.virt_addr() + report_offset; + + // Check that the attestation report length is not greater than 4k. + // If it is, return an error as something is wrong with the report or SNP + // Per page 32 of "Secure VM Service Module for SEV-SNP Guests 58019 Rev. 1.00", + // return 0x8000_1000 i.e. SVSM::UNSUPPORTED_PROTOCOL. + if report.len() > PAGE_SIZE { + log::error!("Malformed VTPM service attestation report"); + return Err(SvsmReqError::unsupported_protocol()); + } + + // SAFETY: report_vaddr points to a new mapped region of size PAGE_SIZE. report_gpa is obtained + // from a guest-provided physical address (untrusted), so it needs to be validated that it + // belongs to the guest and only the guest. That was done inside get_report_gpa_and_size(). + // get_report_gpa_and_size() also validated that report_gpa is page aligned and does not cross + // a page boundary. + // Since we also checked that report.len() is not greater than PAGE_SIZE, we can safely write + // the report to guest_report_buffer. + let guest_report_buffer = + unsafe { from_raw_parts_mut(report_vaddr.as_mut_ptr::(), PAGE_SIZE) }; + guest_report_buffer[..report.len()].copy_from_slice(&report); + + // Set report size in bytes in r8 register + params.r8 = report.len() as u64; + + // Get manifest buffer's GPA from call's Attest Single Service Operation structure + let (manifest_gpa, _) = ops.get_manifest_gpa_and_size()?; + let manifest_start = manifest_gpa.page_align(); + let manifest_offset = manifest_gpa.page_offset(); + + let manifest_guard = PerCPUPageMappingGuard::create_4k(manifest_start)?; + let manifest_vaddr = manifest_guard.virt_addr() + manifest_offset; + + // Check that the length of the manifest is not greater than 4k. + // If it is, return an error as something is wrong with the manifest or vTPM + // Per page 32 of Secure VM Service Module for SEV-SNP Guests 58019 Rev. 1.00 July 2023 + // return SVSM::UNSUPPORTED_PROTOCOL (i.e., 0x8000_1000). + if manifest.len() > PAGE_SIZE { + log::error!("Malformed VTPM service attestation manifest"); + return Err(SvsmReqError::unsupported_protocol()); + } + + // SAFETY: manifest_vaddr points to a new mapped region of size PAGE_SIZE. report_gpa is obtained + // from a guest-provided physical address (untrusted), so it needs to be validated that it + // belongs to the guest and only the guest. That was done inside get_manifest_gpa_and_size(). + // get_manifest_gpa_and_size() also validated that manifest_gpa is page aligned and does not cross + // a page boundary. + // Since we also checked that manifest.len() is not greater than PAGE_SIZE, we can safely write + // the report to guest_manifest_buffer. + let guest_manifest_buffer = + unsafe { from_raw_parts_mut(manifest_vaddr.as_mut_ptr::(), PAGE_SIZE) }; + guest_manifest_buffer[..manifest.len()].copy_from_slice(&manifest); + + // Set the manifest size in bytes in rcx register + params.rcx = manifest.len() as u64; + + // Does not support certificate currently, so setting certificate size to 0 + params.rdx = 0; + + Ok(()) +} + +fn attest_multiple_service(_params: &RequestParams) -> Result<(), SvsmReqError> { + Err(SvsmReqError::unsupported_protocol()) +} + +#[allow(clippy::needless_pass_by_ref_mut)] +fn attest_single_service_handler(params: &mut RequestParams) -> Result<(), SvsmReqError> { + // Get the gpa of Attest Single Service Operation structure + let gpa = PhysAddr::from(params.rcx); + + if !gpa.is_aligned(8) || !valid_phys_address(gpa) || gpa.crosses_page(8) { + return Err(SvsmReqError::invalid_parameter()); + } + + let offset = gpa.page_offset(); + let paddr = gpa.page_align(); + + // Map the Attest Single Service Operation structure page + // Per Table 13 of the spec "Secure VM Service Module for SEV-SNP Guests + // 58019 Rev. 1.00", we only need the first 0x58 bytes. + let guard = PerCPUPageMappingGuard::create_4k(paddr)?; + let vaddr = guard.virt_addr() + offset; + + // SAFETY: The untrusted GPA from the guest is validated above as a valid address. + // The guard ensures that the page is newly mapped and not controlled by the guest. + // We only use a portion of the page, less than the full page size. + let buffer = + unsafe { from_raw_parts(vaddr.as_ptr::(), size_of::()) }; + let attest_op = AttestSingleServiceOp::try_from_as_ref(buffer)?; + + // Extract the GUID from the Attest Single Service Operation structure. + // The GUID is used to determine the specific service to be attested. + // Currently, only the VTPM service with the GUID 0xebf176c4_2301a545_9641b4e7_dde5bfe3 + // is supported, see 8.3.1 of the spec "Secure VM Service Module for SEV-SNP Guests + // 58019 Rev. 1.00" for more details. + let guid = attest_op.get_guid(); + + match guid { + #[cfg(all(feature = "vtpm", not(test)))] + SVSM_ATTEST_VTPM_GUID => attest_single_vtpm(params, attest_op), + _ => Err(SvsmReqError::unsupported_protocol()), + } +} + +pub fn attestation_protocol_request( + request: u32, + params: &mut RequestParams, +) -> Result<(), SvsmReqError> { + match request { + SVSM_ATTEST_SERVICES => attest_multiple_service(params), + SVSM_ATTEST_SINGLE_SERVICE => attest_single_service_handler(params), + _ => Err(SvsmReqError::unsupported_protocol()), + } +} diff --git a/kernel/src/protocols/mod.rs b/kernel/src/protocols/mod.rs index ccadb987f..81bba4b43 100644 --- a/kernel/src/protocols/mod.rs +++ b/kernel/src/protocols/mod.rs @@ -5,6 +5,7 @@ // Author: Dov Murik pub mod apic; +pub mod attestation; pub mod core; pub mod errors; #[cfg(all(feature = "vtpm", not(test)))] @@ -14,6 +15,7 @@ use cpuarch::vmsa::{GuestVMExit, VMSA}; // SVSM protocols pub const SVSM_CORE_PROTOCOL: u32 = 0; +pub const SVSM_ATTESTATION_PROTOCOL: u32 = 1; pub const SVSM_VTPM_PROTOCOL: u32 = 2; pub const SVSM_APIC_PROTOCOL: u32 = 3; diff --git a/kernel/src/requests.rs b/kernel/src/requests.rs index 599a40d34..24afa0114 100644 --- a/kernel/src/requests.rs +++ b/kernel/src/requests.rs @@ -13,6 +13,7 @@ use crate::protocols::core::core_protocol_request; use crate::protocols::errors::{SvsmReqError, SvsmResultCode}; use crate::sev::ghcb::switch_to_vmpl; +use crate::protocols::{attestation::attestation_protocol_request, SVSM_ATTESTATION_PROTOCOL}; #[cfg(all(feature = "vtpm", not(test)))] use crate::protocols::{vtpm::vtpm_protocol_request, SVSM_VTPM_PROTOCOL}; use crate::protocols::{RequestParams, SVSM_APIC_PROTOCOL, SVSM_CORE_PROTOCOL}; @@ -108,6 +109,7 @@ fn request_loop_once( match protocol { SVSM_CORE_PROTOCOL => core_protocol_request(request, params).map(|_| true), + SVSM_ATTESTATION_PROTOCOL => attestation_protocol_request(request, params).map(|_| true), #[cfg(all(feature = "vtpm", not(test)))] SVSM_VTPM_PROTOCOL => vtpm_protocol_request(request, params).map(|_| true), SVSM_APIC_PROTOCOL => apic_protocol_request(request, params).map(|_| true), diff --git a/kernel/src/vtpm/mod.rs b/kernel/src/vtpm/mod.rs index a83df1d3b..6019c896b 100644 --- a/kernel/src/vtpm/mod.rs +++ b/kernel/src/vtpm/mod.rs @@ -10,6 +10,9 @@ /// TPM 2.0 Reference Implementation pub mod tcgtpm; +extern crate alloc; +use alloc::vec::Vec; + use crate::vtpm::tcgtpm::TcgTpm as Vtpm; use crate::{locking::LockGuard, protocols::vtpm::TpmPlatformCommand}; use crate::{locking::SpinLock, protocols::errors::SvsmReqError}; @@ -66,6 +69,29 @@ pub trait VtpmInterface: TcgTpmSimulatorInterface { /// Prepare the TPM to be used for the first time. At this stage, /// the TPM is manufactured. fn init(&mut self) -> Result<(), SvsmReqError>; + + /// Create RSA 2048 Endorsement Key (EK) and cache the public key + /// + /// This function creates an RSA 2048-bit Endorsement Key (EK) from the TPM's Endorsement + /// Primary Seed (EPS) and caches the public key as TMPT_PUBLIC structure. The cached EK + /// public key can be retrieved later and used to provide vTPM service attestation. The + /// EK is created with the TCG default EK template as shown in Table 4 of the "TCG EK + /// Credential Profile For TPM Family 2.0; Level 0 Version 2.5 Revision 2.0". + /// + /// Since the EK is created from the EPS, following the TCG EK Credential Profile, the EK can + /// be recreated at any time. For example, one can recreate the same EK in an OS using TSS2 + /// "tpm2_createek -c ek.ctx -G rsa -u ek.pub command". + /// + /// Retrieve the EK public key using the get_ekpub() function. + /// + /// # Warning + /// + /// The function should only be called after the TPM is powered on and initialised + fn create_ek_rsa2048(&mut self) -> Result<(), SvsmReqError>; + + /// Returns the cached EK public key if it exists, otherwise it returns an error indicating + /// that the EK public key does not exist. + fn get_ekpub(&self) -> Result, SvsmReqError>; } static VTPM: SpinLock = SpinLock::new(Vtpm::new()); @@ -84,3 +110,10 @@ pub fn vtpm_init() -> Result<(), SvsmReqError> { pub fn vtpm_get_locked<'a>() -> LockGuard<'a, Vtpm> { VTPM.lock() } + +/// Get the TPM EK public key by calling the get_ekpub() implementation of the +/// [`VtpmInterface`] +pub fn vtpm_get_ekpub() -> Result, SvsmReqError> { + let vtpm = VTPM.lock(); + vtpm.get_ekpub() +} diff --git a/kernel/src/vtpm/tcgtpm/mod.rs b/kernel/src/vtpm/tcgtpm/mod.rs index 70474f46c..63dff30f6 100644 --- a/kernel/src/vtpm/tcgtpm/mod.rs +++ b/kernel/src/vtpm/tcgtpm/mod.rs @@ -14,6 +14,7 @@ mod wrapper; extern crate alloc; use alloc::vec::Vec; + use core::ffi::c_void; use libtcgtpm::bindings::{ TPM_Manufacture, TPM_TearDown, _plat__LocalitySet, _plat__NVDisable, _plat__NVEnable, @@ -27,15 +28,30 @@ use crate::{ vtpm::{TcgTpmSimulatorInterface, VtpmInterface, VtpmProtocolInterface}, }; -#[derive(Debug, Copy, Clone, Default)] +// Definitions from "Trusted Platform Module Library Part 4: Supporting Routines – Code, +// Family “2.0”, Level 00, Revision 01.38" +const TPM_ST_SESSIONS: u16 = 0x8002; +const TPM_CC_CREATEPRIMARY: u32 = 0x00000131; +const TPM_RH_ENDORSEMENT: u32 = 0x4000000B; +const TPM_ALG_RSA: u16 = 0x0001; +const TPM_ALG_SHA256: u16 = 0x000B; +const TPM_ALG_AES: u16 = 0x0006; +const TPM_ALG_CFB: u16 = 0x0043; +const TPM_ALG_NULL: u16 = 0x0010; +const TPM_KEY_BITS_2048: u16 = 2048; +const TPM_RS_PW: u32 = 0x40000009; + +#[derive(Debug, Clone, Default)] pub struct TcgTpm { is_powered_on: bool, + ekpub: Option>, } impl TcgTpm { pub const fn new() -> TcgTpm { TcgTpm { is_powered_on: false, + ekpub: None, } } @@ -84,8 +100,10 @@ impl TcgTpmSimulatorInterface for TcgTpm { locality: u8, ) -> Result<(), SvsmReqError> { if !self.is_powered_on { + log::error!("TPM is not powered on"); return Err(SvsmReqError::invalid_request()); } + if *length > TPM_BUFFER_MAX_SIZE || *length > buffer.len() { return Err(SvsmReqError::invalid_parameter()); } @@ -148,12 +166,137 @@ impl TcgTpmSimulatorInterface for TcgTpm { } impl VtpmInterface for TcgTpm { + fn get_ekpub(&self) -> Result, SvsmReqError> { + self.ekpub.clone().ok_or_else(SvsmReqError::invalid_request) + } + + fn create_ek_rsa2048(&mut self) -> Result<(), SvsmReqError> { + // Creates RSA 2048-bit EK using TPM2_CreatePrimary command and TCG default EK template + // + // TPM2_CreatePrimary command is defined in Table 173 — TPM2_CreatePrimary Command, 365 of + // "Trusted Platform Module Library Part 3: Commands-Codes, Family “2.0”, Level 00, + // Revision 01.38". + // + // The TCG default EK template is defined in "Table 2: Default EK Template (TPMT_PUBLIC) + // L-1: RSA 2048 (Storage)" of "TCG EK Credential Profile For TPM Family 2.0; Level 0 + // Version 2.5 Revision 2". + // + // See also "TCG TSS 2.0 Overview and Common Structures Specification, Version 1.0, + // Level 2 Revision 10". + + let authpolicy: [u8; 32] = [ + 0x83, 0x71, 0x97, 0x67, 0x44, 0x84, 0xb3, 0xf8, 0x1a, 0x90, 0xcc, 0x8d, 0x46, 0xa5, + 0xd7, 0x24, 0xfd, 0x52, 0xd7, 0x6e, 0x06, 0x52, 0x0b, 0x64, 0xf2, 0xa1, 0xda, 0x1b, + 0x33, 0x14, 0x69, 0xaa, + ]; + + let object_attributes: u32 = 0x000300b2; + + // See Table 12 — Password Authorization Command of + // Trusted Platform Module Library Part 1: Architecture, + // Family “2.0”, Level 00, Revision 01.38 + let mut auth_block: Vec = Vec::new(); + auth_block.extend_from_slice(&TPM_RS_PW.to_be_bytes()); + // nonce == empty buffer + auth_block.extend_from_slice(&[0x00, 0x00]); + // session attributes = continueSession = 0x01 + auth_block.extend_from_slice(&[0x01]); + // password = empty buffer + auth_block.extend_from_slice(&[0x00, 0x00]); + + // TPM2B_PUBLIC with TCG default EK template, + // see Table 2: Default EK Template (TPMT_PUBLIC) L-1: RSA 2048 (Storage) + // of TCG EK Credential Profile For TPM Family 2.0; Level 0 Version 2.5 Revision 2 + let mut public_area: Vec = Vec::new(); + // type + public_area.extend_from_slice(&TPM_ALG_RSA.to_be_bytes()); + // nameAlg + public_area.extend_from_slice(&TPM_ALG_SHA256.to_be_bytes()); + // objectAttributes + public_area.extend_from_slice(&object_attributes.to_be_bytes()); + // authPolicy size + public_area.extend_from_slice(&(authpolicy.len() as u16).to_be_bytes()); + // authPolicy + public_area.extend_from_slice(authpolicy.as_slice()); + // parameters + // symmetric algorithm + public_area.extend_from_slice(&TPM_ALG_AES.to_be_bytes()); + // symmetric keyBits + public_area.extend_from_slice(&128_u16.to_be_bytes()); + // symmetric mode + public_area.extend_from_slice(&TPM_ALG_CFB.to_be_bytes()); + + // scheme + public_area.extend_from_slice(&TPM_ALG_NULL.to_be_bytes()); + // keyBits + public_area.extend_from_slice(&TPM_KEY_BITS_2048.to_be_bytes()); + // exponent + public_area.extend_from_slice(&0_u32.to_be_bytes()); + + // unique size + public_area.extend_from_slice(&256_u16.to_be_bytes()); + // unique + public_area.extend_from_slice(&[0x00; 256]); + + let mut cmd = Vec::::with_capacity(TPM_BUFFER_MAX_SIZE); + + // TPM Command header + cmd.extend_from_slice(&TPM_ST_SESSIONS.to_be_bytes()); + // Placeholder for command size + cmd.extend_from_slice(&(0u32).to_be_bytes()); + cmd.extend_from_slice(&TPM_CC_CREATEPRIMARY.to_be_bytes()); + cmd.extend_from_slice(&TPM_RH_ENDORSEMENT.to_be_bytes()); + + // Authorization block + cmd.extend_from_slice(&(auth_block.len() as u32).to_be_bytes()); + cmd.extend_from_slice(auth_block.as_slice()); + + // inSensitive parameter + // + // TPM2B_SENSITIVE_CREATE structure is defined in + // Table 132 — Definition of TPM2B_SENSITIVE_CREATE Structure, + // Trusted Platform Module Library Part 2: Structures + // sensitive data size + cmd.extend_from_slice(&4_u16.to_be_bytes()); + // user auth + cmd.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]); + + // inPublic parameter + // parameters size + cmd.extend_from_slice(&(public_area.len() as u16).to_be_bytes()); + // parameters + cmd.extend_from_slice(public_area.as_slice()); + + // outsideInfo parameter + cmd.extend_from_slice(&0_u32.to_be_bytes()); + cmd.extend_from_slice(&0_u16.to_be_bytes()); + + // Update command size + let mut command_size = cmd.len(); + cmd[2..6].copy_from_slice(&(command_size as u32).to_be_bytes()); + + cmd.resize(TPM_BUFFER_MAX_SIZE, 0); + + self.send_tpm_command(&mut cmd[..], &mut command_size, 0)?; + + // Check that TPM_RC(UINT32) at byte offset 6 is 0x00000000 (TPM_RC_SUCCESS) + if cmd[6..10] != [0x00, 0x00, 0x00, 0x00] { + return Err(SvsmReqError::incomplete()); + } + + // Get size (UINT16) of TPMT_PUBLIC at offset 18 + let size_of_tpmt_public = u16::from_be_bytes([cmd[18], cmd[19]]); + self.ekpub = Some(cmd[20..(20 + size_of_tpmt_public) as usize].to_vec()); + + Ok(()) + } + fn is_powered_on(&self) -> bool { self.is_powered_on } fn init(&mut self) -> Result<(), SvsmReqError> { - // Initialize the TPM TCG following the same steps done in the Simulator: + // Initialize the TPM TCG following the same steps done in the Simulator and generate EK: // // 1. Manufacture it for the first time // 2. Make sure it does not fail if it is re-manufactured @@ -161,6 +304,13 @@ impl VtpmInterface for TcgTpm { // 4. Manufacture it for the first time // 5. Power it on indicating it requires startup. By default, OVMF will start // and selftest it. + // 6. Selftest it + // 7. Start it up on for next step + // 8. Create RSA2004 EK and cache EKpub for VTPM service attestation requests + // + // Since we have already run TPM2_Startup here, when OVMF runs TPM2_Startup, it will + // get back TPM_RC_INITIALIZE indicating that TPM2_Startup is not required. See, + // https://github.com/tianocore/edk2/blob/master/SecurityPkg/Library/Tpm2CommandLib/Tpm2Startup.c#L75 unsafe { _plat__NVEnable(VirtAddr::null().as_mut_ptr::()) }; @@ -184,6 +334,19 @@ impl VtpmInterface for TcgTpm { self.signal_poweron(false)?; self.signal_nvon()?; + // TPM2_CC_SelfTest + let selftest_cmd: &mut [u8] = &mut [ + 0x80, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x01, 0x43, 0x00, + ]; + // TPM2_CC_Startup + let startup_cmd: &mut [u8] = &mut [ + 0x80, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x01, 0x44, 0x00, 0x00, + ]; + self.send_tpm_command(selftest_cmd, &mut selftest_cmd.len(), 0)?; + self.send_tpm_command(startup_cmd, &mut startup_cmd.len(), 0)?; + + self.create_ek_rsa2048()?; + log::info!("VTPM: TPM 2.0 Reference Implementation initialized"); Ok(()) From 87f312f8aa420b875d5a39130181ee90d68e165b Mon Sep 17 00:00:00 2001 From: Geoffrey Ndu Date: Tue, 26 Nov 2024 15:50:14 +0000 Subject: [PATCH 02/10] Fix comments Closes comment Signed-off-by: Geoffrey Ndu --- kernel/src/protocols/attestation.rs | 33 +++++++++++++---------------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/kernel/src/protocols/attestation.rs b/kernel/src/protocols/attestation.rs index 1e99e9e6d..158341c82 100644 --- a/kernel/src/protocols/attestation.rs +++ b/kernel/src/protocols/attestation.rs @@ -98,15 +98,16 @@ impl AttestSingleServiceOp { let guard = PerCPUPageMappingGuard::create_4k(start)?; let vaddr = guard.virt_addr() + offset; - // Check that the nonce length is not greater than 4k. - // If it is, return an error as something is wrong with the request + // Check that the nonce length is not greater than 4k as we are going to map in only one + // page. Nonce size is 64 bytes per Table 21 of + // "SEV Secure Nested Paging Firmware ABI Specification, Revision 1.56" if size > PAGE_SIZE { return Err(SvsmReqError::invalid_parameter()); } // SAFETY: vaddr points to a new mapped page region. And get_nonce_gpa_and_size() already - // validated that gpa is page aligned, valid and does not cross. We also checked earlier - // that size is not greater than PAGE_SIZE, so we can safely read the nonce. + // validated that gpa is page aligned, valid and does not cross page boundaries. We also + // checked earlier that size is not greater than PAGE_SIZE, so we can safely read the nonce. let buffer = unsafe { from_raw_parts(vaddr.as_mut_ptr::(), size) }; let nonce = buffer.to_vec(); @@ -155,14 +156,6 @@ impl AttestSingleServiceOp { Ok((gpa, size)) } - /// Returns the certificate GPA and size - /// Checks if gpa is page aligned, valid and does not cross page boundary - /// pub fn get_report_gpa_and_size(&self) -> Result<(PhysAddr, usize), SvsmReqError> { - /// Currently not implemented supported as no service attestation currently supports - /// returning certificates. Implement when needed by copying get_report_gpa_and_size() - /// and replacing report's gpa and size with certificate's. - /// } - pub fn get_manifest_version(&self) -> u32 { self.manifest_ver } @@ -182,7 +175,6 @@ fn get_attestation_report(nonce: &[u8]) -> Result, SvsmReqError> { let mut report_req = Vec::::with_capacity(size_of::()); let mut buf = Vec::::with_capacity(USER_DATA_SIZE); buf.fill(0); - if nonce.len() > USER_DATA_SIZE { // If the nonce is greater than the user data size, return an error as something is wrong. return Err(SvsmReqError::invalid_parameter()); @@ -200,7 +192,7 @@ fn get_attestation_report(nonce: &[u8]) -> Result, SvsmReqError> { // Make sure buffer is big enough to hold the report report_req.resize(size_of::(), 0); - //send request to snp + // Send request to snp let response_size = get_regular_report(report_req.as_mut_slice()).unwrap(); // Per Table 24 of "SEV Secure Nested Paging Firmware ABI Specification, Revision 1.56", @@ -232,6 +224,7 @@ fn attest_single_vtpm( // Get attestation report from PSP with Sha512(nonce||manifest) as REPORT_DATA. let report = get_attestation_report(hash.as_slice())?; + // Validate that the report is not empty // Get attestation report buffer's gPA from call's Attest Single Service Operation structure let (report_gpa, _) = ops.get_report_gpa_and_size()?; @@ -241,10 +234,12 @@ fn attest_single_vtpm( let report_guard = PerCPUPageMappingGuard::create_4k(report_start)?; let report_vaddr = report_guard.virt_addr() + report_offset; - // Check that the attestation report length is not greater than 4k. - // If it is, return an error as something is wrong with the report or SNP + // Check that the attestation report length is not greater than 4K as we going to map in one + // page only below. If it is, return an error as something is wrong with the report or SNP // Per page 32 of "Secure VM Service Module for SEV-SNP Guests 58019 Rev. 1.00", // return 0x8000_1000 i.e. SVSM::UNSUPPORTED_PROTOCOL. + // The Attestation report length is 0x5F (95) per Table 21 of + // "SEV Secure Nested Paging Firmware ABI Specification, Revision 1.56" if report.len() > PAGE_SIZE { log::error!("Malformed VTPM service attestation report"); return Err(SvsmReqError::unsupported_protocol()); @@ -272,10 +267,12 @@ fn attest_single_vtpm( let manifest_guard = PerCPUPageMappingGuard::create_4k(manifest_start)?; let manifest_vaddr = manifest_guard.virt_addr() + manifest_offset; - // Check that the length of the manifest is not greater than 4k. - // If it is, return an error as something is wrong with the manifest or vTPM + // Check that the length of the manifest is not greater than 4k as we going to map in one page + // only. If it is, return an error as something is wrong with the manifest or vTPM // Per page 32 of Secure VM Service Module for SEV-SNP Guests 58019 Rev. 1.00 July 2023 // return SVSM::UNSUPPORTED_PROTOCOL (i.e., 0x8000_1000). + // Per "TCG EK Credential Profile For TPM Family 2.0; Level 0 Version 2.5 Revision 2" + // none of the TCG EK profiles will produce a manifest i.e. TPMT_PUBLIC larger than 4K. if manifest.len() > PAGE_SIZE { log::error!("Malformed VTPM service attestation manifest"); return Err(SvsmReqError::unsupported_protocol()); From e9958e1a07b4235281d3f6a5e0219406b30e3d31 Mon Sep 17 00:00:00 2001 From: Geoffrey Ndu Date: Tue, 26 Nov 2024 16:39:19 +0000 Subject: [PATCH 03/10] Switch to use vec! Closes PR comment Signed-off-by: Geoffrey Ndu --- kernel/src/protocols/attestation.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/kernel/src/protocols/attestation.rs b/kernel/src/protocols/attestation.rs index 158341c82..4caacde61 100644 --- a/kernel/src/protocols/attestation.rs +++ b/kernel/src/protocols/attestation.rs @@ -14,6 +14,9 @@ use crate::{ mm::{valid_phys_address, PerCPUPageMappingGuard}, types::PAGE_SIZE, }; +use alloc::vec::Vec; +use core::{mem::size_of, slice::from_raw_parts}; + #[cfg(all(feature = "vtpm", not(test)))] use crate::{ greq::{ @@ -22,12 +25,13 @@ use crate::{ }, vtpm::vtpm_get_ekpub, }; -use alloc::vec::Vec; +#[cfg(all(feature = "vtpm", not(test)))] +use alloc::vec; #[cfg(all(feature = "vtpm", not(test)))] use core::slice::from_raw_parts_mut; -use core::{mem::size_of, slice::from_raw_parts}; #[cfg(all(feature = "vtpm", not(test)))] use sha2::{Digest, Sha512}; + const SVSM_ATTEST_SERVICES: u32 = 0; const SVSM_ATTEST_SINGLE_SERVICE: u32 = 1; @@ -173,8 +177,7 @@ impl AttestSingleServiceOp { fn get_attestation_report(nonce: &[u8]) -> Result, SvsmReqError> { //Construct attestation request message to send to SNP let mut report_req = Vec::::with_capacity(size_of::()); - let mut buf = Vec::::with_capacity(USER_DATA_SIZE); - buf.fill(0); + let mut buf = vec![0u8; USER_DATA_SIZE]; if nonce.len() > USER_DATA_SIZE { // If the nonce is greater than the user data size, return an error as something is wrong. return Err(SvsmReqError::invalid_parameter()); From 5b94433bc4fe5e8d4396df257c266dc0fdd6e1cd Mon Sep 17 00:00:00 2001 From: Geoffrey Ndu Date: Tue, 26 Nov 2024 16:40:18 +0000 Subject: [PATCH 04/10] Remove leftover unwarp() Blah blah Signed-off-by: Geoffrey Ndu --- kernel/src/protocols/attestation.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/src/protocols/attestation.rs b/kernel/src/protocols/attestation.rs index 4caacde61..69dc06282 100644 --- a/kernel/src/protocols/attestation.rs +++ b/kernel/src/protocols/attestation.rs @@ -196,7 +196,7 @@ fn get_attestation_report(nonce: &[u8]) -> Result, SvsmReqError> { report_req.resize(size_of::(), 0); // Send request to snp - let response_size = get_regular_report(report_req.as_mut_slice()).unwrap(); + let response_size = get_regular_report(report_req.as_mut_slice())?; // Per Table 24 of "SEV Secure Nested Paging Firmware ABI Specification, Revision 1.56", // attestation report starts at byte offset 0x20 From 3be1f4dc708c14dd26a94d42ba32231fa6a74e5c Mon Sep 17 00:00:00 2001 From: Geoffrey Ndu Date: Tue, 26 Nov 2024 17:18:52 +0000 Subject: [PATCH 05/10] Revert "Switch to use vec!" Code harcodes request nonce to all zeros! Need to revert. This reverts commit 625c6c86be3f22edede9c6c55e319ca8a669d8aa. Signed-off-by: Geoffrey Ndu --- kernel/src/protocols/attestation.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/kernel/src/protocols/attestation.rs b/kernel/src/protocols/attestation.rs index 69dc06282..95aa246af 100644 --- a/kernel/src/protocols/attestation.rs +++ b/kernel/src/protocols/attestation.rs @@ -14,9 +14,6 @@ use crate::{ mm::{valid_phys_address, PerCPUPageMappingGuard}, types::PAGE_SIZE, }; -use alloc::vec::Vec; -use core::{mem::size_of, slice::from_raw_parts}; - #[cfg(all(feature = "vtpm", not(test)))] use crate::{ greq::{ @@ -25,13 +22,12 @@ use crate::{ }, vtpm::vtpm_get_ekpub, }; -#[cfg(all(feature = "vtpm", not(test)))] -use alloc::vec; +use alloc::vec::Vec; #[cfg(all(feature = "vtpm", not(test)))] use core::slice::from_raw_parts_mut; +use core::{mem::size_of, slice::from_raw_parts}; #[cfg(all(feature = "vtpm", not(test)))] use sha2::{Digest, Sha512}; - const SVSM_ATTEST_SERVICES: u32 = 0; const SVSM_ATTEST_SINGLE_SERVICE: u32 = 1; @@ -177,7 +173,8 @@ impl AttestSingleServiceOp { fn get_attestation_report(nonce: &[u8]) -> Result, SvsmReqError> { //Construct attestation request message to send to SNP let mut report_req = Vec::::with_capacity(size_of::()); - let mut buf = vec![0u8; USER_DATA_SIZE]; + let mut buf = Vec::::with_capacity(USER_DATA_SIZE); + buf.fill(0); if nonce.len() > USER_DATA_SIZE { // If the nonce is greater than the user data size, return an error as something is wrong. return Err(SvsmReqError::invalid_parameter()); From 67b7f5bd24d92c302a7c38835af2a78b16a6d666 Mon Sep 17 00:00:00 2001 From: Geoffrey Ndu Date: Tue, 26 Nov 2024 17:33:20 +0000 Subject: [PATCH 06/10] Remove extraneous vector fill() Fixes PR comment Signed-off-by: Geoffrey Ndu --- kernel/src/protocols/attestation.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/kernel/src/protocols/attestation.rs b/kernel/src/protocols/attestation.rs index 95aa246af..1e354f8d0 100644 --- a/kernel/src/protocols/attestation.rs +++ b/kernel/src/protocols/attestation.rs @@ -174,7 +174,6 @@ fn get_attestation_report(nonce: &[u8]) -> Result, SvsmReqError> { //Construct attestation request message to send to SNP let mut report_req = Vec::::with_capacity(size_of::()); let mut buf = Vec::::with_capacity(USER_DATA_SIZE); - buf.fill(0); if nonce.len() > USER_DATA_SIZE { // If the nonce is greater than the user data size, return an error as something is wrong. return Err(SvsmReqError::invalid_parameter()); From 4ca52eb63d058bcf157d06ed0ddda181c16fcae6 Mon Sep 17 00:00:00 2001 From: Geoffrey Ndu Date: Wed, 27 Nov 2024 11:26:36 +0000 Subject: [PATCH 07/10] Remove superfluous length check Fixes PR comment Signed-off-by: Geoffrey Ndu --- kernel/src/protocols/attestation.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/kernel/src/protocols/attestation.rs b/kernel/src/protocols/attestation.rs index 1e354f8d0..72bb350d9 100644 --- a/kernel/src/protocols/attestation.rs +++ b/kernel/src/protocols/attestation.rs @@ -122,7 +122,7 @@ impl AttestSingleServiceOp { return Err(SvsmReqError::invalid_parameter()); } - //cast won't panic on amd64 as usize > u32 always + // Won't panic on amd64 as usize > u32 always let size = self.nonce_size as usize; Ok((gpa, size)) @@ -192,15 +192,12 @@ fn get_attestation_report(nonce: &[u8]) -> Result, SvsmReqError> { report_req.resize(size_of::(), 0); // Send request to snp - let response_size = get_regular_report(report_req.as_mut_slice())?; + let _response_size = get_regular_report(report_req.as_mut_slice())?; // Per Table 24 of "SEV Secure Nested Paging Firmware ABI Specification, Revision 1.56", - // attestation report starts at byte offset 0x20 - if response_size < 0x20 { - // If the response size is less than 0x20, return an error as something is wrong with - // the report or SNP - return Err(SvsmReqError::unsupported_protocol()); - } + // attestation report starts at byte offset 0x20. And get_regular_report() already called + // SnpReportResponse::validate_report() which checks that the report is the right length. + // So we can always drain the first 0x20 bytes without panicking at runtime. report_req.drain(0..0x20); Ok(report_req) From b57914f763d6f0cc678ef9934a13b29731682e75 Mon Sep 17 00:00:00 2001 From: Geoffrey Ndu Date: Wed, 27 Nov 2024 12:00:00 +0000 Subject: [PATCH 08/10] Check is vtpm is on and remove warning Fixes PR comments Signed-off-by: Geoffrey Ndu --- kernel/src/vtpm/mod.rs | 6 +----- kernel/src/vtpm/tcgtpm/mod.rs | 5 ++++- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/kernel/src/vtpm/mod.rs b/kernel/src/vtpm/mod.rs index 6019c896b..b5662c135 100644 --- a/kernel/src/vtpm/mod.rs +++ b/kernel/src/vtpm/mod.rs @@ -82,11 +82,7 @@ pub trait VtpmInterface: TcgTpmSimulatorInterface { /// be recreated at any time. For example, one can recreate the same EK in an OS using TSS2 /// "tpm2_createek -c ek.ctx -G rsa -u ek.pub command". /// - /// Retrieve the EK public key using the get_ekpub() function. - /// - /// # Warning - /// - /// The function should only be called after the TPM is powered on and initialised + /// Retrieve the EK public key with get_ekpub() function. fn create_ek_rsa2048(&mut self) -> Result<(), SvsmReqError>; /// Returns the cached EK public key if it exists, otherwise it returns an error indicating diff --git a/kernel/src/vtpm/tcgtpm/mod.rs b/kernel/src/vtpm/tcgtpm/mod.rs index 63dff30f6..b72dcb94a 100644 --- a/kernel/src/vtpm/tcgtpm/mod.rs +++ b/kernel/src/vtpm/tcgtpm/mod.rs @@ -29,7 +29,7 @@ use crate::{ }; // Definitions from "Trusted Platform Module Library Part 4: Supporting Routines – Code, -// Family “2.0”, Level 00, Revision 01.38" +// Family “2.0”, Level 00, Revision 01.38" const TPM_ST_SESSIONS: u16 = 0x8002; const TPM_CC_CREATEPRIMARY: u32 = 0x00000131; const TPM_RH_ENDORSEMENT: u32 = 0x4000000B; @@ -183,6 +183,9 @@ impl VtpmInterface for TcgTpm { // // See also "TCG TSS 2.0 Overview and Common Structures Specification, Version 1.0, // Level 2 Revision 10". + if !self.is_powered_on { + return Err(SvsmReqError::invalid_request()); + } let authpolicy: [u8; 32] = [ 0x83, 0x71, 0x97, 0x67, 0x44, 0x84, 0xb3, 0xf8, 0x1a, 0x90, 0xcc, 0x8d, 0x46, 0xa5, From fb975c76b64f97a1a7c3d570035e28d513626454 Mon Sep 17 00:00:00 2001 From: Geoffrey Ndu Date: Wed, 27 Nov 2024 13:54:02 +0000 Subject: [PATCH 09/10] Fold startup and selftest into functions Fold startup and selftest into functions Signed-off-by: Geoffrey Ndu --- kernel/src/vtpm/mod.rs | 7 +++++++ kernel/src/vtpm/tcgtpm/mod.rs | 32 ++++++++++++++++++++++---------- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/kernel/src/vtpm/mod.rs b/kernel/src/vtpm/mod.rs index b5662c135..42917c8c5 100644 --- a/kernel/src/vtpm/mod.rs +++ b/kernel/src/vtpm/mod.rs @@ -11,6 +11,7 @@ pub mod tcgtpm; extern crate alloc; + use alloc::vec::Vec; use crate::vtpm::tcgtpm::TcgTpm as Vtpm; @@ -88,6 +89,12 @@ pub trait VtpmInterface: TcgTpmSimulatorInterface { /// Returns the cached EK public key if it exists, otherwise it returns an error indicating /// that the EK public key does not exist. fn get_ekpub(&self) -> Result, SvsmReqError>; + + /// Run the TPM self-test command + fn run_selftest_cmd(&self) -> Result<(), SvsmReqError>; + + // Run the TPM startup command + fn run_startup_cmd(&self) -> Result<(), SvsmReqError>; } static VTPM: SpinLock = SpinLock::new(Vtpm::new()); diff --git a/kernel/src/vtpm/tcgtpm/mod.rs b/kernel/src/vtpm/tcgtpm/mod.rs index b72dcb94a..4cc55ed1f 100644 --- a/kernel/src/vtpm/tcgtpm/mod.rs +++ b/kernel/src/vtpm/tcgtpm/mod.rs @@ -166,6 +166,26 @@ impl TcgTpmSimulatorInterface for TcgTpm { } impl VtpmInterface for TcgTpm { + fn run_selftest_cmd(&self) -> Result<(), SvsmReqError> { + // TPM2_CC_SelfTest + let selftest_cmd: &mut [u8] = &mut [ + 0x80, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x01, 0x43, 0x00, + ]; + self.send_tpm_command(selftest_cmd, &mut selftest_cmd.len(), 0)?; + + Ok(()) + } + + fn run_startup_cmd(&self) -> Result<(), SvsmReqError> { + // TPM2_CC_Startup + let startup_cmd: &mut [u8] = &mut [ + 0x80, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x01, 0x44, 0x00, 0x00, + ]; + self.send_tpm_command(startup_cmd, &mut startup_cmd.len(), 0)?; + + Ok(()) + } + fn get_ekpub(&self) -> Result, SvsmReqError> { self.ekpub.clone().ok_or_else(SvsmReqError::invalid_request) } @@ -337,16 +357,8 @@ impl VtpmInterface for TcgTpm { self.signal_poweron(false)?; self.signal_nvon()?; - // TPM2_CC_SelfTest - let selftest_cmd: &mut [u8] = &mut [ - 0x80, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x01, 0x43, 0x00, - ]; - // TPM2_CC_Startup - let startup_cmd: &mut [u8] = &mut [ - 0x80, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x01, 0x44, 0x00, 0x00, - ]; - self.send_tpm_command(selftest_cmd, &mut selftest_cmd.len(), 0)?; - self.send_tpm_command(startup_cmd, &mut startup_cmd.len(), 0)?; + self.run_selftest_cmd()?; + self.run_startup_cmd()?; self.create_ek_rsa2048()?; From 23a632d467c39de7ef0eb81c2718202bbf7c2f7f Mon Sep 17 00:00:00 2001 From: Geoffrey Ndu Date: Wed, 27 Nov 2024 17:07:33 +0000 Subject: [PATCH 10/10] Add comment on use of "force-soft" Fixes comment --- kernel/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index dcdf712c8..901ad31ad 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -35,6 +35,7 @@ log = { workspace = true, features = ["max_level_info", "release_max_level_info" packit.workspace = true libtcgtpm = { workspace = true, optional = true } zerocopy.workspace = true +# Need "force-soft", see https://github.com/RustCrypto/hashes/issues/446 sha2 = { workspace = true, features = ["force-soft"] } builtin = { workspace = true, optional = true }