started moving to no_std

This commit is contained in:
Dominik Maier 2020-11-15 02:04:13 +01:00
parent 629287251f
commit ec36301414
12 changed files with 148 additions and 74 deletions

View File

@ -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 = "*"

View File

@ -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<RefCell<Testcase<I>>>) {
fn add(&mut self, testcase: Rc<RefCell<Testcase<I>>>) {
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<I>) -> Option<Rc<RefCell<Testcase<I>>>> {
let mut i: usize = 0;
@ -135,6 +132,7 @@ where
}
}
#[cfg(feature = "std")]
pub struct OnDiskCorpus<I, R>
where
I: Input,
@ -145,6 +143,7 @@ where
dir_path: PathBuf,
}
#[cfg(feature = "std")]
impl<I, R> HasEntriesVec<I> for OnDiskCorpus<I, R>
where
I: Input,
@ -158,6 +157,7 @@ where
}
}
#[cfg(feature = "std")]
impl<I, R> HasRand for OnDiskCorpus<I, R>
where
I: Input,
@ -170,6 +170,7 @@ where
}
}
#[cfg(feature = "std")]
impl<I, R> Corpus<I> for OnDiskCorpus<I, R>
where
I: Input,
@ -179,9 +180,9 @@ where
fn add(&mut self, entry: Rc<RefCell<Testcase<I>>>) {
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<I, R> OnDiskCorpus<I, R>
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");
}
}

View File

@ -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<I, T>
where
I: Input,
T: TestcaseMetadata,
{
fn input(&mut self) -> Option<I>
input: Option<I>,
/// Filename, if this testcase is backed by a file in the filesystem
filename: Option<String>,
/// Map of metadatas associated with this testcase
metadatas: HashMap<&'static str, Box<dyn TestcaseMetadata>>,
}
*/
pub enum FileBackedTestcase<I, P> {
/// A testcase on disk, not yet loaded
Stored { filename: P },
@ -109,7 +130,7 @@ where
/// The input of this testcase
input: Option<I>,
/// Filename, if this testcase is backed by a file in the filesystem
filename: Option<PathBuf>,
filename: Option<String>,
/// Map of metadatas associated with this testcase
metadatas: HashMap<&'static str, Box<dyn TestcaseMetadata>>,
}
@ -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<PathBuf> {
pub fn filename(&self) -> &Option<String> {
&self.filename
}
/// Get the filename, if any (mutable)
pub fn filename_mut(&mut self) -> &mut Option<PathBuf> {
pub fn filename_mut(&mut self) -> &mut Option<String> {
&mut self.filename
}
/// Set the filename
pub fn set_filename(&mut self, filename: Option<PathBuf>) {
pub fn set_filename(&mut self, filename: Option<String>) {
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),

View File

@ -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::<BytesInput, _>::new(&rand);
let testcase = Testcase::new(BytesInput::new(vec![0; 4])).into();

View File

@ -67,11 +67,11 @@ where
self.observers.push(observer);
}
fn observers(&self) -> &Vec<Box<dyn Observer>> {
fn observers(&self) -> &[Box<dyn Observer>] {
&self.observers
}
fn feedbacks(&self) -> &Vec<Box<dyn Feedback<I>>> {
fn feedbacks(&self) -> &[Box<dyn Feedback<I>>] {
&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()))
}
}

View File

@ -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<dyn Observer>);
/// Get the linked observers
fn observers(&self) -> &Vec<Box<dyn Observer>>;
fn observers(&self) -> &[Box<dyn Observer>];
/// Adds a feedback
fn add_feedback(&mut self, feedback: Box<dyn Feedback<I>>);
/// Returns vector of feebacks
fn feedbacks(&self) -> &Vec<Box<dyn Feedback<I>>>;
fn feedbacks(&self) -> &[Box<dyn Feedback<I>>];
/// Returns vector of feebacks (mutable)
fn feedbacks_mut(&mut self) -> &mut Vec<Box<dyn Feedback<I>>>;
@ -66,12 +62,6 @@ where
}
if rate_acc >= 25 {
let new_entry = Rc::new(RefCell::new(Testcase::<I>::new(input.clone())));
for meta in metadatas {
new_entry.borrow_mut().add_metadata(meta);
}
//TODO corpus.add(new_entry);
Ok(true)
} else {
Ok(false)

View File

@ -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;

View File

@ -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<Self, AflError> {
Ok(Self { bytes: buf.into() })
Ok(Self {
bytes: buf.to_owned(),
})
}
}
@ -32,7 +35,7 @@ impl Into<Rc<RefCell<Self>>> for BytesInput {
}
impl HasBytesVec for BytesInput {
fn bytes(&self) -> &Vec<u8> {
fn bytes(&self) -> &[u8] {
&self.bytes
}
fn bytes_mut(&mut self) -> &mut Vec<u8> {
@ -41,7 +44,7 @@ impl HasBytesVec for BytesInput {
}
impl HasTargetBytes for BytesInput {
fn target_bytes(&self) -> &Vec<u8> {
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);

View File

@ -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<P>(&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<P>(&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<P>(path: P) -> Result<Self, AflError>
where
P: AsRef<Path>,
@ -33,6 +47,13 @@ pub trait Input: Clone {
Self::deserialize(&bytes)
}
/// Write this input to the file
#[cfg(not(feature = "std"))]
fn from_file<P>(string: P) -> Result<Self, AflError>
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<u8>;
fn target_bytes(&self) -> &[u8];
}
/// Contains an internal bytes Vector
pub trait HasBytesVec {
/// The internal bytes map
fn bytes(&self) -> &Vec<u8>;
fn bytes(&self) -> &[u8];
/// The internal bytes map (as mutable borrow)
fn bytes_mut(&mut self) -> &mut Vec<u8>;
}

View File

@ -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<io::Error> for AflError {
fn from(err: io::Error) -> Self {
Self::File(err)
}
}
#[cfg(test)]
mod tests {
#[test]

View File

@ -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<MutationFunction<C, Self, I>, 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<MutationFunction<C, Self, I>, 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<u8>, other: &Vec<u8>) -> (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])
}
}

View File

@ -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);