Skip to main content

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