diff --git a/fuzzers/fuzzbench_fork_qemu/.gitignore b/fuzzers/fuzzbench_fork_qemu/.gitignore new file mode 100644 index 0000000000..a977a2ca5b --- /dev/null +++ b/fuzzers/fuzzbench_fork_qemu/.gitignore @@ -0,0 +1 @@ +libpng-* \ No newline at end of file diff --git a/fuzzers/fuzzbench_fork_qemu/Cargo.toml b/fuzzers/fuzzbench_fork_qemu/Cargo.toml new file mode 100644 index 0000000000..102c907b17 --- /dev/null +++ b/fuzzers/fuzzbench_fork_qemu/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "fuzzbench_qemu" +version = "0.7.1" +authors = ["Andrea Fioraldi ", "Dominik Maier "] +edition = "2021" + +[features] +default = ["std"] +std = [] + +[profile.release] +debug = true + +[dependencies] +libafl = { path = "../../libafl/" } +libafl_qemu = { path = "../../libafl_qemu/", features = ["x86_64"] } +clap = { version = "3.0", features = ["default"] } +nix = "0.23" diff --git a/fuzzers/fuzzbench_fork_qemu/Makefile b/fuzzers/fuzzbench_fork_qemu/Makefile new file mode 100644 index 0000000000..a1c3e4ab6a --- /dev/null +++ b/fuzzers/fuzzbench_fork_qemu/Makefile @@ -0,0 +1,43 @@ +FUZZER_NAME="libpng_harness" +PROJECT_DIR=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) +UNAME := $(shell uname) + +PHONY: all + +all: fuzzer + +libpng-1.6.37: + wget https://deac-fra.dl.sourceforge.net/project/libpng/libpng16/1.6.37/libpng-1.6.37.tar.xz + tar -xvf libpng-1.6.37.tar.xz + +target/release/fuzzbench_qemu: src/* + cargo build --release + +libpng-1.6.37/.libs/libpng16.a: libpng-1.6.37 + cd libpng-1.6.37 && ./configure --enable-shared=no --with-pic=yes --enable-hardware-optimizations=yes + $(MAKE) -C libpng-1.6.37 + cc -c $(PROJECT_DIR)/libfuzzer_main.c + # Build the libpng harness + c++ \ + $(PROJECT_DIR)/../libfuzzer_libpng/harness.cc \ + $(PROJECT_DIR)/libpng-1.6.37/.libs/libpng16.a \ + libfuzzer_main.o \ + -I$(PROJECT_DIR)/libpng-1.6.37/ \ + -o $(FUZZER_NAME) \ + -lm -lz + +fuzzer: target/release/fuzzbench_qemu libpng-1.6.37/.libs/libpng16.a + +clean: + rm ./$(FUZZER_NAME) libfuzzer_main.o + $(MAKE) -C libpng-1.6.37 clean + +run: all + cargo run --release -- --libafl-in ../libfuzzer_libpng/corpus --libafl-out ./out ./$(FUZZER_NAME) + +short_test: all + rm -rf libafl_unix_shmem_server || true + timeout 10s cargo run --release -- --libafl-in ../libfuzzer_libpng/corpus --libafl-out ./out ./$(FUZZER_NAME) & + +test: all + timeout 60s cargo run --release -- --libafl-in ../libfuzzer_libpng/corpus --libafl-out ./out ./$(FUZZER_NAME) & diff --git a/fuzzers/fuzzbench_fork_qemu/libfuzzer_main.c b/fuzzers/fuzzbench_fork_qemu/libfuzzer_main.c new file mode 100644 index 0000000000..a834699f30 --- /dev/null +++ b/fuzzers/fuzzbench_fork_qemu/libfuzzer_main.c @@ -0,0 +1,11 @@ +#include +#include + +int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size); + +int main() { + + char buf [10] = {0}; + LLVMFuzzerTestOneInput(buf, 10); + +} diff --git a/fuzzers/fuzzbench_fork_qemu/src/fuzzer.rs b/fuzzers/fuzzbench_fork_qemu/src/fuzzer.rs new file mode 100644 index 0000000000..e165336c27 --- /dev/null +++ b/fuzzers/fuzzbench_fork_qemu/src/fuzzer.rs @@ -0,0 +1,378 @@ +//! A singlethreaded QEMU fuzzer that can auto-restart. + +use clap::{App, Arg}; +use core::cell::RefCell; +#[cfg(unix)] +use nix::{self, unistd::dup}; +#[cfg(unix)] +use std::os::unix::io::{AsRawFd, FromRawFd}; +use std::{ + env, + fs::{self, File, OpenOptions}, + io::{self, Write}, + path::PathBuf, + process, +}; + +use libafl::{ + bolts::{ + current_nanos, current_time, + os::dup2, + rands::StdRand, + shmem::{ShMemProvider, StdShMemProvider}, + tuples::{tuple_list, Merge}, + AsMutSlice, AsSlice, + }, + corpus::{ + Corpus, IndexesLenTimeMinimizerCorpusScheduler, OnDiskCorpus, PowerQueueCorpusScheduler, + }, + events::SimpleRestartingEventManager, + executors::{ExitKind, ShadowExecutor}, + feedback_or, + feedbacks::{CrashFeedback, MapFeedbackState, MaxMapFeedback, TimeFeedback}, + fuzzer::{Fuzzer, StdFuzzer}, + inputs::{BytesInput, HasTargetBytes}, + monitors::SimpleMonitor, + mutators::{ + scheduled::havoc_mutations, token_mutations::I2SRandReplace, tokens_mutations, + StdMOptMutator, StdScheduledMutator, Tokens, + }, + observers::{ConstMapObserver, HitcountsMapObserver, TimeObserver}, + stages::{ + calibrate::CalibrationStage, + power::{PowerMutationalStage, PowerSchedule}, + ShadowTracingStage, StdMutationalStage, + }, + state::{HasCorpus, HasMetadata, StdState}, + Error, +}; +use libafl_qemu::{ + cmplog::{CmpLogMap, CmpLogObserver, QemuCmpLogChildHelper, CMPLOG_MAP_PTR}, + edges::{QemuEdgeCoverageChildHelper, EDGES_MAP_PTR, EDGES_MAP_SIZE}, + elf::EasyElf, + emu::Emulator, + filter_qemu_args, + hooks::QemuHooks, + MmapPerms, QemuForkExecutor, Regs, +}; + +/// The fuzzer main +pub fn main() { + // Registry the metadata types used in this fuzzer + // Needed only on no_std + //RegistryBuilder::register::(); + + let res = match App::new("libafl_qemu_fuzzbench") + .version("0.4.0") + .author("AFLplusplus team") + .about("LibAFL-based fuzzer with QEMU for Fuzzbench") + .arg( + Arg::new("out") + .help("The directory to place finds in ('corpus')") + .long("libafl-out") + .required(true) + .takes_value(true), + ) + .arg( + Arg::new("in") + .help("The directory to read initial inputs from ('seeds')") + .long("libafl-in") + .required(true) + .takes_value(true), + ) + .arg( + Arg::new("tokens") + .long("libafl-tokens") + .help("A file to read tokens from, to be used during fuzzing") + .takes_value(true), + ) + .arg( + Arg::new("logfile") + .long("libafl-logfile") + .help("Duplicates all output to this file") + .default_value("libafl.log"), + ) + .try_get_matches_from(filter_qemu_args()) + { + Ok(res) => res, + Err(err) => { + println!( + "Syntax: {}, --libafl-in --libafl-out \n{:?}", + env::current_exe() + .unwrap_or_else(|_| "fuzzer".into()) + .to_string_lossy(), + err.info, + ); + return; + } + }; + + println!( + "Workdir: {:?}", + env::current_dir().unwrap().to_string_lossy().to_string() + ); + + // For fuzzbench, crashes and finds are inside the same `corpus` directory, in the "queue" and "crashes" subdir. + let mut out_dir = PathBuf::from(res.value_of("out").unwrap().to_string()); + if fs::create_dir(&out_dir).is_err() { + println!("Out dir at {:?} already exists.", &out_dir); + if !out_dir.is_dir() { + println!("Out dir at {:?} is not a valid directory!", &out_dir); + return; + } + } + let mut crashes = out_dir.clone(); + crashes.push("crashes"); + out_dir.push("queue"); + + let in_dir = PathBuf::from(res.value_of("in").unwrap().to_string()); + if !in_dir.is_dir() { + println!("In dir at {:?} is not a valid directory!", &in_dir); + return; + } + + let tokens = res.value_of("tokens").map(PathBuf::from); + + let logfile = PathBuf::from(res.value_of("logfile").unwrap().to_string()); + + fuzz(out_dir, crashes, in_dir, tokens, logfile).expect("An error occurred while fuzzing"); +} + +/// The actual fuzzer +fn fuzz( + corpus_dir: PathBuf, + objective_dir: PathBuf, + seed_dir: PathBuf, + tokenfile: Option, + logfile: PathBuf, +) -> Result<(), Error> { + env::remove_var("LD_LIBRARY_PATH"); + + let args: Vec = env::args().collect(); + let env: Vec<(String, String)> = env::vars().collect(); + let emu = Emulator::new(&args, &env); + + let mut elf_buffer = Vec::new(); + let elf = EasyElf::from_file(emu.binary_path(), &mut elf_buffer)?; + + let test_one_input_ptr = elf + .resolve_symbol("LLVMFuzzerTestOneInput", emu.load_addr()) + .expect("Symbol LLVMFuzzerTestOneInput not found"); + println!("LLVMFuzzerTestOneInput @ {:#x}", test_one_input_ptr); + + emu.set_breakpoint(test_one_input_ptr); // LLVMFuzzerTestOneInput + unsafe { emu.run() }; + + println!("Break at {:#x}", emu.read_reg::<_, u64>(Regs::Rip).unwrap()); + + let stack_ptr: u64 = emu.read_reg(Regs::Rsp).unwrap(); + let mut ret_addr = [0; 8]; + unsafe { emu.read_mem(stack_ptr, &mut ret_addr) }; + let ret_addr = u64::from_le_bytes(ret_addr); + + println!("Stack pointer = {:#x}", stack_ptr); + println!("Return address = {:#x}", ret_addr); + + emu.remove_breakpoint(test_one_input_ptr); // LLVMFuzzerTestOneInput + emu.set_breakpoint(ret_addr); // LLVMFuzzerTestOneInput ret addr + + let input_addr = emu.map_private(0, 4096, MmapPerms::ReadWrite).unwrap(); + println!("Placing input at {:#x}", input_addr); + + let log = RefCell::new( + OpenOptions::new() + .append(true) + .create(true) + .open(&logfile)?, + ); + + #[cfg(unix)] + let mut stdout_cpy = unsafe { + let new_fd = dup(io::stdout().as_raw_fd())?; + File::from_raw_fd(new_fd) + }; + #[cfg(unix)] + let file_null = File::open("/dev/null")?; + + // 'While the stats are state, they are usually used in the broker - which is likely never restarted + let monitor = SimpleMonitor::new(|s| { + #[cfg(unix)] + writeln!(&mut stdout_cpy, "{}", s).unwrap(); + #[cfg(windows)] + println!("{}", s); + writeln!(log.borrow_mut(), "{:?} {}", current_time(), s).unwrap(); + }); + + let mut shmem_provider = StdShMemProvider::new()?; + + let mut edges_shmem = shmem_provider.new_shmem(EDGES_MAP_SIZE).unwrap(); + let edges = edges_shmem.as_mut_slice(); + unsafe { EDGES_MAP_PTR = edges.as_mut_ptr() }; + + let mut cmp_shmem = shmem_provider + .new_shmem(core::mem::size_of::()) + .unwrap(); + let cmplog = cmp_shmem.as_mut_slice(); + unsafe { CMPLOG_MAP_PTR = cmplog.as_mut_ptr() as *mut CmpLogMap }; + + let (state, mut mgr) = match SimpleRestartingEventManager::launch(monitor, &mut shmem_provider) + { + // The restarting state will spawn the same process again as child, then restarted it each time it crashes. + Ok(res) => res, + Err(err) => match err { + Error::ShuttingDown => { + return Ok(()); + } + _ => { + panic!("Failed to setup the restarter: {}", err); + } + }, + }; + + // Create an observation channel using the coverage map + let edges_observer = + HitcountsMapObserver::new(ConstMapObserver::<_, EDGES_MAP_SIZE>::new("edges", edges)); + + // Create an observation channel to keep track of the execution time + let time_observer = TimeObserver::new("time"); + + // Create an observation channel using cmplog map + let cmplog_observer = + CmpLogObserver::new("cmplog", unsafe { CMPLOG_MAP_PTR.as_mut().unwrap() }, true); + + // The state of the edges feedback. + let feedback_state = MapFeedbackState::with_observer(&edges_observer); + + // Feedback to rate the interestingness of an input + // This one is composed by two Feedbacks in OR + let feedback = feedback_or!( + // New maximization map feedback linked to the edges observer and the feedback state + MaxMapFeedback::new_tracking(&feedback_state, &edges_observer, true, false), + // Time feedback, this one does not need a feedback state + TimeFeedback::new_with_observer(&time_observer) + ); + + // A feedback to choose if an input is a solution or not + let objective = CrashFeedback::new(); + + // create a State from scratch + let mut state = state.unwrap_or_else(|| { + StdState::new( + // RNG + StdRand::with_seed(current_nanos()), + // Corpus that will be evolved, we keep it in memory for performance + OnDiskCorpus::new(corpus_dir).unwrap(), + // Corpus in which we store solutions (crashes in this example), + // on disk so the user can get them after stopping the fuzzer + OnDiskCorpus::new(objective_dir).unwrap(), + // States of the feedbacks. + // They are the data related to the feedbacks that you want to persist in the State. + tuple_list!(feedback_state), + ) + }); + + let calibration = CalibrationStage::new(&mut state, &edges_observer); + + // Setup a randomic Input2State stage + let i2s = StdMutationalStage::new(StdScheduledMutator::new(tuple_list!(I2SRandReplace::new()))); + + // Setup a MOPT mutator + let mutator = StdMOptMutator::new(&mut state, havoc_mutations().merge(tokens_mutations()), 5)?; + + let power = PowerMutationalStage::new(mutator, PowerSchedule::FAST, &edges_observer); + + // A minimization+queue policy to get testcasess from the corpus + let scheduler = IndexesLenTimeMinimizerCorpusScheduler::new(PowerQueueCorpusScheduler::new()); + + // A fuzzer with feedbacks and a corpus scheduler + let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); + + // The wrapped harness function, calling out to the LLVM-style harness + let mut harness = |input: &BytesInput| { + let target = input.target_bytes(); + let mut buf = target.as_slice(); + let mut len = buf.len(); + if len > 4096 { + buf = &buf[0..4096]; + len = 4096; + } + + unsafe { + emu.write_mem(input_addr, buf); + + emu.write_reg(Regs::Rdi, input_addr).unwrap(); + emu.write_reg(Regs::Rsi, len).unwrap(); + emu.write_reg(Regs::Rip, test_one_input_ptr).unwrap(); + emu.write_reg(Regs::Rsp, stack_ptr).unwrap(); + + emu.run(); + } + + ExitKind::Ok + }; + + let hooks = QemuHooks::new( + &emu, + tuple_list!( + QemuEdgeCoverageChildHelper::default(), + QemuCmpLogChildHelper::default(), + ), + ); + + let executor = QemuForkExecutor::new( + hooks, + &mut harness, + tuple_list!(edges_observer, time_observer), + &mut fuzzer, + &mut state, + &mut mgr, + shmem_provider, + )?; + + // Show the cmplog observer + let mut executor = ShadowExecutor::new(executor, tuple_list!(cmplog_observer)); + + // Read tokens + if let Some(tokenfile) = tokenfile { + if state.metadata().get::().is_none() { + state.add_metadata(Tokens::from_file(tokenfile)?); + } + } + + if state.corpus().count() < 1 { + state + .load_initial_inputs(&mut fuzzer, &mut executor, &mut mgr, &[seed_dir.clone()]) + .unwrap_or_else(|_| { + println!("Failed to load initial corpus at {:?}", &seed_dir); + process::exit(0); + }); + println!("We imported {} inputs from disk.", state.corpus().count()); + } + + let tracing = ShadowTracingStage::new(&mut executor); + + // The order of the stages matter! + let mut stages = tuple_list!(calibration, tracing, i2s, power); + + // Remove target ouput (logs still survive) + #[cfg(unix)] + { + let null_fd = file_null.as_raw_fd(); + dup2(null_fd, io::stdout().as_raw_fd())?; + dup2(null_fd, io::stderr().as_raw_fd())?; + } + // reopen file to make sure we're at the end + log.replace( + OpenOptions::new() + .append(true) + .create(true) + .open(&logfile)?, + ); + + fuzzer + .fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr) + .expect("Error in the fuzzing loop"); + + // Never reached + Ok(()) +} diff --git a/fuzzers/fuzzbench_fork_qemu/src/main.rs b/fuzzers/fuzzbench_fork_qemu/src/main.rs new file mode 100644 index 0000000000..6f78885bcc --- /dev/null +++ b/fuzzers/fuzzbench_fork_qemu/src/main.rs @@ -0,0 +1,7 @@ +#[cfg(target_os = "linux")] +pub mod fuzzer; + +fn main() { + #[cfg(target_os = "linux")] + fuzzer::main() +} diff --git a/fuzzers/fuzzbench_qemu/src/fuzzer.rs b/fuzzers/fuzzbench_qemu/src/fuzzer.rs index 9998d9dacd..32371f2f06 100644 --- a/fuzzers/fuzzbench_qemu/src/fuzzer.rs +++ b/fuzzers/fuzzbench_qemu/src/fuzzer.rs @@ -55,6 +55,7 @@ use libafl_qemu::{ elf::EasyElf, emu::Emulator, filter_qemu_args, + hooks::QemuHooks, //snapshot::QemuSnapshotHelper, MmapPerms, QemuExecutor, @@ -323,15 +324,19 @@ fn fuzz( ExitKind::Ok }; - let executor = QemuExecutor::new( - &mut harness, + let hooks = QemuHooks::new( &emu, tuple_list!( - QemuEdgeCoverageHelper::new(), - QemuCmpLogHelper::new(), + QemuEdgeCoverageHelper::default(), + QemuCmpLogHelper::default(), //QemuAsanHelper::new(), //QemuSnapshotHelper::new() ), + ); + + let executor = QemuExecutor::new( + hooks, + &mut harness, tuple_list!(edges_observer, time_observer), &mut fuzzer, &mut state, diff --git a/fuzzers/qemu_launcher/src/fuzzer.rs b/fuzzers/qemu_launcher/src/fuzzer.rs index 40c999d632..f1bed73f37 100644 --- a/fuzzers/qemu_launcher/src/fuzzer.rs +++ b/fuzzers/qemu_launcher/src/fuzzer.rs @@ -42,6 +42,7 @@ use libafl_qemu::{ //snapshot::QemuSnapshotHelper, MmapPerms, QemuExecutor, + QemuHooks, Regs, }; @@ -158,12 +159,12 @@ pub fn fuzz() { // A fuzzer with feedbacks and a corpus scheduler let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); + let hooks = QemuHooks::new(&emu, tuple_list!(QemuEdgeCoverageHelper::default(),)); + // Create a QEMU in-process executor let executor = QemuExecutor::new( + hooks, &mut harness, - &emu, - // The QEMU helpers define common hooks like coverage tracking hooks - tuple_list!(QemuEdgeCoverageHelper::new()), tuple_list!(edges_observer, time_observer), &mut fuzzer, &mut state, diff --git a/libafl/src/lib.rs b/libafl/src/lib.rs index 34628bb44f..27199e2ac7 100644 --- a/libafl/src/lib.rs +++ b/libafl/src/lib.rs @@ -182,7 +182,7 @@ impl From for Error { #[cfg(all(unix, feature = "std"))] impl From for Error { fn from(err: nix::Error) -> Self { - Self::Unknown(format!("{:?}", err)) + Self::Unknown(format!("Unix error: {:?}", err)) } } diff --git a/libafl_qemu/src/asan.rs b/libafl_qemu/src/asan.rs index cf9cd9a95b..dad45b4649 100644 --- a/libafl_qemu/src/asan.rs +++ b/libafl_qemu/src/asan.rs @@ -1,11 +1,11 @@ -use libafl::{executors::ExitKind, inputs::Input, observers::ObserversTuple, state::HasMetadata}; +use libafl::{inputs::Input, state::HasMetadata}; use num_enum::{IntoPrimitive, TryFromPrimitive}; -use std::{env, fs, ptr}; +use std::{env, fs, pin::Pin, ptr}; use crate::{ emu::{Emulator, SyscallHookResult}, - executor::QemuExecutor, helper::{QemuHelper, QemuHelperTuple, QemuInstrumentationFilter}, + hooks::QemuHooks, GuestAddr, Regs, }; @@ -172,16 +172,8 @@ pub struct QemuAsanHelper { impl QemuAsanHelper { #[must_use] - pub fn new() -> Self { + pub fn new(filter: QemuInstrumentationFilter) -> Self { assert!(unsafe { ASAN_INITED }, "The ASan runtime is not initialized, use init_with_asan(...) instead of just Emulator::new(...)"); - Self { - enabled: true, - filter: QemuInstrumentationFilter::None, - } - } - - #[must_use] - pub fn with_instrumentation_filter(filter: QemuInstrumentationFilter) -> Self { Self { enabled: true, filter, @@ -410,7 +402,7 @@ impl QemuAsanHelper { impl Default for QemuAsanHelper { fn default() -> Self { - Self::new() + Self::new(QemuInstrumentationFilter::None) } } @@ -421,27 +413,25 @@ where { const HOOKS_DO_SIDE_EFFECTS: bool = false; - fn init<'a, H, OT, QT>(&self, executor: &QemuExecutor<'a, H, I, OT, QT, S>) + fn init_hooks<'a, QT>(&self, hooks: Pin<&QemuHooks<'a, I, QT, S>>) where - H: FnMut(&I) -> ExitKind, - OT: ObserversTuple, QT: QemuHelperTuple, { - //executor.hook_read_generation(gen_readwrite_asan::); - executor.hook_read8_execution(trace_read8_asan::); - executor.hook_read4_execution(trace_read4_asan::); - executor.hook_read2_execution(trace_read2_asan::); - executor.hook_read1_execution(trace_read1_asan::); - executor.hook_read_n_execution(trace_read_n_asan::); + //hooks.read_generation(gen_readwrite_asan::); + hooks.read8_execution(trace_read8_asan::); + hooks.read4_execution(trace_read4_asan::); + hooks.read2_execution(trace_read2_asan::); + hooks.read1_execution(trace_read1_asan::); + hooks.read_n_execution(trace_read_n_asan::); - //executor.hook_write_generation(gen_readwrite_asan::); - executor.hook_write8_execution(trace_write8_asan::); - executor.hook_write4_execution(trace_write4_asan::); - executor.hook_write2_execution(trace_write2_asan::); - executor.hook_write1_execution(trace_write1_asan::); - executor.hook_write_n_execution(trace_write_n_asan::); + //hooks.write_generation(gen_readwrite_asan::); + hooks.write8_execution(trace_write8_asan::); + hooks.write4_execution(trace_write4_asan::); + hooks.write2_execution(trace_write2_asan::); + hooks.write1_execution(trace_write1_asan::); + hooks.write_n_execution(trace_write_n_asan::); - executor.hook_syscalls(qasan_fake_syscall::); + hooks.syscalls(qasan_fake_syscall::); } fn post_exec(&mut self, _emulator: &Emulator, _input: &I) { @@ -449,11 +439,12 @@ where } } +/* // TODO add pc to generation hooks pub fn gen_readwrite_asan( _emulator: &Emulator, helpers: &mut QT, - _state: &mut S, + _state: Option<&mut S>, pc: u64, _size: usize, ) -> Option @@ -468,11 +459,12 @@ where None } } +*/ pub fn trace_read1_asan( emulator: &Emulator, helpers: &mut QT, - _state: &mut S, + _state: Option<&mut S>, _id: u64, addr: GuestAddr, ) where @@ -486,7 +478,7 @@ pub fn trace_read1_asan( pub fn trace_read2_asan( emulator: &Emulator, helpers: &mut QT, - _state: &mut S, + _state: Option<&mut S>, _id: u64, addr: GuestAddr, ) where @@ -500,7 +492,7 @@ pub fn trace_read2_asan( pub fn trace_read4_asan( emulator: &Emulator, helpers: &mut QT, - _state: &mut S, + _state: Option<&mut S>, _id: u64, addr: GuestAddr, ) where @@ -514,7 +506,7 @@ pub fn trace_read4_asan( pub fn trace_read8_asan( emulator: &Emulator, helpers: &mut QT, - _state: &mut S, + _state: Option<&mut S>, _id: u64, addr: GuestAddr, ) where @@ -528,7 +520,7 @@ pub fn trace_read8_asan( pub fn trace_read_n_asan( emulator: &Emulator, helpers: &mut QT, - _state: &mut S, + _state: Option<&mut S>, _id: u64, addr: GuestAddr, size: usize, @@ -543,7 +535,7 @@ pub fn trace_read_n_asan( pub fn trace_write1_asan( emulator: &Emulator, helpers: &mut QT, - _state: &mut S, + _state: Option<&mut S>, _id: u64, addr: GuestAddr, ) where @@ -557,7 +549,7 @@ pub fn trace_write1_asan( pub fn trace_write2_asan( emulator: &Emulator, helpers: &mut QT, - _state: &mut S, + _state: Option<&mut S>, _id: u64, addr: GuestAddr, ) where @@ -571,7 +563,7 @@ pub fn trace_write2_asan( pub fn trace_write4_asan( emulator: &Emulator, helpers: &mut QT, - _state: &mut S, + _state: Option<&mut S>, _id: u64, addr: GuestAddr, ) where @@ -585,7 +577,7 @@ pub fn trace_write4_asan( pub fn trace_write8_asan( emulator: &Emulator, helpers: &mut QT, - _state: &mut S, + _state: Option<&mut S>, _id: u64, addr: GuestAddr, ) where @@ -599,7 +591,7 @@ pub fn trace_write8_asan( pub fn trace_write_n_asan( emulator: &Emulator, helpers: &mut QT, - _state: &mut S, + _state: Option<&mut S>, _id: u64, addr: GuestAddr, size: usize, @@ -615,7 +607,7 @@ pub fn trace_write_n_asan( pub fn qasan_fake_syscall( emulator: &Emulator, helpers: &mut QT, - _state: &mut S, + _state: Option<&mut S>, sys_num: i32, a0: u64, a1: u64, diff --git a/libafl_qemu/src/cmplog.rs b/libafl_qemu/src/cmplog.rs index 4dbf518f71..2304448f0c 100644 --- a/libafl_qemu/src/cmplog.rs +++ b/libafl_qemu/src/cmplog.rs @@ -1,14 +1,16 @@ +use core::pin::Pin; use hashbrown::HashMap; -use libafl::{executors::ExitKind, inputs::Input, observers::ObserversTuple, state::HasMetadata}; +use libafl::{inputs::Input, state::HasMetadata}; pub use libafl_targets::{ - cmplog::__libafl_targets_cmplog_instructions, CmpLogObserver, CMPLOG_MAP, CMPLOG_MAP_W, + cmplog::__libafl_targets_cmplog_instructions, CmpLogMap, CmpLogObserver, CMPLOG_MAP, + CMPLOG_MAP_H, CMPLOG_MAP_PTR, CMPLOG_MAP_SIZE, CMPLOG_MAP_W, }; use serde::{Deserialize, Serialize}; use crate::{ emu::Emulator, - executor::QemuExecutor, helper::{hash_me, QemuHelper, QemuHelperTuple, QemuInstrumentationFilter}, + hooks::QemuHooks, }; #[derive(Debug, Default, Serialize, Deserialize)] @@ -36,14 +38,7 @@ pub struct QemuCmpLogHelper { impl QemuCmpLogHelper { #[must_use] - pub fn new() -> Self { - Self { - filter: QemuInstrumentationFilter::None, - } - } - - #[must_use] - pub fn with_instrumentation_filter(filter: QemuInstrumentationFilter) -> Self { + pub fn new(filter: QemuInstrumentationFilter) -> Self { Self { filter } } @@ -55,7 +50,7 @@ impl QemuCmpLogHelper { impl Default for QemuCmpLogHelper { fn default() -> Self { - Self::new() + Self::new(QemuInstrumentationFilter::None) } } @@ -64,17 +59,15 @@ where I: Input, S: HasMetadata, { - fn init<'a, H, OT, QT>(&self, executor: &QemuExecutor<'a, H, I, OT, QT, S>) + fn init_hooks<'a, QT>(&self, hooks: Pin<&QemuHooks<'a, I, QT, S>>) where - H: FnMut(&I) -> ExitKind, - OT: ObserversTuple, QT: QemuHelperTuple, { - executor.hook_cmp_generation(gen_unique_cmp_ids::); - executor.emulator().set_exec_cmp8_hook(trace_cmp8_cmplog); - executor.emulator().set_exec_cmp4_hook(trace_cmp4_cmplog); - executor.emulator().set_exec_cmp2_hook(trace_cmp2_cmplog); - executor.emulator().set_exec_cmp1_hook(trace_cmp1_cmplog); + hooks.cmp_generation(gen_unique_cmp_ids::); + hooks.emulator().set_exec_cmp8_hook(trace_cmp8_cmplog); + hooks.emulator().set_exec_cmp4_hook(trace_cmp4_cmplog); + hooks.emulator().set_exec_cmp2_hook(trace_cmp2_cmplog); + hooks.emulator().set_exec_cmp1_hook(trace_cmp1_cmplog); } } @@ -85,14 +78,7 @@ pub struct QemuCmpLogChildHelper { impl QemuCmpLogChildHelper { #[must_use] - pub fn new() -> Self { - Self { - filter: QemuInstrumentationFilter::None, - } - } - - #[must_use] - pub fn with_instrumentation_filter(filter: QemuInstrumentationFilter) -> Self { + pub fn new(filter: QemuInstrumentationFilter) -> Self { Self { filter } } @@ -104,7 +90,7 @@ impl QemuCmpLogChildHelper { impl Default for QemuCmpLogChildHelper { fn default() -> Self { - Self::new() + Self::new(QemuInstrumentationFilter::None) } } @@ -115,24 +101,22 @@ where { const HOOKS_DO_SIDE_EFFECTS: bool = false; - fn init<'a, H, OT, QT>(&self, executor: &QemuExecutor<'a, H, I, OT, QT, S>) + fn init_hooks<'a, QT>(&self, hooks: Pin<&QemuHooks<'a, I, QT, S>>) where - H: FnMut(&I) -> ExitKind, - OT: ObserversTuple, QT: QemuHelperTuple, { - executor.hook_cmp_generation(gen_hashed_cmp_ids::); - executor.emulator().set_exec_cmp8_hook(trace_cmp8_cmplog); - executor.emulator().set_exec_cmp4_hook(trace_cmp4_cmplog); - executor.emulator().set_exec_cmp2_hook(trace_cmp2_cmplog); - executor.emulator().set_exec_cmp1_hook(trace_cmp1_cmplog); + hooks.cmp_generation(gen_hashed_cmp_ids::); + hooks.emulator().set_exec_cmp8_hook(trace_cmp8_cmplog); + hooks.emulator().set_exec_cmp4_hook(trace_cmp4_cmplog); + hooks.emulator().set_exec_cmp2_hook(trace_cmp2_cmplog); + hooks.emulator().set_exec_cmp1_hook(trace_cmp1_cmplog); } } pub fn gen_unique_cmp_ids( _emulator: &Emulator, helpers: &mut QT, - state: &mut S, + state: Option<&mut S>, pc: u64, _size: usize, ) -> Option @@ -146,6 +130,7 @@ where return None; } } + let state = state.expect("The gen_unique_cmp_ids hook works only for in-process fuzzing"); if state.metadata().get::().is_none() { state.add_metadata(QemuCmpsMapMetadata::new()); } @@ -164,7 +149,7 @@ where pub fn gen_hashed_cmp_ids( _emulator: &Emulator, helpers: &mut QT, - _state: &mut S, + _state: Option<&mut S>, pc: u64, _size: usize, ) -> Option @@ -178,7 +163,7 @@ where return None; } } - Some(hash_me(pc)) + Some(hash_me(pc) & (CMPLOG_MAP_W as u64 - 1)) } pub extern "C" fn trace_cmp1_cmplog(id: u64, v0: u8, v1: u8) { diff --git a/libafl_qemu/src/edges.rs b/libafl_qemu/src/edges.rs index 9bc8f43d00..230dfd0efb 100644 --- a/libafl_qemu/src/edges.rs +++ b/libafl_qemu/src/edges.rs @@ -1,15 +1,15 @@ use hashbrown::{hash_map::Entry, HashMap}; -use libafl::{executors::ExitKind, inputs::Input, observers::ObserversTuple, state::HasMetadata}; +use libafl::{inputs::Input, state::HasMetadata}; pub use libafl_targets::{ edges_max_num, EDGES_MAP, EDGES_MAP_PTR, EDGES_MAP_PTR_SIZE, EDGES_MAP_SIZE, MAX_EDGES_NUM, }; use serde::{Deserialize, Serialize}; -use std::{cell::UnsafeCell, cmp::max}; +use std::{cell::UnsafeCell, cmp::max, pin::Pin}; use crate::{ emu::Emulator, - executor::QemuExecutor, helper::{hash_me, QemuHelper, QemuHelperTuple, QemuInstrumentationFilter}, + hooks::QemuHooks, }; #[derive(Debug, Default, Serialize, Deserialize)] @@ -33,19 +33,24 @@ libafl::impl_serdeany!(QemuEdgesMapMetadata); #[derive(Debug)] pub struct QemuEdgeCoverageHelper { filter: QemuInstrumentationFilter, + use_hitcounts: bool, } impl QemuEdgeCoverageHelper { #[must_use] - pub fn new() -> Self { + pub fn new(filter: QemuInstrumentationFilter) -> Self { Self { - filter: QemuInstrumentationFilter::None, + filter, + use_hitcounts: true, } } #[must_use] - pub fn with_instrumentation_filter(filter: QemuInstrumentationFilter) -> Self { - Self { filter } + pub fn without_hitcounts(filter: QemuInstrumentationFilter) -> Self { + Self { + filter, + use_hitcounts: false, + } } #[must_use] @@ -56,7 +61,7 @@ impl QemuEdgeCoverageHelper { impl Default for QemuEdgeCoverageHelper { fn default() -> Self { - Self::new() + Self::new(QemuInstrumentationFilter::None) } } @@ -65,35 +70,42 @@ where I: Input, S: HasMetadata, { - fn init<'a, H, OT, QT>(&self, executor: &QemuExecutor<'a, H, I, OT, QT, S>) + fn init_hooks<'a, QT>(&self, hooks: Pin<&QemuHooks<'a, I, QT, S>>) where - H: FnMut(&I) -> ExitKind, - OT: ObserversTuple, QT: QemuHelperTuple, { - executor.hook_edge_generation(gen_unique_edge_ids::); - executor.emulator().set_exec_edge_hook(trace_edge_hitcount); + hooks.edge_generation(gen_unique_edge_ids::); + if self.use_hitcounts { + hooks.emulator().set_exec_edge_hook(trace_edge_hitcount); + } else { + hooks.emulator().set_exec_edge_hook(trace_edge_single); + } } } -pub type QemuEdgeCoverageWithBlocksHelper = QemuEdgeCoverageChildHelper; +pub type QemuCollidingEdgeCoverageHelper = QemuEdgeCoverageChildHelper; #[derive(Debug)] pub struct QemuEdgeCoverageChildHelper { filter: QemuInstrumentationFilter, + use_hitcounts: bool, } impl QemuEdgeCoverageChildHelper { #[must_use] - pub fn new() -> Self { + pub fn new(filter: QemuInstrumentationFilter) -> Self { Self { - filter: QemuInstrumentationFilter::None, + filter, + use_hitcounts: true, } } #[must_use] - pub fn with_instrumentation_filter(filter: QemuInstrumentationFilter) -> Self { - Self { filter } + pub fn without_hitcounts(filter: QemuInstrumentationFilter) -> Self { + Self { + filter, + use_hitcounts: false, + } } #[must_use] @@ -104,7 +116,7 @@ impl QemuEdgeCoverageChildHelper { impl Default for QemuEdgeCoverageChildHelper { fn default() -> Self { - Self::new() + Self::new(QemuInstrumentationFilter::None) } } @@ -115,14 +127,16 @@ where { const HOOKS_DO_SIDE_EFFECTS: bool = false; - fn init<'a, H, OT, QT>(&self, executor: &QemuExecutor<'a, H, I, OT, QT, S>) + fn init_hooks<'a, QT>(&self, hooks: Pin<&QemuHooks<'a, I, QT, S>>) where - H: FnMut(&I) -> ExitKind, - OT: ObserversTuple, QT: QemuHelperTuple, { - executor.hook_edge_generation(gen_unique_edge_ids::); - executor.emulator().set_exec_edge_hook(trace_edge_hitcount); + hooks.edge_generation(gen_hashed_edge_ids::); + if self.use_hitcounts { + hooks.emulator().set_exec_edge_hook(trace_edge_hitcount_ptr); + } else { + hooks.emulator().set_exec_edge_hook(trace_edge_single_ptr); + } } } @@ -131,7 +145,7 @@ thread_local!(static PREV_LOC : UnsafeCell = UnsafeCell::new(0)); pub fn gen_unique_edge_ids( _emulator: &Emulator, helpers: &mut QT, - state: &mut S, + state: Option<&mut S>, src: u64, dest: u64, ) -> Option @@ -145,6 +159,7 @@ where return None; } } + let state = state.expect("The gen_unique_edge_ids hook works only for in-process fuzzing"); if state.metadata().get::().is_none() { state.add_metadata(QemuEdgesMapMetadata::new()); } @@ -174,25 +189,6 @@ where } } -pub fn gen_hashed_edge_ids( - _emulator: &Emulator, - helpers: &mut QT, - _state: &mut S, - src: u64, - dest: u64, -) -> Option -where - I: Input, - QT: QemuHelperTuple, -{ - if let Some(h) = helpers.match_first_type::() { - if !h.must_instrument(src) && !h.must_instrument(dest) { - return None; - } - } - Some(hash_me(src) ^ hash_me(dest)) -} - pub extern "C" fn trace_edge_hitcount(id: u64) { unsafe { EDGES_MAP[id as usize] = EDGES_MAP[id as usize].wrapping_add(1); @@ -205,10 +201,43 @@ pub extern "C" fn trace_edge_single(id: u64) { } } +pub fn gen_hashed_edge_ids( + _emulator: &Emulator, + helpers: &mut QT, + _state: Option<&mut S>, + src: u64, + dest: u64, +) -> Option +where + I: Input, + QT: QemuHelperTuple, +{ + if let Some(h) = helpers.match_first_type::() { + if !h.must_instrument(src) && !h.must_instrument(dest) { + return None; + } + } + Some((hash_me(src) ^ hash_me(dest)) & (unsafe { EDGES_MAP_PTR_SIZE } as u64 - 1)) +} + +pub extern "C" fn trace_edge_hitcount_ptr(id: u64) { + unsafe { + let ptr = EDGES_MAP_PTR.add(id as usize); + *ptr = (*ptr).wrapping_add(1); + } +} + +pub extern "C" fn trace_edge_single_ptr(id: u64) { + unsafe { + let ptr = EDGES_MAP_PTR.add(id as usize); + *ptr = 1; + } +} + pub fn gen_addr_block_ids( _emulator: &Emulator, _helpers: &mut QT, - _state: &mut S, + _state: Option<&mut S>, pc: u64, ) -> Option { Some(pc) @@ -217,7 +246,7 @@ pub fn gen_addr_block_ids( pub fn gen_hashed_block_ids( _emulator: &Emulator, _helpers: &mut QT, - _state: &mut S, + _state: Option<&mut S>, pc: u64, ) -> Option { Some(hash_me(pc)) diff --git a/libafl_qemu/src/emu.rs b/libafl_qemu/src/emu.rs index c076a58422..36725b8ab1 100644 --- a/libafl_qemu/src/emu.rs +++ b/libafl_qemu/src/emu.rs @@ -12,11 +12,11 @@ use num_traits::Num; use std::{slice::from_raw_parts, str::from_utf8_unchecked}; use strum_macros::EnumIter; -#[cfg(not(any(feature = "x86_64", feature = "aarch64")))] +#[cfg(not(any(cpu_target = "x86_64", cpu_target = "aarch64")))] /// `GuestAddr` is u32 for 32-bit targets pub type GuestAddr = u32; -#[cfg(any(feature = "x86_64", feature = "aarch64"))] +#[cfg(any(cpu_target = "x86_64", cpu_target = "aarch64"))] /// `GuestAddr` is u64 for 64-bit targets pub type GuestAddr = u64; @@ -317,7 +317,10 @@ impl Emulator { #[allow(clippy::must_use_candidate, clippy::similar_names)] pub fn new(args: &[String], env: &[(String, String)]) -> Emulator { unsafe { - assert!(!EMULATOR_IS_INITIALIZED); + assert!( + !EMULATOR_IS_INITIALIZED, + "Only an instance of Emulator is permitted" + ); } assert!(!args.is_empty()); let args: Vec = args.iter().map(|x| x.clone() + "\0").collect(); diff --git a/libafl_qemu/src/executor.rs b/libafl_qemu/src/executor.rs index 7e603765b0..3f18cd7fab 100644 --- a/libafl_qemu/src/executor.rs +++ b/libafl_qemu/src/executor.rs @@ -1,16 +1,13 @@ //! A `QEMU`-based executor for binary-only instrumentation in `LibAFL` use core::{ - ffi::c_void, fmt::{self, Debug, Formatter}, - mem::transmute, - ptr::{self, addr_of}, + pin::Pin, }; use libafl::{ + bolts::shmem::ShMemProvider, events::{EventFirer, EventRestarter}, - executors::{ - inprocess::inprocess_get_state, Executor, ExitKind, HasObservers, InProcessExecutor, - }, + executors::{Executor, ExitKind, HasObservers, InProcessExecutor, InProcessForkExecutor}, feedbacks::Feedback, fuzzer::HasObjective, inputs::Input, @@ -20,444 +17,7 @@ use libafl::{ }; pub use crate::emu::SyscallHookResult; -use crate::{ - emu::{Emulator, SKIP_EXEC_HOOK}, - helper::QemuHelperTuple, - GuestAddr, -}; - -static mut QEMU_HELPERS_PTR: *const c_void = ptr::null(); - -static mut GEN_EDGE_HOOK_PTR: *const c_void = ptr::null(); -extern "C" fn gen_edge_hook_wrapper(src: u64, dst: u64) -> u64 -where - I: Input, - QT: QemuHelperTuple, -{ - unsafe { - let helpers = (QEMU_HELPERS_PTR as *mut QT).as_mut().unwrap(); - let state = inprocess_get_state::().unwrap(); - let emulator = Emulator::new_empty(); - let func: fn(&Emulator, &mut QT, &mut S, u64, u64) -> Option = - transmute(GEN_EDGE_HOOK_PTR); - (func)(&emulator, helpers, state, src, dst).map_or(SKIP_EXEC_HOOK, |id| id) - } -} - -static mut EDGE_HOOKS: Vec<*const c_void> = vec![]; -extern "C" fn edge_hooks_wrapper(id: u64) -where - I: Input, - QT: QemuHelperTuple, -{ - let helpers = unsafe { (QEMU_HELPERS_PTR as *mut QT).as_mut().unwrap() }; - let state = inprocess_get_state::().unwrap(); - let emulator = Emulator::new_empty(); - for hook in unsafe { &EDGE_HOOKS } { - let func: fn(&Emulator, &mut QT, &mut S, u64) = unsafe { transmute(*hook) }; - (func)(&emulator, helpers, state, id); - } -} - -static mut GEN_BLOCK_HOOK_PTR: *const c_void = ptr::null(); -extern "C" fn gen_block_hook_wrapper(pc: u64) -> u64 -where - I: Input, - QT: QemuHelperTuple, -{ - unsafe { - let helpers = (QEMU_HELPERS_PTR as *mut QT).as_mut().unwrap(); - let state = inprocess_get_state::().unwrap(); - let emulator = Emulator::new_empty(); - let func: fn(&Emulator, &mut QT, &mut S, u64) -> Option = transmute(GEN_EDGE_HOOK_PTR); - (func)(&emulator, helpers, state, pc).map_or(SKIP_EXEC_HOOK, |id| id) - } -} - -static mut BLOCK_HOOKS: Vec<*const c_void> = vec![]; -extern "C" fn block_hooks_wrapper(id: u64) -where - I: Input, - QT: QemuHelperTuple, -{ - let helpers = unsafe { (QEMU_HELPERS_PTR as *mut QT).as_mut().unwrap() }; - let state = inprocess_get_state::().unwrap(); - let emulator = Emulator::new_empty(); - for hook in unsafe { &BLOCK_HOOKS } { - let func: fn(&Emulator, &mut QT, &mut S, u64) = unsafe { transmute(*hook) }; - (func)(&emulator, helpers, state, id); - } -} - -static mut GEN_READ_HOOK_PTR: *const c_void = ptr::null(); -extern "C" fn gen_read_hook_wrapper(size: u32) -> u64 -where - I: Input, - QT: QemuHelperTuple, -{ - unsafe { - let helpers = (QEMU_HELPERS_PTR as *mut QT).as_mut().unwrap(); - let state = inprocess_get_state::().unwrap(); - let emulator = Emulator::new_empty(); - let func: fn(&Emulator, &mut QT, &mut S, usize) -> Option = - transmute(GEN_READ_HOOK_PTR); - (func)(&emulator, helpers, state, size as usize).map_or(SKIP_EXEC_HOOK, |id| id) - } -} - -static mut GEN_WRITE_HOOK_PTR: *const c_void = ptr::null(); -extern "C" fn gen_write_hook_wrapper(size: u32) -> u64 -where - I: Input, - QT: QemuHelperTuple, -{ - unsafe { - let helpers = (QEMU_HELPERS_PTR as *mut QT).as_mut().unwrap(); - let state = inprocess_get_state::().unwrap(); - let emulator = Emulator::new_empty(); - let func: fn(&Emulator, &mut QT, &mut S, usize) -> Option = - transmute(GEN_WRITE_HOOK_PTR); - (func)(&emulator, helpers, state, size as usize).map_or(SKIP_EXEC_HOOK, |id| id) - } -} - -// function signature for Read or Write hook functions with known length (1, 2, 4, 8) -type FixedLenHook = fn(&Emulator, &mut QT, &mut S, u64, GuestAddr); - -// function signature for Read or Write hook functions with runtime length n -type DynamicLenHook = fn(&Emulator, &mut QT, &mut S, u64, GuestAddr, usize); - -static mut READ1_HOOKS: Vec<*const c_void> = vec![]; -extern "C" fn read1_hooks_wrapper(id: u64, addr: u64) -where - I: Input, - QT: QemuHelperTuple, -{ - let helpers = unsafe { (QEMU_HELPERS_PTR as *mut QT).as_mut().unwrap() }; - let state = inprocess_get_state::().unwrap(); - let emulator = Emulator::new_empty(); - for hook in unsafe { &READ1_HOOKS } { - let func: FixedLenHook = unsafe { transmute(*hook) }; - (func)(&emulator, helpers, state, id, addr as GuestAddr); - } -} - -static mut READ2_HOOKS: Vec<*const c_void> = vec![]; -extern "C" fn read2_hooks_wrapper(id: u64, addr: u64) -where - I: Input, - QT: QemuHelperTuple, -{ - let helpers = unsafe { (QEMU_HELPERS_PTR as *mut QT).as_mut().unwrap() }; - let state = inprocess_get_state::().unwrap(); - let emulator = Emulator::new_empty(); - for hook in unsafe { &READ2_HOOKS } { - let func: FixedLenHook = unsafe { transmute(*hook) }; - (func)(&emulator, helpers, state, id, addr as GuestAddr); - } -} - -static mut READ4_HOOKS: Vec<*const c_void> = vec![]; -extern "C" fn read4_hooks_wrapper(id: u64, addr: u64) -where - I: Input, - QT: QemuHelperTuple, -{ - let helpers = unsafe { (QEMU_HELPERS_PTR as *mut QT).as_mut().unwrap() }; - let state = inprocess_get_state::().unwrap(); - let emulator = Emulator::new_empty(); - for hook in unsafe { &READ4_HOOKS } { - let func: FixedLenHook = unsafe { transmute(*hook) }; - (func)(&emulator, helpers, state, id, addr as GuestAddr); - } -} - -static mut READ8_HOOKS: Vec<*const c_void> = vec![]; -extern "C" fn read8_hooks_wrapper(id: u64, addr: u64) -where - I: Input, - QT: QemuHelperTuple, -{ - let helpers = unsafe { (QEMU_HELPERS_PTR as *mut QT).as_mut().unwrap() }; - let state = inprocess_get_state::().unwrap(); - let emulator = Emulator::new_empty(); - for hook in unsafe { &READ8_HOOKS } { - let func: FixedLenHook = unsafe { transmute(*hook) }; - (func)(&emulator, helpers, state, id, addr as GuestAddr); - } -} - -static mut READ_N_HOOKS: Vec<*const c_void> = vec![]; -extern "C" fn read_n_hooks_wrapper(id: u64, addr: u64, size: u32) -where - I: Input, - QT: QemuHelperTuple, -{ - let helpers = unsafe { (QEMU_HELPERS_PTR as *mut QT).as_mut().unwrap() }; - let state = inprocess_get_state::().unwrap(); - let emulator = Emulator::new_empty(); - for hook in unsafe { &READ_N_HOOKS } { - let func: DynamicLenHook = unsafe { transmute(*hook) }; - (func)( - &emulator, - helpers, - state, - id, - addr as GuestAddr, - size as usize, - ); - } -} - -static mut WRITE1_HOOKS: Vec<*const c_void> = vec![]; -extern "C" fn write1_hooks_wrapper(id: u64, addr: u64) -where - I: Input, - QT: QemuHelperTuple, -{ - let helpers = unsafe { (QEMU_HELPERS_PTR as *mut QT).as_mut().unwrap() }; - let state = inprocess_get_state::().unwrap(); - let emulator = Emulator::new_empty(); - for hook in unsafe { &WRITE1_HOOKS } { - let func: FixedLenHook = unsafe { transmute(*hook) }; - (func)(&emulator, helpers, state, id, addr as GuestAddr); - } -} - -static mut WRITE2_HOOKS: Vec<*const c_void> = vec![]; -extern "C" fn write2_hooks_wrapper(id: u64, addr: u64) -where - I: Input, - QT: QemuHelperTuple, -{ - let helpers = unsafe { (QEMU_HELPERS_PTR as *mut QT).as_mut().unwrap() }; - let state = inprocess_get_state::().unwrap(); - let emulator = Emulator::new_empty(); - for hook in unsafe { &WRITE2_HOOKS } { - let func: FixedLenHook = unsafe { transmute(*hook) }; - (func)(&emulator, helpers, state, id, addr as GuestAddr); - } -} - -static mut WRITE4_HOOKS: Vec<*const c_void> = vec![]; -extern "C" fn write4_hooks_wrapper(id: u64, addr: u64) -where - I: Input, - QT: QemuHelperTuple, -{ - let helpers = unsafe { (QEMU_HELPERS_PTR as *mut QT).as_mut().unwrap() }; - let state = inprocess_get_state::().unwrap(); - let emulator = Emulator::new_empty(); - for hook in unsafe { &WRITE4_HOOKS } { - let func: FixedLenHook = unsafe { transmute(*hook) }; - (func)(&emulator, helpers, state, id, addr as GuestAddr); - } -} - -static mut WRITE8_HOOKS: Vec<*const c_void> = vec![]; -extern "C" fn write8_hooks_wrapper(id: u64, addr: u64) -where - I: Input, - QT: QemuHelperTuple, -{ - let helpers = unsafe { (QEMU_HELPERS_PTR as *mut QT).as_mut().unwrap() }; - let state = inprocess_get_state::().unwrap(); - let emulator = Emulator::new_empty(); - for hook in unsafe { &WRITE8_HOOKS } { - let func: FixedLenHook = unsafe { transmute(*hook) }; - (func)(&emulator, helpers, state, id, addr as GuestAddr); - } -} - -static mut WRITE_N_HOOKS: Vec<*const c_void> = vec![]; -extern "C" fn write_n_hooks_wrapper(id: u64, addr: u64, size: u32) -where - I: Input, - QT: QemuHelperTuple, -{ - let helpers = unsafe { (QEMU_HELPERS_PTR as *mut QT).as_mut().unwrap() }; - let state = inprocess_get_state::().unwrap(); - let emulator = Emulator::new_empty(); - for hook in unsafe { &WRITE_N_HOOKS } { - let func: DynamicLenHook = unsafe { transmute(*hook) }; - (func)( - &emulator, - helpers, - state, - id, - addr as GuestAddr, - size as usize, - ); - } -} - -static mut GEN_CMP_HOOK_PTR: *const c_void = ptr::null(); -extern "C" fn gen_cmp_hook_wrapper(pc: u64, size: u32) -> u64 -where - I: Input, - QT: QemuHelperTuple, -{ - unsafe { - let helpers = (QEMU_HELPERS_PTR as *mut QT).as_mut().unwrap(); - let state = inprocess_get_state::().unwrap(); - let emulator = Emulator::new_empty(); - let func: fn(&Emulator, &mut QT, &mut S, u64, usize) -> Option = - transmute(GEN_CMP_HOOK_PTR); - (func)(&emulator, helpers, state, pc, size as usize).map_or(SKIP_EXEC_HOOK, |id| id) - } -} - -static mut CMP1_HOOKS: Vec<*const c_void> = vec![]; -extern "C" fn cmp1_hooks_wrapper(id: u64, v0: u8, v1: u8) -where - I: Input, - QT: QemuHelperTuple, -{ - let helpers = unsafe { (QEMU_HELPERS_PTR as *mut QT).as_mut().unwrap() }; - let state = inprocess_get_state::().unwrap(); - let emulator = Emulator::new_empty(); - for hook in unsafe { &CMP1_HOOKS } { - let func: fn(&Emulator, &mut QT, &mut S, u64, u8, u8) = unsafe { transmute(*hook) }; - (func)(&emulator, helpers, state, id, v0, v1); - } -} - -static mut CMP2_HOOKS: Vec<*const c_void> = vec![]; -extern "C" fn cmp2_hooks_wrapper(id: u64, v0: u16, v1: u16) -where - I: Input, - QT: QemuHelperTuple, -{ - let helpers = unsafe { (QEMU_HELPERS_PTR as *mut QT).as_mut().unwrap() }; - let state = inprocess_get_state::().unwrap(); - let emulator = Emulator::new_empty(); - for hook in unsafe { &CMP2_HOOKS } { - let func: fn(&Emulator, &mut QT, &mut S, u64, u16, u16) = unsafe { transmute(*hook) }; - (func)(&emulator, helpers, state, id, v0, v1); - } -} - -static mut CMP4_HOOKS: Vec<*const c_void> = vec![]; -extern "C" fn cmp4_hooks_wrapper(id: u64, v0: u32, v1: u32) -where - I: Input, - QT: QemuHelperTuple, -{ - let helpers = unsafe { (QEMU_HELPERS_PTR as *mut QT).as_mut().unwrap() }; - let state = inprocess_get_state::().unwrap(); - let emulator = Emulator::new_empty(); - for hook in unsafe { &CMP4_HOOKS } { - let func: fn(&Emulator, &mut QT, &mut S, u64, u32, u32) = unsafe { transmute(*hook) }; - (func)(&emulator, helpers, state, id, v0, v1); - } -} - -static mut CMP8_HOOKS: Vec<*const c_void> = vec![]; -extern "C" fn cmp8_hooks_wrapper(id: u64, v0: u64, v1: u64) -where - I: Input, - QT: QemuHelperTuple, -{ - let helpers = unsafe { (QEMU_HELPERS_PTR as *mut QT).as_mut().unwrap() }; - let state = inprocess_get_state::().unwrap(); - let emulator = Emulator::new_empty(); - for hook in unsafe { &CMP8_HOOKS } { - let func: fn(&Emulator, &mut QT, &mut S, u64, u64, u64) = unsafe { transmute(*hook) }; - (func)(&emulator, helpers, state, id, v0, v1); - } -} - -static mut SYSCALL_HOOKS: Vec<*const c_void> = vec![]; -extern "C" fn syscall_hooks_wrapper( - sys_num: i32, - a0: u64, - a1: u64, - a2: u64, - a3: u64, - a4: u64, - a5: u64, - a6: u64, - a7: u64, -) -> SyscallHookResult -where - I: Input, - QT: QemuHelperTuple, -{ - let helpers = unsafe { (QEMU_HELPERS_PTR as *mut QT).as_mut().unwrap() }; - let state = inprocess_get_state::().unwrap(); - let emulator = Emulator::new_empty(); - let mut res = SyscallHookResult::new(None); - for hook in unsafe { &SYSCALL_HOOKS } { - #[allow(clippy::type_complexity)] - let func: fn( - &Emulator, - &mut QT, - &mut S, - i32, - u64, - u64, - u64, - u64, - u64, - u64, - u64, - u64, - ) -> SyscallHookResult = unsafe { transmute(*hook) }; - let r = (func)( - &emulator, helpers, state, sys_num, a0, a1, a2, a3, a4, a5, a6, a7, - ); - if r.skip_syscall { - res.skip_syscall = true; - res.retval = r.retval; - } - } - res -} - -static mut SYSCALL_POST_HOOKS: Vec<*const c_void> = vec![]; -extern "C" fn syscall_after_hooks_wrapper( - result: u64, - sys_num: i32, - a0: u64, - a1: u64, - a2: u64, - a3: u64, - a4: u64, - a5: u64, - a6: u64, - a7: u64, -) -> u64 -where - I: Input, - QT: QemuHelperTuple, -{ - let helpers = unsafe { (QEMU_HELPERS_PTR as *mut QT).as_mut().unwrap() }; - let state = inprocess_get_state::().unwrap(); - let emulator = Emulator::new_empty(); - let mut res = result; - for hook in unsafe { &SYSCALL_POST_HOOKS } { - #[allow(clippy::type_complexity)] - let func: fn( - &Emulator, - &mut QT, - &mut S, - u64, - i32, - u64, - u64, - u64, - u64, - u64, - u64, - u64, - u64, - ) -> u64 = unsafe { transmute(*hook) }; - res = (func)( - &emulator, helpers, state, res, sys_num, a0, a1, a2, a3, a4, a5, a6, a7, - ); - } - res -} +use crate::{emu::Emulator, helper::QemuHelperTuple, hooks::QemuHooks}; pub struct QemuExecutor<'a, H, I, OT, QT, S> where @@ -466,8 +26,7 @@ where OT: ObserversTuple, QT: QemuHelperTuple, { - helpers: QT, - emulator: &'a Emulator, + hooks: Pin>>, inner: InProcessExecutor<'a, H, I, OT, S>, } @@ -480,8 +39,7 @@ where { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("QemuExecutor") - .field("helpers", &self.helpers) - .field("emulator", &self.emulator) + .field("hooks", &self.hooks) .field("inner", &self.inner) .finish() } @@ -495,9 +53,8 @@ where QT: QemuHelperTuple, { pub fn new( + hooks: Pin>>, harness_fn: &'a mut H, - emulator: &'a Emulator, - helpers: QT, observers: OT, fuzzer: &mut Z, state: &mut S, @@ -509,13 +66,10 @@ where S: HasSolutions + HasClientPerfMonitor, Z: HasObjective, { - let slf = Self { - helpers, - emulator, + Ok(Self { + hooks, inner: InProcessExecutor::new(harness_fn, observers, fuzzer, state, event_mgr)?, - }; - slf.helpers.init_all(&slf); - Ok(slf) + }) } pub fn inner(&self) -> &InProcessExecutor<'a, H, I, OT, S> { @@ -526,277 +80,16 @@ where &mut self.inner } + pub fn hooks(&self) -> &Pin>> { + &self.hooks + } + + pub fn hooks_mut(&mut self) -> &mut Pin>> { + &mut self.hooks + } + pub fn emulator(&self) -> &Emulator { - self.emulator - } - - #[allow(clippy::unused_self)] - pub fn hook_edge_generation( - &self, - hook: fn(&Emulator, &mut QT, &mut S, src: u64, dest: u64) -> Option, - ) { - unsafe { - GEN_EDGE_HOOK_PTR = hook as *const _; - } - self.emulator - .set_gen_edge_hook(gen_edge_hook_wrapper::); - } - - #[allow(clippy::unused_self)] - pub fn hook_edge_execution(&self, hook: fn(&Emulator, &mut QT, &mut S, id: u64)) { - unsafe { - EDGE_HOOKS.push(hook as *const _); - } - self.emulator - .set_exec_edge_hook(edge_hooks_wrapper::); - } - - #[allow(clippy::unused_self)] - pub fn hook_block_generation( - &self, - hook: fn(&Emulator, &mut QT, &mut S, pc: u64) -> Option, - ) { - unsafe { - GEN_BLOCK_HOOK_PTR = hook as *const _; - } - self.emulator - .set_gen_block_hook(gen_block_hook_wrapper::); - } - - #[allow(clippy::unused_self)] - pub fn hook_block_execution(&self, hook: fn(&Emulator, &mut QT, &mut S, id: u64)) { - unsafe { - BLOCK_HOOKS.push(hook as *const _); - } - self.emulator - .set_exec_block_hook(block_hooks_wrapper::); - } - - #[allow(clippy::unused_self)] - pub fn hook_read_generation( - &self, - hook: fn(&Emulator, &mut QT, &mut S, size: usize) -> Option, - ) { - unsafe { - GEN_READ_HOOK_PTR = hook as *const _; - } - self.emulator - .set_gen_read_hook(gen_read_hook_wrapper::); - } - - #[allow(clippy::unused_self)] - pub fn hook_read1_execution(&self, hook: FixedLenHook) { - unsafe { - READ1_HOOKS.push(hook as *const _); - } - self.emulator - .set_exec_read1_hook(read1_hooks_wrapper::); - } - - #[allow(clippy::unused_self)] - pub fn hook_read2_execution(&self, hook: FixedLenHook) { - unsafe { - READ2_HOOKS.push(hook as *const _); - } - self.emulator - .set_exec_read2_hook(read2_hooks_wrapper::); - } - - #[allow(clippy::unused_self)] - pub fn hook_read4_execution(&self, hook: FixedLenHook) { - unsafe { - READ4_HOOKS.push(hook as *const _); - } - self.emulator - .set_exec_read4_hook(read4_hooks_wrapper::); - } - - #[allow(clippy::unused_self)] - pub fn hook_read8_execution(&self, hook: FixedLenHook) { - unsafe { - READ8_HOOKS.push(hook as *const _); - } - self.emulator - .set_exec_read8_hook(read8_hooks_wrapper::); - } - - #[allow(clippy::unused_self)] - pub fn hook_read_n_execution(&self, hook: DynamicLenHook) { - unsafe { - READ_N_HOOKS.push(hook as *const _); - } - self.emulator - .set_exec_read_n_hook(read_n_hooks_wrapper::); - } - - #[allow(clippy::unused_self)] - pub fn hook_write_generation( - &self, - hook: fn(&Emulator, &mut QT, &mut S, size: usize) -> Option, - ) { - unsafe { - GEN_WRITE_HOOK_PTR = hook as *const _; - } - self.emulator - .set_gen_write_hook(gen_write_hook_wrapper::); - } - - #[allow(clippy::unused_self)] - pub fn hook_write1_execution(&self, hook: FixedLenHook) { - unsafe { - WRITE1_HOOKS.push(hook as *const _); - } - self.emulator - .set_exec_write1_hook(write1_hooks_wrapper::); - } - - #[allow(clippy::unused_self)] - pub fn hook_write2_execution(&self, hook: FixedLenHook) { - unsafe { - WRITE2_HOOKS.push(hook as *const _); - } - self.emulator - .set_exec_write2_hook(write2_hooks_wrapper::); - } - - #[allow(clippy::unused_self)] - pub fn hook_write4_execution(&self, hook: FixedLenHook) { - unsafe { - WRITE4_HOOKS.push(hook as *const _); - } - self.emulator - .set_exec_write4_hook(write4_hooks_wrapper::); - } - - #[allow(clippy::unused_self)] - pub fn hook_write8_execution(&self, hook: FixedLenHook) { - unsafe { - WRITE8_HOOKS.push(hook as *const _); - } - self.emulator - .set_exec_write8_hook(write8_hooks_wrapper::); - } - - #[allow(clippy::unused_self)] - pub fn hook_write_n_execution(&self, hook: DynamicLenHook) { - unsafe { - WRITE_N_HOOKS.push(hook as *const _); - } - self.emulator - .set_exec_write_n_hook(write_n_hooks_wrapper::); - } - - #[allow(clippy::unused_self)] - pub fn hook_cmp_generation( - &self, - hook: fn(&Emulator, &mut QT, &mut S, pc: u64, size: usize) -> Option, - ) { - unsafe { - GEN_CMP_HOOK_PTR = hook as *const _; - } - self.emulator - .set_gen_cmp_hook(gen_cmp_hook_wrapper::); - } - - #[allow(clippy::unused_self)] - pub fn hook_cmp1_execution( - &self, - hook: fn(&Emulator, &mut QT, &mut S, id: u64, v0: u8, v1: u8), - ) { - unsafe { - CMP1_HOOKS.push(hook as *const _); - } - self.emulator - .set_exec_cmp1_hook(cmp1_hooks_wrapper::); - } - - #[allow(clippy::unused_self)] - pub fn hook_cmp2_execution( - &self, - hook: fn(&Emulator, &mut QT, &mut S, id: u64, v0: u16, v1: u16), - ) { - unsafe { - CMP2_HOOKS.push(hook as *const _); - } - self.emulator - .set_exec_cmp2_hook(cmp2_hooks_wrapper::); - } - - #[allow(clippy::unused_self)] - pub fn hook_cmp4_execution( - &self, - hook: fn(&Emulator, &mut QT, &mut S, id: u64, v0: u32, v1: u32), - ) { - unsafe { - CMP4_HOOKS.push(hook as *const _); - } - self.emulator - .set_exec_cmp4_hook(cmp4_hooks_wrapper::); - } - - #[allow(clippy::unused_self)] - pub fn hook_cmp8_execution( - &self, - hook: fn(&Emulator, &mut QT, &mut S, id: u64, v0: u64, v1: u64), - ) { - unsafe { - CMP8_HOOKS.push(hook as *const _); - } - self.emulator - .set_exec_cmp8_hook(cmp8_hooks_wrapper::); - } - - #[allow(clippy::unused_self)] - #[allow(clippy::type_complexity)] - pub fn hook_syscalls( - &self, - hook: fn( - &Emulator, - &mut QT, - &mut S, - sys_num: i32, - u64, - u64, - u64, - u64, - u64, - u64, - u64, - u64, - ) -> SyscallHookResult, - ) { - unsafe { - SYSCALL_HOOKS.push(hook as *const _); - } - self.emulator - .set_pre_syscall_hook(syscall_hooks_wrapper::); - } - - #[allow(clippy::unused_self)] - #[allow(clippy::type_complexity)] - pub fn hook_after_syscalls( - &self, - hook: fn( - &Emulator, - &mut QT, - &mut S, - result: u64, - sys_num: i32, - u64, - u64, - u64, - u64, - u64, - u64, - u64, - u64, - ) -> u64, - ) { - unsafe { - SYSCALL_POST_HOOKS.push(hook as *const _); - } - self.emulator - .set_post_syscall_hook(syscall_after_hooks_wrapper::); + self.hooks.emulator() } } @@ -814,11 +107,22 @@ where mgr: &mut EM, input: &I, ) -> Result { - unsafe { QEMU_HELPERS_PTR = addr_of!(self.helpers) as *const c_void }; - self.helpers.pre_exec_all(self.emulator, input); + let emu = Emulator::new_empty(); + unsafe { + self.hooks + .as_mut() + .get_unchecked_mut() + .helpers_mut() + .pre_exec_all(&emu, input); + } let r = self.inner.run_target(fuzzer, state, mgr, input); - self.helpers.post_exec_all(self.emulator, input); - unsafe { QEMU_HELPERS_PTR = ptr::null() }; + unsafe { + self.hooks + .as_mut() + .get_unchecked_mut() + .helpers_mut() + .post_exec_all(&emu, input); + } r } } @@ -840,3 +144,145 @@ where self.inner.observers_mut() } } + +pub struct QemuForkExecutor<'a, H, I, OT, QT, S, SP> +where + H: FnMut(&I) -> ExitKind, + I: Input, + OT: ObserversTuple, + QT: QemuHelperTuple, + SP: ShMemProvider, +{ + hooks: Pin>>, + inner: InProcessForkExecutor<'a, H, I, OT, S, SP>, +} + +impl<'a, H, I, OT, QT, S, SP> Debug for QemuForkExecutor<'a, H, I, OT, QT, S, SP> +where + H: FnMut(&I) -> ExitKind, + I: Input, + OT: ObserversTuple, + QT: QemuHelperTuple, + SP: ShMemProvider, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("QemuForkExecutor") + .field("hooks", &self.hooks) + .field("inner", &self.inner) + .finish() + } +} + +impl<'a, H, I, OT, QT, S, SP> QemuForkExecutor<'a, H, I, OT, QT, S, SP> +where + H: FnMut(&I) -> ExitKind, + I: Input, + OT: ObserversTuple, + QT: QemuHelperTuple, + SP: ShMemProvider, +{ + pub fn new( + hooks: Pin>>, + harness_fn: &'a mut H, + observers: OT, + fuzzer: &mut Z, + state: &mut S, + event_mgr: &mut EM, + shmem_provider: SP, + ) -> Result + where + EM: EventFirer + EventRestarter, + OF: Feedback, + S: HasSolutions + HasClientPerfMonitor, + Z: HasObjective, + { + assert!(!QT::HOOKS_DO_SIDE_EFFECTS, "When using QemuForkExecutor, the hooks must not do any side effect as they will happen in the child process and then discarded"); + + Ok(Self { + hooks, + inner: InProcessForkExecutor::new( + harness_fn, + observers, + fuzzer, + state, + event_mgr, + shmem_provider, + )?, + }) + } + + pub fn inner(&self) -> &InProcessForkExecutor<'a, H, I, OT, S, SP> { + &self.inner + } + + pub fn inner_mut(&mut self) -> &mut InProcessForkExecutor<'a, H, I, OT, S, SP> { + &mut self.inner + } + + pub fn hooks(&self) -> &Pin>> { + &self.hooks + } + + pub fn hooks_mut(&mut self) -> &mut Pin>> { + &mut self.hooks + } + + pub fn emulator(&self) -> &Emulator { + self.hooks.emulator() + } +} + +impl<'a, EM, H, I, OT, QT, S, Z, SP> Executor + for QemuForkExecutor<'a, H, I, OT, QT, S, SP> +where + H: FnMut(&I) -> ExitKind, + I: Input, + OT: ObserversTuple, + QT: QemuHelperTuple, + SP: ShMemProvider, +{ + fn run_target( + &mut self, + fuzzer: &mut Z, + state: &mut S, + mgr: &mut EM, + input: &I, + ) -> Result { + let emu = Emulator::new_empty(); + unsafe { + self.hooks + .as_mut() + .get_unchecked_mut() + .helpers_mut() + .pre_exec_all(&emu, input); + } + let r = self.inner.run_target(fuzzer, state, mgr, input); + unsafe { + self.hooks + .as_mut() + .get_unchecked_mut() + .helpers_mut() + .post_exec_all(&emu, input); + } + r + } +} + +impl<'a, H, I, OT, QT, S, SP> HasObservers for QemuForkExecutor<'a, H, I, OT, QT, S, SP> +where + H: FnMut(&I) -> ExitKind, + I: Input, + OT: ObserversTuple, + QT: QemuHelperTuple, + SP: ShMemProvider, +{ + #[inline] + fn observers(&self) -> &OT { + self.inner.observers() + } + + #[inline] + fn observers_mut(&mut self) -> &mut OT { + self.inner.observers_mut() + } +} diff --git a/libafl_qemu/src/helper.rs b/libafl_qemu/src/helper.rs index 49596ff672..cb71b473e2 100644 --- a/libafl_qemu/src/helper.rs +++ b/libafl_qemu/src/helper.rs @@ -1,9 +1,7 @@ -use core::{fmt::Debug, ops::Range}; -use libafl::{ - bolts::tuples::MatchFirstType, executors::ExitKind, inputs::Input, observers::ObserversTuple, -}; +use core::{fmt::Debug, ops::Range, pin::Pin}; +use libafl::{bolts::tuples::MatchFirstType, inputs::Input}; -use crate::{emu::Emulator, executor::QemuExecutor}; +use crate::{emu::Emulator, hooks::QemuHooks}; /// A helper for `libafl_qemu`. // TODO remove 'static when specialization will be stable @@ -13,10 +11,8 @@ where { const HOOKS_DO_SIDE_EFFECTS: bool = true; - fn init<'a, H, OT, QT>(&self, _executor: &QemuExecutor<'a, H, I, OT, QT, S>) + fn init_hooks<'a, QT>(&self, _hooks: Pin<&QemuHooks<'a, I, QT, S>>) where - H: FnMut(&I) -> ExitKind, - OT: ObserversTuple, QT: QemuHelperTuple, { } @@ -30,10 +26,10 @@ pub trait QemuHelperTuple: MatchFirstType + Debug where I: Input, { - fn init_all<'a, H, OT, QT>(&self, executor: &QemuExecutor<'a, H, I, OT, QT, S>) + const HOOKS_DO_SIDE_EFFECTS: bool; + + fn init_hooks_all<'a, QT>(&self, hooks: Pin<&QemuHooks<'a, I, QT, S>>) where - H: FnMut(&I) -> ExitKind, - OT: ObserversTuple, QT: QemuHelperTuple; fn pre_exec_all(&mut self, _emulator: &Emulator, input: &I); @@ -45,10 +41,10 @@ impl QemuHelperTuple for () where I: Input, { - fn init_all<'a, H, OT, QT>(&self, _executor: &QemuExecutor<'a, H, I, OT, QT, S>) + const HOOKS_DO_SIDE_EFFECTS: bool = false; + + fn init_hooks_all<'a, QT>(&self, _hooks: Pin<&QemuHooks<'a, I, QT, S>>) where - H: FnMut(&I) -> ExitKind, - OT: ObserversTuple, QT: QemuHelperTuple, { } @@ -64,14 +60,14 @@ where Tail: QemuHelperTuple, I: Input, { - fn init_all<'a, H, OT, QT>(&self, executor: &QemuExecutor<'a, H, I, OT, QT, S>) + const HOOKS_DO_SIDE_EFFECTS: bool = Head::HOOKS_DO_SIDE_EFFECTS || Tail::HOOKS_DO_SIDE_EFFECTS; + + fn init_hooks_all<'a, QT>(&self, hooks: Pin<&QemuHooks<'a, I, QT, S>>) where - H: FnMut(&I) -> ExitKind, - OT: ObserversTuple, QT: QemuHelperTuple, { - self.0.init(executor); - self.1.init_all(executor); + self.0.init_hooks(hooks); + self.1.init_hooks_all(hooks); } fn pre_exec_all(&mut self, emulator: &Emulator, input: &I) { diff --git a/libafl_qemu/src/hooks.rs b/libafl_qemu/src/hooks.rs new file mode 100644 index 0000000000..c32dcc573a --- /dev/null +++ b/libafl_qemu/src/hooks.rs @@ -0,0 +1,860 @@ +//! The high-level hooks +use core::{ + ffi::c_void, + fmt::{self, Debug, Formatter}, + marker::{PhantomData, PhantomPinned}, + mem::transmute, + pin::Pin, + ptr::{self, addr_of}, +}; + +use libafl::{executors::inprocess::inprocess_get_state, inputs::Input}; + +pub use crate::emu::SyscallHookResult; +use crate::{ + emu::{Emulator, SKIP_EXEC_HOOK}, + helper::{QemuHelper, QemuHelperTuple}, + GuestAddr, +}; + +static mut QEMU_HELPERS_PTR: *const c_void = ptr::null(); +unsafe fn get_qemu_helpers<'a, QT>() -> &'a mut QT { + (QEMU_HELPERS_PTR as *mut QT) + .as_mut() + .expect("A high-level hook is installed but QemuHooks is not initialized") +} + +static mut GEN_EDGE_HOOK_PTR: *const c_void = ptr::null(); +extern "C" fn gen_edge_hook_wrapper(src: u64, dst: u64) -> u64 +where + I: Input, + QT: QemuHelperTuple, +{ + unsafe { + let helpers = get_qemu_helpers::(); + let emulator = Emulator::new_empty(); + let func: fn(&Emulator, &mut QT, Option<&mut S>, u64, u64) -> Option = + transmute(GEN_EDGE_HOOK_PTR); + (func)(&emulator, helpers, inprocess_get_state::(), src, dst) + .map_or(SKIP_EXEC_HOOK, |id| id) + } +} + +static mut EDGE_HOOKS: Vec<*const c_void> = vec![]; +extern "C" fn edge_hooks_wrapper(id: u64) +where + I: Input, + QT: QemuHelperTuple, +{ + let helpers = unsafe { get_qemu_helpers::() }; + let emulator = Emulator::new_empty(); + for hook in unsafe { &EDGE_HOOKS } { + let func: fn(&Emulator, &mut QT, Option<&mut S>, u64) = unsafe { transmute(*hook) }; + (func)(&emulator, helpers, inprocess_get_state::(), id); + } +} + +static mut GEN_BLOCK_HOOK_PTR: *const c_void = ptr::null(); +extern "C" fn gen_block_hook_wrapper(pc: u64) -> u64 +where + I: Input, + QT: QemuHelperTuple, +{ + unsafe { + let helpers = get_qemu_helpers::(); + let emulator = Emulator::new_empty(); + let func: fn(&Emulator, &mut QT, Option<&mut S>, u64) -> Option = + transmute(GEN_BLOCK_HOOK_PTR); + (func)(&emulator, helpers, inprocess_get_state::(), pc).map_or(SKIP_EXEC_HOOK, |id| id) + } +} + +static mut BLOCK_HOOKS: Vec<*const c_void> = vec![]; +extern "C" fn block_hooks_wrapper(id: u64) +where + I: Input, + QT: QemuHelperTuple, +{ + let helpers = unsafe { get_qemu_helpers::() }; + let emulator = Emulator::new_empty(); + for hook in unsafe { &BLOCK_HOOKS } { + let func: fn(&Emulator, &mut QT, Option<&mut S>, u64) = unsafe { transmute(*hook) }; + (func)(&emulator, helpers, inprocess_get_state::(), id); + } +} + +static mut GEN_READ_HOOK_PTR: *const c_void = ptr::null(); +extern "C" fn gen_read_hook_wrapper(size: u32) -> u64 +where + I: Input, + QT: QemuHelperTuple, +{ + unsafe { + let helpers = get_qemu_helpers::(); + let emulator = Emulator::new_empty(); + let func: fn(&Emulator, &mut QT, Option<&mut S>, usize) -> Option = + transmute(GEN_READ_HOOK_PTR); + (func)( + &emulator, + helpers, + inprocess_get_state::(), + size as usize, + ) + .map_or(SKIP_EXEC_HOOK, |id| id) + } +} + +static mut GEN_WRITE_HOOK_PTR: *const c_void = ptr::null(); +extern "C" fn gen_write_hook_wrapper(size: u32) -> u64 +where + I: Input, + QT: QemuHelperTuple, +{ + unsafe { + let helpers = get_qemu_helpers::(); + let emulator = Emulator::new_empty(); + let func: fn(&Emulator, &mut QT, Option<&mut S>, usize) -> Option = + transmute(GEN_WRITE_HOOK_PTR); + (func)( + &emulator, + helpers, + inprocess_get_state::(), + size as usize, + ) + .map_or(SKIP_EXEC_HOOK, |id| id) + } +} + +// function signature for Read or Write hook functions with known length (1, 2, 4, 8) +type FixedLenHook = fn(&Emulator, &mut QT, Option<&mut S>, u64, GuestAddr); + +// function signature for Read or Write hook functions with runtime length n +type DynamicLenHook = fn(&Emulator, &mut QT, Option<&mut S>, u64, GuestAddr, usize); + +static mut READ1_HOOKS: Vec<*const c_void> = vec![]; +extern "C" fn read1_hooks_wrapper(id: u64, addr: u64) +where + I: Input, + QT: QemuHelperTuple, +{ + let helpers = unsafe { get_qemu_helpers::() }; + let emulator = Emulator::new_empty(); + for hook in unsafe { &READ1_HOOKS } { + let func: FixedLenHook = unsafe { transmute(*hook) }; + (func)( + &emulator, + helpers, + inprocess_get_state::(), + id, + addr as GuestAddr, + ); + } +} + +static mut READ2_HOOKS: Vec<*const c_void> = vec![]; +extern "C" fn read2_hooks_wrapper(id: u64, addr: u64) +where + I: Input, + QT: QemuHelperTuple, +{ + let helpers = unsafe { get_qemu_helpers::() }; + let emulator = Emulator::new_empty(); + for hook in unsafe { &READ2_HOOKS } { + let func: FixedLenHook = unsafe { transmute(*hook) }; + (func)( + &emulator, + helpers, + inprocess_get_state::(), + id, + addr as GuestAddr, + ); + } +} + +static mut READ4_HOOKS: Vec<*const c_void> = vec![]; +extern "C" fn read4_hooks_wrapper(id: u64, addr: u64) +where + I: Input, + QT: QemuHelperTuple, +{ + let helpers = unsafe { get_qemu_helpers::() }; + let emulator = Emulator::new_empty(); + for hook in unsafe { &READ4_HOOKS } { + let func: FixedLenHook = unsafe { transmute(*hook) }; + (func)( + &emulator, + helpers, + inprocess_get_state::(), + id, + addr as GuestAddr, + ); + } +} + +static mut READ8_HOOKS: Vec<*const c_void> = vec![]; +extern "C" fn read8_hooks_wrapper(id: u64, addr: u64) +where + I: Input, + QT: QemuHelperTuple, +{ + let helpers = unsafe { get_qemu_helpers::() }; + let emulator = Emulator::new_empty(); + for hook in unsafe { &READ8_HOOKS } { + let func: FixedLenHook = unsafe { transmute(*hook) }; + (func)( + &emulator, + helpers, + inprocess_get_state::(), + id, + addr as GuestAddr, + ); + } +} + +static mut READ_N_HOOKS: Vec<*const c_void> = vec![]; +extern "C" fn read_n_hooks_wrapper(id: u64, addr: u64, size: u32) +where + I: Input, + QT: QemuHelperTuple, +{ + let helpers = unsafe { get_qemu_helpers::() }; + let emulator = Emulator::new_empty(); + for hook in unsafe { &READ_N_HOOKS } { + let func: DynamicLenHook = unsafe { transmute(*hook) }; + (func)( + &emulator, + helpers, + inprocess_get_state::(), + id, + addr as GuestAddr, + size as usize, + ); + } +} + +static mut WRITE1_HOOKS: Vec<*const c_void> = vec![]; +extern "C" fn write1_hooks_wrapper(id: u64, addr: u64) +where + I: Input, + QT: QemuHelperTuple, +{ + let helpers = unsafe { get_qemu_helpers::() }; + let emulator = Emulator::new_empty(); + for hook in unsafe { &WRITE1_HOOKS } { + let func: FixedLenHook = unsafe { transmute(*hook) }; + (func)( + &emulator, + helpers, + inprocess_get_state::(), + id, + addr as GuestAddr, + ); + } +} + +static mut WRITE2_HOOKS: Vec<*const c_void> = vec![]; +extern "C" fn write2_hooks_wrapper(id: u64, addr: u64) +where + I: Input, + QT: QemuHelperTuple, +{ + let helpers = unsafe { get_qemu_helpers::() }; + let emulator = Emulator::new_empty(); + for hook in unsafe { &WRITE2_HOOKS } { + let func: FixedLenHook = unsafe { transmute(*hook) }; + (func)( + &emulator, + helpers, + inprocess_get_state::(), + id, + addr as GuestAddr, + ); + } +} + +static mut WRITE4_HOOKS: Vec<*const c_void> = vec![]; +extern "C" fn write4_hooks_wrapper(id: u64, addr: u64) +where + I: Input, + QT: QemuHelperTuple, +{ + let helpers = unsafe { get_qemu_helpers::() }; + let emulator = Emulator::new_empty(); + for hook in unsafe { &WRITE4_HOOKS } { + let func: FixedLenHook = unsafe { transmute(*hook) }; + (func)( + &emulator, + helpers, + inprocess_get_state::(), + id, + addr as GuestAddr, + ); + } +} + +static mut WRITE8_HOOKS: Vec<*const c_void> = vec![]; +extern "C" fn write8_hooks_wrapper(id: u64, addr: u64) +where + I: Input, + QT: QemuHelperTuple, +{ + let helpers = unsafe { get_qemu_helpers::() }; + let emulator = Emulator::new_empty(); + for hook in unsafe { &WRITE8_HOOKS } { + let func: FixedLenHook = unsafe { transmute(*hook) }; + (func)( + &emulator, + helpers, + inprocess_get_state::(), + id, + addr as GuestAddr, + ); + } +} + +static mut WRITE_N_HOOKS: Vec<*const c_void> = vec![]; +extern "C" fn write_n_hooks_wrapper(id: u64, addr: u64, size: u32) +where + I: Input, + QT: QemuHelperTuple, +{ + let helpers = unsafe { get_qemu_helpers::() }; + let emulator = Emulator::new_empty(); + for hook in unsafe { &WRITE_N_HOOKS } { + let func: DynamicLenHook = unsafe { transmute(*hook) }; + (func)( + &emulator, + helpers, + inprocess_get_state::(), + id, + addr as GuestAddr, + size as usize, + ); + } +} + +static mut GEN_CMP_HOOK_PTR: *const c_void = ptr::null(); +extern "C" fn gen_cmp_hook_wrapper(pc: u64, size: u32) -> u64 +where + I: Input, + QT: QemuHelperTuple, +{ + unsafe { + let helpers = get_qemu_helpers::(); + let emulator = Emulator::new_empty(); + let func: fn(&Emulator, &mut QT, Option<&mut S>, u64, usize) -> Option = + transmute(GEN_CMP_HOOK_PTR); + (func)( + &emulator, + helpers, + inprocess_get_state::(), + pc, + size as usize, + ) + .map_or(SKIP_EXEC_HOOK, |id| id) + } +} + +static mut CMP1_HOOKS: Vec<*const c_void> = vec![]; +extern "C" fn cmp1_hooks_wrapper(id: u64, v0: u8, v1: u8) +where + I: Input, + QT: QemuHelperTuple, +{ + let helpers = unsafe { get_qemu_helpers::() }; + let emulator = Emulator::new_empty(); + for hook in unsafe { &CMP1_HOOKS } { + let func: fn(&Emulator, &mut QT, Option<&mut S>, u64, u8, u8) = unsafe { transmute(*hook) }; + (func)(&emulator, helpers, inprocess_get_state::(), id, v0, v1); + } +} + +static mut CMP2_HOOKS: Vec<*const c_void> = vec![]; +extern "C" fn cmp2_hooks_wrapper(id: u64, v0: u16, v1: u16) +where + I: Input, + QT: QemuHelperTuple, +{ + let helpers = unsafe { get_qemu_helpers::() }; + let emulator = Emulator::new_empty(); + for hook in unsafe { &CMP2_HOOKS } { + let func: fn(&Emulator, &mut QT, Option<&mut S>, u64, u16, u16) = + unsafe { transmute(*hook) }; + (func)(&emulator, helpers, inprocess_get_state::(), id, v0, v1); + } +} + +static mut CMP4_HOOKS: Vec<*const c_void> = vec![]; +extern "C" fn cmp4_hooks_wrapper(id: u64, v0: u32, v1: u32) +where + I: Input, + QT: QemuHelperTuple, +{ + let helpers = unsafe { get_qemu_helpers::() }; + let emulator = Emulator::new_empty(); + for hook in unsafe { &CMP4_HOOKS } { + let func: fn(&Emulator, &mut QT, Option<&mut S>, u64, u32, u32) = + unsafe { transmute(*hook) }; + (func)(&emulator, helpers, inprocess_get_state::(), id, v0, v1); + } +} + +static mut CMP8_HOOKS: Vec<*const c_void> = vec![]; +extern "C" fn cmp8_hooks_wrapper(id: u64, v0: u64, v1: u64) +where + I: Input, + QT: QemuHelperTuple, +{ + let helpers = unsafe { get_qemu_helpers::() }; + let emulator = Emulator::new_empty(); + for hook in unsafe { &CMP8_HOOKS } { + let func: fn(&Emulator, &mut QT, Option<&mut S>, u64, u64, u64) = + unsafe { transmute(*hook) }; + (func)(&emulator, helpers, inprocess_get_state::(), id, v0, v1); + } +} + +static mut SYSCALL_HOOKS: Vec<*const c_void> = vec![]; +extern "C" fn syscall_hooks_wrapper( + sys_num: i32, + a0: u64, + a1: u64, + a2: u64, + a3: u64, + a4: u64, + a5: u64, + a6: u64, + a7: u64, +) -> SyscallHookResult +where + I: Input, + QT: QemuHelperTuple, +{ + let helpers = unsafe { get_qemu_helpers::() }; + let emulator = Emulator::new_empty(); + let mut res = SyscallHookResult::new(None); + for hook in unsafe { &SYSCALL_HOOKS } { + #[allow(clippy::type_complexity)] + let func: fn( + &Emulator, + &mut QT, + Option<&mut S>, + i32, + u64, + u64, + u64, + u64, + u64, + u64, + u64, + u64, + ) -> SyscallHookResult = unsafe { transmute(*hook) }; + let r = (func)( + &emulator, + helpers, + inprocess_get_state::(), + sys_num, + a0, + a1, + a2, + a3, + a4, + a5, + a6, + a7, + ); + if r.skip_syscall { + res.skip_syscall = true; + res.retval = r.retval; + } + } + res +} + +static mut SYSCALL_POST_HOOKS: Vec<*const c_void> = vec![]; +extern "C" fn syscall_after_hooks_wrapper( + result: u64, + sys_num: i32, + a0: u64, + a1: u64, + a2: u64, + a3: u64, + a4: u64, + a5: u64, + a6: u64, + a7: u64, +) -> u64 +where + I: Input, + QT: QemuHelperTuple, +{ + let helpers = unsafe { get_qemu_helpers::() }; + let emulator = Emulator::new_empty(); + let mut res = result; + for hook in unsafe { &SYSCALL_POST_HOOKS } { + #[allow(clippy::type_complexity)] + let func: fn( + &Emulator, + &mut QT, + Option<&mut S>, + u64, + i32, + u64, + u64, + u64, + u64, + u64, + u64, + u64, + u64, + ) -> u64 = unsafe { transmute(*hook) }; + res = (func)( + &emulator, + helpers, + inprocess_get_state::(), + res, + sys_num, + a0, + a1, + a2, + a3, + a4, + a5, + a6, + a7, + ); + } + res +} + +static mut HOOKS_IS_INITIALIZED: bool = false; + +pub struct QemuHooks<'a, I, QT, S> +where + QT: QemuHelperTuple, + I: Input, +{ + helpers: QT, + emulator: &'a Emulator, + phantom: PhantomData<(I, S)>, + _pin: PhantomPinned, +} + +impl<'a, I, QT, S> Debug for QemuHooks<'a, I, QT, S> +where + I: Input, + QT: QemuHelperTuple, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("QemuHooks") + .field("helpers", &self.helpers) + .field("emulator", &self.emulator) + .finish() + } +} + +impl<'a, I, QT, S> QemuHooks<'a, I, QT, S> +where + QT: QemuHelperTuple, + I: Input, +{ + pub fn new(emulator: &'a Emulator, helpers: QT) -> Pin> { + unsafe { + assert!( + !HOOKS_IS_INITIALIZED, + "Only an instance of QemuHooks is permitted" + ); + HOOKS_IS_INITIALIZED = true; + } + let slf = Box::pin(Self { + emulator, + helpers, + phantom: PhantomData, + _pin: PhantomPinned, + }); + slf.helpers.init_hooks_all(slf.as_ref()); + unsafe { + QEMU_HELPERS_PTR = addr_of!(slf.helpers) as *const c_void; + } + slf + } + + #[must_use] + pub fn match_helper<'b, T>(self: &'b Pin<&mut Self>) -> Option<&'b T> + where + T: QemuHelper, + { + self.helpers.match_first_type::() + } + + #[must_use] + pub fn match_helper_mut<'b, T>(self: &'b mut Pin<&mut Self>) -> Option<&'b mut T> + where + T: QemuHelper, + { + unsafe { + self.as_mut() + .get_unchecked_mut() + .helpers + .match_first_type_mut::() + } + } + + pub fn emulator(&self) -> &Emulator { + self.emulator + } + + pub fn helpers(&self) -> &QT { + &self.helpers + } + + pub fn helpers_mut(&mut self) -> &mut QT { + &mut self.helpers + } + + pub fn edge_generation( + &self, + hook: fn(&Emulator, &mut QT, Option<&mut S>, src: u64, dest: u64) -> Option, + ) { + unsafe { + GEN_EDGE_HOOK_PTR = hook as *const _; + } + self.emulator + .set_gen_edge_hook(gen_edge_hook_wrapper::); + } + + pub fn edge_execution(&self, hook: fn(&Emulator, &mut QT, Option<&mut S>, id: u64)) { + unsafe { + EDGE_HOOKS.push(hook as *const _); + } + self.emulator + .set_exec_edge_hook(edge_hooks_wrapper::); + } + + pub fn block_generation( + &self, + hook: fn(&Emulator, &mut QT, Option<&mut S>, pc: u64) -> Option, + ) { + unsafe { + GEN_BLOCK_HOOK_PTR = hook as *const _; + } + self.emulator + .set_gen_block_hook(gen_block_hook_wrapper::); + } + + pub fn block_execution(&self, hook: fn(&Emulator, &mut QT, Option<&mut S>, id: u64)) { + unsafe { + BLOCK_HOOKS.push(hook as *const _); + } + self.emulator + .set_exec_block_hook(block_hooks_wrapper::); + } + + pub fn read_generation( + &self, + hook: fn(&Emulator, &mut QT, Option<&mut S>, size: usize) -> Option, + ) { + unsafe { + GEN_READ_HOOK_PTR = hook as *const _; + } + self.emulator + .set_gen_read_hook(gen_read_hook_wrapper::); + } + + pub fn read1_execution(&self, hook: FixedLenHook) { + unsafe { + READ1_HOOKS.push(hook as *const _); + } + self.emulator + .set_exec_read1_hook(read1_hooks_wrapper::); + } + + pub fn read2_execution(&self, hook: FixedLenHook) { + unsafe { + READ2_HOOKS.push(hook as *const _); + } + self.emulator + .set_exec_read2_hook(read2_hooks_wrapper::); + } + + pub fn read4_execution(&self, hook: FixedLenHook) { + unsafe { + READ4_HOOKS.push(hook as *const _); + } + self.emulator + .set_exec_read4_hook(read4_hooks_wrapper::); + } + + pub fn read8_execution(&self, hook: FixedLenHook) { + unsafe { + READ8_HOOKS.push(hook as *const _); + } + self.emulator + .set_exec_read8_hook(read8_hooks_wrapper::); + } + + pub fn read_n_execution(&self, hook: DynamicLenHook) { + unsafe { + READ_N_HOOKS.push(hook as *const _); + } + self.emulator + .set_exec_read_n_hook(read_n_hooks_wrapper::); + } + + pub fn write_generation( + &self, + hook: fn(&Emulator, &mut QT, Option<&mut S>, size: usize) -> Option, + ) { + unsafe { + GEN_WRITE_HOOK_PTR = hook as *const _; + } + self.emulator + .set_gen_write_hook(gen_write_hook_wrapper::); + } + + pub fn write1_execution(&self, hook: FixedLenHook) { + unsafe { + WRITE1_HOOKS.push(hook as *const _); + } + self.emulator + .set_exec_write1_hook(write1_hooks_wrapper::); + } + + pub fn write2_execution(&self, hook: FixedLenHook) { + unsafe { + WRITE2_HOOKS.push(hook as *const _); + } + self.emulator + .set_exec_write2_hook(write2_hooks_wrapper::); + } + + pub fn write4_execution(&self, hook: FixedLenHook) { + unsafe { + WRITE4_HOOKS.push(hook as *const _); + } + self.emulator + .set_exec_write4_hook(write4_hooks_wrapper::); + } + + pub fn write8_execution(&self, hook: FixedLenHook) { + unsafe { + WRITE8_HOOKS.push(hook as *const _); + } + self.emulator + .set_exec_write8_hook(write8_hooks_wrapper::); + } + + pub fn write_n_execution(&self, hook: DynamicLenHook) { + unsafe { + WRITE_N_HOOKS.push(hook as *const _); + } + self.emulator + .set_exec_write_n_hook(write_n_hooks_wrapper::); + } + + pub fn cmp_generation( + &self, + hook: fn(&Emulator, &mut QT, Option<&mut S>, pc: u64, size: usize) -> Option, + ) { + unsafe { + GEN_CMP_HOOK_PTR = hook as *const _; + } + self.emulator + .set_gen_cmp_hook(gen_cmp_hook_wrapper::); + } + + pub fn cmp1_execution( + &self, + hook: fn(&Emulator, &mut QT, Option<&mut S>, id: u64, v0: u8, v1: u8), + ) { + unsafe { + CMP1_HOOKS.push(hook as *const _); + } + self.emulator + .set_exec_cmp1_hook(cmp1_hooks_wrapper::); + } + + pub fn cmp2_execution( + &self, + hook: fn(&Emulator, &mut QT, Option<&mut S>, id: u64, v0: u16, v1: u16), + ) { + unsafe { + CMP2_HOOKS.push(hook as *const _); + } + self.emulator + .set_exec_cmp2_hook(cmp2_hooks_wrapper::); + } + + pub fn cmp4_execution( + &self, + hook: fn(&Emulator, &mut QT, Option<&mut S>, id: u64, v0: u32, v1: u32), + ) { + unsafe { + CMP4_HOOKS.push(hook as *const _); + } + self.emulator + .set_exec_cmp4_hook(cmp4_hooks_wrapper::); + } + + pub fn cmp8_execution( + &self, + hook: fn(&Emulator, &mut QT, Option<&mut S>, id: u64, v0: u64, v1: u64), + ) { + unsafe { + CMP8_HOOKS.push(hook as *const _); + } + self.emulator + .set_exec_cmp8_hook(cmp8_hooks_wrapper::); + } + + #[allow(clippy::type_complexity)] + pub fn syscalls( + &self, + hook: fn( + &Emulator, + &mut QT, + Option<&mut S>, + sys_num: i32, + u64, + u64, + u64, + u64, + u64, + u64, + u64, + u64, + ) -> SyscallHookResult, + ) { + unsafe { + SYSCALL_HOOKS.push(hook as *const _); + } + self.emulator + .set_pre_syscall_hook(syscall_hooks_wrapper::); + } + + #[allow(clippy::type_complexity)] + pub fn after_syscalls( + &self, + hook: fn( + &Emulator, + &mut QT, + Option<&mut S>, + result: u64, + sys_num: i32, + u64, + u64, + u64, + u64, + u64, + u64, + u64, + u64, + ) -> u64, + ) { + unsafe { + SYSCALL_POST_HOOKS.push(hook as *const _); + } + self.emulator + .set_post_syscall_hook(syscall_after_hooks_wrapper::); + } +} diff --git a/libafl_qemu/src/lib.rs b/libafl_qemu/src/lib.rs index ad7814edbe..541f98d0b5 100644 --- a/libafl_qemu/src/lib.rs +++ b/libafl_qemu/src/lib.rs @@ -1,9 +1,10 @@ // This lint triggers too often on the current GuestAddr type when emulating 64-bit targets because // u64::from(GuestAddr) is a no-op, but the .into() call is needed when GuestAddr is u32. #![cfg_attr( - any(feature = "x86_64", feature = "aarch64"), + any(cpu_target = "x86_64", cpu_target = "aarch64"), allow(clippy::useless_conversion) )] +#![allow(clippy::needless_pass_by_value)] use std::env; @@ -33,6 +34,10 @@ pub mod elf; pub mod helper; #[cfg(target_os = "linux")] pub use helper::*; +#[cfg(target_os = "linux")] +pub mod hooks; +#[cfg(target_os = "linux")] +pub use hooks::*; #[cfg(target_os = "linux")] pub mod edges; @@ -54,7 +59,7 @@ pub use asan::{init_with_asan, QemuAsanHelper}; #[cfg(target_os = "linux")] pub mod executor; #[cfg(target_os = "linux")] -pub use executor::QemuExecutor; +pub use executor::{QemuExecutor, QemuForkExecutor}; #[cfg(target_os = "linux")] pub mod emu; diff --git a/libafl_qemu/src/snapshot.rs b/libafl_qemu/src/snapshot.rs index 8c15b66df2..ed52866d06 100644 --- a/libafl_qemu/src/snapshot.rs +++ b/libafl_qemu/src/snapshot.rs @@ -1,16 +1,17 @@ use bio::data_structures::interval_tree::IntervalTree; -use libafl::{executors::ExitKind, inputs::Input, observers::ObserversTuple, state::HasMetadata}; +use libafl::{inputs::Input, state::HasMetadata}; use std::{ cell::UnsafeCell, collections::{HashMap, HashSet}, + pin::Pin, sync::Mutex, }; use thread_local::ThreadLocal; use crate::{ emu::{Emulator, MmapPerms}, - executor::QemuExecutor, helper::{QemuHelper, QemuHelperTuple}, + hooks::QemuHooks, GuestAddr, SYS_mmap, SYS_mprotect, SYS_mremap, }; @@ -184,19 +185,17 @@ where I: Input, S: HasMetadata, { - fn init<'a, H, OT, QT>(&self, executor: &QemuExecutor<'a, H, I, OT, QT, S>) + fn init_hooks<'a, QT>(&self, hooks: Pin<&QemuHooks<'a, I, QT, S>>) where - H: FnMut(&I) -> ExitKind, - OT: ObserversTuple, QT: QemuHelperTuple, { - executor.hook_write8_execution(trace_write8_snapshot::); - executor.hook_write4_execution(trace_write4_snapshot::); - executor.hook_write2_execution(trace_write2_snapshot::); - executor.hook_write1_execution(trace_write1_snapshot::); - executor.hook_write_n_execution(trace_write_n_snapshot::); + hooks.write8_execution(trace_write8_snapshot::); + hooks.write4_execution(trace_write4_snapshot::); + hooks.write2_execution(trace_write2_snapshot::); + hooks.write1_execution(trace_write1_snapshot::); + hooks.write_n_execution(trace_write_n_snapshot::); - executor.hook_after_syscalls(trace_mmap_snapshot::); + hooks.after_syscalls(trace_mmap_snapshot::); } fn pre_exec(&mut self, emulator: &Emulator, _input: &I) { @@ -211,7 +210,7 @@ where pub fn trace_write1_snapshot( _emulator: &Emulator, helpers: &mut QT, - _state: &mut S, + _state: Option<&mut S>, _id: u64, addr: GuestAddr, ) where @@ -227,7 +226,7 @@ pub fn trace_write1_snapshot( pub fn trace_write2_snapshot( _emulator: &Emulator, helpers: &mut QT, - _state: &mut S, + _state: Option<&mut S>, _id: u64, addr: GuestAddr, ) where @@ -243,7 +242,7 @@ pub fn trace_write2_snapshot( pub fn trace_write4_snapshot( _emulator: &Emulator, helpers: &mut QT, - _state: &mut S, + _state: Option<&mut S>, _id: u64, addr: GuestAddr, ) where @@ -259,7 +258,7 @@ pub fn trace_write4_snapshot( pub fn trace_write8_snapshot( _emulator: &Emulator, helpers: &mut QT, - _state: &mut S, + _state: Option<&mut S>, _id: u64, addr: GuestAddr, ) where @@ -275,7 +274,7 @@ pub fn trace_write8_snapshot( pub fn trace_write_n_snapshot( _emulator: &Emulator, helpers: &mut QT, - _state: &mut S, + _state: Option<&mut S>, _id: u64, addr: GuestAddr, size: usize, @@ -293,7 +292,7 @@ pub fn trace_write_n_snapshot( pub fn trace_mmap_snapshot( _emulator: &Emulator, helpers: &mut QT, - _state: &mut S, + _state: Option<&mut S>, result: u64, sys_num: i32, a0: u64, diff --git a/libafl_sugar/src/qemu.rs b/libafl_sugar/src/qemu.rs index 5b5cd00ce3..98471dcd43 100644 --- a/libafl_sugar/src/qemu.rs +++ b/libafl_sugar/src/qemu.rs @@ -34,7 +34,9 @@ use libafl::{ }; pub use libafl_qemu::emu::Emulator; -use libafl_qemu::{cmplog, edges, QemuCmpLogHelper, QemuEdgeCoverageHelper, QemuExecutor}; +use libafl_qemu::{ + cmplog, edges, QemuCmpLogHelper, QemuEdgeCoverageHelper, QemuExecutor, QemuHooks, +}; use libafl_targets::CmpLogObserver; use crate::{CORPUS_CACHE_SIZE, DEFAULT_TIMEOUT_SECS}; @@ -211,10 +213,17 @@ where }; if self.use_cmplog.unwrap_or(false) { - let executor = QemuExecutor::new( - &mut harness, + let hooks = QemuHooks::new( emulator, - tuple_list!(QemuEdgeCoverageHelper::new(), QemuCmpLogHelper::new()), + tuple_list!( + QemuEdgeCoverageHelper::default(), + QemuCmpLogHelper::default(), + ), + ); + + let executor = QemuExecutor::new( + hooks, + &mut harness, tuple_list!(edges_observer, time_observer), &mut fuzzer, &mut state, @@ -313,10 +322,12 @@ where } } } else { + let hooks = + QemuHooks::new(emulator, tuple_list!(QemuEdgeCoverageHelper::default())); + let executor = QemuExecutor::new( + hooks, &mut harness, - emulator, - tuple_list!(QemuEdgeCoverageHelper::new()), tuple_list!(edges_observer, time_observer), &mut fuzzer, &mut state, diff --git a/libafl_targets/src/cmplog.c b/libafl_targets/src/cmplog.c index b6ea283724..a8e2977dce 100644 --- a/libafl_targets/src/cmplog.c +++ b/libafl_targets/src/cmplog.c @@ -37,9 +37,11 @@ __attribute__((weak)) void *__asan_region_is_poisoned(void *beg, size_t size) { #endif +CmpLogMap* libafl_cmplog_map_ptr = &libafl_cmplog_map; + void __libafl_targets_cmplog_instructions(uintptr_t k, uint8_t shape, uint64_t arg1, uint64_t arg2) { - STATIC_ASSERT(sizeof(libafl_cmplog_map.vals.operands) == sizeof(libafl_cmplog_map.vals.routines)); + STATIC_ASSERT(sizeof(libafl_cmplog_map_ptr->vals.operands) == sizeof(libafl_cmplog_map_ptr->vals.routines)); __libafl_targets_cmplog(k, shape, arg1, arg2); @@ -106,20 +108,20 @@ void __libafl_targets_cmplog_routines(uintptr_t k, uint8_t *ptr1, uint8_t *ptr2) uint32_t hits; - if (libafl_cmplog_map.headers[k].kind != CMPLOG_KIND_RTN) { - libafl_cmplog_map.headers[k].kind = CMPLOG_KIND_RTN; - libafl_cmplog_map.headers[k].hits = 1; - libafl_cmplog_map.headers[k].shape = len - 1; + if (libafl_cmplog_map_ptr->headers[k].kind != CMPLOG_KIND_RTN) { + libafl_cmplog_map_ptr->headers[k].kind = CMPLOG_KIND_RTN; + libafl_cmplog_map_ptr->headers[k].hits = 1; + libafl_cmplog_map_ptr->headers[k].shape = len - 1; hits = 0; } else { - hits = libafl_cmplog_map.headers[k].hits++; - if (libafl_cmplog_map.headers[k].shape < len) - libafl_cmplog_map.headers[k].shape = len - 1; + hits = libafl_cmplog_map_ptr->headers[k].hits++; + if (libafl_cmplog_map_ptr->headers[k].shape < len) + libafl_cmplog_map_ptr->headers[k].shape = len - 1; } hits &= CMPLOG_MAP_RTN_H - 1; - MEMCPY(libafl_cmplog_map.vals.routines[k][hits].v0, ptr1, len); - MEMCPY(libafl_cmplog_map.vals.routines[k][hits].v1, ptr2, len); + MEMCPY(libafl_cmplog_map_ptr->vals.routines[k][hits].v0, ptr1, len); + MEMCPY(libafl_cmplog_map_ptr->vals.routines[k][hits].v1, ptr2, len); } diff --git a/libafl_targets/src/cmplog.h b/libafl_targets/src/cmplog.h index 41c43a12e5..5dbdca185c 100644 --- a/libafl_targets/src/cmplog.h +++ b/libafl_targets/src/cmplog.h @@ -42,6 +42,7 @@ typedef struct CmpLogMap { } CmpLogMap; extern CmpLogMap libafl_cmplog_map; +extern CmpLogMap* libafl_cmplog_map_ptr; extern uint8_t libafl_cmplog_enabled; @@ -54,21 +55,21 @@ static inline void __libafl_targets_cmplog(uintptr_t k, uint8_t shape, uint64_t if (!libafl_cmplog_enabled) return; uint16_t hits; - if (libafl_cmplog_map.headers[k].kind != CMPLOG_KIND_INS) { - libafl_cmplog_map.headers[k].kind = CMPLOG_KIND_INS; - libafl_cmplog_map.headers[k].hits = 1; - libafl_cmplog_map.headers[k].shape = shape; + if (libafl_cmplog_map_ptr->headers[k].kind != CMPLOG_KIND_INS) { + libafl_cmplog_map_ptr->headers[k].kind = CMPLOG_KIND_INS; + libafl_cmplog_map_ptr->headers[k].hits = 1; + libafl_cmplog_map_ptr->headers[k].shape = shape; hits = 0; } else { - hits = libafl_cmplog_map.headers[k].hits++; - if (libafl_cmplog_map.headers[k].shape < shape) { - libafl_cmplog_map.headers[k].shape = shape; + hits = libafl_cmplog_map_ptr->headers[k].hits++; + if (libafl_cmplog_map_ptr->headers[k].shape < shape) { + libafl_cmplog_map_ptr->headers[k].shape = shape; } } hits &= CMPLOG_MAP_H - 1; - libafl_cmplog_map.vals.operands[k][hits].v0 = arg1; - libafl_cmplog_map.vals.operands[k][hits].v1 = arg2; + libafl_cmplog_map_ptr->vals.operands[k][hits].v0 = arg1; + libafl_cmplog_map_ptr->vals.operands[k][hits].v1 = arg2; } diff --git a/libafl_targets/src/cmplog.rs b/libafl_targets/src/cmplog.rs index 0b514a6623..de49abb195 100644 --- a/libafl_targets/src/cmplog.rs +++ b/libafl_targets/src/cmplog.rs @@ -33,8 +33,13 @@ pub const CMPLOG_KIND_RTN: u8 = 1; extern "C" { /// Logs an instruction for feedback during fuzzing pub fn __libafl_targets_cmplog_instructions(k: usize, shape: u8, arg1: u64, arg2: u64); + + /// Pointer to the `CmpLog` map + pub static mut libafl_cmplog_map_ptr: *mut CmpLogMap; } +pub use libafl_cmplog_map_ptr as CMPLOG_MAP_PTR; + /// The header for `CmpLog` hits. #[repr(C)] #[derive(Default, Debug, Clone, Copy)] diff --git a/libafl_targets/src/coverage.rs b/libafl_targets/src/coverage.rs index a83e7c0e2a..81ef7949b2 100644 --- a/libafl_targets/src/coverage.rs +++ b/libafl_targets/src/coverage.rs @@ -41,7 +41,7 @@ pub use __afl_area_ptr as EDGES_MAP_PTR; /// /// # Safety /// -/// This fn is safe to call, as long as the compilation diid not break, previously +/// This fn is safe to call, as long as the compilation did not break, previously #[cfg(target_os = "linux")] pub fn autotokens() -> Result { unsafe {