Improve the libafl_libfuzzer corpus (#1539)
* improved libfuzzer corpus * use .into() for converting ids to usize * oops * fix warning about unused arg * fix some lingering CI errors * actually save the last lmao
This commit is contained in:
parent
fd98eabfbf
commit
56b37bb4bd
@ -183,7 +183,7 @@ where
|
|||||||
/// Get the next id given a `CorpusId` (creation order)
|
/// Get the next id given a `CorpusId` (creation order)
|
||||||
#[cfg(not(feature = "corpus_btreemap"))]
|
#[cfg(not(feature = "corpus_btreemap"))]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn next(&self, idx: CorpusId) -> Option<CorpusId> {
|
pub fn next(&self, idx: CorpusId) -> Option<CorpusId> {
|
||||||
if let Some(item) = self.map.get(&idx) {
|
if let Some(item) = self.map.get(&idx) {
|
||||||
item.next
|
item.next
|
||||||
} else {
|
} else {
|
||||||
@ -194,7 +194,7 @@ where
|
|||||||
/// Get the next id given a `CorpusId` (creation order)
|
/// Get the next id given a `CorpusId` (creation order)
|
||||||
#[cfg(feature = "corpus_btreemap")]
|
#[cfg(feature = "corpus_btreemap")]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn next(&self, idx: CorpusId) -> Option<CorpusId> {
|
pub fn next(&self, idx: CorpusId) -> Option<CorpusId> {
|
||||||
// TODO see if using self.keys is faster
|
// TODO see if using self.keys is faster
|
||||||
let mut range = self
|
let mut range = self
|
||||||
.map
|
.map
|
||||||
@ -214,7 +214,7 @@ where
|
|||||||
/// Get the previous id given a `CorpusId` (creation order)
|
/// Get the previous id given a `CorpusId` (creation order)
|
||||||
#[cfg(not(feature = "corpus_btreemap"))]
|
#[cfg(not(feature = "corpus_btreemap"))]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn prev(&self, idx: CorpusId) -> Option<CorpusId> {
|
pub fn prev(&self, idx: CorpusId) -> Option<CorpusId> {
|
||||||
if let Some(item) = self.map.get(&idx) {
|
if let Some(item) = self.map.get(&idx) {
|
||||||
item.prev
|
item.prev
|
||||||
} else {
|
} else {
|
||||||
@ -225,7 +225,7 @@ where
|
|||||||
/// Get the previous id given a `CorpusId` (creation order)
|
/// Get the previous id given a `CorpusId` (creation order)
|
||||||
#[cfg(feature = "corpus_btreemap")]
|
#[cfg(feature = "corpus_btreemap")]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn prev(&self, idx: CorpusId) -> Option<CorpusId> {
|
pub fn prev(&self, idx: CorpusId) -> Option<CorpusId> {
|
||||||
// TODO see if using self.keys is faster
|
// TODO see if using self.keys is faster
|
||||||
let mut range = self
|
let mut range = self
|
||||||
.map
|
.map
|
||||||
@ -245,28 +245,28 @@ where
|
|||||||
/// Get the first created id
|
/// Get the first created id
|
||||||
#[cfg(not(feature = "corpus_btreemap"))]
|
#[cfg(not(feature = "corpus_btreemap"))]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn first(&self) -> Option<CorpusId> {
|
pub fn first(&self) -> Option<CorpusId> {
|
||||||
self.first_idx
|
self.first_idx
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the first created id
|
/// Get the first created id
|
||||||
#[cfg(feature = "corpus_btreemap")]
|
#[cfg(feature = "corpus_btreemap")]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn first(&self) -> Option<CorpusId> {
|
pub fn first(&self) -> Option<CorpusId> {
|
||||||
self.map.iter().next().map(|x| *x.0)
|
self.map.iter().next().map(|x| *x.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the last created id
|
/// Get the last created id
|
||||||
#[cfg(not(feature = "corpus_btreemap"))]
|
#[cfg(not(feature = "corpus_btreemap"))]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn last(&self) -> Option<CorpusId> {
|
pub fn last(&self) -> Option<CorpusId> {
|
||||||
self.last_idx
|
self.last_idx
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the last created id
|
/// Get the last created id
|
||||||
#[cfg(feature = "corpus_btreemap")]
|
#[cfg(feature = "corpus_btreemap")]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn last(&self) -> Option<CorpusId> {
|
pub fn last(&self) -> Option<CorpusId> {
|
||||||
self.map.iter().next_back().map(|x| *x.0)
|
self.map.iter().next_back().map(|x| *x.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ use core::{fmt, fmt::Write, time::Duration};
|
|||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
pub use disk::{OnDiskJSONMonitor, OnDiskTOMLMonitor};
|
pub use disk::{OnDiskJSONMonitor, OnDiskTOMLMonitor};
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
use libafl_bolts::{current_time, format_duration_hms, ClientId};
|
use libafl_bolts::{current_time, format_duration_hms, ClientId, Error};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[cfg(feature = "afl_exec_sec")]
|
#[cfg(feature = "afl_exec_sec")]
|
||||||
@ -210,6 +210,7 @@ impl ClientStats {
|
|||||||
|
|
||||||
/// Update the user-defined stat with name and value
|
/// Update the user-defined stat with name and value
|
||||||
pub fn update_user_stats(&mut self, name: String, value: UserStats) {
|
pub fn update_user_stats(&mut self, name: String, value: UserStats) {
|
||||||
|
log::info!("{}", Error::unknown("dumping backtrace for monitoring"));
|
||||||
self.user_monitor.insert(name, value);
|
self.user_monitor.insert(name, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,7 +140,6 @@ use alloc::vec::Vec;
|
|||||||
use core::hash::BuildHasher;
|
use core::hash::BuildHasher;
|
||||||
#[cfg(any(feature = "xxh3", feature = "alloc"))]
|
#[cfg(any(feature = "xxh3", feature = "alloc"))]
|
||||||
use core::hash::Hasher;
|
use core::hash::Hasher;
|
||||||
use core::{iter::Iterator, time};
|
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
@ -175,18 +174,23 @@ pub mod launcher {}
|
|||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate libafl_derive;
|
extern crate libafl_derive;
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
use alloc::string::{FromUtf8Error, String};
|
|
||||||
use core::{
|
use core::{
|
||||||
array::TryFromSliceError,
|
array::TryFromSliceError,
|
||||||
fmt::{self, Display},
|
fmt::{self, Display},
|
||||||
|
iter::Iterator,
|
||||||
num::{ParseIntError, TryFromIntError},
|
num::{ParseIntError, TryFromIntError},
|
||||||
|
time,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
use std::{env::VarError, io};
|
use std::{env::VarError, io};
|
||||||
|
|
||||||
#[cfg(feature = "libafl_derive")]
|
#[cfg(feature = "libafl_derive")]
|
||||||
pub use libafl_derive::SerdeAny;
|
pub use libafl_derive::SerdeAny;
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
use {
|
||||||
|
alloc::string::{FromUtf8Error, String},
|
||||||
|
core::cell::{BorrowError, BorrowMutError},
|
||||||
|
};
|
||||||
|
|
||||||
/// We need fixed names for many parts of this lib.
|
/// We need fixed names for many parts of this lib.
|
||||||
pub trait Named {
|
pub trait Named {
|
||||||
@ -444,6 +448,24 @@ impl Display for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
impl From<BorrowError> for Error {
|
||||||
|
fn from(err: BorrowError) -> Self {
|
||||||
|
Self::illegal_state(format!(
|
||||||
|
"Couldn't borrow from a RefCell as immutable: {err:?}"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
impl From<BorrowMutError> for Error {
|
||||||
|
fn from(err: BorrowMutError) -> Self {
|
||||||
|
Self::illegal_state(format!(
|
||||||
|
"Couldn't borrow from a RefCell as mutable: {err:?}"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Stringify the postcard serializer error
|
/// Stringify the postcard serializer error
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
impl From<postcard::Error> for Error {
|
impl From<postcard::Error> for Error {
|
||||||
|
@ -40,7 +40,7 @@ log = "0.4.17"
|
|||||||
mimalloc = { version = "0.1.34", default-features = false, optional = true }
|
mimalloc = { version = "0.1.34", default-features = false, optional = true }
|
||||||
num-traits = "0.2.15"
|
num-traits = "0.2.15"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } # serialization lib
|
serde = { version = "1.0", features = ["derive"] } # serialization lib
|
||||||
|
|
||||||
# clippy-suggested optimised byte counter
|
# clippy-suggested optimised byte counter
|
||||||
bytecount = "0.6.3"
|
bytecount = "0.6.3"
|
||||||
|
322
libafl_libfuzzer/libafl_libfuzzer_runtime/src/corpus.rs
Normal file
322
libafl_libfuzzer/libafl_libfuzzer_runtime/src/corpus.rs
Normal file
@ -0,0 +1,322 @@
|
|||||||
|
use std::{
|
||||||
|
cell::RefCell,
|
||||||
|
collections::{hash_map::Entry, BTreeMap, HashMap},
|
||||||
|
io::ErrorKind,
|
||||||
|
path::PathBuf,
|
||||||
|
sync::atomic::{AtomicU64, Ordering},
|
||||||
|
};
|
||||||
|
|
||||||
|
use libafl::{
|
||||||
|
corpus::{inmemory::TestcaseStorage, Corpus, CorpusId, Testcase},
|
||||||
|
inputs::{Input, UsesInput},
|
||||||
|
};
|
||||||
|
use libafl_bolts::Error;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// A corpus which attempts to mimic the behaviour of libFuzzer.
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
#[serde(bound = "I: serde::de::DeserializeOwned")]
|
||||||
|
pub struct LibfuzzerCorpus<I>
|
||||||
|
where
|
||||||
|
I: Input + Serialize,
|
||||||
|
{
|
||||||
|
corpus_dir: PathBuf,
|
||||||
|
loaded_mapping: RefCell<HashMap<CorpusId, u64>>,
|
||||||
|
loaded_entries: RefCell<BTreeMap<u64, CorpusId>>,
|
||||||
|
mapping: TestcaseStorage<I>,
|
||||||
|
max_len: usize,
|
||||||
|
|
||||||
|
current: Option<CorpusId>,
|
||||||
|
next_recency: AtomicU64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I> LibfuzzerCorpus<I>
|
||||||
|
where
|
||||||
|
I: Input + Serialize + for<'de> Deserialize<'de>,
|
||||||
|
{
|
||||||
|
pub fn new(corpus_dir: PathBuf, max_len: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
corpus_dir,
|
||||||
|
loaded_mapping: RefCell::new(HashMap::default()),
|
||||||
|
loaded_entries: RefCell::new(BTreeMap::default()),
|
||||||
|
mapping: TestcaseStorage::new(),
|
||||||
|
max_len,
|
||||||
|
current: None,
|
||||||
|
next_recency: AtomicU64::new(0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dir_path(&self) -> &PathBuf {
|
||||||
|
&self.corpus_dir
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Touch this index and maybe evict an entry if we have touched an input which was unloaded.
|
||||||
|
fn touch(&self, idx: CorpusId) -> Result<(), Error> {
|
||||||
|
let mut loaded_mapping = self.loaded_mapping.borrow_mut();
|
||||||
|
let mut loaded_entries = self.loaded_entries.borrow_mut();
|
||||||
|
match loaded_mapping.entry(idx) {
|
||||||
|
Entry::Occupied(mut e) => {
|
||||||
|
let &old = e.get();
|
||||||
|
let new = self.next_recency.fetch_add(1, Ordering::Relaxed);
|
||||||
|
e.insert(new);
|
||||||
|
loaded_entries.remove(&old);
|
||||||
|
loaded_entries.insert(new, idx);
|
||||||
|
}
|
||||||
|
Entry::Vacant(e) => {
|
||||||
|
// new entry! send it in
|
||||||
|
let new = self.next_recency.fetch_add(1, Ordering::Relaxed);
|
||||||
|
e.insert(new);
|
||||||
|
loaded_entries.insert(new, idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if loaded_entries.len() > self.max_len {
|
||||||
|
let idx = loaded_entries.pop_first().unwrap().1; // cannot panic
|
||||||
|
let cell = self.mapping.get(idx).ok_or_else(|| {
|
||||||
|
Error::key_not_found(format!("Tried to evict non-existent entry {idx}"))
|
||||||
|
})?;
|
||||||
|
let mut tc = cell.try_borrow_mut()?;
|
||||||
|
let _ = tc.input_mut().take();
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I> UsesInput for LibfuzzerCorpus<I>
|
||||||
|
where
|
||||||
|
I: Input + Serialize + for<'de> Deserialize<'de>,
|
||||||
|
{
|
||||||
|
type Input = I;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I> Corpus for LibfuzzerCorpus<I>
|
||||||
|
where
|
||||||
|
I: Input + Serialize + for<'de> Deserialize<'de>,
|
||||||
|
{
|
||||||
|
fn count(&self) -> usize {
|
||||||
|
self.mapping.map.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add(&mut self, testcase: Testcase<Self::Input>) -> Result<CorpusId, Error> {
|
||||||
|
let idx = self.mapping.insert(RefCell::new(testcase));
|
||||||
|
let mut testcase = self.mapping.get(idx).unwrap().borrow_mut();
|
||||||
|
|
||||||
|
match testcase.file_path() {
|
||||||
|
Some(path) if path.canonicalize()?.starts_with(&self.corpus_dir) => {
|
||||||
|
// if it's already in the correct dir, we retain it
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let input = testcase.input().as_ref().ok_or_else(|| {
|
||||||
|
Error::empty(
|
||||||
|
"The testcase, when added to the corpus, must have an input present!",
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let name = input.generate_name(idx.into());
|
||||||
|
let path = self.corpus_dir.join(&name);
|
||||||
|
|
||||||
|
match input.to_file(&path) {
|
||||||
|
Err(Error::File(e, _)) if e.kind() == ErrorKind::AlreadyExists => {
|
||||||
|
// we do not care if the file already exists; in this case, we assume it is equal
|
||||||
|
}
|
||||||
|
res => res?,
|
||||||
|
}
|
||||||
|
|
||||||
|
// we DO NOT save metadata!
|
||||||
|
|
||||||
|
testcase.filename_mut().replace(name);
|
||||||
|
testcase.file_path_mut().replace(path);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.touch(idx)?;
|
||||||
|
Ok(idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn replace(
|
||||||
|
&mut self,
|
||||||
|
_idx: CorpusId,
|
||||||
|
_testcase: Testcase<Self::Input>,
|
||||||
|
) -> Result<Testcase<Self::Input>, Error> {
|
||||||
|
unimplemented!("It is unsafe to use this corpus variant with replace!");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove(&mut self, _id: CorpusId) -> Result<Testcase<Self::Input>, Error> {
|
||||||
|
unimplemented!("It is unsafe to use this corpus variant with replace!");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(&self, id: CorpusId) -> Result<&RefCell<Testcase<Self::Input>>, Error> {
|
||||||
|
self.touch(id)?;
|
||||||
|
self.mapping.map.get(&id).map(|item| &item.testcase).ok_or_else(|| Error::illegal_state("Nonexistent corpus entry {id} requested (present in loaded entries, but not the mapping?)"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current(&self) -> &Option<CorpusId> {
|
||||||
|
&self.current
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_mut(&mut self) -> &mut Option<CorpusId> {
|
||||||
|
&mut self.current
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next(&self, id: CorpusId) -> Option<CorpusId> {
|
||||||
|
self.mapping.next(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prev(&self, id: CorpusId) -> Option<CorpusId> {
|
||||||
|
self.mapping.prev(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn first(&self) -> Option<CorpusId> {
|
||||||
|
self.mapping.first()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn last(&self) -> Option<CorpusId> {
|
||||||
|
self.mapping.last()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_input_into(&self, testcase: &mut Testcase<Self::Input>) -> Result<(), Error> {
|
||||||
|
// we don't need to update the loaded testcases because it must have already been loaded
|
||||||
|
if testcase.input().is_none() {
|
||||||
|
let path = testcase.file_path().as_ref().ok_or_else(|| {
|
||||||
|
Error::empty("The testcase, when being saved, must have a file path!")
|
||||||
|
})?;
|
||||||
|
let input = I::from_file(path)?;
|
||||||
|
testcase.input_mut().replace(input);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn store_input_from(&self, testcase: &Testcase<Self::Input>) -> Result<(), Error> {
|
||||||
|
let input = testcase.input().as_ref().ok_or_else(|| {
|
||||||
|
Error::empty("The testcase, when being saved, must have an input present!")
|
||||||
|
})?;
|
||||||
|
let path = testcase.file_path().as_ref().ok_or_else(|| {
|
||||||
|
Error::empty("The testcase, when being saved, must have a file path!")
|
||||||
|
})?;
|
||||||
|
match input.to_file(path) {
|
||||||
|
Err(Error::File(e, _)) if e.kind() == ErrorKind::AlreadyExists => {
|
||||||
|
// we do not care if the file already exists; in this case, we assume it is equal
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
res => res,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A corpus which attempts to mimic the behaviour of libFuzzer's crash output.
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
#[serde(bound = "I: serde::de::DeserializeOwned")]
|
||||||
|
pub struct ArtifactCorpus<I>
|
||||||
|
where
|
||||||
|
I: Input + Serialize,
|
||||||
|
{
|
||||||
|
last: Option<RefCell<Testcase<I>>>,
|
||||||
|
count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I> ArtifactCorpus<I>
|
||||||
|
where
|
||||||
|
I: Input + Serialize + for<'de> Deserialize<'de>,
|
||||||
|
{
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
last: None,
|
||||||
|
count: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I> UsesInput for ArtifactCorpus<I>
|
||||||
|
where
|
||||||
|
I: Input + Serialize + for<'de> Deserialize<'de>,
|
||||||
|
{
|
||||||
|
type Input = I;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I> Corpus for ArtifactCorpus<I>
|
||||||
|
where
|
||||||
|
I: Input + Serialize + for<'de> Deserialize<'de>,
|
||||||
|
{
|
||||||
|
fn count(&self) -> usize {
|
||||||
|
self.count
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add(&mut self, testcase: Testcase<Self::Input>) -> Result<CorpusId, Error> {
|
||||||
|
let idx = self.count;
|
||||||
|
self.count += 1;
|
||||||
|
|
||||||
|
let input = testcase.input().as_ref().ok_or_else(|| {
|
||||||
|
Error::empty("The testcase, when added to the corpus, must have an input present!")
|
||||||
|
})?;
|
||||||
|
let path = testcase.file_path().as_ref().ok_or_else(|| {
|
||||||
|
Error::illegal_state("Should have set the path in the LibfuzzerCrashCauseFeedback.")
|
||||||
|
})?;
|
||||||
|
match input.to_file(path) {
|
||||||
|
Err(Error::File(e, _)) if e.kind() == ErrorKind::AlreadyExists => {
|
||||||
|
// we do not care if the file already exists; in this case, we assume it is equal
|
||||||
|
}
|
||||||
|
res => res?,
|
||||||
|
}
|
||||||
|
|
||||||
|
// we DO NOT save metadata!
|
||||||
|
self.last = Some(RefCell::new(testcase));
|
||||||
|
|
||||||
|
Ok(CorpusId::from(idx))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn replace(
|
||||||
|
&mut self,
|
||||||
|
_idx: CorpusId,
|
||||||
|
_testcase: Testcase<Self::Input>,
|
||||||
|
) -> Result<Testcase<Self::Input>, Error> {
|
||||||
|
unimplemented!("Artifact prefix is thin and cannot get, replace, or remove.")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove(&mut self, _id: CorpusId) -> Result<Testcase<Self::Input>, Error> {
|
||||||
|
unimplemented!("Artifact prefix is thin and cannot get, replace, or remove.")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(&self, id: CorpusId) -> Result<&RefCell<Testcase<Self::Input>>, Error> {
|
||||||
|
let maybe_last = if self
|
||||||
|
.count
|
||||||
|
.checked_sub(1)
|
||||||
|
.map(CorpusId::from)
|
||||||
|
.map_or(false, |last| last == id)
|
||||||
|
{
|
||||||
|
self.last.as_ref()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
maybe_last.ok_or_else(|| Error::illegal_argument("Can only get the last corpus ID."))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current(&self) -> &Option<CorpusId> {
|
||||||
|
unimplemented!("Artifact prefix is thin and cannot get, replace, or remove.")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_mut(&mut self) -> &mut Option<CorpusId> {
|
||||||
|
unimplemented!("Artifact prefix is thin and cannot get, replace, or remove.")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next(&self, _id: CorpusId) -> Option<CorpusId> {
|
||||||
|
unimplemented!("Artifact prefix is thin and cannot get, replace, or remove.")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prev(&self, _id: CorpusId) -> Option<CorpusId> {
|
||||||
|
unimplemented!("Artifact prefix is thin and cannot get, replace, or remove.")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn first(&self) -> Option<CorpusId> {
|
||||||
|
unimplemented!("Artifact prefix is thin and cannot get, replace, or remove.")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn last(&self) -> Option<CorpusId> {
|
||||||
|
self.count.checked_sub(1).map(CorpusId::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_input_into(&self, _testcase: &mut Testcase<Self::Input>) -> Result<(), Error> {
|
||||||
|
unimplemented!("Artifact prefix is thin and cannot get, replace, or remove.")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn store_input_from(&self, _testcase: &Testcase<Self::Input>) -> Result<(), Error> {
|
||||||
|
unimplemented!("Artifact prefix is thin and cannot get, replace, or remove.")
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,5 @@
|
|||||||
use alloc::rc::Rc;
|
use alloc::rc::Rc;
|
||||||
use core::{cell::RefCell, fmt::Debug};
|
use core::{cell::RefCell, fmt::Debug};
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use libafl::{
|
use libafl::{
|
||||||
alloc,
|
alloc,
|
||||||
@ -77,12 +76,12 @@ impl LibfuzzerCrashCauseMetadata {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct LibfuzzerCrashCauseFeedback {
|
pub struct LibfuzzerCrashCauseFeedback {
|
||||||
artifact_prefix: Option<ArtifactPrefix>,
|
artifact_prefix: ArtifactPrefix,
|
||||||
exit_kind: ExitKind,
|
exit_kind: ExitKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LibfuzzerCrashCauseFeedback {
|
impl LibfuzzerCrashCauseFeedback {
|
||||||
pub fn new(artifact_prefix: Option<ArtifactPrefix>) -> Self {
|
pub fn new(artifact_prefix: ArtifactPrefix) -> Self {
|
||||||
Self {
|
Self {
|
||||||
artifact_prefix,
|
artifact_prefix,
|
||||||
exit_kind: ExitKind::Ok,
|
exit_kind: ExitKind::Ok,
|
||||||
@ -104,17 +103,10 @@ impl LibfuzzerCrashCauseFeedback {
|
|||||||
let name = testcase.input().as_ref().unwrap().generate_name(0);
|
let name = testcase.input().as_ref().unwrap().generate_name(0);
|
||||||
name
|
name
|
||||||
};
|
};
|
||||||
let file_path = if let Some(artifact_prefix) = self.artifact_prefix.as_ref() {
|
let file_path = self.artifact_prefix.dir().join(format!(
|
||||||
if let Some(filename_prefix) = artifact_prefix.filename_prefix() {
|
"{}{prefix}-{base}",
|
||||||
artifact_prefix
|
self.artifact_prefix.filename_prefix()
|
||||||
.dir()
|
));
|
||||||
.join(format!("{filename_prefix}{prefix}-{base}"))
|
|
||||||
} else {
|
|
||||||
artifact_prefix.dir().join(format!("{prefix}-{base}"))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
PathBuf::from(format!("{prefix}-{base}"))
|
|
||||||
};
|
|
||||||
*testcase.file_path_mut() = Some(file_path);
|
*testcase.file_path_mut() = Some(file_path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -79,9 +79,11 @@ use libafl::{
|
|||||||
Error,
|
Error,
|
||||||
};
|
};
|
||||||
use libafl_bolts::AsSlice;
|
use libafl_bolts::AsSlice;
|
||||||
|
use libc::_exit;
|
||||||
|
|
||||||
use crate::options::{LibfuzzerMode, LibfuzzerOptions};
|
use crate::options::{LibfuzzerMode, LibfuzzerOptions};
|
||||||
|
|
||||||
|
mod corpus;
|
||||||
mod feedbacks;
|
mod feedbacks;
|
||||||
mod fuzz;
|
mod fuzz;
|
||||||
mod merge;
|
mod merge;
|
||||||
@ -152,7 +154,7 @@ macro_rules! fuzz_with {
|
|||||||
AsSlice,
|
AsSlice,
|
||||||
};
|
};
|
||||||
use libafl::{
|
use libafl::{
|
||||||
corpus::{CachedOnDiskCorpus, Corpus, OnDiskCorpus},
|
corpus::Corpus,
|
||||||
executors::{ExitKind, InProcessExecutor, TimeoutExecutor},
|
executors::{ExitKind, InProcessExecutor, TimeoutExecutor},
|
||||||
feedback_and_fast, feedback_not, feedback_or, feedback_or_fast,
|
feedback_and_fast, feedback_not, feedback_or, feedback_or_fast,
|
||||||
feedbacks::{ConstFeedback, CrashFeedback, MaxMapFeedback, NewHashFeedback, TimeFeedback, TimeoutFeedback},
|
feedbacks::{ConstFeedback, CrashFeedback, MaxMapFeedback, NewHashFeedback, TimeFeedback, TimeoutFeedback},
|
||||||
@ -179,6 +181,7 @@ macro_rules! fuzz_with {
|
|||||||
use std::{env::temp_dir, fs::create_dir, path::PathBuf};
|
use std::{env::temp_dir, fs::create_dir, path::PathBuf};
|
||||||
|
|
||||||
use crate::{BACKTRACE, CustomMutationStatus};
|
use crate::{BACKTRACE, CustomMutationStatus};
|
||||||
|
use crate::corpus::{ArtifactCorpus, LibfuzzerCorpus};
|
||||||
use crate::feedbacks::{LibfuzzerCrashCauseFeedback, LibfuzzerKeepFeedback, ShrinkMapFeedback};
|
use crate::feedbacks::{LibfuzzerCrashCauseFeedback, LibfuzzerKeepFeedback, ShrinkMapFeedback};
|
||||||
use crate::misc::should_use_grimoire;
|
use crate::misc::should_use_grimoire;
|
||||||
use crate::observers::{MappedEdgeMapObserver, SizeValueObserver};
|
use crate::observers::{MappedEdgeMapObserver, SizeValueObserver};
|
||||||
@ -243,7 +246,7 @@ macro_rules! fuzz_with {
|
|||||||
|
|
||||||
// A feedback to choose if an input is a solution or not
|
// A feedback to choose if an input is a solution or not
|
||||||
let mut objective = feedback_or_fast!(
|
let mut objective = feedback_or_fast!(
|
||||||
LibfuzzerCrashCauseFeedback::new($options.artifact_prefix().cloned()),
|
LibfuzzerCrashCauseFeedback::new($options.artifact_prefix().clone()),
|
||||||
OomFeedback,
|
OomFeedback,
|
||||||
feedback_and_fast!(
|
feedback_and_fast!(
|
||||||
CrashFeedback::new(),
|
CrashFeedback::new(),
|
||||||
@ -269,13 +272,7 @@ macro_rules! fuzz_with {
|
|||||||
dir
|
dir
|
||||||
};
|
};
|
||||||
|
|
||||||
let crash_corpus = if let Some(prefix) = $options.artifact_prefix() {
|
let crash_corpus = ArtifactCorpus::new();
|
||||||
OnDiskCorpus::with_meta_format_and_prefix(prefix.dir(), None, prefix.filename_prefix().clone(), false)
|
|
||||||
.unwrap()
|
|
||||||
} else {
|
|
||||||
OnDiskCorpus::with_meta_format_and_prefix(&std::env::current_dir().unwrap(), None, None, false)
|
|
||||||
.unwrap()
|
|
||||||
};
|
|
||||||
|
|
||||||
// If not restarting, create a State from scratch
|
// If not restarting, create a State from scratch
|
||||||
let mut state = state.unwrap_or_else(|| {
|
let mut state = state.unwrap_or_else(|| {
|
||||||
@ -283,7 +280,7 @@ macro_rules! fuzz_with {
|
|||||||
// RNG
|
// RNG
|
||||||
StdRand::with_seed(current_nanos()),
|
StdRand::with_seed(current_nanos()),
|
||||||
// Corpus that will be evolved, we keep it in memory for performance
|
// Corpus that will be evolved, we keep it in memory for performance
|
||||||
CachedOnDiskCorpus::with_meta_format_and_prefix(corpus_dir.clone(), 4096, None, None, true).unwrap(),
|
LibfuzzerCorpus::new(corpus_dir.clone(), 4096),
|
||||||
// Corpus in which we store solutions (crashes in this example),
|
// Corpus in which we store solutions (crashes in this example),
|
||||||
// on disk so the user can get them after stopping the fuzzer
|
// on disk so the user can get them after stopping the fuzzer
|
||||||
crash_corpus,
|
crash_corpus,
|
||||||
@ -591,7 +588,21 @@ pub unsafe extern "C" fn LLVMFuzzerRunDriver(
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
if !options.unknown().is_empty() {
|
if !options.unknown().is_empty() {
|
||||||
println!("Unrecognised options: {:?}", options.unknown());
|
eprintln!("Unrecognised options: {:?}", options.unknown());
|
||||||
|
}
|
||||||
|
|
||||||
|
for folder in options
|
||||||
|
.dirs()
|
||||||
|
.iter()
|
||||||
|
.chain(std::iter::once(options.artifact_prefix().dir()))
|
||||||
|
{
|
||||||
|
if !folder.try_exists().unwrap_or(false) {
|
||||||
|
eprintln!(
|
||||||
|
"Required folder {} did not exist; failing fast.",
|
||||||
|
folder.to_string_lossy()
|
||||||
|
);
|
||||||
|
_exit(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if *options.mode() != LibfuzzerMode::Tmin
|
if *options.mode() != LibfuzzerMode::Tmin
|
||||||
|
@ -8,7 +8,7 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use libafl::{
|
use libafl::{
|
||||||
corpus::{Corpus, OnDiskCorpus},
|
corpus::Corpus,
|
||||||
events::{EventRestarter, SimpleRestartingEventManager},
|
events::{EventRestarter, SimpleRestartingEventManager},
|
||||||
executors::{ExitKind, InProcessExecutor, TimeoutExecutor},
|
executors::{ExitKind, InProcessExecutor, TimeoutExecutor},
|
||||||
feedback_and_fast, feedback_or_fast,
|
feedback_and_fast, feedback_or_fast,
|
||||||
@ -29,6 +29,7 @@ use libafl_bolts::{
|
|||||||
use libafl_targets::{OomFeedback, OomObserver, COUNTERS_MAPS};
|
use libafl_targets::{OomFeedback, OomObserver, COUNTERS_MAPS};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
corpus::{ArtifactCorpus, LibfuzzerCorpus},
|
||||||
feedbacks::{LibfuzzerCrashCauseFeedback, LibfuzzerKeepFeedback},
|
feedbacks::{LibfuzzerCrashCauseFeedback, LibfuzzerKeepFeedback},
|
||||||
observers::{MappedEdgeMapObserver, SizeTimeValueObserver},
|
observers::{MappedEdgeMapObserver, SizeTimeValueObserver},
|
||||||
options::LibfuzzerOptions,
|
options::LibfuzzerOptions,
|
||||||
@ -44,23 +45,7 @@ pub fn merge(
|
|||||||
return Err(Error::illegal_argument("Missing corpora to minimize; you should provide one directory to minimize into and one-to-many from which the inputs are loaded."));
|
return Err(Error::illegal_argument("Missing corpora to minimize; you should provide one directory to minimize into and one-to-many from which the inputs are loaded."));
|
||||||
}
|
}
|
||||||
|
|
||||||
let crash_corpus = if let Some(prefix) = options.artifact_prefix() {
|
let crash_corpus = ArtifactCorpus::new();
|
||||||
OnDiskCorpus::with_meta_format_and_prefix(
|
|
||||||
prefix.dir(),
|
|
||||||
None,
|
|
||||||
prefix.filename_prefix().clone(),
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
} else {
|
|
||||||
OnDiskCorpus::with_meta_format_and_prefix(
|
|
||||||
&std::env::current_dir().unwrap(),
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
};
|
|
||||||
|
|
||||||
let keep_observer = LibfuzzerKeepFeedback::new();
|
let keep_observer = LibfuzzerKeepFeedback::new();
|
||||||
let keep = keep_observer.keep();
|
let keep = keep_observer.keep();
|
||||||
@ -130,7 +115,7 @@ pub fn merge(
|
|||||||
|
|
||||||
// A feedback to choose if an input is a solution or not
|
// A feedback to choose if an input is a solution or not
|
||||||
let mut objective = feedback_or_fast!(
|
let mut objective = feedback_or_fast!(
|
||||||
LibfuzzerCrashCauseFeedback::new(options.artifact_prefix().cloned()),
|
LibfuzzerCrashCauseFeedback::new(options.artifact_prefix().clone()),
|
||||||
OomFeedback,
|
OomFeedback,
|
||||||
CrashFeedback::new(),
|
CrashFeedback::new(),
|
||||||
TimeoutFeedback::new()
|
TimeoutFeedback::new()
|
||||||
@ -163,7 +148,7 @@ pub fn merge(
|
|||||||
// RNG
|
// RNG
|
||||||
StdRand::new(),
|
StdRand::new(),
|
||||||
// Corpus that will be evolved, we keep it in memory for performance
|
// Corpus that will be evolved, we keep it in memory for performance
|
||||||
OnDiskCorpus::with_meta_format_and_prefix(&corpus_dir, None, None, true).unwrap(),
|
LibfuzzerCorpus::new(corpus_dir, 4096),
|
||||||
// Corpus in which we store solutions (crashes in this example),
|
// Corpus in which we store solutions (crashes in this example),
|
||||||
// on disk so the user can get them after stopping the fuzzer
|
// on disk so the user can get them after stopping the fuzzer
|
||||||
crash_corpus,
|
crash_corpus,
|
||||||
|
@ -2,6 +2,7 @@ use core::fmt::{Display, Formatter};
|
|||||||
use std::{path::PathBuf, time::Duration};
|
use std::{path::PathBuf, time::Duration};
|
||||||
|
|
||||||
use libafl::mutators::Tokens;
|
use libafl::mutators::Tokens;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::options::RawOption::{Directory, Flag};
|
use crate::options::RawOption::{Directory, Flag};
|
||||||
|
|
||||||
@ -52,10 +53,10 @@ impl<'a> Display for OptionsParseError<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct ArtifactPrefix {
|
pub struct ArtifactPrefix {
|
||||||
dir: PathBuf,
|
dir: PathBuf,
|
||||||
filename_prefix: Option<String>,
|
filename_prefix: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ArtifactPrefix {
|
impl ArtifactPrefix {
|
||||||
@ -64,10 +65,10 @@ impl ArtifactPrefix {
|
|||||||
if path.ends_with(std::path::MAIN_SEPARATOR) {
|
if path.ends_with(std::path::MAIN_SEPARATOR) {
|
||||||
Self {
|
Self {
|
||||||
dir,
|
dir,
|
||||||
filename_prefix: None,
|
filename_prefix: String::new(),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let filename_prefix = dir.file_name().map(|s| {
|
let filename_prefix = dir.file_name().map_or_else(String::new, |s| {
|
||||||
s.to_os_string()
|
s.to_os_string()
|
||||||
.into_string()
|
.into_string()
|
||||||
.expect("Provided artifact prefix is not usable")
|
.expect("Provided artifact prefix is not usable")
|
||||||
@ -84,17 +85,26 @@ impl ArtifactPrefix {
|
|||||||
&self.dir
|
&self.dir
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn filename_prefix(&self) -> &Option<String> {
|
pub fn filename_prefix(&self) -> &str {
|
||||||
&self.filename_prefix
|
&self.filename_prefix
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for ArtifactPrefix {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
dir: std::env::current_dir().expect("Must be able to get the current directory!"),
|
||||||
|
filename_prefix: String::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
#[allow(clippy::struct_excessive_bools)]
|
#[allow(clippy::struct_excessive_bools)]
|
||||||
pub struct LibfuzzerOptions {
|
pub struct LibfuzzerOptions {
|
||||||
fuzzer_name: String,
|
fuzzer_name: String,
|
||||||
mode: LibfuzzerMode,
|
mode: LibfuzzerMode,
|
||||||
artifact_prefix: Option<ArtifactPrefix>,
|
artifact_prefix: ArtifactPrefix,
|
||||||
timeout: Duration,
|
timeout: Duration,
|
||||||
grimoire: Option<bool>,
|
grimoire: Option<bool>,
|
||||||
forks: Option<usize>,
|
forks: Option<usize>,
|
||||||
@ -140,8 +150,8 @@ impl LibfuzzerOptions {
|
|||||||
&self.mode
|
&self.mode
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn artifact_prefix(&self) -> Option<&ArtifactPrefix> {
|
pub fn artifact_prefix(&self) -> &ArtifactPrefix {
|
||||||
self.artifact_prefix.as_ref()
|
&self.artifact_prefix
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn timeout(&self) -> Duration {
|
pub fn timeout(&self) -> Duration {
|
||||||
@ -333,7 +343,10 @@ impl<'a> LibfuzzerOptionsBuilder<'a> {
|
|||||||
LibfuzzerOptions {
|
LibfuzzerOptions {
|
||||||
fuzzer_name,
|
fuzzer_name,
|
||||||
mode: self.mode.unwrap_or(LibfuzzerMode::Fuzz),
|
mode: self.mode.unwrap_or(LibfuzzerMode::Fuzz),
|
||||||
artifact_prefix: self.artifact_prefix.map(ArtifactPrefix::new),
|
artifact_prefix: self
|
||||||
|
.artifact_prefix
|
||||||
|
.map(ArtifactPrefix::new)
|
||||||
|
.unwrap_or_default(),
|
||||||
timeout: self.timeout.unwrap_or(Duration::from_secs(1200)),
|
timeout: self.timeout.unwrap_or(Duration::from_secs(1200)),
|
||||||
grimoire: self.grimoire,
|
grimoire: self.grimoire,
|
||||||
forks: self.forks,
|
forks: self.forks,
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
use std::{
|
use std::{
|
||||||
ffi::c_int,
|
ffi::c_int,
|
||||||
fs::{read, write},
|
fs::{read, write},
|
||||||
path::PathBuf,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use libafl::{
|
use libafl::{
|
||||||
@ -119,21 +118,10 @@ fn minimize_crash_with_mutator<M: Mutator<BytesInput, TMinState>>(
|
|||||||
options.dirs()[0].as_path().as_os_str().to_str().unwrap()
|
options.dirs()[0].as_path().as_os_str().to_str().unwrap()
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
let (mut dest, filename_prefix) = options.artifact_prefix().map_or_else(
|
let mut dest = options.artifact_prefix().dir().clone();
|
||||||
|| (PathBuf::default(), ""),
|
|
||||||
|artifact_prefix| {
|
|
||||||
(
|
|
||||||
artifact_prefix.dir().clone(),
|
|
||||||
artifact_prefix
|
|
||||||
.filename_prefix()
|
|
||||||
.as_ref()
|
|
||||||
.map_or("", String::as_str),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
);
|
|
||||||
dest.push(format!(
|
dest.push(format!(
|
||||||
"{}minimized-from-{}",
|
"{}minimized-from-{}",
|
||||||
filename_prefix,
|
options.artifact_prefix().filename_prefix(),
|
||||||
options.dirs()[0].file_name().unwrap().to_str().unwrap()
|
options.dirs()[0].file_name().unwrap().to_str().unwrap()
|
||||||
));
|
));
|
||||||
write(&dest, input)?;
|
write(&dest, input)?;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user