Skip to main content

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}