blob: d40bc4357b8105b9efb3535f35372265a9448054 [file] [log] [blame]
// Copyright 2020 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::cmp::min;
use std::fs::File;
use std::intrinsics::copy_nonoverlapping;
use std::io;
use std::mem::size_of;
use std::ptr::read_unaligned;
use std::ptr::read_volatile;
use std::ptr::write_unaligned;
use std::ptr::write_volatile;
use std::sync::atomic::fence;
use std::sync::atomic::Ordering;
use remain::sorted;
use serde::Deserialize;
use serde::Serialize;
use zerocopy::AsBytes;
use zerocopy::FromBytes;
use crate::descriptor::AsRawDescriptor;
use crate::descriptor::SafeDescriptor;
use crate::platform::MemoryMapping as PlatformMmap;
use crate::SharedMemory;
use crate::VolatileMemory;
use crate::VolatileMemoryError;
use crate::VolatileMemoryResult;
use crate::VolatileSlice;
#[sorted]
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("`add_fd_mapping` is unsupported")]
AddFdMappingIsUnsupported,
#[error("requested memory out of range")]
InvalidAddress,
#[error("invalid argument provided when creating mapping")]
InvalidArgument,
#[error("requested offset is out of range of off_t")]
InvalidOffset,
#[error("requested memory range spans past the end of the region: offset={0} count={1} region_size={2}")]
InvalidRange(usize, usize, usize),
#[error("requested memory is not page aligned")]
NotPageAligned,
#[error("failed to read from file to memory: {0}")]
ReadToMemory(#[source] io::Error),
#[error("`remove_mapping` is unsupported")]
RemoveMappingIsUnsupported,
#[error("system call failed while creating the mapping: {0}")]
StdSyscallFailed(io::Error),
#[error("mmap related system call failed: {0}")]
SystemCallFailed(#[source] crate::Error),
#[error("failed to write from memory to file: {0}")]
WriteFromMemory(#[source] io::Error),
}
pub type Result<T> = std::result::Result<T, Error>;
/// Memory access type for anonymous shared memory mapping.
#[derive(Copy, Clone, Default, Eq, PartialEq, Serialize, Deserialize, Debug)]
pub struct Protection {
pub(crate) read: bool,
pub(crate) write: bool,
}
impl Protection {
/// Returns Protection allowing read/write access.
#[inline(always)]
pub fn read_write() -> Protection {
Protection {
read: true,
write: true,
}
}
/// Returns Protection allowing read access.
#[inline(always)]
pub fn read() -> Protection {
Protection {
read: true,
..Default::default()
}
}
/// Returns Protection allowing write access.
#[inline(always)]
pub fn write() -> Protection {
Protection {
write: true,
..Default::default()
}
}
/// Set read events.
#[inline(always)]
pub fn set_read(self) -> Protection {
Protection { read: true, ..self }
}
/// Set write events.
#[inline(always)]
pub fn set_write(self) -> Protection {
Protection {
write: true,
..self
}
}
/// Returns true if all access allowed by |other| is also allowed by |self|.
#[inline(always)]
pub fn allows(&self, other: &Protection) -> bool {
self.read >= other.read && self.write >= other.write
}
}
/// See [MemoryMapping](crate::platform::MemoryMapping) for struct- and method-level
/// documentation.
#[derive(Debug)]
pub struct MemoryMapping {
pub(crate) mapping: PlatformMmap,
// File backed mappings on Windows need to keep the underlying file open while the mapping is
// open.
// This will be a None in non-windows case. The variable will not be read so the '^_'.
//
// TODO(b:230902713) There was a concern about relying on the kernel's refcounting to keep the
// file object's locks (e.g. exclusive read/write) in place. We need to revisit/validate that
// concern.
pub(crate) _file_descriptor: Option<SafeDescriptor>,
}
impl MemoryMapping {
pub fn write_slice(&self, buf: &[u8], offset: usize) -> Result<usize> {
match self.mapping.size().checked_sub(offset) {
Some(size_past_offset) => {
let bytes_copied = min(size_past_offset, buf.len());
// SAFETY:
// The bytes_copied equation above ensures we don't copy bytes out of range of
// either buf or this slice. We also know that the buffers do not overlap because
// slices can never occupy the same memory as a volatile slice.
unsafe {
copy_nonoverlapping(buf.as_ptr(), self.as_ptr().add(offset), bytes_copied);
}
Ok(bytes_copied)
}
None => Err(Error::InvalidAddress),
}
}
pub fn read_slice(&self, buf: &mut [u8], offset: usize) -> Result<usize> {
match self.size().checked_sub(offset) {
Some(size_past_offset) => {
let bytes_copied = min(size_past_offset, buf.len());
// SAFETY:
// The bytes_copied equation above ensures we don't copy bytes out of range of
// either buf or this slice. We also know that the buffers do not overlap because
// slices can never occupy the same memory as a volatile slice.
unsafe {
copy_nonoverlapping(self.as_ptr().add(offset), buf.as_mut_ptr(), bytes_copied);
}
Ok(bytes_copied)
}
None => Err(Error::InvalidAddress),
}
}
/// Writes an object to the memory region at the specified offset.
/// Returns Ok(()) if the object fits, or Err if it extends past the end.
///
/// This method is for writing to regular memory. If writing to a mapped
/// I/O region, use [`MemoryMapping::write_obj_volatile`].
///
/// # Examples
/// * Write a u64 at offset 16.
///
/// ```
/// # use base::MemoryMappingBuilder;
/// # use base::SharedMemory;
/// # let shm = SharedMemory::new("test", 1024).unwrap();
/// # let mut mem_map = MemoryMappingBuilder::new(1024).from_shared_memory(&shm).build().unwrap();
/// let res = mem_map.write_obj(55u64, 16);
/// assert!(res.is_ok());
/// ```
pub fn write_obj<T: AsBytes>(&self, val: T, offset: usize) -> Result<()> {
self.mapping.range_end(offset, size_of::<T>())?;
// SAFETY:
// This is safe because we checked the bounds above.
unsafe {
write_unaligned(self.as_ptr().add(offset) as *mut T, val);
}
Ok(())
}
/// Reads on object from the memory region at the given offset.
/// Reading from a volatile area isn't strictly safe as it could change
/// mid-read. However, as long as the type T is plain old data and can
/// handle random initialization, everything will be OK.
///
/// This method is for reading from regular memory. If reading from a
/// mapped I/O region, use [`MemoryMapping::read_obj_volatile`].
///
/// # Examples
/// * Read a u64 written to offset 32.
///
/// ```
/// # use base::MemoryMappingBuilder;
/// # let mut mem_map = MemoryMappingBuilder::new(1024).build().unwrap();
/// let res = mem_map.write_obj(55u64, 32);
/// assert!(res.is_ok());
/// let num: u64 = mem_map.read_obj(32).unwrap();
/// assert_eq!(55, num);
/// ```
pub fn read_obj<T: FromBytes>(&self, offset: usize) -> Result<T> {
self.mapping.range_end(offset, size_of::<T>())?;
// SAFETY:
// This is safe because by definition Copy types can have their bits set arbitrarily and
// still be valid.
unsafe {
Ok(read_unaligned(
self.as_ptr().add(offset) as *const u8 as *const T
))
}
}
/// Writes an object to the memory region at the specified offset.
/// Returns Ok(()) if the object fits, or Err if it extends past the end.
///
/// The write operation will be volatile, i.e. it will not be reordered by
/// the compiler and is suitable for I/O, but must be aligned. When writing
/// to regular memory, prefer [`MemoryMapping::write_obj`].
///
/// # Examples
/// * Write a u32 at offset 16.
///
/// ```
/// # use base::MemoryMappingBuilder;
/// # use base::SharedMemory;
/// # let shm = SharedMemory::new("test", 1024).unwrap();
/// # let mut mem_map = MemoryMappingBuilder::new(1024).from_shared_memory(&shm).build().unwrap();
/// let res = mem_map.write_obj_volatile(0xf00u32, 16);
/// assert!(res.is_ok());
/// ```
pub fn write_obj_volatile<T: AsBytes>(&self, val: T, offset: usize) -> Result<()> {
self.mapping.range_end(offset, size_of::<T>())?;
// Make sure writes to memory have been committed before performing I/O that could
// potentially depend on them.
fence(Ordering::SeqCst);
// SAFETY:
// This is safe because we checked the bounds above.
unsafe {
write_volatile(self.as_ptr().add(offset) as *mut T, val);
}
Ok(())
}
/// Reads on object from the memory region at the given offset.
/// Reading from a volatile area isn't strictly safe as it could change
/// mid-read. However, as long as the type T is plain old data and can
/// handle random initialization, everything will be OK.
///
/// The read operation will be volatile, i.e. it will not be reordered by
/// the compiler and is suitable for I/O, but must be aligned. When reading
/// from regular memory, prefer [`MemoryMapping::read_obj`].
///
/// # Examples
/// * Read a u32 written to offset 16.
///
/// ```
/// # use base::MemoryMappingBuilder;
/// # use base::SharedMemory;
/// # let shm = SharedMemory::new("test", 1024).unwrap();
/// # let mut mem_map = MemoryMappingBuilder::new(1024).from_shared_memory(&shm).build().unwrap();
/// let res = mem_map.write_obj(0xf00u32, 16);
/// assert!(res.is_ok());
/// let num: u32 = mem_map.read_obj_volatile(16).unwrap();
/// assert_eq!(0xf00, num);
/// ```
pub fn read_obj_volatile<T: FromBytes>(&self, offset: usize) -> Result<T> {
self.mapping.range_end(offset, size_of::<T>())?;
// SAFETY:
// This is safe because by definition Copy types can have their bits set arbitrarily and
// still be valid.
unsafe {
Ok(read_volatile(
self.as_ptr().add(offset) as *const u8 as *const T
))
}
}
pub fn msync(&self) -> Result<()> {
self.mapping.msync()
}
/// Flush memory which the guest may be accessing through an uncached mapping.
///
/// Reads via an uncached mapping can bypass the cache and directly access main
/// memory. This is outside the memory model of Rust, which means that even with
/// proper synchronization, guest reads via an uncached mapping might not see
/// updates from the host. As such, it is necessary to perform architectural
/// cache maintainance to flush the host writes to main memory.
///
/// Note that this does not support writable uncached guest mappings, as doing so
/// requires invalidating the cache, not flushing the cache.
///
/// Currently only supported on x86_64 and aarch64. Cannot be supported on 32-bit arm.
pub fn flush_uncached_guest_mapping(&self, offset: usize) {
if offset > self.mapping.size() {
return;
}
// SAFETY: We checked that offset is within the mapping, and flushing
// the cache doesn't affect any rust safety properties.
unsafe {
#[allow(unused)]
let target = self.mapping.as_ptr().add(offset);
cfg_if::cfg_if! {
if #[cfg(target_arch = "x86_64")] {
// As per table 11-7 of the SDM, processors are not required to
// snoop UC mappings, so flush the target to memory.
core::arch::x86_64::_mm_clflush(target);
} else if #[cfg(target_arch = "aarch64")] {
// Data cache clean by VA to PoC.
std::arch::asm!("DC CVAC, {x}", x = in(reg) target);
} else if #[cfg(target_arch = "arm")] {
panic!("Userspace cannot flush to PoC");
} else {
unimplemented!("Cache flush not implemented")
}
}
}
}
}
pub struct MemoryMappingBuilder<'a> {
pub(crate) descriptor: Option<&'a dyn AsRawDescriptor>,
pub(crate) is_file_descriptor: bool,
#[cfg_attr(target_os = "macos", allow(unused))]
pub(crate) size: usize,
pub(crate) offset: Option<u64>,
pub(crate) protection: Option<Protection>,
#[cfg_attr(target_os = "macos", allow(unused))]
#[cfg_attr(windows, allow(unused))]
pub(crate) populate: bool,
}
/// Builds a MemoryMapping object from the specified arguments.
impl<'a> MemoryMappingBuilder<'a> {
/// Creates a new builder specifying size of the memory region in bytes.
pub fn new(size: usize) -> MemoryMappingBuilder<'a> {
MemoryMappingBuilder {
descriptor: None,
size,
is_file_descriptor: false,
offset: None,
protection: None,
populate: false,
}
}
/// Build the memory mapping given the specified File to mapped memory
///
/// Default: Create a new memory mapping.
///
/// Note: this is a forward looking interface to accomodate platforms that
/// require special handling for file backed mappings.
#[allow(clippy::wrong_self_convention, unused_mut)]
pub fn from_file(mut self, file: &'a File) -> MemoryMappingBuilder {
// On Windows, files require special handling (next day shipping if possible).
self.is_file_descriptor = true;
self.descriptor = Some(file as &dyn AsRawDescriptor);
self
}
/// Build the memory mapping given the specified SharedMemory to mapped memory
///
/// Default: Create a new memory mapping.
pub fn from_shared_memory(mut self, shm: &'a SharedMemory) -> MemoryMappingBuilder {
self.descriptor = Some(shm as &dyn AsRawDescriptor);
self
}
/// Offset in bytes from the beginning of the mapping to start the mmap.
///
/// Default: No offset
pub fn offset(mut self, offset: u64) -> MemoryMappingBuilder<'a> {
self.offset = Some(offset);
self
}
/// Protection (e.g. readable/writable) of the memory region.
///
/// Default: Read/write
pub fn protection(mut self, protection: Protection) -> MemoryMappingBuilder<'a> {
self.protection = Some(protection);
self
}
}
impl VolatileMemory for MemoryMapping {
fn get_slice(&self, offset: usize, count: usize) -> VolatileMemoryResult<VolatileSlice> {
let mem_end = offset
.checked_add(count)
.ok_or(VolatileMemoryError::Overflow {
base: offset,
offset: count,
})?;
if mem_end > self.size() {
return Err(VolatileMemoryError::OutOfBounds { addr: mem_end });
}
let new_addr =
(self.as_ptr() as usize)
.checked_add(offset)
.ok_or(VolatileMemoryError::Overflow {
base: self.as_ptr() as usize,
offset,
})?;
// SAFETY:
// Safe because we checked that offset + count was within our range and we only ever hand
// out volatile accessors.
Ok(unsafe { VolatileSlice::from_raw_parts(new_addr as *mut u8, count) })
}
}
/// A range of memory that can be msynced, for abstracting over different types of memory mappings.
///
/// # Safety
/// Safe when implementers guarantee `ptr`..`ptr+size` is an mmaped region owned by this object that
/// can't be unmapped during the `MappedRegion`'s lifetime.
pub unsafe trait MappedRegion: Send + Sync {
// SAFETY:
/// Returns a pointer to the beginning of the memory region. Should only be
/// used for passing this region to ioctls for setting guest memory.
fn as_ptr(&self) -> *mut u8;
/// Returns the size of the memory region in bytes.
fn size(&self) -> usize;
/// Maps `size` bytes starting at `fd_offset` bytes from within the given `fd`
/// at `offset` bytes from the start of the region with `prot` protections.
/// `offset` must be page aligned.
///
/// # Arguments
/// * `offset` - Page aligned offset into the arena in bytes.
/// * `size` - Size of memory region in bytes.
/// * `fd` - File descriptor to mmap from.
/// * `fd_offset` - Offset in bytes from the beginning of `fd` to start the mmap.
/// * `prot` - Protection (e.g. readable/writable) of the memory region.
fn add_fd_mapping(
&mut self,
_offset: usize,
_size: usize,
_fd: &dyn AsRawDescriptor,
_fd_offset: u64,
_prot: Protection,
) -> Result<()> {
Err(Error::AddFdMappingIsUnsupported)
}
/// Remove `size`-byte mapping starting at `offset`.
fn remove_mapping(&mut self, _offset: usize, _size: usize) -> Result<()> {
Err(Error::RemoveMappingIsUnsupported)
}
}
// SAFETY:
// Safe because it exclusively forwards calls to a safe implementation.
unsafe impl MappedRegion for MemoryMapping {
fn as_ptr(&self) -> *mut u8 {
self.mapping.as_ptr()
}
fn size(&self) -> usize {
self.mapping.size()
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct ExternalMapping {
pub ptr: u64,
pub size: usize,
}
// SAFETY:
// `ptr`..`ptr+size` is an mmaped region and is owned by this object. Caller
// needs to ensure that the region is not unmapped during the `MappedRegion`'s
// lifetime.
unsafe impl MappedRegion for ExternalMapping {
/// used for passing this region to ioctls for setting guest memory.
fn as_ptr(&self) -> *mut u8 {
self.ptr as *mut u8
}
/// Returns the size of the memory region in bytes.
fn size(&self) -> usize {
self.size
}
}