diff --git a/src/hyperlight_host/examples/map-file-cow-test/main.rs b/src/hyperlight_host/examples/map-file-cow-test/main.rs new file mode 100644 index 000000000..446420e0d --- /dev/null +++ b/src/hyperlight_host/examples/map-file-cow-test/main.rs @@ -0,0 +1,62 @@ +/* +Copyright 2025 The Hyperlight Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Test that map_file_cow works end-to-end: UninitializedSandbox::new → +// map_file_cow → evolve → guest function call. Exercises the cross-process +// section mapping via MapViewOfFileNuma2 on Windows (the surrogate process +// must be able to map the file-backed section). +// +// Before the NULL DACL fix, this fails on Windows with: +// HyperlightVmError(MapRegion(MapMemory(SurrogateProcess( +// "MapViewOfFileNuma2 failed: ... Access is denied.")))) +// +// Run: +// cargo run --release --example map-file-cow-test + +#![allow(clippy::disallowed_macros)] +use std::path::Path; + +use hyperlight_host::sandbox::SandboxConfiguration; +use hyperlight_host::{MultiUseSandbox, UninitializedSandbox}; + +fn main() -> hyperlight_host::Result<()> { + let mut config = SandboxConfiguration::default(); + config.set_heap_size(4 * 1024 * 1024); + config.set_scratch_size(64 * 1024 * 1024); + + // Create a test file to map (simulating an initrd). + let test_file = std::env::temp_dir().join("hl_map_file_cow_test.bin"); + std::fs::write(&test_file, vec![0xABu8; 8192]).unwrap(); + + let mut usbox = UninitializedSandbox::new( + hyperlight_host::GuestBinary::FilePath( + hyperlight_testing::simple_guest_as_string().unwrap(), + ), + Some(config), + )?; + eprintln!("[test] UninitializedSandbox::new OK"); + + usbox.map_file_cow(Path::new(&test_file), 0xC000_0000, Some("test"))?; + eprintln!("[test] map_file_cow OK"); + + let mut mu: MultiUseSandbox = usbox.evolve()?; + eprintln!("[test] evolve OK"); + + let result: String = mu.call("Echo", "map_file_cow works!".to_string())?; + eprintln!("[test] guest returned: {result}"); + + let _ = std::fs::remove_file(&test_file); + Ok(()) +} diff --git a/src/hyperlight_host/src/sandbox/file_mapping.rs b/src/hyperlight_host/src/sandbox/file_mapping.rs index 1ebe1f262..0139fcdba 100644 --- a/src/hyperlight_host/src/sandbox/file_mapping.rs +++ b/src/hyperlight_host/src/sandbox/file_mapping.rs @@ -295,9 +295,13 @@ pub(crate) fn prepare_file_cow( use std::os::windows::io::AsRawHandle; use windows::Win32::Foundation::HANDLE; + use windows::Win32::Security::{ + PSECURITY_DESCRIPTOR, SECURITY_ATTRIBUTES, SECURITY_DESCRIPTOR, + }; use windows::Win32::System::Memory::{ CreateFileMappingW, FILE_MAP_READ, MapViewOfFile, PAGE_READONLY, }; + use windows::Win32::System::SystemServices::SECURITY_DESCRIPTOR_REVISION1; let file = std::fs::File::options().read(true).open(file_path)?; let file_size = file.metadata()?.len(); @@ -313,12 +317,39 @@ pub(crate) fn prepare_file_cow( let file_handle = HANDLE(file.as_raw_handle()); + // Build a security descriptor with a NULL DACL (unrestricted + // access) so the surrogate process can map the section via + // MapViewOfFileNuma2. File-backed sections created with the + // default DACL fail with ERROR_ACCESS_DENIED when mapped + // cross-process on modern Windows. + // https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/Security/struct.SECURITY_DESCRIPTOR.html + // https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/System/SystemServices/constant.SECURITY_DESCRIPTOR_REVISION1.html + let mut sd = SECURITY_DESCRIPTOR::default(); + let psd = PSECURITY_DESCRIPTOR(std::ptr::addr_of_mut!(sd).cast()); + unsafe { + windows::Win32::Security::InitializeSecurityDescriptor( + psd, + SECURITY_DESCRIPTOR_REVISION1, + ) + .map_err(|e| { + HyperlightError::Error(format!("InitializeSecurityDescriptor failed: {e}")) + })?; + windows::Win32::Security::SetSecurityDescriptorDacl(psd, true, None, false).map_err( + |e| HyperlightError::Error(format!("SetSecurityDescriptorDacl failed: {e}")), + )?; + } + let sa = SECURITY_ATTRIBUTES { + nLength: std::mem::size_of::() as u32, + lpSecurityDescriptor: psd.0, + bInheritHandle: false.into(), + }; + // Create a read-only file mapping object backed by the actual file. // Pass 0,0 for size to use the file's actual size — Windows will // NOT extend a read-only file, so requesting page-aligned size // would fail for files smaller than one page. let mapping_handle = - unsafe { CreateFileMappingW(file_handle, None, PAGE_READONLY, 0, 0, None) } + unsafe { CreateFileMappingW(file_handle, Some(&sa), PAGE_READONLY, 0, 0, None) } .map_err(|e| HyperlightError::Error(format!("CreateFileMappingW failed: {e}")))?; // Map a read-only view into the host process.