Skip to content

Commit

Permalink
Feature: Make executors and feedbacks easier to use outside of the fu…
Browse files Browse the repository at this point in the history
…zzing loop (extends AFLplusplus#2511) (AFLplusplus#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>
  • Loading branch information
3 people authored and riesentoaster committed Dec 11, 2024
1 parent a5b3ad7 commit 61efd68
Show file tree
Hide file tree
Showing 12 changed files with 492 additions and 329 deletions.
10 changes: 7 additions & 3 deletions fuzzers/baby/baby_fuzzer_unicode/src/main.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down
39 changes: 26 additions & 13 deletions libafl/src/executors/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down
200 changes: 108 additions & 92 deletions libafl/src/executors/forkserver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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`
Expand Down Expand Up @@ -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)
}
}

Expand Down
22 changes: 16 additions & 6 deletions libafl/src/feedbacks/concolic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<'_> {
Expand All @@ -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(())
}
}
Loading

0 comments on commit 61efd68

Please sign in to comment.