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}