Skip to content

Make input loading fallible in SyncFromDiskStage #3195

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
May 14, 2025
Merged
16 changes: 14 additions & 2 deletions libafl/src/stages/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you link to the error like so

[`Error::InvalidInput`]

#[derive(Debug)]
pub struct SyncFromDiskStage<CB, E, EM, I, S, Z> {
name: Cow<'static, str>,
Expand Down Expand Up @@ -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)?;
}
Expand Down Expand Up @@ -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 {
Expand Down
15 changes: 15 additions & 0 deletions libafl_bolts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
}
}
}
Expand Down
Loading