Mutational Push Stage (#356)

* initial commit for push stage

* cleanup, no_std, clippy

* clippy

* fuzzes

* readme

* fmt
This commit is contained in:
Dominik Maier 2021-11-11 01:49:38 +01:00 committed by GitHub
parent e914cc9c14
commit 3e85cf22de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 466 additions and 9 deletions

1
fuzzers/push_stage_harness/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
libpng-*

View File

@ -0,0 +1,22 @@
[package]
name = "push_stage_harness"
version = "0.5.0"
authors = ["Andrea Fioraldi <andreafioraldi@gmail.com>", "Dominik Maier <domenukk@gmail.com>"]
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/" }

View File

@ -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.

View File

@ -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<RefCell>>, 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.");
}

View File

@ -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,

View File

@ -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(),

View File

@ -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::{

View File

@ -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<E, EM, S, Z> {

View File

@ -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::{

View File

@ -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<E, EM, S, Z>: Iterator {}

View File

@ -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<C, CS, EM, I, M, OT, R, S, Z>
where
C: Corpus<I>,
CS: CorpusScheduler<I, S>,
EM: EventManager<(), I, S, Z>,
I: Input,
M: Mutator<I, S>,
OT: ObserversTuple<I, S>,
R: Rand,
S: HasClientPerfStats + HasCorpus<C, I> + HasRand<R>,
Z: ExecutionProcessor<I, OT, S>
+ EvaluatorObservers<I, OT, S>
+ Fuzzer<(), EM, I, S, ()>
+ HasCorpusScheduler<CS, I, S>,
{
initialized: bool,
state: Rc<RefCell<S>>,
current_iter: Option<usize>,
current_corpus_idx: Option<usize>,
testcases_to_do: usize,
testcases_done: usize,
fuzzer: Rc<RefCell<Z>>,
event_mgr: Rc<RefCell<EM>>,
current_input: Option<I>, // 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<RefCell<OT>>,
exit_kind: Rc<Cell<Option<ExitKind>>>,
}
impl<C, CS, EM, I, M, OT, R, S, Z> StdMutationalPushStage<C, CS, EM, I, M, OT, R, S, Z>
where
C: Corpus<I>,
CS: CorpusScheduler<I, S>,
EM: EventManager<(), I, S, Z>,
I: Input,
M: Mutator<I, S>,
OT: ObserversTuple<I, S>,
R: Rand,
S: HasClientPerfStats + HasCorpus<C, I> + HasRand<R>,
Z: ExecutionProcessor<I, OT, S>
+ EvaluatorObservers<I, OT, S>
+ Fuzzer<(), EM, I, S, ()>
+ HasCorpusScheduler<CS, I, S>,
{
/// 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<usize, Error> {
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<Result<I, Error>> {
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<OT> = 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<C, CS, EM, I, M, OT, R, S, Z> Iterator for StdMutationalPushStage<C, CS, EM, I, M, OT, R, S, Z>
where
C: Corpus<I>,
CS: CorpusScheduler<I, S>,
EM: EventManager<(), I, S, Z>,
I: Input,
M: Mutator<I, S>,
OT: ObserversTuple<I, S>,
R: Rand,
S: HasClientPerfStats + HasCorpus<C, I> + HasRand<R>,
Z: ExecutionProcessor<I, OT, S>
+ EvaluatorObservers<I, OT, S>
+ Fuzzer<(), EM, I, S, ()>
+ HasCorpusScheduler<CS, I, S>,
{
type Item = Result<I, Error>;
fn next(&mut self) -> Option<Result<I, Error>> {
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<C, CS, EM, I, M, OT, R, S, Z> StdMutationalPushStage<C, CS, EM, I, M, OT, R, S, Z>
where
C: Corpus<I>,
CS: CorpusScheduler<I, S>,
EM: EventManager<(), I, S, Z>,
I: Input,
M: Mutator<I, S>,
OT: ObserversTuple<I, S>,
R: Rand,
S: HasClientPerfStats + HasCorpus<C, I> + HasRand<R>,
Z: ExecutionProcessor<I, OT, S>
+ EvaluatorObservers<I, OT, S>
+ Fuzzer<(), EM, I, S, ()>
+ HasCorpusScheduler<CS, I, S>,
{
/// Creates a new default mutational stage
pub fn new(
mutator: M,
fuzzer: Rc<RefCell<Z>>,
state: Rc<RefCell<S>>,
event_mgr: Rc<RefCell<EM>>,
observers: Rc<RefCell<OT>>,
exit_kind: Rc<Cell<Option<ExitKind>>>,
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(),
}
}
}

View File

@ -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;