Differential executor, diff feedback, stdio observers for command executor (#521)

* started diff fuzzer

* finished DifferentialExecutor

* adapt builder, more diff fuzz infra

* diff eq feedback

* stdout observer started:

* stdio observers

* stdio observers

* no_std, fixes

* no_std tests
This commit is contained in:
Dominik Maier 2022-02-06 18:20:57 +01:00 committed by GitHub
parent 1fca710813
commit 98fbe83c15
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 638 additions and 91 deletions

View File

@ -12,7 +12,7 @@ use std::os::unix::prelude::{AsRawFd, RawFd};
use crate::Error; use crate::Error;
/// The default filename to use to deliver testcases to the target /// 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. /// 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`. /// 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, 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 { impl Clone for OutFile {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {

View File

@ -290,7 +290,7 @@ pub fn ucontext() -> Result<ucontext_t, Error> {
} else { } else {
#[cfg(not(feature = "std"))] #[cfg(not(feature = "std"))]
unsafe { 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"))] #[cfg(not(feature = "std"))]
return Err(Error::Unknown("Failed to get ucontex".into())); return Err(Error::Unknown("Failed to get ucontex".into()));

View File

@ -1,4 +1,5 @@
//! A `CombinedExecutor` wraps a primary executor and a secondary one //! 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::{ use crate::{
executors::{Executor, ExitKind, HasObservers}, executors::{Executor, ExitKind, HasObservers},

View File

@ -4,24 +4,25 @@ use core::{
marker::PhantomData, marker::PhantomData,
}; };
#[cfg(unix)]
use std::os::unix::ffi::OsStrExt;
#[cfg(feature = "std")] #[cfg(feature = "std")]
use std::process::Child; use std::process::Child;
use std::{ use std::{
ffi::{OsStr, OsString}, ffi::{OsStr, OsString},
io::Write, io::{Read, Write},
os::unix::prelude::OsStringExt,
path::{Path, PathBuf}, path::{Path, PathBuf},
process::{Command, Stdio}, process::{Command, Stdio},
}; };
use crate::{ use crate::{
bolts::{ bolts::{
fs::{OutFile, DEFAULT_OUTFILE}, fs::{OutFile, OUTFILE_STD},
tuples::MatchName, tuples::MatchName,
AsSlice, AsSlice,
}, },
inputs::HasTargetBytes, inputs::HasTargetBytes,
observers::{ASANBacktraceObserver, ObserversTuple}, observers::{ASANBacktraceObserver, ObserversTuple, StdErrObserver, StdOutObserver},
}; };
#[cfg(feature = "std")] #[cfg(feature = "std")]
use crate::{inputs::Input, Error}; use crate::{inputs::Input, Error};
@ -37,8 +38,8 @@ use super::HasObservers;
/// How to deliver input to an external program /// How to deliver input to an external program
/// `StdIn`: The traget reads from stdin /// `StdIn`: The traget reads from stdin
/// `File`: The target reads from the specified [`OutFile`] /// `File`: The target reads from the specified [`OutFile`]
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum InputLocation { enum InputLocation {
/// Mutate a commandline argument to deliver an input /// Mutate a commandline argument to deliver an input
Arg { Arg {
/// The offset of the argument to mutate /// The offset of the argument to mutate
@ -47,7 +48,7 @@ pub enum InputLocation {
/// Deliver input via `StdIn` /// Deliver input via `StdIn`
StdIn, StdIn,
/// Deliver the iniput via the specified [`OutFile`] /// 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 { File {
/// The fiel to write input to. The target should read input from this location. /// The fiel to write input to. The target should read input from this location.
out_file: OutFile, out_file: OutFile,
@ -71,15 +72,16 @@ fn clone_command(cmd: &Command) -> Command {
/// A simple Configurator that takes the most common parameters /// A simple Configurator that takes the most common parameters
/// Writes the input either to stdio or to a file /// Writes the input either to stdio or to a file
/// Use [`CommandExecutor::builder()`] to use this configurator.
#[derive(Debug)] #[derive(Debug)]
pub struct StdCommandConfigurator { pub struct StdCommandConfigurator {
/// If set to true, the child output will remain visible /// If set to true, the child output will remain visible
/// By default, the child output is hidden to increase execution speed /// By default, the child output is hidden to increase execution speed
pub debug_child: bool, debug_child: bool,
/// true: input gets delivered via stdink /// true: input gets delivered via stdink
pub input_location: InputLocation, input_location: InputLocation,
/// The Command to execute /// The Command to execute
pub command: Command, command: Command,
} }
impl CommandConfigurator for StdCommandConfigurator { impl CommandConfigurator for StdCommandConfigurator {
@ -99,7 +101,13 @@ impl CommandConfigurator for StdCommandConfigurator {
for (i, arg) in args.enumerate() { for (i, arg) in args.enumerate() {
if i == *argnum { 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 { } else {
cmd.arg(arg); cmd.arg(arg);
} }
@ -139,10 +147,17 @@ where
T: Debug, T: Debug,
OT: Debug, OT: Debug,
{ {
inner: T, /// The wrapped comand configurer
configurer: T,
observers: OT, observers: OT,
/// cache if the AsanBacktraceObserver is present /// cache if the AsanBacktraceObserver is present
has_asan_observer: bool, 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)>, phantom: PhantomData<(EM, I, S, Z)>,
} }
@ -150,6 +165,15 @@ impl CommandExecutor<(), (), (), (), (), ()> {
/// Creates a builder for a new [`CommandExecutor`], /// Creates a builder for a new [`CommandExecutor`],
/// backed by a [`StdCommandConfigurator`] /// backed by a [`StdCommandConfigurator`]
/// This is usually the easiest way to construct a [`CommandExecutor`]. /// 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] #[must_use]
pub fn builder() -> CommandExecutorBuilder { pub fn builder() -> CommandExecutorBuilder {
CommandExecutorBuilder::new() CommandExecutorBuilder::new()
@ -163,7 +187,7 @@ where
{ {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("CommandExecutor") f.debug_struct("CommandExecutor")
.field("inner", &self.inner) .field("inner", &self.configurer)
.field("observers", &self.observers) .field("observers", &self.observers)
.finish() .finish()
} }
@ -176,7 +200,7 @@ where
{ {
/// Accesses the inner value /// Accesses the inner value
pub fn inner(&mut self) -> &mut T { pub fn inner(&mut self) -> &mut T {
&mut self.inner &mut self.configurer
} }
} }
@ -202,23 +226,35 @@ where
} }
command.stdin(Stdio::null()); command.stdin(Stdio::null());
let has_stdout_observer = observers
.match_name::<StdOutObserver>("StdOutObserver")
.is_some();
if has_stdout_observer {
command.stderr(Stdio::piped());
}
let has_stderr_observer = observers
.match_name::<StdErrObserver>("StdErrObserver")
.is_some();
let has_asan_observer = observers let has_asan_observer = observers
.match_name::<ASANBacktraceObserver>("ASANBacktraceObserver") .match_name::<ASANBacktraceObserver>("ASANBacktraceObserver")
.is_some(); .is_some();
if has_asan_observer { if has_stderr_observer || has_asan_observer {
command.stderr(Stdio::piped()); command.stderr(Stdio::piped());
} }
Ok(Self { Ok(Self {
observers, observers,
has_asan_observer, has_asan_observer,
inner: StdCommandConfigurator { configurer: StdCommandConfigurator {
input_location: InputLocation::File { input_location: InputLocation::File {
out_file: OutFile::create(path)?, out_file: OutFile::create(path)?,
}, },
command, command,
debug_child, debug_child,
}, },
has_stdout_observer,
has_stderr_observer,
phantom: PhantomData, phantom: PhantomData,
}) })
} }
@ -255,19 +291,12 @@ where
)); ));
} }
atat_at = Some(pos); atat_at = Some(pos);
builder.input(InputLocation::File { builder.arg_input_file_std();
out_file: OutFile::create(DEFAULT_OUTFILE)?,
});
builder.arg(DEFAULT_OUTFILE);
} else { } else {
builder.arg(arg); builder.arg(arg);
} }
} }
if atat_at.is_none() {
builder.input(InputLocation::StdIn);
}
builder.build(observers) builder.build(observers)
} }
} }
@ -291,7 +320,7 @@ where
use std::os::unix::prelude::ExitStatusExt; use std::os::unix::prelude::ExitStatusExt;
use wait_timeout::ChildExt; use wait_timeout::ChildExt;
let mut child = self.inner.spawn_child(input)?; let mut child = self.configurer.spawn_child(input)?;
let res = match child let res = match child
.wait_timeout(Duration::from_secs(5)) .wait_timeout(Duration::from_secs(5))
@ -312,17 +341,38 @@ where
} }
}; };
if self.has_asan_observer { if self.has_asan_observer || self.has_stderr_observer {
let stderr = child.stderr.as_mut().ok_or_else(|| { let mut stderr = String::new();
child.stderr.as_mut().ok_or_else(|| {
Error::IllegalState( 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>("ASANBacktraceObserver")
.unwrap()
.parse_asan_output(&stderr);
}
if self.has_stderr_observer {
self.observers
.match_name_mut::<StdErrObserver>("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 self.observers
.match_name_mut::<ASANBacktraceObserver>("ASANBacktraceObserver") .match_name_mut::<StdOutObserver>("StdOutObserver")
.unwrap() .unwrap()
.parse_asan_output_from_childstderr(stderr); .stdout = Some(stdout);
}; }
res res
} }
@ -345,9 +395,8 @@ impl<EM, I, OT: ObserversTuple<I, S>, S, T: Debug, Z> HasObservers<I, OT, S>
pub struct CommandExecutorBuilder { pub struct CommandExecutorBuilder {
debug_child: bool, debug_child: bool,
program: Option<OsString>, program: Option<OsString>,
args_before: Vec<OsString>, args: Vec<OsString>,
input_location: Option<InputLocation>, input_location: InputLocation,
args_after: Vec<OsString>,
cwd: Option<PathBuf>, cwd: Option<PathBuf>,
envs: Vec<(OsString, OsString)>, envs: Vec<(OsString, OsString)>,
} }
@ -364,9 +413,8 @@ impl CommandExecutorBuilder {
fn new() -> CommandExecutorBuilder { fn new() -> CommandExecutorBuilder {
CommandExecutorBuilder { CommandExecutorBuilder {
program: None, program: None,
args_before: vec![], args: vec![],
input_location: None, input_location: InputLocation::StdIn,
args_after: vec![],
cwd: None, cwd: None,
envs: vec![], envs: vec![],
debug_child: false, debug_child: false,
@ -385,22 +433,50 @@ impl CommandExecutorBuilder {
/// Set the input mode and location. /// Set the input mode and location.
/// This option is mandatory, if not set, the `build` method will error. /// This option is mandatory, if not set, the `build` method will error.
pub fn input(&mut self, input: InputLocation) -> &mut Self { fn input(&mut self, input: InputLocation) -> &mut Self {
// This is an error in the user code, no point in returning Err. // This is a fatal error in the user code, no point in returning Err.
assert!( assert_eq!(
self.input_location.is_none(), self.input_location,
"input location already set, cannot set it again" 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<P: AsRef<Path>>(&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 self
} }
/// Adds an argument to the program's commandline. /// Adds an argument to the program's commandline.
pub fn arg<O: AsRef<OsStr>>(&mut self, arg: O) -> &mut CommandExecutorBuilder { pub fn arg<O: AsRef<OsStr>>(&mut self, arg: O) -> &mut CommandExecutorBuilder {
match self.input_location { self.args.push(arg.as_ref().to_owned());
Some(InputLocation::StdIn) => self.args_before.push(arg.as_ref().to_owned()),
Some(_) | None => self.args_after.push(arg.as_ref().to_owned()),
};
self self
} }
@ -469,26 +545,15 @@ impl CommandExecutorBuilder {
)); ));
}; };
let mut command = Command::new(program); let mut command = Command::new(program);
command.args(&self.args_before);
match &self.input_location { match &self.input_location {
Some(InputLocation::StdIn) => { InputLocation::StdIn => {
command.stdin(Stdio::piped()); command.stdin(Stdio::piped());
} }
Some(InputLocation::File { out_file }) => { InputLocation::File { .. } | InputLocation::Arg { .. } => {
command.stdin(Stdio::null()); 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( command.envs(
self.envs self.envs
.iter() .iter()
@ -504,14 +569,23 @@ impl CommandExecutorBuilder {
if observers if observers
.match_name::<ASANBacktraceObserver>("ASANBacktraceObserver") .match_name::<ASANBacktraceObserver>("ASANBacktraceObserver")
.is_some() .is_some()
|| observers
.match_name::<StdErrObserver>("StdErrObserver")
.is_some()
{ {
// we need stderr for ASANBackt // we need stderr for ASANBackt
command.stderr(Stdio::piped()); command.stderr(Stdio::piped());
} }
if observers
.match_name::<StdOutObserver>("StdOutObserver")
.is_some()
{
command.stdout(Stdio::piped());
}
let configurator = StdCommandConfigurator { let configurator = StdCommandConfigurator {
debug_child: self.debug_child, debug_child: self.debug_child,
input_location: self.input_location.clone().unwrap(), input_location: self.input_location.clone(),
command, command,
}; };
Ok(configurator.into_executor(observers)) Ok(configurator.into_executor(observers))
@ -564,10 +638,20 @@ pub trait CommandConfigurator: Sized + Debug {
.match_name::<ASANBacktraceObserver>("ASANBacktraceObserver") .match_name::<ASANBacktraceObserver>("ASANBacktraceObserver")
.is_some(); .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 { CommandExecutor {
observers, observers,
has_asan_observer, has_asan_observer,
inner: self, has_stdout_observer,
has_stderr_observer,
configurer: self,
phantom: PhantomData, phantom: PhantomData,
} }
} }

View File

@ -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<A, B>
where
A: Debug,
B: Debug,
{
primary: A,
secondary: B,
}
impl<A, B> DiffExecutor<A, B>
where
A: Debug,
B: Debug,
{
/// Create a new `DiffExecutor`, wrapping the given `executor`s.
pub fn new<EM, I, S, Z>(primary: A, secondary: B) -> Self
where
A: Executor<EM, I, S, Z>,
B: Executor<EM, I, S, Z>,
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<A, B, EM, I, S, Z> Executor<EM, I, S, Z> for DiffExecutor<A, B>
where
A: Executor<EM, I, S, Z>,
B: Executor<EM, I, S, Z>,
I: Input,
{
fn run_target(
&mut self,
fuzzer: &mut Z,
state: &mut S,
mgr: &mut EM,
input: &I,
) -> Result<ExitKind, Error> {
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<A, B, I, OT, S> HasObservers<I, OT, S> for DiffExecutor<A, B>
where
A: HasObservers<I, OT, S>,
B: Debug,
OT: ObserversTuple<I, S>,
{
#[inline]
fn observers(&self) -> &OT {
self.primary.observers()
}
#[inline]
fn observers_mut(&mut self) -> &mut OT {
self.primary.observers_mut()
}
}

View File

@ -715,7 +715,7 @@ where
self.observers_mut() self.observers_mut()
.match_name_mut::<ASANBacktraceObserver>("ASANBacktraceObserver") .match_name_mut::<ASANBacktraceObserver>("ASANBacktraceObserver")
.unwrap() .unwrap()
.parse_asan_output_from_asan_log_file(pid); .parse_asan_output_from_asan_log_file(pid)?;
} }
} }

View File

@ -5,6 +5,9 @@ pub use inprocess::InProcessExecutor;
#[cfg(all(feature = "std", feature = "fork", unix))] #[cfg(all(feature = "std", feature = "fork", unix))]
pub use inprocess::InProcessForkExecutor; pub use inprocess::InProcessForkExecutor;
pub mod differential;
pub use differential::DiffExecutor;
/// Timeout executor. /// Timeout executor.
/// Not possible on `no-std` Windows or `no-std`, but works for unix /// Not possible on `no-std` Windows or `no-std`, but works for unix
#[cfg(any(unix, feature = "std"))] #[cfg(any(unix, feature = "std"))]
@ -52,12 +55,50 @@ pub enum ExitKind {
Oom, Oom,
/// The run timed out /// The run timed out
Timeout, 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<dyn SerdeAny>),
}
/// 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`. // The run resulted in a custom `ExitKind`.
// Custom(Box<dyn SerdeAny>), // Custom(Box<dyn SerdeAny>),
} }
crate::impl_serdeany!(ExitKind); crate::impl_serdeany!(ExitKind);
impl From<ExitKind> 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 /// Holds a tuple of Observers
pub trait HasObservers<I, OT, S>: Debug pub trait HasObservers<I, OT, S>: Debug
where where

View File

@ -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<F, O1, O2>
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<F, O1, O2> DiffFeedback<F, O1, O2>
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<Self, Error> {
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<F, O1, O2> Named for DiffFeedback<F, O1, O2>
where
F: FnMut(&O1, &O2) -> DiffResult,
O1: Named,
O2: Named,
{
fn name(&self) -> &str {
&self.name
}
}
impl<F, O1, O2> Debug for DiffFeedback<F, O1, O2>
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<F, I, O1, O2, S> Feedback<I, S> for DiffFeedback<F, O1, O2>
where
F: FnMut(&O1, &O2) -> DiffResult,
I: Input,
S: HasMetadata + HasClientPerfMonitor,
O1: Observer<I, S> + PartialEq<O2>,
O2: Observer<I, S>,
{
fn is_interesting<EM, OT>(
&mut self,
_state: &mut S,
_manager: &mut EM,
_input: &I,
observers: &OT,
_exit_kind: &ExitKind,
) -> Result<bool, Error>
where
EM: EventFirer<I>,
OT: ObserversTuple<I, S> + 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<I, S> Observer<I, S> 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<I: Input> EventFirer<I> for NopEventFirer {
fn fire<S>(
&mut self,
_state: &mut S,
_event: crate::events::Event<I>,
) -> 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<f32> {
unimplemented!()
}
fn stability_mut(&mut self) -> &mut Option<f32> {
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);
}
}

View File

@ -4,6 +4,8 @@
pub mod map; pub mod map;
pub use map::*; pub use map::*;
pub mod differential;
pub use differential::DiffFeedback;
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub mod concolic; pub mod concolic;
#[cfg(feature = "std")] #[cfg(feature = "std")]

View File

@ -7,9 +7,12 @@ pub mod cmp;
pub use cmp::*; pub use cmp::*;
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub mod stacktrace; pub mod stdio;
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub use stacktrace::ASANBacktraceObserver; pub use stdio::{StdErrObserver, StdOutObserver};
#[cfg(feature = "std")]
pub mod stacktrace;
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub use stacktrace::*; pub use stacktrace::*;

View File

@ -52,24 +52,24 @@ impl BacktraceHashValueWrapper {
Self::StaticVariable(_) => { Self::StaticVariable(_) => {
*self = Self::StaticVariable((bt_hash, input_hash)); *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`] /// 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 { match &self {
Self::Shmem(shmem) => { Self::Shmem(shmem) => {
let map = shmem.as_slice(); let map = shmem.as_slice();
( Ok((
u64::from_be_bytes(map[0..8].try_into().expect("Incorrectly sized")), u64::from_be_bytes(map[0..8].try_into()?),
u64::from_be_bytes(map[8..16].try_into().expect("Incorrectly sized")), u64::from_be_bytes(map[8..16].try_into()?),
) ))
}
Self::StaticVariable(hash_tuple) => *hash_tuple,
Self::None => {
panic!("BacktraceSharedMemoryWrapper is not set yet!")
} }
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(); let input_hash = hasher.finish();
// get last backtrace hash and associated input hash // get last backtrace hash and associated input hash
let (bt_hash, current_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 // replace if this is a new input
if current_input_hash != input_hash { if current_input_hash != input_hash {
let bt_hash = collect_backtrace(); let bt_hash = collect_backtrace();
@ -262,27 +262,27 @@ impl ASANBacktraceObserver {
} }
/// read ASAN output from the child stderr and parse it. /// 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(); let mut buf = String::new();
stderr stderr.read_to_string(&mut buf)?;
.read_to_string(&mut buf)
.expect("Failed to read the child process stderr");
self.parse_asan_output(&buf); self.parse_asan_output(&buf);
Ok(())
} }
/// read ASAN output from the log file and parse it. /// 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 log_path = format!("{}.{}", ASAN_LOG_PATH, pid);
let mut asan_output = File::open(Path::new(&log_path)) let mut asan_output = File::open(Path::new(&log_path))?;
.unwrap_or_else(|_| panic!("Can't find asan log at {}", &log_path));
let mut buf = String::new(); let mut buf = String::new();
asan_output asan_output.read_to_string(&mut buf)?;
.read_to_string(&mut buf) fs::remove_file(&log_path)?;
.expect("Failed to read asan log");
fs::remove_file(&log_path).unwrap_or_else(|_| panic!("Failed to delete {}", &log_path));
self.parse_asan_output(&buf); self.parse_asan_output(&buf);
Ok(())
} }
/// parse ASAN error output emited by the target command and compute the hash /// parse ASAN error output emited by the target command and compute the hash

View File

@ -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<String>,
}
/// 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<I, S> Observer<I, S> 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<String>,
}
/// 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<I, S> Observer<I, S> for StdErrObserver {}
impl Named for StdErrObserver {
fn name(&self) -> &str {
&self.name
}
}