Skip to content

Commit 85dd248

Browse files
committed
Review Feedback
Signed-off-by: Simon Davies <simongdavies@users.noreply.github.com>
1 parent e4204dd commit 85dd248

4 files changed

Lines changed: 95 additions & 309 deletions

File tree

src/hyperlight_host/src/hypervisor/virtual_machine/whp.rs

Lines changed: 78 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@ use std::os::raw::c_void;
2020
use tracing::Span;
2121
#[cfg(feature = "trace_guest")]
2222
use tracing_opentelemetry::OpenTelemetrySpanExt;
23-
use windows::Win32::Foundation::{FreeLibrary, HANDLE};
23+
use windows::Win32::Foundation::{CloseHandle, FreeLibrary, HANDLE};
2424
use windows::Win32::System::Hypervisor::*;
2525
use windows::Win32::System::LibraryLoader::*;
26+
use windows::Win32::System::Memory::{MEMORY_MAPPED_VIEW_ADDRESS, UnmapViewOfFile};
2627
use windows::core::s;
2728
use windows_result::HRESULT;
2829

@@ -40,7 +41,8 @@ use crate::hypervisor::virtual_machine::{
4041
CreateVmError, HypervisorError, MapMemoryError, RegisterError, RunVcpuError, UnmapMemoryError,
4142
VirtualMachine, VmExit, XSAVE_MIN_SIZE,
4243
};
43-
use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags};
44+
use crate::hypervisor::wrappers::HandleWrapper;
45+
use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags, MemoryRegionType};
4446
#[cfg(feature = "trace_guest")]
4547
use crate::sandbox::trace::TraceContext as SandboxTraceContext;
4648

@@ -65,19 +67,72 @@ pub(crate) fn is_hypervisor_present() -> bool {
6567
}
6668
}
6769

70+
/// Tracks a host-side file mapping created by [`MultiUseSandbox::map_file_cow`],
71+
/// providing RAII cleanup of the underlying Windows handles.
72+
///
73+
/// When dropped, releases both the `MapViewOfFile` view and the
74+
/// `CreateFileMappingW` handle, preventing resource leaks.
75+
struct OwnedFileMapping {
76+
/// Host-side base pointer returned by `MapViewOfFile`.
77+
view_base: *mut core::ffi::c_void,
78+
/// File mapping handle returned by `CreateFileMappingW`.
79+
mapping_handle: HandleWrapper,
80+
}
81+
82+
impl std::fmt::Debug for OwnedFileMapping {
83+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84+
f.debug_struct("OwnedFileMapping")
85+
.field("view_base", &self.view_base)
86+
.field("mapping_handle", &self.mapping_handle)
87+
.finish()
88+
}
89+
}
90+
91+
impl Drop for OwnedFileMapping {
92+
fn drop(&mut self) {
93+
// Unmap the view first, then close the file mapping handle.
94+
unsafe {
95+
if let Err(e) = UnmapViewOfFile(MEMORY_MAPPED_VIEW_ADDRESS {
96+
Value: self.view_base,
97+
}) {
98+
tracing::error!("Failed to unmap file view at {:?}: {:?}", self.view_base, e);
99+
}
100+
if let Err(e) = CloseHandle(self.mapping_handle.into()) {
101+
tracing::error!(
102+
"Failed to close file mapping handle {:?}: {:?}",
103+
self.mapping_handle,
104+
e
105+
);
106+
}
107+
}
108+
}
109+
}
110+
111+
// SAFETY: `view_base` is a pointer to a Windows memory-mapped view created by
112+
// `MapViewOfFile`. Both `UnmapViewOfFile` and `CloseHandle` are thread-safe
113+
// Win32 APIs that operate on kernel objects, so they can safely be called
114+
// from any thread. The `HandleWrapper` is already `Send + Sync`.
115+
unsafe impl Send for OwnedFileMapping {}
116+
unsafe impl Sync for OwnedFileMapping {}
117+
68118
/// A Windows Hypervisor Platform implementation of a single-vcpu VM
69119
#[derive(Debug)]
70120
pub(crate) struct WhpVm {
71121
partition: WHV_PARTITION_HANDLE,
72122
// Surrogate process for memory mapping
73123
surrogate_process: SurrogateProcess,
124+
/// Tracks host-side file mappings created by `map_file_cow` for
125+
/// RAII cleanup. Keyed by host handle_base address.
126+
file_mappings: Vec<OwnedFileMapping>,
74127
}
75128

