mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
misc: do not use u64 to represent host pointers
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 interpret them as userspace addresses. Therefore, passing bad u64 values would cause memory disclosure or corruption. Fix the bug by using usize and pointer types as appropriate. To make soundness of the code easier to reason about, the PCI code gains a new MmapRegion abstraction that ensures the validity of pointers. The rest of the code already has an MmapRegion abstraction it can use. To avoid having to reason about whether something is keeping the MmapRegion alive, reference counting is added. MmapRegion cannot hold references to other objects, so the reference counting cannot introduce cycles. Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
This commit is contained in:
committed by
Rob Bradford
parent
fdc19ad85e
commit
42522a88c0
+121
-88
@@ -6,9 +6,9 @@
|
||||
use std::any::Any;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::io;
|
||||
use std::os::fd::BorrowedFd;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::path::PathBuf;
|
||||
use std::ptr::null_mut;
|
||||
use std::sync::{Arc, Barrier, Mutex};
|
||||
|
||||
use anyhow::anyhow;
|
||||
@@ -34,6 +34,7 @@ use vm_memory::{Address, GuestAddress, GuestAddressSpace, GuestMemory, GuestUsiz
|
||||
use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable};
|
||||
use vmm_sys_util::eventfd::EventFd;
|
||||
|
||||
use crate::mmap::MmapRegion;
|
||||
use crate::msi::{MSI_CONFIG_ID, MsiConfigState};
|
||||
use crate::msix::MsixConfigState;
|
||||
use crate::{
|
||||
@@ -258,12 +259,11 @@ impl Interrupt {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Clone)]
|
||||
pub struct UserMemoryRegion {
|
||||
pub slot: u32,
|
||||
pub start: u64,
|
||||
pub size: u64,
|
||||
pub host_addr: u64,
|
||||
pub mapping: Arc<crate::mmap::MmapRegion>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -275,12 +275,17 @@ pub struct MmioRegion {
|
||||
pub(crate) user_memory_regions: Vec<UserMemoryRegion>,
|
||||
}
|
||||
|
||||
trait MmioRegionRange {
|
||||
/// # Safety
|
||||
///
|
||||
/// [`Self::find_user_address`] must always either return `Err`
|
||||
/// or a pointer to `size` bytes of valid memory.
|
||||
unsafe trait MmioRegionRange {
|
||||
fn check_range(&self, guest_addr: u64, size: u64) -> bool;
|
||||
fn find_user_address(&self, guest_addr: u64) -> Result<u64, io::Error>;
|
||||
fn find_user_address(&self, guest_addr: u64, size: u64) -> Result<*mut u8, io::Error>;
|
||||
}
|
||||
|
||||
impl MmioRegionRange for Vec<MmioRegion> {
|
||||
// SAFETY: See the comment in `find_user_address`.
|
||||
unsafe impl MmioRegionRange for Vec<MmioRegion> {
|
||||
// Check if a guest address is within the range of mmio regions
|
||||
fn check_range(&self, guest_addr: u64, size: u64) -> bool {
|
||||
for region in self.iter() {
|
||||
@@ -298,14 +303,33 @@ impl MmioRegionRange for Vec<MmioRegion> {
|
||||
}
|
||||
|
||||
// Locate the user region address for a guest address within all mmio regions
|
||||
fn find_user_address(&self, guest_addr: u64) -> Result<u64, io::Error> {
|
||||
fn find_user_address(&self, guest_addr: u64, size: u64) -> Result<*mut u8, io::Error> {
|
||||
for region in self.iter() {
|
||||
for user_region in region.user_memory_regions.iter() {
|
||||
if guest_addr >= user_region.start
|
||||
&& guest_addr < user_region.start + user_region.size
|
||||
{
|
||||
return Ok(user_region.host_addr + (guest_addr - user_region.start));
|
||||
let mapping: &MmapRegion = &user_region.mapping;
|
||||
let start: u64 = user_region.start;
|
||||
let len: u64 = mapping.len().try_into().unwrap();
|
||||
// See if the guest address is inside the region.
|
||||
let Some(offset_from_start) = guest_addr.checked_sub(start) else {
|
||||
continue;
|
||||
};
|
||||
if offset_from_start >= len {
|
||||
continue;
|
||||
}
|
||||
// Check that the size is in bounds.
|
||||
// This enforces the invariant promised by implementing MmioRegionRange.
|
||||
assert!(
|
||||
size <= len - offset_from_start,
|
||||
"Attempt to read {size} bytes at offset {offset_from_start} into \
|
||||
a region of size {len}"
|
||||
);
|
||||
// SAFETY: MmapRegion guarantees that mapping.addr points to at least
|
||||
// mapping.len() bytes of valid memory. offset_from_start is equal
|
||||
// to guest_addr - start, which was checked to be less than mapping.len().
|
||||
// Therefore, the returned pointer is still in the range of valid memory.
|
||||
// Also, since mapping.len() fit in usize, offset_from_start must as well,
|
||||
// so the cast is safe.
|
||||
return Ok(unsafe { mapping.addr().add(offset_from_start as usize) });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1585,7 +1609,8 @@ impl VfioPciDevice {
|
||||
/// * `mem_slot` - The closure to return a memory slot.
|
||||
pub fn map_mmio_regions(&mut self) -> Result<(), VfioPciError> {
|
||||
let fd = self.device.as_raw_fd();
|
||||
|
||||
// SAFETY: fd is guaranteed valid
|
||||
let fd = unsafe { BorrowedFd::borrow_raw(fd) };
|
||||
for region in self.common.mmio_regions.iter_mut() {
|
||||
let region_flags = self.device.get_region_flags(region.index);
|
||||
if region_flags & VFIO_REGION_INFO_FLAG_MMAP != 0 {
|
||||
@@ -1625,71 +1650,72 @@ impl VfioPciDevice {
|
||||
self.common.interrupt.msix.as_ref(),
|
||||
)?;
|
||||
|
||||
for area in sparse_areas.iter() {
|
||||
// SAFETY: FFI call with correct arguments
|
||||
let host_addr = unsafe {
|
||||
libc::mmap(
|
||||
null_mut(),
|
||||
area.size as usize,
|
||||
prot,
|
||||
libc::MAP_SHARED,
|
||||
fd,
|
||||
mmap_offset as libc::off_t + area.offset as libc::off_t,
|
||||
)
|
||||
};
|
||||
|
||||
if std::ptr::eq(host_addr, libc::MAP_FAILED) {
|
||||
for area in &sparse_areas {
|
||||
if !is_page_size_aligned(area.size) || !is_page_size_aligned(area.offset) {
|
||||
error!(
|
||||
"Could not mmap sparse area (offset = 0x{:x}, size = 0x{:x}): {}",
|
||||
area.offset,
|
||||
area.size,
|
||||
std::io::Error::last_os_error()
|
||||
"Could not mmap sparse area that is not page size aligned \
|
||||
(offset = 0x{:x}, size = 0x{:x})",
|
||||
area.offset, area.size
|
||||
);
|
||||
return Err(VfioPciError::MmapArea);
|
||||
}
|
||||
}
|
||||
|
||||
if !is_page_size_aligned(area.size) || !is_page_size_aligned(area.offset) {
|
||||
warn!(
|
||||
"Could not mmap sparse area that is not page size aligned (offset = 0x{:x}, size = 0x{:x})",
|
||||
area.offset, area.size,
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
for area in sparse_areas.iter() {
|
||||
let mapping = match MmapRegion::mmap(
|
||||
area.size,
|
||||
prot,
|
||||
fd,
|
||||
mmap_offset,
|
||||
area.offset,
|
||||
) {
|
||||
Ok(mapping) => mapping,
|
||||
Err(_) => {
|
||||
error!(
|
||||
"Could not mmap sparse area (offset = 0x{:x}, size = 0x{:x}): {}",
|
||||
mmap_offset,
|
||||
area.size,
|
||||
std::io::Error::last_os_error()
|
||||
);
|
||||
return Err(VfioPciError::MmapArea);
|
||||
}
|
||||
};
|
||||
|
||||
let user_memory_region = UserMemoryRegion {
|
||||
slot: self.memory_slot_allocator.next_memory_slot(),
|
||||
start: region.start.0 + area.offset,
|
||||
size: area.size,
|
||||
host_addr: host_addr as u64,
|
||||
mapping: Arc::new(mapping),
|
||||
};
|
||||
|
||||
// SAFETY: host_addr was allocated by mmap() and points to size
|
||||
// bytes of memory.
|
||||
// SAFETY: MmapRegion invariants guarantee that
|
||||
// user_memory_region.mapping.addr() points to
|
||||
// user_memory_region.mapping.len() bytes of
|
||||
// valid memory that will only be unmapped with munmap().
|
||||
unsafe {
|
||||
self.vm.create_user_memory_region(
|
||||
user_memory_region.slot,
|
||||
user_memory_region.start,
|
||||
user_memory_region.size,
|
||||
user_memory_region.host_addr,
|
||||
user_memory_region.mapping.len(),
|
||||
user_memory_region.mapping.addr(),
|
||||
false,
|
||||
false,
|
||||
)
|
||||
}
|
||||
.map_err(VfioPciError::CreateUserMemoryRegion)?;
|
||||
|
||||
region.user_memory_regions.push(user_memory_region);
|
||||
|
||||
if !self.iommu_attached {
|
||||
self.container
|
||||
.vfio_dma_map(
|
||||
user_memory_region.start,
|
||||
user_memory_region.size,
|
||||
user_memory_region.host_addr,
|
||||
user_memory_region.mapping.len().try_into().unwrap(),
|
||||
(user_memory_region.mapping.addr() as usize)
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
)
|
||||
.map_err(|e| {
|
||||
VfioPciError::DmaMap(e, self.device_path.clone(), self.bdf)
|
||||
})?;
|
||||
}
|
||||
region.user_memory_regions.push(user_memory_region);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1698,19 +1724,21 @@ impl VfioPciDevice {
|
||||
}
|
||||
|
||||
pub fn unmap_mmio_regions(&mut self) {
|
||||
for region in self.common.mmio_regions.iter() {
|
||||
for user_memory_region in region.user_memory_regions.iter() {
|
||||
for region in self.common.mmio_regions.iter_mut() {
|
||||
for user_memory_region in region.user_memory_regions.drain(..) {
|
||||
let len = user_memory_region.mapping.len();
|
||||
let host_addr = user_memory_region.mapping.addr();
|
||||
// Unmap from vfio container
|
||||
if !self.iommu_attached
|
||||
&& let Err(e) = self
|
||||
.container
|
||||
.vfio_dma_unmap(user_memory_region.start, user_memory_region.size)
|
||||
.vfio_dma_unmap(user_memory_region.start, len.try_into().unwrap())
|
||||
.map_err(|e| VfioPciError::DmaUnmap(e, self.device_path.clone(), self.bdf))
|
||||
{
|
||||
error!(
|
||||
"Could not unmap mmio region from vfio container: \
|
||||
iova 0x{:x}, size 0x{:x}: {}, ",
|
||||
user_memory_region.start, user_memory_region.size, e
|
||||
user_memory_region.start, len, e
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1721,8 +1749,8 @@ impl VfioPciDevice {
|
||||
self.vm.remove_user_memory_region(
|
||||
user_memory_region.slot,
|
||||
user_memory_region.start,
|
||||
user_memory_region.size,
|
||||
user_memory_region.host_addr,
|
||||
len,
|
||||
host_addr,
|
||||
false,
|
||||
false,
|
||||
)
|
||||
@@ -1732,21 +1760,6 @@ impl VfioPciDevice {
|
||||
|
||||
self.memory_slot_allocator
|
||||
.free_memory_slot(user_memory_region.slot);
|
||||
|
||||
// SAFETY: FFI call with correct arguments
|
||||
let ret = unsafe {
|
||||
libc::munmap(
|
||||
user_memory_region.host_addr as *mut libc::c_void,
|
||||
user_memory_region.size as usize,
|
||||
)
|
||||
};
|
||||
if ret != 0 {
|
||||
error!(
|
||||
"Could not unmap region {}, error:{}",
|
||||
region.index,
|
||||
io::Error::last_os_error()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1886,29 +1899,31 @@ impl PciDevice for VfioPciDevice {
|
||||
region.start = GuestAddress(new_base);
|
||||
|
||||
for user_memory_region in region.user_memory_regions.iter_mut() {
|
||||
let len = user_memory_region.mapping.len();
|
||||
let host_addr = user_memory_region.mapping.addr();
|
||||
// Unmap the old MMIO region from vfio container
|
||||
if !self.iommu_attached
|
||||
&& let Err(e) = self
|
||||
.container
|
||||
.vfio_dma_unmap(user_memory_region.start, user_memory_region.size)
|
||||
.vfio_dma_unmap(user_memory_region.start, len.try_into().unwrap())
|
||||
.map_err(|e| {
|
||||
VfioPciError::DmaUnmap(e, self.device_path.clone(), self.bdf)
|
||||
})
|
||||
{
|
||||
error!(
|
||||
"Could not unmap mmio region from vfio container: \
|
||||
iova 0x{:x}, size 0x{:x}: {}, ",
|
||||
user_memory_region.start, user_memory_region.size, e
|
||||
iova 0x{:x}, size 0x{:x}: {}, ",
|
||||
user_memory_region.start, len, e
|
||||
);
|
||||
}
|
||||
// Remove old region
|
||||
// SAFETY: user_memory_regions has valid entries
|
||||
// SAFETY: validity of len and host_addr guaranteed by hypervisor::mmap::MmapRegion
|
||||
unsafe {
|
||||
self.vm.remove_user_memory_region(
|
||||
user_memory_region.slot,
|
||||
user_memory_region.start,
|
||||
user_memory_region.size,
|
||||
user_memory_region.host_addr,
|
||||
len,
|
||||
host_addr,
|
||||
false,
|
||||
false,
|
||||
)
|
||||
@@ -1923,13 +1938,13 @@ impl PciDevice for VfioPciDevice {
|
||||
}
|
||||
|
||||
// Insert new region
|
||||
// SAFETY: mmio_regions only has valid values
|
||||
// SAFETY: validity of len and host_addr guaranteed by hypervisor::mmap::MmapRegion
|
||||
unsafe {
|
||||
self.vm.create_user_memory_region(
|
||||
user_memory_region.slot,
|
||||
user_memory_region.start,
|
||||
user_memory_region.size,
|
||||
user_memory_region.host_addr,
|
||||
len,
|
||||
host_addr,
|
||||
false,
|
||||
false,
|
||||
)
|
||||
@@ -1941,8 +1956,8 @@ impl PciDevice for VfioPciDevice {
|
||||
self.container
|
||||
.vfio_dma_map(
|
||||
user_memory_region.start,
|
||||
user_memory_region.size,
|
||||
user_memory_region.host_addr,
|
||||
len.try_into().unwrap(),
|
||||
(host_addr as usize).try_into().unwrap(),
|
||||
)
|
||||
.map_err(|e| {
|
||||
VfioPciError::DmaMap(e, self.device_path.clone(), self.bdf)
|
||||
@@ -1950,8 +1965,8 @@ impl PciDevice for VfioPciDevice {
|
||||
.map_err(|e| {
|
||||
io::Error::other(format!(
|
||||
"Could not map mmio region to vfio container: \
|
||||
iova 0x{:x}, size 0x{:x}: {}, ",
|
||||
user_memory_region.start, user_memory_region.size, e
|
||||
iova 0x{:x}, size 0x{:x}: {}, ",
|
||||
user_memory_region.start, len, e
|
||||
))
|
||||
})?;
|
||||
}
|
||||
@@ -1987,6 +2002,7 @@ impl Snapshottable for VfioPciDevice {
|
||||
Ok(vfio_pci_dev_snapshot)
|
||||
}
|
||||
}
|
||||
|
||||
impl Transportable for VfioPciDevice {}
|
||||
impl Migratable for VfioPciDevice {}
|
||||
|
||||
@@ -2020,11 +2036,17 @@ impl<M: GuestAddressSpace> VfioDmaMapping<M> {
|
||||
|
||||
impl<M: GuestAddressSpace + Sync + Send> ExternalDmaMapping for VfioDmaMapping<M> {
|
||||
fn map(&self, iova: u64, gpa: u64, size: u64) -> std::result::Result<(), io::Error> {
|
||||
let Ok(usize_size): Result<usize, _> = size.try_into() else {
|
||||
return Err(io::Error::other(format!("size {size} overflows usize")));
|
||||
};
|
||||
let mem = self.memory.memory();
|
||||
let guest_addr = GuestAddress(gpa);
|
||||
let user_addr = if mem.check_range(guest_addr, size as usize) {
|
||||
match mem.get_host_address(guest_addr) {
|
||||
Ok(t) => t as u64,
|
||||
let user_addr = if mem.check_range(guest_addr, usize_size) {
|
||||
match mem.get_slice(guest_addr, usize_size) {
|
||||
Ok(t) => {
|
||||
assert!(t.len() >= usize_size);
|
||||
Ok(t.ptr_guard_mut())
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(io::Error::other(format!(
|
||||
"unable to retrieve user address for gpa 0x{gpa:x} from guest memory region: {e}"
|
||||
@@ -2032,15 +2054,26 @@ impl<M: GuestAddressSpace + Sync + Send> ExternalDmaMapping for VfioDmaMapping<M
|
||||
}
|
||||
}
|
||||
} else if self.mmio_regions.lock().unwrap().check_range(gpa, size) {
|
||||
self.mmio_regions.lock().unwrap().find_user_address(gpa)?
|
||||
Err(self
|
||||
.mmio_regions
|
||||
.lock()
|
||||
.unwrap()
|
||||
.find_user_address(gpa, size)?)
|
||||
} else {
|
||||
return Err(io::Error::other(format!(
|
||||
"failed to locate guest address 0x{gpa:x} in guest memory"
|
||||
)));
|
||||
};
|
||||
let user_addr = match user_addr {
|
||||
Ok(p) => p.as_ptr(),
|
||||
Err(p) => p,
|
||||
};
|
||||
|
||||
// SAFETY: find_user_address and GuestMemory::get_slice() guarantee that
|
||||
// the returned pointer is valid for up to `usize_size` bytes.
|
||||
// `usize_size` is always equal to `size` due to the above `try_into()` call.
|
||||
self.container
|
||||
.vfio_dma_map(iova, size, user_addr)
|
||||
.vfio_dma_map(iova, size, (user_addr as usize).try_into().unwrap())
|
||||
.map_err(|e| {
|
||||
io::Error::other(format!(
|
||||
"failed to map memory for VFIO container, \
|
||||
|
||||
Reference in New Issue
Block a user