Skip to content

Commit

Permalink
Implement Guard Paging Mechanism
Browse files Browse the repository at this point in the history
This patch implements a page guarding mechanism.
It will be applied at guest memory creation step and
at snapshot memory restore.

Signed-off-by: berciuliviu <[email protected]>
  • Loading branch information
berciuliviu committed Dec 23, 2020
1 parent 6bd320a commit 7fd8398
Show file tree
Hide file tree
Showing 4 changed files with 287 additions and 12 deletions.
22 changes: 13 additions & 9 deletions src/vmm/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use crate::construct_kvm_mpidrs;
use crate::device_manager::legacy::PortIODeviceManager;
use crate::device_manager::mmio::MMIODeviceManager;
use crate::device_manager::persist::MMIODevManagerConstructorArgs;
use crate::guard_pages;
use crate::persist::{MicrovmState, MicrovmStateError};
use crate::vmm_config::boot_source::BootConfig;
use crate::vstate::{
Expand Down Expand Up @@ -50,6 +51,8 @@ pub enum StartMicrovmError {
CreateRateLimiter(io::Error),
/// Memory regions are overlapping or mmap fails.
GuestMemoryMmap(vm_memory::Error),
/// Memory regions are overlapping or mmap fails (page guard mechanism).
GuestMemoryMmapGuarded(guard_pages::GuardPageError),
/// Cannot load initrd due to an invalid memory configuration.
InitrdLoad,
/// Cannot load initrd due to an invalid image.
Expand Down Expand Up @@ -107,6 +110,12 @@ impl Display for StartMicrovmError {
err_msg = err_msg.replace("\"", "");
write!(f, "Invalid Memory Configuration: {}", err_msg)
}
GuestMemoryMmapGuarded(err) => {
// Remove imbricated quotes from error message.
let mut err_msg = format!("{:?}", err);
err_msg = err_msg.replace("\"", "");
write!(f, "Invalid Guarded Memory Configuration: {}", err_msg)
}
InitrdLoad => write!(
f,
"Cannot load initrd due to an invalid memory configuration."
Expand Down Expand Up @@ -456,15 +465,10 @@ pub fn create_guest_memory(
let mem_size = mem_size_mib << 20;
let arch_mem_regions = arch::arch_memory_regions(mem_size);

if !track_dirty_pages {
Ok(GuestMemoryMmap::from_ranges(&arch_mem_regions)
.map_err(StartMicrovmError::GuestMemoryMmap)?)
} else {
Ok(
GuestMemoryMmap::from_ranges_with_tracking(&arch_mem_regions)
.map_err(StartMicrovmError::GuestMemoryMmap)?,
)
}
Ok(
guard_pages::create_guest_memory_guarded(&arch_mem_regions, track_dirty_pages)
.map_err(StartMicrovmError::GuestMemoryMmapGuarded)?,
)
}

fn load_kernel(
Expand Down
268 changes: 268 additions & 0 deletions src/vmm/src/guard_pages.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
use std::borrow::Borrow;
use std::fmt;
use std::io;
use std::os::unix::io::AsRawFd;
use std::ptr::null_mut;
use std::result;

use vm_memory::mmap::{check_file_offset, Error as MmapError};
use vm_memory::{FileOffset, GuestAddress, GuestMemoryMmap, GuestRegionMmap, MmapRegion};

const GUARD_NUMBER: usize = 2;

/// Errors that can occur when creating a memory map.
#[derive(Debug)]
pub enum GuardPageError {
/// Libc::mmap errors
Mmap(io::Error),
/// Error when trying to create a MmapRegion
MmapRegionError(MmapError),
}

impl fmt::Display for GuardPageError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> fmt::Result {
match self {
GuardPageError::Mmap(error) => write!(f, "{}", error),
GuardPageError::MmapRegionError(error) => write!(f, "MmapRegionError: {}", error),
}
}
}

/// Creates a container, allocates anonymous memory for guest memory regions and enables dirty page
/// tracking if set to True.
/// Every memory range is mapped guarded.
///
/// Valid memory regions are specified as a slice of (Address, Size) tuples sorted by Address
pub fn create_guest_memory_guarded(
ranges: &[(GuestAddress, usize)],
track_dirty_pages: bool,
) -> result::Result<GuestMemoryMmap, GuardPageError> {
map_guest_memory_guarded(ranges.iter().map(|r| (r.0, r.1, None)), track_dirty_pages)
}

/// Creates a container and allocates anonymous memory for guest memory regions.
/// Adds guard pages to every region.
///
/// # Arguments
///
/// * 'ranges' - Iterator over a sequence of (Address, Size, Option<FileOffset>)
/// tuples sorted by Address.
/// * 'track_dirty_pages' - Whether or not dirty page tracking is enabled.
/// If set, it creates a dedicated bitmap for tracing memory writes
/// specific to every region.
pub fn map_guest_memory_guarded<A, T>(
ranges: T,
track_dirty_pages: bool,
) -> result::Result<GuestMemoryMmap, GuardPageError>
where
A: Borrow<(GuestAddress, usize, Option<FileOffset>)>,
T: IntoIterator<Item = A>,
{
let prot = libc::PROT_READ | libc::PROT_WRITE;
let file_flags = libc::MAP_NORESERVE | libc::MAP_SHARED;
let create_flags = libc::MAP_NORESERVE | libc::MAP_PRIVATE | libc::MAP_ANONYMOUS;

GuestMemoryMmap::from_regions(
ranges
.into_iter()
.map(|x| {
let guest_base = x.borrow().0;
let size = x.borrow().1;

if let Some(ref f_off) = x.borrow().2 {
build_guarded(Some(f_off.clone()), size, prot, file_flags)
} else {
build_guarded(None, size, prot, create_flags)
}
.and_then(|r| {
let mut mmap = GuestRegionMmap::new(r, guest_base)
.map_err(GuardPageError::MmapRegionError)?;
if track_dirty_pages {
mmap.enable_dirty_page_tracking();
}
Ok(mmap)
})
})
.collect::<result::Result<Vec<_>, GuardPageError>>()?,
)
.map_err(GuardPageError::MmapRegionError)
}

/// Creates a guarded mapping based on the provided arguments.
/// Guard pages will be created at the beginning and the end of the range.
///
/// # Arguments
/// * `file_offset` - if provided, the method will create a file mapping at offset
/// `file_offset.start` in the file referred to by `file_offset.file`.
/// * `size` - The size of the memory region in bytes.
/// * `prot` - The desired memory protection of the mapping.
/// * `flags` - This argument determines whether updates to the mapping are visible to other
/// processes mapping the same region, and whether updates are carried through to
/// the underlying file.
pub fn build_guarded(
file_offset: Option<FileOffset>,
size: usize,
prot: i32,
flags: i32,
) -> Result<MmapRegion, GuardPageError> {
let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize };
// Create the guarded range size (received size + 2 pages)
let guarded_size = size + GUARD_NUMBER * page_size;

// Map the guarded range to PROT_NONE
let guard_addr = unsafe {
libc::mmap(
null_mut(),
guarded_size,
libc::PROT_NONE,
libc::MAP_ANONYMOUS | libc::MAP_PRIVATE | libc::MAP_NORESERVE,
-1,
0,
)
};

if guard_addr == libc::MAP_FAILED {
return Err(GuardPageError::Mmap(io::Error::last_os_error()));
}

let (fd, offset) = if let Some(ref f_off) = file_offset {
check_file_offset(f_off, size)
.map_err(MmapError::MmapRegion)
.map_err(GuardPageError::MmapRegionError)?;
(f_off.file().as_raw_fd(), f_off.start())
} else {
(-1, 0)
};

let map_addr = guard_addr as usize + page_size;

// Inside the protected range, starting with guard_addr + PAGE_SIZE,
// map the requested range with received protection and flags
let addr = unsafe {
libc::mmap(
map_addr as *mut libc::c_void,
size,
prot,
flags | libc::MAP_FIXED,
fd,
offset as libc::off_t,
)
};

if addr == libc::MAP_FAILED {
return Err(GuardPageError::Mmap(io::Error::last_os_error()));
}

Ok(unsafe {
MmapRegion::build_raw(addr as *mut u8, size, prot, flags)
.map_err(MmapError::MmapRegion)
.map_err(GuardPageError::MmapRegionError)?
})
}

#[cfg(test)]
pub mod tests {
use super::*;
use std::os::unix::io::AsRawFd;
use utils::tempfile::TempFile;
use vm_memory::FileOffset;
use vm_memory::GuestMemory;

fn apply_write_on_address(addr: *mut u8) {
let pid = unsafe { libc::fork() };
match pid {
0 => {
unsafe {
std::ptr::write(addr, 0xFF);
}
unreachable!();
}
child_pid => {
let mut child_status: i32 = -1;
let pid_done = unsafe { libc::waitpid(child_pid, &mut child_status, 0) };
assert_eq!(pid_done, child_pid);

// Asserts that the child process terminated because
// it received a signal that was not handled.
assert!(libc::WIFSIGNALED(child_status));
// Signal code should be a SIGSEGV (11)
assert_eq!(libc::WTERMSIG(child_status), 11);
}
};
}

fn validate_guard_region(region: &MmapRegion) {
let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize };

// Check that the created range allows us to write inside it
let addr = region.as_ptr();

unsafe {
std::ptr::write(addr, 0xFF);
assert_eq!(std::ptr::read(addr), 0xFF);
}

// Try a read/write operation against the left guard border of the range
let left_border = (addr as usize - page_size) as *mut u8;
apply_write_on_address(left_border);

// Try a read/write operation against the right guard border of the range
let right_border = (addr as usize + region.size()) as *mut u8;
apply_write_on_address(right_border);
}

#[test]
fn test_create_guard_region() {
let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize };
let size = page_size * 10;
let prot = libc::PROT_READ | libc::PROT_WRITE;
let flags = libc::MAP_ANONYMOUS | libc::MAP_NORESERVE | libc::MAP_PRIVATE;

let region = build_guarded(None, size, prot, flags).unwrap();

// Verify that the region was built correctly
assert_eq!(region.size(), size);
assert!(region.file_offset().is_none());
assert_eq!(region.prot(), prot);
assert_eq!(region.flags(), flags);

validate_guard_region(&region);
}

#[test]
fn test_create_guard_region_from_file() {
let file = TempFile::new().unwrap().into_file();

let prot = libc::PROT_READ | libc::PROT_WRITE;
let flags = libc::MAP_NORESERVE | libc::MAP_PRIVATE;
let offset = 0;
let size = 10 * 4096;
assert_eq!(unsafe { libc::ftruncate(file.as_raw_fd(), 4096 * 10) }, 0);

//
let region = build_guarded(Some(FileOffset::new(file, offset)), size, prot, flags).unwrap();

// Verify that the region was built correctly
assert_eq!(region.size(), size);
// assert_eq!(region.file_offset().unwrap().start(), offset as u64);
assert_eq!(region.prot(), prot);
assert_eq!(region.flags(), flags);

validate_guard_region(&region);
}

#[test]
fn test_create_guest_memory_guarded() {
let mem_size_mib = 128;
let mem_size = mem_size_mib << 20;
let arch_mem_regions = arch::arch_memory_regions(mem_size);

let guest_memory = create_guest_memory_guarded(&arch_mem_regions, true).unwrap();
guest_memory
.with_regions(|_, region| -> Result<(), GuardPageError> {
validate_guard_region(region);
Ok(())
})
.unwrap();
}
}
2 changes: 2 additions & 0 deletions src/vmm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ pub mod builder;
/// Syscalls allowed through the seccomp filter.
pub mod default_syscalls;
pub(crate) mod device_manager;
/// Guard paging mechanism
pub mod guard_pages;
pub mod memory_snapshot;
/// Save/restore utilities.
pub mod persist;
Expand Down
7 changes: 4 additions & 3 deletions src/vmm/src/memory_snapshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ use std::fmt::{Display, Formatter};
use std::fs::File;
use std::io::SeekFrom;

