mapped_vmo/immutable.rs
1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use std::ptr::NonNull;
6
7/// Provides a safe byte slice view of a VMO through a `Deref<Target=[u8]>` implementation.
8#[derive(Debug)]
9pub struct ImmutableMapping {
10 inner: Inner,
11}
12
13#[derive(Debug)]
14enum Inner {
15 Empty,
16 Vmar { addr: NonNull<u8>, size: usize },
17}
18
19impl ImmutableMapping {
20 /// If `vmo.get_size()` is not zero:
21 /// Creates an immutable child VMO (by setting ZX_VMO_CHILD_SNAPSHOT and ZX_VMO_CHILD_NO_WRITE)
22 /// of `vmo`, then creates a non-resizeable mapping of this child VMO and uses the mapping to
23 /// provide a `&[u8]` view of `vmo`'s contents at the time this function was called.
24 ///
25 /// * Writes to `vmo` after this function is called will not be reflected in the `&[u8]` view.
26 /// * `vmo` must have ZX_RIGHT_DUPLICATE and ZX_RIGHT_READ.
27 /// * If `immediately_page` is true, ZX_VM_MAP_RANGE will be set when the mapping is created.
28 ///
29 /// If `vmo.get_size()` is zero:
30 /// No VMOs will be created or mapped.
31 /// The `&[u8]` view will be backed by a static empty array.
32 /// This is for convenience to callers, as zx_vmar_map does not allow zero size mappings.
33 pub fn create_from_vmo(vmo: &zx::Vmo, immediately_page: bool) -> Result<Self, Error> {
34 let size = vmo.get_size().map_err(Error::GetVmoSize)?;
35 if size == 0 {
36 return Ok(Self { inner: Inner::Empty });
37 }
38 // std::slice::from_raw_parts cannot be called with more than isize::MAX bytes.
39 if let Err(source) = isize::try_from(size) {
40 return Err(Error::VmoSizeAsIsize { source, size });
41 }
42 let child = vmo
43 .create_child(zx::VmoChildOptions::SNAPSHOT | zx::VmoChildOptions::NO_WRITE, 0, size)
44 .map_err(Error::CreateChildVmo)?;
45 let size: usize =
46 size.try_into().map_err(|source| Error::VmoSizeAsUsize { source, size })?;
47 let addr = fuchsia_runtime::vmar_root_self()
48 .map(
49 0,
50 &child,
51 0,
52 size,
53 // ZX_VM_ALLOW_FAULTS is not necessary because child is not resizable, the mapping
54 // does not extend past the end of child (child and the mapping have the same size),
55 // child is not discardable, and child was not created with zx_pager_create_vmo.
56 zx::VmarFlags::REQUIRE_NON_RESIZABLE
57 | zx::VmarFlags::PERM_READ
58 | if immediately_page {
59 zx::VmarFlags::MAP_RANGE
60 } else {
61 zx::VmarFlags::empty()
62 },
63 )
64 .map_err(Error::MapChildVmo)?;
65 // This should never fail, zx_vmar_map always returns non-zero.
66 let addr = NonNull::new(addr as *mut u8).ok_or(Error::VmarMapReturnedNull)?;
67 Ok(Self { inner: Inner::Vmar { addr, size } })
68 }
69}
70
71impl std::ops::Deref for ImmutableMapping {
72 type Target = [u8];
73 fn deref(&self) -> &Self::Target {
74 match &self.inner {
75 Inner::Empty => &[],
76 Inner::Vmar { addr, size } => {
77 // Safety:
78 //
79 // The entire range of the slice is contained within a single allocated object, the
80 // memory mapping, which has the same size as the slice. The mapping is not
81 // resizable.
82 //
83 // zx_vmar_map, the syscall behind `fuchsia_runtime::vmar_root_self().map()`
84 // guarantees that addr is non-null and page aligned, and because T is u8 there is
85 // no alignment requirement. Additionally, addr is checked to be non-null when the
86 // ImmutableMapping is created. If size were zero we would take the preceding Empty
87 // branch.
88 //
89 // The slice contents are initialized to the contents of the original VMO at the
90 // time that the snapshot was taken. The contents will always be valid instances
91 // for T = u8.
92 //
93 // The memory referenced by the returned slice will never be mutated. The mapped
94 // child vmo is created with ZX_VMO_CHILD_SNAPSHOT and ZX_VMO_CHILD_NO_WRITE. The
95 // mapping is created with ZX_VM_REQUIRE_NON_RESIZABLE and only ZX_VM_PERM_READ.
96 // Also, at this point all the handles to the child vmo have been dropped and it is
97 // kept alive only by the mapping.
98 //
99 // Self::create_from_vmo guarantees that size <= isize::MAX and zx_vmar_map
100 // guarantees that addr + size will not wrap around the address space.
101 let addr = addr.as_ptr();
102 unsafe { std::slice::from_raw_parts(addr, *size) }
103 }
104 }
105 }
106}
107
108impl Drop for ImmutableMapping {
109 fn drop(&mut self) {
110 match self.inner {
111 Inner::Empty => (),
112 Inner::Vmar { addr, size } => {
113 // Safety:
114 //
115 // The memory pointed to by addr, aka the memory of the mapping we are about to
116 // unmap, is only accessible via the Deref impl on this type. This fn, drop, takes
117 // a mutable reference to self, so at this point there can not be any outstanding
118 // references obtained from <Self as Deref>::deref(&self).
119 //
120 // The child vmo and mapping were both created with only the read permission, so
121 // there cannot be any executable code relying on it.
122 let addr = addr.as_ptr() as usize;
123 unsafe {
124 let _: Result<(), zx::Status> =
125 fuchsia_runtime::vmar_root_self().unmap(addr, size);
126 }
127 }
128 }
129 }
130}
131
132/// Error type for `ImmutableMapping::create_from_vmo`.
133#[allow(missing_docs)]
134#[derive(thiserror::Error, Debug)]
135pub enum Error {
136 #[error("failed to get VMO size")]
137 GetVmoSize(#[source] zx::Status),
138
139 #[error("failed to convert VMO size {size} to isize")]
140 VmoSizeAsIsize { source: std::num::TryFromIntError, size: u64 },
141
142 #[error("failed to create child VMO")]
143 CreateChildVmo(#[source] zx::Status),
144
145 #[error("failed to convert VMO size {size} to usize")]
146 VmoSizeAsUsize { source: std::num::TryFromIntError, size: u64 },
147
148 #[error("failed to map child VMO")]
149 MapChildVmo(#[source] zx::Status),
150
151 #[error("zx_vmar_map returned a base address of zero")]
152 VmarMapReturnedNull,
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158 use std::io::Write as _;
159 use test_case::test_case;
160
161 #[test_case(true; "immediately-page")]
162 #[test_case(false; "do-not-immediately-page")]
163 fn read(immediately_page: bool) {
164 let vmo = zx::Vmo::create(1).unwrap();
165 let () = vmo.write(b"content", 0).unwrap();
166
167 let mapping = ImmutableMapping::create_from_vmo(&vmo, immediately_page).unwrap();
168
169 let mut expected = vec![0u8; zx::system_get_page_size().try_into().unwrap()];
170 assert_eq!(expected.as_mut_slice().write(b"content").unwrap(), 7);
171
172 assert_eq!(&mapping[..], expected.as_slice());
173 }
174
175 #[test_case(true; "immediately-page")]
176 #[test_case(false; "do-not-immediately-page")]
177 fn drop_cleans_up(immediately_page: bool) {
178 let vmo = zx::Vmo::create(7).unwrap();
179 assert!(
180 vmo.wait_one(zx::Signals::VMO_ZERO_CHILDREN, zx::MonotonicInstant::INFINITE_PAST)
181 .is_ok()
182 );
183
184 let mapping = ImmutableMapping::create_from_vmo(&vmo, immediately_page).unwrap();
185 assert!(
186 vmo.wait_one(zx::Signals::VMO_ZERO_CHILDREN, zx::MonotonicInstant::INFINITE_PAST)
187 .is_err()
188 );
189
190 drop(mapping);
191 assert!(
192 vmo.wait_one(zx::Signals::VMO_ZERO_CHILDREN, zx::MonotonicInstant::INFINITE_PAST)
193 .is_ok()
194 );
195 }
196
197 #[test_case(true; "immediately-page")]
198 #[test_case(false; "do-not-immediately-page")]
199 fn empty(immediately_page: bool) {
200 let vmo = zx::Vmo::create(0).unwrap();
201
202 let mapping = ImmutableMapping::create_from_vmo(&vmo, immediately_page).unwrap();
203
204 assert_eq!(*mapping, []);
205 }
206}