From 413f8545640e2b635eba6577c7dce276a6d0ae79 Mon Sep 17 00:00:00 2001 From: Andrew Walbran Date: Fri, 8 Jan 2021 13:29:03 +0000 Subject: [PATCH] Enable KVM_CAP_ARM_PROTECTED_VM when --protected-vm is passed. - Add an address space region for the protected KVM firmware. - Query firmware size, mmap something that size and create a memslot. BUG=b:163789172 TEST=cargo test Change-Id: I054cf5d763c980d073c17bce70e85a781816b64d Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2623942 Auto-Submit: Andrew Walbran Reviewed-by: Dylan Reid Commit-Queue: Andrew Walbran Tested-by: kokoro --- aarch64/src/lib.rs | 15 ++++++++ arch/src/lib.rs | 4 +-- arch/src/serial.rs | 6 ++-- devices/src/lib.rs | 9 +++++ devices/src/serial.rs | 8 ++--- devices/src/serial_device.rs | 3 +- devices/src/virtio/block.rs | 11 +++--- devices/src/virtio/block_async.rs | 11 +++--- devices/src/virtio/console.rs | 4 +-- devices/src/virtio/mod.rs | 5 +-- devices/src/virtio/vhost/net.rs | 3 +- fuzz/block_fuzzer.rs | 3 +- hypervisor/src/aarch64.rs | 5 +++ hypervisor/src/caps.rs | 2 ++ hypervisor/src/kvm/aarch64.rs | 60 ++++++++++++++++++++++++++++--- hypervisor/src/kvm/mod.rs | 47 ++++++++++++++++++------ hypervisor/src/lib.rs | 13 ++----- kvm/src/cap.rs | 1 + kvm_sys/src/aarch64/bindings.rs | 4 +++ kvm_sys/src/x86/bindings.rs | 4 +++ src/crosvm.rs | 5 +-- src/main.rs | 3 +- x86_64/src/lib.rs | 10 ++++-- x86_64/src/test_integration.rs | 12 +++++-- 24 files changed, 190 insertions(+), 58 deletions(-) diff --git a/aarch64/src/lib.rs b/aarch64/src/lib.rs index 5fd963ea21..1af668bd37 100644 --- a/aarch64/src/lib.rs +++ b/aarch64/src/lib.rs @@ -17,6 +17,7 @@ use arch::{ use base::Event; use devices::{ Bus, BusError, IrqChip, IrqChipAArch64, PciAddress, PciConfigMmio, PciDevice, PciInterruptPin, + ProtectionType, }; use hypervisor::{ DeviceKind, Hypervisor, HypervisorCap, PsciVersion, VcpuAArch64, VcpuFeature, VmAArch64, @@ -49,6 +50,10 @@ const AARCH64_FDT_OFFSET_IN_BIOS_MODE: u64 = 0x0; const AARCH64_BIOS_OFFSET: u64 = AARCH64_FDT_MAX_SIZE; const AARCH64_BIOS_MAX_LEN: u64 = 1 << 20; +const AARCH64_PROTECTED_VM_FW_MAX_SIZE: u64 = 0x200000; +const AARCH64_PROTECTED_VM_FW_START: u64 = + AARCH64_PHYS_MEM_START - AARCH64_PROTECTED_VM_FW_MAX_SIZE; + // These constants indicate the placement of the GIC registers in the physical // address space. const AARCH64_GIC_DIST_BASE: u64 = AARCH64_AXI_BASE - AARCH64_GIC_DIST_SIZE; @@ -141,6 +146,7 @@ pub enum Error { GetSerialCmdline(GetSerialCmdlineError), InitrdLoadFailure(arch::LoadImageError), KernelLoadFailure(arch::LoadImageError), + ProtectVm(base::Error), RegisterIrqfd(base::Error), RegisterPci(BusError), RegisterVsock(arch::DeviceRegistrationError), @@ -175,6 +181,7 @@ impl Display for Error { GetSerialCmdline(e) => write!(f, "failed to get serial cmdline: {}", e), InitrdLoadFailure(e) => write!(f, "initrd could not be loaded: {}", e), KernelLoadFailure(e) => write!(f, "kernel could not be loaded: {}", e), + ProtectVm(e) => write!(f, "failed to protect vm: {}", e), RegisterIrqfd(e) => write!(f, "failed to register irq fd: {}", e), RegisterPci(e) => write!(f, "error registering PCI bus: {}", e), RegisterVsock(e) => write!(f, "error registering virtual socket device: {}", e), @@ -249,6 +256,14 @@ impl arch::LinuxArch for AArch64 { let mem = Self::setup_memory(components.memory_size)?; let mut vm = create_vm(mem.clone()).map_err(|e| Error::CreateVm(Box::new(e)))?; + if components.protected_vm == ProtectionType::Protected { + vm.enable_protected_vm( + GuestAddress(AARCH64_PROTECTED_VM_FW_START), + AARCH64_PROTECTED_VM_FW_MAX_SIZE, + ) + .map_err(Error::ProtectVm)?; + } + let mut use_pmu = vm .get_hypervisor() .check_capability(&HypervisorCap::ArmPmuV3); diff --git a/arch/src/lib.rs b/arch/src/lib.rs index a604ca9d14..c59a2cd133 100644 --- a/arch/src/lib.rs +++ b/arch/src/lib.rs @@ -21,7 +21,7 @@ use base::{syslog, AsRawDescriptor, Event}; use devices::virtio::VirtioDevice; use devices::{ Bus, BusDevice, BusError, IrqChip, PciAddress, PciDevice, PciDeviceError, PciInterruptPin, - PciRoot, ProxyDevice, + PciRoot, ProtectionType, ProxyDevice, }; use hypervisor::{IoEventAddress, Vm}; use minijail::Minijail; @@ -91,7 +91,7 @@ pub struct VmComponents { pub wayland_dmabuf: bool, pub acpi_sdts: Vec, pub rt_cpus: Vec, - pub protected_vm: bool, + pub protected_vm: ProtectionType, #[cfg(all(target_arch = "x86_64", feature = "gdb"))] pub gdb: Option<(u32, VmControlRequestSocket)>, // port and control socket. } diff --git a/arch/src/serial.rs b/arch/src/serial.rs index e0ba1c43af..1b9ec1c939 100644 --- a/arch/src/serial.rs +++ b/arch/src/serial.rs @@ -15,7 +15,7 @@ use std::thread; use std::time::Duration; use base::{error, info, read_raw_stdin, syslog, AsRawDescriptor, Event, RawDescriptor}; -use devices::{Bus, ProxyDevice, Serial, SerialDevice}; +use devices::{Bus, ProtectionType, ProxyDevice, Serial, SerialDevice}; use minijail::Minijail; use sync::Mutex; @@ -211,7 +211,7 @@ impl SerialParameters { /// this function. pub fn create_serial_device( &self, - protected_vm: bool, + protected_vm: ProtectionType, evt: &Event, keep_rds: &mut Vec, ) -> std::result::Result { @@ -409,7 +409,7 @@ pub const SERIAL_ADDR: [u64; 4] = [0x3f8, 0x2f8, 0x3e8, 0x2e8]; /// * `serial_parameters` - definitions of serial parameter configurations. /// All four of the traditional PC-style serial ports (COM1-COM4) must be specified. pub fn add_serial_devices( - protected_vm: bool, + protected_vm: ProtectionType, io_bus: &mut Bus, com_evt_1_3: &Event, com_evt_2_4: &Event, diff --git a/devices/src/lib.rs b/devices/src/lib.rs index 64de009b38..5299ef5a6d 100644 --- a/devices/src/lib.rs +++ b/devices/src/lib.rs @@ -48,3 +48,12 @@ pub use self::usb::host_backend::host_backend_device_provider::HostBackendDevice pub use self::usb::xhci::xhci_controller::XhciController; pub use self::vfio::{VfioContainer, VfioDevice}; pub use self::virtio::VirtioPciDevice; + +/// Whether the VM should be run in protected mode or not. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum ProtectionType { + /// The VM should be run in the unprotected mode, where the host has access to its memory. + Unprotected, + /// The VM should be run in protected mode, so the host cannot access its memory directly. + Protected, +} diff --git a/devices/src/serial.rs b/devices/src/serial.rs index d4e6d45837..710f300027 100644 --- a/devices/src/serial.rs +++ b/devices/src/serial.rs @@ -12,7 +12,7 @@ use std::thread::{self}; use base::{error, Event, RawDescriptor, Result}; use crate::bus::BusAccessInfo; -use crate::{BusDevice, SerialDevice}; +use crate::{BusDevice, ProtectionType, SerialDevice}; const LOOP_SIZE: usize = 0x40; @@ -84,7 +84,7 @@ pub struct Serial { impl SerialDevice for Serial { fn new( - _protected_vm: bool, + _protected_vm: ProtectionType, interrupt_evt: Event, input: Option>, out: Option>, @@ -419,7 +419,7 @@ mod tests { let serial_out = SharedBuffer::new(); let mut serial = Serial::new( - false, + ProtectionType::Unprotected, intr_evt, None, Some(Box::new(serial_out.clone())), @@ -441,7 +441,7 @@ mod tests { let serial_out = SharedBuffer::new(); let mut serial = Serial::new( - false, + ProtectionType::Unprotected, intr_evt.try_clone().unwrap(), None, Some(Box::new(serial_out.clone())), diff --git a/devices/src/serial_device.rs b/devices/src/serial_device.rs index 280730e423..9cbd57bf23 100644 --- a/devices/src/serial_device.rs +++ b/devices/src/serial_device.rs @@ -4,13 +4,14 @@ use std::io; +use crate::ProtectionType; use base::{Event, RawDescriptor}; /// Abstraction over serial-like devices that can be created given an event and optional input and /// output streams. pub trait SerialDevice { fn new( - protected_vm: bool, + protected_vm: ProtectionType, interrupt_evt: Event, input: Option>, output: Option>, diff --git a/devices/src/virtio/block.rs b/devices/src/virtio/block.rs index 0bb8205b4e..8021e924ef 100644 --- a/devices/src/virtio/block.rs +++ b/devices/src/virtio/block.rs @@ -868,6 +868,7 @@ mod tests { use crate::virtio::base_features; use crate::virtio::descriptor_utils::{create_descriptor_chain, DescriptorType}; + use crate::ProtectionType; use super::*; @@ -876,7 +877,7 @@ mod tests { let f = tempfile().unwrap(); f.set_len(0x1000).unwrap(); - let features = base_features(false); + let features = base_features(ProtectionType::Unprotected); let b = Block::new(features, Box::new(f), true, false, 512, None, None).unwrap(); let mut num_sectors = [0u8; 4]; b.read_config(0, &mut num_sectors); @@ -893,7 +894,7 @@ mod tests { let f = tempfile().unwrap(); f.set_len(0x1000).unwrap(); - let features = base_features(false); + let features = base_features(ProtectionType::Unprotected); let b = Block::new(features, Box::new(f), true, false, 4096, None, None).unwrap(); let mut blk_size = [0u8; 4]; b.read_config(20, &mut blk_size); @@ -906,7 +907,7 @@ mod tests { // read-write block device { let f = tempfile().unwrap(); - let features = base_features(false); + let features = base_features(ProtectionType::Unprotected); let b = Block::new(features, Box::new(f), false, true, 512, None, None).unwrap(); // writable device should set VIRTIO_BLK_F_FLUSH + VIRTIO_BLK_F_DISCARD // + VIRTIO_BLK_F_WRITE_ZEROES + VIRTIO_F_VERSION_1 + VIRTIO_BLK_F_BLK_SIZE @@ -917,7 +918,7 @@ mod tests { // read-write block device, non-sparse { let f = tempfile().unwrap(); - let features = base_features(false); + let features = base_features(ProtectionType::Unprotected); let b = Block::new(features, Box::new(f), false, false, 512, None, None).unwrap(); // writable device should set VIRTIO_BLK_F_FLUSH // + VIRTIO_BLK_F_WRITE_ZEROES + VIRTIO_F_VERSION_1 + VIRTIO_BLK_F_BLK_SIZE @@ -928,7 +929,7 @@ mod tests { // read-only block device { let f = tempfile().unwrap(); - let features = base_features(false); + let features = base_features(ProtectionType::Unprotected); let b = Block::new(features, Box::new(f), true, true, 512, None, None).unwrap(); // read-only device should set VIRTIO_BLK_F_FLUSH and VIRTIO_BLK_F_RO // + VIRTIO_F_VERSION_1 + VIRTIO_BLK_F_BLK_SIZE + VIRTIO_BLK_F_SEG_MAX diff --git a/devices/src/virtio/block_async.rs b/devices/src/virtio/block_async.rs index ac49ab7cb3..b7d0bc4a1e 100644 --- a/devices/src/virtio/block_async.rs +++ b/devices/src/virtio/block_async.rs @@ -950,6 +950,7 @@ mod tests { use crate::virtio::base_features; use crate::virtio::descriptor_utils::{create_descriptor_chain, DescriptorType}; + use crate::ProtectionType; use super::*; @@ -961,7 +962,7 @@ mod tests { let f = File::create(&path).unwrap(); f.set_len(0x1000).unwrap(); - let features = base_features(false); + let features = base_features(ProtectionType::Unprotected); let b = BlockAsync::new(features, Box::new(f), true, false, 512, None).unwrap(); let mut num_sectors = [0u8; 4]; b.read_config(0, &mut num_sectors); @@ -981,7 +982,7 @@ mod tests { let f = File::create(&path).unwrap(); f.set_len(0x1000).unwrap(); - let features = base_features(false); + let features = base_features(ProtectionType::Unprotected); let b = BlockAsync::new(features, Box::new(f), true, false, 4096, None).unwrap(); let mut blk_size = [0u8; 4]; b.read_config(20, &mut blk_size); @@ -998,7 +999,7 @@ mod tests { // read-write block device { let f = File::create(&path).unwrap(); - let features = base_features(false); + let features = base_features(ProtectionType::Unprotected); let b = BlockAsync::new(features, Box::new(f), false, true, 512, None).unwrap(); // writable device should set VIRTIO_BLK_F_FLUSH + VIRTIO_BLK_F_DISCARD // + VIRTIO_BLK_F_WRITE_ZEROES + VIRTIO_F_VERSION_1 + VIRTIO_BLK_F_BLK_SIZE @@ -1009,7 +1010,7 @@ mod tests { // read-write block device, non-sparse { let f = File::create(&path).unwrap(); - let features = base_features(false); + let features = base_features(ProtectionType::Unprotected); let b = BlockAsync::new(features, Box::new(f), false, false, 512, None).unwrap(); // read-only device should set VIRTIO_BLK_F_FLUSH and VIRTIO_BLK_F_RO // + VIRTIO_F_VERSION_1 + VIRTIO_BLK_F_BLK_SIZE + VIRTIO_BLK_F_SEG_MAX @@ -1020,7 +1021,7 @@ mod tests { // read-only block device { let f = File::create(&path).unwrap(); - let features = base_features(false); + let features = base_features(ProtectionType::Unprotected); let b = BlockAsync::new(features, Box::new(f), true, true, 512, None).unwrap(); // read-only device should set VIRTIO_BLK_F_FLUSH and VIRTIO_BLK_F_RO // + VIRTIO_F_VERSION_1 + VIRTIO_BLK_F_BLK_SIZE + VIRTIO_BLK_F_SEG_MAX diff --git a/devices/src/virtio/console.rs b/devices/src/virtio/console.rs index 36e946716c..5eb7cbcf42 100644 --- a/devices/src/virtio/console.rs +++ b/devices/src/virtio/console.rs @@ -13,7 +13,7 @@ use vm_memory::GuestMemory; use super::{ base_features, copy_config, Interrupt, Queue, Reader, VirtioDevice, Writer, TYPE_CONSOLE, }; -use crate::SerialDevice; +use crate::{ProtectionType, SerialDevice}; const QUEUE_SIZE: u16 = 256; @@ -308,7 +308,7 @@ pub struct Console { impl SerialDevice for Console { fn new( - protected_vm: bool, + protected_vm: ProtectionType, _evt: Event, input: Option>, output: Option>, diff --git a/devices/src/virtio/mod.rs b/devices/src/virtio/mod.rs index 898a768ebd..a859e13bb3 100644 --- a/devices/src/virtio/mod.rs +++ b/devices/src/virtio/mod.rs @@ -56,6 +56,7 @@ pub use self::virtio_device::*; pub use self::virtio_pci_device::*; pub use self::wl::*; +use crate::ProtectionType; use std::cmp; use std::convert::TryFrom; @@ -156,10 +157,10 @@ pub fn copy_config(dst: &mut [u8], dst_offset: u64, src: &[u8], src_offset: u64) } /// Returns the set of reserved base features common to all virtio devices. -pub fn base_features(protected_vm: bool) -> u64 { +pub fn base_features(protected_vm: ProtectionType) -> u64 { let mut features: u64 = 1 << VIRTIO_F_VERSION_1; - if protected_vm { + if protected_vm == ProtectionType::Protected { features |= 1 << VIRTIO_F_ACCESS_PLATFORM; } diff --git a/devices/src/virtio/vhost/net.rs b/devices/src/virtio/vhost/net.rs index fa4a810dde..41dc6da4b9 100644 --- a/devices/src/virtio/vhost/net.rs +++ b/devices/src/virtio/vhost/net.rs @@ -348,6 +348,7 @@ pub mod tests { use super::*; use crate::virtio::base_features; use crate::virtio::VIRTIO_MSI_NO_VECTOR; + use crate::ProtectionType; use net_util::fakes::FakeTap; use std::result; use std::sync::atomic::AtomicUsize; @@ -363,7 +364,7 @@ pub mod tests { fn create_net_common() -> Net> { let guest_memory = create_guest_memory().unwrap(); - let features = base_features(false); + let features = base_features(ProtectionType::Unprotected); Net::>::new( features, Ipv4Addr::new(127, 0, 0, 1), diff --git a/fuzz/block_fuzzer.rs b/fuzz/block_fuzzer.rs index 04afb20b06..124dcacce9 100644 --- a/fuzz/block_fuzzer.rs +++ b/fuzz/block_fuzzer.rs @@ -12,6 +12,7 @@ use std::sync::Arc; use base::Event; use cros_fuzz::fuzz_target; use devices::virtio::{base_features, Block, Interrupt, Queue, VirtioDevice}; +use devices::ProtectionType; use tempfile; use vm_memory::{GuestAddress, GuestMemory}; @@ -77,7 +78,7 @@ fuzz_target!(|bytes| { let queue_evts: Vec = vec![Event::new().unwrap()]; let queue_evt = queue_evts[0].try_clone().unwrap(); - let features = base_features(false); + let features = base_features(ProtectionType::Unprotected); let disk_file = tempfile::tempfile().unwrap(); let mut block = diff --git a/hypervisor/src/aarch64.rs b/hypervisor/src/aarch64.rs index da253dc0f6..bf7bc00d2c 100644 --- a/hypervisor/src/aarch64.rs +++ b/hypervisor/src/aarch64.rs @@ -4,6 +4,7 @@ use base::Result; use downcast_rs::impl_downcast; +use vm_memory::GuestAddress; use crate::{Hypervisor, IrqRoute, IrqSource, IrqSourceChip, Vcpu, Vm}; @@ -18,6 +19,10 @@ pub trait VmAArch64: Vm { /// Gets the `Hypervisor` that created this VM. fn get_hypervisor(&self) -> &dyn Hypervisor; + /// Enables protected mode for the VM, creating a memslot for the firmware as needed. + /// Only works on VMs that support `VmCap::Protected`. + fn enable_protected_vm(&mut self, fw_addr: GuestAddress, fw_max_size: u64) -> Result<()>; + /// Create a Vcpu with the specified Vcpu ID. fn create_vcpu(&self, id: usize) -> Result>; } diff --git a/hypervisor/src/caps.rs b/hypervisor/src/caps.rs index 2b66aec83a..43b80756f6 100644 --- a/hypervisor/src/caps.rs +++ b/hypervisor/src/caps.rs @@ -22,4 +22,6 @@ pub enum VmCap { PvClock, /// PV clock can be notified when guest is being paused PvClockSuspend, + /// VM can be run in protected mode, where the host does not have access to its memory. + Protected, } diff --git a/hypervisor/src/kvm/aarch64.rs b/hypervisor/src/kvm/aarch64.rs index a88bda1a0a..161b4b51b3 100644 --- a/hypervisor/src/kvm/aarch64.rs +++ b/hypervisor/src/kvm/aarch64.rs @@ -2,14 +2,17 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -use libc::ENXIO; +use libc::{EINVAL, ENOMEM, ENOSYS, ENXIO}; -use base::{errno_result, error, ioctl_with_mut_ref, ioctl_with_ref, Error, Result}; +use base::{ + errno_result, error, ioctl_with_mut_ref, ioctl_with_ref, Error, MemoryMappingBuilder, Result, +}; use kvm_sys::*; +use vm_memory::GuestAddress; -use super::{KvmVcpu, KvmVm}; +use super::{KvmCap, KvmVcpu, KvmVm}; use crate::{ - ClockState, DeviceKind, Hypervisor, IrqSourceChip, PsciVersion, VcpuAArch64, VcpuFeature, + ClockState, DeviceKind, Hypervisor, IrqSourceChip, PsciVersion, VcpuAArch64, VcpuFeature, Vm, VmAArch64, VmCap, }; @@ -47,6 +50,29 @@ impl KvmVm { pub fn set_pvclock_arch(&self, _state: &ClockState) -> Result<()> { Err(Error::new(ENXIO)) } + + fn get_protected_vm_info(&self) -> Result { + let mut info = KvmProtectedVmInfo { + firmware_size: 0, + reserved: [0; 7], + }; + // 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::ArmProtectedVm, + KVM_CAP_ARM_PROTECTED_VM_FLAGS_INFO, + &[&mut info as *mut KvmProtectedVmInfo as u64, 0, 0, 0], + ) + }?; + Ok(info) + } +} + +#[repr(C)] +struct KvmProtectedVmInfo { + firmware_size: u64, + reserved: [u64; 7], } impl VmAArch64 for KvmVm { @@ -54,6 +80,32 @@ impl VmAArch64 for KvmVm { &self.kvm } + fn enable_protected_vm(&mut self, fw_addr: GuestAddress, fw_max_size: u64) -> Result<()> { + if !self.check_capability(VmCap::Protected) { + return Err(Error::new(ENOSYS)); + } + let info = self.get_protected_vm_info()?; + let memslot = if info.firmware_size == 0 { + u64::MAX + } else { + if info.firmware_size > fw_max_size { + return Err(Error::new(ENOMEM)); + } + let mem = MemoryMappingBuilder::new(info.firmware_size as usize) + .build() + .map_err(|_| Error::new(EINVAL))?; + self.add_memory_region(fw_addr, Box::new(mem), false, false)? as u64 + }; + // Safe because none of the args are pointers. + unsafe { + self.enable_raw_capability( + KvmCap::ArmProtectedVm, + KVM_CAP_ARM_PROTECTED_VM_FLAGS_ENABLE, + &[memslot, 0, 0, 0], + ) + } + } + 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/kvm/mod.rs b/hypervisor/src/kvm/mod.rs index 9c08d1d1de..49b6af6c01 100644 --- a/hypervisor/src/kvm/mod.rs +++ b/hypervisor/src/kvm/mod.rs @@ -375,6 +375,38 @@ impl KvmVm { errno_result() } } + + /// Checks whether a particular KVM-specific capability is available for this VM. + fn check_raw_capability(&self, capability: KvmCap) -> bool { + // Safe because we know that our file is a KVM fd, and if the cap is invalid KVM assumes + // it's an unavailable extension and returns 0. + unsafe { ioctl_with_val(self, KVM_CHECK_EXTENSION(), capability as c_ulong) == 1 } + } + + // Currently only used on aarch64, but works on any architecture. + #[allow(dead_code)] + /// Enables a KVM-specific capability for this VM, with the given arguments. + unsafe fn enable_raw_capability( + &self, + capability: KvmCap, + flags: u32, + args: &[u64; 4], + ) -> Result<()> { + let kvm_cap = kvm_enable_cap { + cap: capability as u32, + args: *args, + flags, + ..Default::default() + }; + // Safe because we allocated the struct and we know the kernel will read + // exactly the size of the struct. + let ret = ioctl_with_ref(self, KVM_ENABLE_CAP(), &kvm_cap); + if ret == 0 { + Ok(()) + } else { + errno_result() + } + } } impl Vm for KvmVm { @@ -395,16 +427,11 @@ impl Vm for KvmVm { match c { VmCap::DirtyLog => true, VmCap::PvClock => false, - VmCap::PvClockSuspend => self.check_raw_capability(KVM_CAP_KVMCLOCK_CTRL), + VmCap::PvClockSuspend => self.check_raw_capability(KvmCap::KvmclockCtrl), + VmCap::Protected => self.check_raw_capability(KvmCap::ArmProtectedVm), } } - fn check_raw_capability(&self, cap: u32) -> bool { - // Safe because we know that our file is a KVM fd, and if the cap is invalid KVM assumes - // it's an unavailable extension and returns 0. - unsafe { ioctl_with_val(self, KVM_CHECK_EXTENSION(), cap as c_ulong) == 1 } - } - fn get_memory(&self) -> &GuestMemory { &self.guest_mem } @@ -821,7 +848,7 @@ impl Vcpu for KvmVcpu { args: *args, ..Default::default() }; - // Safe becuase we allocated the struct and we know the kernel will read + // Safe because we allocated the struct and we know the kernel will read // exactly the size of the struct. let ret = unsafe { ioctl_with_ref(self, KVM_ENABLE_CAP(), &kvm_cap) }; if ret == 0 { @@ -1176,9 +1203,9 @@ mod tests { let kvm = Kvm::new().unwrap(); let gm = GuestMemory::new(&[(GuestAddress(0), 0x1000)]).unwrap(); let vm = KvmVm::new(&kvm, gm).unwrap(); - assert!(vm.check_raw_capability(KVM_CAP_USER_MEMORY)); + assert!(vm.check_raw_capability(KvmCap::UserMemory)); // I assume nobody is testing this on s390 - assert!(!vm.check_raw_capability(KVM_CAP_S390_USER_SIGP)); + assert!(!vm.check_raw_capability(KvmCap::S390UserSigp)); } #[test] diff --git a/hypervisor/src/lib.rs b/hypervisor/src/lib.rs index eb21598d2b..1fced1c6ce 100644 --- a/hypervisor/src/lib.rs +++ b/hypervisor/src/lib.rs @@ -47,17 +47,10 @@ pub trait Vm: Send { /// Checks if a particular `VmCap` is available. /// /// This is distinct from the `Hypervisor` version of this method because some extensions depend - /// on the particular `Vm` existence. This method is encouraged because it more accurately + /// on the particular `Vm` instance. This method is encouraged because it more accurately /// reflects the usable capabilities. fn check_capability(&self, c: VmCap) -> bool; - /// Checks if a particular hypervisor-specific capability is available. - /// - /// # Arguments - /// - /// * `cap` - hypervisor-specific constant defined by the hypervisor API (e.g., kvm.h) - fn check_raw_capability(&self, cap: u32) -> bool; - /// Gets the guest-mapped memory for the Vm. fn get_memory(&self) -> &GuestMemory; @@ -267,12 +260,12 @@ pub trait Vcpu: downcast_rs::DowncastSync { fn set_data(&self, data: &[u8]) -> Result<()>; /// Signals to the hypervisor that this guest is being paused by userspace. Only works on Vms - /// that support `VmCapability::PvClockSuspend`. + /// that support `VmCap::PvClockSuspend`. fn pvclock_ctrl(&self) -> Result<()>; /// Specifies set of signals that are blocked during execution of `RunnableVcpu::run`. Signals /// that are not blocked will cause run to return with `VcpuExit::Intr`. Only works on Vms that - /// support `VmCapability::SignalMask`. + /// support `VmCap::SignalMask`. fn set_signal_mask(&self, signals: &[c_int]) -> Result<()>; /// Enables a hypervisor-specific extension on this Vcpu. `cap` is a constant defined by the diff --git a/kvm/src/cap.rs b/kvm/src/cap.rs index ff65b59749..8d51279ce2 100644 --- a/kvm/src/cap.rs +++ b/kvm/src/cap.rs @@ -120,4 +120,5 @@ pub enum Cap { S390UserSigp = KVM_CAP_S390_USER_SIGP, ImmediateExit = KVM_CAP_IMMEDIATE_EXIT, ArmPmuV3 = KVM_CAP_ARM_PMU_V3, + ArmProtectedVm = KVM_CAP_ARM_PROTECTED_VM, } diff --git a/kvm_sys/src/aarch64/bindings.rs b/kvm_sys/src/aarch64/bindings.rs index 7e5303ae0c..24ae391443 100644 --- a/kvm_sys/src/aarch64/bindings.rs +++ b/kvm_sys/src/aarch64/bindings.rs @@ -618,6 +618,10 @@ pub const KVM_CAP_ARM_PTRAUTH_GENERIC: u32 = 172; pub const KVM_CAP_PMU_EVENT_FILTER: u32 = 173; pub const KVM_CAP_ARM_IRQ_LINE_LAYOUT_2: u32 = 174; pub const KVM_CAP_HYPERV_DIRECT_TLBFLUSH: u32 = 175; +// TODO(qwandor): Update this once the pKVM patches are merged upstream with a stable capability ID. +pub const KVM_CAP_ARM_PROTECTED_VM: u32 = 0xffbadab1; +pub const KVM_CAP_ARM_PROTECTED_VM_FLAGS_ENABLE: u32 = 0; +pub const KVM_CAP_ARM_PROTECTED_VM_FLAGS_INFO: u32 = 1; pub const KVM_IRQ_ROUTING_IRQCHIP: u32 = 1; pub const KVM_IRQ_ROUTING_MSI: u32 = 2; pub const KVM_IRQ_ROUTING_S390_ADAPTER: u32 = 3; diff --git a/kvm_sys/src/x86/bindings.rs b/kvm_sys/src/x86/bindings.rs index 789cc2ec4b..80e2fc44eb 100644 --- a/kvm_sys/src/x86/bindings.rs +++ b/kvm_sys/src/x86/bindings.rs @@ -568,6 +568,10 @@ pub const KVM_CAP_ARM_PTRAUTH_GENERIC: u32 = 172; pub const KVM_CAP_PMU_EVENT_FILTER: u32 = 173; pub const KVM_CAP_ARM_IRQ_LINE_LAYOUT_2: u32 = 174; pub const KVM_CAP_HYPERV_DIRECT_TLBFLUSH: u32 = 175; +// TODO(qwandor): Update this once the pKVM patches are merged upstream with a stable capability ID. +pub const KVM_CAP_ARM_PROTECTED_VM: u32 = 0xffbadab1; +pub const KVM_CAP_ARM_PROTECTED_VM_FLAGS_ENABLE: u32 = 0; +pub const KVM_CAP_ARM_PROTECTED_VM_FLAGS_INFO: u32 = 1; pub const KVM_IRQ_ROUTING_IRQCHIP: u32 = 1; pub const KVM_IRQ_ROUTING_MSI: u32 = 2; pub const KVM_IRQ_ROUTING_S390_ADAPTER: u32 = 3; diff --git a/src/crosvm.rs b/src/crosvm.rs index 8721ced0b2..3ae82908a2 100644 --- a/src/crosvm.rs +++ b/src/crosvm.rs @@ -25,6 +25,7 @@ use devices::virtio::fs::passthrough; use devices::virtio::gpu::GpuParameters; #[cfg(feature = "audio")] use devices::Ac97Parameters; +use devices::ProtectionType; use libc::{getegid, geteuid}; use vm_control::BatteryType; @@ -223,7 +224,7 @@ pub struct Config { pub video_dec: bool, pub video_enc: bool, pub acpi_tables: Vec, - pub protected_vm: bool, + pub protected_vm: ProtectionType, pub battery_type: Option, #[cfg(all(target_arch = "x86_64", feature = "gdb"))] pub gdb: Option, @@ -283,7 +284,7 @@ impl Default for Config { video_dec: false, video_enc: false, acpi_tables: Vec::new(), - protected_vm: false, + protected_vm: ProtectionType::Unprotected, battery_type: None, #[cfg(all(target_arch = "x86_64", feature = "gdb"))] gdb: None, diff --git a/src/main.rs b/src/main.rs index 4eb7411a3b..45bc2f4bc9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,6 +34,7 @@ use crosvm::{ }; #[cfg(feature = "gpu")] use devices::virtio::gpu::{GpuMode, GpuParameters}; +use devices::ProtectionType; #[cfg(feature = "audio")] use devices::{Ac97Backend, Ac97Parameters}; use disk::QcowFile; @@ -1505,7 +1506,7 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: cfg.acpi_tables.push(acpi_table); } "protected-vm" => { - cfg.protected_vm = true; + cfg.protected_vm = ProtectionType::Protected; cfg.params.push("swiotlb=force".to_string()); } "battery" => { diff --git a/x86_64/src/lib.rs b/x86_64/src/lib.rs index fea03e44b2..da1052d788 100644 --- a/x86_64/src/lib.rs +++ b/x86_64/src/lib.rs @@ -61,7 +61,7 @@ use arch::{ VmComponents, VmImage, }; use base::Event; -use devices::{IrqChip, IrqChipX86_64, PciConfigIo, PciDevice}; +use devices::{IrqChip, IrqChipX86_64, PciConfigIo, PciDevice, ProtectionType}; use hypervisor::{HypervisorX86_64, VcpuX86_64, VmX86_64}; use minijail::Minijail; use remain::sorted; @@ -124,6 +124,7 @@ pub enum Error { SetupSmbios(smbios::Error), SetupSregs(regs::Error), TranslatingVirtAddr, + UnsupportedProtectionType, WriteRegs(base::Error), WritingGuestMemory(GuestMemoryError), ZeroPagePastRamEnd, @@ -183,6 +184,7 @@ impl Display for Error { SetupSmbios(e) => write!(f, "failed to set up SMBIOS: {}", e), SetupSregs(e) => write!(f, "failed to set up sregs: {}", e), TranslatingVirtAddr => write!(f, "failed to translate virtual address"), + UnsupportedProtectionType => write!(f, "protected VMs not supported on x86_64"), WriteRegs(e) => write!(f, "error writing CPU registers {}", e), WritingGuestMemory(e) => write!(f, "error writing guest memory {}", e), ZeroPagePastRamEnd => write!(f, "the zero page extends past the end of guest_mem"), @@ -372,6 +374,10 @@ impl arch::LinuxArch for X8664arch { E2: StdError + 'static, E3: StdError + 'static, { + if components.protected_vm != ProtectionType::Unprotected { + return Err(Error::UnsupportedProtectionType); + } + let bios_size = match components.vm_image { VmImage::Bios(ref mut bios_file) => { Some(bios_file.metadata().map_err(Error::LoadBios)?.len()) @@ -1124,7 +1130,7 @@ impl X8664arch { /// * - `io_bus` the I/O bus to add the devices to /// * - `serial_parmaters` - definitions for how the serial devices should be configured fn setup_serial_devices( - protected_vm: bool, + protected_vm: ProtectionType, irq_chip: &mut impl IrqChip, io_bus: &mut devices::Bus, serial_parameters: &BTreeMap<(SerialHardware, u8), SerialParameters>, diff --git a/x86_64/src/test_integration.rs b/x86_64/src/test_integration.rs index a05293b8d2..6545c85a2a 100644 --- a/x86_64/src/test_integration.rs +++ b/x86_64/src/test_integration.rs @@ -4,7 +4,7 @@ #![cfg(any(target_arch = "x86", target_arch = "x86_64"))] -use devices::IrqChipX86_64; +use devices::{IrqChipX86_64, ProtectionType}; use hypervisor::{HypervisorX86_64, VcpuExit, VcpuX86_64, VmX86_64}; use vm_memory::{GuestAddress, GuestMemory}; @@ -158,8 +158,14 @@ where arch::set_default_serial_parameters(&mut serial_params); - X8664arch::setup_serial_devices(false, &mut irq_chip, &mut io_bus, &serial_params, None) - .unwrap(); + X8664arch::setup_serial_devices( + ProtectionType::Unprotected, + &mut irq_chip, + &mut io_bus, + &serial_params, + None, + ) + .unwrap(); let param_args = "nokaslr";