Skip to content

Commit aefa2ed

Browse files
roypatandreeaflorescu
authored andcommitted
Make copy_slice operate on pointers instead of slices
This function previously took slices, where one slice pointed to rust-allocated memory (fine), and one slices pointed to mmap'd guest memory (potentially problematic). The latter could result in undefined behavior, as we cannot assume exclusive ownership of guest memory if a virtual machine is running: We need to assume that the guest inside the VM implicitly holds a mutable reference to the entire guest memory. By using pointers instead of reference, this is still a problem due to data races being possible, but the worst case scenario here is inconsistent reads/writes, whereas the aliasing rules are something the compiler potentially uses for optimizations. Signed-off-by: Patrick Roy <[email protected]>
1 parent 641d08f commit aefa2ed

File tree

2 files changed

+89
-42
lines changed

2 files changed

+89
-42
lines changed

src/bytes.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ pub unsafe trait ByteValued: Copy + Default + Send + Sync {
120120
/// be no other accesses).
121121
fn as_bytes(&mut self) -> VolatileSlice {
122122
// SAFETY: This is safe because the lifetime is the same as self
123-
unsafe { VolatileSlice::new(self as *mut Self as usize as *mut _, size_of::<Self>()) }
123+
unsafe { VolatileSlice::new(self as *mut Self as *mut _, size_of::<Self>()) }
124124
}
125125
}
126126

src/volatile_memory.rs

