diff --git a/fuzzers/nyx_libxml2_parallel/src/main.rs b/fuzzers/nyx_libxml2_parallel/src/main.rs index fb836c26f6..baa72f7ac2 100644 --- a/fuzzers/nyx_libxml2_parallel/src/main.rs +++ b/fuzzers/nyx_libxml2_parallel/src/main.rs @@ -19,7 +19,7 @@ use libafl_bolts::{ shmem::{ShMemProvider, StdShMemProvider}, tuples::tuple_list, }; -use libafl_nyx::{executor::NyxExecutor, helper::NyxHelper, settings::NyxSettings}; +use libafl_nyx::{executor::NyxExecutorBuilder, helper::NyxHelper, settings::NyxSettings}; fn main() { let shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory"); @@ -54,7 +54,7 @@ fn main() { let mut feedback = MaxMapFeedback::new(&observer); let mut objective = CrashFeedback::new(); let scheduler = RandScheduler::new(); - let mut executor = NyxExecutor::new(helper, tuple_list!(observer)); + let mut executor = NyxExecutorBuilder::new().build(helper, tuple_list!(observer)); // If not restarting, create a State from scratch let mut state = state.unwrap_or_else(|| { diff --git a/fuzzers/nyx_libxml2_standalone/src/main.rs b/fuzzers/nyx_libxml2_standalone/src/main.rs index b2ad16dbc3..22f718e4d5 100644 --- a/fuzzers/nyx_libxml2_standalone/src/main.rs +++ b/fuzzers/nyx_libxml2_standalone/src/main.rs @@ -14,7 +14,7 @@ use libafl::{ Fuzzer, StdFuzzer, }; use libafl_bolts::{rands::StdRand, tuples::tuple_list}; -use libafl_nyx::{executor::NyxExecutor, helper::NyxHelper, settings::NyxSettings}; +use libafl_nyx::{executor::NyxExecutorBuilder, helper::NyxHelper, settings::NyxSettings}; fn main() { // nyx stuff @@ -44,7 +44,7 @@ fn main() { let monitor = TuiMonitor::new(ui); let mut mgr = SimpleEventManager::new(monitor); - let mut executor = NyxExecutor::new(helper, tuple_list!(observer)); + let mut executor = NyxExecutorBuilder::new().build(helper, tuple_list!(observer)); let mutator = StdScheduledMutator::new(havoc_mutations()); let mut stages = tuple_list!(StdMutationalStage::new(mutator)); diff --git a/libafl/src/executors/command.rs b/libafl/src/executors/command.rs index 124d8a936f..4b44d00951 100644 --- a/libafl/src/executors/command.rs +++ b/libafl/src/executors/command.rs @@ -22,17 +22,17 @@ use libafl_bolts::{ AsSlice, }; -use super::HasObservers; #[cfg(all(feature = "std", unix))] use crate::executors::{Executor, ExitKind}; -#[cfg(feature = "std")] -use crate::{inputs::Input, Error}; use crate::{ + executors::HasObservers, inputs::{HasTargetBytes, UsesInput}, - observers::{ObserversTuple, UsesObservers}, + observers::{ObserversTuple, StdErrObserver, StdOutObserver, UsesObservers}, state::{HasExecutions, State, UsesState}, std::borrow::ToOwned, }; +#[cfg(feature = "std")] +use crate::{inputs::Input, Error}; /// How to deliver input to an external program /// `StdIn`: The target reads from stdin @@ -54,21 +54,6 @@ pub enum InputLocation { }, } -/// Clones a [`Command`] (without stdio and stdout/stderr - they are not accesible) -fn clone_command(cmd: &Command) -> Command { - let mut new_cmd = Command::new(cmd.get_program()); - new_cmd.args(cmd.get_args()); - new_cmd.env_clear(); - new_cmd.envs( - cmd.get_envs() - .filter_map(|(key, value)| value.map(|value| (key, value))), - ); - if let Some(cwd) = cmd.get_current_dir() { - new_cmd.current_dir(cwd); - } - new_cmd -} - /// 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. @@ -78,8 +63,8 @@ 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, + stdout_observer: Option, + stderr_observer: Option, timeout: Duration, /// true: input gets delivered via stdink input_location: InputLocation, @@ -91,6 +76,22 @@ impl CommandConfigurator for StdCommandConfigurator where I: HasTargetBytes, { + fn stdout_observer(&self) -> Option<&StdOutObserver> { + self.stdout_observer.as_ref() + } + + fn stdout_observer_mut(&mut self) -> Option<&mut StdOutObserver> { + self.stdout_observer.as_mut() + } + + fn stderr_observer(&self) -> Option<&StdErrObserver> { + self.stderr_observer.as_ref() + } + + fn stderr_observer_mut(&mut self) -> Option<&mut StdErrObserver> { + self.stderr_observer.as_mut() + } + fn spawn_child(&mut self, input: &I) -> Result { match &mut self.input_location { InputLocation::Arg { argnum } => { @@ -102,10 +103,10 @@ where cmd.stderr(Stdio::null()); } - if self.has_stdout_observer { + if self.stdout_observer.is_some() { cmd.stdout(Stdio::piped()); } - if self.has_stderr_observer { + if self.stderr_observer.is_some() { cmd.stderr(Stdio::piped()); } @@ -213,102 +214,6 @@ where } } -impl CommandExecutor -where - OT: MatchName + ObserversTuple, - S: UsesInput, - S::Input: HasTargetBytes, -{ - /// Creates a new `CommandExecutor`. - /// Instead of parsing the Command for `@@`, it will - pub fn from_cmd_with_file

( - cmd: &Command, - debug_child: bool, - timeout: Duration, - observers: OT, - path: P, - ) -> Result - where - P: AsRef, - { - let mut command = clone_command(cmd); - if !debug_child { - command.stdout(Stdio::null()); - command.stderr(Stdio::null()); - } - command.stdin(Stdio::null()); - - let has_stdout_observer = observers.observes_stdout(); - if has_stdout_observer { - command.stdout(Stdio::piped()); - } - let has_stderr_observer = observers.observes_stderr(); - if has_stderr_observer { - command.stderr(Stdio::piped()); - } - - Ok(Self { - observers, - configurer: StdCommandConfigurator { - input_location: InputLocation::File { - out_file: InputFile::create(path)?, - }, - command, - debug_child, - has_stdout_observer, - has_stderr_observer, - timeout, - }, - phantom: PhantomData, - }) - } - - /// Parses an AFL-like commandline, replacing `@@` with the input file - /// generated by the fuzzer (similar to the `afl-fuzz` command line). - /// - /// If no `@@` was found, will use stdin for input. - /// The arg 0 is the program. - pub fn parse_afl_cmdline( - args: IT, - observers: OT, - debug_child: bool, - timeout: Duration, - ) -> Result - where - IT: IntoIterator, - O: AsRef, - { - let mut atat_at = None; - let mut builder = CommandExecutorBuilder::new(); - builder.debug_child(debug_child); - builder.timeout(timeout); - let afl_delim = OsStr::new("@@"); - - for (pos, arg) in args.into_iter().enumerate() { - if pos == 0 { - if arg.as_ref() == afl_delim { - return Err(Error::illegal_argument( - "The first argument must not be @@ but the program to execute", - )); - } - builder.program(arg); - } else if arg.as_ref() == afl_delim { - if atat_at.is_some() { - return Err(Error::illegal_argument( - "Multiple @@ in afl commandline are not permitted", - )); - } - atat_at = Some(pos); - builder.arg_input_file_std(); - } else { - builder.arg(arg); - } - } - - builder.build(observers) - } -} - // this only works on unix because of the reliance on checking the process signal for detecting OOM #[cfg(all(feature = "std", unix))] impl Executor for CommandExecutor @@ -353,25 +258,24 @@ where } }; - if self.observers.observes_stderr() { - let mut stderr = Vec::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_end(&mut stderr)?; - self.observers.observe_stderr(&stderr); - } - if self.observers.observes_stdout() { + if let Some(ref mut ob) = &mut self.configurer.stdout_observer_mut() { let mut stdout = Vec::new(); child.stdout.as_mut().ok_or_else(|| { - Error::illegal_state( - "Observer tries to read stdout, but stdout was not `Stdio::pipe` in CommandExecutor", - ) - })?.read_to_end(&mut stdout)?; - self.observers.observe_stdout(&stdout); + Error::illegal_state( + "Observer tries to read stderr, but stderr was not `Stdio::pipe` in CommandExecutor", + ) + })?.read_to_end(&mut stdout)?; + ob.observe_stdout(&stdout); + } + if let Some(ref mut ob) = &mut self.configurer.stderr_observer_mut() { + let mut stderr = Vec::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_end(&mut stderr)?; + ob.observe_stderr(&stderr); } - res } } @@ -409,6 +313,8 @@ where /// The builder for a default [`CommandExecutor`] that should fit most use-cases. #[derive(Debug, Clone)] pub struct CommandExecutorBuilder { + stdout: Option, + stderr: Option, debug_child: bool, program: Option, args: Vec, @@ -429,6 +335,8 @@ impl CommandExecutorBuilder { #[must_use] fn new() -> CommandExecutorBuilder { CommandExecutorBuilder { + stdout: None, + stderr: None, program: None, args: vec![], input_location: InputLocation::StdIn, @@ -468,7 +376,19 @@ impl CommandExecutorBuilder { pub fn arg_input_arg(&mut self) -> &mut Self { let argnum = self.args.len(); self.input(InputLocation::Arg { argnum }); - self.arg("DUMMY"); + // self.arg("DUMMY"); + self + } + + /// Sets the stdout observer + pub fn stdout_observer(&mut self, stdout: StdOutObserver) -> &mut Self { + self.stdout = Some(stdout); + self + } + + /// Sets the stderr observer + pub fn stderr_observer(&mut self, stderr: StdErrObserver) -> &mut Self { + self.stderr = Some(stderr); self } @@ -493,18 +413,12 @@ impl CommandExecutorBuilder { } /// Adds an argument to the program's commandline. - /// - /// You may want to use [`CommandExecutor::parse_afl_cmdline`] if you're going to pass `@@` - /// represents the input file generated by the fuzzer (similar to the `afl-fuzz` command line). pub fn arg>(&mut self, arg: O) -> &mut CommandExecutorBuilder { self.args.push(arg.as_ref().to_owned()); self } /// Adds a range of arguments to the program's commandline. - /// - /// You may want to use [`CommandExecutor::parse_afl_cmdline`] if you're going to pass `@@` - /// represents the input file generated by the fuzzer (similar to the `afl-fuzz` command line). pub fn args(&mut self, args: IT) -> &mut CommandExecutorBuilder where IT: IntoIterator, @@ -597,18 +511,19 @@ impl CommandExecutorBuilder { command.stdout(Stdio::null()); command.stderr(Stdio::null()); } - if observers.observes_stdout() { + + if self.stdout.is_some() { command.stdout(Stdio::piped()); } - if observers.observes_stderr() { - // we need stderr for `AsanBacktaceObserver`, and others + + if self.stderr.is_some() { command.stderr(Stdio::piped()); } let configurator = StdCommandConfigurator { debug_child: self.debug_child, - has_stdout_observer: observers.observes_stdout(), - has_stderr_observer: observers.observes_stderr(), + stdout_observer: self.stdout.clone(), + stderr_observer: self.stderr.clone(), input_location: self.input_location.clone(), timeout: self.timeout, command, @@ -666,6 +581,24 @@ impl CommandExecutorBuilder { #[cfg(all(feature = "std", any(unix, doc)))] pub trait CommandConfigurator: Sized { + /// Get the stdout + fn stdout_observer(&self) -> Option<&StdOutObserver> { + None + } + /// Get the mut stdout + fn stdout_observer_mut(&mut self) -> Option<&mut StdOutObserver> { + None + } + + /// Get the stderr + fn stderr_observer(&self) -> Option<&StdErrObserver> { + None + } + /// Get the mut stderr + fn stderr_observer_mut(&mut self) -> Option<&mut StdErrObserver> { + None + } + /// Spawns a new process with the given configuration. fn spawn_child(&mut self, input: &I) -> Result; @@ -678,8 +611,8 @@ pub trait CommandConfigurator: Sized { OT: MatchName, { CommandExecutor { - observers, configurer: self, + observers, phantom: PhantomData, } } @@ -723,32 +656,4 @@ mod tests { ) .unwrap(); } - - #[test] - #[cfg(unix)] - #[cfg_attr(miri, ignore)] - fn test_parse_afl_cmdline() { - use alloc::string::ToString; - use core::time::Duration; - - let mut mgr = SimpleEventManager::new(SimpleMonitor::new(|status| { - log::info!("{status}"); - })); - - let mut executor = CommandExecutor::parse_afl_cmdline( - ["file".to_string(), "@@".to_string()], - (), - true, - Duration::from_secs(5), - ) - .unwrap(); - executor - .run_target( - &mut NopFuzzer::new(), - &mut NopState::new(), - &mut mgr, - &BytesInput::new(b"test".to_vec()), - ) - .unwrap(); - } } diff --git a/libafl/src/executors/differential.rs b/libafl/src/executors/differential.rs index c4e2620e97..22fdf36a93 100644 --- a/libafl/src/executors/differential.rs +++ b/libafl/src/executors/differential.rs @@ -155,29 +155,6 @@ where self.differential .post_exec_child_all(state, input, exit_kind) } - - /// Returns true if a `stdout` observer was added to the list - #[inline] - fn observes_stdout(&self) -> bool { - self.primary.as_ref().observes_stdout() || self.secondary.as_ref().observes_stdout() - } - /// Returns true if a `stderr` observer was added to the list - #[inline] - fn observes_stderr(&self) -> bool { - self.primary.as_ref().observes_stderr() || self.secondary.as_ref().observes_stderr() - } - - /// Runs `observe_stdout` for all stdout observers in the list - fn observe_stdout(&mut self, stdout: &[u8]) { - self.primary.as_mut().observe_stderr(stdout); - self.secondary.as_mut().observe_stderr(stdout); - } - - /// Runs `observe_stderr` for all stderr observers in the list - fn observe_stderr(&mut self, stderr: &[u8]) { - self.primary.as_mut().observe_stderr(stderr); - self.secondary.as_mut().observe_stderr(stderr); - } } impl MatchName for ProxyObserversTuple diff --git a/libafl/src/observers/map/mod.rs b/libafl/src/observers/map/mod.rs index a5fb8d3e26..4030a71b23 100644 --- a/libafl/src/observers/map/mod.rs +++ b/libafl/src/observers/map/mod.rs @@ -186,22 +186,6 @@ where ) -> Result<(), Error> { self.0.post_exec_child(state, input, exit_kind) } - - fn observes_stdout(&self) -> bool { - self.0.observes_stdout() - } - - fn observes_stderr(&self) -> bool { - self.0.observes_stderr() - } - - fn observe_stdout(&mut self, stdout: &[u8]) { - self.0.observe_stdout(stdout); - } - - fn observe_stderr(&mut self, stderr: &[u8]) { - self.0.observe_stderr(stderr); - } } impl DifferentialObserver diff --git a/libafl/src/observers/mod.rs b/libafl/src/observers/mod.rs index 6401baaf29..ea68117adf 100644 --- a/libafl/src/observers/mod.rs +++ b/libafl/src/observers/mod.rs @@ -81,29 +81,6 @@ where ) -> Result<(), Error> { Ok(()) } - - /// If this observer observes `stdout` - #[inline] - fn observes_stdout(&self) -> bool { - false - } - /// If this observer observes `stderr` - #[inline] - fn observes_stderr(&self) -> bool { - false - } - - /// React to new `stdout` - /// To use this, always return `true` from `observes_stdout` - #[inline] - #[allow(unused_variables)] - fn observe_stdout(&mut self, stdout: &[u8]) {} - - /// React to new `stderr` - /// To use this, always return `true` from `observes_stderr` - #[inline] - #[allow(unused_variables)] - fn observe_stderr(&mut self, stderr: &[u8]) {} } /// Defines the observer type shared across traits of the type. @@ -139,16 +116,6 @@ where input: &S::Input, exit_kind: &ExitKind, ) -> Result<(), Error>; - - /// Returns true if a `stdout` observer was added to the list - fn observes_stdout(&self) -> bool; - /// Returns true if a `stderr` observer was added to the list - fn observes_stderr(&self) -> bool; - - /// Runs `observe_stdout` for all stdout observers in the list - fn observe_stdout(&mut self, stdout: &[u8]); - /// Runs `observe_stderr` for all stderr observers in the list - fn observe_stderr(&mut self, stderr: &[u8]); } impl ObserversTuple for () @@ -180,28 +147,6 @@ where ) -> Result<(), Error> { Ok(()) } - - /// Returns true if a `stdout` observer was added to the list - #[inline] - fn observes_stdout(&self) -> bool { - false - } - - /// Returns true if a `stderr` observer was added to the list - #[inline] - fn observes_stderr(&self) -> bool { - false - } - - /// Runs `observe_stdout` for all stdout observers in the list - #[inline] - #[allow(unused_variables)] - fn observe_stdout(&mut self, stdout: &[u8]) {} - - /// Runs `observe_stderr` for all stderr observers in the list - #[inline] - #[allow(unused_variables)] - fn observe_stderr(&mut self, stderr: &[u8]) {} } impl ObserversTuple for (Head, Tail) @@ -239,32 +184,6 @@ where self.0.post_exec_child(state, input, exit_kind)?; self.1.post_exec_child_all(state, input, exit_kind) } - - /// Returns true if a `stdout` observer was added to the list - #[inline] - fn observes_stdout(&self) -> bool { - self.0.observes_stdout() || self.1.observes_stdout() - } - - /// Returns true if a `stderr` observer was added to the list - #[inline] - fn observes_stderr(&self) -> bool { - self.0.observes_stderr() || self.1.observes_stderr() - } - - /// Runs `observe_stdout` for all stdout observers in the list - #[inline] - fn observe_stdout(&mut self, stdout: &[u8]) { - self.0.observe_stdout(stdout); - self.1.observe_stdout(stdout); - } - - /// Runs `observe_stderr` for all stderr observers in the list - #[inline] - fn observe_stderr(&mut self, stderr: &[u8]) { - self.0.observe_stderr(stderr); - self.1.observe_stderr(stderr); - } } /// A trait for [`Observer`]`s` with a hash field diff --git a/libafl/src/observers/stacktrace.rs b/libafl/src/observers/stacktrace.rs index 9f082afa43..dacc00f5c8 100644 --- a/libafl/src/observers/stacktrace.rs +++ b/libafl/src/observers/stacktrace.rs @@ -389,17 +389,6 @@ where ) -> Result<(), Error> { Ok(()) } - - /// Do nothing on new `stderr` - #[inline] - fn observes_stderr(&self) -> bool { - true - } - - /// Do nothing on new `stderr` - fn observe_stderr(&mut self, stderr: &[u8]) { - self.parse_asan_output(&String::from_utf8_lossy(stderr)); - } } impl Named for AsanBacktraceObserver { diff --git a/libafl/src/observers/stdio.rs b/libafl/src/observers/stdio.rs index bc921c95f2..43ee321435 100644 --- a/libafl/src/observers/stdio.rs +++ b/libafl/src/observers/stdio.rs @@ -8,8 +8,6 @@ use std::vec::Vec; use libafl_bolts::Named; use serde::{Deserialize, Serialize}; -use crate::{inputs::UsesInput, observers::Observer}; - /// An observer that captures stdout of a target. /// Only works for supported executors. #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] @@ -30,19 +28,9 @@ impl StdOutObserver { stdout: None, } } -} - -impl Observer for StdOutObserver -where - S: UsesInput, -{ - #[inline] - fn observes_stdout(&self) -> bool { - true - } /// React to new `stdout` - fn observe_stdout(&mut self, stdout: &[u8]) { + pub fn observe_stdout(&mut self, stdout: &[u8]) { self.stdout = Some(stdout.into()); } } @@ -73,19 +61,9 @@ impl StdErrObserver { stderr: None, } } -} - -impl Observer for StdErrObserver -where - S: UsesInput, -{ - #[inline] - fn observes_stderr(&self) -> bool { - true - } /// React to new `stderr` - fn observe_stderr(&mut self, stderr: &[u8]) { + pub fn observe_stderr(&mut self, stderr: &[u8]) { self.stderr = Some(stderr.into()); } } diff --git a/libafl_nyx/src/executor.rs b/libafl_nyx/src/executor.rs index 334082d131..218ac7aa75 100644 --- a/libafl_nyx/src/executor.rs +++ b/libafl_nyx/src/executor.rs @@ -7,7 +7,7 @@ use std::{ use libafl::{ executors::{Executor, ExitKind, HasObservers}, inputs::HasTargetBytes, - observers::{ObserversTuple, UsesObservers}, + observers::{ObserversTuple, StdErrObserver, StdOutObserver, UsesObservers}, state::{HasExecutions, State, UsesState}, Error, }; @@ -20,6 +20,10 @@ use crate::helper::NyxHelper; pub struct NyxExecutor { /// implement nyx function pub helper: NyxHelper, + /// stdout + stdout: Option, + /// stderr + // stderr: Option, /// observers observers: OT, /// phantom data to keep generic type @@ -110,14 +114,18 @@ where } }; - if self.observers.observes_stdout() { - let mut stdout = Vec::new(); - self.helper.nyx_stdout.rewind()?; - self.helper - .nyx_stdout - .read_to_end(&mut stdout) - .map_err(|e| Error::illegal_state(format!("Failed to read Nyx stdout: {e}")))?; - self.observers.observe_stdout(&stdout); + match self.stdout.as_mut() { + Some(ob) => { + let mut stdout = Vec::new(); + self.helper.nyx_stdout.rewind()?; + self.helper + .nyx_stdout + .read_to_end(&mut stdout) + .map_err(|e| Error::illegal_state(format!("Failed to read Nyx stdout: {e}")))?; + + ob.observe_stdout(&stdout); + } + None => (), } Ok(exit_kind) @@ -125,14 +133,6 @@ where } impl NyxExecutor { - pub fn new(helper: NyxHelper, observers: OT) -> Self { - Self { - helper, - observers, - phantom: PhantomData, - } - } - /// convert `trace_bits` ptr into real trace map pub fn trace_bits(self) -> &'static mut [u8] { unsafe { @@ -141,6 +141,42 @@ impl NyxExecutor { } } +pub struct NyxExecutorBuilder { + stdout: Option, + // stderr: Option, +} + +impl NyxExecutorBuilder { + pub fn new() -> Self { + Self { + stdout: None, + // stderr: None, + } + } + + pub fn stdout(&mut self, stdout: StdOutObserver) -> &mut Self { + self.stdout = Some(stdout); + self + } + + /* + pub fn stderr(&mut self, stderr: StdErrObserver) -> &mut Self { + self.stderr = Some(stderr); + self + } + */ + + pub fn build(&self, helper: NyxHelper, observers: OT) -> NyxExecutor { + NyxExecutor { + helper, + stdout: self.stdout.clone(), + // stderr: self.stderr.clone(), + observers, + phantom: PhantomData, + } + } +} + impl HasObservers for NyxExecutor where S: State,