Skip to content

Commit 18ba7db

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 f232f42 commit 18ba7db

File tree

3 files changed

+98
-22
lines changed

3 files changed

+98
-22
lines changed

vfio-ioctls/CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@
44

55
- [[114]](https://github.com/rust-vmm/vfio/pull/114) Cargo.toml: Update deps to latest version
66

7+
- [[103]](https://github.com/rust-vmm/vfio/pull/103) Functions that map
8+
memory into the VFIO device are now marked as `unsafe`. The caller
9+
of these functions is responsible for enforcing various complex but
10+
documented invariants to avoid undefined behavior. This requirement
11+
is also present in previous versions of the crate, but the function
12+
was not marked unsafe and the invariants were not documented.
13+
14+
In the future a high-level safe API will be provided that avoids
15+
these requirements at the cost of some flexibility.
16+
717
## Added
818

919
## Fixed

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,17 +321,17 @@ 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
..Default::default()
309331
};
310332

311333
vfio_syscall::unmap_dma(self, &mut dma_unmap)?;
312-
if dma_unmap.size != size {
334+
if dma_unmap.size != u64::try_from(size).unwrap() {
313335
return Err(VfioError::InvalidDmaUnmapSize);
314336
}
315337

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

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

@@ -1361,8 +1422,10 @@ mod tests {
13611422
container.put_group(group3);
13621423
assert_eq!(Arc::strong_count(&group), 1);
13631424

1364-
container.vfio_dma_map(0x1000, 0x1000, 0x8000).unwrap();
1365-
container.vfio_dma_map(0x2000, 0x2000, 0x8000).unwrap_err();
1425+
// SAFETY: this is a test implementation that does not access memory
1426+
unsafe { container.vfio_dma_map(0x1000, 0x1000, 0x8000 as _) }.unwrap();
1427+
// SAFETY: this is a test implementation that does not access memory
1428+
unsafe { container.vfio_dma_map(0x2000, 0x2000, 0x8000 as _) }.unwrap_err();
13661429
container.vfio_dma_unmap(0x1000, 0x1000).unwrap();
13671430
container.vfio_dma_unmap(0x2000, 0x2000).unwrap_err();
13681431
}
@@ -1509,7 +1572,9 @@ mod tests {
15091572
let mem1 = GuestMemoryMmap::<()>::from_ranges(&[(addr1, 0x1000)]).unwrap();
15101573
let container = create_vfio_container();
15111574

1512-
container.vfio_map_guest_memory(&mem1).unwrap();
1575+
// SAFETY: This is a dummy implementation that does not access
1576+
// memory.
1577+
unsafe { container.vfio_map_guest_memory(&mem1) }.unwrap();
15131578

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