Merge pull request #31 from AFLplusplus/timeout_executors

add timeouts for executors
This commit is contained in:
Andrea Fioraldi 2021-03-17 16:49:07 +01:00 committed by GitHub
commit 5d92871b27
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 214 additions and 13 deletions

View File

@ -1,6 +1,7 @@
//! A libfuzzer-like fuzzer with llmp-multithreading support and restarts
//! The example harness is built for libpng.
use core::time::Duration;
use std::{env, path::PathBuf};
#[cfg(unix)]
@ -11,8 +12,8 @@ use libafl::{
QueueCorpusScheduler,
},
events::setup_restarting_mgr,
executors::{inprocess::InProcessExecutor, Executor, ExitKind},
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback},
executors::{inprocess::InProcessExecutor, inprocess::TimeoutExecutor, Executor, ExitKind},
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback},
fuzzer::{Fuzzer, HasCorpusScheduler, StdFuzzer},
inputs::Input,
mutators::{scheduled::HavocBytesMutator, token_mutations::Tokens},
@ -117,7 +118,7 @@ fn fuzz(corpus_dirs: Vec<PathBuf>, objective_dir: PathBuf, broker_port: u16) ->
// on disk so the user can get them after stopping the fuzzer
OnDiskCorpus::new(objective_dir).unwrap(),
// Feedbacks to recognize an input as solution
tuple_list!(CrashFeedback::new()),
tuple_list!(CrashFeedback::new(), TimeoutFeedback::new()),
)
});
@ -143,13 +144,17 @@ fn fuzz(corpus_dirs: Vec<PathBuf>, objective_dir: PathBuf, broker_port: u16) ->
let fuzzer = StdFuzzer::new(scheduler, tuple_list!(stage));
// Create the executor for an in-process function with just one observer for edge coverage
let mut executor = InProcessExecutor::new(
let mut executor = TimeoutExecutor::new(
InProcessExecutor::new(
"in-process(edges)",
harness,
tuple_list!(edges_observer, TimeObserver::new("time")),
&mut state,
&mut restarting_mgr,
)?;
)?,
// 10 seconds timeout
Duration::new(10, 0),
);
// The actual target run starts here.
// Call LLVMFUzzerInitialize() if present.

View File

