Skip to main content

nub_host_kvm/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(kvm)]
21use kvm_bindings::{KVM_MEM_READONLY, kvm_userspace_memory_region};
22use nub_host_common::mem::PAGE_SIZE_USIZE;
23
24pub(crate) const DEFAULT_GUEST_BLOB_MEM_FLAGS: MemoryRegionFlags = MemoryRegionFlags::READ;
25
26bitflags! {
27    /// flags representing memory permission for a memory region
28    #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
29    pub struct MemoryRegionFlags: u32 {
30        /// no permissions
31        const NONE = 0;
32        /// allow guest to read
33        const READ = 1;
34        /// allow guest to write
35        const WRITE = 2;
36        /// allow guest to execute
37        const EXECUTE = 4;
38    }
39}
40
41impl std::fmt::Display for MemoryRegionFlags {
42    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43        if self.is_empty() {
44            write!(f, "NONE")
45        } else {
46            let mut first = true;
47            if self.contains(MemoryRegionFlags::READ) {
48                write!(f, "READ")?;
49                first = false;
50            }
51            if self.contains(MemoryRegionFlags::WRITE) {
52                if !first {
53                    write!(f, " | ")?;
54                }
55                write!(f, "WRITE")?;
56                first = false;
57            }
58            if self.contains(MemoryRegionFlags::EXECUTE) {
59                if !first {
60                    write!(f, " | ")?;
61                }
62                write!(f, "EXECUTE")?;
63            }
64            Ok(())
65        }
66    }
67}
68
69// NOTE: In the future, all host-side knowledge about memory region types
70// should collapse down to Snapshot vs Scratch (see shared_mem.rs). Today
71// only Scratch, Snapshot, and MappedFile are actually constructed; the
72// remaining variants are vestigial. Not part of the public API.
73#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
74/// The type of memory region
75pub enum MemoryRegionType {
76    /// The region contains the guest's code
77    Code,
78    /// The region contains the guest's init data
79    InitData,
80    /// The region contains the PEB
81    Peb,
82    /// The region contains the Heap
83    Heap,
84    /// The region contains scratch (RWX) memory
85    Scratch,
86    /// The snapshot region
87    Snapshot,
88    /// An externally-mapped file (via `MultiUseSandbox::map_file_cow`).
89    /// These regions are backed by file handles (Windows) or mmap
90    /// (Linux) and are read-only + executable. They are cleaned up
91    /// during restore/drop — not part of the guest's own allocator.
92    MappedFile,
93}
94
95/// A trait that distinguishes between different kinds of memory region representations.
96///
97/// This trait is used to parameterize [`MemoryRegion_`]
98pub trait MemoryRegionKind {
99    /// The type used to represent host memory addresses.
100    type HostBaseType: Copy;
101
102    /// Computes an address by adding a size to a base address.
103    ///
104    /// # Arguments
105    /// * `base` - The starting address
106    /// * `size` - The size in bytes to add
107    ///
108    /// # Returns
109    /// The computed end address (`base + size` for host-guest regions,
110    /// `()` for guest-only regions).
111    fn add(base: Self::HostBaseType, size: usize) -> Self::HostBaseType;
112}
113
114/// Type for memory regions that track both host and guest addresses.
115///
116/// When one of these is created, it always ends up in a sandbox
117/// quickly. It's an invariant of this type that as long as one of
118/// these is associated with a sandbox, it's always acceptable to read
119/// from it, since a lot of the snapshot code
120/// does. (Note: this means that _writable_ HostGuestMemoryRegions are
121/// not possible to support at the moment).
122#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
123pub struct HostGuestMemoryRegion {}
124
125impl MemoryRegionKind for HostGuestMemoryRegion {
126    type HostBaseType = usize;
127
128    fn add(base: Self::HostBaseType, size: usize) -> Self::HostBaseType {
129        base + size
130    }
131}
132
133/// Type for memory regions that only track guest addresses.
134///
135#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
136pub(crate) struct GuestMemoryRegion {}
137
138impl MemoryRegionKind for GuestMemoryRegion {
139    type HostBaseType = ();
140
141    fn add(_base: Self::HostBaseType, _size: usize) -> Self::HostBaseType {}
142}
143
144/// represents a single memory region inside the guest. All memory within a region has
145/// the same memory permissions
146#[derive(Debug, Clone, PartialEq, Eq, Hash)]
147pub struct MemoryRegion_<K: MemoryRegionKind> {
148    /// the range of guest memory addresses
149    pub guest_region: Range<usize>,
150    /// the range of host memory addresses
151    ///
152    /// Note that Range<()> = () x () = ().
153    pub host_region: Range<K::HostBaseType>,
154    /// memory access flags for the given region
155    pub flags: MemoryRegionFlags,
156    /// the type of memory region
157    pub region_type: MemoryRegionType,
158}
159
160/// A memory region that tracks both host and guest addresses.
161pub type MemoryRegion = MemoryRegion_<HostGuestMemoryRegion>;
162
163pub(crate) struct MemoryRegionVecBuilder<K: MemoryRegionKind> {
164    guest_base_phys_addr: usize,
165    host_base_virt_addr: K::HostBaseType,
166    regions: Vec<MemoryRegion_<K>>,
167}
168
169impl<K: MemoryRegionKind> MemoryRegionVecBuilder<K> {
170    pub(crate) fn new(guest_base_phys_addr: usize, host_base_virt_addr: K::HostBaseType) -> Self {
171        Self {
172            guest_base_phys_addr,
173            host_base_virt_addr,
174            regions: Vec::new(),
175        }
176    }
177
178    fn push(
179        &mut self,
180        size: usize,
181        flags: MemoryRegionFlags,
182        region_type: MemoryRegionType,
183    ) -> usize {
184        if self.regions.is_empty() {
185            let guest_end = self.guest_base_phys_addr + size;
186            let host_end = <K as MemoryRegionKind>::add(self.host_base_virt_addr, size);
187            self.regions.push(MemoryRegion_ {
188                guest_region: self.guest_base_phys_addr..guest_end,
189                host_region: self.host_base_virt_addr..host_end,
190                flags,
191                region_type,
192            });
193            return guest_end - self.guest_base_phys_addr;
194        }
195
196        #[allow(clippy::unwrap_used)]
197        // we know this is safe because we check if the regions are empty above
198        let last_region = self.regions.last().unwrap();
199        let host_end = <K as MemoryRegionKind>::add(last_region.host_region.end, size);
200        let new_region = MemoryRegion_ {
201            guest_region: last_region.guest_region.end..last_region.guest_region.end + size,
202            host_region: last_region.host_region.end..host_end,
203            flags,
204            region_type,
205        };
206        let ret = new_region.guest_region.end;
207        self.regions.push(new_region);
208        ret - self.guest_base_phys_addr
209    }
210
211    /// Pushes a memory region with the given size. Will round up the size to the nearest page.
212    /// Returns the current size of the all memory regions in the builder after adding the given region.
213    /// # Note:
214    /// Memory regions pushed MUST match the guest's memory layout, in SandboxMemoryLayout::new(..)
215    pub(crate) fn push_page_aligned(
216        &mut self,
217        size: usize,
218        flags: MemoryRegionFlags,
219        region_type: MemoryRegionType,
220    ) -> usize {
221        let aligned_size = (size + PAGE_SIZE_USIZE - 1) & !(PAGE_SIZE_USIZE - 1);
222        self.push(aligned_size, flags, region_type)
223    }
224
225    /// Consumes the builder and returns a vec of memory regions. The regions are guaranteed to be a contiguous chunk
226    /// of memory, in other words, there will be any memory gaps between them.
227    pub(crate) fn build(self) -> Vec<MemoryRegion_<K>> {
228        self.regions
229    }
230}
231
232#[cfg(kvm)]
233impl From<&MemoryRegion> for kvm_bindings::kvm_userspace_memory_region {
234    fn from(region: &MemoryRegion) -> Self {
235        let perm_flags =
236            MemoryRegionFlags::READ | MemoryRegionFlags::WRITE | MemoryRegionFlags::EXECUTE;
237
238        let perm_flags = perm_flags.intersection(region.flags);
239
240        kvm_userspace_memory_region {
241            slot: 0,
242            guest_phys_addr: region.guest_region.start as u64,
243            memory_size: (region.guest_region.end - region.guest_region.start) as u64,
244            userspace_addr: region.host_region.start as u64,
245            flags: if perm_flags.contains(MemoryRegionFlags::WRITE) {
246                0 // RWX
247            } else {
248                // Note: KVM_MEM_READONLY is executable
249                KVM_MEM_READONLY // RX 
250            },
251        }
252    }
253}