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}