Skip to content

Commit 4b8da64

Browse files
committed
Mark VfioContainer::vfio_dma_map as unsafe
vfio_syscall::map_dma causes the kernel to make an arbitrary address accessible for DMA by a device the guest typically controls. This is unsafe, as it can change memory that Rust assumes is immutable. I found this through a review of Cloud Hypervisor [1], where I saw a safe function that took a host address cast to u64 as an argument and accessed that address. It turns out that this function was the source of the unsoundness. Signed-off-by: Demi Marie Obenour <[email protected]>
1 parent 6bc3c50 commit 4b8da64

File tree

3 files changed

+86
-22
lines changed

3 files changed

+86
-22
lines changed

vfio-ioctls/CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
# Upcoming Release
2+
3+
## Changed
4+
5+
- Functions that map memory into the VFIO device are now `unsafe`.
6+
The caller of these functions are responsible for enforcing
7+
various complex, documented invariants. These functions were
8+
never actually safe to begin with, but in the past they had
9+
(incorrectly) not been marked as `unsafe`.
10+
11+
In the future a high-level safe API will be provided that avoids
12+
these requirements at the cost of some flexibility.
13+
114
# [v0.5.0]
215

316
## Changed

vfio-ioctls/src/vfio_device.rs

Lines changed: 68 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
55

