mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
hypervisor, vmm: Add KVM SEV_{INIT2, SNP_LAUNCH_START} support
Introduce the SevFd abstraction that wraps /dev/sev and implements the KVM_SEV_INIT2 and KVM_SEV_SNP_LAUNCH_START ioctls for SEV-SNP VM initialization on KVM. Key changes: - Add sev.rs with KvmSevInit and KvmSevSnpLaunchStart ioctl structs matching the kernel layout (linux/arch/x86/include/uapi/asm/kvm.h) - Implement KVM_SEV_INIT2 and KVM_SEV_SNP_LAUNCH_START ioctls - Set KVM_MEMORY_ATTRIBUTE_PRIVATE on newly created memory regions when guest_memfd is supported - Widen SevSnpPageAccessProxy cfg gates from mshv-only to all sev_snp-enabled builds - Make sev_snp_init a required trait method (remove default impl) - Include KVM_SEV_SNP_LAUNCH_START in the seccomp allowlist - Parse VMSA SEV features from IGVM and include them in the KVM_SEV_INIT2 ioctl Co-authored-by: Keith Adler <kadler@cloudflare.com> Signed-off-by: Keith Adler <kadler@cloudflare.com> Co-authored-by: Alex Orozco <aorozco@google.com> Signed-off-by: Alex Orozco <aorozco@google.com> Co-authored-by: Rob Bradford <rbradford@meta.com> Signed-off-by: Rob Bradford <rbradford@meta.com> Signed-off-by: Ruben Hakobyan <hruben@meta.com>
This commit is contained in:
committed by
Rob Bradford
parent
425609a8b5
commit
2e004521e0
@@ -587,10 +587,11 @@ pub trait Vcpu: Send + Sync {
|
||||
) -> Result<[u32; 4]> {
|
||||
unimplemented!()
|
||||
}
|
||||
#[cfg(feature = "mshv")]
|
||||
fn set_sev_control_register(&self, _reg: u64) -> Result<()> {
|
||||
#[cfg(feature = "sev_snp")]
|
||||
fn set_sev_control_register(&self, _vmsa_pfn: u64) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
///
|
||||
/// Sets the value of GIC redistributor address
|
||||
///
|
||||
|
||||
@@ -96,6 +96,11 @@ pub enum HypervisorError {
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
#[error("Failed to enable AMX tile state components")]
|
||||
CouldNotEnableAmxStateComponents(#[source] crate::arch::x86::AmxGuestSupportError),
|
||||
///
|
||||
/// Failed to retrieve SEV-SNP capabilities
|
||||
///
|
||||
#[error("Failed to retrieve SEV-SNP capabilities:{0}")]
|
||||
SevSnpCapabilities(#[source] anyhow::Error),
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
@@ -140,6 +140,9 @@ use crate::kvm::x86_64::XsaveStateError;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
ioctl_io_nr!(KVM_NMI, kvm_bindings::KVMIO, 0x9a);
|
||||
|
||||
#[cfg(feature = "sev_snp")]
|
||||
use kvm_bindings::{KVM_MEMORY_ATTRIBUTE_PRIVATE, KVM_X86_SNP_VM, kvm_memory_attributes};
|
||||
|
||||
#[cfg(feature = "tdx")]
|
||||
const KVM_EXIT_TDX: u32 = 50;
|
||||
#[cfg(feature = "tdx")]
|
||||
@@ -498,9 +501,11 @@ struct KvmDirtyLogSlot {
|
||||
|
||||
/// Wrapper over KVM VM ioctls.
|
||||
pub struct KvmVm {
|
||||
fd: VmFd,
|
||||
fd: Arc<VmFd>,
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
msrs: Vec<MsrEntry>,
|
||||
#[cfg(all(feature = "sev_snp", target_arch = "x86_64"))]
|
||||
sev_fd: Option<x86_64::sev::SevFd>,
|
||||
dirty_log_slots: RwLock<HashMap<u32, KvmDirtyLogSlot>>,
|
||||
guest_memfds: Option<RwLock<HashMap<u32, OwnedFd>>>,
|
||||
}
|
||||
@@ -621,6 +626,15 @@ impl KvmVm {
|
||||
/// let vm = hypervisor.create_vm(HypervisorVmConfig::default()).expect("new VM fd creation failed");
|
||||
/// ```
|
||||
impl vm::Vm for KvmVm {
|
||||
#[cfg(all(feature = "sev_snp", target_arch = "x86_64"))]
|
||||
fn sev_snp_init(&self, guest_policy: igvm_defs::SnpPolicy) -> vm::Result<()> {
|
||||
self.sev_fd
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.launch_start(&self.fd, guest_policy)
|
||||
.map_err(|e| vm::HypervisorVmError::InitializeSevSnp(e.into()))
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
///
|
||||
/// Sets the address of the one-page region in the VM's address space.
|
||||
@@ -938,6 +952,18 @@ impl vm::Vm for KvmVm {
|
||||
self.set_user_memory_region(region)
|
||||
.map_err(|e| vm::HypervisorVmError::CreateUserMemory(e.into()))?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "sev_snp")]
|
||||
if self.guest_memfds.is_some() {
|
||||
self.fd
|
||||
.set_memory_attributes(kvm_memory_attributes {
|
||||
address: region.guest_phys_addr,
|
||||
size: region.memory_size,
|
||||
attributes: KVM_MEMORY_ATTRIBUTE_PRIVATE as u64,
|
||||
flags: 0,
|
||||
})
|
||||
.map_err(|e| vm::HypervisorVmError::CreateUserMemory(e.into()))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1383,15 +1409,17 @@ impl hypervisor::Hypervisor for KvmHypervisor {
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(feature = "tdx")] {
|
||||
if _config.tdx_enabled {
|
||||
vm_type = KVM_X86_SW_PROTECTED_VM.into();
|
||||
} else {
|
||||
vm_type = KVM_X86_DEFAULT_VM.into();
|
||||
}
|
||||
} else {
|
||||
vm_type = KVM_X86_DEFAULT_VM.into();
|
||||
{
|
||||
vm_type = KVM_X86_DEFAULT_VM.into();
|
||||
|
||||
#[cfg(feature = "sev_snp")]
|
||||
if _config.sev_snp_enabled {
|
||||
vm_type = KVM_X86_SNP_VM.into();
|
||||
}
|
||||
|
||||
#[cfg(feature = "tdx")]
|
||||
if _config.tdx_enabled {
|
||||
vm_type = KVM_X86_SW_PROTECTED_VM.into();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1433,10 +1461,35 @@ impl hypervisor::Hypervisor for KvmHypervisor {
|
||||
guest_memfds = Some(RwLock::new(HashMap::new()));
|
||||
}
|
||||
|
||||
#[cfg(feature = "sev_snp")]
|
||||
let sev_fd = {
|
||||
let sev_snp_enabled = vm_type == KVM_X86_SNP_VM as u64;
|
||||
if sev_snp_enabled {
|
||||
let mask = self.kvm.check_extension_int(crate::kvm::Cap::ExitHypercall);
|
||||
let cap = kvm_bindings::kvm_enable_cap {
|
||||
cap: kvm_bindings::KVM_CAP_EXIT_HYPERCALL,
|
||||
args: [mask as _, 0, 0, 0],
|
||||
..Default::default()
|
||||
};
|
||||
fd.enable_cap(&cap)
|
||||
.map_err(|e| hypervisor::HypervisorError::VmCreate(e.into()))?;
|
||||
let sev_dev = x86_64::sev::SevFd::new("/dev/sev")
|
||||
.map_err(|e| hypervisor::HypervisorError::SevSnpCapabilities(e.into()))?;
|
||||
sev_dev
|
||||
.init2(&fd, _config.vmsa_features)
|
||||
.map_err(|e| hypervisor::HypervisorError::VmCreate(e.into()))?;
|
||||
Some(sev_dev)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Arc::new(KvmVm {
|
||||
fd,
|
||||
fd: Arc::new(fd),
|
||||
msrs,
|
||||
dirty_log_slots: RwLock::new(HashMap::new()),
|
||||
#[cfg(feature = "sev_snp")]
|
||||
sev_fd,
|
||||
guest_memfds,
|
||||
}))
|
||||
}
|
||||
@@ -1444,7 +1497,7 @@ impl hypervisor::Hypervisor for KvmHypervisor {
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
|
||||
{
|
||||
Ok(Arc::new(KvmVm {
|
||||
fd,
|
||||
fd: Arc::new(fd),
|
||||
dirty_log_slots: RwLock::new(HashMap::new()),
|
||||
guest_memfds: None,
|
||||
}))
|
||||
|
||||
@@ -31,6 +31,9 @@ use crate::arch::x86::{
|
||||
};
|
||||
use crate::kvm::{Cap, Kvm, KvmError, KvmResult};
|
||||
|
||||
#[cfg(feature = "sev_snp")]
|
||||
pub(crate) mod sev;
|
||||
|
||||
///
|
||||
/// Check KVM extension for Linux
|
||||
///
|
||||
|
||||
113
hypervisor/src/kvm/x86_64/sev.rs
Normal file
113
hypervisor/src/kvm/x86_64/sev.rs
Normal file
@@ -0,0 +1,113 @@
|
||||
// Copyright 2025 Google LLC.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
use std::fs::OpenOptions;
|
||||
use std::os::fd::{AsRawFd, OwnedFd};
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
use std::path::Path;
|
||||
|
||||
use igvm_defs::SnpPolicy;
|
||||
use kvm_bindings::kvm_sev_cmd;
|
||||
use kvm_ioctls::VmFd;
|
||||
use log::{error, info};
|
||||
use vmm_sys_util::errno;
|
||||
|
||||
pub(crate) type Result<T> = std::result::Result<T, errno::Error>;
|
||||
|
||||
// KVM SEV command IDs — linux/include/uapi/linux/kvm.h
|
||||
const KVM_SEV_INIT2: u32 = 22;
|
||||
const KVM_SEV_SNP_LAUNCH_START: u32 = 100;
|
||||
|
||||
// SNP in VMSA - linux/arch/x86/include/asm/svm.h
|
||||
const SVM_SEV_FEAT_SNP_ACTIVE: u64 = 1 << 0;
|
||||
|
||||
fn sev_op(vm: &VmFd, sev_cmd: &mut kvm_sev_cmd, name: &str) -> Result<()> {
|
||||
let ret = vm.encrypt_op_sev(sev_cmd);
|
||||
if ret.is_err() {
|
||||
error!("{name} op failed. error code: 0x{:x}", sev_cmd.error);
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SevFd {
|
||||
pub fd: OwnedFd,
|
||||
}
|
||||
|
||||
// These ioctl structs must match the kernel layout exactly.
|
||||
// Layouts from linux/arch/x86/include/uapi/asm/kvm.h
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Debug, Copy, Clone, Default)]
|
||||
pub(crate) struct KvmSevInit {
|
||||
pub vmsa_features: u64,
|
||||
pub flags: u32,
|
||||
pub ghcb_version: u16,
|
||||
pub pad1: u16,
|
||||
pub pad2: [u32; 8],
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Debug, Copy, Clone, Default)]
|
||||
pub(crate) struct KvmSevSnpLaunchStart {
|
||||
pub policy: u64,
|
||||
pub gosvw: [u8; 16],
|
||||
pub flags: u16,
|
||||
pub pad0: [u8; 6],
|
||||
pub pad1: [u64; 4],
|
||||
}
|
||||
|
||||
impl SevFd {
|
||||
pub(crate) fn new(sev_path: impl AsRef<Path>) -> Result<Self> {
|
||||
let file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.custom_flags(libc::O_CLOEXEC)
|
||||
.open(sev_path.as_ref())
|
||||
.map_err(|e| errno::Error::new(e.raw_os_error().unwrap_or(libc::EINVAL)))?;
|
||||
Ok(SevFd {
|
||||
fd: OwnedFd::from(file),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn init2(&self, vm: &VmFd, vmsa_features: u64) -> Result<()> {
|
||||
// Clear the SNP bit, KVM sets it directly
|
||||
let vmsa_features = vmsa_features & !SVM_SEV_FEAT_SNP_ACTIVE;
|
||||
|
||||
// TODO: Query KVM for supported VMSA features before calling init2
|
||||
if vmsa_features != 0 {
|
||||
info!("SEV-SNP: requesting vmsa_features: {vmsa_features:#x}");
|
||||
}
|
||||
|
||||
let mut init = KvmSevInit {
|
||||
vmsa_features,
|
||||
..Default::default()
|
||||
};
|
||||
let mut sev_cmd = kvm_sev_cmd {
|
||||
id: KVM_SEV_INIT2,
|
||||
data: &mut init as *mut KvmSevInit as _,
|
||||
sev_fd: self.fd.as_raw_fd() as _,
|
||||
..Default::default()
|
||||
};
|
||||
sev_op(vm, &mut sev_cmd, "KVM_SEV_INIT2")
|
||||
}
|
||||
|
||||
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()
|
||||
};
|
||||
let mut sev_cmd = kvm_sev_cmd {
|
||||
id: KVM_SEV_SNP_LAUNCH_START,
|
||||
data: &mut start as *mut KvmSevSnpLaunchStart as _,
|
||||
sev_fd: self.fd.as_raw_fd() as _,
|
||||
..Default::default()
|
||||
};
|
||||
sev_op(vm, &mut sev_cmd, "KVM_SEV_SNP_LAUNCH_START")
|
||||
}
|
||||
}
|
||||
@@ -190,6 +190,8 @@ pub struct HypervisorVmConfig {
|
||||
pub sev_snp_enabled: bool,
|
||||
#[cfg(feature = "sev_snp")]
|
||||
pub mem_size: u64,
|
||||
#[cfg(feature = "sev_snp")]
|
||||
pub vmsa_features: u64,
|
||||
pub nested: bool,
|
||||
pub smt_enabled: bool,
|
||||
}
|
||||
|
||||
@@ -394,9 +394,7 @@ pub trait Vm: Send + Sync + Any {
|
||||
fn get_dirty_log(&self, slot: u32, base_gpa: u64, memory_size: u64) -> Result<Vec<u64>>;
|
||||
#[cfg(feature = "sev_snp")]
|
||||
/// Initialize SEV-SNP on this VM
|
||||
fn sev_snp_init(&self, _guest_policy: SnpPolicy) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn sev_snp_init(&self, guest_policy: SnpPolicy) -> Result<()>;
|
||||
#[cfg(feature = "tdx")]
|
||||
/// Initialize TDX on this VM
|
||||
fn tdx_init(&self, _cpuid: &[CpuIdEntry], _max_vcpus: u32) -> Result<()> {
|
||||
|
||||
Reference in New Issue
Block a user