diff --git a/Cargo.toml b/Cargo.toml index 4877d9eea2..ca7d392d14 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,9 +9,12 @@ edition = "2018" [dev-dependencies] criterion = "0.3" # Benchmarking +[features] +default = ["std"] +std = [] + [dependencies] xxhash-rust = { version = "0.8.0-beta.4", features = ["xxh3"] } # xxh3 hashing for rust -thiserror = "1.0" # A nicer way to write Errors hashbrown = "0.9" # A faster hashmap, nostd compatible libc = "0.2" # For (*nix) libc num = "*" \ No newline at end of file diff --git a/src/corpus/mod.rs b/src/corpus/mod.rs index 6613bce2de..476a825870 100644 --- a/src/corpus/mod.rs +++ b/src/corpus/mod.rs @@ -3,9 +3,12 @@ extern crate alloc; pub mod testcase; pub use testcase::{Testcase, TestcaseMetadata}; +use alloc::borrow::ToOwned; use alloc::rc::Rc; +use alloc::vec::Vec; use core::cell::RefCell; use core::marker::PhantomData; +#[cfg(feature = "std")] use std::path::PathBuf; use crate::inputs::Input; @@ -34,16 +37,10 @@ where } /// Add an entry to the corpus - #[allow(unused_mut)] - fn add(&mut self, mut testcase: Rc>>) { + fn add(&mut self, testcase: Rc>>) { self.entries_mut().push(testcase); } - /// Add an input to the corpus - fn add_input(&mut self, input: I) { - self.add(Testcase::new(input.into()).into()); - } - /// Removes an entry from the corpus, returning it if it was present. fn remove(&mut self, entry: &Testcase) -> Option>>> { let mut i: usize = 0; @@ -135,6 +132,7 @@ where } } +#[cfg(feature = "std")] pub struct OnDiskCorpus where I: Input, @@ -145,6 +143,7 @@ where dir_path: PathBuf, } +#[cfg(feature = "std")] impl HasEntriesVec for OnDiskCorpus where I: Input, @@ -158,6 +157,7 @@ where } } +#[cfg(feature = "std")] impl HasRand for OnDiskCorpus where I: Input, @@ -170,6 +170,7 @@ where } } +#[cfg(feature = "std")] impl Corpus for OnDiskCorpus where I: Input, @@ -179,9 +180,9 @@ where fn add(&mut self, entry: Rc>>) { if *entry.borrow().filename() == None { // TODO walk entry metadatas to ask for pices of filename (e.g. :havoc in AFL) - let filename = &(String::from("id:") + &self.entries.len().to_string()); - let filename = self.dir_path.join(filename); - *entry.borrow_mut().filename_mut() = Some(filename); + let filename = self.dir_path.join(format!("id_{}", &self.entries.len())); + let filename_str = filename.to_str().expect("Invalid Path"); + *entry.borrow_mut().filename_mut() = Some(filename_str.into()); } self.entries.push(entry); } @@ -189,6 +190,7 @@ where // TODO save and remove files, cache, etc..., ATM use just InMemoryCorpus } +#[cfg(feature = "std")] impl OnDiskCorpus where I: Input, @@ -352,6 +354,7 @@ And then: */ #[cfg(test)] +#[cfg(feature = "std")] mod tests { use crate::corpus::Corpus; use crate::corpus::Testcase; @@ -363,11 +366,11 @@ mod tests { use std::path::PathBuf; #[test] - fn test_queuecorpus() { - let rand: Rc<_> = DefaultRand::preseeded().into(); + let rand: Rc<_> = DefaultRand::new(0).into(); let mut q = QueueCorpus::new(OnDiskCorpus::new(&rand, PathBuf::from("fancy/path"))); - let t: Rc<_> = Testcase::with_filename(BytesInput::new(vec![0 as u8; 4]), PathBuf::from("fancyfile")).into(); + let t: Rc<_> = + Testcase::with_filename(BytesInput::new(vec![0 as u8; 4]), "fancyfile".into()).into(); q.add(t); let filename = q .next() @@ -387,6 +390,6 @@ mod tests { .unwrap() .to_owned() ); - assert_eq!(filename, PathBuf::from("fancyfile")); + assert_eq!(filename, "fancyfile"); } } diff --git a/src/corpus/testcase.rs b/src/corpus/testcase.rs index e2441bf3e5..cc689e9465 100644 --- a/src/corpus/testcase.rs +++ b/src/corpus/testcase.rs @@ -7,9 +7,12 @@ use alloc::rc::Rc; use core::cell::RefCell; use core::convert::Into; use hashbrown::HashMap; +#[cfg(feature = "std")] use std::fs::File; +#[cfg(feature = "std")] use std::io::Write; -use std::path::{Path, PathBuf}; +#[cfg(feature = "std")] +use std::path::Path; // TODO: Give example /// Metadata for a testcase @@ -18,6 +21,24 @@ pub trait TestcaseMetadata { fn name(&self) -> &'static str; } +/* +pub trait Testcase +where + I: Input, + T: TestcaseMetadata, +{ + + fn input(&mut self) -> Option + + input: Option, + /// Filename, if this testcase is backed by a file in the filesystem + filename: Option, + /// Map of metadatas associated with this testcase + metadatas: HashMap<&'static str, Box>, + +} +*/ + pub enum FileBackedTestcase { /// A testcase on disk, not yet loaded Stored { filename: P }, @@ -109,7 +130,7 @@ where /// The input of this testcase input: Option, /// Filename, if this testcase is backed by a file in the filesystem - filename: Option, + filename: Option, /// Map of metadatas associated with this testcase metadatas: HashMap<&'static str, Box>, } @@ -133,7 +154,7 @@ where // TODO: Implement cache to disk match self.input.as_ref() { Some(i) => Ok(i), - None => Err(AflError::NotImplemented("load_input".to_string())), + None => Err(AflError::NotImplemented("load_input".into())), } } @@ -151,15 +172,15 @@ where } /// Get the filename, if any - pub fn filename(&self) -> &Option { + pub fn filename(&self) -> &Option { &self.filename } /// Get the filename, if any (mutable) - pub fn filename_mut(&mut self) -> &mut Option { + pub fn filename_mut(&mut self) -> &mut Option { &mut self.filename } /// Set the filename - pub fn set_filename(&mut self, filename: Option) { + pub fn set_filename(&mut self, filename: Option) { self.filename = filename; } @@ -183,7 +204,7 @@ where } /// Create a new Testcase instace given an input and a filename - pub fn with_filename(input: I, filename: PathBuf) -> Self { + pub fn with_filename(input: I, filename: String) -> Self { Testcase { input: Some(input), filename: Some(filename), diff --git a/src/engines/mod.rs b/src/engines/mod.rs index ade29f6392..c42126af47 100644 --- a/src/engines/mod.rs +++ b/src/engines/mod.rs @@ -103,7 +103,7 @@ mod tests { #[test] fn test_engine() { - let rand: Rc<_> = DefaultRand::preseeded().into(); + let rand: Rc<_> = DefaultRand::new(0).into(); let mut corpus = InMemoryCorpus::::new(&rand); let testcase = Testcase::new(BytesInput::new(vec![0; 4])).into(); diff --git a/src/executors/inmemory.rs b/src/executors/inmemory.rs index 807611b081..bf3c590f31 100644 --- a/src/executors/inmemory.rs +++ b/src/executors/inmemory.rs @@ -67,11 +67,11 @@ where self.observers.push(observer); } - fn observers(&self) -> &Vec> { + fn observers(&self) -> &[Box] { &self.observers } - fn feedbacks(&self) -> &Vec>> { + fn feedbacks(&self) -> &[Box>] { &self.feedbacks } @@ -211,10 +211,10 @@ mod tests { impl Observer for Nopserver { fn reset(&mut self) -> Result<(), AflError> { - Err(AflError::Unknown("Nop reset, testing only".to_string())) + Err(AflError::Unknown("Nop reset, testing only".into())) } fn post_exec(&mut self) -> Result<(), AflError> { - Err(AflError::Unknown("Nop exec, testing only".to_string())) + Err(AflError::Unknown("Nop exec, testing only".into())) } } diff --git a/src/executors/mod.rs b/src/executors/mod.rs index af918a17c1..a539acd372 100644 --- a/src/executors/mod.rs +++ b/src/executors/mod.rs @@ -1,10 +1,6 @@ extern crate alloc; pub mod inmemory; -use alloc::rc::Rc; -use core::cell::RefCell; - -use crate::corpus::Testcase; use crate::corpus::TestcaseMetadata; use crate::feedbacks::Feedback; use crate::inputs::Input; @@ -37,13 +33,13 @@ where fn add_observer(&mut self, observer: Box); /// Get the linked observers - fn observers(&self) -> &Vec>; + fn observers(&self) -> &[Box]; /// Adds a feedback fn add_feedback(&mut self, feedback: Box>); /// Returns vector of feebacks - fn feedbacks(&self) -> &Vec>>; + fn feedbacks(&self) -> &[Box>]; /// Returns vector of feebacks (mutable) fn feedbacks_mut(&mut self) -> &mut Vec>>; @@ -66,12 +62,6 @@ where } if rate_acc >= 25 { - let new_entry = Rc::new(RefCell::new(Testcase::::new(input.clone()))); - for meta in metadatas { - new_entry.borrow_mut().add_metadata(meta); - } - //TODO corpus.add(new_entry); - Ok(true) } else { Ok(false) diff --git a/src/feedbacks/mod.rs b/src/feedbacks/mod.rs index 3d5744ca37..33d3395550 100644 --- a/src/feedbacks/mod.rs +++ b/src/feedbacks/mod.rs @@ -3,6 +3,8 @@ extern crate num; use core::cell::RefCell; use core::marker::PhantomData; + +use alloc::vec::*; use num::Integer; use crate::corpus::TestcaseMetadata; diff --git a/src/inputs/bytes.rs b/src/inputs/bytes.rs index 4150a55b50..f518c98099 100644 --- a/src/inputs/bytes.rs +++ b/src/inputs/bytes.rs @@ -1,9 +1,10 @@ extern crate alloc; -use core::convert::From; - +use alloc::borrow::ToOwned; use alloc::rc::Rc; +use alloc::vec::Vec; use core::cell::RefCell; +use core::convert::From; use crate::inputs::{HasBytesVec, HasTargetBytes, Input}; use crate::AflError; @@ -20,7 +21,9 @@ impl Input for BytesInput { } fn deserialize(buf: &[u8]) -> Result { - Ok(Self { bytes: buf.into() }) + Ok(Self { + bytes: buf.to_owned(), + }) } } @@ -32,7 +35,7 @@ impl Into>> for BytesInput { } impl HasBytesVec for BytesInput { - fn bytes(&self) -> &Vec { + fn bytes(&self) -> &[u8] { &self.bytes } fn bytes_mut(&mut self) -> &mut Vec { @@ -41,7 +44,7 @@ impl HasBytesVec for BytesInput { } impl HasTargetBytes for BytesInput { - fn target_bytes(&self) -> &Vec { + fn target_bytes(&self) -> &[u8] { &self.bytes } } @@ -71,7 +74,7 @@ mod tests { #[test] fn test_input() { - let mut rand = DefaultRand::preseeded(); + let mut rand = DefaultRand::new(0); assert_ne!(rand.next(), rand.next()); assert!(rand.below(100) < 100); assert_eq!(rand.below(1), 0); diff --git a/src/inputs/mod.rs b/src/inputs/mod.rs index fa894fbe46..f291264ac2 100644 --- a/src/inputs/mod.rs +++ b/src/inputs/mod.rs @@ -3,15 +3,21 @@ extern crate alloc; pub mod bytes; pub use bytes::BytesInput; +use alloc::vec::Vec; use core::clone::Clone; -use std::io::Write; + +#[cfg(feature = "std")] +use std::fs::File; +#[cfg(feature = "std")] +use std::io::{Read, Write}; +#[cfg(feature = "std")] use std::path::Path; -use std::{fs::File, io::Read}; use crate::AflError; /// An input for the target pub trait Input: Clone { + #[cfg(feature = "std")] /// Write this input to the file fn to_file

