Skip to content

Commit dc6a698

Browse files
committed
fix: address code review findings
H1: Add null check after MapViewOfFileNuma2 in SurrogateProcess::map H2: Store SurrogateMapping in HandleMapping, add debug_assert_eq on reuse M1: Clean up surrogate mapping on VirtualProtectEx failure (pre-existing) M2: Use checked_sub for ref count, log error on underflow M5: Early return with clear error for empty (0-byte) files L1: Fix incorrect Send/Sync safety comment on OwnedFileMapping L3: Rename _fp/_guest_base to file_path/guest_base L4: Use usize::try_from(file_size) instead of silent truncation N2: Use page_size::get() in test helper instead of magic 4096 N3: Change SurrogateMapping and surrogate_mapping field to pub(crate) N4: Replace low-value derive trait test with meaningful variant test Also: Change MemoryRegionType::Heap to Code for file mappings, add tracing::error in release-mode vacant unmap path. All 266 tests pass, 0 failures. Signed-off-by: Simon Davies <simongdavies@users.noreply.github.com>
1 parent ebe9ee0 commit dc6a698

3 files changed

Lines changed: 74 additions & 26 deletions

File tree

src/hyperlight_host/src/hypervisor/surrogate_process.rs

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ use crate::{Result, log_then_return};
3737
pub(crate) struct HandleMapping {
3838
pub(crate) use_count: u64,
3939
pub(crate) surrogate_base: *mut c_void,
40+
/// The mapping type used when this entry was first created.
41+
/// Used for debug assertions to catch conflicting re-maps.
42+
pub(crate) mapping_type: SurrogateMapping,
4043
}
4144

4245
/// Contains details of a surrogate process to be used by a Sandbox for providing memory to a HyperV VM on Windows.
@@ -79,6 +82,14 @@ impl SurrogateProcess {
7982
) -> Result<*mut c_void> {
8083
match self.mappings.entry(host_base) {
8184
Entry::Occupied(mut oe) => {
85+
debug_assert_eq!(
86+
oe.get().mapping_type,
87+
*mapping,
88+
"Conflicting SurrogateMapping for host_base {host_base:#x}: \
89+
existing={:?}, requested={:?}",
90+
oe.get().mapping_type,
91+
mapping
92+
);
8293
oe.get_mut().use_count += 1;
8394
Ok(oe.get().surrogate_base)
8495
}
@@ -105,6 +116,13 @@ impl SurrogateProcess {
105116
)
106117
};
107118

119+
if surrogate_base.Value.is_null() {
120+
log_then_return!(
121+
"MapViewOfFileNuma2 failed: {:?}",
122+
std::io::Error::last_os_error()
123+
);
124+
}
125+
108126
// Only set guard pages for SandboxMemory mappings.
109127
// File-backed read-only mappings do not need guard pages
110128
// because the host does not write to them.
@@ -122,6 +140,7 @@ impl SurrogateProcess {
122140
&mut unused_out_old_prot_flags,
123141
)
124142
} {
143+
self.unmap_helper(surrogate_base.Value);
125144
log_then_return!(WindowsAPIError(e.clone()));
126145
}
127146

@@ -137,13 +156,15 @@ impl SurrogateProcess {
137156
&mut unused_out_old_prot_flags,
138157
)
139158
} {
159+
self.unmap_helper(surrogate_base.Value);
140160
log_then_return!(WindowsAPIError(e.clone()));
141161
}
142162
}
143163

