diff --git a/libafl/src/stages/sync.rs b/libafl/src/stages/sync.rs index 9ae63d2cf6..8c8641358f 100644 --- a/libafl/src/stages/sync.rs +++ b/libafl/src/stages/sync.rs @@ -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 { 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::() .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 SyncFromDiskStage { /// 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, load_callback: CB, interval: Duration, name: &str) -> Self { Self { diff --git a/libafl_bolts/src/lib.rs b/libafl_bolts/src/lib.rs index 4731829bb1..83805bf087 100644 --- a/libafl_bolts/src/lib.rs +++ b/libafl_bolts/src/lib.rs @@ -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(reason: S) -> Self + where + S: Into, + { + Error::InvalidInput(reason.into(), ErrorBacktrace::new()) + } + /// Key not in Map #[must_use] pub fn key_not_found(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) + } } } }