diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 90d0d21b..7b8676f3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -126,6 +126,29 @@ jobs: flags: unittests fail_ci_if_error: true + memflow: + runs-on: ubuntu-20.04 + + steps: + - name: install stable toolchain with clippy + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + components: clippy + - uses: actions/checkout@v1 + - name: build Memflow driver + uses: actions-rs/cargo@v1 + with: + command: build + args: --features mflow + - name: annotate commit with clippy warnings + uses: actions-rs/clippy-check@v1 + with: + name: clippy memflow + token: ${{ secrets.GITHUB_TOKEN }} + args: --features mflow -- -D warnings + virtualbox: runs-on: ubuntu-20.04 @@ -248,7 +271,7 @@ jobs: uses: satackey/action-docker-layer-caching@v0.0.11 - name: Build Wheels with manylinux - run: nox -r -s generate_wheels -- --features xen,kvm,virtualbox --release + run: nox -r -s generate_wheels -- --features xen,kvm,virtualbox,mflow --release working-directory: python # upload all generated wheels *.whl @@ -311,7 +334,7 @@ jobs: run: cargo install cargo-deb - name: build debian package - run: cargo deb -- --features xen,kvm,virtualbox + run: cargo deb -- --features xen,kvm,virtualbox,mflow - name: upload artifact uses: actions/upload-artifact@v2 diff --git a/Cargo.toml b/Cargo.toml index 1438e06c..4d233088 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,9 @@ xen = ["xenctrl", "xenstore-rs", "xenforeignmemory", "xenevtchn", "xenvmevent-sy kvm = ["kvmi"] # VirtualBox driver virtualbox = ["fdp"] +# memflow driver +# feature name is "mflow" to avoid conflict with the dependency +mflow = ["memflow"] [dependencies] @@ -42,6 +45,7 @@ winapi = { version = "0.3", features = ["tlhelp32", "winnt", "handleapi", "secur widestring = { version = "0.4", optional = true } ntapi = { version = "0.3", optional = true } vid-sys = { version = "=0.3.0", features = ["deprecated-apis"], optional = true } +memflow = { version = "0.1.5", optional = true } [dev-dependencies] utilities = { path = "utilities" } diff --git a/doc/src/reference/drivers.md b/doc/src/reference/drivers.md index 8dc4de94..6eeab460 100644 --- a/doc/src/reference/drivers.md +++ b/doc/src/reference/drivers.md @@ -9,12 +9,18 @@ This section documents the drivers available and the requirements to compile the | `xen` | Build the Xen driver | | `kvm` | Build the KVM driver | | `virtualbox` | Build the VirtualBox driver | +| `mflow` | Build the memflow driver | Example ~~~ $ cargo build --features xen,kvm ~~~ +## Rust API initialization parameters + +To initialize each Driver from the Rust API, +please check [`DriverInitParams`](https://docs.rs/microvmi/api/params/struct.DriverInitParams.html). + ## Xen ~~~ @@ -48,3 +54,7 @@ $ g++ -std=c++11 -shared -fPIC FDP.cpp -o libFDP.so $ sudo mv include/* /usr/local/include/ $ sudo mv libFDP.so /usr/local/lib/ ~~~ + +## Memflow + +Please follow the instructions at [memflow](https://github.com/memflow/memflow) diff --git a/python/Cargo.toml b/python/Cargo.toml index b4a02f59..0d48276c 100644 --- a/python/Cargo.toml +++ b/python/Cargo.toml @@ -23,6 +23,8 @@ xen = ["microvmi/xen"] kvm = ["microvmi/kvm"] # VirtualBox driver virtualbox = ["microvmi/virtualbox"] +# memflow driver +mflow = ["microvmi/mflow"] [dependencies] log = "0.4" diff --git a/python/microvmi/__init__.py b/python/microvmi/__init__.py index 898f4a46..3f201c8b 100644 --- a/python/microvmi/__init__.py +++ b/python/microvmi/__init__.py @@ -1,3 +1,3 @@ from microvmi.microvmi import DriverType, Microvmi -from .pymicrovmi import CommonInitParamsPy, DriverInitParamsPy, KVMInitParamsPy +from .pymicrovmi import CommonInitParamsPy, DriverInitParamsPy, KVMInitParamsPy, MemflowInitParamsPy diff --git a/python/microvmi/volatility/vmi_handler.py b/python/microvmi/volatility/vmi_handler.py index 37c99f0b..88c0e59a 100644 --- a/python/microvmi/volatility/vmi_handler.py +++ b/python/microvmi/volatility/vmi_handler.py @@ -3,7 +3,7 @@ from urllib.parse import parse_qs, urlparse from urllib.request import BaseHandler, Request -from microvmi import CommonInitParamsPy, DriverInitParamsPy, DriverType, KVMInitParamsPy, Microvmi +from microvmi import CommonInitParamsPy, DriverInitParamsPy, DriverType, KVMInitParamsPy, MemflowInitParamsPy, Microvmi # to be used by volatility, the VMIHandler should inherit from VolatilityHandler # in order to be non cacheable @@ -101,6 +101,7 @@ def _parse_driver_init_params(query: str) -> Optional[DriverInitParamsPy]: return None common = None kvm = None + memflow = None for param, list_value in url_params.items(): if param == "vm_name": common = CommonInitParamsPy() @@ -108,9 +109,16 @@ def _parse_driver_init_params(query: str) -> Optional[DriverInitParamsPy]: elif param == "kvm_unix_socket": kvm = KVMInitParamsPy() kvm.unix_socket = list_value[0] + elif param == "memflow_connector_name": + memflow = MemflowInitParamsPy(list_value[0]) + elif param == "memflow_connector_args": + if memflow is None: + raise MicrovmiHandlerError("memflow connector args received but no connector name specified") + memflow.connector_args = list_value else: raise MicrovmiHandlerError(f"Unknown driver initialization parameter: {param}") init_params = DriverInitParamsPy() init_params.common = common init_params.kvm = kvm + init_params.memflow = memflow return init_params diff --git a/python/noxfile.py b/python/noxfile.py index 2c2b5c96..520b6e44 100644 --- a/python/noxfile.py +++ b/python/noxfile.py @@ -105,6 +105,36 @@ def test_volatility_kvm(session): ) +@nox.session +def test_volatility_memflow(session): + """Run the PsList volatility plugin on the memflow connector specified by the URL""" + # example: + # nox -r -s test_volatility_memflow -- vmi:///?memflow_connector_name=qemu_procfs + args = session.posargs + if not args: + raise RuntimeError("URL required. Example: nox -r -s test_volatility_memflow -- vmi:///...") + # we need to compile and install the extension + session.install("-r", "requirements.txt") + # make sure we have volatility + # Note: we need to use the latest unreleased dev code from Github + session.install("git+https://github.com/volatilityfoundation/volatility3@af090bf29e6bb26a5961e0a6c25b5d1ec6e82498") + # can't use pip install + # see: https://github.com/PyO3/maturin/issues/330 + session.run(f'{CUR_DIR / "setup.py"}', "develop", "--features", "mflow") + vol_path = Path(__file__).parent / ".nox" / "test_volatility_memflow" / "bin" / "vol" + plugins_dir = Path(__file__).parent / "microvmi" / "volatility" + session.run( + "sudo", + "-E", + str(vol_path), + "--plugin-dirs", + str(plugins_dir), + "--single-location", + *args, + "windows.pslist.PsList", + ) + + @nox.session def coverage_html(session): session.install("coverage==5.3") diff --git a/python/src/lib.rs b/python/src/lib.rs index 9236ba15..da927482 100644 --- a/python/src/lib.rs +++ b/python/src/lib.rs @@ -11,7 +11,7 @@ use errors::PyMicrovmiError; use microvmi::api as rapi; // rust api use microvmi::api::params as rparams; // rust params use microvmi::init; -use params::{CommonInitParamsPy, DriverInitParamsPy, KVMInitParamsPy}; +use params::{CommonInitParamsPy, DriverInitParamsPy, KVMInitParamsPy, MemflowInitParamsPy}; /// microvmi Python module declaration #[pymodule] @@ -24,6 +24,7 @@ fn pymicrovmi(_py: Python, m: &PyModule) -> PyResult<()> { m.add_class::()?; m.add_class::()?; m.add_class::()?; + m.add_class::()?; Ok(()) } @@ -89,6 +90,12 @@ impl MicrovmiExt { kvm: v.kvm.map(|k| rparams::KVMInitParams::UnixSocket { path: k.unix_socket, }), + memflow: v.memflow.map(|k| rparams::MemflowInitParams { + connector_name: k.connector_name, + connector_args: Some(rparams::MemflowConnectorParams::Default { + args: k.connector_args, + }), + }), ..Default::default() }); diff --git a/python/src/params.rs b/python/src/params.rs index 933c7e63..5c9bae40 100644 --- a/python/src/params.rs +++ b/python/src/params.rs @@ -33,6 +33,27 @@ impl KVMInitParamsPy { } } +/// equivalent of `MemflowInitParams` for Python +#[pyclass] +#[derive(Default, Debug, Clone)] +pub struct MemflowInitParamsPy { + #[pyo3(get, set)] + pub connector_name: String, + #[pyo3(get, set)] + pub connector_args: Vec, +} + +#[pymethods] +impl MemflowInitParamsPy { + #[new] + fn new(name: &str) -> Self { + Self { + connector_name: String::from(name), + connector_args: Vec::new(), + } + } +} + /// equivalent of `DriverInitParams` for Python /// /// # Examples @@ -58,6 +79,8 @@ pub struct DriverInitParamsPy { pub common: Option, #[pyo3(get, set)] pub kvm: Option, + #[pyo3(get, set)] + pub memflow: Option, } #[pymethods] diff --git a/src/api/mod.rs b/src/api/mod.rs index 08f3214a..e9c9ded0 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -26,6 +26,7 @@ bitflags! { #[derive(Debug, Copy, Clone, PartialEq, IntoEnumIterator)] pub enum DriverType { KVM, + Memflow, VirtualBox, Xen, } diff --git a/src/api/params.rs b/src/api/params.rs index e5ca0726..6d39cbc5 100644 --- a/src/api/params.rs +++ b/src/api/params.rs @@ -1,4 +1,7 @@ -/// This module describes initialization parameters for all libmicrovmi drivers +//! This module describes initialization parameters for all libmicrovmi drivers +//! +//! The [`DriverInitParams`](struct.DriverInitParams.html) is used to pass additional driver initialization parameters. +//! You might want to check it's documentation for examples on how to initialize your driver. /// Xen initialization parameters #[derive(Debug, Clone, PartialEq)] @@ -10,6 +13,46 @@ pub enum KVMInitParams { UnixSocket { path: String }, } +/// Memflow connector parameters +/// +/// This enumeration reflects the possibilities to initialize Memflow +/// - default: will simply forward the string arguments to the connector +// TODO +// - [`qemu_procfs`](https://github.com/memflow/memflow-qemu-procfs) +// - [`kvm`](https://github.com/memflow/memflow-kvm) +// - [`pcileech`](https://github.com/memflow/memflow-pcileech) +// - [`coredump`](https://github.com/memflow/memflow-coredump) +#[derive(Debug, Clone, PartialEq)] +pub enum MemflowConnectorParams { + // allow to pass an abritrary list of Strings as parameters + Default { args: Vec }, + // TODO + // // optional vm_name, otherwise will search for the first QEMU process + // QEMUProcFs { + // vm_name: Option, + // }, + // KVM { + // pid: u32, + // }, + // // default value for device: "FPGA" + // PCILeech { + // device: Option, + // memmap: Option, + // }, + // Coredump { + // filepath: String, + // }, +} + +/// Memflow initialization parameters +#[derive(Debug, Default, Clone, PartialEq)] +pub struct MemflowInitParams { + /// connector name + pub connector_name: String, + /// optional connector initialization parameters + pub connector_args: Option, +} + /// VirtualBox initialization parameters #[derive(Debug, Clone, PartialEq)] pub enum VBoxInitParams {} @@ -25,10 +68,45 @@ pub struct CommonInitParams { } /// This struct is used to specify the initialization parameters for all drivers +/// +/// # Examples +/// +/// ```no_run +/// // Xen +/// // common.vm_name: mandatory +/// use microvmi::api::params::{DriverInitParams, CommonInitParams, KVMInitParams, MemflowInitParams}; +/// let init_params = DriverInitParams { +/// common: Some(CommonInitParams { vm_name: String::from("windows10")}), +/// ..Default::default() +/// }; +/// // KVM +/// // common.vm_name: mandatory +/// // kvm.unix_socket: mandatory +/// let init_params = DriverInitParams { +/// common: Some(CommonInitParams { vm_name: String::from("windows10")}), +/// kvm: Some(KVMInitParams::UnixSocket { path: String::from("/tmp/introspector")}), +/// ..Default::default() +/// }; +/// // VirtualBox +/// // common.vm_name: mandatory +/// let init_params = DriverInitParams { +/// common: Some(CommonInitParams { vm_name: String::from("windows10")}), +/// ..Default::default() +/// }; +/// // Memflow +/// // memflow.connector_name: mandatory +/// // memflow.connector_args: optional +/// let init_params = DriverInitParams { +/// memflow: Some(MemflowInitParams { connector_name: String::from("qemu_procfs"), /// +/// ..Default::default()}), +/// ..Default::default() +/// }; +/// ``` #[derive(Default, Debug, Clone, PartialEq)] pub struct DriverInitParams { pub common: Option, pub xen: Option, pub kvm: Option, + pub memflow: Option, pub virtualbox: Option, } diff --git a/src/driver/memflow.rs b/src/driver/memflow.rs new file mode 100644 index 00000000..cbb50201 --- /dev/null +++ b/src/driver/memflow.rs @@ -0,0 +1,82 @@ +use crate::api::params::{DriverInitParams, MemflowConnectorParams}; +use crate::api::{DriverType, Introspectable}; +use std::error::Error; + +use memflow::connector::{ConnectorArgs, ConnectorInstance, ConnectorInventory}; +use memflow::{PhysicalAddress, PhysicalMemory}; +use std::cell::RefCell; + +#[derive(thiserror::Error, Debug)] +pub enum MemflowDriverError { + #[error("Memfow driver initialization requires a connector parameter")] + MissingConnectorParameter, + #[error("Invalid format for Memflow connector argument (key=value), got {0}")] + InvalidConnectorArgument(String), +} + +pub struct Memflow { + // refcell required because read methods are mutable + // contrary to our read_frame signature + connector: RefCell, +} + +impl Memflow { + pub fn new(init_params: DriverInitParams) -> Result> { + info!("init Memflow"); + // check connector name + let memflow_init_params = init_params + .memflow + .ok_or(MemflowDriverError::MissingConnectorParameter)?; + // create inventory + let inventory = unsafe { ConnectorInventory::scan() }; + // parse connector args + let mut create_connector_args = ConnectorArgs::new(); + if memflow_init_params.connector_args.is_some() { + let MemflowConnectorParams::Default { args } = + memflow_init_params.connector_args.unwrap(); + // for each string, split at '=' to get key, value + for s in args.iter() { + let (key, value) = s + .split_once("=") + .ok_or_else(|| MemflowDriverError::InvalidConnectorArgument(s.clone()))?; + // push it into memflow ConnectorArgs type + create_connector_args = create_connector_args.insert(key, value); + } + } + // create memflow connector + debug!( + "Memflow: create connector - name: {}, args: {:#?}", + &memflow_init_params.connector_name, &create_connector_args + ); + let connector = unsafe { + inventory + .create_connector(&memflow_init_params.connector_name, &create_connector_args)? + }; + Ok(Memflow { + connector: RefCell::new(connector), + }) + } +} + +impl Introspectable for Memflow { + fn read_physical( + &self, + paddr: u64, + buf: &mut [u8], + bytes_read: &mut u64, + ) -> Result<(), Box> { + self.connector + .borrow_mut() + .phys_read_into(PhysicalAddress::from(paddr), buf)?; + *bytes_read = buf.len() as u64; + Ok(()) + } + + fn get_max_physical_addr(&self) -> Result> { + Ok(self.connector.borrow_mut().metadata().size as u64) + } + + fn get_driver_type(&self) -> DriverType { + DriverType::Memflow + } +} diff --git a/src/driver/mod.rs b/src/driver/mod.rs index 731645a2..008f1ef4 100644 --- a/src/driver/mod.rs +++ b/src/driver/mod.rs @@ -1,5 +1,7 @@ #[cfg(feature = "kvm")] pub mod kvm; +#[cfg(feature = "mflow")] +pub mod memflow; #[cfg(feature = "virtualbox")] pub mod virtualbox; #[cfg(feature = "xen")] diff --git a/src/lib.rs b/src/lib.rs index a850dcf5..8ea234b7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ //! libmicrovmi is a cross-platform unified virtual machine introsection interface, following a simple design to keep interoperability at heart. //! -//! Click on this [book 📖](https://libmicrovmi.github.io/) to find our project documentation. +//! Click on this [book 📖](https://wenzel.github.io/libmicrovmi/) to find our project documentation. //! //! The library's entrypoint is the [init](fn.init.html) function. @@ -23,6 +23,8 @@ use api::DriverType; use api::Introspectable; #[cfg(feature = "kvm")] use driver::kvm::Kvm; +#[cfg(feature = "mflow")] +use driver::memflow::Memflow; #[cfg(feature = "virtualbox")] use driver::virtualbox::VBox; #[cfg(feature = "xen")] @@ -36,6 +38,8 @@ use kvmi::create_kvmi; /// This function will initialize a libmicrovmi driver and call the hypervisor VMI API. /// It returns a `Box` trait object, which implements the [Introspectable](api/trait.Introspectable.html) trait. /// +/// For complete documentation on driver init params, please check [DriverInitParams](struct.DriverInitParams.html) struct. +/// /// # Arguments /// * `driver_type`: optional driver type to initialize. If None, all compiled drivers will be initialized one by one. The first that succeeds will be returned. /// * `init_params`: optional driver initialization parameters @@ -106,6 +110,8 @@ fn init_driver( match driver_type { #[cfg(feature = "kvm")] DriverType::KVM => Ok(Box::new(Kvm::new(create_kvmi(), _init_params)?)), + #[cfg(feature = "mflow")] + DriverType::Memflow => Ok(Box::new(Memflow::new(_init_params)?)), #[cfg(feature = "virtualbox")] DriverType::VirtualBox => Ok(Box::new(VBox::new(_init_params)?)), #[cfg(feature = "xen")] diff --git a/utilities/src/lib.rs b/utilities/src/lib.rs index 75e88cf9..c8c9f533 100644 --- a/utilities/src/lib.rs +++ b/utilities/src/lib.rs @@ -1,6 +1,8 @@ /// This crate implements utilities and common code shared by libmicrovmi examples use clap::{Arg, ArgMatches}; -use microvmi::api::params::{CommonInitParams, DriverInitParams, KVMInitParams}; +use microvmi::api::params::{ + CommonInitParams, DriverInitParams, KVMInitParams, MemflowConnectorParams, MemflowInitParams, +}; /// This trait allows to convert a struct to Clap's command line arguments /// and to parse back the matches into the struct @@ -24,6 +26,15 @@ impl Clappable for DriverInitParams { .long("kvm_unix_socket") .takes_value(true) .help("Driver parameter (required for KVM): KVM unix socket path"), + // memflow + Arg::with_name("memflow_connector_name") + .long("memflow_connector_name") + .takes_value(true) + .help("Driver parameter (optional for Memflow): Memflow connector name"), + Arg::with_name("memflow_connector_args") + .long("memflow_connector_args") + .multiple(true) + .min_values(1), ] } @@ -36,9 +47,20 @@ impl Clappable for DriverInitParams { .map(|s| KVMInitParams::UnixSocket { path: String::from(s), }); + let memflow = matches + .value_of("memflow_connector_name") + .map(|name| MemflowInitParams { + connector_name: name.to_string(), + connector_args: matches.values_of("memflow_connector_args").map(|v| { + MemflowConnectorParams::Default { + args: v.map(|s| s.to_string()).collect(), + } + }), + }); DriverInitParams { common, kvm, + memflow, ..Default::default() } } @@ -48,7 +70,7 @@ impl Clappable for DriverInitParams { mod tests { use super::Clappable; use clap::App; - use microvmi::api::params::{DriverInitParams, KVMInitParams}; + use microvmi::api::params::{DriverInitParams, KVMInitParams, MemflowConnectorParams}; #[test] fn test_common_vm_name() { @@ -74,4 +96,57 @@ mod tests { params.kvm.unwrap() ); } + + // tests for memflow + #[test] + fn test_memflow_connector_name() { + let cmdline = vec!["test", "--memflow_connector_name=foobar"]; + let matches = App::new("test") + .args(DriverInitParams::to_clap_args().as_ref()) + .get_matches_from(cmdline); + let params = DriverInitParams::from_matches(&matches); + assert_eq!("foobar", params.memflow.unwrap().connector_name) + } + + #[test] + fn test_memflow_connector_args_one() { + let cmdline = vec![ + "test", + "--memflow_connector_name=foobar", + "--memflow_connector_args", + "first", + ]; + let matches = App::new("test") + .args(DriverInitParams::to_clap_args().as_ref()) + .get_matches_from(cmdline); + let params = DriverInitParams::from_matches(&matches); + assert_eq!( + MemflowConnectorParams::Default { + args: vec!["first".into()] + }, + params.memflow.unwrap().connector_args.unwrap() + ) + } + + #[test] + fn test_memflow_connector_args_multiple() { + let cmdline = vec![ + "test", + "--memflow_connector_name=foobar", + "--memflow_connector_args", + "first", + "second", + "third", + ]; + let matches = App::new("test") + .args(DriverInitParams::to_clap_args().as_ref()) + .get_matches_from(cmdline); + let params = DriverInitParams::from_matches(&matches); + assert_eq!( + MemflowConnectorParams::Default { + args: vec!["first".into(), "second".into(), "third".into()] + }, + params.memflow.unwrap().connector_args.unwrap() + ) + } }