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:
Addison Crump 2023-11-03 17:33:38 +01:00 committed by GitHub
parent fd98eabfbf
commit 56b37bb4bd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 415 additions and 81 deletions

View File

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

View File

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

View File

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

View File

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

View 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.")
}
}

View File

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

View File

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

View File

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

View File

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

View File

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