Introduce AFL_EXIT_ON_SEED_ISSUES (#2085)

* introduce load_initial_inputs_disallow_solution to exit if a solution is found during seed loading

* fmt

* rename CorpusError to Corpus

* add LoadConfig to simplify configuration for loading initial inputs

* Rename Error::Corpus to Error::InvalidCorpus
Add documentation to LoadConfig struct
fix nostd for LoadConfig

---------

Co-authored-by: aarnav <aarnav@srlabs.de>
This commit is contained in:
Aarnav 2024-04-23 06:53:18 -07:00 committed by GitHub
parent d34965192d
commit 76a95bc5fd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 103 additions and 24 deletions

View File

@ -193,6 +193,24 @@ pub trait HasLastReportTime {
fn last_report_time_mut(&mut self) -> &mut Option<Duration>; fn last_report_time_mut(&mut self) -> &mut Option<Duration>;
} }
/// Struct that holds the options for input loading
#[cfg(feature = "std")]
pub struct LoadConfig<'a, I, S, Z> {
/// Load Input even if it was deemed "uninteresting" by the fuzzer
forced: bool,
/// Function to load input from a Path
loader: &'a mut dyn FnMut(&mut Z, &mut S, &Path) -> Result<I, Error>,
/// Error if Input leads to a Solution.
exit_on_solution: bool,
}
#[cfg(feature = "std")]
impl<'a, I, S, Z> Debug for LoadConfig<'a, I, S, Z> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "LoadConfig {{}}")
}
}
/// The state a fuzz run. /// The state a fuzz run.
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(bound = " #[serde(bound = "
@ -647,8 +665,7 @@ where
executor: &mut E, executor: &mut E,
manager: &mut EM, manager: &mut EM,
file_list: &[PathBuf], file_list: &[PathBuf],
forced: bool, load_config: LoadConfig<I, Self, Z>,
loader: &mut dyn FnMut(&mut Z, &mut Self, &Path) -> Result<I, Error>,
) -> Result<(), Error> ) -> Result<(), Error>
where where
E: UsesState<State = Self>, E: UsesState<State = Self>,
@ -664,45 +681,45 @@ where
self.remaining_initial_files = Some(file_list.to_vec()); self.remaining_initial_files = Some(file_list.to_vec());
} }
self.continue_loading_initial_inputs_custom(fuzzer, executor, manager, forced, loader) self.continue_loading_initial_inputs_custom(fuzzer, executor, manager, load_config)
} }
fn load_file<E, EM, Z>( fn load_file<E, EM, Z>(
&mut self, &mut self,
path: &PathBuf, path: &PathBuf,
manager: &mut EM, manager: &mut EM,
fuzzer: &mut Z, fuzzer: &mut Z,
executor: &mut E, executor: &mut E,
forced: bool, config: &mut LoadConfig<I, Self, Z>,
loader: &mut dyn FnMut(&mut Z, &mut Self, &Path) -> Result<I, Error>, ) -> Result<ExecuteInputResult, Error>
) -> Result<(), Error>
where where
E: UsesState<State = Self>, E: UsesState<State = Self>,
EM: EventFirer<State = Self>, EM: EventFirer<State = Self>,
Z: Evaluator<E, EM, State = Self>, Z: Evaluator<E, EM, State = Self>,
{ {
log::info!("Loading file {:?} ...", &path); log::info!("Loading file {:?} ...", &path);
let input = loader(fuzzer, self, path)?; let input = (config.loader)(fuzzer, self, path)?;
if forced { if config.forced {
let _: CorpusId = fuzzer.add_input(self, executor, manager, input)?; let _: CorpusId = fuzzer.add_input(self, executor, manager, input)?;
Ok(ExecuteInputResult::Corpus)
} else { } else {
let (res, _) = fuzzer.evaluate_input(self, executor, manager, input.clone())?; let (res, _) = fuzzer.evaluate_input(self, executor, manager, input.clone())?;
if res == ExecuteInputResult::None { if res == ExecuteInputResult::None {
fuzzer.add_disabled_input(self, input)?; fuzzer.add_disabled_input(self, input)?;
log::warn!("input {:?} was not interesting, adding as disabled.", &path); log::warn!("input {:?} was not interesting, adding as disabled.", &path);
} }
Ok(res)
} }
Ok(())
} }
/// Loads initial inputs from the passed-in `in_dirs`. /// Loads initial inputs from the passed-in `in_dirs`.
/// If `forced` is true, will add all testcases, no matter what. /// This method takes a list of files and a `LoadConfig`
/// This method takes a list of files. /// which specifies the special handling of initial inputs
fn continue_loading_initial_inputs_custom<E, EM, Z>( fn continue_loading_initial_inputs_custom<E, EM, Z>(
&mut self, &mut self,
fuzzer: &mut Z, fuzzer: &mut Z,
executor: &mut E, executor: &mut E,
manager: &mut EM, manager: &mut EM,
forced: bool, mut config: LoadConfig<I, Self, Z>,
loader: &mut dyn FnMut(&mut Z, &mut Self, &Path) -> Result<I, Error>,
) -> Result<(), Error> ) -> Result<(), Error>
where where
E: UsesState<State = Self>, E: UsesState<State = Self>,
@ -712,7 +729,13 @@ where
loop { loop {
match self.next_file() { match self.next_file() {
Ok(path) => { Ok(path) => {
self.load_file(&path, manager, fuzzer, executor, forced, loader)?; let res = self.load_file(&path, manager, fuzzer, executor, &mut config)?;
if config.exit_on_solution && matches!(res, ExecuteInputResult::Solution) {
return Err(Error::invalid_corpus(format!(
"Input {} resulted in a solution.",
path.display()
)));
}
} }
Err(Error::IteratorEnd(_, _)) => break, Err(Error::IteratorEnd(_, _)) => break,
Err(e) => return Err(e), Err(e) => return Err(e),
@ -751,8 +774,11 @@ where
executor, executor,
manager, manager,
file_list, file_list,
false, LoadConfig {
&mut |_, _, path| I::from_file(path), loader: &mut |_, _, path| I::from_file(path),
forced: false,
exit_on_solution: false,
},
) )
} }
@ -776,8 +802,11 @@ where
fuzzer, fuzzer,
executor, executor,
manager, manager,
true, LoadConfig {
&mut |_, _, path| I::from_file(path), loader: &mut |_, _, path| I::from_file(path),
forced: true,
exit_on_solution: false,
},
) )
} }
/// Loads initial inputs from the passed-in `in_dirs`. /// Loads initial inputs from the passed-in `in_dirs`.
@ -800,8 +829,11 @@ where
executor, executor,
manager, manager,
file_list, file_list,
true, LoadConfig {
&mut |_, _, path| I::from_file(path), loader: &mut |_, _, path| I::from_file(path),
forced: true,
exit_on_solution: false,
},
) )
} }
@ -823,8 +855,38 @@ where
fuzzer, fuzzer,
executor, executor,
manager, manager,
false, LoadConfig {
&mut |_, _, path| I::from_file(path), loader: &mut |_, _, path| I::from_file(path),
forced: false,
exit_on_solution: false,
},
)
}
/// Loads initial inputs from the passed-in `in_dirs`.
/// Will return a `CorpusError` if a solution is found
pub fn load_initial_inputs_disallow_solution<E, EM, Z>(
&mut self,
fuzzer: &mut Z,
executor: &mut E,
manager: &mut EM,
in_dirs: &[PathBuf],
) -> Result<(), Error>
where
E: UsesState<State = Self>,
EM: EventFirer<State = Self>,
Z: Evaluator<E, EM, State = Self>,
{
self.canonicalize_input_dirs(in_dirs)?;
self.continue_loading_initial_inputs_custom(
fuzzer,
executor,
manager,
LoadConfig {
loader: &mut |_, _, path| I::from_file(path),
forced: false,
exit_on_solution: true,
},
) )
} }
@ -862,8 +924,11 @@ where
fuzzer, fuzzer,
executor, executor,
manager, manager,
false, LoadConfig {
&mut |_, _, path| I::from_file(path), loader: &mut |_, _, path| I::from_file(path),
forced: false,
exit_on_solution: false,
},
)?; )?;
} else { } else {
self.canonicalize_input_dirs(in_dirs)?; self.canonicalize_input_dirs(in_dirs)?;

View File

@ -322,6 +322,8 @@ pub enum Error {
OsError(io::Error, String, ErrorBacktrace), OsError(io::Error, String, ErrorBacktrace),
/// Something else happened /// Something else happened
Unknown(String, ErrorBacktrace), Unknown(String, ErrorBacktrace),
/// Error with the corpora
InvalidCorpus(String, ErrorBacktrace),
} }
impl Error { impl Error {
@ -438,6 +440,14 @@ impl Error {
{ {
Error::Unknown(arg.into(), ErrorBacktrace::new()) Error::Unknown(arg.into(), ErrorBacktrace::new())
} }
/// Error with corpora
#[must_use]
pub fn invalid_corpus<S>(arg: S) -> Self
where
S: Into<String>,
{
Error::InvalidCorpus(arg.into(), ErrorBacktrace::new())
}
} }
impl Display for Error { impl Display for Error {
@ -498,6 +508,10 @@ impl Display for Error {
write!(f, "Unknown error: {0}", &s)?; write!(f, "Unknown error: {0}", &s)?;
display_error_backtrace(f, b) display_error_backtrace(f, b)
} }
Self::InvalidCorpus(s, b) => {
write!(f, "Invalid corpus: {0}", &s)?;
display_error_backtrace(f, b)
}
} }
} }
} }