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:
parent
1fca710813
commit
98fbe83c15
@ -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 {
|
||||
|
@ -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()));
|
||||
|
@ -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},
|
||||
|
@ -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(&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::<ASANBacktraceObserver>("ASANBacktraceObserver")
|
||||
.match_name_mut::<StdOutObserver>("StdOutObserver")
|
||||
.unwrap()
|
||||
.parse_asan_output_from_childstderr(stderr);
|
||||
};
|
||||
.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,
|
||||
}
|
||||
}
|
||||
|
94
libafl/src/executors/differential.rs
Normal file
94
libafl/src/executors/differential.rs
Normal 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()
|
||||
}
|
||||
}
|
@ -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)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
255
libafl/src/feedbacks/differential.rs
Normal file
255
libafl/src/feedbacks/differential.rs
Normal 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);
|
||||
}
|
||||
}
|
@ -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")]
|
||||
|
@ -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::*;
|
||||
|
||||
|
@ -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
|
||||
|
59
libafl/src/observers/stdio.rs
Normal file
59
libafl/src/observers/stdio.rs
Normal 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
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user