Skip to content

Commit 6eb2daf

Browse files
domenukkLukas-Dreseltokatoka
authored
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>
1 parent 0f744a3 commit 6eb2daf

File tree

12 files changed

+492
-329
lines changed

12 files changed

+492
-329
lines changed

fuzzers/baby/baby_fuzzer_unicode/src/main.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
#[cfg(windows)]
22
use std::ptr::write_volatile;
3-
use std::{path::PathBuf, ptr::write};
3+
use std::{
4+
path::PathBuf,
5+
ptr::{addr_of, addr_of_mut, write},
6+
};
47

58
#[cfg(feature = "tui")]
69
use libafl::monitors::tui::TuiMonitor;
@@ -24,7 +27,8 @@ use libafl_bolts::{rands::StdRand, tuples::tuple_list, AsSlice};
2427

2528
/// Coverage map with explicit assignments due to the lack of instrumentation
2629
static mut SIGNALS: [u8; 64] = [0; 64];
27-
static mut SIGNALS_PTR: *mut u8 = unsafe { SIGNALS.as_mut_ptr() };
30+
static mut SIGNALS_PTR: *mut u8 = addr_of_mut!(SIGNALS).cast();
31+
static mut SIGNALS_LEN: usize = unsafe { (*addr_of!(SIGNALS)).len() };
2832

