Use Unix timer_* API instead of setitimer (#510)

* fix linter errors for armv7 (docs)

* introduce HasOnCrashReset trait; use timer_* API instead of setitimer for unix TimeoutExecutor

* fixes: PR #469 annotations and CI issues

* reintroduce setitimer for apple as macOS does not feature the POSIX timer API

* more macos and windows CI fixes

* more macos and windows CI fixes cont.

* HasOnCrashReset -> HasPostRunReset

* remove drop impl for Windows TimeoutExecutor

* adjust target cfgs for timeout stuff (android also did not work)

* add call to inner post_run_reset

* remove HasPostRunReset in favor of making it a trait fn of Executor

* add post_run_reset's to CombinedExecutor

* clippy: addr_of! instead of raw pointer casts

* link librt in libafl_cc (required by timer_* API)

* minor fixes and cleanup

* remove unused import for targets other than linux

* fix win

* merge

* fix

Co-authored-by: pr0me <g33sus@gmail.com>
This commit is contained in:
Dongjia Zhang 2022-02-01 12:48:03 +09:00 committed by GitHub
parent 9dfc6aa404
commit c61fed6ca9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 212 additions and 88 deletions

View File

@ -17,42 +17,72 @@ use std::ffi::CString;
#[cfg(target_arch = "arm")]
pub use libc::c_ulong;
/// ARMv7-specific representation of a saved context
#[cfg(target_arch = "arm")]
#[derive(Debug)]
#[allow(non_camel_case_types)]
#[repr(C)]
pub struct mcontext_t {
/// Signal Number
pub trap_no: c_ulong,
/// Error Code
pub error_code: c_ulong,
/// Old signal mask
pub oldmask: c_ulong,
/// GPR R0
pub arm_r0: c_ulong,
/// GPR R1
pub arm_r1: c_ulong,
/// GPR R2
pub arm_r2: c_ulong,
/// GPR R3
pub arm_r3: c_ulong,
/// GPR R4
pub arm_r4: c_ulong,
/// GPR R5
pub arm_r5: c_ulong,
/// GPR R6
pub arm_r6: c_ulong,
/// GPR R7
pub arm_r7: c_ulong,
/// GPR R8
pub arm_r8: c_ulong,
/// GPR R9
pub arm_r9: c_ulong,
/// GPR R10
pub arm_r10: c_ulong,
/// Frame Pointer
pub arm_fp: c_ulong,
/// Intra-Procedure Scratch Register
pub arm_ip: c_ulong,
/// Stack Pointer
pub arm_sp: c_ulong,
/// Link Register
pub arm_lr: c_ulong,
/// Program Counter
pub arm_pc: c_ulong,
/// Current Program Status Register
pub arm_cpsr: c_ulong,
/// Fault Address
pub fault_address: c_ulong,
}
/// User Context Struct
#[cfg(target_arch = "arm")]
#[derive(Debug)]
#[allow(non_camel_case_types)]
#[repr(C)]
pub struct ucontext_t {
/// Flags
pub uc_flags: u32,
/// Pointer to the context that will be resumed when this context returns
pub uc_link: *mut ucontext_t,
/// Stack used by this context
pub uc_stack: stack_t,
/// Machine-specific representation of the saved context
pub uc_mcontext: mcontext_t,
pub uc_sigmask: nix::sys::signal::SigSet,
/// Set of signals that are blocked when this context is active
pub uc_sigmask: libc::sigset_t,
}
#[cfg(not(target_arch = "arm"))]

View File

@ -1118,7 +1118,6 @@ pub mod win32_shmem {
fmt::{self, Debug, Formatter},
ptr, slice,
};
use std::convert::TryInto;
use uuid::Uuid;
const INVALID_HANDLE_VALUE: isize = -1;

View File

@ -50,7 +50,10 @@ where
mgr: &mut EM,
input: &I,
) -> Result<ExitKind, Error> {
self.primary.run_target(fuzzer, state, mgr, input)
let ret = self.primary.run_target(fuzzer, state, mgr, input);
self.primary.post_run_reset();
self.secondary.post_run_reset();
ret
}
}

View File

