Make input loading fallible in SyncFromDiskStage (#3195)

* Make input loading fallible in SyncFromDiskStage

* fmt

* Add InvalidInput in Error enum and skip the Input in SyncFromDiskStage if it is encountered

* sync: remove file if error on loading in SyncFromDiskStage

* add reason to Error::InvalidInput

* sync make failure log a warning. clippy, fmt

* typo

* fmt

* fmt

---------

Co-authored-by: Dongjia "toka" Zhang <tokazerkje@outlook.com>
Co-authored-by: Dominik Maier <domenukk@gmail.com>
This commit is contained in:
Aarnav 2025-05-14 08:39:25 +02:00 committed by GitHub
parent fef129e23c
commit c44802cf02
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 29 additions and 2 deletions

View File

@ -58,6 +58,7 @@ impl SyncFromDiskMetadata {
}
/// A stage that loads testcases from disk to sync with other fuzzers such as AFL++
/// When syncing, the stage will ignore `Error::InvalidInput` and will skip the file.
#[derive(Debug)]
pub struct SyncFromDiskStage<CB, E, EM, I, S, Z> {
name: Cow<'static, str>,
@ -125,15 +126,25 @@ where
let to_sync = sync_from_disk_metadata.left_to_sync.clone();
log::debug!("Number of files to sync: {:?}", to_sync.len());
for path in to_sync {
let input = (self.load_callback)(fuzzer, state, &path)?;
// Removing each path from the `left_to_sync` Vec before evaluating
// prevents duplicate processing and ensures that each file is evaluated only once. This approach helps
// avoid potential infinite loops that may occur if a file is an objective.
// avoid potential infinite loops that may occur if a file is an objective or an invalid input.
state
.metadata_mut::<SyncFromDiskMetadata>()
.unwrap()
.left_to_sync
.retain(|p| p != &path);
let input = match (self.load_callback)(fuzzer, state, &path) {
Ok(input) => input,
Err(Error::InvalidInput(reason, _)) => {
log::warn!(
"Invalid input found in {} when syncing; reason {reason}; skipping;",
path.display()
);
continue;
}
Err(e) => return Err(e),
};
log::debug!("Syncing and evaluating {}", path.display());
fuzzer.evaluate_input(state, executor, manager, &input)?;
}
@ -161,6 +172,7 @@ where
impl<CB, E, EM, I, S, Z> SyncFromDiskStage<CB, E, EM, I, S, Z> {
/// Creates a new [`SyncFromDiskStage`]
/// To skip a file, you can return `Error::invalid_input` in `load_callback`
#[must_use]
pub fn new(sync_dirs: Vec<PathBuf>, load_callback: CB, interval: Duration, name: &str) -> Self {
Self {

View File

@ -341,6 +341,8 @@ pub enum Error {
InvalidCorpus(String, ErrorBacktrace),
/// Error specific to a runtime like QEMU or Frida
Runtime(String, ErrorBacktrace),
/// The `Input` was invalid.
InvalidInput(String, ErrorBacktrace),
}
impl Error {
@ -369,6 +371,15 @@ impl Error {
Error::EmptyOptional(arg.into(), ErrorBacktrace::new())
}
/// The `Input` was invalid
#[must_use]
pub fn invalid_input<S>(reason: S) -> Self
where
S: Into<String>,
{
Error::InvalidInput(reason.into(), ErrorBacktrace::new())
}
/// Key not in Map
#[must_use]
pub fn key_not_found<S>(arg: S) -> Self
@ -580,6 +591,10 @@ impl Display for Error {
write!(f, "Runtime error: {0}", &s)?;
display_error_backtrace(f, b)
}
Self::InvalidInput(s, b) => {
write!(f, "Encountered an invalid input: {0}", &s)?;
display_error_backtrace(f, b)
}
}
}
}