TimeoutInprocessForkExecutor (#797)

* TimeoutInprocessForkExecutor

* no_std

* linux only

* OK

* crash -> timeout
This commit is contained in:
Dongjia "toka" Zhang 2022-10-03 21:44:03 +02:00 committed by GitHub
parent 3489e9aeaa
commit caa560b7a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -11,8 +11,10 @@ use core::{
ffi::c_void, ffi::c_void,
fmt::{self, Debug, Formatter}, fmt::{self, Debug, Formatter},
marker::PhantomData, marker::PhantomData,
ptr, ptr::{self, null_mut},
}; };
#[cfg(all(target_os = "linux", feature = "std"))]
use core::{ptr::addr_of_mut, time::Duration};
#[cfg(any(unix, all(windows, feature = "std")))] #[cfg(any(unix, all(windows, feature = "std")))]
use core::{ use core::{
ptr::write_volatile, ptr::write_volatile,
@ -446,11 +448,11 @@ impl InProcessExecutorHandlerData {
/// Exception handling needs some nasty unsafe. /// Exception handling needs some nasty unsafe.
pub(crate) static mut GLOBAL_STATE: InProcessExecutorHandlerData = InProcessExecutorHandlerData { pub(crate) static mut GLOBAL_STATE: InProcessExecutorHandlerData = InProcessExecutorHandlerData {
/// The state ptr for signal handling /// The state ptr for signal handling
state_ptr: ptr::null_mut(), state_ptr: null_mut(),
/// The event manager ptr for signal handling /// The event manager ptr for signal handling
event_mgr_ptr: ptr::null_mut(), event_mgr_ptr: null_mut(),
/// The fuzzer ptr for signal handling /// The fuzzer ptr for signal handling
fuzzer_ptr: ptr::null_mut(), fuzzer_ptr: null_mut(),
/// The executor ptr for signal handling /// The executor ptr for signal handling
executor_ptr: ptr::null(), executor_ptr: ptr::null(),
/// The current input for signal handling /// The current input for signal handling
@ -460,13 +462,13 @@ pub(crate) static mut GLOBAL_STATE: InProcessExecutorHandlerData = InProcessExec
/// The timeout handler fn /// The timeout handler fn
timeout_handler: ptr::null(), timeout_handler: ptr::null(),
#[cfg(windows)] #[cfg(windows)]
tp_timer: ptr::null_mut(), tp_timer: null_mut(),
#[cfg(windows)] #[cfg(windows)]
in_target: 0, in_target: 0,
#[cfg(windows)] #[cfg(windows)]
critical: ptr::null_mut(), critical: null_mut(),
#[cfg(windows)] #[cfg(windows)]
timeout_input_ptr: ptr::null_mut(), timeout_input_ptr: null_mut(),
}; };
/// Get the inprocess [`crate::state::State`] /// Get the inprocess [`crate::state::State`]
@ -1254,6 +1256,8 @@ pub(crate) type ForkHandlerFuncPtr =
pub struct InChildProcessHandlers { pub struct InChildProcessHandlers {
/// On crash C function pointer /// On crash C function pointer
pub crash_handler: *const c_void, pub crash_handler: *const c_void,
/// On timeout C function pointer
pub timeout_handler: *const c_void,
} }
#[cfg(all(feature = "std", unix))] #[cfg(all(feature = "std", unix))]
@ -1272,6 +1276,7 @@ impl InChildProcessHandlers {
); );
write_volatile(&mut data.state_ptr, state as *mut _ as *mut c_void); write_volatile(&mut data.state_ptr, state as *mut _ as *mut c_void);
data.crash_handler = self.crash_handler; data.crash_handler = self.crash_handler;
data.timeout_handler = self.timeout_handler;
compiler_fence(Ordering::SeqCst); compiler_fence(Ordering::SeqCst);
} }
} }
@ -1285,12 +1290,34 @@ impl InChildProcessHandlers {
{ {
unsafe { unsafe {
let data = &mut FORK_EXECUTOR_GLOBAL_DATA; let data = &mut FORK_EXECUTOR_GLOBAL_DATA;
child_signal_handlers::setup_child_panic_hook::<E, I, OT, S>(); // child_signal_handlers::setup_child_panic_hook::<E, I, OT, S>();
setup_signal_handler(data)?; setup_signal_handler(data)?;
compiler_fence(Ordering::SeqCst); compiler_fence(Ordering::SeqCst);
Ok(Self { Ok(Self {
crash_handler: child_signal_handlers::child_crash_handler::<E, I, OT, S> crash_handler: child_signal_handlers::child_crash_handler::<E, I, OT, S>
as *const c_void, as *const c_void,
timeout_handler: ptr::null(),
})
}
}
/// Create new [`InChildProcessHandlers`].
pub fn with_timeout<E, I, OT, S>() -> Result<Self, Error>
where
I: Input,
E: HasObservers<I, OT, S>,
OT: ObserversTuple<I, S>,
{
unsafe {
let data = &mut FORK_EXECUTOR_GLOBAL_DATA;
// child_signal_handlers::setup_child_panic_hook::<E, I, OT, S>();
setup_signal_handler(data)?;
compiler_fence(Ordering::SeqCst);
Ok(Self {
crash_handler: child_signal_handlers::child_crash_handler::<E, I, OT, S>
as *const c_void,
timeout_handler: child_signal_handlers::child_timeout_handler::<E, I, OT, S>
as *const c_void,
}) })
} }
} }
@ -1300,6 +1327,7 @@ impl InChildProcessHandlers {
pub fn nop() -> Self { pub fn nop() -> Self {
Self { Self {
crash_handler: ptr::null(), crash_handler: ptr::null(),
timeout_handler: ptr::null(),
} }
} }
} }
@ -1316,6 +1344,8 @@ pub(crate) struct InProcessForkExecutorGlobalData {
pub current_input_ptr: *const c_void, pub current_input_ptr: *const c_void,
/// Stores a pointer to the crash_handler function /// Stores a pointer to the crash_handler function
pub crash_handler: *const c_void, pub crash_handler: *const c_void,
/// Stores a pointer to the timeout_handler function
pub timeout_handler: *const c_void,
} }
#[cfg(all(feature = "std", unix))] #[cfg(all(feature = "std", unix))]
@ -1353,16 +1383,23 @@ impl InProcessForkExecutorGlobalData {
pub(crate) static mut FORK_EXECUTOR_GLOBAL_DATA: InProcessForkExecutorGlobalData = pub(crate) static mut FORK_EXECUTOR_GLOBAL_DATA: InProcessForkExecutorGlobalData =
InProcessForkExecutorGlobalData { InProcessForkExecutorGlobalData {
executor_ptr: ptr::null(), executor_ptr: ptr::null(),
crash_handler: ptr::null(),
state_ptr: ptr::null(), state_ptr: ptr::null(),
current_input_ptr: ptr::null(), current_input_ptr: ptr::null(),
crash_handler: ptr::null(),
timeout_handler: ptr::null(),
}; };
#[cfg(all(feature = "std", unix))] #[cfg(all(feature = "std", unix))]
impl Handler for InProcessForkExecutorGlobalData { impl Handler for InProcessForkExecutorGlobalData {
fn handle(&mut self, signal: Signal, info: siginfo_t, context: &mut ucontext_t) { fn handle(&mut self, signal: Signal, info: siginfo_t, context: &mut ucontext_t) {
match signal { match signal {
Signal::SigUser2 | Signal::SigAlarm => (), Signal::SigUser2 | Signal::SigAlarm => unsafe {
if !FORK_EXECUTOR_GLOBAL_DATA.timeout_handler.is_null() {
let func: ForkHandlerFuncPtr =
transmute(FORK_EXECUTOR_GLOBAL_DATA.timeout_handler);
(func)(signal, info, context, &mut FORK_EXECUTOR_GLOBAL_DATA);
}
},
_ => unsafe { _ => unsafe {
if !FORK_EXECUTOR_GLOBAL_DATA.crash_handler.is_null() { if !FORK_EXECUTOR_GLOBAL_DATA.crash_handler.is_null() {
let func: ForkHandlerFuncPtr = let func: ForkHandlerFuncPtr =
@ -1404,6 +1441,23 @@ where
phantom: PhantomData<(I, S)>, phantom: PhantomData<(I, S)>,
} }
/// Timeout executor for [`InProcessForkExecutor`]
#[cfg(all(feature = "std", target_os = "linux"))]
pub struct TimeoutInProcessForkExecutor<'a, H, I, OT, S, SP>
where
H: FnMut(&I) -> ExitKind + ?Sized,
I: Input,
OT: ObserversTuple<I, S>,
SP: ShMemProvider,
{
harness_fn: &'a mut H,
shmem_provider: SP,
observers: OT,
handlers: InChildProcessHandlers,
itimerspec: libc::itimerspec,
phantom: PhantomData<(I, S)>,
}
#[cfg(all(feature = "std", unix))] #[cfg(all(feature = "std", unix))]
impl<'a, H, I, OT, S, SP> Debug for InProcessForkExecutor<'a, H, I, OT, S, SP> impl<'a, H, I, OT, S, SP> Debug for InProcessForkExecutor<'a, H, I, OT, S, SP>
where where
@ -1420,6 +1474,23 @@ where
} }
} }
#[cfg(all(feature = "std", target_os = "linux"))]
impl<'a, H, I, OT, S, SP> Debug for TimeoutInProcessForkExecutor<'a, H, I, OT, S, SP>
where
H: FnMut(&I) -> ExitKind + ?Sized,
I: Input,
OT: ObserversTuple<I, S>,
SP: ShMemProvider,
{
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("TimeoutInProcessForkExecutor")
.field("observers", &self.observers)
.field("shmem_provider", &self.shmem_provider)
.field("itimerspec", &self.itimerspec)
.finish()
}
}
#[cfg(all(feature = "std", unix))] #[cfg(all(feature = "std", unix))]
impl<'a, EM, H, I, OT, S, SP, Z> Executor<EM, I, S, Z> impl<'a, EM, H, I, OT, S, SP, Z> Executor<EM, I, S, Z>
for InProcessForkExecutor<'a, H, I, OT, S, SP> for InProcessForkExecutor<'a, H, I, OT, S, SP>
@ -1487,6 +1558,93 @@ where
} }
} }
#[cfg(all(feature = "std", target_os = "linux"))]
impl<'a, EM, H, I, OT, S, SP, Z> Executor<EM, I, S, Z>
for TimeoutInProcessForkExecutor<'a, H, I, OT, S, SP>
where
H: FnMut(&I) -> ExitKind + ?Sized,
I: Input,
OT: ObserversTuple<I, S>,
SP: ShMemProvider,
{
#[allow(unreachable_code)]
#[inline]
fn run_target(
&mut self,
_fuzzer: &mut Z,
state: &mut S,
_mgr: &mut EM,
input: &I,
) -> Result<ExitKind, Error> {
unsafe {
self.shmem_provider.pre_fork()?;
match fork() {
Ok(ForkResult::Child) => {
// Child
self.shmem_provider.post_fork(true)?;
self.handlers.pre_run_target(self, state, input);
self.observers
.pre_exec_child_all(state, input)
.expect("Failed to run post_exec on observers");
let mut timerid: libc::timer_t = null_mut();
// creates a new per-process interval timer
// we can't do this from the parent, timerid is unique to each process.
libc::timer_create(libc::CLOCK_MONOTONIC, null_mut(), addr_of_mut!(timerid));
println!("Set timer! {:#?} {:#?}", self.itimerspec, timerid);
let v =
libc::timer_settime(timerid, 0, addr_of_mut!(self.itimerspec), null_mut());
println!("{:#?} {}", v, nix::errno::errno());
(self.harness_fn)(input);
self.observers
.post_exec_child_all(state, input, &ExitKind::Ok)
.expect("Failed to run post_exec on observers");
std::process::exit(0);
Ok(ExitKind::Ok)
}
Ok(ForkResult::Parent { child }) => {
// Parent
// println!("from parent {} child is {}", std::process::id(), child);
self.shmem_provider.post_fork(false)?;
let res = waitpid(child, None)?;
println!("{:#?}", res);
match res {
WaitStatus::Signaled(_, signal, _) => match signal {
nix::sys::signal::Signal::SIGALRM
| nix::sys::signal::Signal::SIGUSR2 => Ok(ExitKind::Timeout),
_ => Ok(ExitKind::Crash),
},
WaitStatus::Exited(_, code) => {
if code > 128 && code < 160 {
// Signal exit codes
let signal = code - 128;
if signal == Signal::SigAlarm as libc::c_int
|| signal == Signal::SigUser2 as libc::c_int
{
Ok(ExitKind::Timeout)
} else {
Ok(ExitKind::Crash)
}
} else {
Ok(ExitKind::Ok)
}
}
_ => Ok(ExitKind::Ok),
}
}
Err(e) => Err(Error::from(e)),
}
}
}
}
#[cfg(all(feature = "std", unix))] #[cfg(all(feature = "std", unix))]
impl<'a, H, I, OT, S, SP> InProcessForkExecutor<'a, H, I, OT, S, SP> impl<'a, H, I, OT, S, SP> InProcessForkExecutor<'a, H, I, OT, S, SP>
where where
@ -1533,6 +1691,68 @@ where
} }
} }
#[cfg(all(feature = "std", target_os = "linux"))]
impl<'a, H, I, OT, S, SP> TimeoutInProcessForkExecutor<'a, H, I, OT, S, SP>
where
H: FnMut(&I) -> ExitKind + ?Sized,
I: Input,
OT: ObserversTuple<I, S>,
SP: ShMemProvider,
{
/// Creates a new [`TimeoutInProcessForkExecutor`]
pub fn new<EM, OF, Z>(
harness_fn: &'a mut H,
observers: OT,
_fuzzer: &mut Z,
_state: &mut S,
_event_mgr: &mut EM,
timeout: Duration,
shmem_provider: SP,
) -> Result<Self, Error>
where
EM: EventFirer<I> + EventRestarter<S>,
OF: Feedback<I, S>,
S: HasSolutions<I> + HasClientPerfMonitor,
Z: HasObjective<I, OF, S>,
{
let handlers = InChildProcessHandlers::with_timeout::<Self, I, OT, S>()?;
let milli_sec = timeout.as_millis();
let it_value = libc::timespec {
tv_sec: (milli_sec / 1000) as _,
tv_nsec: ((milli_sec % 1000) * 1000 * 1000) as _,
};
let it_interval = libc::timespec {
tv_sec: 0,
tv_nsec: 0,
};
let itimerspec = libc::itimerspec {
it_interval,
it_value,
};
Ok(Self {
harness_fn,
shmem_provider,
observers,
handlers,
itimerspec,
phantom: PhantomData,
})
}
/// Retrieve the harness function.
#[inline]
pub fn harness(&self) -> &H {
self.harness_fn
}
/// Retrieve the harness function for a mutable reference.
#[inline]
pub fn harness_mut(&mut self) -> &mut H {
self.harness_fn
}
}
#[cfg(all(feature = "std", unix))] #[cfg(all(feature = "std", unix))]
impl<'a, H, I, OT, S, SP> HasObservers<I, OT, S> for InProcessForkExecutor<'a, H, I, OT, S, SP> impl<'a, H, I, OT, S, SP> HasObservers<I, OT, S> for InProcessForkExecutor<'a, H, I, OT, S, SP>
where where
@ -1552,6 +1772,26 @@ where
} }
} }
#[cfg(all(feature = "std", target_os = "linux"))]
impl<'a, H, I, OT, S, SP> HasObservers<I, OT, S>
for TimeoutInProcessForkExecutor<'a, H, I, OT, S, SP>
where
H: FnMut(&I) -> ExitKind + ?Sized,
I: Input,
OT: ObserversTuple<I, S>,
SP: ShMemProvider,
{
#[inline]
fn observers(&self) -> &OT {
&self.observers
}
#[inline]
fn observers_mut(&mut self) -> &mut OT {
&mut self.observers
}
}
/// signal handlers and `panic_hooks` for the child process /// signal handlers and `panic_hooks` for the child process
#[cfg(all(feature = "std", unix))] #[cfg(all(feature = "std", unix))]
pub mod child_signal_handlers { pub mod child_signal_handlers {
@ -1623,6 +1863,29 @@ pub mod child_signal_handlers {
libc::_exit(128 + (_signal as i32)); libc::_exit(128 + (_signal as i32));
} }
#[cfg(unix)]
pub(crate) unsafe fn child_timeout_handler<E, I, OT, S>(
_signal: Signal,
_info: siginfo_t,
_context: &mut ucontext_t,
data: &mut InProcessForkExecutorGlobalData,
) where
E: HasObservers<I, OT, S>,
OT: ObserversTuple<I, S>,
I: Input,
{
if data.is_valid() {
let executor = data.executor_mut::<E>();
let observers = executor.observers_mut();
let state = data.state_mut::<S>();
let input = data.take_current_input::<I>();
observers
.post_exec_child_all(state, input, &ExitKind::Timeout)
.expect("Failed to run post_exec on observers");
}
libc::_exit(128 + (_signal as i32));
}
} }
#[cfg(test)] #[cfg(test)]