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

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions libafl/src/stages/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@ use core::{marker::PhantomData, time::Duration};
use std::path::{Path, PathBuf};

use libafl_bolts::{
Named, current_time,
current_time,
fs::find_new_files_rec,
shmem::{ShMem, ShMemProvider},
Named,
};
use serde::{Deserialize, Serialize};

use crate::{
Error, HasMetadata, HasNamedMetadata,
corpus::{Corpus, CorpusId, HasCurrentCorpusId},
events::{Event, EventConfig, EventFirer, EventWithStats, llmp::LlmpEventConverter},
events::{llmp::LlmpEventConverter, Event, EventConfig, EventFirer, EventWithStats},
executors::{Executor, ExitKind, HasObservers},
fuzzer::{Evaluator, EvaluatorObservers, ExecutionProcessor, HasObjective},
inputs::{Input, InputConverter},
Expand All @@ -26,6 +26,7 @@ use crate::{
HasCorpus, HasCurrentTestcase, HasExecutions, HasRand, HasSolutions,
MaybeHasClientPerfMonitor, Stoppable,
},
Error, HasMetadata, HasNamedMetadata,
};

/// Default name for `SyncFromDiskStage`; derived from AFL++
Expand Down Expand Up @@ -58,6 +59,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>,
Expand Down Expand Up @@ -125,15 +127,24 @@ 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::debug!(
Copy link
Member

Choose a reason for hiding this comment

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

Think this should be warning!

Copy link
Member

Choose a reason for hiding this comment

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

Or maybe info if you think it's expected

"Invalid input found in {path:?} when syncing; reason {reason}; skipping;"
);
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
25 changes: 21 additions & 4 deletions libafl_bolts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ type String = &'static str;
/// Good enough for simple errors, for anything else, use the `alloc` feature.
#[cfg(not(feature = "alloc"))]
macro_rules! format {
($fmt:literal) => {{ $fmt }};
($fmt:literal) => {{
$fmt
}};
}

#[cfg(feature = "std")]
Expand Down Expand Up @@ -164,7 +166,7 @@ use std::time::{SystemTime, UNIX_EPOCH};
#[cfg(all(unix, feature = "std"))]
use std::{
fs::File,
io::{Write, stderr, stdout},
io::{stderr, stdout, Write},
os::fd::{AsRawFd, FromRawFd, RawFd},
panic,
};
Expand Down Expand Up @@ -341,6 +343,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 +373,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 +593,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 Expand Up @@ -1088,7 +1105,7 @@ pub fn get_thread_id() -> u64 {
#[allow(clippy::cast_sign_loss)]
/// Return thread ID without using TLS
pub fn get_thread_id() -> u64 {
use libc::{SYS_gettid, syscall};
use libc::{syscall, SYS_gettid};
Copy link
Member

Choose a reason for hiding this comment

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

is this a cargo fmt update of some sorts?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yes

Copy link
Member

Choose a reason for hiding this comment

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

Seems like your fmt is not the same as the CI one


unsafe { syscall(SYS_gettid) as u64 }
}
Expand Down Expand Up @@ -1441,7 +1458,7 @@ macro_rules! nonnull_raw_mut {
#[allow(missing_docs)] // expect somehow breaks here
pub mod pybind {

use pyo3::{Bound, PyResult, pymodule, types::PyModule};
use pyo3::{pymodule, types::PyModule, Bound, PyResult};

#[macro_export]
macro_rules! unwrap_me_body {
Expand Down
Loading