@ -305,7 +305,7 @@ impl InProcessHandlers {
pub fn new<E, EM, I, OF, OT, S, Z, H>() -> Result<Self, Error>
where
I: Input,
E: HasObservers<I, OT, S>,
E: Executor<EM, I, S, Z> + HasObservers<I, OT, S>,
OT: ObserversTuple<I, S>,
EM: EventFirer<I> + EventRestarter<S>,
OF: Feedback<I, S>,
@ -628,8 +628,7 @@ mod unix_signal_handler {
events::{Event, EventFirer, EventRestarter},
executors::{
inprocess::{InProcessExecutorHandlerData, GLOBAL_STATE},
timeout::unix_remove_timeout,
ExitKind, HasObservers,
Executor, ExitKind, HasObservers,
},
feedbacks::Feedback,
fuzzer::HasObjective,
@ -775,7 +774,7 @@ mod unix_signal_handler {
_context: &mut ucontext_t,
data: &mut InProcessExecutorHandlerData,
) where
E: HasObservers<I, OT, S>,
E: Executor<EM, I, S, Z> + HasObservers<I, OT, S>,
EM: EventFirer<I> + EventRestarter<S>,
OT: ObserversTuple<I, S>,
OF: Feedback<I, S>,
@ -783,8 +782,6 @@ mod unix_signal_handler {
I: Input,
Z: HasObjective<I, OF, S>,
{
unix_remove_timeout();
#[cfg(all(target_os = "android", target_arch = "aarch64"))]
let _context = &mut *(((_context as *mut _ as *mut libc::c_void as usize) + 128)
as *mut libc::c_void as *mut ucontext_t);
@ -825,11 +822,14 @@ mod unix_signal_handler {
// TODO tell the parent to not restart
} else {
let executor = (data.executor_ptr as *mut E).as_mut().unwrap();
// disarms timeout in case of TimeoutExecutor
executor.post_run_reset();
let state = (data.state_ptr as *mut S).as_mut().unwrap();
let event_mgr = (data.event_mgr_ptr as *mut EM).as_mut().unwrap();
let fuzzer = (data.fuzzer_ptr as *mut Z).as_mut().unwrap();
let executor = (data.executor_ptr as *mut E).as_mut().unwrap();
let observers = executor.observers_mut();
let input = (data.current_input_ptr as *const I).as_ref().unwrap();
data.current_input_ptr = ptr::null();
@ -908,8 +908,7 @@ mod windows_exception_handler {
events::{Event, EventFirer, EventRestarter},
executors::{
inprocess::{InProcessExecutorHandlerData, GLOBAL_STATE},
timeout::windows_delete_timer_queue,
ExitKind, HasObservers,
Executor, ExitKind, HasObservers,
},
feedbacks::Feedback,
fuzzer::HasObjective,
@ -1053,7 +1052,7 @@ mod windows_exception_handler {
exception_pointers: *mut EXCEPTION_POINTERS,
data: &mut InProcessExecutorHandlerData,
) where
E: HasObservers<I, OT, S>,
E: Executor<EM, I, S, Z> + HasObservers<I, OT, S>,
EM: EventFirer<I> + EventRestarter<S>,
OT: ObserversTuple<I, S>,
OF: Feedback<I, S>,
@ -1062,7 +1061,7 @@ mod windows_exception_handler {
Z: HasObjective<I, OF, S>,
{
// Have we set a timer_before?
if let Some(x) =
if let Some(_) =
(data.tp_timer as *mut windows::Win32::System::Threading::TP_TIMER).as_mut()
{
/*
@ -1077,9 +1076,6 @@ mod windows_exception_handler {
compiler_fence(Ordering::SeqCst);
LeaveCriticalSection(data.critical as *mut RTL_CRITICAL_SECTION);
compiler_fence(Ordering::SeqCst);
windows_delete_timer_queue(x);
data.tp_timer = ptr::null_mut();
}
let code = ExceptionCode::try_from(
@ -1124,10 +1120,16 @@ mod windows_exception_handler {
// TODO tell the parent to not restart
} else {
let executor = (data.executor_ptr as *mut E).as_mut().unwrap();
// reset timer
if !data.tp_timer.is_null() {
executor.post_run_reset();
data.tp_timer = ptr::null_mut();
}
let state = (data.state_ptr as *mut S).as_mut().unwrap();
let event_mgr = (data.event_mgr_ptr as *mut EM).as_mut().unwrap();
let fuzzer = (data.fuzzer_ptr as *mut Z).as_mut().unwrap();
let executor = (data.executor_ptr as *const E).as_ref().unwrap();
let observers = executor.observers();
#[cfg(feature = "std")]

View File

@ -95,6 +95,10 @@ where
{
WithObservers::new(self, observers)
}
/// Custom Reset Handler, e.g., to reset timers
#[inline]
fn post_run_reset(&mut self) {}
}
/// A simple executor that does nothing.

View File

@ -18,16 +18,20 @@ use crate::executors::inprocess::{HasInProcessHandlers, GLOBAL_STATE};
#[cfg(unix)]
use core::{mem::zeroed, ptr::null_mut};
#[cfg(unix)]
#[cfg(target_os = "linux")]
use core::ptr::{addr_of, addr_of_mut};
#[cfg(all(unix, not(target_os = "linux")))]
use libc::c_int;
#[cfg(all(windows, feature = "std"))]
use windows::Win32::{
Foundation::FILETIME,
System::Threading::{
CloseThreadpoolTimer, CreateThreadpoolTimer, EnterCriticalSection,
InitializeCriticalSection, LeaveCriticalSection, SetThreadpoolTimer, RTL_CRITICAL_SECTION,
TP_CALLBACK_ENVIRON_V3, TP_CALLBACK_INSTANCE, TP_TIMER,
CreateThreadpoolTimer, EnterCriticalSection, InitializeCriticalSection,
LeaveCriticalSection, SetThreadpoolTimer, RTL_CRITICAL_SECTION, TP_CALLBACK_ENVIRON_V3,
TP_CALLBACK_INSTANCE, TP_TIMER,
},
};
@ -38,13 +42,13 @@ use core::{ffi::c_void, ptr::write_volatile};
use core::sync::atomic::{compiler_fence, Ordering};
#[repr(C)]
#[cfg(unix)]
#[cfg(all(unix, not(target_os = "linux")))]
struct Timeval {
pub tv_sec: i64,
pub tv_usec: i64,
}
#[cfg(unix)]
#[cfg(all(unix, not(target_os = "linux")))]
impl Debug for Timeval {
#[allow(clippy::cast_sign_loss)]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
@ -59,42 +63,29 @@ impl Debug for Timeval {
}
#[repr(C)]
#[cfg(unix)]
#[cfg(all(unix, not(target_os = "linux")))]
#[derive(Debug)]
struct Itimerval {
pub it_interval: Timeval,
pub it_value: Timeval,
}
#[cfg(unix)]
#[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(unix)]
#[cfg(all(unix, not(target_os = "linux")))]
const ITIMER_REAL: c_int = 0;
/// Reset and remove the timeout
#[cfg(unix)]
pub(crate) fn unix_remove_timeout() {
unsafe {
let mut itimerval_zero: Itimerval = zeroed();
setitimer(ITIMER_REAL, &mut itimerval_zero, null_mut());
}
}
/// Deletes this timer queue
/// # Safety
/// Will dereference the given `tp_timer` pointer, unchecked.
#[cfg(all(windows, feature = "std"))]
pub(crate) unsafe fn windows_delete_timer_queue(tp_timer: *mut TP_TIMER) {
CloseThreadpoolTimer(tp_timer);
}
/// The timeout excutor is a wrapper that sets a timeout before each run
pub struct TimeoutExecutor<E> {
executor: E,
#[cfg(unix)]
#[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,
@ -113,7 +104,19 @@ impl<E: Debug> Debug for TimeoutExecutor<E> {
.finish_non_exhaustive()
}
#[cfg(unix)]
#[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)
@ -130,7 +133,56 @@ type PTP_TIMER_CALLBACK = unsafe extern "system" fn(
param2: *mut TP_TIMER,
);
#[cfg(unix)]
#[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.
@ -153,6 +205,24 @@ impl<E> TimeoutExecutor<E> {
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)]
@ -183,25 +253,6 @@ impl<E: HasInProcessHandlers> TimeoutExecutor<E> {
}
}
/// Set the timeout for this executor
#[cfg(unix)]
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;
}
/// Set the timeout for this executor
#[cfg(windows)]
pub fn set_timeout(&mut self, exec_tmout: Duration) {
@ -212,15 +263,6 @@ impl<E: HasInProcessHandlers> TimeoutExecutor<E> {
pub fn inner(&mut self) -> &mut E {
&mut self.executor
}
/// Reset the timeout for this executor
#[cfg(windows)]
pub fn windows_reset_timeout(&self) -> Result<(), Error> {
unsafe {
SetThreadpoolTimer(self.tp_timer, core::ptr::null(), 0, 0);
}
Ok(())
}
}
#[cfg(windows)]
@ -277,13 +319,54 @@ where
write_volatile(&mut data.timeout_input_ptr, core::ptr::null_mut());
self.windows_reset_timeout()?;
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, core::ptr::null(), 0, 0);
}
self.executor.post_run_reset();
}
}
#[cfg(unix)]
#[cfg(target_os = "linux")]
impl<E, EM, I, S, Z> Executor<EM, I, S, Z> for TimeoutExecutor<E>
where
E: Executor<EM, I, S, Z>,
I: Input,
{
fn run_target(
&mut self,
fuzzer: &mut Z,
state: &mut S,
mgr: &mut EM,
input: &I,
) -> 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, I, S, Z> Executor<EM, I, S, Z> for TimeoutExecutor<E>
where
E: Executor<EM, I, S, Z>,
@ -296,14 +379,21 @@ where
mgr: &mut EM,
input: &I,
) -> Result<ExitKind, Error> {
#[cfg(unix)]
unsafe {
setitimer(ITIMER_REAL, &mut self.itimerval, null_mut());
let ret = self.executor.run_target(fuzzer, state, mgr, input);
unix_remove_timeout();
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, I, OT, S> HasObservers<I, OT, S> for TimeoutExecutor<E>
@ -321,12 +411,3 @@ where
self.executor.observers_mut()
}
}
#[cfg(windows)]
impl<E> Drop for TimeoutExecutor<E> {
fn drop(&mut self) {
unsafe {
windows_delete_timer_queue(self.tp_timer);
}
}
}

View File

@ -154,6 +154,11 @@ impl CompilerWrapper for ClangWrapper {
new_args.push("-lBcrypt".into());
new_args.push("-lAdvapi32".into());
}
// required by timer API (timer_create, timer_settime)
#[cfg(target_os = "linux")]
if linking {
new_args.push("-lrt".into());
}
// MacOS has odd linker behavior sometimes
#[cfg(target_vendor = "apple")]
if linking {