diff --git a/fuzzers/fuzzbench_fork_qemu/src/fuzzer.rs b/fuzzers/fuzzbench_fork_qemu/src/fuzzer.rs index 8af53417c8..862bbc243a 100644 --- a/fuzzers/fuzzbench_fork_qemu/src/fuzzer.rs +++ b/fuzzers/fuzzbench_fork_qemu/src/fuzzer.rs @@ -51,8 +51,8 @@ use libafl_qemu::{ elf::EasyElf, filter_qemu_args, hooks::QemuHooks, - GuestReg, MmapPerms, Qemu, QemuExitReason, QemuExitReasonError, QemuForkExecutor, - QemuShutdownCause, Regs, + GuestReg, MmapPerms, Qemu, QemuExitError, QemuExitReason, QemuForkExecutor, QemuShutdownCause, + Regs, }; #[cfg(unix)] use nix::unistd::dup; @@ -328,7 +328,7 @@ fn fuzz( Ok(QemuExitReason::End(QemuShutdownCause::HostSignal(Signal::SigInterrupt))) => { process::exit(0) } - Err(QemuExitReasonError::UnexpectedExit) => return ExitKind::Crash, + Err(QemuExitError::UnexpectedExit) => return ExitKind::Crash, _ => panic!("Unexpected QEMU exit."), } } diff --git a/fuzzers/fuzzbench_qemu/src/fuzzer.rs b/fuzzers/fuzzbench_qemu/src/fuzzer.rs index bd4430825d..1c60226ca0 100644 --- a/fuzzers/fuzzbench_qemu/src/fuzzer.rs +++ b/fuzzers/fuzzbench_qemu/src/fuzzer.rs @@ -60,8 +60,8 @@ use libafl_qemu::{ MmapPerms, Qemu, QemuExecutor, + QemuExitError, QemuExitReason, - QemuExitReasonError, QemuShutdownCause, Regs, }; @@ -350,7 +350,7 @@ fn fuzz( Ok(QemuExitReason::End(QemuShutdownCause::HostSignal(Signal::SigInterrupt))) => { process::exit(0) } - Err(QemuExitReasonError::UnexpectedExit) => return ExitKind::Crash, + Err(QemuExitError::UnexpectedExit) => return ExitKind::Crash, _ => panic!("Unexpected QEMU exit."), } } diff --git a/fuzzers/qemu_cmin/src/fuzzer.rs b/fuzzers/qemu_cmin/src/fuzzer.rs index 18f4cbc7bf..efbdf5a614 100644 --- a/fuzzers/qemu_cmin/src/fuzzer.rs +++ b/fuzzers/qemu_cmin/src/fuzzer.rs @@ -29,8 +29,8 @@ use libafl_bolts::{ use libafl_qemu::{ edges::{QemuEdgeCoverageChildHelper, EDGES_MAP_PTR, EDGES_MAP_SIZE_IN_USE}, elf::EasyElf, - ArchExtras, CallingConvention, GuestAddr, GuestReg, MmapPerms, Qemu, QemuExitReason, - QemuExitReasonError, QemuForkExecutor, QemuHooks, QemuShutdownCause, Regs, + ArchExtras, CallingConvention, GuestAddr, GuestReg, MmapPerms, Qemu, QemuExitError, + QemuExitReason, QemuForkExecutor, QemuHooks, QemuShutdownCause, Regs, }; #[derive(Default)] @@ -211,7 +211,7 @@ pub fn fuzz() -> Result<(), Error> { Ok(QemuExitReason::End(QemuShutdownCause::HostSignal(Signal::SigInterrupt))) => { process::exit(0) } - Err(QemuExitReasonError::UnexpectedExit) => return ExitKind::Crash, + Err(QemuExitError::UnexpectedExit) => return ExitKind::Crash, _ => panic!("Unexpected QEMU exit."), } } diff --git a/fuzzers/qemu_systemmode/src/fuzzer_breakpoint.rs b/fuzzers/qemu_systemmode/src/fuzzer_breakpoint.rs index 09dc99a269..de8bab842e 100644 --- a/fuzzers/qemu_systemmode/src/fuzzer_breakpoint.rs +++ b/fuzzers/qemu_systemmode/src/fuzzer_breakpoint.rs @@ -5,7 +5,7 @@ use std::{env, path::PathBuf, process}; use libafl::{ corpus::{Corpus, InMemoryCorpus, OnDiskCorpus}, - events::{launcher::Launcher, EventConfig, CTRL_C_EXIT}, + events::{launcher::Launcher, EventConfig}, executors::ExitKind, feedback_or, feedback_or_fast, feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback}, @@ -29,13 +29,13 @@ use libafl_bolts::{ }; use libafl_qemu::{ breakpoint::Breakpoint, - command::{Command, EmulatorMemoryChunk, EndCommand, StartCommand}, + command::{Command, EndCommand, StartCommand}, edges::{edges_map_mut_ptr, QemuEdgeCoverageHelper, EDGES_MAP_SIZE_IN_USE, MAX_EDGES_FOUND}, elf::EasyElf, emu::Emulator, executor::{stateful::StatefulQemuExecutor, QemuExecutorState}, - EmuExitReasonError, FastSnapshotManager, GuestPhysAddr, GuestReg, HandlerError, HandlerResult, - QemuHooks, StdEmuExitHandler, + EmulatorMemoryChunk, FastSnapshotManager, GuestPhysAddr, GuestReg, QemuHooks, + StdEmulatorExitHandler, }; // use libafl_qemu::QemuSnapshotBuilder; // for normal qemu snapshot @@ -93,7 +93,7 @@ pub fn fuzz() { let emu_snapshot_manager = FastSnapshotManager::new(false); // Choose Exit Handler - let emu_exit_handler = StdEmuExitHandler::new(emu_snapshot_manager); + let emu_exit_handler = StdEmulatorExitHandler::new(emu_snapshot_manager); // Create emulator let emu = Emulator::new(&args, &env, emu_exit_handler).unwrap(); @@ -126,29 +126,10 @@ pub fn fuzz() { // The wrapped harness function, calling out to the LLVM-style harness let mut harness = |input: &BytesInput, qemu_executor_state: &mut QemuExecutorState<_, _>| unsafe { - match emu.run(input, qemu_executor_state) { - Ok(handler_result) => match handler_result { - HandlerResult::UnhandledExit(unhandled_exit) => { - panic!("Unhandled exit: {}", unhandled_exit) - } - HandlerResult::EndOfRun(exit_kind) => return exit_kind, - HandlerResult::Interrupted => { - std::process::exit(CTRL_C_EXIT); - } - }, - Err(handler_error) => match handler_error { - HandlerError::QemuExitReasonError(emu_exit_reason_error) => { - match emu_exit_reason_error { - EmuExitReasonError::UnknownKind => panic!("unknown kind"), - EmuExitReasonError::UnexpectedExit => return ExitKind::Crash, - _ => { - panic!("Emu Exit unhandled error: {:?}", emu_exit_reason_error) - } - } - } - _ => panic!("Unhandled error: {:?}", handler_error), - }, - } + emu.run(input, qemu_executor_state) + .unwrap() + .try_into() + .unwrap() }; // Create an observation channel using the coverage map diff --git a/fuzzers/qemu_systemmode/src/fuzzer_classic.rs b/fuzzers/qemu_systemmode/src/fuzzer_classic.rs index 362a3d73d0..7e180977c5 100644 --- a/fuzzers/qemu_systemmode/src/fuzzer_classic.rs +++ b/fuzzers/qemu_systemmode/src/fuzzer_classic.rs @@ -5,7 +5,7 @@ use std::{env, path::PathBuf, process}; use libafl::{ corpus::{Corpus, InMemoryCorpus, OnDiskCorpus}, - events::{launcher::Launcher, EventConfig, CTRL_C_EXIT}, + events::{launcher::Launcher, EventConfig}, executors::ExitKind, feedback_or, feedback_or_fast, feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback}, @@ -22,7 +22,7 @@ use libafl::{ use libafl_bolts::{ core_affinity::Cores, current_nanos, - os::unix_signals::Signal, + os::unix_signals::{Signal, CTRL_C_EXIT}, ownedref::OwnedMutSlice, rands::StdRand, shmem::{ShMemProvider, StdShMemProvider}, @@ -32,8 +32,7 @@ use libafl_bolts::{ use libafl_qemu::{ edges::{edges_map_mut_ptr, QemuEdgeCoverageHelper, EDGES_MAP_SIZE_IN_USE, MAX_EDGES_FOUND}, elf::EasyElf, - emu::Qemu, - QemuExecutor, QemuExitReason, QemuExitReasonError, QemuHooks, QemuShutdownCause, Regs, + Qemu, QemuExecutor, QemuExitError, QemuExitReason, QemuHooks, QemuShutdownCause, Regs, }; use libafl_qemu_sys::GuestPhysAddr; @@ -128,7 +127,7 @@ pub fn fuzz() { Ok(QemuExitReason::End(QemuShutdownCause::HostSignal( Signal::SigInterrupt, ))) => process::exit(CTRL_C_EXIT), - Err(QemuExitReasonError::UnexpectedExit) => return ExitKind::Crash, + Err(QemuExitError::UnexpectedExit) => return ExitKind::Crash, _ => panic!("Unexpected QEMU exit."), } diff --git a/fuzzers/qemu_systemmode/src/fuzzer_sync_exit.rs b/fuzzers/qemu_systemmode/src/fuzzer_sync_exit.rs index c6d2dacfd2..b7b45e4954 100644 --- a/fuzzers/qemu_systemmode/src/fuzzer_sync_exit.rs +++ b/fuzzers/qemu_systemmode/src/fuzzer_sync_exit.rs @@ -5,8 +5,7 @@ use std::{env, path::PathBuf, process}; use libafl::{ corpus::{Corpus, InMemoryCorpus, OnDiskCorpus}, - events::{launcher::Launcher, EventConfig, CTRL_C_EXIT}, - executors::ExitKind, + events::{launcher::Launcher, EventConfig}, feedback_or, feedback_or_fast, feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback}, fuzzer::{Fuzzer, StdFuzzer}, @@ -31,8 +30,7 @@ use libafl_qemu::{ edges::{edges_map_mut_ptr, QemuEdgeCoverageHelper, EDGES_MAP_SIZE_IN_USE, MAX_EDGES_FOUND}, emu::Emulator, executor::{stateful::StatefulQemuExecutor, QemuExecutorState}, - EmuExitReasonError, FastSnapshotManager, HandlerError, HandlerResult, QemuHooks, - StdEmuExitHandler, + FastSnapshotManager, QemuHooks, StdEmulatorExitHandler, }; // use libafl_qemu::QemuSnapshotBuilder; for normal qemu snapshot @@ -56,8 +54,8 @@ pub fn fuzz() { let env: Vec<(String, String)> = env::vars().collect(); // let emu_snapshot_manager = QemuSnapshotBuilder::new(true); let emu_snapshot_manager = FastSnapshotManager::new(false); // Create a snapshot manager (normal or fast for now). - let emu_exit_handler: StdEmuExitHandler = - StdEmuExitHandler::new(emu_snapshot_manager); // Create an exit handler: it is the entity taking the decision of what should be done when QEMU returns. + let emu_exit_handler: StdEmulatorExitHandler = + StdEmulatorExitHandler::new(emu_snapshot_manager); // Create an exit handler: it is the entity taking the decision of what should be done when QEMU returns. let emu = Emulator::new(&args, &env, emu_exit_handler).unwrap(); // Create the emulator let devices = emu.list_devices(); @@ -66,30 +64,10 @@ pub fn fuzz() { // The wrapped harness function, calling out to the LLVM-style harness let mut harness = |input: &BytesInput, qemu_executor_state: &mut QemuExecutorState<_, _>| unsafe { - match emu.run(input, qemu_executor_state) { - Ok(handler_result) => match handler_result { - HandlerResult::UnhandledExit(unhandled_exit) => { - panic!("Unhandled exit: {}", unhandled_exit) - } - HandlerResult::EndOfRun(exit_kind) => exit_kind, - HandlerResult::Interrupted => { - println!("Interrupted."); - std::process::exit(CTRL_C_EXIT); - } - }, - Err(handler_error) => match handler_error { - HandlerError::QemuExitReasonError(emu_exit_reason_error) => { - match emu_exit_reason_error { - EmuExitReasonError::UnknownKind => panic!("unknown kind"), - EmuExitReasonError::UnexpectedExit => ExitKind::Crash, - _ => { - panic!("Emu Exit unhandled error: {:?}", emu_exit_reason_error) - } - } - } - _ => panic!("Unhandled error: {:?}", handler_error), - }, - } + emu.run(input, qemu_executor_state) + .unwrap() + .try_into() + .unwrap() }; // Create an observation channel using the coverage map diff --git a/libafl/src/events/llmp.rs b/libafl/src/events/llmp.rs index e350a47dbd..034a9db805 100644 --- a/libafl/src/events/llmp.rs +++ b/libafl/src/events/llmp.rs @@ -36,7 +36,9 @@ use libafl_bolts::{ tuples::{Handle, Handler}, }; #[cfg(feature = "std")] -use libafl_bolts::{llmp::LlmpConnection, shmem::StdShMemProvider, staterestore::StateRestorer}; +use libafl_bolts::{ + llmp::LlmpConnection, os::CTRL_C_EXIT, shmem::StdShMemProvider, staterestore::StateRestorer, +}; use libafl_bolts::{ llmp::{self, LlmpClient, LlmpClientDescription, Tag}, shmem::ShMemProvider, @@ -1581,7 +1583,7 @@ where compiler_fence(Ordering::SeqCst); - if child_status == crate::events::CTRL_C_EXIT || staterestorer.wants_to_exit() { + if child_status == CTRL_C_EXIT || staterestorer.wants_to_exit() { // if ctrl-c is pressed, we end up in this branch if let Err(err) = mgr.detach_from_broker(self.broker_port) { log::error!("Failed to detach from broker: {err}"); diff --git a/libafl/src/events/mod.rs b/libafl/src/events/mod.rs index de3ef0d6af..6480487e87 100644 --- a/libafl/src/events/mod.rs +++ b/libafl/src/events/mod.rs @@ -31,7 +31,7 @@ use ahash::RandomState; #[cfg(feature = "std")] pub use launcher::*; #[cfg(all(unix, feature = "std"))] -use libafl_bolts::os::unix_signals::{siginfo_t, ucontext_t, Handler, Signal}; +use libafl_bolts::os::unix_signals::{siginfo_t, ucontext_t, Handler, Signal, CTRL_C_EXIT}; #[cfg(feature = "adaptive_serialization")] use libafl_bolts::tuples::{Handle, MatchNameRef}; use libafl_bolts::{current_time, ClientId}; @@ -55,13 +55,6 @@ use crate::{ state::HasScalabilityMonitor, }; -/// The special exit code when the target exited throught ctrl-c -#[cfg(unix)] -pub const CTRL_C_EXIT: i32 = 100; -/// The special exit code when the target exited throught ctrl-c -#[cfg(windows)] -pub const CTRL_C_EXIT: i32 = -1073741510; - /// Check if ctrl-c is sent with this struct #[cfg(all(unix, feature = "std"))] pub static mut EVENTMGR_SIGHANDLER_STATE: ShutdownSignalData = ShutdownSignalData {}; diff --git a/libafl/src/events/simple.rs b/libafl/src/events/simple.rs index c77878ea4f..2e2776583b 100644 --- a/libafl/src/events/simple.rs +++ b/libafl/src/events/simple.rs @@ -18,7 +18,7 @@ use libafl_bolts::os::unix_signals::setup_signal_handler; use libafl_bolts::os::{fork, ForkResult}; use libafl_bolts::ClientId; #[cfg(feature = "std")] -use libafl_bolts::{shmem::ShMemProvider, staterestore::StateRestorer}; +use libafl_bolts::{os::CTRL_C_EXIT, shmem::ShMemProvider, staterestore::StateRestorer}; #[cfg(feature = "std")] use serde::{de::DeserializeOwned, Serialize}; @@ -520,7 +520,7 @@ where compiler_fence(Ordering::SeqCst); - if child_status == crate::events::CTRL_C_EXIT || staterestorer.wants_to_exit() { + if child_status == CTRL_C_EXIT || staterestorer.wants_to_exit() { return Err(Error::shutting_down()); } diff --git a/libafl_bolts/src/os/mod.rs b/libafl_bolts/src/os/mod.rs index 4eaabf92a9..03b27da8e1 100644 --- a/libafl_bolts/src/os/mod.rs +++ b/libafl_bolts/src/os/mod.rs @@ -9,6 +9,8 @@ pub mod unix_shmem_server; #[cfg(unix)] pub mod unix_signals; +#[cfg(unix)] +pub use unix_signals::CTRL_C_EXIT; #[cfg(all(unix, feature = "std"))] pub mod pipes; @@ -28,9 +30,10 @@ use std::{fs::File, os::fd::AsRawFd, sync::OnceLock}; #[cfg(all(windows, feature = "std"))] #[allow(missing_docs, overflowing_literals)] pub mod windows_exceptions; - #[cfg(unix)] use libc::pid_t; +#[cfg(all(windows, feature = "std"))] +pub use windows_exceptions::CTRL_C_EXIT; /// A file that we keep open, pointing to /dev/null #[cfg(all(feature = "std", unix))] diff --git a/libafl_bolts/src/os/unix_signals.rs b/libafl_bolts/src/os/unix_signals.rs index b01e0638f1..ed233047f0 100644 --- a/libafl_bolts/src/os/unix_signals.rs +++ b/libafl_bolts/src/os/unix_signals.rs @@ -20,6 +20,9 @@ pub use libc::c_ulong; #[cfg(feature = "std")] use nix::errno::{errno, Errno}; +/// The special exit code when the target exited through ctrl-c +pub const CTRL_C_EXIT: i32 = 100; + /// ARMv7-specific representation of a saved context #[cfg(target_arch = "arm")] #[derive(Debug)] @@ -302,6 +305,19 @@ pub enum Signal { SigTrap = SIGTRAP, } +#[cfg(feature = "std")] +impl Signal { + /// Handle an incoming signal + pub fn handle(&self) { + match self { + Signal::SigInterrupt | Signal::SigQuit | Signal::SigTerm => { + std::process::exit(CTRL_C_EXIT) + } + _ => {} + } + } +} + impl TryFrom<&str> for Signal { type Error = Error; diff --git a/libafl_bolts/src/os/windows_exceptions.rs b/libafl_bolts/src/os/windows_exceptions.rs index a740999abe..b7dca5952c 100644 --- a/libafl_bolts/src/os/windows_exceptions.rs +++ b/libafl_bolts/src/os/windows_exceptions.rs @@ -25,6 +25,9 @@ pub use windows::Win32::{ use crate::Error; +/// The special exit code when the target exited through ctrl-c +pub const CTRL_C_EXIT: i32 = -1073741510; + // For VEH const EXCEPTION_CONTINUE_EXECUTION: c_long = -1; diff --git a/libafl_qemu/Cargo.toml b/libafl_qemu/Cargo.toml index 3042f4537e..8ccf6d50b7 100644 --- a/libafl_qemu/Cargo.toml +++ b/libafl_qemu/Cargo.toml @@ -89,7 +89,8 @@ paste = "1" enum-map = "2.7" serde_yaml = { version = "0.8", optional = true } # For parsing the injections yaml file toml = { version = "0.4.2", optional = true } # For parsing the injections toml file -pyo3 = { version = "0.18", optional = true } +pyo3 = { version = "0.18", optional = true , features = ["multiple-pymethods"]} +bytes-utils = "0.1" # Document all features of this crate (for `cargo doc`) document-features = { version = "0.2", optional = true } diff --git a/libafl_qemu/build.rs b/libafl_qemu/build.rs index e9065387e1..904fabb106 100644 --- a/libafl_qemu/build.rs +++ b/libafl_qemu/build.rs @@ -11,10 +11,12 @@ mod host_specific { #[rustversion::nightly] fn main() { println!("cargo:rustc-cfg=nightly"); + println!("cargo::rustc-check-cfg=cfg(nightly)"); host_specific::build(); } #[rustversion::not(nightly)] fn main() { + println!("cargo::rustc-check-cfg=cfg(nightly)"); host_specific::build(); } diff --git a/libafl_qemu/build_linux.rs b/libafl_qemu/build_linux.rs index 973c519dbe..69d3359011 100644 --- a/libafl_qemu/build_linux.rs +++ b/libafl_qemu/build_linux.rs @@ -37,6 +37,7 @@ pub fn build() { let runtime_bindings_file = out_dir.join("libafl_qemu_bindings.rs"); let stub_runtime_bindings_file = src_dir.join("runtime/libafl_qemu_stub_bindings.rs"); + println!("cargo::rustc-check-cfg=cfg(emulation_mode, values(\"usermode\", \"systemmode\"))"); println!("cargo:rustc-cfg=emulation_mode=\"{emulation_mode}\""); println!("cargo:rerun-if-env-changed=EMULATION_MODE"); @@ -65,6 +66,7 @@ pub fn build() { }; println!("cargo:rerun-if-env-changed=CPU_TARGET"); println!("cargo:rustc-cfg=cpu_target=\"{cpu_target}\""); + println!("cargo::rustc-check-cfg=cfg(cpu_target, values(\"x86_64\", \"arm\", \"aarch64\", \"i386\", \"mips\", \"ppc\", \"hexagon\"))"); let cross_cc = if (emulation_mode == "usermode") && (qemu_asan || qemu_asan_guest) { // TODO try to autodetect a cross compiler with the arch name (e.g. aarch64-linux-gnu-gcc) diff --git a/libafl_qemu/libafl_qemu_build/src/bindings.rs b/libafl_qemu/libafl_qemu_build/src/bindings.rs index bf676273c5..84f0804b17 100644 --- a/libafl_qemu/libafl_qemu_build/src/bindings.rs +++ b/libafl_qemu/libafl_qemu_build/src/bindings.rs @@ -84,6 +84,7 @@ const WRAPPER_HEADER: &str = r#" #include "libafl/exit.h" #include "libafl/hook.h" #include "libafl/jit.h" +#include "libafl/utils.h" "#; diff --git a/libafl_qemu/libafl_qemu_build/src/build.rs b/libafl_qemu/libafl_qemu_build/src/build.rs index 62e7afd48c..02f5cb7325 100644 --- a/libafl_qemu/libafl_qemu_build/src/build.rs +++ b/libafl_qemu/libafl_qemu_build/src/build.rs @@ -11,7 +11,7 @@ use crate::cargo_add_rpath; const QEMU_URL: &str = "https://github.com/AFLplusplus/qemu-libafl-bridge"; const QEMU_DIRNAME: &str = "qemu-libafl-bridge"; -const QEMU_REVISION: &str = "538e6b02c36838e0de469b39dd03fef05678444f"; +const QEMU_REVISION: &str = "9f3e2399ee9b106dfbb8c3afcdfdf30e235fc88f"; #[allow(clippy::module_name_repetitions)] pub struct BuildResult { diff --git a/libafl_qemu/libafl_qemu_sys/build.rs b/libafl_qemu/libafl_qemu_sys/build.rs index 52749d9102..7fa109f2e4 100644 --- a/libafl_qemu/libafl_qemu_sys/build.rs +++ b/libafl_qemu/libafl_qemu_sys/build.rs @@ -8,6 +8,15 @@ mod host_specific { } } +#[rustversion::nightly] fn main() { + println!("cargo:rustc-cfg=nightly"); + println!("cargo::rustc-check-cfg=cfg(nightly)"); + host_specific::build(); +} + +#[rustversion::not(nightly)] +fn main() { + println!("cargo::rustc-check-cfg=cfg(nightly)"); host_specific::build(); } diff --git a/libafl_qemu/libafl_qemu_sys/build_linux.rs b/libafl_qemu/libafl_qemu_sys/build_linux.rs index e59480c6b1..9a6218ce68 100644 --- a/libafl_qemu/libafl_qemu_sys/build_linux.rs +++ b/libafl_qemu/libafl_qemu_sys/build_linux.rs @@ -46,17 +46,18 @@ pub fn build() { "usermode".to_string() }) }; + println!("cargo::rustc-check-cfg=cfg(emulation_mode, values(\"usermode\", \"systemmode\"))"); println!("cargo:rustc-cfg=emulation_mode=\"{emulation_mode}\""); println!("cargo:rerun-if-env-changed=EMULATION_MODE"); // Make sure we have at most one architecutre feature set // Else, we default to `x86_64` - having a default makes CI easier :) - assert_unique_feature!("arm", "aarch64", "i386", "i86_64", "mips", "ppc", "hexagon"); + assert_unique_feature!("arm", "aarch64", "i386", "x86_64", "mips", "ppc", "hexagon"); // Make sure that we don't have BE set for any architecture other than arm and mips // Sure aarch64 may support BE, but its not in common usage and we don't // need it yet and so haven't tested it - assert_unique_feature!("be", "aarch64", "i386", "i86_64", "hexagon"); + assert_unique_feature!("be", "aarch64", "i386", "x86_64", "hexagon"); let cpu_target = if cfg!(feature = "x86_64") { "x86_64".to_string() @@ -82,6 +83,7 @@ pub fn build() { }; println!("cargo:rerun-if-env-changed=CPU_TARGET"); println!("cargo:rustc-cfg=cpu_target=\"{cpu_target}\""); + println!("cargo::rustc-check-cfg=cfg(cpu_target, values(\"x86_64\", \"arm\", \"aarch64\", \"i386\", \"mips\", \"ppc\", \"hexagon\"))"); let jobs = env::var("NUM_JOBS") .ok() diff --git a/libafl_qemu/libafl_qemu_sys/src/lib.rs b/libafl_qemu/libafl_qemu_sys/src/lib.rs index 63722c5c33..ebe2891be9 100644 --- a/libafl_qemu/libafl_qemu_sys/src/lib.rs +++ b/libafl_qemu/libafl_qemu_sys/src/lib.rs @@ -1,3 +1,4 @@ +#![cfg_attr(nightly, feature(used_with_arg))] /*! `libafl_qemu_sys` is the crate exporting C symbols from QEMU. Have a look at `libafl_qemu` for higher-level abstractions. diff --git a/libafl_qemu/libafl_qemu_sys/src/x86_64_stub_bindings.rs b/libafl_qemu/libafl_qemu_sys/src/x86_64_stub_bindings.rs index be87633126..22f43e20da 100644 --- a/libafl_qemu/libafl_qemu_sys/src/x86_64_stub_bindings.rs +++ b/libafl_qemu/libafl_qemu_sys/src/x86_64_stub_bindings.rs @@ -2340,7 +2340,6 @@ pub type DeviceReset = ::std::option::Option for libafl_exit_reason_kind { type Output = Self; #[inline] @@ -12119,21 +12117,18 @@ fn bindgen_test_layout_libafl_exit_reason_breakpoint() { } #[repr(C)] #[derive(Debug, Default, Copy, Clone)] -pub struct libafl_exit_reason_sync_backdoor {} +pub struct libafl_exit_reason_sync_exit {} #[test] -fn bindgen_test_layout_libafl_exit_reason_sync_backdoor() { +fn bindgen_test_layout_libafl_exit_reason_sync_exit() { assert_eq!( - ::std::mem::size_of::(), + ::std::mem::size_of::(), 0usize, - concat!("Size of: ", stringify!(libafl_exit_reason_sync_backdoor)) + concat!("Size of: ", stringify!(libafl_exit_reason_sync_exit)) ); assert_eq!( - ::std::mem::align_of::(), + ::std::mem::align_of::(), 1usize, - concat!( - "Alignment of ", - stringify!(libafl_exit_reason_sync_backdoor) - ) + concat!("Alignment of ", stringify!(libafl_exit_reason_sync_exit)) ); } #[repr(C)] @@ -12200,7 +12195,7 @@ pub struct libafl_exit_reason { pub union libafl_exit_reason__bindgen_ty_1 { pub internal: libafl_exit_reason_internal, pub breakpoint: libafl_exit_reason_breakpoint, - pub backdoor: libafl_exit_reason_sync_backdoor, + pub sync_exit: libafl_exit_reason_sync_exit, } #[test] fn bindgen_test_layout_libafl_exit_reason__bindgen_ty_1() { @@ -12241,13 +12236,13 @@ fn bindgen_test_layout_libafl_exit_reason__bindgen_ty_1() { ) ); assert_eq!( - unsafe { ::std::ptr::addr_of!((*ptr).backdoor) as usize - ptr as usize }, + unsafe { ::std::ptr::addr_of!((*ptr).sync_exit) as usize - ptr as usize }, 0usize, concat!( "Offset of field: ", stringify!(libafl_exit_reason__bindgen_ty_1), "::", - stringify!(backdoor) + stringify!(sync_exit) ) ); } @@ -13890,6 +13885,9 @@ extern "C" { extern "C" { pub fn libafl_jit_trace_block_single(data: u64, id: u64) -> usize; } +extern "C" { + pub fn libafl_qemu_host_page_size() -> usize; +} #[repr(C)] #[derive(Debug, Default, Copy, Clone)] pub struct kvm_dirty_gfn { diff --git a/libafl_qemu/runtime/libafl_qemu_stub_bindings.rs b/libafl_qemu/runtime/libafl_qemu_stub_bindings.rs index b7dbc82441..179a586e02 100644 --- a/libafl_qemu/runtime/libafl_qemu_stub_bindings.rs +++ b/libafl_qemu/runtime/libafl_qemu_stub_bindings.rs @@ -26,6 +26,7 @@ pub const __USE_ATFILE: u32 = 1; pub const __USE_FORTIFY_LEVEL: u32 = 0; pub const __GLIBC_USE_DEPRECATED_GETS: u32 = 0; pub const __GLIBC_USE_DEPRECATED_SCANF: u32 = 0; +pub const __GLIBC_USE_C2X_STRTOL: u32 = 0; pub const _STDC_PREDEF_H: u32 = 1; pub const __STDC_IEC_559__: u32 = 1; pub const __STDC_IEC_60559_BFP__: u32 = 201404; @@ -34,7 +35,7 @@ pub const __STDC_IEC_60559_COMPLEX__: u32 = 201404; pub const __STDC_ISO_10646__: u32 = 201706; pub const __GNU_LIBRARY__: u32 = 6; pub const __GLIBC__: u32 = 2; -pub const __GLIBC_MINOR__: u32 = 35; +pub const __GLIBC_MINOR__: u32 = 39; pub const _SYS_CDEFS_H: u32 = 1; pub const __glibc_c99_flexarr_available: u32 = 1; pub const __LDOUBLE_REDIRECTS_TO_FLOAT128_ABI: u32 = 0; @@ -58,6 +59,7 @@ pub const _BITS_TIME64_H: u32 = 1; pub const _BITS_WCHAR_H: u32 = 1; pub const _BITS_STDINT_INTN_H: u32 = 1; pub const _BITS_STDINT_UINTN_H: u32 = 1; +pub const _BITS_STDINT_LEAST_H: u32 = 1; pub const INT8_MIN: i32 = -128; pub const INT16_MIN: i32 = -32768; pub const INT32_MIN: i32 = -2147483648; diff --git a/libafl_qemu/src/arch/aarch64.rs b/libafl_qemu/src/arch/aarch64.rs index 023cb34da5..dec882c829 100644 --- a/libafl_qemu/src/arch/aarch64.rs +++ b/libafl_qemu/src/arch/aarch64.rs @@ -8,7 +8,7 @@ use pyo3::prelude::*; pub use strum_macros::EnumIter; pub use syscall_numbers::aarch64::*; -use crate::{sync_exit::BackdoorArgs, CallingConvention}; +use crate::{sync_exit::ExitArgs, CallingConvention}; #[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)] #[repr(i32)] @@ -49,19 +49,19 @@ pub enum Regs { Pstate = 33, } -static BACKDOOR_ARCH_REGS: OnceLock> = OnceLock::new(); +static EXIT_ARCH_REGS: OnceLock> = OnceLock::new(); -pub fn get_backdoor_arch_regs() -> &'static EnumMap { - BACKDOOR_ARCH_REGS.get_or_init(|| { +pub fn get_exit_arch_regs() -> &'static EnumMap { + EXIT_ARCH_REGS.get_or_init(|| { enum_map! { - BackdoorArgs::Ret => Regs::X0, - BackdoorArgs::Cmd => Regs::X0, - BackdoorArgs::Arg1 => Regs::X1, - BackdoorArgs::Arg2 => Regs::X2, - BackdoorArgs::Arg3 => Regs::X3, - BackdoorArgs::Arg4 => Regs::X4, - BackdoorArgs::Arg5 => Regs::X5, - BackdoorArgs::Arg6 => Regs::X6, + ExitArgs::Ret => Regs::X0, + ExitArgs::Cmd => Regs::X0, + ExitArgs::Arg1 => Regs::X1, + ExitArgs::Arg2 => Regs::X2, + ExitArgs::Arg3 => Regs::X3, + ExitArgs::Arg4 => Regs::X4, + ExitArgs::Arg5 => Regs::X5, + ExitArgs::Arg6 => Regs::X6, } }) } diff --git a/libafl_qemu/src/arch/arm.rs b/libafl_qemu/src/arch/arm.rs index 42b6d8d24f..19d799af17 100644 --- a/libafl_qemu/src/arch/arm.rs +++ b/libafl_qemu/src/arch/arm.rs @@ -8,7 +8,7 @@ use pyo3::prelude::*; pub use strum_macros::EnumIter; pub use syscall_numbers::arm::*; -use crate::{sync_exit::BackdoorArgs, CallingConvention}; +use crate::{sync_exit::ExitArgs, CallingConvention}; /// Registers for the ARM instruction set. #[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)] @@ -33,19 +33,19 @@ pub enum Regs { R25 = 25, } -static BACKDOOR_ARCH_REGS: OnceLock> = OnceLock::new(); +static EXIT_ARCH_REGS: OnceLock> = OnceLock::new(); -pub fn get_backdoor_arch_regs() -> &'static EnumMap { - BACKDOOR_ARCH_REGS.get_or_init(|| { +pub fn get_exit_arch_regs() -> &'static EnumMap { + EXIT_ARCH_REGS.get_or_init(|| { enum_map! { - BackdoorArgs::Ret => Regs::R0, - BackdoorArgs::Cmd => Regs::R0, - BackdoorArgs::Arg1 => Regs::R1, - BackdoorArgs::Arg2 => Regs::R2, - BackdoorArgs::Arg3 => Regs::R3, - BackdoorArgs::Arg4 => Regs::R4, - BackdoorArgs::Arg5 => Regs::R5, - BackdoorArgs::Arg6 => Regs::R6, + ExitArgs::Ret => Regs::R0, + ExitArgs::Cmd => Regs::R0, + ExitArgs::Arg1 => Regs::R1, + ExitArgs::Arg2 => Regs::R2, + ExitArgs::Arg3 => Regs::R3, + ExitArgs::Arg4 => Regs::R4, + ExitArgs::Arg5 => Regs::R5, + ExitArgs::Arg6 => Regs::R6, } }) } diff --git a/libafl_qemu/src/arch/hexagon.rs b/libafl_qemu/src/arch/hexagon.rs index bd4ff32cd9..e9215f3fd3 100644 --- a/libafl_qemu/src/arch/hexagon.rs +++ b/libafl_qemu/src/arch/hexagon.rs @@ -6,7 +6,7 @@ use num_enum::{IntoPrimitive, TryFromPrimitive}; use pyo3::prelude::*; pub use strum_macros::EnumIter; -use crate::{sync_exit::BackdoorArgs, CallingConvention}; +use crate::{sync_exit::ExitArgs, CallingConvention}; #[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)] #[repr(i32)] @@ -64,19 +64,19 @@ pub enum Regs { Pktcnthi = 51, } -static BACKDOOR_ARCH_REGS: OnceLock> = OnceLock::new(); +static EXIT_ARCH_REGS: OnceLock> = OnceLock::new(); -pub fn get_backdoor_arch_regs() -> &'static EnumMap { - BACKDOOR_ARCH_REGS.get_or_init(|| { +pub fn get_exit_arch_regs() -> &'static EnumMap { + EXIT_ARCH_REGS.get_or_init(|| { enum_map! { - BackdoorArgs::Ret => Regs::R0, - BackdoorArgs::Cmd => Regs::R0, - BackdoorArgs::Arg1 => Regs::R1, - BackdoorArgs::Arg2 => Regs::R2, - BackdoorArgs::Arg3 => Regs::R3, - BackdoorArgs::Arg4 => Regs::R4, - BackdoorArgs::Arg5 => Regs::R5, - BackdoorArgs::Arg6 => Regs::R6, + ExitArgs::Ret => Regs::R0, + ExitArgs::Cmd => Regs::R0, + ExitArgs::Arg1 => Regs::R1, + ExitArgs::Arg2 => Regs::R2, + ExitArgs::Arg3 => Regs::R3, + ExitArgs::Arg4 => Regs::R4, + ExitArgs::Arg5 => Regs::R5, + ExitArgs::Arg6 => Regs::R6, } }) } diff --git a/libafl_qemu/src/arch/i386.rs b/libafl_qemu/src/arch/i386.rs index ca91b8ddf6..2df88e0b75 100644 --- a/libafl_qemu/src/arch/i386.rs +++ b/libafl_qemu/src/arch/i386.rs @@ -8,7 +8,7 @@ use pyo3::prelude::*; pub use strum_macros::EnumIter; pub use syscall_numbers::x86::*; -use crate::{sync_exit::BackdoorArgs, CallingConvention, GuestAddr}; +use crate::{sync_exit::ExitArgs, CallingConvention, GuestAddr}; #[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)] #[repr(i32)] @@ -25,19 +25,19 @@ pub enum Regs { Eflags = 9, } -static BACKDOOR_ARCH_REGS: OnceLock> = OnceLock::new(); +static EXIT_ARCH_REGS: OnceLock> = OnceLock::new(); -pub fn get_backdoor_arch_regs() -> &'static EnumMap { - BACKDOOR_ARCH_REGS.get_or_init(|| { +pub fn get_exit_arch_regs() -> &'static EnumMap { + EXIT_ARCH_REGS.get_or_init(|| { enum_map! { - BackdoorArgs::Ret => Regs::Eax, - BackdoorArgs::Cmd => Regs::Eax, - BackdoorArgs::Arg1 => Regs::Edi, - BackdoorArgs::Arg2 => Regs::Esi, - BackdoorArgs::Arg3 => Regs::Edx, - BackdoorArgs::Arg4 => Regs::Ebx, - BackdoorArgs::Arg5 => Regs::Ecx, - BackdoorArgs::Arg6 => Regs::Ebp, + ExitArgs::Ret => Regs::Eax, + ExitArgs::Cmd => Regs::Eax, + ExitArgs::Arg1 => Regs::Edi, + ExitArgs::Arg2 => Regs::Esi, + ExitArgs::Arg3 => Regs::Edx, + ExitArgs::Arg4 => Regs::Ebx, + ExitArgs::Arg5 => Regs::Ecx, + ExitArgs::Arg6 => Regs::Ebp, } }) } diff --git a/libafl_qemu/src/arch/mips.rs b/libafl_qemu/src/arch/mips.rs index 4810b33508..17930ea213 100644 --- a/libafl_qemu/src/arch/mips.rs +++ b/libafl_qemu/src/arch/mips.rs @@ -7,7 +7,7 @@ use pyo3::prelude::*; pub use strum_macros::EnumIter; pub use syscall_numbers::mips::*; -use crate::{sync_exit::BackdoorArgs, CallingConvention}; +use crate::{sync_exit::ExitArgs, CallingConvention}; /// Registers for the MIPS instruction set. #[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)] @@ -49,19 +49,19 @@ pub enum Regs { Pc = 37, } -static BACKDOOR_ARCH_REGS: OnceLock> = OnceLock::new(); +static EXIT_ARCH_REGS: OnceLock> = OnceLock::new(); -pub fn get_backdoor_arch_regs() -> &'static EnumMap { - BACKDOOR_ARCH_REGS.get_or_init(|| { +pub fn get_exit_arch_regs() -> &'static EnumMap { + EXIT_ARCH_REGS.get_or_init(|| { enum_map! { - BackdoorArgs::Ret => Regs::V0, - BackdoorArgs::Cmd => Regs::V0, - BackdoorArgs::Arg1 => Regs::A0, - BackdoorArgs::Arg2 => Regs::A1, - BackdoorArgs::Arg3 => Regs::A2, - BackdoorArgs::Arg4 => Regs::A3, - BackdoorArgs::Arg5 => Regs::T0, - BackdoorArgs::Arg6 => Regs::T1, + ExitArgs::Ret => Regs::V0, + ExitArgs::Cmd => Regs::V0, + ExitArgs::Arg1 => Regs::A0, + ExitArgs::Arg2 => Regs::A1, + ExitArgs::Arg3 => Regs::A2, + ExitArgs::Arg4 => Regs::A3, + ExitArgs::Arg5 => Regs::T0, + ExitArgs::Arg6 => Regs::T1, } }) } diff --git a/libafl_qemu/src/arch/ppc.rs b/libafl_qemu/src/arch/ppc.rs index 53dd4c1a91..39b50848fb 100644 --- a/libafl_qemu/src/arch/ppc.rs +++ b/libafl_qemu/src/arch/ppc.rs @@ -7,7 +7,7 @@ use pyo3::prelude::*; pub use strum_macros::EnumIter; pub use syscall_numbers::powerpc::*; -use crate::{sync_exit::BackdoorArgs, CallingConvention}; +use crate::{sync_exit::ExitArgs, CallingConvention}; /// Registers for the MIPS instruction set. #[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)] @@ -88,19 +88,19 @@ pub enum Regs { Fpscr = 70, } -static BACKDOOR_ARCH_REGS: OnceLock> = OnceLock::new(); +static EXIT_ARCH_REGS: OnceLock> = OnceLock::new(); -pub fn get_backdoor_arch_regs() -> &'static EnumMap { - BACKDOOR_ARCH_REGS.get_or_init(|| { +pub fn get_exit_arch_regs() -> &'static EnumMap { + EXIT_ARCH_REGS.get_or_init(|| { enum_map! { - BackdoorArgs::Ret => Regs::R3, - BackdoorArgs::Cmd => Regs::R0, - BackdoorArgs::Arg1 => Regs::R3, - BackdoorArgs::Arg2 => Regs::R4, - BackdoorArgs::Arg3 => Regs::R5, - BackdoorArgs::Arg4 => Regs::R6, - BackdoorArgs::Arg5 => Regs::R7, - BackdoorArgs::Arg6 => Regs::R8, + ExitArgs::Ret => Regs::R3, + ExitArgs::Cmd => Regs::R0, + ExitArgs::Arg1 => Regs::R3, + ExitArgs::Arg2 => Regs::R4, + ExitArgs::Arg3 => Regs::R5, + ExitArgs::Arg4 => Regs::R6, + ExitArgs::Arg5 => Regs::R7, + ExitArgs::Arg6 => Regs::R8, } }) } diff --git a/libafl_qemu/src/arch/x86_64.rs b/libafl_qemu/src/arch/x86_64.rs index bdeddc0f75..f32dc200e6 100644 --- a/libafl_qemu/src/arch/x86_64.rs +++ b/libafl_qemu/src/arch/x86_64.rs @@ -8,7 +8,7 @@ use pyo3::prelude::*; pub use strum_macros::EnumIter; pub use syscall_numbers::x86_64::*; -use crate::{sync_exit::BackdoorArgs, CallingConvention}; +use crate::{sync_exit::ExitArgs, CallingConvention}; #[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)] #[repr(i32)] @@ -33,19 +33,19 @@ pub enum Regs { Rflags = 17, } -static BACKDOOR_ARCH_REGS: OnceLock> = OnceLock::new(); +static EXIT_ARCH_REGS: OnceLock> = OnceLock::new(); -pub fn get_backdoor_arch_regs() -> &'static EnumMap { - BACKDOOR_ARCH_REGS.get_or_init(|| { +pub fn get_exit_arch_regs() -> &'static EnumMap { + EXIT_ARCH_REGS.get_or_init(|| { enum_map! { - BackdoorArgs::Ret => Regs::Rax, - BackdoorArgs::Cmd => Regs::Rax, - BackdoorArgs::Arg1 => Regs::Rdi, - BackdoorArgs::Arg2 => Regs::Rsi, - BackdoorArgs::Arg3 => Regs::Rdx, - BackdoorArgs::Arg4 => Regs::R10, - BackdoorArgs::Arg5 => Regs::R8, - BackdoorArgs::Arg6 => Regs::R9, + ExitArgs::Ret => Regs::Rax, + ExitArgs::Cmd => Regs::Rax, + ExitArgs::Arg1 => Regs::Rdi, + ExitArgs::Arg2 => Regs::Rsi, + ExitArgs::Arg3 => Regs::Rdx, + ExitArgs::Arg4 => Regs::R10, + ExitArgs::Arg5 => Regs::R8, + ExitArgs::Arg6 => Regs::R9, } }) } diff --git a/libafl_qemu/src/command.rs b/libafl_qemu/src/command.rs index 3535b76b52..b1ccc5e43f 100644 --- a/libafl_qemu/src/command.rs +++ b/libafl_qemu/src/command.rs @@ -1,24 +1,28 @@ #[cfg(emulation_mode = "systemmode")] use std::collections::HashSet; -use std::fmt::{Debug, Display, Formatter}; +use std::{ + fmt::{Debug, Display, Formatter}, + sync::OnceLock, +}; -use enum_map::Enum; +use enum_map::{enum_map, Enum, EnumMap}; use libafl::{ executors::ExitKind, inputs::HasTargetBytes, state::{HasExecutions, State}, }; use libafl_bolts::AsSlice; -use libafl_qemu_sys::{GuestPhysAddr, GuestVirtAddr}; -use num_enum::TryFromPrimitive; +use libafl_qemu_sys::{GuestAddr, GuestPhysAddr, GuestVirtAddr}; +use num_enum::{TryFromPrimitive, TryFromPrimitiveError}; #[cfg(emulation_mode = "systemmode")] use crate::QemuInstrumentationPagingFilter; use crate::{ - executor::QemuExecutorState, sync_exit::SyncBackdoorError, EmuExitHandler, Emulator, - GuestAddrKind, GuestReg, HandlerError, HasInstrumentationFilter, InnerHandlerResult, - InputLocation, IsFilter, IsSnapshotManager, Qemu, QemuHelperTuple, - QemuInstrumentationAddressRangeFilter, Regs, StdEmuExitHandler, StdInstrumentationFilter, CPU, + executor::QemuExecutorState, get_exit_arch_regs, sync_exit::ExitArgs, Emulator, + EmulatorExitHandler, EmulatorMemoryChunk, ExitHandlerError, ExitHandlerResult, GuestReg, + HasInstrumentationFilter, InputLocation, IsFilter, IsSnapshotManager, Qemu, QemuHelperTuple, + QemuInstrumentationAddressRangeFilter, Regs, StdEmulatorExitHandler, StdInstrumentationFilter, + CPU, }; pub const VERSION: u64 = bindings::LIBAFL_QEMU_HDR_VERSION_NUMBER as u64; @@ -39,7 +43,7 @@ mod bindings { #[derive(Debug, Clone, TryFromPrimitive)] #[repr(u64)] -pub enum NativeBackdoorCommand { +pub enum NativeCommand { StartVirt = bindings::LibaflQemuCommand_LIBAFL_QEMU_COMMAND_START_VIRT.0 as u64, // Shortcut for Save + InputVirt StartPhys = bindings::LibaflQemuCommand_LIBAFL_QEMU_COMMAND_START_PHYS.0 as u64, // Shortcut for Save + InputPhys InputVirt = bindings::LibaflQemuCommand_LIBAFL_QEMU_COMMAND_INPUT_VIRT.0 as u64, // The address is a virtual address using the paging currently running in the VM. @@ -64,7 +68,7 @@ pub trait IsCommand where QT: QemuHelperTuple, S: State + HasExecutions, - E: EmuExitHandler, + E: EmulatorExitHandler, { /// Used to know whether the command can be run during a backdoor, or if it is necessary to go out of /// the QEMU VM to run the command. @@ -81,7 +85,7 @@ where qemu_executor_state: &mut QemuExecutorState, input: &S::Input, ret_reg: Option, - ) -> Result; + ) -> Result, ExitHandlerError>; } #[cfg(emulation_mode = "systemmode")] @@ -102,8 +106,123 @@ pub enum Command { AddressRangeFilterCommand(AddressRangeFilterCommand), } +pub static EMU_EXIT_KIND_MAP: OnceLock>> = OnceLock::new(); + +#[derive(Debug, Clone)] +pub enum CommandError { + UnknownCommand(GuestReg), + RegError(String), + VersionDifference(u64), +} + +impl From> for CommandError { + fn from(error: TryFromPrimitiveError) -> Self { + CommandError::UnknownCommand(error.number.try_into().unwrap()) + } +} + +impl From for CommandError { + fn from(error_string: String) -> Self { + CommandError::RegError(error_string) + } +} + +impl TryFrom for Command { + type Error = CommandError; + + #[allow(clippy::too_many_lines)] + fn try_from(qemu: Qemu) -> Result { + let arch_regs_map: &'static EnumMap = get_exit_arch_regs(); + let cmd_id: GuestReg = qemu.read_reg::(arch_regs_map[ExitArgs::Cmd])?; + + Ok(match u64::from(cmd_id).try_into()? { + NativeCommand::Save => Command::SaveCommand(SaveCommand), + NativeCommand::Load => Command::LoadCommand(LoadCommand), + NativeCommand::InputVirt => { + let virt_addr: GuestVirtAddr = qemu.read_reg(arch_regs_map[ExitArgs::Arg1])?; + let max_input_size: GuestReg = qemu.read_reg(arch_regs_map[ExitArgs::Arg2])?; + + Command::InputCommand(InputCommand::new( + EmulatorMemoryChunk::virt( + virt_addr, + max_input_size, + qemu.current_cpu().unwrap(), + ), + qemu.current_cpu().unwrap(), + )) + } + NativeCommand::InputPhys => { + let phys_addr: GuestPhysAddr = qemu.read_reg(arch_regs_map[ExitArgs::Arg1])?; + let max_input_size: GuestReg = qemu.read_reg(arch_regs_map[ExitArgs::Arg2])?; + + Command::InputCommand(InputCommand::new( + EmulatorMemoryChunk::phys( + phys_addr, + max_input_size, + Some(qemu.current_cpu().unwrap()), + ), + qemu.current_cpu().unwrap(), + )) + } + NativeCommand::End => { + let native_exit_kind: GuestReg = qemu.read_reg(arch_regs_map[ExitArgs::Arg1])?; + let native_exit_kind: Result = + u64::from(native_exit_kind).try_into(); + + let exit_kind = native_exit_kind.ok().and_then(|k| { + EMU_EXIT_KIND_MAP.get_or_init(|| { + enum_map! { + NativeExitKind::Unknown => None, + NativeExitKind::Ok => Some(ExitKind::Ok), + NativeExitKind::Crash => Some(ExitKind::Crash) + } + })[k] + }); + + Command::EndCommand(EndCommand::new(exit_kind)) + } + NativeCommand::StartPhys => { + let input_phys_addr: GuestPhysAddr = + qemu.read_reg(arch_regs_map[ExitArgs::Arg1])?; + let max_input_size: GuestReg = qemu.read_reg(arch_regs_map[ExitArgs::Arg2])?; + + Command::StartCommand(StartCommand::new(EmulatorMemoryChunk::phys( + input_phys_addr, + max_input_size, + Some(qemu.current_cpu().unwrap()), + ))) + } + NativeCommand::StartVirt => { + let input_virt_addr: GuestVirtAddr = + qemu.read_reg(arch_regs_map[ExitArgs::Arg1])?; + let max_input_size: GuestReg = qemu.read_reg(arch_regs_map[ExitArgs::Arg2])?; + + Command::StartCommand(StartCommand::new(EmulatorMemoryChunk::virt( + input_virt_addr, + max_input_size, + qemu.current_cpu().unwrap(), + ))) + } + NativeCommand::Version => { + let client_version = qemu.read_reg(arch_regs_map[ExitArgs::Arg1])?; + + Command::VersionCommand(VersionCommand::new(client_version)) + } + NativeCommand::VaddrFilterAllowRange => { + let vaddr_start: GuestAddr = qemu.read_reg(arch_regs_map[ExitArgs::Arg1])?; + let vaddr_end: GuestAddr = qemu.read_reg(arch_regs_map[ExitArgs::Arg2])?; + + Command::AddressRangeFilterCommand(FilterCommand::new( + #[allow(clippy::single_range_in_vec_init)] + QemuInstrumentationAddressRangeFilter::AllowList(vec![vaddr_start..vaddr_end]), + )) + } + }) + } +} + // TODO: Replace with enum_dispatch implementation -impl IsCommand> for Command +impl IsCommand> for Command where SM: IsSnapshotManager, QT: QemuHelperTuple + StdInstrumentationFilter + Debug, @@ -113,90 +232,94 @@ where fn usable_at_runtime(&self) -> bool { match self { Command::SaveCommand(cmd) => { - >>::usable_at_runtime(cmd) - } - Command::LoadCommand(cmd) => { - >>::usable_at_runtime(cmd) - } - Command::InputCommand(cmd) => { - >>::usable_at_runtime(cmd) - } - Command::StartCommand(cmd) => { - >>::usable_at_runtime(cmd) - } - Command::EndCommand(cmd) => { - >>::usable_at_runtime(cmd) - } - Command::VersionCommand(cmd) => { - >>::usable_at_runtime(cmd) - } - #[cfg(emulation_mode = "systemmode")] - Command::PagingFilterCommand(cmd) => { - >>::usable_at_runtime( + >>::usable_at_runtime( cmd, ) } + Command::LoadCommand(cmd) => { + >>::usable_at_runtime( + cmd, + ) + } + Command::InputCommand(cmd) => { + >>::usable_at_runtime( + cmd, + ) + } + Command::StartCommand(cmd) => { + >>::usable_at_runtime( + cmd, + ) + } + Command::EndCommand(cmd) => { + >>::usable_at_runtime(cmd) + } + Command::VersionCommand(cmd) => { + >>::usable_at_runtime( + cmd, + ) + } + #[cfg(emulation_mode = "systemmode")] + Command::PagingFilterCommand(cmd) => , + >>::usable_at_runtime(cmd), Command::AddressRangeFilterCommand(cmd) => , + StdEmulatorExitHandler, >>::usable_at_runtime(cmd), } } fn run( &self, - emu: &Emulator>, + emu: &Emulator>, qemu_executor_state: &mut QemuExecutorState, input: &S::Input, ret_reg: Option, - ) -> Result { + ) -> Result, ExitHandlerError> { match self { - Command::SaveCommand(cmd) => { - >>::run( - cmd, - emu, - qemu_executor_state, - input, - ret_reg, - ) - } - Command::LoadCommand(cmd) => { - >>::run( - cmd, - emu, - qemu_executor_state, - input, - ret_reg, - ) - } + Command::SaveCommand(cmd) => , + >>::run( + cmd, emu, qemu_executor_state, input, ret_reg + ), + Command::LoadCommand(cmd) => , + >>::run( + cmd, emu, qemu_executor_state, input, ret_reg + ), Command::InputCommand(cmd) => , + StdEmulatorExitHandler, >>::run( cmd, emu, qemu_executor_state, input, ret_reg ), Command::StartCommand(cmd) => , + StdEmulatorExitHandler, + >>::run( + cmd, emu, qemu_executor_state, input, ret_reg + ), + Command::EndCommand(cmd) => , >>::run( cmd, emu, qemu_executor_state, input, ret_reg ), - Command::EndCommand(cmd) => { - >>::run( - cmd, - emu, - qemu_executor_state, - input, - ret_reg, - ) - } Command::VersionCommand(cmd) => , + StdEmulatorExitHandler, >>::run( cmd, emu, qemu_executor_state, input, ret_reg ), @@ -204,12 +327,12 @@ where Command::PagingFilterCommand(cmd) => , + StdEmulatorExitHandler, >>::run( cmd, emu, qemu_executor_state, input, ret_reg ), Command::AddressRangeFilterCommand(cmd) => { - >>::run( + >>::run( cmd, emu, qemu_executor_state, @@ -221,17 +344,10 @@ where } } -#[derive(Debug, Clone)] -pub struct EmulatorMemoryChunk { - addr: GuestAddrKind, - size: GuestReg, - cpu: Option, -} - #[derive(Debug, Clone)] pub struct SaveCommand; -impl IsCommand> for SaveCommand +impl IsCommand> for SaveCommand where SM: IsSnapshotManager, QT: QemuHelperTuple + StdInstrumentationFilter + Debug, @@ -244,7 +360,7 @@ where fn run( &self, - emu: &Emulator>, + emu: &Emulator>, #[cfg(emulation_mode = "systemmode")] qemu_executor_state: &mut QemuExecutorState, #[cfg(not(emulation_mode = "systemmode"))] _qemu_executor_state: &mut QemuExecutorState< QT, @@ -252,14 +368,14 @@ where >, _input: &S::Input, _ret_reg: Option, - ) -> Result { + ) -> Result, ExitHandlerError> { let qemu = emu.qemu(); let emu_exit_handler = emu.exit_handler().borrow_mut(); let snapshot_id = emu_exit_handler.snapshot_manager_borrow_mut().save(qemu); emu_exit_handler .set_snapshot_id(snapshot_id) - .map_err(|_| HandlerError::MultipleSnapshotDefinition)?; + .map_err(|_| ExitHandlerError::MultipleSnapshotDefinition)?; #[cfg(emulation_mode = "systemmode")] { @@ -278,14 +394,14 @@ where *paging_filter = QemuInstrumentationPagingFilter::AllowList(allowed_paging_ids); } - Ok(InnerHandlerResult::Continue) + Ok(None) } } #[derive(Debug, Clone)] pub struct LoadCommand; -impl IsCommand> for LoadCommand +impl IsCommand> for LoadCommand where SM: IsSnapshotManager, QT: QemuHelperTuple + StdInstrumentationFilter + Debug, @@ -298,23 +414,23 @@ where fn run( &self, - emu: &Emulator>, + emu: &Emulator>, _qemu_executor_state: &mut QemuExecutorState, _input: &S::Input, _ret_reg: Option, - ) -> Result { + ) -> Result, ExitHandlerError> { let qemu = emu.qemu(); let emu_exit_handler = emu.exit_handler().borrow_mut(); let snapshot_id = emu_exit_handler .snapshot_id() - .ok_or(HandlerError::SnapshotNotFound)?; + .ok_or(ExitHandlerError::SnapshotNotFound)?; emu_exit_handler .snapshot_manager_borrow_mut() .restore(&snapshot_id, qemu)?; - Ok(InnerHandlerResult::Continue) + Ok(None) } } @@ -324,7 +440,7 @@ pub struct InputCommand { cpu: CPU, } -impl IsCommand> for InputCommand +impl IsCommand> for InputCommand where SM: IsSnapshotManager, QT: QemuHelperTuple + StdInstrumentationFilter + Debug, @@ -337,11 +453,11 @@ where fn run( &self, - emu: &Emulator>, + emu: &Emulator>, _qemu_executor_state: &mut QemuExecutorState, input: &S::Input, ret_reg: Option, - ) -> Result { + ) -> Result, ExitHandlerError> { let qemu = emu.qemu(); let ret_value = self.location.write(qemu, input.target_bytes().as_slice()); @@ -350,7 +466,7 @@ where self.cpu.write_reg(reg, ret_value).unwrap(); } - Ok(InnerHandlerResult::Continue) + Ok(None) } } @@ -359,7 +475,7 @@ pub struct StartCommand { input_location: EmulatorMemoryChunk, } -impl IsCommand> for StartCommand +impl IsCommand> for StartCommand where SM: IsSnapshotManager, QT: QemuHelperTuple + StdInstrumentationFilter + Debug, @@ -372,18 +488,18 @@ where fn run( &self, - emu: &Emulator>, + emu: &Emulator>, _qemu_executor_state: &mut QemuExecutorState, input: &S::Input, ret_reg: Option, - ) -> Result { + ) -> Result, ExitHandlerError> { let emu_exit_handler = emu.exit_handler().borrow_mut(); let qemu = emu.qemu(); let snapshot_id = emu_exit_handler.snapshot_manager_borrow_mut().save(qemu); emu_exit_handler .set_snapshot_id(snapshot_id) - .map_err(|_| HandlerError::MultipleSnapshotDefinition)?; + .map_err(|_| ExitHandlerError::MultipleSnapshotDefinition)?; emu_exit_handler .set_input_location(InputLocation::new( @@ -401,14 +517,14 @@ where qemu.write_reg(reg, ret_value).unwrap(); } - Ok(InnerHandlerResult::Continue) + Ok(None) } } #[derive(Debug, Clone)] pub struct EndCommand(Option); -impl IsCommand> for EndCommand +impl IsCommand> for EndCommand where SM: IsSnapshotManager, QT: QemuHelperTuple + StdInstrumentationFilter + Debug, @@ -421,29 +537,29 @@ where fn run( &self, - emu: &Emulator>, + emu: &Emulator>, _qemu_executor_state: &mut QemuExecutorState, _input: &S::Input, _ret_reg: Option, - ) -> Result { + ) -> Result, ExitHandlerError> { let emu_exit_handler = emu.exit_handler().borrow_mut(); let snapshot_id = emu_exit_handler .snapshot_id() - .ok_or(HandlerError::SnapshotNotFound)?; + .ok_or(ExitHandlerError::SnapshotNotFound)?; emu_exit_handler .snapshot_manager_borrow_mut() .restore(&snapshot_id, emu.qemu())?; - Ok(InnerHandlerResult::EndOfRun(self.0.unwrap())) + Ok(Some(ExitHandlerResult::EndOfRun(self.0.unwrap()))) } } #[derive(Debug, Clone)] pub struct VersionCommand(u64); -impl IsCommand> for VersionCommand +impl IsCommand> for VersionCommand where SM: IsSnapshotManager, QT: QemuHelperTuple + StdInstrumentationFilter + Debug, @@ -456,18 +572,18 @@ where fn run( &self, - _emu: &Emulator>, + _emu: &Emulator>, _qemu_executor_state: &mut QemuExecutorState, _input: &S::Input, _ret_reg: Option, - ) -> Result { + ) -> Result, ExitHandlerError> { let guest_version = self.0; if VERSION == guest_version { - Ok(InnerHandlerResult::Continue) + Ok(None) } else { - Err(HandlerError::SyncBackdoorError( - SyncBackdoorError::VersionDifference(guest_version), + Err(ExitHandlerError::CommandError( + CommandError::VersionDifference(guest_version), )) } } @@ -482,7 +598,7 @@ where } #[cfg(emulation_mode = "systemmode")] -impl IsCommand> for PagingFilterCommand +impl IsCommand> for PagingFilterCommand where SM: IsSnapshotManager, QT: QemuHelperTuple + StdInstrumentationFilter + Debug, @@ -495,11 +611,11 @@ where fn run( &self, - _emu: &Emulator>, + _emu: &Emulator>, qemu_executor_state: &mut QemuExecutorState, _input: &S::Input, _ret_reg: Option, - ) -> Result { + ) -> Result, ExitHandlerError> { let qemu_helpers = qemu_executor_state.hooks_mut().helpers_mut(); let paging_filter = @@ -509,11 +625,11 @@ where *paging_filter = self.filter.clone(); - Ok(InnerHandlerResult::Continue) + Ok(None) } } -impl IsCommand> for AddressRangeFilterCommand +impl IsCommand> for AddressRangeFilterCommand where SM: IsSnapshotManager, QT: QemuHelperTuple + StdInstrumentationFilter + Debug, @@ -527,11 +643,11 @@ where #[allow(clippy::type_complexity)] // TODO: refactor with correct type. fn run( &self, - _emu: &Emulator>, + _emu: &Emulator>, qemu_executor_state: &mut QemuExecutorState, _input: &S::Input, _ret_reg: Option, - ) -> Result { + ) -> Result, ExitHandlerError> { let qemu_helpers = qemu_executor_state.hooks_mut().helpers_mut(); let addr_range_filter = @@ -541,7 +657,7 @@ where *addr_range_filter = self.filter.clone(); - Ok(InnerHandlerResult::Continue) + Ok(None) } } @@ -568,13 +684,13 @@ impl Display for Command { Command::SaveCommand(_) => write!(f, "Save VM"), Command::LoadCommand(_) => write!(f, "Reload VM"), Command::InputCommand(input_command) => { - write!(f, "Set fuzzing input @{}", input_command.location.addr) + write!(f, "Set fuzzing input @{}", input_command.location.addr()) } Command::StartCommand(start_command) => { write!( f, "Start fuzzing with input @{}", - start_command.input_location.addr + start_command.input_location.addr() ) } Command::EndCommand(end_command) => write!(f, "Exit of kind {:?}", end_command.0), @@ -613,66 +729,13 @@ impl InputCommand { } } -impl EmulatorMemoryChunk { - #[must_use] - pub fn phys(addr: GuestPhysAddr, size: GuestReg, cpu: Option) -> Self { - Self { - addr: GuestAddrKind::Physical(addr), - size, - cpu, - } - } - - #[must_use] - pub fn virt(addr: GuestVirtAddr, size: GuestReg, cpu: CPU) -> Self { - Self { - addr: GuestAddrKind::Virtual(addr), - size, - cpu: Some(cpu), - } - } - - /// Returns the number of bytes effectively written. - #[must_use] - pub fn write(&self, qemu: &Qemu, input: &[u8]) -> GuestReg { - let max_len: usize = self.size.try_into().unwrap(); - - let input_sliced = if input.len() > max_len { - &input[0..max_len] - } else { - input - }; - - match self.addr { - GuestAddrKind::Physical(hwaddr) => unsafe { - #[cfg(emulation_mode = "usermode")] - { - // For now the default behaviour is to fall back to virtual addresses - qemu.write_mem(hwaddr.try_into().unwrap(), input_sliced); - } - #[cfg(emulation_mode = "systemmode")] - { - qemu.write_phys_mem(hwaddr, input_sliced); - } - }, - GuestAddrKind::Virtual(vaddr) => unsafe { - self.cpu - .as_ref() - .unwrap() - .write_mem(vaddr.try_into().unwrap(), input_sliced); - }, - }; - - input_sliced.len().try_into().unwrap() - } -} - impl Display for InputCommand { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!( f, "{} (0x{:x} max nb bytes)", - self.location.addr, self.location.size + self.location.addr(), + self.location.size() ) } } diff --git a/libafl_qemu/src/emu/mod.rs b/libafl_qemu/src/emu/mod.rs index 91d9a71cb4..0006d8c08e 100644 --- a/libafl_qemu/src/emu/mod.rs +++ b/libafl_qemu/src/emu/mod.rs @@ -1,56 +1,107 @@ -//! Expose QEMU user `LibAFL` C api to Rust +//! Higher-level abstraction of [`Qemu`] +//! +//! [`Emulator`] is built above [`Qemu`] and provides convenient abstractions. use core::{ fmt::{self, Debug, Display, Formatter}, marker::PhantomData, - mem::{transmute, MaybeUninit}, - ptr::{addr_of, copy_nonoverlapping, null}, }; use std::{ cell::{OnceCell, Ref, RefCell, RefMut}, collections::HashSet, - ffi::CString, - ptr, + ops::Add, }; -use libafl::{events::CTRL_C_EXIT, executors::ExitKind}; -#[cfg(emulation_mode = "systemmode")] -use libafl_qemu_sys::qemu_init; -#[cfg(emulation_mode = "usermode")] -use libafl_qemu_sys::{guest_base, qemu_user_init, VerifyAccess}; -use libafl_qemu_sys::{ - libafl_flush_jit, libafl_get_exit_reason, libafl_page_from_addr, libafl_qemu_add_gdb_cmd, - libafl_qemu_cpu_index, libafl_qemu_current_cpu, libafl_qemu_gdb_reply, libafl_qemu_get_cpu, - libafl_qemu_num_cpus, libafl_qemu_num_regs, libafl_qemu_read_reg, - libafl_qemu_remove_breakpoint, libafl_qemu_set_breakpoint, libafl_qemu_trigger_breakpoint, - libafl_qemu_write_reg, CPUArchStatePtr, CPUStatePtr, FatPtr, GuestUsize, +use libafl::{ + executors::ExitKind, + inputs::HasTargetBytes, + state::{HasExecutions, State}, }; +use libafl_bolts::os::unix_signals::Signal; +use libafl_qemu_sys::{CPUArchStatePtr, GuestUsize}; pub use libafl_qemu_sys::{GuestAddr, GuestPhysAddr, GuestVirtAddr}; #[cfg(emulation_mode = "usermode")] pub use libafl_qemu_sys::{MapInfo, MmapPerms, MmapPermsIter}; use num_traits::Num; -use strum::IntoEnumIterator; use crate::{ - command::IsCommand, sys::TCGTemp, GuestReg, QemuHelperTuple, Regs, StdInstrumentationFilter, + breakpoint::Breakpoint, + command::{Command, CommandError, InputCommand, IsCommand}, + executor::QemuExecutorState, + sync_exit::SyncExit, + sys::TCGTemp, + BackdoorHookId, BlockHookId, CmpHookId, EdgeHookId, EmulatorMemoryChunk, GuestReg, HookData, + HookId, InstructionHookId, MemAccessInfo, Qemu, QemuExitError, QemuExitReason, QemuHelperTuple, + QemuInitError, QemuShutdownCause, ReadHookId, Regs, StdInstrumentationFilter, WriteHookId, CPU, }; -#[cfg(emulation_mode = "systemmode")] -pub mod systemmode; -#[cfg(emulation_mode = "systemmode")] -pub use systemmode::*; - #[cfg(emulation_mode = "usermode")] -pub mod usermode; +mod usermode; #[cfg(emulation_mode = "usermode")] pub use usermode::*; -#[derive(Clone)] +#[cfg(emulation_mode = "systemmode")] +mod systemmode; +#[cfg(emulation_mode = "systemmode")] +pub use systemmode::*; + +#[derive(Clone, Copy)] pub enum GuestAddrKind { Physical(GuestPhysAddr), Virtual(GuestVirtAddr), } +#[derive(Debug, Clone)] +pub enum EmulatorExitResult { + QemuExit(QemuShutdownCause), // QEMU ended for some reason. + Breakpoint(Breakpoint), // Breakpoint triggered. Contains the address of the trigger. + SyncExit(SyncExit), // Synchronous backdoor: The guest triggered a backdoor and should return to LibAFL. +} + +#[derive(Debug, Clone)] +pub enum EmulatorExitError { + UnknownKind, + UnexpectedExit, + CommandError(CommandError), + BreakpointNotFound(GuestAddr), +} + +#[derive(Debug, Clone)] +pub enum ExitHandlerResult { + ReturnToHarness(EmulatorExitResult), // Return to the harness immediately. Can happen at any point of the run when the handler is not supposed to handle a request. + EndOfRun(ExitKind), // The run is over and the emulator is ready for the next iteration. +} + +#[derive(Debug, Clone)] +pub enum ExitHandlerError { + QemuExitReasonError(EmulatorExitError), + SMError(SnapshotManagerError), + CommandError(CommandError), + UnhandledSignal(Signal), + MultipleSnapshotDefinition, + MultipleInputDefinition, + SnapshotNotFound, +} + +#[derive(Debug, Clone)] +pub enum SnapshotManagerError { + SnapshotIdNotFound(SnapshotId), + MemoryInconsistencies(u64), +} + +impl TryInto for ExitHandlerResult { + type Error = String; + + fn try_into(self) -> Result { + match self { + ExitHandlerResult::ReturnToHarness(unhandled_qemu_exit) => { + Err(format!("Unhandled QEMU exit: {:?}", &unhandled_qemu_exit)) + } + ExitHandlerResult::EndOfRun(exit_kind) => Ok(exit_kind), + } + } +} + impl Debug for GuestAddrKind { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { @@ -60,19 +111,15 @@ impl Debug for GuestAddrKind { } } -#[derive(Debug, Clone)] -pub enum QemuShutdownCause { - None, - HostError, - HostQmpQuit, - HostQmpSystemReset, - HostSignal(Signal), - HostUi, - GuestShutdown, - GuestReset, - GuestPanic, - SubsystemReset, - SnapshotLoad, +impl Add for GuestAddrKind { + type Output = Self; + + fn add(self, rhs: GuestUsize) -> Self::Output { + match self { + GuestAddrKind::Physical(paddr) => GuestAddrKind::Physical(paddr + rhs as GuestPhysAddr), + GuestAddrKind::Virtual(vaddr) => GuestAddrKind::Virtual(vaddr + rhs as GuestVirtAddr), + } + } } impl Display for GuestAddrKind { @@ -84,28 +131,12 @@ impl Display for GuestAddrKind { } } -#[derive(Debug, Clone)] -pub enum HandlerError { - QemuExitReasonError(EmuExitReasonError), - SMError(SnapshotManagerError), - SyncBackdoorError(SyncBackdoorError), - MultipleSnapshotDefinition, - MultipleInputDefinition, - SnapshotNotFound, -} - -impl From for HandlerError { +impl From for ExitHandlerError { fn from(sm_error: SnapshotManagerError) -> Self { - HandlerError::SMError(sm_error) + ExitHandlerError::SMError(sm_error) } } -#[derive(Debug, Clone)] -pub enum SnapshotManagerError { - SnapshotIdNotFound(SnapshotId), - MemoryInconsistencies(u64), -} - #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] pub struct SnapshotId { id: u64, @@ -121,53 +152,46 @@ pub trait IsSnapshotManager: Debug + Clone { } // TODO: Rework with generics for command handlers? -pub trait EmuExitHandler: Sized + Debug + Clone +pub trait EmulatorExitHandler: Sized + Debug + Clone where QT: QemuHelperTuple, S: State + HasExecutions, { - fn try_put_input( + fn qemu_pre_run( emu: &Emulator, qemu_executor_state: &mut QemuExecutorState, input: &S::Input, ); - fn handle( + fn qemu_post_run( emu: &Emulator, - exit_reason: Result, + exit_reason: Result, qemu_executor_state: &mut QemuExecutorState, input: &S::Input, - ) -> Result; -} - -pub enum InnerHandlerResult { - EndOfRun(ExitKind), // The run is over and the emulator is ready for the next iteration. - ReturnToHarness(EmuExitReason), // Return to the harness immediately. Can happen at any point of the run when the handler is not supposed to handle a request. - Continue, // Resume QEMU and continue to run the handler. - Interrupt, // QEMU has been interrupted by user. + ) -> Result, ExitHandlerError>; } /// Special kind of Exit handler with no data embedded. /// As a result, it is safe to transmute from any `Emulator` implementing `EmuExitHandler` to this one, /// since it won't use any data which could cause type confusion. #[derive(Clone, Debug)] -pub struct NopEmuExitHandler; +pub struct NopEmulatorExitHandler; -impl EmuExitHandler for NopEmuExitHandler +impl EmulatorExitHandler for NopEmulatorExitHandler where QT: QemuHelperTuple, S: State + HasExecutions, { - fn try_put_input(_: &Emulator, _: &mut QemuExecutorState, _: &S::Input) {} + fn qemu_pre_run(_: &Emulator, _: &mut QemuExecutorState, _: &S::Input) {} - fn handle( + fn qemu_post_run( _: &Emulator, - exit_reason: Result, + exit_reason: Result, _: &mut QemuExecutorState, _: &S::Input, - ) -> Result { + ) -> Result, ExitHandlerError> { match exit_reason { - Ok(reason) => Ok(InnerHandlerResult::ReturnToHarness(reason)), + Ok(reason) => Ok(Some(ExitHandlerResult::ReturnToHarness(reason))), Err(error) => Err(error)?, } } @@ -193,7 +217,7 @@ impl InputLocation { /// Synchronous Exit handler maintaining only one snapshot. #[derive(Debug, Clone)] -pub struct StdEmuExitHandler +pub struct StdEmulatorExitHandler where SM: IsSnapshotManager + Clone, { @@ -202,7 +226,7 @@ where input_location: OnceCell, } -impl StdEmuExitHandler +impl StdEmulatorExitHandler where SM: IsSnapshotManager, { @@ -236,14 +260,14 @@ where } // TODO: replace handlers with generics to permit compile-time customization of handlers -impl EmuExitHandler for StdEmuExitHandler +impl EmulatorExitHandler for StdEmulatorExitHandler where SM: IsSnapshotManager, QT: QemuHelperTuple + StdInstrumentationFilter + Debug, S: State + HasExecutions, S::Input: HasTargetBytes, { - fn try_put_input( + fn qemu_pre_run( emu: &Emulator, qemu_executor_state: &mut QemuExecutorState, input: &S::Input, @@ -252,50 +276,51 @@ where if let Some(input_location) = exit_handler.input_location.get() { let input_command = - InputCommand::new(input_location.mem_chunk.clone(), input_location.cpu.clone()); + InputCommand::new(input_location.mem_chunk.clone(), input_location.cpu); input_command .run(emu, qemu_executor_state, input, input_location.ret_register) .unwrap(); } } - fn handle( + fn qemu_post_run( emu: &Emulator, - exit_reason: Result, + exit_reason: Result, qemu_executor_state: &mut QemuExecutorState, input: &S::Input, - ) -> Result { + ) -> Result, ExitHandlerError> { let exit_handler = emu.exit_handler().borrow_mut(); let qemu = emu.qemu(); let mut exit_reason = match exit_reason { Ok(exit_reason) => exit_reason, Err(exit_error) => match exit_error { - EmuExitReasonError::UnexpectedExit => { + EmulatorExitError::UnexpectedExit => { if let Some(snapshot_id) = exit_handler.snapshot_id.get() { exit_handler .snapshot_manager .borrow_mut() .restore(snapshot_id, qemu)?; } - return Ok(InnerHandlerResult::EndOfRun(ExitKind::Crash)); + return Ok(Some(ExitHandlerResult::EndOfRun(ExitKind::Crash))); } _ => Err(exit_error)?, }, }; let (command, ret_reg): (Option, Option) = match &mut exit_reason { - EmuExitReason::End(shutdown_cause) => match shutdown_cause { - QemuShutdownCause::HostSignal(Signal::SigInterrupt) => { - std::process::exit(CTRL_C_EXIT); + EmulatorExitResult::QemuExit(shutdown_cause) => match shutdown_cause { + QemuShutdownCause::HostSignal(signal) => { + signal.handle(); + return Err(ExitHandlerError::UnhandledSignal(*signal)); } QemuShutdownCause::GuestPanic => { - return Ok(InnerHandlerResult::EndOfRun(ExitKind::Crash)) + return Ok(Some(ExitHandlerResult::EndOfRun(ExitKind::Crash))) } _ => panic!("Unhandled QEMU shutdown cause: {shutdown_cause:?}."), }, - EmuExitReason::Breakpoint(bp) => (bp.trigger(qemu).cloned(), None), - EmuExitReason::SyncBackdoor(sync_backdoor) => { + EmulatorExitResult::Breakpoint(bp) => (bp.trigger(qemu).cloned(), None), + EmulatorExitResult::SyncExit(sync_backdoor) => { let command = sync_backdoor.command().clone(); (Some(command), Some(sync_backdoor.ret_reg())) } @@ -307,551 +332,47 @@ where if let Some(cmd) = command { cmd.run(emu, qemu_executor_state, input, ret_reg) } else { - Ok(InnerHandlerResult::ReturnToHarness(exit_reason)) + Ok(Some(ExitHandlerResult::ReturnToHarness(exit_reason))) } } } -#[repr(transparent)] -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub struct MemAccessInfo { - oi: libafl_qemu_sys::MemOpIdx, -} - -impl MemAccessInfo { - #[must_use] - pub fn memop(&self) -> libafl_qemu_sys::MemOp { - libafl_qemu_sys::MemOp(self.oi >> 4) - } - - #[must_use] - pub fn memopidx(&self) -> libafl_qemu_sys::MemOpIdx { - self.oi - } - - #[must_use] - pub fn mmu_index(&self) -> u32 { - self.oi & 15 - } - - #[must_use] - pub fn size(&self) -> usize { - libafl_qemu_sys::memop_size(self.memop()) as usize - } - - #[must_use] - pub fn is_big_endian(&self) -> bool { - libafl_qemu_sys::memop_big_endian(self.memop()) - } - - #[must_use] - pub fn encode_with(&self, other: u32) -> u64 { - (u64::from(self.oi) << 32) | u64::from(other) - } - - #[must_use] - pub fn decode_from(encoded: u64) -> (Self, u32) { - let low = (encoded & 0xFFFFFFFF) as u32; - let high = (encoded >> 32) as u32; - (Self { oi: high }, low) - } - - #[must_use] - pub fn new(oi: libafl_qemu_sys::MemOpIdx) -> Self { - Self { oi } +impl From for ExitHandlerError { + fn from(error: EmulatorExitError) -> Self { + ExitHandlerError::QemuExitReasonError(error) } } -impl From for MemAccessInfo { - fn from(oi: libafl_qemu_sys::MemOpIdx) -> Self { - Self { oi } +impl From for ExitHandlerError { + fn from(error: CommandError) -> Self { + ExitHandlerError::CommandError(error) } } -#[cfg(feature = "python")] -use pyo3::prelude::*; - -pub const SKIP_EXEC_HOOK: u64 = u64::MAX; - -pub use libafl_qemu_sys::{CPUArchState, CPUState}; - -use crate::sync_exit::{SyncBackdoor, SyncBackdoorError}; - -// syshook_ret -#[repr(C)] -#[cfg_attr(feature = "python", pyclass)] -#[cfg_attr(feature = "python", derive(FromPyObject))] -pub struct SyscallHookResult { - pub retval: GuestAddr, - pub skip_syscall: bool, -} - -#[cfg(feature = "python")] -#[pymethods] -impl SyscallHookResult { - #[new] - #[must_use] - pub fn new(value: Option) -> Self { - value.map_or( - Self { - retval: 0, - skip_syscall: false, - }, - |v| Self { - retval: v, - skip_syscall: true, - }, - ) - } -} - -#[cfg(not(feature = "python"))] -impl SyscallHookResult { - #[must_use] - pub fn new(value: Option) -> Self { - value.map_or( - Self { - retval: 0, - skip_syscall: false, - }, - |v| Self { - retval: v, - skip_syscall: true, - }, - ) - } -} - -#[allow(clippy::vec_box)] -static mut GDB_COMMANDS: Vec> = vec![]; - -extern "C" fn gdb_cmd(data: *const (), buf: *const u8, len: usize) -> i32 { - unsafe { - let closure = &mut *(data as *mut Box FnMut(&Qemu, &'r str) -> bool>); - let cmd = std::str::from_utf8_unchecked(std::slice::from_raw_parts(buf, len)); - let qemu = Qemu::get_unchecked(); - i32::from(closure(&qemu, cmd)) - } -} - -#[derive(Debug, Clone)] -#[repr(transparent)] -pub struct CPU { - ptr: CPUStatePtr, -} - -#[derive(Debug, PartialEq)] -pub enum CallingConvention { - Cdecl, -} - -pub trait ArchExtras { - fn read_return_address(&self) -> Result - where - T: From; - fn write_return_address(&self, val: T) -> Result<(), String> - where - T: Into; - fn read_function_argument(&self, conv: CallingConvention, idx: u8) -> Result - where - T: From; - fn write_function_argument( - &self, - conv: CallingConvention, - idx: i32, - val: T, - ) -> Result<(), String> - where - T: Into; -} - -#[allow(clippy::unused_self)] -impl CPU { - #[must_use] - pub fn qemu(&self) -> Qemu { - unsafe { Qemu::get_unchecked() } - } - - #[must_use] - #[allow(clippy::cast_sign_loss)] - pub fn index(&self) -> usize { - unsafe { libafl_qemu_cpu_index(self.ptr) as usize } - } - - pub fn trigger_breakpoint(&self) { - unsafe { - libafl_qemu_trigger_breakpoint(self.ptr); - } - } - - #[cfg(emulation_mode = "usermode")] - #[must_use] - pub fn g2h(&self, addr: GuestAddr) -> *mut T { - unsafe { (addr as usize + guest_base) as *mut T } - } - - #[cfg(emulation_mode = "usermode")] - #[must_use] - pub fn h2g(&self, addr: *const T) -> GuestAddr { - unsafe { (addr as usize - guest_base) as GuestAddr } - } - - #[cfg(emulation_mode = "usermode")] - #[must_use] - pub fn access_ok(&self, kind: VerifyAccess, addr: GuestAddr, size: usize) -> bool { - unsafe { - // TODO add support for tagged GuestAddr - libafl_qemu_sys::page_check_range(addr, size as GuestAddr, kind.into()) - } - } - - // TODO expose tlb_set_dirty and tlb_reset_dirty - - #[must_use] - pub fn num_regs(&self) -> i32 { - unsafe { libafl_qemu_num_regs(self.ptr) } - } - - pub fn write_reg(&self, reg: R, val: T) -> Result<(), String> - where - R: Into, - T: Into, - { - let reg = reg.into(); - #[cfg(feature = "be")] - let val = GuestReg::to_be(val.into()); - - #[cfg(not(feature = "be"))] - let val = GuestReg::to_le(val.into()); - - let success = unsafe { libafl_qemu_write_reg(self.ptr, reg, addr_of!(val) as *const u8) }; - if success == 0 { - Err(format!("Failed to write to register {reg}")) - } else { - Ok(()) - } - } - - pub fn read_reg(&self, reg: R) -> Result - where - R: Into, - T: From, - { - unsafe { - let reg = reg.into(); - let mut val = MaybeUninit::uninit(); - let success = libafl_qemu_read_reg(self.ptr, reg, val.as_mut_ptr() as *mut u8); - if success == 0 { - Err(format!("Failed to read register {reg}")) - } else { - #[cfg(feature = "be")] - return Ok(GuestReg::from_be(val.assume_init()).into()); - - #[cfg(not(feature = "be"))] - return Ok(GuestReg::from_le(val.assume_init()).into()); - } - } - } - - pub fn reset(&self) { - unsafe { libafl_qemu_sys::cpu_reset(self.ptr) }; - } - - #[must_use] - pub fn save_state(&self) -> CPUArchState { - unsafe { - let mut saved = MaybeUninit::::uninit(); - copy_nonoverlapping( - libafl_qemu_sys::cpu_env(self.ptr.as_mut().unwrap()), - saved.as_mut_ptr(), - 1, - ); - saved.assume_init() - } - } - - pub fn restore_state(&self, saved: &CPUArchState) { - unsafe { - copy_nonoverlapping( - saved, - libafl_qemu_sys::cpu_env(self.ptr.as_mut().unwrap()), - 1, - ); - } - } - - #[must_use] - pub fn raw_ptr(&self) -> CPUStatePtr { - self.ptr - } - - #[must_use] - pub fn display_context(&self) -> String { - let mut display = String::new(); - let mut maxl = 0; - for r in Regs::iter() { - maxl = std::cmp::max(format!("{r:#?}").len(), maxl); - } - for (i, r) in Regs::iter().enumerate() { - let v: GuestAddr = self.read_reg(r).unwrap(); - let sr = format!("{r:#?}"); - display += &format!("{sr:>maxl$}: {v:#016x} "); - if (i + 1) % 4 == 0 { - display += "\n"; - } - } - if !display.ends_with('\n') { - display += "\n"; - } - display - } -} - -pub trait HookId { - fn remove(&self, invalidate_block: bool) -> bool; -} - -macro_rules! create_hook_id { - ($name:ident, $sys:ident, true) => { - paste::paste! { - #[derive(Clone, Copy, PartialEq, Debug)] - pub struct [<$name HookId>](pub(crate) usize); - impl HookId for [<$name HookId>] { - fn remove(&self, invalidate_block: bool) -> bool { - unsafe { libafl_qemu_sys::$sys(self.0, invalidate_block.into()) != 0 } - } - } - } - }; - ($name:ident, $sys:ident, false) => { - paste::paste! { - #[derive(Clone, Copy, PartialEq, Debug)] - pub struct [<$name HookId>](pub(crate) usize); - impl HookId for [<$name HookId>] { - fn remove(&self, _invalidate_block: bool) -> bool { - unsafe { libafl_qemu_sys::$sys(self.0) != 0 } - } - } - } - }; -} - -create_hook_id!(Instruction, libafl_qemu_remove_hook, true); -create_hook_id!(Backdoor, libafl_qemu_remove_backdoor_hook, true); -create_hook_id!(Edge, libafl_qemu_remove_edge_hook, true); -create_hook_id!(Block, libafl_qemu_remove_block_hook, true); -create_hook_id!(Read, libafl_qemu_remove_read_hook, true); -create_hook_id!(Write, libafl_qemu_remove_write_hook, true); -create_hook_id!(Cmp, libafl_qemu_remove_cmp_hook, true); -create_hook_id!(PreSyscall, libafl_qemu_remove_pre_syscall_hook, false); -create_hook_id!(PostSyscall, libafl_qemu_remove_post_syscall_hook, false); -create_hook_id!(NewThread, libafl_qemu_remove_new_thread_hook, false); - -use std::{pin::Pin, ptr::NonNull}; - -use libafl::{ - inputs::HasTargetBytes, - state::{HasExecutions, State}, -}; -use libafl_bolts::os::unix_signals::Signal; - -use crate::{ - breakpoint::Breakpoint, - command::{Command, EmulatorMemoryChunk, InputCommand}, - executor::QemuExecutorState, -}; - -#[derive(Debug)] -pub struct HookData(u64); - -impl From> for HookData { - fn from(value: Pin<&mut T>) -> Self { - unsafe { HookData(transmute::, u64>(value)) } - } -} - -impl From> for HookData { - fn from(value: Pin<&T>) -> Self { - unsafe { HookData(transmute::, u64>(value)) } - } -} - -impl From<&'static mut T> for HookData { - fn from(value: &'static mut T) -> Self { - unsafe { HookData(transmute::<&mut T, u64>(value)) } - } -} - -impl From<&'static T> for HookData { - fn from(value: &'static T) -> Self { - unsafe { HookData(transmute::<&T, u64>(value)) } - } -} - -impl From<*mut T> for HookData { - fn from(value: *mut T) -> Self { - HookData(value as u64) - } -} - -impl From<*const T> for HookData { - fn from(value: *const T) -> Self { - HookData(value as u64) - } -} - -impl From for HookData { - fn from(value: u64) -> Self { - HookData(value) - } -} - -impl From for HookData { - fn from(value: u32) -> Self { - HookData(u64::from(value)) - } -} - -impl From for HookData { - fn from(value: u16) -> Self { - HookData(u64::from(value)) - } -} - -impl From for HookData { - fn from(value: u8) -> Self { - HookData(u64::from(value)) - } -} - -#[derive(Debug)] -pub enum EmuError { - MultipleInstances, - EmptyArgs, - TooManyArgs(usize), -} - -#[derive(Debug, Clone)] -pub enum EmuExitReason { - End(QemuShutdownCause), // QEMU ended for some reason. - Breakpoint(Breakpoint), // Breakpoint triggered. Contains the address of the trigger. - SyncBackdoor(SyncBackdoor), // Synchronous backdoor: The guest triggered a backdoor and should return to LibAFL. -} - -#[derive(Debug, Clone)] -pub enum QemuExitReason { - End(QemuShutdownCause), // QEMU ended for some reason. - Breakpoint(GuestAddr), // Breakpoint triggered. Contains the address of the trigger. - SyncBackdoor, // Synchronous backdoor: The guest triggered a backdoor and should return to LibAFL. -} - -/// High level result when finishing to handle requests -#[derive(Debug, Clone)] -pub enum HandlerResult { - UnhandledExit(EmuExitReason), // QEMU exit not handled by the current exit handler. - EndOfRun(ExitKind), // QEMU ended the current run and should pass some exit kind. - Interrupted, // User sent an interrupt signal -} - -impl From for HandlerError { - fn from(error: EmuExitReasonError) -> Self { - HandlerError::QemuExitReasonError(error) - } -} - -impl From for HandlerError { - fn from(error: SyncBackdoorError) -> Self { - HandlerError::SyncBackdoorError(error) - } -} - -impl Display for QemuExitReason { +impl Display for EmulatorExitResult { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { - QemuExitReason::End(shutdown_cause) => write!(f, "End: {shutdown_cause:?}"), - QemuExitReason::Breakpoint(bp) => write!(f, "Breakpoint: {bp}"), - QemuExitReason::SyncBackdoor => write!(f, "Sync Backdoor"), // QemuExitReason::SyncBackdoor(sync_backdoor) => { - // write!(f, "Sync backdoor exit: {sync_backdoor}") - // } + EmulatorExitResult::QemuExit(shutdown_cause) => write!(f, "End: {shutdown_cause:?}"), + EmulatorExitResult::Breakpoint(bp) => write!(f, "{bp}"), + EmulatorExitResult::SyncExit(sync_exit) => { + write!(f, "Sync exit: {sync_exit}") + } } } } -impl Display for EmuExitReason { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - EmuExitReason::End(shutdown_cause) => write!(f, "End: {shutdown_cause:?}"), - EmuExitReason::Breakpoint(bp) => write!(f, "{bp}"), - EmuExitReason::SyncBackdoor(sync_backdoor) => { - write!(f, "Sync backdoor exit: {sync_backdoor}") - } - } +impl From for EmulatorExitError { + fn from(error: CommandError) -> Self { + EmulatorExitError::CommandError(error) } } #[derive(Debug, Clone)] -pub enum QemuExitReasonError { - UnknownKind, // Exit reason was not NULL, but exit kind is unknown. Should never happen. - UnexpectedExit, // Qemu exited without going through an expected exit point. Can be caused by a crash for example. -} - -#[derive(Debug, Clone)] -pub enum EmuExitReasonError { - UnknownKind, - UnexpectedExit, - SyncBackdoorError(SyncBackdoorError), - BreakpointNotFound(GuestAddr), -} - -impl From for EmuExitReasonError { - fn from(sync_backdoor_error: SyncBackdoorError) -> Self { - EmuExitReasonError::SyncBackdoorError(sync_backdoor_error) - } -} - -impl std::error::Error for EmuError {} - -impl Display for EmuError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - EmuError::MultipleInstances => { - write!(f, "Only one instance of the QEMU Emulator is permitted") - } - EmuError::EmptyArgs => { - write!(f, "QEMU emulator args cannot be empty") - } - EmuError::TooManyArgs(n) => { - write!( - f, - "Too many arguments passed to QEMU emulator ({n} > i32::MAX)" - ) - } - } - } -} - -impl From for libafl::Error { - fn from(err: EmuError) -> Self { - libafl::Error::unknown(format!("{err}")) - } -} - -static mut EMULATOR_STATE: *mut () = ptr::null_mut(); -static mut QEMU_IS_INITIALIZED: bool = false; - -/// The thin wrapper around QEMU. -/// It is considered unsafe to use it directly. -/// Prefer using `Emulator` instead in case of doubt. -#[derive(Clone, Copy, Debug)] -pub struct Qemu { - _private: (), -} - pub struct EmulatorState where QT: QemuHelperTuple, S: State + HasExecutions, - E: EmuExitHandler, + E: EmulatorExitHandler, { exit_handler: RefCell, breakpoints: RefCell>, @@ -863,527 +384,39 @@ pub struct Emulator where QT: QemuHelperTuple, S: State + HasExecutions, - E: EmuExitHandler, + E: EmulatorExitHandler, { - state: ptr::NonNull>, + state: EmulatorState, qemu: Qemu, } -#[allow(clippy::unused_self)] -impl Qemu { - #[allow(clippy::must_use_candidate, clippy::similar_names)] - pub fn init(args: &[String], env: &[(String, String)]) -> Result { - if args.is_empty() { - return Err(EmuError::EmptyArgs); - } - - let argc = args.len(); - if i32::try_from(argc).is_err() { - return Err(EmuError::TooManyArgs(argc)); - } - - unsafe { - if QEMU_IS_INITIALIZED { - return Err(EmuError::MultipleInstances); - } - QEMU_IS_INITIALIZED = true; - } - - #[allow(clippy::cast_possible_wrap)] - let argc = argc as i32; - - let args: Vec = args - .iter() - .map(|x| CString::new(x.clone()).unwrap()) - .collect(); - let mut argv: Vec<*const u8> = args.iter().map(|x| x.as_ptr() as *const u8).collect(); - argv.push(ptr::null()); // argv is always null terminated. - let env_strs: Vec = env - .iter() - .map(|(k, v)| format!("{}={}\0", &k, &v)) - .collect(); - let mut envp: Vec<*const u8> = env_strs.iter().map(|x| x.as_bytes().as_ptr()).collect(); - envp.push(null()); - unsafe { - #[cfg(emulation_mode = "usermode")] - qemu_user_init(argc, argv.as_ptr(), envp.as_ptr()); - #[cfg(emulation_mode = "systemmode")] - { - qemu_init( - argc, - argv.as_ptr() as *const *const u8, - envp.as_ptr() as *const *const u8, - ); - libc::atexit(qemu_cleanup_atexit); - libafl_qemu_sys::syx_snapshot_init(true); - } - } - - Ok(Qemu { _private: () }) - } - - /// Get a QEMU object. - /// Same as `Qemu::get`, but without checking whether QEMU has been correctly initialized. - /// - /// # Safety - /// - /// Should not be used if `Qemu::init` has never been used before (otherwise QEMU will not be initialized, and a crash will occur). - /// Prefer `Qemu::get` for a safe version of this method. - #[must_use] - pub unsafe fn get_unchecked() -> Self { - Qemu { _private: () } - } - - #[must_use] - pub fn get() -> Option { - unsafe { - if QEMU_IS_INITIALIZED { - Some(Qemu { _private: () }) - } else { - None - } - } - } - - fn post_run(&self) -> Result { - let exit_reason = unsafe { libafl_get_exit_reason() }; - if exit_reason.is_null() { - Err(QemuExitReasonError::UnexpectedExit) - } else { - let exit_reason: &mut libafl_qemu_sys::libafl_exit_reason = - unsafe { transmute(&mut *exit_reason) }; - Ok(match exit_reason.kind { - libafl_qemu_sys::libafl_exit_reason_kind_INTERNAL => unsafe { - let qemu_shutdown_cause: QemuShutdownCause = - match exit_reason.data.internal.cause { - libafl_qemu_sys::ShutdownCause_SHUTDOWN_CAUSE_NONE => { - QemuShutdownCause::None - } - libafl_qemu_sys::ShutdownCause_SHUTDOWN_CAUSE_HOST_ERROR => { - QemuShutdownCause::HostError - } - libafl_qemu_sys::ShutdownCause_SHUTDOWN_CAUSE_HOST_QMP_QUIT => { - QemuShutdownCause::HostQmpQuit - } - libafl_qemu_sys::ShutdownCause_SHUTDOWN_CAUSE_HOST_QMP_SYSTEM_RESET => { - QemuShutdownCause::HostQmpSystemReset - } - libafl_qemu_sys::ShutdownCause_SHUTDOWN_CAUSE_HOST_SIGNAL => { - QemuShutdownCause::HostSignal( - Signal::try_from(exit_reason.data.internal.signal).unwrap(), - ) - } - libafl_qemu_sys::ShutdownCause_SHUTDOWN_CAUSE_HOST_UI => { - QemuShutdownCause::HostUi - } - libafl_qemu_sys::ShutdownCause_SHUTDOWN_CAUSE_GUEST_SHUTDOWN => { - QemuShutdownCause::GuestShutdown - } - libafl_qemu_sys::ShutdownCause_SHUTDOWN_CAUSE_GUEST_RESET => { - QemuShutdownCause::GuestReset - } - libafl_qemu_sys::ShutdownCause_SHUTDOWN_CAUSE_GUEST_PANIC => { - QemuShutdownCause::GuestPanic - } - libafl_qemu_sys::ShutdownCause_SHUTDOWN_CAUSE_SUBSYSTEM_RESET => { - QemuShutdownCause::SubsystemReset - } - libafl_qemu_sys::ShutdownCause_SHUTDOWN_CAUSE_SNAPSHOT_LOAD => { - QemuShutdownCause::SnapshotLoad - } - - _ => panic!("shutdown cause not handled."), - }; - - QemuExitReason::End(qemu_shutdown_cause) - }, - libafl_qemu_sys::libafl_exit_reason_kind_BREAKPOINT => unsafe { - let bp_addr = exit_reason.data.breakpoint.addr; - QemuExitReason::Breakpoint(bp_addr) - }, - libafl_qemu_sys::libafl_exit_reason_kind_SYNC_BACKDOOR => { - QemuExitReason::SyncBackdoor - } - _ => return Err(QemuExitReasonError::UnknownKind), - }) - } - } - - #[must_use] - #[allow(clippy::cast_possible_wrap)] - #[allow(clippy::cast_sign_loss)] - pub fn num_cpus(&self) -> usize { - unsafe { libafl_qemu_num_cpus() as usize } - } - - #[must_use] - pub fn current_cpu(&self) -> Option { - let ptr = unsafe { libafl_qemu_current_cpu() }; - if ptr.is_null() { - None - } else { - Some(CPU { ptr }) - } - } - - #[must_use] - #[allow(clippy::cast_possible_wrap)] - pub fn cpu_from_index(&self, index: usize) -> CPU { - unsafe { - CPU { - ptr: libafl_qemu_get_cpu(index as i32), - } - } - } - - #[must_use] - pub fn page_from_addr(&self, addr: GuestAddr) -> GuestAddr { - unsafe { libafl_page_from_addr(addr) } - } - - //#[must_use] - /*pub fn page_size() -> GuestUsize { - unsafe { libafl_page_size } - }*/ - - pub unsafe fn write_mem(&self, addr: GuestAddr, buf: &[u8]) { - self.current_cpu() - .unwrap_or_else(|| self.cpu_from_index(0)) - .write_mem(addr, buf); - } - - pub unsafe fn read_mem(&self, addr: GuestAddr, buf: &mut [u8]) { - self.current_cpu() - .unwrap_or_else(|| self.cpu_from_index(0)) - .read_mem(addr, buf); - } - - #[must_use] - pub fn num_regs(&self) -> i32 { - self.current_cpu().unwrap().num_regs() - } - - pub fn write_reg(&self, reg: R, val: T) -> Result<(), String> - where - T: Num + PartialOrd + Copy + Into, - R: Into, - { - self.current_cpu().unwrap().write_reg(reg, val) - } - - pub fn read_reg(&self, reg: R) -> Result - where - T: Num + PartialOrd + Copy + From, - R: Into, - { - self.current_cpu().unwrap().read_reg(reg) - } - - pub fn set_breakpoint(&self, addr: GuestAddr) { - unsafe { - libafl_qemu_set_breakpoint(addr.into()); - } - } - - pub fn remove_breakpoint(&self, addr: GuestAddr) { - unsafe { - libafl_qemu_remove_breakpoint(addr.into()); - } - } - - pub fn entry_break(&self, addr: GuestAddr) { - self.set_breakpoint(addr); - unsafe { - match self.run() { - Ok(QemuExitReason::Breakpoint(_)) => {} - _ => panic!("Unexpected QEMU exit."), - } - } - self.remove_breakpoint(addr); - } - - pub fn flush_jit(&self) { - unsafe { - libafl_flush_jit(); - } - } - - // TODO set T lifetime to be like Emulator - #[allow(clippy::missing_transmute_annotations)] - pub fn set_hook>( - &self, - data: T, - addr: GuestAddr, - callback: extern "C" fn(T, GuestAddr), - invalidate_block: bool, - ) -> InstructionHookId { - unsafe { - let data: u64 = data.into().0; - let callback: extern "C" fn(u64, GuestAddr) = transmute(callback); - let num = libafl_qemu_sys::libafl_qemu_set_hook( - addr.into(), - Some(callback), - data, - i32::from(invalidate_block), - ); - InstructionHookId(num) - } - } - - #[must_use] - pub fn remove_hook(&self, id: impl HookId, invalidate_block: bool) -> bool { - id.remove(invalidate_block) - } - - #[must_use] - pub fn remove_hooks_at(&self, addr: GuestAddr, invalidate_block: bool) -> usize { - unsafe { - libafl_qemu_sys::libafl_qemu_remove_hooks_at(addr.into(), i32::from(invalidate_block)) - } - } - - #[allow(clippy::missing_transmute_annotations)] - pub fn add_edge_hooks>( - &self, - data: T, - gen: Option u64>, - exec: Option, - ) -> EdgeHookId { - unsafe { - let data: u64 = data.into().0; - let gen: Option u64> = transmute(gen); - let exec: Option = transmute(exec); - let num = libafl_qemu_sys::libafl_add_edge_hook(gen, exec, data); - EdgeHookId(num) - } - } - - #[allow(clippy::missing_transmute_annotations)] - pub fn add_block_hooks>( - &self, - data: T, - gen: Option u64>, - post_gen: Option, - exec: Option, - ) -> BlockHookId { - unsafe { - let data: u64 = data.into().0; - let gen: Option u64> = transmute(gen); - let post_gen: Option = transmute(post_gen); - let exec: Option = transmute(exec); - let num = libafl_qemu_sys::libafl_add_block_hook(gen, post_gen, exec, data); - BlockHookId(num) - } - } - - /// `data` can be used to pass data that can be accessed as the first argument in the `gen` and the `exec` functions - /// - /// `gen` gets passed the current programm counter, mutable access to a `TCGTemp` and information about the memory - /// access being performed. - /// The `u64` return value is an id that gets passed to the `exec` functions as their second argument. - /// - /// `exec` hooks get invoked on every read performed by the guest - /// - /// `exec1`-`exec8` special case accesses of width 1-8 - /// - /// If there is no specialized hook for a given read width, the `exec_n` will be - /// called and its last argument will specify the access width - #[allow(clippy::missing_transmute_annotations)] - pub fn add_read_hooks>( - &self, - data: T, - gen: Option u64>, - exec1: Option, - exec2: Option, - exec4: Option, - exec8: Option, - exec_n: Option, - ) -> ReadHookId { - unsafe { - let data: u64 = data.into().0; - let gen: Option< - unsafe extern "C" fn( - u64, - GuestAddr, - *mut TCGTemp, - libafl_qemu_sys::MemOpIdx, - ) -> u64, - > = transmute(gen); - let exec1: Option = transmute(exec1); - let exec2: Option = transmute(exec2); - let exec4: Option = transmute(exec4); - let exec8: Option = transmute(exec8); - let exec_n: Option = transmute(exec_n); - let num = libafl_qemu_sys::libafl_add_read_hook( - gen, exec1, exec2, exec4, exec8, exec_n, data, - ); - ReadHookId(num) - } - } - - // TODO add MemOp info - #[allow(clippy::missing_transmute_annotations)] - pub fn add_write_hooks>( - &self, - data: T, - gen: Option u64>, - exec1: Option, - exec2: Option, - exec4: Option, - exec8: Option, - exec_n: Option, - ) -> WriteHookId { - unsafe { - let data: u64 = data.into().0; - let gen: Option< - unsafe extern "C" fn( - u64, - GuestAddr, - *mut TCGTemp, - libafl_qemu_sys::MemOpIdx, - ) -> u64, - > = transmute(gen); - let exec1: Option = transmute(exec1); - let exec2: Option = transmute(exec2); - let exec4: Option = transmute(exec4); - let exec8: Option = transmute(exec8); - let exec_n: Option = transmute(exec_n); - let num = libafl_qemu_sys::libafl_add_write_hook( - gen, exec1, exec2, exec4, exec8, exec_n, data, - ); - WriteHookId(num) - } - } - - #[allow(clippy::missing_transmute_annotations)] - pub fn add_cmp_hooks>( - &self, - data: T, - gen: Option u64>, - exec1: Option, - exec2: Option, - exec4: Option, - exec8: Option, - ) -> CmpHookId { - unsafe { - let data: u64 = data.into().0; - let gen: Option u64> = transmute(gen); - let exec1: Option = transmute(exec1); - let exec2: Option = transmute(exec2); - let exec4: Option = transmute(exec4); - let exec8: Option = transmute(exec8); - let num = libafl_qemu_sys::libafl_add_cmp_hook(gen, exec1, exec2, exec4, exec8, data); - CmpHookId(num) - } - } - - #[allow(clippy::missing_transmute_annotations)] - pub fn add_backdoor_hook>( - &self, - data: T, - callback: extern "C" fn(T, CPUArchStatePtr, GuestAddr), - ) -> BackdoorHookId { - unsafe { - let data: u64 = data.into().0; - let callback: extern "C" fn(u64, CPUArchStatePtr, GuestAddr) = transmute(callback); - let num = libafl_qemu_sys::libafl_add_backdoor_hook(Some(callback), data); - BackdoorHookId(num) - } - } - - #[allow(clippy::type_complexity)] - pub fn add_gdb_cmd(&self, callback: Box bool>) { - unsafe { - let fat: Box = Box::new(transmute::< - Box FnMut(&'a Qemu, &'b str) -> bool>, - FatPtr, - >(callback)); - libafl_qemu_add_gdb_cmd(gdb_cmd, core::ptr::from_ref(&*fat) as *const ()); - GDB_COMMANDS.push(fat); - } - } - - pub fn gdb_reply(&self, output: &str) { - unsafe { libafl_qemu_gdb_reply(output.as_bytes().as_ptr(), output.len()) }; - } -} - -impl ArchExtras for Qemu { - fn read_return_address(&self) -> Result - where - T: From, - { - self.current_cpu() - .ok_or("Failed to get current CPU")? - .read_return_address::() - } - - fn write_return_address(&self, val: T) -> Result<(), String> - where - T: Into, - { - self.current_cpu() - .ok_or("Failed to get current CPU")? - .write_return_address::(val) - } - - fn read_function_argument(&self, conv: CallingConvention, idx: u8) -> Result - where - T: From, - { - self.current_cpu() - .ok_or("Failed to get current CPU")? - .read_function_argument::(conv, idx) - } - - fn write_function_argument( - &self, - conv: CallingConvention, - idx: i32, - val: T, - ) -> Result<(), String> - where - T: Into, - { - self.current_cpu() - .ok_or("Failed to get current CPU")? - .write_function_argument::(conv, idx, val) - } -} - #[allow(clippy::unused_self)] impl Emulator where QT: QemuHelperTuple, S: State + HasExecutions, - E: EmuExitHandler, + E: EmulatorExitHandler, { #[allow(clippy::must_use_candidate, clippy::similar_names)] pub fn new( args: &[String], env: &[(String, String)], exit_handler: E, - ) -> Result { + ) -> Result { let qemu = Qemu::init(args, env)?; Self::new_with_qemu(qemu, exit_handler) } - pub fn new_with_qemu(qemu: Qemu, exit_handler: E) -> Result { - let emu_state = Box::new(EmulatorState { + pub fn new_with_qemu(qemu: Qemu, exit_handler: E) -> Result { + let emu_state = EmulatorState { exit_handler: RefCell::new(exit_handler), breakpoints: RefCell::new(HashSet::new()), _phantom: PhantomData, - }); - - let emu_state_ptr = unsafe { - let emu_ptr = NonNull::from(Box::leak(emu_state)); - EMULATOR_STATE = emu_ptr.as_ptr() as *mut (); - emu_ptr }; Ok(Emulator { - state: emu_state_ptr, + state: emu_state, qemu, }) } @@ -1395,12 +428,12 @@ where #[must_use] pub fn state(&self) -> &EmulatorState { - unsafe { self.state.as_ref() } + &self.state } #[must_use] pub fn state_mut(&mut self) -> &mut EmulatorState { - unsafe { self.state.as_mut() } + &mut self.state } #[must_use] @@ -1408,32 +441,6 @@ where &self.state().exit_handler } - #[must_use] - pub fn get() -> Option> { - unsafe { - if QEMU_IS_INITIALIZED { - Some(Emulator::::get_unchecked()) - } else { - None - } - } - } - - /// Get an empty emulator. - /// Same as `Emulator::get`, but without checking whether QEMU has been correctly initialized. - /// - /// # Safety - /// - /// Should not be used if `Qemu::init` or `Emulator::new` has never been used before (otherwise QEMU will not be initialized, and a crash will occur). - /// Prefer `Emulator::get` for a safe version of this method. - #[must_use] - pub unsafe fn get_unchecked() -> Emulator { - Emulator { - state: NonNull::dangling(), - qemu: Qemu::get_unchecked(), - } - } - #[must_use] #[allow(clippy::cast_possible_wrap)] #[allow(clippy::cast_sign_loss)] @@ -1544,25 +551,29 @@ where /// /// Should, in general, be safe to call. /// Of course, the emulated target is not contained securely and can corrupt state or interact with the operating system. - unsafe fn run_qemu(&self) -> Result { + unsafe fn run_qemu(&self) -> Result { match self.qemu.run() { Ok(qemu_exit_reason) => Ok(match qemu_exit_reason { - QemuExitReason::End(qemu_shutdown_cause) => EmuExitReason::End(qemu_shutdown_cause), + QemuExitReason::End(qemu_shutdown_cause) => { + EmulatorExitResult::QemuExit(qemu_shutdown_cause) + } QemuExitReason::Breakpoint(bp_addr) => { let bp = self .state() .breakpoints .borrow() .get(&bp_addr) - .ok_or(EmuExitReasonError::BreakpointNotFound(bp_addr))? + .ok_or(EmulatorExitError::BreakpointNotFound(bp_addr))? .clone(); - EmuExitReason::Breakpoint(bp) + EmulatorExitResult::Breakpoint(bp) + } + QemuExitReason::SyncExit => { + EmulatorExitResult::SyncExit(SyncExit::new(self.qemu.try_into()?)) } - QemuExitReason::SyncBackdoor => EmuExitReason::SyncBackdoor(self.try_into()?), }), Err(qemu_exit_reason_error) => Err(match qemu_exit_reason_error { - QemuExitReasonError::UnexpectedExit => EmuExitReasonError::UnexpectedExit, - QemuExitReasonError::UnknownKind => EmuExitReasonError::UnknownKind, + QemuExitError::UnexpectedExit => EmulatorExitError::UnexpectedExit, + QemuExitError::UnknownKind => EmulatorExitError::UnknownKind, }), } } @@ -1579,27 +590,19 @@ where &self, input: &S::Input, qemu_executor_state: &mut QemuExecutorState, - ) -> Result { + ) -> Result { loop { // Insert input if the location is already known - E::try_put_input(self, qemu_executor_state, input); + E::qemu_pre_run(self, qemu_executor_state, input); // Run QEMU let exit_reason = self.run_qemu(); // Handle QEMU exit - let handler_res = E::handle(self, exit_reason, qemu_executor_state, input)?; - - // Return to harness - match handler_res { - InnerHandlerResult::ReturnToHarness(exit_reason) => { - return Ok(HandlerResult::UnhandledExit(exit_reason)) - } - InnerHandlerResult::EndOfRun(exit_kind) => { - return Ok(HandlerResult::EndOfRun(exit_kind)) - } - InnerHandlerResult::Interrupt => return Ok(HandlerResult::Interrupted), - InnerHandlerResult::Continue => {} + if let Some(exit_handler_result) = + E::qemu_post_run(self, exit_reason, qemu_executor_state, input)? + { + return Ok(exit_handler_result); } } } @@ -1743,236 +746,3 @@ where self.qemu.gdb_reply(output); } } - -// impl ArchExtras for Emulator -// where -// QT: QemuHelperTuple, -// S: State + HasExecutions, -// E: EmuExitHandler, -// { -// fn read_return_address(&self) -> Result -// where -// T: From, -// { -// self.qemu.read_return_address() -// } -// -// fn write_return_address(&self, val: T) -> Result<(), String> -// where -// T: Into, -// { -// self.qemu.write_return_address(val) -// } -// -// fn read_function_argument(&self, conv: CallingConvention, idx: u8) -> Result -// where -// T: From, -// { -// self.qemu.read_function_argument(conv, idx) -// } -// -// fn write_function_argument( -// &self, -// conv: CallingConvention, -// idx: i32, -// val: T, -// ) -> Result<(), String> -// where -// T: Into, -// { -// self.qemu.write_function_argument(conv, idx, val) -// } -// } - -#[cfg(feature = "python")] -pub mod pybind { - use pyo3::{exceptions::PyValueError, prelude::*, types::PyInt}; - - use super::{GuestAddr, GuestUsize, MmapPerms, SyscallHookResult}; - - static mut PY_SYSCALL_HOOK: Option = None; - static mut PY_GENERIC_HOOKS: Vec<(GuestAddr, PyObject)> = vec![]; - - extern "C" fn py_syscall_hook_wrapper( - _data: u64, - sys_num: i32, - a0: u64, - a1: u64, - a2: u64, - a3: u64, - a4: u64, - a5: u64, - a6: u64, - a7: u64, - ) -> SyscallHookResult { - unsafe { PY_SYSCALL_HOOK.as_ref() }.map_or_else( - || SyscallHookResult::new(None), - |obj| { - let args = (sys_num, a0, a1, a2, a3, a4, a5, a6, a7); - Python::with_gil(|py| { - let ret = obj.call1(py, args).expect("Error in the syscall hook"); - let any = ret.as_ref(py); - if any.is_none() { - SyscallHookResult::new(None) - } else { - let a: Result<&PyInt, _> = any.downcast(); - if let Ok(i) = a { - SyscallHookResult::new(Some( - i.extract().expect("Invalid syscall hook return value"), - )) - } else { - SyscallHookResult::extract(any) - .expect("The syscall hook must return a SyscallHookResult") - } - } - }) - }, - ) - } - - extern "C" fn py_generic_hook_wrapper(idx: u64, _pc: GuestAddr) { - let obj = unsafe { &PY_GENERIC_HOOKS[idx as usize].1 }; - Python::with_gil(|py| { - obj.call0(py).expect("Error in the hook"); - }); - } - - #[pyclass(unsendable)] - pub struct Qemu { - pub qemu: super::Qemu, - } - - #[pymethods] - impl Qemu { - #[allow(clippy::needless_pass_by_value)] - #[new] - fn new(args: Vec, env: Vec<(String, String)>) -> PyResult { - let qemu = super::Qemu::init(&args, &env) - .map_err(|e| PyValueError::new_err(format!("{e}")))?; - - Ok(Qemu { qemu }) - } - - fn run(&self) { - unsafe { - self.qemu.run().unwrap(); - } - } - - fn write_mem(&self, addr: GuestAddr, buf: &[u8]) { - unsafe { - self.qemu.write_mem(addr, buf); - } - } - - fn read_mem(&self, addr: GuestAddr, size: usize) -> Vec { - let mut buf = vec![0; size]; - unsafe { - self.qemu.read_mem(addr, &mut buf); - } - buf - } - - fn num_regs(&self) -> i32 { - self.qemu.num_regs() - } - - fn write_reg(&self, reg: i32, val: GuestUsize) -> PyResult<()> { - self.qemu.write_reg(reg, val).map_err(PyValueError::new_err) - } - - fn read_reg(&self, reg: i32) -> PyResult { - self.qemu.read_reg(reg).map_err(PyValueError::new_err) - } - - fn set_breakpoint(&self, addr: GuestAddr) { - self.qemu.set_breakpoint(addr); - } - - fn entry_break(&self, addr: GuestAddr) { - self.qemu.entry_break(addr); - } - - fn remove_breakpoint(&self, addr: GuestAddr) { - self.qemu.remove_breakpoint(addr); - } - - fn g2h(&self, addr: GuestAddr) -> u64 { - self.qemu.g2h::<*const u8>(addr) as u64 - } - - fn h2g(&self, addr: u64) -> GuestAddr { - self.qemu.h2g(addr as *const u8) - } - - fn binary_path(&self) -> String { - self.qemu.binary_path().to_owned() - } - - fn load_addr(&self) -> GuestAddr { - self.qemu.load_addr() - } - - fn flush_jit(&self) { - self.qemu.flush_jit(); - } - - fn map_private(&self, addr: GuestAddr, size: usize, perms: i32) -> PyResult { - if let Ok(p) = MmapPerms::try_from(perms) { - self.qemu - .map_private(addr, size, p) - .map_err(PyValueError::new_err) - } else { - Err(PyValueError::new_err("Invalid perms")) - } - } - - fn map_fixed(&self, addr: GuestAddr, size: usize, perms: i32) -> PyResult { - if let Ok(p) = MmapPerms::try_from(perms) { - self.qemu - .map_fixed(addr, size, p) - .map_err(PyValueError::new_err) - } else { - Err(PyValueError::new_err("Invalid perms")) - } - } - - fn mprotect(&self, addr: GuestAddr, size: usize, perms: i32) -> PyResult<()> { - if let Ok(p) = MmapPerms::try_from(perms) { - self.qemu - .mprotect(addr, size, p) - .map_err(PyValueError::new_err) - } else { - Err(PyValueError::new_err("Invalid perms")) - } - } - - fn unmap(&self, addr: GuestAddr, size: usize) -> PyResult<()> { - self.qemu.unmap(addr, size).map_err(PyValueError::new_err) - } - - fn set_syscall_hook(&self, hook: PyObject) { - unsafe { - PY_SYSCALL_HOOK = Some(hook); - } - self.qemu - .add_pre_syscall_hook(0u64, py_syscall_hook_wrapper); - } - - fn set_hook(&self, addr: GuestAddr, hook: PyObject) { - unsafe { - let idx = PY_GENERIC_HOOKS.len(); - PY_GENERIC_HOOKS.push((addr, hook)); - self.qemu - .set_hook(idx as u64, addr, py_generic_hook_wrapper, true); - } - } - - fn remove_hooks_at(&self, addr: GuestAddr) -> usize { - unsafe { - PY_GENERIC_HOOKS.retain(|(a, _)| *a != addr); - } - self.qemu.remove_hooks_at(addr, true) - } - } -} diff --git a/libafl_qemu/src/emu/systemmode.rs b/libafl_qemu/src/emu/systemmode.rs index cdcfc7bf4a..acbc711658 100644 --- a/libafl_qemu/src/emu/systemmode.rs +++ b/libafl_qemu/src/emu/systemmode.rs @@ -1,22 +1,15 @@ use std::{ collections::HashMap, - ffi::{c_void, CStr, CString}, fmt::Debug, - mem::MaybeUninit, - ptr::null_mut, sync::atomic::{AtomicU64, Ordering}, }; use libafl::state::{HasExecutions, State}; -use libafl_qemu_sys::{ - libafl_load_qemu_snapshot, libafl_qemu_current_paging_id, libafl_save_qemu_snapshot, - qemu_cleanup, qemu_main_loop, vm_start, GuestAddr, GuestPhysAddr, GuestVirtAddr, -}; +use libafl_qemu_sys::GuestPhysAddr; use crate::{ - emu::{libafl_page_from_addr, IsSnapshotManager}, - EmuExitHandler, Emulator, MemAccessInfo, Qemu, QemuExitReason, QemuExitReasonError, - QemuHelperTuple, SnapshotId, SnapshotManagerError, CPU, + emu::IsSnapshotManager, DeviceSnapshotFilter, Emulator, EmulatorExitHandler, Qemu, + QemuHelperTuple, SnapshotId, SnapshotManagerError, }; impl SnapshotId { @@ -25,9 +18,7 @@ impl SnapshotId { let unique_id = UNIQUE_ID.fetch_add(1, Ordering::SeqCst); - SnapshotId { - id: unique_id.clone(), - } + SnapshotId { id: unique_id } } fn inner(&self) -> u64 { @@ -84,7 +75,7 @@ impl FastSnapshotManager { } pub unsafe fn get(&self, id: &SnapshotId) -> FastSnapshotPtr { - self.snapshots.get(id).unwrap().clone() + *self.snapshots.get(id).unwrap() } } @@ -136,18 +127,18 @@ impl IsSnapshotManager for FastSnapshotManager { snapshot_id: &SnapshotId, qemu: &Qemu, ) -> Result<(), SnapshotManagerError> { - let fast_snapshot_ptr = self + let fast_snapshot_ptr = *self .snapshots .get(snapshot_id) - .ok_or(SnapshotManagerError::SnapshotIdNotFound( - snapshot_id.clone(), - ))? - .clone(); + .ok_or(SnapshotManagerError::SnapshotIdNotFound(*snapshot_id))?; - qemu.restore_fast_snapshot(fast_snapshot_ptr); + unsafe { + qemu.restore_fast_snapshot(fast_snapshot_ptr); + } if self.check_memory_consistency { - let nb_inconsistencies = qemu.check_fast_snapshot_memory_consistency(fast_snapshot_ptr); + let nb_inconsistencies = + unsafe { qemu.check_fast_snapshot_memory_consistency(fast_snapshot_ptr) }; if nb_inconsistencies > 0 { return Err(SnapshotManagerError::MemoryInconsistencies( @@ -160,251 +151,11 @@ impl IsSnapshotManager for FastSnapshotManager { } } -pub enum DeviceSnapshotFilter { - All, - AllowList(Vec), - DenyList(Vec), -} - -impl DeviceSnapshotFilter { - fn enum_id(&self) -> libafl_qemu_sys::DeviceSnapshotKind { - match self { - DeviceSnapshotFilter::All => libafl_qemu_sys::DeviceSnapshotKind_DEVICE_SNAPSHOT_ALL, - DeviceSnapshotFilter::AllowList(_) => { - libafl_qemu_sys::DeviceSnapshotKind_DEVICE_SNAPSHOT_ALLOWLIST - } - DeviceSnapshotFilter::DenyList(_) => { - libafl_qemu_sys::DeviceSnapshotKind_DEVICE_SNAPSHOT_DENYLIST - } - } - } - - fn devices(&self, v: &mut Vec<*mut i8>) -> *mut *mut i8 { - v.clear(); - match self { - DeviceSnapshotFilter::All => null_mut(), - DeviceSnapshotFilter::AllowList(l) | DeviceSnapshotFilter::DenyList(l) => { - for name in l { - v.push(name.as_bytes().as_ptr() as *mut i8); - } - v.push(core::ptr::null_mut()); - v.as_mut_ptr() - } - } - } -} - -pub(super) extern "C" fn qemu_cleanup_atexit() { - unsafe { - qemu_cleanup(); - } -} - -impl CPU { - #[must_use] - pub fn get_phys_addr(&self, vaddr: GuestAddr) -> Option { - unsafe { - let page = libafl_page_from_addr(vaddr); - let mut attrs = MaybeUninit::::uninit(); - let paddr = libafl_qemu_sys::cpu_get_phys_page_attrs_debug( - self.ptr, - page as GuestVirtAddr, - attrs.as_mut_ptr(), - ); - if paddr == (-1i64 as GuestPhysAddr) { - None - } else { - Some(paddr) - } - } - } - - #[must_use] - pub fn get_phys_addr_tlb( - &self, - vaddr: GuestAddr, - info: MemAccessInfo, - is_store: bool, - ) -> Option { - unsafe { - let pminfo = libafl_qemu_sys::make_plugin_meminfo( - info.oi, - if is_store { - libafl_qemu_sys::qemu_plugin_mem_rw_QEMU_PLUGIN_MEM_W - } else { - libafl_qemu_sys::qemu_plugin_mem_rw_QEMU_PLUGIN_MEM_R - }, - ); - let phwaddr = libafl_qemu_sys::qemu_plugin_get_hwaddr(pminfo, vaddr as GuestVirtAddr); - if phwaddr.is_null() { - None - } else { - Some(libafl_qemu_sys::qemu_plugin_hwaddr_phys_addr(phwaddr) as GuestPhysAddr) - } - } - } - - #[must_use] - pub fn current_paging_id(&self) -> Option { - let paging_id = unsafe { libafl_qemu_current_paging_id(self.ptr) }; - - if paging_id == 0 { - None - } else { - Some(paging_id) - } - } - - /// Write a value to a guest address. - /// - /// # Safety - /// This will write to a translated guest address (using `g2h`). - /// It just adds `guest_base` and writes to that location, without checking the bounds. - /// This may only be safely used for valid guest addresses! - pub unsafe fn write_mem(&self, addr: GuestAddr, buf: &[u8]) { - // TODO use gdbstub's target_cpu_memory_rw_debug - libafl_qemu_sys::cpu_memory_rw_debug( - self.ptr, - addr as GuestVirtAddr, - buf.as_ptr() as *mut _, - buf.len(), - true, - ); - } - - /// Read a value from a guest address. - /// - /// # Safety - /// This will read from a translated guest address (using `g2h`). - /// It just adds `guest_base` and writes to that location, without checking the bounds. - /// This may only be safely used for valid guest addresses! - pub unsafe fn read_mem(&self, addr: GuestAddr, buf: &mut [u8]) { - // TODO use gdbstub's target_cpu_memory_rw_debug - libafl_qemu_sys::cpu_memory_rw_debug( - self.ptr, - addr as GuestVirtAddr, - buf.as_mut_ptr() as *mut _, - buf.len(), - false, - ); - } - - #[must_use] - pub fn page_size(&self) -> usize { - unsafe { libafl_qemu_sys::qemu_target_page_size() } - } -} - -#[allow(clippy::unused_self)] -impl Qemu { - /// Write a value to a phsical guest address, including ROM areas. - pub unsafe fn write_phys_mem(&self, paddr: GuestPhysAddr, buf: &[u8]) { - libafl_qemu_sys::cpu_physical_memory_rw( - paddr, - buf.as_ptr() as *mut _, - buf.len() as u64, - true, - ); - } - - /// Read a value from a physical guest address. - pub unsafe fn read_phys_mem(&self, paddr: GuestPhysAddr, buf: &mut [u8]) { - libafl_qemu_sys::cpu_physical_memory_rw( - paddr, - buf.as_mut_ptr() as *mut _, - buf.len() as u64, - false, - ); - } - - /// This function will run the emulator until the next breakpoint / sync exit, or until finish. - /// It is a low-level function and simply kicks QEMU. - /// # Safety - /// - /// Should, in general, be safe to call. - /// Of course, the emulated target is not contained securely and can corrupt state or interact with the operating system. - pub unsafe fn run(&self) -> Result { - vm_start(); - qemu_main_loop(); - - self.post_run() - } - - pub fn save_snapshot(&self, name: &str, sync: bool) { - let s = CString::new(name).expect("Invalid snapshot name"); - unsafe { libafl_save_qemu_snapshot(s.as_ptr() as *const _, sync) }; - } - - pub fn load_snapshot(&self, name: &str, sync: bool) { - let s = CString::new(name).expect("Invalid snapshot name"); - unsafe { libafl_load_qemu_snapshot(s.as_ptr() as *const _, sync) }; - } - - #[must_use] - pub fn create_fast_snapshot(&self, track: bool) -> FastSnapshotPtr { - unsafe { - libafl_qemu_sys::syx_snapshot_new( - track, - true, - libafl_qemu_sys::DeviceSnapshotKind_DEVICE_SNAPSHOT_ALL, - null_mut(), - ) - } - } - - #[must_use] - pub fn create_fast_snapshot_filter( - &self, - track: bool, - device_filter: &DeviceSnapshotFilter, - ) -> FastSnapshotPtr { - let mut v = vec![]; - unsafe { - libafl_qemu_sys::syx_snapshot_new( - track, - true, - device_filter.enum_id(), - device_filter.devices(&mut v), - ) - } - } - - pub fn restore_fast_snapshot(&self, snapshot: FastSnapshotPtr) { - unsafe { libafl_qemu_sys::syx_snapshot_root_restore(snapshot) } - } - - pub fn check_fast_snapshot_memory_consistency(&self, snapshot: FastSnapshotPtr) -> u64 { - unsafe { libafl_qemu_sys::syx_snapshot_check_memory_consistency(snapshot) } - } - - pub fn list_devices(&self) -> Vec { - let mut r = vec![]; - unsafe { - let devices = libafl_qemu_sys::device_list_all(); - if devices.is_null() { - return r; - } - - let mut ptr = devices; - while !(*ptr).is_null() { - let c_str: &CStr = CStr::from_ptr(*ptr); - let name = c_str.to_str().unwrap().to_string(); - r.push(name); - - ptr = ptr.add(1); - } - - libc::free(devices as *mut c_void); - r - } - } -} - impl Emulator where QT: QemuHelperTuple, S: State + HasExecutions, - E: EmuExitHandler, + E: EmulatorExitHandler, { /// Write a value to a phsical guest address, including ROM areas. pub unsafe fn write_phys_mem(&self, paddr: GuestPhysAddr, buf: &[u8]) { @@ -438,11 +189,11 @@ where self.qemu.create_fast_snapshot_filter(track, device_filter) } - pub fn restore_fast_snapshot(&self, snapshot: FastSnapshotPtr) { + pub unsafe fn restore_fast_snapshot(&self, snapshot: FastSnapshotPtr) { self.qemu.restore_fast_snapshot(snapshot) } - pub fn check_fast_snapshot_memory_consistency(&self, snapshot: FastSnapshotPtr) -> u64 { + pub unsafe fn check_fast_snapshot_memory_consistency(&self, snapshot: FastSnapshotPtr) -> u64 { self.qemu.check_fast_snapshot_memory_consistency(snapshot) } diff --git a/libafl_qemu/src/emu/usermode.rs b/libafl_qemu/src/emu/usermode.rs index 427192e9ed..dff342547a 100644 --- a/libafl_qemu/src/emu/usermode.rs +++ b/libafl_qemu/src/emu/usermode.rs @@ -1,365 +1,16 @@ -use core::{mem::MaybeUninit, ptr::copy_nonoverlapping}; -use std::{cell::OnceCell, slice::from_raw_parts, str::from_utf8_unchecked}; - -use libafl_qemu_sys::{ - exec_path, free_self_maps, guest_base, libafl_dump_core_hook, libafl_force_dfl, libafl_get_brk, - libafl_load_addr, libafl_maps_first, libafl_maps_next, libafl_qemu_run, libafl_set_brk, - mmap_next_start, pageflags_get_root, read_self_maps, strlen, GuestAddr, GuestUsize, - IntervalTreeNode, IntervalTreeRoot, MapInfo, MmapPerms, VerifyAccess, -}; -use libc::c_int; -#[cfg(feature = "python")] -use pyo3::prelude::*; +use libafl_qemu_sys::{GuestAddr, MmapPerms, VerifyAccess}; use crate::{ emu::{HasExecutions, State}, - sync_exit::SyncBackdoorError, - EmuExitHandler, Emulator, HookData, NewThreadHookId, PostSyscallHookId, PreSyscallHookId, Qemu, - QemuExitReason, QemuExitReasonError, QemuHelperTuple, SyscallHookResult, CPU, + Emulator, EmulatorExitHandler, GuestMaps, HookData, NewThreadHookId, PostSyscallHookId, + PreSyscallHookId, QemuHelperTuple, SyscallHookResult, }; -#[derive(Debug, Clone)] -pub enum HandlerError { - EmuExitReasonError(QemuExitReasonError), - SyncBackdoorError(SyncBackdoorError), - MultipleInputDefinition, -} - -#[cfg_attr(feature = "python", pyclass(unsendable))] -pub struct GuestMaps { - self_maps_root: *mut IntervalTreeRoot, - pageflags_node: *mut IntervalTreeNode, -} - -// Consider a private new only for Emulator -impl GuestMaps { - #[must_use] - pub(crate) fn new() -> Self { - unsafe { - let pageflags_root = pageflags_get_root(); - let self_maps_root = read_self_maps(); - let pageflags_first = libafl_maps_first(pageflags_root); - Self { - self_maps_root, - pageflags_node: pageflags_first, - } - } - } -} - -impl Iterator for GuestMaps { - type Item = MapInfo; - - #[allow(clippy::uninit_assumed_init)] - fn next(&mut self) -> Option { - unsafe { - let mut ret = MaybeUninit::uninit(); - - self.pageflags_node = - libafl_maps_next(self.pageflags_node, self.self_maps_root, ret.as_mut_ptr()); - - let ret = ret.assume_init(); - - if ret.is_valid { - Some(ret.into()) - } else { - None - } - } - } -} - -#[cfg(feature = "python")] -#[pymethods] -impl GuestMaps { - fn __iter__(slf: PyRef) -> PyRef { - slf - } - fn __next__(mut slf: PyRefMut) -> Option { - Python::with_gil(|py| slf.next().map(|x| x.into_py(py))) - } -} - -impl Drop for GuestMaps { - fn drop(&mut self) { - unsafe { - free_self_maps(self.self_maps_root); - } - } -} - -impl CPU { - /// Write a value to a guest address. - /// - /// # Safety - /// This will write to a translated guest address (using `g2h`). - /// It just adds `guest_base` and writes to that location, without checking the bounds. - /// This may only be safely used for valid guest addresses! - pub unsafe fn write_mem(&self, addr: GuestAddr, buf: &[u8]) { - let host_addr = Qemu::get().unwrap().g2h(addr); - copy_nonoverlapping(buf.as_ptr(), host_addr, buf.len()); - } - - /// Read a value from a guest address. - /// - /// # Safety - /// This will read from a translated guest address (using `g2h`). - /// It just adds `guest_base` and writes to that location, without checking the bounds. - /// This may only be safely used for valid guest addresses! - pub unsafe fn read_mem(&self, addr: GuestAddr, buf: &mut [u8]) { - let host_addr = Qemu::get().unwrap().g2h(addr); - copy_nonoverlapping(host_addr, buf.as_mut_ptr(), buf.len()); - } - - #[must_use] - pub fn page_size(&self) -> usize { - thread_local! { - static PAGE_SIZE: OnceCell = const { OnceCell::new() }; - } - - PAGE_SIZE.with(|s| { - *s.get_or_init(|| { - unsafe { libc::sysconf(libc::_SC_PAGE_SIZE) } - .try_into() - .expect("Invalid page size") - }) - }) - } -} - -#[allow(clippy::unused_self)] -impl Qemu { - #[must_use] - pub fn mappings(&self) -> GuestMaps { - GuestMaps::new() - } - - #[must_use] - pub fn g2h(&self, addr: GuestAddr) -> *mut T { - unsafe { (addr as usize + guest_base) as *mut T } - } - - #[must_use] - pub fn h2g(&self, addr: *const T) -> GuestAddr { - unsafe { (addr as usize - guest_base) as GuestAddr } - } - - #[must_use] - pub fn access_ok(&self, kind: VerifyAccess, addr: GuestAddr, size: usize) -> bool { - self.current_cpu() - .unwrap_or_else(|| self.cpu_from_index(0)) - .access_ok(kind, addr, size) - } - - pub fn force_dfl(&self) { - unsafe { - libafl_force_dfl = 1; - } - } - - /// This function will run the emulator until the next breakpoint, or until finish. - /// # Safety - /// - /// Should, in general, be safe to call. - /// Of course, the emulated target is not contained securely and can corrupt state or interact with the operating system. - pub unsafe fn run(&self) -> Result { - libafl_qemu_run(); - - self.post_run() - } - - #[must_use] - pub fn binary_path<'a>(&self) -> &'a str { - unsafe { from_utf8_unchecked(from_raw_parts(exec_path, strlen(exec_path))) } - } - - #[must_use] - pub fn load_addr(&self) -> GuestAddr { - unsafe { libafl_load_addr() as GuestAddr } - } - - #[must_use] - pub fn get_brk(&self) -> GuestAddr { - unsafe { libafl_get_brk() as GuestAddr } - } - - pub fn set_brk(&self, brk: GuestAddr) { - unsafe { libafl_set_brk(brk.into()) }; - } - - #[must_use] - pub fn get_mmap_start(&self) -> GuestAddr { - unsafe { mmap_next_start } - } - - pub fn set_mmap_start(&self, start: GuestAddr) { - unsafe { mmap_next_start = start }; - } - - #[allow(clippy::cast_sign_loss)] - fn mmap( - self, - addr: GuestAddr, - size: usize, - perms: MmapPerms, - flags: c_int, - ) -> Result { - let res = unsafe { - libafl_qemu_sys::target_mmap(addr, size as GuestUsize, perms.into(), flags, -1, 0) - }; - if res <= 0 { - Err(()) - } else { - Ok(res as GuestAddr) - } - } - - pub fn map_private( - &self, - addr: GuestAddr, - size: usize, - perms: MmapPerms, - ) -> Result { - self.mmap(addr, size, perms, libc::MAP_PRIVATE | libc::MAP_ANONYMOUS) - .map_err(|()| format!("Failed to map {addr}")) - .map(|addr| addr as GuestAddr) - } - - pub fn map_fixed( - &self, - addr: GuestAddr, - size: usize, - perms: MmapPerms, - ) -> Result { - self.mmap( - addr, - size, - perms, - libc::MAP_FIXED | libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, - ) - .map_err(|()| format!("Failed to map {addr}")) - .map(|addr| addr as GuestAddr) - } - - pub fn mprotect(&self, addr: GuestAddr, size: usize, perms: MmapPerms) -> Result<(), String> { - let res = unsafe { - libafl_qemu_sys::target_mprotect(addr.into(), size as GuestUsize, perms.into()) - }; - if res == 0 { - Ok(()) - } else { - Err(format!("Failed to mprotect {addr}")) - } - } - - pub fn unmap(&self, addr: GuestAddr, size: usize) -> Result<(), String> { - if unsafe { libafl_qemu_sys::target_munmap(addr.into(), size as GuestUsize) } == 0 { - Ok(()) - } else { - Err(format!("Failed to unmap {addr}")) - } - } - - #[allow(clippy::type_complexity)] - pub fn add_pre_syscall_hook>( - &self, - data: T, - callback: extern "C" fn( - T, - i32, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - ) -> SyscallHookResult, - ) -> PreSyscallHookId { - unsafe { - let data: u64 = data.into().0; - let callback: extern "C" fn( - u64, - i32, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - ) -> libafl_qemu_sys::syshook_ret = core::mem::transmute(callback); - let num = libafl_qemu_sys::libafl_add_pre_syscall_hook(Some(callback), data); - PreSyscallHookId(num) - } - } - - #[allow(clippy::type_complexity)] - pub fn add_post_syscall_hook>( - &self, - data: T, - callback: extern "C" fn( - T, - GuestAddr, - i32, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - ) -> GuestAddr, - ) -> PostSyscallHookId { - unsafe { - let data: u64 = data.into().0; - let callback: extern "C" fn( - u64, - GuestAddr, - i32, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - GuestAddr, - ) -> GuestAddr = core::mem::transmute(callback); - let num = libafl_qemu_sys::libafl_add_post_syscall_hook(Some(callback), data); - PostSyscallHookId(num) - } - } - - pub fn add_new_thread_hook>( - &self, - data: T, - callback: extern "C" fn(T, tid: u32) -> bool, - ) -> NewThreadHookId { - unsafe { - let data: u64 = data.into().0; - let callback: extern "C" fn(u64, u32) -> bool = core::mem::transmute(callback); - let num = libafl_qemu_sys::libafl_add_new_thread_hook(Some(callback), data); - NewThreadHookId(num) - } - } - - #[allow(clippy::type_complexity)] - pub fn set_crash_hook(&self, callback: extern "C" fn(i32)) { - unsafe { - libafl_dump_core_hook = callback; - } - } -} - impl Emulator where QT: QemuHelperTuple, S: State + HasExecutions, - E: EmuExitHandler, + E: EmulatorExitHandler, { /// This function gets the memory mappings from the emulator. #[must_use] diff --git a/libafl_qemu/src/executor/mod.rs b/libafl_qemu/src/executor/mod.rs index d6cd36f76e..2df48fa062 100644 --- a/libafl_qemu/src/executor/mod.rs +++ b/libafl_qemu/src/executor/mod.rs @@ -8,7 +8,9 @@ use core::{ }; #[cfg(feature = "fork")] -use libafl::{events::EventManager, executors::InProcessForkExecutor, state::HasLastReportTime}; +use libafl::{ + events::EventManager, executors::InProcessForkExecutor, state::HasLastReportTime, HasMetadata, +}; use libafl::{ events::{EventFirer, EventRestarter}, executors::{ @@ -20,7 +22,7 @@ use libafl::{ fuzzer::HasObjective, observers::{ObserversTuple, UsesObservers}, state::{HasCorpus, HasExecutions, HasSolutions, State, UsesState}, - Error, HasMetadata, + Error, }; #[cfg(feature = "fork")] use libafl_bolts::shmem::ShMemProvider; diff --git a/libafl_qemu/src/helpers/asan.rs b/libafl_qemu/src/helpers/asan.rs index c738e9bf53..dab971bf21 100644 --- a/libafl_qemu/src/helpers/asan.rs +++ b/libafl_qemu/src/helpers/asan.rs @@ -17,12 +17,12 @@ use num_enum::{IntoPrimitive, TryFromPrimitive}; use rangemap::RangeMap; use crate::{ - emu::{EmuError, MemAccessInfo, SyscallHookResult}, helpers::{ calls::FullBacktraceCollector, HasInstrumentationFilter, IsFilter, QemuHelper, QemuHelperTuple, QemuInstrumentationAddressRangeFilter, }, hooks::{Hook, QemuHooks}, + qemu::{MemAccessInfo, QemuInitError, SyscallHookResult}, snapshot::QemuSnapshotHelper, sys::TCGTemp, GuestAddr, Qemu, Regs, @@ -668,7 +668,7 @@ static mut ASAN_INITED: bool = false; pub fn init_qemu_with_asan( args: &mut Vec, env: &mut [(String, String)], -) -> Result<(Qemu, Pin>), EmuError> { +) -> Result<(Qemu, Pin>), QemuInitError> { let current = env::current_exe().unwrap(); let asan_lib = fs::canonicalize(current) .unwrap() diff --git a/libafl_qemu/src/helpers/asan_guest.rs b/libafl_qemu/src/helpers/asan_guest.rs index df08b75f3e..5f7f800892 100644 --- a/libafl_qemu/src/helpers/asan_guest.rs +++ b/libafl_qemu/src/helpers/asan_guest.rs @@ -12,12 +12,12 @@ use libafl::{inputs::UsesInput, HasMetadata}; #[cfg(not(feature = "clippy"))] use crate::sys::libafl_tcg_gen_asan; use crate::{ - emu::{EmuError, MemAccessInfo, Qemu}, helpers::{ HasInstrumentationFilter, IsFilter, QemuHelper, QemuHelperTuple, QemuInstrumentationAddressRangeFilter, }, hooks::{Hook, QemuHooks}, + qemu::{MemAccessInfo, Qemu, QemuInitError}, sys::TCGTemp, GuestAddr, MapInfo, }; @@ -27,7 +27,7 @@ static mut ASAN_GUEST_INITED: bool = false; pub fn init_qemu_with_asan_guest( args: &mut Vec, env: &mut [(String, String)], -) -> Result<(Qemu, String), EmuError> { +) -> Result<(Qemu, String), QemuInitError> { let current = env::current_exe().unwrap(); let asan_lib = fs::canonicalize(current) .unwrap() diff --git a/libafl_qemu/src/helpers/calls.rs b/libafl_qemu/src/helpers/calls.rs index d40ed697da..ef192b7a85 100644 --- a/libafl_qemu/src/helpers/calls.rs +++ b/libafl_qemu/src/helpers/calls.rs @@ -12,12 +12,12 @@ use thread_local::ThreadLocal; use crate::{ capstone, - emu::ArchExtras, helpers::{ HasInstrumentationFilter, IsFilter, QemuHelper, QemuHelperTuple, QemuInstrumentationAddressRangeFilter, }, hooks::{Hook, QemuHooks}, + qemu::ArchExtras, Qemu, }; diff --git a/libafl_qemu/src/helpers/cmplog.rs b/libafl_qemu/src/helpers/cmplog.rs index 10290dea95..789b40de50 100644 --- a/libafl_qemu/src/helpers/cmplog.rs +++ b/libafl_qemu/src/helpers/cmplog.rs @@ -12,7 +12,7 @@ pub use libafl_targets::{ use serde::{Deserialize, Serialize}; #[cfg(emulation_mode = "usermode")] -use crate::{capstone, emu::ArchExtras, CallingConvention, Qemu}; +use crate::{capstone, qemu::ArchExtras, CallingConvention, Qemu}; use crate::{ helpers::{ hash_me, HasInstrumentationFilter, IsFilter, QemuHelper, QemuHelperTuple, diff --git a/libafl_qemu/src/helpers/edges.rs b/libafl_qemu/src/helpers/edges.rs index 097d4d3c4c..a685bae258 100644 --- a/libafl_qemu/src/helpers/edges.rs +++ b/libafl_qemu/src/helpers/edges.rs @@ -539,8 +539,7 @@ where let paging_id = hooks .qemu() .current_cpu() - .map(|cpu| cpu.current_paging_id()) - .flatten(); + .and_then(|cpu| cpu.current_paging_id()); if !h.must_instrument(src, paging_id) && !h.must_instrument(dest, paging_id) { return None; @@ -609,8 +608,7 @@ where let paging_id = hooks .qemu() .current_cpu() - .map(|cpu| cpu.current_paging_id()) - .flatten(); + .and_then(|cpu| cpu.current_paging_id()); if !h.must_instrument(src, paging_id) && !h.must_instrument(dest, paging_id) { return None; @@ -676,8 +674,7 @@ where let paging_id = hooks .qemu() .current_cpu() - .map(|cpu| cpu.current_paging_id()) - .flatten(); + .and_then(|cpu| cpu.current_paging_id()); if !h.must_instrument(pc, paging_id) { return None; diff --git a/libafl_qemu/src/helpers/injections.rs b/libafl_qemu/src/helpers/injections.rs index be410cb07f..d5a0ca1130 100644 --- a/libafl_qemu/src/helpers/injections.rs +++ b/libafl_qemu/src/helpers/injections.rs @@ -21,7 +21,7 @@ use serde::{Deserialize, Serialize}; #[cfg(not(cpu_target = "hexagon"))] use crate::SYS_execve; use crate::{ - elf::EasyElf, emu::ArchExtras, CallingConvention, Hook, Qemu, QemuHelper, QemuHelperTuple, + elf::EasyElf, qemu::ArchExtras, CallingConvention, Hook, Qemu, QemuHelper, QemuHelperTuple, QemuHooks, SyscallHookResult, }; #[cfg(cpu_target = "hexagon")] diff --git a/libafl_qemu/src/helpers/mod.rs b/libafl_qemu/src/helpers/mod.rs index 2eaab7b140..c0b616ffc5 100644 --- a/libafl_qemu/src/helpers/mod.rs +++ b/libafl_qemu/src/helpers/mod.rs @@ -1,5 +1,5 @@ use core::{fmt::Debug, ops::Range}; -use std::{collections::HashSet, hash::BuildHasher}; +use std::{cell::UnsafeCell, collections::HashSet, hash::BuildHasher}; use libafl::{executors::ExitKind, inputs::UsesInput, observers::ObserversTuple}; use libafl_bolts::tuples::{MatchFirstType, SplitBorrowExtractFirstType}; @@ -137,6 +137,19 @@ where } } +impl HasInstrumentationFilter<(), S> for () +where + S: UsesInput, +{ + fn filter(&self) -> &() { + self + } + + fn filter_mut(&mut self) -> &mut () { + self + } +} + impl HasInstrumentationFilter for (Head, ()) where Head: QemuHelper + HasInstrumentationFilter, @@ -272,6 +285,31 @@ pub trait StdInstrumentationFilter: { } +static mut EMPTY_ADDRESS_FILTER: UnsafeCell = + UnsafeCell::new(QemuFilterList::None); +static mut EMPTY_PAGING_FILTER: UnsafeCell = + UnsafeCell::new(QemuFilterList::None); + +impl HasInstrumentationFilter for () { + fn filter(&self) -> &QemuInstrumentationAddressRangeFilter { + &QemuFilterList::None + } + + fn filter_mut(&mut self) -> &mut QemuInstrumentationAddressRangeFilter { + unsafe { EMPTY_ADDRESS_FILTER.get_mut() } + } +} + +impl HasInstrumentationFilter for () { + fn filter(&self) -> &QemuInstrumentationPagingFilter { + &QemuFilterList::None + } + + fn filter_mut(&mut self) -> &mut QemuInstrumentationPagingFilter { + unsafe { EMPTY_PAGING_FILTER.get_mut() } + } +} + #[cfg(emulation_mode = "systemmode")] impl StdInstrumentationFilter for (Head, ()) where @@ -290,12 +328,26 @@ where { } +#[cfg(emulation_mode = "systemmode")] +impl StdInstrumentationFilter for () where S: UsesInput {} + +#[cfg(emulation_mode = "usermode")] +impl StdInstrumentationFilter for () where S: UsesInput {} + pub trait IsFilter: Debug { type FilterParameter; fn allowed(&self, filter_parameter: Self::FilterParameter) -> bool; } +impl IsFilter for () { + type FilterParameter = (); + + fn allowed(&self, _filter_parameter: Self::FilterParameter) -> bool { + true + } +} + pub trait IsAddressFilter: IsFilter {} #[cfg(emulation_mode = "systemmode")] diff --git a/libafl_qemu/src/helpers/snapshot.rs b/libafl_qemu/src/helpers/snapshot.rs index 0eb544fb78..4c9bb624b8 100644 --- a/libafl_qemu/src/helpers/snapshot.rs +++ b/libafl_qemu/src/helpers/snapshot.rs @@ -25,9 +25,9 @@ use crate::SYS_mmap2; use crate::SYS_newfstatat; use crate::{ asan::QemuAsanHelper, - emu::SyscallHookResult, helpers::{QemuHelper, QemuHelperTuple}, hooks::{Hook, QemuHooks}, + qemu::SyscallHookResult, Qemu, SYS_fstat, SYS_fstatfs, SYS_futex, SYS_getrandom, SYS_mprotect, SYS_mremap, SYS_munmap, SYS_pread64, SYS_read, SYS_readlinkat, SYS_statfs, }; diff --git a/libafl_qemu/src/hooks.rs b/libafl_qemu/src/hooks.rs index 6b51ddda11..939b5310a7 100644 --- a/libafl_qemu/src/hooks.rs +++ b/libafl_qemu/src/hooks.rs @@ -19,10 +19,10 @@ use libafl::{ }; use libafl_qemu_sys::{CPUArchStatePtr, FatPtr, GuestAddr, GuestUsize}; -pub use crate::emu::SyscallHookResult; +pub use crate::qemu::SyscallHookResult; use crate::{ - emu::{MemAccessInfo, Qemu, SKIP_EXEC_HOOK}, helpers::QemuHelperTuple, + qemu::{MemAccessInfo, Qemu, SKIP_EXEC_HOOK}, sys::TCGTemp, BackdoorHookId, BlockHookId, CmpHookId, EdgeHookId, HookId, InstructionHookId, ReadHookId, WriteHookId, diff --git a/libafl_qemu/src/lib.rs b/libafl_qemu/src/lib.rs index de3ef10c88..5a764f6fb2 100644 --- a/libafl_qemu/src/lib.rs +++ b/libafl_qemu/src/lib.rs @@ -1,4 +1,3 @@ -#![cfg_attr(nightly, feature(used_with_arg))] //! Welcome to `LibAFL` QEMU //! //! __Warning__: The documentation is built by default for `x86_64` in `usermode`. To access the documentation of other architectures or `systemmode`, the documentation must be rebuilt with the right features. @@ -49,6 +48,9 @@ pub use executor::QemuExecutor; #[cfg(feature = "fork")] pub use executor::QemuForkExecutor; +pub mod qemu; +pub use qemu::*; + pub mod emu; pub use emu::*; @@ -89,16 +91,19 @@ pub fn python_module(py: Python, m: &PyModule) -> PyResult<()> { m.add_submodule(regsm)?; let mmapm = PyModule::new(py, "mmap")?; - for r in emu::MmapPerms::iter() { + for r in sys::MmapPerms::iter() { let v: i32 = r.into(); mmapm.add(&format!("{r:?}"), v)?; } m.add_submodule(mmapm)?; - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; + m.add_class::()?; + + #[cfg(emulation_mode = "usermode")] + m.add_class::()?; + + m.add_class::()?; + m.add_class::()?; Ok(()) } diff --git a/libafl_qemu/src/qemu/mod.rs b/libafl_qemu/src/qemu/mod.rs new file mode 100644 index 0000000000..a94c91a8df --- /dev/null +++ b/libafl_qemu/src/qemu/mod.rs @@ -0,0 +1,1216 @@ +//! Low-level QEMU library +//! +//! This module exposes the low-level QEMU library through [`Qemu`]. +//! To access higher-level features of QEMU, it is recommanded to use [`crate::Emulator`] instead. + +use core::fmt; +use std::{ + cmp::{Ordering, PartialOrd}, + ffi::CString, + fmt::{Display, Formatter}, + intrinsics::{copy_nonoverlapping, transmute}, + mem::MaybeUninit, + ops::Range, + pin::Pin, + ptr, + ptr::{addr_of, null}, +}; + +use libafl_bolts::os::unix_signals::Signal; +#[cfg(emulation_mode = "systemmode")] +use libafl_qemu_sys::qemu_init; +#[cfg(emulation_mode = "usermode")] +use libafl_qemu_sys::{guest_base, qemu_user_init, VerifyAccess}; +use libafl_qemu_sys::{ + libafl_flush_jit, libafl_get_exit_reason, libafl_page_from_addr, libafl_qemu_add_gdb_cmd, + libafl_qemu_cpu_index, libafl_qemu_current_cpu, libafl_qemu_gdb_reply, libafl_qemu_get_cpu, + libafl_qemu_num_cpus, libafl_qemu_num_regs, libafl_qemu_read_reg, + libafl_qemu_remove_breakpoint, libafl_qemu_set_breakpoint, libafl_qemu_trigger_breakpoint, + libafl_qemu_write_reg, CPUArchState, CPUArchStatePtr, CPUStatePtr, FatPtr, GuestAddr, + GuestPhysAddr, GuestUsize, GuestVirtAddr, TCGTemp, +}; +use num_traits::Num; +#[cfg(feature = "python")] +use pyo3::prelude::*; +use strum::IntoEnumIterator; + +use crate::{GuestAddrKind, GuestReg, Regs}; + +#[cfg(emulation_mode = "usermode")] +mod usermode; +#[cfg(emulation_mode = "usermode")] +pub use usermode::*; + +#[cfg(emulation_mode = "systemmode")] +mod systemmode; +#[cfg(emulation_mode = "systemmode")] +#[allow(unused_imports)] +pub use systemmode::*; + +pub const SKIP_EXEC_HOOK: u64 = u64::MAX; +static mut QEMU_IS_INITIALIZED: bool = false; + +macro_rules! create_hook_id { + ($name:ident, $sys:ident, true) => { + paste::paste! { + #[derive(Clone, Copy, PartialEq, Debug)] + pub struct [<$name HookId>](pub(crate) usize); + impl HookId for [<$name HookId>] { + fn remove(&self, invalidate_block: bool) -> bool { + unsafe { libafl_qemu_sys::$sys(self.0, invalidate_block.into()) != 0 } + } + } + } + }; + ($name:ident, $sys:ident, false) => { + paste::paste! { + #[derive(Clone, Copy, PartialEq, Debug)] + pub struct [<$name HookId>](pub(crate) usize); + impl HookId for [<$name HookId>] { + fn remove(&self, _invalidate_block: bool) -> bool { + unsafe { libafl_qemu_sys::$sys(self.0) != 0 } + } + } + } + }; +} + +create_hook_id!(Instruction, libafl_qemu_remove_hook, true); +create_hook_id!(Backdoor, libafl_qemu_remove_backdoor_hook, true); +create_hook_id!(Edge, libafl_qemu_remove_edge_hook, true); +create_hook_id!(Block, libafl_qemu_remove_block_hook, true); +create_hook_id!(Read, libafl_qemu_remove_read_hook, true); +create_hook_id!(Write, libafl_qemu_remove_write_hook, true); +create_hook_id!(Cmp, libafl_qemu_remove_cmp_hook, true); +create_hook_id!(PreSyscall, libafl_qemu_remove_pre_syscall_hook, false); +create_hook_id!(PostSyscall, libafl_qemu_remove_post_syscall_hook, false); +create_hook_id!(NewThread, libafl_qemu_remove_new_thread_hook, false); + +#[derive(Debug)] +pub enum QemuInitError { + MultipleInstances, + EmptyArgs, + TooManyArgs(usize), +} + +#[derive(Debug, Clone)] +pub enum QemuExitReason { + End(QemuShutdownCause), // QEMU ended for some reason. + Breakpoint(GuestAddr), // Breakpoint triggered. Contains the address of the trigger. + SyncExit, // Synchronous backdoor: The guest triggered a backdoor and should return to LibAFL. +} + +#[derive(Debug, Clone)] +pub enum QemuExitError { + UnknownKind, // Exit reason was not NULL, but exit kind is unknown. Should never happen. + UnexpectedExit, // Qemu exited without going through an expected exit point. Can be caused by a crash for example. +} + +/// The thin wrapper around QEMU. +/// It is considered unsafe to use it directly. +/// Prefer using `Emulator` instead in case of doubt. +#[derive(Clone, Copy, Debug)] +pub struct Qemu { + _private: (), +} + +// syshook_ret +#[repr(C)] +#[cfg_attr(feature = "python", pyclass)] +#[cfg_attr(feature = "python", derive(FromPyObject))] +pub struct SyscallHookResult { + pub retval: GuestAddr, + pub skip_syscall: bool, +} + +#[derive(Debug, Clone)] +pub struct EmulatorMemoryChunk { + addr: GuestAddrKind, + size: GuestReg, + cpu: Option, +} + +#[allow(clippy::vec_box)] +static mut GDB_COMMANDS: Vec> = vec![]; + +extern "C" fn gdb_cmd(data: *const (), buf: *const u8, len: usize) -> i32 { + unsafe { + let closure = &mut *(data as *mut Box FnMut(&Qemu, &'r str) -> bool>); + let cmd = std::str::from_utf8_unchecked(std::slice::from_raw_parts(buf, len)); + let qemu = Qemu::get_unchecked(); + i32::from(closure(&qemu, cmd)) + } +} + +#[derive(Debug, Clone)] +pub enum QemuShutdownCause { + None, + HostError, + HostQmpQuit, + HostQmpSystemReset, + HostSignal(Signal), + HostUi, + GuestShutdown, + GuestReset, + GuestPanic, + SubsystemReset, + SnapshotLoad, +} + +#[repr(transparent)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub struct MemAccessInfo { + oi: libafl_qemu_sys::MemOpIdx, +} + +#[derive(Debug, Clone, Copy)] +#[repr(transparent)] +pub struct CPU { + ptr: CPUStatePtr, +} + +#[derive(Debug, PartialEq)] +pub enum CallingConvention { + Cdecl, +} + +pub trait HookId { + fn remove(&self, invalidate_block: bool) -> bool; +} + +#[derive(Debug)] +pub struct HookData(u64); + +impl std::error::Error for QemuInitError {} + +impl Display for QemuInitError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + QemuInitError::MultipleInstances => { + write!(f, "Only one instance of the QEMU Emulator is permitted") + } + QemuInitError::EmptyArgs => { + write!(f, "QEMU emulator args cannot be empty") + } + QemuInitError::TooManyArgs(n) => { + write!( + f, + "Too many arguments passed to QEMU emulator ({n} > i32::MAX)" + ) + } + } + } +} + +impl From for libafl::Error { + fn from(err: QemuInitError) -> Self { + libafl::Error::unknown(format!("{err}")) + } +} + +impl Display for QemuExitReason { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + QemuExitReason::End(shutdown_cause) => write!(f, "End: {shutdown_cause:?}"), + QemuExitReason::Breakpoint(bp) => write!(f, "Breakpoint: {bp}"), + QemuExitReason::SyncExit => write!(f, "Sync Exit"), + } + } +} + +impl MemAccessInfo { + #[must_use] + pub fn memop(&self) -> libafl_qemu_sys::MemOp { + libafl_qemu_sys::MemOp(self.oi >> 4) + } + + #[must_use] + pub fn memopidx(&self) -> libafl_qemu_sys::MemOpIdx { + self.oi + } + + #[must_use] + pub fn mmu_index(&self) -> u32 { + self.oi & 15 + } + + #[must_use] + pub fn size(&self) -> usize { + libafl_qemu_sys::memop_size(self.memop()) as usize + } + + #[must_use] + pub fn is_big_endian(&self) -> bool { + libafl_qemu_sys::memop_big_endian(self.memop()) + } + + #[must_use] + pub fn encode_with(&self, other: u32) -> u64 { + (u64::from(self.oi) << 32) | u64::from(other) + } + + #[must_use] + pub fn decode_from(encoded: u64) -> (Self, u32) { + let low = (encoded & 0xFFFFFFFF) as u32; + let high = (encoded >> 32) as u32; + (Self { oi: high }, low) + } + + #[must_use] + pub fn new(oi: libafl_qemu_sys::MemOpIdx) -> Self { + Self { oi } + } +} + +impl From for MemAccessInfo { + fn from(oi: libafl_qemu_sys::MemOpIdx) -> Self { + Self { oi } + } +} + +pub trait ArchExtras { + fn read_return_address(&self) -> Result + where + T: From; + fn write_return_address(&self, val: T) -> Result<(), String> + where + T: Into; + fn read_function_argument(&self, conv: CallingConvention, idx: u8) -> Result + where + T: From; + fn write_function_argument( + &self, + conv: CallingConvention, + idx: i32, + val: T, + ) -> Result<(), String> + where + T: Into; +} + +#[allow(clippy::unused_self)] +impl CPU { + #[must_use] + pub fn qemu(&self) -> Qemu { + unsafe { Qemu::get_unchecked() } + } + + #[must_use] + #[allow(clippy::cast_sign_loss)] + pub fn index(&self) -> usize { + unsafe { libafl_qemu_cpu_index(self.ptr) as usize } + } + + pub fn trigger_breakpoint(&self) { + unsafe { + libafl_qemu_trigger_breakpoint(self.ptr); + } + } + + #[cfg(emulation_mode = "usermode")] + #[must_use] + pub fn g2h(&self, addr: GuestAddr) -> *mut T { + unsafe { (addr as usize + guest_base) as *mut T } + } + + #[cfg(emulation_mode = "usermode")] + #[must_use] + pub fn h2g(&self, addr: *const T) -> GuestAddr { + unsafe { (addr as usize - guest_base) as GuestAddr } + } + + #[cfg(emulation_mode = "usermode")] + #[must_use] + pub fn access_ok(&self, kind: VerifyAccess, addr: GuestAddr, size: usize) -> bool { + unsafe { + // TODO add support for tagged GuestAddr + libafl_qemu_sys::page_check_range(addr, size as GuestAddr, kind.into()) + } + } + + // TODO expose tlb_set_dirty and tlb_reset_dirty + + #[must_use] + pub fn num_regs(&self) -> i32 { + unsafe { libafl_qemu_num_regs(self.ptr) } + } + + pub fn write_reg(&self, reg: R, val: T) -> Result<(), String> + where + R: Into, + T: Into, + { + let reg = reg.into(); + #[cfg(feature = "be")] + let val = GuestReg::to_be(val.into()); + + #[cfg(not(feature = "be"))] + let val = GuestReg::to_le(val.into()); + + let success = unsafe { libafl_qemu_write_reg(self.ptr, reg, addr_of!(val) as *const u8) }; + if success == 0 { + Err(format!("Failed to write to register {reg}")) + } else { + Ok(()) + } + } + + pub fn read_reg(&self, reg: R) -> Result + where + R: Into, + T: From, + { + unsafe { + let reg = reg.into(); + let mut val = MaybeUninit::uninit(); + let success = libafl_qemu_read_reg(self.ptr, reg, val.as_mut_ptr() as *mut u8); + if success == 0 { + Err(format!("Failed to read register {reg}")) + } else { + #[cfg(feature = "be")] + return Ok(GuestReg::from_be(val.assume_init()).into()); + + #[cfg(not(feature = "be"))] + return Ok(GuestReg::from_le(val.assume_init()).into()); + } + } + } + + pub fn reset(&self) { + unsafe { libafl_qemu_sys::cpu_reset(self.ptr) }; + } + + #[must_use] + pub fn save_state(&self) -> CPUArchState { + unsafe { + let mut saved = MaybeUninit::::uninit(); + copy_nonoverlapping( + libafl_qemu_sys::cpu_env(self.ptr.as_mut().unwrap()), + saved.as_mut_ptr(), + 1, + ); + saved.assume_init() + } + } + + pub fn restore_state(&self, saved: &CPUArchState) { + unsafe { + copy_nonoverlapping( + saved, + libafl_qemu_sys::cpu_env(self.ptr.as_mut().unwrap()), + 1, + ); + } + } + + #[must_use] + pub fn raw_ptr(&self) -> CPUStatePtr { + self.ptr + } + + #[must_use] + pub fn display_context(&self) -> String { + let mut display = String::new(); + let mut maxl = 0; + for r in Regs::iter() { + maxl = std::cmp::max(format!("{r:#?}").len(), maxl); + } + for (i, r) in Regs::iter().enumerate() { + let v: GuestAddr = self.read_reg(r).unwrap(); + let sr = format!("{r:#?}"); + display += &format!("{sr:>maxl$}: {v:#016x} "); + if (i + 1) % 4 == 0 { + display += "\n"; + } + } + if !display.ends_with('\n') { + display += "\n"; + } + display + } +} + +impl From> for HookData { + fn from(value: Pin<&mut T>) -> Self { + unsafe { HookData(transmute::, u64>(value)) } + } +} + +impl From> for HookData { + fn from(value: Pin<&T>) -> Self { + unsafe { HookData(transmute::, u64>(value)) } + } +} + +impl From<&'static mut T> for HookData { + fn from(value: &'static mut T) -> Self { + unsafe { HookData(transmute::<&mut T, u64>(value)) } + } +} + +impl From<&'static T> for HookData { + fn from(value: &'static T) -> Self { + unsafe { HookData(transmute::<&T, u64>(value)) } + } +} + +impl From<*mut T> for HookData { + fn from(value: *mut T) -> Self { + HookData(value as u64) + } +} + +impl From<*const T> for HookData { + fn from(value: *const T) -> Self { + HookData(value as u64) + } +} + +impl From for HookData { + fn from(value: u64) -> Self { + HookData(value) + } +} + +impl From for HookData { + fn from(value: u32) -> Self { + HookData(u64::from(value)) + } +} + +impl From for HookData { + fn from(value: u16) -> Self { + HookData(u64::from(value)) + } +} + +impl From for HookData { + fn from(value: u8) -> Self { + HookData(u64::from(value)) + } +} + +#[allow(clippy::unused_self)] +impl Qemu { + #[allow(clippy::must_use_candidate, clippy::similar_names)] + pub fn init(args: &[String], env: &[(String, String)]) -> Result { + if args.is_empty() { + return Err(QemuInitError::EmptyArgs); + } + + let argc = args.len(); + if i32::try_from(argc).is_err() { + return Err(QemuInitError::TooManyArgs(argc)); + } + + unsafe { + if QEMU_IS_INITIALIZED { + return Err(QemuInitError::MultipleInstances); + } + QEMU_IS_INITIALIZED = true; + } + + #[allow(clippy::cast_possible_wrap)] + let argc = argc as i32; + + let args: Vec = args + .iter() + .map(|x| CString::new(x.clone()).unwrap()) + .collect(); + let mut argv: Vec<*const u8> = args.iter().map(|x| x.as_ptr() as *const u8).collect(); + argv.push(ptr::null()); // argv is always null terminated. + let env_strs: Vec = env + .iter() + .map(|(k, v)| format!("{}={}\0", &k, &v)) + .collect(); + let mut envp: Vec<*const u8> = env_strs.iter().map(|x| x.as_bytes().as_ptr()).collect(); + envp.push(null()); + unsafe { + #[cfg(emulation_mode = "usermode")] + qemu_user_init(argc, argv.as_ptr(), envp.as_ptr()); + #[cfg(emulation_mode = "systemmode")] + { + qemu_init(argc, argv.as_ptr(), envp.as_ptr()); + libc::atexit(qemu_cleanup_atexit); + libafl_qemu_sys::syx_snapshot_init(true); + } + } + + Ok(Qemu { _private: () }) + } + + /// Get a QEMU object. + /// Same as `Qemu::get`, but without checking whether QEMU has been correctly initialized. + /// + /// # Safety + /// + /// Should not be used if `Qemu::init` has never been used before (otherwise QEMU will not be initialized, and a crash will occur). + /// Prefer `Qemu::get` for a safe version of this method. + #[must_use] + pub unsafe fn get_unchecked() -> Self { + Qemu { _private: () } + } + + #[must_use] + pub fn get() -> Option { + unsafe { + if QEMU_IS_INITIALIZED { + Some(Qemu { _private: () }) + } else { + None + } + } + } + + fn post_run(&self) -> Result { + let exit_reason = unsafe { libafl_get_exit_reason() }; + if exit_reason.is_null() { + Err(QemuExitError::UnexpectedExit) + } else { + let exit_reason: &mut libafl_qemu_sys::libafl_exit_reason = + unsafe { transmute(&mut *exit_reason) }; + Ok(match exit_reason.kind { + libafl_qemu_sys::libafl_exit_reason_kind_INTERNAL => unsafe { + let qemu_shutdown_cause: QemuShutdownCause = + match exit_reason.data.internal.cause { + libafl_qemu_sys::ShutdownCause_SHUTDOWN_CAUSE_NONE => { + QemuShutdownCause::None + } + libafl_qemu_sys::ShutdownCause_SHUTDOWN_CAUSE_HOST_ERROR => { + QemuShutdownCause::HostError + } + libafl_qemu_sys::ShutdownCause_SHUTDOWN_CAUSE_HOST_QMP_QUIT => { + QemuShutdownCause::HostQmpQuit + } + libafl_qemu_sys::ShutdownCause_SHUTDOWN_CAUSE_HOST_QMP_SYSTEM_RESET => { + QemuShutdownCause::HostQmpSystemReset + } + libafl_qemu_sys::ShutdownCause_SHUTDOWN_CAUSE_HOST_SIGNAL => { + QemuShutdownCause::HostSignal( + Signal::try_from(exit_reason.data.internal.signal).unwrap(), + ) + } + libafl_qemu_sys::ShutdownCause_SHUTDOWN_CAUSE_HOST_UI => { + QemuShutdownCause::HostUi + } + libafl_qemu_sys::ShutdownCause_SHUTDOWN_CAUSE_GUEST_SHUTDOWN => { + QemuShutdownCause::GuestShutdown + } + libafl_qemu_sys::ShutdownCause_SHUTDOWN_CAUSE_GUEST_RESET => { + QemuShutdownCause::GuestReset + } + libafl_qemu_sys::ShutdownCause_SHUTDOWN_CAUSE_GUEST_PANIC => { + QemuShutdownCause::GuestPanic + } + libafl_qemu_sys::ShutdownCause_SHUTDOWN_CAUSE_SUBSYSTEM_RESET => { + QemuShutdownCause::SubsystemReset + } + libafl_qemu_sys::ShutdownCause_SHUTDOWN_CAUSE_SNAPSHOT_LOAD => { + QemuShutdownCause::SnapshotLoad + } + + _ => panic!("shutdown cause not handled."), + }; + + QemuExitReason::End(qemu_shutdown_cause) + }, + libafl_qemu_sys::libafl_exit_reason_kind_BREAKPOINT => unsafe { + let bp_addr = exit_reason.data.breakpoint.addr; + QemuExitReason::Breakpoint(bp_addr) + }, + libafl_qemu_sys::libafl_exit_reason_kind_SYNC_EXIT => QemuExitReason::SyncExit, + _ => return Err(QemuExitError::UnknownKind), + }) + } + } + + #[must_use] + #[allow(clippy::cast_possible_wrap)] + #[allow(clippy::cast_sign_loss)] + pub fn num_cpus(&self) -> usize { + unsafe { libafl_qemu_num_cpus() as usize } + } + + #[must_use] + pub fn current_cpu(&self) -> Option { + let ptr = unsafe { libafl_qemu_current_cpu() }; + if ptr.is_null() { + None + } else { + Some(CPU { ptr }) + } + } + + #[must_use] + #[allow(clippy::cast_possible_wrap)] + pub fn cpu_from_index(&self, index: usize) -> CPU { + unsafe { + CPU { + ptr: libafl_qemu_get_cpu(index as i32), + } + } + } + + #[must_use] + pub fn page_from_addr(&self, addr: GuestAddr) -> GuestAddr { + unsafe { libafl_page_from_addr(addr) } + } + + //#[must_use] + /*pub fn page_size() -> GuestUsize { + unsafe { libafl_page_size } + }*/ + + pub unsafe fn write_mem(&self, addr: GuestAddr, buf: &[u8]) { + self.current_cpu() + .unwrap_or_else(|| self.cpu_from_index(0)) + .write_mem(addr, buf); + } + + pub unsafe fn read_mem(&self, addr: GuestAddr, buf: &mut [u8]) { + self.current_cpu() + .unwrap_or_else(|| self.cpu_from_index(0)) + .read_mem(addr, buf); + } + + #[must_use] + pub fn num_regs(&self) -> i32 { + self.current_cpu().unwrap().num_regs() + } + + pub fn write_reg(&self, reg: R, val: T) -> Result<(), String> + where + T: Num + PartialOrd + Copy + Into, + R: Into, + { + self.current_cpu().unwrap().write_reg(reg, val) + } + + pub fn read_reg(&self, reg: R) -> Result + where + T: Num + PartialOrd + Copy + From, + R: Into, + { + self.current_cpu().unwrap().read_reg(reg) + } + + pub fn set_breakpoint(&self, addr: GuestAddr) { + unsafe { + libafl_qemu_set_breakpoint(addr.into()); + } + } + + pub fn remove_breakpoint(&self, addr: GuestAddr) { + unsafe { + libafl_qemu_remove_breakpoint(addr.into()); + } + } + + pub fn entry_break(&self, addr: GuestAddr) { + self.set_breakpoint(addr); + unsafe { + match self.run() { + Ok(QemuExitReason::Breakpoint(_)) => {} + _ => panic!("Unexpected QEMU exit."), + } + } + self.remove_breakpoint(addr); + } + + pub fn flush_jit(&self) { + unsafe { + libafl_flush_jit(); + } + } + + // TODO set T lifetime to be like Emulator + #[allow(clippy::missing_transmute_annotations)] + pub fn set_hook>( + &self, + data: T, + addr: GuestAddr, + callback: extern "C" fn(T, GuestAddr), + invalidate_block: bool, + ) -> InstructionHookId { + unsafe { + let data: u64 = data.into().0; + let callback: extern "C" fn(u64, GuestAddr) = transmute(callback); + let num = libafl_qemu_sys::libafl_qemu_set_hook( + addr.into(), + Some(callback), + data, + i32::from(invalidate_block), + ); + InstructionHookId(num) + } + } + + #[must_use] + pub fn remove_hook(&self, id: impl HookId, invalidate_block: bool) -> bool { + id.remove(invalidate_block) + } + + #[must_use] + pub fn remove_hooks_at(&self, addr: GuestAddr, invalidate_block: bool) -> usize { + unsafe { + libafl_qemu_sys::libafl_qemu_remove_hooks_at(addr.into(), i32::from(invalidate_block)) + } + } + + #[allow(clippy::missing_transmute_annotations)] + pub fn add_edge_hooks>( + &self, + data: T, + gen: Option u64>, + exec: Option, + ) -> EdgeHookId { + unsafe { + let data: u64 = data.into().0; + let gen: Option u64> = transmute(gen); + let exec: Option = transmute(exec); + let num = libafl_qemu_sys::libafl_add_edge_hook(gen, exec, data); + EdgeHookId(num) + } + } + + #[allow(clippy::missing_transmute_annotations)] + pub fn add_block_hooks>( + &self, + data: T, + gen: Option u64>, + post_gen: Option, + exec: Option, + ) -> BlockHookId { + unsafe { + let data: u64 = data.into().0; + let gen: Option u64> = transmute(gen); + let post_gen: Option = transmute(post_gen); + let exec: Option = transmute(exec); + let num = libafl_qemu_sys::libafl_add_block_hook(gen, post_gen, exec, data); + BlockHookId(num) + } + } + + /// `data` can be used to pass data that can be accessed as the first argument in the `gen` and the `exec` functions + /// + /// `gen` gets passed the current programm counter, mutable access to a `TCGTemp` and information about the memory + /// access being performed. + /// The `u64` return value is an id that gets passed to the `exec` functions as their second argument. + /// + /// `exec` hooks get invoked on every read performed by the guest + /// + /// `exec1`-`exec8` special case accesses of width 1-8 + /// + /// If there is no specialized hook for a given read width, the `exec_n` will be + /// called and its last argument will specify the access width + #[allow(clippy::missing_transmute_annotations)] + pub fn add_read_hooks>( + &self, + data: T, + gen: Option u64>, + exec1: Option, + exec2: Option, + exec4: Option, + exec8: Option, + exec_n: Option, + ) -> ReadHookId { + unsafe { + let data: u64 = data.into().0; + let gen: Option< + unsafe extern "C" fn( + u64, + GuestAddr, + *mut TCGTemp, + libafl_qemu_sys::MemOpIdx, + ) -> u64, + > = transmute(gen); + let exec1: Option = transmute(exec1); + let exec2: Option = transmute(exec2); + let exec4: Option = transmute(exec4); + let exec8: Option = transmute(exec8); + let exec_n: Option = transmute(exec_n); + let num = libafl_qemu_sys::libafl_add_read_hook( + gen, exec1, exec2, exec4, exec8, exec_n, data, + ); + ReadHookId(num) + } + } + + // TODO add MemOp info + #[allow(clippy::missing_transmute_annotations)] + pub fn add_write_hooks>( + &self, + data: T, + gen: Option u64>, + exec1: Option, + exec2: Option, + exec4: Option, + exec8: Option, + exec_n: Option, + ) -> WriteHookId { + unsafe { + let data: u64 = data.into().0; + let gen: Option< + unsafe extern "C" fn( + u64, + GuestAddr, + *mut TCGTemp, + libafl_qemu_sys::MemOpIdx, + ) -> u64, + > = transmute(gen); + let exec1: Option = transmute(exec1); + let exec2: Option = transmute(exec2); + let exec4: Option = transmute(exec4); + let exec8: Option = transmute(exec8); + let exec_n: Option = transmute(exec_n); + let num = libafl_qemu_sys::libafl_add_write_hook( + gen, exec1, exec2, exec4, exec8, exec_n, data, + ); + WriteHookId(num) + } + } + + #[allow(clippy::missing_transmute_annotations)] + pub fn add_cmp_hooks>( + &self, + data: T, + gen: Option u64>, + exec1: Option, + exec2: Option, + exec4: Option, + exec8: Option, + ) -> CmpHookId { + unsafe { + let data: u64 = data.into().0; + let gen: Option u64> = transmute(gen); + let exec1: Option = transmute(exec1); + let exec2: Option = transmute(exec2); + let exec4: Option = transmute(exec4); + let exec8: Option = transmute(exec8); + let num = libafl_qemu_sys::libafl_add_cmp_hook(gen, exec1, exec2, exec4, exec8, data); + CmpHookId(num) + } + } + + #[allow(clippy::missing_transmute_annotations)] + pub fn add_backdoor_hook>( + &self, + data: T, + callback: extern "C" fn(T, CPUArchStatePtr, GuestAddr), + ) -> BackdoorHookId { + unsafe { + let data: u64 = data.into().0; + let callback: extern "C" fn(u64, CPUArchStatePtr, GuestAddr) = transmute(callback); + let num = libafl_qemu_sys::libafl_add_backdoor_hook(Some(callback), data); + BackdoorHookId(num) + } + } + + #[allow(clippy::type_complexity)] + pub fn add_gdb_cmd(&self, callback: Box bool>) { + unsafe { + let fat: Box = Box::new(transmute::< + Box FnMut(&'a Qemu, &'b str) -> bool>, + FatPtr, + >(callback)); + libafl_qemu_add_gdb_cmd(gdb_cmd, ptr::from_ref(&*fat) as *const ()); + GDB_COMMANDS.push(fat); + } + } + + pub fn gdb_reply(&self, output: &str) { + unsafe { libafl_qemu_gdb_reply(output.as_bytes().as_ptr(), output.len()) }; + } + + #[must_use] + pub fn host_page_size(&self) -> usize { + unsafe { libafl_qemu_sys::libafl_qemu_host_page_size() } + } +} + +impl ArchExtras for Qemu { + fn read_return_address(&self) -> Result + where + T: From, + { + self.current_cpu() + .ok_or("Failed to get current CPU")? + .read_return_address::() + } + + fn write_return_address(&self, val: T) -> Result<(), String> + where + T: Into, + { + self.current_cpu() + .ok_or("Failed to get current CPU")? + .write_return_address::(val) + } + + fn read_function_argument(&self, conv: CallingConvention, idx: u8) -> Result + where + T: From, + { + self.current_cpu() + .ok_or("Failed to get current CPU")? + .read_function_argument::(conv, idx) + } + + fn write_function_argument( + &self, + conv: CallingConvention, + idx: i32, + val: T, + ) -> Result<(), String> + where + T: Into, + { + self.current_cpu() + .ok_or("Failed to get current CPU")? + .write_function_argument::(conv, idx, val) + } +} + +// TODO: maybe include QEMU in the memory chunk to enable address translation and a more accurate implementation +impl PartialEq for GuestAddrKind { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (GuestAddrKind::Physical(paddr_self), GuestAddrKind::Physical(paddr_other)) => { + paddr_self == paddr_other + } + (GuestAddrKind::Virtual(vaddr_self), GuestAddrKind::Virtual(vaddr_other)) => { + vaddr_self == vaddr_other + } + _ => false, + } + } +} + +// TODO: Check PartialEq comment, same idea +impl PartialOrd for GuestAddrKind { + fn partial_cmp(&self, other: &Self) -> Option { + match (self, other) { + (GuestAddrKind::Physical(paddr_self), GuestAddrKind::Physical(paddr_other)) => { + paddr_self.partial_cmp(paddr_other) + } + (GuestAddrKind::Virtual(vaddr_self), GuestAddrKind::Virtual(vaddr_other)) => { + vaddr_self.partial_cmp(vaddr_other) + } + _ => None, + } + } +} + +impl EmulatorMemoryChunk { + #[must_use] + pub fn addr(&self) -> GuestAddrKind { + self.addr + } + + #[must_use] + pub fn size(&self) -> GuestReg { + self.size + } + + #[must_use] + pub fn phys(addr: GuestPhysAddr, size: GuestReg, cpu: Option) -> Self { + Self { + addr: GuestAddrKind::Physical(addr), + size, + cpu, + } + } + + #[must_use] + pub fn virt(addr: GuestVirtAddr, size: GuestReg, cpu: CPU) -> Self { + Self { + addr: GuestAddrKind::Virtual(addr), + size, + cpu: Some(cpu), + } + } + + #[must_use] + pub fn get_slice(&self, range: &Range) -> Option { + let new_addr = self.addr + range.start; + let slice_size = range.clone().count(); + + if new_addr + (slice_size as GuestUsize) >= self.addr + self.size { + return None; + } + + Some(Self { + addr: new_addr, + size: slice_size as GuestReg, + cpu: self.cpu, + }) + } + + /// Returns the number of bytes effectively written. + #[must_use] + pub fn write(&self, qemu: &Qemu, input: &[u8]) -> GuestReg { + let max_len: usize = self.size.try_into().unwrap(); + + let input_sliced = if input.len() > max_len { + &input[0..max_len] + } else { + input + }; + + match self.addr { + GuestAddrKind::Physical(hwaddr) => unsafe { + #[cfg(emulation_mode = "usermode")] + { + // For now the default behaviour is to fall back to virtual addresses + qemu.write_mem(hwaddr.try_into().unwrap(), input_sliced); + } + #[cfg(emulation_mode = "systemmode")] + { + qemu.write_phys_mem(hwaddr, input_sliced); + } + }, + GuestAddrKind::Virtual(vaddr) => unsafe { + self.cpu + .as_ref() + .unwrap() + .write_mem(vaddr.try_into().unwrap(), input_sliced); + }, + }; + + input_sliced.len().try_into().unwrap() + } +} + +#[cfg(feature = "python")] +#[pymethods] +impl SyscallHookResult { + #[new] + #[must_use] + pub fn new(value: Option) -> Self { + value.map_or( + Self { + retval: 0, + skip_syscall: false, + }, + |v| Self { + retval: v, + skip_syscall: true, + }, + ) + } +} + +#[cfg(not(feature = "python"))] +impl SyscallHookResult { + #[must_use] + pub fn new(value: Option) -> Self { + value.map_or( + Self { + retval: 0, + skip_syscall: false, + }, + |v| Self { + retval: v, + skip_syscall: true, + }, + ) + } +} + +#[cfg(feature = "python")] +pub mod pybind { + use libafl_qemu_sys::MmapPerms; + use pyo3::{exceptions::PyValueError, prelude::*, types::PyInt}; + + use super::{GuestAddr, GuestUsize}; + + static mut PY_GENERIC_HOOKS: Vec<(GuestAddr, PyObject)> = vec![]; + + extern "C" fn py_generic_hook_wrapper(idx: u64, _pc: GuestAddr) { + let obj = unsafe { &PY_GENERIC_HOOKS[idx as usize].1 }; + Python::with_gil(|py| { + obj.call0(py).expect("Error in the hook"); + }); + } + + #[pyclass(unsendable)] + pub struct Qemu { + pub qemu: super::Qemu, + } + + #[pymethods] + impl Qemu { + #[allow(clippy::needless_pass_by_value)] + #[new] + fn new(args: Vec, env: Vec<(String, String)>) -> PyResult { + let qemu = super::Qemu::init(&args, &env) + .map_err(|e| PyValueError::new_err(format!("{e}")))?; + + Ok(Qemu { qemu }) + } + + fn run(&self) { + unsafe { + self.qemu.run().unwrap(); + } + } + + fn write_mem(&self, addr: GuestAddr, buf: &[u8]) { + unsafe { + self.qemu.write_mem(addr, buf); + } + } + + fn read_mem(&self, addr: GuestAddr, size: usize) -> Vec { + let mut buf = vec![0; size]; + unsafe { + self.qemu.read_mem(addr, &mut buf); + } + buf + } + + fn num_regs(&self) -> i32 { + self.qemu.num_regs() + } + + fn write_reg(&self, reg: i32, val: GuestUsize) -> PyResult<()> { + self.qemu.write_reg(reg, val).map_err(PyValueError::new_err) + } + + fn read_reg(&self, reg: i32) -> PyResult { + self.qemu.read_reg(reg).map_err(PyValueError::new_err) + } + + fn set_breakpoint(&self, addr: GuestAddr) { + self.qemu.set_breakpoint(addr); + } + + fn entry_break(&self, addr: GuestAddr) { + self.qemu.entry_break(addr); + } + + fn remove_breakpoint(&self, addr: GuestAddr) { + self.qemu.remove_breakpoint(addr); + } + + fn flush_jit(&self) { + self.qemu.flush_jit(); + } + + fn set_hook(&self, addr: GuestAddr, hook: PyObject) { + unsafe { + let idx = PY_GENERIC_HOOKS.len(); + PY_GENERIC_HOOKS.push((addr, hook)); + self.qemu + .set_hook(idx as u64, addr, py_generic_hook_wrapper, true); + } + } + + fn remove_hooks_at(&self, addr: GuestAddr) -> usize { + unsafe { + PY_GENERIC_HOOKS.retain(|(a, _)| *a != addr); + } + self.qemu.remove_hooks_at(addr, true) + } + } +} diff --git a/libafl_qemu/src/qemu/systemmode.rs b/libafl_qemu/src/qemu/systemmode.rs new file mode 100644 index 0000000000..10d5695214 --- /dev/null +++ b/libafl_qemu/src/qemu/systemmode.rs @@ -0,0 +1,440 @@ +use std::{ + ffi::{c_void, CStr, CString}, + marker::PhantomData, + mem::MaybeUninit, + ptr::null_mut, + slice, +}; + +use bytes_utils::SegmentedBuf; +use libafl_qemu_sys::{ + libafl_load_qemu_snapshot, libafl_page_from_addr, libafl_qemu_current_paging_id, + libafl_save_qemu_snapshot, qemu_cleanup, qemu_main_loop, vm_start, GuestAddr, GuestPhysAddr, + GuestUsize, GuestVirtAddr, +}; +use num_traits::Zero; + +use crate::{ + EmulatorMemoryChunk, FastSnapshotPtr, GuestAddrKind, MemAccessInfo, Qemu, QemuExitError, + QemuExitReason, CPU, +}; + +pub(super) extern "C" fn qemu_cleanup_atexit() { + unsafe { + qemu_cleanup(); + } +} + +pub enum DeviceSnapshotFilter { + All, + AllowList(Vec), + DenyList(Vec), +} + +#[derive(Debug, Clone)] +#[allow(dead_code)] +pub struct PhysMemoryChunk { + addr: GuestPhysAddr, + size: usize, + qemu: Qemu, + cpu: CPU, +} + +pub struct PhysMemoryIter { + addr: GuestAddrKind, // This address is correct when the iterator enters next, except if the remaining len is 0 + remaining_len: usize, + qemu: Qemu, + cpu: CPU, +} + +#[allow(dead_code)] +pub struct HostMemoryIter<'a> { + addr: GuestPhysAddr, // This address is correct when the iterator enters next, except if the remaining len is 0 + remaining_len: usize, + qemu: Qemu, + cpu: CPU, + phantom: PhantomData<&'a ()>, +} + +impl DeviceSnapshotFilter { + fn enum_id(&self) -> libafl_qemu_sys::DeviceSnapshotKind { + match self { + DeviceSnapshotFilter::All => libafl_qemu_sys::DeviceSnapshotKind_DEVICE_SNAPSHOT_ALL, + DeviceSnapshotFilter::AllowList(_) => { + libafl_qemu_sys::DeviceSnapshotKind_DEVICE_SNAPSHOT_ALLOWLIST + } + DeviceSnapshotFilter::DenyList(_) => { + libafl_qemu_sys::DeviceSnapshotKind_DEVICE_SNAPSHOT_DENYLIST + } + } + } + + fn devices(&self, v: &mut Vec<*mut i8>) -> *mut *mut i8 { + v.clear(); + match self { + DeviceSnapshotFilter::All => null_mut(), + DeviceSnapshotFilter::AllowList(l) | DeviceSnapshotFilter::DenyList(l) => { + for name in l { + v.push(name.as_bytes().as_ptr() as *mut i8); + } + v.push(core::ptr::null_mut()); + v.as_mut_ptr() + } + } + } +} + +impl CPU { + #[must_use] + pub fn get_phys_addr(&self, vaddr: GuestVirtAddr) -> Option { + unsafe { + let page = libafl_page_from_addr(vaddr as GuestUsize) as GuestVirtAddr; + let mut attrs = MaybeUninit::::uninit(); + let paddr = libafl_qemu_sys::cpu_get_phys_page_attrs_debug( + self.ptr, + page as GuestVirtAddr, + attrs.as_mut_ptr(), + ); + if paddr == (-1i64 as GuestPhysAddr) { + None + } else { + Some(paddr) + } + } + } + + #[must_use] + pub fn get_phys_addr_tlb( + &self, + vaddr: GuestAddr, + info: MemAccessInfo, + is_store: bool, + ) -> Option { + unsafe { + let pminfo = libafl_qemu_sys::make_plugin_meminfo( + info.oi, + if is_store { + libafl_qemu_sys::qemu_plugin_mem_rw_QEMU_PLUGIN_MEM_W + } else { + libafl_qemu_sys::qemu_plugin_mem_rw_QEMU_PLUGIN_MEM_R + }, + ); + let phwaddr = libafl_qemu_sys::qemu_plugin_get_hwaddr(pminfo, vaddr as GuestVirtAddr); + if phwaddr.is_null() { + None + } else { + Some(libafl_qemu_sys::qemu_plugin_hwaddr_phys_addr(phwaddr) as GuestPhysAddr) + } + } + } + + #[must_use] + pub fn current_paging_id(&self) -> Option { + let paging_id = unsafe { libafl_qemu_current_paging_id(self.ptr) }; + + if paging_id == 0 { + None + } else { + Some(paging_id) + } + } + + /// Write a value to a guest address. + /// + /// # Safety + /// This will write to a translated guest address (using `g2h`). + /// It just adds `guest_base` and writes to that location, without checking the bounds. + /// This may only be safely used for valid guest addresses! + pub unsafe fn write_mem(&self, addr: GuestAddr, buf: &[u8]) { + // TODO use gdbstub's target_cpu_memory_rw_debug + libafl_qemu_sys::cpu_memory_rw_debug( + self.ptr, + addr as GuestVirtAddr, + buf.as_ptr() as *mut _, + buf.len(), + true, + ); + } + + /// Read a value from a guest address. + /// + /// # Safety + /// This will read from a translated guest address (using `g2h`). + /// It just adds `guest_base` and writes to that location, without checking the bounds. + /// This may only be safely used for valid guest addresses! + pub unsafe fn read_mem(&self, addr: GuestAddr, buf: &mut [u8]) { + // TODO use gdbstub's target_cpu_memory_rw_debug + libafl_qemu_sys::cpu_memory_rw_debug( + self.ptr, + addr as GuestVirtAddr, + buf.as_mut_ptr() as *mut _, + buf.len(), + false, + ); + } +} + +#[allow(clippy::unused_self)] +impl Qemu { + pub fn guest_page_size(&self) -> usize { + 4096 + } + + /// Write a value to a physical guest address, including ROM areas. + pub unsafe fn write_phys_mem(&self, paddr: GuestPhysAddr, buf: &[u8]) { + libafl_qemu_sys::cpu_physical_memory_rw( + paddr, + buf.as_ptr() as *mut _, + buf.len() as u64, + true, + ); + } + + /// Read a value from a physical guest address. + pub unsafe fn read_phys_mem(&self, paddr: GuestPhysAddr, buf: &mut [u8]) { + libafl_qemu_sys::cpu_physical_memory_rw( + paddr, + buf.as_mut_ptr() as *mut _, + buf.len() as u64, + false, + ); + } + + /// This function will run the emulator until the next breakpoint / sync exit, or until finish. + /// It is a low-level function and simply kicks QEMU. + /// # Safety + /// + /// Should, in general, be safe to call. + /// Of course, the emulated target is not contained securely and can corrupt state or interact with the operating system. + pub unsafe fn run(&self) -> Result { + vm_start(); + qemu_main_loop(); + + self.post_run() + } + + pub fn save_snapshot(&self, name: &str, sync: bool) { + let s = CString::new(name).expect("Invalid snapshot name"); + unsafe { libafl_save_qemu_snapshot(s.as_ptr() as *const _, sync) }; + } + + pub fn load_snapshot(&self, name: &str, sync: bool) { + let s = CString::new(name).expect("Invalid snapshot name"); + unsafe { libafl_load_qemu_snapshot(s.as_ptr() as *const _, sync) }; + } + + #[must_use] + pub fn create_fast_snapshot(&self, track: bool) -> FastSnapshotPtr { + unsafe { + libafl_qemu_sys::syx_snapshot_new( + track, + true, + libafl_qemu_sys::DeviceSnapshotKind_DEVICE_SNAPSHOT_ALL, + null_mut(), + ) + } + } + + #[must_use] + pub fn create_fast_snapshot_filter( + &self, + track: bool, + device_filter: &DeviceSnapshotFilter, + ) -> FastSnapshotPtr { + let mut v = vec![]; + unsafe { + libafl_qemu_sys::syx_snapshot_new( + track, + true, + device_filter.enum_id(), + device_filter.devices(&mut v), + ) + } + } + + pub unsafe fn restore_fast_snapshot(&self, snapshot: FastSnapshotPtr) { + libafl_qemu_sys::syx_snapshot_root_restore(snapshot) + } + + pub unsafe fn check_fast_snapshot_memory_consistency(&self, snapshot: FastSnapshotPtr) -> u64 { + libafl_qemu_sys::syx_snapshot_check_memory_consistency(snapshot) + } + + pub fn list_devices(&self) -> Vec { + let mut r = vec![]; + unsafe { + let devices = libafl_qemu_sys::device_list_all(); + if devices.is_null() { + return r; + } + + let mut ptr = devices; + while !(*ptr).is_null() { + let c_str: &CStr = CStr::from_ptr(*ptr); + let name = c_str.to_str().unwrap().to_string(); + r.push(name); + + ptr = ptr.add(1); + } + + libc::free(devices as *mut c_void); + r + } + } + + #[must_use] + pub fn target_page_size(&self) -> usize { + unsafe { libafl_qemu_sys::qemu_target_page_size() } + } +} + +impl EmulatorMemoryChunk { + pub fn phys_iter(&self, qemu: Qemu) -> PhysMemoryIter { + PhysMemoryIter { + addr: self.addr, + remaining_len: self.size as usize, + qemu, + cpu: if let Some(cpu) = self.cpu { + cpu + } else { + qemu.current_cpu().unwrap() + }, + } + } + + pub fn host_iter(&self, qemu: Qemu) -> Box> { + Box::new( + self.phys_iter(qemu) + .map(move |phys_mem_chunk| HostMemoryIter { + addr: phys_mem_chunk.addr, + remaining_len: phys_mem_chunk.size, + qemu, + cpu: phys_mem_chunk.cpu, + phantom: PhantomData, + }) + .flatten() + .into_iter(), + ) + } + + pub fn to_host_segmented_buf(&self, qemu: Qemu) -> SegmentedBuf<&[u8]> { + self.host_iter(qemu).collect() + } +} + +impl PhysMemoryChunk { + pub fn new(addr: GuestPhysAddr, size: usize, qemu: Qemu, cpu: CPU) -> Self { + Self { + addr, + size, + qemu, + cpu, + } + } +} + +impl<'a> Iterator for HostMemoryIter<'a> { + type Item = &'a [u8]; + + fn next(&mut self) -> Option { + if self.remaining_len.is_zero() { + None + } else { + // Host memory allocation is always host-page aligned, so we can freely go from host page to host page. + let start_host_addr: *const u8 = + unsafe { libafl_qemu_sys::libafl_paddr2host(self.cpu.ptr, self.addr, false) }; + let host_page_size = Qemu::get().unwrap().host_page_size(); + let mut size_taken: usize = std::cmp::min( + (start_host_addr as usize).next_multiple_of(host_page_size), + self.remaining_len, + ); + + self.remaining_len -= size_taken; + self.addr += size_taken as GuestPhysAddr; + + // Now self.addr is host-page aligned + while self.remaining_len > 0 { + let next_page_host_addr: *const u8 = + unsafe { libafl_qemu_sys::libafl_paddr2host(self.cpu.ptr, self.addr, false) }; + + // Non-contiguous, we stop here for the slice + if next_page_host_addr != start_host_addr { + unsafe { return Some(slice::from_raw_parts(start_host_addr, size_taken)) } + } + + // The host memory is contiguous, we can widen the slice up to the next host page + size_taken += std::cmp::min(self.remaining_len, host_page_size); + + self.remaining_len -= size_taken; + self.addr += size_taken as GuestPhysAddr; + } + + // We finished to explore the memory, return the last slice. + assert_eq!(self.remaining_len, 0); + + unsafe { return Some(slice::from_raw_parts(start_host_addr, size_taken)) } + } + } +} + +impl Iterator for PhysMemoryIter { + type Item = PhysMemoryChunk; + + fn next(&mut self) -> Option { + if self.remaining_len.is_zero() { + None + } else { + // Physical memory allocation is always physical-page aligned, so we can freely go from host page to host page. + let vaddr = match &mut self.addr { + GuestAddrKind::Virtual(vaddr) => vaddr, + GuestAddrKind::Physical(paddr) => { + let sz = self.remaining_len; + self.remaining_len = 0; + return Some(PhysMemoryChunk::new(*paddr, sz, self.qemu, self.cpu)); + } + }; + let start_phys_addr: GuestPhysAddr = self.cpu.get_phys_addr(*vaddr)?; + let phys_page_size = self.qemu.guest_page_size(); + + // TODO: Turn this into a generic function + let mut size_taken: usize = std::cmp::min( + (start_phys_addr as usize).next_multiple_of(phys_page_size), + self.remaining_len, + ); + + self.remaining_len -= size_taken; + *vaddr += size_taken as GuestPhysAddr; + + // Now self.addr is host-page aligned + while self.remaining_len > 0 { + let next_page_phys_addr: GuestPhysAddr = self.cpu.get_phys_addr(*vaddr)?; + + // Non-contiguous, we stop here for the slice + if next_page_phys_addr != start_phys_addr { + return Some(PhysMemoryChunk::new( + start_phys_addr, + size_taken, + self.qemu, + self.cpu, + )); + } + + // The host memory is contiguous, we can widen the slice up to the next host page + size_taken += std::cmp::min(self.remaining_len, phys_page_size); + + self.remaining_len -= size_taken; + *vaddr += size_taken as GuestPhysAddr; + } + + // We finished to explore the memory, return the last slice. + assert_eq!(self.remaining_len, 0); + + Some(PhysMemoryChunk::new( + start_phys_addr, + size_taken, + self.qemu, + self.cpu, + )) + } + } +} diff --git a/libafl_qemu/src/qemu/usermode.rs b/libafl_qemu/src/qemu/usermode.rs new file mode 100644 index 0000000000..fe5fb1eca7 --- /dev/null +++ b/libafl_qemu/src/qemu/usermode.rs @@ -0,0 +1,444 @@ +use std::{ + intrinsics::copy_nonoverlapping, mem::MaybeUninit, slice::from_raw_parts, + str::from_utf8_unchecked, +}; + +use libafl_qemu_sys::{ + exec_path, free_self_maps, guest_base, libafl_dump_core_hook, libafl_force_dfl, libafl_get_brk, + libafl_load_addr, libafl_maps_first, libafl_maps_next, libafl_qemu_run, libafl_set_brk, + mmap_next_start, pageflags_get_root, read_self_maps, strlen, GuestAddr, GuestUsize, + IntervalTreeNode, IntervalTreeRoot, MapInfo, MmapPerms, VerifyAccess, +}; +use libc::c_int; +#[cfg(feature = "python")] +use pyo3::{pyclass, pymethods, IntoPy, PyObject, PyRef, PyRefMut, Python}; + +use crate::{ + HookData, NewThreadHookId, PostSyscallHookId, PreSyscallHookId, Qemu, QemuExitError, + QemuExitReason, SyscallHookResult, CPU, +}; + +#[cfg_attr(feature = "python", pyclass(unsendable))] +pub struct GuestMaps { + self_maps_root: *mut IntervalTreeRoot, + pageflags_node: *mut IntervalTreeNode, +} + +// Consider a private new only for Emulator +impl GuestMaps { + #[must_use] + pub(crate) fn new() -> Self { + unsafe { + let pageflags_root = pageflags_get_root(); + let self_maps_root = read_self_maps(); + let pageflags_first = libafl_maps_first(pageflags_root); + Self { + self_maps_root, + pageflags_node: pageflags_first, + } + } + } +} + +impl Iterator for GuestMaps { + type Item = MapInfo; + + #[allow(clippy::uninit_assumed_init)] + fn next(&mut self) -> Option { + unsafe { + let mut ret = MaybeUninit::uninit(); + + self.pageflags_node = + libafl_maps_next(self.pageflags_node, self.self_maps_root, ret.as_mut_ptr()); + + let ret = ret.assume_init(); + + if ret.is_valid { + Some(ret.into()) + } else { + None + } + } + } +} + +#[cfg(feature = "python")] +#[pymethods] +impl GuestMaps { + fn __iter__(slf: PyRef) -> PyRef { + slf + } + fn __next__(mut slf: PyRefMut) -> Option { + Python::with_gil(|py| slf.next().map(|x| x.into_py(py))) + } +} + +impl Drop for GuestMaps { + fn drop(&mut self) { + unsafe { + free_self_maps(self.self_maps_root); + } + } +} + +impl CPU { + /// Write a value to a guest address. + /// + /// # Safety + /// This will write to a translated guest address (using `g2h`). + /// It just adds `guest_base` and writes to that location, without checking the bounds. + /// This may only be safely used for valid guest addresses! + pub unsafe fn write_mem(&self, addr: GuestAddr, buf: &[u8]) { + let host_addr = Qemu::get().unwrap().g2h(addr); + copy_nonoverlapping(buf.as_ptr(), host_addr, buf.len()); + } + + /// Read a value from a guest address. + /// + /// # Safety + /// This will read from a translated guest address (using `g2h`). + /// It just adds `guest_base` and writes to that location, without checking the bounds. + /// This may only be safely used for valid guest addresses! + pub unsafe fn read_mem(&self, addr: GuestAddr, buf: &mut [u8]) { + let host_addr = Qemu::get().unwrap().g2h(addr); + copy_nonoverlapping(host_addr, buf.as_mut_ptr(), buf.len()); + } +} + +#[allow(clippy::unused_self)] +impl Qemu { + #[must_use] + pub fn mappings(&self) -> GuestMaps { + GuestMaps::new() + } + + #[must_use] + pub fn g2h(&self, addr: GuestAddr) -> *mut T { + unsafe { (addr as usize + guest_base) as *mut T } + } + + #[must_use] + pub fn h2g(&self, addr: *const T) -> GuestAddr { + unsafe { (addr as usize - guest_base) as GuestAddr } + } + + #[must_use] + pub fn access_ok(&self, kind: VerifyAccess, addr: GuestAddr, size: usize) -> bool { + self.current_cpu() + .unwrap_or_else(|| self.cpu_from_index(0)) + .access_ok(kind, addr, size) + } + + pub fn force_dfl(&self) { + unsafe { + libafl_force_dfl = 1; + } + } + + /// This function will run the emulator until the next breakpoint, or until finish. + /// # Safety + /// + /// Should, in general, be safe to call. + /// Of course, the emulated target is not contained securely and can corrupt state or interact with the operating system. + pub unsafe fn run(&self) -> Result { + libafl_qemu_run(); + + self.post_run() + } + + #[must_use] + pub fn binary_path<'a>(&self) -> &'a str { + unsafe { from_utf8_unchecked(from_raw_parts(exec_path, strlen(exec_path))) } + } + + #[must_use] + pub fn load_addr(&self) -> GuestAddr { + unsafe { libafl_load_addr() as GuestAddr } + } + + #[must_use] + pub fn get_brk(&self) -> GuestAddr { + unsafe { libafl_get_brk() as GuestAddr } + } + + pub fn set_brk(&self, brk: GuestAddr) { + unsafe { libafl_set_brk(brk.into()) }; + } + + #[must_use] + pub fn get_mmap_start(&self) -> GuestAddr { + unsafe { mmap_next_start } + } + + pub fn set_mmap_start(&self, start: GuestAddr) { + unsafe { mmap_next_start = start }; + } + + #[allow(clippy::cast_sign_loss)] + fn mmap( + self, + addr: GuestAddr, + size: usize, + perms: MmapPerms, + flags: c_int, + ) -> Result { + let res = unsafe { + libafl_qemu_sys::target_mmap(addr, size as GuestUsize, perms.into(), flags, -1, 0) + }; + if res <= 0 { + Err(()) + } else { + Ok(res as GuestAddr) + } + } + + pub fn map_private( + &self, + addr: GuestAddr, + size: usize, + perms: MmapPerms, + ) -> Result { + self.mmap(addr, size, perms, libc::MAP_PRIVATE | libc::MAP_ANONYMOUS) + .map_err(|()| format!("Failed to map {addr}")) + .map(|addr| addr as GuestAddr) + } + + pub fn map_fixed( + &self, + addr: GuestAddr, + size: usize, + perms: MmapPerms, + ) -> Result { + self.mmap( + addr, + size, + perms, + libc::MAP_FIXED | libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, + ) + .map_err(|()| format!("Failed to map {addr}")) + .map(|addr| addr as GuestAddr) + } + + pub fn mprotect(&self, addr: GuestAddr, size: usize, perms: MmapPerms) -> Result<(), String> { + let res = unsafe { + libafl_qemu_sys::target_mprotect(addr.into(), size as GuestUsize, perms.into()) + }; + if res == 0 { + Ok(()) + } else { + Err(format!("Failed to mprotect {addr}")) + } + } + + pub fn unmap(&self, addr: GuestAddr, size: usize) -> Result<(), String> { + if unsafe { libafl_qemu_sys::target_munmap(addr.into(), size as GuestUsize) } == 0 { + Ok(()) + } else { + Err(format!("Failed to unmap {addr}")) + } + } + + #[allow(clippy::type_complexity)] + pub fn add_pre_syscall_hook>( + &self, + data: T, + callback: extern "C" fn( + T, + i32, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + ) -> SyscallHookResult, + ) -> PreSyscallHookId { + unsafe { + let data: u64 = data.into().0; + let callback: extern "C" fn( + u64, + i32, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + ) -> libafl_qemu_sys::syshook_ret = core::mem::transmute(callback); + let num = libafl_qemu_sys::libafl_add_pre_syscall_hook(Some(callback), data); + PreSyscallHookId(num) + } + } + + #[allow(clippy::type_complexity)] + pub fn add_post_syscall_hook>( + &self, + data: T, + callback: extern "C" fn( + T, + GuestAddr, + i32, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + ) -> GuestAddr, + ) -> PostSyscallHookId { + unsafe { + let data: u64 = data.into().0; + let callback: extern "C" fn( + u64, + GuestAddr, + i32, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + GuestAddr, + ) -> GuestAddr = core::mem::transmute(callback); + let num = libafl_qemu_sys::libafl_add_post_syscall_hook(Some(callback), data); + PostSyscallHookId(num) + } + } + + pub fn add_new_thread_hook>( + &self, + data: T, + callback: extern "C" fn(T, tid: u32) -> bool, + ) -> NewThreadHookId { + unsafe { + let data: u64 = data.into().0; + let callback: extern "C" fn(u64, u32) -> bool = core::mem::transmute(callback); + let num = libafl_qemu_sys::libafl_add_new_thread_hook(Some(callback), data); + NewThreadHookId(num) + } + } + + #[allow(clippy::type_complexity)] + pub fn set_crash_hook(&self, callback: extern "C" fn(i32)) { + unsafe { + libafl_dump_core_hook = callback; + } + } +} + +#[cfg(feature = "python")] +pub mod pybind { + use libafl_qemu_sys::{GuestAddr, MmapPerms}; + use pyo3::{ + exceptions::PyValueError, pymethods, types::PyInt, FromPyObject, PyObject, PyResult, Python, + }; + + use crate::{pybind::Qemu, SyscallHookResult}; + + static mut PY_SYSCALL_HOOK: Option = None; + + extern "C" fn py_syscall_hook_wrapper( + _data: u64, + sys_num: i32, + a0: u64, + a1: u64, + a2: u64, + a3: u64, + a4: u64, + a5: u64, + a6: u64, + a7: u64, + ) -> SyscallHookResult { + unsafe { PY_SYSCALL_HOOK.as_ref() }.map_or_else( + || SyscallHookResult::new(None), + |obj| { + let args = (sys_num, a0, a1, a2, a3, a4, a5, a6, a7); + Python::with_gil(|py| { + let ret = obj.call1(py, args).expect("Error in the syscall hook"); + let any = ret.as_ref(py); + if any.is_none() { + SyscallHookResult::new(None) + } else { + let a: Result<&PyInt, _> = any.downcast(); + if let Ok(i) = a { + SyscallHookResult::new(Some( + i.extract().expect("Invalid syscall hook return value"), + )) + } else { + SyscallHookResult::extract(any) + .expect("The syscall hook must return a SyscallHookResult") + } + } + }) + }, + ) + } + + #[pymethods] + impl Qemu { + fn g2h(&self, addr: GuestAddr) -> u64 { + self.qemu.g2h::<*const u8>(addr) as u64 + } + + fn h2g(&self, addr: u64) -> GuestAddr { + self.qemu.h2g(addr as *const u8) + } + + fn binary_path(&self) -> String { + self.qemu.binary_path().to_owned() + } + + fn load_addr(&self) -> GuestAddr { + self.qemu.load_addr() + } + + fn map_private(&self, addr: GuestAddr, size: usize, perms: i32) -> PyResult { + if let Ok(p) = MmapPerms::try_from(perms) { + self.qemu + .map_private(addr, size, p) + .map_err(PyValueError::new_err) + } else { + Err(PyValueError::new_err("Invalid perms")) + } + } + + fn map_fixed(&self, addr: GuestAddr, size: usize, perms: i32) -> PyResult { + if let Ok(p) = MmapPerms::try_from(perms) { + self.qemu + .map_fixed(addr, size, p) + .map_err(PyValueError::new_err) + } else { + Err(PyValueError::new_err("Invalid perms")) + } + } + + fn mprotect(&self, addr: GuestAddr, size: usize, perms: i32) -> PyResult<()> { + if let Ok(p) = MmapPerms::try_from(perms) { + self.qemu + .mprotect(addr, size, p) + .map_err(PyValueError::new_err) + } else { + Err(PyValueError::new_err("Invalid perms")) + } + } + + fn unmap(&self, addr: GuestAddr, size: usize) -> PyResult<()> { + self.qemu.unmap(addr, size).map_err(PyValueError::new_err) + } + + fn set_syscall_hook(&self, hook: PyObject) { + unsafe { + PY_SYSCALL_HOOK = Some(hook); + } + self.qemu + .add_pre_syscall_hook(0u64, py_syscall_hook_wrapper); + } + } +} diff --git a/libafl_qemu/src/sync_exit.rs b/libafl_qemu/src/sync_exit.rs index 7697232dba..691282424e 100644 --- a/libafl_qemu/src/sync_exit.rs +++ b/libafl_qemu/src/sync_exit.rs @@ -1,40 +1,11 @@ -use std::{ - fmt::{Display, Formatter}, - sync::OnceLock, -}; +use std::fmt::{Display, Formatter}; -use enum_map::{enum_map, Enum, EnumMap}; -use libafl::{ - executors::ExitKind, - state::{HasExecutions, State}, -}; -use libafl_qemu_sys::{GuestAddr, GuestPhysAddr, GuestVirtAddr}; -use num_enum::TryFromPrimitiveError; +use enum_map::Enum; -use crate::{ - command::{ - Command, EmulatorMemoryChunk, EndCommand, FilterCommand, InputCommand, LoadCommand, - NativeBackdoorCommand, NativeExitKind, SaveCommand, StartCommand, VersionCommand, - }, - get_backdoor_arch_regs, EmuExitHandler, Emulator, GuestReg, QemuHelperTuple, - QemuInstrumentationAddressRangeFilter, Regs, CPU, -}; - -#[derive(Debug, Clone)] -pub enum SyncBackdoorError { - UnknownCommand(GuestReg), - RegError(String), - VersionDifference(u64), -} - -impl From for SyncBackdoorError { - fn from(error_string: String) -> Self { - SyncBackdoorError::RegError(error_string) - } -} +use crate::{command::Command, get_exit_arch_regs, GuestReg, Regs, CPU}; #[derive(Debug, Clone, Enum)] -pub enum BackdoorArgs { +pub enum ExitArgs { Ret, Cmd, Arg1, @@ -45,177 +16,35 @@ pub enum BackdoorArgs { Arg6, } -static EMU_EXIT_KIND_MAP: OnceLock>> = OnceLock::new(); - -impl From> for SyncBackdoorError { - fn from(error: TryFromPrimitiveError) -> Self { - SyncBackdoorError::UnknownCommand(error.number.try_into().unwrap()) - } -} - #[derive(Debug, Clone)] -pub struct SyncBackdoor { +pub struct SyncExit { command: Command, - arch_regs_map: &'static EnumMap, } -impl SyncBackdoor { +impl SyncExit { + #[must_use] + pub fn new(command: Command) -> Self { + Self { command } + } + #[must_use] pub fn command(&self) -> &Command { &self.command } - pub fn ret(&self, cpu: &CPU, value: GuestReg) -> Result<(), SyncBackdoorError> { - Ok(cpu.write_reg(self.arch_regs_map[BackdoorArgs::Ret], value)?) + pub fn ret(&self, cpu: &CPU, value: GuestReg) { + cpu.write_reg(get_exit_arch_regs()[ExitArgs::Ret], value) + .unwrap(); } #[must_use] pub fn ret_reg(&self) -> Regs { - self.arch_regs_map[BackdoorArgs::Ret] + get_exit_arch_regs()[ExitArgs::Ret] } } -impl Display for SyncBackdoor { +impl Display for SyncExit { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.command) } } - -impl TryFrom<&Emulator> for SyncBackdoor -where - E: EmuExitHandler, - QT: QemuHelperTuple, - S: State + HasExecutions, -{ - type Error = SyncBackdoorError; - - #[allow(clippy::too_many_lines)] - fn try_from(emu: &Emulator) -> Result { - let arch_regs_map: &'static EnumMap = get_backdoor_arch_regs(); - let cmd_id: GuestReg = emu - .qemu() - .read_reg::(arch_regs_map[BackdoorArgs::Cmd])?; - - Ok(match u64::from(cmd_id).try_into()? { - NativeBackdoorCommand::Save => SyncBackdoor { - command: Command::SaveCommand(SaveCommand), - arch_regs_map, - }, - NativeBackdoorCommand::Load => SyncBackdoor { - command: Command::LoadCommand(LoadCommand), - arch_regs_map, - }, - NativeBackdoorCommand::InputVirt => { - let virt_addr: GuestVirtAddr = - emu.qemu().read_reg(arch_regs_map[BackdoorArgs::Arg1])?; - let max_input_size: GuestReg = - emu.qemu().read_reg(arch_regs_map[BackdoorArgs::Arg2])?; - - SyncBackdoor { - command: Command::InputCommand(InputCommand::new( - EmulatorMemoryChunk::virt( - virt_addr, - max_input_size, - emu.qemu().current_cpu().unwrap().clone(), - ), - emu.qemu().current_cpu().unwrap(), - )), - arch_regs_map, - } - } - NativeBackdoorCommand::InputPhys => { - let phys_addr: GuestPhysAddr = - emu.qemu().read_reg(arch_regs_map[BackdoorArgs::Arg1])?; - let max_input_size: GuestReg = - emu.qemu().read_reg(arch_regs_map[BackdoorArgs::Arg2])?; - - SyncBackdoor { - command: Command::InputCommand(InputCommand::new( - EmulatorMemoryChunk::phys( - phys_addr, - max_input_size, - Some(emu.qemu().current_cpu().unwrap().clone()), - ), - emu.qemu().current_cpu().unwrap(), - )), - arch_regs_map, - } - } - NativeBackdoorCommand::End => { - let native_exit_kind: GuestReg = - emu.qemu().read_reg(arch_regs_map[BackdoorArgs::Arg1])?; - let native_exit_kind: Result = - u64::from(native_exit_kind).try_into(); - - let exit_kind = native_exit_kind.ok().and_then(|k| { - EMU_EXIT_KIND_MAP.get_or_init(|| { - enum_map! { - NativeExitKind::Unknown => None, - NativeExitKind::Ok => Some(ExitKind::Ok), - NativeExitKind::Crash => Some(ExitKind::Crash) - } - })[k] - }); - - SyncBackdoor { - command: Command::EndCommand(EndCommand::new(exit_kind)), - arch_regs_map, - } - } - NativeBackdoorCommand::StartPhys => { - let input_phys_addr: GuestPhysAddr = - emu.qemu().read_reg(arch_regs_map[BackdoorArgs::Arg1])?; - let max_input_size: GuestReg = - emu.qemu().read_reg(arch_regs_map[BackdoorArgs::Arg2])?; - - SyncBackdoor { - command: Command::StartCommand(StartCommand::new(EmulatorMemoryChunk::phys( - input_phys_addr, - max_input_size, - Some(emu.qemu().current_cpu().unwrap().clone()), - ))), - arch_regs_map, - } - } - NativeBackdoorCommand::StartVirt => { - let input_virt_addr: GuestVirtAddr = - emu.qemu().read_reg(arch_regs_map[BackdoorArgs::Arg1])?; - let max_input_size: GuestReg = - emu.qemu().read_reg(arch_regs_map[BackdoorArgs::Arg2])?; - - SyncBackdoor { - command: Command::StartCommand(StartCommand::new(EmulatorMemoryChunk::virt( - input_virt_addr, - max_input_size, - emu.qemu().current_cpu().unwrap().clone(), - ))), - arch_regs_map, - } - } - NativeBackdoorCommand::Version => { - let client_version = emu.qemu().read_reg(arch_regs_map[BackdoorArgs::Arg1])?; - - SyncBackdoor { - command: Command::VersionCommand(VersionCommand::new(client_version)), - arch_regs_map, - } - } - NativeBackdoorCommand::VaddrFilterAllowRange => { - let vaddr_start: GuestAddr = - emu.qemu().read_reg(arch_regs_map[BackdoorArgs::Arg1])?; - let vaddr_end: GuestAddr = - emu.qemu().read_reg(arch_regs_map[BackdoorArgs::Arg2])?; - - SyncBackdoor { - command: Command::AddressRangeFilterCommand(FilterCommand::new( - #[allow(clippy::single_range_in_vec_init)] - QemuInstrumentationAddressRangeFilter::AllowList(vec![ - vaddr_start..vaddr_end, - ]), - )), - arch_regs_map, - } - } - }) - } -} diff --git a/libafl_sugar/src/qemu.rs b/libafl_sugar/src/qemu.rs index a4ec440c37..236fa63fde 100644 --- a/libafl_sugar/src/qemu.rs +++ b/libafl_sugar/src/qemu.rs @@ -35,7 +35,7 @@ use libafl_bolts::{ tuples::{tuple_list, Handler, Merge}, AsSlice, }; -pub use libafl_qemu::emu::Qemu; +pub use libafl_qemu::qemu::Qemu; #[cfg(not(any(feature = "mips", feature = "hexagon")))] use libafl_qemu::QemuCmpLogHelper; use libafl_qemu::{edges, QemuEdgeCoverageHelper, QemuExecutor, QemuHooks}; @@ -454,7 +454,7 @@ pub mod pybind { use std::path::PathBuf; use libafl_bolts::core_affinity::Cores; - use libafl_qemu::emu::pybind::Qemu; + use libafl_qemu::qemu::pybind::Qemu; use pyo3::{prelude::*, types::PyBytes}; use crate::qemu;