diff --git a/libafl/src/bolts/fs.rs b/libafl/src/bolts/fs.rs index 970d85638e..fd2a928824 100644 --- a/libafl/src/bolts/fs.rs +++ b/libafl/src/bolts/fs.rs @@ -12,7 +12,7 @@ use std::os::unix::prelude::{AsRawFd, RawFd}; use crate::Error; /// The default filename to use to deliver testcases to the target -pub const DEFAULT_OUTFILE: &str = ".cur_input"; +pub const OUTFILE_STD: &str = ".cur_input"; /// Creates a `.{file_name}.tmp` file, and writes all bytes to it. /// After all bytes have been written, the tmp-file is moved to it's original `path`. @@ -55,6 +55,14 @@ pub struct OutFile { pub file: File, } +impl Eq for OutFile {} + +impl PartialEq for OutFile { + fn eq(&self, other: &Self) -> bool { + self.path == other.path + } +} + impl Clone for OutFile { fn clone(&self) -> Self { Self { diff --git a/libafl/src/bolts/os/unix_signals.rs b/libafl/src/bolts/os/unix_signals.rs index 89b7c5fc5d..18b7d49f4e 100644 --- a/libafl/src/bolts/os/unix_signals.rs +++ b/libafl/src/bolts/os/unix_signals.rs @@ -290,7 +290,7 @@ pub fn ucontext() -> Result { } else { #[cfg(not(feature = "std"))] unsafe { - libc::perror(b"Failed to get ucontext\n".as_ptr() as _) + libc::perror(b"Failed to get ucontext\n".as_ptr() as _); }; #[cfg(not(feature = "std"))] return Err(Error::Unknown("Failed to get ucontex".into())); diff --git a/libafl/src/executors/combined.rs b/libafl/src/executors/combined.rs index e7dd148545..ba68356a42 100644 --- a/libafl/src/executors/combined.rs +++ b/libafl/src/executors/combined.rs @@ -1,4 +1,5 @@ //! A `CombinedExecutor` wraps a primary executor and a secondary one +//! In comparison to the [`crate::executors::DiffExecutor`] it does not run the secondary executor in `run_target`. use crate::{ executors::{Executor, ExitKind, HasObservers}, diff --git a/libafl/src/executors/command.rs b/libafl/src/executors/command.rs index 67dca47ac6..9a13297ad0 100644 --- a/libafl/src/executors/command.rs +++ b/libafl/src/executors/command.rs @@ -4,24 +4,25 @@ use core::{ marker::PhantomData, }; +#[cfg(unix)] +use std::os::unix::ffi::OsStrExt; #[cfg(feature = "std")] use std::process::Child; use std::{ ffi::{OsStr, OsString}, - io::Write, - os::unix::prelude::OsStringExt, + io::{Read, Write}, path::{Path, PathBuf}, process::{Command, Stdio}, }; use crate::{ bolts::{ - fs::{OutFile, DEFAULT_OUTFILE}, + fs::{OutFile, OUTFILE_STD}, tuples::MatchName, AsSlice, }, inputs::HasTargetBytes, - observers::{ASANBacktraceObserver, ObserversTuple}, + observers::{ASANBacktraceObserver, ObserversTuple, StdErrObserver, StdOutObserver}, }; #[cfg(feature = "std")] use crate::{inputs::Input, Error}; @@ -37,8 +38,8 @@ use super::HasObservers; /// How to deliver input to an external program /// `StdIn`: The traget reads from stdin /// `File`: The target reads from the specified [`OutFile`] -#[derive(Debug, Clone)] -pub enum InputLocation { +#[derive(Debug, Clone, PartialEq, Eq)] +enum InputLocation { /// Mutate a commandline argument to deliver an input Arg { /// The offset of the argument to mutate @@ -47,7 +48,7 @@ pub enum InputLocation { /// Deliver input via `StdIn` StdIn, /// Deliver the iniput via the specified [`OutFile`] - /// You can use specify [`OutFile::create(DEFAULT_OUTFILE)`] to use a default filename. + /// You can use specify [`OutFile::create(OUTFILE_STD)`] to use a default filename. File { /// The fiel to write input to. The target should read input from this location. out_file: OutFile, @@ -71,15 +72,16 @@ fn clone_command(cmd: &Command) -> Command { /// A simple Configurator that takes the most common parameters /// Writes the input either to stdio or to a file +/// Use [`CommandExecutor::builder()`] to use this configurator. #[derive(Debug)] pub struct StdCommandConfigurator { /// If set to true, the child output will remain visible /// By default, the child output is hidden to increase execution speed - pub debug_child: bool, + debug_child: bool, /// true: input gets delivered via stdink - pub input_location: InputLocation, + input_location: InputLocation, /// The Command to execute - pub command: Command, + command: Command, } impl CommandConfigurator for StdCommandConfigurator { @@ -99,7 +101,13 @@ impl CommandConfigurator for StdCommandConfigurator { for (i, arg) in args.enumerate() { if i == *argnum { - cmd.arg(OsString::from_vec(input.target_bytes().as_slice().to_vec())); + debug_assert_eq!(arg, "DUMMY"); + #[cfg(unix)] + cmd.arg(OsStr::from_bytes(input.target_bytes().as_slice())); + // There is an issue here that the chars on windows are 16 bit wide. + // I can't really test it. Please open a PR if this goes wrong. + #[cfg(not(unix))] + cmd.arg(OsString::from_vec(input.target_bytes().as_vec())); } else { cmd.arg(arg); } @@ -139,10 +147,17 @@ where T: Debug, OT: Debug, { - inner: T, + /// The wrapped comand configurer + configurer: T, observers: OT, /// cache if the AsanBacktraceObserver is present has_asan_observer: bool, + /// If set, we found a [`StdErrObserver`] in the observer list. + /// Pipe the child's `stderr` instead of closing it. + has_stdout_observer: bool, + /// If set, we found a [`StdOutObserver`] in the observer list + /// Pipe the child's `stdout` instead of closing it. + has_stderr_observer: bool, phantom: PhantomData<(EM, I, S, Z)>, } @@ -150,6 +165,15 @@ impl CommandExecutor<(), (), (), (), (), ()> { /// Creates a builder for a new [`CommandExecutor`], /// backed by a [`StdCommandConfigurator`] /// This is usually the easiest way to construct a [`CommandExecutor`]. + /// + /// It mimics the api of [`Command`], specifically, you will use + /// `arg`, `args`, `env`, and so on. + /// + /// By default, input is read from stdin, unless you specify a different location using + /// * `arg_input_arg` for input delivered _as_ an command line argument + /// * `arg_input_file` for input via a file of a specific name + /// * `arg_input_file_std` for a file with default name + /// (at the right location in the arguments) #[must_use] pub fn builder() -> CommandExecutorBuilder { CommandExecutorBuilder::new() @@ -163,7 +187,7 @@ where { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("CommandExecutor") - .field("inner", &self.inner) + .field("inner", &self.configurer) .field("observers", &self.observers) .finish() } @@ -176,7 +200,7 @@ where { /// Accesses the inner value pub fn inner(&mut self) -> &mut T { - &mut self.inner + &mut self.configurer } } @@ -202,23 +226,35 @@ where } command.stdin(Stdio::null()); + let has_stdout_observer = observers + .match_name::("StdOutObserver") + .is_some(); + if has_stdout_observer { + command.stderr(Stdio::piped()); + } + + let has_stderr_observer = observers + .match_name::("StdErrObserver") + .is_some(); let has_asan_observer = observers .match_name::("ASANBacktraceObserver") .is_some(); - if has_asan_observer { + if has_stderr_observer || has_asan_observer { command.stderr(Stdio::piped()); } Ok(Self { observers, has_asan_observer, - inner: StdCommandConfigurator { + configurer: StdCommandConfigurator { input_location: InputLocation::File { out_file: OutFile::create(path)?, }, command, debug_child, }, + has_stdout_observer, + has_stderr_observer, phantom: PhantomData, }) } @@ -255,19 +291,12 @@ where )); } atat_at = Some(pos); - builder.input(InputLocation::File { - out_file: OutFile::create(DEFAULT_OUTFILE)?, - }); - builder.arg(DEFAULT_OUTFILE); + builder.arg_input_file_std(); } else { builder.arg(arg); } } - if atat_at.is_none() { - builder.input(InputLocation::StdIn); - } - builder.build(observers) } } @@ -291,7 +320,7 @@ where use std::os::unix::prelude::ExitStatusExt; use wait_timeout::ChildExt; - let mut child = self.inner.spawn_child(input)?; + let mut child = self.configurer.spawn_child(input)?; let res = match child .wait_timeout(Duration::from_secs(5)) @@ -312,17 +341,38 @@ where } }; - if self.has_asan_observer { - let stderr = child.stderr.as_mut().ok_or_else(|| { + if self.has_asan_observer || self.has_stderr_observer { + let mut stderr = String::new(); + child.stderr.as_mut().ok_or_else(|| { Error::IllegalState( - "Using ASANBacktraceObserver but stderr was not `Stdio::pipe` in CommandExecutor".into(), + "Observer tries to read stderr, but stderr was not `Stdio::pipe` in CommandExecutor".into(), ) - })?; + })?.read_to_string(&mut stderr)?; + if self.has_asan_observer { + self.observers + .match_name_mut::("ASANBacktraceObserver") + .unwrap() + .parse_asan_output(&stderr); + } + if self.has_stderr_observer { + self.observers + .match_name_mut::("StdErrObserver") + .unwrap() + .stderr = Some(stderr); + } + } + if self.has_stdout_observer { + let mut stdout = String::new(); + child.stdout.as_mut().ok_or_else(|| { + Error::IllegalState( + "Observer tries to read stdout, but stdout was not `Stdio::pipe` in CommandExecutor".into(), + ) + })?.read_to_string(&mut stdout)?; self.observers - .match_name_mut::("ASANBacktraceObserver") + .match_name_mut::("StdOutObserver") .unwrap() - .parse_asan_output_from_childstderr(stderr); - }; + .stdout = Some(stdout); + } res } @@ -345,9 +395,8 @@ impl, S, T: Debug, Z> HasObservers pub struct CommandExecutorBuilder { debug_child: bool, program: Option, - args_before: Vec, - input_location: Option, - args_after: Vec, + args: Vec, + input_location: InputLocation, cwd: Option, envs: Vec<(OsString, OsString)>, } @@ -364,9 +413,8 @@ impl CommandExecutorBuilder { fn new() -> CommandExecutorBuilder { CommandExecutorBuilder { program: None, - args_before: vec![], - input_location: None, - args_after: vec![], + args: vec![], + input_location: InputLocation::StdIn, cwd: None, envs: vec![], debug_child: false, @@ -385,22 +433,50 @@ impl CommandExecutorBuilder { /// Set the input mode and location. /// This option is mandatory, if not set, the `build` method will error. - pub fn input(&mut self, input: InputLocation) -> &mut Self { - // This is an error in the user code, no point in returning Err. - assert!( - self.input_location.is_none(), - "input location already set, cannot set it again" + fn input(&mut self, input: InputLocation) -> &mut Self { + // This is a fatal error in the user code, no point in returning Err. + assert_eq!( + self.input_location, + InputLocation::StdIn, + "input location already set to non-stdin, cannot set it again" ); - self.input_location = Some(input); + self.input_location = input; + self + } + + /// Sets the input mode to [`InputLocation::Arg`] and uses the current arg offset as `argnum`. + /// During execution, at input will be provided _as argument_ at this position. + /// Use [`Self::arg_input_file_std`] if you want to provide the input as a file instead. + pub fn arg_input_arg(&mut self) -> &mut Self { + let argnum = self.args.len(); + self.input(InputLocation::Arg { argnum }); + self.arg("DUMMY"); + self + } + + /// Sets the input mode to [`InputLocation::File`] + /// and adds the filename as arg to at the current position. + /// Uses a default filename. + /// Use [`Self::arg_input_file`] to specify a custom filename. + pub fn arg_input_file_std(&mut self) -> &mut Self { + self.arg_input_file(OUTFILE_STD); + self + } + + /// Sets the input mode to [`InputLocation::File`] + /// and adds the filename as arg to at the current position. + pub fn arg_input_file>(&mut self, path: P) -> &mut Self { + self.arg(path.as_ref()); + let out_file_std = OutFile::create(path.as_ref()).unwrap(); + self.input(InputLocation::File { + out_file: out_file_std, + }); self } /// Adds an argument to the program's commandline. pub fn arg>(&mut self, arg: O) -> &mut CommandExecutorBuilder { - match self.input_location { - Some(InputLocation::StdIn) => self.args_before.push(arg.as_ref().to_owned()), - Some(_) | None => self.args_after.push(arg.as_ref().to_owned()), - }; + self.args.push(arg.as_ref().to_owned()); self } @@ -469,26 +545,15 @@ impl CommandExecutorBuilder { )); }; let mut command = Command::new(program); - command.args(&self.args_before); match &self.input_location { - Some(InputLocation::StdIn) => { + InputLocation::StdIn => { command.stdin(Stdio::piped()); } - Some(InputLocation::File { out_file }) => { + InputLocation::File { .. } | InputLocation::Arg { .. } => { command.stdin(Stdio::null()); - command.arg(&out_file.path); - } - Some(InputLocation::Arg { .. }) => { - command.stdin(Stdio::null()); - command.arg("DUMMY"); - } - None => { - return Err(Error::IllegalArgument( - "ComandExecutor::builder: no input_location set!".into(), - )) } } - command.args(&self.args_after); + command.args(&self.args); command.envs( self.envs .iter() @@ -504,14 +569,23 @@ impl CommandExecutorBuilder { if observers .match_name::("ASANBacktraceObserver") .is_some() + || observers + .match_name::("StdErrObserver") + .is_some() { // we need stderr for ASANBackt command.stderr(Stdio::piped()); } + if observers + .match_name::("StdOutObserver") + .is_some() + { + command.stdout(Stdio::piped()); + } let configurator = StdCommandConfigurator { debug_child: self.debug_child, - input_location: self.input_location.clone().unwrap(), + input_location: self.input_location.clone(), command, }; Ok(configurator.into_executor(observers)) @@ -564,10 +638,20 @@ pub trait CommandConfigurator: Sized + Debug { .match_name::("ASANBacktraceObserver") .is_some(); + let has_stdout_observer = observers + .match_name::("StdOutObserver") + .is_some(); + + let has_stderr_observer = observers + .match_name::("StdErrObserver") + .is_some(); + CommandExecutor { observers, has_asan_observer, - inner: self, + has_stdout_observer, + has_stderr_observer, + configurer: self, phantom: PhantomData, } } diff --git a/libafl/src/executors/differential.rs b/libafl/src/executors/differential.rs new file mode 100644 index 0000000000..61af045523 --- /dev/null +++ b/libafl/src/executors/differential.rs @@ -0,0 +1,94 @@ +//! Executor for differential fuzzing. +//! It wraps two exeutors that will be run after each other with the same input. +//! In comparison to the [`crate::executors::CombinedExecutor`] it also runs the secondary executor in `run_target`. +//! +use crate::{ + executors::{Executor, ExitKind, HasObservers}, + inputs::Input, + observers::ObserversTuple, + Error, +}; +use core::fmt::Debug; + +/// A [`DiffExecutor`] wraps a primary executor, forwarding its methods, and a secondary one +#[derive(Debug)] +pub struct DiffExecutor +where + A: Debug, + B: Debug, +{ + primary: A, + secondary: B, +} + +impl DiffExecutor +where + A: Debug, + B: Debug, +{ + /// Create a new `DiffExecutor`, wrapping the given `executor`s. + pub fn new(primary: A, secondary: B) -> Self + where + A: Executor, + B: Executor, + I: Input, + { + Self { primary, secondary } + } + + /// Retrieve the primary `Executor` that is wrapped by this `DiffExecutor`. + pub fn primary(&mut self) -> &mut A { + &mut self.primary + } + + /// Retrieve the secondary `Executor` that is wrapped by this `DiffExecutor`. + pub fn secondary(&mut self) -> &mut B { + &mut self.secondary + } +} + +impl Executor for DiffExecutor +where + A: Executor, + B: Executor, + I: Input, +{ + fn run_target( + &mut self, + fuzzer: &mut Z, + state: &mut S, + mgr: &mut EM, + input: &I, + ) -> Result { + let ret1 = self.primary.run_target(fuzzer, state, mgr, input)?; + self.primary.post_run_reset(); + let ret2 = self.secondary.run_target(fuzzer, state, mgr, input)?; + self.secondary.post_run_reset(); + if ret1 == ret2 { + Ok(ret1) + } else { + // We found a diff in the exit codes! + Ok(ExitKind::Diff { + primary: ret1.into(), + secondary: ret2.into(), + }) + } + } +} + +impl HasObservers for DiffExecutor +where + A: HasObservers, + B: Debug, + OT: ObserversTuple, +{ + #[inline] + fn observers(&self) -> &OT { + self.primary.observers() + } + + #[inline] + fn observers_mut(&mut self) -> &mut OT { + self.primary.observers_mut() + } +} diff --git a/libafl/src/executors/forkserver.rs b/libafl/src/executors/forkserver.rs index bad91a4b52..8560777a70 100644 --- a/libafl/src/executors/forkserver.rs +++ b/libafl/src/executors/forkserver.rs @@ -715,7 +715,7 @@ where self.observers_mut() .match_name_mut::("ASANBacktraceObserver") .unwrap() - .parse_asan_output_from_asan_log_file(pid); + .parse_asan_output_from_asan_log_file(pid)?; } } diff --git a/libafl/src/executors/mod.rs b/libafl/src/executors/mod.rs index 2c39dc9677..fbc3fa5b74 100644 --- a/libafl/src/executors/mod.rs +++ b/libafl/src/executors/mod.rs @@ -5,6 +5,9 @@ pub use inprocess::InProcessExecutor; #[cfg(all(feature = "std", feature = "fork", unix))] pub use inprocess::InProcessForkExecutor; +pub mod differential; +pub use differential::DiffExecutor; + /// Timeout executor. /// Not possible on `no-std` Windows or `no-std`, but works for unix #[cfg(any(unix, feature = "std"))] @@ -52,12 +55,50 @@ pub enum ExitKind { Oom, /// The run timed out Timeout, + /// Special case for [`DiffExecutor`] when both exitkinds don't match + Diff { + /// The exitkind of the primary executor + primary: DiffExitKind, + /// The exitkind of the secondary executor + secondary: DiffExitKind, + }, + // The run resulted in a custom `ExitKind`. + // Custom(Box), +} + +/// How one of the diffing executions finished. +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +pub enum DiffExitKind { + /// The run exited normally. + Ok, + /// The run resulted in a target crash. + Crash, + /// The run hit an out of memory error. + Oom, + /// The run timed out + Timeout, + /// One of the executors itelf repots a differential, we can't go into further details. + Diff, // The run resulted in a custom `ExitKind`. // Custom(Box), } crate::impl_serdeany!(ExitKind); +impl From for DiffExitKind { + fn from(exitkind: ExitKind) -> Self { + match exitkind { + ExitKind::Ok => DiffExitKind::Ok, + ExitKind::Crash => DiffExitKind::Crash, + ExitKind::Oom => DiffExitKind::Oom, + ExitKind::Timeout => DiffExitKind::Timeout, + ExitKind::Diff { .. } => DiffExitKind::Diff, + } + } +} + +crate::impl_serdeany!(DiffExitKind); + /// Holds a tuple of Observers pub trait HasObservers: Debug where diff --git a/libafl/src/feedbacks/differential.rs b/libafl/src/feedbacks/differential.rs new file mode 100644 index 0000000000..cacd84ead9 --- /dev/null +++ b/libafl/src/feedbacks/differential.rs @@ -0,0 +1,255 @@ +//! Diff Feedback, comparing the content of two observers of the same type. +//! + +use alloc::string::{String, ToString}; +use core::{ + fmt::{self, Debug, Formatter}, + marker::PhantomData, +}; +use serde::{Deserialize, Serialize}; + +use crate::{ + bolts::tuples::{MatchName, Named}, + events::EventFirer, + executors::ExitKind, + feedbacks::Feedback, + inputs::Input, + observers::{Observer, ObserversTuple}, + state::{HasClientPerfMonitor, HasMetadata}, + Error, +}; + +/// The result of a differential test between two observers. +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +pub enum DiffResult { + /// The two observers report the same outcome. + Equal, + /// The two observers report different outcomes. + Diff, +} + +/// A [`DiffFeedback`] compares the content of two [`Observer`]s using the given compare function. +#[derive(Serialize, Deserialize)] +pub struct DiffFeedback +where + F: FnMut(&O1, &O2) -> DiffResult, +{ + /// This feedback's name + name: String, + /// The first observer to compare against + o1_name: String, + /// The second observer to compare against + o2_name: String, + /// The function used to compare the two observers + compare_fn: F, + phantomm: PhantomData<(O1, O2)>, +} + +impl DiffFeedback +where + F: FnMut(&O1, &O2) -> DiffResult, + O1: Named, + O2: Named, +{ + /// Create a new [`DiffFeedback`] using two observers and a test function. + pub fn new(name: &str, o1: &O1, o2: &O2, compare_fn: F) -> Result { + let o1_name = o1.name().to_string(); + let o2_name = o2.name().to_string(); + if o1_name == o2_name { + Err(Error::IllegalArgument(format!( + "DiffFeedback: observer names must be different (both were {})", + o1_name + ))) + } else { + Ok(Self { + o1_name, + o2_name, + name: name.to_string(), + compare_fn, + phantomm: PhantomData, + }) + } + } +} + +impl Named for DiffFeedback +where + F: FnMut(&O1, &O2) -> DiffResult, + O1: Named, + O2: Named, +{ + fn name(&self) -> &str { + &self.name + } +} + +impl Debug for DiffFeedback +where + F: FnMut(&O1, &O2) -> DiffResult, + O1: Named, + O2: Named, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!( + f, + "DiffFeedback {{ name: {}, o1: {}, o2: {} }}", + self.name, self.o1_name, self.o2_name + ) + } +} + +impl Feedback for DiffFeedback +where + F: FnMut(&O1, &O2) -> DiffResult, + I: Input, + S: HasMetadata + HasClientPerfMonitor, + O1: Observer + PartialEq, + O2: Observer, +{ + fn is_interesting( + &mut self, + _state: &mut S, + _manager: &mut EM, + _input: &I, + observers: &OT, + _exit_kind: &ExitKind, + ) -> Result + where + EM: EventFirer, + OT: ObserversTuple + MatchName, + { + fn err(name: &str) -> Error { + Error::IllegalArgument(format!("DiffFeedback: observer {} not found", name)) + } + + let o1: &O1 = observers + .match_name(&self.o1_name) + .ok_or_else(|| err(&self.o1_name))?; + let o2: &O2 = observers + .match_name(&self.o2_name) + .ok_or_else(|| err(&self.o2_name))?; + + Ok(o1 != o2) + } +} + +#[cfg(test)] +mod tests { + use crate::{ + bolts::{ + serdeany::SerdeAnyMap, + tuples::{tuple_list, Named}, + }, + events::EventFirer, + executors::ExitKind, + feedbacks::{differential::DiffResult, DiffFeedback, Feedback}, + inputs::{BytesInput, Input}, + monitors::ClientPerfMonitor, + observers::Observer, + state::{HasClientPerfMonitor, HasMetadata}, + }; + use alloc::string::{String, ToString}; + + #[derive(Debug)] + struct NopObserver { + name: String, + value: bool, + } + impl NopObserver { + fn new(name: &str, value: bool) -> Self { + Self { + name: name.to_string(), + value: value, + } + } + } + impl Observer for NopObserver {} + impl PartialEq for NopObserver { + fn eq(&self, other: &Self) -> bool { + self.value == other.value + } + } + impl Named for NopObserver { + fn name(&self) -> &str { + &self.name + } + } + + struct NopEventFirer; + impl EventFirer for NopEventFirer { + fn fire( + &mut self, + _state: &mut S, + _event: crate::events::Event, + ) -> Result<(), crate::Error> { + Ok(()) + } + } + + struct NopState; + impl HasMetadata for NopState { + fn metadata(&self) -> &SerdeAnyMap { + unimplemented!() + } + + fn metadata_mut(&mut self) -> &mut SerdeAnyMap { + unimplemented!() + } + } + impl HasClientPerfMonitor for NopState { + fn introspection_monitor(&self) -> &ClientPerfMonitor { + unimplemented!() + } + + fn introspection_monitor_mut(&mut self) -> &mut ClientPerfMonitor { + unimplemented!() + } + + fn stability(&self) -> &Option { + unimplemented!() + } + + fn stability_mut(&mut self) -> &mut Option { + unimplemented!() + } + } + + fn test_diff(should_equal: bool) { + let mut nop_state = NopState; + + let o1 = NopObserver::new("o1", true); + let o2 = NopObserver::new("o2", should_equal); + + let mut diff_feedback = DiffFeedback::new("diff_feedback", &o1, &o2, |o1, o2| { + if o1 == o2 { + DiffResult::Equal + } else { + DiffResult::Diff + } + }) + .unwrap(); + let observers = tuple_list![o1, o2]; + assert_eq!( + !should_equal, + diff_feedback + .is_interesting( + &mut nop_state, + &mut NopEventFirer {}, + &BytesInput::new(vec![0]), + &observers, + &ExitKind::Ok + ) + .unwrap() + ); + } + + #[test] + fn test_diff_eq() { + test_diff(true); + } + + #[test] + fn test_diff_neq() { + test_diff(false); + } +} diff --git a/libafl/src/feedbacks/mod.rs b/libafl/src/feedbacks/mod.rs index 74fc2ed9d6..e60a0fd49e 100644 --- a/libafl/src/feedbacks/mod.rs +++ b/libafl/src/feedbacks/mod.rs @@ -4,6 +4,8 @@ pub mod map; pub use map::*; +pub mod differential; +pub use differential::DiffFeedback; #[cfg(feature = "std")] pub mod concolic; #[cfg(feature = "std")] diff --git a/libafl/src/observers/mod.rs b/libafl/src/observers/mod.rs index 38335e59a3..1bf41b041d 100644 --- a/libafl/src/observers/mod.rs +++ b/libafl/src/observers/mod.rs @@ -7,9 +7,12 @@ pub mod cmp; pub use cmp::*; #[cfg(feature = "std")] -pub mod stacktrace; +pub mod stdio; #[cfg(feature = "std")] -pub use stacktrace::ASANBacktraceObserver; +pub use stdio::{StdErrObserver, StdOutObserver}; + +#[cfg(feature = "std")] +pub mod stacktrace; #[cfg(feature = "std")] pub use stacktrace::*; diff --git a/libafl/src/observers/stacktrace.rs b/libafl/src/observers/stacktrace.rs index edc8dd514e..1f59af8e82 100644 --- a/libafl/src/observers/stacktrace.rs +++ b/libafl/src/observers/stacktrace.rs @@ -52,24 +52,24 @@ impl BacktraceHashValueWrapper { Self::StaticVariable(_) => { *self = Self::StaticVariable((bt_hash, input_hash)); } - Self::None => panic!("BacktraceSharedMemoryWrapper is not set yet22!"), + Self::None => panic!("BacktraceSharedMemoryWrapper is not set yet!"), } } /// get the hash value from the [`BacktraceHashValueWrapper`] - fn get_stacktrace_hash(&self) -> (u64, u64) { + fn get_stacktrace_hash(&self) -> Result<(u64, u64), Error> { match &self { Self::Shmem(shmem) => { let map = shmem.as_slice(); - ( - u64::from_be_bytes(map[0..8].try_into().expect("Incorrectly sized")), - u64::from_be_bytes(map[8..16].try_into().expect("Incorrectly sized")), - ) - } - Self::StaticVariable(hash_tuple) => *hash_tuple, - Self::None => { - panic!("BacktraceSharedMemoryWrapper is not set yet!") + Ok(( + u64::from_be_bytes(map[0..8].try_into()?), + u64::from_be_bytes(map[8..16].try_into()?), + )) } + Self::StaticVariable(hash_tuple) => Ok(*hash_tuple), + Self::None => Err(Error::IllegalState( + "BacktraceSharedMemoryWrapper is not set yet!".into(), + )), } } } @@ -188,7 +188,7 @@ where let input_hash = hasher.finish(); // get last backtrace hash and associated input hash let (bt_hash, current_input_hash) = - unsafe { BACKTRACE_HASH_VALUE.get_stacktrace_hash() }; + unsafe { BACKTRACE_HASH_VALUE.get_stacktrace_hash()? }; // replace if this is a new input if current_input_hash != input_hash { let bt_hash = collect_backtrace(); @@ -262,27 +262,27 @@ impl ASANBacktraceObserver { } /// read ASAN output from the child stderr and parse it. - pub fn parse_asan_output_from_childstderr(&mut self, stderr: &mut ChildStderr) { + pub fn parse_asan_output_from_childstderr( + &mut self, + stderr: &mut ChildStderr, + ) -> Result<(), Error> { let mut buf = String::new(); - stderr - .read_to_string(&mut buf) - .expect("Failed to read the child process stderr"); + stderr.read_to_string(&mut buf)?; self.parse_asan_output(&buf); + Ok(()) } /// read ASAN output from the log file and parse it. - pub fn parse_asan_output_from_asan_log_file(&mut self, pid: i32) { + pub fn parse_asan_output_from_asan_log_file(&mut self, pid: i32) -> Result<(), Error> { let log_path = format!("{}.{}", ASAN_LOG_PATH, pid); - let mut asan_output = File::open(Path::new(&log_path)) - .unwrap_or_else(|_| panic!("Can't find asan log at {}", &log_path)); + let mut asan_output = File::open(Path::new(&log_path))?; let mut buf = String::new(); - asan_output - .read_to_string(&mut buf) - .expect("Failed to read asan log"); - fs::remove_file(&log_path).unwrap_or_else(|_| panic!("Failed to delete {}", &log_path)); + asan_output.read_to_string(&mut buf)?; + fs::remove_file(&log_path)?; self.parse_asan_output(&buf); + Ok(()) } /// parse ASAN error output emited by the target command and compute the hash diff --git a/libafl/src/observers/stdio.rs b/libafl/src/observers/stdio.rs new file mode 100644 index 0000000000..4d565c3e75 --- /dev/null +++ b/libafl/src/observers/stdio.rs @@ -0,0 +1,59 @@ +//! The [`StdOutObserver`] and [`StdErrObserver`] observers look at the stdout of a program +//! The executor must explicitely support these observers. +//! For example, they are supported on the [`crate::executors::CommandExecutor`]. + +use crate::{bolts::tuples::Named, observers::Observer}; + +/// An observer that captures stdout of a target. +/// Only works for supported executors. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct StdOutObserver { + /// The name of the observer. + pub name: String, + /// The stdout of the target during its last execution. + pub stdout: Option, +} + +/// An observer that captures stdout of a target. +impl StdOutObserver { + /// Create a new [`StdOutObserver`] with the given name. + #[must_use] + pub fn new(name: String) -> Self { + Self { name, stdout: None } + } +} + +impl Observer for StdOutObserver {} + +impl Named for StdOutObserver { + fn name(&self) -> &str { + &self.name + } +} + +/// An observer that captures stderr of a target. +/// Only works for supported executors. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct StdErrObserver { + /// The name of the observer. + pub name: String, + /// The stderr of the target during its last execution. + pub stderr: Option, +} + +/// An observer that captures stderr of a target. +impl StdErrObserver { + /// Create a new [`StdErrObserver`] with the given name. + #[must_use] + pub fn new(name: String) -> Self { + Self { name, stderr: None } + } +} + +impl Observer for StdErrObserver {} + +impl Named for StdErrObserver { + fn name(&self) -> &str { + &self.name + } +}