From 66a1d4f206d924bb8f5692bca302a12016074264 Mon Sep 17 00:00:00 2001 From: Dominik Maier Date: Fri, 13 Nov 2020 05:51:31 +0100 Subject: [PATCH] input reworked as state machine --- src/corpus/mod.rs | 3 +- src/corpus/testcase.rs | 88 ++++++++++++++++++++++++++++++++++++++- src/engines/mod.rs | 4 ++ src/executors/inmemory.rs | 4 +- src/inputs/bytes.rs | 11 +++-- src/inputs/mod.rs | 26 +++++++----- src/lib.rs | 2 + src/mutators/scheduled.rs | 4 +- src/utils.rs | 23 ++++------ 9 files changed, 128 insertions(+), 37 deletions(-) diff --git a/src/corpus/mod.rs b/src/corpus/mod.rs index 6179a44c37..87c9acbc9a 100644 --- a/src/corpus/mod.rs +++ b/src/corpus/mod.rs @@ -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")); diff --git a/src/corpus/testcase.rs b/src/corpus/testcase.rs index 4400d62d37..da56cda41d 100644 --- a/src/corpus/testcase.rs +++ b/src/corpus/testcase.rs @@ -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 { + /// 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 FileBackedTestcase +where + I: Input, + P: AsRef, +{ + /// 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 { + 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 { + match self { + Self::Dirty { + input: _, + filename: _, + } => self.save(), + other => other.load(), + } + } + + /// Writes changes to disk + pub fn save(self) -> Result { + 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 { + 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 @@ -22,7 +106,7 @@ where I: Input, { /// The input of this testcase - input: Option, // TODO remove box + input: Option, /// Filename, if this testcase is backed by a file in the filesystem filename: Option, /// Map of metadatas associated with this testcase diff --git a/src/engines/mod.rs b/src/engines/mod.rs index 014013b5f5..a3fabac95c 100644 --- a/src/engines/mod.rs +++ b/src/engines/mod.rs @@ -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::::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) diff --git a/src/executors/inmemory.rs b/src/executors/inmemory.rs index 6a3148b145..778367fa96 100644 --- a/src/executors/inmemory.rs +++ b/src/executors/inmemory.rs @@ -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 { + Ok(Self {}) } } diff --git a/src/inputs/bytes.rs b/src/inputs/bytes.rs index 3f07a3e4d2..06c324c31a 100644 --- a/src/inputs/bytes.rs +++ b/src/inputs/bytes.rs @@ -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 { + Ok(Self { bytes: buf.into() }) } } diff --git a/src/inputs/mod.rs b/src/inputs/mod.rs index 0efbbd27a6..fa894fbe46 100644 --- a/src/inputs/mod.rs +++ b/src/inputs/mod.rs @@ -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

(&self, path: P) -> Result<(), AflError> + where + P: AsRef, + { 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

(path: P) -> Result + where + P: AsRef, + { + let mut file = File::open(path).map_err(AflError::File)?; + let mut bytes: Vec = 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; } /// Can be serialized to a bytes representation diff --git a/src/lib.rs b/src/lib.rs index 01a9fcca56..24f81c932a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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), } diff --git a/src/mutators/scheduled.rs b/src/mutators/scheduled.rs index b20d76ff1d..ae78b39387 100644 --- a/src/mutators/scheduled.rs +++ b/src/mutators/scheduled.rs @@ -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])); diff --git a/src/utils.rs b/src/utils.rs index ff4b8fa132..49934fa4b9 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -128,11 +128,6 @@ impl Xoshiro256StarRand { self.into() } - /// Creates a new Xoshiro rand with the given seed, wrapped in a Rc>. - pub fn new_rr(seed: u64) -> Rc> { - 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> { - Rc::new(RefCell::new(Self::preseeded())) - } } /// fake rand, for testing purposes @@ -166,15 +156,18 @@ impl Rand for XKCDRand { } } +#[cfg(test)] +impl Into>> for XKCDRand { + fn into(self) -> Rc> { + Rc::new(RefCell::new(self)) + } +} + #[cfg(test)] impl XKCDRand { pub fn new() -> Self { Self { val: 4 } } - - pub fn new_rr() -> Rc> { - 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);