hyperlight_host/mem/
memory_region.rs

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