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::inputs::bytes::BytesInput;
use crate::utils::DefaultRand; use crate::utils::DefaultRand;
use alloc::rc::Rc;
use std::path::PathBuf; use std::path::PathBuf;
#[test] #[test]
fn test_queuecorpus() { 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 mut q = QueueCorpus::new(OnDiskCorpus::new(&rand, PathBuf::from("fancy/path")));
let i = BytesInput::new(vec![0; 4]); let i = BytesInput::new(vec![0; 4]);
let t = Testcase::with_filename_rr(i, PathBuf::from("fancyfile")); let t = Testcase::with_filename_rr(i, PathBuf::from("fancyfile"));

View File

@ -6,7 +6,9 @@ use crate::AflError;
use alloc::rc::Rc; use alloc::rc::Rc;
use core::cell::RefCell; use core::cell::RefCell;
use hashbrown::HashMap; use hashbrown::HashMap;
use std::path::PathBuf; use std::fs::File;
use std::io::Write;
use std::path::{Path, PathBuf};
// TODO: Give example // TODO: Give example
/// Metadata for a testcase /// Metadata for a testcase
@ -15,6 +17,88 @@ pub trait TestcaseMetadata {
fn name(&self) -> &'static str; 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 /// An entry in the Testcase Corpus
#[derive(Default)] #[derive(Default)]
pub struct Testcase<I> pub struct Testcase<I>
@ -22,7 +106,7 @@ where
I: Input, I: Input,
{ {
/// The input of this testcase /// 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, if this testcase is backed by a file in the filesystem
filename: Option<PathBuf>, filename: Option<PathBuf>,
/// Map of metadatas associated with this testcase /// Map of metadatas associated with this testcase

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -324,16 +324,16 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::corpus::{Corpus, InMemoryCorpus}; use crate::corpus::{Corpus, InMemoryCorpus};
use crate::inputs::{BytesInput, HasBytesVec}; use crate::inputs::{BytesInput, HasBytesVec};
use crate::mutators::scheduled::mutation_splice; use crate::mutators::scheduled::mutation_splice;
use crate::utils::{DefaultHasRand, Rand, XKCDRand}; use crate::utils::{DefaultHasRand, Rand, XKCDRand};
use alloc::rc::Rc;
#[test] #[test]
fn test_mut_splice() { fn test_mut_splice() {
// With the current impl, seed of 1 will result in a split at pos 2. // 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 has_rand = DefaultHasRand::new(&rand);
let mut corpus = InMemoryCorpus::new(&rand); let mut corpus = InMemoryCorpus::new(&rand);
corpus.add_input(BytesInput::new(vec!['a' as u8, 'b' as u8, 'c' as u8])); 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() 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. /// Creates a rand instance, pre-seeded with the current time in nanoseconds.
pub fn preseeded() -> Self { pub fn preseeded() -> Self {
let seed = SystemTime::now() let seed = SystemTime::now()
@ -141,11 +136,6 @@ impl Xoshiro256StarRand {
.as_nanos() as u64; .as_nanos() as u64;
Self::new(seed) 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 /// 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)] #[cfg(test)]
impl XKCDRand { impl XKCDRand {
pub fn new() -> Self { pub fn new() -> Self {
Self { val: 4 } Self { val: 4 }
} }
pub fn new_rr() -> Rc<RefCell<Self>> {
Rc::new(RefCell::new(Self::new()))
}
} }
/// A very basic HasRand /// A very basic HasRand
@ -238,7 +231,7 @@ mod tests {
#[test] #[test]
fn test_has_rand() { fn test_has_rand() {
let rand = DefaultRand::preseeded_rr(); let rand = DefaultRand::preseeded().into();
let has_rand = DefaultHasRand::new(&rand); let has_rand = DefaultHasRand::new(&rand);
assert!(has_rand.rand_below(100) < 100); assert!(has_rand.rand_below(100) < 100);