diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4535103e..50a9d8d3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -117,14 +117,6 @@ jobs: runs-on: ubuntu-22.04 steps: - - name: clone Icebox repository - run: git clone https://github.com/thalium/icebox --depth 1 - - name: Compile and install FDP library - run: | - cd icebox/src/FDP - g++ -std=c++11 -shared -fPIC FDP.cpp -o libFDP.so -lrt - sudo cp include/* /usr/local/include - sudo cp libFDP.so /usr/local/lib - name: Install Rust toolchain uses: dtolnay/rust-toolchain@1.99.0 with: @@ -132,17 +124,15 @@ jobs: - uses: actions/checkout@v1 - name: build and check VirtualBox driver run: | - cargo build --features virtualbox - cargo clippy --features virtualbox -- -D warnings cargo build --features virtualbox-api cargo clippy --features virtualbox-api -- -D warnings + cargo test --features virtualbox-api --test virtualbox_errors cargo build --features virtualbox-vmi cargo clippy --features virtualbox-vmi -- -D warnings - cargo clippy --features virtualbox-api,virtualbox-vmi --tests -- -D warnings cargo test --features virtualbox-vmi --test virtualbox_errors - env: - LIBRARY_PATH: /usr/local/lib - LD_LIBRARY_PATH: /usr/local/lib + cargo build --features virtualbox + cargo clippy --features virtualbox -- -D warnings + cargo clippy --features virtualbox --tests -- -D warnings virtualbox_windows: runs-on: windows-2022 @@ -153,41 +143,16 @@ jobs: with: components: clippy - - name: Clone Icebox repository - uses: actions/checkout@v2 - with: - repository: thalium/icebox - path: icebox - uses: actions/checkout@v2 with: path: libmicrovmi - # adds cl.exe in the PATH - - name: Setup command prompt for Microsoft Visual C++ - uses: ilammy/msvc-dev-cmd@v1 - - name: Compile and install FDP library + - name: compile-check VirtualBox API bindings (runtime unavailable on Windows) run: | - # install in C:\FDP to avoid issues with quotes etc - cl /c FDP.cpp /out:FDP.obj - cl /LD FDP.obj /link /out:FDP.dll - lib /NOLOGO FDP.obj /OUT:FDP.lib - dir - New-Item -Path "C:\FDP" -ItemType "directory" - copy FDP.dll "C:\FDP" - copy FDP.lib "C:\FDP" - copy "include\*" "C:\FDP" - working-directory: icebox/src/FDP - - - name: Install Clang - run: choco install llvm - - - name: build VirtualBox driver - run: cargo build --manifest-path libmicrovmi/Cargo.toml --features virtualbox - env: - BINDGEN_EXTRA_CLANG_ARGS: -I"C:\FDP" -L"C:\FDP" - CARGO_BUILD_RUSTFLAGS: -L C:\FDP + cargo build --manifest-path libmicrovmi/Cargo.toml --features virtualbox-api + cargo clippy --manifest-path libmicrovmi/Cargo.toml --features virtualbox-api -- -D warnings c_api: runs-on: ${{ matrix.os }} @@ -264,7 +229,7 @@ jobs: run: python3 -c 'from microvmi import Microvmi, DriverType, CommonInitParamsPy, DriverInitParamsPy, KVMInitParamsPy, VBoxInitParamsPy' - name: Build Wheels with manylinux - run: nox -r -s generate_wheels -- --features xen,kvm,virtualbox,virtualbox-api,virtualbox-vmi,mflow --release + run: nox -r -s generate_wheels -- --features xen,kvm,virtualbox,mflow --release working-directory: python # upload all generated wheels *.whl @@ -286,18 +251,6 @@ jobs: - name: install Xen headers run: sudo apt-get install -y libxen-dev - - name: clone Icebox - uses: actions/checkout@v2 - with: - repository: thalium/icebox - path: icebox - - - name: install VirtualBox's FDP headers - run: | - g++ -std=c++11 -shared -fPIC FDP.cpp -o libFDP.so -lrt - sudo cp include/* /usr/local/include - sudo cp libFDP.so /usr/local/lib - working-directory: icebox/src/FDP - name: clone libkvmi uses: actions/checkout@v2 @@ -352,41 +305,13 @@ jobs: with: components: clippy - - name: Clone Icebox repository - uses: actions/checkout@v2 - with: - repository: thalium/icebox - path: icebox - - uses: actions/checkout@v2 with: path: libmicrovmi - # adds cl.exe in the PATH - - name: Setup command prompt for Microsoft Visual C++ - uses: ilammy/msvc-dev-cmd@v1 - - name: Compile and install FDP library - run: | - # install in C:\FDP to avoid issues with quotes etc - cl /c FDP.cpp /out:FDP.obj - cl /LD FDP.obj /link /out:FDP.dll - lib /NOLOGO FDP.obj /OUT:FDP.lib - dir - New-Item -Path "C:\FDP" -ItemType "directory" - copy FDP.dll "C:\FDP" - copy FDP.lib "C:\FDP" - copy "include\*" "C:\FDP" - working-directory: icebox/src/FDP - - - name: Install Clang - run: choco install llvm - - - name: build libmicrovmi with virtualbox and memflow driver - run: cargo build --manifest-path libmicrovmi/Cargo.toml --features virtualbox,mflow --release - env: - BINDGEN_EXTRA_CLANG_ARGS: -I"C:\FDP" -L"C:\FDP" - CARGO_BUILD_RUSTFLAGS: -L C:\FDP + - name: build Windows artifact with compile-only VirtualBox API bindings and memflow + run: cargo build --manifest-path libmicrovmi/Cargo.toml --features virtualbox-api,mflow --release - name: create output directory and copy artifact content run: | diff --git a/.gitpod.Dockerfile b/.gitpod.Dockerfile index bddb579b..a57b7cd7 100644 --- a/.gitpod.Dockerfile +++ b/.gitpod.Dockerfile @@ -14,14 +14,6 @@ RUN git clone https://github.com/bitdefender/libkvmi.git \ && cd .. \ && rm -rf libkvmi -RUN git clone --depth 1 https://github.com/thalium/icebox \ - && cd icebox/src/FDP \ - && g++ -std=c++11 -shared -fPIC FDP.cpp -o libFDP.so \ - && sudo mv include/* /usr/local/include/ \ - && sudo mv libFDP.so /usr/local/lib/ \ - && cd - \ - && rm -rf icebox - RUN rustup default stable \ && cargo install cbindgen \ && rustup component add clippy rustfmt diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..380489ae --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,12 @@ +# Changelog + +## Unreleased + +### Changed + +- **Breaking:** removed the legacy FDP VirtualBox access method and its icebox/libFDP dependency. + VirtualBox callers must now select `MachineDebugger` or `VBoxVMI` explicitly in Rust, C and + Python; omitting the access method is an error. +- The `virtualbox` Cargo feature now enables both `virtualbox-api` and `virtualbox-vmi` on Unix. + Windows CI still compiles `virtualbox-api`, but neither replacement method currently works at + runtime on Windows: VBoxVMI is Unix-only, and `vboxapi` has no `VBoxCAPI.dll` loader. diff --git a/CONTEXT.md b/CONTEXT.md index a9a1ae37..cd2891ac 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -13,7 +13,7 @@ One way a single **Driver** reaches its hypervisor, selected at init time. A Dri _Avoid_: backend, mode, transport **VBoxVMI**: -A VMI protocol served from inside a patched VirtualBox VM process over a Unix domain socket, with one client per VM. Successor to FDP; not compatible with it. +A VMI protocol served from inside a patched VirtualBox VM process over a Unix domain socket, with one client per VM. **VBoxVMI method**: The VirtualBox **access method** that speaks **VBoxVMI**. Fastest option and the path toward **VMI events**; requires a patched VirtualBox. @@ -21,10 +21,6 @@ The VirtualBox **access method** that speaks **VBoxVMI**. Fastest option and the **MachineDebugger method**: The VirtualBox **access method** that uses VirtualBox's public `IMachineDebugger` COM API. Slower (cross-process RPC per call, and each memory access waits for the vCPU) and limited to **VM state access**, but intended to need no patched VirtualBox once upstreamed. -**FDP**: -Legacy protocol from the icebox project, for a patched VirtualBox 5.2 over shared memory. Superseded by **VBoxVMI**. -_Avoid_: using "FDP" for the new protocol - **RAM ranges**: The list of guest-physical address ranges backed by RAM. The highest address in it is the guest's max physical address, which can exceed the RAM size because of the MMIO hole below 4 GiB. _Avoid_: "memory size" as a stand-in for max physical address diff --git a/Cargo.lock b/Cargo.lock index ce5f4218..47051856 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -454,12 +454,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" -[[package]] -name = "custom_error" -version = "1.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f8a51dd197fa6ba5b4dc98a990a43cc13693c23eb0089ebb0fcc1f04152bca6" - [[package]] name = "darling" version = "0.20.10" @@ -658,29 +652,6 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" -[[package]] -name = "fdp" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b56b0367bf56da29c926bbec33691fe42b47bbaad3f1a0fda2eaea8c27ca603" -dependencies = [ - "custom_error", - "enum-primitive-derive 0.3.0", - "fdp-sys", - "libloading 0.8.6", - "log", - "num-traits 0.2.19", -] - -[[package]] -name = "fdp-sys" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3abffb9e8071cd13e97086d2647d44ec396368969e91de251a2dda8a9ef24d6e" -dependencies = [ - "bindgen", -] - [[package]] name = "fixed-slice-vec" version = "0.10.0" @@ -1158,7 +1129,6 @@ dependencies = [ "cty", "enum-iterator", "env_logger 0.9.3", - "fdp", "indicatif", "inventory", "kvmi", diff --git a/Cargo.toml b/Cargo.toml index 1f465fd3..f3dbc756 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,12 +25,11 @@ xen = [ ] # KVM driver kvm = ["kvmi"] -# VirtualBox driver -# FDP access method (legacy; selected when no VBoxInitParams is given) -virtualbox = ["fdp"] +# VirtualBox driver (MachineDebugger and VBoxVMI access methods) +virtualbox = ["virtualbox-api", "virtualbox-vmi"] # MachineDebugger access method virtualbox-api = ["vboxapi"] -# VBoxVMI access method (Linux only) +# VBoxVMI access method (Unix only) virtualbox-vmi = ["vboxvmi"] # memflow driver # feature name is "mflow" to avoid conflict with the dependency @@ -52,7 +51,6 @@ xenforeignmemory = { version = "=0.3.0", optional = true } xenevtchn = { version = "=0.2.0", optional = true } xenvmevent-sys = { version = "=0.2.0", optional = true } kvmi = { version = "=0.5.0", optional = true } -fdp = { version = "=0.3.0", optional = true } vboxapi = { git = "https://github.com/Wenzel/rs-vboxapi", rev = "5ba8d86d1cc58a5935208588580782a31fb2d27c", optional = true } vboxvmi = { git = "https://github.com/Wenzel/rs-vboxvmi", rev = "9a349ca535b612232a0d0b26031fedadd3d0ecb0", optional = true } winapi = { version = "0.3", features = [ diff --git a/c_examples/CMakeLists.txt b/c_examples/CMakeLists.txt index a1e578df..c78ebaf8 100644 --- a/c_examples/CMakeLists.txt +++ b/c_examples/CMakeLists.txt @@ -57,7 +57,7 @@ if (NOT WIN32) # disable pause example for windows add_executable(pause pause.c "${CMAKE_CURRENT_BINARY_DIR}/libmicrovmi.h") target_link_libraries(pause PRIVATE microvmi) - # VirtualBox MachineDebugger access method (Linux only) + # VirtualBox access-method example add_executable(vbox-read-page vbox-read-page.c "${CMAKE_CURRENT_BINARY_DIR}/libmicrovmi.h") target_link_libraries(vbox-read-page PRIVATE microvmi) endif () diff --git a/c_examples/README.md b/c_examples/README.md index 87de7cf8..801c0065 100644 --- a/c_examples/README.md +++ b/c_examples/README.md @@ -49,6 +49,6 @@ LD_LIBRARY_PATH="$LD_LIBRARY_PATH:../../target/debug" ~~~ `vbox-read-page [physical_address]` selects the VirtualBox MachineDebugger access method -(build libmicrovmi with `--features virtualbox-api`) and prints one page of guest physical memory. -Given a VBoxVMI socket path instead of a VM name (`vbox-read-page /path/to/.sock`), it uses -the VBoxVMI access method (`--features virtualbox-vmi`). +and prints one page of guest physical memory. Given a VBoxVMI socket path instead of a VM name +(`vbox-read-page /path/to/.sock`), it uses the VBoxVMI access method. Build either with the +method-specific feature or build both on Unix with `--features virtualbox`. diff --git a/doc/src/explanation/vmi_ecosystem.md b/doc/src/explanation/vmi_ecosystem.md index 5cd6ac81..cce355f4 100644 --- a/doc/src/explanation/vmi_ecosystem.md +++ b/doc/src/explanation/vmi_ecosystem.md @@ -8,7 +8,6 @@ See our talk at FOSDEM 2020: [Rustifying the Virtual Machine Introspection ecosy ## Debugging - [pyvmidbg](https://github.com/Wenzel/pyvmidbg) -- [icebox](https://github.com/thalium/icebox) - [rVMI](https://github.com/fireeye/rvmi) ## Live Memory Forensics diff --git a/doc/src/reference/api/python_api.md b/doc/src/reference/api/python_api.md index 83c5e29e..3acccebb 100644 --- a/doc/src/reference/api/python_api.md +++ b/doc/src/reference/api/python_api.md @@ -49,7 +49,7 @@ micro = Microvmi(DriverType.KVM, init_params) ### Selecting the VirtualBox access method `init_params.virtualbox` takes a `VBoxInitParamsPy`, the equivalent of the Rust `VBoxInitParams`. -Without it, the VirtualBox driver uses its default access method. +VirtualBox initialization requires one of its two access methods explicitly. ~~~Python from microvmi import Microvmi, DriverType, DriverInitParamsPy, CommonInitParamsPy, VBoxInitParamsPy diff --git a/doc/src/reference/drivers/virtualbox.md b/doc/src/reference/drivers/virtualbox.md index 02b917bf..1f56b695 100644 --- a/doc/src/reference/drivers/virtualbox.md +++ b/doc/src/reference/drivers/virtualbox.md @@ -1,16 +1,17 @@ # VirtualBox -The VirtualBox driver offers three access methods, selected at init time by `DriverInitParams.virtualbox`. -MachineDebugger and FDP name the VM with `common.vm_name`; VBoxVMI names it by its socket. +The VirtualBox driver offers two access methods, selected explicitly at init time by +`DriverInitParams.virtualbox`. MachineDebugger names the VM with `common.vm_name`; VBoxVMI names it +by its socket. The `virtualbox` Cargo feature enables both methods on Unix; the method-specific +features remain available separately. VirtualBox has one EFER register: `X86Registers::efer` and `msr_efer` both read it, and a register write with MachineDebugger or VBoxVMI fails (before writing anything) unless they are equal. | Access method | Cargo feature | Selected by | |---------------|---------------|-------------| -| VBoxVMI | `virtualbox-vmi` | `VBoxInitParams::VBoxVMI { socket_path }` | -| MachineDebugger | `virtualbox-api` | `VBoxInitParams::MachineDebugger` | -| FDP (legacy) | `virtualbox` | no `virtualbox` parameter | +| VBoxVMI | `virtualbox-vmi` or `virtualbox` | `VBoxInitParams::VBoxVMI { socket_path }` | +| MachineDebugger | `virtualbox-api` or `virtualbox` | `VBoxInitParams::MachineDebugger` | ## VBoxVMI method @@ -79,9 +80,14 @@ Not implemented yet: writing guest physical memory. ### Requirements - Platform: Linux -- A VirtualBox whose `IMachineDebugger::readPhysicalMemory` is implemented. Stock VirtualBox releases return "not implemented"; it needs a VirtualBox built with the IMachineDebugger memory methods patch. -- The VM is running, and owned by the user running libmicrovmi (the API attaches a shared session through that user's `VBoxSVC`). -- The VirtualBox C API library `VBoxXPCOMC.so` is found in `VBOX_APP_HOME`, else in `/opt/VirtualBox` or `/usr/lib/virtualbox`. Set `VBOX_APP_HOME` to the directory of a VirtualBox that is not installed (e.g. a build output `out/linux.amd64/release/bin`). +- A VirtualBox whose `IMachineDebugger::readPhysicalMemory` is implemented. Stock VirtualBox + releases return "not implemented"; it needs a VirtualBox built with the IMachineDebugger memory + methods patch. +- The VM is running and owned by the user running libmicrovmi (the API attaches a shared session + through that user's VirtualBox service). +- The VirtualBox C API runtime must be installed. On Linux, `VBoxXPCOMC.so` is found in + `VBOX_APP_HOME`, `/opt/VirtualBox` or `/usr/lib/virtualbox`; set `VBOX_APP_HOME` to a build output + such as `out/linux.amd64/release/bin`. - The driver must be used on the thread that initialized it. ### Initialization parameters @@ -89,7 +95,7 @@ Not implemented yet: writing guest physical memory. - `vm_name`: required - `virtualbox`: `VBoxInitParams::MachineDebugger` - Python: `init_params.virtualbox = VBoxInitParamsPy.MachineDebugger()` - - C: `.virtualbox = { .access_method = VBOX_ACCESS_METHOD_MACHINE_DEBUGGER }` (`VBOX_ACCESS_METHOD_NONE`, the zero value, means no `VBoxInitParams`; see `c_examples/vbox-read-page.c`) + - C: `.virtualbox = { .access_method = VBOX_ACCESS_METHOD_MACHINE_DEBUGGER }` (see `c_examples/vbox-read-page.c`) ~~~ $ cargo build --features virtualbox-api --examples @@ -104,23 +110,3 @@ $ python3 python/examples/mem-dump.py -d --vbox-machinedebugger The binding comes from the [vboxapi](https://github.com/Wenzel/rs-vboxapi) crate. To build against a local checkout of it, override the git dependency: `cargo build --features virtualbox-api --config 'patch."https://github.com/Wenzel/rs-vboxapi".vboxapi.path="../rs-vboxapi"'` - -## FDP method (legacy) - -### Requirements - -- virtualbox modified via [icebox](https://github.com/thalium/icebox) project -- Platform: Windows/Linux - -To compile `libFDP` -~~~ -$ git clone --depth 1 https://github.com/thalium/icebox -$ cd icebox/src/FDP -$ g++ -std=c++11 -shared -fPIC FDP.cpp -o libFDP.so -$ sudo mv include/* /usr/local/include/ -$ sudo mv libFDP.so /usr/local/lib/ -~~~ - -### Initialization parameters - -- `vm_name`: required diff --git a/doc/src/tutorial/installation.md b/doc/src/tutorial/installation.md index dc49f523..b66f6efa 100644 --- a/doc/src/tutorial/installation.md +++ b/doc/src/tutorial/installation.md @@ -45,9 +45,11 @@ It contains both the library and the headers to get started with C development - `libmicrovmi.h` Bundled drivers: -- Virtualbox - memflow +The Windows artifact compile-checks `virtualbox-api`, but no VirtualBox access method currently +works at runtime on Windows: VBoxVMI is Unix-only, and `vboxapi` does not yet load `VBoxCAPI.dll`. + ## Python - PyPI libmicrovmi is exposing a Python interface through a native extension. diff --git a/docs/adr/0003-drop-fdp-access-method.md b/docs/adr/0003-drop-fdp-access-method.md new file mode 100644 index 00000000..9d52495c --- /dev/null +++ b/docs/adr/0003-drop-fdp-access-method.md @@ -0,0 +1,24 @@ +# Drop the FDP VirtualBox access method + +## Context + +The legacy **FDP** access method depends on icebox's patched VirtualBox 5.2 and an external libFDP +build. The **MachineDebugger method** now provides VM state access through VirtualBox's public API, +and the **VBoxVMI method** provides the fast patched-VirtualBox path and the route to VMI events. +Keeping FDP would retain a third implementation, obsolete build infrastructure and an implicit +fallback when callers omitted an access method. + +## Decision + +Remove the FDP driver, dependency and build setup. The `virtualbox` Cargo feature enables both +replacement methods on Unix. Windows CI compiles `virtualbox-api`, but this is compile coverage only. +VirtualBox initialization must select `MachineDebugger` or `VBoxVMI` explicitly; there is no default +access method. + +## Consequences + +- Existing FDP and icebox users must migrate to MachineDebugger or VBoxVMI. +- Rust, C and Python callers that relied on an omitted VirtualBox access method now receive an error. +- Neither replacement currently works at runtime on Windows: VBoxVMI is Unix-only, and `vboxapi` + loads the Linux `VBoxXPCOMC.so` rather than Windows' `VBoxCAPI.dll`. +- The project no longer builds or distributes libFDP. diff --git a/python/Dockerfile b/python/Dockerfile index 8b668645..da8832b2 100644 --- a/python/Dockerfile +++ b/python/Dockerfile @@ -29,13 +29,3 @@ RUN git clone https://github.com/bitdefender/libkvmi.git \ && make install \ && cd .. \ && rm -rf libkvmi - -# libFDP.so -RUN git clone --depth 1 https://github.com/thalium/icebox \ - && cd icebox/src/FDP \ - && g++ -std=c++11 -shared -fPIC FDP.cpp -o libFDP.so -lrt \ - && mv include/* /usr/local/include/ \ - && mv libFDP.so /usr/local/lib/ \ - && cd - \ - && rm -rf icebox - diff --git a/python/microvmi/volatility/vmi_handler.py b/python/microvmi/volatility/vmi_handler.py index 9252043b..6f6a5edb 100644 --- a/python/microvmi/volatility/vmi_handler.py +++ b/python/microvmi/volatility/vmi_handler.py @@ -65,6 +65,8 @@ def url_to_driver_parameters(url: str) -> Tuple[Optional[DriverType], Optional[D driver_type: Optional[DriverType] = _parse_hypervisor(parsed_url.netloc) # init params init_params: Optional[DriverInitParamsPy] = _parse_driver_init_params(parsed_url.query) + if driver_type == DriverType.VirtualBox and (init_params is None or init_params.virtualbox is None): + raise MicrovmiHandlerError("VirtualBox requires vbox_access_method=machinedebugger or vboxvmi") return driver_type, init_params diff --git a/python/tests/unit/test_handler.py b/python/tests/unit/test_handler.py index d629ba53..fb51edc0 100644 --- a/python/tests/unit/test_handler.py +++ b/python/tests/unit/test_handler.py @@ -38,7 +38,8 @@ def test_parse_hypervisor_none(): @pytest.mark.parametrize("drv_type_enum_member", [d for d in DriverType]) def test_parse_hypervisor_each(drv_type_enum_member): expected = drv_type_enum_member - url = f"vmi://{drv_type_enum_member.name}/vm_name" + query = "?vbox_access_method=machinedebugger" if expected == DriverType.VirtualBox else "" + url = f"vmi://{drv_type_enum_member.name}/vm_name{query}" drv_type, _ = url_to_driver_parameters(url) assert expected == drv_type @@ -71,6 +72,11 @@ def test_parse_init_param_vbox_access_method(): assert isinstance(init_params.virtualbox, VBoxInitParamsPy.MachineDebugger) +def test_parse_vbox_requires_access_method(): + with pytest.raises(MicrovmiHandlerError, match="requires vbox_access_method"): + url_to_driver_parameters("vmi://VirtualBox/?vm_name=windows10") + + def test_parse_init_param_vbox_vboxvmi(): url = "vmi://VirtualBox/?vbox_access_method=vboxvmi&vbox_socket_path=/tmp/vm.sock" _, init_params = url_to_driver_parameters(url) @@ -83,13 +89,13 @@ def test_parse_init_param_vbox_vboxvmi_no_socket(): with pytest.raises(MicrovmiHandlerError): url_to_driver_parameters(url) + def test_parse_init_param_vbox_socket_requires_vboxvmi(): url = "vmi:///?vbox_access_method=machinedebugger&vbox_socket_path=/tmp/vm.sock" with pytest.raises(MicrovmiHandlerError): url_to_driver_parameters(url) - def test_parse_init_param_vbox_access_method_unknown(): url = "vmi:///?vbox_access_method=unknown" with pytest.raises(MicrovmiHandlerError): diff --git a/src/api/params.rs b/src/api/params.rs index 7fd255be..756522de 100644 --- a/src/api/params.rs +++ b/src/api/params.rs @@ -53,15 +53,16 @@ pub struct MemflowInitParams { pub connector_args: Option, } -/// VirtualBox initialization parameters: selects the access method +/// VirtualBox initialization parameters: selects one of the two access methods. /// -/// FDP and MachineDebugger name the VM by `common.vm_name`; VBoxVMI ignores it, as the socket -/// belongs to one VM. Without these parameters, the legacy FDP access method is used. +/// MachineDebugger names the VM by `common.vm_name`; VBoxVMI names it by its socket. VirtualBox +/// initialization requires this parameter. #[derive(Debug, Clone, PartialEq)] pub enum VBoxInitParams { - /// MachineDebugger access method (`virtualbox-api` feature): VirtualBox's `IMachineDebugger` API + /// MachineDebugger access method (`virtualbox-api` or `virtualbox` feature): + /// VirtualBox's `IMachineDebugger` API MachineDebugger, - /// VBoxVMI access method (`virtualbox-vmi` feature): the VM's VBoxVMI socket + /// VBoxVMI access method (`virtualbox-vmi` or `virtualbox` feature): the VM's VBoxVMI socket VBoxVMI { socket_path: String }, } @@ -95,12 +96,6 @@ pub struct CommonInitParams { /// kvm: Some(KVMInitParams::UnixSocket { path: String::from("/tmp/introspector")}), /// ..Default::default() /// }; -/// // VirtualBox, FDP access method -/// // common.vm_name: mandatory -/// let init_params = DriverInitParams { -/// common: Some(CommonInitParams { vm_name: String::from("windows10")}), -/// ..Default::default() -/// }; /// // VirtualBox, MachineDebugger access method /// // common.vm_name: mandatory /// let init_params = DriverInitParams { diff --git a/src/capi/params.rs b/src/capi/params.rs index b80a7998..4fe4209a 100644 --- a/src/capi/params.rs +++ b/src/capi/params.rs @@ -41,20 +41,20 @@ pub struct MemflowInitParamsFFI { pub connector_args: MemflowConnectorParamsFFI, } -/// `VBoxInitParamsFFI.access_method`: no `VBoxInitParams` (the VirtualBox driver's default access method) -pub const VBOX_ACCESS_METHOD_NONE: u32 = 0; +// `access_method = 0` means no VirtualBox parameters. Selecting the VirtualBox driver with this +// value is an error. /// `VBoxInitParamsFFI.access_method`: `VBoxInitParams::MachineDebugger` pub const VBOX_ACCESS_METHOD_MACHINE_DEBUGGER: u32 = 1; /// `VBoxInitParamsFFI.access_method`: `VBoxInitParams::VBoxVMI`, with `VBoxInitParamsFFI.socket_path` pub const VBOX_ACCESS_METHOD_VBOXVMI: u32 = 2; -/// equivalent of `Option` with C compatibility +/// equivalent of `Option` with C compatibility. /// -/// A zero-initialized struct means no `VBoxInitParams`. +/// A zero-initialized struct has no VirtualBox parameters. #[repr(C)] #[derive(Debug, Clone)] pub struct VBoxInitParamsFFI { - /// one of the `VBOX_ACCESS_METHOD_*` constants + /// one of the `VBOX_ACCESS_METHOD_*` constants, or zero for no VirtualBox parameters // plain integer, not a Rust enum: an out-of-range value from C would be UB in an enum pub access_method: u32, /// `VBOX_ACCESS_METHOD_VBOXVMI`: the VM's VBoxVMI socket path (mandatory); ignored otherwise @@ -136,7 +136,7 @@ impl TryFrom for DriverInitParams { }) }; let virtualbox = match value.virtualbox.access_method { - VBOX_ACCESS_METHOD_NONE => None, + 0 => None, VBOX_ACCESS_METHOD_MACHINE_DEBUGGER => Some(VBoxInitParams::MachineDebugger), VBOX_ACCESS_METHOD_VBOXVMI => { let path = value.virtualbox.socket_path; diff --git a/src/driver/mod.rs b/src/driver/mod.rs index fb98df2b..cefc3003 100644 --- a/src/driver/mod.rs +++ b/src/driver/mod.rs @@ -2,8 +2,6 @@ pub mod kvm; #[cfg(feature = "mflow")] pub mod memflow; -#[cfg(feature = "virtualbox")] -pub mod virtualbox; #[cfg(feature = "virtualbox-api")] pub mod virtualbox_api; #[cfg(feature = "virtualbox-vmi")] diff --git a/src/driver/virtualbox.rs b/src/driver/virtualbox.rs deleted file mode 100644 index 083966fc..00000000 --- a/src/driver/virtualbox.rs +++ /dev/null @@ -1,130 +0,0 @@ -use std::error::Error; - -use fdp::{RegisterType, FDP}; - -use crate::api::params::DriverInitParams; -use crate::api::registers::{Registers, SegmentReg, SystemTableReg, X86Registers}; -use crate::api::{DriverType, Introspectable, PAGE_SIZE}; - -#[derive(Debug)] -pub struct VBox { - fdp: FDP, -} - -#[derive(thiserror::Error, Debug)] -pub enum VBoxDriverError { - #[error("VirtualBox driver requires a VM name parameter")] - MissingVMName, -} - -impl VBox { - pub fn new(init_params: DriverInitParams) -> Result> { - let domain_name = init_params - .common - .ok_or(VBoxDriverError::MissingVMName)? - .vm_name; - - // init FDP - let fdp = FDP::new(&domain_name)?; - Ok(VBox { fdp }) - } -} - -impl Introspectable for VBox { - fn get_vcpu_count(&self) -> Result> { - // no API to fetch VCPU count, hardcode to 1 for now - Ok(1) - } - - fn read_physical( - &self, - paddr: u64, - buf: &mut [u8], - bytes_read: &mut u64, - ) -> Result<(), Box> { - for (i, chunk) in buf.chunks_mut(PAGE_SIZE as usize).enumerate() { - let offset = i * PAGE_SIZE as usize; - let cur_paddr = paddr + offset as u64; - self.fdp.read_physical_memory(cur_paddr, chunk)?; - } - *bytes_read = buf.len() as u64; - Ok(()) - } - - fn get_max_physical_addr(&self) -> Result> { - self.fdp.get_physical_memory_size() - } - - fn read_registers(&self, vcpu: u16) -> Result> { - let fdp_vcpu = vcpu as u32; - let regs = X86Registers { - rax: self.fdp.read_register(fdp_vcpu, RegisterType::RAX)?, - rbx: self.fdp.read_register(fdp_vcpu, RegisterType::RBX)?, - rcx: self.fdp.read_register(fdp_vcpu, RegisterType::RCX)?, - rdx: self.fdp.read_register(fdp_vcpu, RegisterType::RDX)?, - rsi: self.fdp.read_register(fdp_vcpu, RegisterType::RSI)?, - rdi: self.fdp.read_register(fdp_vcpu, RegisterType::RDI)?, - rbp: self.fdp.read_register(fdp_vcpu, RegisterType::RBP)?, - rsp: self.fdp.read_register(fdp_vcpu, RegisterType::RSP)?, - r8: self.fdp.read_register(fdp_vcpu, RegisterType::R8)?, - r9: self.fdp.read_register(fdp_vcpu, RegisterType::R9)?, - r10: self.fdp.read_register(fdp_vcpu, RegisterType::R10)?, - r11: self.fdp.read_register(fdp_vcpu, RegisterType::R11)?, - r12: self.fdp.read_register(fdp_vcpu, RegisterType::R12)?, - r13: self.fdp.read_register(fdp_vcpu, RegisterType::R13)?, - r14: self.fdp.read_register(fdp_vcpu, RegisterType::R14)?, - r15: self.fdp.read_register(fdp_vcpu, RegisterType::R15)?, - rip: self.fdp.read_register(fdp_vcpu, RegisterType::RIP)?, - cr0: self.fdp.read_register(fdp_vcpu, RegisterType::CR0)?, - cr2: self.fdp.read_register(fdp_vcpu, RegisterType::CR2)?, - cr3: self.fdp.read_register(fdp_vcpu, RegisterType::CR3)?, - cr4: self.fdp.read_register(fdp_vcpu, RegisterType::CR4)?, - cs: SegmentReg { - base: self.fdp.read_register(fdp_vcpu, RegisterType::CS)?, - ..Default::default() - }, - ds: SegmentReg { - base: self.fdp.read_register(fdp_vcpu, RegisterType::DS)?, - ..Default::default() - }, - es: SegmentReg { - base: self.fdp.read_register(fdp_vcpu, RegisterType::ES)?, - ..Default::default() - }, - fs: SegmentReg { - base: self.fdp.read_register(fdp_vcpu, RegisterType::FS)?, - ..Default::default() - }, - gs: SegmentReg { - base: self.fdp.read_register(fdp_vcpu, RegisterType::GS)?, - ..Default::default() - }, - ss: SegmentReg { - base: self.fdp.read_register(fdp_vcpu, RegisterType::SS)?, - ..Default::default() - }, - gdt: SystemTableReg { - base: self.fdp.read_register(fdp_vcpu, RegisterType::GDTR_BASE)?, - limit: self.fdp.read_register(fdp_vcpu, RegisterType::GDTR_LIMIT)? as u16, - }, - idt: SystemTableReg { - base: self.fdp.read_register(fdp_vcpu, RegisterType::IDTR_BASE)?, - limit: self.fdp.read_register(fdp_vcpu, RegisterType::IDTR_LIMIT)? as u16, - }, - ..Default::default() - }; - Ok(Registers::X86(regs)) - } - - fn pause(&mut self) -> Result<(), Box> { - self.fdp.pause() - } - - fn resume(&mut self) -> Result<(), Box> { - self.fdp.resume() - } - - fn get_driver_type(&self) -> DriverType { - DriverType::VirtualBox - } -} diff --git a/src/errors.rs b/src/errors.rs index 08f7e148..1a33771f 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -7,6 +7,8 @@ pub enum MicrovmiError { NoDriverAvailable, #[error("driver {0:?} has not been compiled")] DriverNotCompiled(DriverType), + #[error("{0:?} requires an explicit access method")] + AccessMethodRequired(DriverType), #[error("{driver:?} access method {method} has not been compiled (enable feature {feature})")] AccessMethodNotCompiled { driver: DriverType, diff --git a/src/lib.rs b/src/lib.rs index c83e9cac..dfe247d7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,8 +25,6 @@ use api::Introspectable; use driver::kvm::Kvm; #[cfg(feature = "mflow")] use driver::memflow::Memflow; -#[cfg(feature = "virtualbox")] -use driver::virtualbox::VBox; #[cfg(feature = "virtualbox-api")] use driver::virtualbox_api::VBoxApi; #[cfg(feature = "virtualbox-vmi")] @@ -135,11 +133,7 @@ fn init_driver( DriverType::KVM => Ok(Box::new(Kvm::new(create_kvmi()?, _init_params)?)), #[cfg(feature = "mflow")] DriverType::Memflow => Ok(Box::new(Memflow::new(_init_params)?)), - #[cfg(any( - feature = "virtualbox", - feature = "virtualbox-api", - feature = "virtualbox-vmi" - ))] + #[cfg(any(feature = "virtualbox-api", feature = "virtualbox-vmi"))] DriverType::VirtualBox => init_virtualbox(_init_params), #[cfg(feature = "xen")] DriverType::Xen => Ok(Box::new(Xen::new(_init_params)?)), @@ -149,11 +143,7 @@ fn init_driver( } /// Initialize the VirtualBox driver with the access method selected by `init_params.virtualbox` -#[cfg(any( - feature = "virtualbox", - feature = "virtualbox-api", - feature = "virtualbox-vmi" -))] +#[cfg(any(feature = "virtualbox-api", feature = "virtualbox-vmi"))] fn init_virtualbox( init_params: DriverInitParams, ) -> Result, MicrovmiError> { @@ -177,14 +167,6 @@ fn init_virtualbox( method: "VBoxVMI", feature: "virtualbox-vmi", }), - #[cfg(feature = "virtualbox")] - None => Ok(Box::new(VBox::new(init_params)?)), - // FDP is the default when no access method is selected - #[cfg(not(feature = "virtualbox"))] - None => Err(MicrovmiError::AccessMethodNotCompiled { - driver: DriverType::VirtualBox, - method: "FDP", - feature: "virtualbox", - }), + None => Err(MicrovmiError::AccessMethodRequired(DriverType::VirtualBox)), } } diff --git a/tests/virtualbox_errors.rs b/tests/virtualbox_errors.rs index 4afbc632..e0326e2c 100644 --- a/tests/virtualbox_errors.rs +++ b/tests/virtualbox_errors.rs @@ -26,6 +26,21 @@ fn init_error( } } +#[cfg(any( + feature = "virtualbox-vmi", + all(feature = "virtualbox-api", not(feature = "kvm")) +))] +#[test] +fn virtualbox_access_method_is_required() { + use microvmi::api::params::DriverInitParams; + use microvmi::api::DriverType; + + assert_eq!( + init_error(Some(DriverType::VirtualBox), DriverInitParams::default()), + "VirtualBox requires an explicit access method" + ); +} + #[cfg(feature = "virtualbox-vmi")] mod vbox_vmi { use std::fs::DirBuilder;