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;
/// 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.
/// 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,
}
impl Eq for OutFile {}
impl PartialEq for OutFile {
fn eq(&self, other: &Self) -> bool {
self.path == other.path
}
}
impl Clone for OutFile {
fn clone(&self) -> Self {
Self {

View File

@ -290,7 +290,7 @@ pub fn ucontext() -> Result<ucontext_t, Error> {
} else {
#[cfg(not(feature = "std"))]
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"))]
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
//! In comparison to the [`crate::executors::DiffExecutor`] it does not run the secondary executor in `run_target`.
use crate::{
executors::{Executor, ExitKind, HasObservers},

View File

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

View File

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

View File

@ -52,24 +52,24 @@ impl BacktraceHashValueWrapper {
Self::StaticVariable(_) => {
*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`]
fn get_stacktrace_hash(&self) -> (u64, u64) {
fn get_stacktrace_hash(&self) -> Result<(u64, u64), Error> {
match &self {
Self::Shmem(shmem) => {
let map = shmem.as_slice();
(
u64::from_be_bytes(map[0..8].try_into().expect("Incorrectly sized")),
u64::from_be_bytes(map[8..16].try_into().expect("Incorrectly sized")),
)
}
Self::StaticVariable(hash_tuple) => *hash_tuple,
Self::None => {
panic!("BacktraceSharedMemoryWrapper is not set yet!")
Ok((
u64::from_be_bytes(map[0..8].try_into()?),
u64::from_be_bytes(map[8..16].try_into()?),
))
}
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();
// get last backtrace hash and associated 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
if current_input_hash != input_hash {
let bt_hash = collect_backtrace();
@ -262,27 +262,27 @@ impl ASANBacktraceObserver {
}
/// 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();
stderr
.read_to_string(&mut buf)
.expect("Failed to read the child process stderr");
stderr.read_to_string(&mut buf)?;
self.parse_asan_output(&buf);
Ok(())
}
/// 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 mut asan_output = File::open(Path::new(&log_path))
.unwrap_or_else(|_| panic!("Can't find asan log at {}", &log_path));
let mut asan_output = File::open(Path::new(&log_path))?;
let mut buf = String::new();
asan_output
.read_to_string(&mut buf)
.expect("Failed to read asan log");
fs::remove_file(&log_path).unwrap_or_else(|_| panic!("Failed to delete {}", &log_path));
asan_output.read_to_string(&mut buf)?;
fs::remove_file(&log_path)?;
self.parse_asan_output(&buf);
Ok(())
}
/// 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
}
}