hypervisor, vmm: Build and pass SNP ID block to launch finish

Add KvmSevSnpIdBlock and KvmSevSnpIdAuth structs matching the AMD
SEV-SNP Firmware ABI Spec (Rev 1.58), and build them from the IGVM
SNP ID block directive during launch finish. This properly populates
id_block_uaddr/id_auth_uaddr in KVM_SEV_SNP_LAUNCH_FINISH and derives
auth_key_en from the assembled author key, matching QEMU's behavior.

Thread the guest policy from sev_snp_init to launch_finish via an
atomic on KvmVm so the ID block gets the correct policy value.

Also track has_snp_id_block in IgvmLoadedInfo to enable the ID block
based on whether the IGVM file actually contains one, rather than
hardcoding it for KVM.

Signed-off-by: Ruben Hakobyan <hruben@meta.com>
This commit is contained in:
Ruben Hakobyan
2026-04-30 06:57:40 -07:00
committed by Rob Bradford
parent bfab43e252
commit c97d635d40
6 changed files with 228 additions and 9 deletions

View File

@@ -563,6 +563,8 @@ pub struct KvmVm {
msrs: Vec<MsrEntry>,
#[cfg(all(feature = "sev_snp", target_arch = "x86_64"))]
sev_fd: Option<x86_64::sev::SevFd>,
#[cfg(all(feature = "sev_snp", target_arch = "x86_64"))]
snp_guest_policy: std::sync::OnceLock<u64>,
dirty_log_slots: RwLock<HashMap<u32, KvmDirtyLogSlot>>,
guest_memfds: Option<RwLock<HashMap<u32, OwnedFd>>>,
}
@@ -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,
}))
}

View File

@@ -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<T> = std::result::Result<T, errno::Error>;
@@ -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<Path>) -> Result<Self> {
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::<KvmSevSnpIdBlock>(), 96);
assert_eq!(size_of::<KvmSevSnpIdAuth>(), 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));
}
}

View File

@@ -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,
},
},
};

View File

@@ -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!()
}