Skip to content

Commit 2fca7ae

Browse files
authored
fix(whp): map file-backed sections to end-of-section in surrogate (hyperlight-dev#1388)
Windows CreateFileMappingW with a read-only file handle caps the section size at the file's actual size on disk (requesting max_size > file_size fails with ERROR_NOT_ENOUGH_MEMORY). For files whose size is not a multiple of the page size, the surrogate's MapViewOfFileNuma2 call was passing a page-aligned host_size that exceeded the section, failing with ERROR_ACCESS_DENIED — the map_file_cow zero-copy path was broken for any non-page-aligned file. Pass NumberOfBytesToMap=0 for ReadOnlyFile mappings so Windows maps the section to its end. MapViewOfFile always returns a view whose region is rounded up to a page boundary, and the OS zero-fills the tail of the final page — matching POSIX mmap semantics. SandboxMemory sections are always created with an explicit page-aligned size and still need the exact host_size so the guard-page bookkeeping below lines up, so keep host_size for that case. The map-file-cow-test example now covers an intentionally 8193-byte file to exercise the unaligned path. Signed-off-by: danbugs <danilochiarlone@gmail.com>
1 parent 2b803de commit 2fca7ae

2 files changed

Lines changed: 48 additions & 14 deletions

File tree

src/hyperlight_host/examples/map-file-cow-test/main.rs

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,12 @@ limitations under the License.
1818
// section mapping via MapViewOfFileNuma2 on Windows (the surrogate process
1919
// must be able to map the file-backed section).
2020
//
21-
// Before the NULL DACL fix, this fails on Windows with:
21+
// Covers both a page-aligned file and an intentionally unaligned file.
22+
// Before fix: the unaligned case fails on Windows with
2223
// HyperlightVmError(MapRegion(MapMemory(SurrogateProcess(
2324
// "MapViewOfFileNuma2 failed: ... Access is denied."))))
25+
// because the file-backed section has max_size == file_size (< the
26+
// page-aligned host_size the surrogate requests).
2427
//
2528
// Run:
2629
// cargo run --release --example map-file-cow-test
@@ -31,32 +34,48 @@ use std::path::Path;
3134
use hyperlight_host::sandbox::SandboxConfiguration;
3235
use hyperlight_host::{MultiUseSandbox, UninitializedSandbox};
3336

34-
fn main() -> hyperlight_host::Result<()> {
37+
fn run_once(test_file: &Path, label: &str) -> hyperlight_host::Result<()> {
3538
let mut config = SandboxConfiguration::default();
3639
config.set_heap_size(4 * 1024 * 1024);
3740
config.set_scratch_size(64 * 1024 * 1024);
3841

39-
// Create a test file to map (simulating an initrd).
40-
let test_file = std::env::temp_dir().join("hl_map_file_cow_test.bin");
41-
std::fs::write(&test_file, vec![0xABu8; 8192]).unwrap();
42-
4342
let mut usbox = UninitializedSandbox::new(
4443
hyperlight_host::GuestBinary::FilePath(
4544
hyperlight_testing::simple_guest_as_string().unwrap(),
4645
),
4746
Some(config),
4847
)?;
49-
eprintln!("[test] UninitializedSandbox::new OK");
48+
eprintln!("[{label}] UninitializedSandbox::new OK");
5049

51-
usbox.map_file_cow(Path::new(&test_file), 0xC000_0000, Some("test"))?;
52-
eprintln!("[test] map_file_cow OK");
50+
usbox.map_file_cow(test_file, 0xC000_0000, Some(label))?;
51+
eprintln!(
52+
"[{label}] map_file_cow OK ({} bytes)",
53+
std::fs::metadata(test_file)?.len()
54+
);
5355

5456
let mut mu: MultiUseSandbox = usbox.evolve()?;
55-
eprintln!("[test] evolve OK");
57+
eprintln!("[{label}] evolve OK");
58+
59+
let result: String = mu.call("Echo", format!("{label}: map_file_cow works!"))?;
60+
eprintln!("[{label}] guest returned: {result}");
61+
Ok(())
62+
}
63+
64+
fn main() -> hyperlight_host::Result<()> {
65+
let aligned = std::env::temp_dir().join("hl_map_file_cow_aligned.bin");
66+
let unaligned = std::env::temp_dir().join("hl_map_file_cow_unaligned.bin");
67+
68+
// 2 full pages.
69+
std::fs::write(&aligned, vec![0xABu8; 8192]).unwrap();
70+
// Deliberately unaligned: not a multiple of 4 KiB. Must succeed
71+
// (Windows: requires the surrogate to map "to end of section" rather
72+
// than the caller's page-aligned host_size).
73+
std::fs::write(&unaligned, vec![0xCDu8; 8193]).unwrap();
5674

57-
let result: String = mu.call("Echo", "map_file_cow works!".to_string())?;
58-
eprintln!("[test] guest returned: {result}");
75+
run_once(&aligned, "aligned")?;
76+
run_once(&unaligned, "unaligned")?;
5977

60-
let _ = std::fs::remove_file(&test_file);
78+
let _ = std::fs::remove_file(&aligned);
79+
let _ = std::fs::remove_file(&unaligned);
6180
Ok(())
6281
}

src/hyperlight_host/src/hypervisor/surrogate_process.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,13 +103,28 @@ impl SurrogateProcess {
103103
// Use MapViewOfFile2 to map memory into the surrogate process, the MapViewOfFile2 API is implemented in as an inline function in a windows header file
104104
// (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
105105
// MapViewOfFile2 which does not exist in the rust crate (see https://github.com/microsoft/windows-rs/issues/2595)
106+
//
107+
// For ReadOnlyFile mappings backed by a file-sized section
108+
// (max_size == file_size, potentially not page-aligned),
109+
// passing the caller's page-aligned `host_size` would exceed
110+
// the section size and fail with ERROR_ACCESS_DENIED. Pass 0
111+
// instead to request "map to end of section": Windows returns
112+
// a page-aligned view and zero-fills the tail of the final
113+
// page, matching POSIX mmap semantics. For SandboxMemory
114+
// sections the section size is already page-aligned and set
115+
// by the caller, so pass host_size explicitly (guard-page
116+
// bookkeeping below depends on knowing the exact extent).
117+
let bytes_to_map = match mapping {
118+
SurrogateMapping::SandboxMemory => host_size,
119+
SurrogateMapping::ReadOnlyFile => 0,
120+
};
106121
let surrogate_base = unsafe {
107122
MapViewOfFileNuma2(
108123
handle.into(),
109124
self.process_handle.into(),
110125
0,
111126
None,
112-
host_size,
127+
bytes_to_map,
113128
0,
114129
page_protection.0,
115130
NUMA_NO_PREFERRED_NODE,

0 commit comments

Comments
 (0)