misc: Mark memory region APIs as unsafe

To ensure that struct sizes are the same on 32-bit and 64-bit, various
kernel APIs use __u64 (Rust u64) to represent userspace pointers.
Userspace is expected to cast pointers to __u64 before passing them to
the kernel, and cast kernel-provided __u64 to a pointer before using
them.  However, various safe APIs in Cloud Hypervisor took
caller-provided u64 values and passed them to syscalls that treat them
as userspace addresses.  Therefore, passing bad u64 values would cause
memory disclosure or corruption.  The memory region APIs are one example
of this, so mark them as unsafe.

Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
This commit is contained in:
Demi Marie Obenour
2025-06-13 14:27:00 -04:00
committed by Rob Bradford
parent 00f0b9e42c
commit fdc19ad85e
12 changed files with 489 additions and 407 deletions

View File

@@ -69,11 +69,7 @@ use crate::ClockData;
use crate::arch::x86::{ use crate::arch::x86::{
CpuIdEntry, FpuState, LapicState, MsrEntry, NUM_IOAPIC_PINS, SpecialRegisters, XsaveState, CpuIdEntry, FpuState, LapicState, MsrEntry, NUM_IOAPIC_PINS, SpecialRegisters, XsaveState,
}; };
use crate::{ use crate::{CpuState, IoEventAddress, IrqRoutingEntry, MpState, StandardRegisters};
CpuState, IoEventAddress, IrqRoutingEntry, MpState, StandardRegisters,
USER_MEMORY_REGION_LOG_DIRTY, USER_MEMORY_REGION_READ, USER_MEMORY_REGION_WRITE,
UserMemoryRegion,
};
// aarch64 dependencies // aarch64 dependencies
#[cfg(target_arch = "aarch64")] #[cfg(target_arch = "aarch64")]
pub mod aarch64; pub mod aarch64;
@@ -234,51 +230,6 @@ pub struct KvmTdxExitVmcall {
pub out_rdx: u64, pub out_rdx: u64,
} }
impl From<kvm_userspace_memory_region> for UserMemoryRegion {
fn from(region: kvm_userspace_memory_region) -> Self {
let mut flags = USER_MEMORY_REGION_READ;
if region.flags & KVM_MEM_READONLY == 0 {
flags |= USER_MEMORY_REGION_WRITE;
}
if region.flags & KVM_MEM_LOG_DIRTY_PAGES != 0 {
flags |= USER_MEMORY_REGION_LOG_DIRTY;
}
UserMemoryRegion {
slot: region.slot,
guest_phys_addr: region.guest_phys_addr,
memory_size: region.memory_size,
userspace_addr: region.userspace_addr,
flags,
}
}
}
impl From<UserMemoryRegion> for kvm_userspace_memory_region {
fn from(region: UserMemoryRegion) -> Self {
assert!(
region.flags & USER_MEMORY_REGION_READ != 0,
"KVM mapped memory is always readable"
);
let mut flags = 0;
if region.flags & USER_MEMORY_REGION_WRITE == 0 {
flags |= KVM_MEM_READONLY;
}
if region.flags & USER_MEMORY_REGION_LOG_DIRTY != 0 {
flags |= KVM_MEM_LOG_DIRTY_PAGES;
}
kvm_userspace_memory_region {
slot: region.slot,
guest_phys_addr: region.guest_phys_addr,
memory_size: region.memory_size,
userspace_addr: region.userspace_addr,
flags,
}
}
}
impl From<kvm_mp_state> for MpState { impl From<kvm_mp_state> for MpState {
fn from(s: kvm_mp_state) -> Self { fn from(s: kvm_mp_state) -> Self {
MpState::Kvm(s) MpState::Kvm(s)
@@ -758,10 +709,17 @@ impl vm::Vm for KvmVm {
.map_err(|e| vm::HypervisorVmError::SetGsiRouting(e.into())) .map_err(|e| vm::HypervisorVmError::SetGsiRouting(e.into()))
} }
/// Creates a guest physical memory region.
/// ///
/// Creates a memory region structure that can be used with {create/remove}_user_memory_region /// # Safety
/// ///
fn make_user_memory_region( /// `userspace_addr` must point to `memory_size` bytes of memory
/// that will stay mapped until a successful call to
/// `remove_user_memory_region().` Freeing them with `munmap()`
/// before then will cause undefined guest behavior but at least
/// should not cause undefined behavior in the host. In theory,
/// at least.
unsafe fn create_user_memory_region(
&self, &self,
slot: u32, slot: u32,
guest_phys_addr: u64, guest_phys_addr: u64,
@@ -769,27 +727,24 @@ impl vm::Vm for KvmVm {
userspace_addr: u64, userspace_addr: u64,
readonly: bool, readonly: bool,
log_dirty_pages: bool, log_dirty_pages: bool,
) -> UserMemoryRegion { ) -> vm::Result<()> {
kvm_userspace_memory_region { let mut flags = 0;
if readonly {
flags |= KVM_MEM_READONLY;
}
if log_dirty_pages {
flags |= KVM_MEM_LOG_DIRTY_PAGES;
}
const _: () = assert!(core::mem::size_of::<usize>() <= core::mem::size_of::<u64>());
let mut region = kvm_userspace_memory_region {
slot, slot,
guest_phys_addr, guest_phys_addr,
memory_size, memory_size,
userspace_addr, userspace_addr,
flags: if readonly { KVM_MEM_READONLY } else { 0 } flags,
| if log_dirty_pages { };
KVM_MEM_LOG_DIRTY_PAGES
} else {
0
},
}
.into()
}
///
/// Creates a guest physical memory region.
///
fn create_user_memory_region(&self, user_memory_region: UserMemoryRegion) -> vm::Result<()> {
let mut region: kvm_userspace_memory_region = user_memory_region.into();
if (region.flags & KVM_MEM_LOG_DIRTY_PAGES) != 0 { if (region.flags & KVM_MEM_LOG_DIRTY_PAGES) != 0 {
if (region.flags & KVM_MEM_READONLY) != 0 { if (region.flags & KVM_MEM_READONLY) != 0 {
@@ -814,7 +769,7 @@ impl vm::Vm for KvmVm {
region.flags = 0; region.flags = 0;
} }
// SAFETY: Safe because guest regions are guaranteed not to overlap. // SAFETY: Safe because caller promised this is safe.
unsafe { unsafe {
self.fd self.fd
.set_user_memory_region(region) .set_user_memory_region(region)
@@ -822,18 +777,45 @@ impl vm::Vm for KvmVm {
} }
} }
///
/// Removes a guest physical memory region. /// Removes a guest physical memory region.
/// ///
fn remove_user_memory_region(&self, user_memory_region: UserMemoryRegion) -> vm::Result<()> { /// # Safety
let mut region: kvm_userspace_memory_region = user_memory_region.into(); ///
/// `userspace_addr` must point to `memory_size` bytes of memory,
/// and `add_user_memory_region()` must have been successfully called.
unsafe fn remove_user_memory_region(
&self,
slot: u32,
guest_phys_addr: u64,
memory_size: u64,
userspace_addr: u64,
readonly: bool,
log_dirty_pages: bool,
) -> vm::Result<()> {
let mut flags = 0;
if readonly {
flags |= KVM_MEM_READONLY;
}
if log_dirty_pages {
flags |= KVM_MEM_LOG_DIRTY_PAGES;
}
const _: () = assert!(core::mem::size_of::<usize>() <= core::mem::size_of::<u64>());
let mut region = kvm_userspace_memory_region {
slot,
guest_phys_addr,
memory_size,
userspace_addr,
flags,
};
// Remove the corresponding entry from "self.dirty_log_slots" if needed // Remove the corresponding entry from "self.dirty_log_slots" if needed
self.dirty_log_slots.write().unwrap().remove(&region.slot); self.dirty_log_slots.write().unwrap().remove(&region.slot);
// Setting the size to 0 means "remove" // Setting the size to 0 means "remove"
region.memory_size = 0; region.memory_size = 0;
// SAFETY: Safe because guest regions are guaranteed not to overlap. // SAFETY: Safe because caller promised this is safe.
unsafe { unsafe {
self.fd self.fd
.set_user_memory_region(region) .set_user_memory_region(region)
@@ -1211,7 +1193,7 @@ impl hypervisor::Hypervisor for KvmHypervisor {
vm_type = KVM_X86_SW_PROTECTED_VM.into(); vm_type = KVM_X86_SW_PROTECTED_VM.into();
} else { } else {
vm_type = KVM_X86_DEFAULT_VM.into(); vm_type = KVM_X86_DEFAULT_VM.into();
}; }
loop { loop {
match self.kvm.create_vm_with_type(vm_type) { match self.kvm.create_vm_with_type(vm_type) {

View File

@@ -123,27 +123,6 @@ pub fn vec_with_array_field<T: Default, F>(count: usize) -> Vec<T> {
vec_with_size_in_bytes(vec_size_bytes) vec_with_size_in_bytes(vec_size_bytes)
} }
///
/// User memory region structure
///
#[derive(Debug, Default, Eq, PartialEq)]
pub struct UserMemoryRegion {
pub slot: u32,
pub guest_phys_addr: u64,
pub memory_size: u64,
pub userspace_addr: u64,
pub flags: u32,
}
///
/// Flags for user memory region
///
pub const USER_MEMORY_REGION_READ: u32 = 1;
pub const USER_MEMORY_REGION_WRITE: u32 = 1 << 1;
pub const USER_MEMORY_REGION_EXECUTE: u32 = 1 << 2;
pub const USER_MEMORY_REGION_LOG_DIRTY: u32 = 1 << 3;
pub const USER_MEMORY_REGION_ADJUSTABLE: u32 = 1 << 4;
#[derive(Debug)] #[derive(Debug)]
pub enum MpState { pub enum MpState {
#[cfg(feature = "kvm")] #[cfg(feature = "kvm")]

View File

@@ -74,35 +74,10 @@ use crate::arch::aarch64::gic::{Vgic, VgicConfig};
use crate::arch::aarch64::regs; use crate::arch::aarch64::regs;
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
use crate::arch::x86::{CpuIdEntry, FpuState, MsrEntry}; use crate::arch::x86::{CpuIdEntry, FpuState, MsrEntry};
use crate::{ use crate::{CpuState, IoEventAddress, IrqRoutingEntry, MpState};
CpuState, IoEventAddress, IrqRoutingEntry, MpState, USER_MEMORY_REGION_ADJUSTABLE,
USER_MEMORY_REGION_EXECUTE, USER_MEMORY_REGION_READ, USER_MEMORY_REGION_WRITE,
UserMemoryRegion,
};
pub const PAGE_SHIFT: usize = 12; pub const PAGE_SHIFT: usize = 12;
impl From<mshv_user_mem_region> for UserMemoryRegion {
fn from(region: mshv_user_mem_region) -> Self {
let mut flags: u32 = USER_MEMORY_REGION_READ | USER_MEMORY_REGION_ADJUSTABLE;
if region.flags & (1 << MSHV_SET_MEM_BIT_WRITABLE) != 0 {
flags |= USER_MEMORY_REGION_WRITE;
}
if region.flags & (1 << MSHV_SET_MEM_BIT_EXECUTABLE) != 0 {
flags |= USER_MEMORY_REGION_EXECUTE;
}
UserMemoryRegion {
guest_phys_addr: (region.guest_pfn << PAGE_SHIFT as u64)
+ (region.userspace_addr & ((1 << PAGE_SHIFT) - 1)),
memory_size: region.size,
userspace_addr: region.userspace_addr,
flags,
..Default::default()
}
}
}
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
impl From<MshvClockData> for ClockData { impl From<MshvClockData> for ClockData {
fn from(d: MshvClockData) -> Self { fn from(d: MshvClockData) -> Self {
@@ -122,26 +97,6 @@ impl From<ClockData> for MshvClockData {
} }
} }
impl From<UserMemoryRegion> for mshv_user_mem_region {
fn from(region: UserMemoryRegion) -> Self {
let mut flags: u8 = 0;
if region.flags & USER_MEMORY_REGION_WRITE != 0 {
flags |= 1 << MSHV_SET_MEM_BIT_WRITABLE;
}
if region.flags & USER_MEMORY_REGION_EXECUTE != 0 {
flags |= 1 << MSHV_SET_MEM_BIT_EXECUTABLE;
}
mshv_user_mem_region {
guest_pfn: region.guest_phys_addr >> PAGE_SHIFT,
size: region.memory_size,
userspace_addr: region.userspace_addr,
flags,
..Default::default()
}
}
}
impl From<mshv_ioctls::IoEventAddress> for IoEventAddress { impl From<mshv_ioctls::IoEventAddress> for IoEventAddress {
fn from(a: mshv_ioctls::IoEventAddress) -> Self { fn from(a: mshv_ioctls::IoEventAddress) -> Self {
match a { match a {
@@ -1918,8 +1873,36 @@ impl vm::Vm for MshvVm {
} }
/// Creates a guest physical memory region. /// Creates a guest physical memory region.
fn create_user_memory_region(&self, user_memory_region: UserMemoryRegion) -> vm::Result<()> { ///
let user_memory_region: mshv_user_mem_region = user_memory_region.into(); /// # Safety
///
/// `userspace_addr` must point to `memory_size` bytes of memory
/// that will stay mapped until a successful call to
/// `remove_user_memory_region().` Freeing them with `munmap()`
/// before then will cause undefined guest behavior but at least
/// should not cause undefined behavior in the host. In theory,
/// at least.
unsafe fn create_user_memory_region(
&self,
_slot: u32,
guest_phys_addr: u64,
memory_size: u64,
userspace_addr: u64,
readonly: bool,
_log_dirty_pages: bool,
) -> vm::Result<()> {
let mut flags = 1 << MSHV_SET_MEM_BIT_EXECUTABLE;
if !readonly {
flags |= 1 << MSHV_SET_MEM_BIT_WRITABLE;
}
let user_memory_region = mshv_user_mem_region {
flags,
guest_pfn: guest_phys_addr >> PAGE_SHIFT,
size: memory_size,
userspace_addr,
..Default::default()
};
// No matter read only or not we keep track the slots. // No matter read only or not we keep track the slots.
// For readonly hypervisor can enable the dirty bits, // For readonly hypervisor can enable the dirty bits,
// but a VM exit happens before setting the dirty bits // but a VM exit happens before setting the dirty bits
@@ -1938,8 +1921,32 @@ impl vm::Vm for MshvVm {
} }
/// Removes a guest physical memory region. /// Removes a guest physical memory region.
fn remove_user_memory_region(&self, user_memory_region: UserMemoryRegion) -> vm::Result<()> { ///
let user_memory_region: mshv_user_mem_region = user_memory_region.into(); /// # Safety
///
/// `userspace_addr` must point to `memory_size` bytes of memory,
/// and `add_user_memory_region()` must have been successfully called.
unsafe fn remove_user_memory_region(
&self,
_slot: u32,
guest_phys_addr: u64,
memory_size: u64,
userspace_addr: u64,
readonly: bool,
_log_dirty_pages: bool,
) -> vm::Result<()> {
let mut flags = 1 << MSHV_SET_MEM_BIT_EXECUTABLE;
if !readonly {
flags |= 1 << MSHV_SET_MEM_BIT_WRITABLE;
}
let user_memory_region = mshv_user_mem_region {
flags,
guest_pfn: guest_phys_addr >> PAGE_SHIFT,
size: memory_size,
userspace_addr,
..Default::default()
};
// Remove the corresponding entry from "self.dirty_log_slots" if needed // Remove the corresponding entry from "self.dirty_log_slots" if needed
self.dirty_log_slots self.dirty_log_slots
.write() .write()
@@ -1952,30 +1959,6 @@ impl vm::Vm for MshvVm {
Ok(()) Ok(())
} }
fn make_user_memory_region(
&self,
_slot: u32,
guest_phys_addr: u64,
memory_size: u64,
userspace_addr: u64,
readonly: bool,
_log_dirty_pages: bool,
) -> UserMemoryRegion {
let mut flags = 1 << MSHV_SET_MEM_BIT_EXECUTABLE;
if !readonly {
flags |= 1 << MSHV_SET_MEM_BIT_WRITABLE;
}
mshv_user_mem_region {
flags,
guest_pfn: guest_phys_addr >> PAGE_SHIFT,
size: memory_size,
userspace_addr,
..Default::default()
}
.into()
}
fn create_passthrough_device(&self) -> vm::Result<VfioDeviceFd> { fn create_passthrough_device(&self) -> vm::Result<VfioDeviceFd> {
let mut vfio_dev = mshv_create_device { let mut vfio_dev = mshv_create_device {
type_: MSHV_DEV_TYPE_VFIO, type_: MSHV_DEV_TYPE_VFIO,

View File

@@ -29,7 +29,7 @@ use crate::arch::riscv64::aia::{Vaia, VaiaConfig};
#[cfg(feature = "tdx")] #[cfg(feature = "tdx")]
use crate::arch::x86::CpuIdEntry; use crate::arch::x86::CpuIdEntry;
use crate::cpu::Vcpu; use crate::cpu::Vcpu;
use crate::{IoEventAddress, IrqRoutingEntry, UserMemoryRegion}; use crate::{IoEventAddress, IrqRoutingEntry};
/// ///
/// I/O events data matches (32 or 64 bits). /// I/O events data matches (32 or 64 bits).
@@ -335,8 +335,13 @@ pub trait Vm: Send + Sync + Any {
fn make_routing_entry(&self, gsi: u32, config: &InterruptSourceConfig) -> IrqRoutingEntry; fn make_routing_entry(&self, gsi: u32, config: &InterruptSourceConfig) -> IrqRoutingEntry;
/// Sets the GSI routing table entries, overwriting any previously set /// Sets the GSI routing table entries, overwriting any previously set
fn set_gsi_routing(&self, entries: &[IrqRoutingEntry]) -> Result<()>; fn set_gsi_routing(&self, entries: &[IrqRoutingEntry]) -> Result<()>;
/// Creates a memory region structure that can be used with {create/remove}_user_memory_region /// Creates a guest physical memory slot.
fn make_user_memory_region( ///
/// # Safety
///
/// `[userspace_addr, userspace_addr + memory_size)` must be valid memory,
/// and that address range must remain valid until [`Vm::remove_user_memory_region`] is called.
unsafe fn create_user_memory_region(
&self, &self,
slot: u32, slot: u32,
guest_phys_addr: u64, guest_phys_addr: u64,
@@ -344,11 +349,21 @@ pub trait Vm: Send + Sync + Any {
userspace_addr: u64, userspace_addr: u64,
readonly: bool, readonly: bool,
log_dirty_pages: bool, log_dirty_pages: bool,
) -> UserMemoryRegion; ) -> Result<()>;
/// Creates a guest physical memory slot.
fn create_user_memory_region(&self, user_memory_region: UserMemoryRegion) -> Result<()>;
/// Removes a guest physical memory slot. /// Removes a guest physical memory slot.
fn remove_user_memory_region(&self, user_memory_region: UserMemoryRegion) -> Result<()>; ///
/// # Safety
///
/// `[userspace_addr, userspace_addr + memory_size)` must be valid memory,
unsafe fn remove_user_memory_region(
&self,
slot: u32,
guest_phys_addr: u64,
memory_size: u64,
userspace_addr: u64,
readonly: bool,
log_dirty_pages: bool,
) -> Result<()>;
/// Returns the preferred CPU target type which can be emulated by KVM on underlying host. /// Returns the preferred CPU target type which can be emulated by KVM on underlying host.
#[cfg(target_arch = "aarch64")] #[cfg(target_arch = "aarch64")]
fn get_preferred_target(&self, kvi: &mut crate::VcpuInit) -> Result<()>; fn get_preferred_target(&self, kvi: &mut crate::VcpuInit) -> Result<()>;

View File

@@ -1663,21 +1663,22 @@ impl VfioPciDevice {
host_addr: host_addr as u64, host_addr: host_addr as u64,
}; };
// SAFETY: host_addr was allocated by mmap() and points to size
// bytes of memory.
unsafe {
self.vm.create_user_memory_region(
user_memory_region.slot,
user_memory_region.start,
user_memory_region.size,
user_memory_region.host_addr,
false,
false,
)
}
.map_err(VfioPciError::CreateUserMemoryRegion)?;
region.user_memory_regions.push(user_memory_region); region.user_memory_regions.push(user_memory_region);
let mem_region = self.vm.make_user_memory_region(
user_memory_region.slot,
user_memory_region.start,
user_memory_region.size,
user_memory_region.host_addr,
false,
false,
);
self.vm
.create_user_memory_region(mem_region)
.map_err(VfioPciError::CreateUserMemoryRegion)?;
if !self.iommu_attached { if !self.iommu_attached {
self.container self.container
.vfio_dma_map( .vfio_dma_map(
@@ -1714,16 +1715,18 @@ impl VfioPciDevice {
} }
// Remove region // Remove region
let r = self.vm.make_user_memory_region( // SAFETY: only valid entries are added to the user_memory_regions field
user_memory_region.slot, // of the entries of self.common.mmio_regions.
user_memory_region.start, if let Err(e) = unsafe {
user_memory_region.size, self.vm.remove_user_memory_region(
user_memory_region.host_addr, user_memory_region.slot,
false, user_memory_region.start,
false, user_memory_region.size,
); user_memory_region.host_addr,
false,
if let Err(e) = self.vm.remove_user_memory_region(r) { false,
)
} {
error!("Could not remove the userspace memory region: {e}"); error!("Could not remove the userspace memory region: {e}");
} }
@@ -1898,20 +1901,19 @@ impl PciDevice for VfioPciDevice {
user_memory_region.start, user_memory_region.size, e user_memory_region.start, user_memory_region.size, e
); );
} }
// Remove old region // Remove old region
let old_mem_region = self.vm.make_user_memory_region( // SAFETY: user_memory_regions has valid entries
user_memory_region.slot, unsafe {
user_memory_region.start, self.vm.remove_user_memory_region(
user_memory_region.size, user_memory_region.slot,
user_memory_region.host_addr, user_memory_region.start,
false, user_memory_region.size,
false, user_memory_region.host_addr,
); false,
false,
self.vm )
.remove_user_memory_region(old_mem_region) }
.map_err(io::Error::other)?; .map_err(io::Error::other)?;
// Update the user memory region with the correct start address. // Update the user memory region with the correct start address.
if new_base > old_base { if new_base > old_base {
@@ -1921,18 +1923,18 @@ impl PciDevice for VfioPciDevice {
} }
// Insert new region // Insert new region
let new_mem_region = self.vm.make_user_memory_region( // SAFETY: mmio_regions only has valid values
user_memory_region.slot, unsafe {
user_memory_region.start, self.vm.create_user_memory_region(
user_memory_region.size, user_memory_region.slot,
user_memory_region.host_addr, user_memory_region.start,
false, user_memory_region.size,
false, user_memory_region.host_addr,
); false,
false,
self.vm )
.create_user_memory_region(new_mem_region) }
.map_err(io::Error::other)?; .map_err(io::Error::other)?;
// Map the moved mmio region to vfio container // Map the moved mmio region to vfio container
if !self.iommu_attached { if !self.iommu_attached {

View File

@@ -110,7 +110,10 @@ impl VfioUserPciDevice {
}) })
} }
pub fn map_mmio_regions(&mut self) -> Result<(), VfioUserPciDeviceError> { /// # Safety
///
/// Not known yet (TODO)
pub unsafe fn map_mmio_regions(&mut self) -> Result<(), VfioUserPciDeviceError> {
for mmio_region in &mut self.common.mmio_regions { for mmio_region in &mut self.common.mmio_regions {
let region_flags = self let region_flags = self
.client .client
@@ -184,20 +187,21 @@ impl VfioUserPciDevice {
host_addr: host_addr as u64, host_addr: host_addr as u64,
}; };
// SAFETY: host_addr was just allocated with mmap()
// and points to size bytes of valid address.
unsafe {
self.vm.create_user_memory_region(
user_memory_region.slot,
user_memory_region.start,
user_memory_region.size,
user_memory_region.host_addr,
false,
false,
)
}
.map_err(VfioUserPciDeviceError::MapRegionGuest)?;
mmio_region.user_memory_regions.push(user_memory_region); mmio_region.user_memory_regions.push(user_memory_region);
let mem_region = self.vm.make_user_memory_region(
user_memory_region.slot,
user_memory_region.start,
user_memory_region.size,
user_memory_region.host_addr,
false,
false,
);
self.vm
.create_user_memory_region(mem_region)
.map_err(VfioUserPciDeviceError::MapRegionGuest)?;
} }
} }
} }
@@ -209,16 +213,17 @@ impl VfioUserPciDevice {
for mmio_region in self.common.mmio_regions.iter() { for mmio_region in self.common.mmio_regions.iter() {
for user_memory_region in mmio_region.user_memory_regions.iter() { for user_memory_region in mmio_region.user_memory_regions.iter() {
// Remove region // Remove region
let r = self.vm.make_user_memory_region( // SAFETY: only valid regions are in user_memory_regions
user_memory_region.slot, if let Err(e) = unsafe {
user_memory_region.start, self.vm.remove_user_memory_region(
user_memory_region.size, user_memory_region.slot,
user_memory_region.host_addr, user_memory_region.start,
false, user_memory_region.size,
false, user_memory_region.host_addr,
); false,
false,
if let Err(e) = self.vm.remove_user_memory_region(r) { )
} {
error!("Could not remove the userspace memory region: {e}"); error!("Could not remove the userspace memory region: {e}");
} }
@@ -455,18 +460,18 @@ impl PciDevice for VfioUserPciDevice {
for user_memory_region in mmio_region.user_memory_regions.iter_mut() { for user_memory_region in mmio_region.user_memory_regions.iter_mut() {
// Remove old region // Remove old region
let old_region = self.vm.make_user_memory_region( // SAFETY: only valid regions are in user_memory_regions
user_memory_region.slot, unsafe {
user_memory_region.start, self.vm.remove_user_memory_region(
user_memory_region.size, user_memory_region.slot,
user_memory_region.host_addr, user_memory_region.start,
false, user_memory_region.size,
false, user_memory_region.host_addr,
); false,
false,
self.vm )
.remove_user_memory_region(old_region) }
.map_err(std::io::Error::other)?; .map_err(std::io::Error::other)?;
// Update the user memory region with the correct start address. // Update the user memory region with the correct start address.
if new_base > old_base { if new_base > old_base {
@@ -476,18 +481,18 @@ impl PciDevice for VfioUserPciDevice {
} }
// Insert new region // Insert new region
let new_region = self.vm.make_user_memory_region( // SAFETY: only valid regions are in user_memory_regions
user_memory_region.slot, unsafe {
user_memory_region.start, self.vm.create_user_memory_region(
user_memory_region.size, user_memory_region.slot,
user_memory_region.host_addr, user_memory_region.start,
false, user_memory_region.size,
false, user_memory_region.host_addr,
); false,
false,
self.vm )
.create_user_memory_region(new_region) }
.map_err(std::io::Error::other)?; .map_err(std::io::Error::other)?;
} }
info!("Moved bar 0x{old_base:x} -> 0x{new_base:x}"); info!("Moved bar 0x{old_base:x} -> 0x{new_base:x}");
} }

View File

@@ -356,7 +356,7 @@ pub fn start_dbus_thread(
apply_filter(&api_seccomp_filter) apply_filter(&api_seccomp_filter)
.map_err(VmmError::ApplySeccompFilter) .map_err(VmmError::ApplySeccompFilter)
.map_err(|e| { .map_err(|e| {
error!("Error applying seccomp filter: {:?}", e); error!("Error applying seccomp filter: {e:?}");
exit_evt.write(1).ok(); exit_evt.write(1).ok();
e e
})?; })?;
@@ -383,7 +383,7 @@ pub fn start_dbus_thread(
} }
} }
} }
}) });
})) }))
.map_err(|_| { .map_err(|_| {
error!("dbus-api thread panicked"); error!("dbus-api thread panicked");

View File

@@ -2521,7 +2521,7 @@ impl VmConfig {
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
if self.debug_console.mode == ConsoleOutputMode::Tty { if self.debug_console.mode == ConsoleOutputMode::Tty {
tty_consoles.push("debug-console"); tty_consoles.push("debug-console");
}; }
if tty_consoles.len() > 1 { if tty_consoles.len() > 1 {
warn!("Using TTY output for multiple consoles: {tty_consoles:?}"); warn!("Using TTY output for multiple consoles: {tty_consoles:?}");
} }

View File

@@ -817,32 +817,43 @@ impl DeviceRelocation for AddressManager {
if let Some(mut shm_regions) = virtio_dev.get_shm_regions() if let Some(mut shm_regions) = virtio_dev.get_shm_regions()
&& shm_regions.addr.raw_value() == old_base && shm_regions.addr.raw_value() == old_base
{ {
let mem_region = self.vm.make_user_memory_region( // SAFETY: TODO what are the invariants here?
shm_regions.mem_slot, unsafe {
old_base, // Remove old mapping
shm_regions.len, self.vm
shm_regions.host_addr, .remove_user_memory_region(
false, shm_regions.mem_slot,
false, old_base,
); shm_regions.len,
shm_regions.host_addr,
false,
false,
)
.map_err(|e| {
io::Error::other(format!(
"failed to remove user memory region: {e:?}"
))
})?;
}
self.vm.remove_user_memory_region(mem_region).map_err(|e| { // SAFETY: TODO what are the invariants here?
io::Error::other(format!("failed to remove user memory region: {e:?}")) unsafe {
})?; // Create new mapping by inserting new region to KVM.
self.vm
// Create new mapping by inserting new region to KVM. .create_user_memory_region(
let mem_region = self.vm.make_user_memory_region( shm_regions.mem_slot,
shm_regions.mem_slot, new_base,
new_base, shm_regions.len,
shm_regions.len, shm_regions.host_addr,
shm_regions.host_addr, false,
false, false,
false, )
); .map_err(|e| {
io::Error::other(format!(
self.vm.create_user_memory_region(mem_region).map_err(|e| { "failed to create user memory regions: {e:?}"
io::Error::other(format!("failed to create user memory regions: {e:?}")) ))
})?; })?;
}
// Update shared memory regions to reflect the new mapping. // Update shared memory regions to reflect the new mapping.
shm_regions.addr = GuestAddress(new_base); shm_regions.addr = GuestAddress(new_base);
@@ -3240,12 +3251,14 @@ impl DeviceManager {
.map_err(DeviceManagerError::NewMmapRegion)?; .map_err(DeviceManagerError::NewMmapRegion)?;
let host_addr: u64 = mmap_region.as_ptr() as u64; let host_addr: u64 = mmap_region.as_ptr() as u64;
let mem_slot = self // SAFETY: host_addr points to region_size bytes of mmap-allocated memory.
.memory_manager let mem_slot = unsafe {
.lock() self.memory_manager
.unwrap() .lock()
.create_userspace_mapping(region_base, region_size, host_addr, false, false, false) .unwrap()
.map_err(DeviceManagerError::MemoryManager)?; .create_userspace_mapping(region_base, region_size, host_addr, false, false, false)
.map_err(DeviceManagerError::MemoryManager)
}?;
let mapping = UserspaceMapping { let mapping = UserspaceMapping {
host_addr, host_addr,
@@ -3997,13 +4010,16 @@ impl DeviceManager {
resources, resources,
)?; )?;
// SAFETY: TODO
// Note it is required to call 'add_pci_device()' in advance to have the list of // Note it is required to call 'add_pci_device()' in advance to have the list of
// mmio regions provisioned correctly // mmio regions provisioned correctly
vfio_user_pci_device unsafe {
.lock() vfio_user_pci_device
.unwrap() .lock()
.map_mmio_regions() .unwrap()
.map_err(DeviceManagerError::VfioUserMapRegion)?; .map_mmio_regions()
.map_err(DeviceManagerError::VfioUserMapRegion)
}?;
let mut node = device_node!(vfio_user_name, vfio_user_pci_device); let mut node = device_node!(vfio_user_name, vfio_user_pci_device);
@@ -4722,17 +4738,22 @@ impl DeviceManager {
// Shutdown and remove the underlying virtio-device if present // Shutdown and remove the underlying virtio-device if present
if let Some(virtio_device) = virtio_device { if let Some(virtio_device) = virtio_device {
for mapping in virtio_device.lock().unwrap().userspace_mappings() { for mapping in virtio_device.lock().unwrap().userspace_mappings() {
self.memory_manager // SAFETY: userspace_mappings only has valid mappings.
.lock() // TODO: do not rely on the correctness of all the code in this file
.unwrap() // for this to hold.
.remove_userspace_mapping( unsafe {
mapping.addr.raw_value(), self.memory_manager
mapping.len, .lock()
mapping.host_addr, .unwrap()
mapping.mergeable, .remove_userspace_mapping(
mapping.mem_slot, mapping.addr.raw_value(),
) mapping.len,
.map_err(DeviceManagerError::MemoryManager)?; mapping.host_addr,
mapping.mergeable,
mapping.mem_slot,
)
.map_err(DeviceManagerError::MemoryManager)
}?;
} }
virtio_device.lock().unwrap().shutdown(); virtio_device.lock().unwrap().shutdown();
@@ -4984,7 +5005,7 @@ impl IvshmemOps for IvshmemHandler {
) -> Result<(Arc<GuestRegionMmap>, UserspaceMapping), IvshmemError> { ) -> Result<(Arc<GuestRegionMmap>, UserspaceMapping), IvshmemError> {
info!("Creating ivshmem mem region at 0x{start_addr:x}"); info!("Creating ivshmem mem region at 0x{start_addr:x}");
let region = MemoryManager::create_ram_region( let region: Arc<GuestRegionMmap> = MemoryManager::create_ram_region(
&backing_file, &backing_file,
0, 0,
GuestAddress(start_addr), GuestAddress(start_addr),
@@ -4998,19 +5019,21 @@ impl IvshmemOps for IvshmemHandler {
false, false,
) )
.map_err(|_| IvshmemError::CreateUserMemoryRegion)?; .map_err(|_| IvshmemError::CreateUserMemoryRegion)?;
let mem_slot = self let mem_slot = {
.memory_manager let mut manager = self.memory_manager.lock().unwrap();
.lock() // SAFETY: guaranteed by GuestRegionMmap invariants
.unwrap() unsafe {
.create_userspace_mapping( manager.create_userspace_mapping(
region.start_addr().0, region.start_addr().0,
region.len(), region.len(),
region.as_ptr() as u64, region.as_ptr() as u64,
false, false,
false, false,
false, false,
) )
.map_err(|_| IvshmemError::CreateUserspaceMapping)?; }
}
.map_err(|_| IvshmemError::CreateUserspaceMapping)?;
let mapping = UserspaceMapping { let mapping = UserspaceMapping {
host_addr: region.as_ptr() as u64, host_addr: region.as_ptr() as u64,
mem_slot, mem_slot,
@@ -5022,17 +5045,18 @@ impl IvshmemOps for IvshmemHandler {
} }
fn unmap_ram_region(&mut self, mapping: UserspaceMapping) -> Result<(), IvshmemError> { fn unmap_ram_region(&mut self, mapping: UserspaceMapping) -> Result<(), IvshmemError> {
self.memory_manager let mut manager = self.memory_manager.lock().unwrap();
.lock() // SAFETY: UserspaceMapping is valid due to other code being correct
.unwrap() unsafe {
.remove_userspace_mapping( manager.remove_userspace_mapping(
mapping.addr.raw_value(), mapping.addr.raw_value(),
mapping.len, mapping.len,
mapping.host_addr, mapping.host_addr,
mapping.mergeable, mapping.mergeable,
mapping.mem_slot, mapping.mem_slot,
) )
.map_err(|_| IvshmemError::RemoveUserspaceMapping)?; }
.map_err(|_| IvshmemError::RemoveUserspaceMapping)?;
Ok(()) Ok(())
} }
} }

View File

@@ -1128,7 +1128,7 @@ impl Vmm {
return Err(MigratableError::MigrateSend(anyhow!( return Err(MigratableError::MigrateSend(anyhow!(
"Live Migration is not supported when TDX is enabled" "Live Migration is not supported when TDX is enabled"
))); )));
}; }
let amx = vm_config.lock().unwrap().cpus.features.amx; let amx = vm_config.lock().unwrap().cpus.features.amx;
let phys_bits = vm::physical_bits( let phys_bits = vm::physical_bits(
@@ -1265,7 +1265,7 @@ impl Vmm {
return Err(MigratableError::MigrateReceive(anyhow!( return Err(MigratableError::MigrateReceive(anyhow!(
"Live Migration is not supported when TDX is enabled" "Live Migration is not supported when TDX is enabled"
))); )));
}; }
// We check the `CPUID` compatibility of between the source vm and destination, which is // We check the `CPUID` compatibility of between the source vm and destination, which is
// mostly about feature compatibility. // mostly about feature compatibility.

View File

@@ -900,14 +900,18 @@ impl MemoryManager {
for (zone_id, regions) in list { for (zone_id, regions) in list {
for (region, virtio_mem) in regions { for (region, virtio_mem) in regions {
let slot = self.create_userspace_mapping( // SAFETY: regions only holds valid addresses.
region.start_addr().raw_value(), // TODO: encapsulate this unsafety in a small part of the file.
region.len(), let slot = unsafe {
region.as_ptr() as u64, self.create_userspace_mapping(
self.mergeable, region.start_addr().raw_value(),
false, region.len(),
self.log_dirty, region.as_ptr() as u64,
)?; self.mergeable,
false,
self.log_dirty,
)
}?;
let file_offset = if let Some(file_offset) = region.file_offset() { let file_offset = if let Some(file_offset) = region.file_offset() {
file_offset.start() file_offset.start()
@@ -958,18 +962,18 @@ impl MemoryManager {
arch::layout::UEFI_START, arch::layout::UEFI_START,
) )
.unwrap(); .unwrap();
let uefi_mem_region = self.vm.make_user_memory_region( unsafe {
uefi_mem_slot, self.vm
uefi_region.start_addr().raw_value(), .create_user_memory_region(
uefi_region.len(), uefi_mem_slot,
uefi_region.as_ptr() as u64, uefi_region.start_addr().raw_value(),
false, uefi_region.len(),
false, uefi_region.as_ptr() as u64,
); false,
self.vm false,
.create_user_memory_region(uefi_mem_region) )
.map_err(Error::CreateUefiFlash)?; .map_err(Error::CreateUefiFlash)?;
}
let uefi_flash = let uefi_flash =
GuestMemoryAtomic::new(GuestMemoryMmap::from_regions(vec![uefi_region]).unwrap()); GuestMemoryAtomic::new(GuestMemoryMmap::from_regions(vec![uefi_region]).unwrap());
@@ -1608,14 +1612,17 @@ impl MemoryManager {
)?; )?;
// Map it into the guest // Map it into the guest
let slot = self.create_userspace_mapping( // SAFETY: create_ram_region only produces valid mappings.
region.start_addr().0, let slot = unsafe {
region.len(), self.create_userspace_mapping(
region.as_ptr() as u64, region.start_addr().0,
self.mergeable, region.len(),
false, region.as_ptr() as u64,
self.log_dirty, self.mergeable,
)?; false,
self.log_dirty,
)
}?;
self.guest_ram_mappings.push(GuestRamMapping { self.guest_ram_mappings.push(GuestRamMapping {
gpa: region.start_addr().raw_value(), gpa: region.start_addr().raw_value(),
size: region.len(), size: region.len(),
@@ -1708,7 +1715,11 @@ impl MemoryManager {
self.memory_slot_allocator().next_memory_slot() self.memory_slot_allocator().next_memory_slot()
} }
pub fn create_userspace_mapping( /// # Safety
///
/// `userspace_addr` and `memory_size` must be and remain valid
/// until `remove_userspace_mapping` is called.
pub unsafe fn create_userspace_mapping(
&mut self, &mut self,
guest_phys_addr: u64, guest_phys_addr: u64,
memory_size: u64, memory_size: u64,
@@ -1718,22 +1729,24 @@ impl MemoryManager {
log_dirty: bool, log_dirty: bool,
) -> Result<u32, Error> { ) -> Result<u32, Error> {
let slot = self.allocate_memory_slot(); let slot = self.allocate_memory_slot();
let mem_region = self.vm.make_user_memory_region(
slot,
guest_phys_addr,
memory_size,
userspace_addr,
readonly,
log_dirty,
);
info!( info!(
"Creating userspace mapping: {guest_phys_addr:x} -> {userspace_addr:x} {memory_size:x}, slot {slot}" "Creating userspace mapping: {guest_phys_addr:x} -> {userspace_addr:x} {memory_size:x}, slot {slot}"
); );
self.vm // SAFETY: promised by caller
.create_user_memory_region(mem_region) unsafe {
.map_err(Error::CreateUserMemoryRegion)?; self.vm
.create_user_memory_region(
slot,
guest_phys_addr,
memory_size,
userspace_addr,
readonly,
log_dirty,
)
.map_err(Error::CreateUserMemoryRegion)?;
}
// SAFETY: the address and size are valid since the // SAFETY: the address and size are valid since the
// mmap succeeded. // mmap succeeded.
@@ -1781,7 +1794,16 @@ impl MemoryManager {
Ok(slot) Ok(slot)
} }
pub fn remove_userspace_mapping( /// # Safety
///
/// `userspace_addr` and `memory_size` must have previously been passed
/// to `create_userspace_mapping`.
///
/// # Errors
///
/// If this function fails there is no way to clean up resources and you
/// should probably crash the process.
pub unsafe fn remove_userspace_mapping(
&mut self, &mut self,
guest_phys_addr: u64, guest_phys_addr: u64,
memory_size: u64, memory_size: u64,
@@ -1789,18 +1811,19 @@ impl MemoryManager {
mergeable: bool, mergeable: bool,
slot: u32, slot: u32,
) -> Result<(), Error> { ) -> Result<(), Error> {
let mem_region = self.vm.make_user_memory_region( // SAFETY: The caller promises that the parameters are correct.
slot, unsafe {
guest_phys_addr, self.vm
memory_size, .remove_user_memory_region(
userspace_addr, slot,
false, /* readonly -- don't care */ guest_phys_addr,
false, /* log dirty */ memory_size,
); userspace_addr,
false, /* readonly -- don't care */
self.vm false, /* log dirty */
.remove_user_memory_region(mem_region) )
.map_err(Error::RemoveUserMemoryRegion)?; .map_err(Error::RemoveUserMemoryRegion)?;
}
// Mark the pages as unmergeable if there were previously marked as // Mark the pages as unmergeable if there were previously marked as
// mergeable. // mergeable.

View File

@@ -3464,17 +3464,18 @@ mod unit_tests {
.expect("new VM creation failed"); .expect("new VM creation failed");
for (index, region) in mem.iter().enumerate() { for (index, region) in mem.iter().enumerate() {
let mem_region = vm.make_user_memory_region( // SAFETY: inputs are valid
index as u32, unsafe {
region.start_addr().raw_value(), vm.create_user_memory_region(
region.len(), index as u32,
region.as_ptr() as u64, region.start_addr().raw_value(),
false, region.len(),
false, region.as_ptr() as u64,
); false,
false,
vm.create_user_memory_region(mem_region) )
.expect("Cannot configure guest memory"); .expect("Cannot configure guest memory");
}
} }
mem.write_slice(&code, load_addr) mem.write_slice(&code, load_addr)
.expect("Writing code to memory failed"); .expect("Writing code to memory failed");
@@ -3574,3 +3575,71 @@ mod unit_tests {
.unwrap(); .unwrap();
} }
} }
#[cfg(all(feature = "kvm", target_arch = "x86_64"))]
#[test]
pub fn test_vm() {
use hypervisor::VmExit;
use vm_memory::{Address, GuestMemory, GuestMemoryRegion};
// This example based on https://lwn.net/Articles/658511/
let code = [
0xba, 0xf8, 0x03, /* mov $0x3f8, %dx */
0x00, 0xd8, /* add %bl, %al */
0x04, b'0', /* add $'0', %al */
0xee, /* out %al, (%dx) */
0xb0, b'\n', /* mov $'\n', %al */
0xee, /* out %al, (%dx) */
0xf4, /* hlt */
];
let mem_size = 0x1000;
let load_addr = GuestAddress(0x1000);
let mem = GuestMemoryMmap::from_ranges(&[(load_addr, mem_size)]).unwrap();
let hv = hypervisor::new().unwrap();
let vm = hv
.create_vm(HypervisorVmConfig::default())
.expect("new VM creation failed");
for (index, region) in mem.iter().enumerate() {
// SAFETY: parameters are correct
unsafe {
vm.create_user_memory_region(
index as u32,
region.start_addr().raw_value(),
region.len(),
region.as_ptr() as u64,
false,
false,
)
.expect("Cannot configure guest memory");
}
}
mem.write_slice(&code, load_addr)
.expect("Writing code to memory failed");
let mut vcpu = vm.create_vcpu(0, None).expect("new Vcpu failed");
let mut vcpu_sregs = vcpu.get_sregs().expect("get sregs failed");
vcpu_sregs.cs.base = 0;
vcpu_sregs.cs.selector = 0;
vcpu.set_sregs(&vcpu_sregs).expect("set sregs failed");
let mut vcpu_regs = vcpu.get_regs().expect("get regs failed");
vcpu_regs.set_rip(0x1000);
vcpu_regs.set_rax(2);
vcpu_regs.set_rbx(3);
vcpu_regs.set_rflags(2);
vcpu.set_regs(&vcpu_regs).expect("set regs failed");
loop {
match vcpu.run().expect("run failed") {
VmExit::Reset => {
println!("HLT");
break;
}
VmExit::Ignore => {}
r => panic!("unexpected exit reason: {r:?}"),
}
}
}