blob: 8067ac8a7c80db1d01baf5bd65727bd7dc1f1f99 [file] [log] [blame]
// Copyright 2024 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use {fuchsia_zircon as zx, std::ptr::NonNull};
/// Provides a safe byte slice view of a VMO through a `Deref<Target=[u8]>` implementation.
pub struct ImmutableMapping {
inner: Inner,
}
enum Inner {
Empty,
Vmar { addr: NonNull<u8>, size: usize },
}
impl ImmutableMapping {
/// If `vmo.get_size()` is not zero:
/// Creates an immutable child VMO (by setting ZX_VMO_CHILD_SNAPSHOT and ZX_VMO_CHILD_NO_WRITE)
/// of `vmo`, then creates a non-resizeable mapping of this child VMO and uses the mapping to
/// provide a `&[u8]` view of `vmo`'s contents at the time this function was called.
///
/// * Writes to `vmo` after this function is called will not be reflected in the `&[u8]` view.
/// * `vmo` must have ZX_RIGHT_DUPLICATE and ZX_RIGHT_READ.
/// * If `immediately_page` is true, ZX_VM_MAP_RANGE will be set when the mapping is created.
///
/// If `vmo.get_size()` is zero:
/// No VMOs will be created or mapped.
/// The `&[u8]` view will be backed by a static empty array.
/// This is for convenience to callers, as zx_vmar_map does not allow zero size mappings.
pub fn create_from_vmo(vmo: &zx::Vmo, immediately_page: bool) -> Result<Self, Error> {
let size = vmo.get_size().map_err(Error::GetVmoSize)?;
if size == 0 {
return Ok(Self { inner: Inner::Empty });
}
// std::slice::from_raw_parts cannot be called with more than isize::MAX bytes.
if let Err(source) = isize::try_from(size) {
return Err(Error::VmoSizeAsIsize { source, size });
}
let child = vmo
.create_child(zx::VmoChildOptions::SNAPSHOT | zx::VmoChildOptions::NO_WRITE, 0, size)
.map_err(Error::CreateChildVmo)?;
let size: usize =
size.try_into().map_err(|source| Error::VmoSizeAsUsize { source, size })?;
let addr = fuchsia_runtime::vmar_root_self()
.map(
0,
&child,
0,
size,
// ZX_VM_ALLOW_FAULTS is not necessary because child is not resizable, the mapping
// does not extend past the end of child (child and the mapping have the same size),
// child is not discardable, and child was not created with zx_pager_create_vmo.
zx::VmarFlags::REQUIRE_NON_RESIZABLE
| zx::VmarFlags::PERM_READ
| if immediately_page {
zx::VmarFlags::MAP_RANGE
} else {
zx::VmarFlags::empty()
},
)
.map_err(Error::MapChildVmo)?;
// This should never fail, zx_vmar_map always returns non-zero.
let addr = NonNull::new(addr as *mut u8).ok_or(Error::VmarMapReturnedNull)?;
Ok(Self { inner: Inner::Vmar { addr, size } })
}
}
impl std::ops::Deref for ImmutableMapping {
type Target = [u8];
fn deref(&self) -> &Self::Target {
match &self.inner {
Inner::Empty => &[],
Inner::Vmar { addr, size } => {
// Safety:
//
// The entire range of the slice is contained within a single allocated object, the
// memory mapping, which has the same size as the slice. The mapping is not
// resizable.
//
// zx_vmar_map, the syscall behind `fuchsia_runtime::vmar_root_self().map()`
// guarantees that addr is non-null and page aligned, and because T is u8 there is
// no alignment requirement. Additionally, addr is checked to be non-null when the
// ImmutableMapping is created. If size were zero we would take the preceding Empty
// branch.
//
// The slice contents are initialized to the contents of the original VMO at the
// time that the snapshot was taken. The contents will always be valid instances
// for T = u8.
//
// The memory referenced by the returned slice will never be mutated. The mapped
// child vmo is created with ZX_VMO_CHILD_SNAPSHOT and ZX_VMO_CHILD_NO_WRITE. The
// mapping is created with ZX_VM_REQUIRE_NON_RESIZABLE and only ZX_VM_PERM_READ.
// Also, at this point all the handles to the child vmo have been dropped and it is
// kept alive only by the mapping.
//
// Self::create_from_vmo guarantees that size <= isize::MAX and zx_vmar_map
// guarantees that addr + size will not wrap around the address space.
let addr = addr.as_ptr();
unsafe { std::slice::from_raw_parts(addr, *size) }
}
}
}
}
impl Drop for ImmutableMapping {
fn drop(&mut self) {
match self.inner {
Inner::Empty => (),
Inner::Vmar { addr, size } => {
// Safety:
//
// The memory pointed to by addr, aka the memory of the mapping we are about to
// unmap, is only accessible via the Deref impl on this type. This fn, drop, takes
// a mutable reference to self, so at this point there can not be any outstanding
// references obtained from <Self as Deref>::deref(&self).
//
// The child vmo and mapping were both created with only the read permission, so
// there cannot be any executable code relying on it.
let addr = addr.as_ptr() as usize;
unsafe {
let _: Result<(), zx::Status> =
fuchsia_runtime::vmar_root_self().unmap(addr, size);
}
}
}
}
}
/// Error type for `ImmutableMapping::create_from_vmo`.
#[allow(missing_docs)]
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("failed to get VMO size")]
GetVmoSize(#[source] zx::Status),
#[error("failed to convert VMO size {size} to isize")]
VmoSizeAsIsize { source: std::num::TryFromIntError, size: u64 },
#[error("failed to create child VMO")]
CreateChildVmo(#[source] zx::Status),
#[error("failed to convert VMO size {size} to usize")]
VmoSizeAsUsize { source: std::num::TryFromIntError, size: u64 },
#[error("failed to map child VMO")]
MapChildVmo(#[source] zx::Status),
#[error("zx_vmar_map returned a base address of zero")]
VmarMapReturnedNull,
}
#[cfg(test)]
mod tests {
use {super::*, std::io::Write as _, test_case::test_case};
#[test_case(true; "immediately-page")]
#[test_case(false; "do-not-immediately-page")]
fn read(immediately_page: bool) {
let vmo = zx::Vmo::create(1).unwrap();
let () = vmo.write(b"content", 0).unwrap();
let mapping = ImmutableMapping::create_from_vmo(&vmo, immediately_page).unwrap();
let mut expected = vec![0u8; zx::system_get_page_size().try_into().unwrap()];
assert_eq!(expected.as_mut_slice().write(b"content").unwrap(), 7);
assert_eq!(&mapping[..], expected.as_slice());
}
#[test_case(true; "immediately-page")]
#[test_case(false; "do-not-immediately-page")]
fn drop_cleans_up(immediately_page: bool) {
use fuchsia_zircon::AsHandleRef as _;
let vmo = zx::Vmo::create(7).unwrap();
assert!(vmo.wait_handle(zx::Signals::VMO_ZERO_CHILDREN, zx::Time::INFINITE_PAST).is_ok());
let mapping = ImmutableMapping::create_from_vmo(&vmo, immediately_page).unwrap();
assert!(vmo.wait_handle(zx::Signals::VMO_ZERO_CHILDREN, zx::Time::INFINITE_PAST).is_err());
drop(mapping);
assert!(vmo.wait_handle(zx::Signals::VMO_ZERO_CHILDREN, zx::Time::INFINITE_PAST).is_ok());
}
#[test_case(true; "immediately-page")]
#[test_case(false; "do-not-immediately-page")]
fn empty(immediately_page: bool) {
let vmo = zx::Vmo::create(0).unwrap();
let mapping = ImmutableMapping::create_from_vmo(&vmo, immediately_page).unwrap();
assert_eq!(*mapping, []);
}
}