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:
Dominik Maier 2024-10-29 18:35:17 +01:00 committed by GitHub
parent 0f744a3abb
commit 6eb2dafd34
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 498 additions and 335 deletions

View File

@ -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);

View File

@ -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,

View File

@ -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)
}
}

View File

@ -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(())
}
}

View File

@ -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>
fn has_interesting_list_observer_feedback<OT, S>(
&mut self,
state: &mut S,
observers: &OT,
) -> bool
where
OT: MatchName,
S: HasNamedMetadata,
T: Debug + Serialize + Hash + Eq + DeserializeOwned + 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> {
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(())
}
}

View File

@ -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,

View File

@ -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> {

View File

@ -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())

View File

@ -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)
}
}

View File

@ -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,
})
}
}

View File

@ -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> {

View File

@ -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]