@@ -13,7 +13,7 @@ use ic_interfaces::execution_environment::{HypervisorError, HypervisorResult};
1313use ic_replicated_state:: canister_state:: execution_state:: WasmMetadata ;
1414use ic_types:: { DiskBytes , NumInstructions , methods:: WasmMethod } ;
1515use ic_wasm_types:: WasmEngineError ;
16- use nix:: sys:: mman:: { MapFlags , ProtFlags , mmap} ;
16+ use nix:: sys:: mman:: { MapFlags , ProtFlags , mmap, munmap } ;
1717use serde:: { Deserialize , Serialize } ;
1818use wasmtime:: Module ;
1919
@@ -259,8 +259,15 @@ impl OnDiskSerializedModule {
259259 } ) as * mut u8 ;
260260 // Safety: allocation was made with length `mmap_size`.
261261 let data = unsafe { std:: slice:: from_raw_parts ( mmap_ptr, mmap_size) } ;
262- bincode:: deserialize :: < InitialStateData > ( data)
263- . expect ( "Error parsing initial state data file" )
262+ let initial_state_data = bincode:: deserialize :: < InitialStateData > ( data)
263+ . expect ( "Error parsing initial state data file" ) ;
264+ // Safety: `mmap_ptr`/`mmap_size` are the pointer and length returned by
265+ // the `mmap` above; `data` is not used past this point.
266+ unsafe {
267+ munmap ( mmap_ptr as * mut std:: ffi:: c_void , mmap_size)
268+ . expect ( "Unable to unmap initial state data file" ) ;
269+ }
270+ initial_state_data
264271 }
265272}
266273
@@ -427,4 +434,67 @@ mod test {
427434 } ) ;
428435 }
429436 }
437+
438+ #[ cfg( target_os = "linux" ) ]
439+ #[ test]
440+ fn initial_state_data_does_not_leak_mappings ( ) {
441+ let module = SerializedModule {
442+ bytes : Arc :: new ( SerializedModuleBytes ( vec ! [ 0_u8 ; 4096 ] ) ) ,
443+ exported_functions : BTreeSet :: new ( ) ,
444+ data_segments : vec ! [ ( 0_usize , vec![ 7_u8 ; 4096 * 8 ] ) ] . into_iter ( ) . collect ( ) ,
445+ wasm_metadata : WasmMetadata :: new ( std:: collections:: BTreeMap :: new ( ) ) ,
446+ compilation_cost : NumInstructions :: from ( 0 ) ,
447+ imports_details : WasmImportsDetails {
448+ imports_call_cycles_add : false ,
449+ imports_canister_cycle_balance : false ,
450+ imports_msg_cycles_available : false ,
451+ imports_msg_cycles_refunded : false ,
452+ imports_msg_cycles_accept : false ,
453+ imports_mint_cycles : false ,
454+ } ,
455+ is_wasm64 : false ,
456+ } ;
457+
458+ let dir = tempfile:: tempdir ( ) . unwrap ( ) ;
459+ let mut bytes_path: PathBuf = dir. path ( ) . into ( ) ;
460+ let mut data_path: PathBuf = dir. path ( ) . into ( ) ;
461+ bytes_path. push ( "bytes" ) ;
462+ data_path. push ( "data" ) ;
463+ let on_disk =
464+ OnDiskSerializedModule :: from_serialized_module ( module, & bytes_path, & data_path) ;
465+
466+ let map_size = {
467+ let len = on_disk. initial_state_data . metadata ( ) . unwrap ( ) . len ( ) as usize ;
468+ len. div_ceil ( 4096 ) * 4096
469+ } ;
470+ let count_leaked_maps = || {
471+ std:: fs:: read_to_string ( "/proc/self/maps" )
472+ . unwrap ( )
473+ . lines ( )
474+ . filter ( |line| {
475+ line. split_once ( ' ' )
476+ . and_then ( |( range, _) | range. split_once ( '-' ) )
477+ . and_then ( |( s, e) | {
478+ Some (
479+ usize:: from_str_radix ( e, 16 ) . ok ( ) ?
480+ - usize:: from_str_radix ( s, 16 ) . ok ( ) ?,
481+ )
482+ } )
483+ == Some ( map_size)
484+ } )
485+ . count ( )
486+ } ;
487+
488+ let before = count_leaked_maps ( ) ;
489+ const ITERS : usize = 500 ;
490+ for _ in 0 ..ITERS {
491+ let _ = on_disk. initial_state_data ( ) ;
492+ }
493+ let after = count_leaked_maps ( ) ;
494+
495+ assert ! (
496+ after <= before + 2 ,
497+ "initial_state_data() leaked mappings: {before} -> {after} over {ITERS} calls"
498+ ) ;
499+ }
430500}
0 commit comments