76129
// Safety: `WhpVm` is !Send because it holds `SurrogateProcess` which contains a raw pointer
77130
// `allocated_address` (*mut c_void). This pointer represents a memory mapped view address
78131
// in the surrogate process. It is never dereferenced, only used for address arithmetic and
79132
// resource management (unmapping). This is a system resource that is not bound to the creating
80133
// thread and can be safely transferred between threads.
134+
// `OwnedFileMapping` contains a raw pointer (`view_base`) that is also a kernel resource
135+
// handle, safe to use from any thread.
81136
unsafe impl Send for WhpVm {}
82137

83138
impl WhpVm {
@@ -109,6 +164,7 @@ impl WhpVm {
109164
Ok(WhpVm {
110165
partition,
111166
surrogate_process,
167+
file_mappings: Vec::new(),
112168
})
113169
}
114170

@@ -144,7 +200,7 @@ impl VirtualMachine for WhpVm {
144200
region.host_region.start.from_handle,
145201
region.host_region.start.handle_base,
146202
region.host_region.start.handle_size,
147-
&region.host_region.start.surrogate_mapping,
203+
&region.region_type.surrogate_mapping(),
148204
)
149205
.map_err(|e| MapMemoryError::SurrogateProcess(e.to_string()))?;
150206
let surrogate_addr = surrogate_base.wrapping_add(region.host_region.start.offset);
@@ -194,6 +250,16 @@ impl VirtualMachine for WhpVm {
194250
)));
195251
}
196252

253+
// Track host-side file mappings for RAII cleanup.
254+
// MappedFile regions have host views + handles that need
255+
// UnmapViewOfFile + CloseHandle when the region is unmapped.
256+
if region.region_type == MemoryRegionType::MappedFile {
257+
self.file_mappings.push(OwnedFileMapping {
258+
view_base: region.host_region.start.handle_base as *mut core::ffi::c_void,
259+
mapping_handle: region.host_region.start.from_handle,
260+
});
261+
}
262+
197263
Ok(())
198264
}
199265

@@ -211,6 +277,15 @@ impl VirtualMachine for WhpVm {
211277
}
212278
self.surrogate_process
213279
.unmap(region.host_region.start.handle_base);
280+
281+
// Clean up host-side file mapping resources for MappedFile regions.
282+
// OwnedFileMapping::Drop calls UnmapViewOfFile + CloseHandle.
283+
if region.region_type == MemoryRegionType::MappedFile {
284+
let handle_base = region.host_region.start.handle_base;
285+
self.file_mappings
286+
.retain(|fm| fm.view_base as usize != handle_base);
287+
}
288+
214289
Ok(())
215290
}
216291

src/hyperlight_host/src/mem/memory_region.rs

Lines changed: 15 additions & 163 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,21 @@ pub(crate) enum MemoryRegionType {
138138
MappedFile,
139139
}
140140