66
use std::collections::HashMap;
7+
use std::convert::{TryFrom as _, TryInto as _};
78
use std::ffi::CString;
89
use std::fs::{File, OpenOptions};
910
use std::mem::{self, ManuallyDrop};
@@ -279,16 +280,36 @@ impl VfioContainer {
279280
/// Map a region of guest memory regions into the vfio container's iommu table.
280281
///
281282
/// # Parameters
283+
///
282284
/// * iova: IO virtual address to mapping the memory.
283285
/// * size: size of the memory region.
284286
/// * user_addr: host virtual address for the guest memory region to map.
285-
pub fn vfio_dma_map(&self, iova: u64, size: u64, user_addr: u64) -> Result<()> {
287+
///
288+
/// # Safety
289+
///
290+
/// Until [`Self::vfio_dma_unmap`] is successfully called with identical
291+
/// values for iova and size, or until the entire range of `[user_addr..user_addr+size]`
292+
/// has been unmapped with successful calls to `munmap` or replaced with successful calls
293+
/// to `mmap(MAP_FIXED)`, it is not safe to use any of the address range in
294+
/// `[user_addr..user_addr+size]` for almost any purpose. The only permitted purposes are
295+
///
296+
/// - Atomic and/or volatile operations.
297+
/// - Assignment to a guest.
298+
/// - Sharing with another process.
299+
/// - Passing a raw pointer to functions that only do one of the above things.
300+
///
301+
/// In particular, creating a Rust reference to this memory is instant undefined behavior
302+
/// due to the Rust aliasing rules. It is also undefined behavior to call this function if
303+
/// a Rust reference to this memory is live.
304+
pub unsafe fn vfio_dma_map(&self, iova: u64, size: usize, user_addr: *mut u8) -> Result<()> {
305+
const _: () = assert!(mem::size_of::<u64>() >= mem::size_of::<*mut u8>());
306+
const _: () = assert!(mem::size_of::<u64>() >= mem::size_of::<usize>());
286307
let dma_map = vfio_iommu_type1_dma_map {
287308
argsz: mem::size_of::<vfio_iommu_type1_dma_map>() as u32,
288309
flags: VFIO_DMA_MAP_FLAG_READ | VFIO_DMA_MAP_FLAG_WRITE,
289-
vaddr: user_addr,
310+
vaddr: (user_addr as usize).try_into().unwrap(),
290311
iova,
291-
size,
312+
size: size.try_into().unwrap(),
292313
};
293314

294315
vfio_syscall::map_dma(self, &dma_map)
@@ -299,16 +320,16 @@ impl VfioContainer {
299320
/// # Parameters
300321
/// * iova: IO virtual address to mapping the memory.
301322
/// * size: size of the memory region.
302-
pub fn vfio_dma_unmap(&self, iova: u64, size: u64) -> Result<()> {
323+
pub fn vfio_dma_unmap(&self, iova: u64, size: usize) -> Result<()> {
303324
let mut dma_unmap = vfio_iommu_type1_dma_unmap {
304325
argsz: mem::size_of::<vfio_iommu_type1_dma_unmap>() as u32,
305326
flags: 0,
306327
iova,
307-
size,
328+
size: size.try_into().unwrap(),
308329
};
309330

310331
vfio_syscall::unmap_dma(self, &mut dma_unmap)?;
311-
if dma_unmap.size != size {
332+
if dma_unmap.size != u64::try_from(size).unwrap() {
312333
return Err(VfioError::InvalidDmaUnmapSize);
313334
}
314335

@@ -319,29 +340,54 @@ impl VfioContainer {
319340
///
320341
/// # Parameters
321342
/// * mem: pinned guest memory which could be accessed by devices binding to the container.
322-
pub fn vfio_map_guest_memory<M: GuestMemory>(&self, mem: &M) -> Result<()> {
343+
///
344+
/// # Safety
345+
///
346+
/// You have to promise that the requirements of [`Self::vfio_dma_map`] are upheld for each
347+
/// of the regions. A future version of the crate will ensure that they are upheld by having
348+
/// `vfio-ioctls` own a reference to the memory mapped into the guest. You also have to
349+
/// promise that the [`GuestMemory`] implementation is well-behaved and upholds the contracts
350+
/// in its documentation.
351+
///
352+
/// # Panics
353+
///
354+
/// Panics if the length of one of the regions overflows `usize`.
355+
pub unsafe fn vfio_map_guest_memory<M: GuestMemory>(&self, mem: &M) -> Result<()> {
323356
mem.iter().try_for_each(|region| {
324357
let host_addr = region
325358
.get_host_address(MemoryRegionAddress(0))
326359
.map_err(|_| VfioError::GetHostAddress)?;
327-
self.vfio_dma_map(
328-
region.start_addr().raw_value(),
329-
region.len(),
330-
host_addr as u64,
331-
)
360+
// SAFETY: GuestMemory guarantees the requirements
361+
// are upheld.
362+
unsafe {
363+
self.vfio_dma_map(
364+
region.start_addr().raw_value(),
365+
region.len().try_into().unwrap(),
366+
host_addr,
367+
)
368+
}
332369
})
333370
}
334371

335372
/// Remove all guest memory regions from the vfio container's iommu table.
336373
///
337-
/// The vfio kernel driver and device hardware couldn't access this guest memory after
338-
/// returning from the function.
374+
/// The vfio kernel driver and device hardware can't access this guest memory after
375+
/// the function returns successfully.
339376
///
340377
/// # Parameters
341378
/// * mem: pinned guest memory which could be accessed by devices binding to the container.
379+
///
380+
/// # Panics
381+
///
382+
/// Panics if the length of any of the regions overflows `usize`. That should have been
383+
/// caught by [`Self::vfio_map_guest_memory`], so it indicates a bogus [`GuestMemory`]
384+
/// implementation.
342385
pub fn vfio_unmap_guest_memory<M: GuestMemory>(&self, mem: &M) -> Result<()> {
343386
mem.iter().try_for_each(|region| {
344-
self.vfio_dma_unmap(region.start_addr().raw_value(), region.len())
387+
self.vfio_dma_unmap(
388+
region.start_addr().raw_value(),
389+
region.len().try_into().unwrap(),
390+
)
345391
})
346392
}
347393

@@ -1358,8 +1404,10 @@ mod tests {
13581404
container.put_group(group3);
13591405
assert_eq!(Arc::strong_count(&group), 1);
13601406

1361-
container.vfio_dma_map(0x1000, 0x1000, 0x8000).unwrap();
1362-
container.vfio_dma_map(0x2000, 0x2000, 0x8000).unwrap_err();
1407+
// SAFETY: this is a test implementation that does not access memory
1408+
unsafe { container.vfio_dma_map(0x1000, 0x1000, 0x8000 as _) }.unwrap();
1409+
// SAFETY: this is a test implementation that does not access memory
1410+
unsafe { container.vfio_dma_map(0x2000, 0x2000, 0x8000 as _) }.unwrap_err();
13631411
container.vfio_dma_unmap(0x1000, 0x1000).unwrap();
13641412
container.vfio_dma_unmap(0x2000, 0x2000).unwrap_err();
13651413
}
@@ -1506,7 +1554,9 @@ mod tests {
15061554
let mem1 = GuestMemoryMmap::<()>::from_ranges(&[(addr1, 0x1000)]).unwrap();
15071555
let container = create_vfio_container();
15081556

1509-
container.vfio_map_guest_memory(&mem1).unwrap();
1557+
// SAFETY: This is a dummy implementation that does not access
1558+
// memory.
1559+
unsafe { container.vfio_map_guest_memory(&mem1) }.unwrap();
15101560

15111561
let addr2 = GuestAddress(0x3000);
15121562
let mem2 = GuestMemoryMmap::<()>::from_ranges(&[(addr2, 0x1000)]).unwrap();

vfio-ioctls/src/vfio_ioctls.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,12 +85,13 @@ pub(crate) mod vfio_syscall {
8585
}
8686
}
8787

88-
pub(crate) fn map_dma(
88+
/// # Safety
89+
///
90+
/// See [`VfioContainer::vfio_dma_map`]
91+
pub(crate) unsafe fn map_dma(
8992
container: &VfioContainer,
9093
dma_map: &vfio_iommu_type1_dma_map,
9194
) -> Result<()> {
92-
// SAFETY: file is vfio container, dma_map is constructed by us, and
93-
// we check the return value
9495
let ret = unsafe { ioctl_with_ref(container, VFIO_IOMMU_MAP_DMA(), dma_map) };
9596
if ret != 0 {
9697
Err(VfioError::IommuDmaMap(SysError::last()))
@@ -268,7 +269,7 @@ pub(crate) mod vfio_syscall {
268269
Ok(())
269270
}
270271

271-
pub(crate) fn map_dma(
272+
pub(crate) unsafe fn map_dma(
272273
_container: &VfioContainer,
273274
dma_map: &vfio_iommu_type1_dma_map,
274275
) -> Result<()> {

0 commit comments

Comments
 (0)