From 3e85cf22de7382ad1a61ab78c7a7a3e268e99ce9 Mon Sep 17 00:00:00 2001 From: Dominik Maier Date: Thu, 11 Nov 2021 01:49:38 +0100 Subject: [PATCH] Mutational Push Stage (#356) * initial commit for push stage * cleanup, no_std, clippy * clippy * fuzzes * readme * fmt --- fuzzers/push_stage_harness/.gitignore | 1 + fuzzers/push_stage_harness/Cargo.toml | 22 ++ fuzzers/push_stage_harness/README.md | 4 + fuzzers/push_stage_harness/src/main.rs | 128 +++++++++++ libafl/src/executors/mod.rs | 2 +- libafl/src/fuzzer/mod.rs | 2 +- libafl/src/stages/concolic.rs | 4 + libafl/src/stages/mod.rs | 4 +- libafl/src/stages/mutational.rs | 3 + libafl/src/stages/push/mod.rs | 12 ++ libafl/src/stages/push/mutational.rs | 280 +++++++++++++++++++++++++ libafl/src/stages/tracing.rs | 13 +- 12 files changed, 466 insertions(+), 9 deletions(-) create mode 100644 fuzzers/push_stage_harness/.gitignore create mode 100644 fuzzers/push_stage_harness/Cargo.toml create mode 100644 fuzzers/push_stage_harness/README.md create mode 100644 fuzzers/push_stage_harness/src/main.rs create mode 100644 libafl/src/stages/push/mod.rs create mode 100644 libafl/src/stages/push/mutational.rs diff --git a/fuzzers/push_stage_harness/.gitignore b/fuzzers/push_stage_harness/.gitignore new file mode 100644 index 0000000000..a977a2ca5b --- /dev/null +++ b/fuzzers/push_stage_harness/.gitignore @@ -0,0 +1 @@ +libpng-* \ No newline at end of file diff --git a/fuzzers/push_stage_harness/Cargo.toml b/fuzzers/push_stage_harness/Cargo.toml new file mode 100644 index 0000000000..e309d81351 --- /dev/null +++ b/fuzzers/push_stage_harness/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "push_stage_harness" +version = "0.5.0" +authors = ["Andrea Fioraldi ", "Dominik Maier "] +edition = "2021" + +[features] +default = ["std"] +std = [] + +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" +lto = true +codegen-units = 1 +opt-level = 3 +debug = true + +[dependencies] +libafl = { path = "../../libafl/" } diff --git a/fuzzers/push_stage_harness/README.md b/fuzzers/push_stage_harness/README.md new file mode 100644 index 0000000000..af99358e0d --- /dev/null +++ b/fuzzers/push_stage_harness/README.md @@ -0,0 +1,4 @@ +# Push Stage Harness + +This is a minimalistic example create a fuzzer that pulls data out of LibAFL, instead of being called by it repeatedly. +In contrast to Kloroutines, this should be reasonably sound and fast. \ No newline at end of file diff --git a/fuzzers/push_stage_harness/src/main.rs b/fuzzers/push_stage_harness/src/main.rs new file mode 100644 index 0000000000..ede15267d8 --- /dev/null +++ b/fuzzers/push_stage_harness/src/main.rs @@ -0,0 +1,128 @@ +//! A fuzzer that uses a `PushStage`, generating input to be subsequently executed, +//! instead of executing input iteslf in a loop. +//! Using this method, we can add LibAFL, for example, into an emulation loop +//! or use its mutations for another fuzzer. +//! This is a less hacky alternative to the `KloRoutines` based fuzzer, that will also work on non-`Unix`. + +use core::cell::{Cell, RefCell}; +use std::{path::PathBuf, rc::Rc}; + +use libafl::{ + bolts::{current_nanos, rands::StdRand, tuples::tuple_list}, + corpus::{ + Corpus, CorpusScheduler, InMemoryCorpus, OnDiskCorpus, QueueCorpusScheduler, Testcase, + }, + events::SimpleEventManager, + executors::ExitKind, + feedbacks::{CrashFeedback, MapFeedbackState, MaxMapFeedback}, + fuzzer::StdFuzzer, + inputs::{BytesInput, HasTargetBytes}, + mutators::scheduled::{havoc_mutations, StdScheduledMutator}, + observers::StdMapObserver, + stages::push::StdMutationalPushStage, + state::{HasCorpus, StdState}, + stats::SimpleStats, +}; + +/// Coverage map with explicit assignments due to the lack of instrumentation +static mut SIGNALS: [u8; 16] = [0; 16]; + +/// Assign a signal to the signals map +fn signals_set(idx: usize) { + unsafe { SIGNALS[idx] = 1 }; +} + +#[allow(clippy::similar_names)] +pub fn main() { + // Create an observation channel using the signals map + let observer = StdMapObserver::new("signals", unsafe { &mut SIGNALS }); + + // The state of the edges feedback. + let feedback_state = MapFeedbackState::with_observer(&observer); + + // Feedback to rate the interestingness of an input + let feedback = MaxMapFeedback::<_, BytesInput, _, _, _>::new(&feedback_state, &observer); + + // A feedback to choose if an input is a solution or not + let objective = CrashFeedback::new(); + + // create a State from scratch + let mut state = StdState::new( + // RNG + StdRand::with_seed(current_nanos()), + // Corpus that will be evolved, we keep it in memory for performance + InMemoryCorpus::new(), + // Corpus in which we store solutions (crashes in this example), + // on disk so the user can get them after stopping the fuzzer + OnDiskCorpus::new(PathBuf::from("./crashes")).unwrap(), + // States of the feedbacks. + // They are the data related to the feedbacks that you want to persist in the State. + tuple_list!(feedback_state), + ); + + // The Stats trait define how the fuzzer stats are reported to the user + let stats = SimpleStats::new(|s| println!("{}", s)); + + // The event manager handle the various events generated during the fuzzing loop + // such as the notification of the addition of a new item to the corpus + let mgr = SimpleEventManager::new(stats); + + // A queue policy to get testcasess from the corpus + let scheduler = QueueCorpusScheduler::new(); + + // Create the executor for an in-process function with just one observer + //let mut executor = InProcessExecutor::new(&mut harness, &mut fuzzer, &mut state, &mut mgr) + // .expect("Failed to create the Executor"); + + let testcase = Testcase::new(BytesInput::new(b"aaaa".to_vec())); + //self.feedback_mut().append_metadata(state, &mut testcase)?; + let idx = state.corpus_mut().add(testcase).unwrap(); + scheduler.on_add(&mut state, idx).unwrap(); + + // A fuzzer with feedbacks and a corpus scheduler + let fuzzer = StdFuzzer::new(scheduler, feedback, objective); + + // Generate 8 initial inputs + //state.generate_initial_inputs(&mut fuzzer, &mut executor, &mut generator, &mut mgr, 8); + // .expect("Failed to generate the initial corpus"); + + // Setup a mutational stage with a basic bytes mutator + let mutator = StdScheduledMutator::new(havoc_mutations()); + + let exit_kind = Rc::new(Cell::new(None)); + + let stage_idx = 0; + + let observers = tuple_list!(observer); + + // All fuzzer elements are hidden behind Rc>, so we can reuse them for multiple stages. + let push_stage = StdMutationalPushStage::new( + mutator, + Rc::new(RefCell::new(fuzzer)), + Rc::new(RefCell::new(state)), + Rc::new(RefCell::new(mgr)), + Rc::new(RefCell::new(observers)), + exit_kind.clone(), + stage_idx, + ); + + // Loop, the input, getting a new entry from the push stage each iteration. + for input in push_stage { + let input = input.unwrap(); + let target = input.target_bytes(); + let buf = target.as_slice(); + signals_set(0); + if !buf.is_empty() && buf[0] == b'a' { + signals_set(1); + if buf.len() > 1 && buf[1] == b'b' { + signals_set(2); + if buf.len() > 2 && buf[2] == b'c' { + panic!("=)"); + } + } + } + (*exit_kind).replace(Some(ExitKind::Ok)); + } + + println!("One iteration done."); +} diff --git a/libafl/src/executors/mod.rs b/libafl/src/executors/mod.rs index 88058eb387..f4c79c8b1e 100644 --- a/libafl/src/executors/mod.rs +++ b/libafl/src/executors/mod.rs @@ -40,7 +40,7 @@ use crate::{ use serde::{Deserialize, Serialize}; /// How an execution finished. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub enum ExitKind { /// The run exited normally. Ok, diff --git a/libafl/src/fuzzer/mod.rs b/libafl/src/fuzzer/mod.rs index fd77a9e190..403400f164 100644 --- a/libafl/src/fuzzer/mod.rs +++ b/libafl/src/fuzzer/mod.rs @@ -385,7 +385,7 @@ where Event::NewTestcase { input, observers_buf, - exit_kind: exit_kind.clone(), + exit_kind: *exit_kind, corpus_size: state.corpus().count(), client_config: manager.configuration(), time: current_time(), diff --git a/libafl/src/stages/concolic.rs b/libafl/src/stages/concolic.rs index 1fc7656f85..909768b67c 100644 --- a/libafl/src/stages/concolic.rs +++ b/libafl/src/stages/concolic.rs @@ -1,3 +1,7 @@ +//! This module contains the `concolic` stages, which can trace a target using symbolic execution +//! and use the results for fuzzer input and mutations. +//! + use core::marker::PhantomData; use crate::{ diff --git a/libafl/src/stages/mod.rs b/libafl/src/stages/mod.rs index 28ec5e7a75..e2d740f68a 100644 --- a/libafl/src/stages/mod.rs +++ b/libafl/src/stages/mod.rs @@ -8,6 +8,8 @@ Other stages may enrich [`crate::corpus::Testcase`]s with metadata. pub mod mutational; pub use mutational::{MutationalStage, StdMutationalStage}; +pub mod push; + pub mod tracing; pub use tracing::{ShadowTracingStage, TracingStage}; @@ -15,7 +17,6 @@ pub mod calibrate; pub use calibrate::{CalibrationStage, PowerScheduleMetadata}; pub mod power; -use crate::Error; pub use power::PowerMutationalStage; #[cfg(feature = "std")] @@ -25,6 +26,7 @@ pub use concolic::ConcolicTracingStage; #[cfg(feature = "std")] pub use concolic::SimpleConcolicMutationalStage; +use crate::Error; /// A stage is one step in the fuzzing process. /// Multiple stages will be scheduled one by one for each input. pub trait Stage { diff --git a/libafl/src/stages/mutational.rs b/libafl/src/stages/mutational.rs index 0d5f73806f..f81dfe28a1 100644 --- a/libafl/src/stages/mutational.rs +++ b/libafl/src/stages/mutational.rs @@ -1,3 +1,6 @@ +//| The [`MutationalStage`] is the default stage used during fuzzing. +//! For the current input, it will perform a range of random mutations, and then run them in the executor. + use core::marker::PhantomData; use crate::{ diff --git a/libafl/src/stages/push/mod.rs b/libafl/src/stages/push/mod.rs new file mode 100644 index 0000000000..5db0f13702 --- /dev/null +++ b/libafl/src/stages/push/mod.rs @@ -0,0 +1,12 @@ +//! While normal stages call the executor over and over again, push stages turn this concept upside down: +//! A push stage instead returns an iterator that generates a new result for each time it gets called. +//! With the new testcase, you will have to take care about testcase execution, manually. +//! The push stage relies on internal muttability of the supplied `Observers`. +//! + +/// Mutational stage is the normal fuzzing stage, +pub mod mutational; +pub use mutational::StdMutationalPushStage; + +/// A push stage is a generator that returns a single testcase for each call. +pub trait PushStage: Iterator {} diff --git a/libafl/src/stages/push/mutational.rs b/libafl/src/stages/push/mutational.rs new file mode 100644 index 0000000000..d657935d4a --- /dev/null +++ b/libafl/src/stages/push/mutational.rs @@ -0,0 +1,280 @@ +//| The [`MutationalStage`] is the default stage used during fuzzing. +//! For the current input, it will perform a range of random mutations, and then run them in the executor. + +use alloc::rc::Rc; +use core::{ + borrow::BorrowMut, + cell::{Cell, RefCell}, + marker::PhantomData, + time::Duration, +}; + +use crate::{ + bolts::{current_time, rands::Rand}, + corpus::{Corpus, CorpusScheduler}, + events::EventManager, + executors::ExitKind, + inputs::Input, + mark_feature_time, + mutators::Mutator, + observers::ObserversTuple, + start_timer, + state::{HasClientPerfStats, HasCorpus, HasRand}, + Error, EvaluatorObservers, ExecutionProcessor, Fuzzer, HasCorpusScheduler, +}; + +#[cfg(feature = "introspection")] +use crate::stats::PerfFeature; + +/// Send a stats update all 15 (or more) seconds +const STATS_TIMEOUT_DEFAULT: Duration = Duration::from_secs(15); + +pub static DEFAULT_MUTATIONAL_MAX_ITERATIONS: u64 = 128; + +/// A Mutational push stage is the stage in a fuzzing run that mutates inputs. +/// Mutational push stages will usually have a range of mutations that are +/// being applied to the input one by one, between executions. +/// The push version, in contrast to the normal stage, will return each testcase, instead of executing it. +/// +/// Default value, how many iterations each stage gets, as an upper bound +/// It may randomly continue earlier. +/// +/// The default mutational push stage +#[derive(Clone, Debug)] +pub struct StdMutationalPushStage +where + C: Corpus, + CS: CorpusScheduler, + EM: EventManager<(), I, S, Z>, + I: Input, + M: Mutator, + OT: ObserversTuple, + R: Rand, + S: HasClientPerfStats + HasCorpus + HasRand, + Z: ExecutionProcessor + + EvaluatorObservers + + Fuzzer<(), EM, I, S, ()> + + HasCorpusScheduler, +{ + initialized: bool, + state: Rc>, + current_iter: Option, + current_corpus_idx: Option, + testcases_to_do: usize, + testcases_done: usize, + + fuzzer: Rc>, + event_mgr: Rc>, + + current_input: Option, // Todo: Get rid of copy + + stage_idx: i32, + + mutator: M, + #[allow(clippy::type_complexity)] + phantom: PhantomData<(C, CS, (), EM, I, R, OT, S, Z)>, + last_stats_time: Duration, + observers: Rc>, + exit_kind: Rc>>, +} + +impl StdMutationalPushStage +where + C: Corpus, + CS: CorpusScheduler, + EM: EventManager<(), I, S, Z>, + I: Input, + M: Mutator, + OT: ObserversTuple, + R: Rand, + S: HasClientPerfStats + HasCorpus + HasRand, + Z: ExecutionProcessor + + EvaluatorObservers + + Fuzzer<(), EM, I, S, ()> + + HasCorpusScheduler, +{ + /// Gets the number of iterations as a random number + #[allow(clippy::unused_self, clippy::unnecessary_wraps)] // TODO: we should put this function into a trait later + fn iterations(&self, state: &mut S, _corpus_idx: usize) -> Result { + Ok(1 + state.rand_mut().below(DEFAULT_MUTATIONAL_MAX_ITERATIONS) as usize) + } + + pub fn set_current_corpus_idx(&mut self, current_corpus_idx: usize) { + self.current_corpus_idx = Some(current_corpus_idx); + } + + /// Creates a new default mutational stage + fn init(&mut self) -> Result<(), Error> { + let state: &mut S = &mut (*self.state).borrow_mut(); + + // Find a testcase to work on, unless someone already set it + self.current_corpus_idx = Some(if let Some(corpus_idx) = self.current_corpus_idx { + corpus_idx + } else { + let fuzzer: &mut Z = &mut (*self.fuzzer).borrow_mut(); + fuzzer.scheduler().next(state)? + }); + + self.testcases_to_do = self.iterations(state, self.current_corpus_idx.unwrap())?; + self.testcases_done = 0; + Ok(()) + } + + fn pre_exec(&mut self) -> Option> { + let state: &mut S = &mut (*self.state).borrow_mut(); + + if self.testcases_done >= self.testcases_to_do { + // finished with this cicle. + return None; + } + + start_timer!(state); + let mut input = state + .corpus() + .get(self.current_corpus_idx.unwrap()) + .unwrap() + .borrow_mut() + .load_input() + .unwrap() + .clone(); + mark_feature_time!(state, PerfFeature::GetInputFromCorpus); + + start_timer!(state); + self.mutator + .mutate(state, &mut input, self.stage_idx as i32) + .unwrap(); + mark_feature_time!(state, PerfFeature::Mutate); + + self.current_input = Some(input.clone()); // TODO: Get rid of this + + Some(Ok(input)) + } + + fn post_exec(&mut self) -> Result<(), Error> { + // todo: isintersting, etc. + + let state: &mut S = &mut (*self.state).borrow_mut(); + + let fuzzer: &mut Z = &mut (*self.fuzzer).borrow_mut(); + let event_mgr: &mut EM = &mut (*self.event_mgr).borrow_mut(); + let observers_refcell: &RefCell = self.observers.borrow_mut(); + let observers: &mut OT = &mut observers_refcell.borrow_mut(); + + fuzzer.process_execution( + state, + event_mgr, + self.current_input.take().unwrap(), + observers, + &self.exit_kind.get().unwrap(), + true, + )?; + + start_timer!(state); + self.mutator + .post_exec(state, self.stage_idx as i32, Some(self.testcases_done))?; + mark_feature_time!(state, PerfFeature::MutatePostExec); + self.testcases_done += 1; + + Ok(()) + } +} + +impl Iterator for StdMutationalPushStage +where + C: Corpus, + CS: CorpusScheduler, + EM: EventManager<(), I, S, Z>, + I: Input, + M: Mutator, + OT: ObserversTuple, + R: Rand, + S: HasClientPerfStats + HasCorpus + HasRand, + Z: ExecutionProcessor + + EvaluatorObservers + + Fuzzer<(), EM, I, S, ()> + + HasCorpusScheduler, +{ + type Item = Result; + + fn next(&mut self) -> Option> { + let step_success = if self.initialized { + // We already ran once + self.post_exec() + } else { + self.init() // TODO: Corpus idx + }; + if let Err(err) = step_success { + //let errored = true; + return Some(Err(err)); + } + + //for i in 0..num { + let ret = self.pre_exec(); + if ret.is_none() { + // We're done. + self.initialized = false; + self.current_corpus_idx = None; + + let state: &mut S = &mut (*self.state).borrow_mut(); + //let fuzzer: &mut Z = &mut (*self.fuzzer).borrow_mut(); + let event_mgr: &mut EM = &mut (*self.event_mgr).borrow_mut(); + + self.last_stats_time = Z::maybe_report_stats( + state, + event_mgr, + self.last_stats_time, + STATS_TIMEOUT_DEFAULT, + ) + .unwrap(); + //self.fuzzer.maybe_report_stats(); + } else { + self.exit_kind.replace(None); + } + ret + } +} + +impl StdMutationalPushStage +where + C: Corpus, + CS: CorpusScheduler, + EM: EventManager<(), I, S, Z>, + I: Input, + M: Mutator, + OT: ObserversTuple, + R: Rand, + S: HasClientPerfStats + HasCorpus + HasRand, + Z: ExecutionProcessor + + EvaluatorObservers + + Fuzzer<(), EM, I, S, ()> + + HasCorpusScheduler, +{ + /// Creates a new default mutational stage + pub fn new( + mutator: M, + fuzzer: Rc>, + state: Rc>, + event_mgr: Rc>, + observers: Rc>, + exit_kind: Rc>>, + stage_idx: i32, + ) -> Self { + Self { + mutator, + phantom: PhantomData, + initialized: false, + state, + current_iter: None, + current_corpus_idx: None, // todo + testcases_to_do: 0, + testcases_done: 0, + current_input: None, + stage_idx, + fuzzer, + event_mgr, + observers, + exit_kind, + last_stats_time: current_time(), + } + } +} diff --git a/libafl/src/stages/tracing.rs b/libafl/src/stages/tracing.rs index 5545731a17..d0c004eac7 100644 --- a/libafl/src/stages/tracing.rs +++ b/libafl/src/stages/tracing.rs @@ -1,4 +1,6 @@ -use core::{marker::PhantomData, mem::drop}; +//! The tracing stage can trace the target and enrich a testcase with metadata, for example for `CmpLog`. + +use core::marker::PhantomData; use crate::{ corpus::Corpus, @@ -63,10 +65,9 @@ where mark_feature_time!(state, PerfFeature::PreExecObservers); start_timer!(state); - drop( - self.tracer_executor - .run_target(fuzzer, state, manager, &input)?, - ); + let _ = self + .tracer_executor + .run_target(fuzzer, state, manager, &input)?; mark_feature_time!(state, PerfFeature::TargetExecution); *state.executions_mut() += 1; @@ -145,7 +146,7 @@ where mark_feature_time!(state, PerfFeature::PreExecObservers); start_timer!(state); - drop(executor.run_target(fuzzer, state, manager, &input)?); + let _ = executor.run_target(fuzzer, state, manager, &input)?; mark_feature_time!(state, PerfFeature::TargetExecution); *state.executions_mut() += 1;