rand and queue corpus schedulers

This commit is contained in:
Andrea Fioraldi 2021-02-22 14:13:00 +01:00
parent ff626a124b
commit 3b0883721e
9 changed files with 155 additions and 264 deletions

View File

@ -1,74 +1,74 @@
//! In-memory corpus, keeps all test cases in memory at all times //! In-memory corpus, keeps all test cases in memory at all times
use alloc::{borrow::ToOwned, vec::Vec}; use alloc::vec::Vec;
use core::{cell::RefCell, marker::PhantomData}; use core::cell::RefCell;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::{corpus::Corpus, corpus::Testcase, inputs::Input, Error};
corpus::Corpus, corpus::HasTestcaseVec, corpus::Testcase, inputs::Input, utils::Rand, Error,
};
/// A corpus handling all important fuzzing in memory. /// A corpus handling all in memory.
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Default, Serialize, Deserialize, Clone, Debug)]
#[serde(bound = "I: serde::de::DeserializeOwned")] #[serde(bound = "I: serde::de::DeserializeOwned")]
pub struct InMemoryCorpus<I, R> pub struct InMemoryCorpus<I>
where where
I: Input, I: Input,
R: Rand,
{ {
entries: Vec<RefCell<Testcase<I>>>, entries: Vec<RefCell<Testcase<I>>>,
pos: usize, current: Option<usize>,
phantom: PhantomData<R>,
} }
impl<I, R> HasTestcaseVec<I> for InMemoryCorpus<I, R> impl<I> Corpus<I> for InMemoryCorpus<I>
where where
I: Input, I: Input,
R: Rand,
{ {
fn entries(&self) -> &[RefCell<Testcase<I>>] { /// Returns the number of elements
&self.entries
}
fn entries_mut(&mut self) -> &mut Vec<RefCell<Testcase<I>>> {
&mut self.entries
}
}
impl<I, R> Corpus<I, R> for InMemoryCorpus<I, R>
where
I: Input,
R: Rand,
{
/// Gets the next entry
#[inline] #[inline]
fn next(&mut self, rand: &mut R) -> Result<(&RefCell<Testcase<I>>, usize), Error> { fn count(&self) -> usize {
if self.count() == 0 { self.entries.len()
Err(Error::Empty("No entries in corpus".to_owned())) }
/// Add an entry to the corpus and return its index
#[inline]
fn add(&mut self, testcase: Testcase<I>) -> Result<usize, Error> {
self.entries.push(RefCell::new(testcase));
Ok(self.entries.len() - 1)
}
/// Replaces the testcase at the given idx
#[inline]
fn replace(&mut self, idx: usize, testcase: Testcase<I>) -> Result<(), Error> {
if idx >= self.entries.len() {
return Err(Error::KeyNotFound(format!("Index {} out of bounds", idx)));
}
self.entries[idx] = RefCell::new(testcase);
Ok(())
}
/// Removes an entry from the corpus, returning it if it was present.
#[inline]
fn remove(&mut self, idx: usize) -> Result<Option<Testcase<I>>, Error> {
if idx >= self.entries.len() {
Ok(None)
} else { } else {
let len = { self.entries().len() }; Ok(Some(self.entries.remove(idx).into_inner()))
let id = rand.below(len as u64) as usize;
self.pos = id;
Ok((self.get(id), id))
} }
} }
/// Returns the testacase we currently use /// Get by id
#[inline] #[inline]
fn current_testcase(&self) -> (&RefCell<Testcase<I>>, usize) { fn get(&self, idx: usize) -> Result<&RefCell<Testcase<I>>, Error> {
(self.get(self.pos), self.pos) Ok(&self.entries[idx])
} }
}
impl<I, R> InMemoryCorpus<I, R> /// Current testcase scheduled
where #[inline]
I: Input, fn current(&self) -> &Option<usize> {
R: Rand, &self.current
{ }
pub fn new() -> Self {
Self { /// Current testcase scheduled (mut)
entries: vec![], #[inline]
pos: 0, fn current_mut(&mut self) -> &mut Option<usize> {
phantom: PhantomData, &mut self.current
}
} }
} }

View File

