diff --git a/fuzzers/forkserver_libafl_cc/src/main.rs b/fuzzers/forkserver_libafl_cc/src/main.rs index f4bba8d01e3..f4151d4f0c9 100644 --- a/fuzzers/forkserver_libafl_cc/src/main.rs +++ b/fuzzers/forkserver_libafl_cc/src/main.rs @@ -21,7 +21,7 @@ use libafl::{ use libafl_bolts::{ rands::StdRand, shmem::{ShMem, ShMemProvider, UnixShMemProvider}, - tuples::{tuple_list, Handler, MatchNameRef, Merge}, + tuples::{tuple_list, Handled, MatchNameRef, Merge}, AsSliceMut, Truncate, }; use libafl_targets::EDGES_MAP_SIZE_IN_USE; @@ -166,7 +166,7 @@ pub fn main() { // Create the executor for the forkserver let args = opt.arguments; - let observer_ref = edges_observer.handle(); + let observer_handle = edges_observer.handle(); let mut tokens = Tokens::new(); let mut executor = ForkserverExecutor::builder() @@ -182,7 +182,7 @@ pub fn main() { .unwrap(); if let Some(dynamic_map_size) = executor.coverage_map_size() { - executor.observers_mut()[&observer_ref] + executor.observers_mut()[&observer_handle] .as_mut() .truncate(dynamic_map_size); } diff --git a/fuzzers/forkserver_simple/src/main.rs b/fuzzers/forkserver_simple/src/main.rs index ac80e2da4fb..2c79253e2f2 100644 --- a/fuzzers/forkserver_simple/src/main.rs +++ b/fuzzers/forkserver_simple/src/main.rs @@ -22,7 +22,7 @@ use libafl_bolts::{ current_nanos, rands::StdRand, shmem::{ShMem, ShMemProvider, UnixShMemProvider}, - tuples::{tuple_list, Handler, Merge}, + tuples::{tuple_list, Handled, Merge}, AsSliceMut, Truncate, }; use nix::sys::signal::Signal; diff --git a/fuzzers/frida_gdiplus/Makefile.toml b/fuzzers/frida_gdiplus/Makefile.toml index 5d7f24b5558..29fbe48a114 100644 --- a/fuzzers/frida_gdiplus/Makefile.toml +++ b/fuzzers/frida_gdiplus/Makefile.toml @@ -39,7 +39,7 @@ windows_alias = "fuzzer_windows" [tasks.fuzzer_windows] script_runner="@shell" script=''' -cargo build --profile ${PROFILE} +cargo +nightly build --profile ${PROFILE} cp ./target/${PROFILE_DIR}/${FUZZER_NAME} . ''' diff --git a/fuzzers/frida_gdiplus/src/fuzzer.rs b/fuzzers/frida_gdiplus/src/fuzzer.rs index 32f177e9334..69a68be1d3f 100644 --- a/fuzzers/frida_gdiplus/src/fuzzer.rs +++ b/fuzzers/frida_gdiplus/src/fuzzer.rs @@ -41,7 +41,8 @@ use libafl_bolts::{ tuples::{tuple_list, Merge}, AsSlice, }; - +use libafl_frida::asan::asan_rt::AsanRuntime; +use libafl_frida::asan::errors::{AsanErrorsFeedback, AsanErrorsObserver}; use libafl_frida::{ asan::{ asan_rt::AsanRuntime, @@ -50,7 +51,8 @@ use libafl_frida::{ cmplog_rt::CmpLogRuntime, coverage_rt::{CoverageRuntime, MAP_SIZE}, executor::FridaInProcessExecutor, - helper::FridaInstrumentationHelper, hook_rt::HookRuntime, + helper::FridaInstrumentationHelper, + hook_rt::HookRuntime, }; use libafl_targets::cmplog::CmpLogObserver; @@ -101,9 +103,12 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> { let coverage = CoverageRuntime::new(); let asan = AsanRuntime::new(&options); let hooks = HookRuntime::new(); - - let mut frida_helper = - FridaInstrumentationHelper::new(&gum, options, tuple_list!(coverage, asan, hooks)); + + let mut frida_helper = FridaInstrumentationHelper::new( + &gum, + options, + tuple_list!(coverage, asan, hooks), + ); // // Create an observation channel using the coverage map let edges_observer = HitcountsMapObserver::new(StdMapObserver::from_mut_ptr( @@ -177,11 +182,9 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> { // A fuzzer with feedbacks and a corpus scheduler let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); - let observers = tuple_list!( - edges_observer, - time_observer, - asan_observer, - ); + + let observers = tuple_list!(edges_observer, time_observer, asan_observer); + // Create the executor for an in-process function with just one observer for edge coverage let mut executor = FridaInProcessExecutor::new( @@ -221,8 +224,11 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> { let cmplog = CmpLogRuntime::new(); let hooks = HookRuntime::new(); - let mut frida_helper = - FridaInstrumentationHelper::new(&gum, options, tuple_list!(coverage, cmplog, hooks)); + let mut frida_helper = FridaInstrumentationHelper::new( + &gum, + options, + tuple_list!(coverage, cmplog, hooks), + ); // Create an observation channel using the coverage map let edges_observer = HitcountsMapObserver::new(StdMapObserver::from_mut_ptr( @@ -294,11 +300,9 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> { // A fuzzer with feedbacks and a corpus scheduler let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); - let observers = tuple_list!( - edges_observer, - time_observer, - asan_observer - ); + + let observers = tuple_list!(edges_observer, time_observer, asan_observer); + // Create the executor for an in-process function with just one observer for edge coverage let mut executor = FridaInProcessExecutor::new( @@ -379,8 +383,8 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> { let mut objective = feedback_or_fast!( CrashFeedback::new(), - // TimeoutFeedback::new(), - feedback_and_fast!(ConstFeedback::from(false), AsanErrorsFeedback::new(&asan_observer)) + TimeoutFeedback::new(), + AsanErrorsFeedback::new(&asan_observer) ); // If not restarting, create a State from scratch @@ -423,11 +427,9 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> { // A fuzzer with feedbacks and a corpus scheduler let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); - let observers = tuple_list!( - edges_observer, - time_observer, - asan_observer - ); + + + let observers = tuple_list!(edges_observer, time_observer); // Create the executor for an in-process function with just one observer for edge coverage let mut executor = FridaInProcessExecutor::new( @@ -438,7 +440,7 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> { &mut fuzzer, &mut state, &mut mgr, - options.timeout + options.timeout, )?, &mut frida_helper, ); @@ -455,7 +457,9 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> { let mut stages = tuple_list!(StdMutationalStage::new(mutator)); - fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr).unwrap(); + fuzzer + .fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr) + .unwrap(); Ok(()) })(state, mgr, core_id) diff --git a/fuzzers/frida_libpng/Cargo.toml b/fuzzers/frida_libpng/Cargo.toml index 30ffd0e54af..a31c3191c0e 100644 --- a/fuzzers/frida_libpng/Cargo.toml +++ b/fuzzers/frida_libpng/Cargo.toml @@ -29,7 +29,7 @@ reqwest = { version = "0.11.4", features = ["blocking"] } libafl = { path = "../../libafl/", features = [ "std", "llmp_compression", "llmp_bind_public", "frida_cli", "errors_backtrace" ] } #, "llmp_small_maps", "llmp_debug"]} libafl_bolts = { path = "../../libafl_bolts/" } -frida-gum = { version = "0.13.3", features = [ "auto-download", "event-sink", "invocation-listener"] } +frida-gum = { path = "../../../frida-rust/frida-gum", features = [ "auto-download", "event-sink", "invocation-listener"] } libafl_frida = { path = "../../libafl_frida", features = ["cmplog"] } libafl_targets = { path = "../../libafl_targets", features = ["sancov_cmplog"] } libloading = "0.7" diff --git a/fuzzers/frida_libpng/Makefile.toml b/fuzzers/frida_libpng/Makefile.toml index 531f77445b6..64e66b7b142 100644 --- a/fuzzers/frida_libpng/Makefile.toml +++ b/fuzzers/frida_libpng/Makefile.toml @@ -69,14 +69,14 @@ windows_alias = "fuzzer_windows" [tasks.fuzzer_unix] script_runner="@shell" script=''' -cargo build --profile ${PROFILE} +cargo +nightly build --profile ${PROFILE} cp ${CARGO_TARGET_DIR}/${PROFILE_DIR}/${FUZZER_NAME} . ''' [tasks.fuzzer_windows] script_runner="@shell" script=''' -cargo build --profile ${PROFILE} +cargo +nightly build --profile ${PROFILE} cp ./target/${PROFILE_DIR}/${FUZZER_NAME} . ''' diff --git a/fuzzers/frida_libpng/src/fuzzer.rs b/fuzzers/frida_libpng/src/fuzzer.rs index d7a5fc6f948..51ca62eb0dc 100644 --- a/fuzzers/frida_libpng/src/fuzzer.rs +++ b/fuzzers/frida_libpng/src/fuzzer.rs @@ -22,7 +22,6 @@ use libafl::{ state::{HasCorpus, StdState}, Error, HasMetadata, }; -#[cfg(unix)] use libafl::{feedback_and_fast, feedbacks::ConstFeedback}; use libafl_bolts::{ cli::{parse_args, FuzzerOptions}, @@ -31,7 +30,6 @@ use libafl_bolts::{ tuples::{tuple_list, Merge}, AsSlice, }; -#[cfg(unix)] use libafl_frida::asan::{ asan_rt::AsanRuntime, errors::{AsanErrorsFeedback, AsanErrorsObserver}, @@ -94,15 +92,12 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> { let gum = Gum::obtain(); let coverage = CoverageRuntime::new(); - #[cfg(unix)] + let asan = AsanRuntime::new(options); - #[cfg(unix)] let mut frida_helper = FridaInstrumentationHelper::new(&gum, options, tuple_list!(asan, coverage)); - #[cfg(windows)] - let mut frida_helper = - FridaInstrumentationHelper::new(&gum, &options, tuple_list!(coverage)); + // Create an observation channel using the coverage map let edges_observer = HitcountsMapObserver::new(StdMapObserver::from_mut_ptr( @@ -114,7 +109,6 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> { // Create an observation channel to keep track of the execution time let time_observer = TimeObserver::new("time"); - #[cfg(unix)] let asan_observer = AsanErrorsObserver::from_static_asan_errors(); // Feedback to rate the interestingness of an input @@ -127,18 +121,13 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> { ); // Feedbacks to recognize an input as solution - #[cfg(unix)] let mut objective = feedback_or_fast!( CrashFeedback::new(), TimeoutFeedback::new(), // true enables the AsanErrorFeedback - feedback_and_fast!( - ConstFeedback::from(true), - AsanErrorsFeedback::new(&asan_observer) - ) + AsanErrorsFeedback::new(&asan_observer) ); - #[cfg(windows)] - let mut objective = feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new()); + // If not restarting, create a State from scratch let mut state = state.unwrap_or_else(|| { @@ -180,10 +169,8 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> { // A fuzzer with feedbacks and a corpus scheduler let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); - #[cfg(unix)] let observers = tuple_list!(edges_observer, time_observer, asan_observer); - #[cfg(windows)] - let observers = tuple_list!(edges_observer, time_observer); + // Create the executor for an in-process function with just one observer for edge coverage let mut executor = FridaInProcessExecutor::new( @@ -235,7 +222,6 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> { // Create an observation channel to keep track of the execution time let time_observer = TimeObserver::new("time"); - #[cfg(unix)] let asan_observer = AsanErrorsObserver::from_static_asan_errors(); // Feedback to rate the interestingness of an input @@ -247,17 +233,12 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> { TimeFeedback::new(&time_observer) ); - #[cfg(unix)] let mut objective = feedback_or_fast!( CrashFeedback::new(), TimeoutFeedback::new(), - feedback_and_fast!( - ConstFeedback::from(false), - AsanErrorsFeedback::new(&asan_observer) - ) + AsanErrorsFeedback::new(&asan_observer) ); - #[cfg(windows)] - let mut objective = feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new()); + // If not restarting, create a State from scratch let mut state = state.unwrap_or_else(|| { @@ -299,10 +280,8 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> { // A fuzzer with feedbacks and a corpus scheduler let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); - #[cfg(unix)] + let observers = tuple_list!(edges_observer, time_observer, asan_observer); - #[cfg(windows)] - let observers = tuple_list!(edges_observer, time_observer); // Create the executor for an in-process function with just one observer for edge coverage let mut executor = FridaInProcessExecutor::new( @@ -368,7 +347,6 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> { // Create an observation channel to keep track of the execution time let time_observer = TimeObserver::new("time"); - #[cfg(unix)] let asan_observer = AsanErrorsObserver::from_static_asan_errors(); // Feedback to rate the interestingness of an input @@ -380,17 +358,11 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> { TimeFeedback::new(&time_observer) ); - #[cfg(unix)] let mut objective = feedback_or_fast!( CrashFeedback::new(), TimeoutFeedback::new(), - feedback_and_fast!( - ConstFeedback::from(false), - AsanErrorsFeedback::new(&asan_observer) - ) + AsanErrorsFeedback::new(&asan_observer) ); - #[cfg(windows)] - let mut objective = feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new()); // If not restarting, create a State from scratch let mut state = state.unwrap_or_else(|| { @@ -432,10 +404,7 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> { // A fuzzer with feedbacks and a corpus scheduler let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); - #[cfg(unix)] let observers = tuple_list!(edges_observer, time_observer, asan_observer); - #[cfg(windows)] - let observers = tuple_list!(edges_observer, time_observer); // Create the executor for an in-process function with just one observer for edge coverage let mut executor = FridaInProcessExecutor::new( diff --git a/fuzzers/fuzzbench_fork_qemu/src/fuzzer.rs b/fuzzers/fuzzbench_fork_qemu/src/fuzzer.rs index 8af53417c8f..862bbc243a7 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_forkserver_cmplog/src/main.rs b/fuzzers/fuzzbench_forkserver_cmplog/src/main.rs index 08676f96689..403296744d3 100644 --- a/fuzzers/fuzzbench_forkserver_cmplog/src/main.rs +++ b/fuzzers/fuzzbench_forkserver_cmplog/src/main.rs @@ -37,7 +37,7 @@ use libafl_bolts::{ ownedref::OwnedRefMut, rands::StdRand, shmem::{ShMem, ShMemProvider, UnixShMemProvider}, - tuples::{tuple_list, Handler, Merge}, + tuples::{tuple_list, Handled, Merge}, AsSliceMut, }; use libafl_targets::{ diff --git a/fuzzers/fuzzbench_qemu/src/fuzzer.rs b/fuzzers/fuzzbench_qemu/src/fuzzer.rs index bd4430825d1..1c60226ca0a 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/libfuzzer_stb_image_concolic/fuzzer/src/main.rs b/fuzzers/libfuzzer_stb_image_concolic/fuzzer/src/main.rs index 26d5af417e4..f68bea3abfe 100644 --- a/fuzzers/libfuzzer_stb_image_concolic/fuzzer/src/main.rs +++ b/fuzzers/libfuzzer_stb_image_concolic/fuzzer/src/main.rs @@ -42,7 +42,7 @@ use libafl_bolts::{ current_nanos, rands::StdRand, shmem::{ShMem, ShMemProvider, StdShMemProvider}, - tuples::{tuple_list, Handler}, + tuples::{tuple_list, Handled}, AsSlice, AsSliceMut, }; use libafl_targets::{ diff --git a/fuzzers/qemu_cmin/src/fuzzer.rs b/fuzzers/qemu_cmin/src/fuzzer.rs index 18f4cbc7bf0..efbdf5a614f 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 09dc99a269e..de8bab842e8 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 362a3d73d0c..7e180977c57 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 c6d2dacfd22..b7b45e49544 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/corpus/minimizer.rs b/libafl/src/corpus/minimizer.rs index 9b9c2c48560..b8987a1bf65 100644 --- a/libafl/src/corpus/minimizer.rs +++ b/libafl/src/corpus/minimizer.rs @@ -7,7 +7,7 @@ use core::{hash::Hash, marker::PhantomData}; use hashbrown::{HashMap, HashSet}; use libafl_bolts::{ current_time, - tuples::{Handle, Handler}, + tuples::{Handle, Handled}, AsIter, Named, }; use num_traits::ToPrimitive; @@ -51,7 +51,7 @@ where /// Algorithm based on WMOPT: #[derive(Debug)] pub struct MapCorpusMinimizer { - obs_ref: Handle, + observer_handle: Handle, phantom: PhantomData<(E, O, T, TS)>, } @@ -70,7 +70,7 @@ where /// in the future to get observed maps from an executed input. pub fn new(obs: &C) -> Self { Self { - obs_ref: obs.handle(), + observer_handle: obs.handle(), phantom: PhantomData, } } @@ -161,7 +161,7 @@ where let seed_expr = Bool::fresh_const(&ctx, "seed"); let observers = executor.observers(); - let obs = observers[&self.obs_ref].as_ref(); + let obs = observers[&self.observer_handle].as_ref(); // Store coverage, mapping coverage map indices to hit counts (if present) and the // associated seeds for the map indices with those hit counts. diff --git a/libafl/src/events/centralized.rs b/libafl/src/events/centralized.rs index ba4d1ac05ad..547c751f93d 100644 --- a/libafl/src/events/centralized.rs +++ b/libafl/src/events/centralized.rs @@ -11,7 +11,7 @@ use alloc::{boxed::Box, string::String, vec::Vec}; use core::{marker::PhantomData, num::NonZeroUsize, time::Duration}; #[cfg(feature = "adaptive_serialization")] -use libafl_bolts::tuples::{Handle, Handler}; +use libafl_bolts::tuples::{Handle, Handled}; #[cfg(feature = "llmp_compression")] use libafl_bolts::{ compress::GzipCompressor, diff --git a/libafl/src/events/launcher.rs b/libafl/src/events/launcher.rs index b377364d98a..46c45f1aea5 100644 --- a/libafl/src/events/launcher.rs +++ b/libafl/src/events/launcher.rs @@ -33,7 +33,7 @@ use libafl_bolts::os::dup2; #[cfg(all(feature = "std", any(windows, not(feature = "fork"))))] use libafl_bolts::os::startable_self; #[cfg(feature = "adaptive_serialization")] -use libafl_bolts::tuples::{Handle, Handler}; +use libafl_bolts::tuples::{Handle, Handled}; #[cfg(all(unix, feature = "std", feature = "fork"))] use libafl_bolts::{ core_affinity::get_core_ids, diff --git a/libafl/src/events/llmp.rs b/libafl/src/events/llmp.rs index e350a47dbd2..e3df2f81794 100644 --- a/libafl/src/events/llmp.rs +++ b/libafl/src/events/llmp.rs @@ -33,10 +33,12 @@ use libafl_bolts::{ #[cfg(feature = "adaptive_serialization")] use libafl_bolts::{ current_time, - tuples::{Handle, Handler}, + tuples::{Handle, Handled}, }; #[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}"); @@ -2058,7 +2060,7 @@ mod tests { use core::sync::atomic::{compiler_fence, Ordering}; #[cfg(feature = "adaptive_serialization")] - use libafl_bolts::tuples::Handler; + use libafl_bolts::tuples::Handled; use libafl_bolts::{ llmp::{LlmpClient, LlmpSharedMap}, rands::StdRand, diff --git a/libafl/src/events/mod.rs b/libafl/src/events/mod.rs index de3ef0d6aff..6480487e87a 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 c77878ea4f2..f489d68bfed 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,19 +520,16 @@ 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()); } #[allow(clippy::manual_assert)] if !staterestorer.has_content() { #[cfg(unix)] - if child_status == 137 { - // Out of Memory, see https://tldp.org/LDP/abs/html/exitcodes.html - // and https://github.com/AFLplusplus/LibAFL/issues/32 for discussion. - panic!("Fuzzer-respawner: The fuzzed target crashed with an out of memory error! Fix your harness, or switch to another executor (for example, a forkserver)."); + if child_status == 9 { + panic!("Target received SIGKILL!. This could indicate the target crashed due to OOM, user sent SIGKILL, or the target was in an unrecoverable situation and could not save state to restart"); } - // Storing state in the last round did not work panic!("Fuzzer-respawner: Storing state in crashed fuzzer instance did not work, no point to spawn the next client! This can happen if the child calls `exit()`, in that case make sure it uses `abort()`, if it got killed unrecoverable (OOM), or if there is a bug in the fuzzer itself. (Child exited with: {child_status})"); } diff --git a/libafl/src/executors/forkserver.rs b/libafl/src/executors/forkserver.rs index 39f9d0f0d6c..d82a4fb866e 100644 --- a/libafl/src/executors/forkserver.rs +++ b/libafl/src/executors/forkserver.rs @@ -22,7 +22,7 @@ use libafl_bolts::{ fs::{get_unique_std_input_file, InputFile}, os::{dup2, pipes::Pipe}, shmem::{ShMem, ShMemProvider, UnixShMemProvider}, - tuples::{Handle, Handler, MatchNameRef, Prepend, RefIndexable}, + tuples::{Handle, Handled, MatchNameRef, Prepend, RefIndexable}, AsSlice, AsSliceMut, Truncate, }; use nix::{ diff --git a/libafl/src/executors/hooks/windows.rs b/libafl/src/executors/hooks/windows.rs index ec5be577dec..d771f1d0ad7 100644 --- a/libafl/src/executors/hooks/windows.rs +++ b/libafl/src/executors/hooks/windows.rs @@ -112,10 +112,8 @@ pub mod windows_exception_handler { ptr::addr_of_mut, sync::atomic::{compiler_fence, Ordering}, }; - #[cfg(feature = "std")] use std::io::Write; - #[cfg(feature = "std")] use std::panic; @@ -135,7 +133,7 @@ pub mod windows_exception_handler { }, feedbacks::Feedback, fuzzer::HasObjective, - inputs::{UsesInput, Input}, + inputs::{Input, UsesInput}, state::{HasCorpus, HasExecutions, HasSolutions, State}, }; @@ -391,7 +389,6 @@ pub mod windows_exception_handler { if is_crash { log::error!("Child crashed!"); - } else { // log::info!("Exception received!"); } @@ -404,11 +401,8 @@ pub mod windows_exception_handler { { let mut writer = std::io::BufWriter::new(&mut bsod); writeln!(writer, "input: {:?}", input.generate_name(0)).unwrap(); - libafl_bolts::minibsod::generate_minibsod( - &mut writer, - exception_pointers - ) - .unwrap(); + libafl_bolts::minibsod::generate_minibsod(&mut writer, exception_pointers) + .unwrap(); writer.flush().unwrap(); } log::error!("{}", std::str::from_utf8(&bsod).unwrap()); diff --git a/libafl/src/feedbacks/concolic.rs b/libafl/src/feedbacks/concolic.rs index 7d6d6d9310c..33593fb7308 100644 --- a/libafl/src/feedbacks/concolic.rs +++ b/libafl/src/feedbacks/concolic.rs @@ -7,7 +7,7 @@ use alloc::borrow::Cow; use core::{fmt::Debug, marker::PhantomData}; use libafl_bolts::{ - tuples::{Handle, Handler, MatchNameRef}, + tuples::{Handle, Handled, MatchNameRef}, Named, }; @@ -28,7 +28,7 @@ use crate::{ /// Requires a [`ConcolicObserver`] to observe the concolic trace. #[derive(Debug)] pub struct ConcolicFeedback<'map, S> { - obs_ref: Handle>, + observer_handle: Handle>, phantom: PhantomData, } @@ -38,7 +38,7 @@ impl<'map, S> ConcolicFeedback<'map, S> { #[must_use] pub fn from_observer(observer: &ConcolicObserver<'map>) -> Self { Self { - obs_ref: observer.handle(), + observer_handle: observer.handle(), phantom: PhantomData, } } @@ -46,7 +46,7 @@ impl<'map, S> ConcolicFeedback<'map, S> { impl Named for ConcolicFeedback<'_, S> { fn name(&self) -> &Cow<'static, str> { - self.obs_ref.name() + self.observer_handle.name() } } @@ -82,7 +82,7 @@ where EM: EventFirer, { if let Some(metadata) = observers - .get(&self.obs_ref) + .get(&self.observer_handle) .map(ConcolicObserver::create_metadata_from_current_map) { testcase.metadata_map_mut().insert(metadata); diff --git a/libafl/src/feedbacks/differential.rs b/libafl/src/feedbacks/differential.rs index 5b69c54d9c9..1f70d0efd46 100644 --- a/libafl/src/feedbacks/differential.rs +++ b/libafl/src/feedbacks/differential.rs @@ -8,7 +8,7 @@ use core::{ }; use libafl_bolts::{ - tuples::{Handle, Handler, MatchName, MatchNameRef}, + tuples::{Handle, Handled, MatchName, MatchNameRef}, Named, }; use serde::{Deserialize, Serialize}; diff --git a/libafl/src/feedbacks/list.rs b/libafl/src/feedbacks/list.rs index c433fc372c8..0f5e89d200e 100644 --- a/libafl/src/feedbacks/list.rs +++ b/libafl/src/feedbacks/list.rs @@ -3,7 +3,7 @@ use core::{fmt::Debug, hash::Hash}; use hashbrown::HashSet; use libafl_bolts::{ - tuples::{Handle, Handler, MatchNameRef}, + tuples::{Handle, Handled, MatchNameRef}, Error, HasRefCnt, Named, }; use serde::{de::DeserializeOwned, Deserialize, Serialize}; @@ -73,7 +73,7 @@ pub struct ListFeedback where T: Hash + Eq, { - obs_ref: Handle>, + observer_handle: Handle>, novelty: HashSet, } @@ -106,7 +106,7 @@ where OT: ObserversTuple, { // TODO Replace with match_name_type when stable - let observer = observers.get(&self.obs_ref).unwrap(); + let observer = observers.get(&self.observer_handle).unwrap(); // TODO register the list content in a testcase metadata self.novelty.clear(); // can't fail @@ -151,7 +151,7 @@ where { #[inline] fn name(&self) -> &Cow<'static, str> { - self.obs_ref.name() + self.observer_handle.name() } } @@ -163,7 +163,7 @@ where #[must_use] pub fn new(observer: &ListObserver) -> Self { Self { - obs_ref: observer.handle(), + observer_handle: observer.handle(), novelty: HashSet::::new(), } } diff --git a/libafl/src/feedbacks/map.rs b/libafl/src/feedbacks/map.rs index 14294406feb..e80053326d9 100644 --- a/libafl/src/feedbacks/map.rs +++ b/libafl/src/feedbacks/map.rs @@ -12,7 +12,7 @@ use core::{ #[rustversion::nightly] use libafl_bolts::AsSlice; use libafl_bolts::{ - tuples::{Handle, Handler, MatchNameRef}, + tuples::{Handle, Handled, MatchNameRef}, AsIter, HasRefCnt, Named, }; use num_traits::PrimInt; @@ -22,7 +22,7 @@ use crate::{ corpus::Testcase, events::{Event, EventFirer}, executors::ExitKind, - feedbacks::{Feedback, HasObserverReference}, + feedbacks::{Feedback, HasObserverHandle}, inputs::UsesInput, monitors::{AggregatorOps, UserStats, UserStatsValue}, observers::{CanTrack, MapObserver, Observer, ObserversTuple}, @@ -660,7 +660,7 @@ impl Named for MapFeedback { } } -impl HasObserverReference for MapFeedback +impl HasObserverHandle for MapFeedback where O: Named, C: AsRef, @@ -668,7 +668,7 @@ where type Observer = C; #[inline] - fn observer_ref(&self) -> &Handle { + fn observer_handle(&self) -> &Handle { &self.map_ref } } diff --git a/libafl/src/feedbacks/mod.rs b/libafl/src/feedbacks/mod.rs index 83b8598982b..c997ebabb35 100644 --- a/libafl/src/feedbacks/mod.rs +++ b/libafl/src/feedbacks/mod.rs @@ -14,7 +14,7 @@ use core::{ pub use concolic::ConcolicFeedback; pub use differential::DiffFeedback; use libafl_bolts::{ - tuples::{Handle, Handler, MatchNameRef}, + tuples::{Handle, Handled, MatchNameRef}, Named, }; pub use list::*; @@ -138,12 +138,12 @@ where } /// Has an associated observer name (mostly used to retrieve the observer with `MatchName` from an `ObserverTuple`) -pub trait HasObserverReference { +pub trait HasObserverHandle { /// The observer for which we hold a reference type Observer: ?Sized; /// The name associated with the observer - fn observer_ref(&self) -> &Handle; + fn observer_handle(&self) -> &Handle; } /// A combined feedback consisting of multiple [`Feedback`]s @@ -934,7 +934,7 @@ pub type TimeoutFeedbackFactory = DefaultFeedbackFactory; /// It decides, if the given [`TimeObserver`] value of a run is interesting. #[derive(Serialize, Deserialize, Clone, Debug)] pub struct TimeFeedback { - obs_ref: Handle, + observer_handle: Handle, } impl Feedback for TimeFeedback @@ -971,7 +971,7 @@ where OT: ObserversTuple, EM: EventFirer, { - let observer = observers.get(&self.obs_ref).unwrap(); + let observer = observers.get(&self.observer_handle).unwrap(); *testcase.exec_time_mut() = *observer.last_runtime(); Ok(()) } @@ -986,7 +986,7 @@ where impl Named for TimeFeedback { #[inline] fn name(&self) -> &Cow<'static, str> { - self.obs_ref.name() + self.observer_handle.name() } } @@ -995,7 +995,7 @@ impl TimeFeedback { #[must_use] pub fn new(observer: &TimeObserver) -> Self { Self { - obs_ref: observer.handle(), + observer_handle: observer.handle(), } } } diff --git a/libafl/src/feedbacks/new_hash_feedback.rs b/libafl/src/feedbacks/new_hash_feedback.rs index 4b90b18422d..b52806801c6 100644 --- a/libafl/src/feedbacks/new_hash_feedback.rs +++ b/libafl/src/feedbacks/new_hash_feedback.rs @@ -5,7 +5,7 @@ use std::{fmt::Debug, marker::PhantomData}; use hashbrown::HashSet; use libafl_bolts::{ - tuples::{Handle, Handler, MatchNameRef}, + tuples::{Handle, Handled, MatchNameRef}, Named, }; use serde::{Deserialize, Serialize}; @@ -13,7 +13,7 @@ use serde::{Deserialize, Serialize}; use crate::{ events::EventFirer, executors::ExitKind, - feedbacks::{Feedback, HasObserverReference}, + feedbacks::{Feedback, HasObserverHandle}, inputs::UsesInput, observers::{ObserverWithHashField, ObserversTuple}, state::State, @@ -145,11 +145,11 @@ impl Named for NewHashFeedback { } } -impl HasObserverReference for NewHashFeedback { +impl HasObserverHandle for NewHashFeedback { type Observer = O; #[inline] - fn observer_ref(&self) -> &Handle { + fn observer_handle(&self) -> &Handle { &self.o_ref } } diff --git a/libafl/src/feedbacks/stdio.rs b/libafl/src/feedbacks/stdio.rs index bda7619421d..6c8137a5150 100644 --- a/libafl/src/feedbacks/stdio.rs +++ b/libafl/src/feedbacks/stdio.rs @@ -4,7 +4,7 @@ use alloc::{borrow::Cow, string::String}; use libafl_bolts::{ impl_serdeany, - tuples::{Handle, Handler, MatchNameRef}, + tuples::{Handle, Handled, MatchNameRef}, Named, }; use serde::{Deserialize, Serialize}; diff --git a/libafl/src/schedulers/mod.rs b/libafl/src/schedulers/mod.rs index 212f97a4951..2a0b65df48b 100644 --- a/libafl/src/schedulers/mod.rs +++ b/libafl/src/schedulers/mod.rs @@ -82,7 +82,7 @@ where fn set_last_hash(&mut self, value: usize); /// Get the observer map observer name - fn map_observer_ref(&self) -> &Handle; + fn map_observer_handle(&self) -> &Handle; /// Called when a [`Testcase`] is added to the corpus fn on_add_metadata(&self, state: &mut Self::State, idx: CorpusId) -> Result<(), Error> { @@ -121,7 +121,7 @@ where OT: ObserversTuple, { let observer = observers - .get(self.map_observer_ref()) + .get(self.map_observer_handle()) .ok_or_else(|| Error::key_not_found("MapObserver not found".to_string()))? .as_ref(); diff --git a/libafl/src/schedulers/powersched.rs b/libafl/src/schedulers/powersched.rs index d131a66d508..0601e061049 100644 --- a/libafl/src/schedulers/powersched.rs +++ b/libafl/src/schedulers/powersched.rs @@ -4,7 +4,7 @@ use alloc::vec::Vec; use core::{marker::PhantomData, time::Duration}; use libafl_bolts::{ - tuples::{Handle, Handler}, + tuples::{Handle, Handled}, Named, }; use serde::{Deserialize, Serialize}; @@ -173,7 +173,7 @@ pub enum PowerSchedule { #[derive(Clone, Debug)] pub struct PowerQueueScheduler { strat: PowerSchedule, - map_observer_ref: Handle, + map_observer_handle: Handle, last_hash: usize, phantom: PhantomData<(O, S)>, } @@ -226,8 +226,8 @@ where self.last_hash = hash; } - fn map_observer_ref(&self) -> &Handle { - &self.map_observer_ref + fn map_observer_handle(&self) -> &Handle { + &self.map_observer_handle } } @@ -305,7 +305,7 @@ where } PowerQueueScheduler { strat, - map_observer_ref: map_observer.handle(), + map_observer_handle: map_observer.handle(), last_hash: 0, phantom: PhantomData, } diff --git a/libafl/src/schedulers/weighted.rs b/libafl/src/schedulers/weighted.rs index b6c7719f428..79dce6a5a95 100644 --- a/libafl/src/schedulers/weighted.rs +++ b/libafl/src/schedulers/weighted.rs @@ -6,7 +6,7 @@ use core::marker::PhantomData; use hashbrown::HashMap; use libafl_bolts::{ rands::Rand, - tuples::{Handle, Handler}, + tuples::{Handle, Handled}, Named, }; use serde::{Deserialize, Serialize}; @@ -98,7 +98,7 @@ libafl_bolts::impl_serdeany!(WeightedScheduleMetadata); pub struct WeightedScheduler { table_invalidated: bool, strat: Option, - map_observer_ref: Handle, + map_observer_handle: Handle, last_hash: usize, phantom: PhantomData<(F, O, S)>, } @@ -124,7 +124,7 @@ where Self { strat, - map_observer_ref: map_observer.handle(), + map_observer_handle: map_observer.handle(), last_hash: 0, table_invalidated: true, phantom: PhantomData, @@ -274,8 +274,8 @@ where self.last_hash = hash; } - fn map_observer_ref(&self) -> &Handle { - &self.map_observer_ref + fn map_observer_handle(&self) -> &Handle { + &self.map_observer_handle } } diff --git a/libafl/src/stages/calibrate.rs b/libafl/src/stages/calibrate.rs index 1a4c796a935..867000e745c 100644 --- a/libafl/src/stages/calibrate.rs +++ b/libafl/src/stages/calibrate.rs @@ -12,7 +12,7 @@ use crate::{ corpus::{Corpus, SchedulerTestcaseMetadata}, events::{Event, EventFirer, LogSeverity}, executors::{Executor, ExitKind, HasObservers}, - feedbacks::{map::MapFeedbackMetadata, HasObserverReference}, + feedbacks::{map::MapFeedbackMetadata, HasObserverHandle}, fuzzer::Evaluator, inputs::UsesInput, monitors::{AggregatorOps, UserStats, UserStatsValue}, @@ -63,7 +63,7 @@ impl UnstableEntriesMetadata { /// The calibration stage will measure the average exec time and the target's stability for this input. #[derive(Clone, Debug)] pub struct CalibrationStage { - map_observer_ref: Handle, + map_observer_handle: Handle, map_name: Cow<'static, str>, stage_max: usize, /// If we should track stability @@ -144,7 +144,7 @@ where .observers_mut() .post_exec_all(state, &input, &exit_kind)?; - let map_first = &executor.observers()[&self.map_observer_ref] + let map_first = &executor.observers()[&self.map_observer_handle] .as_ref() .to_vec(); @@ -185,7 +185,7 @@ where .post_exec_all(state, &input, &exit_kind)?; if self.track_stability { - let map = &executor.observers()[&self.map_observer_ref] + let map = &executor.observers()[&self.map_observer_handle] .as_ref() .to_vec(); @@ -240,7 +240,7 @@ where // If weighted scheduler or powerscheduler is used, update it if state.has_metadata::() { let observers = executor.observers(); - let map = observers[&self.map_observer_ref].as_ref(); + let map = observers[&self.map_observer_handle].as_ref(); let mut bitmap_size = map.count_bytes(); assert!(bitmap_size != 0); @@ -340,10 +340,10 @@ where #[must_use] pub fn new(map_feedback: &F) -> Self where - F: HasObserverReference + Named, + F: HasObserverHandle + Named, { Self { - map_observer_ref: map_feedback.observer_ref().clone(), + map_observer_handle: map_feedback.observer_handle().clone(), map_name: map_feedback.name().clone(), stage_max: CAL_STAGE_START, track_stability: true, @@ -356,10 +356,10 @@ where #[must_use] pub fn ignore_stability(map_feedback: &F) -> Self where - F: HasObserverReference + Named, + F: HasObserverHandle + Named, { Self { - map_observer_ref: map_feedback.observer_ref().clone(), + map_observer_handle: map_feedback.observer_handle().clone(), map_name: map_feedback.name().clone(), stage_max: CAL_STAGE_START, track_stability: false, diff --git a/libafl/src/stages/colorization.rs b/libafl/src/stages/colorization.rs index e57420f9b3c..627f5561620 100644 --- a/libafl/src/stages/colorization.rs +++ b/libafl/src/stages/colorization.rs @@ -4,7 +4,7 @@ use core::{cmp::Ordering, fmt::Debug, marker::PhantomData, ops::Range}; use libafl_bolts::{ rands::Rand, - tuples::{Handle, Handler}, + tuples::{Handle, Handled}, Named, }; use serde::{Deserialize, Serialize}; @@ -55,7 +55,7 @@ impl Ord for Earlier { /// The mutational stage using power schedules #[derive(Clone, Debug)] pub struct ColorizationStage { - map_observer_ref: Handle, + map_observer_handle: Handle, #[allow(clippy::type_complexity)] phantom: PhantomData<(E, EM, O, E, Z)>, } @@ -72,7 +72,7 @@ where E: UsesState, { fn name(&self) -> &Cow<'static, str> { - self.map_observer_ref.name() + self.map_observer_handle.name() } } @@ -96,7 +96,7 @@ where manager: &mut EM, ) -> Result<(), Error> { // Run with the mutated input - Self::colorize(fuzzer, executor, state, manager, &self.map_observer_ref)?; + Self::colorize(fuzzer, executor, state, manager, &self.map_observer_handle)?; Ok(()) } @@ -168,7 +168,7 @@ where executor: &mut E, state: &mut E::State, manager: &mut EM, - obs_ref: &Handle, + observer_handle: &Handle, ) -> Result { let mut input = state.current_input_cloned()?; // The backup of the input @@ -182,8 +182,14 @@ where // First, run orig_input once and get the original hash // Idea: No need to do this every time - let orig_hash = - Self::get_raw_map_hash_run(fuzzer, executor, state, manager, consumed_input, obs_ref)?; + let orig_hash = Self::get_raw_map_hash_run( + fuzzer, + executor, + state, + manager, + consumed_input, + observer_handle, + )?; let changed_bytes = changed.bytes_mut(); let input_len = changed_bytes.len(); @@ -228,7 +234,7 @@ where state, manager, consumed_input, - obs_ref, + observer_handle, )?; if orig_hash == changed_hash { @@ -301,7 +307,7 @@ where /// Creates a new [`ColorizationStage`] pub fn new(map_observer: &C) -> Self { Self { - map_observer_ref: map_observer.handle(), + map_observer_handle: map_observer.handle(), phantom: PhantomData, } } @@ -313,14 +319,14 @@ where state: &mut E::State, manager: &mut EM, input: E::Input, - obs_ref: &Handle, + observer_handle: &Handle, ) -> Result { executor.observers_mut().pre_exec_all(state, &input)?; let exit_kind = executor.run_target(fuzzer, state, manager, &input)?; let observers = executor.observers(); - let observer = observers[obs_ref].as_ref(); + let observer = observers[observer_handle].as_ref(); let hash = observer.hash_simple() as usize; diff --git a/libafl/src/stages/concolic.rs b/libafl/src/stages/concolic.rs index 54e8f1dd10a..118bfc6464a 100644 --- a/libafl/src/stages/concolic.rs +++ b/libafl/src/stages/concolic.rs @@ -39,7 +39,7 @@ use crate::{ #[derive(Clone, Debug)] pub struct ConcolicTracingStage<'a, EM, TE, Z> { inner: TracingStage, - obs_ref: Handle>, + observer_handle: Handle>, } impl UsesState for ConcolicTracingStage<'_, EM, TE, Z> @@ -73,7 +73,7 @@ where manager: &mut EM, ) -> Result<(), Error> { self.inner.trace(fuzzer, state, manager)?; - if let Some(observer) = self.inner.executor().observers().get(&self.obs_ref) { + if let Some(observer) = self.inner.executor().observers().get(&self.observer_handle) { let metadata = observer.create_metadata_from_current_map(); state .current_testcase_mut()? @@ -95,8 +95,14 @@ where impl<'a, EM, TE, Z> ConcolicTracingStage<'a, EM, TE, Z> { /// Creates a new default tracing stage using the given [`Executor`], observing traces from a /// [`ConcolicObserver`] with the given name. - pub fn new(inner: TracingStage, obs_ref: Handle>) -> Self { - Self { inner, obs_ref } + pub fn new( + inner: TracingStage, + observer_handle: Handle>, + ) -> Self { + Self { + inner, + observer_handle, + } } } diff --git a/libafl/src/stages/generalization.rs b/libafl/src/stages/generalization.rs index 8f69e533800..40d65707eeb 100644 --- a/libafl/src/stages/generalization.rs +++ b/libafl/src/stages/generalization.rs @@ -4,7 +4,7 @@ use alloc::{borrow::Cow, vec::Vec}; use core::{fmt::Debug, marker::PhantomData}; use libafl_bolts::{ - tuples::{Handle, Handler}, + tuples::{Handle, Handled}, AsSlice, Named, }; @@ -43,7 +43,7 @@ fn find_next_char(list: &[Option], mut idx: usize, ch: u8) -> usize { /// A stage that runs a tracer executor #[derive(Clone, Debug)] pub struct GeneralizationStage { - map_observer_ref: Handle, + map_observer_handle: Handle, #[allow(clippy::type_complexity)] phantom: PhantomData<(EM, O, OT, Z)>, } @@ -347,7 +347,7 @@ where pub fn new(map_observer: &C) -> Self { require_novelties_tracking!("GeneralizationStage", C); Self { - map_observer_ref: map_observer.handle(), + map_observer_handle: map_observer.handle(), phantom: PhantomData, } } @@ -381,7 +381,7 @@ where .post_exec_all(state, input, &exit_kind)?; mark_feature_time!(state, PerfFeature::PostExecObservers); - let cnt = executor.observers()[&self.map_observer_ref] + let cnt = executor.observers()[&self.map_observer_handle] .as_ref() .how_many_set(novelties); diff --git a/libafl/src/stages/tmin.rs b/libafl/src/stages/tmin.rs index 26f982893e9..01d2f287153 100644 --- a/libafl/src/stages/tmin.rs +++ b/libafl/src/stages/tmin.rs @@ -5,7 +5,7 @@ use core::{borrow::BorrowMut, fmt::Debug, hash::Hash, marker::PhantomData}; use ahash::RandomState; use libafl_bolts::{ - tuples::{Handle, Handler, MatchNameRef}, + tuples::{Handle, Handled, MatchNameRef}, HasLen, Named, }; @@ -13,7 +13,7 @@ use crate::{ corpus::{Corpus, HasCurrentCorpusIdx, Testcase}, events::EventFirer, executors::{Executor, ExitKind, HasObservers}, - feedbacks::{Feedback, FeedbackFactory, HasObserverReference}, + feedbacks::{Feedback, FeedbackFactory, HasObserverHandle}, inputs::UsesInput, mark_feature_time, mutators::{MutationResult, Mutator}, @@ -364,10 +364,10 @@ impl Named for MapEqualityFeedback { } } -impl HasObserverReference for MapEqualityFeedback { +impl HasObserverHandle for MapEqualityFeedback { type Observer = C; - fn observer_ref(&self) -> &Handle { + fn observer_handle(&self) -> &Handle { &self.map_ref } } @@ -391,7 +391,7 @@ where OT: ObserversTuple, { let obs = observers - .get(self.observer_ref()) + .get(self.observer_handle()) .expect("Should have been provided valid observer name."); Ok(obs.as_ref().hash_simple() == self.orig_hash) } @@ -407,7 +407,7 @@ pub struct MapEqualityFactory { impl MapEqualityFactory where M: MapObserver, - C: AsRef + Handler, + C: AsRef + Handled, { /// Creates a new map equality feedback for the given observer pub fn new(obs: &C) -> Self { @@ -418,10 +418,10 @@ where } } -impl HasObserverReference for MapEqualityFactory { +impl HasObserverHandle for MapEqualityFactory { type Observer = C; - fn observer_ref(&self) -> &Handle { + fn observer_handle(&self) -> &Handle { &self.map_ref } } @@ -430,13 +430,13 @@ impl FeedbackFactory, S, OT> for MapEqualityFactory where M: MapObserver, - C: AsRef + Handler, + C: AsRef + Handled, OT: ObserversTuple, S: State + Debug, { fn create_feedback(&self, observers: &OT) -> MapEqualityFeedback { let obs = observers - .get(self.observer_ref()) + .get(self.observer_handle()) .expect("Should have been provided valid observer name."); MapEqualityFeedback { name: Cow::from("MapEq"), diff --git a/libafl_bolts/Cargo.toml b/libafl_bolts/Cargo.toml index feb0938d18c..f0a0dbb3b76 100644 --- a/libafl_bolts/Cargo.toml +++ b/libafl_bolts/Cargo.toml @@ -27,7 +27,7 @@ document-features = ["dep:document-features"] std = ["serde_json", "serde_json/std", "hostname", "nix", "serde/std", "uuid", "backtrace", "uds", "serial_test", "alloc"] ## Enables all features that allocate in `no_std` -alloc = ["serde/alloc", "hashbrown", "postcard", "erased-serde/alloc", "ahash"] +alloc = ["serde/alloc", "hashbrown", "postcard", "erased-serde/alloc", "ahash"] ## Provide the `#[derive(SerdeAny)]` macro. derive = ["libafl_derive"] @@ -95,25 +95,24 @@ rustversion = "1.0" libafl_derive = { version = "0.12.0", optional = true, path = "../libafl_derive" } static_assertions = "1.1.0" -rustversion = "1.0" tuple_list = { version = "0.1.3" } -hashbrown = { version = "0.14", features = ["serde", "ahash"], default-features=false, optional = true } # A faster hashmap, nostd compatible +hashbrown = { version = "0.14", features = ["serde", "ahash"], default-features = false, optional = true } # A faster hashmap, nostd compatible xxhash-rust = { version = "0.8.5", features = ["xxh3"], optional = true } # xxh3 hashing for rust serde = { version = "1.0", default-features = false, features = ["derive"] } # serialization lib erased-serde = { version = "0.3.21", default-features = false, optional = true } # erased serde postcard = { version = "1.0", features = ["alloc"], default-features = false, optional = true } # no_std compatible serde serialization format num_enum = { version = "0.7", default-features = false } -ahash = { version = "0.8", default-features=false, optional = true } # The hash function already used in hashbrown -backtrace = {version = "0.3", optional = true} # Used to get the stacktrace in StacktraceObserver +ahash = { version = "0.8", default-features = false, optional = true } # The hash function already used in hashbrown +backtrace = { version = "0.3", optional = true } # Used to get the stacktrace in StacktraceObserver ctor = { optional = true, version = "0.2" } serde_json = { version = "1.0", optional = true, default-features = false, features = ["alloc"] } -miniz_oxide = { version = "0.7.1", optional = true} +miniz_oxide = { version = "0.7.1", optional = true } hostname = { version = "^0.3", optional = true } # Is there really no gethostname in the stdlib? rand_core = { version = "0.6", optional = true } nix = { version = "0.27", default-features = false, optional = true, features = ["signal", "socket", "poll"] } uuid = { version = "1.4", optional = true, features = ["serde", "v4"] } -clap = {version = "4.5", features = ["derive", "wrap_help"], optional = true} # CLI parsing, for libafl_bolts::cli / the `cli` feature +clap = { version = "4.5", features = ["derive", "wrap_help"], optional = true } # CLI parsing, for libafl_bolts::cli / the `cli` feature log = "0.4.20" pyo3 = { version = "0.18", optional = true, features = ["serde", "macros"] } diff --git a/libafl_bolts/src/os/mod.rs b/libafl_bolts/src/os/mod.rs index 4eaabf92a9f..03b27da8e1b 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 b01e0638f1c..ed233047f0d 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 5eb5b82126d..04b8d2cc12f 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_bolts/src/tuples.rs b/libafl_bolts/src/tuples.rs index 9d93fdddb8c..31593b781f1 100644 --- a/libafl_bolts/src/tuples.rs +++ b/libafl_bolts/src/tuples.rs @@ -6,6 +6,7 @@ use alloc::{borrow::Cow, vec::Vec}; use core::ops::{Deref, DerefMut}; use core::{ any::{type_name, TypeId}, + cell::Cell, fmt::{Debug, Formatter}, marker::PhantomData, mem::transmute, @@ -23,40 +24,37 @@ use crate::HasLen; #[cfg(feature = "alloc")] use crate::Named; -/// Returns if the type `T` is equal to `U` -/// From -#[rustversion::nightly] -#[inline] +/// Returns if the type `T` is equal to `U`, ignoring lifetimes. +#[inline] // this entire call gets optimized away :) #[must_use] -pub const fn type_eq() -> bool { - // Helper trait. `VALUE` is false, except for the specialization of the - // case where `T == U`. - trait TypeEq { - const VALUE: bool; - } - - // Default implementation. - impl TypeEq for T { - default const VALUE: bool = false; - } - - // Specialization for `T == U`. - impl TypeEq for T { - const VALUE: bool = true; +pub fn type_eq() -> bool { + // decider struct: hold a cell (which we will update if the types are unequal) and some + // phantom data using a function pointer to allow for Copy to be implemented + struct W<'a, T: ?Sized, U: ?Sized>(&'a Cell, PhantomData (&'a T, &'a U)>); + + // default implementation: if the types are unequal, we will use the clone implementation + impl<'a, T: ?Sized, U: ?Sized> Clone for W<'a, T, U> { + #[inline] + fn clone(&self) -> Self { + // indicate that the types are unequal + // unfortunately, use of interior mutability (Cell) makes this not const-compatible + // not really possible to get around at this time + self.0.set(false); + W(self.0, self.1) + } } - >::VALUE -} + // specialized implementation: Copy is only implemented if the types are the same + #[allow(clippy::mismatching_type_param_order)] + impl<'a, T: ?Sized> Copy for W<'a, T, T> {} -/// Returns if the type `T` is equal to `U` -/// As this relies on [`type_name`](https://doc.rust-lang.org/std/any/fn.type_name.html#note) internally, -/// there is a chance for collisions. -/// Use `nightly` if you need a perfect match at all times. -#[rustversion::not(nightly)] -#[inline] -#[must_use] -pub fn type_eq() -> bool { - type_name::() == type_name::() + let detected = Cell::new(true); + // [].clone() is *specialized* in core. + // Types which implement copy will have their copy implementations used, falling back to clone. + // If the types are the same, then our clone implementation (which sets our Cell to false) + // will never be called, meaning that our Cell's content remains true. + let res = [W::(&detected, PhantomData)].clone(); + res[0].0.get() } /// Borrow each member of the tuple @@ -453,10 +451,6 @@ where } /// Match for a name and return the value -/// -/// # Note -/// This operation may not be 100% accurate with Rust stable, see the notes for [`type_eq`] -/// (in `nightly`, it uses [specialization](https://stackoverflow.com/a/60138532/7658998)). #[cfg(feature = "alloc")] pub trait MatchName { /// Match for a name and return the borrowed value @@ -501,11 +495,11 @@ where } } -/// Structs that has `Handle ` -/// You should use this when you want to avoid specifying types using `match_name_type_mut` +/// Structs that have a [`Handle`] to reference this element by, in maps. +/// You should use this when you want to avoid specifying types. #[cfg(feature = "alloc")] -pub trait Handler: Named { - /// Return the `Handle ` +pub trait Handled: Named { + /// Return the [`Handle`] fn handle(&self) -> Handle { Handle { name: Named::name(self).clone(), @@ -515,7 +509,7 @@ pub trait Handler: Named { } #[cfg(feature = "alloc")] -impl Handler for N where N: Named {} +impl Handled for N where N: Named {} /// Object with the type T and the name associated with its concrete value #[derive(Serialize, Deserialize)] diff --git a/libafl_frida/Cargo.toml b/libafl_frida/Cargo.toml index f1aa7c53993..1b326bb966f 100644 --- a/libafl_frida/Cargo.toml +++ b/libafl_frida/Cargo.toml @@ -56,7 +56,7 @@ libc = "0.2" hashbrown = "0.14" rangemap = "1.3" frida-gum-sys = { path = "../../frida-rust/frida-gum-sys/", version = "0.13.6", features = [ - # "auto-download", + "auto-download", "event-sink", "invocation-listener", ] } diff --git a/libafl_frida/src/asan/asan_rt.rs b/libafl_frida/src/asan/asan_rt.rs index a87817069cc..f6b2cf2b180 100644 --- a/libafl_frida/src/asan/asan_rt.rs +++ b/libafl_frida/src/asan/asan_rt.rs @@ -10,12 +10,7 @@ use core::{ fmt::{self, Debug, Formatter}, ptr::addr_of_mut, }; -use std::{ - ffi::c_void, - ptr::write_volatile, - rc::Rc, - sync::MutexGuard, -}; +use std::{ffi::c_void, num::NonZeroUsize, ptr::write_volatile, rc::Rc, sync::MutexGuard}; use backtrace::Backtrace; use dynasmrt::{dynasm, DynasmApi, DynasmLabelApi}; @@ -30,6 +25,7 @@ use frida_gum::{ use frida_gum_sys::Insn; use hashbrown::HashMap; use libafl_bolts::{cli::FuzzerOptions, AsSlice}; +use nix::sys::mman::{mmap, MapFlags, ProtFlags}; use rangemap::RangeMap; #[cfg(target_arch = "aarch64")] use yaxpeax_arch::Arch; @@ -129,6 +125,7 @@ pub struct AsanRuntime { suppressed_addresses: Vec, skip_ranges: Vec, continue_on_error: bool, + shadow_check_func: Option bool>, pub(crate) hooks_enabled: bool, pc: Option, @@ -162,6 +159,8 @@ impl FridaRuntime for AsanRuntime { AsanErrors::get_mut_blocking().set_continue_on_error(self.continue_on_error); + self.generate_shadow_check_function(); + self.generate_instrumentation_blobs(); self.unpoison_all_existing_memory(); @@ -248,6 +247,11 @@ impl AsanRuntime { &mut self.allocator } + #[must_use] + pub fn shadow_check_func(&self) -> &Option bool> { + &self.shadow_check_func + } + /// Check if the test leaked any memory and report it if so. pub fn check_for_leaks(&mut self) { self.allocator.check_for_leaks(); @@ -1605,6 +1609,412 @@ impl AsanRuntime { } } + // https://godbolt.org/z/EvWPzqjeK + + // https://godbolt.org/z/oajhcP5sv + /* + #include + #include + uint8_t shadow_bit = 44; + + uint64_t generate_shadow_check_function(uint64_t start, uint64_t size){ + // calculate the shadow address + uint64_t addr = 0; + addr = addr + (start >> 3); + uint64_t mask = (1ULL << (shadow_bit + 1)) - 1; + addr = addr & mask; + addr = addr + (1ULL << shadow_bit); + + if(size == 0){ + // goto return_success + return 1; + } + else{ + // check if the ptr is not aligned to 8 bytes + uint8_t remainder = start & 0b111; + if(remainder != 0){ + // we need to test the high bits from the first shadow byte + uint8_t shift; + if(size < 8){ + shift = size; + } + else{ + shift = 8 - remainder; + } + // goto check_bits + uint8_t mask = (1 << shift) - 1; + + // bitwise reverse for amd64 :< + // https://gist.github.com/yantonov/4359090 + // we need 16bit number here, (not 8bit) + uint16_t val = *(uint16_t *)addr; + val = (val & 0xff00) >> 8 | (val & 0x00ff) << 8; + val = (val & 0xf0f0) >> 4 | (val & 0x0f0f) << 4; + val = (val & 0xcccc) >> 2 | (val & 0x3333) << 2; + val = (val & 0xaaaa) >> 1 | (val & 0x5555) << 1; + val = (val >> 8) | (val << 8); // swap the byte + val = (val >> remainder); + if((val & mask) != mask){ + // goto return failure + return 0; + } + + size = size - shift; + addr += 1; + } + + // no_start_offset + uint64_t num_shadow_bytes = size >> 3; + uint64_t mask = -1; + + while(true){ + if(num_shadow_bytes < 8){ + // goto less_than_8_shadow_bytes_remaining + break; + } + else{ + uint64_t val = *(uint64_t *)addr; + addr += 8; + if(val != mask){ + // goto return failure + return 0; + } + num_shadow_bytes -= 8; + size -= 64; + } + } + + while(true){ + if(num_shadow_bytes < 1){ + // goto check_trailing_bits + break; + } + else{ + uint8_t val = *(uint8_t *)addr; + addr += 1; + if(val != 0xff){ + // goto return failure + return 0; + } + num_shadow_bytes -= 1; + size -= 8; + } + } + + if(size == 0){ + // goto return success + return 1; + } + + uint8_t mask2 = ((1 << (size & 0b111)) - 1); + uint8_t val = *(uint8_t *)addr; + val = (val & 0xf0) >> 4 | (val & 0x0f) << 4; + val = (val & 0xff) >> 2 | (val & 0x33) << 2; + val = (val & 0xaa) >> 1 | (val & 0x55) << 1; + + if((val & mask2) != mask2){ + // goto return failure + return 0; + } + return 1; + } + } + */ + #[cfg(target_arch = "x86_64")] + #[allow(clippy::unused_self, clippy::identity_op)] + #[allow(clippy::too_many_lines)] + fn generate_shadow_check_function(&mut self) { + use std::fs::File; + + let shadow_bit = self.allocator.shadow_bit(); + let mut ops = dynasmrt::VecAssembler::::new(0); + + // Rdi start, Rsi size + dynasm!(ops + ; .arch x64 + ; mov cl, BYTE shadow_bit as i8 + ; mov r10, -2 + ; shl r10, cl + ; mov eax, 1 + ; mov edx, 1 + ; shl rdx, cl + ; test rsi, rsi + ; je >LBB0_15 + ; mov rcx, rdi + ; shr rcx, 3 + ; not r10 + ; and r10, rcx + ; add r10, rdx + ; and edi, 7 + ; je >LBB0_4 + ; mov cl, 8 + ; sub cl, dil + ; cmp rsi, 8 + ; movzx ecx, cl + ; mov r8d, esi + ; cmovae r8d, ecx + ; mov r9d, -1 + ; mov ecx, r8d + ; shl r9d, cl + ; movzx ecx, WORD [r10] + ; rol cx, 8 + ; mov edx, ecx + ; shr edx, 4 + ; and edx, 3855 + ; shl ecx, 4 + ; and ecx, -3856 + ; or ecx, edx + ; mov edx, ecx + ; shr edx, 2 + ; and edx, 13107 + ; and ecx, -3277 + ; lea ecx, [rdx + 4*rcx] + ; mov edx, ecx + ; shr edx, 1 + ; and edx, 21845 + ; and ecx, -10923 + ; lea ecx, [rdx + 2*rcx] + ; rol cx, 8 + ; movzx edx, cx + ; mov ecx, edi + ; shr edx, cl + ; not r9d + ; movzx ecx, r9b + ; and edx, ecx + ; cmp edx, ecx + ; jne >LBB0_11 + ; movzx ecx, r8b + ; sub rsi, rcx + ; add r10, 1 + ;LBB0_4: + ; mov r8, rsi + ; shr r8, 3 + ; mov r9, r8 + ; and r9, -8 + ; mov edi, r8d + ; and edi, 7 + ; add r9, r10 + ; and esi, 63 + ; mov rdx, r8 + ; mov rcx, r10 + ;LBB0_5: + ; cmp rdx, 7 + ; jbe >LBB0_8 + ; add rdx, -8 + ; cmp QWORD [rcx], -1 + ; lea rcx, [rcx + 8] + ; je LBB0_11 + ;LBB0_8: + ; lea rcx, [8*rdi] + ; sub rsi, rcx + ;LBB0_9: + ; test rdi, rdi + ; je >LBB0_13 + ; add rdi, -1 + ; cmp BYTE [r9], -1 + ; lea r9, [r9 + 1] + ; je LBB0_15 + ; and sil, 7 + ; mov dl, -1 + ; mov ecx, esi + ; shl dl, cl + ; not dl + ; mov cl, BYTE [r8 + r10] + ; rol cl, 4 + ; mov eax, ecx + ; shr al, 2 + ; shl cl, 2 + ; and cl, -52 + ; or cl, al + ; mov eax, ecx + ; shr al, 1 + ; and al, 85 + ; add cl, cl + ; and cl, -86 + ; or cl, al + ; and cl, dl + ; xor eax, eax + ; cmp cl, dl + ; sete al + ;LBB0_15: + ; ret + ); + let blob = ops.finalize().unwrap(); + unsafe { + let mapping = mmap::( + None, + NonZeroUsize::new_unchecked(0x1000), + ProtFlags::all(), + MapFlags::MAP_ANON | MapFlags::MAP_PRIVATE, + None, + 0, + ) + .unwrap(); + blob.as_ptr() + .copy_to_nonoverlapping(mapping as *mut u8, blob.len()); + self.shadow_check_func = Some(std::mem::transmute::< + *mut u8, + extern "C" fn(*const c_void, usize) -> bool, + >(mapping as *mut u8)); + } + } + + #[cfg(target_arch = "aarch64")] + // identity_op appears to be a false positive in ubfx + #[allow(clippy::unused_self, clippy::identity_op, clippy::too_many_lines)] + fn generate_shadow_check_function(&mut self) { + use std::fs::File; + + let shadow_bit = self.allocator.shadow_bit(); + let mut ops = dynasmrt::VecAssembler::::new(0); + dynasm!(ops + ; .arch aarch64 + + // calculate the shadow address + ; mov x5, #0 + // ; add x5, xzr, x5, lsl #shadow_bit + ; add x5, x5, x0, lsr #3 + ; ubfx x5, x5, #0, #(shadow_bit + 1) + ; mov x6, #1 + ; add x5, x5, x6, lsl #shadow_bit + + ; cmp x1, #0 + ; b.eq >return_success + // check if the ptr is not aligned to 8 bytes + ; ands x6, x0, #7 + ; b.eq >no_start_offset + + // we need to test the high bits from the first shadow byte + ; ldrh w7, [x5, #0] + ; rev16 w7, w7 + ; rbit w7, w7 + ; lsr x7, x7, #16 + ; lsr x7, x7, x6 + + ; cmp x1, #8 + ; b.lt >dont_fill_to_8 + ; mov x2, #8 + ; sub x6, x2, x6 + ; b >check_bits + ; dont_fill_to_8: + ; mov x6, x1 + ; check_bits: + ; mov x2, #1 + ; lsl x2, x2, x6 + ; sub x4, x2, #1 + + // if shadow_bits & size_to_test != size_to_test: fail + ; and x7, x7, x4 + ; cmp x7, x4 + ; b.ne >return_failure + + // size -= size_to_test + ; sub x1, x1, x6 + // shadow_addr += 1 (we consumed the initial byte in the above test) + ; add x5, x5, 1 + + ; no_start_offset: + // num_shadow_bytes = size / 8 + ; lsr x4, x1, #3 + ; eor x3, x3, x3 + ; sub x3, x3, #1 + + // if num_shadow_bytes < 8; then goto check_bytes; else check_8_shadow_bytes + ; check_8_shadow_bytes: + ; cmp x4, #0x8 + ; b.lt >less_than_8_shadow_bytes_remaining + ; ldr x7, [x5], #8 + ; cmp x7, x3 + ; b.ne >return_failure + ; sub x4, x4, #8 + ; sub x1, x1, #64 + ; b check_trailing_bits + ; ldrb w7, [x5], #1 + ; cmp w7, #0xff + ; b.ne >return_failure + ; sub x4, x4, #1 + ; sub x1, x1, #8 + ; b return_success + + ; and x4, x1, #7 + ; mov x2, #1 + ; lsl x2, x2, x4 + ; sub x4, x2, #1 + + ; ldrh w7, [x5, #0] + ; rev16 w7, w7 + ; rbit w7, w7 + ; lsr x7, x7, #16 + ; and x7, x7, x4 + ; cmp x7, x4 + ; b.ne >return_failure + + ; return_success: + ; mov x0, #1 + ; b >prologue + + ; return_failure: + ; mov x0, #0 + + + ; prologue: + ; ret + ); + + let blob = ops.finalize().unwrap(); + + // apple aarch64 requires MAP_JIT to allocates WX pages + #[cfg(target_vendor = "apple")] + let map_flags = MapFlags::MAP_ANON | MapFlags::MAP_PRIVATE | MapFlags::MAP_JIT; + #[cfg(not(target_vendor = "apple"))] + let map_flags = MapFlags::MAP_ANON | MapFlags::MAP_PRIVATE; + + unsafe { + let mapping = mmap::( + None, + NonZeroUsize::try_from(0x1000).unwrap(), + ProtFlags::all(), + map_flags, + None, + 0, + ) + .unwrap(); + + // on apple aarch64, WX pages can't be both writable and executable at the same time. + // pthread_jit_write_protect_np flips them from executable (1) to writable (0) + #[cfg(all(target_vendor = "apple", target_arch = "aarch64"))] + { + libc::pthread_jit_write_protect_np(0); + } + + blob.as_ptr() + .copy_to_nonoverlapping(mapping as *mut u8, blob.len()); + + #[cfg(all(target_vendor = "apple", target_arch = "aarch64"))] + { + libc::pthread_jit_write_protect_np(1); + } + self.shadow_check_func = Some(std::mem::transmute::< + *mut u8, + extern "C" fn(*const c_void, usize) -> bool, + >(mapping as *mut u8)); + } + } // https://godbolt.org/z/ah8vG8sWo /* @@ -1644,12 +2054,12 @@ impl AsanRuntime { FRIDA ASAN IMPLEMENTATION DETAILS - The format of Frida's ASAN is signficantly different from LLVM ASAN. - - In Frida ASAN, we attempt to find the lowest possible bit such that there is no mapping with that bit. That is to say, for some bit x, there is no mapping greater than + The format of Frida's ASAN is signficantly different from LLVM ASAN. + + In Frida ASAN, we attempt to find the lowest possible bit such that there is no mapping with that bit. That is to say, for some bit x, there is no mapping greater than 1 << x. This is our shadow base and is similar to Ultra compact shadow in LLVM ASAN. Unlike ASAN where 0 represents a poisoned byte and 1 represents an unpoisoned byte, in Frida-ASAN - - The reasoning for this is that new pages are zeroed, so, by default, every qword is poisoned and we must explicitly unpoison any byte. + + The reasoning for this is that new pages are zeroed, so, by default, every qword is poisoned and we must explicitly unpoison any byte. Much like LLVM ASAN, shadow bytes are qword based. This is to say that each shadow byte maps to one qword. The shadow calculation is as follows: (1ULL << shadow_bit) | (address >> 3) @@ -1999,7 +2409,7 @@ impl AsanRuntime { self.blob_check_mem_qword = Some(self.generate_shadow_check_blob(8)); self.blob_check_mem_16bytes = Some(self.generate_shadow_check_blob(16)); - self.blob_check_mem_3bytes = Some(self.generate_shadow_check_blob(3)); //the below are all possible with vector intrinsics + self.blob_check_mem_3bytes = Some(self.generate_shadow_check_blob(3)); //the below are all possible with vector intrinsics self.blob_check_mem_6bytes = Some(self.generate_shadow_check_blob(6)); self.blob_check_mem_12bytes = Some(self.generate_shadow_check_blob(12)); self.blob_check_mem_24bytes = Some(self.generate_shadow_check_blob(24)); @@ -2683,6 +3093,7 @@ impl Default for AsanRuntime { suppressed_addresses: Vec::new(), skip_ranges: Vec::new(), continue_on_error: false, + shadow_check_func: None, hooks_enabled: false, #[cfg(target_arch = "aarch64")] eh_frame: [0; ASAN_EH_FRAME_DWORD_COUNT], diff --git a/libafl_frida/src/asan/errors.rs b/libafl_frida/src/asan/errors.rs index e5bd7503d26..096b5241de4 100644 --- a/libafl_frida/src/asan/errors.rs +++ b/libafl_frida/src/asan/errors.rs @@ -24,7 +24,7 @@ use libafl::{ }; use libafl_bolts::{ ownedref::OwnedPtr, - tuples::{Handle, Handler, MatchNameRef}, + tuples::{Handle, Handled, MatchNameRef}, Named, SerdeAny, }; use serde::{Deserialize, Serialize}; diff --git a/libafl_frida/src/asan/hook_funcs.rs b/libafl_frida/src/asan/hook_funcs.rs index c589a760393..3c80a503f30 100644 --- a/libafl_frida/src/asan/hook_funcs.rs +++ b/libafl_frida/src/asan/hook_funcs.rs @@ -17,7 +17,7 @@ impl AsanRuntime { #[inline] #[allow(non_snake_case)] #[cfg(windows)] - pub fn hook_NtGdiCreateCompatibleDC(&mut self, _hdc: *const c_void) -> *mut c_void { + pub fn hook_NtGdiCreateCompatibleDC(&mut self, hdc: *const c_void) -> *mut c_void { unsafe { self.allocator_mut().alloc(8, 8) } } @@ -140,19 +140,19 @@ impl AsanRuntime { #[cfg(windows)] pub fn hook_RtlCreateHeap( &mut self, - _flags: u32, - _heap_base: *const c_void, - _reserve_size: usize, - _commit_size: usize, - _lock: *const c_void, - _parameters: *const c_void, + flags: u32, + heap_base: *const c_void, + reserve_size: usize, + commit_size: usize, + lock: *const c_void, + parameters: *const c_void, ) -> *mut c_void { - 0xc0debeef as *mut c_void + 0xc0debeef as u64 as *mut c_void } #[inline] #[allow(non_snake_case)] #[cfg(windows)] - pub fn hook_RtlDestroyHeap(&mut self, _handle: *const c_void) -> *mut c_void { + pub fn hook_RtlDestroyHeap(&mut self, handle: *const c_void) -> *mut c_void { std::ptr::null_mut() } @@ -342,7 +342,7 @@ impl AsanRuntime { } #[allow(non_snake_case)] #[cfg(windows)] - pub fn hook_LocalReAlloc(&mut self, mem: *mut c_void, _flags: u32, size: usize) -> *mut c_void { + pub fn hook_LocalReAlloc(&mut self, mem: *mut c_void, flags: u32, size: usize) -> *mut c_void { unsafe { let ret = self.allocator_mut().alloc(size, 0x8); if mem != std::ptr::null_mut() && ret != std::ptr::null_mut() { @@ -403,7 +403,7 @@ impl AsanRuntime { } #[allow(non_snake_case)] #[cfg(windows)] - pub fn hook_LocalUnlock(&mut self, _mem: *mut c_void) -> bool { + pub fn hook_LocalUnlock(&mut self, mem: *mut c_void) -> bool { log::trace!("LocalUnlock"); false } @@ -417,7 +417,7 @@ impl AsanRuntime { #[cfg(windows)] pub fn hook_LocalSize(&mut self, mem: *mut c_void) -> usize { log::trace!("LocalSize"); - self.allocator_mut().get_usable_size(mem) + unsafe { self.allocator_mut().get_usable_size(mem) } } #[allow(non_snake_case)] #[cfg(windows)] @@ -426,7 +426,7 @@ impl AsanRuntime { } #[allow(non_snake_case)] #[cfg(windows)] - pub fn hook_LocalFlags(&mut self, _mem: *mut c_void) -> u32 { + pub fn hook_LocalFlags(&mut self, mem: *mut c_void) -> u32 { 0 } @@ -448,7 +448,7 @@ impl AsanRuntime { } #[allow(non_snake_case)] #[cfg(windows)] - pub fn hook_GlobalReAlloc(&mut self, mem: *mut c_void, _flags: u32, size: usize) -> *mut c_void { + pub fn hook_GlobalReAlloc(&mut self, mem: *mut c_void, flags: u32, size: usize) -> *mut c_void { unsafe { let ret = self.allocator_mut().alloc(size, 0x8); if mem != std::ptr::null_mut() && ret != std::ptr::null_mut() { @@ -507,7 +507,7 @@ impl AsanRuntime { } #[allow(non_snake_case)] #[cfg(windows)] - pub fn hook_GlobalUnlock(&mut self, _mem: *mut c_void) -> bool { + pub fn hook_GlobalUnlock(&mut self, mem: *mut c_void) -> bool { log::trace!("GlobalUnlock"); false } @@ -521,7 +521,7 @@ impl AsanRuntime { #[cfg(windows)] pub fn hook_GlobalSize(&mut self, mem: *mut c_void) -> usize { log::trace!("GlobalSize"); - self.allocator_mut().get_usable_size(mem) + unsafe { self.allocator_mut().get_usable_size(mem) } } #[allow(non_snake_case)] #[cfg(windows)] @@ -530,7 +530,7 @@ impl AsanRuntime { } #[allow(non_snake_case)] #[cfg(windows)] - pub fn hook_GlobalFlags(&mut self, _mem: *mut c_void) -> u32 { + pub fn hook_GlobalFlags(&mut self, mem: *mut c_void) -> u32 { 0 } @@ -939,7 +939,7 @@ impl AsanRuntime { extern "system" { fn write(fd: i32, buf: *const c_void, count: usize) -> usize; } - if !self.allocator_mut().check_shadow(buf, count) { + if !(self.shadow_check_func().unwrap())(buf, count) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgWrite(( "write".to_string(), self.real_address_for_stalked(self.pc()), @@ -961,7 +961,7 @@ impl AsanRuntime { extern "system" { fn read(fd: i32, buf: *mut c_void, count: usize) -> usize; } - if !self.allocator_mut().check_shadow(buf, count) { + if !(self.shadow_check_func().unwrap())(buf, count) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( "read".to_string(), self.real_address_for_stalked(self.pc()), @@ -978,7 +978,7 @@ impl AsanRuntime { extern "system" { fn fgets(s: *mut c_void, size: u32, stream: *mut c_void) -> *mut c_void; } - if !self.allocator_mut().check_shadow(s, size as usize) { + if !(self.shadow_check_func().unwrap())(s, size as usize) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( "fgets".to_string(), self.real_address_for_stalked(self.pc()), @@ -995,7 +995,7 @@ impl AsanRuntime { extern "system" { fn memcmp(s1: *const c_void, s2: *const c_void, n: usize) -> i32; } - if !self.allocator_mut().check_shadow(s1, n) { + if !(self.shadow_check_func().unwrap())(s1, n) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( "memcmp".to_string(), self.real_address_for_stalked(self.pc()), @@ -1004,7 +1004,7 @@ impl AsanRuntime { Backtrace::new(), ))); } - if !self.allocator_mut().check_shadow(s2, n) { + if !(self.shadow_check_func().unwrap())(s2, n) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( "memcmp".to_string(), self.real_address_for_stalked(self.pc()), @@ -1021,7 +1021,7 @@ impl AsanRuntime { extern "system" { fn memcpy(dest: *mut c_void, src: *const c_void, n: usize) -> *mut c_void; } - if !self.allocator_mut().check_shadow(dest, n) { + if !(self.shadow_check_func().unwrap())(dest, n) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgWrite(( "memcpy".to_string(), self.real_address_for_stalked(self.pc()), @@ -1030,7 +1030,7 @@ impl AsanRuntime { Backtrace::new(), ))); } - if !self.allocator_mut().check_shadow(src, n) { + if !(self.shadow_check_func().unwrap())(src, n) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( "memcpy".to_string(), self.real_address_for_stalked(self.pc()), @@ -1048,7 +1048,7 @@ impl AsanRuntime { extern "system" { fn mempcpy(dest: *mut c_void, src: *const c_void, n: usize) -> *mut c_void; } - if !self.allocator_mut().check_shadow(dest, n) { + if !(self.shadow_check_func().unwrap())(dest, n) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgWrite(( "mempcpy".to_string(), self.real_address_for_stalked(self.pc()), @@ -1057,7 +1057,7 @@ impl AsanRuntime { Backtrace::new(), ))); } - if !self.allocator_mut().check_shadow(src, n) { + if !(self.shadow_check_func().unwrap())(src, n) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( "mempcpy".to_string(), self.real_address_for_stalked(self.pc()), @@ -1074,7 +1074,7 @@ impl AsanRuntime { extern "system" { fn memmove(dest: *mut c_void, src: *const c_void, n: usize) -> *mut c_void; } - if !self.allocator_mut().check_shadow(dest, n) { + if !(self.shadow_check_func().unwrap())(dest, n) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgWrite(( "memmove".to_string(), self.real_address_for_stalked(self.pc()), @@ -1083,7 +1083,7 @@ impl AsanRuntime { Backtrace::new(), ))); } - if !self.allocator_mut().check_shadow(src, n) { + if !(self.shadow_check_func().unwrap())(src, n) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( "memmove".to_string(), self.real_address_for_stalked(self.pc()), @@ -1103,7 +1103,7 @@ impl AsanRuntime { extern "system" { fn memset(dest: *mut c_void, c: i32, n: usize) -> *mut c_void; } - if !self.allocator_mut().check_shadow(dest, n) { + if !(self.shadow_check_func().unwrap())(dest, n) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgWrite(( "memset".to_string(), self.real_address_for_stalked(self.pc()), @@ -1120,7 +1120,7 @@ impl AsanRuntime { extern "system" { fn memchr(s: *mut c_void, c: i32, n: usize) -> *mut c_void; } - if !self.allocator_mut().check_shadow(s, n) { + if !(self.shadow_check_func().unwrap())(s, n) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( "memchr".to_string(), self.real_address_for_stalked(self.pc()), @@ -1138,7 +1138,7 @@ impl AsanRuntime { extern "system" { fn memrchr(s: *mut c_void, c: i32, n: usize) -> *mut c_void; } - if !self.allocator_mut().check_shadow(s, n) { + if !(self.shadow_check_func().unwrap())(s, n) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( "memrchr".to_string(), self.real_address_for_stalked(self.pc()), @@ -1166,7 +1166,7 @@ impl AsanRuntime { needlelen: usize, ) -> *mut c_void; } - if !self.allocator_mut().check_shadow(haystack, haystacklen) { + if !(self.shadow_check_func().unwrap())(haystack, haystacklen) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( "memmem".to_string(), self.real_address_for_stalked(self.pc()), @@ -1175,7 +1175,7 @@ impl AsanRuntime { Backtrace::new(), ))); } - if !self.allocator_mut().check_shadow(needle, needlelen) { + if !(self.shadow_check_func().unwrap())(needle, needlelen) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( "memmem".to_string(), self.real_address_for_stalked(self.pc()), @@ -1193,7 +1193,7 @@ impl AsanRuntime { extern "system" { fn bzero(s: *mut c_void, n: usize) -> usize; } - if !self.allocator_mut().check_shadow(s, n) { + if !(self.shadow_check_func().unwrap())(s, n) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgWrite(( "bzero".to_string(), self.real_address_for_stalked(self.pc()), @@ -1211,7 +1211,7 @@ impl AsanRuntime { extern "system" { fn explicit_bzero(s: *mut c_void, n: usize) -> usize; } - if !self.allocator_mut().check_shadow(s, n) { + if !(self.shadow_check_func().unwrap())(s, n) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgWrite(( "explicit_bzero".to_string(), self.real_address_for_stalked(self.pc()), @@ -1229,7 +1229,7 @@ impl AsanRuntime { extern "system" { fn bcmp(s1: *const c_void, s2: *const c_void, n: usize) -> i32; } - if !self.allocator_mut().check_shadow(s1, n) { + if !(self.shadow_check_func().unwrap())(s1, n) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( "bcmp".to_string(), self.real_address_for_stalked(self.pc()), @@ -1238,7 +1238,7 @@ impl AsanRuntime { Backtrace::new(), ))); } - if !self.allocator_mut().check_shadow(s2, n) { + if !(self.shadow_check_func().unwrap())(s2, n) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( "bcmp".to_string(), self.real_address_for_stalked(self.pc()), @@ -1256,7 +1256,7 @@ impl AsanRuntime { fn strchr(s: *mut c_char, c: i32) -> *mut c_char; fn strlen(s: *const c_char) -> usize; } - if !self.allocator_mut().check_shadow(s as *const c_void, unsafe { strlen(s) }) { + if !(self.shadow_check_func().unwrap())(s as *const c_void, unsafe { strlen(s) }) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( "strchr".to_string(), self.real_address_for_stalked(self.pc()), @@ -1274,7 +1274,7 @@ impl AsanRuntime { fn strrchr(s: *mut c_char, c: i32) -> *mut c_char; fn strlen(s: *const c_char) -> usize; } - if !self.allocator_mut().check_shadow(s as *const c_void, unsafe { strlen(s) }) { + if !(self.shadow_check_func().unwrap())(s as *const c_void, unsafe { strlen(s) }) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( "strrchr".to_string(), self.real_address_for_stalked(self.pc()), @@ -1292,7 +1292,7 @@ impl AsanRuntime { fn strcasecmp(s1: *const c_char, s2: *const c_char) -> i32; fn strlen(s: *const c_char) -> usize; } - if !self.allocator_mut().check_shadow(s1 as *const c_void, unsafe { strlen(s1) }) { + if !(self.shadow_check_func().unwrap())(s1 as *const c_void, unsafe { strlen(s1) }) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( "strcasecmp".to_string(), self.real_address_for_stalked(self.pc()), @@ -1301,7 +1301,7 @@ impl AsanRuntime { Backtrace::new(), ))); } - if !self.allocator_mut().check_shadow(s2 as *const c_void, unsafe { strlen(s2) }) { + if !(self.shadow_check_func().unwrap())(s2 as *const c_void, unsafe { strlen(s2) }) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( "strcasecmp".to_string(), self.real_address_for_stalked(self.pc()), @@ -1318,7 +1318,7 @@ impl AsanRuntime { extern "system" { fn strncasecmp(s1: *const c_char, s2: *const c_char, n: usize) -> i32; } - if !self.allocator_mut().check_shadow(s1 as *const c_void, n) { + if !(self.shadow_check_func().unwrap())(s1 as *const c_void, n) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( "strncasecmp".to_string(), self.real_address_for_stalked(self.pc()), @@ -1327,7 +1327,7 @@ impl AsanRuntime { Backtrace::new(), ))); } - if !self.allocator_mut().check_shadow(s2 as *const c_void, n) { + if !(self.shadow_check_func().unwrap())(s2 as *const c_void, n) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( "strncasecmp".to_string(), self.real_address_for_stalked(self.pc()), @@ -1345,7 +1345,7 @@ impl AsanRuntime { fn strcat(s1: *mut c_char, s2: *const c_char) -> *mut c_char; fn strlen(s: *const c_char) -> usize; } - if !self.allocator_mut().check_shadow(s1 as *const c_void, unsafe { strlen(s1) }) { + if !(self.shadow_check_func().unwrap())(s1 as *const c_void, unsafe { strlen(s1) }) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( "strcat".to_string(), self.real_address_for_stalked(self.pc()), @@ -1354,7 +1354,7 @@ impl AsanRuntime { Backtrace::new(), ))); } - if !self.allocator_mut().check_shadow(s2 as *const c_void, unsafe { strlen(s2) }) { + if !(self.shadow_check_func().unwrap())(s2 as *const c_void, unsafe { strlen(s2) }) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( "strcat".to_string(), self.real_address_for_stalked(self.pc()), @@ -1372,7 +1372,7 @@ impl AsanRuntime { fn strcmp(s1: *const c_char, s2: *const c_char) -> i32; fn strlen(s: *const c_char) -> usize; } - if !self.allocator_mut().check_shadow(s1 as *const c_void, unsafe { strlen(s1) }) { + if !(self.shadow_check_func().unwrap())(s1 as *const c_void, unsafe { strlen(s1) }) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( "strcmp".to_string(), self.real_address_for_stalked(self.pc()), @@ -1381,7 +1381,7 @@ impl AsanRuntime { Backtrace::new(), ))); } - if !self.allocator_mut().check_shadow(s2 as *const c_void, unsafe { strlen(s2) }) { + if !(self.shadow_check_func().unwrap())(s2 as *const c_void, unsafe { strlen(s2) }) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( "strcmp".to_string(), self.real_address_for_stalked(self.pc()), @@ -1399,7 +1399,7 @@ impl AsanRuntime { fn strncmp(s1: *const c_char, s2: *const c_char, n: usize) -> i32; fn strnlen(s: *const c_char, n: usize) -> usize; } - if !self.allocator_mut().check_shadow(s1 as *const c_void, unsafe { strnlen(s1, n) }) { + if !(self.shadow_check_func().unwrap())(s1 as *const c_void, unsafe { strnlen(s1, n) }) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( "strncmp".to_string(), self.real_address_for_stalked(self.pc()), @@ -1408,7 +1408,7 @@ impl AsanRuntime { Backtrace::new(), ))); } - if !self.allocator_mut().check_shadow(s2 as *const c_void, unsafe { strnlen(s2, n) }) { + if !(self.shadow_check_func().unwrap())(s2 as *const c_void, unsafe { strnlen(s2, n) }) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( "strncmp".to_string(), self.real_address_for_stalked(self.pc()), @@ -1426,7 +1426,7 @@ impl AsanRuntime { fn strcpy(dest: *mut c_char, src: *const c_char) -> *mut c_char; fn strlen(s: *const c_char) -> usize; } - if !self.allocator_mut().check_shadow(dest as *const c_void, unsafe { strlen(src) }) { + if !(self.shadow_check_func().unwrap())(dest as *const c_void, unsafe { strlen(src) }) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgWrite(( "strcpy".to_string(), self.real_address_for_stalked(self.pc()), @@ -1435,7 +1435,7 @@ impl AsanRuntime { Backtrace::new(), ))); } - if !self.allocator_mut().check_shadow(src as *const c_void, unsafe { strlen(src) }) { + if !(self.shadow_check_func().unwrap())(src as *const c_void, unsafe { strlen(src) }) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( "strcpy".to_string(), self.real_address_for_stalked(self.pc()), @@ -1452,7 +1452,7 @@ impl AsanRuntime { extern "system" { fn strncpy(dest: *mut c_char, src: *const c_char, n: usize) -> *mut c_char; } - if !self.allocator_mut().check_shadow(dest as *const c_void, n) { + if !(self.shadow_check_func().unwrap())(dest as *const c_void, n) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgWrite(( "strncpy".to_string(), self.real_address_for_stalked(self.pc()), @@ -1461,7 +1461,7 @@ impl AsanRuntime { Backtrace::new(), ))); } - if !self.allocator_mut().check_shadow(src as *const c_void, n) { + if !(self.shadow_check_func().unwrap())(src as *const c_void, n) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( "strncpy".to_string(), self.real_address_for_stalked(self.pc()), @@ -1479,7 +1479,7 @@ impl AsanRuntime { fn stpcpy(dest: *mut c_char, src: *const c_char) -> *mut c_char; fn strlen(s: *const c_char) -> usize; } - if !self.allocator_mut().check_shadow(dest as *const c_void, unsafe { strlen(src) }) { + if !(self.shadow_check_func().unwrap())(dest as *const c_void, unsafe { strlen(src) }) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgWrite(( "stpcpy".to_string(), self.real_address_for_stalked(self.pc()), @@ -1488,7 +1488,7 @@ impl AsanRuntime { Backtrace::new(), ))); } - if !self.allocator_mut().check_shadow(src as *const c_void, unsafe { strlen(src) }) { + if !(self.shadow_check_func().unwrap())(src as *const c_void, unsafe { strlen(src) }) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( "stpcpy".to_string(), self.real_address_for_stalked(self.pc()), @@ -1512,7 +1512,7 @@ impl AsanRuntime { fn strcpy(dest: *mut c_char, src: *const c_char) -> *mut c_char; } let size = unsafe { strlen(s) }; - if !self.allocator_mut().check_shadow(s as *const c_void, size) { + if !(self.shadow_check_func().unwrap())(s as *const c_void, size) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( "strdup".to_string(), self.real_address_for_stalked(self.pc()), @@ -1535,7 +1535,7 @@ impl AsanRuntime { fn strlen(s: *const c_char) -> usize; } let size = unsafe { strlen(s) }; - if !self.allocator_mut().check_shadow(s as *const c_void, size) { + if !(self.shadow_check_func().unwrap())(s as *const c_void, size) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( "strlen".to_string(), self.real_address_for_stalked(self.pc()), @@ -1553,7 +1553,7 @@ impl AsanRuntime { fn strnlen(s: *const c_char, n: usize) -> usize; } let size = unsafe { strnlen(s, n) }; - if !self.allocator_mut().check_shadow(s as *const c_void, size) { + if !(self.shadow_check_func().unwrap())(s as *const c_void, size) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( "strnlen".to_string(), self.real_address_for_stalked(self.pc()), @@ -1571,7 +1571,7 @@ impl AsanRuntime { fn strstr(haystack: *const c_char, needle: *const c_char) -> *mut c_char; fn strlen(s: *const c_char) -> usize; } - if !self.allocator_mut().check_shadow(haystack as *const c_void, unsafe { + if !(self.shadow_check_func().unwrap())(haystack as *const c_void, unsafe { strlen(haystack) }) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( @@ -1607,7 +1607,7 @@ impl AsanRuntime { fn strcasestr(haystack: *const c_char, needle: *const c_char) -> *mut c_char; fn strlen(s: *const c_char) -> usize; } - if !self.allocator_mut().check_shadow(haystack as *const c_void, unsafe { + if !(self.shadow_check_func().unwrap())(haystack as *const c_void, unsafe { strlen(haystack) }) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( @@ -1639,7 +1639,7 @@ impl AsanRuntime { fn atoi(s: *const c_char) -> i32; fn strlen(s: *const c_char) -> usize; } - if !self.allocator_mut().check_shadow(s as *const c_void, unsafe { strlen(s) }) { + if !(self.shadow_check_func().unwrap())(s as *const c_void, unsafe { strlen(s) }) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( "atoi".to_string(), self.real_address_for_stalked(self.pc()), @@ -1658,7 +1658,7 @@ impl AsanRuntime { fn atol(s: *const c_char) -> i32; fn strlen(s: *const c_char) -> usize; } - if !self.allocator_mut().check_shadow(s as *const c_void, unsafe { strlen(s) }) { + if !(self.shadow_check_func().unwrap())(s as *const c_void, unsafe { strlen(s) }) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( "atol".to_string(), self.real_address_for_stalked(self.pc()), @@ -1677,7 +1677,7 @@ impl AsanRuntime { fn atoll(s: *const c_char) -> i64; fn strlen(s: *const c_char) -> usize; } - if !self.allocator_mut().check_shadow(s as *const c_void, unsafe { strlen(s) }) { + if !(self.shadow_check_func().unwrap())(s as *const c_void, unsafe { strlen(s) }) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( "atoll".to_string(), self.real_address_for_stalked(self.pc()), @@ -1696,7 +1696,7 @@ impl AsanRuntime { fn wcslen(s: *const wchar_t) -> usize; } let size = unsafe { wcslen(s) }; - if !self.allocator_mut().check_shadow(s as *const c_void, (size + 1) * 2) { + if !(self.shadow_check_func().unwrap())(s as *const c_void, (size + 1) * 2) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( "wcslen".to_string(), self.real_address_for_stalked(self.pc()), @@ -1715,7 +1715,7 @@ impl AsanRuntime { fn wcscpy(dest: *mut wchar_t, src: *const wchar_t) -> *mut wchar_t; fn wcslen(s: *const wchar_t) -> usize; } - if !self.allocator_mut().check_shadow(dest as *const c_void, unsafe { + if !(self.shadow_check_func().unwrap())(dest as *const c_void, unsafe { (wcslen(src) + 1) * 2 }) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgWrite(( @@ -1726,7 +1726,7 @@ impl AsanRuntime { Backtrace::new(), ))); } - if !self.allocator_mut().check_shadow(src as *const c_void, unsafe { + if !(self.shadow_check_func().unwrap())(src as *const c_void, unsafe { (wcslen(src) + 1) * 2 }) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( @@ -1747,7 +1747,7 @@ impl AsanRuntime { fn wcscmp(s1: *const wchar_t, s2: *const wchar_t) -> i32; fn wcslen(s: *const wchar_t) -> usize; } - if !self.allocator_mut().check_shadow(s1 as *const c_void, unsafe { + if !(self.shadow_check_func().unwrap())(s1 as *const c_void, unsafe { (wcslen(s1) + 1) * 2 }) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( @@ -1758,7 +1758,7 @@ impl AsanRuntime { Backtrace::new(), ))); } - if !self.allocator_mut().check_shadow(s2 as *const c_void, unsafe { + if !(self.shadow_check_func().unwrap())(s2 as *const c_void, unsafe { (wcslen(s2) + 1) * 2 }) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgRead(( @@ -1778,7 +1778,7 @@ impl AsanRuntime { extern "system" { fn memset_pattern4(s: *mut c_void, p4: *const c_void, n: usize); } - if !self.allocator_mut().check_shadow(s, n) { + if !(self.shadow_check_func().unwrap())(s, n) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgWrite(( "memset_pattern4".to_string(), self.real_address_for_stalked(self.pc()), @@ -1787,7 +1787,7 @@ impl AsanRuntime { Backtrace::new(), ))); } - if !self.allocator_mut().check_shadow(p4, n / 4) { + if !(self.shadow_check_func().unwrap())(p4, n / 4) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgWrite(( "memset_pattern4".to_string(), self.real_address_for_stalked(self.pc()), @@ -1805,7 +1805,7 @@ impl AsanRuntime { extern "system" { fn memset_pattern8(s: *mut c_void, p8: *const c_void, n: usize); } - if !self.allocator_mut().check_shadow(s, n) { + if !(self.shadow_check_func().unwrap())(s, n) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgWrite(( "memset_pattern8".to_string(), self.real_address_for_stalked(self.pc()), @@ -1814,7 +1814,7 @@ impl AsanRuntime { Backtrace::new(), ))); } - if !self.allocator_mut().check_shadow(p8, n / 8) { + if !(self.shadow_check_func().unwrap())(p8, n / 8) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgWrite(( "memset_pattern8".to_string(), self.real_address_for_stalked(self.pc()), @@ -1832,7 +1832,7 @@ impl AsanRuntime { extern "system" { fn memset_pattern16(s: *mut c_void, p16: *const c_void, n: usize); } - if !self.allocator_mut().check_shadow(s, n) { + if !(self.shadow_check_func().unwrap())(s, n) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgWrite(( "memset_pattern16".to_string(), self.real_address_for_stalked(self.pc()), @@ -1841,7 +1841,7 @@ impl AsanRuntime { Backtrace::new(), ))); } - if !self.allocator_mut().check_shadow(p16, n / 16) { + if !(self.shadow_check_func().unwrap())(p16, n / 16) { AsanErrors::get_mut_blocking().report_error(AsanError::BadFuncArgWrite(( "memset_pattern16".to_string(), self.real_address_for_stalked(self.pc()), diff --git a/libafl_frida/src/executor.rs b/libafl_frida/src/executor.rs index 73d0ec8fee4..197a783eae8 100644 --- a/libafl_frida/src/executor.rs +++ b/libafl_frida/src/executor.rs @@ -108,9 +108,9 @@ where self.stalker.deactivate(); } - #[cfg(not(test))] + #[cfg(all(unix, not(test)))] unsafe { - if !AsanErrors::get_mut_blocking().is_empty() { + if !AsanErrors::get_mut_blocking().borrow().is_empty() { log::error!("Crashing target as it had ASan errors"); libc::raise(libc::SIGABRT); #[cfg(windows)] diff --git a/libafl_frida/src/helper.rs b/libafl_frida/src/helper.rs index 190d6e30e70..ba3050540d5 100644 --- a/libafl_frida/src/helper.rs +++ b/libafl_frida/src/helper.rs @@ -543,24 +543,27 @@ where #[cfg(target_arch = "x86_64")] if let Some(rt) = runtimes.match_first_type_mut::() { - if let Some(call_target) = rt.is_interesting(decoder, instr) { + if let Some((call_target, is_jmp)) = rt.is_interesting(decoder, instr) { rt.emit_callout( call_target, + is_jmp, &instruction, output.writer(), runtimes_unborrowed.clone(), ); - keep_instr = false; + keep_instr = false; //we keep the instruction in the emit if needed } } #[cfg(target_arch = "aarch64")] if let Some(rt) = runtimes.match_first_type_mut::() { - if let Some((call_target, is_reg)) = rt.is_interesting(decoder, instr) { + if let Some((call_target, is_reg, no_link)) = rt.is_interesting(decoder, instr) + { rt.emit_callout( call_target, &instruction, is_reg, + no_link, output.writer(), runtimes_unborrowed.clone(), ); diff --git a/libafl_frida/src/hook_rt.rs b/libafl_frida/src/hook_rt.rs index d0a6606d0dd..17cede140a4 100644 --- a/libafl_frida/src/hook_rt.rs +++ b/libafl_frida/src/hook_rt.rs @@ -1,10 +1,7 @@ //! Functionality implementing hooks for instrumented code -use std::{ - cell::RefCell, - collections::HashMap, - ptr::addr_of, - rc::Rc, -}; +#[cfg(target_arch = "x86_64")] +use std::ptr::read_unaligned; +use std::{cell::RefCell, collections::HashMap, ptr::addr_of, rc::Rc}; #[cfg(target_arch = "aarch64")] use frida_gum::instruction_writer::{ @@ -30,27 +27,16 @@ use crate::{ utils::frida_to_cs, }; -#[cfg(target_arch = "x86_64")] -use std::ptr::read_unaligned; - /* LibAFL hook_rt design: -The objective of this runtime is to move away from using Interceptor for hooking and move to -something that hooks during the stalk. The way this does this is different for direct and indirect -branches. +The objective of this runtime is to move away from using Interceptor for hooking and move to something that hooks during the stalk. The way this does this is different for direct and indirect branches. -For direct branches, the hooking is easy. We simply check if the branch target is hooked. If it is, -run the hooked function. If it is not, then continue as per normal. If it is hooked, we chaining -return to return the caller. +For direct branches, the hooking is easy. We simply check if the branch target is hooked. If it is, run the hooked function. If it is not, then continue as per normal. If it is hooked, we chaining return to return the caller. -For indirect branches (i.e., jmp rax/blr x16), it is harder as the branch target is difficult to -know at block-compile time. In the case of indirect branches, we check the register during runtime. -If the value of the register is a hooked function then run the hooked function in the callout and -set HookRuntime::hooked = 1. If it is not then set HookRuntime::hooked = 0. +For indirect branches (i.e., jmp rax/blr x16), it is harder as the branch target is difficult to know at block-compile time. In the case of indirect branches, we check the register during runtime. If the value of the register is a hooked function then run the hooked function in the callout and set HookRuntime::hooked = 1. If it is not then set HookRuntime::hooked = 0. -From here, we either chaining return if HookRuntime::hooked == 1 or continue on to the next block -via a keeping the instruction if HookRuntime::hooked = 0 +From here, we either chaining return if HookRuntime::hooked == 1 or continue on to the next block via a keeping the instruction if HookRuntime::hooked = 0 */ @@ -98,15 +84,11 @@ impl FridaRuntime for HookRuntime { } } -/// The type of a call instruction #[derive(Debug)] #[cfg(target_arch = "x86_64")] pub enum CallType { - /// Call an immediate address Imm(usize), - /// Call through a register Reg(X86Register), - /// Call through a memory dereference Mem((X86Register, X86Register, u8, i32)), //this is the return type from operand_details } @@ -133,7 +115,7 @@ impl HookRuntime { /// Determine if this instruction is interesting for the purposes of hooking #[inline] #[cfg(target_arch = "x86_64")] - pub fn is_interesting(&self, decoder: InstDecoder, instr: &Insn) -> Option { + pub fn is_interesting(&self, decoder: InstDecoder, instr: &Insn) -> Option<(CallType, bool)> { let result = frida_to_cs(decoder, instr); if let Err(e) = result { @@ -153,20 +135,24 @@ impl HookRuntime { if let Some((reg, index_reg, scale, disp)) = mem_details { if reg == X86Register::Rip { - //rip relative loads are from the end of the instruction - return Some(CallType::Mem(( - reg, - index_reg, - scale, - disp + instr.len() as i32, - ))); + //rip relative loads are from the end of the instruction, so add the instruction size to disp + return Some(( + CallType::Mem((reg, index_reg, scale, disp + instr.len() as i32)), + instruction.opcode() == Opcode::JMP, + )); } - return Some(CallType::Mem((reg, index_reg, scale, disp))); + return Some(( + CallType::Mem((reg, index_reg, scale, disp)), + instruction.opcode() == Opcode::JMP, + )); } } else { match instruction.operand(0) { Operand::Register(reg_spec) => { - return Some(CallType::Reg(writer_register(reg_spec))); + return Some(( + CallType::Reg(writer_register(reg_spec)), + instruction.opcode() == Opcode::JMP, + )); } Operand::ImmediateI32(imm) => { //https://www.felixcloutier.com/x86/call @@ -174,7 +160,7 @@ impl HookRuntime { if !self.hooks.contains_key(&target) { return None; } - return Some(CallType::Imm(target)); + return Some((CallType::Imm(target), instruction.opcode() == Opcode::JMP)); } _ => panic!("Invalid call/jmp instructions"), } @@ -185,7 +171,11 @@ impl HookRuntime { #[inline] #[cfg(target_arch = "aarch64")] - pub fn is_interesting(&self, decoder: InstDecoder, instr: &Insn) -> Option<(usize, bool)> { + pub fn is_interesting( + &self, + decoder: InstDecoder, + instr: &Insn, + ) -> Option<(usize, bool, bool)> { let result = frida_to_cs(decoder, instr); if let Err(e) = result { @@ -208,8 +198,9 @@ impl HookRuntime { }; //we could probably introduce some kind of speculative backpatching as it is unlikely that if it is hooked the first time that it ever hooks again - - return Some((reg_num as usize, true)); //the reg should always be checked + //we only need chaining return if its a branch register + return Some((reg_num as usize, true, instruction.opcode == Opcode::BR)); + //the reg should always be checked } Opcode::BL | Opcode::B => { let call_address = if let Operand::PCOffset(off) = instruction.operands[0] { @@ -218,14 +209,14 @@ impl HookRuntime { panic!( "Invalid instruction - opcode: {:?}, operands: {:?}", instruction.opcode, instruction.operands - ); //impossible to have b/bl with a PCOffset + ); //impossible to have b/bl without a PCOffset }; if !self.hooks.contains_key(&call_address) { return None; } - - return Some((call_address, false)); + //we only need chaining return if its a branch + return Some((call_address, false, instruction.opcode == Opcode::B)); } _ => { @@ -240,6 +231,7 @@ impl HookRuntime { pub fn emit_callout( &mut self, call_type: CallType, + is_jmp: bool, insn: &Instruction, writer: X86InstructionWriter, runtimes: Rc>, @@ -248,6 +240,7 @@ impl HookRuntime { log::trace!("call: {:?}", call_type); let hooked_address = addr_of!(self.hooked) as u64; let rip = insn.instr().address(); + let next_insn = rip + insn.instr().len() as u64; let is_imm = if let CallType::Imm(_) = call_type { //log::trace!("needs return at {:x}", address); true @@ -308,7 +301,18 @@ impl HookRuntime { //if we are here we did not hook writer.put_pop_reg(X86Register::Rdi); writer.put_add_reg_imm(X86Register::Rsp, frida_gum_sys::GUM_RED_ZONE_SIZE as isize); - insn.put_chaining_return(); //we hooked, run the chaining return + if !is_jmp { + //if its a call return to the previous block + //this is super hacky but works because we just emulate a call/ret by pushing our address of the next insn on to the stack + //rcx is caller saved on both unix and windows + writer.put_mov_reg_address(X86Register::Rcx, next_insn); + writer.put_push_reg(X86Register::Rcx); + } + + // if its a jmp then its a tail call. + // in the case of a tail call, we can just chaining return because the first address on the stack (i.e., at rsp) should be the return address. + + insn.put_chaining_return(); writer.put_label(not_hooked_label_id); //we did not hook, normally run the function @@ -317,6 +321,14 @@ impl HookRuntime { insn.keep(); } else { + if !is_jmp { + //if its a call return to the previous block + //this is super hacky but works because we just emulate a call/ret by pushing our address of the next insn on to the stack + //rcx is caller saved on both unix and windows + writer.put_mov_reg_address(X86Register::Rcx, next_insn); + writer.put_push_reg(X86Register::Rcx); + } + insn.put_chaining_return(); } } @@ -328,10 +340,12 @@ impl HookRuntime { address_or_reg: usize, insn: &Instruction, is_reg: bool, + no_link: bool, writer: Aarch64InstructionWriter, runtimes: Rc>, ) { let hooked_address = addr_of!(self.hooked) as u64; + let next_insn = insn.instr().address() + insn.instr().len() as u64; log::trace!("emit_callout: {:x}", address_or_reg); insn.put_callout(move |context| { if !is_reg { @@ -395,7 +409,13 @@ impl HookRuntime { 16 + i64::from(redzone_size), IndexMode::PostAdjust, ); - //then we chaining return because we hooked + + if !no_link { + //if we do a linked branch (i.e. BLR) then put the return address into x30 and return + writer.put_ldr_reg_u64(Aarch64Register::Lr, next_insn); + } + + //if we don't have a link then we can just chaining return as it is a tail call insn.put_chaining_return(); writer.put_label(not_hooked_label_id); @@ -411,6 +431,12 @@ impl HookRuntime { insn.keep(); //the keep will dispatch to the next block } else { //Opcode::B/Opcode::BL + if !no_link { + //if we do a linked branch (i.e. BL) then put the return address into x30 and return + writer.put_ldr_reg_u64(Aarch64Register::Lr, next_insn); + } + + //if we don't have a link then we can just chaining return as it is a tail call insn.put_chaining_return(); } } diff --git a/libafl_frida/src/lib.rs b/libafl_frida/src/lib.rs index 81d53732871..43db387e461 100644 --- a/libafl_frida/src/lib.rs +++ b/libafl_frida/src/lib.rs @@ -353,7 +353,7 @@ mod tests { corpus::{Corpus, InMemoryCorpus, Testcase}, events::NopEventManager, executors::{ExitKind, InProcessExecutor}, - feedback_and_fast, feedback_or_fast, + feedback_or_fast, feedbacks::ConstFeedback, inputs::{BytesInput, HasTargetBytes}, mutators::{mutations::BitFlipMutator, StdScheduledMutator}, @@ -369,7 +369,7 @@ mod tests { use crate::{ asan::{ asan_rt::AsanRuntime, - errors::{AsanErrorsFeedback, AsanErrorsObserver, AsanErrors}, + errors::{AsanErrors, AsanErrorsFeedback, AsanErrorsObserver}, }, coverage_rt::CoverageRuntime, executor::FridaInProcessExecutor, @@ -456,13 +456,7 @@ mod tests { let asan_obs = AsanErrorsObserver::from_static_asan_errors(); // Feedbacks to recognize an input as solution - let mut objective = feedback_or_fast!( - // true enables the AsanErrorFeedback - feedback_and_fast!( - ConstFeedback::from(true), - AsanErrorsFeedback::new(&asan_obs) - ) - ); + let mut objective = feedback_or_fast!(AsanErrorsFeedback::new(&asan_obs)); let mut state = StdState::new( rand, diff --git a/libafl_frida/src/utils.rs b/libafl_frida/src/utils.rs index 459cfcd1deb..c25840c0afb 100644 --- a/libafl_frida/src/utils.rs +++ b/libafl_frida/src/utils.rs @@ -159,11 +159,9 @@ const X86_64_REGS: [(RegSpec, X86Register); 34] = [ (RegSpec::rip(), X86Register::Rip), ]; - /// Get the value of a register given a context #[cfg(target_arch = "x86_64")] -pub fn get_register(context: &CpuContext, reg: X86Register) -> u64 -{ +pub fn get_register(context: &CpuContext, reg: X86Register) -> u64 { match reg { X86Register::Rax => context.rax(), X86Register::Rbx => context.rbx(), diff --git a/libafl_qemu/Cargo.toml b/libafl_qemu/Cargo.toml index 3042f4537e8..8ccf6d50b74 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 e9065387e16..904fabb1065 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 973c519dbed..69d33590110 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 bf676273c59..84f0804b173 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 62e7afd48c1..02f5cb7325e 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 52749d91025..7fa109f2e43 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 e59480c6b1a..9a6218ce683 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 63722c5c339..ebe2891be9f 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 be876331263..8e1cd142f46 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 @@ -12055,8 +12055,7 @@ extern "C" { } pub const libafl_exit_reason_kind_INTERNAL: libafl_exit_reason_kind = libafl_exit_reason_kind(0); pub const libafl_exit_reason_kind_BREAKPOINT: libafl_exit_reason_kind = libafl_exit_reason_kind(1); -pub const libafl_exit_reason_kind_SYNC_BACKDOOR: libafl_exit_reason_kind = - libafl_exit_reason_kind(2); +pub const libafl_exit_reason_kind_SYNC_EXIT: libafl_exit_reason_kind = libafl_exit_reason_kind(2); impl ::std::ops::BitOr for libafl_exit_reason_kind { type Output = Self; #[inline] @@ -12119,21 +12118,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 +12196,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 +12237,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 +13886,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 b7dbc824416..179a586e02d 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 023cb34da5a..dec882c8292 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 42b6d8d24f8..19d799af17a 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 bd4ff32cd9f..e9215f3fd30 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 ca91b8ddf6a..2df88e0b754 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 4810b335084..17930ea213b 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 53dd4c1a912..39b50848fb8 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 bdeddc0f75a..f32dc200e66 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 3535b76b529..b1ccc5e43f1 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) + >>::usable_at_runtime( + cmd, + ) } Command::LoadCommand(cmd) => { - >>::usable_at_runtime(cmd) + >>::usable_at_runtime( + cmd, + ) } Command::InputCommand(cmd) => { - >>::usable_at_runtime(cmd) + >>::usable_at_runtime( + cmd, + ) } Command::StartCommand(cmd) => { - >>::usable_at_runtime(cmd) + >>::usable_at_runtime( + cmd, + ) } Command::EndCommand(cmd) => { - >>::usable_at_runtime(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, ) } + #[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 91d9a71cb42..0006d8c08e5 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,1153 +226,168 @@ where input_location: OnceCell, } -impl StdEmuExitHandler -where - SM: IsSnapshotManager, -{ - pub fn new(snapshot_manager: SM) -> Self { - Self { - snapshot_manager: RefCell::new(snapshot_manager), - snapshot_id: OnceCell::new(), - input_location: OnceCell::new(), - } - } - - pub fn set_input_location(&self, input_location: InputLocation) -> Result<(), InputLocation> { - self.input_location.set(input_location) - } - - pub fn set_snapshot_id(&self, snapshot_id: SnapshotId) -> Result<(), SnapshotId> { - self.snapshot_id.set(snapshot_id) - } - - pub fn snapshot_id(&self) -> Option { - Some(*self.snapshot_id.get()?) - } - - pub fn snapshot_manager_borrow(&self) -> Ref { - self.snapshot_manager.borrow() - } - - pub fn snapshot_manager_borrow_mut(&self) -> RefMut { - self.snapshot_manager.borrow_mut() - } -} - -// TODO: replace handlers with generics to permit compile-time customization of handlers -impl EmuExitHandler for StdEmuExitHandler +impl StdEmulatorExitHandler where - SM: IsSnapshotManager, - QT: QemuHelperTuple + StdInstrumentationFilter + Debug, - S: State + HasExecutions, - S::Input: HasTargetBytes, -{ - fn try_put_input( - emu: &Emulator, - qemu_executor_state: &mut QemuExecutorState, - input: &S::Input, - ) { - let exit_handler = emu.state().exit_handler.borrow(); - - if let Some(input_location) = exit_handler.input_location.get() { - let input_command = - InputCommand::new(input_location.mem_chunk.clone(), input_location.cpu.clone()); - input_command - .run(emu, qemu_executor_state, input, input_location.ret_register) - .unwrap(); - } - } - - fn handle( - emu: &Emulator, - exit_reason: Result, - qemu_executor_state: &mut QemuExecutorState, - input: &S::Input, - ) -> Result { - 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 => { - 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)); - } - _ => 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); - } - QemuShutdownCause::GuestPanic => { - return Ok(InnerHandlerResult::EndOfRun(ExitKind::Crash)) - } - _ => panic!("Unhandled QEMU shutdown cause: {shutdown_cause:?}."), - }, - EmuExitReason::Breakpoint(bp) => (bp.trigger(qemu).cloned(), None), - EmuExitReason::SyncBackdoor(sync_backdoor) => { - let command = sync_backdoor.command().clone(); - (Some(command), Some(sync_backdoor.ret_reg())) - } - }; - - // manually drop ref cell here to avoid keeping it alive in cmd. - drop(exit_handler); - - if let Some(cmd) = command { - cmd.run(emu, qemu_executor_state, input, ret_reg) - } else { - Ok(InnerHandlerResult::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 MemAccessInfo { - fn from(oi: libafl_qemu_sys::MemOpIdx) -> Self { - Self { oi } - } -} - -#[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 { - 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}") - // } - } - } -} - -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}") - } - } - } -} - -#[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, -{ - exit_handler: RefCell, - breakpoints: RefCell>, - _phantom: PhantomData<(QT, S)>, -} - -#[derive(Clone, Debug)] -pub struct Emulator -where - QT: QemuHelperTuple, - S: State + HasExecutions, - E: EmuExitHandler, -{ - state: ptr::NonNull>, - 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) + SM: IsSnapshotManager, +{ + pub fn new(snapshot_manager: SM) -> Self { + Self { + snapshot_manager: RefCell::new(snapshot_manager), + snapshot_id: OnceCell::new(), + input_location: OnceCell::new(), } } - #[must_use] - pub fn remove_hook(&self, id: impl HookId, invalidate_block: bool) -> bool { - id.remove(invalidate_block) + pub fn set_input_location(&self, input_location: InputLocation) -> Result<(), InputLocation> { + self.input_location.set(input_location) } - #[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)) - } + pub fn set_snapshot_id(&self, snapshot_id: SnapshotId) -> Result<(), SnapshotId> { + self.snapshot_id.set(snapshot_id) } - #[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) - } + pub fn snapshot_id(&self) -> Option { + Some(*self.snapshot_id.get()?) } - #[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) - } + pub fn snapshot_manager_borrow(&self) -> Ref { + self.snapshot_manager.borrow() } - /// `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) - } + pub fn snapshot_manager_borrow_mut(&self) -> RefMut { + self.snapshot_manager.borrow_mut() } +} - // 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) - } - } +// TODO: replace handlers with generics to permit compile-time customization of handlers +impl EmulatorExitHandler for StdEmulatorExitHandler +where + SM: IsSnapshotManager, + QT: QemuHelperTuple + StdInstrumentationFilter + Debug, + S: State + HasExecutions, + S::Input: HasTargetBytes, +{ + fn qemu_pre_run( + emu: &Emulator, + qemu_executor_state: &mut QemuExecutorState, + input: &S::Input, + ) { + let exit_handler = emu.state().exit_handler.borrow(); - #[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) + if let Some(input_location) = exit_handler.input_location.get() { + let input_command = + InputCommand::new(input_location.mem_chunk.clone(), input_location.cpu); + input_command + .run(emu, qemu_executor_state, input, input_location.ret_register) + .unwrap(); } } - #[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) - } - } + fn qemu_post_run( + emu: &Emulator, + exit_reason: Result, + qemu_executor_state: &mut QemuExecutorState, + input: &S::Input, + ) -> Result, ExitHandlerError> { + let exit_handler = emu.exit_handler().borrow_mut(); + let qemu = emu.qemu(); - #[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); + let mut exit_reason = match exit_reason { + Ok(exit_reason) => exit_reason, + Err(exit_error) => match exit_error { + EmulatorExitError::UnexpectedExit => { + if let Some(snapshot_id) = exit_handler.snapshot_id.get() { + exit_handler + .snapshot_manager + .borrow_mut() + .restore(snapshot_id, qemu)?; + } + return Ok(Some(ExitHandlerResult::EndOfRun(ExitKind::Crash))); + } + _ => Err(exit_error)?, + }, + }; + + let (command, ret_reg): (Option, Option) = match &mut exit_reason { + EmulatorExitResult::QemuExit(shutdown_cause) => match shutdown_cause { + QemuShutdownCause::HostSignal(signal) => { + signal.handle(); + return Err(ExitHandlerError::UnhandledSignal(*signal)); + } + QemuShutdownCause::GuestPanic => { + return Ok(Some(ExitHandlerResult::EndOfRun(ExitKind::Crash))) + } + _ => panic!("Unhandled QEMU shutdown cause: {shutdown_cause:?}."), + }, + 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())) + } + }; + + // manually drop ref cell here to avoid keeping it alive in cmd. + drop(exit_handler); + + if let Some(cmd) = command { + cmd.run(emu, qemu_executor_state, input, ret_reg) + } else { + Ok(Some(ExitHandlerResult::ReturnToHarness(exit_reason))) } } +} - pub fn gdb_reply(&self, output: &str) { - unsafe { libafl_qemu_gdb_reply(output.as_bytes().as_ptr(), output.len()) }; +impl From for ExitHandlerError { + fn from(error: EmulatorExitError) -> Self { + ExitHandlerError::QemuExitReasonError(error) } } -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::() +impl From for ExitHandlerError { + fn from(error: CommandError) -> Self { + ExitHandlerError::CommandError(error) } +} - 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) +impl Display for EmulatorExitResult { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + 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}") + } + } } +} - 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) +impl From for EmulatorExitError { + fn from(error: CommandError) -> Self { + EmulatorExitError::CommandError(error) } +} - 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) - } +#[derive(Debug, Clone)] +pub struct EmulatorState +where + QT: QemuHelperTuple, + S: State + HasExecutions, + E: EmulatorExitHandler, +{ + exit_handler: RefCell, + breakpoints: RefCell>, + _phantom: PhantomData<(QT, S)>, +} + +#[derive(Clone, Debug)] +pub struct Emulator +where + QT: QemuHelperTuple, + S: State + HasExecutions, + E: EmulatorExitHandler, +{ + state: EmulatorState, + qemu: Qemu, } #[allow(clippy::unused_self)] @@ -1356,34 +395,28 @@ 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 4db07028dca..acbc7116583 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,250 +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.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]) { @@ -437,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 427192e9ed5..dff342547a1 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 d6cd36f76ef..2df48fa0626 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 c738e9bf53e..dab971bf219 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 df08b75f3e7..5f7f800892c 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 d40ed697da3..1bd6c194123 100644 --- a/libafl_qemu/src/helpers/calls.rs +++ b/libafl_qemu/src/helpers/calls.rs @@ -6,18 +6,18 @@ use libafl::{ inputs::{Input, UsesInput}, observers::{stacktrace::BacktraceObserver, ObserversTuple}, }; -use libafl_bolts::tuples::{Handle, Handler, MatchFirstType, MatchNameRef}; +use libafl_bolts::tuples::{Handle, Handled, MatchFirstType, MatchNameRef}; use libafl_qemu_sys::GuestAddr; use thread_local::ThreadLocal; use crate::{ capstone, - emu::ArchExtras, helpers::{ HasInstrumentationFilter, IsFilter, QemuHelper, QemuHelperTuple, QemuInstrumentationAddressRangeFilter, }, hooks::{Hook, QemuHooks}, + qemu::ArchExtras, Qemu, }; @@ -439,7 +439,7 @@ where #[derive(Debug)] pub struct OnCrashBacktraceCollector<'a> { callstack_hash: u64, - obs_ref: Handle>, + observer_handle: Handle>, } impl<'a> OnCrashBacktraceCollector<'a> { @@ -447,7 +447,7 @@ impl<'a> OnCrashBacktraceCollector<'a> { pub fn new(observer: &BacktraceObserver<'a>) -> Self { Self { callstack_hash: 0, - obs_ref: observer.handle(), + observer_handle: observer.handle(), } } @@ -511,7 +511,7 @@ where S: UsesInput, { let observer = observers - .get_mut(&self.obs_ref) + .get_mut(&self.observer_handle) .expect("A OnCrashBacktraceCollector needs a BacktraceObserver"); observer.fill_external(self.callstack_hash, exit_kind); } diff --git a/libafl_qemu/src/helpers/cmplog.rs b/libafl_qemu/src/helpers/cmplog.rs index 10290dea95a..789b40de508 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 097d4d3c4c2..a685bae2585 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 be410cb07f7..d5a0ca11305 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 2eaab7b1405..c0b616ffc5f 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 0eb544fb78f..4c9bb624b80 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 6b51ddda112..939b5310a7f 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 de3ef10c884..5a764f6fb20 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 00000000000..a94c91a8df7 --- /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 00000000000..10d5695214a --- /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 00000000000..fe5fb1eca7e --- /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 7697232dbab..691282424ee 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/forkserver.rs b/libafl_sugar/src/forkserver.rs index cc3a4d7b656..c6a2eba63de 100644 --- a/libafl_sugar/src/forkserver.rs +++ b/libafl_sugar/src/forkserver.rs @@ -25,7 +25,7 @@ use libafl_bolts::{ core_affinity::Cores, rands::StdRand, shmem::{ShMem, ShMemProvider, UnixShMemProvider}, - tuples::{tuple_list, Handler, Merge}, + tuples::{tuple_list, Handled, Merge}, AsSliceMut, }; use typed_builder::TypedBuilder; diff --git a/libafl_sugar/src/inmemory.rs b/libafl_sugar/src/inmemory.rs index 49269d1230e..7c7f8b5d20f 100644 --- a/libafl_sugar/src/inmemory.rs +++ b/libafl_sugar/src/inmemory.rs @@ -29,7 +29,7 @@ use libafl_bolts::{ ownedref::OwnedMutSlice, rands::StdRand, shmem::{ShMemProvider, StdShMemProvider}, - tuples::{tuple_list, Handler, Merge}, + tuples::{tuple_list, Handled, Merge}, AsSlice, }; use libafl_targets::{edges_map_mut_ptr, CmpLogObserver}; diff --git a/libafl_sugar/src/qemu.rs b/libafl_sugar/src/qemu.rs index a4ec440c379..c3b68160285 100644 --- a/libafl_sugar/src/qemu.rs +++ b/libafl_sugar/src/qemu.rs @@ -32,10 +32,10 @@ use libafl_bolts::{ ownedref::OwnedMutSlice, rands::StdRand, shmem::{ShMemProvider, StdShMemProvider}, - tuples::{tuple_list, Handler, Merge}, + tuples::{tuple_list, Handled, 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; diff --git a/libafl_targets/src/cmps/stages/aflpptracing.rs b/libafl_targets/src/cmps/stages/aflpptracing.rs index 56284643b09..310ab95e302 100644 --- a/libafl_targets/src/cmps/stages/aflpptracing.rs +++ b/libafl_targets/src/cmps/stages/aflpptracing.rs @@ -25,7 +25,7 @@ where TE: UsesState, { tracer_executor: TE, - cmplog_observer_ref: Option>>, + cmplog_observer_handle: Option>>, #[allow(clippy::type_complexity)] phantom: PhantomData<(EM, TE, Z)>, } @@ -67,8 +67,12 @@ where // First run with the un-mutated input let unmutated_input = state.current_input_cloned()?; - if let Some(obs_ref) = &self.cmplog_observer_ref { - if let Some(ob) = self.tracer_executor.observers_mut().get_mut(obs_ref) { + if let Some(observer_handle) = &self.cmplog_observer_handle { + if let Some(ob) = self + .tracer_executor + .observers_mut() + .get_mut(observer_handle) + { // This is not the original input, // Set it to false ob.set_original(true); @@ -97,8 +101,12 @@ where None => return Err(Error::unknown("No metadata found")), }; - if let Some(obs_ref) = &self.cmplog_observer_ref { - if let Some(ob) = self.tracer_executor.observers_mut().get_mut(obs_ref) { + if let Some(observer_handle) = &self.cmplog_observer_handle { + if let Some(ob) = self + .tracer_executor + .observers_mut() + .get_mut(observer_handle) + { // This is not the original input, // Set it to false ob.set_original(false); @@ -142,7 +150,7 @@ where /// Creates a new default stage pub fn new(tracer_executor: TE) -> Self { Self { - cmplog_observer_ref: None, + cmplog_observer_handle: None, tracer_executor, phantom: PhantomData, } @@ -151,10 +159,10 @@ where /// With cmplog observer pub fn with_cmplog_observer( tracer_executor: TE, - obs_ref: Handle>, + observer_handle: Handle>, ) -> Self { Self { - cmplog_observer_ref: Some(obs_ref), + cmplog_observer_handle: Some(observer_handle), tracer_executor, phantom: PhantomData, } diff --git a/libafl_targets/src/sancov_cmp.c b/libafl_targets/src/sancov_cmp.c index 26dfb106515..42eb6aa3ee7 100644 --- a/libafl_targets/src/sancov_cmp.c +++ b/libafl_targets/src/sancov_cmp.c @@ -6,9 +6,6 @@ #ifdef SANCOV_CMPLOG #include "cmplog.h" - #ifndef _WIN32 - #include - #endif #endif void __sanitizer_cov_trace_cmp1(uint8_t arg1, uint8_t arg2) {