(&self, path: P) -> Result<(), AflError> where @@ -22,7 +28,15 @@ pub trait Input: Clone { Ok(()) } + #[cfg(not(feature = "std"))] + /// Write this input to the file + fn to_file

(&self, string: P) -> Result<(), AflError> +where { + Err(AflError::NotImplemented("Not suppored in no_std".into())) + } + /// Load the contents of this input from a file + #[cfg(feature = "std")] fn from_file

(path: P) -> Result where P: AsRef, @@ -33,6 +47,13 @@ pub trait Input: Clone { Self::deserialize(&bytes) } + /// Write this input to the file + #[cfg(not(feature = "std"))] + fn from_file

(string: P) -> Result +where { + Err(AflError::NotImplemented("Not suppored in no_std".into())) + } + /// Serialize this input, for later deserialization. /// This is not necessarily the representation to be used by the target /// Instead, to get bytes for a target, use [HasTargetBytes](afl::inputs::HasTargetBytes). @@ -47,13 +68,13 @@ pub trait Input: Clone { /// Instead, it can be used as bytes input for a target pub trait HasTargetBytes { /// Target bytes, that can be written to a target - fn target_bytes(&self) -> &Vec; + fn target_bytes(&self) -> &[u8]; } /// Contains an internal bytes Vector pub trait HasBytesVec { /// The internal bytes map - fn bytes(&self) -> &Vec; + fn bytes(&self) -> &[u8]; /// The internal bytes map (as mutable borrow) fn bytes_mut(&mut self) -> &mut Vec; } diff --git a/src/lib.rs b/src/lib.rs index 24f81c932a..af0253b448 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +#![cfg_attr(not(feature = "std"), no_std)] +#[macro_use] extern crate alloc; pub mod corpus; @@ -11,32 +13,52 @@ pub mod observers; pub mod stages; pub mod utils; +use alloc::string::String; +use core::fmt; +#[cfg(feature = "std")] use std::io; -use thiserror::Error; /// Main error struct for AFL -#[derive(Error, Debug)] +#[derive(Debug)] pub enum AflError { - #[error("Error in Serialization: `{0}`")] Serialize(String), - #[error("File IO failed")] - File(#[from] io::Error), - #[error("Optional value `{0}` was not set")] + #[cfg(feature = "std")] + File(io::Error), EmptyOptional(String), - #[error("Key `{0}` not in Corpus")] KeyNotFound(String), - #[error("No items in {0}")] Empty(String), - #[error("All elements have been processed in {0} iterator")] IteratorEnd(String), - #[error("Not implemented: {0}")] NotImplemented(String), - #[error("Illegal state: {0}")] IllegalState(String), - #[error("Unknown error: {0}")] Unknown(String), } +impl fmt::Display for AflError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Serialize(s) => write!(f, "Error in Serialization: `{0}`", &s), + #[cfg(feature = "std")] + Self::File(err) => write!(f, "File IO failed: {:?}", &err), + Self::EmptyOptional(s) => write!(f, "Optional value `{0}` was not set", &s), + Self::KeyNotFound(s) => write!(f, "Key `{0}` not in Corpus", &s), + Self::Empty(s) => write!(f, "No items in {0}", &s), + Self::IteratorEnd(s) => { + write!(f, "All elements have been processed in {0} iterator", &s) + } + Self::NotImplemented(s) => write!(f, "Not implemented: {0}", &s), + Self::IllegalState(s) => write!(f, "Illegal state: {0}", &s), + Self::Unknown(s) => write!(f, "Unknown error: {0}", &s), + } + } +} + +#[cfg(feature = "std")] +impl From for AflError { + fn from(err: io::Error) -> Self { + Self::File(err) + } +} + #[cfg(test)] mod tests { #[test] diff --git a/src/mutators/scheduled.rs b/src/mutators/scheduled.rs index ae78b39387..ed7bfe6e6d 100644 --- a/src/mutators/scheduled.rs +++ b/src/mutators/scheduled.rs @@ -7,6 +7,7 @@ use crate::utils::{HasRand, Rand}; use crate::AflError; use alloc::rc::Rc; +use alloc::vec::Vec; use core::cell::RefCell; use core::marker::PhantomData; @@ -42,7 +43,7 @@ where fn schedule(&mut self, _input: &I) -> Result, AflError> { let count = self.mutations_count() as u64; if count == 0 { - return Err(AflError::Empty("no mutations".to_string())); + return Err(AflError::Empty("no mutations".into())); } let idx; { @@ -109,7 +110,7 @@ where { fn mutation_by_idx(&self, index: usize) -> Result, AflError> { if index >= self.mutations.len() { - return Err(AflError::Unknown("oob".to_string())); + return Err(AflError::Unknown("oob".into())); } Ok(self.mutations[index]) } @@ -172,7 +173,7 @@ where } /// Returns the first and last diff position between the given vectors, stopping at the min len -fn locate_diffs(this: &Vec, other: &Vec) -> (i64, i64) { +fn locate_diffs(this: &[u8], other: &[u8]) -> (i64, i64) { let mut first_diff: i64 = -1; let mut last_diff: i64 = -1; for (i, (this_el, other_el)) in this.iter().zip(other.iter()).enumerate() { @@ -208,7 +209,7 @@ where Err(_) => { if retry_count == 20 { return Err(AflError::Empty( - "No suitable testcase found for splicing".to_owned(), + "No suitable testcase found for splicing".into(), )); } retry_count += 1; @@ -231,7 +232,7 @@ where break (f, l); } if counter == 20 { - return Err(AflError::Empty("No valid diff found".to_owned())); + return Err(AflError::Empty("No valid diff found".into())); } counter += 1; }; @@ -324,10 +325,13 @@ where #[cfg(test)] mod tests { - use crate::corpus::{Corpus, InMemoryCorpus}; - use crate::inputs::{BytesInput, HasBytesVec}; + use crate::inputs::BytesInput; use crate::mutators::scheduled::mutation_splice; use crate::utils::{DefaultHasRand, Rand, XKCDRand}; + use crate::{ + corpus::{Corpus, InMemoryCorpus, Testcase}, + inputs::HasBytesVec, + }; use alloc::rc::Rc; #[test] @@ -336,8 +340,8 @@ mod tests { 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])); - corpus.add_input(BytesInput::new(vec!['d' as u8, 'e' as u8, 'f' as u8])); + corpus.add(Testcase::new(BytesInput::new(vec!['a' as u8, 'b' as u8, 'c' as u8])).into()); + corpus.add(Testcase::new(BytesInput::new(vec!['d' as u8, 'e' as u8, 'f' as u8])).into()); let testcase_rr = corpus.next().expect("Corpus did not contain entries"); let mut testcase = testcase_rr.borrow_mut(); @@ -346,10 +350,11 @@ mod tests { rand.borrow_mut().set_seed(5); mutation_splice(&mut has_rand, &mut corpus, &mut input).unwrap(); + #[cfg(feature = "std")] println!("{:?}", input.bytes()); // The pre-seeded rand should have spliced at position 2. // TODO: Maybe have a fixed rand for this purpose? - assert_eq!(input.bytes(), &vec!['a' as u8, 'b' as u8, 'f' as u8]) + assert_eq!(input.bytes(), &['a' as u8, 'b' as u8, 'f' as u8]) } } diff --git a/src/utils.rs b/src/utils.rs index 21d383e6af..553664c13f 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -5,9 +5,11 @@ use alloc::rc::Rc; use core::cell::RefCell; use core::debug_assert; use core::fmt::Debug; -use std::time::{SystemTime, UNIX_EPOCH}; use xxhash_rust::xxh3::xxh3_64_with_seed; +#[cfg(feature = "std")] +use std::time::{SystemTime, UNIX_EPOCH}; + pub type DefaultRand = Xoshiro256StarRand; /// Ways to get random around here @@ -128,6 +130,8 @@ impl Xoshiro256StarRand { } /// Creates a rand instance, pre-seeded with the current time in nanoseconds. + /// Needs stdlib timer + #[cfg(feature = "std")] pub fn preseeded() -> Self { let seed = SystemTime::now() .duration_since(UNIX_EPOCH) @@ -220,7 +224,7 @@ mod tests { #[test] fn test_rand() { - let mut rand = DefaultRand::preseeded(); + let mut rand = DefaultRand::new(0); assert_ne!(rand.next(), rand.next()); assert!(rand.below(100) < 100); assert_eq!(rand.below(1), 0); @@ -230,7 +234,7 @@ mod tests { #[test] fn test_has_rand() { - let rand = DefaultRand::preseeded().into(); + let rand = DefaultRand::new(0).into(); let has_rand = DefaultHasRand::new(&rand); assert!(has_rand.rand_below(100) < 100);