Introduce Persistent Record for libafl-fuzz (#2411)
* libafl-fuzz: fix PERSISTENT_SIG and DEFERRED_SIG * libafl-fuzz: add AFL_PERSISTENT_RECORD * libafl-fuzz: update README
This commit is contained in:
parent
713652e5d8
commit
b9da7dd87f
@ -44,7 +44,7 @@ Rewrite of afl-fuzz in Rust.
|
|||||||
- [ ] AFL_FAST_CAL
|
- [ ] AFL_FAST_CAL
|
||||||
- [ ] AFL_NO_CRASH_README
|
- [ ] AFL_NO_CRASH_README
|
||||||
- [ ] AFL_KEEP_TIMEOUTS
|
- [ ] AFL_KEEP_TIMEOUTS
|
||||||
- [ ] AFL_PERSISTENT_RECORD
|
- [x] AFL_PERSISTENT_RECORD
|
||||||
- [ ] AFL_TESTCACHE_SIZE
|
- [ ] AFL_TESTCACHE_SIZE
|
||||||
- [ ] AFL_NO_ARITH
|
- [ ] AFL_NO_ARITH
|
||||||
- [ ] AFL_DISABLE_TRIM
|
- [ ] AFL_DISABLE_TRIM
|
||||||
|
@ -102,6 +102,9 @@ pub fn parse_envs(opt: &mut Opt) -> Result<(), Error> {
|
|||||||
if let Ok(res) = std::env::var("AFL_KILL_SIGNAL") {
|
if let Ok(res) = std::env::var("AFL_KILL_SIGNAL") {
|
||||||
opt.kill_signal = Some(res.parse()?);
|
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") {
|
if let Ok(res) = std::env::var("AFL_SYNC_TIME") {
|
||||||
opt.foreign_sync_interval = Duration::from_secs(res.parse::<u64>()? * 60);
|
opt.foreign_sync_interval = Duration::from_secs(res.parse::<u64>()? * 60);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
pub mod filepath;
|
pub mod filepath;
|
||||||
|
pub mod persistent_record;
|
||||||
pub mod seed;
|
pub mod seed;
|
||||||
|
161
fuzzers/libafl-fuzz/src/feedback/persistent_record.rs
Normal file
161
fuzzers/libafl-fuzz/src/feedback/persistent_record.rs
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -43,7 +43,10 @@ use crate::{
|
|||||||
afl_stats::AflStatsStage,
|
afl_stats::AflStatsStage,
|
||||||
corpus::{set_corpus_filepath, set_solution_filepath},
|
corpus::{set_corpus_filepath, set_solution_filepath},
|
||||||
env_parser::AFL_DEFAULT_MAP_SIZE,
|
env_parser::AFL_DEFAULT_MAP_SIZE,
|
||||||
feedback::{filepath::CustomFilepathToTestcaseFeedback, seed::SeedFeedback},
|
feedback::{
|
||||||
|
filepath::CustomFilepathToTestcaseFeedback, persistent_record::PersitentRecordFeedback,
|
||||||
|
seed::SeedFeedback,
|
||||||
|
},
|
||||||
mutational_stage::SupportedMutationalStages,
|
mutational_stage::SupportedMutationalStages,
|
||||||
scheduler::SupportedSchedulers,
|
scheduler::SupportedSchedulers,
|
||||||
Opt, AFL_DEFAULT_INPUT_LEN_MAX, AFL_DEFAULT_INPUT_LEN_MIN, SHMEM_ENV_VAR,
|
Opt, AFL_DEFAULT_INPUT_LEN_MAX, AFL_DEFAULT_INPUT_LEN_MIN, SHMEM_ENV_VAR,
|
||||||
@ -119,6 +122,7 @@ where
|
|||||||
* We check if it's a crash or a timeout (if we are configured to consider timeouts)
|
* 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 `CustomFilepathToTestcaseFeedback is used to adhere to AFL++'s corpus format.
|
||||||
* The `MaxMapFeedback` saves objectives only if they hit new edges
|
* The `MaxMapFeedback` saves objectives only if they hit new edges
|
||||||
|
* Note: The order of the feedbacks matter!
|
||||||
* */
|
* */
|
||||||
let mut objective = feedback_or!(
|
let mut objective = feedback_or!(
|
||||||
feedback_and!(
|
feedback_and!(
|
||||||
@ -131,7 +135,8 @@ where
|
|||||||
),
|
),
|
||||||
MaxMapFeedback::with_name("edges_objective", &edges_observer)
|
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
|
// Initialize our State if necessary
|
||||||
|
@ -37,8 +37,8 @@ const AFL_DEFAULT_INPUT_LEN_MAX: usize = 1_048_576;
|
|||||||
const AFL_DEFAULT_INPUT_LEN_MIN: usize = 1;
|
const AFL_DEFAULT_INPUT_LEN_MIN: usize = 1;
|
||||||
const OUTPUT_GRACE: u64 = 25;
|
const OUTPUT_GRACE: u64 = 25;
|
||||||
pub const AFL_DEFAULT_BROKER_PORT: u16 = 1337;
|
pub const AFL_DEFAULT_BROKER_PORT: u16 = 1337;
|
||||||
const PERSIST_SIG: &str = "##SIG_AFL_PERSISTENT##";
|
const PERSIST_SIG: &str = "##SIG_AFL_PERSISTENT##\0";
|
||||||
const DEFER_SIG: &str = "##SIG_AFL_DEFER_FORKSRV##";
|
const DEFER_SIG: &str = "##SIG_AFL_DEFER_FORKSRV##\0";
|
||||||
const SHMEM_ENV_VAR: &str = "__AFL_SHM_ID";
|
const SHMEM_ENV_VAR: &str = "__AFL_SHM_ID";
|
||||||
static AFL_HARNESS_FILE_INPUT: &str = "@@";
|
static AFL_HARNESS_FILE_INPUT: &str = "@@";
|
||||||
|
|
||||||
@ -201,6 +201,8 @@ struct Opt {
|
|||||||
|
|
||||||
#[clap(skip)]
|
#[clap(skip)]
|
||||||
foreign_sync_interval: Duration,
|
foreign_sync_interval: Duration,
|
||||||
|
#[clap(skip)]
|
||||||
|
persistent_record: usize,
|
||||||
|
|
||||||
// TODO:
|
// TODO:
|
||||||
#[clap(skip)]
|
#[clap(skip)]
|
||||||
|
@ -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
|
where
|
||||||
E: UsesState<State = S>,
|
E: UsesState<State = S>,
|
||||||
EM: UsesState<State = S>,
|
EM: UsesState<State = S>,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user