From b9da7dd87f0edbc322db7250f34557afdcfd65da Mon Sep 17 00:00:00 2001 From: Aarnav Date: Wed, 17 Jul 2024 00:35:06 +0200 Subject: [PATCH] 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 --- fuzzers/libafl-fuzz/README.md | 2 +- fuzzers/libafl-fuzz/src/env_parser.rs | 3 + fuzzers/libafl-fuzz/src/feedback/mod.rs | 1 + .../src/feedback/persistent_record.rs | 161 ++++++++++++++++++ fuzzers/libafl-fuzz/src/fuzzer.rs | 9 +- fuzzers/libafl-fuzz/src/main.rs | 6 +- fuzzers/libafl-fuzz/src/mutational_stage.rs | 3 +- 7 files changed, 179 insertions(+), 6 deletions(-) create mode 100644 fuzzers/libafl-fuzz/src/feedback/persistent_record.rs diff --git a/fuzzers/libafl-fuzz/README.md b/fuzzers/libafl-fuzz/README.md index 4e1b3e3109..47884f07e3 100644 --- a/fuzzers/libafl-fuzz/README.md +++ b/fuzzers/libafl-fuzz/README.md @@ -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 diff --git a/fuzzers/libafl-fuzz/src/env_parser.rs b/fuzzers/libafl-fuzz/src/env_parser.rs index 684b377176..c1e6f385da 100644 --- a/fuzzers/libafl-fuzz/src/env_parser.rs +++ b/fuzzers/libafl-fuzz/src/env_parser.rs @@ -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::()? * 60); } else { diff --git a/fuzzers/libafl-fuzz/src/feedback/mod.rs b/fuzzers/libafl-fuzz/src/feedback/mod.rs index a7f4209910..7b824a9269 100644 --- a/fuzzers/libafl-fuzz/src/feedback/mod.rs +++ b/fuzzers/libafl-fuzz/src/feedback/mod.rs @@ -1,2 +1,3 @@ pub mod filepath; +pub mod persistent_record; pub mod seed; diff --git a/fuzzers/libafl-fuzz/src/feedback/persistent_record.rs b/fuzzers/libafl-fuzz/src/feedback/persistent_record.rs new file mode 100644 index 0000000000..3c70bf4447 --- /dev/null +++ b/fuzzers/libafl-fuzz/src/feedback/persistent_record.rs @@ -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 +where + S: State, +{ + /// Vec that tracks the last `record_size` [`Input`] + record: VecDeque, + record_size: usize, + phantomm: PhantomData<(I, S)>, +} + +impl PersitentRecordFeedback +where + I: Input, + S: State, +{ + /// Create a new [`PersitentRecordFeedback`]. + pub fn new(record_size: usize) -> Self { + Self { + record_size, + record: VecDeque::default(), + phantomm: PhantomData, + } + } +} + +impl FeedbackFactory, T> for PersitentRecordFeedback +where + I: Input, + S: State, +{ + fn create_feedback(&self, _ctx: &T) -> PersitentRecordFeedback { + Self { + record_size: self.record_size.clone(), + record: self.record.clone(), + phantomm: self.phantomm, + } + } +} + +impl Named for PersitentRecordFeedback +where + I: Input, + S: State, +{ + fn name(&self) -> &Cow<'static, str> { + static NAME: Cow<'static, str> = Cow::Borrowed("PersitentRecordFeedback"); + &NAME + } +} + +impl Debug for PersitentRecordFeedback +where + I: Input, + S: State, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("PersitentRecordFeedback") + .finish_non_exhaustive() + } +} + +impl Feedback for PersitentRecordFeedback +where + S: State + HasCorpus, + I: Input, +{ + #[allow(clippy::wrong_self_convention)] + #[inline] + fn is_interesting( + &mut self, + _state: &mut S, + _manager: &mut EM, + input: &I, + _observers: &OT, + _exit_kind: &ExitKind, + ) -> Result + where + EM: EventFirer, + { + 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( + &mut self, + state: &mut S, + _manager: &mut EM, + _observers: &OT, + testcase: &mut Testcase<::Input>, + ) -> Result<(), Error> + where + OT: ObserversTuple, + EM: EventFirer, + { + 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 { + Ok(false) + } +} + +impl PersitentRecordFeedback +where + I: Input, + S: State, +{ + fn should_run(&self) -> bool { + return self.record_size > 0; + } +} diff --git a/fuzzers/libafl-fuzz/src/fuzzer.rs b/fuzzers/libafl-fuzz/src/fuzzer.rs index 2945b34aab..392315bfb7 100644 --- a/fuzzers/libafl-fuzz/src/fuzzer.rs +++ b/fuzzers/libafl-fuzz/src/fuzzer.rs @@ -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, @@ -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!( @@ -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 diff --git a/fuzzers/libafl-fuzz/src/main.rs b/fuzzers/libafl-fuzz/src/main.rs index f9e63b039b..ac2bc00f15 100644 --- a/fuzzers/libafl-fuzz/src/main.rs +++ b/fuzzers/libafl-fuzz/src/main.rs @@ -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 = "@@"; @@ -201,6 +201,8 @@ struct Opt { #[clap(skip)] foreign_sync_interval: Duration, + #[clap(skip)] + persistent_record: usize, // TODO: #[clap(skip)] diff --git a/fuzzers/libafl-fuzz/src/mutational_stage.rs b/fuzzers/libafl-fuzz/src/mutational_stage.rs index cdd0af9730..3f5bef3dd9 100644 --- a/fuzzers/libafl-fuzz/src/mutational_stage.rs +++ b/fuzzers/libafl-fuzz/src/mutational_stage.rs @@ -74,7 +74,8 @@ where } } -impl Stage for SupportedMutationalStages +impl Stage + for SupportedMutationalStages where E: UsesState, EM: UsesState,