Allow two different observers for DiffExecutor (#843)

* DifferentialExecutor for CommandExecutor along with StdIO observer

* format

* fix CI issues

* fix format and unit test

* fix documentation

* allow three structs and doc only for linux

* resolve documentation test failure

* minor

* running fmt_all.sh

* into_executor() takes 4 params, not just 1

Co-authored-by: Dominik Maier <domenukk@gmail.com>
This commit is contained in:
Alessandro Mantovani 2022-10-23 01:59:40 +02:00 committed by GitHub
parent 64bc5d5bdb
commit 0307dadcd1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 247 additions and 84 deletions

View File

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

View File

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

View File

@ -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<String>,
/// 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<String>,
/// 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<String>,
phantom: PhantomData<(EM, I, S, Z)>,
}
@ -213,6 +224,9 @@ where
debug_child: bool,
observers: OT,
path: P,
has_stdout_observer: Option<String>,
has_stderr_observer: Option<String>,
has_asan_observer: Option<String>,
) -> Result<Self, Error>
where
P: AsRef<Path>,
@ -224,35 +238,29 @@ where
}
command.stdin(Stdio::null());
let has_stdout_observer = observers
.match_name::<StdOutObserver>("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>("StdErrObserver")
.is_some();
let has_asan_observer = observers
.match_name::<ASANBacktraceObserver>("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<String>,
has_stderr_observer: Option<String>,
has_asan_observer: Option<String>,
) -> Result<Self, Error>
where
IT: IntoIterator<Item = O>,
@ -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>("ASANBacktraceObserver")
.match_name_mut::<ASANBacktraceObserver>(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>("StdErrObserver")
.match_name_mut::<StdErrObserver>(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>("StdOutObserver")
.match_name_mut::<StdOutObserver>(name)
.unwrap()
.stdout = Some(stdout);
}
@ -398,6 +419,9 @@ pub struct CommandExecutorBuilder {
input_location: InputLocation,
cwd: Option<PathBuf>,
envs: Vec<(OsString, OsString)>,
has_stdout_observer: Option<String>,
has_stderr_observer: Option<String>,
has_asan_observer: Option<String>,
}
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<O>(&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<P: AsRef<Path>>(&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>("ASANBacktraceObserver")
.is_some()
|| observers
.match_name::<StdErrObserver>("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>("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::<EM, I, OT, S, Z>(
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<EM, I: Input + HasTargetBytes, S, Z>() -> impl Executor<EM, I, S, Z> {
/// 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<EM, I, OT, S, Z>(self, observers: OT) -> CommandExecutor<EM, I, OT, S, Self, Z>
fn into_executor<EM, I, OT, S, Z>(
self,
observers: OT,
has_stdout_observer: Option<String>,
has_stderr_observer: Option<String>,
has_asan_observer: Option<String>,
) -> CommandExecutor<EM, I, OT, S, Self, Z>
where
OT: Debug + MatchName,
{
let has_asan_observer = observers
.match_name::<ASANBacktraceObserver>("ASANBacktraceObserver")
.is_some();
let has_stdout_observer = observers
.match_name::<StdOutObserver>("StdOutObserver")
.is_some();
let has_stderr_observer = observers
.match_name::<StdErrObserver>("StdErrObserver")
.is_some();
CommandExecutor {
observers,
has_asan_observer,
@ -697,13 +736,18 @@ mod tests {
#[cfg(unix)]
fn test_parse_afl_cmdline() {
use alloc::string::ToString;
let mut mgr = SimpleEventManager::<BytesInput, _, ()>::new(SimpleMonitor::new(|status| {
println!("{status}");
}));
let mut executor =
CommandExecutor::parse_afl_cmdline(&["file".to_string(), "@@".to_string()], (), true)
let mut executor = CommandExecutor::parse_afl_cmdline(
&["file".to_string(), "@@".to_string()],
(),
true,
None,
None,
None,
)
.unwrap();
executor
.run_target(

View File

@ -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<A, B>
pub struct DiffExecutor<A, B, OTA, OTB>
where
A: Debug,
B: Debug,
OTA: Debug,
OTB: Debug,
{
primary: A,
secondary: B,
observers: UnsafeCell<ProxyObserversTuple<OTA, OTB>>,
}
impl<A, B> DiffExecutor<A, B>
impl<A, B, OTA, OTB> DiffExecutor<A, B, OTA, OTB>
where
A: Debug,
B: Debug,
OTA: Debug,
OTB: Debug,
{
/// Create a new `DiffExecutor`, wrapping the given `executor`s.
pub fn new<EM, I, S, Z>(primary: A, secondary: B) -> Self
@ -34,7 +42,14 @@ where
B: Executor<EM, I, S, Z>,
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<A, B, EM, I, S, Z> Executor<EM, I, S, Z> for DiffExecutor<A, B>
impl<A, B, EM, I, OTA, OTB, S, Z> Executor<EM, I, S, Z> for DiffExecutor<A, B, OTA, OTB>
where
A: Executor<EM, I, S, Z>,
B: Executor<EM, I, S, Z>,
I: Input,
OTA: Debug,
OTB: Debug,
{
fn run_target(
&mut self,
@ -77,19 +94,115 @@ where
}
}
impl<A, B, I, OT, S> HasObservers<I, OT, S> for DiffExecutor<A, B>
/// 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<A, B> {
primary: OwnedPtrMut<A>,
secondary: OwnedPtrMut<B>,
}
impl<A, B, I, S> ObserversTuple<I, S> for ProxyObserversTuple<A, B>
where
A: HasObservers<I, OT, S>,
B: Debug,
OT: ObserversTuple<I, S>,
A: ObserversTuple<I, S>,
B: ObserversTuple<I, S>,
{
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<A, B> MatchName for ProxyObserversTuple<A, B>
where
A: MatchName,
B: MatchName,
{
fn match_name<T>(&self, name: &str) -> Option<&T> {
if let Some(t) = self.primary.as_ref().match_name::<T>(name) {
return Some(t);
}
self.secondary.as_ref().match_name::<T>(name)
}
fn match_name_mut<T>(&mut self, name: &str) -> Option<&mut T> {
if let Some(t) = self.primary.as_mut().match_name_mut::<T>(name) {
return Some(t);
}
self.secondary.as_mut().match_name_mut::<T>(name)
}
}
impl<A, B> ProxyObserversTuple<A, B> {
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<A, B, I, OTA, OTB, S> HasObservers<I, ProxyObserversTuple<OTA, OTB>, S>
for DiffExecutor<A, B, OTA, OTB>
where
A: HasObservers<I, OTA, S>,
B: HasObservers<I, OTB, S>,
OTA: ObserversTuple<I, S>,
OTB: ObserversTuple<I, S>,
{
#[inline]
fn observers(&self) -> &OT {
self.primary.observers()
fn observers(&self) -> &ProxyObserversTuple<OTA, OTB> {
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<OTA, OTB> {
unsafe {
self.observers
.get()
.as_mut()
.unwrap()
.set(self.primary.observers(), self.secondary.observers());
self.observers.get().as_mut().unwrap()
}
}
}

View File

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

View File

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