mapped_vmo/immutable.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
// 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 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::*;
use std::io::Write as _;
use 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 zx::AsHandleRef as _;
let vmo = zx::Vmo::create(7).unwrap();
assert!(vmo
.wait_handle(zx::Signals::VMO_ZERO_CHILDREN, zx::MonotonicInstant::INFINITE_PAST)
.is_ok());
let mapping = ImmutableMapping::create_from_vmo(&vmo, immediately_page).unwrap();
assert!(vmo
.wait_handle(zx::Signals::VMO_ZERO_CHILDREN, zx::MonotonicInstant::INFINITE_PAST)
.is_err());
drop(mapping);
assert!(vmo
.wait_handle(zx::Signals::VMO_ZERO_CHILDREN, zx::MonotonicInstant::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, []);
}
}