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