blob: 9905d71a42f8ac1261bfa7f5292243887d62d07d [file] [log] [blame]
// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
//! Module containing versions of the standard library's [`Read`](std::io::Read) and
//! [`Write`](std::io::Write) traits compatible with volatile memory accesses.
use crate::bitmap::BitmapSlice;
use crate::volatile_memory::copy_slice_impl::{copy_from_volatile_slice, copy_to_volatile_slice};
use crate::{VolatileMemoryError, VolatileSlice};
use std::io::{Cursor, ErrorKind, Stdout};
use std::os::fd::AsRawFd;
/// A version of the standard library's [`Read`](std::io::Read) trait that operates on volatile
/// memory instead of slices
///
/// This trait is needed as rust slices (`&[u8]` and `&mut [u8]`) cannot be used when operating on
/// guest memory [1].
///
/// [1]: https://github.com/rust-vmm/vm-memory/pull/217
pub trait ReadVolatile {
/// Tries to read some bytes into the given [`VolatileSlice`] buffer, returning how many bytes
/// were read.
///
/// The behavior of implementations should be identical to [`Read::read`](std::io::Read::read)
fn read_volatile<B: BitmapSlice>(
&mut self,
buf: &mut VolatileSlice<B>,
) -> Result<usize, VolatileMemoryError>;
/// Tries to fill the given [`VolatileSlice`] buffer by reading from `self` returning an error
/// if insufficient bytes could be read.
///
/// The default implementation is identical to that of [`Read::read_exact`](std::io::Read::read_exact)
fn read_exact_volatile<B: BitmapSlice>(
&mut self,
buf: &mut VolatileSlice<B>,
) -> Result<(), VolatileMemoryError> {
// Implementation based on https://github.com/rust-lang/rust/blob/7e7483d26e3cec7a44ef00cf7ae6c9c8c918bec6/library/std/src/io/mod.rs#L465
let mut partial_buf = buf.offset(0)?;
while !partial_buf.is_empty() {
match self.read_volatile(&mut partial_buf) {
Err(VolatileMemoryError::IOError(err)) if err.kind() == ErrorKind::Interrupted => {
continue
}
Ok(0) => {
return Err(VolatileMemoryError::IOError(std::io::Error::new(
ErrorKind::UnexpectedEof,
"failed to fill whole buffer",
)))
}
Ok(bytes_read) => partial_buf = partial_buf.offset(bytes_read)?,
Err(err) => return Err(err),
}
}
Ok(())
}
}
/// A version of the standard library's [`Write`](std::io::Write) trait that operates on volatile
/// memory instead of slices.
///
/// This trait is needed as rust slices (`&[u8]` and `&mut [u8]`) cannot be used when operating on
/// guest memory [1].
///
/// [1]: https://github.com/rust-vmm/vm-memory/pull/217
pub trait WriteVolatile {
/// Tries to write some bytes from the given [`VolatileSlice`] buffer, returning how many bytes
/// were written.
///
/// The behavior of implementations should be identical to [`Write::write`](std::io::Write::write)
fn write_volatile<B: BitmapSlice>(
&mut self,
buf: &VolatileSlice<B>,
) -> Result<usize, VolatileMemoryError>;
/// Tries write the entire content of the given [`VolatileSlice`] buffer to `self` returning an
/// error if not all bytes could be written.
///
/// The default implementation is identical to that of [`Write::write_all`](std::io::Write::write_all)
fn write_all_volatile<B: BitmapSlice>(
&mut self,
buf: &VolatileSlice<B>,
) -> Result<(), VolatileMemoryError> {
// Based on https://github.com/rust-lang/rust/blob/7e7483d26e3cec7a44ef00cf7ae6c9c8c918bec6/library/std/src/io/mod.rs#L1570
let mut partial_buf = buf.offset(0)?;
while !partial_buf.is_empty() {
match self.write_volatile(&partial_buf) {
Err(VolatileMemoryError::IOError(err)) if err.kind() == ErrorKind::Interrupted => {
continue
}
Ok(0) => {
return Err(VolatileMemoryError::IOError(std::io::Error::new(
ErrorKind::WriteZero,
"failed to write whole buffer",
)))
}
Ok(bytes_written) => partial_buf = partial_buf.offset(bytes_written)?,
Err(err) => return Err(err),
}
}
Ok(())
}
}
// We explicitly implement our traits for [`std::fs::File`] and [`std::os::unix::net::UnixStream`]
// instead of providing blanket implementation for [`AsRawFd`] due to trait coherence limitations: A
// blanket implementation would prevent us from providing implementations for `&mut [u8]` below, as
// "an upstream crate could implement AsRawFd for &mut [u8]`.
macro_rules! impl_read_write_volatile_for_raw_fd {
($raw_fd_ty:ty) => {
impl ReadVolatile for $raw_fd_ty {
fn read_volatile<B: BitmapSlice>(
&mut self,
buf: &mut VolatileSlice<B>,
) -> Result<usize, VolatileMemoryError> {
read_volatile_raw_fd(self, buf)
}
}
impl WriteVolatile for $raw_fd_ty {
fn write_volatile<B: BitmapSlice>(
&mut self,
buf: &VolatileSlice<B>,
) -> Result<usize, VolatileMemoryError> {
write_volatile_raw_fd(self, buf)
}
}
};
}
impl WriteVolatile for Stdout {
fn write_volatile<B: BitmapSlice>(
&mut self,
buf: &VolatileSlice<B>,
) -> Result<usize, VolatileMemoryError> {
write_volatile_raw_fd(self, buf)
}
}
impl_read_write_volatile_for_raw_fd!(std::fs::File);
impl_read_write_volatile_for_raw_fd!(std::net::TcpStream);
impl_read_write_volatile_for_raw_fd!(std::os::unix::net::UnixStream);
impl_read_write_volatile_for_raw_fd!(std::os::fd::OwnedFd);
impl_read_write_volatile_for_raw_fd!(std::os::fd::BorrowedFd<'_>);
/// Tries to do a single `read` syscall on the provided file descriptor, storing the data raed in
/// the given [`VolatileSlice`].
///
/// Returns the numbers of bytes read.
fn read_volatile_raw_fd<Fd: AsRawFd>(
raw_fd: &mut Fd,
buf: &mut VolatileSlice<impl BitmapSlice>,
) -> Result<usize, VolatileMemoryError> {
let fd = raw_fd.as_raw_fd();
let guard = buf.ptr_guard_mut();
let dst = guard.as_ptr().cast::<libc::c_void>();
// SAFETY: We got a valid file descriptor from `AsRawFd`. The memory pointed to by `dst` is
// valid for writes of length `buf.len() by the invariants upheld by the constructor
// of `VolatileSlice`.
let bytes_read = unsafe { libc::read(fd, dst, buf.len()) };
if bytes_read < 0 {
// We don't know if a partial read might have happened, so mark everything as dirty
buf.bitmap().mark_dirty(0, buf.len());
Err(VolatileMemoryError::IOError(std::io::Error::last_os_error()))
} else {
let bytes_read = bytes_read.try_into().unwrap();
buf.bitmap().mark_dirty(0, bytes_read);
Ok(bytes_read)
}
}
/// Tries to do a single `write` syscall on the provided file descriptor, attempting to write the
/// data stored in the given [`VolatileSlice`].
///
/// Returns the numbers of bytes written.
fn write_volatile_raw_fd<Fd: AsRawFd>(
raw_fd: &mut Fd,
buf: &VolatileSlice<impl BitmapSlice>,
) -> Result<usize, VolatileMemoryError> {
let fd = raw_fd.as_raw_fd();
let guard = buf.ptr_guard();
let src = guard.as_ptr().cast::<libc::c_void>();
// SAFETY: We got a valid file descriptor from `AsRawFd`. The memory pointed to by `src` is
// valid for reads of length `buf.len() by the invariants upheld by the constructor
// of `VolatileSlice`.
let bytes_written = unsafe { libc::write(fd, src, buf.len()) };
if bytes_written < 0 {
Err(VolatileMemoryError::IOError(std::io::Error::last_os_error()))
} else {
Ok(bytes_written.try_into().unwrap())
}
}
impl WriteVolatile for &mut [u8] {
fn write_volatile<B: BitmapSlice>(
&mut self,
buf: &VolatileSlice<B>,
) -> Result<usize, VolatileMemoryError> {
let total = buf.len().min(self.len());
let src = buf.subslice(0, total)?;
// SAFETY:
// We check above that `src` is contiguously allocated memory of length `total <= self.len())`.
// Furthermore, both src and dst of the call to
// copy_from_volatile_slice are valid for reads and writes respectively of length `total`
// since total is the minimum of lengths of the memory areas pointed to. The areas do not
// overlap, since `dst` is inside guest memory, and buf is a slice (no slices to guest
// memory are possible without violating rust's aliasing rules).
let written = unsafe { copy_from_volatile_slice(self.as_mut_ptr(), &src, total) };
// Advance the slice, just like the stdlib: https://doc.rust-lang.org/src/std/io/impls.rs.html#335
*self = std::mem::take(self).split_at_mut(written).1;
Ok(written)
}
fn write_all_volatile<B: BitmapSlice>(
&mut self,
buf: &VolatileSlice<B>,
) -> Result<(), VolatileMemoryError> {
// Based on https://github.com/rust-lang/rust/blob/f7b831ac8a897273f78b9f47165cf8e54066ce4b/library/std/src/io/impls.rs#L376-L382
if self.write_volatile(buf)? == buf.len() {
Ok(())
} else {
Err(VolatileMemoryError::IOError(std::io::Error::new(
ErrorKind::WriteZero,
"failed to write whole buffer",
)))
}
}
}
impl ReadVolatile for &[u8] {
fn read_volatile<B: BitmapSlice>(
&mut self,
buf: &mut VolatileSlice<B>,
) -> Result<usize, VolatileMemoryError> {
let total = buf.len().min(self.len());
let dst = buf.subslice(0, total)?;
// SAFETY:
// We check above that `dst` is contiguously allocated memory of length `total <= self.len())`.
// Furthermore, both src and dst of the call to copy_to_volatile_slice are valid for reads
// and writes respectively of length `total` since total is the minimum of lengths of the
// memory areas pointed to. The areas do not overlap, since `dst` is inside guest memory,
// and buf is a slice (no slices to guest memory are possible without violating rust's aliasing rules).
let read = unsafe { copy_to_volatile_slice(&dst, self.as_ptr(), total) };
// Advance the slice, just like the stdlib: https://doc.rust-lang.org/src/std/io/impls.rs.html#232-310
*self = self.split_at(read).1;
Ok(read)
}
fn read_exact_volatile<B: BitmapSlice>(
&mut self,
buf: &mut VolatileSlice<B>,
) -> Result<(), VolatileMemoryError> {
// Based on https://github.com/rust-lang/rust/blob/f7b831ac8a897273f78b9f47165cf8e54066ce4b/library/std/src/io/impls.rs#L282-L302
if buf.len() > self.len() {
return Err(VolatileMemoryError::IOError(std::io::Error::new(
ErrorKind::UnexpectedEof,
"failed to fill whole buffer",
)));
}
self.read_volatile(buf).map(|_| ())
}
}
// WriteVolatile implementation for Vec<u8> is based upon the Write impl for Vec, which
// defers to Vec::append_elements, after which the below functionality is modelled.
impl WriteVolatile for Vec<u8> {
fn write_volatile<B: BitmapSlice>(
&mut self,
buf: &VolatileSlice<B>,
) -> Result<usize, VolatileMemoryError> {
let count = buf.len();
self.reserve(count);
let len = self.len();
// SAFETY: Calling Vec::reserve() above guarantees the the backing storage of the Vec has
// length at least `len + count`. This means that self.as_mut_ptr().add(len) remains within
// the same allocated object, the offset does not exceed isize (as otherwise reserve would
// have panicked), and does not rely on address space wrapping around.
// In particular, the entire `count` bytes after `self.as_mut_ptr().add(count)` is
// contiguously allocated and valid for writes.
// Lastly, `copy_to_volatile_slice` correctly initialized `copied_len` additional bytes
// in the Vec's backing storage, and we assert this to be equal to `count`. Additionally,
// `len + count` is at most the reserved capacity of the vector. Thus the call to `set_len`
// is safe.
unsafe {
let copied_len = copy_from_volatile_slice(self.as_mut_ptr().add(len), buf, count);
assert_eq!(copied_len, count);
self.set_len(len + count);
}
Ok(count)
}
}
// ReadVolatile and WriteVolatile implementations for Cursor<T> is modelled after the standard
// library's implementation (modulo having to inline `Cursor::remaining_slice`, as that's nightly only)
impl<T> ReadVolatile for Cursor<T>
where
T: AsRef<[u8]>,
{
fn read_volatile<B: BitmapSlice>(
&mut self,
buf: &mut VolatileSlice<B>,
) -> Result<usize, VolatileMemoryError> {
let inner = self.get_ref().as_ref();
let len = self.position().min(inner.len() as u64);
let n = ReadVolatile::read_volatile(&mut &inner[(len as usize)..], buf)?;
self.set_position(self.position() + n as u64);
Ok(n)
}
fn read_exact_volatile<B: BitmapSlice>(
&mut self,
buf: &mut VolatileSlice<B>,
) -> Result<(), VolatileMemoryError> {
let inner = self.get_ref().as_ref();
let n = buf.len();
let len = self.position().min(inner.len() as u64);
ReadVolatile::read_exact_volatile(&mut &inner[(len as usize)..], buf)?;
self.set_position(self.position() + n as u64);
Ok(())
}
}
impl WriteVolatile for Cursor<&mut [u8]> {
fn write_volatile<B: BitmapSlice>(
&mut self,
buf: &VolatileSlice<B>,
) -> Result<usize, VolatileMemoryError> {
let pos = self.position().min(self.get_ref().len() as u64);
let n = WriteVolatile::write_volatile(&mut &mut self.get_mut()[(pos as usize)..], buf)?;
self.set_position(self.position() + n as u64);
Ok(n)
}
// no write_all provided in standard library, since our default for write_all is based on the
// standard library's write_all, omitting it here as well will correctly mimic stdlib behavior.
}
#[cfg(test)]
mod tests {
use crate::io::{ReadVolatile, WriteVolatile};
use crate::{VolatileMemoryError, VolatileSlice};
use std::io::{Cursor, ErrorKind, Read, Seek, Write};
use vmm_sys_util::tempfile::TempFile;
// ---- Test ReadVolatile for &[u8] ----
fn read_4_bytes_to_5_byte_memory(source: Vec<u8>, expected_output: [u8; 5]) {
// Test read_volatile for &[u8] works
let mut memory = vec![0u8; 5];
assert_eq!(
(&source[..])
.read_volatile(&mut VolatileSlice::from(&mut memory[..4]))
.unwrap(),
source.len().min(4)
);
assert_eq!(&memory, &expected_output);
// Test read_exact_volatile for &[u8] works
let mut memory = vec![0u8; 5];
let result = (&source[..]).read_exact_volatile(&mut VolatileSlice::from(&mut memory[..4]));
// read_exact fails if there are not enough bytes in input to completely fill
// memory[..4]
if source.len() < 4 {
match result.unwrap_err() {
VolatileMemoryError::IOError(ioe) => {
assert_eq!(ioe.kind(), ErrorKind::UnexpectedEof)
}
err => panic!("{:?}", err),
}
assert_eq!(memory, vec![0u8; 5]);
} else {
result.unwrap();
assert_eq!(&memory, &expected_output);
}
}
// ---- Test ReadVolatile for File ----
fn read_4_bytes_from_file(source: Vec<u8>, expected_output: [u8; 5]) {
let mut temp_file = TempFile::new().unwrap().into_file();
temp_file.write_all(source.as_ref()).unwrap();
temp_file.rewind().unwrap();
// Test read_volatile for File works
let mut memory = vec![0u8; 5];
assert_eq!(
temp_file
.read_volatile(&mut VolatileSlice::from(&mut memory[..4]))
.unwrap(),
source.len().min(4)
);
assert_eq!(&memory, &expected_output);
temp_file.rewind().unwrap();
// Test read_exact_volatile for File works
let mut memory = vec![0u8; 5];
let read_exact_result =
temp_file.read_exact_volatile(&mut VolatileSlice::from(&mut memory[..4]));
if source.len() < 4 {
read_exact_result.unwrap_err();
} else {
read_exact_result.unwrap();
}
assert_eq!(&memory, &expected_output);
}
#[test]
fn test_read_volatile() {
let test_cases = [
(vec![1u8, 2], [1u8, 2, 0, 0, 0]),
(vec![1, 2, 3, 4], [1, 2, 3, 4, 0]),
// ensure we don't have a buffer overrun
(vec![5, 6, 7, 8, 9], [5, 6, 7, 8, 0]),
];
for (input, output) in test_cases {
read_4_bytes_to_5_byte_memory(input.clone(), output);
read_4_bytes_from_file(input, output);
}
}
// ---- Test WriteVolatile for &mut [u8] ----
fn write_4_bytes_to_5_byte_vec(mut source: Vec<u8>, expected_result: [u8; 5]) {
let mut memory = vec![0u8; 5];
// Test write_volatile for &mut [u8] works
assert_eq!(
(&mut memory[..4])
.write_volatile(&VolatileSlice::from(source.as_mut_slice()))
.unwrap(),
source.len().min(4)
);
assert_eq!(&memory, &expected_result);
// Test write_all_volatile for &mut [u8] works
let mut memory = vec![0u8; 5];
let result =
(&mut memory[..4]).write_all_volatile(&VolatileSlice::from(source.as_mut_slice()));
if source.len() > 4 {
match result.unwrap_err() {
VolatileMemoryError::IOError(ioe) => {
assert_eq!(ioe.kind(), ErrorKind::WriteZero)
}
err => panic!("{:?}", err),
}
// This quirky behavior of writing to the slice even in the case of failure is also
// exhibited by the stdlib
assert_eq!(&memory, &expected_result);
} else {
result.unwrap();
assert_eq!(&memory, &expected_result);
}
}
// ---- Test ẂriteVolatile for File works ----
fn write_5_bytes_to_file(mut source: Vec<u8>) {
// Test write_volatile for File works
let mut temp_file = TempFile::new().unwrap().into_file();
temp_file
.write_volatile(&VolatileSlice::from(source.as_mut_slice()))
.unwrap();
temp_file.rewind().unwrap();
let mut written = vec![0u8; source.len()];
temp_file.read_exact(written.as_mut_slice()).unwrap();
assert_eq!(source, written);
// check no excess bytes were written to the file
assert_eq!(temp_file.read(&mut [0u8]).unwrap(), 0);
// Test write_all_volatile for File works
let mut temp_file = TempFile::new().unwrap().into_file();
temp_file
.write_all_volatile(&VolatileSlice::from(source.as_mut_slice()))
.unwrap();
temp_file.rewind().unwrap();
let mut written = vec![0u8; source.len()];
temp_file.read_exact(written.as_mut_slice()).unwrap();
assert_eq!(source, written);
// check no excess bytes were written to the file
assert_eq!(temp_file.read(&mut [0u8]).unwrap(), 0);
}
#[test]
fn test_write_volatile() {
let test_cases = [
(vec![1u8, 2], [1u8, 2, 0, 0, 0]),
(vec![1, 2, 3, 4], [1, 2, 3, 4, 0]),
// ensure we don't have a buffer overrun
(vec![5, 6, 7, 8, 9], [5, 6, 7, 8, 0]),
];
for (input, output) in test_cases {
write_4_bytes_to_5_byte_vec(input.clone(), output);
write_5_bytes_to_file(input);
}
}
#[test]
fn test_read_volatile_for_cursor() {
let read_buffer = [1, 2, 3, 4, 5, 6, 7];
let mut output = vec![0u8; 5];
let mut cursor = Cursor::new(read_buffer);
// Read 4 bytes from cursor to volatile slice (amount read limited by volatile slice length)
assert_eq!(
cursor
.read_volatile(&mut VolatileSlice::from(&mut output[..4]))
.unwrap(),
4
);
assert_eq!(output, vec![1, 2, 3, 4, 0]);
// Read next 3 bytes from cursor to volatile slice (amount read limited by length of remaining data in cursor)
assert_eq!(
cursor
.read_volatile(&mut VolatileSlice::from(&mut output[..4]))
.unwrap(),
3
);
assert_eq!(output, vec![5, 6, 7, 4, 0]);
cursor.set_position(0);
// Same as first test above, but with read_exact
cursor
.read_exact_volatile(&mut VolatileSlice::from(&mut output[..4]))
.unwrap();
assert_eq!(output, vec![1, 2, 3, 4, 0]);
// Same as above, but with read_exact. Should fail now, because we cannot fill a 4 byte buffer
// with whats remaining in the cursor (3 bytes). Output should remain unchanged.
assert!(cursor
.read_exact_volatile(&mut VolatileSlice::from(&mut output[..4]))
.is_err());
assert_eq!(output, vec![1, 2, 3, 4, 0]);
}
#[test]
fn test_write_volatile_for_cursor() {
let mut write_buffer = vec![0u8; 7];
let mut input = [1, 2, 3, 4];
let mut cursor = Cursor::new(write_buffer.as_mut_slice());
// Write 4 bytes from volatile slice to cursor (amount written limited by volatile slice length)
assert_eq!(
cursor
.write_volatile(&VolatileSlice::from(input.as_mut_slice()))
.unwrap(),
4
);
assert_eq!(cursor.get_ref(), &[1, 2, 3, 4, 0, 0, 0]);
// Write 3 bytes from volatile slice to cursor (amount written limited by remaining space in cursor)
assert_eq!(
cursor
.write_volatile(&VolatileSlice::from(input.as_mut_slice()))
.unwrap(),
3
);
assert_eq!(cursor.get_ref(), &[1, 2, 3, 4, 1, 2, 3]);
}
#[test]
fn test_write_volatile_for_vec() {
let mut write_buffer = Vec::new();
let mut input = [1, 2, 3, 4];
assert_eq!(
write_buffer
.write_volatile(&VolatileSlice::from(input.as_mut_slice()))
.unwrap(),
4
);
assert_eq!(&write_buffer, &input);
}
}