diff --git a/fuzzers/libfuzzer_libpng_launcher/src/lib.rs b/fuzzers/libfuzzer_libpng_launcher/src/lib.rs index d7667f2a7d..ffef102464 100644 --- a/fuzzers/libfuzzer_libpng_launcher/src/lib.rs +++ b/fuzzers/libfuzzer_libpng_launcher/src/lib.rs @@ -202,17 +202,21 @@ pub fn libafl_main() { }; // 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( - InProcessExecutor::new( - &mut harness, - tuple_list!(edges_observer, time_observer), - &mut fuzzer, - &mut state, - &mut restarting_mgr, - )?, - // 10 seconds timeout - opt.timeout, - ); + let executor = InProcessExecutor::new( + &mut harness, + tuple_list!(edges_observer, time_observer), + &mut fuzzer, + &mut state, + &mut restarting_mgr, + )?; + + // Wrap the executor with a timeout + #[cfg(target_os = "linux")] + let mut executor = TimeoutExecutor::batch_mode(executor, opt.timeout); + + // Wrap the executor with a timeout + #[cfg(not(target_os = "linux"))] + let mut executor = TimeoutExecutor::new(executor, opt.timeout); // The actual target run starts here. // Call LLVMFUzzerInitialize() if present. diff --git a/libafl/src/executors/inprocess.rs b/libafl/src/executors/inprocess.rs index 8a9100ea58..b069305a65 100644 --- a/libafl/src/executors/inprocess.rs +++ b/libafl/src/executors/inprocess.rs @@ -246,11 +246,12 @@ pub trait HasInProcessHandlers { } #[cfg(windows)] -impl<'a, H, OT, S> HasInProcessHandlers for InProcessExecutor<'a, H, OT, S> +impl HasInProcessHandlers for GenericInProcessExecutor where - H: FnMut(&S::Input) -> ExitKind, + H: FnMut(&::Input) -> ExitKind + ?Sized, + HB: BorrowMut, OT: ObserversTuple, - S: UsesInput, + S: HasSolutions + HasClientPerfMonitor + HasCorpus, { /// the timeout handler #[inline] @@ -339,6 +340,7 @@ impl InProcessHandlers { } /// Create new [`InProcessHandlers`]. + #[cfg(not(all(windows, feature = "std")))] pub fn new() -> Result where E: Executor + HasObservers, @@ -363,7 +365,20 @@ impl InProcessHandlers { as *const _, }) } - #[cfg(all(windows, feature = "std"))] + #[cfg(not(any(unix, feature = "std")))] + Ok(Self {}) + } + + /// Create new [`InProcessHandlers`]. + #[cfg(all(windows, feature = "std"))] + pub fn new() -> Result + where + E: Executor + HasObservers + HasInProcessHandlers, + EM: EventFirer + EventRestarter, + OF: Feedback, + E::State: HasSolutions + HasClientPerfMonitor + HasCorpus, + Z: HasObjective, + { unsafe { let data = &mut GLOBAL_STATE; #[cfg(feature = "std")] @@ -378,8 +393,6 @@ impl InProcessHandlers { as *const c_void, }) } - #[cfg(not(any(unix, feature = "std")))] - Ok(Self {}) } /// Replace the handlers with `nop` handlers, deactivating the handlers @@ -409,12 +422,14 @@ pub(crate) struct InProcessExecutorHandlerData { fuzzer_ptr: *mut c_void, executor_ptr: *const c_void, pub current_input_ptr: *const c_void, + /// The timeout handler #[cfg(any(unix, feature = "std"))] crash_handler: *const c_void, /// The timeout handler #[cfg(any(unix, feature = "std"))] timeout_handler: *const c_void, + #[cfg(all(windows, feature = "std"))] pub(crate) tp_timer: *mut c_void, #[cfg(all(windows, feature = "std"))] @@ -423,6 +438,9 @@ pub(crate) struct InProcessExecutorHandlerData { pub(crate) critical: *mut c_void, #[cfg(all(windows, feature = "std"))] pub(crate) timeout_input_ptr: *mut c_void, + + #[cfg(any(unix, feature = "std"))] + pub(crate) timeout_executor_ptr: *mut c_void, } unsafe impl Send for InProcessExecutorHandlerData {} @@ -457,14 +475,23 @@ impl InProcessExecutorHandlerData { } #[cfg(all(windows, feature = "std"))] - fn is_valid(&self) -> bool { + pub(crate) fn is_valid(&self) -> bool { self.in_target == 1 } #[cfg(unix)] - fn is_valid(&self) -> bool { + pub(crate) fn is_valid(&self) -> bool { !self.current_input_ptr.is_null() } + + #[cfg(any(unix, feature = "std"))] + fn timeout_executor_mut<'a, E>(&self) -> &'a mut crate::executors::timeout::TimeoutExecutor { + unsafe { + (self.timeout_executor_ptr as *mut crate::executors::timeout::TimeoutExecutor) + .as_mut() + .unwrap() + } + } } /// Exception handling needs some nasty unsafe. @@ -479,6 +506,7 @@ pub(crate) static mut GLOBAL_STATE: InProcessExecutorHandlerData = InProcessExec executor_ptr: ptr::null(), /// The current input for signal handling current_input_ptr: ptr::null(), + /// The crash handler fn #[cfg(any(unix, feature = "std"))] crash_handler: ptr::null(), @@ -493,6 +521,9 @@ pub(crate) static mut GLOBAL_STATE: InProcessExecutorHandlerData = InProcessExec critical: null_mut(), #[cfg(all(windows, feature = "std"))] timeout_input_ptr: null_mut(), + + #[cfg(any(unix, feature = "std"))] + timeout_executor_ptr: null_mut(), }; /// Get the inprocess [`crate::state::State`] @@ -715,6 +746,12 @@ mod unix_signal_handler { E::State: HasSolutions + HasClientPerfMonitor + HasCorpus, Z: HasObjective, { + if !data.timeout_executor_ptr.is_null() + && data.timeout_executor_mut::().handle_timeout(data) + { + return; + } + if !data.is_valid() { log::warn!("TIMEOUT or SIGUSR2 happened, but currently not fuzzing."); return; @@ -966,7 +1003,10 @@ mod windows_exception_handler { }, events::{EventFirer, EventRestarter}, executors::{ - inprocess::{run_observers_and_save_state, InProcessExecutorHandlerData, GLOBAL_STATE}, + inprocess::{ + run_observers_and_save_state, HasInProcessHandlers, InProcessExecutorHandlerData, + GLOBAL_STATE, + }, Executor, ExitKind, HasObservers, }, feedbacks::Feedback, @@ -1067,7 +1107,7 @@ mod windows_exception_handler { global_state: *mut c_void, _p1: *mut u8, ) where - E: HasObservers, + E: HasObservers + HasInProcessHandlers, EM: EventFirer + EventRestarter, OF: Feedback, E::State: HasSolutions + HasClientPerfMonitor + HasCorpus, @@ -1083,6 +1123,20 @@ mod windows_exception_handler { ); compiler_fence(Ordering::SeqCst); + if !data.timeout_executor_ptr.is_null() + && data.timeout_executor_mut::().handle_timeout(data) + { + compiler_fence(Ordering::SeqCst); + LeaveCriticalSection( + (data.critical as *mut RTL_CRITICAL_SECTION) + .as_mut() + .unwrap(), + ); + compiler_fence(Ordering::SeqCst); + + return; + } + if data.in_target == 1 { let executor = data.executor_mut::(); let state = data.state_mut::(); diff --git a/libafl/src/executors/timeout.rs b/libafl/src/executors/timeout.rs index 4ba73df54b..5c9e2322bf 100644 --- a/libafl/src/executors/timeout.rs +++ b/libafl/src/executors/timeout.rs @@ -2,7 +2,7 @@ #[cfg(target_os = "linux")] use core::ptr::{addr_of, addr_of_mut}; -#[cfg(all(windows, feature = "std"))] +#[cfg(any(windows, target_os = "linux"))] use core::{ffi::c_void, ptr::write_volatile}; #[cfg(any(windows, unix))] use core::{ @@ -29,10 +29,14 @@ use windows::Win32::{ }, }; +#[cfg(target_os = "linux")] +use crate::bolts::current_time; #[cfg(all(windows, feature = "std"))] -use crate::executors::inprocess::{HasInProcessHandlers, GLOBAL_STATE}; +use crate::executors::inprocess::HasInProcessHandlers; +#[cfg(any(windows, target_os = "linux"))] +use crate::executors::inprocess::GLOBAL_STATE; use crate::{ - executors::{Executor, ExitKind, HasObservers}, + executors::{inprocess::InProcessExecutorHandlerData, Executor, ExitKind, HasObservers}, observers::UsesObservers, state::UsesState, Error, @@ -40,7 +44,7 @@ use crate::{ #[repr(C)] #[cfg(all(unix, not(target_os = "linux")))] -struct Timeval { +pub(crate) struct Timeval { pub tv_sec: i64, pub tv_usec: i64, } @@ -62,7 +66,7 @@ impl Debug for Timeval { #[repr(C)] #[cfg(all(unix, not(target_os = "linux")))] #[derive(Debug)] -struct Itimerval { +pub(crate) struct Itimerval { pub it_interval: Timeval, pub it_value: Timeval, } @@ -91,6 +95,24 @@ pub struct TimeoutExecutor { tp_timer: *mut TP_TIMER, #[cfg(windows)] critical: RTL_CRITICAL_SECTION, + + exec_tmout: Duration, + + // for batch mode (linux only atm) + #[allow(unused)] + batch_mode: bool, + #[allow(unused)] + executions: u32, + #[allow(unused)] + avg_mul_k: u32, + #[allow(unused)] + last_signal_time: Duration, + #[allow(unused)] + avg_exec_time: Duration, + #[allow(unused)] + start_time: Duration, + #[allow(unused)] + tmout_start_time: Duration, } impl Debug for TimeoutExecutor { @@ -158,9 +180,25 @@ impl TimeoutExecutor { executor, itimerspec, timerid, + exec_tmout, + batch_mode: false, + executions: 0, + avg_mul_k: 1, + last_signal_time: Duration::ZERO, + avg_exec_time: Duration::ZERO, + start_time: Duration::ZERO, + tmout_start_time: Duration::ZERO, } } + /// Create a new [`TimeoutExecutor`], wrapping the given `executor` and checking for timeouts. + /// With this method batch mode is enabled. + pub fn batch_mode(executor: E, exec_tmout: Duration) -> Self { + let mut me = Self::new(executor, exec_tmout); + me.batch_mode = true; + me + } + /// Set the timeout for this executor pub fn set_timeout(&mut self, exec_tmout: Duration) { let milli_sec = exec_tmout.as_millis(); @@ -177,6 +215,50 @@ impl TimeoutExecutor { it_value, }; self.itimerspec = itimerspec; + self.exec_tmout = exec_tmout; + } + + pub(crate) fn handle_timeout(&mut self, data: &mut InProcessExecutorHandlerData) -> bool { + if !self.batch_mode { + return false; + } + // eprintln!("handle_timeout {:?} {}", self.avg_exec_time, self.avg_mul_k); + let cur_time = current_time(); + if !data.is_valid() { + // outside the target + unsafe { + let disarmed: libc::itimerspec = zeroed(); + libc::timer_settime(self.timerid, 0, addr_of!(disarmed), null_mut()); + } + let elapsed = cur_time - self.tmout_start_time; + // set timer the next exec + if self.executions > 0 { + self.avg_exec_time = elapsed / self.executions; + self.executions = 0; + } + self.avg_mul_k += 1; + self.last_signal_time = cur_time; + return true; + } + + let elapsed_run = cur_time - self.start_time; + if elapsed_run < self.exec_tmout { + // fp, reset timeout + unsafe { + libc::timer_settime(self.timerid, 0, addr_of!(self.itimerspec), null_mut()); + } + if self.executions > 0 { + let elapsed = cur_time - self.tmout_start_time; + self.avg_exec_time = elapsed / self.executions; + self.executions = 0; // It will be 1 when the exec finish + } + self.tmout_start_time = current_time(); + self.avg_mul_k += 1; + self.last_signal_time = cur_time; + true + } else { + false + } } } @@ -201,6 +283,14 @@ impl TimeoutExecutor { Self { executor, itimerval, + exec_tmout, + batch_mode: false, + executions: 0, + avg_mul_k: 1, + last_signal_time: Duration::ZERO, + avg_exec_time: Duration::ZERO, + start_time: Duration::ZERO, + tmout_start_time: Duration::ZERO, } } @@ -220,6 +310,12 @@ impl TimeoutExecutor { it_value, }; self.itimerval = itimerval; + self.exec_tmout = exec_tmout; + } + + #[allow(clippy::unused_self)] + pub(crate) fn handle_timeout(&mut self, _data: &mut InProcessExecutorHandlerData) -> bool { + false // TODO } } @@ -248,13 +344,26 @@ impl TimeoutExecutor { milli_sec, tp_timer, critical, + exec_tmout, + batch_mode: false, + executions: 0, + avg_mul_k: 1, + last_signal_time: Duration::ZERO, + avg_exec_time: Duration::ZERO, + start_time: Duration::ZERO, + tmout_start_time: Duration::ZERO, } } /// Set the timeout for this executor - #[cfg(windows)] pub fn set_timeout(&mut self, exec_tmout: Duration) { self.milli_sec = exec_tmout.as_millis() as i64; + self.exec_tmout = exec_tmout; + } + + #[allow(clippy::unused_self)] + pub(crate) fn handle_timeout(&mut self, _data: &mut InProcessExecutorHandlerData) -> bool { + false // TODO } /// Retrieve the inner `Executor` that is wrapped by this `TimeoutExecutor`. @@ -280,6 +389,11 @@ where ) -> Result { unsafe { let data = &mut GLOBAL_STATE; + write_volatile( + &mut data.timeout_executor_ptr, + self as *mut _ as *mut c_void, + ); + write_volatile(&mut data.tp_timer, self.tp_timer as *mut _ as *mut c_void); write_volatile( &mut data.critical, @@ -349,7 +463,22 @@ where input: &Self::Input, ) -> Result { unsafe { - libc::timer_settime(self.timerid, 0, addr_of_mut!(self.itimerspec), null_mut()); + if self.batch_mode { + let data = &mut GLOBAL_STATE; + write_volatile( + &mut data.timeout_executor_ptr, + self as *mut _ as *mut c_void, + ); + + if self.executions == 0 { + libc::timer_settime(self.timerid, 0, addr_of_mut!(self.itimerspec), null_mut()); + self.tmout_start_time = current_time(); + } + self.start_time = current_time(); + } else { + libc::timer_settime(self.timerid, 0, addr_of_mut!(self.itimerspec), null_mut()); + } + let ret = self.executor.run_target(fuzzer, state, mgr, input); // reset timer self.post_run_reset(); @@ -358,9 +487,35 @@ where } fn post_run_reset(&mut self) { - unsafe { - let disarmed: libc::itimerspec = zeroed(); - libc::timer_settime(self.timerid, 0, addr_of!(disarmed), null_mut()); + if self.batch_mode { + unsafe { + let elapsed = current_time() - self.tmout_start_time; + // elapsed may be > than tmout in case of reveived but ingored signal + if elapsed > self.exec_tmout + || self.exec_tmout - elapsed < self.avg_exec_time * self.avg_mul_k + { + let disarmed: libc::itimerspec = zeroed(); + libc::timer_settime(self.timerid, 0, addr_of!(disarmed), null_mut()); + // set timer the next exec + if self.executions > 0 { + self.avg_exec_time = elapsed / self.executions; + self.executions = 0; + } + // readjust K + if self.last_signal_time > self.exec_tmout * self.avg_mul_k + && self.avg_mul_k > 1 + { + self.avg_mul_k -= 1; + } + } else { + self.executions += 1; + } + } + } else { + unsafe { + let disarmed: libc::itimerspec = zeroed(); + libc::timer_settime(self.timerid, 0, addr_of!(disarmed), null_mut()); + } } self.executor.post_run_reset(); } diff --git a/libafl/src/observers/mod.rs b/libafl/src/observers/mod.rs index 03869240d9..d17c319d62 100644 --- a/libafl/src/observers/mod.rs +++ b/libafl/src/observers/mod.rs @@ -498,7 +498,7 @@ where #[cfg(feature = "no_std")] fn pre_exec(&mut self, _state: &mut S, _input: &S::Input) -> Result<(), Error> { self.last_runtime = None; - self.start_time = Duration::from_secs(0); + self.start_time = current_time(); Ok(()) }