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(feature = "init-paging")]
35use super::mgr::{PAGE_NX, PAGE_PRESENT, PAGE_RW, PAGE_USER};
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        /// identifier that this is a stack guard page
52        const STACK_GUARD = 8;
53    }
54}
55
56impl MemoryRegionFlags {
57    #[cfg(feature = "init-paging")]
58    pub(crate) fn translate_flags(&self) -> u64 {
59        let mut page_flags = 0;
60
61        page_flags |= PAGE_PRESENT; // Mark page as present
62
63        if self.contains(MemoryRegionFlags::WRITE) {
64            page_flags |= PAGE_RW; // Allow read/write
65        }
66
67        if self.contains(MemoryRegionFlags::STACK_GUARD) {
68            page_flags |= PAGE_RW; // The guard page is marked RW so that if it gets written to we can detect it in the host
69        }
70
71        if self.contains(MemoryRegionFlags::EXECUTE) {
72            page_flags |= PAGE_USER; // Allow user access
73        } else {
74            page_flags |= PAGE_NX; // Mark as non-executable if EXECUTE is not set
75        }
76
77        page_flags
78    }
79}
80
81impl std::fmt::Display for MemoryRegionFlags {
82    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83        if self.is_empty() {
84            write!(f, "NONE")
85        } else {
86            let mut first = true;
87            if self.contains(MemoryRegionFlags::READ) {
88                write!(f, "READ")?;
89                first = false;
90            }
91            if self.contains(MemoryRegionFlags::WRITE) {
92                if !first {
93                    write!(f, " | ")?;
94                }
95                write!(f, "WRITE")?;
96                first = false;
97            }
98            if self.contains(MemoryRegionFlags::EXECUTE) {
99                if !first {
100                    write!(f, " | ")?;
101                }
102                write!(f, "EXECUTE")?;
103            }
104            Ok(())
105        }
106    }
107}
108
109#[cfg(target_os = "windows")]
110impl TryFrom<WHV_MEMORY_ACCESS_TYPE> for MemoryRegionFlags {
111    type Error = crate::HyperlightError;
112
113    fn try_from(flags: WHV_MEMORY_ACCESS_TYPE) -> crate::Result<Self> {
114        match flags {
115            Hypervisor::WHvMemoryAccessRead => Ok(MemoryRegionFlags::READ),
116            Hypervisor::WHvMemoryAccessWrite => Ok(MemoryRegionFlags::WRITE),
117            Hypervisor::WHvMemoryAccessExecute => Ok(MemoryRegionFlags::EXECUTE),
118            _ => Err(crate::HyperlightError::Error(
119                "unknown memory access type".to_string(),
120            )),
121        }
122    }
123}
124
125#[cfg(mshv3)]
126impl TryFrom<hv_x64_memory_intercept_message> for MemoryRegionFlags {
127    type Error = crate::HyperlightError;
128
129    fn try_from(msg: hv_x64_memory_intercept_message) -> crate::Result<Self> {
130        let access_type = msg.header.intercept_access_type;
131        match access_type {
132            0 => Ok(MemoryRegionFlags::READ),
133            1 => Ok(MemoryRegionFlags::WRITE),
134            2 => Ok(MemoryRegionFlags::EXECUTE),
135            _ => Err(crate::HyperlightError::Error(
136                "unknown memory access type".to_string(),
137            )),
138        }
139    }
140}
141
142// only used for debugging
143#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
144/// The type of memory region
145pub enum MemoryRegionType {
146    /// The region contains the guest's page tables
147    PageTables,
148    /// The region contains the guest's code
149    Code,
150    /// The region contains the guest's init data
151    InitData,
152    /// The region contains the PEB
153    Peb,
154    /// The region contains the Host Function Definitions
155    HostFunctionDefinitions,
156    /// The region contains the Input Data
157    InputData,
158    /// The region contains the Output Data
159    OutputData,
160    /// The region contains the Heap
161    Heap,
162    /// The region contains the Guard Page
163    GuardPage,
164    /// The region contains the Stack
165    Stack,
166}
167
168/// represents a single memory region inside the guest. All memory within a region has
169/// the same memory permissions
170#[derive(Debug, Clone, PartialEq, Eq, Hash)]
171pub struct MemoryRegion {
172    /// the range of guest memory addresses
173    pub guest_region: Range<usize>,
174    /// the range of host memory addresses
175    pub host_region: Range<usize>,
176    /// memory access flags for the given region
177    pub flags: MemoryRegionFlags,
178    /// the type of memory region
179    pub region_type: MemoryRegionType,
180}
181
182pub(crate) struct MemoryRegionVecBuilder {
183    guest_base_phys_addr: usize,
184    host_base_virt_addr: usize,
185    regions: Vec<MemoryRegion>,
186}
187
188impl MemoryRegionVecBuilder {
189    pub(crate) fn new(guest_base_phys_addr: usize, host_base_virt_addr: usize) -> Self {
190        Self {
191            guest_base_phys_addr,
192            host_base_virt_addr,
193            regions: Vec::new(),
194        }
195    }
196
197    fn push(
198        &mut self,
199        size: usize,
200        flags: MemoryRegionFlags,
201        region_type: MemoryRegionType,
202    ) -> usize {
203        if self.regions.is_empty() {
204            let guest_end = self.guest_base_phys_addr + size;
205            let host_end = self.host_base_virt_addr + size;
206            self.regions.push(MemoryRegion {
207                guest_region: self.guest_base_phys_addr..guest_end,
208                host_region: self.host_base_virt_addr..host_end,
209                flags,
210                region_type,
211            });
212            return guest_end - self.guest_base_phys_addr;
213        }
214
215        #[allow(clippy::unwrap_used)]
216        // we know this is safe because we check if the regions are empty above
217        let last_region = self.regions.last().unwrap();
218        let new_region = MemoryRegion {
219            guest_region: last_region.guest_region.end..last_region.guest_region.end + size,
220            host_region: last_region.host_region.end..last_region.host_region.end + size,
221            flags,
222            region_type,
223        };
224        let ret = new_region.guest_region.end;
225        self.regions.push(new_region);
226        ret - self.guest_base_phys_addr
227    }
228
229    /// Pushes a memory region with the given size. Will round up the size to the nearest page.
230    /// Returns the current size of the all memory regions in the builder after adding the given region.
231    /// # Note:
232    /// Memory regions pushed MUST match the guest's memory layout, in SandboxMemoryLayout::new(..)
233    pub(crate) fn push_page_aligned(
234        &mut self,
235        size: usize,
236        flags: MemoryRegionFlags,
237        region_type: MemoryRegionType,
238    ) -> usize {
239        let aligned_size = (size + PAGE_SIZE_USIZE - 1) & !(PAGE_SIZE_USIZE - 1);
240        self.push(aligned_size, flags, region_type)
241    }
242
243    /// Consumes the builder and returns a vec of memory regions. The regions are guaranteed to be a contiguous chunk
244    /// of memory, in other words, there will be any memory gaps between them.
245    pub(crate) fn build(self) -> Vec<MemoryRegion> {
246        self.regions
247    }
248}
249
250#[cfg(mshv3)]
251impl From<&MemoryRegion> for mshv_user_mem_region {
252    fn from(region: &MemoryRegion) -> Self {
253        let size = (region.guest_region.end - region.guest_region.start) as u64;
254        let guest_pfn = region.guest_region.start as u64 >> PAGE_SHIFT;
255        let userspace_addr = region.host_region.start as u64;
256
257        let flags: u8 = region.flags.iter().fold(0, |acc, flag| {
258            let flag_value = match flag {
259                MemoryRegionFlags::NONE => 1 << MSHV_SET_MEM_BIT_UNMAP,
260                MemoryRegionFlags::READ => 0,
261                MemoryRegionFlags::WRITE => 1 << MSHV_SET_MEM_BIT_WRITABLE,
262                MemoryRegionFlags::EXECUTE => 1 << MSHV_SET_MEM_BIT_EXECUTABLE,
263                _ => 0, // ignore any unknown flags
264            };
265            acc | flag_value
266        });
267
268        mshv_user_mem_region {
269            guest_pfn,
270            size,
271            userspace_addr,
272            flags,
273            ..Default::default()
274        }
275    }
276}
277
278#[cfg(kvm)]
279impl From<&MemoryRegion> for kvm_bindings::kvm_userspace_memory_region {
280    fn from(region: &MemoryRegion) -> Self {
281        let perm_flags =
282            MemoryRegionFlags::READ | MemoryRegionFlags::WRITE | MemoryRegionFlags::EXECUTE;
283
284        let perm_flags = perm_flags.intersection(region.flags);
285
286        kvm_userspace_memory_region {
287            slot: 0,
288            guest_phys_addr: region.guest_region.start as u64,
289            memory_size: (region.guest_region.end - region.guest_region.start) as u64,
290            userspace_addr: region.host_region.start as u64,
291            flags: if perm_flags.contains(MemoryRegionFlags::WRITE) {
292                0 // RWX
293            } else {
294                // Note: KVM_MEM_READONLY is executable
295                KVM_MEM_READONLY // RX 
296            },
297        }
298    }
299}