Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Make executors and feedbacks easier to use outside of the fuzzing loop #2511

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 26 additions & 9 deletions libafl/src/executors/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,20 +209,16 @@ 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<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>,
Z: UsesState<State = S>,
OT: Debug + ObserversTuple<S>,
{
fn run_target(
fn execute_input_with_command(
&mut self,
_fuzzer: &mut Z,
state: &mut Self::State,
_mgr: &mut EM,
input: &Self::Input,
state: &mut S,
input: &<S as UsesInput>::Input,
) -> Result<ExitKind, Error> {
use std::os::unix::prelude::ExitStatusExt;

Expand Down Expand Up @@ -283,6 +279,27 @@ 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>
where
EM: UsesState<State = S>,
S: State + HasExecutions,
T: CommandConfigurator<S::Input> + Debug,
OT: Debug + MatchName + ObserversTuple<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)
}
}

impl<OT, S, T> UsesState for CommandExecutor<OT, S, T>
where
S: State,
Expand Down
205 changes: 112 additions & 93 deletions libafl/src/executors/forkserver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,117 @@ where
pub fn coverage_map_size(&self) -> Option<usize> {
self.map_size
}

#[inline]
fn execute_input_tracked<I: Input + HasTargetBytes>(
&mut self,
state: &mut S,
input: &I,
) -> Result<ExitKind, Error>
where
S: HasExecutions,
{
*state.executions_mut() += 1;

self.execute_input(input)
}

#[inline]
fn execute_input<I: Input + HasTargetBytes>(&mut self, input: &I) -> 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 = input.target_bytes();
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])?;
}

let send_len = self.forkserver.write_ctl(last_run_timed_out)?;

self.forkserver.set_last_run_timed_out(false);

if send_len != 4 {
return Err(Error::unknown(
"Unable to request new process from fork server (OOM?)".to_string(),
));
}

let (recv_pid_len, pid) = self.forkserver.read_st()?;
if recv_pid_len != 4 {
return Err(Error::unknown(
"Unable to request new process from fork server (OOM?)".to_string(),
));
}

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);
let (recv_status_len, _) = self.forkserver.read_st()?;
if recv_status_len != 4 {
return Err(Error::unknown("Could not kill timed-out child".to_string()));
}
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 @@ -1372,99 +1483,7 @@ where
) -> 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 = input.target_bytes();
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])?;
}

let send_len = self.forkserver.write_ctl(last_run_timed_out)?;

self.forkserver.set_last_run_timed_out(false);

if send_len != 4 {
return Err(Error::unknown(
"Unable to request new process from fork server (OOM?)".to_string(),
));
}

let (recv_pid_len, pid) = self.forkserver.read_st()?;
if recv_pid_len != 4 {
return Err(Error::unknown(
"Unable to request new process from fork server (OOM?)".to_string(),
));
}

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);
let (recv_status_len, _) = self.forkserver.read_st()?;
if recv_status_len != 4 {
return Err(Error::unknown("Could not kill timed-out child".to_string()));
}
exit_kind = ExitKind::Timeout;
}

if !libc::WIFSTOPPED(self.forkserver().status()) {
self.forkserver.reset_child_pid();
}

Ok(exit_kind)
self.execute_input_tracked(state, input)
}
}

Expand Down
23 changes: 17 additions & 6 deletions libafl/src/feedbacks/concolic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,22 @@ impl<'map, S> ConcolicFeedback<'map, S> {
phantom: PhantomData,
}
}

fn add_concolic_feedback_to_metadata<OT>(
&mut self,
observers: &OT,
testcase: &mut Testcase<S::Input>,
) where
OT: ObserversTuple<S>,
S: UsesInput,
{
if let Some(metadata) = observers
.get(&self.observer_handle)
.map(ConcolicObserver::create_metadata_from_current_map)
{
testcase.metadata_map_mut().insert(metadata);
}
}
}

impl<S> Named for ConcolicFeedback<'_, S> {
Expand Down Expand Up @@ -83,12 +99,7 @@ where
OT: ObserversTuple<S>,
EM: EventFirer<State = S>,
{
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(())
}

Expand Down
Loading