Skip to main content

hyperlight_host/mem/
memory_region.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2025 The Hyperlight Authors.
3
4use std::ops::Range;
5
6use bitflags::bitflags;
7use hyperlight_common::vmem::PAGE_SIZE;
8#[cfg(kvm)]
9use kvm_bindings::{KVM_MEM_READONLY, kvm_userspace_memory_region};
10#[cfg(mshv3)]
11use mshv_bindings::{
12    MSHV_SET_MEM_BIT_EXECUTABLE, MSHV_SET_MEM_BIT_UNMAP, MSHV_SET_MEM_BIT_WRITABLE,
13};
14#[cfg(all(mshv3, target_arch = "aarch64"))]
15use mshv_bindings::{hv_arm64_memory_intercept_message, mshv_user_mem_region};
16#[cfg(all(mshv3, target_arch = "x86_64"))]
17use mshv_bindings::{hv_x64_memory_intercept_message, mshv_user_mem_region};
18#[cfg(target_os = "windows")]
19use windows::Win32::System::Hypervisor::{self, WHV_MEMORY_ACCESS_TYPE};
20
21#[cfg(target_os = "windows")]
22use crate::hypervisor::wrappers::HandleWrapper;
23
24pub(crate) const DEFAULT_GUEST_BLOB_MEM_FLAGS: MemoryRegionFlags = MemoryRegionFlags::READ;
25
26bitflags! {
27    /// flags representing memory permission for a memory region
28    #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
29    pub struct MemoryRegionFlags: u32 {
30        /// no permissions
31        const NONE = 0;
32        /// allow guest to read
33        const READ = 1;
34        /// allow guest to write
35        const WRITE = 2;
36        /// allow guest to execute
37        const EXECUTE = 4;
38    }
39}
40
41impl std::fmt::Display for MemoryRegionFlags {
42    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43        if self.is_empty() {
44            write!(f, "NONE")
45        } else {
46            let mut first = true;
47            if self.contains(MemoryRegionFlags::READ) {
48                write!(f, "READ")?;
49                first = false;
50            }
51            if self.contains(MemoryRegionFlags::WRITE) {
52                if !first {
53                    write!(f, " | ")?;
54                }
55                write!(f, "WRITE")?;
56                first = false;
57            }
58            if self.contains(MemoryRegionFlags::EXECUTE) {
59                if !first {
60                    write!(f, " | ")?;
61                }
62                write!(f, "EXECUTE")?;
63            }
64            Ok(())
65        }
66    }
67}
68
69#[cfg(target_os = "windows")]
70impl TryFrom<WHV_MEMORY_ACCESS_TYPE> for MemoryRegionFlags {
71    type Error = crate::HyperlightError;
72
73    fn try_from(flags: WHV_MEMORY_ACCESS_TYPE) -> crate::Result<Self> {
74        match flags {
75            Hypervisor::WHvMemoryAccessRead => Ok(MemoryRegionFlags::READ),
76            Hypervisor::WHvMemoryAccessWrite => Ok(MemoryRegionFlags::WRITE),
77            Hypervisor::WHvMemoryAccessExecute => Ok(MemoryRegionFlags::EXECUTE),
78            _ => Err(crate::HyperlightError::Error(
79                "unknown memory access type".to_string(),
80            )),
81        }
82    }
83}
84
85#[cfg(all(mshv3, target_arch = "x86_64"))]
86impl TryFrom<hv_x64_memory_intercept_message> for MemoryRegionFlags {
87    type Error = crate::HyperlightError;
88
89    fn try_from(msg: hv_x64_memory_intercept_message) -> crate::Result<Self> {
90        let access_type = msg.header.intercept_access_type;
91        match access_type {
92            0 => Ok(MemoryRegionFlags::READ),
93            1 => Ok(MemoryRegionFlags::WRITE),
94            2 => Ok(MemoryRegionFlags::EXECUTE),
95            _ => Err(crate::HyperlightError::Error(
96                "unknown memory access type".to_string(),
97            )),
98        }
99    }
100}
101
102#[cfg(all(mshv3, target_arch = "aarch64"))]
103impl TryFrom<hv_arm64_memory_intercept_message> for MemoryRegionFlags {
104    type Error = crate::HyperlightError;
105
106    fn try_from(_msg: hv_arm64_memory_intercept_message) -> crate::Result<Self> {
107        unimplemented!("try_from")
108    }
109}
110
111// NOTE: In the future, all host-side knowledge about memory region types
112// should collapse down to Snapshot vs Scratch (see shared_mem.rs).
113// Until then, these variants help distinguish regions for diagnostics
114// and crash dumps. Not part of the public API.
115#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
116/// The type of memory region
117pub enum MemoryRegionType {
118    /// The region contains the guest's code
119    Code,
120    /// The region contains the guest's init data
121    InitData,
122    /// The region contains the PEB
123    Peb,
124    /// The region contains the Heap
125    Heap,
126    /// The region contains the Guard Page
127    Scratch,
128    /// The snapshot region
129    Snapshot,
130    /// An externally-mapped file (via [`MultiUseSandbox::map_file_cow`]).
131    /// These regions are backed by file handles (Windows) or mmap
132    /// (Linux) and are read-only + executable. They are cleaned up
133    /// during restore/drop — not part of the guest's own allocator.
134    MappedFile,
135}
136
137#[cfg(target_os = "windows")]
138impl MemoryRegionType {
139    /// Derives the [`SurrogateMapping`] from this region type.
140    ///
141    /// `MappedFile` and `Snapshot` regions use read-only file-backed
142    /// mappings with no guard pages. All other region types use the
143    /// standard sandbox shared memory mapping with guard pages.
144    pub fn surrogate_mapping(&self) -> SurrogateMapping {
145        match self {
146            MemoryRegionType::MappedFile | MemoryRegionType::Snapshot => {
147                SurrogateMapping::ReadOnlyFile
148            }
149            _ => SurrogateMapping::SandboxMemory,
150        }
151    }
152}
153
154/// A trait that distinguishes between different kinds of memory region representations.
155///
156/// This trait is used to parameterize [`MemoryRegion_`]
157pub trait MemoryRegionKind {
158    /// The type used to represent host memory addresses.
159    type HostBaseType: Copy;
160
161    /// Computes an address by adding a size to a base address.
162    ///
163    /// # Arguments
164    /// * `base` - The starting address
165    /// * `size` - The size in bytes to add
166    ///
167    /// # Returns
168    /// The computed end address (`base + size` for host-guest regions,
169    /// `()` for guest-only regions).
170    fn add(base: Self::HostBaseType, size: usize) -> Self::HostBaseType;
171}
172
173/// Type for memory regions that track both host and guest addresses.
174///
175/// When one of these is created, it always ends up in a sandbox
176/// quickly. It's an invariant of this type that as long as one of
177/// these is associated with a sandbox, it's always acceptable to read
178/// from it, since a lot of the debug/crashdump/snapshot code
179/// does. (Note: this means that _writable_ HostGuestMemoryRegions are
180/// not possible to support at the moment).
181#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
182pub struct HostGuestMemoryRegion {}
183
184#[cfg(not(target_os = "windows"))]
185impl MemoryRegionKind for HostGuestMemoryRegion {
186    type HostBaseType = usize;
187
188    fn add(base: Self::HostBaseType, size: usize) -> Self::HostBaseType {
189        base + size
190    }
191}
192/// Describes how a memory region should be mapped through the surrogate process
193/// pipeline on Windows (WHP).
194///
195/// Different mapping types require different page protections and guard page
196/// behaviour when projected into the surrogate process via `MapViewOfFileNuma2`.
197#[cfg(target_os = "windows")]
198#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
199pub enum SurrogateMapping {
200    /// Standard sandbox shared memory: mapped with `PAGE_READWRITE` protection
201    /// and guard pages (`PAGE_NOACCESS`) set on the first and last pages.
202    SandboxMemory,
203    /// File-backed read-only mapping: mapped with `PAGE_READONLY` protection
204    /// and **no** guard pages.
205    ReadOnlyFile,
206}
207
208/// A [`HostRegionBase`] keeps track of not just a pointer, but also a
209/// file mapping into which it is pointing.  This is used on WHP,
210/// where mapping the actual pointer into the VM actually involves
211/// first mapping the file into a surrogate process.
212#[cfg(target_os = "windows")]
213#[derive(Debug, PartialEq, Eq, Copy, Clone)]
214pub struct HostRegionBase {
215    /// The file handle from which the file mapping was created
216    pub from_handle: HandleWrapper,
217    /// The base of the file mapping
218    pub handle_base: usize,
219    /// The size of the file mapping
220    pub handle_size: usize,
221    /// The offset into file mapping region where this
222    /// [`HostRegionBase`] is pointing.
223    pub offset: usize,
224}
225#[cfg(target_os = "windows")]
226impl std::hash::Hash for HostRegionBase {
227    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
228        // it's safe not to hash the handle (which is not hashable)
229        // since, for any of these in use at the same time, the handle
230        // should be uniquely determined by the
231        // handle_base/handle_size combination.
232        self.handle_base.hash(state);
233        self.handle_size.hash(state);
234        self.offset.hash(state);
235    }
236}
237#[cfg(target_os = "windows")]
238impl From<HostRegionBase> for usize {
239    fn from(x: HostRegionBase) -> usize {
240        x.handle_base + x.offset
241    }
242}
243#[cfg(target_os = "windows")]
244impl TryFrom<HostRegionBase> for isize {
245    type Error = <isize as TryFrom<usize>>::Error;
246    fn try_from(x: HostRegionBase) -> Result<isize, Self::Error> {
247        <isize as TryFrom<usize>>::try_from(x.into())
248    }
249}
250#[cfg(target_os = "windows")]
251impl MemoryRegionKind for HostGuestMemoryRegion {
252    type HostBaseType = HostRegionBase;
253
254    fn add(base: Self::HostBaseType, size: usize) -> Self::HostBaseType {
255        HostRegionBase {
256            from_handle: base.from_handle,
257            handle_base: base.handle_base,
258            handle_size: base.handle_size,
259            offset: base.offset + size,
260        }
261    }
262}
263
264/// Type for memory regions that only track guest addresses.
265///
266#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
267pub(crate) struct GuestMemoryRegion {}
268
269impl MemoryRegionKind for GuestMemoryRegion {
270    type HostBaseType = ();
271
272    fn add(_base: Self::HostBaseType, _size: usize) -> Self::HostBaseType {}
273}
274
275/// represents a single memory region inside the guest. All memory within a region has
276/// the same memory permissions
277#[derive(Debug, Clone, PartialEq, Eq, Hash)]
278pub struct MemoryRegion_<K: MemoryRegionKind> {
279    /// the range of guest memory addresses
280    pub guest_region: Range<usize>,
281    /// the range of host memory addresses
282    ///
283    /// Note that Range<()> = () x () = ().
284    pub host_region: Range<K::HostBaseType>,
285    /// memory access flags for the given region
286    pub flags: MemoryRegionFlags,
287    /// the type of memory region
288    pub region_type: MemoryRegionType,
289}
290
291/// A memory region that tracks both host and guest addresses.
292pub type MemoryRegion = MemoryRegion_<HostGuestMemoryRegion>;
293
294/// A [`MemoryRegionKind`] for crash dump regions that always uses raw
295/// `usize` host addresses.  The crash dump path only reads host memory
296/// through raw pointers, so it never needs the file-mapping metadata
297/// stored in [`HostRegionBase`] on Windows.
298#[cfg(crashdump)]
299#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
300pub(crate) struct CrashDumpMemoryRegion;
301
302#[cfg(crashdump)]
303impl MemoryRegionKind for CrashDumpMemoryRegion {
304    type HostBaseType = usize;
305
306    fn add(base: Self::HostBaseType, size: usize) -> Self::HostBaseType {
307        base + size
308    }
309}
310
311/// A memory region used exclusively by the crash dump path.
312///
313/// Host addresses are always raw `usize` pointers, avoiding the need
314/// to construct platform-specific wrappers like [`HostRegionBase`].
315#[cfg(crashdump)]
316pub(crate) type CrashDumpRegion = MemoryRegion_<CrashDumpMemoryRegion>;
317
318pub(crate) struct MemoryRegionVecBuilder<K: MemoryRegionKind> {
319    guest_base_phys_addr: usize,
320    host_base_virt_addr: K::HostBaseType,
321    regions: Vec<MemoryRegion_<K>>,
322}
323
324impl<K: MemoryRegionKind> MemoryRegionVecBuilder<K> {
325    pub(crate) fn new(guest_base_phys_addr: usize, host_base_virt_addr: K::HostBaseType) -> Self {
326        Self {
327            guest_base_phys_addr,
328            host_base_virt_addr,
329            regions: Vec::new(),
330        }
331    }
332
333    fn push(
334        &mut self,
335        size: usize,
336        flags: MemoryRegionFlags,
337        region_type: MemoryRegionType,
338    ) -> usize {
339        if self.regions.is_empty() {
340            let guest_end = self.guest_base_phys_addr + size;
341            let host_end = <K as MemoryRegionKind>::add(self.host_base_virt_addr, size);
342            self.regions.push(MemoryRegion_ {
343                guest_region: self.guest_base_phys_addr..guest_end,
344                host_region: self.host_base_virt_addr..host_end,
345                flags,
346                region_type,
347            });
348            return guest_end - self.guest_base_phys_addr;
349        }
350
351        #[allow(clippy::unwrap_used)]
352        // we know this is safe because we check if the regions are empty above
353        let last_region = self.regions.last().unwrap();
354        let host_end = <K as MemoryRegionKind>::add(last_region.host_region.end, size);
355        let new_region = MemoryRegion_ {
356            guest_region: last_region.guest_region.end..last_region.guest_region.end + size,
357            host_region: last_region.host_region.end..host_end,
358            flags,
359            region_type,
360        };
361        let ret = new_region.guest_region.end;
362        self.regions.push(new_region);
363        ret - self.guest_base_phys_addr
364    }
365
366    /// Pushes a memory region with the given size. Will round up the size to the nearest page.
367    /// Returns the current size of the all memory regions in the builder after adding the given region.
368    /// # Note:
369    /// Memory regions pushed MUST match the guest's memory layout, in SandboxMemoryLayout::new(..)
370    pub(crate) fn push_page_aligned(
371        &mut self,
372        size: usize,
373        flags: MemoryRegionFlags,
374        region_type: MemoryRegionType,
375    ) -> usize {
376        let aligned_size = (size + PAGE_SIZE - 1) & !(PAGE_SIZE - 1);
377        self.push(aligned_size, flags, region_type)
378    }
379
380    /// Consumes the builder and returns a vec of memory regions. The regions are guaranteed to be a contiguous chunk
381    /// of memory, in other words, there will be any memory gaps between them.
382    pub(crate) fn build(self) -> Vec<MemoryRegion_<K>> {
383        self.regions
384    }
385}
386
387#[cfg(mshv3)]
388impl From<&MemoryRegion> for mshv_user_mem_region {
389    fn from(region: &MemoryRegion) -> Self {
390        let size = (region.guest_region.end - region.guest_region.start) as u64;
391        let guest_pfn = (region.guest_region.start / page_size::get()) as u64;
392        let userspace_addr = region.host_region.start as u64;
393
394        let flags: u8 = region.flags.iter().fold(0, |acc, flag| {
395            let flag_value = match flag {
396                MemoryRegionFlags::NONE => 1 << MSHV_SET_MEM_BIT_UNMAP,
397                MemoryRegionFlags::READ => 0,
398                MemoryRegionFlags::WRITE => 1 << MSHV_SET_MEM_BIT_WRITABLE,
399                MemoryRegionFlags::EXECUTE => 1 << MSHV_SET_MEM_BIT_EXECUTABLE,
400                _ => 0, // ignore any unknown flags
401            };
402            acc | flag_value
403        });
404
405        mshv_user_mem_region {
406            guest_pfn,
407            size,
408            userspace_addr,
409            flags,
410            ..Default::default()
411        }
412    }
413}
414
415#[cfg(kvm)]
416impl From<&MemoryRegion> for kvm_bindings::kvm_userspace_memory_region {
417    fn from(region: &MemoryRegion) -> Self {
418        let perm_flags =
419            MemoryRegionFlags::READ | MemoryRegionFlags::WRITE | MemoryRegionFlags::EXECUTE;
420
421        let perm_flags = perm_flags.intersection(region.flags);
422
423        kvm_userspace_memory_region {
424            slot: 0,
425            guest_phys_addr: region.guest_region.start as u64,
426            memory_size: (region.guest_region.end - region.guest_region.start) as u64,
427            userspace_addr: region.host_region.start as u64,
428            flags: if perm_flags.contains(MemoryRegionFlags::WRITE) {
429                0 // RWX
430            } else {
431                // Note: KVM_MEM_READONLY is executable
432                KVM_MEM_READONLY // RX 
433            },
434        }
435    }
436}