From 7958af31a1a6c7fe9b0336555b78a65af9e7e784 Mon Sep 17 00:00:00 2001 From: Mathieu Tarral Date: Wed, 14 Apr 2021 15:59:40 +0200 Subject: [PATCH 01/33] add Memory impl --- src/capi.rs | 7 +-- src/lib.rs | 67 +---------------------------- src/memory.rs | 94 ++++++++++++++++++++++++++++++++++++++++ src/microvmi.rs | 112 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 212 insertions(+), 68 deletions(-) create mode 100644 src/memory.rs create mode 100644 src/microvmi.rs diff --git a/src/capi.rs b/src/capi.rs index e1869521..6757fceb 100644 --- a/src/capi.rs +++ b/src/capi.rs @@ -1,5 +1,5 @@ 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; @@ -55,8 +55,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)) diff --git a/src/lib.rs b/src/lib.rs index 799183f7..56d4b89a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,73 +6,10 @@ pub mod api; pub mod capi; mod driver; pub mod errors; +mod memory; +pub mod 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..b1b1e146 --- /dev/null +++ b/src/memory.rs @@ -0,0 +1,94 @@ +//! This module defines a physical memory implementation behaving like a File-IO + +use crate::microvmi::Microvmi; +use std::convert::TryFrom; +use std::io::Error; +use std::io::{ErrorKind, Result}; +use std::io::{Read, Seek, SeekFrom, Write}; + +const PAGE_SIZE: usize = 4096; + +impl Read for Microvmi { + fn read(&mut self, buf: &mut [u8]) -> Result { + let mut total_bytes_read: usize = 0; + for chunk in buf.chunks_mut(PAGE_SIZE) { + let mut bytes_read: u64 = 0; + let paddr = self.stream_position()?; + self.drv + .read_physical(paddr, chunk, &mut bytes_read) + .map_err(|_| Error::new(ErrorKind::Other, "driver read failure"))?; + // advance pos from bytes_read + self.seek(SeekFrom::Current(bytes_read as i64))?; + // add to total + total_bytes_read += bytes_read as usize; + } + Ok(total_bytes_read) + } + + /// Read the exact number of bytes required to fill buf. + /// + /// Read the physical memory and add padding to fill the blanks + fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> { + let mut bytes_read: u64 = 0; + for chunk in buf.chunks_mut(PAGE_SIZE) { + let paddr = self.stream_position()?; + self.drv + .read_physical(paddr, chunk, &mut bytes_read) + .unwrap_or_else(|_| chunk.fill(0)); + self.seek(SeekFrom::Current(chunk.len() as i64))?; + } + Ok(()) + } +} + +impl Write for Microvmi { + 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 + .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 Microvmi { + fn seek(&mut self, pos: SeekFrom) -> Result { + match pos { + SeekFrom::Start(p) => { + self.pos = self.pos.saturating_add(p); + if self.pos > self.max_addr { + self.pos = self.max_addr; + } + } + SeekFrom::End(p) => { + if p > 0 { + // seeking beyond the end of physical address space is not allowed + self.pos = self.max_addr; + } else { + self.pos = self.pos.saturating_sub(u64::try_from(-p).unwrap()); + } + } + 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) + } +} diff --git a/src/microvmi.rs b/src/microvmi.rs new file mode 100644 index 00000000..1b1aed71 --- /dev/null +++ b/src/microvmi.rs @@ -0,0 +1,112 @@ +//! 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::Introspectable; +use crate::api::{DriverInitParam, DriverType}; +use crate::errors::MicrovmiError; +#[cfg(feature = "kvm")] +use driver::kvm::Kvm; +#[cfg(feature = "virtualbox")] +use driver::virtualbox::VBox; +#[cfg(feature = "xen")] +use driver::xen::Xen; + +/// Main struct to interact with the library +pub struct Microvmi { + // runtime VMI driver + pub(crate) drv: Box, + // position in the physical memory (seek) + pub(crate) pos: u64, + // maximum physical address + pub(crate) max_addr: u64, +} + +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 + /// + /// ``` + /// use self::microvmi::Microvmi; + /// use crate::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"); + 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(drv) => { + return { + let max_addr = drv.get_max_physical_addr()?; + Ok(Microvmi { + drv, + pos: 0, + max_addr, + }) + } + } + Err(e) => { + debug!("{:?} driver initialization failed: {}", drv_type, e); + continue; + } + } + } + Err(MicrovmiError::NoDriverAvailable) + } + Some(drv_type) => { + let drv = init_driver(domain_name, drv_type, init_option)?; + return { + let max_addr = drv.get_max_physical_addr()?; + Ok(Microvmi { + drv, + pos: 0, + max_addr, + }) + }; + } + } + } +} + +/// 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)), + } +} From 35400e6046be12f2f86604e594603de20e82c592 Mon Sep 17 00:00:00 2001 From: Mathieu Tarral Date: Wed, 14 Apr 2021 16:38:40 +0200 Subject: [PATCH 02/33] memory: add unit tests for Seek --- src/api.rs | 3 ++ src/memory.rs | 81 ++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 74 insertions(+), 10 deletions(-) diff --git a/src/api.rs b/src/api.rs index 096d29c5..8d7fbe0b 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,3 +1,5 @@ +#[cfg(test)] +use mockall::*; use std::convert::TryInto; use std::error::Error; use std::ffi::{CStr, IntoStringError}; @@ -172,6 +174,7 @@ pub enum Registers { pub const PAGE_SHIFT: u32 = 12; pub const PAGE_SIZE: u32 = 4096; +#[cfg_attr(test, automock)] pub trait Introspectable { /// Retrieve the number of VCPUs. /// diff --git a/src/memory.rs b/src/memory.rs index b1b1e146..fd8d840c 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -1,5 +1,7 @@ //! This module defines a physical memory implementation behaving like a File-IO +#[cfg(test)] +use crate::api::MockIntrospectable; use crate::microvmi::Microvmi; use std::convert::TryFrom; use std::io::Error; @@ -65,18 +67,13 @@ impl Seek for Microvmi { fn seek(&mut self, pos: SeekFrom) -> Result { match pos { SeekFrom::Start(p) => { - self.pos = self.pos.saturating_add(p); - if self.pos > self.max_addr { - self.pos = self.max_addr; - } + 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) => { - if p > 0 { - // seeking beyond the end of physical address space is not allowed - self.pos = self.max_addr; - } else { - self.pos = self.pos.saturating_sub(u64::try_from(-p).unwrap()); - } + self.pos = self.max_addr; + self.seek(SeekFrom::Current(p))?; } SeekFrom::Current(p) => { if p > 0 { @@ -92,3 +89,67 @@ impl Seek for Microvmi { Ok(self.pos) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_seek_start() -> Result<()> { + // create microvmi with mock driver + let mock_introspectable = MockIntrospectable::new(); + let max_addr: u64 = 1000; + let mut microvmi = Microvmi { + drv: Box::new(mock_introspectable), + pos: 0, + max_addr, + }; + // seek 0 doesn't move position + assert_eq!(0, microvmi.seek(SeekFrom::Start(0))?); + // seek beyond max_addr saturates at max_addr + assert_eq!(max_addr, microvmi.seek(SeekFrom::Start(max_addr + 1))?); + Ok(()) + } + + #[test] + fn test_seek_end() -> Result<()> { + // create microvmi with mock driver + let mock_introspectable = MockIntrospectable::new(); + let max_addr: u64 = 1000; + let mut microvmi = Microvmi { + drv: Box::new(mock_introspectable), + pos: 0, + max_addr, + }; + // seek end should move to max_addr + assert_eq!(max_addr, microvmi.seek(SeekFrom::End(0))?); + // seek end beyond should saturates to max_addr + assert_eq!(max_addr, microvmi.seek(SeekFrom::End(50))?); + // seek end with a negative number should update the position + assert_eq!(max_addr - 50, microvmi.seek(SeekFrom::End(-50))?); + // seek below 0 should saturate at 0 + assert_eq!(0, microvmi.seek(SeekFrom::End(i64::MIN))?); + Ok(()) + } + + #[test] + fn test_seek_current() -> Result<()> { + // create microvmi with mock driver + let mock_introspectable = MockIntrospectable::new(); + let max_addr: u64 = 1000; + let mut microvmi = Microvmi { + drv: Box::new(mock_introspectable), + pos: 0, + max_addr, + }; + // seek current below 0 should saturate at 0 + assert_eq!(0, microvmi.seek(SeekFrom::Current(-5))?); + // seek current should move the cursor + assert_eq!(50, microvmi.seek(SeekFrom::Current(50))?); + assert_eq!(49, microvmi.seek(SeekFrom::Current(-1))?); + assert_eq!(59, microvmi.seek(SeekFrom::Current(10))?); + // seek current beyond max_addr should saturate at max_addr + assert_eq!(max_addr, microvmi.seek(SeekFrom::Current(i64::MAX))?); + Ok(()) + } +} From 823c1a73152c4c4931f1b5c1b61576bbc1f0b312 Mon Sep 17 00:00:00 2001 From: Mathieu Tarral Date: Mon, 19 Apr 2021 10:08:17 +0200 Subject: [PATCH 03/33] microvmi: expose pause, resume and get_max_physical_addr --- examples/mem-dump.rs | 15 ++++++++------- src/lib.rs | 1 + src/microvmi.rs | 13 +++++++++++++ 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/examples/mem-dump.rs b/examples/mem-dump.rs index 71cd62be..11880b14 100644 --- a/examples/mem-dump.rs +++ b/examples/mem-dump.rs @@ -1,12 +1,13 @@ 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; @@ -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"); @@ -85,8 +86,8 @@ fn main() { // 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)); + drv.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"); diff --git a/src/lib.rs b/src/lib.rs index 56d4b89a..9bab3c00 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,7 @@ mod driver; pub mod errors; mod memory; pub mod microvmi; +pub use microvmi::Microvmi; #[macro_use] extern crate log; diff --git a/src/microvmi.rs b/src/microvmi.rs index 1b1aed71..653c6f7c 100644 --- a/src/microvmi.rs +++ b/src/microvmi.rs @@ -13,6 +13,7 @@ use driver::kvm::Kvm; use driver::virtualbox::VBox; #[cfg(feature = "xen")] use driver::xen::Xen; +use std::error::Error; /// Main struct to interact with the library pub struct Microvmi { @@ -85,6 +86,18 @@ impl Microvmi { } } } + + pub fn get_max_physical_addr(&self) -> Result> { + Ok(self.drv.get_max_physical_addr()?) + } + + pub fn pause(&mut self) -> Result<(), Box> { + Ok(self.drv.pause()?) + } + + pub fn resume(&mut self) -> Result<(), Box> { + Ok(self.drv.resume()?) + } } /// Initialize a given driver type From 52ccd6ddd9ec17c3eca8dcd3862e5257540dfb3f Mon Sep 17 00:00:00 2001 From: Mathieu Tarral Date: Mon, 19 Apr 2021 10:11:29 +0200 Subject: [PATCH 04/33] microvmi: remove max_addr field --- src/memory.rs | 11 ++++++++--- src/microvmi.rs | 22 ++-------------------- 2 files changed, 10 insertions(+), 23 deletions(-) diff --git a/src/memory.rs b/src/memory.rs index fd8d840c..50520a3f 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -72,7 +72,9 @@ impl Seek for Microvmi { self.seek(SeekFrom::Current(i64::try_from(p).unwrap_or(i64::MAX)))?; } SeekFrom::End(p) => { - self.pos = self.max_addr; + self.pos = self.drv.get_max_physical_addr().map_err(|_| { + Error::new(ErrorKind::Other, "Failed to get maximum physical address") + })?; self.seek(SeekFrom::Current(p))?; } SeekFrom::Current(p) => { @@ -81,8 +83,11 @@ impl Seek for Microvmi { } else { self.pos = self.pos.saturating_sub(p.unsigned_abs()); } - if self.pos > self.max_addr { - self.pos = self.max_addr; + let max_addr = self.drv.get_max_physical_addr().map_err(|_| { + Error::new(ErrorKind::Other, "Failed to get maximum physical address") + })?; + if self.pos > max_addr { + self.pos = max_addr; } } }; diff --git a/src/microvmi.rs b/src/microvmi.rs index 653c6f7c..85416274 100644 --- a/src/microvmi.rs +++ b/src/microvmi.rs @@ -21,8 +21,6 @@ pub struct Microvmi { pub(crate) drv: Box, // position in the physical memory (seek) pub(crate) pos: u64, - // maximum physical address - pub(crate) max_addr: u64, } impl Microvmi { @@ -55,16 +53,7 @@ impl Microvmi { for drv_type in DriverType::into_enum_iter() { // try to init match init_driver(domain_name, drv_type, init_option.clone()) { - Ok(drv) => { - return { - let max_addr = drv.get_max_physical_addr()?; - Ok(Microvmi { - drv, - pos: 0, - max_addr, - }) - } - } + Ok(drv) => return { Ok(Microvmi { drv, pos: 0 }) }, Err(e) => { debug!("{:?} driver initialization failed: {}", drv_type, e); continue; @@ -75,14 +64,7 @@ impl Microvmi { } Some(drv_type) => { let drv = init_driver(domain_name, drv_type, init_option)?; - return { - let max_addr = drv.get_max_physical_addr()?; - Ok(Microvmi { - drv, - pos: 0, - max_addr, - }) - }; + return { Ok(Microvmi { drv, pos: 0 }) }; } } } From eadbcb494ef086b3335252c52f0f854eb08290ec Mon Sep 17 00:00:00 2001 From: Mathieu Tarral Date: Mon, 19 Apr 2021 10:12:43 +0200 Subject: [PATCH 05/33] microvmi: clippy fixes --- src/microvmi.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/microvmi.rs b/src/microvmi.rs index 85416274..1986e016 100644 --- a/src/microvmi.rs +++ b/src/microvmi.rs @@ -53,7 +53,7 @@ impl Microvmi { for drv_type in DriverType::into_enum_iter() { // try to init match init_driver(domain_name, drv_type, init_option.clone()) { - Ok(drv) => return { Ok(Microvmi { drv, pos: 0 }) }, + Ok(drv) => return Ok(Microvmi { drv, pos: 0 }), Err(e) => { debug!("{:?} driver initialization failed: {}", drv_type, e); continue; @@ -64,21 +64,21 @@ impl Microvmi { } Some(drv_type) => { let drv = init_driver(domain_name, drv_type, init_option)?; - return { Ok(Microvmi { drv, pos: 0 }) }; + Ok(Microvmi { drv, pos: 0 }) } } } pub fn get_max_physical_addr(&self) -> Result> { - Ok(self.drv.get_max_physical_addr()?) + self.drv.get_max_physical_addr() } pub fn pause(&mut self) -> Result<(), Box> { - Ok(self.drv.pause()?) + self.drv.pause() } pub fn resume(&mut self) -> Result<(), Box> { - Ok(self.drv.resume()?) + self.drv.resume() } } From eafe09b291d3a5827895061e456935f0f0d13382 Mon Sep 17 00:00:00 2001 From: Mathieu Tarral Date: Mon, 19 Apr 2021 10:28:19 +0200 Subject: [PATCH 06/33] microvmi: fix driver import --- src/microvmi.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/microvmi.rs b/src/microvmi.rs index 1986e016..5c817209 100644 --- a/src/microvmi.rs +++ b/src/microvmi.rs @@ -6,13 +6,13 @@ use kvmi::create_kvmi; use crate::api::Introspectable; use crate::api::{DriverInitParam, DriverType}; -use crate::errors::MicrovmiError; #[cfg(feature = "kvm")] -use driver::kvm::Kvm; +use crate::driver::kvm::Kvm; #[cfg(feature = "virtualbox")] -use driver::virtualbox::VBox; +use crate::driver::virtualbox::VBox; #[cfg(feature = "xen")] -use driver::xen::Xen; +use crate::driver::xen::Xen; +use crate::errors::MicrovmiError; use std::error::Error; /// Main struct to interact with the library From b4c646d3ba0cbeea4b1b8ca80ac835c696164ad4 Mon Sep 17 00:00:00 2001 From: Mathieu Tarral Date: Mon, 19 Apr 2021 10:45:27 +0200 Subject: [PATCH 07/33] memory: fix performance issues in Seek --- src/memory.rs | 11 +++-------- src/microvmi.rs | 27 ++++++++++++++++++--------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/memory.rs b/src/memory.rs index 50520a3f..fd8d840c 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -72,9 +72,7 @@ impl Seek for Microvmi { self.seek(SeekFrom::Current(i64::try_from(p).unwrap_or(i64::MAX)))?; } SeekFrom::End(p) => { - self.pos = self.drv.get_max_physical_addr().map_err(|_| { - Error::new(ErrorKind::Other, "Failed to get maximum physical address") - })?; + self.pos = self.max_addr; self.seek(SeekFrom::Current(p))?; } SeekFrom::Current(p) => { @@ -83,11 +81,8 @@ impl Seek for Microvmi { } else { self.pos = self.pos.saturating_sub(p.unsigned_abs()); } - let max_addr = self.drv.get_max_physical_addr().map_err(|_| { - Error::new(ErrorKind::Other, "Failed to get maximum physical address") - })?; - if self.pos > max_addr { - self.pos = max_addr; + if self.pos > self.max_addr { + self.pos = self.max_addr; } } }; diff --git a/src/microvmi.rs b/src/microvmi.rs index 5c817209..b8c25fdc 100644 --- a/src/microvmi.rs +++ b/src/microvmi.rs @@ -21,6 +21,8 @@ pub struct Microvmi { pub(crate) drv: Box, // position in the physical memory (seek) pub(crate) pos: u64, + // maximum physical address + pub(crate) max_addr: u64, } impl Microvmi { @@ -47,30 +49,37 @@ impl Microvmi { init_option: Option, ) -> Result { info!("Microvmi init"); - match driver_type { + 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) => return Ok(Microvmi { drv, pos: 0 }), + Ok(drv) => { + driver = Some(drv); + break; + } Err(e) => { debug!("{:?} driver initialization failed: {}", drv_type, e); continue; } } } - Err(MicrovmiError::NoDriverAvailable) - } - Some(drv_type) => { - let drv = init_driver(domain_name, drv_type, init_option)?; - Ok(Microvmi { drv, pos: 0 }) + driver.ok_or(MicrovmiError::NoDriverAvailable)? } - } + Some(drv_type) => init_driver(domain_name, drv_type, init_option)?, + }; + let max_addr = drv.get_max_physical_addr()?; + Ok(Microvmi { + drv, + pos: 0, + max_addr, + }) } pub fn get_max_physical_addr(&self) -> Result> { - self.drv.get_max_physical_addr() + Ok(self.max_addr) } pub fn pause(&mut self) -> Result<(), Box> { From e3c45a855fe2fe944caed519e88c76dce1ed5461 Mon Sep 17 00:00:00 2001 From: Mathieu Tarral Date: Mon, 19 Apr 2021 11:02:57 +0200 Subject: [PATCH 08/33] mem-dump: update buffer size --- examples/mem-dump.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/mem-dump.rs b/examples/mem-dump.rs index 11880b14..4dfd388e 100644 --- a/examples/mem-dump.rs +++ b/examples/mem-dump.rs @@ -9,7 +9,7 @@ use log::trace; 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!()) @@ -77,14 +77,14 @@ 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 buffer: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]; let mut _bytes_read = 0; drv.read_exact(&mut buffer) .expect(&*format!("Failed to read memory at {:#X}", cur_addr)); @@ -93,7 +93,7 @@ fn main() { .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!( From c5a7e1d4816126691de6b6435131f0aec6e3ce22 Mon Sep 17 00:00:00 2001 From: Mathieu Tarral Date: Mon, 19 Apr 2021 13:09:06 +0200 Subject: [PATCH 09/33] memory: refcell impl --- src/memory.rs | 44 ++++++++++++++++++++++++++------------------ src/microvmi.rs | 23 +++++++++++------------ 2 files changed, 37 insertions(+), 30 deletions(-) diff --git a/src/memory.rs b/src/memory.rs index fd8d840c..212e05eb 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -1,22 +1,44 @@ //! This module defines a physical memory implementation behaving like a File-IO +use crate::api::Introspectable; #[cfg(test)] use crate::api::MockIntrospectable; use crate::microvmi::Microvmi; +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; -impl Read for Microvmi { +pub struct Memory { + drv: Rc>>, + pos: u64, + max_addr: u64, +} + +impl Memory { + pub fn new(drv: Rc>>) -> StdResult> { + Ok(Memory { + drv: drv.clone(), + pos: 0, + max_addr: drv.borrow().get_max_physical_addr()?, + }) + } +} + +impl Read for Memory { fn read(&mut self, buf: &mut [u8]) -> Result { let mut total_bytes_read: usize = 0; for chunk in buf.chunks_mut(PAGE_SIZE) { let mut bytes_read: u64 = 0; let paddr = self.stream_position()?; self.drv + .borrow() .read_physical(paddr, chunk, &mut bytes_read) .map_err(|_| Error::new(ErrorKind::Other, "driver read failure"))?; // advance pos from bytes_read @@ -26,29 +48,15 @@ impl Read for Microvmi { } Ok(total_bytes_read) } - - /// Read the exact number of bytes required to fill buf. - /// - /// Read the physical memory and add padding to fill the blanks - fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> { - let mut bytes_read: u64 = 0; - for chunk in buf.chunks_mut(PAGE_SIZE) { - let paddr = self.stream_position()?; - self.drv - .read_physical(paddr, chunk, &mut bytes_read) - .unwrap_or_else(|_| chunk.fill(0)); - self.seek(SeekFrom::Current(chunk.len() as i64))?; - } - Ok(()) - } } -impl Write for Microvmi { +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))?; @@ -63,7 +71,7 @@ impl Write for Microvmi { } } -impl Seek for Microvmi { +impl Seek for Memory { fn seek(&mut self, pos: SeekFrom) -> Result { match pos { SeekFrom::Start(p) => { diff --git a/src/microvmi.rs b/src/microvmi.rs index b8c25fdc..5bbda8b5 100644 --- a/src/microvmi.rs +++ b/src/microvmi.rs @@ -13,16 +13,16 @@ use crate::driver::virtualbox::VBox; #[cfg(feature = "xen")] use crate::driver::xen::Xen; use crate::errors::MicrovmiError; +use crate::memory::Memory; +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: Box, - // position in the physical memory (seek) - pub(crate) pos: u64, - // maximum physical address - pub(crate) max_addr: u64, + pub(crate) drv: Rc>>, + pub memory: Memory, } impl Microvmi { @@ -70,24 +70,23 @@ impl Microvmi { } Some(drv_type) => init_driver(domain_name, drv_type, init_option)?, }; - let max_addr = drv.get_max_physical_addr()?; + let ref_drv = Rc::new(RefCell::new(drv)); Ok(Microvmi { - drv, - pos: 0, - max_addr, + drv: ref_drv.clone(), + memory: Memory::new(ref_drv.clone())?, }) } pub fn get_max_physical_addr(&self) -> Result> { - Ok(self.max_addr) + self.drv.borrow().get_max_physical_addr() } pub fn pause(&mut self) -> Result<(), Box> { - self.drv.pause() + self.drv.borrow_mut().resume() } pub fn resume(&mut self) -> Result<(), Box> { - self.drv.resume() + self.drv.borrow_mut().resume() } } From f1dd5aed3c8c1b07f3c004646be64fcc950a3279 Mon Sep 17 00:00:00 2001 From: Mathieu Tarral Date: Mon, 19 Apr 2021 13:38:50 +0200 Subject: [PATCH 10/33] memory: add PaddedMemory --- examples/mem-dump.rs | 5 ++-- src/memory.rs | 59 ++++++++++++++++++++++++++++++++++++++++++++ src/microvmi.rs | 3 +++ 3 files changed, 65 insertions(+), 2 deletions(-) diff --git a/examples/mem-dump.rs b/examples/mem-dump.rs index 4dfd388e..8cac35db 100644 --- a/examples/mem-dump.rs +++ b/examples/mem-dump.rs @@ -8,6 +8,7 @@ use log::trace; use microvmi::api::DriverInitParam; use microvmi::Microvmi; +use std::io; const BUFFER_SIZE: usize = 64535; // 64K @@ -85,8 +86,8 @@ fn main() { ); // reset buffer each loop let mut buffer: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]; - let mut _bytes_read = 0; - drv.read_exact(&mut buffer) + drv.padded_memory + .read(&mut buffer) .expect(&*format!("Failed to read memory at {:#X}", cur_addr)); dump_file .write_all(&buffer) diff --git a/src/memory.rs b/src/memory.rs index 212e05eb..ac1cd5c7 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -98,6 +98,65 @@ impl Seek for Memory { } } +pub struct PaddedMemory { + drv: Rc>>, + pos: u64, + max_addr: u64, +} + +impl PaddedMemory { + pub fn new(drv: Rc>>) -> StdResult> { + Ok(PaddedMemory { + drv: drv.clone(), + pos: 0, + max_addr: drv.borrow().get_max_physical_addr()?, + }) + } +} + +impl Read for PaddedMemory { + fn read(&mut self, buf: &mut [u8]) -> Result { + for chunk in buf.chunks_mut(PAGE_SIZE) { + let mut bytes_read: u64 = 0; + let paddr = self.stream_position()?; + self.drv + .borrow() + .read_physical(paddr, chunk, &mut bytes_read) + .unwrap_or_else(|_| chunk.fill(0)); + // advance pos + self.seek(SeekFrom::Current(chunk.len() as i64))?; + } + Ok(buf.len()) + } +} + +impl Seek for PaddedMemory { + 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) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/microvmi.rs b/src/microvmi.rs index 5bbda8b5..54b5dd19 100644 --- a/src/microvmi.rs +++ b/src/microvmi.rs @@ -14,6 +14,7 @@ use crate::driver::virtualbox::VBox; 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; @@ -23,6 +24,7 @@ pub struct Microvmi { // runtime VMI driver pub(crate) drv: Rc>>, pub memory: Memory, + pub padded_memory: PaddedMemory, } impl Microvmi { @@ -74,6 +76,7 @@ impl Microvmi { Ok(Microvmi { drv: ref_drv.clone(), memory: Memory::new(ref_drv.clone())?, + padded_memory: PaddedMemory::new(ref_drv.clone())?, }) } From a92de1b4667bdeaa13194c49de8983f0bc43ea84 Mon Sep 17 00:00:00 2001 From: Mathieu Tarral Date: Mon, 19 Apr 2021 14:26:41 +0200 Subject: [PATCH 11/33] memory: fix tests --- src/memory.rs | 43 +++++++++++++++++-------------------------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/src/memory.rs b/src/memory.rs index ac1cd5c7..349c91da 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -166,15 +166,12 @@ mod tests { // create microvmi with mock driver let mock_introspectable = MockIntrospectable::new(); let max_addr: u64 = 1000; - let mut microvmi = Microvmi { - drv: Box::new(mock_introspectable), - pos: 0, - max_addr, - }; + let drv = Rc::new(RefCell::new(Box::new(mock_introspectable))); + let mut memory = Memory::new(drv).unwrap(); // seek 0 doesn't move position - assert_eq!(0, microvmi.seek(SeekFrom::Start(0))?); + assert_eq!(0, memory.seek(SeekFrom::Start(0))?); // seek beyond max_addr saturates at max_addr - assert_eq!(max_addr, microvmi.seek(SeekFrom::Start(max_addr + 1))?); + assert_eq!(max_addr, memory.seek(SeekFrom::Start(max_addr + 1))?); Ok(()) } @@ -183,19 +180,16 @@ mod tests { // create microvmi with mock driver let mock_introspectable = MockIntrospectable::new(); let max_addr: u64 = 1000; - let mut microvmi = Microvmi { - drv: Box::new(mock_introspectable), - pos: 0, - max_addr, - }; + let drv = Rc::new(RefCell::new(Box::new(mock_introspectable))); + let mut memory = Memory::new(drv).unwrap(); // seek end should move to max_addr - assert_eq!(max_addr, microvmi.seek(SeekFrom::End(0))?); + assert_eq!(max_addr, memory.seek(SeekFrom::End(0))?); // seek end beyond should saturates to max_addr - assert_eq!(max_addr, microvmi.seek(SeekFrom::End(50))?); + assert_eq!(max_addr, memory.seek(SeekFrom::End(50))?); // seek end with a negative number should update the position - assert_eq!(max_addr - 50, microvmi.seek(SeekFrom::End(-50))?); + assert_eq!(max_addr - 50, memory.seek(SeekFrom::End(-50))?); // seek below 0 should saturate at 0 - assert_eq!(0, microvmi.seek(SeekFrom::End(i64::MIN))?); + assert_eq!(0, memory.seek(SeekFrom::End(i64::MIN))?); Ok(()) } @@ -204,19 +198,16 @@ mod tests { // create microvmi with mock driver let mock_introspectable = MockIntrospectable::new(); let max_addr: u64 = 1000; - let mut microvmi = Microvmi { - drv: Box::new(mock_introspectable), - pos: 0, - max_addr, - }; + let drv = Rc::new(RefCell::new(Box::new(mock_introspectable))); + let mut memory = Memory::new(drv).unwrap(); // seek current below 0 should saturate at 0 - assert_eq!(0, microvmi.seek(SeekFrom::Current(-5))?); + assert_eq!(0, memory.seek(SeekFrom::Current(-5))?); // seek current should move the cursor - assert_eq!(50, microvmi.seek(SeekFrom::Current(50))?); - assert_eq!(49, microvmi.seek(SeekFrom::Current(-1))?); - assert_eq!(59, microvmi.seek(SeekFrom::Current(10))?); + 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, microvmi.seek(SeekFrom::Current(i64::MAX))?); + assert_eq!(max_addr, memory.seek(SeekFrom::Current(i64::MAX))?); Ok(()) } } From e53d49b6b4640947d8da0b65a4af543820e3c818 Mon Sep 17 00:00:00 2001 From: Mathieu Tarral Date: Mon, 19 Apr 2021 14:26:50 +0200 Subject: [PATCH 12/33] capi: update with Microvmi struct --- src/capi.rs | 47 +++++++++++++++++++++++++++-------------------- src/microvmi.rs | 10 +++++++++- 2 files changed, 36 insertions(+), 21 deletions(-) diff --git a/src/capi.rs b/src/capi.rs index 6757fceb..df870da7 100644 --- a/src/capi.rs +++ b/src/capi.rs @@ -4,6 +4,7 @@ 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 @@ -78,15 +79,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)] @@ -98,19 +99,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 } @@ -122,7 +129,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 @@ -139,7 +146,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 @@ -153,12 +160,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/microvmi.rs b/src/microvmi.rs index 54b5dd19..037e06a2 100644 --- a/src/microvmi.rs +++ b/src/microvmi.rs @@ -4,8 +4,8 @@ use enum_iterator::IntoEnumIterator; #[cfg(feature = "kvm")] use kvmi::create_kvmi; -use crate::api::Introspectable; use crate::api::{DriverInitParam, DriverType}; +use crate::api::{Introspectable, Registers}; #[cfg(feature = "kvm")] use crate::driver::kvm::Kvm; #[cfg(feature = "virtualbox")] @@ -84,6 +84,10 @@ impl Microvmi { self.drv.borrow().get_max_physical_addr() } + pub fn read_registers(&self, vcpu: u16) -> Result> { + self.drv.borrow().read_registers(vcpu) + } + pub fn pause(&mut self) -> Result<(), Box> { self.drv.borrow_mut().resume() } @@ -91,6 +95,10 @@ impl Microvmi { pub fn resume(&mut self) -> Result<(), Box> { self.drv.borrow_mut().resume() } + + pub fn get_driver_type(&self) -> DriverType { + self.drv.borrow().get_driver_type() + } } /// Initialize a given driver type From aaaa5a4fb560329d11ce959a2a373d79849934b7 Mon Sep 17 00:00:00 2001 From: Mathieu Tarral Date: Mon, 19 Apr 2021 14:47:40 +0200 Subject: [PATCH 13/33] python: adapt read methods --- python/src/lib.rs | 34 ++++++++++++++++------------------ src/memory.rs | 1 - 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/python/src/lib.rs b/python/src/lib.rs index 386b73d9..ad31d2aa 100644 --- a/python/src/lib.rs +++ b/python/src/lib.rs @@ -7,8 +7,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 +75,7 @@ impl DriverInitParam { // compatible #[pyclass(unsendable)] struct MicrovmiExt { - driver: Box, + microvmi: Microvmi, } #[pymethods] @@ -120,9 +121,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 +135,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 +155,22 @@ 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() }; - let mut bytes_read: u64 = 0; // ignore read error - self.driver - .read_physical(paddr, mut_buf, &mut bytes_read) - .ok(); - bytes_read + self.microvmi.memory.seek(SeekFrom::Start(paddr))?; + let bytes_read = self.microvmi.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 +179,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/memory.rs b/src/memory.rs index 349c91da..5b4ae202 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -3,7 +3,6 @@ use crate::api::Introspectable; #[cfg(test)] use crate::api::MockIntrospectable; -use crate::microvmi::Microvmi; use std::cell::RefCell; use std::convert::TryFrom; use std::error::Error as StdError; From 3197da0f24e52c5d0cd67791a13b80ef48149e5f Mon Sep 17 00:00:00 2001 From: Mathieu Tarral Date: Fri, 23 Apr 2021 09:53:23 +0200 Subject: [PATCH 14/33] python: add padded read --- python/microvmi/memory.py | 15 +++++---------- python/src/lib.rs | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 10 deletions(-) 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 ad31d2aa..c280afa9 100644 --- a/python/src/lib.rs +++ b/python/src/lib.rs @@ -163,6 +163,43 @@ impl MicrovmiExt { 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.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.microvmi.pause().map_err(PyMicrovmiError::from)?) From bc6f49d3162aaceb4238adb19f27a6f6631b175c Mon Sep 17 00:00:00 2001 From: Mathieu Tarral Date: Fri, 23 Apr 2021 10:39:24 +0200 Subject: [PATCH 15/33] memory: fix seek unit tests --- src/memory.rs | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/memory.rs b/src/memory.rs index 5b4ae202..afc27d3b 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -160,13 +160,15 @@ impl Seek for PaddedMemory { mod tests { use super::*; + // Seek #[test] fn test_seek_start() -> Result<()> { - // create microvmi with mock driver - let mock_introspectable = MockIntrospectable::new(); let max_addr: u64 = 1000; - let drv = Rc::new(RefCell::new(Box::new(mock_introspectable))); - let mut memory = Memory::new(drv).unwrap(); + 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 @@ -176,11 +178,12 @@ mod tests { #[test] fn test_seek_end() -> Result<()> { - // create microvmi with mock driver - let mock_introspectable = MockIntrospectable::new(); let max_addr: u64 = 1000; - let drv = Rc::new(RefCell::new(Box::new(mock_introspectable))); - let mut memory = Memory::new(drv).unwrap(); + 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 @@ -194,11 +197,12 @@ mod tests { #[test] fn test_seek_current() -> Result<()> { - // create microvmi with mock driver - let mock_introspectable = MockIntrospectable::new(); let max_addr: u64 = 1000; - let drv = Rc::new(RefCell::new(Box::new(mock_introspectable))); - let mut memory = Memory::new(drv).unwrap(); + 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 From bb4bfa02474c54ad2f5866ee131df7d28b620e0e Mon Sep 17 00:00:00 2001 From: Mathieu Tarral Date: Fri, 23 Apr 2021 12:45:28 +0200 Subject: [PATCH 16/33] memory: add TODO --- src/memory.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/memory.rs b/src/memory.rs index afc27d3b..c11e0fa9 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -113,6 +113,7 @@ impl PaddedMemory { } } +// TODO: slight duplication impl Read for PaddedMemory { fn read(&mut self, buf: &mut [u8]) -> Result { for chunk in buf.chunks_mut(PAGE_SIZE) { @@ -129,6 +130,7 @@ impl Read for PaddedMemory { } } +// TODO: duplicated implementation impl Seek for PaddedMemory { fn seek(&mut self, pos: SeekFrom) -> Result { match pos { From 1408ae4a7484cf19d0bb46f8926762b4011aedae Mon Sep 17 00:00:00 2001 From: Mathieu Tarral Date: Fri, 23 Apr 2021 12:45:34 +0200 Subject: [PATCH 17/33] add doc --- src/capi.rs | 2 ++ src/errors.rs | 2 ++ src/microvmi.rs | 6 ++++-- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/capi.rs b/src/capi.rs index df870da7..ae3abf20 100644 --- a/src/capi.rs +++ b/src/capi.rs @@ -1,3 +1,5 @@ +//! This module defines the C API + use crate::api::{DriverInitParam, DriverType, Introspectable, Registers}; use crate::microvmi::Microvmi; use bitflags::_core::ptr::null_mut; 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/microvmi.rs b/src/microvmi.rs index 037e06a2..997775f2 100644 --- a/src/microvmi.rs +++ b/src/microvmi.rs @@ -23,7 +23,9 @@ use std::rc::Rc; 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, } @@ -39,8 +41,8 @@ impl Microvmi { /// # Example /// /// ``` - /// use self::microvmi::Microvmi; - /// use crate::api::{DriverType, DriverInitParam}; + /// 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()))); From 1efcd20c48f55f8b4a605e4852faec222fd672a1 Mon Sep 17 00:00:00 2001 From: Mathieu Tarral Date: Wed, 28 Apr 2021 13:38:38 +0200 Subject: [PATCH 18/33] examples: fix cr-events compilation --- examples/cr-events.rs | 9 +++++---- src/microvmi.rs | 41 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 5 deletions(-) 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/src/microvmi.rs b/src/microvmi.rs index 997775f2..dacc4eec 100644 --- a/src/microvmi.rs +++ b/src/microvmi.rs @@ -4,7 +4,7 @@ use enum_iterator::IntoEnumIterator; #[cfg(feature = "kvm")] use kvmi::create_kvmi; -use crate::api::{DriverInitParam, DriverType}; +use crate::api::{DriverInitParam, DriverType, Event, InterceptType}; use crate::api::{Introspectable, Registers}; #[cfg(feature = "kvm")] use crate::driver::kvm::Kvm; @@ -82,25 +82,64 @@ impl Microvmi { }) } + /// 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) } + /// 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) + } } /// Initialize a given driver type From 820d32d67227b48978be509e87c8ba52de1ad52e Mon Sep 17 00:00:00 2001 From: Mathieu Tarral Date: Wed, 28 Apr 2021 13:42:04 +0200 Subject: [PATCH 19/33] examples: fix interrupt-events --- examples/interrupt-events.rs | 13 +++++++------ src/microvmi.rs | 21 ++++++++++++++++++++- 2 files changed, 27 insertions(+), 7 deletions(-) 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/src/microvmi.rs b/src/microvmi.rs index dacc4eec..60990579 100644 --- a/src/microvmi.rs +++ b/src/microvmi.rs @@ -4,7 +4,7 @@ use enum_iterator::IntoEnumIterator; #[cfg(feature = "kvm")] use kvmi::create_kvmi; -use crate::api::{DriverInitParam, DriverType, Event, InterceptType}; +use crate::api::{DriverInitParam, DriverType, Event, EventReplyType, InterceptType}; use crate::api::{Introspectable, Registers}; #[cfg(feature = "kvm")] use crate::driver::kvm::Kvm; @@ -82,6 +82,11 @@ impl Microvmi { }) } + /// 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. @@ -140,6 +145,20 @@ impl Microvmi { 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 From 75116df3616497e83cc2885171a5ac9c94deb33a Mon Sep 17 00:00:00 2001 From: Mathieu Tarral Date: Wed, 28 Apr 2021 13:42:38 +0200 Subject: [PATCH 20/33] examples: remove unused import --- examples/mem-dump.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/mem-dump.rs b/examples/mem-dump.rs index 8cac35db..ef56c7a8 100644 --- a/examples/mem-dump.rs +++ b/examples/mem-dump.rs @@ -8,7 +8,6 @@ use log::trace; use microvmi::api::DriverInitParam; use microvmi::Microvmi; -use std::io; const BUFFER_SIZE: usize = 64535; // 64K From 39c678b53c95955058324d400ae5d56b07a37eb7 Mon Sep 17 00:00:00 2001 From: Mathieu Tarral Date: Wed, 28 Apr 2021 13:46:45 +0200 Subject: [PATCH 21/33] examples: fix mem-events --- examples/mem-events.rs | 11 +++++------ src/microvmi.rs | 25 +++++++++++++++++++++++-- 2 files changed, 28 insertions(+), 8 deletions(-) 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/src/microvmi.rs b/src/microvmi.rs index 60990579..e0e896cd 100644 --- a/src/microvmi.rs +++ b/src/microvmi.rs @@ -4,8 +4,10 @@ use enum_iterator::IntoEnumIterator; #[cfg(feature = "kvm")] use kvmi::create_kvmi; -use crate::api::{DriverInitParam, DriverType, Event, EventReplyType, InterceptType}; -use crate::api::{Introspectable, Registers}; +use crate::api::{ + Access, DriverInitParam, DriverType, Event, EventReplyType, InterceptType, Introspectable, + Registers, +}; #[cfg(feature = "kvm")] use crate::driver::kvm::Kvm; #[cfg(feature = "virtualbox")] @@ -104,6 +106,25 @@ impl Microvmi { 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() From 921ed60629c4e0848ec2845a40af0fa6fb825888 Mon Sep 17 00:00:00 2001 From: Mathieu Tarral Date: Wed, 28 Apr 2021 13:51:08 +0200 Subject: [PATCH 22/33] examples: fix msr-events --- examples/msr-events.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) 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); From 9ee376dfa60755bb32cba1f6594644788f87a07b Mon Sep 17 00:00:00 2001 From: Mathieu Tarral Date: Wed, 28 Apr 2021 13:53:53 +0200 Subject: [PATCH 23/33] examples: fix pause --- examples/pause.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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"); From ba48425b6c24bcc666f9fdf2565427c195797278 Mon Sep 17 00:00:00 2001 From: Mathieu Tarral Date: Wed, 28 Apr 2021 13:54:28 +0200 Subject: [PATCH 24/33] examples: fix regs-dump --- examples/regs-dump.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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"); From 38cbc76b901b5c09a72686ec6968ce175676fe17 Mon Sep 17 00:00:00 2001 From: Mathieu Tarral Date: Wed, 28 Apr 2021 13:58:42 +0200 Subject: [PATCH 25/33] microvmi: fix imports --- src/lib.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 9bab3c00..6fe08ed1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,13 +2,18 @@ //! //! Click on this [book 📖](https://libmicrovmi.github.io/) to find our project documentation. + + + +mod memory; +mod driver; + pub mod api; pub mod capi; -mod driver; pub mod errors; -mod memory; pub mod microvmi; -pub use microvmi::Microvmi; +// reexport +pub use crate::microvmi::Microvmi; #[macro_use] extern crate log; From 556182c92a39c215a4011d5d054e0d19af9f00da Mon Sep 17 00:00:00 2001 From: Mathieu Tarral Date: Wed, 28 Apr 2021 14:39:07 +0200 Subject: [PATCH 26/33] api: replace read_physical by read_frame --- src/api.rs | 45 +++++++++++++++++++++++++--------- src/lib.rs | 5 +--- src/memory.rs | 67 +++++++++++++++++++++++++++++++++++---------------- 3 files changed, 81 insertions(+), 36 deletions(-) diff --git a/src/api.rs b/src/api.rs index 8d7fbe0b..1cdc54fd 100644 --- a/src/api.rs +++ b/src/api.rs @@ -3,6 +3,7 @@ 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; @@ -171,6 +172,35 @@ 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; @@ -182,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/lib.rs b/src/lib.rs index 6fe08ed1..e987f255 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,11 +2,8 @@ //! //! Click on this [book 📖](https://libmicrovmi.github.io/) to find our project documentation. - - - -mod memory; mod driver; +mod memory; pub mod api; pub mod capi; diff --git a/src/memory.rs b/src/memory.rs index c11e0fa9..b367e531 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -1,8 +1,8 @@ //! This module defines a physical memory implementation behaving like a File-IO -use crate::api::Introspectable; #[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; @@ -32,20 +32,27 @@ impl Memory { impl Read for Memory { fn read(&mut self, buf: &mut [u8]) -> Result { - let mut total_bytes_read: usize = 0; - for chunk in buf.chunks_mut(PAGE_SIZE) { - let mut bytes_read: u64 = 0; + // 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()?; - self.drv - .borrow() - .read_physical(paddr, chunk, &mut bytes_read) - .map_err(|_| Error::new(ErrorKind::Other, "driver read failure"))?; - // advance pos from bytes_read - self.seek(SeekFrom::Current(bytes_read as i64))?; - // add to total - total_bytes_read += bytes_read as usize; + 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(total_bytes_read) + Ok(bytes_read as usize) } } @@ -116,17 +123,35 @@ impl PaddedMemory { // TODO: slight duplication impl Read for PaddedMemory { fn read(&mut self, buf: &mut [u8]) -> Result { - for chunk in buf.chunks_mut(PAGE_SIZE) { - let mut bytes_read: u64 = 0; + // 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()?; - self.drv - .borrow() - .read_physical(paddr, chunk, &mut bytes_read) - .unwrap_or_else(|_| chunk.fill(0)); + 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(chunk.len() as i64))?; + self.seek(SeekFrom::Current(next_chunk_size as i64))?; + // update loop vars + bytes_read += next_chunk_size; + read_remain -= next_chunk_size; } - Ok(buf.len()) + Ok(bytes_read as usize) } } From 4f017ad55470cf45e4ec8d2d53437bddca99a2a0 Mon Sep 17 00:00:00 2001 From: Mathieu Tarral Date: Wed, 28 Apr 2021 15:12:40 +0200 Subject: [PATCH 27/33] xen: impl read_frame --- src/driver/xen.rs | 59 ++++++++++++++--------------------------------- 1 file changed, 17 insertions(+), 42 deletions(-) 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(()) } From 1fbc8632f9bc0d2ef6c3cfdc3cd955d1d1635e21 Mon Sep 17 00:00:00 2001 From: Mathieu Tarral Date: Wed, 28 Apr 2021 15:20:27 +0200 Subject: [PATCH 28/33] kvm: impl read_frame --- src/driver/kvm.rs | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) 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> { From 47fb25c2ab172e6c814a23c1579e53519725a44f Mon Sep 17 00:00:00 2001 From: Mathieu Tarral Date: Wed, 28 Apr 2021 15:39:56 +0200 Subject: [PATCH 29/33] clippy: allow upper case acronyms --- python/src/lib.rs | 2 ++ src/lib.rs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/python/src/lib.rs b/python/src/lib.rs index c280afa9..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}; diff --git a/src/lib.rs b/src/lib.rs index e987f255..e2ee20ca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,8 @@ //! //! Click on this [book 📖](https://libmicrovmi.github.io/) to find our project documentation. +#![allow(clippy::upper_case_acronyms)] + mod driver; mod memory; From 49a0b0a6b6a091940b65b00e6b01d3ffc90db301 Mon Sep 17 00:00:00 2001 From: Mathieu Tarral Date: Wed, 28 Apr 2021 15:45:08 +0200 Subject: [PATCH 30/33] examples: use read_exact to ignore the return value --- examples/mem-dump.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/mem-dump.rs b/examples/mem-dump.rs index ef56c7a8..452b44ce 100644 --- a/examples/mem-dump.rs +++ b/examples/mem-dump.rs @@ -86,7 +86,7 @@ fn main() { // reset buffer each loop let mut buffer: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]; drv.padded_memory - .read(&mut buffer) + .read_exact(&mut buffer) .expect(&*format!("Failed to read memory at {:#X}", cur_addr)); dump_file .write_all(&buffer) From cd4dc16e3b33610edfe8cbcbf111d93c5011460a Mon Sep 17 00:00:00 2001 From: Mathieu Tarral Date: Thu, 29 Apr 2021 15:47:48 +0200 Subject: [PATCH 31/33] virtualbox: impl read_frame --- src/driver/virtualbox.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) 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> { From 3381be79ba565178baa6ca6aa71f2b30a6aa9da5 Mon Sep 17 00:00:00 2001 From: Mathieu Tarral Date: Fri, 30 Apr 2021 10:11:09 +0200 Subject: [PATCH 32/33] microvmi: disable doctest for new example --- src/microvmi.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/microvmi.rs b/src/microvmi.rs index e0e896cd..9e28be81 100644 --- a/src/microvmi.rs +++ b/src/microvmi.rs @@ -42,7 +42,7 @@ impl Microvmi { /// /// # Example /// - /// ``` + /// ```no_run /// use crate::microvmi::microvmi::Microvmi; /// use crate::microvmi::api::{DriverType, DriverInitParam}; /// Microvmi::new("win10", None, None); From 9fef107443e803fddfa9f1efcdd642891883697e Mon Sep 17 00:00:00 2001 From: Mathieu Tarral Date: Wed, 19 May 2021 13:18:07 +0200 Subject: [PATCH 33/33] memory: remove duplicated Seek implementation --- src/memory.rs | 98 +++++++++++++++++++++++---------------------------- 1 file changed, 45 insertions(+), 53 deletions(-) diff --git a/src/memory.rs b/src/memory.rs index b367e531..1d04743a 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -14,18 +14,52 @@ use std::result::Result as StdResult; const PAGE_SIZE: usize = 4096; -pub struct Memory { - drv: Rc>>, +// 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(), - pos: 0, - max_addr: drv.borrow().get_max_physical_addr()?, + addr_seek: AddressSeek { + pos: 0, + max_addr: drv.borrow().get_max_physical_addr()?, + }, }) } } @@ -79,43 +113,23 @@ impl Write for Memory { impl Seek for Memory { 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) + self.addr_seek.seek(pos) } } pub struct PaddedMemory { drv: Rc>>, - pos: u64, - max_addr: u64, + addr_seek: AddressSeek, } impl PaddedMemory { pub fn new(drv: Rc>>) -> StdResult> { Ok(PaddedMemory { drv: drv.clone(), - pos: 0, - max_addr: drv.borrow().get_max_physical_addr()?, + addr_seek: AddressSeek { + pos: 0, + max_addr: drv.borrow().get_max_physical_addr()?, + }, }) } } @@ -155,31 +169,9 @@ impl Read for PaddedMemory { } } -// TODO: duplicated implementation impl Seek for PaddedMemory { 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) + self.addr_seek.seek(pos) } }