diff --git a/hypervisor/src/haxm/vm.rs b/hypervisor/src/haxm/vm.rs index d82a6d002b..8876d18dd2 100644 --- a/hypervisor/src/haxm/vm.rs +++ b/hypervisor/src/haxm/vm.rs @@ -485,6 +485,15 @@ impl VmX86_64 for HaxmVm { fn set_identity_map_addr(&self, _addr: GuestAddress) -> Result<()> { Ok(()) } + + fn load_protected_vm_firmware( + &mut self, + _fw_addr: GuestAddress, + _fw_max_size: u64, + ) -> Result<()> { + // Haxm does not support protected VMs + Err(Error::new(libc::ENXIO)) + } } // TODO(b:241252288): Enable tests disabled with dummy feature flag - enable_haxm_tests. diff --git a/hypervisor/src/kvm/cap.rs b/hypervisor/src/kvm/cap.rs index f5c59d5813..568c681cda 100644 --- a/hypervisor/src/kvm/cap.rs +++ b/hypervisor/src/kvm/cap.rs @@ -122,6 +122,7 @@ pub enum KvmCap { ImmediateExit = KVM_CAP_IMMEDIATE_EXIT, ArmPmuV3 = KVM_CAP_ARM_PMU_V3, ArmProtectedVm = KVM_CAP_ARM_PROTECTED_VM, + X86ProtectedVm = KVM_CAP_X86_PROTECTED_VM, ArmMte = KVM_CAP_ARM_MTE, #[cfg(target_arch = "x86_64")] BusLockDetect = KVM_CAP_X86_BUS_LOCK_EXIT, diff --git a/hypervisor/src/kvm/x86_64.rs b/hypervisor/src/kvm/x86_64.rs index 7d0ce4ffe6..2529b3b124 100644 --- a/hypervisor/src/kvm/x86_64.rs +++ b/hypervisor/src/kvm/x86_64.rs @@ -23,7 +23,9 @@ use data_model::FlexibleArrayWrapper; use kvm_sys::*; use libc::E2BIG; use libc::EAGAIN; +use libc::EINVAL; use libc::EIO; +use libc::ENOMEM; use libc::ENXIO; use serde::Deserialize; use serde::Serialize; @@ -31,6 +33,7 @@ use vm_memory::GuestAddress; use super::Config; use super::Kvm; +use super::KvmCap; use super::KvmVcpu; use super::KvmVm; use crate::host_phys_addr_bits; @@ -445,6 +448,48 @@ impl KvmVm { Ok(()) } } + + /// Get pKVM hypervisor details, e.g. the firmware size. + /// + /// Returns `Err` if not running under pKVM. + /// + /// Uses `KVM_ENABLE_CAP` internally, but it is only a getter, there should be no side effects + /// in KVM. + fn get_protected_vm_info(&self) -> Result { + let mut info = KvmProtectedVmInfo { + firmware_size: 0, + reserved: [0; 7], + }; + // SAFETY: + // Safe because we allocated the struct and we know the kernel won't write beyond the end of + // the struct or keep a pointer to it. + unsafe { + self.enable_raw_capability( + KvmCap::X86ProtectedVm, + KVM_CAP_X86_PROTECTED_VM_FLAGS_INFO, + &[&mut info as *mut KvmProtectedVmInfo as u64, 0, 0, 0], + ) + }?; + Ok(info) + } + + fn set_protected_vm_firmware_gpa(&self, fw_addr: GuestAddress) -> Result<()> { + // SAFETY: + // Safe because none of the args are pointers. + unsafe { + self.enable_raw_capability( + KvmCap::X86ProtectedVm, + KVM_CAP_X86_PROTECTED_VM_FLAGS_SET_FW_GPA, + &[fw_addr.0, 0, 0, 0], + ) + } + } +} + +#[repr(C)] +struct KvmProtectedVmInfo { + firmware_size: u64, + reserved: [u64; 7], } impl VmX86_64 for KvmVm { @@ -452,6 +497,22 @@ impl VmX86_64 for KvmVm { &self.kvm } + fn load_protected_vm_firmware( + &mut self, + fw_addr: GuestAddress, + fw_max_size: u64, + ) -> Result<()> { + let info = self.get_protected_vm_info()?; + if info.firmware_size == 0 { + Err(Error::new(EINVAL)) + } else { + if info.firmware_size > fw_max_size { + return Err(Error::new(ENOMEM)); + } + self.set_protected_vm_firmware_gpa(fw_addr) + } + } + fn create_vcpu(&self, id: usize) -> Result> { // create_vcpu is declared separately in VmAArch64 and VmX86, so it can return VcpuAArch64 // or VcpuX86. But both use the same implementation in KvmVm::create_vcpu. diff --git a/hypervisor/src/whpx/vm.rs b/hypervisor/src/whpx/vm.rs index 62e477c09c..2aae1c4552 100644 --- a/hypervisor/src/whpx/vm.rs +++ b/hypervisor/src/whpx/vm.rs @@ -776,6 +776,15 @@ impl VmX86_64 for WhpxVm { fn set_identity_map_addr(&self, _addr: GuestAddress) -> Result<()> { Ok(()) } + + fn load_protected_vm_firmware( + &mut self, + _fw_addr: GuestAddress, + _fw_max_size: u64, + ) -> Result<()> { + // WHPX does not support protected VMs + Err(Error::new(libc::ENXIO)) + } } // NOTE: WHPX Tests need to be run serially as otherwise it barfs unless we map new regions of guest diff --git a/hypervisor/src/x86_64.rs b/hypervisor/src/x86_64.rs index 1b9dc3f36a..fe47e1d923 100644 --- a/hypervisor/src/x86_64.rs +++ b/hypervisor/src/x86_64.rs @@ -65,6 +65,12 @@ pub trait VmX86_64: Vm { /// Sets the address of a one-page region in the VM's address space. fn set_identity_map_addr(&self, addr: GuestAddress) -> Result<()>; + + /// Load pVM firmware for the VM, creating a memslot for it as needed. + /// + /// Only works on protected VMs (i.e. those with vm_type == KVM_X86_PKVM_PROTECTED_VM). + fn load_protected_vm_firmware(&mut self, fw_addr: GuestAddress, fw_max_size: u64) + -> Result<()>; } /// A wrapper around creating and using a VCPU on x86_64. diff --git a/kvm/src/cap.rs b/kvm/src/cap.rs index 032403a001..f789c3190e 100644 --- a/kvm/src/cap.rs +++ b/kvm/src/cap.rs @@ -122,6 +122,7 @@ pub enum Cap { ImmediateExit = KVM_CAP_IMMEDIATE_EXIT, ArmPmuV3 = KVM_CAP_ARM_PMU_V3, ArmProtectedVm = KVM_CAP_ARM_PROTECTED_VM, + X86ProtectedVm = KVM_CAP_X86_PROTECTED_VM, ArmMte = KVM_CAP_ARM_MTE, #[cfg(target_arch = "x86_64")] BusLockDetect = KVM_CAP_X86_BUS_LOCK_EXIT, diff --git a/kvm_sys/bindgen.sh b/kvm_sys/bindgen.sh index 9c6b51860b..0130b1c23f 100755 --- a/kvm_sys/bindgen.sh +++ b/kvm_sys/bindgen.sh @@ -25,6 +25,9 @@ pub const KVM_CAP_ARM_PROTECTED_VM_FLAGS_SET_FW_IPA: u32 = 0; pub const KVM_CAP_ARM_PROTECTED_VM_FLAGS_INFO: u32 = 1; pub const KVM_VM_TYPE_ARM_PROTECTED: u32 = 0x80000000; pub const KVM_X86_PKVM_PROTECTED_VM: u32 = 28; +pub const KVM_CAP_X86_PROTECTED_VM: u32 = 0xffbadab2; +pub const KVM_CAP_X86_PROTECTED_VM_FLAGS_SET_FW_GPA: u32 = 0; +pub const KVM_CAP_X86_PROTECTED_VM_FLAGS_INFO: u32 = 1; pub const KVM_DEV_VFIO_PVIOMMU: u32 = 2; pub const KVM_DEV_VFIO_PVIOMMU_ATTACH: u32 = 1; #[repr(C)] diff --git a/kvm_sys/src/aarch64/bindings.rs b/kvm_sys/src/aarch64/bindings.rs index d181722f02..83109d8bc2 100644 --- a/kvm_sys/src/aarch64/bindings.rs +++ b/kvm_sys/src/aarch64/bindings.rs @@ -23,6 +23,9 @@ pub const KVM_CAP_ARM_PROTECTED_VM_FLAGS_SET_FW_IPA: u32 = 0; pub const KVM_CAP_ARM_PROTECTED_VM_FLAGS_INFO: u32 = 1; pub const KVM_VM_TYPE_ARM_PROTECTED: u32 = 0x80000000; pub const KVM_X86_PKVM_PROTECTED_VM: u32 = 28; +pub const KVM_CAP_X86_PROTECTED_VM: u32 = 0xffbadab2; +pub const KVM_CAP_X86_PROTECTED_VM_FLAGS_SET_FW_GPA: u32 = 0; +pub const KVM_CAP_X86_PROTECTED_VM_FLAGS_INFO: u32 = 1; pub const KVM_DEV_VFIO_PVIOMMU: u32 = 2; pub const KVM_DEV_VFIO_PVIOMMU_ATTACH: u32 = 1; #[repr(C)] diff --git a/kvm_sys/src/riscv64/bindings.rs b/kvm_sys/src/riscv64/bindings.rs index 0dfbbd2c71..c52e7a899e 100644 --- a/kvm_sys/src/riscv64/bindings.rs +++ b/kvm_sys/src/riscv64/bindings.rs @@ -23,6 +23,9 @@ pub const KVM_CAP_ARM_PROTECTED_VM_FLAGS_SET_FW_IPA: u32 = 0; pub const KVM_CAP_ARM_PROTECTED_VM_FLAGS_INFO: u32 = 1; pub const KVM_VM_TYPE_ARM_PROTECTED: u32 = 0x80000000; pub const KVM_X86_PKVM_PROTECTED_VM: u32 = 28; +pub const KVM_CAP_X86_PROTECTED_VM: u32 = 0xffbadab2; +pub const KVM_CAP_X86_PROTECTED_VM_FLAGS_SET_FW_GPA: u32 = 0; +pub const KVM_CAP_X86_PROTECTED_VM_FLAGS_INFO: u32 = 1; pub const KVM_DEV_VFIO_PVIOMMU: u32 = 2; pub const KVM_DEV_VFIO_PVIOMMU_ATTACH: u32 = 1; #[repr(C)] diff --git a/kvm_sys/src/x86/bindings.rs b/kvm_sys/src/x86/bindings.rs index 6702b3f818..a8d6e6249f 100644 --- a/kvm_sys/src/x86/bindings.rs +++ b/kvm_sys/src/x86/bindings.rs @@ -23,6 +23,9 @@ pub const KVM_CAP_ARM_PROTECTED_VM_FLAGS_SET_FW_IPA: u32 = 0; pub const KVM_CAP_ARM_PROTECTED_VM_FLAGS_INFO: u32 = 1; pub const KVM_VM_TYPE_ARM_PROTECTED: u32 = 0x80000000; pub const KVM_X86_PKVM_PROTECTED_VM: u32 = 28; +pub const KVM_CAP_X86_PROTECTED_VM: u32 = 0xffbadab2; +pub const KVM_CAP_X86_PROTECTED_VM_FLAGS_SET_FW_GPA: u32 = 0; +pub const KVM_CAP_X86_PROTECTED_VM_FLAGS_INFO: u32 = 1; pub const KVM_DEV_VFIO_PVIOMMU: u32 = 2; pub const KVM_DEV_VFIO_PVIOMMU_ATTACH: u32 = 1; #[repr(C)] diff --git a/x86_64/src/lib.rs b/x86_64/src/lib.rs index ebd5831040..e96bd3cda0 100644 --- a/x86_64/src/lib.rs +++ b/x86_64/src/lib.rs @@ -241,6 +241,8 @@ pub enum Error { LoadKernel(kernel_loader::Error), #[error("error loading pflash: {0}")] LoadPflash(io::Error), + #[error("error loading pVM firmware: {0}")] + LoadPvmFw(base::Error), #[error("error translating address: Page not present")] PageNotPresent, #[error("pci mmio overlaps with pVM firmware memory")] @@ -1180,15 +1182,26 @@ impl arch::LinuxArch for X8664arch { PROTECTED_VM_FW_MAX_SIZE, ) .map_err(Error::LoadCustomPvmFw)?; + } else if protection_type.runs_firmware() { + // Tell the hypervisor to load the pVM firmware. + vm.load_protected_vm_firmware( + GuestAddress(PROTECTED_VM_FW_START), + PROTECTED_VM_FW_MAX_SIZE, + ) + .map_err(Error::LoadPvmFw)?; } - let entry_addr = if protection_type.runs_firmware() { - PROTECTED_VM_FW_START + let entry_addr = if protection_type.needs_firmware_loaded() { + Some(PROTECTED_VM_FW_START) + } else if protection_type.runs_firmware() { + None // Initial RIP value is set by the hypervisor } else { - kernel_entry.offset() + Some(kernel_entry.offset()) }; - vcpu_init[0].regs.rip = entry_addr; + if let Some(entry) = entry_addr { + vcpu_init[0].regs.rip = entry; + } match kernel_type { KernelType::BzImage | KernelType::Elf => {