use crate::guard_pages::{build_guarded, GuardPageError};
use versionize::{VersionMap, Versionize, VersionizeResult};
use versionize_derive::Versionize;
use vm_memory::{
Bytes, FileOffset, GuestAddress, GuestMemory, GuestMemoryError, GuestMemoryMmap,
GuestMemoryRegion, GuestRegionMmap, MemoryRegionAddress, MmapRegion,
GuestMemoryRegion, GuestRegionMmap, MemoryRegionAddress,
};

use crate::DirtyBitmap;
Expand Down Expand Up @@ -68,7 +69,7 @@ pub enum Error {
/// Cannot create memory.
CreateMemory(vm_memory::Error),
/// Cannot create region.
CreateRegion(vm_memory::mmap::MmapRegionError),
CreateRegion(GuardPageError),
/// Cannot dump memory.
WriteMemory(GuestMemoryError),
}
Expand Down Expand Up @@ -174,7 +175,7 @@ impl SnapshotMemory for GuestMemoryMmap {
) -> std::result::Result<Self, Error> {
let mut mmap_regions = Vec::new();
for region in state.regions.iter() {
let mmap_region = MmapRegion::build(
let mmap_region = build_guarded(
Some(FileOffset::new(
file.try_clone().map_err(Error::FileHandle)?,
region.offset,
Expand Down

0 comments on commit 7fd8398

Please sign in to comment.