Skip to content

Commit ff0b76d

Browse files
authored
add marker trait to help check safety of guest memory reads (#794)
* add marker trait to help check safety of guest memory reads we noted that a pointer into guest memory must point to a properly-initialized T when read into Propolis, but there was no way to actually check that was a case. for example, it may be tempting to write an enum describing states of a guest device like: ``` enum MyCoolDevicePower { Off = 0, On = 1, } ``` and read/write to guest memory using the convenient read/write helpers. but a devious guest could put a `2` at that address, where reading that into Propolis would be UB. zerocopy::FromBytes happens to have the same requirements about its implementors as we need, that they're always valid to view from bytes, so use it to check that we can safely read a type out of guest memory. in our case we'll always copy those bytes to our own buffer, but zerocopy::FromBytes also comes with a great proc macro so we can #[derive(FromBytes)] on structs to be copied out.
1 parent ea01303 commit ff0b76d

File tree

4 files changed

+26
-13
lines changed

4 files changed

+26
-13
lines changed

lib/propolis/src/hw/nvme/bits.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@
55
#![allow(dead_code)]
66

77
use bitstruct::bitstruct;
8+
use zerocopy::{FromBytes, FromZeroes};
89

910
/// A Submission Queue Entry as represented in memory.
1011
///
1112
/// See NVMe 1.0e Section 4.2 Submission Queue Entry - Command Format
12-
#[derive(Debug, Default, Copy, Clone)]
13+
#[derive(Debug, Default, Copy, Clone, FromBytes, FromZeroes)]
1314
#[repr(C, packed(1))]
1415
pub struct SubmissionQueueEntry {
1516
/// Command Dword 0 (CDW0)

lib/propolis/src/hw/qemu/fwcfg.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -815,7 +815,7 @@ mod bits {
815815
use zerocopy::byteorder::big_endian::{
816816
U16 as BE16, U32 as BE32, U64 as BE64,
817817
};
818-
use zerocopy::AsBytes;
818+
use zerocopy::{AsBytes, FromBytes, FromZeroes};
819819

820820
pub const FW_CFG_IOP_SELECTOR: u16 = 0x0510;
821821
pub const FW_CFG_IOP_DATA: u16 = 0x0511;
@@ -867,7 +867,7 @@ mod bits {
867867
}
868868
}
869869

870-
#[derive(AsBytes, Default, Copy, Clone, Debug)]
870+
#[derive(AsBytes, Default, Copy, Clone, Debug, FromBytes, FromZeroes)]
871871
#[repr(C)]
872872
pub struct FwCfgDmaAccess {
873873
pub ctrl: BE32,

lib/propolis/src/hw/virtio/queue.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ use crate::common::*;
1717
use crate::migrate::MigrateStateError;
1818
use crate::vmm::MemCtx;
1919

20+
use zerocopy::{FromBytes, FromZeroes};
21+
2022
#[repr(C)]
21-
#[derive(Copy, Clone)]
23+
#[derive(Copy, Clone, FromBytes, FromZeroes)]
2224
struct VqdDesc {
2325
addr: u64,
2426
len: u32,

lib/propolis/src/vmm/mem.rs

+19-9
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ use crate::common::{GuestAddr, GuestRegion};
1919
use crate::util::aspace::ASpace;
2020
use crate::vmm::VmmHdl;
2121

22+
use zerocopy::FromBytes;
23+
2224
bitflags! {
2325
/// Bitflags representing memory protections.
2426
#[derive(Debug, Copy, Clone)]
@@ -480,21 +482,27 @@ impl<'a> SubMapping<'a> {
480482
}
481483

482484
/// Reads a `T` object from the mapping.
483-
pub fn read<T: Copy>(&self) -> Result<T> {
485+
pub fn read<T: Copy + FromBytes>(&self) -> Result<T> {
484486
self.check_read_access()?;
485487
let typed = self.ptr.as_ptr() as *const T;
486488
if self.len < std::mem::size_of::<T>() {
487489
return Err(Error::new(ErrorKind::InvalidData, "Buffer too small"));
488490
}
489491

490492
// Safety:
491-
// - typed must be valid for reads
492-
// - typed must point to a properly initialized value of T
493+
// - typed must be valid for reads: `check_read_access()` succeeded
494+
// - typed must point to a properly initialized value of T: always true
495+
// because we require `T: FromBytes`. `zerocopy::FromBytes` happens
496+
// to have the same concerns as us - that T is valid for all bit
497+
// patterns.
493498
Ok(unsafe { typed.read_unaligned() })
494499
}
495500

496501
/// Read `values` from the mapping.
497-
pub fn read_many<T: Copy>(&self, values: &mut [T]) -> Result<()> {
502+
pub fn read_many<T: Copy + FromBytes>(
503+
&self,
504+
values: &mut [T],
505+
) -> Result<()> {
498506
self.check_read_access()?;
499507
let copy_len = size_of_val(values);
500508
if self.len < copy_len {
@@ -508,11 +516,13 @@ impl<'a> SubMapping<'a> {
508516
// not guaranteed for the source pointer. Cast it down to a u8, which
509517
// will appease those alignment concerns
510518
let src = self.ptr.as_ptr() as *const u8;
519+
// We know reinterpreting `*mut T` as `*mut u8` and writing to it cannot
520+
// result in invalid `T` because `T: FromBytes`
511521
let dst = values.as_mut_ptr() as *mut u8;
512522

513523
// Safety
514524
// - `src` is valid for read for the `copy_len` as checked above
515-
// - `src` is valid for writes for its entire length, since it is from a
525+
// - `dst` is valid for writes for its entire length, since it is from a
516526
// valid mutable reference passed in to us
517527
// - both are aligned for a `u8` copy
518528
// - `dst` cannot be overlapped by `src`, since the former came from a
@@ -788,7 +798,7 @@ pub struct MemCtx {
788798
}
789799
impl MemCtx {
790800
/// Reads a generic value from a specified guest address.
791-
pub fn read<T: Copy>(&self, addr: GuestAddr) -> Option<T> {
801+
pub fn read<T: Copy + FromBytes>(&self, addr: GuestAddr) -> Option<T> {
792802
if let Some(mapping) =
793803
self.region_covered(addr, size_of::<T>(), Prot::READ)
794804
{
@@ -828,7 +838,7 @@ impl MemCtx {
828838
}
829839

830840
/// Reads multiple objects from a guest address.
831-
pub fn read_many<T: Copy>(
841+
pub fn read_many<T: Copy + FromBytes>(
832842
&self,
833843
base: GuestAddr,
834844
count: usize,
@@ -1015,7 +1025,7 @@ pub struct MemMany<'a, T: Copy> {
10151025
pos: usize,
10161026
phantom: PhantomData<T>,
10171027
}
1018-
impl<'a, T: Copy> MemMany<'a, T> {
1028+
impl<'a, T: Copy + FromBytes> MemMany<'a, T> {
10191029
/// Gets the object at position `pos` within the memory region.
10201030
///
10211031
/// Returns [`Option::None`] if out of range.
@@ -1028,7 +1038,7 @@ impl<'a, T: Copy> MemMany<'a, T> {
10281038
}
10291039
}
10301040
}
1031-
impl<'a, T: Copy> Iterator for MemMany<'a, T> {
1041+
impl<'a, T: Copy + FromBytes> Iterator for MemMany<'a, T> {
10321042
type Item = T;
10331043

10341044
fn next(&mut self) -> Option<Self::Item> {

0 commit comments

Comments
 (0)