diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index 2f7137d55..c127a1549 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -563,6 +563,8 @@ pub struct KvmVm { msrs: Vec, #[cfg(all(feature = "sev_snp", target_arch = "x86_64"))] sev_fd: Option, + #[cfg(all(feature = "sev_snp", target_arch = "x86_64"))] + snp_guest_policy: std::sync::OnceLock, dirty_log_slots: RwLock>, guest_memfds: Option>>, } @@ -689,7 +691,11 @@ impl vm::Vm for KvmVm { .as_ref() .unwrap() .launch_start(&self.fd, guest_policy) - .map_err(|e| vm::HypervisorVmError::InitializeSevSnp(e.into())) + .map_err(|e| vm::HypervisorVmError::InitializeSevSnp(e.into()))?; + self.snp_guest_policy + .set(guest_policy.into_bits()) + .expect("sev_snp_init called more than once"); + Ok(()) } #[cfg(all(feature = "sev_snp", target_arch = "x86_64"))] @@ -736,15 +742,22 @@ impl vm::Vm for KvmVm { snp_id_block: igvm_defs::IGVM_VHS_SNP_ID_BLOCK, host_data: [u8; 32], id_block_enabled: u8, + auth_key_enabled: u8, ) -> vm::Result<()> { + let guest_policy = *self + .snp_guest_policy + .get() + .expect("complete_isolated_import called before sev_snp_init"); self.sev_fd .as_ref() .unwrap() .launch_finish( &self.fd, + &snp_id_block, host_data, id_block_enabled, - snp_id_block.author_key_enabled, + auth_key_enabled, + guest_policy, ) .map_err(|e| vm::HypervisorVmError::CompleteIsolatedImport(e.into())) } @@ -1606,6 +1619,8 @@ impl hypervisor::Hypervisor for KvmHypervisor { dirty_log_slots: RwLock::new(HashMap::new()), #[cfg(feature = "sev_snp")] sev_fd, + #[cfg(feature = "sev_snp")] + snp_guest_policy: std::sync::OnceLock::new(), guest_memfds, })) } diff --git a/hypervisor/src/kvm/x86_64/sev.rs b/hypervisor/src/kvm/x86_64/sev.rs index 6249468fe..390a0ca84 100644 --- a/hypervisor/src/kvm/x86_64/sev.rs +++ b/hypervisor/src/kvm/x86_64/sev.rs @@ -8,11 +8,12 @@ use std::os::fd::{AsRawFd, OwnedFd}; use std::os::unix::fs::OpenOptionsExt; use std::path::Path; -use igvm_defs::SnpPolicy; +use igvm_defs::{IGVM_VHS_SNP_ID_BLOCK, SnpPolicy}; use kvm_bindings::kvm_sev_cmd; use kvm_ioctls::VmFd; use log::{debug, error, info}; use vmm_sys_util::errno; +use zerocopy::{FromZeros, Immutable, IntoBytes}; pub(crate) type Result = std::result::Result; @@ -96,6 +97,88 @@ pub(crate) struct KvmSevSnpLaunchFinish { pub pad1: [u64; 4], } +// See AMD Spec Section 8.18 — Structure of the ID Block +// https://docs.amd.com/v/u/en-US/56860_PUB_1.58_SEV_SNP +#[repr(C)] +#[derive(Debug, Copy, Clone, IntoBytes, Immutable)] +pub(crate) struct KvmSevSnpIdBlock { + pub ld: [u8; 48], + pub family_id: [u8; 16], + pub image_id: [u8; 16], + pub version: u32, + pub guest_svn: u32, + pub policy: u64, +} + +// See AMD Spec Section 8.18 — Layout of the ID Authentication Information Structure +// https://docs.amd.com/v/u/en-US/56860_PUB_1.58_SEV_SNP +#[repr(C)] +#[derive(Clone, FromZeros, IntoBytes, Immutable)] +pub(crate) struct KvmSevSnpIdAuth { + pub id_key_alg: u32, + pub auth_key_algo: u32, + pub reserved: [u8; 56], + pub id_block_sig: [u8; 512], + pub id_key: [u8; 1028], + pub reserved2: [u8; 60], + pub id_key_sig: [u8; 512], + pub author_key: [u8; 1028], + pub reserved3: [u8; 892], +} + +// Must be 1 +// AMD SEV-SNP Firmware ABI, Section 8.18 — Structure of the ID Block +// https://docs.amd.com/v/u/en-US/56860_PUB_1.58_SEV_SNP +const IGVM_SEV_ID_BLOCK_VERSION: u32 = 1; + +fn build_id_block(snp_id_block: &IGVM_VHS_SNP_ID_BLOCK, guest_policy: u64) -> KvmSevSnpIdBlock { + KvmSevSnpIdBlock { + ld: snp_id_block.ld, + family_id: snp_id_block.family_id, + image_id: snp_id_block.image_id, + version: IGVM_SEV_ID_BLOCK_VERSION, + guest_svn: snp_id_block.guest_svn, + policy: guest_policy, + } +} + +// SEV-SNP Firmware ABI Spec Chapter 10: Format for an ECDSA P-384 Public Key +// https://docs.amd.com/v/u/en-US/56860_PUB_1.58_SEV_SNP +fn serialize_public_key(curve: u32, qx: &[u8; 72], qy: &[u8; 72]) -> [u8; 1028] { + let mut key = [0u8; 0x404]; + key[..0x004].copy_from_slice(&curve.to_le_bytes()); + key[0x004..0x04C].copy_from_slice(qx); + key[0x04C..0x094].copy_from_slice(qy); + key +} + +fn build_id_auth(snp_id_block: &IGVM_VHS_SNP_ID_BLOCK) -> KvmSevSnpIdAuth { + let mut id_auth = KvmSevSnpIdAuth::new_zeroed(); + + id_auth.id_key_alg = snp_id_block.id_key_algorithm; + id_auth.auth_key_algo = snp_id_block.author_key_algorithm; + + let sig = snp_id_block.id_key_signature.as_bytes(); + id_auth.id_block_sig[..sig.len()].copy_from_slice(sig); + + id_auth.id_key = serialize_public_key( + snp_id_block.id_public_key.curve, + &snp_id_block.id_public_key.qx, + &snp_id_block.id_public_key.qy, + ); + + let sig = snp_id_block.author_key_signature.as_bytes(); + id_auth.id_key_sig[..sig.len()].copy_from_slice(sig); + + id_auth.author_key = serialize_public_key( + snp_id_block.author_public_key.curve, + &snp_id_block.author_public_key.qx, + &snp_id_block.author_public_key.qy, + ); + + id_auth +} + impl SevFd { pub(crate) fn new(sev_path: impl AsRef) -> Result { let file = OpenOptions::new() @@ -132,9 +215,6 @@ impl SevFd { } pub(crate) fn launch_start(&self, vm: &VmFd, guest_policy: SnpPolicy) -> Result<()> { - // See AMD Spec Section 4.3 - Guest Policy - // Bit 17 is reserved and has to be one. - // https://docs.amd.com/v/u/en-US/56860_PUB_1.58_SEV_SNP let mut start: KvmSevSnpLaunchStart = KvmSevSnpLaunchStart { policy: guest_policy.into_bits(), ..Default::default() @@ -177,14 +257,21 @@ impl SevFd { pub(crate) fn launch_finish( &self, vm: &VmFd, + snp_id_block: &IGVM_VHS_SNP_ID_BLOCK, host_data: [u8; 32], id_block_en: u8, auth_key_en: u8, + guest_policy: u64, ) -> Result<()> { + let id_block = build_id_block(snp_id_block, guest_policy); + let id_auth = build_id_auth(snp_id_block); + let mut finish = KvmSevSnpLaunchFinish { - host_data, + id_block_uaddr: id_block.as_bytes().as_ptr() as u64, + id_auth_uaddr: id_auth.as_bytes().as_ptr() as u64, id_block_en, auth_key_en, + host_data, ..Default::default() }; let mut sev_cmd = kvm_sev_cmd { @@ -194,7 +281,108 @@ impl SevFd { ..Default::default() }; let flags = finish.flags; - debug!("Calling KVM_SEV_SNP_LAUNCH_FINISH, flags: {flags}"); + debug!( + "KVM_SEV_SNP_LAUNCH_FINISH: id_block_en={id_block_en}, auth_key_en={auth_key_en}, policy={guest_policy:#x}, flags={flags}" + ); sev_op(vm, &mut sev_cmd, "KVM_SEV_SNP_LAUNCH_FINISH") } } + +#[cfg(test)] +mod tests { + use core::mem::size_of; + + use super::*; + + fn make_test_igvm_id_block() -> IGVM_VHS_SNP_ID_BLOCK { + let mut block = IGVM_VHS_SNP_ID_BLOCK::new_zeroed(); + block.ld[0] = 0xAA; + block.ld[47] = 0xBB; + block.family_id[0] = 0x01; + block.image_id[0] = 0x02; + block.version = 42; + block.guest_svn = 7; + block.id_key_algorithm = 1; + block.author_key_algorithm = 1; + block.id_key_signature.r_comp[0] = 0x10; + block.id_key_signature.s_comp[0] = 0x20; + block.id_public_key.curve = 2; + block.id_public_key.qx[0] = 0x30; + block.id_public_key.qy[0] = 0x40; + block.author_key_signature.r_comp[0] = 0x50; + block.author_key_signature.s_comp[0] = 0x60; + block.author_public_key.curve = 2; + block.author_public_key.qx[0] = 0x70; + block.author_public_key.qy[0] = 0x80; + block + } + + #[test] + fn id_block_struct_sizes() { + assert_eq!(size_of::(), 96); + assert_eq!(size_of::(), 4096); + } + + #[test] + fn build_id_block_maps_fields_correctly() { + let igvm = make_test_igvm_id_block(); + let policy = 0x30000u64; + let id_block = build_id_block(&igvm, policy); + + assert_eq!(id_block.ld, igvm.ld); + assert_eq!(id_block.family_id, igvm.family_id); + assert_eq!(id_block.image_id, igvm.image_id); + assert_eq!(id_block.version, IGVM_SEV_ID_BLOCK_VERSION); + assert_eq!(id_block.guest_svn, igvm.guest_svn); + assert_eq!(id_block.policy, policy); + } + + #[test] + fn build_id_block_policy_at_offset_88() { + let igvm = IGVM_VHS_SNP_ID_BLOCK::new_zeroed(); + let policy = 0xDEAD_BEEF_CAFE_BABEu64; + let id_block = build_id_block(&igvm, policy); + let bytes = id_block.as_bytes(); + assert_eq!(&bytes[88..96], &policy.to_le_bytes()); + } + + #[test] + fn build_id_auth_maps_signatures_correctly() { + let igvm = make_test_igvm_id_block(); + let id_auth = build_id_auth(&igvm); + + assert_eq!(id_auth.id_key_alg, igvm.id_key_algorithm); + assert_eq!(id_auth.auth_key_algo, igvm.author_key_algorithm); + + assert_eq!(id_auth.id_block_sig[0], 0x10); + assert_eq!(id_auth.id_block_sig[72], 0x20); + assert!(id_auth.id_block_sig[144..].iter().all(|&b| b == 0)); + + assert_eq!(id_auth.id_key_sig[0], 0x50); + assert_eq!(id_auth.id_key_sig[72], 0x60); + assert!(id_auth.id_key_sig[144..].iter().all(|&b| b == 0)); + } + + #[test] + fn build_id_auth_serializes_public_keys() { + let igvm = make_test_igvm_id_block(); + let id_auth = build_id_auth(&igvm); + + assert_eq!(&id_auth.id_key[..4], &2u32.to_le_bytes()); + assert_eq!(id_auth.id_key[4], 0x30); + assert_eq!(id_auth.id_key[76], 0x40); + assert!(id_auth.id_key[148..].iter().all(|&b| b == 0)); + + assert_eq!(&id_auth.author_key[..4], &2u32.to_le_bytes()); + assert_eq!(id_auth.author_key[4], 0x70); + assert_eq!(id_auth.author_key[76], 0x80); + assert!(id_auth.author_key[148..].iter().all(|&b| b == 0)); + } + + #[test] + fn build_id_auth_zeroed_input() { + let igvm = IGVM_VHS_SNP_ID_BLOCK::new_zeroed(); + let id_auth = build_id_auth(&igvm); + assert!(id_auth.as_bytes().iter().all(|&b| b == 0)); + } +} diff --git a/hypervisor/src/mshv/mod.rs b/hypervisor/src/mshv/mod.rs index 502c36f10..962045456 100644 --- a/hypervisor/src/mshv/mod.rs +++ b/hypervisor/src/mshv/mod.rs @@ -2318,6 +2318,7 @@ impl vm::Vm for MshvVm { snp_id_block: IGVM_VHS_SNP_ID_BLOCK, host_data: [u8; 32], id_block_enabled: u8, + auth_key_enabled: u8, ) -> vm::Result<()> { let mut auth_info = hv_snp_id_auth_info { id_key_algorithm: snp_id_block.id_key_algorithm, @@ -2351,7 +2352,7 @@ impl vm::Vm for MshvVm { id_auth_info: auth_info, host_data, id_block_enabled, - author_key_enabled: 0, + author_key_enabled: auth_key_enabled, }, }, }; diff --git a/hypervisor/src/vm.rs b/hypervisor/src/vm.rs index 6d3a4a4ae..03e0dd691 100644 --- a/hypervisor/src/vm.rs +++ b/hypervisor/src/vm.rs @@ -440,6 +440,7 @@ pub trait Vm: Send + Sync + Any { _snp_id_block: IGVM_VHS_SNP_ID_BLOCK, _host_data: [u8; 32], _id_block_enabled: u8, + _auth_key_enabled: u8, ) -> Result<()> { unimplemented!() } diff --git a/vmm/src/igvm/igvm_loader.rs b/vmm/src/igvm/igvm_loader.rs index f24fe9c28..4113436a8 100644 --- a/vmm/src/igvm/igvm_loader.rs +++ b/vmm/src/igvm/igvm_loader.rs @@ -87,6 +87,8 @@ pub enum Error { Igvm(#[source] std::io::Error), #[error("invalid igvm file")] InvalidIgvmFile(#[source] igvm::Error), + #[error("multiple SNP ID blocks in IGVM file")] + DuplicateSnpIdBlock, #[error("invalid guest memory map")] InvalidGuestMemmap(#[source] arch::Error), #[error("loader error")] @@ -678,6 +680,9 @@ pub fn load_igvm( author_key_signature, author_public_key, } => { + if loaded_info.has_snp_id_block { + return Err(Error::DuplicateSnpIdBlock); + } loaded_info.snp_id_block.compatibility_mask = *compatibility_mask; loaded_info.snp_id_block.author_key_enabled = *author_key_enabled; loaded_info.snp_id_block.reserved = *reserved; @@ -692,6 +697,7 @@ pub fn load_igvm( loaded_info.snp_id_block.id_public_key = **id_public_key; loaded_info.snp_id_block.author_key_signature = **author_key_signature; loaded_info.snp_id_block.author_public_key = **author_public_key; + loaded_info.has_snp_id_block = true; } IgvmDirectiveHeader::X64VbsVpContext { vtl: _, @@ -929,7 +935,12 @@ pub fn load_igvm( let id_block_enabled = if hypervisor_type == HypervisorType::Mshv { 1 } else { + u8::from(loaded_info.has_snp_id_block) + }; + let auth_key_enabled = if hypervisor_type == HypervisorType::Mshv { 0 + } else { + loaded_info.snp_id_block.author_key_enabled }; now = Instant::now(); @@ -942,6 +953,7 @@ pub fn load_igvm( loaded_info.snp_id_block, host_data_contents, id_block_enabled, + auth_key_enabled, ) .map_err(Error::CompleteIsolatedImport)?; diff --git a/vmm/src/igvm/mod.rs b/vmm/src/igvm/mod.rs index ded102bd3..5065adf4e 100644 --- a/vmm/src/igvm/mod.rs +++ b/vmm/src/igvm/mod.rs @@ -45,6 +45,7 @@ pub struct IgvmLoadedInfo { pub gpas: Vec, pub vmsa_gpa: u64, pub snp_id_block: IGVM_VHS_SNP_ID_BLOCK, + pub has_snp_id_block: bool, pub vmsa: SevVmsa, } @@ -54,6 +55,7 @@ impl Default for IgvmLoadedInfo { gpas: Vec::new(), vmsa_gpa: 0, snp_id_block: IGVM_VHS_SNP_ID_BLOCK::new_zeroed(), + has_snp_id_block: false, vmsa: SevVmsa::new_zeroed(), } }