@ -3,11 +3,21 @@
pub mod testcase; pub mod testcase;
pub use testcase::Testcase; pub use testcase::Testcase;
use alloc::vec::Vec; pub mod inmemory;
use core::cell::RefCell; pub use inmemory::InMemoryCorpus;
use serde::{Deserialize, Serialize};
use crate::{inputs::Input, Error}; pub mod queue;
pub use queue::QueueCorpusScheduler;
use core::cell::RefCell;
use core::marker::PhantomData;
use crate::{
inputs::Input,
state::{HasCorpus, HasRand},
utils::Rand,
Error,
};
/// Corpus with all current testcases /// Corpus with all current testcases
pub trait Corpus<I>: serde::Serialize + serde::de::DeserializeOwned pub trait Corpus<I>: serde::Serialize + serde::de::DeserializeOwned
@ -46,7 +56,12 @@ where
} }
/// Replaces the testcase at the given idx /// Replaces the testcase at the given idx
fn on_replace(&self, _state: &mut S, _idx: usize, _testcase: &Testcase<I>) -> Result<(), Error> { fn on_replace(
&self,
_state: &mut S,
_idx: usize,
_testcase: &Testcase<I>,
) -> Result<(), Error> {
Ok(()) Ok(())
} }
@ -65,89 +80,34 @@ where
fn next(&self, state: &mut S) -> Result<usize, Error>; fn next(&self, state: &mut S) -> Result<usize, Error>;
} }
/* pub struct RandCorpusScheduler<C, I, R, S>
pub struct RandCorpusScheduler {} where
S: HasCorpus<C, I> + HasRand<R>,
C: Corpus<I>,
I: Input,
R: Rand,
{
phantom: PhantomData<(C, I, R, S)>,
}
impl CorpusScheduler for RandCorpusScheduler { impl<C, I, R, S> CorpusScheduler<I, S> for RandCorpusScheduler<C, I, R, S>
where
S: HasCorpus<C, I> + HasRand<R>,
C: Corpus<I>,
I: Input,
R: Rand,
{
/// Gets the next entry at random /// Gets the next entry at random
fn next<C, I, R, S>(state: &mut S) -> Result<usize, Error> fn next(&self, state: &mut S) -> Result<usize, Error> {
where
S: HasCorpus<C, I> + HasRand<R>,
C: Corpus<I>,
I: Input,
R: Rand,
{
if state.corpus().count() == 0 { if state.corpus().count() == 0 {
Err(Error::Empty("No entries in corpus".to_owned())) Err(Error::Empty("No entries in corpus".to_owned()))
} else { } else {
let len = state.corpus().count(); let len = state.corpus().count();
let id = state.rand_mut().below(len as u64) as usize; let id = state.rand_mut().below(len as u64) as usize;
*state.corpus_mut().current_mut() = Some(id);
Ok(id) Ok(id)
} }
} }
} }
*/
#[derive(Default, Serialize, Deserialize, Clone, Debug)] pub type StdCorpusScheduler<C, I, R, S> = RandCorpusScheduler<C, I, R, S>;
#[serde(bound = "I: serde::de::DeserializeOwned")]
pub struct InMemoryCorpus<I>
where
I: Input,
{
entries: Vec<RefCell<Testcase<I>>>,
current: Option<usize>,
}
impl<I> Corpus<I> for InMemoryCorpus<I>
where
I: Input,
{
/// Returns the number of elements
#[inline]
fn count(&self) -> usize {
self.entries.len()
}
/// Add an entry to the corpus and return its index
#[inline]
fn add(&mut self, testcase: Testcase<I>) -> Result<usize, Error> {
self.entries.push(RefCell::new(testcase));
Ok(self.entries.len() - 1)
}
/// Replaces the testcase at the given idx
#[inline]
fn replace(&mut self, idx: usize, testcase: Testcase<I>) -> Result<(), Error> {
if idx >= self.entries.len() {
return Err(Error::KeyNotFound(format!("Index {} out of bounds", idx)));
}
self.entries[idx] = RefCell::new(testcase);
Ok(())
}
/// Removes an entry from the corpus, returning it if it was present.
#[inline]
fn remove(&mut self, idx: usize) -> Result<Option<Testcase<I>>, Error> {
if idx >= self.entries.len() {
Ok(None)
} else {
Ok(Some(self.entries.remove(idx).into_inner()))
}
}
/// Get by id
#[inline]
fn get(&self, idx: usize) -> Result<&RefCell<Testcase<I>>, Error> {
Ok(&self.entries[idx])
}
/// Current testcase scheduled
fn current(&self) -> &Option<usize> {
&self.current
}
/// Current testcase scheduled (mut)
fn current_mut(&mut self) -> &mut Option<usize> {
&mut self.current
}
}

View File

