@@ -48,6 +48,8 @@ pub enum MemoryError {
4848 MemfdSetLen ( std:: io:: Error ) ,
4949 /// Total sum of memory regions exceeds largest possible file offset
5050 OffsetTooLarge ,
51+ /// Cannot retrieve snapshot file metadata: {0}
52+ FileMetadata ( std:: io:: Error ) ,
5153}
5254
5355/// Creates a `Vec` of `GuestRegionMmap` with the given configuration
@@ -129,7 +131,22 @@ pub fn snapshot_file(
129131 regions : impl Iterator < Item = ( GuestAddress , usize ) > ,
130132 track_dirty_pages : bool ,
131133) -> Result < Vec < GuestRegionMmap > , MemoryError > {
132- create ( regions, libc:: MAP_PRIVATE , Some ( file) , track_dirty_pages)
134+ let regions: Vec < _ > = regions. collect ( ) ;
135+ let memory_size: u64 = regions. iter ( ) . map ( |( _, size) | * size as u64 ) . sum ( ) ;
136+ let file_size = file. metadata ( ) . map_err ( MemoryError :: FileMetadata ) ?. len ( ) ;
137+
138+ // ensure we do not mmap beyond EOF. The kernel would allow that but a SIGBUS is triggered
139+ // on an attempted access to a page of the buffer that lies beyond the end of the mapped file.
140+ if memory_size > file_size {
141+ return Err ( MemoryError :: OffsetTooLarge ) ;
142+ }
143+
144+ create (
145+ regions. into_iter ( ) ,
146+ libc:: MAP_PRIVATE ,
147+ Some ( file) ,
148+ track_dirty_pages,
149+ )
133150}
134151
135152/// Defines the interface for snapshotting memory.
@@ -343,7 +360,7 @@ mod tests {
343360 #![ allow( clippy:: undocumented_unsafe_blocks) ]
344361
345362 use std:: collections:: HashMap ;
346- use std:: io:: { Read , Seek } ;
363+ use std:: io:: { Read , Seek , Write } ;
347364
348365 use vmm_sys_util:: tempfile:: TempFile ;
349366
@@ -374,6 +391,53 @@ mod tests {
374391 }
375392 }
376393
394+ #[ test]
395+ fn test_snapshot_file_success ( ) {
396+ for dirty_page_tracking in [ true , false ] {
397+ let page_size = 0x1000 ;
398+ let mut file = TempFile :: new ( ) . unwrap ( ) . into_file ( ) ;
399+ file. set_len ( page_size as u64 ) . unwrap ( ) ;
400+ file. write_all ( & vec ! [ 0x42u8 ; page_size] ) . unwrap ( ) ;
401+
402+ let regions = vec ! [ ( GuestAddress ( 0 ) , page_size) ] ;
403+ let guest_regions =
404+ snapshot_file ( file, regions. into_iter ( ) , dirty_page_tracking) . unwrap ( ) ;
405+ assert_eq ! ( guest_regions. len( ) , 1 ) ;
406+ guest_regions. iter ( ) . for_each ( |region| {
407+ assert_eq ! ( region. bitmap( ) . is_some( ) , dirty_page_tracking) ;
408+ } ) ;
409+ }
410+ }
411+
412+ #[ test]
413+ fn test_snapshot_file_multiple_regions ( ) {
414+ let page_size = 0x1000 ;
415+ let total_size = 3 * page_size;
416+ let mut file = TempFile :: new ( ) . unwrap ( ) . into_file ( ) ;
417+ file. set_len ( total_size as u64 ) . unwrap ( ) ;
418+ file. write_all ( & vec ! [ 0x42u8 ; total_size] ) . unwrap ( ) ;
419+
420+ let regions = vec ! [
421+ ( GuestAddress ( 0 ) , page_size) ,
422+ ( GuestAddress ( 0x10000 ) , page_size) ,
423+ ( GuestAddress ( 0x20000 ) , page_size) ,
424+ ] ;
425+ let guest_regions = snapshot_file ( file, regions. into_iter ( ) , false ) . unwrap ( ) ;
426+ assert_eq ! ( guest_regions. len( ) , 3 ) ;
427+ }
428+
429+ #[ test]
430+ fn test_snapshot_file_offset_too_large ( ) {
431+ let page_size = 0x1000 ;
432+ let mut file = TempFile :: new ( ) . unwrap ( ) . into_file ( ) ;
433+ file. set_len ( page_size as u64 ) . unwrap ( ) ;
434+ file. write_all ( & vec ! [ 0x42u8 ; page_size] ) . unwrap ( ) ;
435+
436+ let regions = vec ! [ ( GuestAddress ( 0 ) , 2 * page_size) ] ;
437+ let result = snapshot_file ( file, regions. into_iter ( ) , false ) ;
438+ assert ! ( matches!( result. unwrap_err( ) , MemoryError :: OffsetTooLarge ) ) ;
439+ }
440+
377441 #[ test]
378442 fn test_mark_dirty ( ) {
379443 let page_size = get_page_size ( ) . unwrap ( ) ;
0 commit comments