From 3b3e2f6efa8303b93ca168349950d211658d1882 Mon Sep 17 00:00:00 2001 From: Romain Malmain Date: Wed, 6 Mar 2024 00:30:35 +0100 Subject: [PATCH] Improve readability of InProcessExecutor-related code (#1912) * inital commit. * clippy * tests * clippy * adapt example * systemmode. * renaming * fmt * fix lints. * more lint fix. * even more lint fixes. * always more lint fixes. * lint fix. * allow unused qualifications for crate when it could be confusing. * Still lint fixes. * Lint fixes on generated code. * Some lint fixes. * renamed modules as well. * Separated inner from InProcessExecutor. * fix * unused import * unused import * fix import * fix import --- libafl/src/executors/inprocess/inner.rs | 414 +++++++++++++++++++ libafl/src/executors/inprocess/mod.rs | 405 +----------------- libafl/src/executors/inprocess_fork/inner.rs | 351 ++++++++++++++++ libafl/src/executors/inprocess_fork/mod.rs | 338 +-------------- 4 files changed, 781 insertions(+), 727 deletions(-) create mode 100644 libafl/src/executors/inprocess/inner.rs create mode 100644 libafl/src/executors/inprocess_fork/inner.rs diff --git a/libafl/src/executors/inprocess/inner.rs b/libafl/src/executors/inprocess/inner.rs new file mode 100644 index 0000000000..04afc5e660 --- /dev/null +++ b/libafl/src/executors/inprocess/inner.rs @@ -0,0 +1,414 @@ +use core::{ + ffi::c_void, + fmt::{self, Debug, Formatter}, + marker::PhantomData, + ptr::{self, addr_of_mut, null, write_volatile}, + sync::atomic::{compiler_fence, Ordering}, + time::Duration, +}; + +use libafl_bolts::tuples::{tuple_list, Merge}; +#[cfg(windows)] +use windows::Win32::System::Threading::SetThreadStackGuarantee; + +#[cfg(all(feature = "std", target_os = "linux"))] +use crate::executors::hooks::inprocess::HasTimeout; +#[cfg(all(windows, feature = "std"))] +use crate::executors::hooks::inprocess::HasTimeout; +use crate::{ + events::{EventFirer, EventRestarter}, + executors::{ + hooks::{ + inprocess::{InProcessHooks, GLOBAL_STATE}, + ExecutorHooksTuple, + }, + inprocess::HasInProcessHooks, + Executor, HasObservers, + }, + feedbacks::Feedback, + fuzzer::HasObjective, + inputs::UsesInput, + observers::{ObserversTuple, UsesObservers}, + state::{HasCorpus, HasExecutions, HasSolutions, State, UsesState}, + Error, +}; + +/// The internal state of `GenericInProcessExecutor`. +pub struct GenericInProcessExecutorInner +where + HT: ExecutorHooksTuple, + OT: ObserversTuple, + S: State, +{ + /// The observers, observing each run + pub(super) observers: OT, + // Crash and timeout hah + pub(super) hooks: (InProcessHooks, HT), + phantom: PhantomData, +} + +impl Debug for GenericInProcessExecutorInner +where + HT: ExecutorHooksTuple, + OT: ObserversTuple + Debug, + S: State, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("GenericInProcessExecutorState") + .field("observers", &self.observers) + .finish_non_exhaustive() + } +} + +impl UsesState for GenericInProcessExecutorInner +where + HT: ExecutorHooksTuple, + OT: ObserversTuple, + S: State, +{ + type State = S; +} + +impl UsesObservers for GenericInProcessExecutorInner +where + HT: ExecutorHooksTuple, + OT: ObserversTuple, + S: State, +{ + type Observers = OT; +} + +impl HasObservers for GenericInProcessExecutorInner +where + HT: ExecutorHooksTuple, + OT: ObserversTuple, + S: State, +{ + #[inline] + fn observers(&self) -> &OT { + &self.observers + } + + #[inline] + fn observers_mut(&mut self) -> &mut OT { + &mut self.observers + } +} + +impl GenericInProcessExecutorInner +where + HT: ExecutorHooksTuple, + OT: ObserversTuple, + S: State, +{ + /// This function marks the boundary between the fuzzer and the target + #[inline] + pub fn enter_target( + &mut self, + fuzzer: &mut Z, + state: &mut ::State, + mgr: &mut EM, + input: &::Input, + ) { + unsafe { + let data = addr_of_mut!(GLOBAL_STATE); + write_volatile( + addr_of_mut!((*data).current_input_ptr), + ptr::from_ref(input) as *const c_void, + ); + write_volatile( + addr_of_mut!((*data).executor_ptr), + ptr::from_ref(self) as *const c_void, + ); + // Direct raw pointers access /aliasing is pretty undefined behavior. + // Since the state and event may have moved in memory, refresh them right before the signal may happen + write_volatile( + addr_of_mut!((*data).state_ptr), + ptr::from_mut(state) as *mut c_void, + ); + write_volatile( + addr_of_mut!((*data).event_mgr_ptr), + ptr::from_mut(mgr) as *mut c_void, + ); + write_volatile( + addr_of_mut!((*data).fuzzer_ptr), + ptr::from_mut(fuzzer) as *mut c_void, + ); + compiler_fence(Ordering::SeqCst); + } + } + + /// This function marks the boundary between the fuzzer and the target + #[inline] + pub fn leave_target( + &mut self, + _fuzzer: &mut Z, + _state: &mut ::State, + _mgr: &mut EM, + _input: &::Input, + ) { + unsafe { + let data = addr_of_mut!(GLOBAL_STATE); + + write_volatile(addr_of_mut!((*data).current_input_ptr), null()); + compiler_fence(Ordering::SeqCst); + } + } +} + +impl GenericInProcessExecutorInner<(), OT, S> +where + OT: ObserversTuple, + S: HasExecutions + HasSolutions + HasCorpus + State, +{ + /// Create a new in mem executor with the default timeout (5 sec) + pub fn new( + observers: OT, + fuzzer: &mut Z, + state: &mut S, + event_mgr: &mut EM, + ) -> Result + where + E: Executor + HasObservers + HasInProcessHooks, + EM: EventFirer + EventRestarter, + OF: Feedback, + S: State, + Z: HasObjective, + { + Self::with_timeout_generic::( + tuple_list!(), + observers, + fuzzer, + state, + event_mgr, + Duration::from_millis(5000), + ) + } + + /// Create a new in mem executor with the default timeout and use batch mode (5 sec) + /// Do not use batched mode timeouts with cmplog cores. It is not supported + #[cfg(all(feature = "std", target_os = "linux"))] + pub fn batched_timeouts( + observers: OT, + fuzzer: &mut Z, + state: &mut S, + event_mgr: &mut EM, + exec_tmout: Duration, + ) -> Result + where + E: Executor + HasObservers + HasInProcessHooks, + EM: EventFirer + EventRestarter, + OF: Feedback, + S: State, + Z: HasObjective, + { + let mut me = Self::with_timeout_generic::( + tuple_list!(), + observers, + fuzzer, + state, + event_mgr, + exec_tmout, + )?; + me.hooks_mut().0.timer_mut().batch_mode = true; + Ok(me) + } + + /// Create a new in mem executor. + /// Caution: crash and restart in one of them will lead to odd behavior if multiple are used, + /// depending on different corpus or state. + /// * `user_hooks` - the hooks run before and after the harness's execution + /// * `harness_fn` - the harness, executing the function + /// * `observers` - the observers observing the target during execution + /// This may return an error on unix, if signal handler setup fails + pub fn with_timeout( + observers: OT, + _fuzzer: &mut Z, + state: &mut S, + _event_mgr: &mut EM, + timeout: Duration, + ) -> Result + where + E: Executor + HasObservers + HasInProcessHooks, + EM: EventFirer + EventRestarter, + OF: Feedback, + S: State, + Z: HasObjective, + { + let default = InProcessHooks::new::(timeout)?; + let mut hooks = tuple_list!(default).merge(tuple_list!()); + hooks.init_all::(state); + + #[cfg(windows)] + // Some initialization necessary for windows. + unsafe { + /* + See https://github.com/AFLplusplus/LibAFL/pull/403 + This one reserves certain amount of memory for the stack. + If stack overflow happens during fuzzing on windows, the program is transferred to our exception handler for windows. + However, if we run out of the stack memory again in this exception handler, we'll crash with STATUS_ACCESS_VIOLATION. + We need this API call because with the llmp_compression + feature enabled, the exception handler uses a lot of stack memory (in the compression lib code) on release build. + As far as I have observed, the compression uses around 0x10000 bytes, but for safety let's just reserve 0x20000 bytes for our exception handlers. + This number 0x20000 could vary depending on the compilers optimization for future compression library changes. + */ + let mut stack_reserved = 0x20000; + SetThreadStackGuarantee(&mut stack_reserved)?; + } + + #[cfg(all(feature = "std", windows))] + { + // set timeout for the handler + *hooks.0.millis_sec_mut() = timeout.as_millis() as i64; + } + + Ok(Self { + observers, + hooks, + phantom: PhantomData, + }) + } +} + +impl GenericInProcessExecutorInner +where + HT: ExecutorHooksTuple, + OT: ObserversTuple, + S: HasExecutions + HasSolutions + HasCorpus + State, +{ + /// Create a new in mem executor with the default timeout (5 sec) + pub fn generic( + user_hooks: HT, + observers: OT, + fuzzer: &mut Z, + state: &mut S, + event_mgr: &mut EM, + ) -> Result + where + E: Executor + HasObservers + HasInProcessHooks, + EM: EventFirer + EventRestarter, + OF: Feedback, + S: State, + Z: HasObjective, + { + Self::with_timeout_generic::( + user_hooks, + observers, + fuzzer, + state, + event_mgr, + Duration::from_millis(5000), + ) + } + + /// Create a new in mem executor with the default timeout and use batch mode(5 sec) + #[cfg(all(feature = "std", target_os = "linux"))] + pub fn batched_timeout_generic( + user_hooks: HT, + observers: OT, + fuzzer: &mut Z, + state: &mut S, + event_mgr: &mut EM, + exec_tmout: Duration, + ) -> Result + where + E: Executor + HasObservers + HasInProcessHooks, + EM: EventFirer + EventRestarter, + OF: Feedback, + S: State, + Z: HasObjective, + { + let mut me = Self::with_timeout_generic::( + user_hooks, observers, fuzzer, state, event_mgr, exec_tmout, + )?; + me.hooks_mut().0.timer_mut().batch_mode = true; + Ok(me) + } + + /// Create a new in mem executor. + /// Caution: crash and restart in one of them will lead to odd behavior if multiple are used, + /// depending on different corpus or state. + /// * `user_hooks` - the hooks run before and after the harness's execution + /// * `harness_fn` - the harness, executing the function + /// * `observers` - the observers observing the target during execution + /// This may return an error on unix, if signal handler setup fails + pub fn with_timeout_generic( + user_hooks: HT, + observers: OT, + _fuzzer: &mut Z, + state: &mut S, + _event_mgr: &mut EM, + timeout: Duration, + ) -> Result + where + E: Executor + HasObservers + HasInProcessHooks, + EM: EventFirer + EventRestarter, + OF: Feedback, + S: State, + Z: HasObjective, + { + let default = InProcessHooks::new::(timeout)?; + let mut hooks = tuple_list!(default).merge(user_hooks); + hooks.init_all::(state); + + #[cfg(windows)] + // Some initialization necessary for windows. + unsafe { + /* + See https://github.com/AFLplusplus/LibAFL/pull/403 + This one reserves certain amount of memory for the stack. + If stack overflow happens during fuzzing on windows, the program is transferred to our exception handler for windows. + However, if we run out of the stack memory again in this exception handler, we'll crash with STATUS_ACCESS_VIOLATION. + We need this API call because with the llmp_compression + feature enabled, the exception handler uses a lot of stack memory (in the compression lib code) on release build. + As far as I have observed, the compression uses around 0x10000 bytes, but for safety let's just reserve 0x20000 bytes for our exception handlers. + This number 0x20000 could vary depending on the compilers optimization for future compression library changes. + */ + let mut stack_reserved = 0x20000; + SetThreadStackGuarantee(&mut stack_reserved)?; + } + + #[cfg(all(feature = "std", windows))] + { + // set timeout for the handler + *hooks.0.millis_sec_mut() = timeout.as_millis() as i64; + } + + Ok(Self { + observers, + hooks, + phantom: PhantomData, + }) + } + + /// The inprocess handlers + #[inline] + pub fn hooks(&self) -> &(InProcessHooks, HT) { + &self.hooks + } + + /// The inprocess handlers (mutable) + #[inline] + pub fn hooks_mut(&mut self) -> &mut (InProcessHooks, HT) { + &mut self.hooks + } +} + +impl HasInProcessHooks for GenericInProcessExecutorInner +where + HT: ExecutorHooksTuple, + OT: ObserversTuple, + S: State + HasExecutions + HasSolutions + HasCorpus, +{ + /// the timeout handler + #[inline] + fn inprocess_hooks(&self) -> &InProcessHooks { + &self.hooks.0 + } + + /// the timeout handler + #[inline] + fn inprocess_hooks_mut(&mut self) -> &mut InProcessHooks { + &mut self.hooks.0 + } +} diff --git a/libafl/src/executors/inprocess/mod.rs b/libafl/src/executors/inprocess/mod.rs index f7b79d9000..b0962d192a 100644 --- a/libafl/src/executors/inprocess/mod.rs +++ b/libafl/src/executors/inprocess/mod.rs @@ -5,32 +5,25 @@ #![allow(clippy::needless_pass_by_value)] use alloc::boxed::Box; +#[cfg(any(unix, feature = "std"))] +use core::ptr::addr_of_mut; use core::{ borrow::BorrowMut, - ffi::c_void, fmt::{self, Debug, Formatter}, marker::PhantomData, - ptr::{self, addr_of_mut, null, write_volatile}, - sync::atomic::{compiler_fence, Ordering}, time::Duration, }; -use libafl_bolts::tuples::{tuple_list, Merge}; -#[cfg(windows)] -use windows::Win32::System::Threading::SetThreadStackGuarantee; +use libafl_bolts::tuples::tuple_list; -#[cfg(all(feature = "std", target_os = "linux"))] -use crate::executors::hooks::inprocess::HasTimeout; -#[cfg(all(windows, feature = "std"))] -use crate::executors::hooks::inprocess::HasTimeout; +#[cfg(any(unix, feature = "std"))] +use crate::executors::hooks::inprocess::GLOBAL_STATE; use crate::{ corpus::{Corpus, Testcase}, events::{Event, EventFirer, EventRestarter}, executors::{ - hooks::{ - inprocess::{InProcessHooks, GLOBAL_STATE}, - ExecutorHooksTuple, - }, + hooks::{inprocess::InProcessHooks, ExecutorHooksTuple}, + inprocess::inner::GenericInProcessExecutorInner, Executor, ExitKind, HasObservers, }, feedbacks::Feedback, @@ -41,6 +34,8 @@ use crate::{ Error, }; +/// The inner structure of `InProcessExecutor`. +pub mod inner; /// A version of `InProcessExecutor` with a state accessible from the harness. pub mod stateful; @@ -59,20 +54,6 @@ pub type OwnedInProcessExecutor = GenericInProcessExecutor< S, >; -/// The internal state of `GenericInProcessExecutor`. -pub struct GenericInProcessExecutorInner -where - HT: ExecutorHooksTuple, - OT: ObserversTuple, - S: State, -{ - /// The observers, observing each run - observers: OT, - // Crash and timeout hah - hooks: (InProcessHooks, HT), - phantom: PhantomData, -} - /// The inmem executor simply calls a target function, then returns afterwards. #[allow(dead_code)] pub struct GenericInProcessExecutor @@ -88,19 +69,6 @@ where phantom: PhantomData<(*const H, HB)>, } -impl Debug for GenericInProcessExecutorInner -where - HT: ExecutorHooksTuple, - OT: ObserversTuple + Debug, - S: State, -{ - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_struct("GenericInProcessExecutorState") - .field("observers", &self.observers) - .finish_non_exhaustive() - } -} - impl Debug for GenericInProcessExecutor where H: FnMut(&S::Input) -> ExitKind + ?Sized, @@ -117,15 +85,6 @@ where } } -impl UsesState for GenericInProcessExecutorInner -where - HT: ExecutorHooksTuple, - OT: ObserversTuple, - S: State, -{ - type State = S; -} - impl UsesState for GenericInProcessExecutor where H: FnMut(&S::Input) -> ExitKind + ?Sized, @@ -137,15 +96,6 @@ where type State = S; } -impl UsesObservers for GenericInProcessExecutorInner -where - HT: ExecutorHooksTuple, - OT: ObserversTuple, - S: State, -{ - type Observers = OT; -} - impl UsesObservers for GenericInProcessExecutor where H: FnMut(&S::Input) -> ExitKind + ?Sized, @@ -186,23 +136,6 @@ where } } -impl HasObservers for GenericInProcessExecutorInner -where - HT: ExecutorHooksTuple, - OT: ObserversTuple, - S: State, -{ - #[inline] - fn observers(&self) -> &OT { - &self.observers - } - - #[inline] - fn observers_mut(&mut self) -> &mut OT { - &mut self.observers - } -} - impl HasObservers for GenericInProcessExecutor where H: FnMut(&S::Input) -> ExitKind + ?Sized, @@ -222,181 +155,6 @@ where } } -impl GenericInProcessExecutorInner -where - HT: ExecutorHooksTuple, - OT: ObserversTuple, - S: State, -{ - /// This function marks the boundary between the fuzzer and the target - #[inline] - pub fn enter_target( - &mut self, - fuzzer: &mut Z, - state: &mut ::State, - mgr: &mut EM, - input: &::Input, - ) { - unsafe { - let data = addr_of_mut!(GLOBAL_STATE); - write_volatile( - addr_of_mut!((*data).current_input_ptr), - ptr::from_ref(input) as *const c_void, - ); - write_volatile( - addr_of_mut!((*data).executor_ptr), - ptr::from_ref(self) as *const c_void, - ); - // Direct raw pointers access /aliasing is pretty undefined behavior. - // Since the state and event may have moved in memory, refresh them right before the signal may happen - write_volatile( - addr_of_mut!((*data).state_ptr), - ptr::from_mut(state) as *mut c_void, - ); - write_volatile( - addr_of_mut!((*data).event_mgr_ptr), - ptr::from_mut(mgr) as *mut c_void, - ); - write_volatile( - addr_of_mut!((*data).fuzzer_ptr), - ptr::from_mut(fuzzer) as *mut c_void, - ); - compiler_fence(Ordering::SeqCst); - } - } - - /// This function marks the boundary between the fuzzer and the target - #[inline] - pub fn leave_target( - &mut self, - _fuzzer: &mut Z, - _state: &mut ::State, - _mgr: &mut EM, - _input: &::Input, - ) { - unsafe { - let data = addr_of_mut!(GLOBAL_STATE); - - write_volatile(addr_of_mut!((*data).current_input_ptr), null()); - compiler_fence(Ordering::SeqCst); - } - } -} - -impl GenericInProcessExecutorInner<(), OT, S> -where - OT: ObserversTuple, - S: HasExecutions + HasSolutions + HasCorpus + State, -{ - /// Create a new in mem executor with the default timeout (5 sec) - pub fn new( - observers: OT, - fuzzer: &mut Z, - state: &mut S, - event_mgr: &mut EM, - ) -> Result - where - E: Executor + HasObservers + HasInProcessHooks, - EM: EventFirer + EventRestarter, - OF: Feedback, - S: State, - Z: HasObjective, - { - Self::with_timeout_generic::( - tuple_list!(), - observers, - fuzzer, - state, - event_mgr, - Duration::from_millis(5000), - ) - } - - /// Create a new in mem executor with the default timeout and use batch mode (5 sec) - /// Do not use batched mode timeouts with cmplog cores. It is not supported - #[cfg(all(feature = "std", target_os = "linux"))] - pub fn batched_timeouts( - observers: OT, - fuzzer: &mut Z, - state: &mut S, - event_mgr: &mut EM, - exec_tmout: Duration, - ) -> Result - where - E: Executor + HasObservers + HasInProcessHooks, - EM: EventFirer + EventRestarter, - OF: Feedback, - S: State, - Z: HasObjective, - { - let mut me = Self::with_timeout_generic::( - tuple_list!(), - observers, - fuzzer, - state, - event_mgr, - exec_tmout, - )?; - me.hooks_mut().0.timer_mut().batch_mode = true; - Ok(me) - } - - /// Create a new in mem executor. - /// Caution: crash and restart in one of them will lead to odd behavior if multiple are used, - /// depending on different corpus or state. - /// * `user_hooks` - the hooks run before and after the harness's execution - /// * `harness_fn` - the harness, executing the function - /// * `observers` - the observers observing the target during execution - /// This may return an error on unix, if signal handler setup fails - pub fn with_timeout( - observers: OT, - _fuzzer: &mut Z, - state: &mut S, - _event_mgr: &mut EM, - timeout: Duration, - ) -> Result - where - E: Executor + HasObservers + HasInProcessHooks, - EM: EventFirer + EventRestarter, - OF: Feedback, - S: State, - Z: HasObjective, - { - let default = InProcessHooks::new::(timeout)?; - let mut hooks = tuple_list!(default).merge(tuple_list!()); - hooks.init_all::(state); - - #[cfg(windows)] - // Some initialization necessary for windows. - unsafe { - /* - See https://github.com/AFLplusplus/LibAFL/pull/403 - This one reserves certain amount of memory for the stack. - If stack overflow happens during fuzzing on windows, the program is transferred to our exception handler for windows. - However, if we run out of the stack memory again in this exception handler, we'll crash with STATUS_ACCESS_VIOLATION. - We need this API call because with the llmp_compression - feature enabled, the exception handler uses a lot of stack memory (in the compression lib code) on release build. - As far as I have observed, the compression uses around 0x10000 bytes, but for safety let's just reserve 0x20000 bytes for our exception handlers. - This number 0x20000 could vary depending on the compilers optimization for future compression library changes. - */ - let mut stack_reserved = 0x20000; - SetThreadStackGuarantee(&mut stack_reserved)?; - } - - #[cfg(all(feature = "std", windows))] - { - // set timeout for the handler - *hooks.0.millis_sec_mut() = timeout.as_millis() as i64; - } - - Ok(Self { - observers, - hooks, - phantom: PhantomData, - }) - } -} - impl<'a, H, OT, S> InProcessExecutor<'a, H, OT, S> where H: FnMut(&S::Input) -> ExitKind + ?Sized, @@ -440,7 +198,7 @@ where exec_tmout: Duration, ) -> Result where - Self: Executor + HasObservers + HasInProcessHooks, + Self: Executor, EM: EventFirer + EventRestarter, OF: Feedback, S: State, @@ -491,130 +249,6 @@ where } } -impl GenericInProcessExecutorInner -where - HT: ExecutorHooksTuple, - OT: ObserversTuple, - S: HasExecutions + HasSolutions + HasCorpus + State, -{ - /// Create a new in mem executor with the default timeout (5 sec) - pub fn generic( - user_hooks: HT, - observers: OT, - fuzzer: &mut Z, - state: &mut S, - event_mgr: &mut EM, - ) -> Result - where - E: Executor + HasObservers + HasInProcessHooks, - EM: EventFirer + EventRestarter, - OF: Feedback, - S: State, - Z: HasObjective, - { - Self::with_timeout_generic::( - user_hooks, - observers, - fuzzer, - state, - event_mgr, - Duration::from_millis(5000), - ) - } - - /// Create a new in mem executor with the default timeout and use batch mode(5 sec) - #[cfg(all(feature = "std", target_os = "linux"))] - pub fn batched_timeout_generic( - user_hooks: HT, - observers: OT, - fuzzer: &mut Z, - state: &mut S, - event_mgr: &mut EM, - exec_tmout: Duration, - ) -> Result - where - E: Executor + HasObservers + HasInProcessHooks, - EM: EventFirer + EventRestarter, - OF: Feedback, - S: State, - Z: HasObjective, - { - let mut me = Self::with_timeout_generic::( - user_hooks, observers, fuzzer, state, event_mgr, exec_tmout, - )?; - me.hooks_mut().0.timer_mut().batch_mode = true; - Ok(me) - } - - /// Create a new in mem executor. - /// Caution: crash and restart in one of them will lead to odd behavior if multiple are used, - /// depending on different corpus or state. - /// * `user_hooks` - the hooks run before and after the harness's execution - /// * `harness_fn` - the harness, executing the function - /// * `observers` - the observers observing the target during execution - /// This may return an error on unix, if signal handler setup fails - pub fn with_timeout_generic( - user_hooks: HT, - observers: OT, - _fuzzer: &mut Z, - state: &mut S, - _event_mgr: &mut EM, - timeout: Duration, - ) -> Result - where - E: Executor + HasObservers + HasInProcessHooks, - EM: EventFirer + EventRestarter, - OF: Feedback, - S: State, - Z: HasObjective, - { - let default = InProcessHooks::new::(timeout)?; - let mut hooks = tuple_list!(default).merge(user_hooks); - hooks.init_all::(state); - - #[cfg(windows)] - // Some initialization necessary for windows. - unsafe { - /* - See https://github.com/AFLplusplus/LibAFL/pull/403 - This one reserves certain amount of memory for the stack. - If stack overflow happens during fuzzing on windows, the program is transferred to our exception handler for windows. - However, if we run out of the stack memory again in this exception handler, we'll crash with STATUS_ACCESS_VIOLATION. - We need this API call because with the llmp_compression - feature enabled, the exception handler uses a lot of stack memory (in the compression lib code) on release build. - As far as I have observed, the compression uses around 0x10000 bytes, but for safety let's just reserve 0x20000 bytes for our exception handlers. - This number 0x20000 could vary depending on the compilers optimization for future compression library changes. - */ - let mut stack_reserved = 0x20000; - SetThreadStackGuarantee(&mut stack_reserved)?; - } - - #[cfg(all(feature = "std", windows))] - { - // set timeout for the handler - *hooks.0.millis_sec_mut() = timeout.as_millis() as i64; - } - - Ok(Self { - observers, - hooks, - phantom: PhantomData, - }) - } - - /// The inprocess handlers - #[inline] - pub fn hooks(&self) -> &(InProcessHooks, HT) { - &self.hooks - } - - /// The inprocess handlers (mutable) - #[inline] - pub fn hooks_mut(&mut self) -> &mut (InProcessHooks, HT) { - &mut self.hooks - } -} - impl GenericInProcessExecutor where H: FnMut(&S::Input) -> ExitKind + ?Sized, @@ -747,25 +381,6 @@ pub trait HasInProcessHooks { fn inprocess_hooks_mut(&mut self) -> &mut InProcessHooks; } -impl HasInProcessHooks for GenericInProcessExecutorInner -where - HT: ExecutorHooksTuple, - OT: ObserversTuple, - S: State + HasExecutions + HasSolutions + HasCorpus, -{ - /// the timeout handler - #[inline] - fn inprocess_hooks(&self) -> &InProcessHooks { - &self.hooks.0 - } - - /// the timeout handler - #[inline] - fn inprocess_hooks_mut(&mut self) -> &mut InProcessHooks { - &mut self.hooks.0 - } -} - impl HasInProcessHooks for GenericInProcessExecutor where H: FnMut(&::Input) -> ExitKind + ?Sized, diff --git a/libafl/src/executors/inprocess_fork/inner.rs b/libafl/src/executors/inprocess_fork/inner.rs new file mode 100644 index 0000000000..ed567d2292 --- /dev/null +++ b/libafl/src/executors/inprocess_fork/inner.rs @@ -0,0 +1,351 @@ +use core::{ + ffi::c_void, + fmt::{self, Debug, Formatter}, + marker::PhantomData, + ptr::{self, addr_of_mut, null_mut, write_volatile}, + sync::atomic::{compiler_fence, Ordering}, + time::Duration, +}; + +use libafl_bolts::{ + os::unix_signals::Signal, + shmem::ShMemProvider, + tuples::{tuple_list, Merge}, +}; +use nix::{ + sys::wait::{waitpid, WaitStatus}, + unistd::Pid, +}; + +#[cfg(all(unix, not(target_os = "linux")))] +use crate::executors::hooks::timer::{setitimer, Itimerval, Timeval, ITIMER_REAL}; +use crate::{ + events::{EventFirer, EventRestarter}, + executors::{ + hooks::{ + inprocess_fork::{InChildProcessHooks, FORK_EXECUTOR_GLOBAL_DATA}, + ExecutorHooksTuple, + }, + ExitKind, HasObservers, + }, + inputs::UsesInput, + observers::{ObserversTuple, UsesObservers}, + state::{State, UsesState}, + Error, +}; + +/// Inner state of GenericInProcessExecutor-like structures. +pub struct GenericInProcessForkExecutorInner +where + OT: ObserversTuple, + S: UsesInput, + SP: ShMemProvider, + HT: ExecutorHooksTuple, + EM: UsesState, + Z: UsesState, +{ + pub(super) hooks: (InChildProcessHooks, HT), + pub(super) shmem_provider: SP, + pub(super) observers: OT, + #[cfg(target_os = "linux")] + pub(super) itimerspec: libc::itimerspec, + #[cfg(all(unix, not(target_os = "linux")))] + pub(super) itimerval: Itimerval, + pub(super) phantom: PhantomData<(S, EM, Z)>, +} + +impl Debug for GenericInProcessForkExecutorInner +where + OT: ObserversTuple + Debug, + S: UsesInput, + SP: ShMemProvider, + HT: ExecutorHooksTuple + Debug, + EM: UsesState, + Z: UsesState, +{ + #[cfg(target_os = "linux")] + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("GenericInProcessForkExecutorInner") + .field("hooks", &self.hooks) + .field("observers", &self.observers) + .field("shmem_provider", &self.shmem_provider) + .field("itimerspec", &self.itimerspec) + .finish() + } + + #[cfg(not(target_os = "linux"))] + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + #[cfg(not(target_os = "linux"))] + return f + .debug_struct("GenericInProcessForkExecutorInner") + .field("observers", &self.observers) + .field("shmem_provider", &self.shmem_provider) + .field("itimerval", &self.itimerval) + .finish(); + } +} + +impl UsesState for GenericInProcessForkExecutorInner +where + OT: ObserversTuple, + S: State, + SP: ShMemProvider, + HT: ExecutorHooksTuple, + EM: UsesState, + Z: UsesState, +{ + type State = S; +} + +impl GenericInProcessForkExecutorInner +where + OT: ObserversTuple + Debug, + S: State + UsesInput, + SP: ShMemProvider, + HT: ExecutorHooksTuple, + EM: EventFirer + EventRestarter, + Z: UsesState, +{ + pub(super) unsafe fn pre_run_target_child( + &mut self, + fuzzer: &mut Z, + state: &mut as UsesState>::State, + mgr: &mut EM, + input: & as UsesInput>::Input, + ) -> Result<(), Error> { + self.shmem_provider.post_fork(true)?; + + self.enter_target(fuzzer, state, mgr, input); + self.hooks.pre_exec_all(fuzzer, state, mgr, input); + + self.observers + .pre_exec_child_all(state, input) + .expect("Failed to run post_exec on observers"); + + #[cfg(target_os = "linux")] + { + 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)); + + // log::info!("Set timer! {:#?} {timerid:#?}", self.itimerspec); + let _: i32 = libc::timer_settime(timerid, 0, addr_of_mut!(self.itimerspec), null_mut()); + } + #[cfg(not(target_os = "linux"))] + { + setitimer(ITIMER_REAL, &mut self.itimerval, null_mut()); + } + // log::trace!("{v:#?} {}", nix::errno::errno()); + + Ok(()) + } + + pub(super) unsafe fn post_run_target_child( + &mut self, + fuzzer: &mut Z, + state: &mut as UsesState>::State, + mgr: &mut EM, + input: & as UsesInput>::Input, + ) { + self.observers + .post_exec_child_all(state, input, &ExitKind::Ok) + .expect("Failed to run post_exec on observers"); + + self.hooks.post_exec_all(fuzzer, state, mgr, input); + self.leave_target(fuzzer, state, mgr, input); + + libc::_exit(0); + } + + pub(super) fn parent(&mut self, child: Pid) -> Result { + // log::trace!("from parent {} child is {}", std::process::id(), child); + self.shmem_provider.post_fork(false)?; + + let res = waitpid(child, None)?; + log::trace!("{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), + } + } +} + +impl GenericInProcessForkExecutorInner +where + HT: ExecutorHooksTuple, + S: State, + OT: ObserversTuple, + SP: ShMemProvider, + EM: EventFirer + EventRestarter, + Z: UsesState, +{ + #[inline] + /// This function marks the boundary between the fuzzer and the target. + pub fn enter_target( + &mut self, + _fuzzer: &mut Z, + state: &mut ::State, + _event_mgr: &mut EM, + input: &::Input, + ) { + unsafe { + let data = addr_of_mut!(FORK_EXECUTOR_GLOBAL_DATA); + write_volatile( + addr_of_mut!((*data).executor_ptr), + ptr::from_ref(self) as *const c_void, + ); + write_volatile( + addr_of_mut!((*data).current_input_ptr), + ptr::from_ref(input) as *const c_void, + ); + write_volatile( + addr_of_mut!((*data).state_ptr), + ptr::from_mut(state) as *mut c_void, + ); + compiler_fence(Ordering::SeqCst); + } + } + + #[inline] + /// This function marks the boundary between the fuzzer and the target. + pub fn leave_target( + &mut self, + _fuzzer: &mut Z, + _state: &mut ::State, + _event_mgr: &mut EM, + _input: &::Input, + ) { + // do nothing + } + + /// Creates a new [`GenericInProcessForkExecutorInner`] with custom hooks + #[cfg(target_os = "linux")] + #[allow(clippy::too_many_arguments)] + pub fn with_hooks( + userhooks: HT, + observers: OT, + _fuzzer: &mut Z, + state: &mut S, + _event_mgr: &mut EM, + timeout: Duration, + shmem_provider: SP, + ) -> Result { + let default_hooks = InChildProcessHooks::new::()?; + let mut hooks = tuple_list!(default_hooks).merge(userhooks); + hooks.init_all::(state); + + 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 { + shmem_provider, + observers, + hooks, + itimerspec, + phantom: PhantomData, + }) + } + + /// Creates a new [`GenericInProcessForkExecutor`], non linux + #[cfg(not(target_os = "linux"))] + #[allow(clippy::too_many_arguments)] + pub fn with_hooks( + userhooks: HT, + observers: OT, + _fuzzer: &mut Z, + state: &mut S, + _event_mgr: &mut EM, + timeout: Duration, + shmem_provider: SP, + ) -> Result { + let default_hooks = InChildProcessHooks::new::()?; + let mut hooks = tuple_list!(default_hooks).merge(userhooks); + hooks.init_all::(state); + + let milli_sec = timeout.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, + }; + + Ok(Self { + shmem_provider, + observers, + hooks, + itimerval, + phantom: PhantomData, + }) + } +} + +impl UsesObservers for GenericInProcessForkExecutorInner +where + HT: ExecutorHooksTuple, + OT: ObserversTuple, + S: State, + SP: ShMemProvider, + EM: UsesState, + Z: UsesState, +{ + type Observers = OT; +} + +impl HasObservers for GenericInProcessForkExecutorInner +where + HT: ExecutorHooksTuple, + S: State, + OT: ObserversTuple, + SP: ShMemProvider, + EM: UsesState, + Z: UsesState, +{ + #[inline] + fn observers(&self) -> &OT { + &self.observers + } + + #[inline] + fn observers_mut(&mut self) -> &mut OT { + &mut self.observers + } +} diff --git a/libafl/src/executors/inprocess_fork/mod.rs b/libafl/src/executors/inprocess_fork/mod.rs index fd0da8867a..2c7e814cf5 100644 --- a/libafl/src/executors/inprocess_fork/mod.rs +++ b/libafl/src/executors/inprocess_fork/mod.rs @@ -1,32 +1,23 @@ //! The `GenericInProcessForkExecutor` to do forking before executing the harness in-processly use core::{ - ffi::c_void, fmt::{self, Debug, Formatter}, - marker::PhantomData, - ptr::{self, addr_of_mut, null_mut, write_volatile}, - sync::atomic::{compiler_fence, Ordering}, time::Duration, }; use libafl_bolts::{ os::unix_signals::{ucontext_t, Signal}, shmem::ShMemProvider, - tuples::{tuple_list, Merge}, + tuples::tuple_list, }; use libc::siginfo_t; -use nix::{ - sys::wait::{waitpid, WaitStatus}, - unistd::{fork, ForkResult, Pid}, -}; +use nix::unistd::{fork, ForkResult}; use super::hooks::ExecutorHooksTuple; use crate::{ events::{EventFirer, EventRestarter}, executors::{ - hooks::inprocess_fork::{ - InChildProcessHooks, InProcessForkExecutorGlobalData, FORK_EXECUTOR_GLOBAL_DATA, - }, - Executor, ExitKind, HasObservers, + hooks::inprocess_fork::InProcessForkExecutorGlobalData, + inprocess_fork::inner::GenericInProcessForkExecutorInner, Executor, ExitKind, HasObservers, }, feedbacks::Feedback, fuzzer::HasObjective, @@ -44,9 +35,8 @@ pub(crate) type ForkHandlerFuncPtr = unsafe fn( data: *mut InProcessForkExecutorGlobalData, ); -#[cfg(all(unix, not(target_os = "linux")))] -use crate::executors::hooks::timer::{setitimer, Itimerval, Timeval, ITIMER_REAL}; - +/// The inner structure of `InProcessForkExecutor`. +pub mod inner; /// A version of `InProcessForkExecutor` with a state accessible from the harness. pub mod stateful; @@ -89,26 +79,6 @@ where } } -/// Inner state of GenericInProcessExecutor-like structures. -pub struct GenericInProcessForkExecutorInner -where - OT: ObserversTuple, - S: UsesInput, - SP: ShMemProvider, - HT: ExecutorHooksTuple, - EM: UsesState, - Z: UsesState, -{ - hooks: (InChildProcessHooks, HT), - shmem_provider: SP, - observers: OT, - #[cfg(target_os = "linux")] - itimerspec: libc::itimerspec, - #[cfg(all(unix, not(target_os = "linux")))] - itimerval: Itimerval, - phantom: PhantomData<(S, EM, Z)>, -} - /// [`GenericInProcessForkExecutor`] is an executor that forks the current process before each execution. pub struct GenericInProcessForkExecutor<'a, H, HT, OT, S, SP, EM, Z> where @@ -124,37 +94,6 @@ where inner: GenericInProcessForkExecutorInner, } -impl Debug for GenericInProcessForkExecutorInner -where - OT: ObserversTuple + Debug, - S: UsesInput, - SP: ShMemProvider, - HT: ExecutorHooksTuple + Debug, - EM: UsesState, - Z: UsesState, -{ - #[cfg(target_os = "linux")] - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_struct("GenericInProcessForkExecutorInner") - .field("hooks", &self.hooks) - .field("observers", &self.observers) - .field("shmem_provider", &self.shmem_provider) - .field("itimerspec", &self.itimerspec) - .finish() - } - - #[cfg(not(target_os = "linux"))] - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - #[cfg(not(target_os = "linux"))] - return f - .debug_struct("GenericInProcessForkExecutorInner") - .field("observers", &self.observers) - .field("shmem_provider", &self.shmem_provider) - .field("itimerval", &self.itimerval) - .finish(); - } -} - impl<'a, H, HT, OT, S, SP, EM, Z> Debug for GenericInProcessForkExecutor<'a, H, HT, OT, S, SP, EM, Z> where @@ -183,18 +122,6 @@ where } } -impl UsesState for GenericInProcessForkExecutorInner -where - OT: ObserversTuple, - S: State, - SP: ShMemProvider, - HT: ExecutorHooksTuple, - EM: UsesState, - Z: UsesState, -{ - type State = S; -} - impl<'a, H, HT, OT, S, SP, EM, Z> UsesState for GenericInProcessForkExecutor<'a, H, HT, OT, S, SP, EM, Z> where @@ -209,100 +136,6 @@ where type State = S; } -impl GenericInProcessForkExecutorInner -where - OT: ObserversTuple + Debug, - S: State + UsesInput, - SP: ShMemProvider, - HT: ExecutorHooksTuple, - EM: EventFirer + EventRestarter, - Z: UsesState, -{ - unsafe fn pre_run_target_child( - &mut self, - fuzzer: &mut Z, - state: &mut as UsesState>::State, - mgr: &mut EM, - input: & as UsesInput>::Input, - ) -> Result<(), Error> { - self.shmem_provider.post_fork(true)?; - - self.enter_target(fuzzer, state, mgr, input); - self.hooks.pre_exec_all(fuzzer, state, mgr, input); - - self.observers - .pre_exec_child_all(state, input) - .expect("Failed to run post_exec on observers"); - - #[cfg(target_os = "linux")] - { - 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)); - - // log::info!("Set timer! {:#?} {timerid:#?}", self.itimerspec); - let _: i32 = libc::timer_settime(timerid, 0, addr_of_mut!(self.itimerspec), null_mut()); - } - #[cfg(not(target_os = "linux"))] - { - setitimer(ITIMER_REAL, &mut self.itimerval, null_mut()); - } - // log::trace!("{v:#?} {}", nix::errno::errno()); - - Ok(()) - } - - unsafe fn post_run_target_child( - &mut self, - fuzzer: &mut Z, - state: &mut as UsesState>::State, - mgr: &mut EM, - input: & as UsesInput>::Input, - ) { - self.observers - .post_exec_child_all(state, input, &ExitKind::Ok) - .expect("Failed to run post_exec on observers"); - - self.hooks.post_exec_all(fuzzer, state, mgr, input); - self.leave_target(fuzzer, state, mgr, input); - - libc::_exit(0); - } - - fn parent(&mut self, child: Pid) -> Result { - // log::trace!("from parent {} child is {}", std::process::id(), child); - self.shmem_provider.post_fork(false)?; - - let res = waitpid(child, None)?; - log::trace!("{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), - } - } -} - impl<'a, EM, H, HT, OT, S, SP, Z> Executor for GenericInProcessForkExecutor<'a, H, HT, OT, S, SP, EM, Z> where @@ -345,133 +178,6 @@ where } } -impl GenericInProcessForkExecutorInner -where - HT: ExecutorHooksTuple, - S: State, - OT: ObserversTuple, - SP: ShMemProvider, - EM: EventFirer + EventRestarter, - Z: UsesState, -{ - #[inline] - /// This function marks the boundary between the fuzzer and the target. - pub fn enter_target( - &mut self, - _fuzzer: &mut Z, - state: &mut ::State, - _event_mgr: &mut EM, - input: &::Input, - ) { - unsafe { - let data = addr_of_mut!(FORK_EXECUTOR_GLOBAL_DATA); - write_volatile( - addr_of_mut!((*data).executor_ptr), - ptr::from_ref(self) as *const c_void, - ); - write_volatile( - addr_of_mut!((*data).current_input_ptr), - ptr::from_ref(input) as *const c_void, - ); - write_volatile( - addr_of_mut!((*data).state_ptr), - ptr::from_mut(state) as *mut c_void, - ); - compiler_fence(Ordering::SeqCst); - } - } - - #[inline] - /// This function marks the boundary between the fuzzer and the target. - pub fn leave_target( - &mut self, - _fuzzer: &mut Z, - _state: &mut ::State, - _event_mgr: &mut EM, - _input: &::Input, - ) { - // do nothing - } - - /// Creates a new [`GenericInProcessForkExecutor`] with custom hooks - #[cfg(target_os = "linux")] - #[allow(clippy::too_many_arguments)] - pub fn with_hooks( - userhooks: HT, - observers: OT, - _fuzzer: &mut Z, - state: &mut S, - _event_mgr: &mut EM, - timeout: Duration, - shmem_provider: SP, - ) -> Result { - let default_hooks = InChildProcessHooks::new::()?; - let mut hooks = tuple_list!(default_hooks).merge(userhooks); - hooks.init_all::(state); - - 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 { - shmem_provider, - observers, - hooks, - itimerspec, - phantom: PhantomData, - }) - } - - /// Creates a new [`GenericInProcessForkExecutor`], non linux - #[cfg(not(target_os = "linux"))] - #[allow(clippy::too_many_arguments)] - pub fn with_hooks( - userhooks: HT, - observers: OT, - _fuzzer: &mut Z, - state: &mut S, - _event_mgr: &mut EM, - timeout: Duration, - shmem_provider: SP, - ) -> Result { - let default_hooks = InChildProcessHooks::new::()?; - let mut hooks = tuple_list!(default_hooks).merge(userhooks); - hooks.init_all::(state); - - let milli_sec = timeout.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, - }; - - Ok(Self { - shmem_provider, - observers, - hooks, - itimerval, - phantom: PhantomData, - }) - } -} - impl<'a, H, HT, OT, S, SP, EM, Z, OF> GenericInProcessForkExecutor<'a, H, HT, OT, S, SP, EM, Z> where H: FnMut(&S::Input) -> ExitKind + ?Sized, @@ -523,18 +229,6 @@ where { } } -impl UsesObservers for GenericInProcessForkExecutorInner -where - HT: ExecutorHooksTuple, - OT: ObserversTuple, - S: State, - SP: ShMemProvider, - EM: UsesState, - Z: UsesState, -{ - type Observers = OT; -} - impl<'a, H, HT, OT, S, SP, EM, Z> UsesObservers for GenericInProcessForkExecutor<'a, H, HT, OT, S, SP, EM, Z> where @@ -549,26 +243,6 @@ where type Observers = OT; } -impl HasObservers for GenericInProcessForkExecutorInner -where - HT: ExecutorHooksTuple, - S: State, - OT: ObserversTuple, - SP: ShMemProvider, - EM: UsesState, - Z: UsesState, -{ - #[inline] - fn observers(&self) -> &OT { - &self.observers - } - - #[inline] - fn observers_mut(&mut self) -> &mut OT { - &mut self.observers - } -} - impl<'a, H, HT, OT, S, SP, EM, Z> HasObservers for GenericInProcessForkExecutor<'a, H, HT, OT, S, SP, EM, Z> where