Skip to content

Commit a72ea54

Browse files
committed
Mark VfioContainer::vfio_dma_map as unsafe
vfio_syscall::map_dma() accepts a u64 and tells the Linux kernel to make the corresponding address accessible for DMA by a device. Therefore, passing bad u64 values can result in memory corruption or disclosure. Change the u64 argument to a pointer to avoid confusion. To reflect that the caller must uphold invariants to prevent undefined behavior, mark the API as unsafe. I found this through a review of Cloud Hypervisor [1]. Some of its internal APIs took a host took a host address cast to u64 as an argument and accessed that address. In one case, the u64 was passed directly to vfio_syscall::map_dma(). [1]: cloud-hypervisor/cloud-hypervisor#7129 Signed-off-by: Demi Marie Obenour <[email protected]>
1 parent c52ddc9 commit a72ea54

File tree

3 files changed

+102
-22
lines changed

3 files changed

+102
-22
lines changed

vfio-ioctls/CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
# Upcoming Release
2+
3+
## Changed
4+
5+
- Functions that map memory into the VFIO device are now marked as
6+
`unsafe`. The caller of these functions is responsible for enforcing
7+
various complex but documented invariants to avoid undefined behavior.
8+
This requirement is also present in previous versions of the crate,
9+
but the function was not marked unsafe and the invariants were not
10+
documented.
11+
12+
In the future a high-level safe API will be provided that avoids
13+
these requirements at the cost of some flexibility.
14+
115
# [v0.5.0]
216

317
## Changed

vfio-ioctls/src/vfio_device.rs

