Skip to content

Commit ad01764

Browse files
committed
whp: support dynamically mapping/unmapping regions from the surrogate
This changes the representation of a memory region on Windows to include a file mapping from which it is derived, allowing the WHP code to dynamically map any files necessary into the surrogate. This removes the restriction that all mappings into a hyperlight VM on Windows must come from a single file-backed host mapping. Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com>
1 parent 667ff93 commit ad01764

File tree

13 files changed

+284
-236
lines changed

13 files changed

+284
-236
lines changed

src/hyperlight_host/src/hypervisor/crashdump.rs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,11 @@ impl GuestView {
8686
let regions = ctx
8787
.regions
8888
.iter()
89-
.filter(|r| !r.host_region.is_empty())
89+
.filter(|r| !r.guest_region.is_empty())
9090
.map(|r| VaRegion {
9191
begin: r.guest_region.start as u64,
9292
end: r.guest_region.end as u64,
93-
offset: r.host_region.start as u64,
93+
offset: <_ as Into<usize>>::into(r.host_region.start) as u64,
9494
protection: VaProtection {
9595
is_private: false,
9696
read: r.flags.contains(MemoryRegionFlags::READ),
@@ -225,8 +225,8 @@ impl ReadProcessMemory for GuestMemReader {
225225
let offset = base - r.guest_region.start;
226226
let region_slice = unsafe {
227227
std::slice::from_raw_parts(
228-
r.host_region.start as *const u8,
229-
r.host_region.len(),
228+
<_ as Into<usize>>::into(r.host_region.start) as *const u8,
229+
r.guest_region.len(),
230230
)
231231
};
232232

@@ -463,9 +463,20 @@ mod test {
463463
#[test]
464464
fn test_crashdump_dummy_core_dump() {
465465
let dummy_vec = vec![0; 0x1000];
466+
use crate::mem::memory_region::{HostGuestMemoryRegion, MemoryRegionKind};
467+
#[cfg(target_os = "windows")]
468+
let host_base = crate::mem::memory_region::HostRegionBase {
469+
from_handle: windows::Win32::Foundation::INVALID_HANDLE_VALUE.into(),
470+
handle_base: 0,
471+
handle_size: -1isize as usize,
472+
offset: dummy_vec.as_ptr() as usize,
473+
};
474+
#[cfg(not(target_os = "windows"))]
475+
let host_base = dummy_vec.as_ptr() as usize;
476+
let host_end = <HostGuestMemoryRegion as MemoryRegionKind>::add(host_base, dummy_vec.len());
466477
let regions = vec![MemoryRegion {
467478
guest_region: 0x1000..0x2000,
468-
host_region: dummy_vec.as_ptr() as usize..dummy_vec.as_ptr() as usize + dummy_vec.len(),
479+
host_region: host_base..host_end,
469480
flags: MemoryRegionFlags::READ | MemoryRegionFlags::WRITE,
470481
region_type: crate::mem::memory_region::MemoryRegionType::Code,
471482
}];

src/hyperlight_host/src/hypervisor/gdb/mod.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,9 @@ impl DebugMemoryAccess {
141141
DebugMemoryAccessError::TranslateGuestAddress(mem_offset as u64)
142142
})?;
143143

144+
let host_start_ptr = <_ as Into<usize>>::into(reg.host_region.start);
144145
let bytes: &[u8] = unsafe {
145-
slice::from_raw_parts(reg.host_region.start as *const u8, reg.host_region.len())
146+
slice::from_raw_parts(host_start_ptr as *const u8, reg.guest_region.len())
146147
};
147148
data[..read_len].copy_from_slice(&bytes[region_offset..region_offset + read_len]);
148149

@@ -211,11 +212,9 @@ impl DebugMemoryAccess {
211212
DebugMemoryAccessError::TranslateGuestAddress(mem_offset as u64)
212213
})?;
213214

215+
let host_start_ptr = <_ as Into<usize>>::into(reg.host_region.start);
214216
let bytes: &mut [u8] = unsafe {
215-
slice::from_raw_parts_mut(
216-
reg.host_region.start as *mut u8,
217-
reg.host_region.len(),
218-
)
217+
slice::from_raw_parts_mut(host_start_ptr as *mut u8, reg.guest_region.len())
219218
};
220219
bytes[region_offset..region_offset + write_len].copy_from_slice(&data[..write_len]);
221220

src/hyperlight_host/src/hypervisor/hyperlight_vm.rs

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,6 @@ use crate::hypervisor::virtual_machine::{
6161
HypervisorType, MapMemoryError, RegisterError, RunVcpuError, UnmapMemoryError, VmError, VmExit,
6262
get_available_hypervisor,
6363
};
64-
#[cfg(target_os = "windows")]
65-
use crate::hypervisor::wrappers::HandleWrapper;
6664
use crate::hypervisor::{InterruptHandle, InterruptHandleImpl, get_max_log_level};
6765
use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags, MemoryRegionType};
6866
use crate::mem::mgr::SandboxMemoryManager;
@@ -325,8 +323,6 @@ impl HyperlightVm {
325323
entrypoint: u64,
326324
rsp: u64,
327325
#[cfg_attr(target_os = "windows", allow(unused_variables))] config: &SandboxConfiguration,
328-
#[cfg(target_os = "windows")] handle: HandleWrapper,
329-
#[cfg(target_os = "windows")] raw_size: usize,
330326
#[cfg(gdb)] gdb_conn: Option<DebugCommChannel<DebugResponse, DebugMsg>>,
331327
#[cfg(crashdump)] rt_cfg: SandboxRuntimeConfig,
332328
#[cfg(feature = "mem_profile")] trace_info: MemTraceInfo,
@@ -343,9 +339,7 @@ impl HyperlightVm {
343339
#[cfg(mshv3)]
344340
Some(HypervisorType::Mshv) => Box::new(MshvVm::new().map_err(VmError::CreateVm)?),
345341
#[cfg(target_os = "windows")]
346-
Some(HypervisorType::Whp) => {
347-
Box::new(WhpVm::new(handle, raw_size).map_err(VmError::CreateVm)?)
348-
}
342+
Some(HypervisorType::Whp) => Box::new(WhpVm::new().map_err(VmError::CreateVm)?),
349343
None => return Err(CreateHyperlightVmError::NoHypervisorFound),
350344
};
351345

@@ -500,8 +494,10 @@ impl HyperlightVm {
500494
if [
501495
region.guest_region.start,
502496
region.guest_region.end,
503-
region.host_region.start,
504-
region.host_region.end,
497+
#[allow(clippy::useless_conversion)]
498+
region.host_region.start.into(),
499+
#[allow(clippy::useless_conversion)]
500+
region.host_region.end.into(),
505501
]
506502
.iter()
507503
.any(|x| x % self.page_size != 0)

src/hyperlight_host/src/hypervisor/surrogate_process.rs

Lines changed: 122 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,60 +15,162 @@ limitations under the License.
1515
*/
1616

1717
use core::ffi::c_void;
18+
use std::collections::HashMap;
19+
use std::collections::hash_map::Entry;
1820

21+
use hyperlight_common::mem::PAGE_SIZE_USIZE;
1922
use tracing::{Span, instrument};
2023
use windows::Win32::Foundation::HANDLE;
2124
use windows::Win32::System::Memory::{
22-
MEMORY_MAPPED_VIEW_ADDRESS, UNMAP_VIEW_OF_FILE_FLAGS, UnmapViewOfFile2,
25+
MEMORY_MAPPED_VIEW_ADDRESS, MapViewOfFileNuma2, PAGE_NOACCESS, PAGE_PROTECTION_FLAGS,
26+
PAGE_READWRITE, UNMAP_VIEW_OF_FILE_FLAGS, UnmapViewOfFile2, VirtualProtectEx,
2327
};
28+
use windows::Win32::System::SystemServices::NUMA_NO_PREFERRED_NODE;
2429

2530
use super::surrogate_process_manager::get_surrogate_process_manager;
2631
use super::wrappers::HandleWrapper;
32+
use crate::HyperlightError::WindowsAPIError;
33+
use crate::{Result, log_then_return};
34+
35+
#[derive(Debug)]
36+
pub(crate) struct HandleMapping {
37+
pub(crate) use_count: u64,
38+
pub(crate) surrogate_base: *mut c_void,
39+
}
2740

2841
/// Contains details of a surrogate process to be used by a Sandbox for providing memory to a HyperV VM on Windows.
2942
/// See surrogate_process_manager for details on why this is needed.
3043
#[derive(Debug)]
3144
pub(super) struct SurrogateProcess {
32-
/// The address of memory allocated in the surrogate process to be mapped to the VM.
33-
/// This includes the first guard page
34-
pub(crate) allocated_address: *mut c_void,
45+
/// The various mappings between handles in the host and surrogate process
46+
pub(crate) mappings: HashMap<usize, HandleMapping>,
3547
/// The handle to the surrogate process.
3648
pub(crate) process_handle: HandleWrapper,
3749
}
3850

3951
impl SurrogateProcess {
4052
#[instrument(skip_all, parent = Span::current(), level= "Trace")]
41-
pub(super) fn new(allocated_address: *mut c_void, process_handle: HANDLE) -> Self {
53+
pub(super) fn new(process_handle: HANDLE) -> Self {
4254
Self {
43-
allocated_address,
55+
mappings: HashMap::new(),
4456
process_handle: HandleWrapper::from(process_handle),
4557
}
4658
}
59+
60+
pub(super) fn map(
61+
&mut self,
62+
handle: HandleWrapper,
63+
host_base: usize,
64+
host_size: usize,
65+
) -> Result<*mut c_void> {
66+
match self.mappings.entry(host_base) {
67+
Entry::Occupied(mut oe) => {
68+
oe.get_mut().use_count += 1;
69+
Ok(oe.get().surrogate_base)
70+
}
71+
Entry::Vacant(ve) => {
72+
// Use MapViewOfFile2 to map memory into the surrogate process, the MapViewOfFile2 API is implemented in as an inline function in a windows header file
73+
// (see https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-mapviewoffile2#remarks) so we use the same API it uses in the header file here instead of
74+
// MapViewOfFile2 which does not exist in the rust crate (see https://github.com/microsoft/windows-rs/issues/2595)
75+
let surrogate_base = unsafe {
76+
MapViewOfFileNuma2(
77+
handle.into(),
78+
self.process_handle.into(),
79+
0,
80+
None,
81+
host_size,
82+
0,
83+
PAGE_READWRITE.0,
84+
NUMA_NO_PREFERRED_NODE,
85+
)
86+
};
87+
let mut unused_out_old_prot_flags = PAGE_PROTECTION_FLAGS(0);
88+
89+
// the first page of the raw_size is the guard page
90+
let first_guard_page_start = surrogate_base.Value;
91+
if let Err(e) = unsafe {
92+
VirtualProtectEx(
93+
self.process_handle.into(),
94+
first_guard_page_start,
95+
PAGE_SIZE_USIZE,
96+
PAGE_NOACCESS,
97+
&mut unused_out_old_prot_flags,
98+
)
99+
} {
100+
log_then_return!(WindowsAPIError(e.clone()));
101+
}
102+
103+
// the last page of the raw_size is the guard page
104+
let last_guard_page_start =
105+
unsafe { first_guard_page_start.add(host_size - PAGE_SIZE_USIZE) };
106+
if let Err(e) = unsafe {
107+
VirtualProtectEx(
108+
self.process_handle.into(),
109+
last_guard_page_start,
110+
PAGE_SIZE_USIZE,
111+
PAGE_NOACCESS,
112+
&mut unused_out_old_prot_flags,
113+
)
114+
} {
115+
log_then_return!(WindowsAPIError(e.clone()));
116+
}
117+
ve.insert(HandleMapping {
118+
use_count: 1,
119+
surrogate_base: surrogate_base.Value,
120+
});
121+
Ok(surrogate_base.Value)
122+
}
123+
}
124+
}
125+
126+
pub(super) fn unmap(&mut self, host_base: usize) {
127+
match self.mappings.entry(host_base) {
128+
Entry::Occupied(mut oe) => {
129+
oe.get_mut().use_count -= 1;
130+
if oe.get().use_count == 0 {
131+
let entry = oe.remove();
132+
self.unmap_helper(entry.surrogate_base);
133+
}
134+
}
135+
Entry::Vacant(_) => {
136+
#[cfg(debug_assertions)]
137+
panic!("Attempted to unmap from surrogate a region that was never mapped")
138+
}
139+
}
140+
}
141+
142+
fn unmap_helper(&self, surrogate_base: *mut c_void) {
143+
let memory_mapped_view_address = MEMORY_MAPPED_VIEW_ADDRESS {
144+
Value: surrogate_base,
145+
};
146+
let flags = UNMAP_VIEW_OF_FILE_FLAGS(0);
147+
if let Err(e) = unsafe {
148+
UnmapViewOfFile2(
149+
self.process_handle.into(),
150+
memory_mapped_view_address,
151+
flags,
152+
)
153+
} {
154+
tracing::error!(
155+
"Failed to free surrogate process resources (UnmapViewOfFile2 failed): {:?}",
156+
e
157+
);
158+
}
159+
}
47160
}
48161

49162
impl Default for SurrogateProcess {
50163
#[instrument(skip_all, parent = Span::current(), level= "Trace")]
51164
fn default() -> Self {
52-
let allocated_address = std::ptr::null_mut();
53-
Self::new(allocated_address, Default::default())
165+
Self::new(Default::default())
54166
}
55167
}
56168

57169
impl Drop for SurrogateProcess {
58170
#[instrument(skip_all, parent = Span::current(), level= "Trace")]
59171
fn drop(&mut self) {
60-
let process_handle: HANDLE = self.process_handle.into();
61-
let memory_mapped_view_address = MEMORY_MAPPED_VIEW_ADDRESS {
62-
Value: self.allocated_address,
63-
};
64-
let flags = UNMAP_VIEW_OF_FILE_FLAGS(0);
65-
if let Err(e) =
66-
unsafe { UnmapViewOfFile2(process_handle, memory_mapped_view_address, flags) }
67-
{
68-
tracing::error!(
69-
"Failed to free surrogate process resources (UnmapViewOfFile2 failed): {:?}",
70-
e
71-
);
172+
for mapping in self.mappings.values() {
173+
self.unmap_helper(mapping.surrogate_base);
72174
}
73175

74176
// we need to do this take so we can take ownership

0 commit comments

Comments
 (0)