@ -2,6 +2,14 @@
//! It should usually be paired with extra error-handling, such as a restarting event manager, to be effective.
use core::marker::PhantomData;
#[cfg(unix)]
use core::time::Duration;
#[cfg(unix)]
use std::os::raw::c_int;
#[cfg(unix)]
use std::ptr::null_mut;
#[cfg(unix)]
use core::{
ptr::{self, write_volatile},
@ -164,6 +172,152 @@ where
}
}
#[repr(C)]
#[cfg(unix)]
struct Timeval {
pub tv_sec: i64,
pub tv_usec: i64,
}
#[repr(C)]
#[cfg(unix)]
struct Itimerval {
pub it_interval: Timeval,
pub it_value: Timeval,
}
#[cfg(unix)]
extern "C" {
fn setitimer(which: c_int, new_value: *mut Itimerval, old_value: *mut Itimerval) -> c_int;
}
#[cfg(unix)]
const ITIMER_REAL: c_int = 0;
//timeout excutor wrap a InProcessExecutor
#[cfg(unix)]
pub struct TimeoutExecutor<I, OT, EX>
where
EX: Executor<I> + HasObservers<OT>,
I: Input + HasTargetBytes,
OT: ObserversTuple,
{
executor: EX,
exec_tmout: Duration,
phantom: PhantomData<(I, OT)>,
}
impl<I, OT, EX> Named for TimeoutExecutor<I, OT, EX>
where
EX: Executor<I> + HasObservers<OT>,
I: Input + HasTargetBytes,
OT: ObserversTuple,
{
fn name(&self) -> &str {
self.executor.name()
}
}
impl<I, OT, EX> HasObservers<OT> for TimeoutExecutor<I, OT, EX>
where
EX: Executor<I> + HasObservers<OT>,
I: Input + HasTargetBytes,
OT: ObserversTuple,
{
#[inline]
fn observers(&self) -> &OT {
self.executor.observers()
}
#[inline]
fn observers_mut(&mut self) -> &mut OT {
self.executor.observers_mut()
}
}
impl<I, OT, EX> TimeoutExecutor<I, OT, EX>
where
EX: Executor<I> + HasObservers<OT>,
I: Input + HasTargetBytes,
OT: ObserversTuple,
{
pub fn new(executor: EX, exec_tmout: Duration) -> Self {
Self {
executor,
exec_tmout,
phantom: PhantomData,
}
}
}
impl<I, OT, EX> Executor<I> for TimeoutExecutor<I, OT, EX>
where
EX: Executor<I> + HasObservers<OT>,
I: Input + HasTargetBytes,
OT: ObserversTuple,
{
#[inline]
fn pre_exec<EM: EventManager<I, S>, S>(
&mut self,
_state: &mut S,
_event_mgr: &mut EM,
_input: &I,
) -> Result<(), Error> {
unsafe {
let milli_sec = self.exec_tmout.as_millis();
let it_value = Timeval {
tv_sec: (milli_sec / 1000) as i64,
tv_usec: (milli_sec % 1000) as i64,
};
let it_interval = Timeval {
tv_sec: 0,
tv_usec: 0,
};
setitimer(
ITIMER_REAL,
&mut Itimerval {
it_interval,
it_value,
},
null_mut(),
);
}
self.executor.pre_exec(_state, _event_mgr, _input)
}
#[inline]
fn post_exec<EM: EventManager<I, S>, S>(
&mut self,
_state: &S,
_event_mgr: &mut EM,
_input: &I,
) -> Result<(), Error> {
unsafe {
let it_value = Timeval {
tv_sec: 0,
tv_usec: 0,
};
let it_interval = Timeval {
tv_sec: 0,
tv_usec: 0,
};
setitimer(
ITIMER_REAL,
&mut Itimerval {
it_interval,
it_value,
},
null_mut(),
);
}
self.executor.post_exec(_state, _event_mgr, _input)
}
fn run_target(&mut self, input: &I) -> Result<ExitKind, Error> {
self.executor.run_target(input)
}
}
#[cfg(unix)]
mod unix_signal_handler {
use alloc::vec::Vec;
@ -228,7 +382,9 @@ mod unix_signal_handler {
unsafe {
let data = &mut GLOBAL_STATE;
match signal {
Signal::SigUser2 => (data.timeout_handler)(signal, info, void, data),
Signal::SigUser2 | Signal::SigAlarm => {
(data.timeout_handler)(signal, info, void, data)
}
_ => (data.crash_handler)(signal, info, void, data),
}
}
@ -236,6 +392,7 @@ mod unix_signal_handler {
fn signals(&self) -> Vec<Signal> {
vec![
Signal::SigAlarm,
Signal::SigUser2,
Signal::SigAbort,
Signal::SigBus,
@ -279,7 +436,7 @@ mod unix_signal_handler {
let obj_fitness = state
.objectives_mut()
.is_interesting_all(&input, observers, ExitKind::Crash)
.is_interesting_all(&input, observers, ExitKind::Timeout)
.expect("In timeout handler objectives failure.");
if obj_fitness > 0 {
state
@ -425,7 +582,6 @@ mod tests {
let mut in_process_executor = InProcessExecutor::<NopInput, ()> {
harness_fn: test_harness_fn_nop,
// TODO: on_crash_fn: Box::new(|_, _, _, _, _| ()),
observers: tuple_list!(),
name: "main",
phantom: PhantomData,

View File

@ -157,6 +157,46 @@ impl Default for CrashFeedback {
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct TimeoutFeedback {}
impl<I> Feedback<I> for TimeoutFeedback
where
I: Input,
{
fn is_interesting<OT: ObserversTuple>(
&mut self,
_input: &I,
_observers: &OT,
exit_kind: ExitKind,
) -> Result<u32, Error> {
if exit_kind == ExitKind::Timeout {
Ok(1)
} else {
Ok(0)
}
}
}
impl Named for TimeoutFeedback {
#[inline]
fn name(&self) -> &str {
"TimeoutFeedback"
}
}
impl TimeoutFeedback {
pub fn new() -> Self {
Self {}
}
}
impl Default for TimeoutFeedback {
fn default() -> Self {
Self::new()
}
}
/// Nop feedback that annotates execution time in the new testcase, if any
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct TimeFeedback {