Skip to main content

nub_host_kvm/mem/
shared_mem.rs

1/*
2Copyright 2025  The Hyperlight Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17use std::any::type_name;
18use std::ffi::c_void;
19use std::io::Error;
20use std::mem::{align_of, size_of};
21use std::ptr::null_mut;
22use std::sync::{Arc, RwLock};
23
24use nub_host_common::mem::PAGE_SIZE_USIZE;
25use tracing::{Span, instrument};
26
27use super::memory_region::{
28    HostGuestMemoryRegion, MemoryRegion, MemoryRegionFlags, MemoryRegionKind, MemoryRegionType,
29};
30use crate::{HyperlightError, Result, log_then_return, new_error};
31
32/// Makes sure that the given `offset` and `size` are within the bounds of the memory with size `mem_size`.
33macro_rules! bounds_check {
34    ($offset:expr, $size:expr, $mem_size:expr) => {
35        if $offset.checked_add($size).is_none_or(|end| end > $mem_size) {
36            return Err(new_error!(
37                "Cannot read value from offset {} with size {} in memory of size {}",
38                $offset,
39                $size,
40                $mem_size
41            ));
42        }
43    };
44}
45
46/// generates a reader function for the given type
47macro_rules! generate_reader {
48    ($fname:ident, $ty:ty) => {
49        /// Read a value of type `$ty` from the memory at the given offset.
50        #[allow(dead_code)]
51        #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
52        pub(crate) fn $fname(&self, offset: usize) -> Result<$ty> {
53            let data = self.as_slice();
54            bounds_check!(offset, std::mem::size_of::<$ty>(), data.len());
55            Ok(<$ty>::from_le_bytes(
56                data[offset..offset + std::mem::size_of::<$ty>()].try_into()?,
57            ))
58        }
59    };
60}
61
62/// generates a writer function for the given type
63macro_rules! generate_writer {
64    ($fname:ident, $ty:ty) => {
65        /// Write a value of type `$ty` to the memory at the given offset.
66        #[allow(dead_code)]
67        pub(crate) fn $fname(&mut self, offset: usize, value: $ty) -> Result<()> {
68            let data = self.as_mut_slice();
69            bounds_check!(offset, std::mem::size_of::<$ty>(), data.len());
70            data[offset..offset + std::mem::size_of::<$ty>()].copy_from_slice(&value.to_le_bytes());
71            Ok(())
72        }
73    };
74}
75
76/// A representation of a host mapping of a shared memory region,
77/// which will be released when this structure is Drop'd. This is not
78/// individually Clone (since it holds ownership of the mapping), or
79/// Send or Sync, since it doesn't ensure any particular synchronization.
80#[derive(Debug)]
81pub struct HostMapping {
82    ptr: *mut u8,
83    size: usize,
84}
85
86impl Drop for HostMapping {
87    fn drop(&mut self) {
88        use libc::munmap;
89
90        unsafe {
91            munmap(self.ptr as *mut c_void, self.size);
92        }
93    }
94}
95
96/// These three structures represent various phases of the lifecycle of
97/// a memory buffer that is shared with the guest. An
98/// ExclusiveSharedMemory is used for certain operations that
99/// unrestrictedly write to the shared memory, including setting it up
100/// and taking snapshots.
101#[derive(Debug)]
102pub struct ExclusiveSharedMemory {
103    region: Arc<HostMapping>,
104}
105unsafe impl Send for ExclusiveSharedMemory {}
106
107/// A GuestSharedMemory is used to represent
108/// the reference to all-of-memory that is taken by the virtual cpu.
109/// Because of the memory model limitations that affect
110/// HostSharedMemory, it is likely fairly important (to ensure that
111/// our UB remains limited to interaction with an external compilation
112/// unit that likely can't be discovered by the compiler) that _rust_
113/// users do not perform racy accesses to the guest communication
114/// buffers that are also accessed by HostSharedMemory.
115#[derive(Debug)]
116pub struct GuestSharedMemory {
117    region: Arc<HostMapping>,
118    /// The lock that indicates this shared memory is being used by non-Rust code
119    ///
120    /// This lock _must_ be held whenever the guest is executing,
121    /// because it prevents the host from converting its
122    /// HostSharedMemory to an ExclusiveSharedMemory. Since the guest
123    /// may arbitrarily mutate the shared memory, only synchronized
124    /// accesses from Rust should be allowed!
125    ///
126    /// We cannot enforce this in the type system, because the memory
127    /// is mapped in to the VM at VM creation time.
128    pub lock: Arc<RwLock<()>>,
129}
130// SAFETY: `GuestSharedMemory` is the KVM-facing handle for a mapping whose
131// lifetime is owned by `Arc<HostMapping>`. The wrapper exposes no direct Rust
132// slice access except through `SharedMemory::with_exclusivity`, which takes the
133// shared RwLock's write side and therefore excludes host-side volatile access
134// while exclusive remapping/snapshotting is in progress. Sharing this handle
135// between host worker threads only shares the registered mapping/lifetime
136// handle; synchronized host reads/writes still go through `HostSharedMemory`.
137unsafe impl Send for GuestSharedMemory {}
138unsafe impl Sync for GuestSharedMemory {}
139
140/// A HostSharedMemory allows synchronized accesses to guest
141/// communication buffers, allowing it to be used concurrently with a
142/// GuestSharedMemory.
143///
144/// # Concurrency model
145///
146/// Given future requirements for asynchronous I/O with a minimum
147/// amount of copying (e.g. WASIp3 streams), we would like it to be
148/// possible to safely access these buffers concurrently with the
149/// guest, ensuring that (1) data is read appropriately if the guest
150/// is well-behaved; and (2) the host's behaviour is defined
151/// regardless of whether or not the guest is well-behaved.
152///
153/// The ideal (future) flow for a guest->host message is something like
154///   - Guest writes (unordered) bytes describing a work item into a buffer
155///   - Guest reveals buffer via a release-store of a pointer into an
156///     MMIO ring-buffer
157///   - Host acquire-loads the buffer pointer from the "MMIO" ring
158///     buffer
159///   - Host (unordered) reads the bytes from the buffer
160///   - Host performs validation of those bytes and uses them
161///
162/// Unfortunately, there appears to be no way to do this with defined
163/// behaviour in present Rust (see
164/// e.g. <https://github.com/rust-lang/unsafe-code-guidelines/issues/152>).
165/// Rust does not yet have its own defined memory model, but in the
166/// interim, it is widely treated as inheriting the current C/C++
167/// memory models.  The most immediate problem is that regardless of
168/// anything else, under those memory models \[1, p. 17-18; 2, p. 88\],
169///
170///   > The execution of a program contains a _data race_ if it
171///   > contains two [C++23: "potentially concurrent"] conflicting
172///   > actions [C23: "in different threads"], at least one of which
173///   > is not atomic, and neither happens before the other [C++23: ",
174///   > except for the special case for signal handlers described
175///   > below"].  Any such data race results in undefined behavior.
176///
177/// Consequently, if a misbehaving guest fails to correctly
178/// synchronize its stores with the host, the host's innocent loads
179/// will trigger undefined behaviour for the entire program, including
180/// the host.  Note that this also applies if the guest makes an
181/// unsynchronized read of a location that the host is writing!
182///
183/// Despite Rust's de jure inheritance of the C memory model at the
184/// present time, the compiler in many cases de facto adheres to LLVM
185/// semantics, so it is worthwhile to consider what LLVM does in this
186/// case as well.  According to the the LangRef \[3\] memory model,
187/// loads which are involved in a race that includes at least one
188/// non-atomic access (whether the load or a store) return `undef`,
189/// making them roughly equivalent to reading uninitialized
190/// memory. While this is much better, it is still bad.
191///
192/// Considering a different direction, recent C++ papers have seemed
193/// to lean towards using `volatile` for similar use cases. For
194/// example, in P1152R0 \[4\], JF Bastien notes that
195///
196///   > We’ve shown that volatile is purposely defined to denote
197///   > external modifications. This happens for:
198///   >   - Shared memory with untrusted code, where volatile is the
199///   >     right way to avoid time-of-check time-of-use (ToCToU)
200///   >     races which lead to security bugs such as \[PWN2OWN\] and
201///   >     \[XENXSA155\].
202///
203/// Unfortunately, although this paper was adopted for C++20 (and,
204/// sadly, mostly un-adopted for C++23, although that does not concern
205/// us), the paper did not actually redefine volatile accesses or data
206/// races to prevent volatile accesses from racing with other accesses
207/// and causing undefined behaviour.  P1382R1 \[5\] would have amended
208/// the wording of the data race definition to specifically exclude
209/// volatile, but, unfortunately, despite receiving a
210/// generally-positive reception at its first WG21 meeting more than
211/// five years ago, it has not progressed.
212///
213/// Separately from the data race issue, there is also a concern that
214/// according to the various memory models in use, there may be ways
215/// in which the guest can semantically obtain uninitialized memory
216/// and write it into the shared buffer, which may also result in
217/// undefined behaviour on reads.  The degree to which this is a
218/// concern is unclear, however, since it is unclear to what degree
219/// the Rust abstract machine's conception of uninitialized memory
220/// applies to the sandbox.  Returning briefly to the LLVM level,
221/// rather than the Rust level, this, combined with the fact that
222/// racing loads in LLVM return `undef`, as discussed above, we would
223/// ideally `llvm.freeze` the result of any load out of the sandbox.
224///
225/// It would furthermore be ideal if we could run the flatbuffers
226/// parsing code directly on the guest memory, in order to avoid
227/// unnecessary copies.  That is unfortunately probably not viable at
228/// the present time: because the generated flatbuffers parsing code
229/// doesn't use atomic or volatile accesses, it is likely to introduce
230/// double-read vulnerabilities.
231///
232/// In short, none of the Rust-level operations available to us do the
233/// right thing, at the Rust spec level or the LLVM spec level. Our
234/// major remaining options are therefore:
235///   - Choose one of the options that is available to us, and accept
236///     that we are doing something unsound according to the spec, but
237///     hope that no reasonable compiler could possibly notice.
238///   - Use inline assembly per architecture, for which we would only
239///     need to worry about the _architecture_'s memory model (which
240///     is far less demanding).
241///
242/// The leading candidate for the first option would seem to be to
243/// simply use volatile accesses; there seems to be wide agreement
244/// that this _should_ be a valid use case for them (even if it isn't
245/// now), and projects like Linux and rust-vmm already use C11
246/// `volatile` for this purpose.  It is also worth noting that because
247/// we still do need to synchronize with the guest when it _is_ being
248/// well-behaved, we would ideally use volatile acquire loads and
249/// volatile release stores for interacting with the stack pointer in
250/// the guest in this case.  Unfortunately, while those operations are
251/// defined in LLVM, they are not presently exposed to Rust. While
252/// atomic fences that are not associated with memory accesses
253/// ([`std::sync::atomic::fence`]) might at first glance seem to help with
254/// this problem, they unfortunately do not \[6\]:
255///
256///    > A fence ‘A’ which has (at least) Release ordering semantics,
257///    > synchronizes with a fence ‘B’ with (at least) Acquire
258///    > semantics, if and only if there exist operations X and Y,
259///    > both operating on some atomic object ‘M’ such that A is
260///    > sequenced before X, Y is sequenced before B and Y observes
261///    > the change to M. This provides a happens-before dependence
262///    > between A and B.
263///
264/// Note that the X and Y must be to an _atomic_ object.
265///
266/// We consequently assume that there has been a strong architectural
267/// fence on a vmenter/vmexit between data being read and written.
268/// This is unsafe (not guaranteed in the type system)!
269///
270/// \[1\] N3047 C23 Working Draft. <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3047.pdf>
271/// \[2\] N4950 C++23 Working Draft. <https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/n4950.pdf>
272/// \[3\] LLVM Language Reference Manual, Memory Model for Concurrent Operations. <https://llvm.org/docs/LangRef.html#memmodel>
273/// \[4\] P1152R0: Deprecating `volatile`. JF Bastien. <https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1152r0.html>
274/// \[5\] P1382R1: `volatile_load<T>` and `volatile_store<T>`. JF Bastien, Paul McKenney, Jeffrey Yasskin, and the indefatigable TBD. <https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1382r1.pdf>
275/// \[6\] Documentation for std::sync::atomic::fence. <https://doc.rust-lang.org/std/sync/atomic/fn.fence.html>
276#[derive(Clone, Debug)]
277pub struct HostSharedMemory {
278    region: Arc<HostMapping>,
279    lock: Arc<RwLock<()>>,
280}
281unsafe impl Send for HostSharedMemory {}
282
283impl ExclusiveSharedMemory {
284    /// Create a new region of shared memory with the given minimum
285    /// size in bytes. The region will be surrounded by guard pages.
286    ///
287    /// Return `Err` if shared memory could not be allocated.
288    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
289    pub fn new(min_size_bytes: usize) -> Result<Self> {
290        use libc::{
291            MAP_ANONYMOUS, MAP_FAILED, MAP_PRIVATE, PROT_READ, PROT_WRITE, c_int, mmap, off_t,
292            size_t,
293        };
294        #[cfg(not(miri))]
295        use libc::{MAP_NORESERVE, PROT_NONE, mprotect};
296
297        if min_size_bytes == 0 {
298            return Err(new_error!("Cannot create shared memory with size 0"));
299        }
300
301        let total_size = min_size_bytes
302            .checked_add(2 * PAGE_SIZE_USIZE) // guard page around the memory
303            .ok_or_else(|| new_error!("Memory required for sandbox exceeded usize::MAX"))?;
304
305        if total_size % PAGE_SIZE_USIZE != 0 {
306            return Err(new_error!(
307                "shared memory must be a multiple of {}",
308                PAGE_SIZE_USIZE
309            ));
310        }
311
312        // usize and isize are guaranteed to be the same size, and
313        // isize::MAX should be positive, so this cast should be safe.
314        if total_size > isize::MAX as usize {
315            return Err(HyperlightError::MemoryRequestTooBig(
316                total_size,
317                isize::MAX as usize,
318            ));
319        }
320
321        // allocate the memory
322        #[cfg(not(miri))]
323        let flags = MAP_ANONYMOUS | MAP_PRIVATE | MAP_NORESERVE;
324        #[cfg(miri)]
325        let flags = MAP_ANONYMOUS | MAP_PRIVATE;
326
327        let addr = unsafe {
328            mmap(
329                null_mut(),
330                total_size as size_t,
331                PROT_READ | PROT_WRITE,
332                flags,
333                -1 as c_int,
334                0 as off_t,
335            )
336        };
337        if addr == MAP_FAILED {
338            log_then_return!(HyperlightError::MmapFailed(
339                Error::last_os_error().raw_os_error()
340            ));
341        }
342
343        // protect the guard pages
344        #[cfg(not(miri))]
345        {
346            let res = unsafe { mprotect(addr, PAGE_SIZE_USIZE, PROT_NONE) };
347            if res != 0 {
348                return Err(HyperlightError::MprotectFailed(
349                    Error::last_os_error().raw_os_error(),
350                ));
351            }
352            let res = unsafe {
353                mprotect(
354                    (addr as *const u8).add(total_size - PAGE_SIZE_USIZE) as *mut c_void,
355                    PAGE_SIZE_USIZE,
356                    PROT_NONE,
357                )
358            };
359            if res != 0 {
360                return Err(HyperlightError::MprotectFailed(
361                    Error::last_os_error().raw_os_error(),
362                ));
363            }
364        }
365
366        Ok(Self {
367            // HostMapping is only non-Send/Sync because raw pointers
368            // are not ("as a lint", as the Rust docs say). We don't
369            // want to mark HostMapping Send/Sync immediately, because
370            // that could socially imply that it's "safe" to use
371            // unsafe accesses from multiple threads at once. Instead, we
372            // directly impl Send and Sync on this type. Since this
373            // type does have Send and Sync manually impl'd, the Arc
374            // is not pointless as the lint suggests.
375            #[allow(clippy::arc_with_non_send_sync)]
376            region: Arc::new(HostMapping {
377                ptr: addr as *mut u8,
378                size: total_size,
379            }),
380        })
381    }
382
383    /// Internal helper method to get the backing memory as a mutable slice.
384    ///
385    /// # Safety
386    /// As per std::slice::from_raw_parts_mut:
387    /// - self.base_addr() must be valid for both reads and writes for
388    ///   self.mem_size() * mem::size_of::<u8>() many bytes, and it
389    ///   must be properly aligned.
390    ///
391    ///   The rules on validity are still somewhat unspecified, but we
392    ///   assume that the result of our calls to mmap/CreateFileMappings may
393    ///   be considered a single "allocated object". The use of
394    ///   non-atomic accesses is alright from a Safe Rust standpoint,
395    ///   because SharedMemoryBuilder is  not Sync.
396    /// - self.base_addr() must point to self.mem_size() consecutive
397    ///   properly initialized values of type u8
398    ///
399    ///   Again, the exact provenance restrictions on what is
400    ///   considered to be initialized values are unclear, but we make
401    ///   sure to use mmap(MAP_ANONYMOUS) and
402    ///   CreateFileMapping(SEC_COMMIT), so the pages in question are
403    ///   zero-initialized, which we hope counts for u8.
404    /// - The memory referenced by the returned slice must not be
405    ///   accessed through any other pointer (not derived from the
406    ///   return value) for the duration of the lifetime 'a. Both read
407    ///   and write accesses are forbidden.
408    ///
409    ///   Accesses from Safe Rust necessarily follow this rule,
410    ///   because the returned slice's lifetime is the same as that of
411    ///   a mutable borrow of self.
412    /// - The total size self.mem_size() * mem::size_of::<u8>() of the
413    ///   slice must be no larger than isize::MAX, and adding that
414    ///   size to data must not "wrap around" the address space. See
415    ///   the safety documentation of pointer::offset.
416    ///
417    ///   This is ensured by a check in ::new()
418    pub(super) fn as_mut_slice(&mut self) -> &mut [u8] {
419        unsafe { std::slice::from_raw_parts_mut(self.base_ptr(), self.mem_size()) }
420    }
421
422    /// Internal helper method to get the backing memory as a slice.
423    ///
424    /// # Safety
425    /// See the discussion on as_mut_slice, with the third point
426    /// replaced by:
427    /// - The memory referenced by the returned slice must not be
428    ///   mutated for the duration of lifetime 'a, except inside an
429    ///   UnsafeCell.
430    ///
431    ///   Host accesses from Safe Rust necessarily follow this rule,
432    ///   because the returned slice's lifetime is the same as that of
433    ///   a borrow of self, preventing mutations via other methods.
434    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
435    pub fn as_slice<'a>(&'a self) -> &'a [u8] {
436        unsafe { std::slice::from_raw_parts(self.base_ptr(), self.mem_size()) }
437    }
438
439    /// Copies all bytes from `src` to `self` starting at offset
440    #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
441    pub fn copy_from_slice(&mut self, src: &[u8], offset: usize) -> Result<()> {
442        let data = self.as_mut_slice();
443        bounds_check!(offset, src.len(), data.len());
444        data[offset..offset + src.len()].copy_from_slice(src);
445        Ok(())
446    }
447
448    generate_reader!(read_u8, u8);
449    generate_reader!(read_i8, i8);
450    generate_reader!(read_u16, u16);
451    generate_reader!(read_i16, i16);
452    generate_reader!(read_u32, u32);
453    generate_reader!(read_i32, i32);
454    generate_reader!(read_u64, u64);
455    generate_reader!(read_i64, i64);
456    generate_reader!(read_usize, usize);
457    generate_reader!(read_isize, isize);
458
459    generate_writer!(write_u8, u8);
460    generate_writer!(write_i8, i8);
461    generate_writer!(write_u16, u16);
462    generate_writer!(write_i16, i16);
463    generate_writer!(write_u32, u32);
464    generate_writer!(write_i32, i32);
465    generate_writer!(write_u64, u64);
466    generate_writer!(write_i64, i64);
467    generate_writer!(write_usize, usize);
468    generate_writer!(write_isize, isize);
469
470    /// Convert the ExclusiveSharedMemory, which may be freely
471    /// modified, into a GuestSharedMemory, which may be somewhat
472    /// freely modified (mostly by the guest), and a HostSharedMemory,
473    /// which may only make certain kinds of accesses that do not race
474    /// in the presence of malicious code inside the guest mutating
475    /// the GuestSharedMemory.
476    pub fn build(self) -> (HostSharedMemory, GuestSharedMemory) {
477        let lock = Arc::new(RwLock::new(()));
478        let hshm = HostSharedMemory {
479            region: self.region.clone(),
480            lock: lock.clone(),
481        };
482        (
483            hshm,
484            GuestSharedMemory {
485                region: self.region.clone(),
486                lock,
487            },
488        )
489    }
490}
491
492fn mapping_at(
493    s: &impl SharedMemory,
494    gpa: u64,
495    size: usize,
496    region_type: MemoryRegionType,
497    flags: MemoryRegionFlags,
498) -> MemoryRegion {
499    let guest_base = gpa as usize;
500
501    MemoryRegion {
502        guest_region: guest_base..(guest_base + size),
503        host_region: s.host_region_base()
504            ..<HostGuestMemoryRegion as MemoryRegionKind>::add(s.host_region_base(), size),
505        region_type,
506        flags,
507    }
508}
509
510impl GuestSharedMemory {
511    /// Create a [`super::memory_region::MemoryRegion`] structure
512    /// suitable for mapping this region into a VM
513    pub(crate) fn mapping_at(
514        &self,
515        guest_base: u64,
516        region_type: MemoryRegionType,
517    ) -> MemoryRegion {
518        let flags = match region_type {
519            MemoryRegionType::Scratch => {
520                MemoryRegionFlags::READ | MemoryRegionFlags::WRITE | MemoryRegionFlags::EXECUTE
521            }
522            #[allow(clippy::panic)]
523            // This will not ever actually panic: the only places this
524            // is called are HyperlightVm::update_snapshot_mapping and
525            // HyperlightVm::update_scratch_mapping. The latter
526            // statically uses the Scratch region type, and the former
527            // does not use this at all — the snapshot scratch mapping is
528            // a ReadonlySharedMemory, not a GuestSharedMemory.
529            _ => panic!(
530                "GuestSharedMemory::mapping_at should only be used for Scratch or Snapshot regions"
531            ),
532        };
533        mapping_at(self, guest_base, self.mem_size(), region_type, flags)
534    }
535}
536
537/// A trait that abstracts over the particular kind of SharedMemory,
538/// used when invoking operations from Rust that absolutely must have
539/// exclusive control over the shared memory for correctness +
540/// performance, like snapshotting.
541pub trait SharedMemory {
542    /// Return a readonly reference to the host mapping backing this SharedMemory
543    fn region(&self) -> &HostMapping;
544
545    /// Return the base address of the host mapping of this
546    /// region. Following the general Rust philosophy, this does not
547    /// need to be marked as `unsafe` because doing anything with this
548    /// pointer itself requires `unsafe`.
549    fn base_addr(&self) -> usize {
550        self.region().ptr as usize + PAGE_SIZE_USIZE
551    }
552
553    /// Return the base address of the host mapping of this region as
554    /// a pointer. Following the general Rust philosophy, this does
555    /// not need to be marked as `unsafe` because doing anything with
556    /// this pointer itself requires `unsafe`.
557    fn base_ptr(&self) -> *mut u8 {
558        self.region().ptr.wrapping_add(PAGE_SIZE_USIZE)
559    }
560
561    /// Return the length of usable memory contained in `self`.
562    /// The returned size does not include the size of the surrounding
563    /// guard pages.
564    fn mem_size(&self) -> usize {
565        self.region().size - 2 * PAGE_SIZE_USIZE
566    }
567
568    /// Return the raw base address of the host mapping, including the
569    /// guard pages.
570    fn raw_ptr(&self) -> *mut u8 {
571        self.region().ptr
572    }
573
574    /// Return the raw size of the host mapping, including the guard
575    /// pages.
576    fn raw_mem_size(&self) -> usize {
577        self.region().size
578    }
579
580    /// Extract a base address that can be mapped into a VM for this
581    /// SharedMemory.
582    ///
583    /// Returns the host base address as a raw `usize` pointer.
584    fn host_region_base(&self) -> <HostGuestMemoryRegion as MemoryRegionKind>::HostBaseType {
585        self.base_addr()
586    }
587
588    /// Return the end address of the host region (base + usable size).
589    fn host_region_end(&self) -> <HostGuestMemoryRegion as MemoryRegionKind>::HostBaseType {
590        <HostGuestMemoryRegion as MemoryRegionKind>::add(self.host_region_base(), self.mem_size())
591    }
592
593    /// Run some code with exclusive access to the SharedMemory
594    /// underlying this.  If the SharedMemory is not an
595    /// ExclusiveSharedMemory, any concurrent accesses to the relevant
596    /// HostSharedMemory/GuestSharedMemory may make this fail, or be
597    /// made to fail by this, and should be avoided.
598    fn with_exclusivity<T, F: FnOnce(&mut ExclusiveSharedMemory) -> T>(
599        &mut self,
600        f: F,
601    ) -> Result<T>;
602
603    /// Run some code that is allowed to access the contents of the
604    /// SharedMemory as if it is a normal slice.  By default, this is
605    /// implemented via [`SharedMemory::with_exclusivity`], which is
606    /// the correct implementation for a memory that can be mutated,
607    /// but a [`ReadonlySharedMemory`], can support this.
608    fn with_contents<T, F: FnOnce(&[u8]) -> T>(&mut self, f: F) -> Result<T> {
609        self.with_exclusivity(|m| f(m.as_slice()))
610    }
611
612    /// Zero a shared memory region
613    fn zero(&mut self) -> Result<()> {
614        self.with_exclusivity(|e| {
615            #[allow(unused_mut)] // unused on some platforms, although not others
616            let mut do_copy = true;
617            // TODO: Compare & add heuristic thresholds: mmap, MADV_DONTNEED, MADV_REMOVE, MADV_FREE (?)
618            #[cfg(feature = "kvm")]
619            unsafe {
620                let ret = libc::madvise(
621                    e.region.ptr as *mut libc::c_void,
622                    e.region.size,
623                    libc::MADV_DONTNEED,
624                );
625                if ret == 0 {
626                    do_copy = false;
627                }
628            }
629            if do_copy {
630                e.as_mut_slice().fill(0);
631            }
632        })
633    }
634}
635
636impl SharedMemory for ExclusiveSharedMemory {
637    fn region(&self) -> &HostMapping {
638        &self.region
639    }
640    fn with_exclusivity<T, F: FnOnce(&mut ExclusiveSharedMemory) -> T>(
641        &mut self,
642        f: F,
643    ) -> Result<T> {
644        Ok(f(self))
645    }
646}
647
648impl SharedMemory for GuestSharedMemory {
649    fn region(&self) -> &HostMapping {
650        &self.region
651    }
652    fn with_exclusivity<T, F: FnOnce(&mut ExclusiveSharedMemory) -> T>(
653        &mut self,
654        f: F,
655    ) -> Result<T> {
656        let guard = self
657            .lock
658            .try_write()
659            .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))?;
660        let mut excl = ExclusiveSharedMemory {
661            region: self.region.clone(),
662        };
663        let ret = f(&mut excl);
664        drop(excl);
665        drop(guard);
666        Ok(ret)
667    }
668}
669
670/// An unsafe marker trait for types for which all bit patterns are valid.
671/// This is required in order for it to be safe to read a value of a particular
672/// type out of the sandbox from the HostSharedMemory.
673///
674/// # Safety
675/// This must only be implemented for types for which all bit patterns
676/// are valid. It requires that any (non-undef/poison) value of the
677/// correct size can be transmuted to the type.
678pub unsafe trait AllValid {}
679unsafe impl AllValid for u8 {}
680unsafe impl AllValid for u16 {}
681unsafe impl AllValid for u32 {}
682unsafe impl AllValid for u64 {}
683unsafe impl AllValid for i8 {}
684unsafe impl AllValid for i16 {}
685unsafe impl AllValid for i32 {}
686unsafe impl AllValid for i64 {}
687unsafe impl AllValid for [u8; 16] {}
688
689impl HostSharedMemory {
690    /// Read a value of type T, whose representation is the same
691    /// between the sandbox and the host, and which has no invalid bit
692    /// patterns
693    pub fn read<T: AllValid>(&self, offset: usize) -> Result<T> {
694        bounds_check!(offset, std::mem::size_of::<T>(), self.mem_size());
695        unsafe {
696            let mut ret: core::mem::MaybeUninit<T> = core::mem::MaybeUninit::uninit();
697            {
698                let slice: &mut [u8] = core::slice::from_raw_parts_mut(
699                    ret.as_mut_ptr() as *mut u8,
700                    std::mem::size_of::<T>(),
701                );
702                self.copy_to_slice(slice, offset)?;
703            }
704            Ok(ret.assume_init())
705        }
706    }
707
708    /// Write a value of type T, whose representation is the same
709    /// between the sandbox and the host, and which has no invalid bit
710    /// patterns
711    pub fn write<T: AllValid>(&self, offset: usize, data: T) -> Result<()> {
712        bounds_check!(offset, std::mem::size_of::<T>(), self.mem_size());
713        unsafe {
714            let slice: &[u8] = core::slice::from_raw_parts(
715                core::ptr::addr_of!(data) as *const u8,
716                std::mem::size_of::<T>(),
717            );
718            self.copy_from_slice(slice, offset)?;
719        }
720        Ok(())
721    }
722
723    /// Copy the contents of the slice into the sandbox at the
724    /// specified offset
725    pub fn copy_to_slice(&self, slice: &mut [u8], offset: usize) -> Result<()> {
726        bounds_check!(offset, slice.len(), self.mem_size());
727        let base = self.base_ptr().wrapping_add(offset);
728        let guard = self
729            .lock
730            .try_read()
731            .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))?;
732
733        const CHUNK: usize = size_of::<u128>();
734        let len = slice.len();
735        let mut i = 0;
736
737        // Handle unaligned head bytes until we reach u128 alignment.
738        // Note: align_offset can return usize::MAX if alignment is impossible.
739        // In that case, head_len = len via .min(), so we fall back to byte-by-byte
740        // operations for the entire slice.
741        let align_offset = base.align_offset(align_of::<u128>());
742        let head_len = align_offset.min(len);
743        while i < head_len {
744            unsafe {
745                slice[i] = base.add(i).read_volatile();
746            }
747            i += 1;
748        }
749
750        // Read aligned u128 chunks
751        // SAFETY: After processing head_len bytes, base.add(i) is u128-aligned.
752        // We use write_unaligned for the destination since the slice may not be u128-aligned.
753        let dst = slice.as_mut_ptr();
754        while i + CHUNK <= len {
755            unsafe {
756                let value = (base.add(i) as *const u128).read_volatile();
757                std::ptr::write_unaligned(dst.add(i) as *mut u128, value);
758            }
759            i += CHUNK;
760        }
761
762        // Handle remaining tail bytes
763        while i < len {
764            unsafe {
765                slice[i] = base.add(i).read_volatile();
766            }
767            i += 1;
768        }
769
770        drop(guard);
771        Ok(())
772    }
773
774    /// Copy the contents of the sandbox at the specified offset into
775    /// the slice
776    pub fn copy_from_slice(&self, slice: &[u8], offset: usize) -> Result<()> {
777        bounds_check!(offset, slice.len(), self.mem_size());
778        let base = self.base_ptr().wrapping_add(offset);
779        let guard = self
780            .lock
781            .try_read()
782            .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))?;
783
784        const CHUNK: usize = size_of::<u128>();
785        let len = slice.len();
786        let mut i = 0;
787
788        // Handle unaligned head bytes until we reach u128 alignment.
789        // Note: align_offset can return usize::MAX if alignment is impossible.
790        // In that case, head_len = len via .min(), so we fall back to byte-by-byte
791        // operations for the entire slice.
792        let align_offset = base.align_offset(align_of::<u128>());
793        let head_len = align_offset.min(len);
794        while i < head_len {
795            unsafe {
796                base.add(i).write_volatile(slice[i]);
797            }
798            i += 1;
799        }
800
801        // Write aligned u128 chunks
802        // SAFETY: After processing head_len bytes, base.add(i) is u128-aligned.
803        // We use read_unaligned for the source since the slice may not be u128-aligned.
804        let src = slice.as_ptr();
805        while i + CHUNK <= len {
806            unsafe {
807                let value = std::ptr::read_unaligned(src.add(i) as *const u128);
808                (base.add(i) as *mut u128).write_volatile(value);
809            }
810            i += CHUNK;
811        }
812
813        // Handle remaining tail bytes
814        while i < len {
815            unsafe {
816                base.add(i).write_volatile(slice[i]);
817            }
818            i += 1;
819        }
820
821        drop(guard);
822        Ok(())
823    }
824
825    /// Fill the memory in the range `[offset, offset + len)` with `value`
826    #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
827    pub fn fill(&mut self, value: u8, offset: usize, len: usize) -> Result<()> {
828        bounds_check!(offset, len, self.mem_size());
829        let base = self.base_ptr().wrapping_add(offset);
830        let guard = self
831            .lock
832            .try_read()
833            .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))?;
834
835        const CHUNK: usize = size_of::<u128>();
836        let value_u128 = u128::from_ne_bytes([value; CHUNK]);
837        let mut i = 0;
838
839        // Handle unaligned head bytes until we reach u128 alignment.
840        // Note: align_offset can return usize::MAX if alignment is impossible.
841        // In that case, head_len = len via .min(), so we fall back to byte-by-byte
842        // operations for the entire slice.
843        let align_offset = base.align_offset(align_of::<u128>());
844        let head_len = align_offset.min(len);
845        while i < head_len {
846            unsafe {
847                base.add(i).write_volatile(value);
848            }
849            i += 1;
850        }
851
852        // Write aligned u128 chunks
853        // SAFETY: After processing head_len bytes, base.add(i) is u128-aligned
854        while i + CHUNK <= len {
855            unsafe {
856                (base.add(i) as *mut u128).write_volatile(value_u128);
857            }
858            i += CHUNK;
859        }
860
861        // Handle remaining tail bytes
862        while i < len {
863            unsafe {
864                base.add(i).write_volatile(value);
865            }
866            i += 1;
867        }
868
869        drop(guard);
870        Ok(())
871    }
872
873    /// Pushes the given data onto shared memory to the buffer at the given offset.
874    /// NOTE! buffer_start_offset must point to the beginning of the buffer
875    #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
876    pub fn push_buffer(
877        &mut self,
878        buffer_start_offset: usize,
879        buffer_size: usize,
880        data: &[u8],
881    ) -> Result<()> {
882        let stack_pointer_rel = self.read::<u64>(buffer_start_offset)? as usize;
883        let buffer_size_u64: u64 = buffer_size.try_into()?;
884
885        if stack_pointer_rel > buffer_size || stack_pointer_rel < 8 {
886            return Err(new_error!(
887                "Unable to push data to buffer: Stack pointer is out of bounds. Stack pointer: {}, Buffer size: {}",
888                stack_pointer_rel,
889                buffer_size_u64
890            ));
891        }
892
893        let size_required = data.len() + 8;
894        let size_available = buffer_size - stack_pointer_rel;
895
896        if size_required > size_available {
897            return Err(new_error!(
898                "Not enough space in buffer to push data. Required: {}, Available: {}",
899                size_required,
900                size_available
901            ));
902        }
903
904        // get absolute
905        let stack_pointer_abs = stack_pointer_rel + buffer_start_offset;
906
907        // write the actual data to the top of stack
908        self.copy_from_slice(data, stack_pointer_abs)?;
909
910        // write the offset to the newly written data, to the top of stack.
911        // this is used when popping the stack, to know how far back to jump
912        self.write::<u64>(stack_pointer_abs + data.len(), stack_pointer_rel as u64)?;
913
914        // update stack pointer to point to the next free address
915        self.write::<u64>(
916            buffer_start_offset,
917            (stack_pointer_rel + data.len() + 8) as u64,
918        )?;
919        Ok(())
920    }
921
922    /// Pop the top element of the ring as raw bytes. Unlike
923    /// [`Self::try_pop_buffer_into`], this doesn't peek at the element's
924    /// contents — the element size is recovered from the trailing
925    /// back-pointer that [`Self::push_buffer`] wrote.
926    pub fn try_pop_buffer_raw(
927        &mut self,
928        buffer_start_offset: usize,
929        buffer_size: usize,
930    ) -> Result<Vec<u8>> {
931        let stack_pointer_rel = self.read::<u64>(buffer_start_offset)? as usize;
932
933        if stack_pointer_rel > buffer_size || stack_pointer_rel < 16 {
934            return Err(new_error!(
935                "try_pop_buffer_raw: stack pointer {} out of bounds (size {})",
936                stack_pointer_rel,
937                buffer_size
938            ));
939        }
940
941        let back_ptr_abs = stack_pointer_rel + buffer_start_offset - 8;
942        let element_offset_rel = self.read::<u64>(back_ptr_abs)? as usize;
943
944        if element_offset_rel < 8 || element_offset_rel > stack_pointer_rel.saturating_sub(8) {
945            return Err(new_error!(
946                "try_pop_buffer_raw: back-pointer {} outside [8, {}]",
947                element_offset_rel,
948                stack_pointer_rel.saturating_sub(8)
949            ));
950        }
951
952        let element_size = stack_pointer_rel - element_offset_rel - 8;
953        let element_abs = element_offset_rel + buffer_start_offset;
954        let mut out = vec![0u8; element_size];
955        self.copy_to_slice(&mut out, element_abs)?;
956
957        // Pop: rewind stack pointer.
958        self.write::<u64>(buffer_start_offset, element_offset_rel as u64)?;
959        // Zero out the popped slot + its back-pointer.
960        self.fill(0, element_abs, stack_pointer_rel - element_offset_rel)?;
961
962        Ok(out)
963    }
964
965    /// Pops the given given buffer into a `T` and returns it.
966    /// NOTE! the data must be a size-prefixed flatbuffer, and
967    /// buffer_start_offset must point to the beginning of the buffer
968    pub fn try_pop_buffer_into<T>(
969        &mut self,
970        buffer_start_offset: usize,
971        buffer_size: usize,
972    ) -> Result<T>
973    where
974        T: for<'b> TryFrom<&'b [u8]>,
975    {
976        // get the stackpointer
977        let stack_pointer_rel = self.read::<u64>(buffer_start_offset)? as usize;
978
979        if stack_pointer_rel > buffer_size || stack_pointer_rel < 16 {
980            return Err(new_error!(
981                "Unable to pop data from buffer: Stack pointer is out of bounds. Stack pointer: {}, Buffer size: {}",
982                stack_pointer_rel,
983                buffer_size
984            ));
985        }
986
987        // make it absolute
988        let last_element_offset_abs = stack_pointer_rel + buffer_start_offset;
989
990        // go back 8 bytes to get offset to element on top of stack
991        let last_element_offset_rel: usize =
992            self.read::<u64>(last_element_offset_abs - 8)? as usize;
993
994        // Validate element offset (guest-writable): must be in [8, stack_pointer_rel - 16]
995        // to leave room for the 8-byte back-pointer plus at least 8 bytes of element data
996        // (the minimum for a size-prefixed flatbuffer: 4-byte prefix + 4-byte root offset).
997        if last_element_offset_rel > stack_pointer_rel.saturating_sub(16)
998            || last_element_offset_rel < 8
999        {
1000            return Err(new_error!(
1001                "Corrupt buffer back-pointer: element offset {} is outside valid range [8, {}].",
1002                last_element_offset_rel,
1003                stack_pointer_rel.saturating_sub(16),
1004            ));
1005        }
1006
1007        // make it absolute
1008        let last_element_offset_abs = last_element_offset_rel + buffer_start_offset;
1009
1010        // Max bytes the element can span (excluding the 8-byte back-pointer).
1011        let max_element_size = stack_pointer_rel - last_element_offset_rel - 8;
1012
1013        // Get the size of the flatbuffer buffer from memory
1014        let fb_buffer_size = {
1015            let raw_prefix = self.read::<u32>(last_element_offset_abs)?;
1016            // flatbuffer byte arrays are prefixed by 4 bytes indicating
1017            // the remaining size; add 4 for the prefix itself.
1018            let total = raw_prefix.checked_add(4).ok_or_else(|| {
1019                new_error!(
1020                    "Corrupt buffer size prefix: value {} overflows when adding 4-byte header.",
1021                    raw_prefix
1022                )
1023            })?;
1024            usize::try_from(total)
1025        }?;
1026
1027        if fb_buffer_size > max_element_size {
1028            return Err(new_error!(
1029                "Corrupt buffer size prefix: flatbuffer claims {} bytes but the element slot is only {} bytes.",
1030                fb_buffer_size,
1031                max_element_size
1032            ));
1033        }
1034
1035        let mut result_buffer = vec![0; fb_buffer_size];
1036
1037        self.copy_to_slice(&mut result_buffer, last_element_offset_abs)?;
1038        let to_return = T::try_from(result_buffer.as_slice()).map_err(|_e| {
1039            new_error!(
1040                "pop_buffer_into: failed to convert buffer to {}",
1041                type_name::<T>()
1042            )
1043        })?;
1044
1045        // update the stack pointer to point to the element we just popped off since that is now free
1046        self.write::<u64>(buffer_start_offset, last_element_offset_rel as u64)?;
1047
1048        // zero out the memory we just popped off
1049        let num_bytes_to_zero = stack_pointer_rel - last_element_offset_rel;
1050        self.fill(0, last_element_offset_abs, num_bytes_to_zero)?;
1051
1052        Ok(to_return)
1053    }
1054}
1055
1056impl SharedMemory for HostSharedMemory {
1057    fn region(&self) -> &HostMapping {
1058        &self.region
1059    }
1060    fn with_exclusivity<T, F: FnOnce(&mut ExclusiveSharedMemory) -> T>(
1061        &mut self,
1062        f: F,
1063    ) -> Result<T> {
1064        let guard = self
1065            .lock
1066            .try_write()
1067            .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))?;
1068        let mut excl = ExclusiveSharedMemory {
1069            region: self.region.clone(),
1070        };
1071        let ret = f(&mut excl);
1072        drop(excl);
1073        drop(guard);
1074        Ok(ret)
1075    }
1076}
1077
1078/// A ReadonlySharedMemory is a different kind of shared memory,
1079/// separate from the exclusive/host/guest lifecycle, used to
1080/// represent read-only mappings of snapshot pages into the guest
1081/// efficiently.
1082#[derive(Clone, Debug)]
1083pub struct ReadonlySharedMemory {
1084    region: Arc<HostMapping>,
1085    /// If `Some`, only this many bytes are mapped into guest PA space
1086    /// by `mapping_at`. If `None`, the full `mem_size()` is mapped.
1087    guest_mapped_size: Option<usize>,
1088}
1089// Safety: HostMapping is only non-Send/Sync (causing
1090// ReadonlySharedMemory to not be automatically Send/Sync) because raw
1091// pointers are not ("as a lint", as the Rust docs say). We don't want
1092// to mark HostMapping Send/Sync immediately, because that could
1093// socially imply that it's "safe" to use unsafe accesses from
1094// multiple threads at once in more cases, including ones that don't
1095// actually ensure immutability/synchronisation. Since
1096// ReadonlySharedMemory can only be accessed by reading, and reading
1097// concurrently from multiple threads is not racy,
1098// ReadonlySharedMemory can be Send and Sync.
1099unsafe impl Send for ReadonlySharedMemory {}
1100unsafe impl Sync for ReadonlySharedMemory {}
1101
1102impl ReadonlySharedMemory {
1103    pub(crate) fn from_bytes(contents: &[u8]) -> Result<Self> {
1104        let mut anon = ExclusiveSharedMemory::new(contents.len())?;
1105        anon.copy_from_slice(contents, 0)?;
1106        Ok(ReadonlySharedMemory {
1107            region: anon.region,
1108            guest_mapped_size: None,
1109        })
1110    }
1111
1112    /// The number of bytes that should be mapped into guest PA space.
1113    /// Returns `guest_mapped_size` if set, otherwise `mem_size()`.
1114    pub(crate) fn guest_mapped_size(&self) -> usize {
1115        self.guest_mapped_size.unwrap_or_else(|| self.mem_size())
1116    }
1117
1118    pub(crate) fn as_slice(&self) -> &[u8] {
1119        unsafe { std::slice::from_raw_parts(self.base_ptr(), self.mem_size()) }
1120    }
1121
1122    pub(crate) fn build(self) -> (Self, Self) {
1123        (self.clone(), self)
1124    }
1125
1126    pub(crate) fn mapping_at(
1127        &self,
1128        guest_base: u64,
1129        region_type: MemoryRegionType,
1130    ) -> MemoryRegion {
1131        #[allow(clippy::panic)]
1132        // This will not ever actually panic: the only place this is
1133        // called is HyperlightVm::update_snapshot_mapping, which
1134        // always calls it with the Snapshot region type.
1135        if region_type != MemoryRegionType::Snapshot {
1136            panic!("ReadonlySharedMemory::mapping_at should only be used for Snapshot regions");
1137        }
1138        // Register snapshot mem RWX at the KVM level. Upstream marked
1139        // this RX-only and relied on guest-PT CoW for write semantics,
1140        // which trapped first writes and resolved them to scratch frames
1141        // — driving a slow leak via prim_alloc. The underlying mmap is
1142        // already PROT_READ|PROT_WRITE; `ReadonlySharedMemory` is a
1143        // host-side Rust-API artifact, not a KVM-level constraint.
1144        mapping_at(
1145            self,
1146            guest_base,
1147            self.guest_mapped_size(),
1148            region_type,
1149            MemoryRegionFlags::READ | MemoryRegionFlags::WRITE | MemoryRegionFlags::EXECUTE,
1150        )
1151    }
1152}
1153
1154impl SharedMemory for ReadonlySharedMemory {
1155    fn region(&self) -> &HostMapping {
1156        &self.region
1157    }
1158    // There's no way to get exclusive (and therefore writable) access
1159    // to a ReadonlySharedMemory.
1160    fn with_exclusivity<T, F: FnOnce(&mut ExclusiveSharedMemory) -> T>(
1161        &mut self,
1162        _: F,
1163    ) -> Result<T> {
1164        Err(new_error!(
1165            "Cannot take exclusive access to a ReadonlySharedMemory"
1166        ))
1167    }
1168    // However, just access to the contents as a slice is doable
1169    fn with_contents<T, F: FnOnce(&[u8]) -> T>(&mut self, f: F) -> Result<T> {
1170        Ok(f(self.as_slice()))
1171    }
1172}
1173
1174impl<S: SharedMemory> PartialEq<S> for ReadonlySharedMemory {
1175    fn eq(&self, other: &S) -> bool {
1176        self.raw_ptr() == other.raw_ptr()
1177    }
1178}