input reworked as state machine

This commit is contained in:
Dominik Maier 2020-11-13 05:51:31 +01:00
parent dec37fcfdd
commit 66a1d4f206
9 changed files with 128 additions and 37 deletions

View File

@ -310,12 +310,13 @@ mod tests {
use crate::inputs::bytes::BytesInput;
use crate::utils::DefaultRand;
use alloc::rc::Rc;
use std::path::PathBuf;
#[test]
fn test_queuecorpus() {
let rand = DefaultRand::preseeded_rr();
let rand: Rc<_> = DefaultRand::preseeded().into();
let mut q = QueueCorpus::new(OnDiskCorpus::new(&rand, PathBuf::from("fancy/path")));
let i = BytesInput::new(vec![0; 4]);
let t = Testcase::with_filename_rr(i, PathBuf::from("fancyfile"));

View File

@ -6,7 +6,9 @@ use crate::AflError;
use alloc::rc::Rc;
use core::cell::RefCell;
use hashbrown::HashMap;
use std::path::PathBuf;
use std::fs::File;
use std::io::Write;
use std::path::{Path, PathBuf};
// TODO: Give example
/// Metadata for a testcase
@ -15,6 +17,88 @@ pub trait TestcaseMetadata {
fn name(&self) -> &'static str;
}
enum FileBackedTestcase<I, P> {
/// A testcase on disk, not yet loaded
Stored { filename: P },
/// A testcase that has been loaded, and not yet dirtied.
/// The input should be equal to the on-disk state.
Loaded { input: I, filename: P },
/// A testcase that has been mutated, but not yet written to disk
Dirty { input: I, filename: P },
}
impl<I, P> FileBackedTestcase<I, P>
where
I: Input,
P: AsRef<Path>,
{
/// Load a testcase from disk if it is not already loaded.
///
/// # Errors
/// Errors if the testcase is [Dirty](FileBackedTestcase::Dirty)
pub fn load(self) -> Result<Self, AflError> {
match self {
Self::Stored { filename } => {
let input = I::from_file(&filename)?;
Ok(Self::Loaded { filename, input })
}
Self::Loaded {
input: _,
filename: _,
} => Ok(self),
_ => Err(AflError::IllegalState(
"Attempted load on dirty testcase".into(),
)),
}
}
/// Make sure that the in-memory state is syncd to disk, and load it from disk if
/// Nece
pub fn refresh(self) -> Result<Self, AflError> {
match self {
Self::Dirty {
input: _,
filename: _,
} => self.save(),
other => other.load(),
}
}
/// Writes changes to disk
pub fn save(self) -> Result<Self, AflError> {
match self {
Self::Loaded {
input: _,
filename: _,
} => Ok(self),
Self::Dirty { input, filename } => {
let mut file = File::create(&filename)?;
file.write_all(input.serialize()?)?;
Ok(Self::Loaded { input, filename })
}
Self::Stored { filename } => Err(AflError::IllegalState(format!(
"Tried to store to {:?} without input (in stored state)",
filename.as_ref()
))),
}
}
// Removes contents of this testcase from memory
pub fn unload(self) -> Result<Self, AflError> {
match self {
Self::Loaded { input: _, filename } => Ok(Self::Stored { filename }),
Self::Stored { filename: _ } => Ok(self),
Self::Dirty {
filename: _,
input: _,
} => self.save(),
}
}
}
/// An entry in the Testcase Corpus
#[derive(Default)]
pub struct Testcase<I>
@ -22,7 +106,7 @@ where
I: Input,
{
/// The input of this testcase
input: Option<I>, // TODO remove box
input: Option<I>,
/// Filename, if this testcase is backed by a file in the filesystem
filename: Option<PathBuf>,
/// Map of metadatas associated with this testcase

View File

@ -110,6 +110,7 @@ mod tests {
#[test]
fn test_engine() {
// TODO: Replace _rr with .Into traits
let rand: Rc<_> = DefaultRand::preseeded().into();
let mut corpus = InMemoryCorpus::<BytesInput, _>::new(&rand);
@ -121,6 +122,9 @@ mod tests {
mutator.add_mutation(mutation_bitflip);
let stage = DefaultMutationalStage::new(&rand, &executor, mutator);
engine.add_stage(Box::new(stage));
//
for i in 0..1000 {
engine
.fuzz_one(&mut corpus)

View File

@ -197,8 +197,8 @@ mod tests {
fn serialize(&self) -> Result<&[u8], AflError> {
Ok("NOP".as_bytes())
}
fn deserialize(&mut self, _buf: &[u8]) -> Result<(), AflError> {
Ok(())
fn deserialize(buf: &[u8]) -> Result<Self, AflError> {
Ok(Self {})
}
}

View File

@ -2,6 +2,10 @@ extern crate alloc;
use core::convert::From;
use core::convert::TryFrom;
use std::fs::File;
use std::path::Path;
use crate::inputs::{HasBytesVec, HasTargetBytes, Input};
use crate::AflError;
@ -15,10 +19,9 @@ impl Input for BytesInput {
fn serialize(&self) -> Result<&[u8], AflError> {
Ok(&self.bytes)
}
fn deserialize(&mut self, buf: &[u8]) -> Result<(), AflError> {
self.bytes.truncate(0);
self.bytes.extend_from_slice(buf);
Ok(())
fn deserialize(buf: &[u8]) -> Result<Self, AflError> {
Ok(Self { bytes: buf.into() })
}
}

View File

@ -4,29 +4,33 @@ pub mod bytes;
pub use bytes::BytesInput;
use core::clone::Clone;
use std::fs::File;
use std::io::Read;
use std::io::Write;
use std::path::PathBuf;
use std::path::Path;
use std::{fs::File, io::Read};
use crate::AflError;
/// An input for the target
pub trait Input: Clone {
/// Write this input to the file
fn to_file(&self, path: &PathBuf) -> Result<(), AflError> {
fn to_file<P>(&self, path: P) -> Result<(), AflError>
where
P: AsRef<Path>,
{
let mut file = File::create(path)?;
file.write_all(self.serialize()?)?;
Ok(())
}
/// Load the contents of this input from a file
fn from_file(&mut self, path: &PathBuf) -> Result<(), AflError> {
let mut file = File::create(path)?;
let mut buf = vec![];
file.read_to_end(&mut buf)?;
self.deserialize(&buf)?;
Ok(())
fn from_file<P>(path: P) -> Result<Self, AflError>
where
P: AsRef<Path>,
{
let mut file = File::open(path).map_err(AflError::File)?;
let mut bytes: Vec<u8> = vec![];
file.read_to_end(&mut bytes).map_err(AflError::File)?;
Self::deserialize(&bytes)
}
/// Serialize this input, for later deserialization.
@ -35,7 +39,7 @@ pub trait Input: Clone {
fn serialize(&self) -> Result<&[u8], AflError>;
/// Deserialize this input, using the bytes serialized before.
fn deserialize(&mut self, buf: &[u8]) -> Result<(), AflError>;
fn deserialize(buf: &[u8]) -> Result<Self, AflError>;
}
/// Can be serialized to a bytes representation

View File

@ -31,6 +31,8 @@ pub enum AflError {
IteratorEnd(String),
#[error("Not implemented: {0}")]
NotImplemented(String),
#[error("Illegal state: {0}")]
IllegalState(String),
#[error("Unknown error: {0}")]
Unknown(String),
}

View File

@ -324,16 +324,16 @@ where
#[cfg(test)]
mod tests {
use crate::corpus::{Corpus, InMemoryCorpus};
use crate::inputs::{BytesInput, HasBytesVec};
use crate::mutators::scheduled::mutation_splice;
use crate::utils::{DefaultHasRand, Rand, XKCDRand};
use alloc::rc::Rc;
#[test]
fn test_mut_splice() {
// With the current impl, seed of 1 will result in a split at pos 2.
let rand = &XKCDRand::new_rr();
let rand: Rc<_> = XKCDRand::new().into();
let mut has_rand = DefaultHasRand::new(&rand);
let mut corpus = InMemoryCorpus::new(&rand);
corpus.add_input(BytesInput::new(vec!['a' as u8, 'b' as u8, 'c' as u8]));

View File

@ -128,11 +128,6 @@ impl Xoshiro256StarRand {
self.into()
}
/// Creates a new Xoshiro rand with the given seed, wrapped in a Rc<RefCell<T>>.
pub fn new_rr(seed: u64) -> Rc<RefCell<Self>> {
Rc::new(RefCell::new(Self::new(seed)))
}
/// Creates a rand instance, pre-seeded with the current time in nanoseconds.
pub fn preseeded() -> Self {
let seed = SystemTime::now()
@ -141,11 +136,6 @@ impl Xoshiro256StarRand {
.as_nanos() as u64;
Self::new(seed)
}
/// Creates a new rand instance, pre-seeded
pub fn preseeded_rr() -> Rc<RefCell<Self>> {
Rc::new(RefCell::new(Self::preseeded()))
}
}
/// fake rand, for testing purposes
@ -166,15 +156,18 @@ impl Rand for XKCDRand {
}
}
#[cfg(test)]
impl Into<Rc<RefCell<Self>>> for XKCDRand {
fn into(self) -> Rc<RefCell<Self>> {
Rc::new(RefCell::new(self))
}
}
#[cfg(test)]
impl XKCDRand {
pub fn new() -> Self {
Self { val: 4 }
}
pub fn new_rr() -> Rc<RefCell<Self>> {
Rc::new(RefCell::new(Self::new()))
}
}
/// A very basic HasRand
@ -238,7 +231,7 @@ mod tests {
#[test]
fn test_has_rand() {
let rand = DefaultRand::preseeded_rr();
let rand = DefaultRand::preseeded().into();
let has_rand = DefaultHasRand::new(&rand);
assert!(has_rand.rand_below(100) < 100);