Skip to content

Commit daa1f7d

Browse files
committed
feat(whp): copy-on-write file mapping for snapshot load
The Windows path in ReadonlySharedMemory::from_file_windows was created with PAGE_READONLY + FILE_MAP_READ. That matches the name ('ReadonlySharedMemory') but not the semantics the caller needs: a sandbox loaded from a snapshot still has to be a writable view of the guest's memory from the host's perspective, so WHP/MSHV can service copy-on-write faults the guest takes on first write. A read-only mapping triggers an access violation on the host thread the moment the guest touches any page, before the VMM can vector the fault into the in-kernel CoW path. Switch to PAGE_WRITECOPY + FILE_MAP_COPY — the Windows equivalent of Linux's mmap(MAP_PRIVATE) that Linux's from_file path already uses. Reads still come from the backing file; writes transparently allocate private copy-on-write pages. Follow-up to hyperlight-dev#1373; depends on that PR landing first. Signed-off-by: danbugs <danilochiarlone@gmail.com>
1 parent b9fba5f commit daa1f7d

1 file changed

Lines changed: 25 additions & 14 deletions

File tree

src/hyperlight_host/src/mem/shared_mem.rs

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2148,43 +2148,54 @@ impl ReadonlySharedMemory {
21482148
})
21492149
}
21502150

2151-
/// Windows implementation of file-backed read-only shared memory.
2151+
/// Windows implementation of file-backed shared memory for snapshots.
21522152
///
21532153
/// The snapshot file layout is:
21542154
/// `[header (PAGE_SIZE)][memory blob][trailing padding (PAGE_SIZE)]`.
2155-
/// We create a read-only file mapping covering the entire file and
2156-
/// map a view of `len + 2*PAGE_SIZE` bytes starting at file offset 0.
2157-
/// The header becomes the leading guard page and the trailing padding
2158-
/// becomes the trailing guard page, both via
2159-
/// `VirtualProtect(PAGE_NOACCESS)`. This gives the standard
2155+
/// We create a copy-on-write file mapping covering the entire file
2156+
/// and map a view of `len + 2*PAGE_SIZE` bytes starting at file
2157+
/// offset 0. The header becomes the leading guard page and the
2158+
/// trailing padding becomes the trailing guard page, both via
2159+
/// `VirtualProtect(PAGE_NOACCESS)`. This gives the standard
21602160
/// `HostMapping` layout: `[guard | usable | guard]`.
2161+
///
2162+
/// The mapping is created with `PAGE_WRITECOPY` and the view with
2163+
/// `FILE_MAP_COPY`, matching Linux's `MAP_PRIVATE` semantics: guest
2164+
/// writes through this mapping allocate private copy-on-write
2165+
/// pages rather than modifying the backing file. A read-only
2166+
/// mapping (`PAGE_READONLY` + `FILE_MAP_READ`) would fail with an
2167+
/// access violation on the first guest write — CoW faults from
2168+
/// both WHP and MSHV require the host-side view to be writable.
21612169
#[cfg(target_os = "windows")]
21622170
fn from_file_windows(file: &std::fs::File, total_size: usize) -> Result<Self> {
21632171
use std::os::windows::io::AsRawHandle;
21642172

21652173
use windows::Win32::Foundation::HANDLE;
21662174
use windows::Win32::System::Memory::{
2167-
CreateFileMappingA, FILE_MAP_READ, MapViewOfFile, PAGE_NOACCESS, PAGE_PROTECTION_FLAGS,
2168-
PAGE_READONLY, VirtualProtect,
2175+
CreateFileMappingA, FILE_MAP_COPY, MapViewOfFile, PAGE_NOACCESS, PAGE_PROTECTION_FLAGS,
2176+
PAGE_WRITECOPY, VirtualProtect,
21692177
};
21702178
use windows::core::PCSTR;
21712179

21722180
let file_handle = HANDLE(file.as_raw_handle());
21732181

2174-
// Create a read-only file mapping at the exact file size (pass 0,0).
2175-
// The file includes trailing PAGE_SIZE padding written by to_file(),
2176-
// so the file is at least offset + len + PAGE_SIZE = total_size bytes.
2182+
// Create a copy-on-write file mapping at the exact file size
2183+
// (pass 0,0). The file includes trailing PAGE_SIZE padding
2184+
// written by to_file(), so it's at least offset + len +
2185+
// PAGE_SIZE = total_size bytes.
21772186
let handle =
2178-
unsafe { CreateFileMappingA(file_handle, None, PAGE_READONLY, 0, 0, PCSTR::null())? };
2187+
unsafe { CreateFileMappingA(file_handle, None, PAGE_WRITECOPY, 0, 0, PCSTR::null())? };
21792188

21802189
if handle.is_invalid() {
21812190
log_then_return!(HyperlightError::MemoryAllocationFailed(
21822191
Error::last_os_error().raw_os_error()
21832192
));
21842193
}
21852194

2186-
// Map exactly total_size (header + blob + trailing padding) bytes.
2187-
let addr = unsafe { MapViewOfFile(handle, FILE_MAP_READ, 0, 0, total_size) };
2195+
// Map exactly total_size (header + blob + trailing padding)
2196+
// bytes with FILE_MAP_COPY — reads come from the file; writes
2197+
// allocate private pages transparently on first fault.
2198+
let addr = unsafe { MapViewOfFile(handle, FILE_MAP_COPY, 0, 0, total_size) };
21882199
if addr.Value.is_null() {
21892200
unsafe {
21902201
let _ = windows::Win32::Foundation::CloseHandle(handle);

0 commit comments

Comments
 (0)