From d96d833760606d60b1132ea6bab1ca2cb3af591d Mon Sep 17 00:00:00 2001 From: Romain Malmain Date: Mon, 21 Oct 2024 17:59:04 +0200 Subject: [PATCH] Support on_crash & on_timeout callbacks for libafl_qemu modules (#2620) * support (unsafe) on_crash / on_timeout callbacks for modules * use libc types in bindgen --- libafl_qemu/libafl_qemu_build/src/bindings.rs | 2 + .../src/bindings/x86_64_stub_bindings.rs | 40 +--- .../runtime/libafl_qemu_stub_bindings.rs | 4 +- libafl_qemu/src/executor.rs | 71 ++++--- libafl_qemu/src/modules/drcov.rs | 180 ++++++++++-------- libafl_qemu/src/modules/mod.rs | 34 ++++ 6 files changed, 187 insertions(+), 144 deletions(-) diff --git a/libafl_qemu/libafl_qemu_build/src/bindings.rs b/libafl_qemu/libafl_qemu_build/src/bindings.rs index 8a8318ea12..555931505d 100644 --- a/libafl_qemu/libafl_qemu_build/src/bindings.rs +++ b/libafl_qemu/libafl_qemu_build/src/bindings.rs @@ -181,6 +181,8 @@ pub fn generate( .allowlist_function("qemu_main_loop") .allowlist_function("qemu_cleanup") .blocklist_function("main_loop_wait") // bindgen issue #1313 + .blocklist_type("siginfo_t") + .raw_line("use libc::siginfo_t;") .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())); // arch specific functions 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 e057a90ecf..38b54df602 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,7 +1,9 @@ -/* 1.83.0-nightly */ -/* qemu git hash: d6637939526f453c69f4c6bfe4635feb5dc5c0be */ +/* 1.84.0-nightly */ +/* qemu git hash: 805b14ffc44999952562e8f219d81c21a4fa50b9 */ /* automatically generated by rust-bindgen 0.70.1 */ +use libc::siginfo_t; + #[repr(C)] #[derive(Copy, Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct __BindgenBitfieldUnit { @@ -516,15 +518,6 @@ impl ::std::fmt::Debug for sigval { pub type __sigval_t = sigval; #[repr(C)] #[derive(Copy, Clone)] -pub struct siginfo_t { - pub si_signo: ::std::os::raw::c_int, - pub si_errno: ::std::os::raw::c_int, - pub si_code: ::std::os::raw::c_int, - pub __pad0: ::std::os::raw::c_int, - pub _sifields: siginfo_t__bindgen_ty_1, -} -#[repr(C)] -#[derive(Copy, Clone)] pub union siginfo_t__bindgen_ty_1 { pub _pad: [::std::os::raw::c_int; 28usize], pub _kill: siginfo_t__bindgen_ty_1__bindgen_ty_1, @@ -827,31 +820,6 @@ impl ::std::fmt::Debug for siginfo_t__bindgen_ty_1 { write!(f, "siginfo_t__bindgen_ty_1 {{ union }}") } } -#[allow(clippy::unnecessary_operation, clippy::identity_op)] -const _: () = { - ["Size of siginfo_t"][::std::mem::size_of::() - 128usize]; - ["Alignment of siginfo_t"][::std::mem::align_of::() - 8usize]; - ["Offset of field: siginfo_t::si_signo"][::std::mem::offset_of!(siginfo_t, si_signo) - 0usize]; - ["Offset of field: siginfo_t::si_errno"][::std::mem::offset_of!(siginfo_t, si_errno) - 4usize]; - ["Offset of field: siginfo_t::si_code"][::std::mem::offset_of!(siginfo_t, si_code) - 8usize]; - ["Offset of field: siginfo_t::__pad0"][::std::mem::offset_of!(siginfo_t, __pad0) - 12usize]; - ["Offset of field: siginfo_t::_sifields"] - [::std::mem::offset_of!(siginfo_t, _sifields) - 16usize]; -}; -impl Default for siginfo_t { - fn default() -> Self { - let mut s = ::std::mem::MaybeUninit::::uninit(); - unsafe { - ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); - s.assume_init() - } - } -} -impl ::std::fmt::Debug for siginfo_t { - fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { - write ! (f , "siginfo_t {{ si_signo: {:?}, si_errno: {:?}, si_code: {:?}, __pad0: {:?}, _sifields: {:?} }}" , self . si_signo , self . si_errno , self . si_code , self . __pad0 , self . _sifields) - } -} pub type guint8 = ::std::os::raw::c_uchar; pub type gchar = ::std::os::raw::c_char; pub type guint = ::std::os::raw::c_uint; diff --git a/libafl_qemu/runtime/libafl_qemu_stub_bindings.rs b/libafl_qemu/runtime/libafl_qemu_stub_bindings.rs index f6c692b783..706cddb088 100644 --- a/libafl_qemu/runtime/libafl_qemu_stub_bindings.rs +++ b/libafl_qemu/runtime/libafl_qemu_stub_bindings.rs @@ -1,5 +1,5 @@ -/* 1.83.0-nightly */ -/* qemu git hash: d6637939526f453c69f4c6bfe4635feb5dc5c0be */ +/* 1.84.0-nightly */ +/* qemu git hash: 805b14ffc44999952562e8f219d81c21a4fa50b9 */ /* automatically generated by rust-bindgen 0.70.1 */ pub const LIBAFL_SYNC_EXIT_OPCODE: u32 = 1727150607; diff --git a/libafl_qemu/src/executor.rs b/libafl_qemu/src/executor.rs index ad28c00149..d77746afcf 100644 --- a/libafl_qemu/src/executor.rs +++ b/libafl_qemu/src/executor.rs @@ -35,9 +35,6 @@ use libafl_bolts::{ use libafl_qemu_sys::libafl_exit_request_timeout; #[cfg(emulation_mode = "usermode")] use libafl_qemu_sys::libafl_qemu_handle_crash; -#[cfg(emulation_mode = "usermode")] -use libafl_qemu_sys::siginfo_t; -#[cfg(emulation_mode = "systemmode")] use libc::siginfo_t; #[cfg(emulation_mode = "usermode")] @@ -60,17 +57,26 @@ where /// /// This should be used as a crash handler, and nothing else. #[cfg(emulation_mode = "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, -) { +) where + ET: EmulatorModuleTuple, + S: UsesInput + Unpin, +{ let puc = match &mut context { Some(v) => ptr::from_mut::(*v) as *mut c_void, None => ptr::null_mut(), }; - libafl_qemu_handle_crash(signal as i32, ptr::from_mut::(info), puc); + + // run modules' crash callback + if let Some(emulator_modules) = EmulatorModules::::emulator_modules_mut() { + emulator_modules.modules_mut().on_crash_all(); + } + + libafl_qemu_handle_crash(signal as i32, info, puc); } #[cfg(emulation_mode = "systemmode")] @@ -79,8 +85,7 @@ pub(crate) static BREAK_ON_TMOUT: AtomicBool = AtomicBool::new(false); /// # Safety /// Can call through the `unix_signal_handler::inproc_timeout_handler`. /// Calling this method multiple times concurrently can lead to race conditions. -#[cfg(emulation_mode = "systemmode")] -pub unsafe fn inproc_qemu_timeout_handler( +pub unsafe fn inproc_qemu_timeout_handler( signal: Signal, info: &mut siginfo_t, context: Option<&mut ucontext_t>, @@ -88,16 +93,36 @@ pub unsafe fn inproc_qemu_timeout_handler( ) where E: HasObservers + HasInProcessHooks + Executor, E::Observers: ObserversTuple, - EM: EventFirer + EventRestarter, - OF: Feedback, E::State: HasExecutions + HasSolutions + HasCorpus, + EM: EventFirer + EventRestarter, + ET: EmulatorModuleTuple, + OF: Feedback, + S: State + Unpin, Z: HasObjective, <::State as HasSolutions>::Solutions: Corpus, //delete me <<::State as HasCorpus>::Corpus as Corpus>::Input: Clone, //delete me { - if BREAK_ON_TMOUT.load(Ordering::Acquire) { - libafl_exit_request_timeout(); - } else { + #[cfg(emulation_mode = "systemmode")] + { + if BREAK_ON_TMOUT.load(Ordering::Acquire) { + libafl_exit_request_timeout(); + } else { + libafl::executors::hooks::unix::unix_signal_handler::inproc_timeout_handler::< + E, + EM, + OF, + Z, + >(signal, info, context, data); + } + } + + #[cfg(emulation_mode = "usermode")] + { + // run modules' crash callback + if let Some(emulator_modules) = EmulatorModules::::emulator_modules_mut() { + emulator_modules.modules_mut().on_timeout_all(); + } + libafl::executors::hooks::unix::unix_signal_handler::inproc_timeout_handler::( signal, info, context, data, ); @@ -153,7 +178,8 @@ where #[cfg(emulation_mode = "usermode")] { - inner.inprocess_hooks_mut().crash_handler = inproc_qemu_crash_handler as *const c_void; + inner.inprocess_hooks_mut().crash_handler = + inproc_qemu_crash_handler:: as *const c_void; let handler = |emulator_modules: &mut EmulatorModules, host_sig| { eprintln!("Crashed with signal {host_sig}"); @@ -175,15 +201,14 @@ where } } - #[cfg(emulation_mode = "systemmode")] - { - inner.inprocess_hooks_mut().timeout_handler = inproc_qemu_timeout_handler::< - StatefulInProcessExecutor<'a, H, OT, S, Emulator>, - EM, - OF, - Z, - > as *const c_void; - } + inner.inprocess_hooks_mut().timeout_handler = inproc_qemu_timeout_handler::< + StatefulInProcessExecutor<'a, H, OT, S, Emulator>, + EM, + ET, + OF, + S, + Z, + > as *const c_void; Ok(Self { inner, diff --git a/libafl_qemu/src/modules/drcov.rs b/libafl_qemu/src/modules/drcov.rs index f411756faa..a41777bf36 100644 --- a/libafl_qemu/src/modules/drcov.rs +++ b/libafl_qemu/src/modules/drcov.rs @@ -108,6 +108,7 @@ pub struct DrCovModule { full_trace: bool, drcov_len: usize, } + impl DrCovModule { #[must_use] pub fn builder() -> DrCovModuleBuilder { @@ -119,11 +120,7 @@ impl DrCovModule { } } } - -impl DrCovModule -where - F: AddressFilter, -{ +impl DrCovModule { #[must_use] #[allow(clippy::let_underscore_untyped)] pub fn new( @@ -146,84 +143,7 @@ where } } - #[must_use] - pub fn must_instrument(&self, addr: GuestAddr) -> bool { - self.filter.allowed(&addr) - } -} - -impl EmulatorModule for DrCovModule -where - F: AddressFilter, - S: Unpin + UsesInput + HasMetadata, -{ - type ModuleAddressFilter = F; - #[cfg(emulation_mode = "systemmode")] - type ModulePageFilter = NopPageFilter; - - fn init_module(&self, emulator_modules: &mut EmulatorModules) - where - ET: EmulatorModuleTuple, - { - emulator_modules.blocks( - Hook::Function(gen_unique_block_ids::), - Hook::Function(gen_block_lengths::), - Hook::Function(exec_trace_block::), - ); - } - - #[cfg(emulation_mode = "usermode")] - fn first_exec(&mut self, emulator_modules: &mut EmulatorModules, _state: &mut S) - where - ET: EmulatorModuleTuple, - { - if self.module_mapping.is_none() { - log::info!("Auto-filling module mapping for DrCov module from QEMU mapping."); - - let qemu = emulator_modules.qemu(); - - let mut module_mapping: RangeMap = RangeMap::new(); - - for (i, (r, p)) in qemu - .mappings() - .filter_map(|m| { - m.path() - .map(|p| ((m.start() as usize)..(m.end() as usize), p.to_string())) - .filter(|(_, p)| !p.is_empty()) - }) - .enumerate() - { - module_mapping.insert(r, (i as u16, p)); - } - - self.module_mapping = Some(module_mapping); - } else { - log::info!("Using user-provided module mapping for DrCov module."); - } - } - - #[cfg(emulation_mode = "systemmode")] - fn first_exec(&mut self, _emulator_modules: &mut EmulatorModules, _state: &mut S) - where - ET: EmulatorModuleTuple, - { - assert!( - self.module_mapping.is_some(), - "DrCov should have a module mapping already set." - ); - } - - fn post_exec( - &mut self, - _emulator_modules: &mut EmulatorModules, - _state: &mut S, - _input: &S::Input, - _observers: &mut OT, - _exit_kind: &mut ExitKind, - ) where - OT: ObserversTuple, - ET: EmulatorModuleTuple, - { + pub fn write(&mut self) { let lengths_opt = DRCOV_LENGTHS.lock().unwrap(); let lengths = lengths_opt.as_ref().unwrap(); if self.full_trace { @@ -322,6 +242,100 @@ where self.drcov_len = DRCOV_MAP.lock().unwrap().as_ref().unwrap().len(); } } +} + +impl DrCovModule +where + F: AddressFilter, +{ + #[must_use] + pub fn must_instrument(&self, addr: GuestAddr) -> bool { + self.filter.allowed(&addr) + } +} + +impl EmulatorModule for DrCovModule +where + F: AddressFilter, + S: Unpin + UsesInput + HasMetadata, +{ + type ModuleAddressFilter = F; + #[cfg(emulation_mode = "systemmode")] + type ModulePageFilter = NopPageFilter; + + fn init_module(&self, emulator_modules: &mut EmulatorModules) + where + ET: EmulatorModuleTuple, + { + emulator_modules.blocks( + Hook::Function(gen_unique_block_ids::), + Hook::Function(gen_block_lengths::), + Hook::Function(exec_trace_block::), + ); + } + + #[cfg(emulation_mode = "usermode")] + fn first_exec(&mut self, emulator_modules: &mut EmulatorModules, _state: &mut S) + where + ET: EmulatorModuleTuple, + { + if self.module_mapping.is_none() { + log::info!("Auto-filling module mapping for DrCov module from QEMU mapping."); + + let qemu = emulator_modules.qemu(); + + let mut module_mapping: RangeMap = RangeMap::new(); + + for (i, (r, p)) in qemu + .mappings() + .filter_map(|m| { + m.path() + .map(|p| ((m.start() as usize)..(m.end() as usize), p.to_string())) + .filter(|(_, p)| !p.is_empty()) + }) + .enumerate() + { + module_mapping.insert(r, (i as u16, p)); + } + + self.module_mapping = Some(module_mapping); + } else { + log::info!("Using user-provided module mapping for DrCov module."); + } + } + + #[cfg(emulation_mode = "systemmode")] + fn first_exec(&mut self, _emulator_modules: &mut EmulatorModules, _state: &mut S) + where + ET: EmulatorModuleTuple, + { + assert!( + self.module_mapping.is_some(), + "DrCov should have a module mapping already set." + ); + } + + fn post_exec( + &mut self, + _emulator_modules: &mut EmulatorModules, + _state: &mut S, + _input: &S::Input, + _observers: &mut OT, + _exit_kind: &mut ExitKind, + ) where + OT: ObserversTuple, + ET: EmulatorModuleTuple, + { + self.write(); + } + + unsafe fn on_crash(&mut self) { + self.write(); + } + + unsafe fn on_timeout(&mut self) { + self.write(); + } fn address_filter(&self) -> &Self::ModuleAddressFilter { &self.filter diff --git a/libafl_qemu/src/modules/mod.rs b/libafl_qemu/src/modules/mod.rs index 5d4ed01712..e68d328888 100644 --- a/libafl_qemu/src/modules/mod.rs +++ b/libafl_qemu/src/modules/mod.rs @@ -95,6 +95,16 @@ where { } + /// # Safety + /// + /// This is getting executed in a signal handler. + unsafe fn on_crash(&mut self) {} + + /// # Safety + /// + /// This is getting executed in a signal handler. + unsafe fn on_timeout(&mut self) {} + fn address_filter(&self) -> &Self::ModuleAddressFilter; fn address_filter_mut(&mut self) -> &mut Self::ModuleAddressFilter; fn update_address_filter(&mut self, qemu: Qemu, filter: Self::ModuleAddressFilter) { @@ -149,6 +159,16 @@ where OT: ObserversTuple, ET: EmulatorModuleTuple; + /// # Safety + /// + /// This is getting executed in a signal handler. + unsafe fn on_crash_all(&mut self); + + /// # Safety + /// + /// This is getting executed in a signal handler. + unsafe fn on_timeout_all(&mut self); + fn allow_address_range_all(&mut self, address_range: Range); #[cfg(emulation_mode = "systemmode")] @@ -196,6 +216,10 @@ where { } + unsafe fn on_crash_all(&mut self) {} + + unsafe fn on_timeout_all(&mut self) {} + fn allow_address_range_all(&mut self, _address_range: Range) {} #[cfg(emulation_mode = "systemmode")] @@ -255,6 +279,16 @@ where .post_exec_all(emulator_modules, state, input, observers, exit_kind); } + unsafe fn on_crash_all(&mut self) { + self.0.on_crash(); + self.1.on_crash_all(); + } + + unsafe fn on_timeout_all(&mut self) { + self.0.on_timeout(); + self.1.on_timeout_all(); + } + fn allow_address_range_all(&mut self, address_range: Range) { self.0.address_filter_mut().register(address_range.clone()); self.1.allow_address_range_all(address_range);