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:
Andrea Fioraldi 2023-04-17 18:16:44 +02:00 committed by GitHub
parent fafa27a7e9
commit fd68c8a81f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 245 additions and 32 deletions

View File

@ -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.

View File

@ -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>();

View File

@ -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();
}

View File

@ -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(())
}