diff --git a/src/mem/ca.rs b/src/mem/ca.rs index f83e3e9..8648437 100644 --- a/src/mem/ca.rs +++ b/src/mem/ca.rs @@ -6,6 +6,9 @@ * Tom Lendacky */ +use x86_64::structures::paging::PhysFrame; +use x86_64::PhysAddr; + use crate::*; #[repr(C, packed)] @@ -19,3 +22,10 @@ impl Ca { funcs!(call_pending, u8); funcs!(mem_available, u8); } + +pub fn is_in_calling_area(gpa: PhysAddr) -> bool { + let gfn: PhysFrame = PhysFrame::containing_address(gpa); + let caa_gpa: PhysAddr = unsafe { PERCPU.caa(VMPL::Vmpl1) }; + let caa_gfn: PhysFrame = PhysFrame::containing_address(caa_gpa); + gfn == caa_gfn +} diff --git a/src/mem/mod.rs b/src/mem/mod.rs index 1f386c2..c84ace7 100644 --- a/src/mem/mod.rs +++ b/src/mem/mod.rs @@ -24,6 +24,8 @@ pub use crate::mem::alloc::{ mem_free, mem_free_frame, mem_free_frames, mem_init, mem_reallocate, }; +pub use crate::mem::ca::is_in_calling_area; + pub use crate::mem::pgtable::{ pgtable_init, pgtable_make_pages_np, pgtable_make_pages_nx, pgtable_make_pages_private, pgtable_make_pages_shared, pgtable_pa_to_va, pgtable_print_pte_pa, pgtable_print_pte_va, diff --git a/src/protocols/attestation.rs b/src/protocols/attestation.rs new file mode 100644 index 0000000..59c2ca0 --- /dev/null +++ b/src/protocols/attestation.rs @@ -0,0 +1,304 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (C) 2023 IBM Corporation + * + * Authors: Dov Murik + */ + +use alloc::vec::Vec; +use core::mem::size_of; +use uuid::Uuid; +use x86_64::structures::paging::mapper::MapToError; +use x86_64::structures::paging::{PhysFrame, Size4KiB}; +use x86_64::PhysAddr; + +use crate::cpu::vmsa::Vmsa; +use crate::mem::is_in_calling_area; +use crate::mem::MapGuard; +use crate::protocols::error_codes::*; +use crate::PAGE_SIZE; + +use super::SERVICES; + +/// 0 +const SVSM_ATTEST_SERVICES: u32 = 0; +/// 1 +const SVSM_ATTEST_SINGLE_SERVICE: u32 = 1; + +/// 0x8000_1000 +const SVSM_FAIL_SNP_ATTESTATION: u64 = 0x8000_1000; + +#[derive(Default, Debug)] +struct AttestationResult { + code: u64, + services_manifest_size: u64, + certs_size: u64, + report_size: u64, +} + +impl AttestationResult { + pub fn from_code(code: u64) -> Self { + Self { + code, + ..Default::default() + } + } +} + +/// SVSM Spec Chapter 7 (Attestation): Table 11: Attest Services operation +#[derive(Clone, Copy, Debug)] +#[repr(C, packed)] +struct AttestServicesRequest { + report_gpa: u64, + report_size: u32, + _reserved1: [u8; 4], + nonce_gpa: u64, + nonce_size: u16, + _reserved2: [u8; 6], + services_manifest_gpa: u64, + services_manifest_size: u32, + _reserved3: [u8; 4], + certs_gpa: u64, + certs_size: u32, + _reserved4: [u8; 4], +} + +/// SVSM Spec Chapter 7 (Attestation): Table 13: Attest Single Service operation +#[derive(Clone, Copy, Debug)] +#[repr(C, packed)] +struct AttestSingleServiceRequest { + base: AttestServicesRequest, + service_guid: [u8; 16], + manifest_version: u32, + _reserved5: [u8; 4], +} + +fn add_nonce_to_buffer( + nonce_gpa: u64, + nonce_size: u16, + buf: &mut Vec, +) -> Result<(), AttestationResult> { + let nonce_gpa: PhysAddr = PhysAddr::new(nonce_gpa); + verify_fits_in_one_page(nonce_gpa, nonce_size as usize)?; + let map: MapGuard = MapGuard::new_private(nonce_gpa, nonce_size as u64) + .map_err(|_| AttestationResult::from_code(SVSM_ERR_INVALID_PARAMETER))?; + let nonce: &[u8] = map.as_bytes(); + buf.extend_from_slice(nonce); + Ok(()) +} + +fn fits_in_one_page(start_gpa: PhysAddr, size: usize) -> bool { + if size == 0 { + return false; + } + let end_gpa: PhysAddr = start_gpa + size - 1usize; + let start_frame: PhysFrame = PhysFrame::containing_address(start_gpa); + let end_frame: PhysFrame = PhysFrame::containing_address(end_gpa); + start_frame == end_frame +} + +fn verify_fits_in_one_page(start_gpa: PhysAddr, size: usize) -> Result<(), AttestationResult> { + if fits_in_one_page(start_gpa, size) { + Ok(()) + } else { + Err(AttestationResult::from_code(SVSM_ERR_INVALID_PARAMETER)) + } +} + +fn verify_aligned(gpa: PhysAddr, align: u64) -> Result<(), AttestationResult> { + if gpa.is_aligned(align) { + Ok(()) + } else { + Err(AttestationResult::from_code(SVSM_ERR_INVALID_PARAMETER)) + } +} + +fn verify_page_aligned(gpa: PhysAddr) -> Result<(), AttestationResult> { + if gpa.is_aligned(PAGE_SIZE) { + Ok(()) + } else { + Err(AttestationResult::from_code(SVSM_ERR_INVALID_ADDRESS)) + } +} + +/// Map `dest_len` bytes starting at `dest_gpa` and copy the entire +/// content of `src` into that memory area. `dest_len` must be equal or +/// greater than `src.len()` +fn copy_to_caller(src: &[u8], dest_gpa: PhysAddr, dest_len: u32) -> Result<(), AttestationResult> { + assert!(dest_len as usize >= src.len()); + let mut map: MapGuard = MapGuard::new_private(dest_gpa, dest_len as u64) + .map_err(|_| AttestationResult::from_code(SVSM_ERR_INVALID_PARAMETER))?; + let dest_buf: &mut [u8] = map.as_bytes_mut(); + dest_buf[..src.len()].copy_from_slice(src); + Ok(()) +} + +fn get_snp_attestation_report( + report_data: &[u8; 64], +) -> Result<(Vec, Vec), AttestationResult> { + // Dummy function until we have PSP support + let mut attestation_report: Vec = [0xddu8; 2000].to_vec(); + // Simulate possible PSP error condition + if report_data[0] == 0xee { + return Err(AttestationResult::from_code(SVSM_FAIL_SNP_ATTESTATION)); + } + attestation_report.extend_from_slice(report_data); + let certs: Vec = [0xccu8; 6000].to_vec(); + Ok((attestation_report, certs)) +} + +fn build_services_manifest(service_guid: Option, manifest_version: Option) -> Vec { + SERVICES.lock().get_manifest(service_guid, manifest_version) +} + +fn todo_sha_512(input: &[u8]) -> [u8; 64] { + // Hook this to OpenSSL sha512 digest + [input[0]; 64] +} + +fn attest_services( + request: &AttestServicesRequest, + service_guid: Option, + manifest_version: Option, +) -> Result { + let report_gpa: PhysAddr = PhysAddr::new(request.report_gpa); + verify_page_aligned(report_gpa)?; + + let services_manifest_gpa: PhysAddr = PhysAddr::new(request.services_manifest_gpa); + verify_page_aligned(services_manifest_gpa)?; + + let certs_gpa: PhysAddr = PhysAddr::new(request.certs_gpa); + if request.certs_size > 0 { + verify_page_aligned(certs_gpa)?; + } + + let services_manifest: Vec = build_services_manifest(service_guid, manifest_version); + let services_manifest_size: u64 = services_manifest.len() as u64; + if services_manifest.len() > request.services_manifest_size as usize { + return Err(AttestationResult { + code: SVSM_ERR_INVALID_PARAMETER, + services_manifest_size, + certs_size: 0, + report_size: 0, + }); + } + + let mut buffer_to_hash: Vec = Vec::new(); + add_nonce_to_buffer(request.nonce_gpa, request.nonce_size, &mut buffer_to_hash)?; + buffer_to_hash.extend_from_slice(&services_manifest); + let report_data: [u8; 64] = todo_sha_512(&buffer_to_hash); + + let (attestation_report, certs) = get_snp_attestation_report(&report_data)?; + let report_size: u64 = attestation_report.len() as u64; + let certs_size: u64 = certs.len() as u64; + + if attestation_report.len() > request.report_size as usize { + return Err(AttestationResult { + code: SVSM_ERR_INVALID_PARAMETER, + services_manifest_size, + certs_size, + report_size, + }); + } + + copy_to_caller(&attestation_report, report_gpa, request.report_size)?; + copy_to_caller( + &services_manifest, + services_manifest_gpa, + request.services_manifest_size, + )?; + + if request.certs_size > 0 { + if certs_size > request.certs_size as u64 { + return Err(AttestationResult { + code: SVSM_ERR_INVALID_PARAMETER, + services_manifest_size, + certs_size, + report_size, + }); + } + + copy_to_caller(&certs, certs_gpa, request.certs_size)?; + } + + Ok(AttestationResult { + code: SVSM_SUCCESS, + services_manifest_size, + certs_size, + report_size, + }) +} + +fn map_request_area( + request_gpa: PhysAddr, + request_size: usize, +) -> Result { + verify_aligned(request_gpa, 8)?; + verify_fits_in_one_page(request_gpa, request_size)?; + + let map_res: Result>; + if is_in_calling_area(request_gpa) { + map_res = MapGuard::new_private_persistent(request_gpa, request_size as u64); + } else { + map_res = MapGuard::new_private(request_gpa, request_size as u64); + } + let request_map: MapGuard = + map_res.map_err(|_| AttestationResult::from_code(SVSM_ERR_INVALID_PARAMETER))?; + Ok(request_map) +} + +fn handle_attest_services_request_inner( + request_gpa: PhysAddr, +) -> Result { + let request_map: MapGuard = map_request_area(request_gpa, size_of::())?; + let request: &AttestServicesRequest = request_map.as_object(); + attest_services(request, None, None) +} + +unsafe fn handle_attest_services_request(vmsa: *mut Vmsa) { + let request_gpa: PhysAddr = PhysAddr::new((*vmsa).rcx()); + let r: AttestationResult = match handle_attest_services_request_inner(request_gpa) { + Ok(r) => r, + Err(r) => r, + }; + (*vmsa).set_rax(r.code); + (*vmsa).set_rcx(r.services_manifest_size); + (*vmsa).set_rdx(r.certs_size); + (*vmsa).set_r8(r.report_size); +} + +fn handle_attest_single_services_request_inner( + request_gpa: PhysAddr, +) -> Result { + let request_map: MapGuard = + map_request_area(request_gpa, size_of::())?; + let request: &AttestSingleServiceRequest = request_map.as_object(); + let base_request: &AttestServicesRequest = &request.base; + let service_guid: Uuid = Uuid::from_bytes_le(request.service_guid); + attest_services( + base_request, + Some(service_guid), + Some(request.manifest_version), + ) +} + +unsafe fn handle_attest_single_service_request(vmsa: *mut Vmsa) { + let request_gpa: PhysAddr = PhysAddr::new((*vmsa).rcx()); + let r: AttestationResult = match handle_attest_single_services_request_inner(request_gpa) { + Ok(r) => r, + Err(r) => r, + }; + (*vmsa).set_rax(r.code); + (*vmsa).set_rcx(r.services_manifest_size); + (*vmsa).set_rdx(r.certs_size); + (*vmsa).set_r8(r.report_size); +} + +pub unsafe fn attestation_handle_request(callid: u32, vmsa: *mut Vmsa) { + match callid { + SVSM_ATTEST_SERVICES => handle_attest_services_request(vmsa), + SVSM_ATTEST_SINGLE_SERVICE => handle_attest_single_service_request(vmsa), + + _ => (*vmsa).set_rax(SVSM_ERR_UNSUPPORTED_CALLID), + }; +} diff --git a/src/protocols/core.rs b/src/protocols/core.rs index d43f3c4..4f2de97 100644 --- a/src/protocols/core.rs +++ b/src/protocols/core.rs @@ -42,11 +42,14 @@ impl VersionInfo { funcs!(max, u32); } -static PROTOCOL_INFO: [VersionInfo; 1] = [VersionInfo { min: 1, max: 1 }]; +static PROTOCOL_INFO: [VersionInfo; 2] = [ + VersionInfo { min: 1, max: 1 }, + VersionInfo { min: 1, max: 1 }, +]; -#[allow(dead_code)] -enum ProtocolId { +pub enum ProtocolId { ProtocolId0, + ProtocolId1, MaxProtocolId, } @@ -413,13 +416,6 @@ unsafe fn handle_pvalidate(vmsa: *mut Vmsa, entry: *const PvalidateEntry) -> (bo (true, flush) } -fn is_in_calling_area(gpa: PhysAddr) -> bool { - let gfn: PhysFrame = PhysFrame::containing_address(gpa); - let caa_gpa: PhysAddr = unsafe { PERCPU.caa(VMPL::Vmpl1) }; - let caa_gfn: PhysFrame = PhysFrame::containing_address(caa_gpa); - gfn == caa_gfn -} - unsafe fn handle_pvalidate_request(vmsa: *mut Vmsa) { (*vmsa).set_rax(SVSM_ERR_INVALID_PARAMETER); diff --git a/src/protocols/mod.rs b/src/protocols/mod.rs index 43a2a88..d109ae6 100644 --- a/src/protocols/mod.rs +++ b/src/protocols/mod.rs @@ -5,12 +5,20 @@ * Authors: Dov Murik */ +/// Implementation of the attestation protocol (1) +pub mod attestation; /// Implementation of the core protocol (0) pub mod core; /// Error codes returned from the SVSM calls pub mod error_codes; +/// Services manifest table +pub mod services_manifest; +pub use crate::protocols::attestation::*; pub use crate::protocols::core::*; +pub use crate::protocols::services_manifest::*; /// 0 -pub const SVSM_CORE_PROTOCOL: u32 = 0; +pub const SVSM_CORE_PROTOCOL: u32 = ProtocolId::ProtocolId0 as u32; +/// 1 +pub const SVSM_ATTESTATION_PROTOCOL: u32 = ProtocolId::ProtocolId1 as u32; diff --git a/src/protocols/services_manifest.rs b/src/protocols/services_manifest.rs new file mode 100644 index 0000000..d27080b --- /dev/null +++ b/src/protocols/services_manifest.rs @@ -0,0 +1,158 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (C) 2023 IBM Corporation + * + * Authors: Dov Murik + */ + +use crate::locking::SpinLock; + +use alloc::vec::Vec; +use core::mem::size_of; +use core::slice; +use lazy_static::lazy_static; +use uuid::{uuid, Uuid}; + +const SERVICES_MANIFEST_HEADER_UUID: Uuid = uuid!("63849ebb-3d92-4670-a1ff-58f9c94b87bb"); + +/// SVSM Spec Chapter 7 (Attestation): Table 12: Services Manifest +#[allow(dead_code)] +#[repr(C, packed)] +struct ManifestHeader { + guid: uuid::Bytes, + size: u32, + num_services: u32, +} + +impl ManifestHeader { + pub fn as_bytes(&self) -> &[u8] { + unsafe { slice::from_raw_parts(self as *const Self as *const u8, size_of::()) } + } +} + +/// SVSM Spec Chapter 7 (Attestation): Table 12: Services Manifest +#[allow(dead_code)] +#[repr(C, packed)] +struct ServiceEntry { + guid: uuid::Bytes, + data_offset: u32, + data_size: u32, +} + +impl ServiceEntry { + pub fn as_bytes(&self) -> &[u8] { + unsafe { slice::from_raw_parts(self as *const Self as *const u8, size_of::()) } + } +} + +struct Service { + guid: Uuid, + data: Vec, +} + +pub struct Services { + list: Vec, +} + +impl Services { + pub const fn new() -> Self { + Services { list: Vec::new() } + } + + pub fn add_service(&mut self, guid: Uuid, data: &[u8]) { + self.list.push(Service { + guid, + data: data.to_vec(), + }); + } + + /// Serialize the services manifest. Set `single_service_guid` to `None` to + /// include all services in the manifest, or to `Some(guid)` to include only + /// a single service. + pub fn get_manifest( + &self, + single_service_guid: Option, + _manifest_version: Option, + ) -> Vec { + let mut service_entries: Vec = Vec::new(); + let data_start_offset: usize = size_of::() as usize + + (size_of::() as usize * self.list.len()); + let mut data: Vec = Vec::new(); + for service in &self.list { + if let Some(filter_guid) = single_service_guid { + if service.guid != filter_guid { + continue; + } + } + let entry: ServiceEntry = ServiceEntry { + guid: service.guid.to_bytes_le(), + data_offset: (data_start_offset + data.len()) as u32, + data_size: service.data.len() as u32, + }; + service_entries.extend_from_slice(entry.as_bytes()); + data.extend_from_slice(&service.data); + } + let total_size: usize = + size_of::() as usize + service_entries.len() + data.len(); + let header: ManifestHeader = ManifestHeader { + guid: SERVICES_MANIFEST_HEADER_UUID.to_bytes_le(), + size: total_size as u32, + num_services: self.list.len() as u32, + }; + let mut res: Vec = Vec::with_capacity(total_size); + res.extend_from_slice(header.as_bytes()); + res.extend_from_slice(&service_entries); + res.extend_from_slice(&data); + res + } +} + +lazy_static! { + /// Global registry of services + pub static ref SERVICES: SpinLock = SpinLock::new(Services::new()); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + pub fn test_serialize_empty_manifest() { + let s: Services = Services::new(); + let b: Vec = s.get_manifest(None, None); + assert_eq!(b.len(), 24); + } + + #[test] + pub fn test_serialize_manifest_with_services_and_data() { + let mut s: Services = Services::new(); + s.add_service( + uuid!("11112222-1234-5678-9abc-ddddeeeeffff"), + b"TheServiceData", + ); + s.add_service( + uuid!("88889999-8888-9999-8888-999988889999"), + b"OtherServiceData", + ); + let b: Vec = s.get_manifest(None, None); + assert_eq!(b.len(), 24 + 24 + 24 + 14 + 16); + assert_eq!(&b[72..86], b"TheServiceData"); + assert_eq!(&b[86..], b"OtherServiceData"); + } + + #[test] + pub fn test_serialize_single_service_manifest() { + let mut s: Services = Services::new(); + s.add_service( + uuid!("11112222-1234-5678-9abc-ddddeeeeffff"), + b"TheServiceData", + ); + s.add_service( + uuid!("88889999-8888-9999-8888-999988889999"), + b"OtherServiceData", + ); + let b: Vec = s.get_manifest(Some(uuid!("11112222-1234-5678-9abc-ddddeeeeffff")), None); + assert_eq!(b.len(), 24 + 24 + 14); + assert_eq!(&b[48..], b"TheServiceData"); + } +} diff --git a/src/svsm_request.rs b/src/svsm_request.rs index 9525ef9..05e02db 100644 --- a/src/svsm_request.rs +++ b/src/svsm_request.rs @@ -28,6 +28,7 @@ unsafe fn handle_request(vmsa: *mut Vmsa) { match protocol { SVSM_CORE_PROTOCOL => core_handle_request(callid, vmsa), + SVSM_ATTESTATION_PROTOCOL => attestation_handle_request(callid, vmsa), _ => (*vmsa).set_rax(SVSM_ERR_UNSUPPORTED_PROTOCOL), } }