diff --git a/fuzzers/fuzzbench_qemu/src/fuzzer.rs b/fuzzers/fuzzbench_qemu/src/fuzzer.rs index 7b5e3e874a..c6954e6561 100644 --- a/fuzzers/fuzzbench_qemu/src/fuzzer.rs +++ b/fuzzers/fuzzbench_qemu/src/fuzzer.rs @@ -40,8 +40,13 @@ use libafl::{ Error, }; use libafl_qemu::{ - amd64::Amd64Regs, elf::EasyElf, emu, filter_qemu_args, hooks, hooks::CmpLogObserver, MmapPerms, - QemuExecutor, + amd64::Amd64Regs, + elf::EasyElf, + emu, filter_qemu_args, + helpers::{QemuCmpLogHelper, QemuEdgeCoverageHelper, QemuSnapshotHelper}, + hooks, + hooks::CmpLogObserver, + MmapPerms, QemuExecutor, }; /// The fuzzer main @@ -296,21 +301,13 @@ fn fuzz( let executor = QemuExecutor::new( &mut harness, + tuple_list!(QemuEdgeCoverageHelper::new(), QemuCmpLogHelper::new(), QemuSnapshotHelper::new()), tuple_list!(edges_observer, time_observer), &mut fuzzer, &mut state, &mut mgr, )?; - // Track edge coverage - executor.hook_edge_generation(hooks::gen_unique_edge_ids); - executor.hook_edge_execution(hooks::trace_edge_hitcount); - // Track 2-4-8 cmps with CmpLog - executor.hook_cmp_generation(hooks::gen_unique_cmp_ids); - executor.hook_cmp8_execution(hooks::trace_cmp8_cmplog); - executor.hook_cmp4_execution(hooks::trace_cmp4_cmplog); - executor.hook_cmp2_execution(hooks::trace_cmp2_cmplog); - // Create the executor for an in-process function with one observer for edge coverage and one for the execution time let executor = TimeoutExecutor::new(executor, timeout); // Show the cmplog observer diff --git a/libafl/src/executors/inprocess.rs b/libafl/src/executors/inprocess.rs index 4fe09b307d..13db302c2b 100644 --- a/libafl/src/executors/inprocess.rs +++ b/libafl/src/executors/inprocess.rs @@ -79,10 +79,7 @@ where &mut data.current_input_ptr, input as *const _ as *const c_void, ); - write_volatile( - &mut data.observers_ptr, - &self.observers as *const _ as *const c_void, - ); + write_volatile(&mut data.executor_ptr, self as *const _ as *const c_void); data.crash_handler = self.crash_handler; data.timeout_handler = self.timeout_handler; // Direct raw pointers access /aliasing is pretty undefined behavior. @@ -99,10 +96,7 @@ where &mut data.current_input_ptr, input as *const _ as *const c_void, ); - write_volatile( - &mut data.observers_ptr, - &self.observers as *const _ as *const c_void, - ); + write_volatile(&mut data.executor_ptr, self as *const _ as *const c_void); data.crash_handler = self.crash_handler; data.timeout_handler = self.timeout_handler; // Direct raw pointers access /aliasing is pretty undefined behavior. @@ -182,9 +176,18 @@ where Ok(Self { harness_fn, observers, - crash_handler: unix_signal_handler::inproc_crash_handler:: - as *const _, + crash_handler: unix_signal_handler::inproc_crash_handler::< + Self, + EM, + I, + OC, + OF, + OT, + S, + Z, + > as *const _, timeout_handler: unix_signal_handler::inproc_timeout_handler::< + Self, EM, I, OC, @@ -206,6 +209,7 @@ where harness_fn, observers, crash_handler: windows_exception_handler::inproc_crash_handler::< + Self, EM, I, OC, @@ -215,6 +219,7 @@ where Z, > as *const _, timeout_handler: windows_exception_handler::inproc_timeout_handler::< + Self, EM, I, OC, @@ -254,7 +259,7 @@ pub struct InProcessExecutorHandlerData { pub state_ptr: *mut c_void, pub event_mgr_ptr: *mut c_void, pub fuzzer_ptr: *mut c_void, - pub observers_ptr: *const c_void, + pub executor_ptr: *const c_void, pub current_input_ptr: *const c_void, pub crash_handler: *const c_void, pub timeout_handler: *const c_void, @@ -273,8 +278,8 @@ pub static mut GLOBAL_STATE: InProcessExecutorHandlerData = InProcessExecutorHan event_mgr_ptr: ptr::null_mut(), /// The fuzzer ptr for signal handling fuzzer_ptr: ptr::null_mut(), - /// The observers ptr for signal handling - observers_ptr: ptr::null(), + /// The executor ptr for signal handling + executor_ptr: ptr::null(), /// The current input for signal handling current_input_ptr: ptr::null(), /// The crash handler fn @@ -286,20 +291,25 @@ pub static mut GLOBAL_STATE: InProcessExecutorHandlerData = InProcessExecutorHan }; #[must_use] -pub fn inprocess_run_state<'a, S>() -> Option<&'a mut S> { +pub fn inprocess_get_state<'a, S>() -> Option<&'a mut S> { unsafe { (GLOBAL_STATE.state_ptr as *mut S).as_mut() } } #[must_use] -pub fn inprocess_run_event_manager<'a, EM>() -> Option<&'a mut EM> { +pub fn inprocess_get_event_manager<'a, EM>() -> Option<&'a mut EM> { unsafe { (GLOBAL_STATE.event_mgr_ptr as *mut EM).as_mut() } } #[must_use] -pub fn inprocess_run_event_fuzzer<'a, F>() -> Option<&'a mut F> { +pub fn inprocess_get_fuzzer<'a, F>() -> Option<&'a mut F> { unsafe { (GLOBAL_STATE.fuzzer_ptr as *mut F).as_mut() } } +#[must_use] +pub fn inprocess_get_executor<'a, E>() -> Option<&'a mut E> { + unsafe { (GLOBAL_STATE.executor_ptr as *mut E).as_mut() } +} + #[cfg(unix)] mod unix_signal_handler { use alloc::vec::Vec; @@ -315,7 +325,7 @@ mod unix_signal_handler { executors::{ inprocess::{InProcessExecutorHandlerData, GLOBAL_STATE}, timeout::unix_remove_timeout, - ExitKind, + ExitKind, HasObservers, }, feedbacks::Feedback, fuzzer::HasObjective, @@ -374,12 +384,13 @@ mod unix_signal_handler { } #[cfg(unix)] - pub unsafe fn inproc_timeout_handler( + pub unsafe fn inproc_timeout_handler( _signal: Signal, _info: siginfo_t, _context: &mut ucontext_t, data: &mut InProcessExecutorHandlerData, ) where + E: HasObservers, EM: EventFirer + EventRestarter, OT: ObserversTuple, OC: Corpus, @@ -391,7 +402,8 @@ mod unix_signal_handler { let state = (data.state_ptr as *mut S).as_mut().unwrap(); let event_mgr = (data.event_mgr_ptr as *mut EM).as_mut().unwrap(); let fuzzer = (data.fuzzer_ptr as *mut Z).as_mut().unwrap(); - let observers = (data.observers_ptr as *const OT).as_ref().unwrap(); + let executor = (data.executor_ptr as *const E).as_ref().unwrap(); + let observers = executor.observers(); if data.current_input_ptr.is_null() { #[cfg(feature = "std")] @@ -450,12 +462,13 @@ mod unix_signal_handler { /// Will be used for signal handling. /// It will store the current State to shmem, then exit. #[allow(clippy::too_many_lines)] - pub unsafe fn inproc_crash_handler( + pub unsafe fn inproc_crash_handler( signal: Signal, _info: siginfo_t, _context: &mut ucontext_t, data: &mut InProcessExecutorHandlerData, ) where + E: HasObservers, EM: EventFirer + EventRestarter, OT: ObserversTuple, OC: Corpus, @@ -506,7 +519,8 @@ mod unix_signal_handler { let state = (data.state_ptr as *mut S).as_mut().unwrap(); let event_mgr = (data.event_mgr_ptr as *mut EM).as_mut().unwrap(); let fuzzer = (data.fuzzer_ptr as *mut Z).as_mut().unwrap(); - let observers = (data.observers_ptr as *const OT).as_ref().unwrap(); + let executor = (data.executor_ptr as *const E).as_ref().unwrap(); + let observers = executor.observers(); #[cfg(feature = "std")] println!("Child crashed!"); @@ -629,7 +643,7 @@ mod windows_exception_handler { executors::{ inprocess::{InProcessExecutorHandlerData, GLOBAL_STATE}, timeout::windows_delete_timer_queue, - ExitKind, + ExitKind, HasObservers, }, feedbacks::Feedback, fuzzer::HasObjective, @@ -665,10 +679,11 @@ mod windows_exception_handler { } } - pub unsafe extern "system" fn inproc_timeout_handler( + pub unsafe extern "system" fn inproc_timeout_handler( global_state: *mut c_void, _p1: u8, ) where + E: HasObservers, EM: EventFirer + EventRestarter, OT: ObserversTuple, OC: Corpus, @@ -683,7 +698,8 @@ mod windows_exception_handler { let state = (data.state_ptr as *mut S).as_mut().unwrap(); let event_mgr = (data.event_mgr_ptr as *mut EM).as_mut().unwrap(); let fuzzer = (data.fuzzer_ptr as *mut Z).as_mut().unwrap(); - let observers = (data.observers_ptr as *const OT).as_ref().unwrap(); + let executor = (data.executor_ptr as *const E).as_ref().unwrap(); + let observers = executor.observers(); if data.current_input_ptr.is_null() { #[cfg(feature = "std")] @@ -738,11 +754,12 @@ mod windows_exception_handler { // println!("TIMER INVOKED!"); } - pub unsafe fn inproc_crash_handler( + pub unsafe fn inproc_crash_handler( code: ExceptionCode, exception_pointers: *mut EXCEPTION_POINTERS, data: &mut InProcessExecutorHandlerData, ) where + E: HasObservers, EM: EventFirer + EventRestarter, OT: ObserversTuple, OC: Corpus, @@ -789,7 +806,8 @@ mod windows_exception_handler { let state = (data.state_ptr as *mut S).as_mut().unwrap(); let event_mgr = (data.event_mgr_ptr as *mut EM).as_mut().unwrap(); let fuzzer = (data.fuzzer_ptr as *mut Z).as_mut().unwrap(); - let observers = (data.observers_ptr as *const OT).as_ref().unwrap(); + let executor = (data.executor_ptr as *const E).as_ref().unwrap(); + let observers = executor.observers(); #[cfg(feature = "std")] println!("Child crashed!"); diff --git a/libafl_qemu/build.rs b/libafl_qemu/build.rs index 69f502758b..5ad04db206 100644 --- a/libafl_qemu/build.rs +++ b/libafl_qemu/build.rs @@ -1,13 +1,9 @@ -#[cfg(not(feature = "python"))] -use std::fs::copy; -#[cfg(feature = "python")] -use std::fs::read_dir; -use std::{env, path::Path, process::Command}; +use std::{env, fs, path::Path, process::Command}; use which::which; const QEMU_URL: &str = "https://github.com/AFLplusplus/qemu-libafl-bridge"; const QEMU_DIRNAME: &str = "qemu-libafl-bridge"; -const QEMU_REVISION: &str = "22daaa7d0c76b32f8391bad40c0b220f3e659f66"; +const QEMU_REVISION: &str = "6065cb8a84b305146d37ae540926bac439fc5601"; fn build_dep_check(tools: &[&str]) { for tool in tools { @@ -42,7 +38,15 @@ fn main() { build_dep_check(&["git", "make"]); + let qemu_rev = out_dir_path.join("QEMU_REVISION"); let qemu_path = out_dir_path.join(QEMU_DIRNAME); + + if qemu_rev.exists() + && fs::read_to_string(&qemu_rev).expect("Failed to read QEMU_REVISION") != QEMU_REVISION + { + fs::remove_dir_all(&qemu_path).unwrap(); + } + if !qemu_path.is_dir() { println!( "cargo:warning=Qemu not found, cloning with git ({})...", @@ -60,6 +64,7 @@ fn main() { .arg(QEMU_REVISION) .status() .unwrap(); + fs::write(&qemu_rev, QEMU_REVISION).unwrap(); } let build_dir = qemu_path.join("build"); @@ -164,7 +169,7 @@ fn main() { //build_dir.join("libhwcore.fa.p"), //build_dir.join("libcapstone.a.p"), ] { - for path in read_dir(dir).unwrap() { + for path in fs::read_dir(dir).unwrap() { let path = path.unwrap().path(); if path.is_file() { if let Some(name) = path.file_name() { @@ -224,7 +229,7 @@ fn main() { #[cfg(not(feature = "python"))] { - copy( + fs::copy( build_dir.join(&format!("libqemu-{}.so", cpu_target)), target_dir.join(&format!("libqemu-{}.so", cpu_target)), ) diff --git a/libafl_qemu/src/emu.rs b/libafl_qemu/src/emu.rs index 26450f8225..62b5687ded 100644 --- a/libafl_qemu/src/emu.rs +++ b/libafl_qemu/src/emu.rs @@ -20,6 +20,7 @@ pub const SKIP_EXEC_HOOK: u64 = u64::MAX; #[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy, EnumIter)] #[repr(i32)] pub enum MmapPerms { + None = 0, Read = libc::PROT_READ, Write = libc::PROT_WRITE, Execute = libc::PROT_EXEC, @@ -29,6 +30,41 @@ pub enum MmapPerms { ReadWriteExecute = libc::PROT_READ | libc::PROT_WRITE | libc::PROT_EXEC, } +impl MmapPerms { + #[must_use] + pub fn is_r(&self) -> bool { + matches!( + self, + MmapPerms::Read + | MmapPerms::ReadWrite + | MmapPerms::ReadExecute + | MmapPerms::ReadWriteExecute + ) + } + + #[must_use] + pub fn is_w(&self) -> bool { + matches!( + self, + MmapPerms::Write + | MmapPerms::ReadWrite + | MmapPerms::WriteExecute + | MmapPerms::ReadWriteExecute + ) + } + + #[must_use] + pub fn is_x(&self) -> bool { + matches!( + self, + MmapPerms::Execute + | MmapPerms::ReadExecute + | MmapPerms::WriteExecute + | MmapPerms::ReadWriteExecute + ) + } +} + #[cfg(feature = "python")] impl IntoPy for MmapPerms { fn into_py(self, py: Python) -> PyObject { @@ -144,6 +180,8 @@ extern "C" { fn libafl_qemu_remove_breakpoint(addr: u64) -> i32; fn libafl_qemu_run() -> i32; fn libafl_load_addr() -> u64; + fn libafl_get_brk() -> u64; + fn libafl_set_brk(brk: u64) -> u64; fn strlen(s: *const u8) -> usize; @@ -383,6 +421,15 @@ pub fn load_addr() -> u64 { unsafe { libafl_load_addr() } } +#[must_use] +pub fn get_brk() -> u64 { + unsafe { libafl_get_brk() } +} + +pub fn set_brk(brk: u64) { + unsafe { libafl_set_brk(brk) }; +} + pub fn map_private(addr: u64, size: usize, perms: MmapPerms) -> Result { let res = unsafe { target_mmap( @@ -409,6 +456,8 @@ pub fn unmap(addr: u64, size: usize) -> Result<(), String> { } } +// TODO add has_X_hook() and panic when setting a hook for the second time + pub fn set_exec_edge_hook(hook: extern "C" fn(id: u64)) { unsafe { libafl_exec_edge_hook = hook; @@ -499,6 +548,7 @@ pub fn set_exec_write_n_hook(hook: extern "C" fn(id: u64, addr: u64, size: u32)) } } +// TODO add pc arg pub fn set_gen_write_hook(hook: extern "C" fn(size: u32) -> u64) { unsafe { libafl_gen_write_hook = hook; diff --git a/libafl_qemu/src/executor.rs b/libafl_qemu/src/executor.rs index e271829f2a..86c3ada8ea 100644 --- a/libafl_qemu/src/executor.rs +++ b/libafl_qemu/src/executor.rs @@ -3,7 +3,9 @@ use core::{ffi::c_void, mem::transmute, ptr}; use libafl::{ corpus::Corpus, events::{EventFirer, EventRestarter}, - executors::{inprocess::GLOBAL_STATE, Executor, ExitKind, HasObservers, InProcessExecutor}, + executors::{ + inprocess::inprocess_get_state, Executor, ExitKind, HasObservers, InProcessExecutor, + }, feedbacks::Feedback, fuzzer::HasObjective, inputs::Input, @@ -13,56 +15,306 @@ use libafl::{ }; pub use crate::emu::SyscallHookResult; -use crate::{emu, emu::SKIP_EXEC_HOOK}; +use crate::{emu, emu::SKIP_EXEC_HOOK, helpers::QemuHelperTuple}; + +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 func: fn(&mut QT, &mut S, u64, u64) -> Option = transmute(GEN_EDGE_HOOK_PTR); + (func)(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(); + for hook in unsafe { &EDGE_HOOKS } { + let func: fn(&mut QT, &mut S, u64) = unsafe { transmute(*hook) }; + (func)(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 func: fn(&mut QT, &mut S, u64) -> Option = transmute(GEN_EDGE_HOOK_PTR); + (func)(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(); + for hook in unsafe { &BLOCK_HOOKS } { + let func: fn(&mut QT, &mut S, u64) = unsafe { transmute(*hook) }; + (func)(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 func: fn(&mut QT, &mut S, usize) -> Option = transmute(GEN_READ_HOOK_PTR); + (func)(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 func: fn(&mut QT, &mut S, usize) -> Option = transmute(GEN_WRITE_HOOK_PTR); + (func)(helpers, state, size as usize).map_or(SKIP_EXEC_HOOK, |id| id) + } +} + +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(); + for hook in unsafe { &READ1_HOOKS } { + let func: fn(&mut QT, &mut S, u64, u64) = unsafe { transmute(*hook) }; + (func)(helpers, state, id, addr); + } +} + +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(); + for hook in unsafe { &READ2_HOOKS } { + let func: fn(&mut QT, &mut S, u64, u64) = unsafe { transmute(*hook) }; + (func)(helpers, state, id, addr); + } +} + +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(); + for hook in unsafe { &READ4_HOOKS } { + let func: fn(&mut QT, &mut S, u64, u64) = unsafe { transmute(*hook) }; + (func)(helpers, state, id, addr); + } +} + +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(); + for hook in unsafe { &READ8_HOOKS } { + let func: fn(&mut QT, &mut S, u64, u64) = unsafe { transmute(*hook) }; + (func)(helpers, state, id, addr); + } +} + +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(); + for hook in unsafe { &READ_N_HOOKS } { + let func: fn(&mut QT, &mut S, u64, u64, usize) = unsafe { transmute(*hook) }; + (func)(helpers, state, id, addr, 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(); + for hook in unsafe { &WRITE1_HOOKS } { + let func: fn(&mut QT, &mut S, u64, u64) = unsafe { transmute(*hook) }; + (func)(helpers, state, id, addr); + } +} + +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(); + for hook in unsafe { &WRITE2_HOOKS } { + let func: fn(&mut QT, &mut S, u64, u64) = unsafe { transmute(*hook) }; + (func)(helpers, state, id, addr); + } +} + +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(); + for hook in unsafe { &WRITE4_HOOKS } { + let func: fn(&mut QT, &mut S, u64, u64) = unsafe { transmute(*hook) }; + (func)(helpers, state, id, addr); + } +} + +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(); + for hook in unsafe { &WRITE8_HOOKS } { + let func: fn(&mut QT, &mut S, u64, u64) = unsafe { transmute(*hook) }; + (func)(helpers, state, id, addr); + } +} + +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(); + for hook in unsafe { &WRITE_N_HOOKS } { + let func: fn(&mut QT, &mut S, u64, u64, usize) = unsafe { transmute(*hook) }; + (func)(helpers, state, id, addr, size as usize); + } +} + static mut GEN_CMP_HOOK_PTR: *const c_void = ptr::null(); -static mut SYSCALL_HOOK_PTR: *const c_void = ptr::null(); - -extern "C" fn gen_edge_hook_wrapper(src: u64, dst: u64) -> u64 { +extern "C" fn gen_cmp_hook_wrapper(pc: u64, size: u32) -> u64 +where + I: Input, + QT: QemuHelperTuple, +{ unsafe { - let state = (GLOBAL_STATE.state_ptr as *mut S).as_mut().unwrap(); - let func: fn(&mut S, u64, u64) -> Option = transmute(GEN_EDGE_HOOK_PTR); - (func)(state, src, dst).map_or(SKIP_EXEC_HOOK, |id| id) + let helpers = (QEMU_HELPERS_PTR as *mut QT).as_mut().unwrap(); + let state = inprocess_get_state::().unwrap(); + let func: fn(&mut QT, &mut S, u64, usize) -> Option = transmute(GEN_CMP_HOOK_PTR); + (func)(helpers, state, pc, size as usize).map_or(SKIP_EXEC_HOOK, |id| id) } } -extern "C" fn gen_block_hook_wrapper(pc: u64) -> u64 { - unsafe { - let state = (GLOBAL_STATE.state_ptr as *mut S).as_mut().unwrap(); - let func: fn(&mut S, u64) -> Option = transmute(GEN_BLOCK_HOOK_PTR); - (func)(state, pc).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(); + for hook in unsafe { &CMP1_HOOKS } { + let func: fn(&mut QT, &mut S, u64, u8, u8) = unsafe { transmute(*hook) }; + (func)(helpers, state, id, v0, v1); } } -extern "C" fn gen_read_hook_wrapper(size: u32) -> u64 { - unsafe { - let state = (GLOBAL_STATE.state_ptr as *mut S).as_mut().unwrap(); - let func: fn(&mut S, usize) -> Option = transmute(GEN_READ_HOOK_PTR); - (func)(state, size as usize).map_or(SKIP_EXEC_HOOK, |id| id) +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(); + for hook in unsafe { &CMP2_HOOKS } { + let func: fn(&mut QT, &mut S, u64, u16, u16) = unsafe { transmute(*hook) }; + (func)(helpers, state, id, v0, v1); } } -extern "C" fn gen_write_hook_wrapper(size: u32) -> u64 { - unsafe { - let state = (GLOBAL_STATE.state_ptr as *mut S).as_mut().unwrap(); - let func: fn(&mut S, usize) -> Option = transmute(GEN_WRITE_HOOK_PTR); - (func)(state, size as usize).map_or(SKIP_EXEC_HOOK, |id| id) +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(); + for hook in unsafe { &CMP4_HOOKS } { + let func: fn(&mut QT, &mut S, u64, u32, u32) = unsafe { transmute(*hook) }; + (func)(helpers, state, id, v0, v1); } } -extern "C" fn gen_cmp_hook_wrapper(pc: u64, size: u32) -> u64 { - unsafe { - let state = (GLOBAL_STATE.state_ptr as *mut S).as_mut().unwrap(); - let func: fn(&mut S, u64, usize) -> Option = transmute(GEN_CMP_HOOK_PTR); - (func)(state, pc, size as usize).map_or(SKIP_EXEC_HOOK, |id| id) +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(); + for hook in unsafe { &CMP8_HOOKS } { + let func: fn(&mut QT, &mut S, u64, u64, u64) = unsafe { transmute(*hook) }; + (func)(helpers, state, id, v0, v1); } } -extern "C" fn gen_syscall_hook_wrapper( +static mut SYSCALL_HOOKS: Vec<*const c_void> = vec![]; +extern "C" fn syscall_hooks_wrapper( sys_num: i32, a0: u64, a1: u64, @@ -72,32 +324,58 @@ extern "C" fn gen_syscall_hook_wrapper( a5: u64, a6: u64, a7: u64, -) -> SyscallHookResult { - unsafe { - let state = (GLOBAL_STATE.state_ptr as *mut S).as_mut().unwrap(); - let func: fn(&mut S, i32, u64, u64, u64, u64, u64, u64, u64, u64) -> SyscallHookResult = - transmute(SYSCALL_HOOK_PTR); - (func)(state, sys_num, a0, a1, a2, a3, a4, a5, a6, a7) +) -> 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 mut res = SyscallHookResult::new(None); + for hook in unsafe { &SYSCALL_HOOKS } { + let func: fn( + &mut QT, + &mut S, + i32, + u64, + u64, + u64, + u64, + u64, + u64, + u64, + u64, + ) -> SyscallHookResult = unsafe { transmute(*hook) }; + let r = (func)(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 } -pub struct QemuExecutor<'a, H, I, OT, S> +pub struct QemuExecutor<'a, H, I, OT, QT, S> where H: FnMut(&I) -> ExitKind, I: Input, OT: ObserversTuple, + QT: QemuHelperTuple, { + helpers: QT, inner: InProcessExecutor<'a, H, I, OT, S>, } -impl<'a, H, I, OT, S> QemuExecutor<'a, H, I, OT, S> +impl<'a, H, I, OT, QT, S> QemuExecutor<'a, H, I, OT, QT, S> where H: FnMut(&I) -> ExitKind, I: Input, OT: ObserversTuple, + QT: QemuHelperTuple, { pub fn new( harness_fn: &'a mut H, + helpers: QT, observers: OT, fuzzer: &mut Z, state: &mut S, @@ -110,9 +388,12 @@ where S: HasSolutions + HasClientPerfStats, Z: HasObjective, { - Ok(Self { + let slf = Self { + helpers, 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> { @@ -124,129 +405,191 @@ where } #[allow(clippy::unused_self)] - pub fn hook_edge_generation(&self, hook: fn(&mut S, src: u64, dest: u64) -> Option) { + pub fn hook_edge_generation( + &self, + hook: fn(&mut QT, &mut S, src: u64, dest: u64) -> Option, + ) { unsafe { GEN_EDGE_HOOK_PTR = hook as *const _; } - emu::set_gen_edge_hook(gen_edge_hook_wrapper::); + emu::set_gen_edge_hook(gen_edge_hook_wrapper::); } #[allow(clippy::unused_self)] - pub fn hook_edge_execution(&self, hook: extern "C" fn(id: u64)) { - emu::set_exec_edge_hook(hook); + pub fn hook_edge_execution(&self, hook: fn(&mut QT, &mut S, id: u64)) { + unsafe { + EDGE_HOOKS.push(hook as *const _); + } + emu::set_exec_edge_hook(edge_hooks_wrapper::); } #[allow(clippy::unused_self)] - pub fn hook_block_generation(&self, hook: fn(&mut S, pc: u64) -> Option) { + pub fn hook_block_generation(&self, hook: fn(&mut QT, &mut S, pc: u64) -> Option) { unsafe { GEN_BLOCK_HOOK_PTR = hook as *const _; } - emu::set_gen_block_hook(gen_block_hook_wrapper::); + emu::set_gen_block_hook(gen_block_hook_wrapper::); } #[allow(clippy::unused_self)] - pub fn hook_block_execution(&self, hook: extern "C" fn(id: u64)) { - emu::set_exec_block_hook(hook); + pub fn hook_block_execution(&self, hook: fn(&mut QT, &mut S, id: u64)) { + unsafe { + BLOCK_HOOKS.push(hook as *const _); + } + emu::set_exec_block_hook(block_hooks_wrapper::); } #[allow(clippy::unused_self)] - pub fn hook_read_generation(&self, hook: fn(&mut S, size: usize) -> Option) { + pub fn hook_read_generation(&self, hook: fn(&mut QT, &mut S, size: usize) -> Option) { unsafe { GEN_READ_HOOK_PTR = hook as *const _; } - emu::set_gen_read_hook(gen_read_hook_wrapper::); + emu::set_gen_read_hook(gen_read_hook_wrapper::); } #[allow(clippy::unused_self)] - pub fn hook_read1_execution(&self, hook: extern "C" fn(id: u64, addr: u64)) { - emu::set_exec_read1_hook(hook); + pub fn hook_read1_execution(&self, hook: fn(&mut QT, &mut S, id: u64, addr: u64)) { + unsafe { + READ1_HOOKS.push(hook as *const _); + } + emu::set_exec_read1_hook(read1_hooks_wrapper::); } #[allow(clippy::unused_self)] - pub fn hook_read2_execution(&self, hook: extern "C" fn(id: u64, addr: u64)) { - emu::set_exec_read2_hook(hook); + pub fn hook_read2_execution(&self, hook: fn(&mut QT, &mut S, id: u64, addr: u64)) { + unsafe { + READ2_HOOKS.push(hook as *const _); + } + emu::set_exec_read2_hook(read2_hooks_wrapper::); } #[allow(clippy::unused_self)] - pub fn hook_read4_execution(&self, hook: extern "C" fn(id: u64, addr: u64)) { - emu::set_exec_read4_hook(hook); + pub fn hook_read4_execution(&self, hook: fn(&mut QT, &mut S, id: u64, addr: u64)) { + unsafe { + READ4_HOOKS.push(hook as *const _); + } + emu::set_exec_read4_hook(read4_hooks_wrapper::); } #[allow(clippy::unused_self)] - pub fn hook_read8_execution(&self, hook: extern "C" fn(id: u64, addr: u64)) { - emu::set_exec_read8_hook(hook); + pub fn hook_read8_execution(&self, hook: fn(&mut QT, &mut S, id: u64, addr: u64)) { + unsafe { + READ8_HOOKS.push(hook as *const _); + } + emu::set_exec_read8_hook(read8_hooks_wrapper::); } #[allow(clippy::unused_self)] - pub fn hook_read_n_execution(&self, hook: extern "C" fn(id: u64, addr: u64, size: u32)) { - emu::set_exec_read_n_hook(hook); + pub fn hook_read_n_execution( + &self, + hook: fn(&mut QT, &mut S, id: u64, addr: u64, size: usize), + ) { + unsafe { + READ_N_HOOKS.push(hook as *const _); + } + emu::set_exec_read_n_hook(read_n_hooks_wrapper::); } #[allow(clippy::unused_self)] - pub fn hook_write_generation(&self, hook: fn(&mut S, size: usize) -> Option) { + pub fn hook_write_generation(&self, hook: fn(&mut QT, &mut S, size: usize) -> Option) { unsafe { GEN_WRITE_HOOK_PTR = hook as *const _; } - emu::set_gen_write_hook(gen_write_hook_wrapper::); + emu::set_gen_write_hook(gen_write_hook_wrapper::); } #[allow(clippy::unused_self)] - pub fn hook_write1_execution(&self, hook: extern "C" fn(id: u64, addr: u64)) { - emu::set_exec_write1_hook(hook); + pub fn hook_write1_execution(&self, hook: fn(&mut QT, &mut S, id: u64, addr: u64)) { + unsafe { + WRITE1_HOOKS.push(hook as *const _); + } + emu::set_exec_write1_hook(write1_hooks_wrapper::); } #[allow(clippy::unused_self)] - pub fn hook_write2_execution(&self, hook: extern "C" fn(id: u64, addr: u64)) { - emu::set_exec_write2_hook(hook); + pub fn hook_write2_execution(&self, hook: fn(&mut QT, &mut S, id: u64, addr: u64)) { + unsafe { + WRITE2_HOOKS.push(hook as *const _); + } + emu::set_exec_write2_hook(write2_hooks_wrapper::); } #[allow(clippy::unused_self)] - pub fn hook_write4_execution(&self, hook: extern "C" fn(id: u64, addr: u64)) { - emu::set_exec_write4_hook(hook); + pub fn hook_write4_execution(&self, hook: fn(&mut QT, &mut S, id: u64, addr: u64)) { + unsafe { + WRITE4_HOOKS.push(hook as *const _); + } + emu::set_exec_write4_hook(write4_hooks_wrapper::); } #[allow(clippy::unused_self)] - pub fn hook_write8_execution(&self, hook: extern "C" fn(id: u64, addr: u64)) { - emu::set_exec_write8_hook(hook); + pub fn hook_write8_execution(&self, hook: fn(&mut QT, &mut S, id: u64, addr: u64)) { + unsafe { + WRITE8_HOOKS.push(hook as *const _); + } + emu::set_exec_write8_hook(write8_hooks_wrapper::); } #[allow(clippy::unused_self)] - pub fn hook_write_n_execution(&self, hook: extern "C" fn(id: u64, addr: u64, size: u32)) { - emu::set_exec_write_n_hook(hook); + pub fn hook_write_n_execution( + &self, + hook: fn(&mut QT, &mut S, id: u64, addr: u64, size: usize), + ) { + unsafe { + WRITE_N_HOOKS.push(hook as *const _); + } + emu::set_exec_write_n_hook(write_n_hooks_wrapper::); } #[allow(clippy::unused_self)] - pub fn hook_cmp_generation(&self, hook: fn(&mut S, pc: u64, size: usize) -> Option) { + pub fn hook_cmp_generation( + &self, + hook: fn(&mut QT, &mut S, pc: u64, size: usize) -> Option, + ) { unsafe { GEN_CMP_HOOK_PTR = hook as *const _; } - emu::set_gen_cmp_hook(gen_cmp_hook_wrapper::); + emu::set_gen_cmp_hook(gen_cmp_hook_wrapper::); } #[allow(clippy::unused_self)] - pub fn hook_cmp1_execution(&self, hook: extern "C" fn(id: u64, v0: u8, v1: u8)) { - emu::set_exec_cmp1_hook(hook); + pub fn hook_cmp1_execution(&self, hook: fn(&mut QT, &mut S, id: u64, v0: u8, v1: u8)) { + unsafe { + CMP1_HOOKS.push(hook as *const _); + } + emu::set_exec_cmp1_hook(cmp1_hooks_wrapper::); } #[allow(clippy::unused_self)] - pub fn hook_cmp2_execution(&self, hook: extern "C" fn(id: u64, v0: u16, v1: u16)) { - emu::set_exec_cmp2_hook(hook); + pub fn hook_cmp2_execution(&self, hook: fn(&mut QT, &mut S, id: u64, v0: u16, v1: u16)) { + unsafe { + CMP2_HOOKS.push(hook as *const _); + } + emu::set_exec_cmp2_hook(cmp2_hooks_wrapper::); } #[allow(clippy::unused_self)] - pub fn hook_cmp4_execution(&self, hook: extern "C" fn(id: u64, v0: u32, v1: u32)) { - emu::set_exec_cmp4_hook(hook); + pub fn hook_cmp4_execution(&self, hook: fn(&mut QT, &mut S, id: u64, v0: u32, v1: u32)) { + unsafe { + CMP4_HOOKS.push(hook as *const _); + } + emu::set_exec_cmp4_hook(cmp4_hooks_wrapper::); } #[allow(clippy::unused_self)] - pub fn hook_cmp8_execution(&self, hook: extern "C" fn(id: u64, v0: u64, v1: u64)) { - emu::set_exec_cmp8_hook(hook); + pub fn hook_cmp8_execution(&self, hook: fn(&mut QT, &mut S, id: u64, v0: u64, v1: u64)) { + unsafe { + CMP8_HOOKS.push(hook as *const _); + } + emu::set_exec_cmp8_hook(cmp8_hooks_wrapper::); } #[allow(clippy::unused_self)] pub fn hook_syscalls( &self, - hook: extern "C" fn( + hook: fn( + &mut QT, + &mut S, sys_num: i32, u64, u64, @@ -257,27 +600,20 @@ where u64, u64, ) -> SyscallHookResult, - ) { - emu::set_syscall_hook(hook); - } - - #[allow(clippy::unused_self)] - pub fn hook_syscalls_state( - &self, - hook: fn(&mut S, sys_num: i32, u64, u64, u64, u64, u64, u64, u64, u64) -> SyscallHookResult, ) { unsafe { - SYSCALL_HOOK_PTR = hook as *const _; + SYSCALL_HOOKS.push(hook as *const _); } - emu::set_syscall_hook(gen_syscall_hook_wrapper::); + emu::set_syscall_hook(syscall_hooks_wrapper::); } } -impl<'a, EM, H, I, OT, S, Z> Executor for QemuExecutor<'a, H, I, OT, S> +impl<'a, EM, H, I, OT, QT, S, Z> Executor for QemuExecutor<'a, H, I, OT, QT, S> where H: FnMut(&I) -> ExitKind, I: Input, OT: ObserversTuple, + QT: QemuHelperTuple, { fn run_target( &mut self, @@ -286,15 +622,21 @@ where mgr: &mut EM, input: &I, ) -> Result { - self.inner.run_target(fuzzer, state, mgr, input) + unsafe { QEMU_HELPERS_PTR = &self.helpers as *const _ as *const c_void }; + self.helpers.pre_exec_all(input); + let r = self.inner.run_target(fuzzer, state, mgr, input); + self.helpers.post_exec_all(input); + unsafe { QEMU_HELPERS_PTR = ptr::null() }; + r } } -impl<'a, H, I, OT, S> HasObservers for QemuExecutor<'a, H, I, OT, S> +impl<'a, H, I, OT, QT, S> HasObservers for QemuExecutor<'a, H, I, OT, QT, S> where H: FnMut(&I) -> ExitKind, I: Input, OT: ObserversTuple, + QT: QemuHelperTuple, { #[inline] fn observers(&self) -> &OT { diff --git a/libafl_qemu/src/helpers.rs b/libafl_qemu/src/helpers.rs new file mode 100644 index 0000000000..f004201485 --- /dev/null +++ b/libafl_qemu/src/helpers.rs @@ -0,0 +1,261 @@ +use std::collections::HashMap; + +use libafl::{ + bolts::tuples::MatchFirstType, executors::ExitKind, inputs::Input, observers::ObserversTuple, + state::HasMetadata, +}; + +use crate::{emu, emu::GuestMaps, executor::QemuExecutor, hooks}; + +// TODO remove 'static when specialization will be stable +pub trait QemuHelper: 'static +where + I: Input, +{ + fn init<'a, H, OT, QT>(&self, _executor: &QemuExecutor<'a, H, I, OT, QT, S>) + where + H: FnMut(&I) -> ExitKind, + OT: ObserversTuple, + QT: QemuHelperTuple, + { + } + + fn pre_exec(&mut self, _input: &I) {} + + fn post_exec(&mut self, _input: &I) {} +} + +pub trait QemuHelperTuple: MatchFirstType +where + I: Input, +{ + fn init_all<'a, H, OT, QT>(&self, executor: &QemuExecutor<'a, H, I, OT, QT, S>) + where + H: FnMut(&I) -> ExitKind, + OT: ObserversTuple, + QT: QemuHelperTuple; + + fn pre_exec_all(&mut self, input: &I); + + fn post_exec_all(&mut self, input: &I); +} + +impl QemuHelperTuple for () +where + I: Input, +{ + fn init_all<'a, H, OT, QT>(&self, _executor: &QemuExecutor<'a, H, I, OT, QT, S>) + where + H: FnMut(&I) -> ExitKind, + OT: ObserversTuple, + QT: QemuHelperTuple, + { + } + + fn pre_exec_all(&mut self, _input: &I) {} + + fn post_exec_all(&mut self, _input: &I) {} +} + +impl QemuHelperTuple for (Head, Tail) +where + Head: QemuHelper, + Tail: QemuHelperTuple, + I: Input, +{ + fn init_all<'a, H, OT, QT>(&self, executor: &QemuExecutor<'a, H, I, OT, QT, S>) + where + H: FnMut(&I) -> ExitKind, + OT: ObserversTuple, + QT: QemuHelperTuple, + { + self.0.init(executor); + self.1.init_all(executor); + } + + fn pre_exec_all(&mut self, input: &I) { + self.0.pre_exec(input); + self.1.pre_exec_all(input); + } + + fn post_exec_all(&mut self, input: &I) { + self.0.post_exec(input); + self.1.post_exec_all(input); + } +} + +pub struct QemuEdgeCoverageHelper {} + +impl QemuEdgeCoverageHelper { + #[must_use] + pub fn new() -> Self { + Self {} + } +} + +impl QemuHelper for QemuEdgeCoverageHelper +where + I: Input, + S: HasMetadata, +{ + fn init<'a, H, OT, QT>(&self, executor: &QemuExecutor<'a, H, I, OT, QT, S>) + where + H: FnMut(&I) -> ExitKind, + OT: ObserversTuple, + QT: QemuHelperTuple, + { + executor.hook_edge_generation(hooks::gen_unique_edge_ids::); + emu::set_exec_edge_hook(hooks::trace_edge_hitcount); + } +} + +pub struct QemuCmpLogHelper {} + +impl QemuCmpLogHelper { + #[must_use] + pub fn new() -> Self { + Self {} + } +} + +impl QemuHelper for QemuCmpLogHelper +where + I: Input, + S: HasMetadata, +{ + fn init<'a, H, OT, QT>(&self, executor: &QemuExecutor<'a, H, I, OT, QT, S>) + where + H: FnMut(&I) -> ExitKind, + OT: ObserversTuple, + QT: QemuHelperTuple, + { + executor.hook_cmp_generation(hooks::gen_unique_cmp_ids::); + emu::set_exec_cmp8_hook(hooks::trace_cmp8_cmplog); + emu::set_exec_cmp4_hook(hooks::trace_cmp4_cmplog); + emu::set_exec_cmp2_hook(hooks::trace_cmp2_cmplog); + emu::set_exec_cmp1_hook(hooks::trace_cmp1_cmplog); + } +} + +pub const SNAPSHOT_PAGE_SIZE: usize = 4096; + +pub struct SnapshotPageInfo { + pub addr: u64, + pub dirty: bool, + pub data: [u8; SNAPSHOT_PAGE_SIZE], +} + +// TODO be thread-safe maybe with https://amanieu.github.io/thread_local-rs/thread_local/index.html +pub struct QemuSnapshotHelper { + pub access_cache: [u64; 4], + pub access_cache_idx: usize, + pub pages: HashMap, + pub dirty: Vec, + pub brk: u64, + pub empty: bool, +} + +impl QemuSnapshotHelper { + #[must_use] + pub fn new() -> Self { + Self { + access_cache: [u64::MAX; 4], + access_cache_idx: 0, + pages: HashMap::default(), + dirty: vec![], + brk: 0, + empty: true, + } + } + + pub fn snapshot(&mut self) { + self.brk = emu::get_brk(); + self.pages.clear(); + for map in GuestMaps::new() { + // TODO track all the pages OR track mproctect + if !map.flags().is_w() { + continue; + } + let mut addr = map.start(); + while addr < map.end() { + let mut info = SnapshotPageInfo { + addr, + dirty: false, + data: [0; SNAPSHOT_PAGE_SIZE], + }; + emu::read_mem(addr, &mut info.data); + self.pages.insert(addr, info); + addr += SNAPSHOT_PAGE_SIZE as u64; + } + } + self.empty = false; + } + + pub fn page_access(&mut self, page: u64) { + if self.access_cache[0] == page + || self.access_cache[1] == page + || self.access_cache[2] == page + || self.access_cache[3] == page + { + return; + } + self.access_cache[self.access_cache_idx] = page; + self.access_cache_idx = (self.access_cache_idx + 1) & 3; + if let Some(info) = self.pages.get_mut(&page) { + if info.dirty { + return; + } + info.dirty = true; + } + self.dirty.push(page); + } + + pub fn access(&mut self, addr: u64, size: usize) { + debug_assert!(size > 0); + let page = addr & (SNAPSHOT_PAGE_SIZE as u64 - 1); + self.page_access(page); + let second_page = (addr + size as u64 - 1) & (SNAPSHOT_PAGE_SIZE as u64 - 1); + if page != second_page { + self.page_access(second_page); + } + } + + pub fn reset(&mut self) { + self.access_cache = [u64::MAX; 4]; + self.access_cache_idx = 0; + while let Some(page) = self.dirty.pop() { + if let Some(info) = self.pages.get_mut(&page) { + emu::write_mem(page, &info.data); + info.dirty = false; + } + } + emu::set_brk(self.brk); + } +} + +impl QemuHelper for QemuSnapshotHelper +where + I: Input, + S: HasMetadata, +{ + fn init<'a, H, OT, QT>(&self, executor: &QemuExecutor<'a, H, I, OT, QT, S>) + where + H: FnMut(&I) -> ExitKind, + OT: ObserversTuple, + QT: QemuHelperTuple, + { + executor.hook_write8_execution(hooks::trace_write8_snapshot::); + executor.hook_write4_execution(hooks::trace_write4_snapshot::); + executor.hook_write2_execution(hooks::trace_write2_snapshot::); + executor.hook_write1_execution(hooks::trace_write1_snapshot::); + executor.hook_write_n_execution(hooks::trace_write_n_snapshot::); + } + + fn pre_exec(&mut self, _input: &I) { + if self.empty { + self.snapshot(); + } else { + self.reset(); + } + } +} diff --git a/libafl_qemu/src/hooks.rs b/libafl_qemu/src/hooks.rs index 91513a0040..257974bb34 100644 --- a/libafl_qemu/src/hooks.rs +++ b/libafl_qemu/src/hooks.rs @@ -2,12 +2,14 @@ use core::{cell::UnsafeCell, cmp::max}; use hashbrown::HashMap; use serde::{Deserialize, Serialize}; -use libafl::state::HasMetadata; +use libafl::{inputs::Input, state::HasMetadata}; pub use libafl_targets::{ cmplog::__libafl_targets_cmplog_instructions, CmpLogObserver, CMPLOG_MAP, CMPLOG_MAP_W, EDGES_MAP, EDGES_MAP_SIZE, MAX_EDGES_NUM, }; +use crate::helpers::{QemuHelperTuple, QemuSnapshotHelper}; + #[derive(Default, Serialize, Deserialize)] pub struct QemuEdgesMapMetadata { pub map: HashMap<(u64, u64), u64>, @@ -53,7 +55,12 @@ fn hash_me(mut x: u64) -> u64 { x } -pub fn gen_unique_edge_ids(state: &mut S, src: u64, dest: u64) -> Option +pub fn gen_unique_edge_ids( + _helpers: &mut QT, + state: &mut S, + src: u64, + dest: u64, +) -> Option where S: HasMetadata, { @@ -76,7 +83,12 @@ where } } -pub fn gen_hashed_edge_ids(_state: &mut S, src: u64, dest: u64) -> Option { +pub fn gen_hashed_edge_ids( + _helpers: &mut QT, + _state: &mut S, + src: u64, + dest: u64, +) -> Option { Some(hash_me(src) ^ hash_me(dest)) } @@ -92,11 +104,11 @@ pub extern "C" fn trace_edge_single(id: u64) { } } -pub fn gen_addr_block_ids(_state: &mut S, pc: u64) -> Option { +pub fn gen_addr_block_ids(_helpers: &mut QT, _state: &mut S, pc: u64) -> Option { Some(pc) } -pub fn gen_hashed_block_ids(_state: &mut S, pc: u64) -> Option { +pub fn gen_hashed_block_ids(_helpers: &mut QT, _state: &mut S, pc: u64) -> Option { Some(hash_me(pc)) } @@ -120,7 +132,12 @@ pub extern "C" fn trace_block_transition_single(id: u64) { } } -pub fn gen_unique_cmp_ids(state: &mut S, pc: u64, _size: usize) -> Option +pub fn gen_unique_cmp_ids( + _helpers: &mut QT, + state: &mut S, + pc: u64, + _size: usize, +) -> Option where S: HasMetadata, { @@ -163,3 +180,63 @@ pub extern "C" fn trace_cmp8_cmplog(id: u64, v0: u64, v1: u64) { __libafl_targets_cmplog_instructions(id as usize, 8, v0, v1); } } + +pub fn trace_write1_snapshot(helpers: &mut QT, _state: &mut S, _id: u64, addr: u64) +where + I: Input, + QT: QemuHelperTuple, +{ + let h = helpers + .match_first_type_mut::() + .unwrap(); + h.access(addr, 1); +} + +pub fn trace_write2_snapshot(helpers: &mut QT, _state: &mut S, _id: u64, addr: u64) +where + I: Input, + QT: QemuHelperTuple, +{ + let h = helpers + .match_first_type_mut::() + .unwrap(); + h.access(addr, 2); +} + +pub fn trace_write4_snapshot(helpers: &mut QT, _state: &mut S, _id: u64, addr: u64) +where + I: Input, + QT: QemuHelperTuple, +{ + let h = helpers + .match_first_type_mut::() + .unwrap(); + h.access(addr, 4); +} + +pub fn trace_write8_snapshot(helpers: &mut QT, _state: &mut S, _id: u64, addr: u64) +where + I: Input, + QT: QemuHelperTuple, +{ + let h = helpers + .match_first_type_mut::() + .unwrap(); + h.access(addr, 8); +} + +pub fn trace_write_n_snapshot( + helpers: &mut QT, + _state: &mut S, + _id: u64, + addr: u64, + size: usize, +) where + I: Input, + QT: QemuHelperTuple, +{ + let h = helpers + .match_first_type_mut::() + .unwrap(); + h.access(addr, size); +} diff --git a/libafl_qemu/src/lib.rs b/libafl_qemu/src/lib.rs index efb4d721ae..62bca35f6d 100644 --- a/libafl_qemu/src/lib.rs +++ b/libafl_qemu/src/lib.rs @@ -4,6 +4,7 @@ pub mod amd64; pub mod x86; pub mod elf; +#[cfg(target_os = "linux")] pub mod hooks; #[cfg(target_os = "linux")] @@ -16,6 +17,11 @@ pub mod emu; #[cfg(target_os = "linux")] pub use emu::*; +#[cfg(target_os = "linux")] +pub mod helpers; +#[cfg(target_os = "linux")] +pub use helpers::*; + #[must_use] pub fn filter_qemu_args() -> Vec { let mut args = vec![env::args().next().unwrap()]; @@ -114,6 +120,10 @@ pub fn python_module(py: Python, m: &PyModule) -> PyResult<()> { Err(PyValueError::new_err("Invalid perms")) } } + #[pyfn(m)] + fn unmap(addr: u64, size: usize) -> PyResult<()> { + emu::unmap(addr, size).map_err(PyValueError::new_err) + } extern "C" fn py_syscall_hook_wrapper( sys_num: i32, diff --git a/libafl_sugar/src/qemu.rs b/libafl_sugar/src/qemu.rs index 3b2396e757..ab55883fb2 100644 --- a/libafl_sugar/src/qemu.rs +++ b/libafl_sugar/src/qemu.rs @@ -30,7 +30,7 @@ use libafl::{ }; pub use libafl_qemu::emu; -use libafl_qemu::{hooks, QemuExecutor}; +use libafl_qemu::{hooks, QemuEdgeCoverageHelper, QemuExecutor}; use crate::{CORPUS_CACHE_SIZE, DEFAULT_TIMEOUT_SECS}; @@ -66,21 +66,6 @@ where /// Bytes harness #[builder(setter(strip_option))] harness: Option, - // Syscall hook - #[builder(default = None, setter(strip_option))] - syscall_hook: Option< - extern "C" fn( - sys_num: i32, - u64, - u64, - u64, - u64, - u64, - u64, - u64, - u64, - ) -> emu::SyscallHookResult, - >, } impl<'a, H> QemuBytesCoverageSugar<'a, H> @@ -178,21 +163,13 @@ where let executor = QemuExecutor::new( &mut harness, + tuple_list!(QemuEdgeCoverageHelper::new()), tuple_list!(edges_observer, time_observer), &mut fuzzer, &mut state, &mut mgr, )?; - // Track edge coverage - executor.hook_edge_generation(hooks::gen_unique_edge_ids); - executor.hook_edge_execution(hooks::trace_edge_hitcount); - - // Hook the syscalls - if let Some(hook) = self.syscall_hook { - executor.hook_syscalls(hook); - } - // Create the executor for an in-process function with one observer for edge coverage and one for the execution time let mut executor = TimeoutExecutor::new(executor, timeout);