144164
ve.insert(HandleMapping {
145165
use_count: 1,
146166
surrogate_base: surrogate_base.Value,
167+
mapping_type: *mapping,
147168
});
148169
Ok(surrogate_base.Value)
149170
}
@@ -153,15 +174,25 @@ impl SurrogateProcess {
153174
pub(super) fn unmap(&mut self, host_base: usize) {
154175
match self.mappings.entry(host_base) {
155176
Entry::Occupied(mut oe) => {
156-
oe.get_mut().use_count -= 1;
177+
oe.get_mut().use_count = oe.get().use_count.checked_sub(1).unwrap_or_else(|| {
178+
tracing::error!(
179+
"Surrogate unmap ref count underflow for host_base {:#x}",
180+
host_base
181+
);
182+
0
183+
});
157184
if oe.get().use_count == 0 {
158185
let entry = oe.remove();
159186
self.unmap_helper(entry.surrogate_base);
160187
}
161188
}
162189
Entry::Vacant(_) => {
190+
tracing::error!(
191+
"Attempted to unmap from surrogate a region at host_base {:#x} that was never mapped",
192+
host_base
193+
);
163194
#[cfg(debug_assertions)]
164-
panic!("Attempted to unmap from surrogate a region that was never mapped")
195+
panic!("Attempted to unmap from surrogate a region that was never mapped");
165196
}
166197
}
167198
}

src/hyperlight_host/src/mem/memory_region.rs

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ impl MemoryRegionKind for HostGuestMemoryRegion {
169169
/// behaviour when projected into the surrogate process via `MapViewOfFileNuma2`.
170170
#[cfg(target_os = "windows")]
171171
#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
172-
pub enum SurrogateMapping {
172+
pub(crate) enum SurrogateMapping {
173173
/// Standard sandbox shared memory: mapped with `PAGE_READWRITE` protection
174174
/// and guard pages (`PAGE_NOACCESS`) set on the first and last pages.
175175
SandboxMemory,
@@ -196,7 +196,7 @@ pub struct HostRegionBase {
196196
pub offset: usize,
197197
/// How this region should be mapped through the surrogate process.
198198
/// Controls page protection and guard page behaviour.
199-
pub surrogate_mapping: SurrogateMapping,
199+
pub(crate) surrogate_mapping: SurrogateMapping,
200200
}
201201
#[cfg(target_os = "windows")]
202202
impl std::hash::Hash for HostRegionBase {
@@ -497,14 +497,16 @@ mod tests {
497497
}
498498

499499
#[test]
500-
fn surrogate_mapping_copy_clone_debug() {
501-
let a = SurrogateMapping::ReadOnlyFile;
502-
let b = a; // Copy
503-
let c = a; // Also Copy (SurrogateMapping implements Copy)
504-
assert_eq!(a, b);
505-
assert_eq!(a, c);
506-
// Debug should produce a non-empty string
507-
assert!(!format!("{:?}", a).is_empty());
500+
fn surrogate_mapping_variants_are_distinct() {
501+
// Verify the two variants are distinct values that can be
502+
// used to key different behaviour in the surrogate pipeline
503+
let sandbox = SurrogateMapping::SandboxMemory;
504+
let readonly = SurrogateMapping::ReadOnlyFile;
505+
assert_ne!(sandbox, readonly);
506+
507+
// Verify Copy semantics work (enum is Copy + Eq)
508+
let copy = sandbox;
509+
assert_eq!(sandbox, copy);
508510
}
509511

510512
#[test]

src/hyperlight_host/src/sandbox/initialized_multi_use.rs

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,10 @@ impl Drop for OwnedFileMapping {
9595
}
9696
}
9797

98-
// SAFETY: The raw pointer `view_base` is only accessed during `Drop`
99-
// (to call `UnmapViewOfFile`), which happens on the owning thread.
100-
// The `HandleWrapper` is already `Send + Sync`.
98+
// SAFETY: `view_base` is a pointer to a Windows memory-mapped view created by
99+
// `MapViewOfFile`. Both `UnmapViewOfFile` and `CloseHandle` are thread-safe
100+
// Win32 APIs that operate on kernel objects, so they can safely be called
101+
// from any thread. The `HandleWrapper` is already `Send + Sync`.
101102
#[cfg(target_os = "windows")]
102103
unsafe impl Send for OwnedFileMapping {}
103104
#[cfg(target_os = "windows")]
@@ -621,8 +622,8 @@ impl MultiUseSandbox {
621622
///
622623
/// This method will return [`crate::HyperlightError::PoisonedSandbox`] if the sandbox
623624
/// is currently poisoned. Use [`restore()`](Self::restore) to recover from a poisoned state.
624-
#[instrument(err(Debug), skip(self, _fp, _guest_base), parent = Span::current())]
625-
pub fn map_file_cow(&mut self, _fp: &Path, _guest_base: u64) -> Result<u64> {
625+
#[instrument(err(Debug), skip(self, file_path, guest_base), parent = Span::current())]
626+
pub fn map_file_cow(&mut self, file_path: &Path, guest_base: u64) -> Result<u64> {
626627
if self.poisoned {
627628
return Err(crate::HyperlightError::PoisonedSandbox);
628629
}
@@ -632,10 +633,18 @@ impl MultiUseSandbox {
632633

633634
use windows::Win32::Foundation::HANDLE;
634635

635-
let file = std::fs::File::options().read(true).open(_fp)?;
636+
let file = std::fs::File::options().read(true).open(file_path)?;
636637
let file_size = file.metadata()?.len();
638+
if file_size == 0 {
639+
log_then_return!("map_file_cow: cannot map an empty file: {:?}", file_path);
640+
}
637641
let page_size = page_size::get();
638-
let size = (file_size as usize).div_ceil(page_size) * page_size;
642+
let size = usize::try_from(file_size).map_err(|_| {
643+
HyperlightError::Error(format!(
644+
"File size {file_size} exceeds addressable range on this platform"
645+
))
646+
})?;
647+
let size = size.div_ceil(page_size) * page_size;
639648

640649
let file_handle = HANDLE(file.as_raw_handle());
641650

@@ -680,9 +689,9 @@ impl MultiUseSandbox {
680689

681690
let region = MemoryRegion {
682691
host_region: host_base..host_end,
683-
guest_region: _guest_base as usize.._guest_base as usize + size,
692+
guest_region: guest_base as usize..guest_base as usize + size,
684693
flags: MemoryRegionFlags::READ | MemoryRegionFlags::EXECUTE,
685-
region_type: MemoryRegionType::Heap,
694+
region_type: MemoryRegionType::Code,
686695
};
687696

688697
// Reset snapshot since we are mutating the sandbox state
@@ -702,7 +711,7 @@ impl MultiUseSandbox {
702711

703712
self.mem_mgr.mapped_rgns += 1;
704713
self.file_mappings.push(OwnedFileMapping {
705-
guest_base: _guest_base as usize,
714+
guest_base: guest_base as usize,
706715
view_base: view.Value,
707716
mapping_handle: HandleWrapper::from(mapping_handle),
708717
});
@@ -711,8 +720,14 @@ impl MultiUseSandbox {
711720
}
712721
#[cfg(unix)]
713722
unsafe {
714-
let file = std::fs::File::options().read(true).write(true).open(_fp)?;
723+
let file = std::fs::File::options()
724+
.read(true)
725+
.write(true)
726+
.open(file_path)?;
715727
let file_size = file.metadata()?.st_size();
728+
if file_size == 0 {
729+
log_then_return!("map_file_cow: cannot map an empty file: {:?}", file_path);
730+
}
716731
let page_size = page_size::get();
717732
let size = (file_size as usize).div_ceil(page_size) * page_size;
718733
let base = libc::mmap(
@@ -729,9 +744,9 @@ impl MultiUseSandbox {
729744

730745
if let Err(err) = self.map_region(&MemoryRegion {
731746
host_region: base as usize..base.wrapping_add(size) as usize,
732-
guest_region: _guest_base as usize.._guest_base as usize + size,
747+
guest_region: guest_base as usize..guest_base as usize + size,
733748
flags: MemoryRegionFlags::READ | MemoryRegionFlags::EXECUTE,
734-
region_type: MemoryRegionType::Heap,
749+
region_type: MemoryRegionType::Code,
735750
}) {
736751
libc::munmap(base, size);
737752
return Err(err);
@@ -1767,7 +1782,7 @@ mod tests {
17671782
fn create_test_file(name: &str, content: &[u8]) -> (std::path::PathBuf, Vec<u8>) {
17681783
use std::io::Write;
17691784

1770-
let page_size = 4096usize;
1785+
let page_size = page_size::get();
17711786
let padded_len = content.len().max(page_size).div_ceil(page_size) * page_size;
17721787
let mut padded = vec![0u8; padded_len];
17731788
padded[..content.len()].copy_from_slice(content);

0 commit comments

Comments
 (0)