// 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( &mut self, buf: &mut VolatileSlice, ) -> Result; /// 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( &mut self, buf: &mut VolatileSlice, ) -> 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( &mut self, buf: &VolatileSlice, ) -> Result; /// 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( &mut self, buf: &VolatileSlice, ) -> 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( &mut self, buf: &mut VolatileSlice, ) -> Result { read_volatile_raw_fd(self, buf) } } impl WriteVolatile for $raw_fd_ty { fn write_volatile( &mut self, buf: &VolatileSlice, ) -> Result { write_volatile_raw_fd(self, buf) } } }; } impl WriteVolatile for Stdout { fn write_volatile( &mut self, buf: &VolatileSlice, ) -> Result { 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( raw_fd: &mut Fd, buf: &mut VolatileSlice, ) -> Result { let fd = raw_fd.as_raw_fd(); let guard = buf.ptr_guard_mut(); let dst = guard.as_ptr().cast::(); // 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( raw_fd: &mut Fd, buf: &VolatileSlice, ) -> Result { let fd = raw_fd.as_raw_fd(); let guard = buf.ptr_guard(); let src = guard.as_ptr().cast::(); // 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( &mut self, buf: &VolatileSlice, ) -> Result { 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( &mut self, buf: &VolatileSlice, ) -> 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( &mut self, buf: &mut VolatileSlice, ) -> Result { 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( &mut self, buf: &mut VolatileSlice, ) -> 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 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 { fn write_volatile( &mut self, buf: &VolatileSlice, ) -> Result { 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 is modelled after the standard // library's implementation (modulo having to inline `Cursor::remaining_slice`, as that's nightly only) impl ReadVolatile for Cursor where T: AsRef<[u8]>, { fn read_volatile( &mut self, buf: &mut VolatileSlice, ) -> Result { 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( &mut self, buf: &mut VolatileSlice, ) -> 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( &mut self, buf: &VolatileSlice, ) -> Result { 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, 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, 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, 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) { // 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); } }