@ -1,132 +1,52 @@
//! The queue corpus implements an afl-like queue mechanism //! The queue corpus implements an afl-like queue mechanism
use alloc::{borrow::ToOwned, vec::Vec}; use alloc::borrow::ToOwned;
use core::{cell::RefCell, marker::PhantomData}; use core::marker::PhantomData;
use serde::{Deserialize, Serialize};
use crate::{ use crate::{
corpus::Corpus, corpus::HasTestcaseVec, corpus::Testcase, inputs::Input, utils::Rand, Error, corpus::{Corpus, CorpusScheduler},
inputs::Input,
state::HasCorpus,
Error,
}; };
/// A Queue-like corpus, wrapping an existing Corpus instance pub struct QueueCorpusScheduler<C, I, S>
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(bound = "I: serde::de::DeserializeOwned")]
pub struct QueueCorpus<C, I, R>
where where
C: Corpus<I, R>, S: HasCorpus<C, I>,
C: Corpus<I>,
I: Input, I: Input,
R: Rand,
{ {
corpus: C, phantom: PhantomData<(C, I, S)>,
pos: usize,
cycles: u64,
phantom: PhantomData<(I, R)>,
} }
impl<C, I, R> HasTestcaseVec<I> for QueueCorpus<C, I, R> impl<C, I, S> CorpusScheduler<I, S> for QueueCorpusScheduler<C, I, S>
where where
C: Corpus<I, R>, S: HasCorpus<C, I>,
C: Corpus<I>,
I: Input, I: Input,
R: Rand,
{ {
#[inline] /// Gets the next entry at random
fn entries(&self) -> &[RefCell<Testcase<I>>] { fn next(&self, state: &mut S) -> Result<usize, Error> {
self.corpus.entries() if state.corpus().count() == 0 {
} Err(Error::Empty("No entries in corpus".to_owned()))
#[inline] } else {
fn entries_mut(&mut self) -> &mut Vec<RefCell<Testcase<I>>> { let id = match state.corpus().current() {
self.corpus.entries_mut() Some(cur) => {
} if *cur + 1 > state.corpus().count() {
} 0
} else {
impl<C, I, R> Corpus<I, R> for QueueCorpus<C, I, R> *cur + 1
where }
C: Corpus<I, R>, }
I: Input, None => 0,
R: Rand, };
{ *state.corpus_mut().current_mut() = Some(id);
/// Returns the number of elements Ok(id)
#[inline]
fn count(&self) -> usize {
self.corpus.count()
}
#[inline]
fn add(&mut self, entry: Testcase<I>) -> usize {
self.corpus.add(entry)
}
/// Removes an entry from the corpus, returning it if it was present.
#[inline]
fn remove(&mut self, entry: &Testcase<I>) -> Option<Testcase<I>> {
self.corpus.remove(entry)
}
/// Gets a random entry
#[inline]
fn random_entry(&self, rand: &mut R) -> Result<(&RefCell<Testcase<I>>, usize), Error> {
self.corpus.random_entry(rand)
}
/// Returns the testacase we currently use
#[inline]
fn current_testcase(&self) -> (&RefCell<Testcase<I>>, usize) {
(self.get(self.pos - 1), self.pos - 1)
}
/// Gets the next entry
#[inline]
fn next(&mut self, _rand: &mut R) -> Result<(&RefCell<Testcase<I>>, usize), Error> {
self.pos += 1;
if self.corpus.count() == 0 {
return Err(Error::Empty("Corpus".to_owned()));
}
if self.pos > self.corpus.count() {
// TODO: Always loop or return informational error?
self.pos = 1;
self.cycles += 1;
}
Ok((&self.corpus.entries()[self.pos - 1], self.pos - 1))
}
}
impl<C, I, R> QueueCorpus<C, I, R>
where
C: Corpus<I, R>,
I: Input,
R: Rand,
{
pub fn new(corpus: C) -> Self {
Self {
corpus: corpus,
phantom: PhantomData,
cycles: 0,
pos: 0,
} }
} }
#[inline]
pub fn cycles(&self) -> u64 {
self.cycles
}
#[inline]
pub fn pos(&self) -> usize {
self.pos
}
// TODO maybe impl HasCorpus
#[inline]
pub fn corpus(&self) -> &C {
&self.corpus
}
#[inline]
pub fn corpus_mut(&mut self) -> &mut C {
&mut self.corpus
}
} }
/*
#[cfg(test)] #[cfg(test)]
#[cfg(feature = "std")] #[cfg(feature = "std")]
mod tests { mod tests {
@ -170,3 +90,4 @@ mod tests {
assert_eq!(filename, "fancyfile"); assert_eq!(filename, "fancyfile");
} }
} }
*/