141+
#[cfg(target_os = "windows")]
142+
impl MemoryRegionType {
143+
/// Derives the [`SurrogateMapping`] from this region type.
144+
///
145+
/// `MappedFile` regions use read-only file-backed mappings with no
146+
/// guard pages; all other region types use the standard sandbox
147+
/// shared memory mapping with guard pages.
148+
pub(crate) fn surrogate_mapping(&self) -> SurrogateMapping {
149+
match self {
150+
MemoryRegionType::MappedFile => SurrogateMapping::ReadOnlyFile,
151+
_ => SurrogateMapping::SandboxMemory,
152+
}
153+
}
154+
}
155+
141156
/// A trait that distinguishes between different kinds of memory region representations.
142157
///
143158
/// This trait is used to parameterize [`MemoryRegion_`]
@@ -202,9 +217,6 @@ pub struct HostRegionBase {
202217
/// The offset into file mapping region where this
203218
/// [`HostRegionBase`] is pointing.
204219
pub offset: usize,
205-
/// How this region should be mapped through the surrogate process.
206-
/// Controls page protection and guard page behaviour.
207-
pub(crate) surrogate_mapping: SurrogateMapping,
208220
}
209221
#[cfg(target_os = "windows")]
210222
impl std::hash::Hash for HostRegionBase {
@@ -216,7 +228,6 @@ impl std::hash::Hash for HostRegionBase {
216228
self.handle_base.hash(state);
217229
self.handle_size.hash(state);
218230
self.offset.hash(state);
219-
self.surrogate_mapping.hash(state);
220231
}
221232
}
222233
#[cfg(target_os = "windows")]
@@ -242,7 +253,6 @@ impl MemoryRegionKind for HostGuestMemoryRegion {
242253
handle_base: base.handle_base,
243254
handle_size: base.handle_size,
244255
offset: base.offset + size,
245-
surrogate_mapping: base.surrogate_mapping,
246256
}
247257
}
248258
}
@@ -442,161 +452,3 @@ impl From<&MemoryRegion> for kvm_bindings::kvm_userspace_memory_region {
442452
}
443453
}
444454
}
445-
446-
#[cfg(test)]
447-
mod tests {
448-
use super::*;
449-
450-
#[test]
451-
fn memory_region_flags_display() {
452-
assert_eq!(format!("{}", MemoryRegionFlags::NONE), "NONE");
453-
assert_eq!(format!("{}", MemoryRegionFlags::READ), "READ");
454-
assert_eq!(
455-
format!("{}", MemoryRegionFlags::READ | MemoryRegionFlags::WRITE),
456-
"READ | WRITE"
457-
);
458-
assert_eq!(
459-
format!(
460-
"{}",
461-
MemoryRegionFlags::READ | MemoryRegionFlags::WRITE | MemoryRegionFlags::EXECUTE
462-
),
463-
"READ | WRITE | EXECUTE"
464-
);
465-
}
466-
467-
#[cfg(target_os = "windows")]
468-
mod windows_tests {
469-
use std::collections::HashSet;
470-
use std::hash::{DefaultHasher, Hash, Hasher};
471-
472-
use super::*;
473-
474-
/// Helper to create a `HostRegionBase` with the given `SurrogateMapping`.
475-
fn make_host_region_base(mapping: SurrogateMapping) -> HostRegionBase {
476-
HostRegionBase {
477-
from_handle: windows::Win32::Foundation::INVALID_HANDLE_VALUE.into(),
478-
handle_base: 0x1000,
479-
handle_size: 0x2000,
480-
offset: 0x100,
481-
surrogate_mapping: mapping,
482-
}
483-
}
484-
485-
fn hash_of(val: &impl Hash) -> u64 {
486-
let mut hasher = DefaultHasher::new();
487-
val.hash(&mut hasher);
488-
hasher.finish()
489-
}
490-
491-
#[test]
492-
fn surrogate_mapping_equality() {
493-
assert_eq!(
494-
SurrogateMapping::SandboxMemory,
495-
SurrogateMapping::SandboxMemory
496-
);
497-
assert_eq!(
498-
SurrogateMapping::ReadOnlyFile,
499-
SurrogateMapping::ReadOnlyFile
500-
);
501-
assert_ne!(
502-
SurrogateMapping::SandboxMemory,
503-
SurrogateMapping::ReadOnlyFile
504-
);
505-
}
506-
507-
#[test]
508-
fn surrogate_mapping_variants_are_distinct() {
509-
// Verify the two variants are distinct values that can be
510-
// used to key different behaviour in the surrogate pipeline
511-
let sandbox = SurrogateMapping::SandboxMemory;
512-
let readonly = SurrogateMapping::ReadOnlyFile;
513-
assert_ne!(sandbox, readonly);
514-
515-
// Verify Copy semantics work (enum is Copy + Eq)
516-
let copy = sandbox;
517-
assert_eq!(sandbox, copy);
518-
}
519-
520-
#[test]
521-
fn host_region_base_different_surrogate_mapping_not_equal() {
522-
let sandbox = make_host_region_base(SurrogateMapping::SandboxMemory);
523-
let readonly = make_host_region_base(SurrogateMapping::ReadOnlyFile);
524-
assert_ne!(sandbox, readonly);
525-
}
526-
527-
#[test]
528-
fn host_region_base_different_surrogate_mapping_different_hash() {
529-
let sandbox = make_host_region_base(SurrogateMapping::SandboxMemory);
530-
let readonly = make_host_region_base(SurrogateMapping::ReadOnlyFile);
531-
assert_ne!(hash_of(&sandbox), hash_of(&readonly));
532-
}
533-
534-
#[test]
535-
fn host_region_base_same_surrogate_mapping_equal_and_same_hash() {
536-
let a = make_host_region_base(SurrogateMapping::SandboxMemory);
537-
let b = make_host_region_base(SurrogateMapping::SandboxMemory);
538-
assert_eq!(a, b);
539-
assert_eq!(hash_of(&a), hash_of(&b));
540-
}
541-
542-
#[test]
543-
fn host_region_base_into_usize() {
544-
let hrb = make_host_region_base(SurrogateMapping::SandboxMemory);
545-
let addr: usize = hrb.into();
546-
assert_eq!(addr, 0x1000 + 0x100); // handle_base + offset
547-
}
548-
549-
#[test]
550-
fn memory_region_kind_add_preserves_surrogate_mapping() {
551-
let base = make_host_region_base(SurrogateMapping::ReadOnlyFile);
552-
let result = <HostGuestMemoryRegion as MemoryRegionKind>::add(base, 0x400);
553-
554-
assert_eq!(result.from_handle, base.from_handle);
555-
assert_eq!(result.handle_base, base.handle_base);
556-
assert_eq!(result.handle_size, base.handle_size);
557-
assert_eq!(result.offset, base.offset + 0x400);
558-
assert_eq!(result.surrogate_mapping, SurrogateMapping::ReadOnlyFile);
559-
}
560-
561-
#[test]
562-
fn host_region_base_works_in_hashset() {
563-
let sandbox = make_host_region_base(SurrogateMapping::SandboxMemory);
564-
let readonly = make_host_region_base(SurrogateMapping::ReadOnlyFile);
565-
566-
let mut set = HashSet::new();
567-
set.insert(sandbox);
568-
set.insert(readonly);
569-
// Both should be present since they differ by surrogate_mapping
570-
assert_eq!(set.len(), 2);
571-
572-
// Inserting a duplicate should not increase the count
573-
set.insert(make_host_region_base(SurrogateMapping::SandboxMemory));
574-
assert_eq!(set.len(), 2);
575-
}
576-
577-
#[test]
578-
fn memory_region_with_surrogate_mapping_equality() {
579-
let base_sandbox = make_host_region_base(SurrogateMapping::SandboxMemory);
580-
let base_readonly = make_host_region_base(SurrogateMapping::ReadOnlyFile);
581-
582-
let region_sandbox = MemoryRegion {
583-
guest_region: 0x0..0x2000,
584-
host_region: base_sandbox
585-
..<HostGuestMemoryRegion as MemoryRegionKind>::add(base_sandbox, 0x2000),
586-
flags: MemoryRegionFlags::READ,
587-
region_type: MemoryRegionType::Code,
588-
};
589-
590-
let region_readonly = MemoryRegion {
591-
guest_region: 0x0..0x2000,
592-
host_region: base_readonly
593-
..<HostGuestMemoryRegion as MemoryRegionKind>::add(base_readonly, 0x2000),
594-
flags: MemoryRegionFlags::READ,
595-
region_type: MemoryRegionType::Code,
596-
};
597-
598-
// Regions with different surrogate_mapping should not be equal
599-
assert_ne!(region_sandbox, region_readonly);
600-
}
601-
}
602-
}

src/hyperlight_host/src/mem/shared_mem.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -774,7 +774,6 @@ pub trait SharedMemory {
774774
handle_base: self.region().ptr as usize,
775775
handle_size: self.region().size,
776776
offset: PAGE_SIZE_USIZE,
777-
surrogate_mapping: super::memory_region::SurrogateMapping::SandboxMemory,
778777
}
779778
}
780779

0 commit comments

Comments
 (0)