Skip to content

Commit

Permalink
Introduce Persistent Record for libafl-fuzz (AFLplusplus#2411)
Browse files Browse the repository at this point in the history
* libafl-fuzz: fix PERSISTENT_SIG and DEFERRED_SIG

* libafl-fuzz: add AFL_PERSISTENT_RECORD

* libafl-fuzz: update README
  • Loading branch information
R9295 authored Jul 16, 2024
1 parent 713652e commit b9da7dd
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 6 deletions.
2 changes: 1 addition & 1 deletion fuzzers/libafl-fuzz/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Rewrite of afl-fuzz in Rust.
- [ ] AFL_FAST_CAL
- [ ] AFL_NO_CRASH_README
- [ ] AFL_KEEP_TIMEOUTS
- [ ] AFL_PERSISTENT_RECORD
- [x] AFL_PERSISTENT_RECORD
- [ ] AFL_TESTCACHE_SIZE
- [ ] AFL_NO_ARITH
- [ ] AFL_DISABLE_TRIM
Expand Down
3 changes: 3 additions & 0 deletions fuzzers/libafl-fuzz/src/env_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ pub fn parse_envs(opt: &mut Opt) -> Result<(), Error> {
if let Ok(res) = std::env::var("AFL_KILL_SIGNAL") {
opt.kill_signal = Some(res.parse()?);
}
if let Ok(res) = std::env::var("AFL_PERSISTENT_RECORD") {
opt.persistent_record = res.parse()?;
}
if let Ok(res) = std::env::var("AFL_SYNC_TIME") {
opt.foreign_sync_interval = Duration::from_secs(res.parse::<u64>()? * 60);
} else {
Expand Down
1 change: 1 addition & 0 deletions fuzzers/libafl-fuzz/src/feedback/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod filepath;
pub mod persistent_record;
pub mod seed;
161 changes: 161 additions & 0 deletions fuzzers/libafl-fuzz/src/feedback/persistent_record.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
use std::{
borrow::Cow,
collections::VecDeque,
fmt::{Debug, Formatter},
marker::PhantomData,
};

use libafl::{
corpus::{Corpus, Testcase},
events::EventFirer,
executors::ExitKind,
feedbacks::{Feedback, FeedbackFactory},
inputs::Input,
observers::ObserversTuple,
state::{HasCorpus, State},
};
use libafl_bolts::{Error, Named};
use serde::{Deserialize, Serialize};

/// A [`PersitentRecordFeedback`] tracks the last N inputs that the fuzzer has run.
/// TODO: Kept in memory for now but should write to disk.
#[derive(Serialize, Deserialize)]
pub struct PersitentRecordFeedback<I, S>
where
S: State<Input = I>,
{
/// Vec that tracks the last `record_size` [`Input`]
record: VecDeque<I>,
record_size: usize,
phantomm: PhantomData<(I, S)>,
}

impl<I, S> PersitentRecordFeedback<I, S>
where
I: Input,
S: State<Input = I>,
{
/// Create a new [`PersitentRecordFeedback`].
pub fn new(record_size: usize) -> Self {
Self {
record_size,
record: VecDeque::default(),
phantomm: PhantomData,
}
}
}

impl<I, S, T> FeedbackFactory<PersitentRecordFeedback<I, S>, T> for PersitentRecordFeedback<I, S>
where
I: Input,
S: State<Input = I>,
{
fn create_feedback(&self, _ctx: &T) -> PersitentRecordFeedback<I, S> {
Self {
record_size: self.record_size.clone(),
record: self.record.clone(),
phantomm: self.phantomm,
}
}
}

impl<I, S> Named for PersitentRecordFeedback<I, S>
where
I: Input,
S: State<Input = I>,
{
fn name(&self) -> &Cow<'static, str> {
static NAME: Cow<'static, str> = Cow::Borrowed("PersitentRecordFeedback");
&NAME
}
}

impl<I, S> Debug for PersitentRecordFeedback<I, S>
where
I: Input,
S: State<Input = I>,
{
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PersitentRecordFeedback")
.finish_non_exhaustive()
}
}

impl<I, S> Feedback<S> for PersitentRecordFeedback<I, S>
where
S: State<Input = I> + HasCorpus,
I: Input,
{
#[allow(clippy::wrong_self_convention)]
#[inline]
fn is_interesting<EM, OT>(
&mut self,
_state: &mut S,
_manager: &mut EM,
input: &I,
_observers: &OT,
_exit_kind: &ExitKind,
) -> Result<bool, Error>
where
EM: EventFirer<State = S>,
{
if self.should_run() {
self.record.push_back(input.clone());
if self.record.len() == self.record_size {
self.record.pop_front();
}
}
Ok(false)
}

fn append_metadata<EM, OT>(
&mut self,
state: &mut S,
_manager: &mut EM,
_observers: &OT,
testcase: &mut Testcase<<S>::Input>,
) -> Result<(), Error>
where
OT: ObserversTuple<S>,
EM: EventFirer<State = S>,
{
if self.should_run() {
let file_path = testcase
.file_path()
.as_ref()
.expect("file path for the testcase must be set!");
let file_dir = file_path
.parent()
.expect("testcase must have a parent directory!");
// fetch the ID for this testcase
let id = state.corpus().peek_free_id().0;
let record = format!("RECORD:{id:0>6}");
// save all inputs in our persistent record
for (i, input) in self.record.iter().enumerate() {
let filename = file_dir.join(format!("{record},cnt{i:0>6}"));
input.to_file(file_dir.join(filename))?;
}
// rewrite this current testcase's filepath
let filename = format!("RECORD:{id:0>6},cnt:{0:0>6}", self.record.len());
*testcase.file_path_mut() = Some(file_dir.join(&filename));
*testcase.filename_mut() = Some(filename);
}
Ok(())
}

#[cfg(feature = "track_hit_feedbacks")]
#[inline]
fn last_result(&self) -> Result<bool, Error> {
Ok(false)
}
}

impl<I, S> PersitentRecordFeedback<I, S>
where
I: Input,
S: State<Input = I>,
{
fn should_run(&self) -> bool {
return self.record_size > 0;
}
}
9 changes: 7 additions & 2 deletions fuzzers/libafl-fuzz/src/fuzzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ use crate::{
afl_stats::AflStatsStage,
corpus::{set_corpus_filepath, set_solution_filepath},
env_parser::AFL_DEFAULT_MAP_SIZE,
feedback::{filepath::CustomFilepathToTestcaseFeedback, seed::SeedFeedback},
feedback::{
filepath::CustomFilepathToTestcaseFeedback, persistent_record::PersitentRecordFeedback,
seed::SeedFeedback,
},
mutational_stage::SupportedMutationalStages,
scheduler::SupportedSchedulers,
Opt, AFL_DEFAULT_INPUT_LEN_MAX, AFL_DEFAULT_INPUT_LEN_MIN, SHMEM_ENV_VAR,
Expand Down Expand Up @@ -119,6 +122,7 @@ where
* We check if it's a crash or a timeout (if we are configured to consider timeouts)
* The `CustomFilepathToTestcaseFeedback is used to adhere to AFL++'s corpus format.
* The `MaxMapFeedback` saves objectives only if they hit new edges
* Note: The order of the feedbacks matter!
* */
let mut objective = feedback_or!(
feedback_and!(
Expand All @@ -131,7 +135,8 @@ where
),
MaxMapFeedback::with_name("edges_objective", &edges_observer)
),
CustomFilepathToTestcaseFeedback::new(set_solution_filepath, fuzzer_dir.clone())
CustomFilepathToTestcaseFeedback::new(set_solution_filepath, fuzzer_dir.clone()),
PersitentRecordFeedback::new(opt.persistent_record),
);

// Initialize our State if necessary
Expand Down
6 changes: 4 additions & 2 deletions fuzzers/libafl-fuzz/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ const AFL_DEFAULT_INPUT_LEN_MAX: usize = 1_048_576;
const AFL_DEFAULT_INPUT_LEN_MIN: usize = 1;
const OUTPUT_GRACE: u64 = 25;
pub const AFL_DEFAULT_BROKER_PORT: u16 = 1337;
const PERSIST_SIG: &str = "##SIG_AFL_PERSISTENT##";
const DEFER_SIG: &str = "##SIG_AFL_DEFER_FORKSRV##";
const PERSIST_SIG: &str = "##SIG_AFL_PERSISTENT##\0";
const DEFER_SIG: &str = "##SIG_AFL_DEFER_FORKSRV##\0";
const SHMEM_ENV_VAR: &str = "__AFL_SHM_ID";
static AFL_HARNESS_FILE_INPUT: &str = "@@";

Expand Down Expand Up @@ -201,6 +201,8 @@ struct Opt {

#[clap(skip)]
foreign_sync_interval: Duration,
#[clap(skip)]
persistent_record: usize,

// TODO:
#[clap(skip)]
Expand Down
3 changes: 2 additions & 1 deletion fuzzers/libafl-fuzz/src/mutational_stage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ where
}
}

impl<S, SM, P, E, EM, M, I, Z> Stage<E, EM, Z> for SupportedMutationalStages<S, SM, P, E, EM, M, I, Z>
impl<S, SM, P, E, EM, M, I, Z> Stage<E, EM, Z>
for SupportedMutationalStages<S, SM, P, E, EM, M, I, Z>
where
E: UsesState<State = S>,
EM: UsesState<State = S>,
Expand Down

0 comments on commit b9da7dd

Please sign in to comment.