View File

@ -249,7 +249,10 @@ pub mod unix_signals {
.is_interesting_all(&input, observers, ExitKind::Crash) .is_interesting_all(&input, observers, ExitKind::Crash)
.expect("In crash handler objectives failure.".into()); .expect("In crash handler objectives failure.".into());
if obj_fitness > 0 { if obj_fitness > 0 {
state.solutions_mut().add(Testcase::new(input.clone())).expect("In crash handler solutions failure.".into()); state
.solutions_mut()
.add(Testcase::new(input.clone()))
.expect("In crash handler solutions failure.".into());
mgr.fire( mgr.fire(
state, state,
Event::Objective { Event::Objective {
@ -302,7 +305,10 @@ pub mod unix_signals {
.is_interesting_all(&input, observers, ExitKind::Crash) .is_interesting_all(&input, observers, ExitKind::Crash)
.expect("In timeout handler objectives failure.".into()); .expect("In timeout handler objectives failure.".into());
if obj_fitness > 0 { if obj_fitness > 0 {
state.solutions_mut().add(Testcase::new(input.clone())).expect("In timeout handler solutions failure.".into()); state
.solutions_mut()
.add(Testcase::new(input.clone()))
.expect("In timeout handler solutions failure.".into());
mgr.fire( mgr.fire(
state, state,
Event::Objective { Event::Objective {

View File

@ -711,7 +711,13 @@ where
} }
} }
let other_size = state.corpus().get(idx)?.borrow_mut().load_input()?.bytes().len(); let other_size = state
.corpus()
.get(idx)?
.borrow_mut()
.load_input()?
.bytes()
.len();
if other_size < 2 { if other_size < 2 {
return Ok(MutationResult::Skipped); return Ok(MutationResult::Skipped);
} }
@ -762,7 +768,13 @@ where
} }
} }
let other_size = state.corpus().get(idx)?.borrow_mut().load_input()?.bytes().len(); let other_size = state
.corpus()
.get(idx)?
.borrow_mut()
.load_input()?
.bytes()
.len();
if other_size < 2 { if other_size < 2 {
return Ok(MutationResult::Skipped); return Ok(MutationResult::Skipped);
} }

View File

@ -1,7 +1,7 @@
pub mod mutational; pub mod mutational;
pub use mutational::StdMutationalStage; pub use mutational::StdMutationalStage;
use crate::{bolts::tuples::TupleList, Error}; use crate::{bolts::tuples::TupleList, Error};
/// A stage is one step in the fuzzing process. /// A stage is one step in the fuzzing process.
/// Multiple stages will be scheduled one by one for each input. /// Multiple stages will be scheduled one by one for each input.
@ -29,14 +29,7 @@ pub trait StagesTuple<E, EM, F, S> {
} }
impl<E, EM, F, S> StagesTuple<E, EM, F, S> for () { impl<E, EM, F, S> StagesTuple<E, EM, F, S> for () {
fn perform_all( fn perform_all(&self, _: &F, _: &mut S, _: &mut E, _: &mut EM, _: usize) -> Result<(), Error> {
&self,
_: &F,
_: &mut S,
_: &mut E,
_: &mut EM,
_: usize,
) -> Result<(), Error> {
Ok(()) Ok(())
} }
} }

View File

@ -59,8 +59,7 @@ where
let fitness = state.evaluate_input(input_mut, executor, manager)?; let fitness = state.evaluate_input(input_mut, executor, manager)?;
self.mutator() self.mutator().post_exec(fuzzer, state, fitness, i as i32)?;
.post_exec(fuzzer, state, fitness, i as i32)?;
} }
Ok(()) Ok(())
} }

View File

@ -583,14 +583,14 @@ where
executor.post_exec_observers()?; executor.post_exec_observers()?;
let observers = executor.observers(); let observers = executor.observers();
let fitness = self let fitness =
.feedbacks_mut() self.feedbacks_mut()
.is_interesting_all(&input, observers, exit_kind.clone())?; .is_interesting_all(&input, observers, exit_kind.clone())?;
let is_solution = let is_solution = self
self.objectives_mut() .objectives_mut()
.is_interesting_all(&input, observers, exit_kind)? .is_interesting_all(&input, observers, exit_kind)?
> 0; > 0;
Ok((fitness, is_solution)) Ok((fitness, is_solution))
} }