@@ -2012,6 +2012,14 @@ pub struct ReadonlySharedMemory {
20122012 /// by `mapping_at`. If `None`, the full `mem_size()` is mapped.
20132013 #[ cfg_attr( unshared_snapshot_mem, allow( dead_code) ) ]
20142014 guest_mapped_size : Option < usize > ,
2015+ /// Size of the leading guard region (the bytes between
2016+ /// `region.ptr` and the start of the usable memory). For most
2017+ /// constructors this is exactly `PAGE_SIZE_USIZE`. The Windows
2018+ /// `from_file` path can use a larger leading guard when the
2019+ /// snapshot file's `memory_offset` exceeds one page (which
2020+ /// happens whenever the file carries host-function metadata
2021+ /// before the memory blob).
2022+ leading_guard_size : usize ,
20152023}
20162024// Safety: HostMapping is only non-Send/Sync (causing
20172025// ReadonlySharedMemory to not be automatically Send/Sync) because raw
@@ -2033,6 +2041,7 @@ impl ReadonlySharedMemory {
20332041 Ok ( ReadonlySharedMemory {
20342042 region : anon. region ,
20352043 guest_mapped_size : None ,
2044+ leading_guard_size : PAGE_SIZE_USIZE ,
20362045 } )
20372046 }
20382047
@@ -2045,6 +2054,7 @@ impl ReadonlySharedMemory {
20452054 Ok ( ReadonlySharedMemory {
20462055 region : anon. region ,
20472056 guest_mapped_size : Some ( guest_mapped_size) ,
2057+ leading_guard_size : PAGE_SIZE_USIZE ,
20482058 } )
20492059 }
20502060
@@ -2055,6 +2065,223 @@ impl ReadonlySharedMemory {
20552065 self . guest_mapped_size . unwrap_or_else ( || self . mem_size ( ) )
20562066 }
20572067
2068+ /// Create a `ReadonlySharedMemory` backed by a file on disk.
2069+ ///
2070+ /// Only the `len` bytes at `[offset..offset+len)` (the memory
2071+ /// blob) are exposed via `base_ptr()` and `mem_size()`.
2072+ ///
2073+ /// `[offset..offset+len)` is surrounded by guard regions on the
2074+ /// host.
2075+ pub ( crate ) fn from_file (
2076+ file : & std:: fs:: File ,
2077+ offset : usize ,
2078+ len : usize ,
2079+ guest_mapped_size : Option < usize > ,
2080+ ) -> Result < Self > {
2081+ if len == 0 {
2082+ return Err ( new_error ! (
2083+ "Cannot create file-backed shared memory with size 0"
2084+ ) ) ;
2085+ }
2086+
2087+ if offset == 0 || offset % PAGE_SIZE_USIZE != 0 {
2088+ return Err ( new_error ! (
2089+ "snapshot file offset {} must be a non-zero multiple of PAGE_SIZE" ,
2090+ offset
2091+ ) ) ;
2092+ }
2093+
2094+ #[ cfg( target_os = "linux" ) ]
2095+ {
2096+ Self :: from_file_linux ( file, offset, len, guest_mapped_size)
2097+ }
2098+ #[ cfg( target_os = "windows" ) ]
2099+ {
2100+ Self :: from_file_windows ( file, offset, len, guest_mapped_size)
2101+ }
2102+ }
2103+
2104+ #[ cfg( target_os = "linux" ) ]
2105+ fn from_file_linux (
2106+ file : & std:: fs:: File ,
2107+ offset : usize ,
2108+ len : usize ,
2109+ guest_mapped_size : Option < usize > ,
2110+ ) -> Result < Self > {
2111+ use std:: ffi:: c_void;
2112+ use std:: os:: unix:: io:: AsRawFd ;
2113+
2114+ use libc:: {
2115+ MAP_ANONYMOUS , MAP_FAILED , MAP_FIXED , MAP_NORESERVE , MAP_PRIVATE , PROT_NONE , PROT_READ ,
2116+ PROT_WRITE , mmap, off_t, size_t,
2117+ } ;
2118+
2119+ let total_size = len. checked_add ( 2 * PAGE_SIZE_USIZE ) . ok_or_else ( || {
2120+ new_error ! ( "Memory required for file-backed snapshot exceeded usize::MAX" )
2121+ } ) ?;
2122+
2123+ let fd = file. as_raw_fd ( ) ;
2124+ let offset: off_t = offset
2125+ . try_into ( )
2126+ . map_err ( |_| new_error ! ( "snapshot file offset {} exceeds off_t range" , offset) ) ?;
2127+
2128+ // Allocate the full region (guard + usable + guard) as anonymous
2129+ let base = unsafe {
2130+ mmap (
2131+ null_mut ( ) ,
2132+ total_size as size_t ,
2133+ PROT_NONE ,
2134+ MAP_ANONYMOUS | MAP_PRIVATE | MAP_NORESERVE ,
2135+ -1 ,
2136+ 0 as off_t ,
2137+ )
2138+ } ;
2139+ if base == MAP_FAILED {
2140+ return Err ( HyperlightError :: MmapFailed (
2141+ std:: io:: Error :: last_os_error ( ) . raw_os_error ( ) ,
2142+ ) ) ;
2143+ }
2144+
2145+ // Map the file content over the usable portion (between guard pages).
2146+ // PROT_READ | PROT_WRITE: KVM/MSHV require writable host mappings
2147+ // to handle copy-on-write page faults from the guest.
2148+ // MAP_PRIVATE: writes go to private copies, not the file.
2149+ let usable_ptr = unsafe { ( base as * mut u8 ) . add ( PAGE_SIZE_USIZE ) } ;
2150+ let mapped = unsafe {
2151+ mmap (
2152+ usable_ptr as * mut c_void ,
2153+ len as size_t ,
2154+ PROT_READ | PROT_WRITE ,
2155+ MAP_PRIVATE | MAP_FIXED | MAP_NORESERVE ,
2156+ fd,
2157+ offset,
2158+ )
2159+ } ;
2160+ if mapped == MAP_FAILED {
2161+ unsafe { libc:: munmap ( base, total_size as size_t ) } ;
2162+ return Err ( HyperlightError :: MmapFailed (
2163+ std:: io:: Error :: last_os_error ( ) . raw_os_error ( ) ,
2164+ ) ) ;
2165+ }
2166+
2167+ // Guard pages at base and base+total_size-PAGE_SIZE are already
2168+ // PROT_NONE from the anonymous mapping; MAP_FIXED only replaced
2169+ // the middle portion.
2170+
2171+ #[ allow( clippy:: arc_with_non_send_sync) ]
2172+ Ok ( ReadonlySharedMemory {
2173+ region : Arc :: new ( HostMapping {
2174+ ptr : base as * mut u8 ,
2175+ size : total_size,
2176+ } ) ,
2177+ guest_mapped_size,
2178+ leading_guard_size : PAGE_SIZE_USIZE ,
2179+ } )
2180+ }
2181+
2182+ /// Windows file mappings must start at file offset 0 and cannot
2183+ /// extend beyond the file's size, so the view covers
2184+ /// `[0 .. offset + len + PAGE_SIZE)`. The leading `offset` bytes
2185+ /// (header plus any host function metadata) become the leading
2186+ /// guard, recorded in `leading_guard_size`. The trailing
2187+ /// `PAGE_SIZE` bytes (written explicitly by `to_file`) become
2188+ /// the trailing guard. Both ends are protected with
2189+ /// `VirtualProtect(PAGE_NOACCESS)`.
2190+ #[ cfg( target_os = "windows" ) ]
2191+ fn from_file_windows (
2192+ file : & std:: fs:: File ,
2193+ offset : usize ,
2194+ len : usize ,
2195+ guest_mapped_size : Option < usize > ,
2196+ ) -> Result < Self > {
2197+ use std:: os:: windows:: io:: AsRawHandle ;
2198+
2199+ use windows:: Win32 :: Foundation :: HANDLE ;
2200+ use windows:: Win32 :: System :: Memory :: {
2201+ CreateFileMappingA , FILE_MAP_READ , MapViewOfFile , PAGE_NOACCESS , PAGE_PROTECTION_FLAGS ,
2202+ PAGE_READONLY , VirtualProtect ,
2203+ } ;
2204+ use windows:: core:: PCSTR ;
2205+
2206+ let leading_guard_size = offset;
2207+ let total_size = leading_guard_size
2208+ . checked_add ( len)
2209+ . and_then ( |n| n. checked_add ( PAGE_SIZE_USIZE ) )
2210+ . ok_or_else ( || {
2211+ new_error ! ( "Memory required for file-backed snapshot exceeded usize::MAX" )
2212+ } ) ?;
2213+ debug_assert ! ( leading_guard_size >= PAGE_SIZE_USIZE ) ;
2214+ debug_assert ! ( leading_guard_size % PAGE_SIZE_USIZE == 0 ) ;
2215+
2216+ let file_handle = HANDLE ( file. as_raw_handle ( ) ) ;
2217+
2218+ // Create a read-only file mapping at the exact file size (pass 0,0).
2219+ // The file includes trailing PAGE_SIZE padding written by to_file(),
2220+ // so the file is at least leading_guard_size + len + PAGE_SIZE bytes.
2221+ let handle =
2222+ unsafe { CreateFileMappingA ( file_handle, None , PAGE_READONLY , 0 , 0 , PCSTR :: null ( ) ) ? } ;
2223+
2224+ if handle. is_invalid ( ) {
2225+ log_then_return ! ( HyperlightError :: MemoryAllocationFailed (
2226+ Error :: last_os_error( ) . raw_os_error( )
2227+ ) ) ;
2228+ }
2229+
2230+ // Map exactly total_size (leading region + blob + trailing padding) bytes.
2231+ let addr = unsafe { MapViewOfFile ( handle, FILE_MAP_READ , 0 , 0 , total_size) } ;
2232+ if addr. Value . is_null ( ) {
2233+ unsafe {
2234+ let _ = windows:: Win32 :: Foundation :: CloseHandle ( handle) ;
2235+ }
2236+ log_then_return ! ( HyperlightError :: MemoryAllocationFailed (
2237+ Error :: last_os_error( ) . raw_os_error( )
2238+ ) ) ;
2239+ }
2240+
2241+ #[ allow( clippy:: arc_with_non_send_sync) ]
2242+ let region = Arc :: new ( HostMapping {
2243+ ptr : addr. Value as * mut u8 ,
2244+ size : total_size,
2245+ handle,
2246+ } ) ;
2247+
2248+ // Set guard pages on both ends.
2249+ let mut unused_old_prot = PAGE_PROTECTION_FLAGS ( 0 ) ;
2250+
2251+ // Leading guard: covers the fixed header and any host-function
2252+ // metadata
2253+ let first_guard = addr. Value ;
2254+ if let Err ( e) = unsafe {
2255+ VirtualProtect (
2256+ first_guard,
2257+ leading_guard_size,
2258+ PAGE_NOACCESS ,
2259+ & mut unused_old_prot,
2260+ )
2261+ } {
2262+ log_then_return ! ( WindowsAPIError ( e. clone( ) ) ) ;
2263+ }
2264+
2265+ // Trailing guard: the explicit PAGE_SIZE padding at the end of the file.
2266+ let last_guard = unsafe { first_guard. add ( total_size - PAGE_SIZE_USIZE ) } ;
2267+ if let Err ( e) = unsafe {
2268+ VirtualProtect (
2269+ last_guard,
2270+ PAGE_SIZE_USIZE ,
2271+ PAGE_NOACCESS ,
2272+ & mut unused_old_prot,
2273+ )
2274+ } {
2275+ log_then_return ! ( WindowsAPIError ( e. clone( ) ) ) ;
2276+ }
2277+
2278+ Ok ( ReadonlySharedMemory {
2279+ region,
2280+ guest_mapped_size,
2281+ leading_guard_size,
2282+ } )
2283+ }
2284+
20582285 pub ( crate ) fn as_slice ( & self ) -> & [ u8 ] {
20592286 unsafe { std:: slice:: from_raw_parts ( self . base_ptr ( ) , self . mem_size ( ) ) }
20602287 }
@@ -2098,6 +2325,32 @@ impl SharedMemory for ReadonlySharedMemory {
20982325 fn region ( & self ) -> & HostMapping {
20992326 & self . region
21002327 }
2328+ // Override the default trait accessors to use the variable-sized
2329+ // leading guard. The trailing guard is always `PAGE_SIZE_USIZE`.
2330+ fn base_addr ( & self ) -> usize {
2331+ self . region ( ) . ptr as usize + self . leading_guard_size
2332+ }
2333+ fn base_ptr ( & self ) -> * mut u8 {
2334+ self . region ( ) . ptr . wrapping_add ( self . leading_guard_size )
2335+ }
2336+ fn mem_size ( & self ) -> usize {
2337+ self . region ( ) . size - self . leading_guard_size - PAGE_SIZE_USIZE
2338+ }
2339+ fn host_region_base ( & self ) -> <HostGuestMemoryRegion as MemoryRegionKind >:: HostBaseType {
2340+ #[ cfg( not( windows) ) ]
2341+ {
2342+ self . base_addr ( )
2343+ }
2344+ #[ cfg( windows) ]
2345+ {
2346+ super :: memory_region:: HostRegionBase {
2347+ from_handle : self . region ( ) . handle . into ( ) ,
2348+ handle_base : self . region ( ) . ptr as usize ,
2349+ handle_size : self . region ( ) . size ,
2350+ offset : self . leading_guard_size ,
2351+ }
2352+ }
2353+ }
21012354 // There's no way to get exclusive (and therefore writable) access
21022355 // to a ReadonlySharedMemory.
21032356 fn with_exclusivity < T , F : FnOnce ( & mut ExclusiveSharedMemory ) -> T > (
0 commit comments