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