Lines changed: 83 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,37 @@ 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)`, the only safe uses of the address range
294+
/// `[user_addr..user_addr+size)` are:
295+
///
296+
/// - Atomic and/or volatile operations on raw pointers.
297+
/// - Sharing the underlying storage with another process or a guest VM.
298+
/// - Passing a pointer to the memory to a system call (such as `read()`
299+
/// or `write()`) that is safe regardless of the memory's contents.
300+
/// - Passing a raw pointer to functions that only do one of the above things.
301+
///
302+
/// In particular, creating a Rust reference to this memory is instant undefined behavior
303+
/// due to the Rust aliasing rules. It is also undefined behavior to call this function if
304+
/// a Rust reference to this memory is live.
305+
pub unsafe fn vfio_dma_map(&self, iova: u64, size: usize, user_addr: *mut u8) -> Result<()> {
306+
const _: () = assert!(mem::size_of::<u64>() >= mem::size_of::<*mut u8>());
307+
const _: () = assert!(mem::size_of::<u64>() >= mem::size_of::<usize>());
286308
let dma_map = vfio_iommu_type1_dma_map {
287309
argsz: mem::size_of::<vfio_iommu_type1_dma_map>() as u32,
288310
flags: VFIO_DMA_MAP_FLAG_READ | VFIO_DMA_MAP_FLAG_WRITE,
289-
vaddr: user_addr,
311+
vaddr: (user_addr as usize).try_into().unwrap(),
290312
iova,
291-
size,
313+
size: size.try_into().unwrap(),
292314
};
293315

294316
vfio_syscall::map_dma(self, &dma_map)
@@ -299,16 +321,16 @@ impl VfioContainer {
299321
/// # Parameters
300322
/// * iova: IO virtual address to mapping the memory.
301323
/// * size: size of the memory region.
302-
pub fn vfio_dma_unmap(&self, iova: u64, size: u64) -> Result<()> {
324+
pub fn vfio_dma_unmap(&self, iova: u64, size: usize) -> Result<()> {
303325
let mut dma_unmap = vfio_iommu_type1_dma_unmap {
304326
argsz: mem::size_of::<vfio_iommu_type1_dma_unmap>() as u32,
305327
flags: 0,
306328
iova,
307-
size,
329+
size: size.try_into().unwrap(),
308330
};
309331

310332
vfio_syscall::unmap_dma(self, &mut dma_unmap)?;
311-
if dma_unmap.size != size {
333+
if dma_unmap.size != u64::try_from(size).unwrap() {
312334
return Err(VfioError::InvalidDmaUnmapSize);
313335
}
314336

@@ -319,29 +341,68 @@ impl VfioContainer {
319341
///
320342
/// # Parameters
321343
/// * 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<()> {
344+
///
345+
/// # Safety
346+
///
347+
/// Each of the memory regions must uphold the safety invariants of [`Self::vfio_dma_map`].
348+
/// Additionally, the [`GuestMemory`] implementation must be well-behaved and uphold the
349+
/// contracts in its documentation.
350+
///
351+
/// If this function fails (returning [`core::result::Result::Err`]) there is no way to know
352+
/// which parts of the guest memory were successfully mapped. The only safe action to take
353+
/// to avoid undefined guest behavior is to crash the guest.
354+
///
355+
/// # Errors
356+
///
357+
/// Fails if any of the mapping operations fail.
358+
///
359+
/// # Panics
360+
///
361+
/// Panics if the length of one of the regions overflows `usize`.
362+
pub unsafe fn vfio_map_guest_memory<M: GuestMemory>(&self, mem: &M) -> Result<()> {
323363
mem.iter().try_for_each(|region| {
324364
let host_addr = region
325365
.get_host_address(MemoryRegionAddress(0))
326366
.map_err(|_| VfioError::GetHostAddress)?;
327-
self.vfio_dma_map(
328-
region.start_addr().raw_value(),
329-
region.len(),
330-
host_addr as u64,
331-
)
367+
// SAFETY: GuestMemory guarantees the requirements
368+
// are upheld.
369+
unsafe {
370+
self.vfio_dma_map(
371+
region.start_addr().raw_value(),
372+
region.len().try_into().unwrap(),
373+
host_addr,
374+
)
375+
}
332376
})
333377
}
334378

335379
/// Remove all guest memory regions from the vfio container's iommu table.
336380
///
337-
/// The vfio kernel driver and device hardware couldn't access this guest memory after
338-
/// returning from the function.
381+
/// The vfio kernel driver and device hardware can't access this guest memory after
382+
/// the function returns successfully, **provided that the following precondition holds**.
383+
///
384+
/// # Precondition
385+
///
386+
/// A previous call to [`Self::vfio_map_guest_memory`] must have succeeded, and iterating
387+
/// over the regions in the [`GuestMemory`] must produce the same values it did in the
388+
/// past. This latter contract will be upheld by a correct [`GuestMemory`] implementation,
389+
/// but [`GuestMemory`] is a safe trait and so unsafe code must not rely on this unless it
390+
/// is explicitly part of a safety contract.
339391
///
340392
/// # Parameters
341393
/// * mem: pinned guest memory which could be accessed by devices binding to the container.
394+
///
395+
/// # Panics
396+
///
397+
/// Panics if the length of any of the regions overflows `usize`. That should have been
398+
/// caught by [`Self::vfio_map_guest_memory`], so it indicates a bogus [`GuestMemory`]
399+
/// implementation.
342400
pub fn vfio_unmap_guest_memory<M: GuestMemory>(&self, mem: &M) -> Result<()> {
343401
mem.iter().try_for_each(|region| {
344-
self.vfio_dma_unmap(region.start_addr().raw_value(), region.len())
402+
self.vfio_dma_unmap(
403+
region.start_addr().raw_value(),
404+
region.len().try_into().unwrap(),
405+
)
345406
})
346407
}
347408

@@ -1358,8 +1419,10 @@ mod tests {
13581419
container.put_group(group3);
13591420
assert_eq!(Arc::strong_count(&group), 1);
13601421

1361-
container.vfio_dma_map(0x1000, 0x1000, 0x8000).unwrap();
1362-
container.vfio_dma_map(0x2000, 0x2000, 0x8000).unwrap_err();
1422+
// SAFETY: this is a test implementation that does not access memory
1423+
unsafe { container.vfio_dma_map(0x1000, 0x1000, 0x8000 as _) }.unwrap();
1424+
// SAFETY: this is a test implementation that does not access memory
1425+
unsafe { container.vfio_dma_map(0x2000, 0x2000, 0x8000 as _) }.unwrap_err();
13631426
container.vfio_dma_unmap(0x1000, 0x1000).unwrap();
13641427
container.vfio_dma_unmap(0x2000, 0x2000).unwrap_err();
13651428
}
@@ -1506,7 +1569,9 @@ mod tests {
15061569
let mem1 = GuestMemoryMmap::<()>::from_ranges(&[(addr1, 0x1000)]).unwrap();
15071570
let container = create_vfio_container();
15081571

1509-
container.vfio_map_guest_memory(&mem1).unwrap();
1572+
// SAFETY: This is a dummy implementation that does not access
1573+
// memory.
1574+
unsafe { container.vfio_map_guest_memory(&mem1) }.unwrap();
15101575

15111576
let addr2 = GuestAddress(0x3000);
15121577
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)