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` and `Snapshot` regions use read-only file-backed
157    /// mappings with no guard pages. All other region types use the
158    /// standard sandbox shared memory mapping with guard pages.
159    pub fn surrogate_mapping(&self) -> SurrogateMapping {
160        match self {
161            MemoryRegionType::MappedFile | MemoryRegionType::Snapshot => {
162                SurrogateMapping::ReadOnlyFile
163            }
164            _ => SurrogateMapping::SandboxMemory,
165        }
166    }
167}
168
169/// A trait that distinguishes between different kinds of memory region representations.
170///
171/// This trait is used to parameterize [`MemoryRegion_`]
172pub trait MemoryRegionKind {
173    /// The type used to represent host memory addresses.
174    type HostBaseType: Copy;
175
176    /// Computes an address by adding a size to a base address.
177    ///
178    /// # Arguments
179    /// * `base` - The starting address
180    /// * `size` - The size in bytes to add
181    ///
182    /// # Returns
183    /// The computed end address (`base + size` for host-guest regions,
184    /// `()` for guest-only regions).
185    fn add(base: Self::HostBaseType, size: usize) -> Self::HostBaseType;
186}
187
188/// Type for memory regions that track both host and guest addresses.
189///
190/// When one of these is created, it always ends up in a sandbox
191/// quickly. It's an invariant of this type that as long as one of
192/// these is associated with a sandbox, it's always acceptable to read
193/// from it, since a lot of the debug/crashdump/snapshot code
194/// does. (Note: this means that _writable_ HostGuestMemoryRegions are
195/// not possible to support at the moment).
196#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
197pub struct HostGuestMemoryRegion {}
198
199#[cfg(not(target_os = "windows"))]
200impl MemoryRegionKind for HostGuestMemoryRegion {
201    type HostBaseType = usize;
202
203    fn add(base: Self::HostBaseType, size: usize) -> Self::HostBaseType {
204        base + size
205    }
206}
207/// Describes how a memory region should be mapped through the surrogate process
208/// pipeline on Windows (WHP).
209///
210/// Different mapping types require different page protections and guard page
211/// behaviour when projected into the surrogate process via `MapViewOfFileNuma2`.
212#[cfg(target_os = "windows")]
213#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
214pub enum SurrogateMapping {
215    /// Standard sandbox shared memory: mapped with `PAGE_READWRITE` protection
216    /// and guard pages (`PAGE_NOACCESS`) set on the first and last pages.
217    SandboxMemory,
218    /// File-backed read-only mapping: mapped with `PAGE_READONLY` protection
219    /// and **no** guard pages.
220    ReadOnlyFile,
221}
222
223/// A [`HostRegionBase`] keeps track of not just a pointer, but also a
224/// file mapping into which it is pointing.  This is used on WHP,
225/// where mapping the actual pointer into the VM actually involves
226/// first mapping the file into a surrogate process.
227#[cfg(target_os = "windows")]
228#[derive(Debug, PartialEq, Eq, Copy, Clone)]
229pub struct HostRegionBase {
230    /// The file handle from which the file mapping was created
231    pub from_handle: HandleWrapper,
232    /// The base of the file mapping
233    pub handle_base: usize,
234    /// The size of the file mapping
235    pub handle_size: usize,
236    /// The offset into file mapping region where this
237    /// [`HostRegionBase`] is pointing.
238    pub offset: usize,
239}
240#[cfg(target_os = "windows")]
241impl std::hash::Hash for HostRegionBase {
242    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
243        // it's safe not to hash the handle (which is not hashable)
244        // since, for any of these in use at the same time, the handle
245        // should be uniquely determined by the
246        // handle_base/handle_size combination.
247        self.handle_base.hash(state);
248        self.handle_size.hash(state);
249        self.offset.hash(state);
250    }
251}
252#[cfg(target_os = "windows")]
253impl From<HostRegionBase> for usize {
254    fn from(x: HostRegionBase) -> usize {
255        x.handle_base + x.offset
256    }
257}
258#[cfg(target_os = "windows")]
259impl TryFrom<HostRegionBase> for isize {
260    type Error = <isize as TryFrom<usize>>::Error;
261    fn try_from(x: HostRegionBase) -> Result<isize, Self::Error> {
262        <isize as TryFrom<usize>>::try_from(x.into())
263    }
264}
265#[cfg(target_os = "windows")]
266impl MemoryRegionKind for HostGuestMemoryRegion {
267    type HostBaseType = HostRegionBase;
268
269    fn add(base: Self::HostBaseType, size: usize) -> Self::HostBaseType {
270        HostRegionBase {
271            from_handle: base.from_handle,
272            handle_base: base.handle_base,
273            handle_size: base.handle_size,
274            offset: base.offset + size,
275        }
276    }
277}
278
279/// Type for memory regions that only track guest addresses.
280///
281#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
282pub(crate) struct GuestMemoryRegion {}
283
284impl MemoryRegionKind for GuestMemoryRegion {
285    type HostBaseType = ();
286
287    fn add(_base: Self::HostBaseType, _size: usize) -> Self::HostBaseType {}
288}
289
290/// represents a single memory region inside the guest. All memory within a region has
291/// the same memory permissions
292#[derive(Debug, Clone, PartialEq, Eq, Hash)]
293pub struct MemoryRegion_<K: MemoryRegionKind> {
294    /// the range of guest memory addresses
295    pub guest_region: Range<usize>,
296    /// the range of host memory addresses
297    ///
298    /// Note that Range<()> = () x () = ().
299    pub host_region: Range<K::HostBaseType>,
300    /// memory access flags for the given region
301    pub flags: MemoryRegionFlags,
302    /// the type of memory region
303    pub region_type: MemoryRegionType,
304}
305
306/// A memory region that tracks both host and guest addresses.
307pub type MemoryRegion = MemoryRegion_<HostGuestMemoryRegion>;
308
309/// A [`MemoryRegionKind`] for crash dump regions that always uses raw
310/// `usize` host addresses.  The crash dump path only reads host memory
311/// through raw pointers, so it never needs the file-mapping metadata
312/// stored in [`HostRegionBase`] on Windows.
313#[cfg(crashdump)]
314#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
315pub(crate) struct CrashDumpMemoryRegion;
316
317#[cfg(crashdump)]
318impl MemoryRegionKind for CrashDumpMemoryRegion {
319    type HostBaseType = usize;
320
321    fn add(base: Self::HostBaseType, size: usize) -> Self::HostBaseType {
322        base + size
323    }
324}
325
326/// A memory region used exclusively by the crash dump path.
327///
328/// Host addresses are always raw `usize` pointers, avoiding the need
329/// to construct platform-specific wrappers like [`HostRegionBase`].
330#[cfg(crashdump)]
331pub(crate) type CrashDumpRegion = MemoryRegion_<CrashDumpMemoryRegion>;
332
333pub(crate) struct MemoryRegionVecBuilder<K: MemoryRegionKind> {
334    guest_base_phys_addr: usize,
335    host_base_virt_addr: K::HostBaseType,
336    regions: Vec<MemoryRegion_<K>>,
337}
338
339impl<K: MemoryRegionKind> MemoryRegionVecBuilder<K> {
340    pub(crate) fn new(guest_base_phys_addr: usize, host_base_virt_addr: K::HostBaseType) -> Self {
341        Self {
342            guest_base_phys_addr,
343            host_base_virt_addr,
344            regions: Vec::new(),
345        }
346    }
347
348    fn push(
349        &mut self,
350        size: usize,
351        flags: MemoryRegionFlags,
352        region_type: MemoryRegionType,
353    ) -> usize {
354        if self.regions.is_empty() {
355            let guest_end = self.guest_base_phys_addr + size;
356            let host_end = <K as MemoryRegionKind>::add(self.host_base_virt_addr, size);
357            self.regions.push(MemoryRegion_ {
358                guest_region: self.guest_base_phys_addr..guest_end,
359                host_region: self.host_base_virt_addr..host_end,
360                flags,
361                region_type,
362            });
363            return guest_end - self.guest_base_phys_addr;
364        }
365
366        #[allow(clippy::unwrap_used)]
367        // we know this is safe because we check if the regions are empty above
368        let last_region = self.regions.last().unwrap();
369        let host_end = <K as MemoryRegionKind>::add(last_region.host_region.end, size);
370        let new_region = MemoryRegion_ {
371            guest_region: last_region.guest_region.end..last_region.guest_region.end + size,
372            host_region: last_region.host_region.end..host_end,
373            flags,
374            region_type,
375        };
376        let ret = new_region.guest_region.end;
377        self.regions.push(new_region);
378        ret - self.guest_base_phys_addr
379    }
380
381    /// Pushes a memory region with the given size. Will round up the size to the nearest page.
382    /// Returns the current size of the all memory regions in the builder after adding the given region.
383    /// # Note:
384    /// Memory regions pushed MUST match the guest's memory layout, in SandboxMemoryLayout::new(..)
385    pub(crate) fn push_page_aligned(
386        &mut self,
387        size: usize,
388        flags: MemoryRegionFlags,
389        region_type: MemoryRegionType,
390    ) -> usize {
391        let aligned_size = (size + PAGE_SIZE_USIZE - 1) & !(PAGE_SIZE_USIZE - 1);
392        self.push(aligned_size, flags, region_type)
393    }
394
395    /// Consumes the builder and returns a vec of memory regions. The regions are guaranteed to be a contiguous chunk
396    /// of memory, in other words, there will be any memory gaps between them.
397    pub(crate) fn build(self) -> Vec<MemoryRegion_<K>> {
398        self.regions
399    }
400}
401
402#[cfg(mshv3)]
403impl From<&MemoryRegion> for mshv_user_mem_region {
404    fn from(region: &MemoryRegion) -> Self {
405        let size = (region.guest_region.end - region.guest_region.start) as u64;
406        let guest_pfn = region.guest_region.start as u64 >> PAGE_SHIFT;
407        let userspace_addr = region.host_region.start as u64;
408
409        let flags: u8 = region.flags.iter().fold(0, |acc, flag| {
410            let flag_value = match flag {
411                MemoryRegionFlags::NONE => 1 << MSHV_SET_MEM_BIT_UNMAP,
412                MemoryRegionFlags::READ => 0,
413                MemoryRegionFlags::WRITE => 1 << MSHV_SET_MEM_BIT_WRITABLE,
414                MemoryRegionFlags::EXECUTE => 1 << MSHV_SET_MEM_BIT_EXECUTABLE,
415                _ => 0, // ignore any unknown flags
416            };
417            acc | flag_value
418        });
419
420        mshv_user_mem_region {
421            guest_pfn,
422            size,
423            userspace_addr,
424            flags,
425            ..Default::default()
426        }
427    }
428}
429
430#[cfg(kvm)]
431impl From<&MemoryRegion> for kvm_bindings::kvm_userspace_memory_region {
432    fn from(region: &MemoryRegion) -> Self {
433        let perm_flags =
434            MemoryRegionFlags::READ | MemoryRegionFlags::WRITE | MemoryRegionFlags::EXECUTE;
435
436        let perm_flags = perm_flags.intersection(region.flags);
437
438        kvm_userspace_memory_region {
439            slot: 0,
440            guest_phys_addr: region.guest_region.start as u64,
441            memory_size: (region.guest_region.end - region.guest_region.start) as u64,
442            userspace_addr: region.host_region.start as u64,
443            flags: if perm_flags.contains(MemoryRegionFlags::WRITE) {
444                0 // RWX
445            } else {
446                // Note: KVM_MEM_READONLY is executable
447                KVM_MEM_READONLY // RX 
448            },
449        }
450    }
451}