Feature: Make executors and feedbacks easier to use outside of the fuzzing loop (extends #2511) (#2637)
* feat(libafl_core): make executors and feedbacks more cleanly usable outside of LibAFLs Fuzzer loop * cargo +nightly fmt * updated type constraints * reformatted and final type constraint fixes * made unicode extraction stage useful separately * fix libafl_cc error message * fix state type constraint to be constrained on the method * removed unnecessary observer constraint * renamed unused variables * fix unnecessary error wrapping in helper functions * converted unicode conversion stage into associated function and fixed nautilus changes * more update * Remove extra I * more fmt * bounds? * less bounds * more less bounds * different trait bounds again * more less generics * fix unicode * fix list * remove unneeded bound --------- Co-authored-by: Lukas Dresel <Lukas-Dresel@users.noreply.github.com> Co-authored-by: Toka <tokazerkje@outlook.com>
This commit is contained in:
parent
0f744a3abb
commit
6eb2dafd34
@ -1,6 +1,9 @@
|
||||
#[cfg(windows)]
|
||||
use std::ptr::write_volatile;
|
||||
use std::{path::PathBuf, ptr::write};
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
ptr::{addr_of, addr_of_mut, write},
|
||||
};
|
||||
|
||||
#[cfg(feature = "tui")]
|
||||
use libafl::monitors::tui::TuiMonitor;
|
||||
@ -24,7 +27,8 @@ use libafl_bolts::{rands::StdRand, tuples::tuple_list, AsSlice};
|
||||
|
||||
/// Coverage map with explicit assignments due to the lack of instrumentation
|
||||
static mut SIGNALS: [u8; 64] = [0; 64];
|
||||
static mut SIGNALS_PTR: *mut u8 = unsafe { SIGNALS.as_mut_ptr() };
|
||||
static mut SIGNALS_PTR: *mut u8 = addr_of_mut!(SIGNALS).cast();
|
||||
static mut SIGNALS_LEN: usize = unsafe { (*addr_of!(SIGNALS)).len() };
|
||||
|
||||
/// Assign a signal to the signals map
|
||||
fn signals_set(idx: usize) {
|
||||
@ -56,7 +60,7 @@ pub fn main() {
|
||||
};
|
||||
|
||||
// Create an observation channel using the signals map
|
||||
let observer = unsafe { StdMapObserver::from_mut_ptr("signals", SIGNALS_PTR, SIGNALS.len()) };
|
||||
let observer = unsafe { StdMapObserver::from_mut_ptr("signals", SIGNALS_PTR, SIGNALS_LEN) };
|
||||
|
||||
// Feedback to rate the interestingness of an input
|
||||
let mut feedback = MaxMapFeedback::new(&observer);
|
||||
|
@ -214,21 +214,13 @@ where
|
||||
|
||||
// this only works on unix because of the reliance on checking the process signal for detecting OOM
|
||||
#[cfg(all(feature = "std", unix))]
|
||||
impl<EM, OT, S, T, Z> Executor<EM, Z> for CommandExecutor<OT, S, T>
|
||||
impl<I, OT, S, T> CommandExecutor<OT, S, T>
|
||||
where
|
||||
EM: UsesState<State = S>,
|
||||
S: State + HasExecutions,
|
||||
T: CommandConfigurator<S::Input> + Debug,
|
||||
OT: Debug + MatchName + ObserversTuple<S::Input, S>,
|
||||
Z: UsesState<State = S>,
|
||||
S: State + HasExecutions + UsesInput<Input = I>,
|
||||
T: CommandConfigurator<I> + Debug,
|
||||
OT: Debug + ObserversTuple<I, S>,
|
||||
{
|
||||
fn run_target(
|
||||
&mut self,
|
||||
_fuzzer: &mut Z,
|
||||
state: &mut Self::State,
|
||||
_mgr: &mut EM,
|
||||
input: &Self::Input,
|
||||
) -> Result<ExitKind, Error> {
|
||||
fn execute_input_with_command(&mut self, state: &mut S, input: &I) -> Result<ExitKind, Error> {
|
||||
use std::os::unix::prelude::ExitStatusExt;
|
||||
|
||||
use wait_timeout::ChildExt;
|
||||
@ -288,6 +280,27 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "std", unix))]
|
||||
impl<EM, OT, S, T, Z> Executor<EM, Z> for CommandExecutor<OT, S, T>
|
||||
where
|
||||
EM: UsesState<State = S>,
|
||||
S: State + HasExecutions + UsesInput,
|
||||
T: CommandConfigurator<S::Input> + Debug,
|
||||
OT: Debug + MatchName + ObserversTuple<S::Input, S>,
|
||||
Z: UsesState<State = S>,
|
||||
{
|
||||
fn run_target(
|
||||
&mut self,
|
||||
_fuzzer: &mut Z,
|
||||
state: &mut Self::State,
|
||||
_mgr: &mut EM,
|
||||
input: &Self::Input,
|
||||
) -> Result<ExitKind, Error> {
|
||||
self.execute_input_with_command(state, input)
|
||||
}
|
||||
}
|
||||
|
||||
// this only works on unix because of the reliance on checking the process signal for detecting OOM
|
||||
impl<OT, S, T> HasTimeout for CommandExecutor<OT, S, T>
|
||||
where
|
||||
S: HasCorpus,
|
||||
|
@ -644,6 +644,7 @@ where
|
||||
OT: ObserversTuple<S::Input, S>,
|
||||
S: UsesInput,
|
||||
SP: ShMemProvider,
|
||||
TC: TargetBytesConverter,
|
||||
{
|
||||
/// The `target` binary that's going to run.
|
||||
pub fn target(&self) -> &OsString {
|
||||
@ -674,6 +675,112 @@ where
|
||||
pub fn coverage_map_size(&self) -> Option<usize> {
|
||||
self.map_size
|
||||
}
|
||||
|
||||
/// Execute input and increase the execution counter.
|
||||
#[inline]
|
||||
fn execute_input(&mut self, state: &mut S, input: &TC::Input) -> Result<ExitKind, Error>
|
||||
where
|
||||
S: HasExecutions,
|
||||
{
|
||||
*state.executions_mut() += 1;
|
||||
|
||||
self.execute_input_uncounted(input)
|
||||
}
|
||||
|
||||
/// Execute input, but side-step the execution counter.
|
||||
#[inline]
|
||||
fn execute_input_uncounted(&mut self, input: &TC::Input) -> Result<ExitKind, Error> {
|
||||
let mut exit_kind = ExitKind::Ok;
|
||||
|
||||
let last_run_timed_out = self.forkserver.last_run_timed_out_raw();
|
||||
|
||||
let mut input_bytes = self.target_bytes_converter.to_target_bytes(input);
|
||||
let mut input_size = input_bytes.as_slice().len();
|
||||
if input_size > self.max_input_size {
|
||||
// Truncate like AFL++ does
|
||||
input_size = self.max_input_size;
|
||||
} else if input_size < self.min_input_size {
|
||||
// Extend like AFL++ does
|
||||
input_size = self.min_input_size;
|
||||
let mut input_bytes_copy = Vec::with_capacity(input_size);
|
||||
input_bytes_copy
|
||||
.as_slice_mut()
|
||||
.copy_from_slice(input_bytes.as_slice());
|
||||
input_bytes = OwnedSlice::from(input_bytes_copy);
|
||||
}
|
||||
let input_size_in_bytes = input_size.to_ne_bytes();
|
||||
if self.uses_shmem_testcase {
|
||||
debug_assert!(
|
||||
self.map.is_some(),
|
||||
"The uses_shmem_testcase() bool can only exist when a map is set"
|
||||
);
|
||||
// # Safety
|
||||
// Struct can never be created when uses_shmem_testcase is true and map is none.
|
||||
let map = unsafe { self.map.as_mut().unwrap_unchecked() };
|
||||
// The first four bytes declares the size of the shmem.
|
||||
map.as_slice_mut()[..SHMEM_FUZZ_HDR_SIZE]
|
||||
.copy_from_slice(&input_size_in_bytes[..SHMEM_FUZZ_HDR_SIZE]);
|
||||
map.as_slice_mut()[SHMEM_FUZZ_HDR_SIZE..(SHMEM_FUZZ_HDR_SIZE + input_size)]
|
||||
.copy_from_slice(&input_bytes.as_slice()[..input_size]);
|
||||
} else {
|
||||
self.input_file
|
||||
.write_buf(&input_bytes.as_slice()[..input_size])?;
|
||||
}
|
||||
|
||||
self.forkserver.set_last_run_timed_out(false);
|
||||
if let Err(err) = self.forkserver.write_ctl(last_run_timed_out) {
|
||||
return Err(Error::unknown(format!(
|
||||
"Unable to request new process from fork server (OOM?): {err:?}"
|
||||
)));
|
||||
}
|
||||
|
||||
let pid = self.forkserver.read_st().map_err(|err| {
|
||||
Error::unknown(format!(
|
||||
"Unable to request new process from fork server (OOM?): {err:?}"
|
||||
))
|
||||
})?;
|
||||
|
||||
if pid <= 0 {
|
||||
return Err(Error::unknown(
|
||||
"Fork server is misbehaving (OOM?)".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
self.forkserver.set_child_pid(Pid::from_raw(pid));
|
||||
|
||||
if let Some(status) = self.forkserver.read_st_timed(&self.timeout)? {
|
||||
self.forkserver.set_status(status);
|
||||
let exitcode_is_crash = if let Some(crash_exitcode) = self.crash_exitcode {
|
||||
(libc::WEXITSTATUS(self.forkserver().status()) as i8) == crash_exitcode
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if libc::WIFSIGNALED(self.forkserver().status()) || exitcode_is_crash {
|
||||
exit_kind = ExitKind::Crash;
|
||||
#[cfg(feature = "regex")]
|
||||
if let Some(asan_observer) = self.observers.get_mut(&self.asan_obs) {
|
||||
asan_observer.parse_asan_output_from_asan_log_file(pid)?;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.forkserver.set_last_run_timed_out(true);
|
||||
|
||||
// We need to kill the child in case he has timed out, or we can't get the correct pid in the next call to self.executor.forkserver_mut().read_st()?
|
||||
let _ = kill(self.forkserver().child_pid(), self.forkserver.kill_signal);
|
||||
if let Err(err) = self.forkserver.read_st() {
|
||||
return Err(Error::unknown(format!(
|
||||
"Could not kill timed-out child: {err:?}"
|
||||
)));
|
||||
}
|
||||
exit_kind = ExitKind::Timeout;
|
||||
}
|
||||
|
||||
if !libc::WIFSTOPPED(self.forkserver().status()) {
|
||||
self.forkserver.reset_child_pid();
|
||||
}
|
||||
|
||||
Ok(exit_kind)
|
||||
}
|
||||
}
|
||||
|
||||
/// The builder for `ForkserverExecutor`
|
||||
@ -1453,98 +1560,7 @@ where
|
||||
_mgr: &mut EM,
|
||||
input: &Self::Input,
|
||||
) -> Result<ExitKind, Error> {
|
||||
*state.executions_mut() += 1;
|
||||
|
||||
let mut exit_kind = ExitKind::Ok;
|
||||
|
||||
let last_run_timed_out = self.forkserver.last_run_timed_out_raw();
|
||||
|
||||
let mut input_bytes = self.target_bytes_converter.to_target_bytes(input);
|
||||
let mut input_size = input_bytes.as_slice().len();
|
||||
if input_size > self.max_input_size {
|
||||
// Truncate like AFL++ does
|
||||
input_size = self.max_input_size;
|
||||
} else if input_size < self.min_input_size {
|
||||
// Extend like AFL++ does
|
||||
input_size = self.min_input_size;
|
||||
let mut input_bytes_copy = Vec::with_capacity(input_size);
|
||||
input_bytes_copy
|
||||
.as_slice_mut()
|
||||
.copy_from_slice(input_bytes.as_slice());
|
||||
input_bytes = OwnedSlice::from(input_bytes_copy);
|
||||
}
|
||||
let input_size_in_bytes = input_size.to_ne_bytes();
|
||||
if self.uses_shmem_testcase {
|
||||
debug_assert!(
|
||||
self.map.is_some(),
|
||||
"The uses_shmem_testcase() bool can only exist when a map is set"
|
||||
);
|
||||
// # Safety
|
||||
// Struct can never be created when uses_shmem_testcase is true and map is none.
|
||||
let map = unsafe { self.map.as_mut().unwrap_unchecked() };
|
||||
// The first four bytes declares the size of the shmem.
|
||||
map.as_slice_mut()[..SHMEM_FUZZ_HDR_SIZE]
|
||||
.copy_from_slice(&input_size_in_bytes[..SHMEM_FUZZ_HDR_SIZE]);
|
||||
map.as_slice_mut()[SHMEM_FUZZ_HDR_SIZE..(SHMEM_FUZZ_HDR_SIZE + input_size)]
|
||||
.copy_from_slice(&input_bytes.as_slice()[..input_size]);
|
||||
} else {
|
||||
self.input_file
|
||||
.write_buf(&input_bytes.as_slice()[..input_size])?;
|
||||
}
|
||||
|
||||
self.forkserver.set_last_run_timed_out(false);
|
||||
if let Err(err) = self.forkserver.write_ctl(last_run_timed_out) {
|
||||
return Err(Error::unknown(format!(
|
||||
"Unable to request new process from fork server (OOM?): {err:?}"
|
||||
)));
|
||||
}
|
||||
|
||||
let pid = self.forkserver.read_st().map_err(|err| {
|
||||
Error::unknown(format!(
|
||||
"Unable to request new process from fork server (OOM?): {err:?}"
|
||||
))
|
||||
})?;
|
||||
|
||||
if pid <= 0 {
|
||||
return Err(Error::unknown(
|
||||
"Fork server is misbehaving (OOM?)".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
self.forkserver.set_child_pid(Pid::from_raw(pid));
|
||||
|
||||
if let Some(status) = self.forkserver.read_st_timed(&self.timeout)? {
|
||||
self.forkserver.set_status(status);
|
||||
let exitcode_is_crash = if let Some(crash_exitcode) = self.crash_exitcode {
|
||||
(libc::WEXITSTATUS(self.forkserver().status()) as i8) == crash_exitcode
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if libc::WIFSIGNALED(self.forkserver().status()) || exitcode_is_crash {
|
||||
exit_kind = ExitKind::Crash;
|
||||
#[cfg(feature = "regex")]
|
||||
if let Some(asan_observer) = self.observers.get_mut(&self.asan_obs) {
|
||||
asan_observer.parse_asan_output_from_asan_log_file(pid)?;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.forkserver.set_last_run_timed_out(true);
|
||||
|
||||
// We need to kill the child in case he has timed out, or we can't get the correct pid in the next call to self.executor.forkserver_mut().read_st()?
|
||||
let _ = kill(self.forkserver().child_pid(), self.forkserver.kill_signal);
|
||||
if let Err(err) = self.forkserver.read_st() {
|
||||
return Err(Error::unknown(format!(
|
||||
"Could not kill timed-out child: {err:?}"
|
||||
)));
|
||||
}
|
||||
exit_kind = ExitKind::Timeout;
|
||||
}
|
||||
|
||||
if !libc::WIFSTOPPED(self.forkserver().status()) {
|
||||
self.forkserver.reset_child_pid();
|
||||
}
|
||||
|
||||
Ok(exit_kind)
|
||||
self.execute_input(state, input)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,6 +38,21 @@ impl<'map> ConcolicFeedback<'map> {
|
||||
observer_handle: observer.handle(),
|
||||
}
|
||||
}
|
||||
|
||||
fn add_concolic_feedback_to_metadata<I, OT>(
|
||||
&mut self,
|
||||
observers: &OT,
|
||||
testcase: &mut Testcase<I>,
|
||||
) where
|
||||
OT: MatchName,
|
||||
{
|
||||
if let Some(metadata) = observers
|
||||
.get(&self.observer_handle)
|
||||
.map(ConcolicObserver::create_metadata_from_current_map)
|
||||
{
|
||||
testcase.metadata_map_mut().insert(metadata);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Named for ConcolicFeedback<'_> {
|
||||
@ -64,12 +79,7 @@ where
|
||||
observers: &OT,
|
||||
testcase: &mut Testcase<I>,
|
||||
) -> Result<(), Error> {
|
||||
if let Some(metadata) = observers
|
||||
.get(&self.observer_handle)
|
||||
.map(ConcolicObserver::create_metadata_from_current_map)
|
||||
{
|
||||
testcase.metadata_map_mut().insert(metadata);
|
||||
}
|
||||
self.add_concolic_feedback_to_metadata(observers, testcase);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ use crate::{
|
||||
};
|
||||
|
||||
/// The metadata to remember past observed value
|
||||
#[derive(Default, Serialize, Deserialize, Clone, Debug)]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(bound = "T: Eq + Hash + for<'a> Deserialize<'a> + Serialize")]
|
||||
pub struct ListFeedbackMetadata<T> {
|
||||
/// Contains the information of past observed set of values.
|
||||
@ -42,6 +42,13 @@ impl<T> ListFeedbackMetadata<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for ListFeedbackMetadata<T> {
|
||||
#[must_use]
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> HasRefCnt for ListFeedbackMetadata<T> {
|
||||
fn refcnt(&self) -> isize {
|
||||
self.tcref
|
||||
@ -60,36 +67,23 @@ pub struct ListFeedback<T> {
|
||||
}
|
||||
|
||||
libafl_bolts::impl_serdeany!(
|
||||
ListFeedbackMetadata<T: Debug + Default + Copy + 'static + Serialize + DeserializeOwned + Eq + Hash>,
|
||||
ListFeedbackMetadata<T: Debug + 'static + Serialize + DeserializeOwned + Eq + Hash>,
|
||||
<u8>,<u16>,<u32>,<u64>,<i8>,<i16>,<i32>,<i64>,<bool>,<char>,<usize>
|
||||
);
|
||||
|
||||
impl<S, T> StateInitializer<S> for ListFeedback<T>
|
||||
impl<T> ListFeedback<T>
|
||||
where
|
||||
S: HasNamedMetadata,
|
||||
T: Debug + Serialize + Hash + Eq + DeserializeOwned + Default + Copy + 'static,
|
||||
T: Debug + Eq + Hash + for<'a> Deserialize<'a> + Serialize + 'static + Copy,
|
||||
{
|
||||
fn init_state(&mut self, state: &mut S) -> Result<(), Error> {
|
||||
state.add_named_metadata(self.name(), ListFeedbackMetadata::<T>::default());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<EM, I, OT, S, T> Feedback<EM, I, OT, S> for ListFeedback<T>
|
||||
where
|
||||
OT: MatchName,
|
||||
S: HasNamedMetadata,
|
||||
T: Debug + Serialize + Hash + Eq + DeserializeOwned + Default + Copy + 'static,
|
||||
{
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
fn is_interesting(
|
||||
fn has_interesting_list_observer_feedback<OT, S>(
|
||||
&mut self,
|
||||
state: &mut S,
|
||||
_manager: &mut EM,
|
||||
_input: &I,
|
||||
observers: &OT,
|
||||
_exit_kind: &ExitKind,
|
||||
) -> Result<bool, Error> {
|
||||
) -> bool
|
||||
where
|
||||
OT: MatchName,
|
||||
S: HasNamedMetadata,
|
||||
{
|
||||
let observer = observers.get(&self.observer_handle).unwrap();
|
||||
// TODO register the list content in a testcase metadata
|
||||
self.novelty.clear();
|
||||
@ -103,7 +97,48 @@ where
|
||||
self.novelty.insert(*v);
|
||||
}
|
||||
}
|
||||
Ok(!self.novelty.is_empty())
|
||||
!self.novelty.is_empty()
|
||||
}
|
||||
|
||||
fn append_list_observer_metadata<S: HasNamedMetadata>(&mut self, state: &mut S) {
|
||||
let history_set = state
|
||||
.named_metadata_map_mut()
|
||||
.get_mut::<ListFeedbackMetadata<T>>(self.name())
|
||||
.unwrap();
|
||||
|
||||
for v in &self.novelty {
|
||||
history_set.set.insert(*v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, T> StateInitializer<S> for ListFeedback<T>
|
||||
where
|
||||
S: HasNamedMetadata,
|
||||
T: Debug + Eq + Hash + for<'a> Deserialize<'a> + Serialize + Default + Copy + 'static,
|
||||
{
|
||||
fn init_state(&mut self, state: &mut S) -> Result<(), Error> {
|
||||
state.add_named_metadata(self.name(), ListFeedbackMetadata::<T>::default());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<EM, I, OT, S, T> Feedback<EM, I, OT, S> for ListFeedback<T>
|
||||
where
|
||||
OT: MatchName,
|
||||
S: HasNamedMetadata,
|
||||
T: Debug + Eq + Hash + for<'a> Deserialize<'a> + Serialize + Default + Copy + 'static,
|
||||
{
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
fn is_interesting(
|
||||
&mut self,
|
||||
state: &mut S,
|
||||
_manager: &mut EM,
|
||||
_input: &I,
|
||||
observers: &OT,
|
||||
_exit_kind: &ExitKind,
|
||||
) -> Result<bool, Error> {
|
||||
Ok(self.has_interesting_list_observer_feedback(state, observers))
|
||||
}
|
||||
|
||||
#[cfg(feature = "track_hit_feedbacks")]
|
||||
@ -118,14 +153,7 @@ where
|
||||
_observers: &OT,
|
||||
_testcase: &mut crate::corpus::Testcase<I>,
|
||||
) -> Result<(), Error> {
|
||||
let history_set = state
|
||||
.named_metadata_map_mut()
|
||||
.get_mut::<ListFeedbackMetadata<T>>(self.name())
|
||||
.unwrap();
|
||||
|
||||
for v in &self.novelty {
|
||||
history_set.set.insert(*v);
|
||||
}
|
||||
self.append_list_observer_metadata(state);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -407,12 +407,12 @@ where
|
||||
default fn is_interesting(
|
||||
&mut self,
|
||||
state: &mut S,
|
||||
manager: &mut EM,
|
||||
input: &I,
|
||||
_manager: &mut EM,
|
||||
_input: &I,
|
||||
observers: &OT,
|
||||
exit_kind: &ExitKind,
|
||||
_exit_kind: &ExitKind,
|
||||
) -> Result<bool, Error> {
|
||||
let res = self.is_interesting_default(state, manager, input, observers, exit_kind);
|
||||
let res = self.is_interesting_default(state, observers);
|
||||
#[cfg(feature = "track_hit_feedbacks")]
|
||||
{
|
||||
self.last_result = Some(res);
|
||||
@ -424,12 +424,12 @@ where
|
||||
fn is_interesting(
|
||||
&mut self,
|
||||
state: &mut S,
|
||||
manager: &mut EM,
|
||||
input: &I,
|
||||
_manager: &mut EM,
|
||||
_input: &I,
|
||||
observers: &OT,
|
||||
exit_kind: &ExitKind,
|
||||
_exit_kind: &ExitKind,
|
||||
) -> Result<bool, Error> {
|
||||
let res = self.is_interesting_default(state, manager, input, observers, exit_kind);
|
||||
let res = self.is_interesting_default(state, observers);
|
||||
|
||||
#[cfg(feature = "track_hit_feedbacks")]
|
||||
{
|
||||
@ -553,6 +553,76 @@ where
|
||||
observers: &OT,
|
||||
_exit_kind: &ExitKind,
|
||||
) -> Result<bool, Error> {
|
||||
Ok(self.is_interesting_u8_simd_optimized(state, observers))
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, N, O, R> Named for MapFeedback<C, N, O, R> {
|
||||
#[inline]
|
||||
fn name(&self) -> &Cow<'static, str> {
|
||||
&self.name
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::ptr_arg)]
|
||||
fn create_stats_name(name: &Cow<'static, str>) -> Cow<'static, str> {
|
||||
if name.chars().all(char::is_lowercase) {
|
||||
name.clone()
|
||||
} else {
|
||||
name.to_lowercase().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, N, O, R> MapFeedback<C, N, O, R>
|
||||
where
|
||||
C: CanTrack + AsRef<O> + Named,
|
||||
{
|
||||
/// Create new `MapFeedback`
|
||||
#[must_use]
|
||||
pub fn new(map_observer: &C) -> Self {
|
||||
Self {
|
||||
novelties: if C::NOVELTIES { Some(vec![]) } else { None },
|
||||
name: map_observer.name().clone(),
|
||||
map_ref: map_observer.handle(),
|
||||
stats_name: create_stats_name(map_observer.name()),
|
||||
#[cfg(feature = "track_hit_feedbacks")]
|
||||
last_result: None,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creating a new `MapFeedback` with a specific name. This is usefully whenever the same
|
||||
/// feedback is needed twice, but with a different history. Using `new()` always results in the
|
||||
/// same name and therefore also the same history.
|
||||
#[must_use]
|
||||
pub fn with_name(name: &'static str, map_observer: &C) -> Self {
|
||||
let name = Cow::from(name);
|
||||
Self {
|
||||
novelties: if C::NOVELTIES { Some(vec![]) } else { None },
|
||||
map_ref: map_observer.handle(),
|
||||
stats_name: create_stats_name(&name),
|
||||
name,
|
||||
#[cfg(feature = "track_hit_feedbacks")]
|
||||
last_result: None,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Specialize for the common coverage map size, maximization of u8s
|
||||
#[rustversion::nightly]
|
||||
impl<C, O> MapFeedback<C, DifferentIsNovel, O, MaxReducer>
|
||||
where
|
||||
O: MapObserver<Entry = u8> + for<'a> AsSlice<'a, Entry = u8> + for<'a> AsIter<'a, Item = u8>,
|
||||
C: CanTrack + AsRef<O>,
|
||||
{
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
#[allow(clippy::needless_range_loop)]
|
||||
fn is_interesting_u8_simd_optimized<S, OT>(&mut self, state: &mut S, observers: &OT) -> bool
|
||||
where
|
||||
S: HasNamedMetadata,
|
||||
OT: MatchName,
|
||||
{
|
||||
// 128 bits vectors
|
||||
type VectorType = core::simd::u8x16;
|
||||
|
||||
@ -648,14 +718,7 @@ where
|
||||
{
|
||||
self.last_result = Some(interesting);
|
||||
}
|
||||
Ok(interesting)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, N, O, R> Named for MapFeedback<C, N, O, R> {
|
||||
#[inline]
|
||||
fn name(&self) -> &Cow<'static, str> {
|
||||
&self.name
|
||||
interesting
|
||||
}
|
||||
}
|
||||
|
||||
@ -668,51 +731,6 @@ impl<C, N, O, R> HasObserverHandle for MapFeedback<C, N, O, R> {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::ptr_arg)]
|
||||
fn create_stats_name(name: &Cow<'static, str>) -> Cow<'static, str> {
|
||||
if name.chars().all(char::is_lowercase) {
|
||||
name.clone()
|
||||
} else {
|
||||
name.to_lowercase().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, N, O, R> MapFeedback<C, N, O, R>
|
||||
where
|
||||
C: CanTrack + Named + AsRef<O>,
|
||||
{
|
||||
/// Create new `MapFeedback`
|
||||
#[must_use]
|
||||
pub fn new(map_observer: &C) -> Self {
|
||||
Self {
|
||||
novelties: if C::NOVELTIES { Some(vec![]) } else { None },
|
||||
name: map_observer.name().clone(),
|
||||
map_ref: map_observer.handle(),
|
||||
stats_name: create_stats_name(map_observer.name()),
|
||||
#[cfg(feature = "track_hit_feedbacks")]
|
||||
last_result: None,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creating a new `MapFeedback` with a specific name. This is usefully whenever the same
|
||||
/// feedback is needed twice, but with a different history. Using `new()` always results in the
|
||||
/// same name and therefore also the same history.
|
||||
#[must_use]
|
||||
pub fn with_name(name: &'static str, map_observer: &C) -> Self {
|
||||
let name = Cow::from(name);
|
||||
Self {
|
||||
novelties: if C::NOVELTIES { Some(vec![]) } else { None },
|
||||
map_ref: map_observer.handle(),
|
||||
stats_name: create_stats_name(&name),
|
||||
name,
|
||||
#[cfg(feature = "track_hit_feedbacks")]
|
||||
last_result: None,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, N, O, R> MapFeedback<C, N, O, R>
|
||||
where
|
||||
R: Reducer<O::Entry>,
|
||||
@ -724,14 +742,7 @@ where
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
#[allow(clippy::needless_range_loop)]
|
||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||
fn is_interesting_default<EM, I, OT, S>(
|
||||
&mut self,
|
||||
state: &mut S,
|
||||
_manager: &mut EM,
|
||||
_input: &I,
|
||||
observers: &OT,
|
||||
_exit_kind: &ExitKind,
|
||||
) -> bool
|
||||
fn is_interesting_default<OT, S>(&mut self, state: &mut S, observers: &OT) -> bool
|
||||
where
|
||||
S: HasNamedMetadata,
|
||||
OT: MatchName,
|
||||
|
@ -60,6 +60,26 @@ impl<'a> NautilusFeedback<'a> {
|
||||
pub fn new(context: &'a NautilusContext) -> Self {
|
||||
Self { ctx: &context.ctx }
|
||||
}
|
||||
|
||||
fn append_nautilus_metadata_to_state<S>(
|
||||
&mut self,
|
||||
state: &mut S,
|
||||
testcase: &mut Testcase<NautilusInput>,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
S: HasCorpus + HasMetadata,
|
||||
S::Corpus: Corpus<Input = NautilusInput>,
|
||||
{
|
||||
state.corpus().load_input_into(testcase)?;
|
||||
let input = testcase.input().as_ref().unwrap().clone();
|
||||
let meta = state
|
||||
.metadata_map_mut()
|
||||
.get_mut::<NautilusChunksMetadata>()
|
||||
.expect("NautilusChunksMetadata not in the state");
|
||||
meta.cks.add_tree(input.tree, self.ctx);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Named for NautilusFeedback<'_> {
|
||||
@ -95,14 +115,7 @@ where
|
||||
_observers: &OT,
|
||||
testcase: &mut Testcase<NautilusInput>,
|
||||
) -> Result<(), Error> {
|
||||
state.corpus().load_input_into(testcase)?;
|
||||
let input = testcase.input().as_ref().unwrap().clone();
|
||||
let meta = state
|
||||
.metadata_map_mut()
|
||||
.get_mut::<NautilusChunksMetadata>()
|
||||
.expect("NautilusChunksMetadata not in the state");
|
||||
meta.cks.add_tree(input.tree, self.ctx);
|
||||
Ok(())
|
||||
self.append_nautilus_metadata_to_state(state, testcase)
|
||||
}
|
||||
|
||||
fn discard_metadata(&mut self, _state: &mut S, _input: &NautilusInput) -> Result<(), Error> {
|
||||
|
@ -100,34 +100,19 @@ pub struct NewHashFeedback<O> {
|
||||
last_result: Option<bool>,
|
||||
}
|
||||
|
||||
impl<O, S> StateInitializer<S> for NewHashFeedback<O>
|
||||
impl<O> NewHashFeedback<O>
|
||||
where
|
||||
S: HasNamedMetadata,
|
||||
{
|
||||
fn init_state(&mut self, state: &mut S) -> Result<(), Error> {
|
||||
state.add_named_metadata(
|
||||
&self.name,
|
||||
NewHashFeedbackMetadata::with_capacity(self.capacity),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<O, EM, I, OT, S> Feedback<EM, I, OT, S> for NewHashFeedback<O>
|
||||
where
|
||||
O: ObserverWithHashField,
|
||||
OT: MatchName,
|
||||
S: HasNamedMetadata,
|
||||
O: ObserverWithHashField + Named,
|
||||
{
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
fn is_interesting(
|
||||
fn has_interesting_backtrace_hash_observation<OT, S: HasNamedMetadata>(
|
||||
&mut self,
|
||||
state: &mut S,
|
||||
_manager: &mut EM,
|
||||
_input: &I,
|
||||
observers: &OT,
|
||||
_exit_kind: &ExitKind,
|
||||
) -> Result<bool, Error> {
|
||||
) -> Result<bool, Error>
|
||||
where
|
||||
OT: MatchName,
|
||||
{
|
||||
let observer = observers
|
||||
.get(&self.o_ref)
|
||||
.expect("A NewHashFeedback needs a BacktraceObserver");
|
||||
@ -150,6 +135,39 @@ where
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl<O, S> StateInitializer<S> for NewHashFeedback<O>
|
||||
where
|
||||
S: HasNamedMetadata,
|
||||
{
|
||||
fn init_state(&mut self, state: &mut S) -> Result<(), Error> {
|
||||
state.add_named_metadata(
|
||||
&self.name,
|
||||
NewHashFeedbackMetadata::with_capacity(self.capacity),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<O, EM, I, OT, S> Feedback<EM, I, OT, S> for NewHashFeedback<O>
|
||||
where
|
||||
O: ObserverWithHashField + Named,
|
||||
OT: MatchName,
|
||||
S: HasNamedMetadata,
|
||||
{
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
fn is_interesting(
|
||||
&mut self,
|
||||
state: &mut S,
|
||||
_manager: &mut EM,
|
||||
_input: &I,
|
||||
observers: &OT,
|
||||
_exit_kind: &ExitKind,
|
||||
) -> Result<bool, Error> {
|
||||
self.has_interesting_backtrace_hash_observation(state, observers)
|
||||
}
|
||||
|
||||
#[cfg(feature = "track_hit_feedbacks")]
|
||||
fn last_result(&self) -> Result<bool, Error> {
|
||||
self.last_result.ok_or(premature_last_result_err())
|
||||
|
@ -32,6 +32,34 @@ pub struct StdOutToMetadataFeedback {
|
||||
o_ref: Handle<StdOutObserver>,
|
||||
}
|
||||
|
||||
impl StdOutToMetadataFeedback {
|
||||
/// Append to the testcase the generated metadata in case of a new corpus item.
|
||||
#[inline]
|
||||
fn append_stdout_observation_to_testcase<I, OT>(
|
||||
&mut self,
|
||||
observers: &OT,
|
||||
testcase: &mut Testcase<I>,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
OT: MatchName,
|
||||
{
|
||||
let observer = observers
|
||||
.get(&self.o_ref)
|
||||
.ok_or(Error::illegal_state("StdOutObserver is missing"))?;
|
||||
let buffer = observer
|
||||
.stdout
|
||||
.as_ref()
|
||||
.ok_or(Error::illegal_state("StdOutObserver has no stdout"))?;
|
||||
let stdout = String::from_utf8_lossy(buffer).into_owned();
|
||||
|
||||
testcase
|
||||
.metadata_map_mut()
|
||||
.insert(StdOutMetadata { stdout });
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> StateInitializer<S> for StdOutToMetadataFeedback {}
|
||||
|
||||
impl<EM, I, OT, S> Feedback<EM, I, OT, S> for StdOutToMetadataFeedback
|
||||
@ -52,20 +80,7 @@ where
|
||||
observers: &OT,
|
||||
testcase: &mut Testcase<I>,
|
||||
) -> Result<(), Error> {
|
||||
let observer = observers
|
||||
.get(&self.o_ref)
|
||||
.ok_or(Error::illegal_state("StdOutObserver is missing"))?;
|
||||
let buffer = observer
|
||||
.stdout
|
||||
.as_ref()
|
||||
.ok_or(Error::illegal_state("StdOutObserver has no stdout"))?;
|
||||
let stdout = String::from_utf8_lossy(buffer).into_owned();
|
||||
|
||||
testcase
|
||||
.metadata_map_mut()
|
||||
.insert(StdOutMetadata { stdout });
|
||||
|
||||
Ok(())
|
||||
self.append_stdout_observation_to_testcase(observers, testcase)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -61,6 +61,70 @@ where
|
||||
state: &mut Self::State,
|
||||
_manager: &mut EM,
|
||||
) -> Result<(), Error> {
|
||||
self.dump_state_to_disk(state)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn should_restart(&mut self, _state: &mut Self::State) -> Result<bool, Error> {
|
||||
// Not executing the target, so restart safety is not needed
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn clear_progress(&mut self, _state: &mut Self::State) -> Result<(), Error> {
|
||||
// Not executing the target, so restart safety is not needed
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<CB, EM, Z> DumpToDiskStage<CB, EM, Z>
|
||||
where
|
||||
EM: UsesState,
|
||||
Z: UsesState,
|
||||
<EM as UsesState>::State: HasCorpus + HasSolutions + HasRand + HasMetadata,
|
||||
<<EM as UsesState>::State as HasCorpus>::Corpus: Corpus<Input = EM::Input>,
|
||||
<<EM as UsesState>::State as HasSolutions>::Solutions: Corpus<Input = EM::Input>,
|
||||
{
|
||||
/// Create a new [`DumpToDiskStage`]
|
||||
pub fn new<A, B>(to_bytes: CB, corpus_dir: A, solutions_dir: B) -> Result<Self, Error>
|
||||
where
|
||||
A: Into<PathBuf>,
|
||||
B: Into<PathBuf>,
|
||||
{
|
||||
let corpus_dir = corpus_dir.into();
|
||||
if let Err(e) = fs::create_dir(&corpus_dir) {
|
||||
if !corpus_dir.is_dir() {
|
||||
return Err(Error::os_error(
|
||||
e,
|
||||
format!("Error creating directory {corpus_dir:?}"),
|
||||
));
|
||||
}
|
||||
}
|
||||
let solutions_dir = solutions_dir.into();
|
||||
if let Err(e) = fs::create_dir(&solutions_dir) {
|
||||
if !corpus_dir.is_dir() {
|
||||
return Err(Error::os_error(
|
||||
e,
|
||||
format!("Error creating directory {solutions_dir:?}"),
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(Self {
|
||||
to_bytes,
|
||||
solutions_dir,
|
||||
corpus_dir,
|
||||
phantom: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn dump_state_to_disk(&mut self, state: &mut <Self as UsesState>::State) -> Result<(), Error>
|
||||
where
|
||||
CB: FnMut(
|
||||
&<<<EM as UsesState>::State as HasCorpus>::Corpus as Corpus>::Input,
|
||||
&<EM as UsesState>::State,
|
||||
) -> Vec<u8>,
|
||||
{
|
||||
let (mut corpus_id, mut solutions_id) =
|
||||
if let Some(meta) = state.metadata_map().get::<DumpToDiskMetadata>() {
|
||||
(
|
||||
@ -114,55 +178,4 @@ where
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn should_restart(&mut self, _state: &mut Self::State) -> Result<bool, Error> {
|
||||
// Not executing the target, so restart safety is not needed
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn clear_progress(&mut self, _state: &mut Self::State) -> Result<(), Error> {
|
||||
// Not executing the target, so restart safety is not needed
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<CB, EM, Z> DumpToDiskStage<CB, EM, Z>
|
||||
where
|
||||
EM: UsesState,
|
||||
Z: UsesState,
|
||||
<Self as UsesState>::State: HasCorpus + HasSolutions + HasRand + HasMetadata,
|
||||
{
|
||||
/// Create a new [`DumpToDiskStage`]
|
||||
pub fn new<A, B>(to_bytes: CB, corpus_dir: A, solutions_dir: B) -> Result<Self, Error>
|
||||
where
|
||||
A: Into<PathBuf>,
|
||||
B: Into<PathBuf>,
|
||||
{
|
||||
let corpus_dir = corpus_dir.into();
|
||||
if let Err(e) = fs::create_dir(&corpus_dir) {
|
||||
if !corpus_dir.is_dir() {
|
||||
return Err(Error::os_error(
|
||||
e,
|
||||
format!("Error creating directory {corpus_dir:?}"),
|
||||
));
|
||||
}
|
||||
}
|
||||
let solutions_dir = solutions_dir.into();
|
||||
if let Err(e) = fs::create_dir(&solutions_dir) {
|
||||
if !corpus_dir.is_dir() {
|
||||
return Err(Error::os_error(
|
||||
e,
|
||||
format!("Error creating directory {solutions_dir:?}"),
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(Self {
|
||||
to_bytes,
|
||||
solutions_dir,
|
||||
corpus_dir,
|
||||
phantom: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -62,6 +62,33 @@ where
|
||||
state: &mut Self::State,
|
||||
_manager: &mut EM,
|
||||
) -> Result<(), Error> {
|
||||
self.update_and_report_afl_stats(state, _manager)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn should_restart(&mut self, _state: &mut Self::State) -> Result<bool, Error> {
|
||||
// Not running the target so we wont't crash/timeout and, hence, don't need to restore anything
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn clear_progress(&mut self, _state: &mut Self::State) -> Result<(), Error> {
|
||||
// Not running the target so we wont't crash/timeout and, hence, don't need to restore anything
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, EM, Z> StatsStage<E, EM, Z> {
|
||||
fn update_and_report_afl_stats(
|
||||
&mut self,
|
||||
state: &mut <Self as UsesState>::State,
|
||||
_manager: &mut EM,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
E: UsesState,
|
||||
EM: EventFirer<State = E::State>,
|
||||
<Self as UsesState>::State: HasCorpus + HasImported,
|
||||
{
|
||||
let Some(corpus_id) = state.current_corpus_id()? else {
|
||||
return Err(Error::illegal_state(
|
||||
"state is not currently processing a corpus index",
|
||||
@ -124,18 +151,6 @@ where
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn should_restart(&mut self, _state: &mut Self::State) -> Result<bool, Error> {
|
||||
// Not running the target so we wont't crash/timeout and, hence, don't need to restore anything
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn clear_progress(&mut self, _state: &mut Self::State) -> Result<(), Error> {
|
||||
// Not running the target so we wont't crash/timeout and, hence, don't need to restore anything
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, EM, Z> StatsStage<E, EM, Z> {
|
||||
|
@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
corpus::Corpus,
|
||||
inputs::{BytesInput, HasMutatorBytes},
|
||||
inputs::{BytesInput, HasTargetBytes},
|
||||
stages::Stage,
|
||||
state::{HasCorpus, HasCurrentTestcase, State, UsesState},
|
||||
HasMetadata,
|
||||
@ -89,6 +89,24 @@ impl<S> UnicodeIdentificationStage<S> {
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
fn identify_unicode_in_current_testcase(state: &mut S) -> Result<(), Error>
|
||||
where
|
||||
S: HasCurrentTestcase,
|
||||
<S::Corpus as Corpus>::Input: HasTargetBytes,
|
||||
{
|
||||
let mut tc = state.current_testcase_mut()?;
|
||||
if tc.has_metadata::<UnicodeIdentificationMetadata>() {
|
||||
return Ok(()); // skip recompute
|
||||
}
|
||||
|
||||
let input = tc.load_input(state.corpus())?;
|
||||
|
||||
let bytes = input.target_bytes();
|
||||
let metadata = extract_metadata(&bytes);
|
||||
tc.add_metadata(metadata);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> UsesState for UnicodeIdentificationStage<S>
|
||||
@ -113,18 +131,7 @@ where
|
||||
state: &mut Self::State,
|
||||
_manager: &mut EM,
|
||||
) -> Result<(), Error> {
|
||||
let mut tc = state.current_testcase_mut()?;
|
||||
if tc.has_metadata::<UnicodeIdentificationMetadata>() {
|
||||
return Ok(()); // skip recompute
|
||||
}
|
||||
|
||||
let input = tc.load_input(state.corpus())?;
|
||||
|
||||
let bytes = input.bytes();
|
||||
let metadata = extract_metadata(bytes);
|
||||
tc.add_metadata(metadata);
|
||||
|
||||
Ok(())
|
||||
UnicodeIdentificationStage::identify_unicode_in_current_testcase(state)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
Loading…
x
Reference in New Issue
Block a user