Remove observer_stdout/observer_stderr from Observer trait (#2167)

* stuff

* upd

* cargo test

* doc

* fmt

* nyx stuff
This commit is contained in:
Dongjia "toka" Zhang 2024-05-13 17:10:55 +02:00 committed by GitHub
parent 5872d24021
commit bf4d1de7cd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 140 additions and 352 deletions

View File

@ -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(|| {

View File

@ -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));

View File

@ -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<StdOutObserver>,
stderr_observer: Option<StdErrObserver>,
timeout: Duration,
/// true: input gets delivered via stdink
input_location: InputLocation,
@ -91,6 +76,22 @@ impl<I> CommandConfigurator<I> 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<Child, Error> {
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<OT, S> CommandExecutor<OT, S, StdCommandConfigurator>
where
OT: MatchName + ObserversTuple<S>,
S: UsesInput,
S::Input: HasTargetBytes,
{
/// Creates a new `CommandExecutor`.
/// Instead of parsing the Command for `@@`, it will
pub fn from_cmd_with_file<P>(
cmd: &Command,
debug_child: bool,
timeout: Duration,
observers: OT,
path: P,
) -> Result<Self, Error>
where
P: AsRef<Path>,
{
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<IT, O>(
args: IT,
observers: OT,
debug_child: bool,
timeout: Duration,
) -> Result<Self, Error>
where
IT: IntoIterator<Item = O>,
O: AsRef<OsStr>,
{
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<EM, OT, S, T, Z> Executor<EM, Z> for CommandExecutor<OT, S, T>
@ -353,25 +258,24 @@ where
}
};
if self.observers.observes_stderr() {
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 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)?;
self.observers.observe_stderr(&stderr);
ob.observe_stderr(&stderr);
}
if self.observers.observes_stdout() {
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);
}
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<StdOutObserver>,
stderr: Option<StdErrObserver>,
debug_child: bool,
program: Option<OsString>,
args: Vec<OsString>,
@ -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<O: AsRef<OsStr>>(&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<IT, O>(&mut self, args: IT) -> &mut CommandExecutorBuilder
where
IT: IntoIterator<Item = O>,
@ -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<I>: 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<Child, Error>;
@ -678,8 +611,8 @@ pub trait CommandConfigurator<I>: 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();
}
}

View File

@ -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<A, B, DOT> MatchName for ProxyObserversTuple<A, B, DOT>

View File

@ -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<S, T, OTA, OTB, const ITH: bool, const NTH: bool> DifferentialObserver<OTA, OTB, S>

View File

@ -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<S> ObserversTuple<S> 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<Head, Tail, S> ObserversTuple<S> 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

View File

@ -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 {

View File

@ -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<S> Observer<S> 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<S> Observer<S> 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());
}
}

View File

@ -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<S, OT> {
/// implement nyx function
pub helper: NyxHelper,
/// stdout
stdout: Option<StdOutObserver>,
/// stderr
// stderr: Option<StdErrObserver>,
/// observers
observers: OT,
/// phantom data to keep generic type <I,S>
@ -110,14 +114,18 @@ where
}
};
if self.observers.observes_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}")))?;
self.observers.observe_stdout(&stdout);
ob.observe_stdout(&stdout);
}
None => (),
}
Ok(exit_kind)
@ -125,14 +133,6 @@ where
}
impl<S, OT> NyxExecutor<S, OT> {
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<S, OT> NyxExecutor<S, OT> {
}
}
pub struct NyxExecutorBuilder {
stdout: Option<StdOutObserver>,
// stderr: Option<StdErrObserver>,
}
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<S, OT>(&self, helper: NyxHelper, observers: OT) -> NyxExecutor<S, OT> {
NyxExecutor {
helper,
stdout: self.stdout.clone(),
// stderr: self.stderr.clone(),
observers,
phantom: PhantomData,
}
}
}
impl<S, OT> HasObservers for NyxExecutor<S, OT>
where
S: State,