Batch mode timeouts (Linux only ATM) (#1193)
* batch mode timeouts for linux * batch_mode is linux only atm * fix * fix * fix * imports * winfix * more fix * winfix * fix * fix * fix * fix * clippy * fix macos --------- Co-authored-by: Dongjia "toka" Zhang <tokazerkje@outlook.com>
This commit is contained in:
parent
fafa27a7e9
commit
fd68c8a81f
@ -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.
|
||||
|
@ -246,11 +246,12 @@ pub trait HasInProcessHandlers {
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
impl<'a, H, OT, S> HasInProcessHandlers for InProcessExecutor<'a, H, OT, S>
|
||||
impl<H, HB, OT, S> HasInProcessHandlers for GenericInProcessExecutor<H, HB, OT, S>
|
||||
where
|
||||
H: FnMut(&S::Input) -> ExitKind,
|
||||
H: FnMut(&<S as UsesInput>::Input) -> ExitKind + ?Sized,
|
||||
HB: BorrowMut<H>,
|
||||
OT: ObserversTuple<S>,
|
||||
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<E, EM, OF, Z>() -> Result<Self, Error>
|
||||
where
|
||||
E: Executor<EM, Z> + 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<E, EM, OF, Z>() -> Result<Self, Error>
|
||||
where
|
||||
E: Executor<EM, Z> + HasObservers + HasInProcessHandlers,
|
||||
EM: EventFirer<State = E::State> + EventRestarter<State = E::State>,
|
||||
OF: Feedback<E::State>,
|
||||
E::State: HasSolutions + HasClientPerfMonitor + HasCorpus,
|
||||
Z: HasObjective<Objective = OF, State = E::State>,
|
||||
{
|
||||
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<E> {
|
||||
unsafe {
|
||||
(self.timeout_executor_ptr as *mut crate::executors::timeout::TimeoutExecutor<E>)
|
||||
.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<Objective = OF, State = E::State>,
|
||||
{
|
||||
if !data.timeout_executor_ptr.is_null()
|
||||
&& data.timeout_executor_mut::<E>().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<State = E::State> + EventRestarter<State = E::State>,
|
||||
OF: Feedback<E::State>,
|
||||
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::<E>().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::<E>();
|
||||
let state = data.state_mut::<E::State>();
|
||||
|
@ -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<E> {
|
||||
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<E: Debug> Debug for TimeoutExecutor<E> {
|
||||
@ -158,9 +180,25 @@ impl<E> TimeoutExecutor<E> {
|
||||
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<E> TimeoutExecutor<E> {
|
||||
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<E> TimeoutExecutor<E> {
|
||||
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<E> TimeoutExecutor<E> {
|
||||
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<E: HasInProcessHandlers> TimeoutExecutor<E> {
|
||||
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<ExitKind, Error> {
|
||||
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<ExitKind, Error> {
|
||||
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();
|
||||
}
|
||||
|
@ -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(())
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user