Skip to content

Commit a174b03

Browse files
ManciukicShadowCurse
authored andcommitted
fix(memory): ensure we are not mmapping beyond EOF on restore from file
[email protected] removed the bounds check in the memory region creation as not every fd is seekable. This causes a SIGBUS if for some reason the snapshot file is smaller than the sum of the regions and the guest tries to access a page beyond EOF. This issue was caught by the fuzzer. This change adds the bounds check in the snapshot_file function and unit tests to ensure this behaviour is maintained. Signed-off-by: Riccardo Mancini <[email protected]>
1 parent 5c47cd7 commit a174b03

File tree

1 file changed

+66
-2
lines changed

1 file changed

+66
-2
lines changed

src/vmm/src/vstate/memory.rs

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)