diff --git a/fuzzers/backtrace_baby_fuzzers/command_executor/src/main.rs b/fuzzers/backtrace_baby_fuzzers/command_executor/src/main.rs index f3b6065e95..85485ab855 100644 --- a/fuzzers/backtrace_baby_fuzzers/command_executor/src/main.rs +++ b/fuzzers/backtrace_baby_fuzzers/command_executor/src/main.rs @@ -108,7 +108,12 @@ pub fn main() { } } - let mut executor = MyExecutor { shmem_id }.into_executor(tuple_list!(observer, bt_observer)); + let mut executor = MyExecutor { shmem_id }.into_executor( + tuple_list!(observer, bt_observer), + None, + None, + Some("ASANBacktraceObserver".to_string()), + ); // Generator of printable bytearrays of max size 32 let mut generator = RandPrintablesGenerator::new(32); diff --git a/fuzzers/libfuzzer_stb_image_concolic/fuzzer/src/main.rs b/fuzzers/libfuzzer_stb_image_concolic/fuzzer/src/main.rs index 8455d45243..d0e7aff0da 100644 --- a/fuzzers/libfuzzer_stb_image_concolic/fuzzer/src/main.rs +++ b/fuzzers/libfuzzer_stb_image_concolic/fuzzer/src/main.rs @@ -224,7 +224,7 @@ fn fuzz( // Create a concolic trace ConcolicTracingStage::new( TracingStage::new( - MyCommandConfigurator::default().into_executor(tuple_list!(concolic_observer)) + MyCommandConfigurator::default().into_executor(tuple_list!(concolic_observer), None, None, None) ), concolic_observer_name, ), diff --git a/libafl/src/executors/command.rs b/libafl/src/executors/command.rs index b69378f1f9..72e9d9b9e2 100644 --- a/libafl/src/executors/command.rs +++ b/libafl/src/executors/command.rs @@ -1,6 +1,5 @@ //! The command executor executes a sub program for each run -#[cfg(feature = "std")] -use alloc::{borrow::ToOwned, string::String, vec::Vec}; +use alloc::{string::String, vec::Vec}; use core::{ fmt::{self, Debug, Formatter}, marker::PhantomData, @@ -29,6 +28,7 @@ use crate::{ }, inputs::HasTargetBytes, observers::{ASANBacktraceObserver, ObserversTuple, StdErrObserver, StdOutObserver}, + std::borrow::ToOwned, }; #[cfg(feature = "std")] use crate::{inputs::Input, Error}; @@ -37,19 +37,19 @@ use crate::{inputs::Input, Error}; /// `StdIn`: The traget reads from stdin /// `File`: The target reads from the specified [`InputFile`] #[derive(Debug, Clone, PartialEq, Eq)] -pub enum InputLocation { - /// Mutate a command line argument to deliver an input +enum InputLocation { + /// Mutate a commandline argument to deliver an input Arg { /// The offset of the argument to mutate argnum: usize, }, /// Deliver input via `StdIn` StdIn, - /// Deliver the input via the specified [`InputFile`] + /// Deliver the iniput via the specified [`InputFile`] /// You can use specify [`InputFile::create(INPUTFILE_STD)`] to use a default filename. File { - /// The file to write input to. The target should read input from this location. - input_file: InputFile, + /// The fiel to write input to. The target should read input from this location. + out_file: InputFile, }, } @@ -71,11 +71,15 @@ 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. +#[allow(clippy::struct_excessive_bools)] #[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 debug_child: bool, + has_stdout_observer: bool, + has_stderr_observer: bool, + has_asan_observer: bool, /// true: input gets delivered via stdink input_location: InputLocation, /// The Command to execute @@ -97,6 +101,13 @@ impl CommandConfigurator for StdCommandConfigurator { cmd.stderr(Stdio::null()); } + if self.has_stdout_observer { + cmd.stdout(Stdio::piped()); + } + if self.has_stderr_observer || self.has_asan_observer { + cmd.stderr(Stdio::piped()); + } + for (i, arg) in args.enumerate() { if i == *argnum { debug_assert_eq!(arg, "DUMMY"); @@ -129,8 +140,8 @@ impl CommandConfigurator for StdCommandConfigurator { drop(stdin); Ok(handle) } - InputLocation::File { input_file } => { - input_file.write_buf(input.target_bytes().as_slice())?; + InputLocation::File { out_file } => { + out_file.write_buf(input.target_bytes().as_slice())?; Ok(self.command.spawn()?) } } @@ -149,13 +160,13 @@ where configurer: T, observers: OT, /// cache if the AsanBacktraceObserver is present - has_asan_observer: bool, + has_asan_observer: Option, /// If set, we found a [`StdErrObserver`] in the observer list. /// Pipe the child's `stderr` instead of closing it. - has_stdout_observer: bool, + has_stdout_observer: Option, /// If set, we found a [`StdOutObserver`] in the observer list /// Pipe the child's `stdout` instead of closing it. - has_stderr_observer: bool, + has_stderr_observer: Option, phantom: PhantomData<(EM, I, S, Z)>, } @@ -213,6 +224,9 @@ where debug_child: bool, observers: OT, path: P, + has_stdout_observer: Option, + has_stderr_observer: Option, + has_asan_observer: Option, ) -> Result where P: AsRef, @@ -224,35 +238,29 @@ where } command.stdin(Stdio::null()); - let has_stdout_observer = observers - .match_name::("StdOutObserver") - .is_some(); - if has_stdout_observer { - command.stderr(Stdio::piped()); + if has_stdout_observer.is_some() { + command.stdout(Stdio::piped()); } - - let has_stderr_observer = observers - .match_name::("StdErrObserver") - .is_some(); - let has_asan_observer = observers - .match_name::("ASANBacktraceObserver") - .is_some(); - if has_stderr_observer || has_asan_observer { + if has_stderr_observer.is_some() || has_asan_observer.is_some() { command.stderr(Stdio::piped()); } Ok(Self { observers, - has_asan_observer, + configurer: StdCommandConfigurator { input_location: InputLocation::File { - input_file: InputFile::create(path)?, + out_file: InputFile::create(path)?, }, command, debug_child, + has_stdout_observer: has_stdout_observer.is_some(), + has_stderr_observer: has_stderr_observer.is_some(), + has_asan_observer: has_asan_observer.is_some(), }, has_stdout_observer, has_stderr_observer, + has_asan_observer, phantom: PhantomData, }) } @@ -264,6 +272,9 @@ where args: IT, observers: OT, debug_child: bool, + has_stdout_observer: Option, + has_stderr_observer: Option, + has_asan_observer: Option, ) -> Result where IT: IntoIterator, @@ -272,6 +283,16 @@ where let mut atat_at = None; let mut builder = CommandExecutorBuilder::new(); builder.debug_child(debug_child); + if let Some(name) = has_stdout_observer { + builder.stdout_observer(name); + } + if let Some(name) = has_stderr_observer { + builder.stderr_observer(name); + } + if let Some(name) = has_asan_observer { + builder.asan_observer(name); + } + let afl_delim = OsStr::new("@@"); for (pos, arg) in args.into_iter().enumerate() { @@ -340,27 +361,27 @@ where } }; - if self.has_asan_observer || self.has_stderr_observer { + if self.has_asan_observer.is_some() || self.has_stderr_observer.is_some() { let mut stderr = String::new(); child.stderr.as_mut().ok_or_else(|| { Error::illegal_state( "Observer tries to read stderr, but stderr was not `Stdio::pipe` in CommandExecutor", ) })?.read_to_string(&mut stderr)?; - if self.has_asan_observer { + if let Some(name) = self.has_asan_observer.as_ref() { self.observers - .match_name_mut::("ASANBacktraceObserver") + .match_name_mut::(name) .unwrap() .parse_asan_output(&stderr); } - if self.has_stderr_observer { + if let Some(name) = self.has_stderr_observer.as_ref() { self.observers - .match_name_mut::("StdErrObserver") + .match_name_mut::(name) .unwrap() .stderr = Some(stderr); } } - if self.has_stdout_observer { + if let Some(name) = self.has_stdout_observer.as_ref() { let mut stdout = String::new(); child.stdout.as_mut().ok_or_else(|| { Error::illegal_state( @@ -368,7 +389,7 @@ where ) })?.read_to_string(&mut stdout)?; self.observers - .match_name_mut::("StdOutObserver") + .match_name_mut::(name) .unwrap() .stdout = Some(stdout); } @@ -398,6 +419,9 @@ pub struct CommandExecutorBuilder { input_location: InputLocation, cwd: Option, envs: Vec<(OsString, OsString)>, + has_stdout_observer: Option, + has_stderr_observer: Option, + has_asan_observer: Option, } impl Default for CommandExecutorBuilder { @@ -417,9 +441,30 @@ impl CommandExecutorBuilder { cwd: None, envs: vec![], debug_child: false, + has_stdout_observer: None, + has_stderr_observer: None, + has_asan_observer: None, } } + /// Set the stdout observer name + pub fn stdout_observer(&mut self, name: String) -> &mut Self { + self.has_stdout_observer = Some(name); + self + } + + /// Set the stderr observer name + pub fn stderr_observer(&mut self, name: String) -> &mut Self { + self.has_stderr_observer = Some(name); + self + } + + /// Set the asan observer name + pub fn asan_observer(&mut self, name: String) -> &mut Self { + self.has_asan_observer = Some(name); + self + } + /// Set the binary to execute /// This option is required. pub fn program(&mut self, program: O) -> &mut Self @@ -466,9 +511,9 @@ impl CommandExecutorBuilder { /// 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 input_file_std = InputFile::create(path.as_ref()).unwrap(); + let out_file_std = InputFile::create(path.as_ref()).unwrap(); self.input(InputLocation::File { - input_file: input_file_std, + out_file: out_file_std, }); self } @@ -565,29 +610,28 @@ impl CommandExecutorBuilder { command.stdout(Stdio::null()); command.stderr(Stdio::null()); } - if observers - .match_name::("ASANBacktraceObserver") - .is_some() - || observers - .match_name::("StdErrObserver") - .is_some() - { + if self.has_stderr_observer.is_some() || self.has_asan_observer.is_some() { // we need stderr for ASANBackt command.stderr(Stdio::piped()); } - if observers - .match_name::("StdOutObserver") - .is_some() - { + if self.has_stdout_observer.is_some() { command.stdout(Stdio::piped()); } let configurator = StdCommandConfigurator { debug_child: self.debug_child, + has_stdout_observer: self.has_stdout_observer.is_some(), + has_stderr_observer: self.has_stderr_observer.is_some(), + has_asan_observer: self.has_asan_observer.is_some(), input_location: self.input_location.clone(), command, }; - Ok(configurator.into_executor(observers)) + Ok(configurator.into_executor::( + observers, + self.has_stdout_observer.clone(), + self.has_stderr_observer.clone(), + self.has_asan_observer.clone(), + )) } } @@ -619,9 +663,10 @@ impl CommandExecutorBuilder { /// } /// /// fn make_executor() -> impl Executor { -/// MyExecutor.into_executor(()) +/// MyExecutor.into_executor((), None, None, None) /// } /// ``` + #[cfg(all(feature = "std", any(unix, doc)))] pub trait CommandConfigurator: Sized + Debug { /// Spawns a new process with the given configuration. @@ -630,22 +675,16 @@ pub trait CommandConfigurator: Sized + Debug { I: Input + HasTargetBytes; /// Create an `Executor` from this `CommandConfigurator`. - fn into_executor(self, observers: OT) -> CommandExecutor + fn into_executor( + self, + observers: OT, + has_stdout_observer: Option, + has_stderr_observer: Option, + has_asan_observer: Option, + ) -> CommandExecutor where OT: Debug + MatchName, { - let has_asan_observer = observers - .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, @@ -697,14 +736,19 @@ mod tests { #[cfg(unix)] fn test_parse_afl_cmdline() { use alloc::string::ToString; - let mut mgr = SimpleEventManager::::new(SimpleMonitor::new(|status| { println!("{status}"); })); - let mut executor = - CommandExecutor::parse_afl_cmdline(&["file".to_string(), "@@".to_string()], (), true) - .unwrap(); + let mut executor = CommandExecutor::parse_afl_cmdline( + &["file".to_string(), "@@".to_string()], + (), + true, + None, + None, + None, + ) + .unwrap(); executor .run_target( &mut (), diff --git a/libafl/src/executors/differential.rs b/libafl/src/executors/differential.rs index feae0af1fd..903601ec2f 100644 --- a/libafl/src/executors/differential.rs +++ b/libafl/src/executors/differential.rs @@ -2,9 +2,12 @@ //! 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 core::fmt::Debug; +use core::{cell::UnsafeCell, fmt::Debug}; + +use serde::{Deserialize, Serialize}; use crate::{ + bolts::{ownedref::OwnedPtrMut, tuples::MatchName}, executors::{Executor, ExitKind, HasObservers}, inputs::Input, observers::ObserversTuple, @@ -13,19 +16,24 @@ use crate::{ /// A [`DiffExecutor`] wraps a primary executor, forwarding its methods, and a secondary one #[derive(Debug)] -pub struct DiffExecutor +pub struct DiffExecutor where A: Debug, B: Debug, + OTA: Debug, + OTB: Debug, { primary: A, secondary: B, + observers: UnsafeCell>, } -impl DiffExecutor +impl DiffExecutor where A: Debug, B: Debug, + OTA: Debug, + OTB: Debug, { /// Create a new `DiffExecutor`, wrapping the given `executor`s. pub fn new(primary: A, secondary: B) -> Self @@ -34,7 +42,14 @@ where B: Executor, I: Input, { - Self { primary, secondary } + Self { + primary, + secondary, + observers: UnsafeCell::new(ProxyObserversTuple { + primary: OwnedPtrMut::Ptr(core::ptr::null_mut()), + secondary: OwnedPtrMut::Ptr(core::ptr::null_mut()), + }), + } } /// Retrieve the primary `Executor` that is wrapped by this `DiffExecutor`. @@ -48,11 +63,13 @@ where } } -impl Executor for DiffExecutor +impl Executor for DiffExecutor where A: Executor, B: Executor, I: Input, + OTA: Debug, + OTB: Debug, { fn run_target( &mut self, @@ -77,19 +94,115 @@ where } } -impl HasObservers for DiffExecutor +/// Proxy the observers of the inner executors +#[derive(Serialize, Deserialize, Debug)] +#[serde( + bound = "A: serde::Serialize + serde::de::DeserializeOwned, B: serde::Serialize + serde::de::DeserializeOwned" +)] +pub struct ProxyObserversTuple { + primary: OwnedPtrMut, + secondary: OwnedPtrMut, +} + +impl ObserversTuple for ProxyObserversTuple where - A: HasObservers, - B: Debug, - OT: ObserversTuple, + A: ObserversTuple, + B: ObserversTuple, +{ + fn pre_exec_all(&mut self, state: &mut S, input: &I) -> Result<(), Error> { + self.primary.as_mut().pre_exec_all(state, input)?; + self.secondary.as_mut().pre_exec_all(state, input) + } + + fn post_exec_all( + &mut self, + state: &mut S, + input: &I, + exit_kind: &ExitKind, + ) -> Result<(), Error> { + self.primary + .as_mut() + .post_exec_all(state, input, exit_kind)?; + self.secondary + .as_mut() + .post_exec_all(state, input, exit_kind) + } + + fn pre_exec_child_all(&mut self, state: &mut S, input: &I) -> Result<(), Error> { + self.primary.as_mut().pre_exec_child_all(state, input)?; + self.secondary.as_mut().pre_exec_child_all(state, input) + } + + fn post_exec_child_all( + &mut self, + state: &mut S, + input: &I, + exit_kind: &ExitKind, + ) -> Result<(), Error> { + self.primary + .as_mut() + .post_exec_child_all(state, input, exit_kind)?; + self.secondary + .as_mut() + .post_exec_child_all(state, input, exit_kind) + } +} + +impl MatchName for ProxyObserversTuple +where + A: MatchName, + B: MatchName, +{ + fn match_name(&self, name: &str) -> Option<&T> { + if let Some(t) = self.primary.as_ref().match_name::(name) { + return Some(t); + } + self.secondary.as_ref().match_name::(name) + } + fn match_name_mut(&mut self, name: &str) -> Option<&mut T> { + if let Some(t) = self.primary.as_mut().match_name_mut::(name) { + return Some(t); + } + self.secondary.as_mut().match_name_mut::(name) + } +} + +impl ProxyObserversTuple { + fn set(&mut self, primary: &A, secondary: &B) { + self.primary = OwnedPtrMut::Ptr(primary as *const A as *mut A); + self.secondary = OwnedPtrMut::Ptr(secondary as *const B as *mut B); + } +} + +impl HasObservers, S> + for DiffExecutor +where + A: HasObservers, + B: HasObservers, + OTA: ObserversTuple, + OTB: ObserversTuple, { #[inline] - fn observers(&self) -> &OT { - self.primary.observers() + fn observers(&self) -> &ProxyObserversTuple { + unsafe { + self.observers + .get() + .as_mut() + .unwrap() + .set(self.primary.observers(), self.secondary.observers()); + self.observers.get().as_ref().unwrap() + } } #[inline] - fn observers_mut(&mut self) -> &mut OT { - self.primary.observers_mut() + fn observers_mut(&mut self) -> &mut ProxyObserversTuple { + unsafe { + self.observers + .get() + .as_mut() + .unwrap() + .set(self.primary.observers(), self.secondary.observers()); + self.observers.get().as_mut().unwrap() + } } } diff --git a/libafl/src/feedbacks/differential.rs b/libafl/src/feedbacks/differential.rs index 482fcca186..fc674493c7 100644 --- a/libafl/src/feedbacks/differential.rs +++ b/libafl/src/feedbacks/differential.rs @@ -140,7 +140,6 @@ where fn err(name: &str) -> Error { Error::illegal_argument(format!("DiffFeedback: observer {name} not found")) } - let o1: &O1 = observers .match_name(&self.o1_name) .ok_or_else(|| err(&self.o1_name))?; @@ -148,7 +147,7 @@ where .match_name(&self.o2_name) .ok_or_else(|| err(&self.o2_name))?; - Ok(o1 != o2) + Ok((self.compare_fn)(o1, o2) == DiffResult::Diff) } } diff --git a/libafl/src/observers/stdio.rs b/libafl/src/observers/stdio.rs index 4cea5f1402..fdcadc8a99 100644 --- a/libafl/src/observers/stdio.rs +++ b/libafl/src/observers/stdio.rs @@ -4,11 +4,13 @@ use alloc::string::String; +use serde::{Deserialize, Serialize}; + 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)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct StdOutObserver { /// The name of the observer. pub name: String,