2933
/// Assign a signal to the signals map
3034
fn signals_set(idx: usize) {
@@ -56,7 +60,7 @@ pub fn main() {
5660
};
5761

5862
// Create an observation channel using the signals map
59-
let observer = unsafe { StdMapObserver::from_mut_ptr("signals", SIGNALS_PTR, SIGNALS.len()) };
63+
let observer = unsafe { StdMapObserver::from_mut_ptr("signals", SIGNALS_PTR, SIGNALS_LEN) };
6064

6165
// Feedback to rate the interestingness of an input
6266
let mut feedback = MaxMapFeedback::new(&observer);

libafl/src/executors/command.rs

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -214,21 +214,13 @@ where
214214

215215
// this only works on unix because of the reliance on checking the process signal for detecting OOM
216216
#[cfg(all(feature = "std", unix))]
217-
impl<EM, OT, S, T, Z> Executor<EM, Z> for CommandExecutor<OT, S, T>
217+
impl<I, OT, S, T> CommandExecutor<OT, S, T>
218218
where
219-
EM: UsesState<State = S>,
220-
S: State + HasExecutions,
221-
T: CommandConfigurator<S::Input> + Debug,
222-
OT: Debug + MatchName + ObserversTuple<S::Input, S>,
223-
Z: UsesState<State = S>,
219+
S: State + HasExecutions + UsesInput<Input = I>,
220+
T: CommandConfigurator<I> + Debug,
221+
OT: Debug + ObserversTuple<I, S>,
224222
{
225-
fn run_target(
226-
&mut self,
227-
_fuzzer: &mut Z,
228-
state: &mut Self::State,
229-
_mgr: &mut EM,
230-
input: &Self::Input,
231-
) -> Result<ExitKind, Error> {
223+
fn execute_input_with_command(&mut self, state: &mut S, input: &I) -> Result<ExitKind, Error> {
232224
use std::os::unix::prelude::ExitStatusExt;
233225

234226
use wait_timeout::ChildExt;
@@ -288,6 +280,27 @@ where
288280
}
289281
}
290282

283+
#[cfg(all(feature = "std", unix))]
284+
impl<EM, OT, S, T, Z> Executor<EM, Z> for CommandExecutor<OT, S, T>
285+
where
286+
EM: UsesState<State = S>,
287+
S: State + HasExecutions + UsesInput,
288+
T: CommandConfigurator<S::Input> + Debug,
289+
OT: Debug + MatchName + ObserversTuple<S::Input, S>,
290+
Z: UsesState<State = S>,
291+
{
292+
fn run_target(
293+
&mut self,
294+
_fuzzer: &mut Z,
295+
state: &mut Self::State,
296+
_mgr: &mut EM,
297+
input: &Self::Input,
298+
) -> Result<ExitKind, Error> {
299+
self.execute_input_with_command(state, input)
300+
}
301+
}
302+
303+
// this only works on unix because of the reliance on checking the process signal for detecting OOM
291304
impl<OT, S, T> HasTimeout for CommandExecutor<OT, S, T>
292305
where
293306
S: HasCorpus,

libafl/src/executors/forkserver.rs

Lines changed: 108 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -644,6 +644,7 @@ where
644644
OT: ObserversTuple<S::Input, S>,
645645
S: UsesInput,
646646
SP: ShMemProvider,
647+
TC: TargetBytesConverter,
647648
{
648649
/// The `target` binary that's going to run.
649650
pub fn target(&self) -> &OsString {
@@ -674,6 +675,112 @@ where
674675
pub fn coverage_map_size(&self) -> Option<usize> {
675676
self.map_size
676677
}
678+
679+
/// Execute input and increase the execution counter.
680+
#[inline]
681+
fn execute_input(&mut self, state: &mut S, input: &TC::Input) -> Result<ExitKind, Error>
682+
where
683+
S: HasExecutions,
684+
{
685+
*state.executions_mut() += 1;
686+
687+
self.execute_input_uncounted(input)
688+
}
689+
690+
/// Execute input, but side-step the execution counter.
691+
#[inline]
692+
fn execute_input_uncounted(&mut self, input: &TC::Input) -> Result<ExitKind, Error> {
693+
let mut exit_kind = ExitKind::Ok;
694+
695+
let last_run_timed_out = self.forkserver.last_run_timed_out_raw();
696+
697+
let mut input_bytes = self.target_bytes_converter.to_target_bytes(input);
698+
let mut input_size = input_bytes.as_slice().len();
699+
if input_size > self.max_input_size {
700+
// Truncate like AFL++ does
701+
input_size = self.max_input_size;
702+
} else if input_size < self.min_input_size {
703+
// Extend like AFL++ does
704+
input_size = self.min_input_size;
705+
let mut input_bytes_copy = Vec::with_capacity(input_size);
706+
input_bytes_copy
707+
.as_slice_mut()
708+
.copy_from_slice(input_bytes.as_slice());
709+
input_bytes = OwnedSlice::from(input_bytes_copy);
710+
}
711+
let input_size_in_bytes = input_size.to_ne_bytes();
712+
if self.uses_shmem_testcase {
713+
debug_assert!(
714+
self.map.is_some(),
715+
"The uses_shmem_testcase() bool can only exist when a map is set"
716+
);
717+
// # Safety
718+
// Struct can never be created when uses_shmem_testcase is true and map is none.
719+
let map = unsafe { self.map.as_mut().unwrap_unchecked() };
720+
// The first four bytes declares the size of the shmem.
721+
map.as_slice_mut()[..SHMEM_FUZZ_HDR_SIZE]
722+
.copy_from_slice(&input_size_in_bytes[..SHMEM_FUZZ_HDR_SIZE]);
723+
map.as_slice_mut()[SHMEM_FUZZ_HDR_SIZE..(SHMEM_FUZZ_HDR_SIZE + input_size)]
724+
.copy_from_slice(&input_bytes.as_slice()[..input_size]);
725+
} else {
726+
self.input_file
727+
.write_buf(&input_bytes.as_slice()[..input_size])?;
728+
}
729+
730+
self.forkserver.set_last_run_timed_out(false);
731+
if let Err(err) = self.forkserver.write_ctl(last_run_timed_out) {
732+
return Err(Error::unknown(format!(
733+
"Unable to request new process from fork server (OOM?): {err:?}"
734+
)));
735+
}
736+
737+
let pid = self.forkserver.read_st().map_err(|err| {
738+
Error::unknown(format!(
739+
"Unable to request new process from fork server (OOM?): {err:?}"
740+
))
741+
})?;
742+
743+
if pid <= 0 {
744+
return Err(Error::unknown(
745+
"Fork server is misbehaving (OOM?)".to_string(),
746+
));
747+
}
748+
749+
self.forkserver.set_child_pid(Pid::from_raw(pid));
750+
751+
if let Some(status) = self.forkserver.read_st_timed(&self.timeout)? {
752+
self.forkserver.set_status(status);
753+
let exitcode_is_crash = if let Some(crash_exitcode) = self.crash_exitcode {
754+
(libc::WEXITSTATUS(self.forkserver().status()) as i8) == crash_exitcode
755+
} else {
756+
false
757+
};
758+
if libc::WIFSIGNALED(self.forkserver().status()) || exitcode_is_crash {
759+
exit_kind = ExitKind::Crash;
760+
#[cfg(feature = "regex")]
761+
if let Some(asan_observer) = self.observers.get_mut(&self.asan_obs) {
762+
asan_observer.parse_asan_output_from_asan_log_file(pid)?;
763+
}
764+
}
765+
} else {
766+
self.forkserver.set_last_run_timed_out(true);
767+
768+
// 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()?
769+
let _ = kill(self.forkserver().child_pid(), self.forkserver.kill_signal);
770+
if let Err(err) = self.forkserver.read_st() {
771+
return Err(Error::unknown(format!(
772+
"Could not kill timed-out child: {err:?}"
773+
)));
774+
}
775+
exit_kind = ExitKind::Timeout;
776+
}
777+
778+
if !libc::WIFSTOPPED(self.forkserver().status()) {
779+
self.forkserver.reset_child_pid();
780+
}
781+
782+
Ok(exit_kind)
783+
}
677784
}
678785

679786
/// The builder for `ForkserverExecutor`
@@ -1453,98 +1560,7 @@ where
14531560
_mgr: &mut EM,
14541561
input: &Self::Input,
14551562
) -> Result<ExitKind, Error> {
1456-
*state.executions_mut() += 1;
1457-
1458-
let mut exit_kind = ExitKind::Ok;
1459-
1460-
let last_run_timed_out = self.forkserver.last_run_timed_out_raw();
1461-
1462-
let mut input_bytes = self.target_bytes_converter.to_target_bytes(input);
1463-
let mut input_size = input_bytes.as_slice().len();
1464-
if input_size > self.max_input_size {
1465-
// Truncate like AFL++ does
1466-
input_size = self.max_input_size;
1467-
} else if input_size < self.min_input_size {
1468-
// Extend like AFL++ does
1469-
input_size = self.min_input_size;
1470-
let mut input_bytes_copy = Vec::with_capacity(input_size);
1471-
input_bytes_copy
1472-
.as_slice_mut()
1473-
.copy_from_slice(input_bytes.as_slice());
1474-
input_bytes = OwnedSlice::from(input_bytes_copy);
1475-
}
1476-
let input_size_in_bytes = input_size.to_ne_bytes();
1477-
if self.uses_shmem_testcase {
1478-
debug_assert!(
1479-
self.map.is_some(),
1480-
"The uses_shmem_testcase() bool can only exist when a map is set"
1481-
);
1482-
// # Safety
1483-
// Struct can never be created when uses_shmem_testcase is true and map is none.
1484-
let map = unsafe { self.map.as_mut().unwrap_unchecked() };
1485-
// The first four bytes declares the size of the shmem.
1486-
map.as_slice_mut()[..SHMEM_FUZZ_HDR_SIZE]
1487-
.copy_from_slice(&input_size_in_bytes[..SHMEM_FUZZ_HDR_SIZE]);
1488-
map.as_slice_mut()[SHMEM_FUZZ_HDR_SIZE..(SHMEM_FUZZ_HDR_SIZE + input_size)]
1489-
.copy_from_slice(&input_bytes.as_slice()[..input_size]);
1490-
} else {
1491-
self.input_file
1492-
.write_buf(&input_bytes.as_slice()[..input_size])?;
1493-
}
1494-
1495-
self.forkserver.set_last_run_timed_out(false);
1496-
if let Err(err) = self.forkserver.write_ctl(last_run_timed_out) {
1497-
return Err(Error::unknown(format!(
1498-
"Unable to request new process from fork server (OOM?): {err:?}"
1499-
)));
1500-
}
1501-
1502-
let pid = self.forkserver.read_st().map_err(|err| {
1503-
Error::unknown(format!(
1504-
"Unable to request new process from fork server (OOM?): {err:?}"
1505-
))
1506-
})?;
1507-
1508-
if pid <= 0 {
1509-
return Err(Error::unknown(
1510-
"Fork server is misbehaving (OOM?)".to_string(),
1511-
));
1512-
}
1513-
1514-
self.forkserver.set_child_pid(Pid::from_raw(pid));
1515-
1516-
if let Some(status) = self.forkserver.read_st_timed(&self.timeout)? {
1517-
self.forkserver.set_status(status);
1518-
let exitcode_is_crash = if let Some(crash_exitcode) = self.crash_exitcode {
1519-
(libc::WEXITSTATUS(self.forkserver().status()) as i8) == crash_exitcode
1520-
} else {
1521-
false
1522-
};
1523-
if libc::WIFSIGNALED(self.forkserver().status()) || exitcode_is_crash {
1524-
exit_kind = ExitKind::Crash;
1525-
#[cfg(feature = "regex")]
1526-
if let Some(asan_observer) = self.observers.get_mut(&self.asan_obs) {
1527-
asan_observer.parse_asan_output_from_asan_log_file(pid)?;
1528-
}
1529-
}
1530-
} else {
1531-
self.forkserver.set_last_run_timed_out(true);
1532-
1533-
// 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()?
1534-
let _ = kill(self.forkserver().child_pid(), self.forkserver.kill_signal);
1535-
if let Err(err) = self.forkserver.read_st() {
1536-
return Err(Error::unknown(format!(
1537-
"Could not kill timed-out child: {err:?}"
1538-
)));
1539-
}
1540-
exit_kind = ExitKind::Timeout;
1541-
}
1542-
1543-
if !libc::WIFSTOPPED(self.forkserver().status()) {
1544-
self.forkserver.reset_child_pid();
1545-
}
1546-
1547-
Ok(exit_kind)
1563+
self.execute_input(state, input)
15481564
}
15491565
}
15501566

libafl/src/feedbacks/concolic.rs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,21 @@ impl<'map> ConcolicFeedback<'map> {
3838
observer_handle: observer.handle(),
3939
}
4040
}
41+
42+
fn add_concolic_feedback_to_metadata<I, OT>(
43+
&mut self,
44+
observers: &OT,
45+
testcase: &mut Testcase<I>,
46+
) where
47+
OT: MatchName,
48+
{
49+
if let Some(metadata) = observers
50+
.get(&self.observer_handle)
51+
.map(ConcolicObserver::create_metadata_from_current_map)
52+
{
53+
testcase.metadata_map_mut().insert(metadata);
54+
}
55+
}
4156
}
4257

4358
impl Named for ConcolicFeedback<'_> {
@@ -64,12 +79,7 @@ where
6479
observers: &OT,
6580
testcase: &mut Testcase<I>,
6681
) -> Result<(), Error> {
67-
if let Some(metadata) = observers
68-
.get(&self.observer_handle)
69-
.map(ConcolicObserver::create_metadata_from_current_map)
70-
{
71-
testcase.metadata_map_mut().insert(metadata);
72-
}
82+
self.add_concolic_feedback_to_metadata(observers, testcase);
7383
Ok(())
7484
}
7585
}

0 commit comments

Comments
 (0)