Lines changed: 88 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,7 @@ impl<'a, B: BitmapSlice> VolatileSlice<'a, B> {
387387
// memory of the original slice.
388388
unsafe {
389389
Ok(VolatileSlice::with_bitmap(
390-
new_addr as *mut u8,
390+
self.addr.add(count),
391391
new_size,
392392
self.bitmap.slice_at(count),
393393
))
@@ -424,12 +424,16 @@ impl<'a, B: BitmapSlice> VolatileSlice<'a, B> {
424424
{
425425
// A fast path for u8/i8
426426
if size_of::<T>() == 1 {
427-
// SAFETY: It is safe because the pointers are range-checked when the slices are
428-
// created, and they never escape the VolatileSlices.
429-
let source = unsafe { self.as_slice() };
430-
// SAFETY: Safe because `T` is a one-byte data structure.
431-
let dst = unsafe { from_raw_parts_mut(buf.as_mut_ptr() as *mut u8, buf.len()) };
432-
copy_slice(dst, source)
427+
let total = buf.len().min(self.len());
428+
429+
// SAFETY:
430+
// - dst is valid for writes of at least `total`, since total <= buf.len()
431+
// - src is valid for reads of at least `total` as total <= self.len()
432+
// - The regions are non-overlapping as `src` points to guest memory and `buf` is
433+
// a slice and thus has to live outside of guest memory (there can be more slices to
434+
// guest memory without violating rust's aliasing rules)
435+
// - size is always a multiple of alignment, so treating *mut T as *mut u8 is fine
436+
unsafe { copy_slice(buf.as_mut_ptr() as *mut u8, self.as_ptr(), total) }
433437
} else {
434438
let count = self.size / size_of::<T>();
435439
let source = self.get_array_ref::<T>(0, count).unwrap();
@@ -504,12 +508,16 @@ impl<'a, B: BitmapSlice> VolatileSlice<'a, B> {
504508
{
505509
// A fast path for u8/i8
506510
if size_of::<T>() == 1 {
507-
// SAFETY: It is safe because the pointers are range-checked when the slices are created,
508-
// and they never escape the VolatileSlices.
509-
let dst = unsafe { self.as_mut_slice() };
510-
// SAFETY: Safe because `T` is a one-byte data structure.
511-
let src = unsafe { from_raw_parts(buf.as_ptr() as *const u8, buf.len()) };
512-
let count = copy_slice(dst, src);
511+
let total = buf.len().min(self.len());
512+
// SAFETY:
513+
// - dst is valid for writes of at least `total`, since total <= self.len()
514+
// - src is valid for reads of at least `total` as total <= buf.len()
515+
// - The regions are non-overlapping as `dst` points to guest memory and `buf` is
516+
// a slice and thus has to live outside of guest memory (there can be more slices to
517+
// guest memory without violating rust's aliasing rules)
518+
// - size is always a multiple of alignment, so treating *mut T as *mut u8 is fine
519+
let count = unsafe { copy_slice(self.as_ptr(), buf.as_ptr() as *const u8, total) };
520+
513521
self.bitmap.mark_dirty(0, count * size_of::<T>());
514522
} else {
515523
let count = self.size / size_of::<T>();
@@ -587,12 +595,21 @@ impl<B: BitmapSlice> Bytes<usize> for VolatileSlice<'_, B> {
587595
return Err(Error::OutOfBounds { addr });
588596
}
589597

590-
// SAFETY: Guest memory can't strictly be modeled as a slice because it is
591-
// volatile. Writing to it with what is essentially a fancy memcpy
592-
// won't hurt anything as long as we get the bounds checks right.
593-
let slice = unsafe { self.as_mut_slice() }.split_at_mut(addr).1;
598+
let total = buf.len().min(self.len() - addr);
599+
600+
// SAFETY:
601+
// We check above that `addr` is a valid offset within this volatile slice, and by
602+
// the invariants of `VolatileSlice::new`, this volatile slice points to contiguous
603+
// memory of length self.len(). Furthermore, both src and dst of the call to copy_slice
604+
// are valid for reads and writes respectively of length `total` since total is the minimum
605+
// of lengths of the memory areas pointed to. The areas do not overlap, since `dst` is
606+
// inside guest memory, and buf is a slice (no slices to guest memory are possible without
607+
// violating rust's aliasing rules).
608+
let count = unsafe {
609+
let dst = self.as_ptr().add(addr);
610+
copy_slice(dst, buf.as_ptr(), total)
611+
};
594612

595-
let count = copy_slice(slice, buf);
596613
self.bitmap.mark_dirty(addr, count);
597614
Ok(count)
598615
}
@@ -621,11 +638,20 @@ impl<B: BitmapSlice> Bytes<usize> for VolatileSlice<'_, B> {
621638
return Err(Error::OutOfBounds { addr });
622639
}
623640

624-
// SAFETY: Guest memory can't strictly be modeled as a slice because it is
625-
// volatile. Writing to it with what is essentially a fancy memcpy
626-
// won't hurt anything as long as we get the bounds checks right.
627-
let slice = unsafe { self.as_slice() }.split_at(addr).1;
628-
Ok(copy_slice(buf, slice))
641+
let total = buf.len().min(self.len() - addr);
642+
643+
// SAFETY:
644+
// We check above that `addr` is a valid offset within this volatile slice, and by
645+
// the invariants of `VolatileSlice::new`, this volatile slice points to contiguous
646+
// memory of length self.len(). Furthermore, both src and dst of the call to copy_slice
647+
// are valid for reads and writes respectively of length `total` since total is the minimum
648+
// of lengths of the memory areas pointed to. The areas do not overlap, since `dst` is
649+
// inside guest memory, and buf is a slice (no slices to guest memory are possible without
650+
// violating rust's aliasing rules).
651+
unsafe {
652+
let src = self.as_ptr().add(addr);
653+
Ok(copy_slice(buf.as_mut_ptr(), src, total))
654+
}
629655
}
630656

631657
/// # Examples
@@ -1190,12 +1216,16 @@ where
11901216
// A fast path for u8/i8
11911217
if size_of::<T>() == 1 {
11921218
let source = self.to_slice();
1193-
// SAFETY: It is safe because the pointers are range-checked when the slices are
1194-
// created, and they never escape the VolatileSlices.
1195-
let src = unsafe { source.as_slice() };
1196-
// SAFETY: Safe because `T` is a one-byte data structure.
1197-
let dst = unsafe { from_raw_parts_mut(buf.as_mut_ptr() as *mut u8, buf.len()) };
1198-
return copy_slice(dst, src);
1219+
let total = buf.len().min(source.len());
1220+
1221+
// SAFETY:
1222+
// - dst is valid for writes of at least `total`, since total <= buf.len()
1223+
// - src is valid for reads of at least `total` as total <= source.len()
1224+
// - The regions are non-overlapping as `src` points to guest memory and `buf` is
1225+
// a slice and thus has to live outside of guest memory (there can be more slices to
1226+
// guest memory without violating rust's aliasing rules)
1227+
// - size is always a multiple of alignment, so treating *mut T as *mut u8 is fine
1228+
return unsafe { copy_slice(buf.as_mut_ptr() as *mut u8, source.as_ptr(), total) };
11991229
}
12001230

12011231
let mut addr = self.addr;
@@ -1269,12 +1299,18 @@ where
12691299
// A fast path for u8/i8
12701300
if size_of::<T>() == 1 {
12711301
let destination = self.to_slice();
1272-
// SAFETY: It is safe because the pointers are range-checked when the slices are
1273-
// created, and they never escape the VolatileSlices.
1274-
let dst = unsafe { destination.as_mut_slice() };
1275-
// SAFETY: Safe because `T` is a one-byte data structure.
1276-
let src = unsafe { from_raw_parts(buf.as_ptr() as *const u8, buf.len()) };
1277-
let count = copy_slice(dst, src);
1302+
let total = buf.len().min(destination.len());
1303+
1304+
// absurd formatting brought to you by clippy
1305+
let count =
1306+
// SAFETY:
1307+
// - dst is valid for writes of at least `total`, since total <= destination.len()
1308+
// - src is valid for reads of at least `total` as total <= buf.len()
1309+
// - The regions are non-overlapping as `dst` points to guest memory and `buf` is
1310+
// a slice and thus has to live outside of guest memory (there can be more slices to
1311+
// guest memory without violating rust's aliasing rules)
1312+
// - size is always a multiple of alignment, so treating *const T as *const u8 is fine
1313+
unsafe { copy_slice(destination.as_ptr(), buf.as_ptr() as *const u8, total) };
12781314
self.bitmap.mark_dirty(0, count);
12791315
} else {
12801316
let mut addr = self.addr;
@@ -1380,14 +1416,25 @@ mod copy_slice_impl {
13801416
total
13811417
}
13821418

1383-
pub(super) fn copy_slice(dst: &mut [u8], src: &[u8]) -> usize {
1384-
let total = min(src.len(), dst.len());
1419+
/// Copies `total` bytes from `src` to `dst`
1420+
///
1421+
/// SAFETY: `src` and `dst` must be point to a contiguously allocated memory region of at least
1422+
/// length `total`. The regions must not overlap
1423+
pub(super) unsafe fn copy_slice(dst: *mut u8, src: *const u8, total: usize) -> usize {
13851424
if total <= size_of::<usize>() {
1386-
// SAFETY: we take pointers to slices, which are assured to reference contiguously
1387-
// allocated memory of length total (as total is the minimum of the lengths of the slices)
1388-
unsafe { copy_slice_volatile(dst.as_mut_ptr(), src.as_ptr(), total) };
1425+
// SAFETY: Invariants of copy_slice_volatile are the same as invariants of copy_slice
1426+
unsafe {
1427+
copy_slice_volatile(dst, src, total);
1428+
};
13891429
} else {
1390-
dst[..total].copy_from_slice(&src[..total]);
1430+
// SAFETY:
1431+
// - Both src and dst are allocated for reads/writes of length `total` by function
1432+
// invariant
1433+
// - src and dst are properly aligned, as any alignment is valid for u8
1434+
// - The regions are not overlapping by function invariant
1435+
unsafe {
1436+
std::ptr::copy_nonoverlapping(src, dst, total);
1437+
}
13911438
}
13921439

13931440
total

0 commit comments

Comments
 (0)