diff --git a/examples/cr-events.rs b/examples/cr-events.rs index 08ffe501..562e001c 100644 --- a/examples/cr-events.rs +++ b/examples/cr-events.rs @@ -5,7 +5,8 @@ use std::time::Instant; use clap::{App, Arg, ArgMatches}; use colored::*; -use microvmi::api::{CrType, DriverInitParam, EventType, InterceptType, Introspectable}; +use microvmi::api::{CrType, DriverInitParam, EventType, InterceptType}; +use microvmi::Microvmi; fn parse_args() -> ArgMatches<'static> { App::new(file!()) @@ -32,7 +33,7 @@ fn parse_args() -> ArgMatches<'static> { .get_matches() } -fn toggle_cr_intercepts(drv: &mut Box, vec_cr: &[CrType], enabled: bool) { +fn toggle_cr_intercepts(drv: &mut Microvmi, vec_cr: &[CrType], enabled: bool) { drv.pause().expect("Failed to pause VM"); for cr in vec_cr { @@ -88,8 +89,8 @@ fn main() { let init_option = matches .value_of("kvmi_socket") .map(|socket| DriverInitParam::KVMiSocket(socket.into())); - let mut drv: Box = - microvmi::init(domain_name, None, init_option).expect("Failed to init libmicrovmi"); + let mut drv = + Microvmi::new(domain_name, None, init_option).expect("Failed to init libmicrovmi"); // enable control register interception toggle_cr_intercepts(&mut drv, &vec_cr, true); diff --git a/examples/interrupt-events.rs b/examples/interrupt-events.rs index d081eafd..18a28694 100644 --- a/examples/interrupt-events.rs +++ b/examples/interrupt-events.rs @@ -5,7 +5,8 @@ use std::time::Instant; use clap::{App, Arg, ArgMatches}; use colored::*; -use microvmi::api::{DriverInitParam, EventReplyType, EventType, InterceptType, Introspectable}; +use microvmi::api::{DriverInitParam, EventReplyType, EventType, InterceptType}; +use microvmi::Microvmi; fn parse_args() -> ArgMatches<'static> { App::new(file!()) @@ -23,7 +24,7 @@ fn parse_args() -> ArgMatches<'static> { .get_matches() } -fn toggle_int3_interception(drv: &mut Box, enabled: bool) { +fn toggle_int3_interception(drv: &mut Microvmi, enabled: bool) { drv.pause().expect("Failed to pause VM"); let intercept = InterceptType::Breakpoint; @@ -56,10 +57,10 @@ fn main() { .expect("Error setting Ctrl-C handler"); println!("Initialize Libmicrovmi"); - let mut drv: Box = - microvmi::init(domain_name, None, init_option).expect("Failed to init libmicrovmi"); + let mut drv = + Microvmi::new(domain_name, None, init_option).expect("Failed to init libmicrovmi"); - //Enable int3 interception + // enable int3 interception toggle_int3_interception(&mut drv, true); println!("Listen for interrupt events..."); @@ -91,7 +92,7 @@ fn main() { } let duration = start.elapsed(); - //disable int3 interception + // disable int3 interception toggle_int3_interception(&mut drv, false); let ev_per_sec = i as f64 / duration.as_secs_f64(); diff --git a/examples/mem-dump.rs b/examples/mem-dump.rs index 71cd62be..452b44ce 100644 --- a/examples/mem-dump.rs +++ b/examples/mem-dump.rs @@ -1,14 +1,15 @@ use std::fs::File; -use std::io::Write; +use std::io::{Read, Write}; use std::path::Path; use clap::{App, Arg, ArgMatches}; use indicatif::{ProgressBar, ProgressStyle}; -use log::{debug, trace}; +use log::trace; -use microvmi::api::{DriverInitParam, Introspectable}; +use microvmi::api::DriverInitParam; +use microvmi::Microvmi; -const PAGE_SIZE: usize = 4096; +const BUFFER_SIZE: usize = 64535; // 64K fn parse_args() -> ArgMatches<'static> { App::new(file!()) @@ -55,8 +56,8 @@ fn main() { let spinner = ProgressBar::new_spinner(); spinner.enable_steady_tick(200); spinner.set_message("Initializing libmicrovmi..."); - let mut drv: Box = - microvmi::init(domain_name, None, init_option).expect("Failed to init libmicrovmi"); + let mut drv = + Microvmi::new(domain_name, None, init_option).expect("Failed to init libmicrovmi"); spinner.finish_and_clear(); println!("pausing the VM"); @@ -76,23 +77,23 @@ fn main() { // redraw every 0.1% change, otherwise it becomes the bottleneck bar.set_draw_delta(max_addr / 1000); - for cur_addr in (0..max_addr).step_by(PAGE_SIZE) { + for cur_addr in (0..max_addr).step_by(BUFFER_SIZE) { trace!( "reading {:#X} bytes of memory at {:#X}", - PAGE_SIZE, + BUFFER_SIZE, cur_addr ); // reset buffer each loop - let mut buffer: [u8; PAGE_SIZE] = [0; PAGE_SIZE]; - let mut _bytes_read = 0; - drv.read_physical(cur_addr, &mut buffer, &mut _bytes_read) - .unwrap_or_else(|_| debug!("failed to read memory at {:#X}", cur_addr)); + let mut buffer: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]; + drv.padded_memory + .read_exact(&mut buffer) + .expect(&*format!("Failed to read memory at {:#X}", cur_addr)); dump_file .write_all(&buffer) .expect("failed to write to file"); // update bar bar.set_prefix(&*format!("{:#X}", cur_addr)); - bar.inc(PAGE_SIZE as u64); + bar.inc(BUFFER_SIZE as u64); } bar.finish(); println!( diff --git a/examples/mem-events.rs b/examples/mem-events.rs index 61f2130e..a3e53491 100644 --- a/examples/mem-events.rs +++ b/examples/mem-events.rs @@ -4,9 +4,8 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::time::Instant; -use microvmi::api::{ - Access, DriverInitParam, EventReplyType, EventType, InterceptType, Introspectable, -}; +use microvmi::api::{Access, DriverInitParam, EventReplyType, EventType, InterceptType}; +use microvmi::Microvmi; const PAGE_SIZE: usize = 4096; @@ -18,7 +17,7 @@ fn parse_args() -> ArgMatches<'static> { .get_matches() } -fn toggle_pf_intercept(drv: &mut Box, enabled: bool) { +fn toggle_pf_intercept(drv: &mut Microvmi, enabled: bool) { drv.pause().expect("Failed to pause VM"); let intercept = InterceptType::Pagefault; @@ -51,8 +50,8 @@ fn main() { let init_option = matches .value_of("kvmi_socket") .map(|socket| DriverInitParam::KVMiSocket(socket.into())); - let mut drv: Box = - microvmi::init(domain_name, None, init_option).expect("Failed to init libmicrovmi"); + let mut drv = + Microvmi::new(domain_name, None, init_option).expect("Failed to init libmicrovmi"); println!("Listen for memory events..."); // record elapsed time let start = Instant::now(); diff --git a/examples/msr-events.rs b/examples/msr-events.rs index 41da8511..9ec7eef3 100644 --- a/examples/msr-events.rs +++ b/examples/msr-events.rs @@ -5,7 +5,8 @@ use std::sync::Arc; use std::time::Instant; use std::u32; -use microvmi::api::{DriverInitParam, EventReplyType, EventType, InterceptType, Introspectable}; +use microvmi::api::{DriverInitParam, EventReplyType, EventType, InterceptType}; +use microvmi::Microvmi; // default set of MSRs to be intercepted const DEFAULT_MSR: [u32; 6] = [0x174, 0x175, 0x176, 0xc0000080, 0xc0000081, 0xc0000082]; @@ -47,7 +48,7 @@ fn parse_args() -> ArgMatches<'static> { .get_matches() } -fn toggle_msr_intercepts(drv: &mut Box, vec_msr: &[u32], enabled: bool) { +fn toggle_msr_intercepts(drv: &mut Microvmi, vec_msr: &[u32], enabled: bool) { drv.pause().expect("Failed to pause VM"); for msr in vec_msr { @@ -94,8 +95,8 @@ fn main() { .expect("Error setting Ctrl-C handler"); println!("Initialize Libmicrovmi"); - let mut drv: Box = - microvmi::init(domain_name, None, init_option).expect("Failed to init libmicrovmi"); + let mut drv = + Microvmi::new(domain_name, None, init_option).expect("Failed to init libmicrovmi"); toggle_msr_intercepts(&mut drv, ®isters, true); diff --git a/examples/pause.rs b/examples/pause.rs index c4025b9d..c3c35f16 100644 --- a/examples/pause.rs +++ b/examples/pause.rs @@ -2,7 +2,8 @@ use std::{thread, time}; use clap::{App, Arg, ArgMatches}; -use microvmi::api::{DriverInitParam, Introspectable}; +use microvmi::api::DriverInitParam; +use microvmi::Microvmi; fn parse_args() -> ArgMatches<'static> { App::new(file!()) @@ -37,8 +38,8 @@ fn main() { let init_option = matches .value_of("kvmi_socket") .map(|socket| DriverInitParam::KVMiSocket(socket.into())); - let mut drv: Box = - microvmi::init(domain_name, None, init_option).expect("Failed to init libmicrovmi"); + let mut drv = + Microvmi::new(domain_name, None, init_option).expect("Failed to init libmicrovmi"); println!("pausing VM for {} seconds", timeout); drv.pause().expect("Failed to pause VM"); diff --git a/examples/regs-dump.rs b/examples/regs-dump.rs index 6d0e218d..72607aae 100644 --- a/examples/regs-dump.rs +++ b/examples/regs-dump.rs @@ -1,6 +1,7 @@ use clap::{App, Arg, ArgMatches}; -use microvmi::api::{DriverInitParam, Introspectable}; +use microvmi::api::DriverInitParam; +use microvmi::Microvmi; fn parse_args() -> ArgMatches<'static> { App::new(file!()) @@ -28,8 +29,8 @@ fn main() { let init_option = matches .value_of("kvmi_socket") .map(|socket| DriverInitParam::KVMiSocket(socket.into())); - let mut drv: Box = - microvmi::init(domain_name, None, init_option).expect("Failed to init libmicrovmi"); + let mut drv = + Microvmi::new(domain_name, None, init_option).expect("Failed to init libmicrovmi"); println!("pausing the VM"); drv.pause().expect("Failed to pause VM"); diff --git a/python/microvmi/memory.py b/python/microvmi/memory.py index 3002d1ea..3b0c208d 100644 --- a/python/microvmi/memory.py +++ b/python/microvmi/memory.py @@ -94,13 +94,8 @@ def read(self, size: int = ...) -> Optional[bytes]: # type: ignore if size < 0: # -1: read all bytes until EOF raise NotImplementedError - data = bytearray(size) - for offset in range(0, size, PAGE_SIZE): - read_len = min(PAGE_SIZE, size - offset) - pos = self.tell() - chunk, _ = self._m.read_physical(pos, read_len) - end_offset = offset + read_len - data[offset:end_offset] = chunk - self.seek(read_len, SEEK_CUR) - self._log.debug("read return: len: %s, content: %s", len(data), data[:100]) - return bytes(data) + pos = self.tell() + buffer, bytes_read = self._m.read_physical_padded(pos, size) + self.seek(size, SEEK_CUR) + self._log.debug("read return: len: %s, content: %s", len(buffer), buffer[:100]) + return buffer diff --git a/python/src/lib.rs b/python/src/lib.rs index 386b73d9..d5aac05b 100644 --- a/python/src/lib.rs +++ b/python/src/lib.rs @@ -1,3 +1,5 @@ +#![allow(clippy::upper_case_acronyms)] + mod errors; use log::{debug, info}; @@ -7,8 +9,9 @@ use pyo3::prelude::*; use errors::PyMicrovmiError; use microvmi::api as rapi; // rust api -use microvmi::init; +use microvmi::Microvmi; use pyo3::types::{PyByteArray, PyBytes}; +use std::io::{Read, Seek, SeekFrom}; /// microvmi Python module declaration #[pymodule] @@ -74,7 +77,7 @@ impl DriverInitParam { // compatible #[pyclass(unsendable)] struct MicrovmiExt { - driver: Box, + microvmi: Microvmi, } #[pymethods] @@ -120,9 +123,9 @@ impl MicrovmiExt { rapi::DriverInitParam::KVMiSocket(param.param_data_string) } }); - let driver = - init(domain_name, rust_driver_type, rust_init_param).map_err(PyMicrovmiError::from)?; - Ok(MicrovmiExt { driver }) + let microvmi = Microvmi::new(domain_name, rust_driver_type, rust_init_param) + .map_err(PyMicrovmiError::from)?; + Ok(MicrovmiExt { microvmi }) } /// read VM physical memory starting from paddr, of a given size @@ -134,16 +137,15 @@ impl MicrovmiExt { /// Returns: /// Tuple[bytes, int]: the read operation result and the amount bytes read fn read_physical<'p>( - &self, + &mut self, py: Python<'p>, paddr: u64, size: usize, ) -> PyResult<(&'p PyBytes, u64)> { let mut bytes_read: u64 = 0; + self.microvmi.memory.seek(SeekFrom::Start(paddr))?; let pybuffer: &PyBytes = PyBytes::new_with(py, size, |mut buffer| { - self.driver - .read_physical(paddr, &mut buffer, &mut bytes_read) - .ok(); + bytes_read = self.microvmi.memory.read(&mut buffer).unwrap_or(0) as u64; Ok(()) })?; @@ -155,24 +157,59 @@ impl MicrovmiExt { /// Args: /// paddr (int): the physical address to start reading from /// buffer (bytearray): the buffer to read into - fn read_physical_into(&self, paddr: u64, buffer: &PyByteArray) -> u64 { + fn read_physical_into(&mut self, paddr: u64, buffer: &PyByteArray) -> PyResult { let mut_buf: &mut [u8] = unsafe { buffer.as_bytes_mut() }; + // ignore read error + self.microvmi.memory.seek(SeekFrom::Start(paddr))?; + let bytes_read = self.microvmi.memory.read(mut_buf).unwrap_or(0) as u64; + Ok(bytes_read) + } + + /// read VM physical memory starting from paddr, of a given size, adding padding if necessary + /// + /// Args: + /// paddr: (int) physical address from where the read operation should start + /// size: (int) size of the read operation + /// + /// Returns: + /// Tuple[bytes, int]: the read operation result and the amount bytes read + fn read_physical_padded<'p>( + &mut self, + py: Python<'p>, + paddr: u64, + size: usize, + ) -> PyResult<(&'p PyBytes, u64)> { let mut bytes_read: u64 = 0; + self.microvmi.padded_memory.seek(SeekFrom::Start(paddr))?; + let pybuffer: &PyBytes = PyBytes::new_with(py, size, |mut buffer| { + bytes_read = self.microvmi.padded_memory.read(&mut buffer).unwrap_or(0) as u64; + Ok(()) + })?; + + Ok((pybuffer, bytes_read)) + } + + /// read VM physical memory starting from paddr into the given buffer, adding padding if necessary + /// + /// Args: + /// paddr (int): the physical address to start reading from + /// buffer (bytearray): the buffer to read into + fn read_physical_padded_into(&mut self, paddr: u64, buffer: &PyByteArray) -> PyResult { + let mut_buf: &mut [u8] = unsafe { buffer.as_bytes_mut() }; // ignore read error - self.driver - .read_physical(paddr, mut_buf, &mut bytes_read) - .ok(); - bytes_read + self.microvmi.padded_memory.seek(SeekFrom::Start(paddr))?; + let bytes_read = self.microvmi.padded_memory.read(mut_buf).unwrap_or(0) as u64; + Ok(bytes_read) } /// pause the VM fn pause(&mut self) -> PyResult<()> { - Ok(self.driver.pause().map_err(PyMicrovmiError::from)?) + Ok(self.microvmi.pause().map_err(PyMicrovmiError::from)?) } /// resume the VM fn resume(&mut self) -> PyResult<()> { - Ok(self.driver.resume().map_err(PyMicrovmiError::from)?) + Ok(self.microvmi.resume().map_err(PyMicrovmiError::from)?) } /// get maximum physical address @@ -181,7 +218,7 @@ impl MicrovmiExt { /// int: the maximum physical address fn get_max_physical_addr(&self) -> PyResult { let max_addr = self - .driver + .microvmi .get_max_physical_addr() .map_err(PyMicrovmiError::from)?; Ok(max_addr) diff --git a/src/api.rs b/src/api.rs index 096d29c5..1cdc54fd 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,6 +1,9 @@ +#[cfg(test)] +use mockall::*; use std::convert::TryInto; use std::error::Error; use std::ffi::{CStr, IntoStringError}; +use std::io::Error as IoError; use enum_iterator::IntoEnumIterator; @@ -169,9 +172,39 @@ pub enum Registers { X86(X86Registers), } +#[derive(Copy, Clone, Debug)] +pub struct PageFrame { + /// frame number + pub number: u64, + /// offset inside the frame + pub offset: u16, +} + +impl PageFrame { + pub fn with_paddr(paddr: u64) -> Self { + PageFrame { + number: paddr >> PAGE_SHIFT, + offset: (paddr & 0xfff_u64) as u16, + } + } + + pub fn to_paddr(&self) -> u64 { + (self.number << PAGE_SHIFT) + self.offset as u64 + } + + pub fn window_len(&self) -> u32 { + PAGE_SIZE - (self.offset as u32) + } + + pub fn is_aligned(&self) -> bool { + self.offset == 0 + } +} + pub const PAGE_SHIFT: u32 = 12; pub const PAGE_SIZE: u32 = 4096; +#[cfg_attr(test, automock)] pub trait Introspectable { /// Retrieve the number of VCPUs. /// @@ -179,20 +212,13 @@ pub trait Introspectable { unimplemented!(); } - /// read the physical memory, starting from paddr, into buf + /// read memory from the specified page frame into buf /// /// # Arguments /// - /// * 'paddr' - the physical address to read from - /// * 'buf' - the data read from memory - /// * 'bytes_read' - the number of bytes read - /// - fn read_physical( - &self, - _paddr: u64, - _buf: &mut [u8], - _bytes_read: &mut u64, - ) -> Result<(), Box> { + /// * 'frame' - the page frame to read from + /// * 'buf' - the buffer to write into + fn read_frame(&self, _frame: PageFrame, _buf: &mut [u8]) -> Result<(), IoError> { unimplemented!(); } diff --git a/src/capi.rs b/src/capi.rs index e1869521..ae3abf20 100644 --- a/src/capi.rs +++ b/src/capi.rs @@ -1,9 +1,12 @@ +//! This module defines the C API + use crate::api::{DriverInitParam, DriverType, Introspectable, Registers}; -use crate::init; +use crate::microvmi::Microvmi; use bitflags::_core::ptr::null_mut; use cty::{c_char, size_t, uint16_t, uint64_t, uint8_t}; use std::convert::TryInto; use std::ffi::{c_void, CStr, CString}; +use std::io::{Read, Seek, SeekFrom}; use std::slice; /// Support passing initialization options @@ -55,8 +58,9 @@ pub unsafe extern "C" fn microvmi_init( .expect("Failed to convert DriverInitParam C struct to Rust equivalent"), ) }; - match init(&safe_domain_name, optional_driver_type, init_option) { - Ok(driver) => Box::into_raw(Box::new(driver)) as *mut c_void, + + match Microvmi::new(&safe_domain_name, optional_driver_type, init_option) { + Ok(m) => Box::into_raw(Box::new(m)) as *mut c_void, Err(err) => { if !init_error.is_null() { (*init_error) = CString::new(format!("{}", err)) @@ -77,15 +81,15 @@ pub unsafe extern "C" fn microvmi_destroy(context: *mut c_void) { #[allow(clippy::missing_safety_doc)] #[no_mangle] pub unsafe extern "C" fn microvmi_pause(context: *mut c_void) -> bool { - let driver = get_driver_mut_ptr(context); - (*driver).pause().is_ok() + let mut driver = get_driver_mut_ptr(context); + driver.pause().is_ok() } #[allow(clippy::missing_safety_doc)] #[no_mangle] pub unsafe extern "C" fn microvmi_resume(context: *mut c_void) -> bool { - let driver = get_driver_mut_ptr(context); - (*driver).resume().is_ok() + let mut driver = get_driver_mut_ptr(context); + driver.resume().is_ok() } #[allow(clippy::missing_safety_doc)] @@ -97,19 +101,25 @@ pub unsafe extern "C" fn microvmi_read_physical( size: size_t, bytes_read: *mut uint64_t, ) -> bool { - let driver = get_driver_mut_ptr(context); + let mut driver = get_driver_mut_ptr(context); - let mut bytes_read_local = 0; - let res = (*driver) - .read_physical( - physical_address, - slice::from_raw_parts_mut(buffer, size), - &mut bytes_read_local, - ) - .is_ok(); + if driver + .memory + .seek(SeekFrom::Start(physical_address)) + .is_err() + { + return false; + } + + let mut res = false; + let read_res = driver.memory.read(slice::from_raw_parts_mut(buffer, size)); + if read_res.is_ok() { + res = true; + } + let bytes_read_local = read_res.unwrap_or(0); // update bytes_read if not NULL if !bytes_read.is_null() { - bytes_read.write(bytes_read_local); + bytes_read.write(bytes_read_local as u64); } res } @@ -121,7 +131,7 @@ pub unsafe extern "C" fn microvmi_get_max_physical_addr( address_ptr: *mut uint64_t, ) -> bool { let driver = get_driver_mut_ptr(context); - match (*driver).get_max_physical_addr() { + match driver.get_max_physical_addr() { Ok(max_addr) => { address_ptr.write(max_addr); true @@ -138,7 +148,7 @@ pub unsafe extern "C" fn microvmi_read_registers( registers: *mut Registers, ) -> bool { let driver = get_driver_mut_ptr(context); - match (*driver).read_registers(vcpu) { + match driver.read_registers(vcpu) { Ok(regs) => { registers.write(regs); true @@ -152,12 +162,12 @@ pub unsafe extern "C" fn microvmi_read_registers( #[no_mangle] pub unsafe extern "C" fn microvmi_get_driver_type(context: *mut c_void) -> DriverType { let drv = get_driver_mut_ptr(context); - (*drv).get_driver_type() + drv.get_driver_type() } -unsafe fn get_driver_mut_ptr(context: *mut c_void) -> *mut dyn Introspectable { - let driver: *mut *mut dyn Introspectable = context as *mut _; - driver.read() +unsafe fn get_driver_mut_ptr(context: *mut c_void) -> Microvmi { + let microvmi_box: *mut Microvmi = context as *mut _; + microvmi_box.read() } unsafe fn get_driver_box(context: *mut c_void) -> Box> { diff --git a/src/driver/kvm.rs b/src/driver/kvm.rs index 447d403d..52588ad0 100644 --- a/src/driver/kvm.rs +++ b/src/driver/kvm.rs @@ -8,14 +8,14 @@ use std::convert::From; use std::convert::TryFrom; use std::convert::TryInto; use std::error::Error; +use std::io::Error as IoError; use std::mem; use std::vec::Vec; use crate::api::{ Access, CrType, DriverInitParam, DriverType, Event, EventReplyType, EventType, InterceptType, - Introspectable, Registers, SegmentReg, SystemTableReg, X86Registers, + Introspectable, PageFrame, Registers, SegmentReg, SystemTableReg, X86Registers, }; -use kvmi::constants::PAGE_SIZE; impl TryFrom for KVMiPageAccess { type Error = &'static str; @@ -147,22 +147,8 @@ impl Introspectable for Kvm { Ok(self.kvmi.get_vcpu_count()?.try_into()?) } - fn read_physical( - &self, - paddr: u64, - buf: &mut [u8], - bytes_read: &mut u64, - ) -> Result<(), Box> { - // kvmi read_physical can only handle a 4K buf request - // any buffer bigger than that will result in an IOError (KVM_EINVAL) - // need to chunk the read in 4K - for (i, chunk) in buf.chunks_mut(PAGE_SIZE).enumerate() { - let offset = i * PAGE_SIZE; - let cur_paddr = paddr + offset as u64; - self.kvmi.read_physical(cur_paddr, chunk)?; - *bytes_read = offset as u64; - } - Ok(()) + fn read_frame(&self, frame: PageFrame, buf: &mut [u8]) -> Result<(), IoError> { + self.kvmi.read_physical(frame.to_paddr(), buf) } fn write_physical(&self, paddr: u64, buf: &[u8]) -> Result<(), Box> { diff --git a/src/driver/virtualbox.rs b/src/driver/virtualbox.rs index 01e98700..5258689c 100644 --- a/src/driver/virtualbox.rs +++ b/src/driver/virtualbox.rs @@ -1,9 +1,10 @@ use std::error::Error; +use std::io::{Error as IoError, ErrorKind}; use fdp::{RegisterType, FDP}; use crate::api::{ - DriverInitParam, DriverType, Introspectable, Registers, SegmentReg, SystemTableReg, + DriverInitParam, DriverType, Introspectable, PageFrame, Registers, SegmentReg, SystemTableReg, X86Registers, }; @@ -30,15 +31,12 @@ impl Introspectable for VBox { Ok(1) } - fn read_physical( - &self, - paddr: u64, - buf: &mut [u8], - bytes_read: &mut u64, - ) -> Result<(), Box> { - self.fdp.read_physical_memory(paddr, buf)?; - *bytes_read = buf.len() as u64; - Ok(()) + fn read_frame(&self, frame: PageFrame, buf: &mut [u8]) -> Result<(), IoError> { + // TODO: read_physical_memory should return an IO error to tell + // if a page frame was not mapped + self.fdp + .read_physical_memory(frame.to_paddr(), buf) + .map_err(|e| IoError::new(ErrorKind::Other, e.to_string())) } fn get_max_physical_addr(&self) -> Result> { diff --git a/src/driver/xen.rs b/src/driver/xen.rs index 4466ab4d..e316aa7b 100644 --- a/src/driver/xen.rs +++ b/src/driver/xen.rs @@ -1,6 +1,6 @@ use crate::api::{ CrType, DriverInitParam, DriverType, Event, EventType, InterceptType, Introspectable, - Registers, SegmentReg, SystemTableReg, X86Registers, + PageFrame, Registers, SegmentReg, SystemTableReg, X86Registers, }; use libc::{PROT_READ, PROT_WRITE}; use nix::poll::PollFlags; @@ -112,48 +112,23 @@ impl Xen { } impl Introspectable for Xen { - fn read_physical( - &self, - paddr: u64, - buf: &mut [u8], - bytes_read: &mut u64, - ) -> Result<(), Box> { - let mut cur_paddr: u64; - let mut count_mut: u64 = buf.len() as u64; - let mut buf_offset: u64 = 0; - *bytes_read = 0; - while count_mut > 0 { - // compute new paddr - cur_paddr = paddr + buf_offset; - // get the current gfn - let gfn = cur_paddr >> PAGE_SHIFT; - let page_offset = u64::from(PAGE_SIZE - 1) & cur_paddr; - // map gfn - let page = self - .xen_fgn - .map(self.domid, PROT_READ, gfn) - .map_err(XenDriverError::from)?; - // determine how much we can read - let read_len = if (page_offset + count_mut as u64) > u64::from(PAGE_SIZE) { - u64::from(PAGE_SIZE) - page_offset - } else { - count_mut - }; + fn read_frame(&self, frame: PageFrame, buf: &mut [u8]) -> Result<(), IoError> { + // map gfn + let page = self + .xen_fgn + .map(self.domid, PROT_READ, frame.number) + .map_err(|_| IoError::new(ErrorKind::NotFound, "Page not found"))?; - // prepare offsets - let buf_start = buf_offset as usize; - let buf_end = (buf_offset + read_len) as usize; - let page_start = page_offset as usize; - let page_end = (page_offset + read_len) as usize; - // do the read - buf[buf_start..buf_end].copy_from_slice(&page[page_start..page_end]); - // update loop variables - count_mut -= read_len; - buf_offset += read_len; - *bytes_read += read_len; - // unmap page - self.xen_fgn.unmap(page).map_err(XenDriverError::from)?; - } + // prepare offsets + let page_start = frame.offset as usize; + let page_end = (frame.offset + buf.len() as u16) as usize; + // do the read + buf.copy_from_slice(&page[page_start..page_end]); + + // unmap page + self.xen_fgn + .unmap(page) + .map_err(|e| IoError::new(ErrorKind::Other, e))?; Ok(()) } diff --git a/src/errors.rs b/src/errors.rs index 2eb9e349..8a7bfde8 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,3 +1,5 @@ +//! This module defines the library error types + use crate::api::DriverType; use std::error::Error; diff --git a/src/lib.rs b/src/lib.rs index 799183f7..e2ee20ca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,77 +2,19 @@ //! //! Click on this [book 📖](https://libmicrovmi.github.io/) to find our project documentation. +#![allow(clippy::upper_case_acronyms)] + +mod driver; +mod memory; + pub mod api; pub mod capi; -mod driver; pub mod errors; +pub mod microvmi; +// reexport +pub use crate::microvmi::Microvmi; #[macro_use] extern crate log; #[macro_use] extern crate bitflags; - -use enum_iterator::IntoEnumIterator; - -use api::Introspectable; -use api::{DriverInitParam, DriverType}; -#[cfg(feature = "kvm")] -use driver::kvm::Kvm; -#[cfg(feature = "virtualbox")] -use driver::virtualbox::VBox; -#[cfg(feature = "xen")] -use driver::xen::Xen; -use errors::MicrovmiError; -#[cfg(feature = "kvm")] -use kvmi::create_kvmi; - -pub fn init( - domain_name: &str, - driver_type: Option, - init_option: Option, -) -> Result, MicrovmiError> { - info!("Microvmi init"); - match driver_type { - None => { - // for each possible DriverType - for drv_type in DriverType::into_enum_iter() { - // try to init - match init_driver(domain_name, drv_type, init_option.clone()) { - Ok(driver) => { - return Ok(driver); - } - Err(e) => { - debug!("{:?} driver initialization failed: {}", drv_type, e); - continue; - } - } - } - Err(MicrovmiError::NoDriverAvailable) - } - Some(drv_type) => init_driver(domain_name, drv_type, init_option), - } -} - -/// Initialize a given driver type -/// return None if the requested driver has not been compiled in libmicrovmi -fn init_driver( - _domain_name: &str, - driver_type: DriverType, - _init_option: Option, -) -> Result, MicrovmiError> { - #[allow(clippy::match_single_binding)] - match driver_type { - #[cfg(feature = "kvm")] - DriverType::KVM => Ok(Box::new(Kvm::new( - _domain_name, - create_kvmi(), - _init_option, - )?)), - #[cfg(feature = "virtualbox")] - DriverType::VirtualBox => Ok(Box::new(VBox::new(_domain_name, _init_option)?)), - #[cfg(feature = "xen")] - DriverType::Xen => Ok(Box::new(Xen::new(_domain_name, _init_option)?)), - #[allow(unreachable_patterns)] - _ => Err(MicrovmiError::DriverNotCompiled(driver_type)), - } -} diff --git a/src/memory.rs b/src/memory.rs new file mode 100644 index 00000000..1d04743a --- /dev/null +++ b/src/memory.rs @@ -0,0 +1,235 @@ +//! This module defines a physical memory implementation behaving like a File-IO + +#[cfg(test)] +use crate::api::MockIntrospectable; +use crate::api::{Introspectable, PageFrame}; +use std::cell::RefCell; +use std::convert::TryFrom; +use std::error::Error as StdError; +use std::io::Error; +use std::io::{ErrorKind, Result}; +use std::io::{Read, Seek, SeekFrom, Write}; +use std::rc::Rc; +use std::result::Result as StdResult; + +const PAGE_SIZE: usize = 4096; + +// define shared Seek behavior between the 2 Memory objects +struct AddressSeek { + pos: u64, + max_addr: u64, +} + +impl Seek for AddressSeek { + fn seek(&mut self, pos: SeekFrom) -> Result { + match pos { + SeekFrom::Start(p) => { + self.pos = 0; + // force cast from u64 to i64, default to i64 MAX if conversion fail + self.seek(SeekFrom::Current(i64::try_from(p).unwrap_or(i64::MAX)))?; + } + SeekFrom::End(p) => { + self.pos = self.max_addr; + self.seek(SeekFrom::Current(p))?; + } + SeekFrom::Current(p) => { + if p > 0 { + self.pos = self.pos.saturating_add(p.unsigned_abs()); + } else { + self.pos = self.pos.saturating_sub(p.unsigned_abs()); + } + if self.pos > self.max_addr { + self.pos = self.max_addr; + } + } + }; + Ok(self.pos) + } +} + +pub struct Memory { + drv: Rc>>, + addr_seek: AddressSeek, +} + +impl Memory { + pub fn new(drv: Rc>>) -> StdResult> { + Ok(Memory { + drv: drv.clone(), + addr_seek: AddressSeek { + pos: 0, + max_addr: drv.borrow().get_max_physical_addr()?, + }, + }) + } +} + +impl Read for Memory { + fn read(&mut self, buf: &mut [u8]) -> Result { + // amount of bytes we need to read + let mut read_remain: usize = buf.len(); + let mut bytes_read: usize = 0; + while read_remain > 0 { + // determine size of next chunk + let paddr = self.stream_position()?; + let frame = PageFrame::with_paddr(paddr); + // windows_len -> 4K or less, if offset in frame + let next_chunk_size = std::cmp::min(frame.window_len(), read_remain as u32) as usize; + let chunk_end = bytes_read + next_chunk_size; + // get chunk + let chunk = &mut buf[bytes_read..chunk_end]; + // read the frame + self.drv.borrow().read_frame(frame, chunk)?; + // advance pos + self.seek(SeekFrom::Current(next_chunk_size as i64))?; + // update loop vars + bytes_read += next_chunk_size; + read_remain -= next_chunk_size; + } + Ok(bytes_read as usize) + } +} + +impl Write for Memory { + fn write(&mut self, buf: &[u8]) -> Result { + let mut total_bytes_written: usize = 0; + for chunk in buf.chunks(PAGE_SIZE) { + let paddr = self.stream_position()?; + self.drv + .borrow() + .write_physical(paddr, chunk) + .map_err(|_| Error::new(ErrorKind::Other, "driver write failure"))?; + self.seek(SeekFrom::Current(chunk.len() as i64))?; + total_bytes_written += chunk.len(); + } + Ok(total_bytes_written) + } + + fn flush(&mut self) -> Result<()> { + // nothing to do + Ok(()) + } +} + +impl Seek for Memory { + fn seek(&mut self, pos: SeekFrom) -> Result { + self.addr_seek.seek(pos) + } +} + +pub struct PaddedMemory { + drv: Rc>>, + addr_seek: AddressSeek, +} + +impl PaddedMemory { + pub fn new(drv: Rc>>) -> StdResult> { + Ok(PaddedMemory { + drv: drv.clone(), + addr_seek: AddressSeek { + pos: 0, + max_addr: drv.borrow().get_max_physical_addr()?, + }, + }) + } +} + +// TODO: slight duplication +impl Read for PaddedMemory { + fn read(&mut self, buf: &mut [u8]) -> Result { + // amount of bytes we need to read + let mut read_remain: usize = buf.len(); + let mut bytes_read: usize = 0; + while read_remain > 0 { + // determine size of next chunk + let paddr = self.stream_position()?; + let frame = PageFrame::with_paddr(paddr); + // windows_len -> 4K or less, if offset in frame + let next_chunk_size = std::cmp::min(frame.window_len(), read_remain as u32) as usize; + let chunk_end = bytes_read + next_chunk_size; + // get chunk + let chunk = &mut buf[bytes_read..chunk_end]; + // read the frame + match self.drv.borrow().read_frame(frame, chunk) { + // handle non existing frames by padding + Err(ref e) if e.kind() == ErrorKind::NotFound => { + trace!("PaddedMemory: frame not found: {:X}", frame.number); + chunk.fill(0) + } + Err(e) => return Err(e), + _ => (), + }; + // advance pos + self.seek(SeekFrom::Current(next_chunk_size as i64))?; + // update loop vars + bytes_read += next_chunk_size; + read_remain -= next_chunk_size; + } + Ok(bytes_read as usize) + } +} + +impl Seek for PaddedMemory { + fn seek(&mut self, pos: SeekFrom) -> Result { + self.addr_seek.seek(pos) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + // Seek + #[test] + fn test_seek_start() -> Result<()> { + let max_addr: u64 = 1000; + let mut mock_introspectable = MockIntrospectable::new(); + mock_introspectable + .expect_get_max_physical_addr() + .returning(move || Ok(max_addr)); + let mut memory = Memory::new(Rc::new(RefCell::new(Box::new(mock_introspectable)))).unwrap(); + // seek 0 doesn't move position + assert_eq!(0, memory.seek(SeekFrom::Start(0))?); + // seek beyond max_addr saturates at max_addr + assert_eq!(max_addr, memory.seek(SeekFrom::Start(max_addr + 1))?); + Ok(()) + } + + #[test] + fn test_seek_end() -> Result<()> { + let max_addr: u64 = 1000; + let mut mock_introspectable = MockIntrospectable::new(); + mock_introspectable + .expect_get_max_physical_addr() + .returning(move || Ok(max_addr)); + let mut memory = Memory::new(Rc::new(RefCell::new(Box::new(mock_introspectable)))).unwrap(); + // seek end should move to max_addr + assert_eq!(max_addr, memory.seek(SeekFrom::End(0))?); + // seek end beyond should saturates to max_addr + assert_eq!(max_addr, memory.seek(SeekFrom::End(50))?); + // seek end with a negative number should update the position + assert_eq!(max_addr - 50, memory.seek(SeekFrom::End(-50))?); + // seek below 0 should saturate at 0 + assert_eq!(0, memory.seek(SeekFrom::End(i64::MIN))?); + Ok(()) + } + + #[test] + fn test_seek_current() -> Result<()> { + let max_addr: u64 = 1000; + let mut mock_introspectable = MockIntrospectable::new(); + mock_introspectable + .expect_get_max_physical_addr() + .returning(move || Ok(max_addr)); + let mut memory = Memory::new(Rc::new(RefCell::new(Box::new(mock_introspectable)))).unwrap(); + // seek current below 0 should saturate at 0 + assert_eq!(0, memory.seek(SeekFrom::Current(-5))?); + // seek current should move the cursor + assert_eq!(50, memory.seek(SeekFrom::Current(50))?); + assert_eq!(49, memory.seek(SeekFrom::Current(-1))?); + assert_eq!(59, memory.seek(SeekFrom::Current(10))?); + // seek current beyond max_addr should saturate at max_addr + assert_eq!(max_addr, memory.seek(SeekFrom::Current(i64::MAX))?); + Ok(()) + } +} diff --git a/src/microvmi.rs b/src/microvmi.rs new file mode 100644 index 00000000..9e28be81 --- /dev/null +++ b/src/microvmi.rs @@ -0,0 +1,207 @@ +//! This module defines the Microvmi struct which should be the entrypoint to interact with libmicrovmi + +use enum_iterator::IntoEnumIterator; +#[cfg(feature = "kvm")] +use kvmi::create_kvmi; + +use crate::api::{ + Access, DriverInitParam, DriverType, Event, EventReplyType, InterceptType, Introspectable, + Registers, +}; +#[cfg(feature = "kvm")] +use crate::driver::kvm::Kvm; +#[cfg(feature = "virtualbox")] +use crate::driver::virtualbox::VBox; +#[cfg(feature = "xen")] +use crate::driver::xen::Xen; +use crate::errors::MicrovmiError; +use crate::memory::Memory; +use crate::memory::PaddedMemory; +use std::cell::RefCell; +use std::error::Error; +use std::rc::Rc; + +/// Main struct to interact with the library +pub struct Microvmi { + // runtime VMI driver + pub(crate) drv: Rc>>, + /// Exposes the physical memory as a file-like interface + pub memory: Memory, + /// Exposes the physical memory as a file-like interface, with padding + pub padded_memory: PaddedMemory, +} + +impl Microvmi { + /// Initializes a new Microvmi instance + /// + /// # Arguments + /// + /// * `domain_name` - The domain name + /// * `driver_type` - The driver type to initialize. None will attempt to initialize every driver avaiable + /// * `init_option` - Initialization parameters for the driver. + /// + /// # Example + /// + /// ```no_run + /// use crate::microvmi::microvmi::Microvmi; + /// use crate::microvmi::api::{DriverType, DriverInitParam}; + /// Microvmi::new("win10", None, None); + /// Microvmi::new("win10", Some(DriverType::Xen), None); + /// Microvmi::new("win10", Some(DriverType::KVM), Some(DriverInitParam::KVMiSocket("/tmp/introspector".to_string()))); + /// ``` + pub fn new( + domain_name: &str, + driver_type: Option, + init_option: Option, + ) -> Result { + info!("Microvmi init"); + let drv = match driver_type { + None => { + // for each possible DriverType + let mut driver: Option> = None; + for drv_type in DriverType::into_enum_iter() { + // try to init + match init_driver(domain_name, drv_type, init_option.clone()) { + Ok(drv) => { + driver = Some(drv); + break; + } + Err(e) => { + debug!("{:?} driver initialization failed: {}", drv_type, e); + continue; + } + } + } + driver.ok_or(MicrovmiError::NoDriverAvailable)? + } + Some(drv_type) => init_driver(domain_name, drv_type, init_option)?, + }; + let ref_drv = Rc::new(RefCell::new(drv)); + Ok(Microvmi { + drv: ref_drv.clone(), + memory: Memory::new(ref_drv.clone())?, + padded_memory: PaddedMemory::new(ref_drv.clone())?, + }) + } + + /// Retrieve the number of VCPUs. + pub fn get_vcpu_count(&self) -> Result> { + self.drv.borrow().get_vcpu_count() + } + + /// Get the maximum physical address + /// + /// Returns maximum physical address in 64 bit unsigned integer format. + /// + pub fn get_max_physical_addr(&self) -> Result> { + self.drv.borrow().get_max_physical_addr() + } + + /// Read register values + /// + /// # Arguments + /// * 'vcpu' - vcpu id for which the value of registers are to be dumped as the argument + /// + pub fn read_registers(&self, vcpu: u16) -> Result> { + self.drv.borrow().read_registers(vcpu) + } + + ///get page access + /// + /// # Arguments + /// * 'paddr' - physical address of the page whose access we want to know. + /// + pub fn get_page_access(&self, paddr: u64) -> Result> { + self.drv.borrow().get_page_access(paddr) + } + + ///set page access + /// + /// # Arguments + /// * 'paddr' - physical address of the page whose access we want to set + /// * 'access' - access flags to be set on the given page + /// + pub fn set_page_access(&self, paddr: u64, access: Access) -> Result<(), Box> { + self.drv.borrow().set_page_access(paddr, access) + } + + /// Pauses the VM + pub fn pause(&mut self) -> Result<(), Box> { + self.drv.borrow_mut().resume() + } + + /// Resumes the VM + pub fn resume(&mut self) -> Result<(), Box> { + self.drv.borrow_mut().resume() + } + + /// Return the concrete DriverType + pub fn get_driver_type(&self) -> DriverType { + self.drv.borrow().get_driver_type() + } + + /// Used to enable/disable an event interception + /// + /// # Arguments + /// * 'vcpu' - vcpu id for which we are to enable/disable intercept monitoring + /// * 'intercept_type' - to specify event type for which to raise flag + /// * 'enabled' - flag to specify whether to enable/disable event monitoring + /// + pub fn toggle_intercept( + &mut self, + vcpu: u16, + intercept_type: InterceptType, + enabled: bool, + ) -> Result<(), Box> { + self.drv + .borrow_mut() + .toggle_intercept(vcpu, intercept_type, enabled) + } + + /// Listen and return the next event, or None + /// + /// # Arguments + /// * 'timeout' - Time for which it will wait for a new event + /// + pub fn listen(&mut self, timeout: u32) -> Result, Box> { + self.drv.borrow_mut().listen(timeout) + } + + /// Send reply corresponding to the current event being popped + /// + /// # Arguments + /// * 'event' + /// * 'reply_type' + /// + pub fn reply_event( + &mut self, + event: Event, + reply_type: EventReplyType, + ) -> Result<(), Box> { + self.drv.borrow_mut().reply_event(event, reply_type) + } +} + +/// Initialize a given driver type +/// return None if the requested driver has not been compiled in libmicrovmi +fn init_driver( + _domain_name: &str, + driver_type: DriverType, + _init_option: Option, +) -> Result, MicrovmiError> { + #[allow(clippy::match_single_binding)] + match driver_type { + #[cfg(feature = "kvm")] + DriverType::KVM => Ok(Box::new(Kvm::new( + _domain_name, + create_kvmi(), + _init_option, + )?)), + #[cfg(feature = "virtualbox")] + DriverType::VirtualBox => Ok(Box::new(VBox::new(_domain_name, _init_option)?)), + #[cfg(feature = "xen")] + DriverType::Xen => Ok(Box::new(Xen::new(_domain_name, _init_option)?)), + #[allow(unreachable_patterns)] + _ => Err(MicrovmiError::DriverNotCompiled(driver_type)), + } +}