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
// 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 {fuchsia_zircon as zx, 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::*, std::io::Write as _, 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 fuchsia_zircon::AsHandleRef as _;
        let vmo = zx::Vmo::create(7).unwrap();
        assert!(vmo.wait_handle(zx::Signals::VMO_ZERO_CHILDREN, zx::Time::INFINITE_PAST).is_ok());

        let mapping = ImmutableMapping::create_from_vmo(&vmo, immediately_page).unwrap();
        assert!(vmo.wait_handle(zx::Signals::VMO_ZERO_CHILDREN, zx::Time::INFINITE_PAST).is_err());

        drop(mapping);
        assert!(vmo.wait_handle(zx::Signals::VMO_ZERO_CHILDREN, zx::Time::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, []);
    }
}