Dominik Maier 663a33168e
Associated types for Corpus, State (#767)
* Associated types for Corpus, State

* cleanup

* fix no_std

* drop unused clauses

* Corpus

* cleanup

* adding things

* fixed fuzzer

* remove phantom data

* python

* progress?

* more more

* oof

* wow it builds?

* python fixes, tests

* fix python fun

* black fmt for python

* clippy, added Nop things

* fixes

* fix merge

* make it compile (#836)

* doc-test fixes, prelude-b-gone for cargo-hack compat

* fixes for windows, concolic

* really fix windows, maybe

* imagine using windows

* ...

* elide I generic when used with S: State

* Elide many, many generics, but at what cost?

* progress on push

* Constraint HasCorpus, HasSolutions at trait definition

* remove unused feature

* remove unstable usage since we constrained HasCorpus at definition

* compiled, but still no type inference for MaxMapFeedback

* cleanup inprocess

* resolve some std conflicts

* simplify map

* undo unnecessary cfg specification

* fix breaking test case for CI on no-std

* fix concolic build failures

* fix macos build

* fixes for windows build

* timeout fixes for windows build

* fix pybindings issues

* fixup qemu

* fix outstanding local build issues

* maybe fix windows inprocess

* doc fixes

* unbridled fury

* de-associate State from Feedback, replace with generic as AT inference is not sufficient to derive specialisation for MapFeedback

* merge update

* refactor + speed up fuzzer builds by sharing build work

* cleanup lingering compiler errors

* lol missed one

* revert QEMU-Nyx change, not sure how I did that

* move HasInput to inputs

* HasInput => KnowsInput

* update bounds to enforce via associated types

* disentangle observers with fuzzer

* revert --target; update some fuzzers to match new API

* resolve outstanding fuzzer build blockers (that I can run on my system)

* fixes for non-linux unixes

* fix for windows

* Knows => Uses, final fixes for windows

* <guttural screaming>

* fixes for concolic

* loosen bound for frida executor so windows builds correctly

* cleanup generics for eventmanager/eventprocessor to drop observers requirement

* improve inference over fuzz_one and friends

* update migration notes

* fixes for python bindings

* fixes for generic counts in event managers

* finish migration notes

* post-merge fix

Co-authored-by: Addison Crump <addison.crump@cispa.de>
2022-10-24 03:22:26 +02:00

428 lines
12 KiB
Rust

//! A `TimeoutExecutor` sets a timeout before each target run
#[cfg(target_os = "linux")]
use core::ptr::{addr_of, addr_of_mut};
#[cfg(all(windows, feature = "std"))]
use core::{ffi::c_void, ptr::write_volatile};
#[cfg(any(windows, unix))]
use core::{
fmt::{self, Debug, Formatter},
time::Duration,
};
#[cfg(unix)]
use core::{mem::zeroed, ptr::null_mut};
#[cfg(windows)]
use core::{
ptr::addr_of_mut,
sync::atomic::{compiler_fence, Ordering},
};
#[cfg(all(unix, not(target_os = "linux")))]
use libc::c_int;
#[cfg(all(windows, feature = "std"))]
use windows::Win32::{
Foundation::FILETIME,
System::Threading::{
CreateThreadpoolTimer, EnterCriticalSection, InitializeCriticalSection,
LeaveCriticalSection, SetThreadpoolTimer, RTL_CRITICAL_SECTION, TP_CALLBACK_ENVIRON_V3,
TP_CALLBACK_INSTANCE, TP_TIMER,
},
};
#[cfg(all(windows, feature = "std"))]
use crate::executors::inprocess::{HasInProcessHandlers, GLOBAL_STATE};
use crate::{
executors::{Executor, ExitKind, HasObservers},
observers::UsesObservers,
state::UsesState,
Error,
};
#[repr(C)]
#[cfg(all(unix, not(target_os = "linux")))]
struct Timeval {
pub tv_sec: i64,
pub tv_usec: i64,
}
#[cfg(all(unix, not(target_os = "linux")))]
impl Debug for Timeval {
#[allow(clippy::cast_sign_loss)]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"Timeval {{ tv_sec: {:?}, tv_usec: {:?} (tv: {:?}) }}",
self.tv_sec,
self.tv_usec,
Duration::new(self.tv_sec as _, (self.tv_usec * 1000) as _)
)
}
}
#[repr(C)]
#[cfg(all(unix, not(target_os = "linux")))]
#[derive(Debug)]
struct Itimerval {
pub it_interval: Timeval,
pub it_value: Timeval,
}
#[cfg(all(unix, not(target_os = "linux")))]
extern "C" {
fn setitimer(which: c_int, new_value: *mut Itimerval, old_value: *mut Itimerval) -> c_int;
}
#[cfg(all(unix, not(target_os = "linux")))]
const ITIMER_REAL: c_int = 0;
/// The timeout executor is a wrapper that sets a timeout before each run
pub struct TimeoutExecutor<E> {
/// The wrapped [`Executor`]
executor: E,
#[cfg(target_os = "linux")]
itimerspec: libc::itimerspec,
#[cfg(target_os = "linux")]
timerid: libc::timer_t,
#[cfg(all(unix, not(target_os = "linux")))]
itimerval: Itimerval,
#[cfg(windows)]
milli_sec: i64,
#[cfg(windows)]
tp_timer: *mut TP_TIMER,
#[cfg(windows)]
critical: RTL_CRITICAL_SECTION,
}
impl<E: Debug> Debug for TimeoutExecutor<E> {
#[cfg(windows)]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("TimeoutExecutor")
.field("executor", &self.executor)
.field("milli_sec", &self.milli_sec)
.finish_non_exhaustive()
}
#[cfg(target_os = "linux")]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("TimeoutExecutor")
.field("executor", &self.executor)
.field(
"milli_sec",
&(&self.itimerspec.it_value.tv_sec * 1000
+ &self.itimerspec.it_value.tv_nsec / 1000 / 1000),
)
.finish()
}
#[cfg(all(unix, not(target_os = "linux")))]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("TimeoutExecutor")
.field("executor", &self.executor)
.field("itimerval", &self.itimerval)
.finish()
}
}
#[cfg(windows)]
#[allow(non_camel_case_types)]
type PTP_TIMER_CALLBACK = unsafe extern "system" fn(
param0: *mut TP_CALLBACK_INSTANCE,
param1: *mut c_void,
param2: *mut TP_TIMER,
);
#[cfg(target_os = "linux")]
impl<E> TimeoutExecutor<E> {
/// Create a new [`TimeoutExecutor`], wrapping the given `executor` and checking for timeouts.
/// This should usually be used for `InProcess` fuzzing.
pub fn new(executor: E, exec_tmout: Duration) -> Self {
let milli_sec = exec_tmout.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,
};
let mut timerid: libc::timer_t = null_mut();
unsafe {
// creates a new per-process interval timer
libc::timer_create(libc::CLOCK_MONOTONIC, null_mut(), addr_of_mut!(timerid));
}
Self {
executor,
itimerspec,
timerid,
}
}
/// Set the timeout for this executor
pub fn set_timeout(&mut self, exec_tmout: Duration) {
let milli_sec = exec_tmout.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,
};
self.itimerspec = itimerspec;
}
}
#[cfg(all(unix, not(target_os = "linux")))]
impl<E> TimeoutExecutor<E> {
/// Create a new [`TimeoutExecutor`], wrapping the given `executor` and checking for timeouts.
/// This should usually be used for `InProcess` fuzzing.
pub fn new(executor: E, exec_tmout: Duration) -> Self {
let milli_sec = 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,
};
let itimerval = Itimerval {
it_interval,
it_value,
};
Self {
executor,
itimerval,
}
}
/// Set the timeout for this executor
pub fn set_timeout(&mut self, exec_tmout: Duration) {
let milli_sec = 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,
};
let itimerval = Itimerval {
it_interval,
it_value,
};
self.itimerval = itimerval;
}
}
#[cfg(windows)]
impl<E: HasInProcessHandlers> TimeoutExecutor<E> {
/// Create a new [`TimeoutExecutor`], wrapping the given `executor` and checking for timeouts.
pub fn new(executor: E, exec_tmout: Duration) -> Self {
let milli_sec = exec_tmout.as_millis() as i64;
let timeout_handler: PTP_TIMER_CALLBACK =
unsafe { std::mem::transmute(executor.inprocess_handlers().timeout_handler) };
let tp_timer = unsafe {
CreateThreadpoolTimer(
Some(timeout_handler),
Some(addr_of_mut!(GLOBAL_STATE) as *mut c_void),
Some(&TP_CALLBACK_ENVIRON_V3::default()),
)
};
let mut critical = RTL_CRITICAL_SECTION::default();
unsafe {
InitializeCriticalSection(&mut critical);
}
Self {
executor,
milli_sec,
tp_timer,
critical,
}
}
/// 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;
}
/// Retrieve the inner `Executor` that is wrapped by this `TimeoutExecutor`.
pub fn inner(&mut self) -> &mut E {
&mut self.executor
}
}
#[cfg(windows)]
impl<E, EM, Z> Executor<EM, Z> for TimeoutExecutor<E>
where
E: Executor<EM, Z> + HasInProcessHandlers,
EM: UsesState<State = E::State>,
Z: UsesState<State = E::State>,
{
#[allow(clippy::cast_sign_loss)]
fn run_target(
&mut self,
fuzzer: &mut Z,
state: &mut Self::State,
mgr: &mut EM,
input: &Self::Input,
) -> Result<ExitKind, Error> {
unsafe {
let data = &mut GLOBAL_STATE;
write_volatile(&mut data.tp_timer, self.tp_timer as *mut _ as *mut c_void);
write_volatile(
&mut data.critical,
addr_of_mut!(self.critical) as *mut c_void,
);
write_volatile(
&mut data.timeout_input_ptr,
addr_of_mut!(data.current_input_ptr) as *mut c_void,
);
let tm: i64 = -self.milli_sec * 10 * 1000;
let ft = FILETIME {
dwLowDateTime: (tm & 0xffffffff) as u32,
dwHighDateTime: (tm >> 32) as u32,
};
compiler_fence(Ordering::SeqCst);
EnterCriticalSection(&mut self.critical);
compiler_fence(Ordering::SeqCst);
data.in_target = 1;
compiler_fence(Ordering::SeqCst);
LeaveCriticalSection(&mut self.critical);
compiler_fence(Ordering::SeqCst);
SetThreadpoolTimer(self.tp_timer, Some(&ft), 0, 0);
let ret = self.executor.run_target(fuzzer, state, mgr, input);
compiler_fence(Ordering::SeqCst);
EnterCriticalSection(&mut self.critical);
compiler_fence(Ordering::SeqCst);
// Timeout handler will do nothing after we increment in_target value.
data.in_target = 0;
compiler_fence(Ordering::SeqCst);
LeaveCriticalSection(&mut self.critical);
compiler_fence(Ordering::SeqCst);
write_volatile(&mut data.timeout_input_ptr, core::ptr::null_mut());
self.post_run_reset();
ret
}
}
/// Deletes this timer queue
/// # Safety
/// Will dereference the given `tp_timer` pointer, unchecked.
fn post_run_reset(&mut self) {
unsafe {
SetThreadpoolTimer(self.tp_timer, None, 0, 0);
}
self.executor.post_run_reset();
}
}
#[cfg(target_os = "linux")]
impl<E, EM, Z> Executor<EM, Z> for TimeoutExecutor<E>
where
E: Executor<EM, Z>,
EM: UsesState<State = E::State>,
Z: UsesState<State = E::State>,
{
fn run_target(
&mut self,
fuzzer: &mut Z,
state: &mut Self::State,
mgr: &mut EM,
input: &Self::Input,
) -> Result<ExitKind, Error> {
unsafe {
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();
ret
}
}
fn post_run_reset(&mut self) {
unsafe {
let disarmed: libc::itimerspec = zeroed();
libc::timer_settime(self.timerid, 0, addr_of!(disarmed), null_mut());
}
self.executor.post_run_reset();
}
}
#[cfg(all(unix, not(target_os = "linux")))]
impl<E, EM, Z> Executor<EM, Z> for TimeoutExecutor<E>
where
E: Executor<EM, Z>,
EM: UsesState<State = E::State>,
Z: UsesState<State = E::State>,
{
fn run_target(
&mut self,
fuzzer: &mut Z,
state: &mut Self::State,
mgr: &mut EM,
input: &Self::Input,
) -> Result<ExitKind, Error> {
unsafe {
setitimer(ITIMER_REAL, &mut self.itimerval, null_mut());
let ret = self.executor.run_target(fuzzer, state, mgr, input);
self.post_run_reset();
ret
}
}
fn post_run_reset(&mut self) {
unsafe {
let mut itimerval_zero: Itimerval = zeroed();
setitimer(ITIMER_REAL, &mut itimerval_zero, null_mut());
}
self.executor.post_run_reset();
}
}
impl<E> UsesState for TimeoutExecutor<E>
where
E: UsesState,
{
type State = E::State;
}
impl<E> UsesObservers for TimeoutExecutor<E>
where
E: UsesObservers,
{
type Observers = E::Observers;
}
impl<E> HasObservers for TimeoutExecutor<E>
where
E: HasObservers,
{
#[inline]
fn observers(&self) -> &Self::Observers {
self.executor.observers()
}
#[inline]
fn observers_mut(&mut self) -> &mut Self::Observers {
self.executor.observers_mut()
}
}