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
This commit is contained in:
parent
55a300d508
commit
3b3e2f6efa
414
libafl/src/executors/inprocess/inner.rs
Normal file
414
libafl/src/executors/inprocess/inner.rs
Normal file
@ -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<HT, OT, S>
|
||||
where
|
||||
HT: ExecutorHooksTuple,
|
||||
OT: ObserversTuple<S>,
|
||||
S: State,
|
||||
{
|
||||
/// The observers, observing each run
|
||||
pub(super) observers: OT,
|
||||
// Crash and timeout hah
|
||||
pub(super) hooks: (InProcessHooks, HT),
|
||||
phantom: PhantomData<S>,
|
||||
}
|
||||
|
||||
impl<HT, OT, S> Debug for GenericInProcessExecutorInner<HT, OT, S>
|
||||
where
|
||||
HT: ExecutorHooksTuple,
|
||||
OT: ObserversTuple<S> + Debug,
|
||||
S: State,
|
||||
{
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("GenericInProcessExecutorState")
|
||||
.field("observers", &self.observers)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
impl<HT, OT, S> UsesState for GenericInProcessExecutorInner<HT, OT, S>
|
||||
where
|
||||
HT: ExecutorHooksTuple,
|
||||
OT: ObserversTuple<S>,
|
||||
S: State,
|
||||
{
|
||||
type State = S;
|
||||
}
|
||||
|
||||
impl<HT, OT, S> UsesObservers for GenericInProcessExecutorInner<HT, OT, S>
|
||||
where
|
||||
HT: ExecutorHooksTuple,
|
||||
OT: ObserversTuple<S>,
|
||||
S: State,
|
||||
{
|
||||
type Observers = OT;
|
||||
}
|
||||
|
||||
impl<HT, OT, S> HasObservers for GenericInProcessExecutorInner<HT, OT, S>
|
||||
where
|
||||
HT: ExecutorHooksTuple,
|
||||
OT: ObserversTuple<S>,
|
||||
S: State,
|
||||
{
|
||||
#[inline]
|
||||
fn observers(&self) -> &OT {
|
||||
&self.observers
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn observers_mut(&mut self) -> &mut OT {
|
||||
&mut self.observers
|
||||
}
|
||||
}
|
||||
|
||||
impl<HT, OT, S> GenericInProcessExecutorInner<HT, OT, S>
|
||||
where
|
||||
HT: ExecutorHooksTuple,
|
||||
OT: ObserversTuple<S>,
|
||||
S: State,
|
||||
{
|
||||
/// This function marks the boundary between the fuzzer and the target
|
||||
#[inline]
|
||||
pub fn enter_target<EM, Z>(
|
||||
&mut self,
|
||||
fuzzer: &mut Z,
|
||||
state: &mut <Self as UsesState>::State,
|
||||
mgr: &mut EM,
|
||||
input: &<Self as UsesInput>::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<EM, Z>(
|
||||
&mut self,
|
||||
_fuzzer: &mut Z,
|
||||
_state: &mut <Self as UsesState>::State,
|
||||
_mgr: &mut EM,
|
||||
_input: &<Self as UsesInput>::Input,
|
||||
) {
|
||||
unsafe {
|
||||
let data = addr_of_mut!(GLOBAL_STATE);
|
||||
|
||||
write_volatile(addr_of_mut!((*data).current_input_ptr), null());
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<OT, S> GenericInProcessExecutorInner<(), OT, S>
|
||||
where
|
||||
OT: ObserversTuple<S>,
|
||||
S: HasExecutions + HasSolutions + HasCorpus + State,
|
||||
{
|
||||
/// Create a new in mem executor with the default timeout (5 sec)
|
||||
pub fn new<E, EM, OF, Z>(
|
||||
observers: OT,
|
||||
fuzzer: &mut Z,
|
||||
state: &mut S,
|
||||
event_mgr: &mut EM,
|
||||
) -> Result<Self, Error>
|
||||
where
|
||||
E: Executor<EM, Z, State = S> + HasObservers + HasInProcessHooks,
|
||||
EM: EventFirer<State = S> + EventRestarter,
|
||||
OF: Feedback<S>,
|
||||
S: State,
|
||||
Z: HasObjective<Objective = OF, State = S>,
|
||||
{
|
||||
Self::with_timeout_generic::<E, EM, OF, Z>(
|
||||
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<E, EM, OF, Z>(
|
||||
observers: OT,
|
||||
fuzzer: &mut Z,
|
||||
state: &mut S,
|
||||
event_mgr: &mut EM,
|
||||
exec_tmout: Duration,
|
||||
) -> Result<Self, Error>
|
||||
where
|
||||
E: Executor<EM, Z, State = S> + HasObservers + HasInProcessHooks,
|
||||
EM: EventFirer<State = S> + EventRestarter,
|
||||
OF: Feedback<S>,
|
||||
S: State,
|
||||
Z: HasObjective<Objective = OF, State = S>,
|
||||
{
|
||||
let mut me = Self::with_timeout_generic::<E, EM, OF, Z>(
|
||||
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<E, EM, OF, Z>(
|
||||
observers: OT,
|
||||
_fuzzer: &mut Z,
|
||||
state: &mut S,
|
||||
_event_mgr: &mut EM,
|
||||
timeout: Duration,
|
||||
) -> Result<Self, Error>
|
||||
where
|
||||
E: Executor<EM, Z, State = S> + HasObservers + HasInProcessHooks,
|
||||
EM: EventFirer<State = S> + EventRestarter,
|
||||
OF: Feedback<S>,
|
||||
S: State,
|
||||
Z: HasObjective<Objective = OF, State = S>,
|
||||
{
|
||||
let default = InProcessHooks::new::<E, EM, OF, Z>(timeout)?;
|
||||
let mut hooks = tuple_list!(default).merge(tuple_list!());
|
||||
hooks.init_all::<Self, S>(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<HT, OT, S> GenericInProcessExecutorInner<HT, OT, S>
|
||||
where
|
||||
HT: ExecutorHooksTuple,
|
||||
OT: ObserversTuple<S>,
|
||||
S: HasExecutions + HasSolutions + HasCorpus + State,
|
||||
{
|
||||
/// Create a new in mem executor with the default timeout (5 sec)
|
||||
pub fn generic<E, EM, OF, Z>(
|
||||
user_hooks: HT,
|
||||
observers: OT,
|
||||
fuzzer: &mut Z,
|
||||
state: &mut S,
|
||||
event_mgr: &mut EM,
|
||||
) -> Result<Self, Error>
|
||||
where
|
||||
E: Executor<EM, Z, State = S> + HasObservers + HasInProcessHooks,
|
||||
EM: EventFirer<State = S> + EventRestarter,
|
||||
OF: Feedback<S>,
|
||||
S: State,
|
||||
Z: HasObjective<Objective = OF, State = S>,
|
||||
{
|
||||
Self::with_timeout_generic::<E, EM, OF, Z>(
|
||||
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<E, EM, OF, Z>(
|
||||
user_hooks: HT,
|
||||
observers: OT,
|
||||
fuzzer: &mut Z,
|
||||
state: &mut S,
|
||||
event_mgr: &mut EM,
|
||||
exec_tmout: Duration,
|
||||
) -> Result<Self, Error>
|
||||
where
|
||||
E: Executor<EM, Z, State = S> + HasObservers + HasInProcessHooks,
|
||||
EM: EventFirer<State = S> + EventRestarter,
|
||||
OF: Feedback<S>,
|
||||
S: State,
|
||||
Z: HasObjective<Objective = OF, State = S>,
|
||||
{
|
||||
let mut me = Self::with_timeout_generic::<E, EM, OF, Z>(
|
||||
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<E, EM, OF, Z>(
|
||||
user_hooks: HT,
|
||||
observers: OT,
|
||||
_fuzzer: &mut Z,
|
||||
state: &mut S,
|
||||
_event_mgr: &mut EM,
|
||||
timeout: Duration,
|
||||
) -> Result<Self, Error>
|
||||
where
|
||||
E: Executor<EM, Z, State = S> + HasObservers + HasInProcessHooks,
|
||||
EM: EventFirer<State = S> + EventRestarter,
|
||||
OF: Feedback<S>,
|
||||
S: State,
|
||||
Z: HasObjective<Objective = OF, State = S>,
|
||||
{
|
||||
let default = InProcessHooks::new::<E, EM, OF, Z>(timeout)?;
|
||||
let mut hooks = tuple_list!(default).merge(user_hooks);
|
||||
hooks.init_all::<Self, S>(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<HT, OT, S> HasInProcessHooks for GenericInProcessExecutorInner<HT, OT, S>
|
||||
where
|
||||
HT: ExecutorHooksTuple,
|
||||
OT: ObserversTuple<S>,
|
||||
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
|
||||
}
|
||||
}
|
@ -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<OT, S> = GenericInProcessExecutor<
|
||||
S,
|
||||
>;
|
||||
|
||||
/// The internal state of `GenericInProcessExecutor`.
|
||||
pub struct GenericInProcessExecutorInner<HT, OT, S>
|
||||
where
|
||||
HT: ExecutorHooksTuple,
|
||||
OT: ObserversTuple<S>,
|
||||
S: State,
|
||||
{
|
||||
/// The observers, observing each run
|
||||
observers: OT,
|
||||
// Crash and timeout hah
|
||||
hooks: (InProcessHooks, HT),
|
||||
phantom: PhantomData<S>,
|
||||
}
|
||||
|
||||
/// The inmem executor simply calls a target function, then returns afterwards.
|
||||
#[allow(dead_code)]
|
||||
pub struct GenericInProcessExecutor<H, HB, HT, OT, S>
|
||||
@ -88,19 +69,6 @@ where
|
||||
phantom: PhantomData<(*const H, HB)>,
|
||||
}
|
||||
|
||||
impl<HT, OT, S> Debug for GenericInProcessExecutorInner<HT, OT, S>
|
||||
where
|
||||
HT: ExecutorHooksTuple,
|
||||
OT: ObserversTuple<S> + Debug,
|
||||
S: State,
|
||||
{
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("GenericInProcessExecutorState")
|
||||
.field("observers", &self.observers)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
impl<H, HB, HT, OT, S> Debug for GenericInProcessExecutor<H, HB, HT, OT, S>
|
||||
where
|
||||
H: FnMut(&S::Input) -> ExitKind + ?Sized,
|
||||
@ -117,15 +85,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<HT, OT, S> UsesState for GenericInProcessExecutorInner<HT, OT, S>
|
||||
where
|
||||
HT: ExecutorHooksTuple,
|
||||
OT: ObserversTuple<S>,
|
||||
S: State,
|
||||
{
|
||||
type State = S;
|
||||
}
|
||||
|
||||
impl<H, HB, HT, OT, S> UsesState for GenericInProcessExecutor<H, HB, HT, OT, S>
|
||||
where
|
||||
H: FnMut(&S::Input) -> ExitKind + ?Sized,
|
||||
@ -137,15 +96,6 @@ where
|
||||
type State = S;
|
||||
}
|
||||
|
||||
impl<HT, OT, S> UsesObservers for GenericInProcessExecutorInner<HT, OT, S>
|
||||
where
|
||||
HT: ExecutorHooksTuple,
|
||||
OT: ObserversTuple<S>,
|
||||
S: State,
|
||||
{
|
||||
type Observers = OT;
|
||||
}
|
||||
|
||||
impl<H, HB, HT, OT, S> UsesObservers for GenericInProcessExecutor<H, HB, HT, OT, S>
|
||||
where
|
||||
H: FnMut(&S::Input) -> ExitKind + ?Sized,
|
||||
@ -186,23 +136,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<HT, OT, S> HasObservers for GenericInProcessExecutorInner<HT, OT, S>
|
||||
where
|
||||
HT: ExecutorHooksTuple,
|
||||
OT: ObserversTuple<S>,
|
||||
S: State,
|
||||
{
|
||||
#[inline]
|
||||
fn observers(&self) -> &OT {
|
||||
&self.observers
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn observers_mut(&mut self) -> &mut OT {
|
||||
&mut self.observers
|
||||
}
|
||||
}
|
||||
|
||||
impl<H, HB, HT, OT, S> HasObservers for GenericInProcessExecutor<H, HB, HT, OT, S>
|
||||
where
|
||||
H: FnMut(&S::Input) -> ExitKind + ?Sized,
|
||||
@ -222,181 +155,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<HT, OT, S> GenericInProcessExecutorInner<HT, OT, S>
|
||||
where
|
||||
HT: ExecutorHooksTuple,
|
||||
OT: ObserversTuple<S>,
|
||||
S: State,
|
||||
{
|
||||
/// This function marks the boundary between the fuzzer and the target
|
||||
#[inline]
|
||||
pub fn enter_target<EM, Z>(
|
||||
&mut self,
|
||||
fuzzer: &mut Z,
|
||||
state: &mut <Self as UsesState>::State,
|
||||
mgr: &mut EM,
|
||||
input: &<Self as UsesInput>::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<EM, Z>(
|
||||
&mut self,
|
||||
_fuzzer: &mut Z,
|
||||
_state: &mut <Self as UsesState>::State,
|
||||
_mgr: &mut EM,
|
||||
_input: &<Self as UsesInput>::Input,
|
||||
) {
|
||||
unsafe {
|
||||
let data = addr_of_mut!(GLOBAL_STATE);
|
||||
|
||||
write_volatile(addr_of_mut!((*data).current_input_ptr), null());
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<OT, S> GenericInProcessExecutorInner<(), OT, S>
|
||||
where
|
||||
OT: ObserversTuple<S>,
|
||||
S: HasExecutions + HasSolutions + HasCorpus + State,
|
||||
{
|
||||
/// Create a new in mem executor with the default timeout (5 sec)
|
||||
pub fn new<E, EM, OF, Z>(
|
||||
observers: OT,
|
||||
fuzzer: &mut Z,
|
||||
state: &mut S,
|
||||
event_mgr: &mut EM,
|
||||
) -> Result<Self, Error>
|
||||
where
|
||||
E: Executor<EM, Z, State = S> + HasObservers + HasInProcessHooks,
|
||||
EM: EventFirer<State = S> + EventRestarter,
|
||||
OF: Feedback<S>,
|
||||
S: State,
|
||||
Z: HasObjective<Objective = OF, State = S>,
|
||||
{
|
||||
Self::with_timeout_generic::<E, EM, OF, Z>(
|
||||
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<E, EM, OF, Z>(
|
||||
observers: OT,
|
||||
fuzzer: &mut Z,
|
||||
state: &mut S,
|
||||
event_mgr: &mut EM,
|
||||
exec_tmout: Duration,
|
||||
) -> Result<Self, Error>
|
||||
where
|
||||
E: Executor<EM, Z, State = S> + HasObservers + HasInProcessHooks,
|
||||
EM: EventFirer<State = S> + EventRestarter,
|
||||
OF: Feedback<S>,
|
||||
S: State,
|
||||
Z: HasObjective<Objective = OF, State = S>,
|
||||
{
|
||||
let mut me = Self::with_timeout_generic::<E, EM, OF, Z>(
|
||||
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<E, EM, OF, Z>(
|
||||
observers: OT,
|
||||
_fuzzer: &mut Z,
|
||||
state: &mut S,
|
||||
_event_mgr: &mut EM,
|
||||
timeout: Duration,
|
||||
) -> Result<Self, Error>
|
||||
where
|
||||
E: Executor<EM, Z, State = S> + HasObservers + HasInProcessHooks,
|
||||
EM: EventFirer<State = S> + EventRestarter,
|
||||
OF: Feedback<S>,
|
||||
S: State,
|
||||
Z: HasObjective<Objective = OF, State = S>,
|
||||
{
|
||||
let default = InProcessHooks::new::<E, EM, OF, Z>(timeout)?;
|
||||
let mut hooks = tuple_list!(default).merge(tuple_list!());
|
||||
hooks.init_all::<Self, S>(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<Self, Error>
|
||||
where
|
||||
Self: Executor<EM, Z, State = S> + HasObservers + HasInProcessHooks,
|
||||
Self: Executor<EM, Z, State = S>,
|
||||
EM: EventFirer<State = S> + EventRestarter,
|
||||
OF: Feedback<S>,
|
||||
S: State,
|
||||
@ -491,130 +249,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<HT, OT, S> GenericInProcessExecutorInner<HT, OT, S>
|
||||
where
|
||||
HT: ExecutorHooksTuple,
|
||||
OT: ObserversTuple<S>,
|
||||
S: HasExecutions + HasSolutions + HasCorpus + State,
|
||||
{
|
||||
/// Create a new in mem executor with the default timeout (5 sec)
|
||||
pub fn generic<E, EM, OF, Z>(
|
||||
user_hooks: HT,
|
||||
observers: OT,
|
||||
fuzzer: &mut Z,
|
||||
state: &mut S,
|
||||
event_mgr: &mut EM,
|
||||
) -> Result<Self, Error>
|
||||
where
|
||||
E: Executor<EM, Z, State = S> + HasObservers + HasInProcessHooks,
|
||||
EM: EventFirer<State = S> + EventRestarter,
|
||||
OF: Feedback<S>,
|
||||
S: State,
|
||||
Z: HasObjective<Objective = OF, State = S>,
|
||||
{
|
||||
Self::with_timeout_generic::<E, EM, OF, Z>(
|
||||
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<E, EM, OF, Z>(
|
||||
user_hooks: HT,
|
||||
observers: OT,
|
||||
fuzzer: &mut Z,
|
||||
state: &mut S,
|
||||
event_mgr: &mut EM,
|
||||
exec_tmout: Duration,
|
||||
) -> Result<Self, Error>
|
||||
where
|
||||
E: Executor<EM, Z, State = S> + HasObservers + HasInProcessHooks,
|
||||
EM: EventFirer<State = S> + EventRestarter,
|
||||
OF: Feedback<S>,
|
||||
S: State,
|
||||
Z: HasObjective<Objective = OF, State = S>,
|
||||
{
|
||||
let mut me = Self::with_timeout_generic::<E, EM, OF, Z>(
|
||||
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<E, EM, OF, Z>(
|
||||
user_hooks: HT,
|
||||
observers: OT,
|
||||
_fuzzer: &mut Z,
|
||||
state: &mut S,
|
||||
_event_mgr: &mut EM,
|
||||
timeout: Duration,
|
||||
) -> Result<Self, Error>
|
||||
where
|
||||
E: Executor<EM, Z, State = S> + HasObservers + HasInProcessHooks,
|
||||
EM: EventFirer<State = S> + EventRestarter,
|
||||
OF: Feedback<S>,
|
||||
S: State,
|
||||
Z: HasObjective<Objective = OF, State = S>,
|
||||
{
|
||||
let default = InProcessHooks::new::<E, EM, OF, Z>(timeout)?;
|
||||
let mut hooks = tuple_list!(default).merge(user_hooks);
|
||||
hooks.init_all::<Self, S>(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<H, HB, HT, OT, S> GenericInProcessExecutor<H, HB, HT, OT, S>
|
||||
where
|
||||
H: FnMut(&S::Input) -> ExitKind + ?Sized,
|
||||
@ -747,25 +381,6 @@ pub trait HasInProcessHooks {
|
||||
fn inprocess_hooks_mut(&mut self) -> &mut InProcessHooks;
|
||||
}
|
||||
|
||||
impl<HT, OT, S> HasInProcessHooks for GenericInProcessExecutorInner<HT, OT, S>
|
||||
where
|
||||
HT: ExecutorHooksTuple,
|
||||
OT: ObserversTuple<S>,
|
||||
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<H, HB, HT, OT, S> HasInProcessHooks for GenericInProcessExecutor<H, HB, HT, OT, S>
|
||||
where
|
||||
H: FnMut(&<S as UsesInput>::Input) -> ExitKind + ?Sized,
|
||||
|
351
libafl/src/executors/inprocess_fork/inner.rs
Normal file
351
libafl/src/executors/inprocess_fork/inner.rs
Normal file
@ -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<HT, OT, S, SP, EM, Z>
|
||||
where
|
||||
OT: ObserversTuple<S>,
|
||||
S: UsesInput,
|
||||
SP: ShMemProvider,
|
||||
HT: ExecutorHooksTuple,
|
||||
EM: UsesState<State = S>,
|
||||
Z: UsesState<State = S>,
|
||||
{
|
||||
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<HT, OT, S, SP, EM, Z> Debug for GenericInProcessForkExecutorInner<HT, OT, S, SP, EM, Z>
|
||||
where
|
||||
OT: ObserversTuple<S> + Debug,
|
||||
S: UsesInput,
|
||||
SP: ShMemProvider,
|
||||
HT: ExecutorHooksTuple + Debug,
|
||||
EM: UsesState<State = S>,
|
||||
Z: UsesState<State = S>,
|
||||
{
|
||||
#[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<HT, OT, S, SP, EM, Z> UsesState for GenericInProcessForkExecutorInner<HT, OT, S, SP, EM, Z>
|
||||
where
|
||||
OT: ObserversTuple<S>,
|
||||
S: State,
|
||||
SP: ShMemProvider,
|
||||
HT: ExecutorHooksTuple,
|
||||
EM: UsesState<State = S>,
|
||||
Z: UsesState<State = S>,
|
||||
{
|
||||
type State = S;
|
||||
}
|
||||
|
||||
impl<EM, HT, OT, S, SP, Z> GenericInProcessForkExecutorInner<HT, OT, S, SP, EM, Z>
|
||||
where
|
||||
OT: ObserversTuple<S> + Debug,
|
||||
S: State + UsesInput,
|
||||
SP: ShMemProvider,
|
||||
HT: ExecutorHooksTuple,
|
||||
EM: EventFirer<State = S> + EventRestarter<State = S>,
|
||||
Z: UsesState<State = S>,
|
||||
{
|
||||
pub(super) unsafe fn pre_run_target_child(
|
||||
&mut self,
|
||||
fuzzer: &mut Z,
|
||||
state: &mut <GenericInProcessForkExecutorInner<HT, OT, S, SP, EM, Z> as UsesState>::State,
|
||||
mgr: &mut EM,
|
||||
input: &<GenericInProcessForkExecutorInner<HT, OT, S, SP, EM, Z> 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 <GenericInProcessForkExecutorInner<HT, OT, S, SP, EM, Z> as UsesState>::State,
|
||||
mgr: &mut EM,
|
||||
input: &<GenericInProcessForkExecutorInner<HT, OT, S, SP, EM, Z> 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<ExitKind, Error> {
|
||||
// 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<HT, OT, S, SP, EM, Z> GenericInProcessForkExecutorInner<HT, OT, S, SP, EM, Z>
|
||||
where
|
||||
HT: ExecutorHooksTuple,
|
||||
S: State,
|
||||
OT: ObserversTuple<S>,
|
||||
SP: ShMemProvider,
|
||||
EM: EventFirer<State = S> + EventRestarter<State = S>,
|
||||
Z: UsesState<State = S>,
|
||||
{
|
||||
#[inline]
|
||||
/// This function marks the boundary between the fuzzer and the target.
|
||||
pub fn enter_target(
|
||||
&mut self,
|
||||
_fuzzer: &mut Z,
|
||||
state: &mut <Self as UsesState>::State,
|
||||
_event_mgr: &mut EM,
|
||||
input: &<Self as UsesInput>::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 <Self as UsesState>::State,
|
||||
_event_mgr: &mut EM,
|
||||
_input: &<Self as UsesInput>::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<Self, Error> {
|
||||
let default_hooks = InChildProcessHooks::new::<Self>()?;
|
||||
let mut hooks = tuple_list!(default_hooks).merge(userhooks);
|
||||
hooks.init_all::<Self, S>(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<Self, Error> {
|
||||
let default_hooks = InChildProcessHooks::new::<Self>()?;
|
||||
let mut hooks = tuple_list!(default_hooks).merge(userhooks);
|
||||
hooks.init_all::<Self, S>(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<HT, OT, S, SP, EM, Z> UsesObservers for GenericInProcessForkExecutorInner<HT, OT, S, SP, EM, Z>
|
||||
where
|
||||
HT: ExecutorHooksTuple,
|
||||
OT: ObserversTuple<S>,
|
||||
S: State,
|
||||
SP: ShMemProvider,
|
||||
EM: UsesState<State = S>,
|
||||
Z: UsesState<State = S>,
|
||||
{
|
||||
type Observers = OT;
|
||||
}
|
||||
|
||||
impl<HT, OT, S, SP, EM, Z> HasObservers for GenericInProcessForkExecutorInner<HT, OT, S, SP, EM, Z>
|
||||
where
|
||||
HT: ExecutorHooksTuple,
|
||||
S: State,
|
||||
OT: ObserversTuple<S>,
|
||||
SP: ShMemProvider,
|
||||
EM: UsesState<State = S>,
|
||||
Z: UsesState<State = S>,
|
||||
{
|
||||
#[inline]
|
||||
fn observers(&self) -> &OT {
|
||||
&self.observers
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn observers_mut(&mut self) -> &mut OT {
|
||||
&mut self.observers
|
||||
}
|
||||
}
|
@ -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<HT, OT, S, SP, EM, Z>
|
||||
where
|
||||
OT: ObserversTuple<S>,
|
||||
S: UsesInput,
|
||||
SP: ShMemProvider,
|
||||
HT: ExecutorHooksTuple,
|
||||
EM: UsesState<State = S>,
|
||||
Z: UsesState<State = S>,
|
||||
{
|
||||
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<HT, OT, S, SP, EM, Z>,
|
||||
}
|
||||
|
||||
impl<HT, OT, S, SP, EM, Z> Debug for GenericInProcessForkExecutorInner<HT, OT, S, SP, EM, Z>
|
||||
where
|
||||
OT: ObserversTuple<S> + Debug,
|
||||
S: UsesInput,
|
||||
SP: ShMemProvider,
|
||||
HT: ExecutorHooksTuple + Debug,
|
||||
EM: UsesState<State = S>,
|
||||
Z: UsesState<State = S>,
|
||||
{
|
||||
#[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<HT, OT, S, SP, EM, Z> UsesState for GenericInProcessForkExecutorInner<HT, OT, S, SP, EM, Z>
|
||||
where
|
||||
OT: ObserversTuple<S>,
|
||||
S: State,
|
||||
SP: ShMemProvider,
|
||||
HT: ExecutorHooksTuple,
|
||||
EM: UsesState<State = S>,
|
||||
Z: UsesState<State = S>,
|
||||
{
|
||||
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<EM, HT, OT, S, SP, Z> GenericInProcessForkExecutorInner<HT, OT, S, SP, EM, Z>
|
||||
where
|
||||
OT: ObserversTuple<S> + Debug,
|
||||
S: State + UsesInput,
|
||||
SP: ShMemProvider,
|
||||
HT: ExecutorHooksTuple,
|
||||
EM: EventFirer<State = S> + EventRestarter<State = S>,
|
||||
Z: UsesState<State = S>,
|
||||
{
|
||||
unsafe fn pre_run_target_child(
|
||||
&mut self,
|
||||
fuzzer: &mut Z,
|
||||
state: &mut <GenericInProcessForkExecutorInner<HT, OT, S, SP, EM, Z> as UsesState>::State,
|
||||
mgr: &mut EM,
|
||||
input: &<GenericInProcessForkExecutorInner<HT, OT, S, SP, EM, Z> 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 <GenericInProcessForkExecutorInner<HT, OT, S, SP, EM, Z> as UsesState>::State,
|
||||
mgr: &mut EM,
|
||||
input: &<GenericInProcessForkExecutorInner<HT, OT, S, SP, EM, Z> 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<ExitKind, Error> {
|
||||
// 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<EM, Z>
|
||||
for GenericInProcessForkExecutor<'a, H, HT, OT, S, SP, EM, Z>
|
||||
where
|
||||
@ -345,133 +178,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<HT, OT, S, SP, EM, Z> GenericInProcessForkExecutorInner<HT, OT, S, SP, EM, Z>
|
||||
where
|
||||
HT: ExecutorHooksTuple,
|
||||
S: State,
|
||||
OT: ObserversTuple<S>,
|
||||
SP: ShMemProvider,
|
||||
EM: EventFirer<State = S> + EventRestarter<State = S>,
|
||||
Z: UsesState<State = S>,
|
||||
{
|
||||
#[inline]
|
||||
/// This function marks the boundary between the fuzzer and the target.
|
||||
pub fn enter_target(
|
||||
&mut self,
|
||||
_fuzzer: &mut Z,
|
||||
state: &mut <Self as UsesState>::State,
|
||||
_event_mgr: &mut EM,
|
||||
input: &<Self as UsesInput>::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 <Self as UsesState>::State,
|
||||
_event_mgr: &mut EM,
|
||||
_input: &<Self as UsesInput>::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<Self, Error> {
|
||||
let default_hooks = InChildProcessHooks::new::<Self>()?;
|
||||
let mut hooks = tuple_list!(default_hooks).merge(userhooks);
|
||||
hooks.init_all::<Self, S>(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<Self, Error> {
|
||||
let default_hooks = InChildProcessHooks::new::<Self>()?;
|
||||
let mut hooks = tuple_list!(default_hooks).merge(userhooks);
|
||||
hooks.init_all::<Self, S>(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<HT, OT, S, SP, EM, Z> UsesObservers for GenericInProcessForkExecutorInner<HT, OT, S, SP, EM, Z>
|
||||
where
|
||||
HT: ExecutorHooksTuple,
|
||||
OT: ObserversTuple<S>,
|
||||
S: State,
|
||||
SP: ShMemProvider,
|
||||
EM: UsesState<State = S>,
|
||||
Z: UsesState<State = S>,
|
||||
{
|
||||
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<HT, OT, S, SP, EM, Z> HasObservers for GenericInProcessForkExecutorInner<HT, OT, S, SP, EM, Z>
|
||||
where
|
||||
HT: ExecutorHooksTuple,
|
||||
S: State,
|
||||
OT: ObserversTuple<S>,
|
||||
SP: ShMemProvider,
|
||||
EM: UsesState<State = S>,
|
||||
Z: UsesState<State = S>,
|
||||
{
|
||||
#[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
|
||||
|
Loading…
x
Reference in New Issue
Block a user