From c61fed6ca95c68bf163505310dbc9f45f3c08b01 Mon Sep 17 00:00:00 2001 From: Dongjia Zhang Date: Tue, 1 Feb 2022 12:48:03 +0900 Subject: [PATCH] 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 --- libafl/src/bolts/os/unix_signals.rs | 32 +++- libafl/src/bolts/shmem.rs | 1 - libafl/src/executors/combined.rs | 5 +- libafl/src/executors/inprocess.rs | 32 ++-- libafl/src/executors/mod.rs | 4 + libafl/src/executors/timeout.rs | 221 +++++++++++++++++++--------- libafl_cc/src/clang.rs | 5 + 7 files changed, 212 insertions(+), 88 deletions(-) diff --git a/libafl/src/bolts/os/unix_signals.rs b/libafl/src/bolts/os/unix_signals.rs index ca9b093049..89b7c5fc5d 100644 --- a/libafl/src/bolts/os/unix_signals.rs +++ b/libafl/src/bolts/os/unix_signals.rs @@ -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"))] diff --git a/libafl/src/bolts/shmem.rs b/libafl/src/bolts/shmem.rs index c17b4a7382..b2b7591ae0 100644 --- a/libafl/src/bolts/shmem.rs +++ b/libafl/src/bolts/shmem.rs @@ -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; diff --git a/libafl/src/executors/combined.rs b/libafl/src/executors/combined.rs index 8a091832c5..e7dd148545 100644 --- a/libafl/src/executors/combined.rs +++ b/libafl/src/executors/combined.rs @@ -50,7 +50,10 @@ where mgr: &mut EM, input: &I, ) -> Result { - 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 } } diff --git a/libafl/src/executors/inprocess.rs b/libafl/src/executors/inprocess.rs index 0d69a090c4..afe244566f 100644 --- a/libafl/src/executors/inprocess.rs +++ b/libafl/src/executors/inprocess.rs @@ -305,7 +305,7 @@ impl InProcessHandlers { pub fn new() -> Result where I: Input, - E: HasObservers, + E: Executor + HasObservers, OT: ObserversTuple, EM: EventFirer + EventRestarter, OF: Feedback, @@ -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, + E: Executor + HasObservers, EM: EventFirer + EventRestarter, OT: ObserversTuple, OF: Feedback, @@ -783,8 +782,6 @@ mod unix_signal_handler { I: Input, Z: HasObjective, { - 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, + E: Executor + HasObservers, EM: EventFirer + EventRestarter, OT: ObserversTuple, OF: Feedback, @@ -1062,7 +1061,7 @@ mod windows_exception_handler { Z: HasObjective, { // 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")] diff --git a/libafl/src/executors/mod.rs b/libafl/src/executors/mod.rs index 2afabb8ab1..56c146d2ed 100644 --- a/libafl/src/executors/mod.rs +++ b/libafl/src/executors/mod.rs @@ -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. diff --git a/libafl/src/executors/timeout.rs b/libafl/src/executors/timeout.rs index 93747bce79..8c33a4d2b0 100644 --- a/libafl/src/executors/timeout.rs +++ b/libafl/src/executors/timeout.rs @@ -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 { 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 Debug for TimeoutExecutor { .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 TimeoutExecutor { + /// 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 TimeoutExecutor { /// 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 TimeoutExecutor { 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 TimeoutExecutor { } } - /// 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 TimeoutExecutor { 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 Executor for TimeoutExecutor +where + E: Executor, + I: Input, +{ + fn run_target( + &mut self, + fuzzer: &mut Z, + state: &mut S, + mgr: &mut EM, + input: &I, + ) -> Result { + 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 Executor for TimeoutExecutor where E: Executor, @@ -296,14 +379,21 @@ where mgr: &mut EM, input: &I, ) -> Result { - #[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 HasObservers for TimeoutExecutor @@ -321,12 +411,3 @@ where self.executor.observers_mut() } } - -#[cfg(windows)] -impl Drop for TimeoutExecutor { - fn drop(&mut self) { - unsafe { - windows_delete_timer_queue(self.tp_timer); - } - } -} diff --git a/libafl_cc/src/clang.rs b/libafl_cc/src/clang.rs index 5f926f831b..fd30fda047 100644 --- a/libafl_cc/src/clang.rs +++ b/libafl_cc/src/clang.rs @@ -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 {