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