diff --git a/fuzzers/binary_only/fuzzbench_qemu/Makefile.toml b/fuzzers/binary_only/fuzzbench_qemu/Makefile.toml index 1ebe6632bb..73b978b586 100644 --- a/fuzzers/binary_only/fuzzbench_qemu/Makefile.toml +++ b/fuzzers/binary_only/fuzzbench_qemu/Makefile.toml @@ -48,19 +48,21 @@ mac_alias = "run_unix" windows_alias = "unsupported" [tasks.run_unix] -command = "cargo" -args = [ - "run", - "--profile", - "${PROFILE}", - "./${FUZZER_NAME}", - "--", - "--libafl-in", - "../../inprocess/libfuzzer_libpng/corpus", - "--libafl-out", - "./out", - "./${FUZZER_NAME}", -] +script_runner = "@shell" +script = ''' +cargo build \ + --profile \ + ${PROFILE} + +${TARGET_DIR}/${PROFILE_DIR}/fuzzbench_qemu \ + --libafl-in \ + ../../inprocess/libfuzzer_libpng/corpus \ + --libafl-out \ + ./out \ + ./${FUZZER_NAME} \ + -- \ + ./${FUZZER_NAME} +''' dependencies = ["harness"] # Run the fuzzer diff --git a/fuzzers/binary_only/fuzzbench_qemu/src/fuzzer.rs b/fuzzers/binary_only/fuzzbench_qemu/src/fuzzer.rs index 5b1df52099..240d2932df 100644 --- a/fuzzers/binary_only/fuzzbench_qemu/src/fuzzer.rs +++ b/fuzzers/binary_only/fuzzbench_qemu/src/fuzzer.rs @@ -51,6 +51,7 @@ use libafl_qemu::{ // asan::{init_with_asan, QemuAsanHelper}, modules::cmplog::{CmpLogModule, CmpLogObserver}, modules::edges::StdEdgeCoverageModule, + modules::AsanModule, Emulator, GuestReg, //snapshot::QemuSnapshotHelper, diff --git a/fuzzers/binary_only/qemu_launcher/Makefile.toml b/fuzzers/binary_only/qemu_launcher/Makefile.toml index 23c00f433a..1f008f561b 100644 --- a/fuzzers/binary_only/qemu_launcher/Makefile.toml +++ b/fuzzers/binary_only/qemu_launcher/Makefile.toml @@ -375,6 +375,9 @@ echo "Profile: ${PROFILE}" export QEMU_LAUNCHER=${TARGET_DIR}/${PROFILE_DIR}/qemu_launcher ./tests/injection/test.sh || exit 1 + +# complie again with simple mgr +cargo build --profile=${PROFILE} --features="simplemgr" --target-dir=${TARGET_DIR} ./tests/qasan/test.sh || exit 1 ''' dependencies = ["build_unix"] diff --git a/fuzzers/binary_only/qemu_launcher/src/fuzzer.rs b/fuzzers/binary_only/qemu_launcher/src/fuzzer.rs index 64a661ed37..b4980f124e 100644 --- a/fuzzers/binary_only/qemu_launcher/src/fuzzer.rs +++ b/fuzzers/binary_only/qemu_launcher/src/fuzzer.rs @@ -86,7 +86,6 @@ impl Fuzzer { // The shared memory allocator #[cfg(not(feature = "simplemgr"))] let mut shmem_provider = StdShMemProvider::new()?; - /* If we are running in verbose, don't provide a replacement stdout, otherwise, use /dev/null */ #[cfg(not(feature = "simplemgr"))] let stdout = if self.options.verbose { @@ -97,34 +96,15 @@ impl Fuzzer { let client = Client::new(&self.options); - #[cfg(not(feature = "simplemgr"))] + #[cfg(feature = "simplemgr")] if self.options.rerun_input.is_some() { - // If we want to rerun a single input but we use a restarting mgr, we'll have to create a fake restarting mgr that doesn't actually restart. - // It's not pretty but better than recompiling with simplemgr. + // only for simplemgr + // DON'T USE LLMP HERE!! + // it doesn't work like that - // Just a random number, let's hope it's free :) - let broker_port = 13120; - let _fake_broker = LlmpBroker::create_attach_to_tcp( - shmem_provider.clone(), - tuple_list!(), - broker_port, - ) - .unwrap(); - - // To rerun an input, instead of using a launcher, we create dummy parameters and run the client directly. return client.run( None, - MonitorTypedEventManager::<_, M>::new( - LlmpEventManagerBuilder::builder().build_on_port( - shmem_provider.clone(), - broker_port, - EventConfig::AlwaysUnique, - None, - Some(StateRestorer::new( - shmem_provider.new_shmem(0x1000).unwrap(), - )), - )?, - ), + SimpleEventManager::new(monitor), ClientDescription::new(0, 0, CoreId(0)), ); } diff --git a/fuzzers/binary_only/qemu_launcher/tests/qasan/test.sh b/fuzzers/binary_only/qemu_launcher/tests/qasan/test.sh index dbccd12685..64164eca72 100755 --- a/fuzzers/binary_only/qemu_launcher/tests/qasan/test.sh +++ b/fuzzers/binary_only/qemu_launcher/tests/qasan/test.sh @@ -25,7 +25,7 @@ tests_expected=( "is 0 bytes to the right of the 10-byte chunk" "is 1 bytes to the left of the 10-byte chunk" "is 0 bytes inside the 10-byte chunk" - "is 0 bytes to the right of the 10-byte chunk" + "Invalid 11 bytes write at" "is 0 bytes inside the 10-byte chunk" "Test-Limits - No Error" ) diff --git a/libafl/src/executors/hooks/inprocess.rs b/libafl/src/executors/hooks/inprocess.rs index 6357f16592..8793d4a236 100644 --- a/libafl/src/executors/hooks/inprocess.rs +++ b/libafl/src/executors/hooks/inprocess.rs @@ -12,6 +12,8 @@ use core::{ #[cfg(all(target_os = "linux", feature = "std"))] use libafl_bolts::current_time; +#[cfg(all(unix, feature = "std"))] +use libafl_bolts::minibsod::{generate_minibsod_to_vec, BsodInfo}; #[cfg(all(unix, feature = "std", not(miri)))] use libafl_bolts::os::unix_signals::setup_signal_handler; #[cfg(all(windows, feature = "std"))] @@ -21,8 +23,6 @@ use windows::Win32::System::Threading::{CRITICAL_SECTION, PTP_TIMER}; #[cfg(feature = "std")] use crate::executors::hooks::timer::TimerStruct; -#[cfg(all(unix, feature = "std"))] -use crate::executors::hooks::unix::unix_signal_handler; use crate::{ events::{EventFirer, EventRestarter}, executors::{hooks::ExecutorHook, inprocess::HasInProcessHooks, Executor, HasObservers}, @@ -30,6 +30,13 @@ use crate::{ state::{HasExecutions, HasSolutions}, Error, HasObjective, }; +#[cfg(all(unix, feature = "std"))] +use crate::{ + executors::{ + hooks::unix::unix_signal_handler, inprocess::run_observers_and_save_state, ExitKind, + }, + state::HasCorpus, +}; #[cfg(any(unix, windows))] use crate::{inputs::Input, observers::ObserversTuple, state::HasCurrentTestcase}; @@ -386,52 +393,111 @@ unsafe impl Sync for InProcessExecutorHandlerData {} impl InProcessExecutorHandlerData { /// # Safety /// Only safe if not called twice and if the executor is not used from another borrow after this. - #[cfg(any(unix, feature = "std"))] + #[cfg(all(feature = "std", any(unix, windows)))] pub(crate) unsafe fn executor_mut<'a, E>(&self) -> &'a mut E { unsafe { (self.executor_ptr as *mut E).as_mut().unwrap() } } /// # Safety /// Only safe if not called twice and if the state is not used from another borrow after this. - #[cfg(any(unix, feature = "std"))] + #[cfg(all(feature = "std", any(unix, windows)))] pub(crate) unsafe fn state_mut<'a, S>(&self) -> &'a mut S { unsafe { (self.state_ptr as *mut S).as_mut().unwrap() } } /// # Safety /// Only safe if not called twice and if the event manager is not used from another borrow after this. - #[cfg(any(unix, feature = "std"))] + #[cfg(all(feature = "std", any(unix, windows)))] pub(crate) unsafe fn event_mgr_mut<'a, EM>(&self) -> &'a mut EM { unsafe { (self.event_mgr_ptr as *mut EM).as_mut().unwrap() } } /// # Safety /// Only safe if not called twice and if the fuzzer is not used from another borrow after this. - #[cfg(any(unix, feature = "std"))] + #[cfg(all(feature = "std", any(unix, windows)))] pub(crate) unsafe fn fuzzer_mut<'a, Z>(&self) -> &'a mut Z { unsafe { (self.fuzzer_ptr as *mut Z).as_mut().unwrap() } } /// # Safety /// Only safe if not called concurrently. - #[cfg(any(unix, feature = "std"))] + #[cfg(all(feature = "std", any(unix, windows)))] pub(crate) unsafe fn take_current_input<'a, I>(&mut self) -> &'a I { let r = unsafe { (self.current_input_ptr as *const I).as_ref().unwrap() }; self.current_input_ptr = ptr::null(); r } - #[cfg(any(unix, feature = "std"))] + #[cfg(all(feature = "std", any(unix, windows)))] pub(crate) fn is_valid(&self) -> bool { !self.current_input_ptr.is_null() } - #[cfg(any(unix, feature = "std"))] + #[cfg(all(feature = "std", any(unix, windows)))] pub(crate) fn set_in_handler(&mut self, v: bool) -> bool { let old = self.in_handler; self.in_handler = v; old } + + /// if data is valid, safely report a crash and return true. + /// return false otherwise. + /// + /// # Safety + /// + /// Should only be called to signal a crash in the target + #[cfg(all(unix, feature = "std"))] + pub unsafe fn maybe_report_crash( + &mut self, + bsod_info: Option, + ) -> bool + where + E: Executor + HasObservers, + E::Observers: ObserversTuple, + EM: EventFirer + EventRestarter, + OF: Feedback, + S: HasExecutions + HasSolutions + HasCorpus + HasCurrentTestcase, + Z: HasObjective, + I: Input + Clone, + { + if self.is_valid() { + let executor = self.executor_mut::(); + // disarms timeout in case of timeout + let state = self.state_mut::(); + let event_mgr = self.event_mgr_mut::(); + let fuzzer = self.fuzzer_mut::(); + let input = self.take_current_input::(); + + log::error!("Target crashed!"); + + if let Some(bsod_info) = bsod_info { + let bsod = generate_minibsod_to_vec( + bsod_info.signal, + &bsod_info.siginfo, + bsod_info.ucontext.as_ref(), + ); + + if let Ok(bsod) = bsod { + if let Ok(r) = std::str::from_utf8(&bsod) { + log::error!("{}", r); + } + } + } + + run_observers_and_save_state::( + executor, + state, + input, + fuzzer, + event_mgr, + ExitKind::Crash, + ); + + return true; + } + + false + } } /// Exception handling needs some nasty unsafe. diff --git a/libafl/src/executors/inprocess/mod.rs b/libafl/src/executors/inprocess/mod.rs index 3b77ef59cc..6c7ddd54a3 100644 --- a/libafl/src/executors/inprocess/mod.rs +++ b/libafl/src/executors/inprocess/mod.rs @@ -14,10 +14,6 @@ use core::{ use libafl_bolts::tuples::{tuple_list, RefIndexable}; -#[cfg(any(unix, feature = "std"))] -use crate::executors::hooks::inprocess::GLOBAL_STATE; -#[cfg(any(unix, feature = "std"))] -use crate::ExecutionProcessor; use crate::{ corpus::{Corpus, Testcase}, events::{Event, EventFirer, EventRestarter}, @@ -436,45 +432,6 @@ pub fn run_observers_and_save_state( log::info!("Bye!"); } -// TODO remove this after executor refactor and libafl qemu new executor -/// Expose a version of the crash handler that can be called from e.g. an emulator -/// -/// # Safety -/// This will directly access `GLOBAL_STATE` and related data pointers -#[cfg(any(unix, feature = "std"))] -pub unsafe fn generic_inproc_crash_handler() -where - E: Executor + HasObservers, - E::Observers: ObserversTuple, - EM: EventFirer + EventRestarter, - OF: Feedback, - S: HasExecutions + HasSolutions + HasCurrentTestcase, - I: Input + Clone, - Z: HasObjective + ExecutionProcessor, -{ - let data = &raw mut GLOBAL_STATE; - let in_handler = (*data).set_in_handler(true); - - if (*data).is_valid() { - let executor = (*data).executor_mut::(); - let state = (*data).state_mut::(); - let event_mgr = (*data).event_mgr_mut::(); - let fuzzer = (*data).fuzzer_mut::(); - let input = (*data).take_current_input::(); - - run_observers_and_save_state::( - executor, - state, - input, - fuzzer, - event_mgr, - ExitKind::Crash, - ); - } - - (*data).set_in_handler(in_handler); -} - #[cfg(test)] mod tests { use libafl_bolts::{rands::XkcdRand, tuples::tuple_list}; diff --git a/libafl_bolts/src/minibsod.rs b/libafl_bolts/src/minibsod.rs index bdb838afc2..eb28fa63ba 100644 --- a/libafl_bolts/src/minibsod.rs +++ b/libafl_bolts/src/minibsod.rs @@ -6,6 +6,8 @@ use core::mem::size_of; use std::io::{BufWriter, Write}; #[cfg(any(target_os = "solaris", target_os = "illumos"))] use std::process::Command; +#[cfg(unix)] +use std::vec::Vec; #[cfg(unix)] use libc::siginfo_t; @@ -24,6 +26,18 @@ use windows::Win32::System::Diagnostics::Debug::{CONTEXT, EXCEPTION_POINTERS}; #[cfg(unix)] use crate::os::unix_signals::{ucontext_t, Signal}; +/// Necessary info to print a mini-BSOD. +#[derive(Debug)] +#[cfg(unix)] +pub struct BsodInfo { + /// the signal + pub signal: Signal, + /// siginfo + pub siginfo: siginfo_t, + /// ucontext + pub ucontext: Option, +} + /// Write the content of all important registers #[cfg(all( any(target_os = "linux", target_os = "android"), @@ -1110,6 +1124,24 @@ pub fn generate_minibsod( write_minibsod(writer) } +/// Generates a mini-BSOD given a signal and context and dump it to a [`Vec`] +#[cfg(unix)] +pub fn generate_minibsod_to_vec( + signal: Signal, + siginfo: &siginfo_t, + ucontext: Option<&ucontext_t>, +) -> Result, std::io::Error> { + let mut bsod = Vec::new(); + { + let mut writer = BufWriter::new(&mut bsod); + + generate_minibsod(&mut writer, signal, siginfo, ucontext)?; + + writer.flush()?; + } + Ok(bsod) +} + /// Generates a mini-BSOD given an `EXCEPTION_POINTERS` structure. #[cfg(windows)] #[expect(clippy::non_ascii_literal, clippy::not_unsafe_ptr_arg_deref)] diff --git a/libafl_qemu/libafl_qemu_build/src/build.rs b/libafl_qemu/libafl_qemu_build/src/build.rs index b990affcdf..62f5f2a1a5 100644 --- a/libafl_qemu/libafl_qemu_build/src/build.rs +++ b/libafl_qemu/libafl_qemu_build/src/build.rs @@ -11,7 +11,7 @@ use crate::cargo_add_rpath; pub const QEMU_URL: &str = "https://github.com/AFLplusplus/qemu-libafl-bridge"; pub const QEMU_DIRNAME: &str = "qemu-libafl-bridge"; -pub const QEMU_REVISION: &str = "7e0dc68430c509ad50c6b0c9887f7e642a4bba2d"; +pub const QEMU_REVISION: &str = "695657e4f3f408c34b146d5191b102d5eb99b74b"; pub struct BuildResult { pub qemu_path: PathBuf, diff --git a/libafl_qemu/libafl_qemu_sys/src/bindings/x86_64_stub_bindings.rs b/libafl_qemu/libafl_qemu_sys/src/bindings/x86_64_stub_bindings.rs index 60e453775e..13389ebd01 100644 --- a/libafl_qemu/libafl_qemu_sys/src/bindings/x86_64_stub_bindings.rs +++ b/libafl_qemu/libafl_qemu_sys/src/bindings/x86_64_stub_bindings.rs @@ -1,5 +1,5 @@ /* 1.86.0-nightly */ -/* qemu git hash: 3f7b2d86635aaf03818aaec1d285dba88255f831 */ +/* qemu git hash: 695657e4f3f408c34b146d5191b102d5eb99b74b */ /* automatically generated by rust-bindgen 0.71.1 */ use libc::siginfo_t; @@ -6315,15 +6315,42 @@ impl Default for libafl_mapinfo { } } } -unsafe extern "C" { - pub static mut libafl_dump_core_hook: - ::std::option::Option; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct libafl_qemu_sig_ctx { + pub in_qemu_sig_hdlr: bool, + pub is_target_signal: bool, } +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of libafl_qemu_sig_ctx"][::std::mem::size_of::() - 2usize]; + ["Alignment of libafl_qemu_sig_ctx"][::std::mem::align_of::() - 1usize]; + ["Offset of field: libafl_qemu_sig_ctx::in_qemu_sig_hdlr"] + [::std::mem::offset_of!(libafl_qemu_sig_ctx, in_qemu_sig_hdlr) - 0usize]; + ["Offset of field: libafl_qemu_sig_ctx::is_target_signal"] + [::std::mem::offset_of!(libafl_qemu_sig_ctx, is_target_signal) - 1usize]; +}; unsafe extern "C" { pub static mut libafl_force_dfl: ::std::os::raw::c_int; } unsafe extern "C" { - pub fn libafl_dump_core_exec(signal: ::std::os::raw::c_int); + pub fn libafl_qemu_native_signal_handler( + host_sig: ::std::os::raw::c_int, + info: *mut siginfo_t, + puc: *mut ::std::os::raw::c_void, + ); +} +unsafe extern "C" { + pub fn libafl_qemu_signal_context() -> *mut libafl_qemu_sig_ctx; +} +unsafe extern "C" { + pub fn libafl_set_in_target_signal_ctx(); +} +unsafe extern "C" { + pub fn libafl_set_in_host_signal_ctx(); +} +unsafe extern "C" { + pub fn libafl_unset_in_signal_ctx(); } unsafe extern "C" { pub fn libafl_qemu_handle_crash( diff --git a/libafl_qemu/runtime/libafl_qemu_stub_bindings.rs b/libafl_qemu/runtime/libafl_qemu_stub_bindings.rs index 350f4cb434..62517cefc2 100644 --- a/libafl_qemu/runtime/libafl_qemu_stub_bindings.rs +++ b/libafl_qemu/runtime/libafl_qemu_stub_bindings.rs @@ -1,5 +1,5 @@ /* 1.86.0-nightly */ -/* qemu git hash: 3f7b2d86635aaf03818aaec1d285dba88255f831 */ +/* qemu git hash: 695657e4f3f408c34b146d5191b102d5eb99b74b */ /* automatically generated by rust-bindgen 0.71.1 */ pub const LIBAFL_SYNC_EXIT_OPCODE: u32 = 1727150607; diff --git a/libafl_qemu/runtime/nyx_stub_bindings.rs b/libafl_qemu/runtime/nyx_stub_bindings.rs index 63ad8771ea..6366fcee33 100644 --- a/libafl_qemu/runtime/nyx_stub_bindings.rs +++ b/libafl_qemu/runtime/nyx_stub_bindings.rs @@ -1,5 +1,5 @@ /* 1.86.0-nightly */ -/* qemu git hash: 3f7b2d86635aaf03818aaec1d285dba88255f831 */ +/* qemu git hash: 695657e4f3f408c34b146d5191b102d5eb99b74b */ /* automatically generated by rust-bindgen 0.71.1 */ #[repr(C)] diff --git a/libafl_qemu/src/emu/hooks.rs b/libafl_qemu/src/emu/hooks.rs index 040ecf61b7..1c3a0dd2b1 100644 --- a/libafl_qemu/src/emu/hooks.rs +++ b/libafl_qemu/src/emu/hooks.rs @@ -73,7 +73,7 @@ macro_rules! hook_to_repr { static mut EMULATOR_MODULES: *mut () = ptr::null_mut(); #[cfg(feature = "usermode")] -pub extern "C" fn crash_hook_wrapper(target_sig: i32) +pub extern "C" fn run_target_crash_hooks(target_sig: i32) where ET: EmulatorModuleTuple, S: Unpin, @@ -974,8 +974,6 @@ where pub fn crash_function(&mut self, hook: CrashHookFn) { // # Safety // Will cast the valid hook to a ptr. - self.qemu_hooks - .set_crash_hook(crash_hook_wrapper::); self.hook_collection .crash_hooks .push(HookRepr::Function(hook as *const libc::c_void)); @@ -985,8 +983,6 @@ where // # Safety // Will cast the hook to a [`FatPtr`]. unsafe { - self.qemu_hooks - .set_crash_hook(crash_hook_wrapper::); self.hook_collection .crash_hooks .push(HookRepr::Closure(transmute(hook))); diff --git a/libafl_qemu/src/executor.rs b/libafl_qemu/src/executor.rs index 3abc2f6e8e..ea1b4ed2ad 100644 --- a/libafl_qemu/src/executor.rs +++ b/libafl_qemu/src/executor.rs @@ -4,11 +4,13 @@ use core::{ fmt::{self, Debug, Formatter}, time::Duration, }; -#[cfg(feature = "usermode")] -use std::ptr; #[cfg(feature = "systemmode")] use std::sync::atomic::{AtomicBool, Ordering}; +#[cfg(feature = "usermode")] +use std::{ptr, str}; +#[cfg(feature = "usermode")] +use libafl::state::HasCorpus; use libafl::{ events::{EventFirer, EventRestarter}, executors::{ @@ -24,6 +26,8 @@ use libafl::{ state::{HasCurrentTestcase, HasExecutions, HasSolutions}, Error, ExecutionProcessor, HasScheduler, }; +#[cfg(feature = "usermode")] +use libafl_bolts::minibsod; #[cfg(feature = "fork")] use libafl_bolts::shmem::ShMemProvider; use libafl_bolts::{ @@ -32,15 +36,11 @@ use libafl_bolts::{ }; #[cfg(feature = "systemmode")] use libafl_qemu_sys::libafl_exit_request_timeout; -#[cfg(feature = "usermode")] -use libafl_qemu_sys::libafl_qemu_handle_crash; use libc::siginfo_t; -#[cfg(feature = "usermode")] -use crate::EmulatorModules; -#[cfg(feature = "usermode")] -use crate::Qemu; use crate::{command::CommandManager, modules::EmulatorModuleTuple, Emulator, EmulatorDriver}; +#[cfg(feature = "usermode")] +use crate::{run_target_crash_hooks, EmulatorModules, Qemu, QemuSignalContext}; type EmulatorInProcessExecutor<'a, C, CM, ED, EM, ET, H, I, OT, S, SM, Z> = StatefulInProcessExecutor<'a, EM, Emulator, H, I, OT, S, Z>; @@ -50,31 +50,103 @@ pub struct QemuExecutor<'a, C, CM, ED, EM, ET, H, I, OT, S, SM, Z> { first_exec: bool, } +/// `LibAFL` QEMU main crash handler. +/// Be careful, it can come after QEMU's native crash handler if a signal is caught by QEMU but +/// not by `LibAFL`. +/// +/// Signals handlers can be nested. +/// /// # Safety /// /// This should be used as a crash handler, and nothing else. #[cfg(feature = "usermode")] -unsafe fn inproc_qemu_crash_handler( +unsafe fn inproc_qemu_crash_handler( signal: Signal, info: &mut siginfo_t, mut context: Option<&mut ucontext_t>, - _data: &mut InProcessExecutorHandlerData, + data: &mut InProcessExecutorHandlerData, ) where ET: EmulatorModuleTuple, - I: Unpin, - S: Unpin, + E: Executor + HasObservers, + E::Observers: ObserversTuple, + EM: EventFirer + EventRestarter, + OF: Feedback, + S: HasExecutions + HasSolutions + HasCorpus + HasCurrentTestcase + Unpin, + Z: HasObjective, + I: Input + Clone + Unpin, { + log::debug!("QEMU signal handler has been triggered (signal {signal})"); + let puc = match &mut context { Some(v) => ptr::from_mut::(*v) as *mut c_void, None => ptr::null_mut(), }; - // run modules' crash callback - if let Some(emulator_modules) = EmulatorModules::::emulator_modules_mut() { - emulator_modules.modules_mut().on_crash_all(); + if let Some(qemu) = Qemu::get() { + // QEMU is already initialized, we have to route the signal to QEMU's handler or + // consider it as a host (i.e. fuzzer) signal + + if qemu.is_running() { + // QEMU is running, we must determine if we are coming from qemu's signal handler or not + log::debug!("Signal has been triggered while QEMU was running"); + + match qemu.signal_ctx() { + QemuSignalContext::OutOfQemuSignalHandler => { + // we did not run QEMU's signal handler, run it not + log::debug!("It's a simple signal, let QEMU handle it first"); + + qemu.run_signal_handler(signal.into(), info, puc); + + // if we are there, we can safely to execution + return; + } + QemuSignalContext::InQemuSignalHandlerHost => { + // we are running in a nested signal handling + // and the signal is a host QEMU signal + + let si_addr = { info.si_addr() as usize }; + log::error!("QEMU Host crash crashed at addr 0x{si_addr:x}... Bug in QEMU or Emulator modules? Exiting.\n"); + + if let Some(cpu) = qemu.current_cpu() { + eprint!("QEMU Context:\n{}", cpu.display_context()); + } + } + QemuSignalContext::InQemuSignalHandlerTarget => { + // we are running in a nested signal handler and the signal is a target signal. + // run qemu hooks then report the crash. + + log::debug!("QEMU Target signal received that should be handled by host. Most likely a target crash."); + + log::debug!("Running crash hooks."); + run_target_crash_hooks::(signal.into()); + + assert!(data.maybe_report_crash::(None)); + + if let Some(cpu) = qemu.current_cpu() { + eprint!("QEMU Context:\n{}", cpu.display_context()); + } + } + } + } else { + // qemu is not running, it is a bug in LibAFL + let si_addr = { info.si_addr() as usize }; + log::error!("The fuzzer crashed at addr 0x{si_addr:x}... Bug in the fuzzer? Exiting."); + + let bsod = minibsod::generate_minibsod_to_vec(signal, info, context.as_deref()); + + if let Ok(bsod) = bsod { + if let Ok(bsod_str) = str::from_utf8(&bsod) { + log::error!("\n{}", bsod_str); + } else { + log::error!("convert minibsod to string failed"); + } + } else { + log::error!("generate_minibsod failed"); + } + } } - libafl_qemu_handle_crash(signal as i32, info, puc); + libc::_exit(128 + (signal as i32)); } #[cfg(feature = "systemmode")] @@ -178,34 +250,7 @@ where #[cfg(feature = "usermode")] { inner.inprocess_hooks_mut().crash_handler = - inproc_qemu_crash_handler:: as *const c_void; - - let handler = - |qemu: Qemu, _emulator_modules: &mut EmulatorModules, host_sig| { - eprintln!("Crashed with signal {host_sig}"); - unsafe { - libafl::executors::inprocess::generic_inproc_crash_handler::< - Self, - EM, - I, - OF, - S, - Z, - >(); - } - if let Some(cpu) = qemu.current_cpu() { - eprint!("Context:\n{}", cpu.display_context()); - } - }; - - // # Safety - // We assume our crash handlers to be safe/quit after execution. - unsafe { - inner - .exposed_executor_state_mut() - .modules_mut() - .crash_closure(Box::new(handler)); - } + inproc_qemu_crash_handler:: as *const c_void; } inner.inprocess_hooks_mut().timeout_handler = inproc_qemu_timeout_handler::< diff --git a/libafl_qemu/src/modules/usermode/asan.rs b/libafl_qemu/src/modules/usermode/asan.rs index 146e7cd247..e0f0b886f9 100644 --- a/libafl_qemu/src/modules/usermode/asan.rs +++ b/libafl_qemu/src/modules/usermode/asan.rs @@ -9,12 +9,12 @@ use std::{ fs, path::PathBuf, pin::Pin, - process, sync::Mutex, }; use hashbrown::{HashMap, HashSet}; use libafl::{executors::ExitKind, observers::ObserversTuple}; +use libafl_bolts::os::unix_signals::Signal; use libafl_qemu_sys::GuestAddr; use libc::{ c_void, MAP_ANON, MAP_FAILED, MAP_FIXED, MAP_NORESERVE, MAP_PRIVATE, PROT_READ, PROT_WRITE, @@ -73,6 +73,17 @@ pub struct AsanGiovese { pub dirty_shadow: Mutex>, pub saved_shadow: HashMap>, pub snapshot_shadow: bool, + pub target_crash: AsanTargetCrash, + pub error_found: bool, +} + +pub struct AsanModuleBuilder { + env: Vec<(String, String)>, + detect_leaks: bool, + snapshot: bool, + filter: StdAddressFilter, + error_callback: Option, + target_crash: AsanTargetCrash, } #[derive(IntoPrimitive, TryFromPrimitive, Debug, Clone, Copy)] @@ -132,12 +143,11 @@ pub enum AsanError { Signal(i32), } -pub struct AsanModuleBuilder { - env: Vec<(String, String)>, - detect_leaks: bool, - snapshot: bool, - filter: StdAddressFilter, - error_callback: Option, +#[derive(Clone, Debug)] +pub enum AsanTargetCrash { + Never, + OnFirstError, + OnTargetStop, } #[derive(Debug, Clone)] @@ -239,6 +249,7 @@ impl AsanModuleBuilder { snapshot: bool, filter: StdAddressFilter, error_callback: Option, + target_crash: AsanTargetCrash, ) -> Self { Self { env, @@ -246,6 +257,7 @@ impl AsanModuleBuilder { snapshot, filter, error_callback, + target_crash, } } @@ -257,6 +269,7 @@ impl AsanModuleBuilder { self.snapshot, self.filter, self.error_callback, + self.target_crash, ) } @@ -268,6 +281,7 @@ impl AsanModuleBuilder { self.snapshot, self.filter, self.error_callback, + self.target_crash, ) } @@ -279,6 +293,7 @@ impl AsanModuleBuilder { snapshot, self.filter, self.error_callback, + self.target_crash, ) } @@ -290,6 +305,7 @@ impl AsanModuleBuilder { self.snapshot, filter, self.error_callback, + self.target_crash, ) } @@ -301,6 +317,7 @@ impl AsanModuleBuilder { self.snapshot, self.filter, Some(callback), + self.target_crash, ) } @@ -318,6 +335,19 @@ impl AsanModuleBuilder { self.snapshot, self.filter, Some(AsanErrorCallback::report()), + self.target_crash, + ) + } + + #[must_use] + pub fn target_crash(self, target_crash: AsanTargetCrash) -> Self { + Self::new( + self.env, + self.detect_leaks, + self.snapshot, + self.filter, + self.error_callback, + target_crash, ) } @@ -329,6 +359,7 @@ impl AsanModuleBuilder { self.snapshot, self.filter, self.error_callback, + self.target_crash, ) } } @@ -338,8 +369,14 @@ impl Default for AsanModuleBuilder { let env = env::vars() .filter(|(k, _v)| k != "LD_LIBRARY_PATH") .collect::>(); - - Self::new(env, false, true, StdAddressFilter::default(), None) + Self::new( + env, + false, + true, + StdAddressFilter::default(), + None, + AsanTargetCrash::OnFirstError, + ) } } @@ -356,6 +393,7 @@ impl AsanModule { snapshot: bool, filter: StdAddressFilter, error_callback: Option, + target_crash: AsanTargetCrash, ) -> Self { let mut rt = AsanGiovese::new(); @@ -364,6 +402,8 @@ impl AsanModule { rt.set_error_callback(cb); } + rt.set_target_crash(target_crash); + Self { env: env.to_vec(), enabled: true, @@ -488,6 +528,8 @@ impl AsanGiovese { dirty_shadow: Mutex::new(HashSet::default()), saved_shadow: HashMap::default(), snapshot_shadow: true, // By default, track the dirty shadow pages + target_crash: AsanTargetCrash::OnFirstError, + error_found: false, }; Box::pin(res) } @@ -551,6 +593,10 @@ impl AsanGiovese { self.snapshot_shadow = snapshot_shadow; } + fn set_target_crash(&mut self, target_crash: AsanTargetCrash) { + self.target_crash = target_crash; + } + #[inline] #[must_use] pub fn is_invalid_access(qemu: Qemu, addr: GuestAddr) -> bool { @@ -708,8 +754,12 @@ impl AsanGiovese { if let Some(mut cb) = self.error_callback.take() { cb.call(self, qemu, pc, error); self.error_callback = Some(cb); - } else { - process::abort(); + } + + if let AsanTargetCrash::OnFirstError = self.target_crash { + unsafe { + qemu.target_signal(Signal::SigSegmentationFault); + } } } @@ -1037,6 +1087,8 @@ where ) where ET: EmulatorModuleTuple, { + self.rt.error_found = false; + if self.empty { self.rt.snapshot(qemu); self.empty = false; @@ -1055,6 +1107,12 @@ where ET: EmulatorModuleTuple, OT: ObserversTuple, { + if let AsanTargetCrash::OnTargetStop = self.rt.target_crash { + unsafe { + qemu.target_signal(Signal::SigSegmentationFault); + } + } + if self.reset(qemu) == AsanRollback::HasLeaks { *exit_kind = ExitKind::Crash; } diff --git a/libafl_qemu/src/qemu/hooks.rs b/libafl_qemu/src/qemu/hooks.rs index fcd8dab375..a64066bcb6 100644 --- a/libafl_qemu/src/qemu/hooks.rs +++ b/libafl_qemu/src/qemu/hooks.rs @@ -6,8 +6,6 @@ use core::{ffi::c_void, fmt::Debug, mem::transmute, ptr}; use libafl::executors::hooks::inprocess::inprocess_get_state; -#[cfg(feature = "usermode")] -use libafl_qemu_sys::libafl_dump_core_hook; use libafl_qemu_sys::{CPUArchStatePtr, CPUStatePtr, FatPtr, GuestAddr, GuestUsize}; #[cfg(feature = "python")] use pyo3::{pyclass, pymethods, FromPyObject}; @@ -1242,13 +1240,6 @@ impl QemuHooks { PostSyscallHookId(num) } } - - #[expect(clippy::unused_self)] - pub(crate) fn set_crash_hook(self, callback: extern "C" fn(i32)) { - unsafe { - libafl_dump_core_hook = Some(callback); - } - } } #[cfg(feature = "python")] diff --git a/libafl_qemu/src/qemu/mod.rs b/libafl_qemu/src/qemu/mod.rs index d97e7793d4..9ff89bb49e 100644 --- a/libafl_qemu/src/qemu/mod.rs +++ b/libafl_qemu/src/qemu/mod.rs @@ -54,6 +54,7 @@ pub use hooks::*; use libafl_bolts::{vec_init, AsSliceMut}; static mut QEMU_IS_INITIALIZED: bool = false; +static mut QEMU_IS_RUNNING: bool = false; pub(super) static QEMU_CONFIG: OnceLock = OnceLock::new(); @@ -631,7 +632,9 @@ impl Qemu { /// Should, in general, be safe to call. /// Of course, the emulated target is not contained securely and can corrupt state or interact with the operating system. pub unsafe fn run(&self) -> Result { + QEMU_IS_RUNNING = true; self.run_inner(); + QEMU_IS_RUNNING = false; let exit_reason = unsafe { libafl_get_exit_reason() }; if exit_reason.is_null() { @@ -902,6 +905,11 @@ impl Qemu { pub fn host_page_size(&self) -> usize { unsafe { libafl_qemu_sys::libafl_qemu_host_page_size() } } + + #[must_use] + pub fn is_running(&self) -> bool { + unsafe { QEMU_IS_RUNNING } + } } impl ArchExtras for Qemu { diff --git a/libafl_qemu/src/qemu/usermode.rs b/libafl_qemu/src/qemu/usermode.rs index 7ffb3796de..c7f2a93c5f 100644 --- a/libafl_qemu/src/qemu/usermode.rs +++ b/libafl_qemu/src/qemu/usermode.rs @@ -1,19 +1,20 @@ use std::{ - mem::MaybeUninit, ops::Range, ptr::copy_nonoverlapping, slice::from_raw_parts_mut, + ffi::c_void, mem::MaybeUninit, ops::Range, ptr::copy_nonoverlapping, slice::from_raw_parts_mut, str::from_utf8_unchecked_mut, }; +use libafl_bolts::os::unix_signals::Signal; use libafl_qemu_sys::{ exec_path, free_self_maps, guest_base, libafl_force_dfl, libafl_get_brk, libafl_get_initial_brk, libafl_load_addr, libafl_maps_first, libafl_maps_next, libafl_qemu_run, libafl_set_brk, mmap_next_start, pageflags_get_root, read_self_maps, GuestAddr, GuestUsize, IntervalTreeNode, IntervalTreeRoot, MapInfo, MmapPerms, VerifyAccess, }; -use libc::{c_int, c_uchar, strlen}; +use libc::{c_int, c_uchar, siginfo_t, strlen}; #[cfg(feature = "python")] use pyo3::{pyclass, pymethods, IntoPyObject, Py, PyRef, PyRefMut, Python}; -use crate::{Qemu, CPU}; +use crate::{qemu::QEMU_IS_RUNNING, Qemu, CPU}; #[cfg_attr(feature = "python", pyclass(unsendable))] pub struct GuestMaps { @@ -33,6 +34,15 @@ pub struct ImageInfo { pub exec_stack: bool, } +pub enum QemuSignalContext { + /// We are not in QEMU's signal handler, no signal is being propagated. + OutOfQemuSignalHandler, + /// We are propagating a host signal from QEMU signal handler. + InQemuSignalHandlerHost, + /// We are propagating a target signal from QEMU signal handler + InQemuSignalHandlerTarget, +} + // Consider a private new only for Emulator impl GuestMaps { #[must_use] @@ -296,6 +306,54 @@ impl Qemu { Err(format!("Failed to unmap {addr}")) } } + + #[must_use] + pub fn signal_ctx(&self) -> QemuSignalContext { + unsafe { + let qemu_signal_ctx = *libafl_qemu_sys::libafl_qemu_signal_context(); + + if qemu_signal_ctx.in_qemu_sig_hdlr { + if qemu_signal_ctx.is_target_signal { + QemuSignalContext::InQemuSignalHandlerTarget + } else { + QemuSignalContext::InQemuSignalHandlerHost + } + } else { + QemuSignalContext::OutOfQemuSignalHandler + } + } + } + + /// Runs QEMU signal's handler + /// If it is already running, returns true. + /// In that case, it would most likely mean we are in a signal loop. + /// + /// # Safety + /// + /// Run QEMU's native signal handler. + /// + /// Needlessly to say, it should be used very carefully. + /// It will run QEMU's signal handler, and maybe propagate new signals. + pub(crate) unsafe fn run_signal_handler( + &self, + host_sig: c_int, + info: *mut siginfo_t, + puc: *mut c_void, + ) { + libafl_qemu_sys::libafl_qemu_native_signal_handler(host_sig, info, puc); + } + + /// Emulate a signal coming from the target + /// + /// # Safety + /// + /// This may raise a signal to host. Some signals could have a funky behaviour. + /// SIGSEGV is safe to use. + pub unsafe fn target_signal(&self, signal: Signal) { + QEMU_IS_RUNNING = true; + libafl_qemu_sys::libafl_set_in_target_signal_ctx(); + libc::raise(signal.into()); + } } #[cfg(feature = "python")]