use alloc::vec::Vec;
use core::{
fmt::{self, Debug, Formatter},
marker::PhantomData,
};
#[cfg(unix)]
use std::os::unix::ffi::OsStrExt;
#[cfg(feature = "std")]
use std::process::Child;
#[cfg(all(feature = "std", unix))]
use std::time::Duration;
use std::{
ffi::{OsStr, OsString},
io::{Read, Write},
path::{Path, PathBuf},
process::{Command, Stdio},
};
use super::HasObservers;
#[cfg(all(feature = "std", unix))]
use crate::executors::{Executor, ExitKind};
use crate::{
bolts::{
fs::{InputFile, INPUTFILE_STD},
tuples::MatchName,
AsSlice,
},
inputs::{HasTargetBytes, UsesInput},
observers::{ObserversTuple, UsesObservers},
state::UsesState,
std::borrow::ToOwned,
};
#[cfg(feature = "std")]
use crate::{inputs::Input, Error};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum InputLocation {
Arg {
argnum: usize,
},
StdIn,
File {
out_file: InputFile,
},
}
fn clone_command(cmd: &Command) -> Command {
let mut new_cmd = Command::new(cmd.get_program());
new_cmd.args(cmd.get_args());
new_cmd.env_clear();
new_cmd.envs(
cmd.get_envs()
.filter_map(|(key, value)| value.map(|value| (key, value))),
);
if let Some(cwd) = cmd.get_current_dir() {
new_cmd.current_dir(cwd);
}
new_cmd
}
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug)]
pub struct StdCommandConfigurator {
debug_child: bool,
has_stdout_observer: bool,
has_stderr_observer: bool,
input_location: InputLocation,
command: Command,
}
impl CommandConfigurator for StdCommandConfigurator {
fn spawn_child<I>(&mut self, input: &I) -> Result<Child, Error>
where
I: Input + HasTargetBytes,
{
match &mut self.input_location {
InputLocation::Arg { argnum } => {
let args = self.command.get_args();
let mut cmd = Command::new(self.command.get_program());
if !self.debug_child {
cmd.stdout(Stdio::null());
cmd.stderr(Stdio::null());
}
if self.has_stdout_observer {
cmd.stdout(Stdio::piped());
}
if self.has_stderr_observer {
cmd.stderr(Stdio::piped());
}
for (i, arg) in args.enumerate() {
if i == *argnum {
debug_assert_eq!(arg, "DUMMY");
#[cfg(unix)]
cmd.arg(OsStr::from_bytes(input.target_bytes().as_slice()));
#[cfg(not(unix))]
cmd.arg(OsString::from_vec(input.target_bytes().as_vec()));
} else {
cmd.arg(arg);
}
}
cmd.envs(
self.command
.get_envs()
.filter_map(|(key, value)| value.map(|value| (key, value))),
);
if let Some(cwd) = self.command.get_current_dir() {
cmd.current_dir(cwd);
}
Ok(cmd.spawn()?)
}
InputLocation::StdIn => {
self.command.stdin(Stdio::piped()).spawn()?;
let mut handle = self.command.spawn()?;
let mut stdin = handle.stdin.take().unwrap();
stdin.write_all(input.target_bytes().as_slice())?;
stdin.flush()?;
drop(stdin);
Ok(handle)
}
InputLocation::File { out_file } => {
out_file.write_buf(input.target_bytes().as_slice())?;
Ok(self.command.spawn()?)
}
}
}
}
pub struct CommandExecutor<EM, OT, S, T, Z> {
configurer: T,
observers: OT,
phantom: PhantomData<(EM, S, Z)>,
}
impl CommandExecutor<(), (), (), (), ()> {
#[must_use]
pub fn builder() -> CommandExecutorBuilder {
CommandExecutorBuilder::new()
}
}
impl<EM, OT, S, T, Z> Debug for CommandExecutor<EM, OT, S, T, Z>
where
T: Debug,
OT: Debug,
{
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("CommandExecutor")
.field("inner", &self.configurer)
.field("observers", &self.observers)
.finish()
}
}
impl<EM, OT, S, T, Z> CommandExecutor<EM, OT, S, T, Z>
where
T: Debug,
OT: Debug,
{
pub fn inner(&mut self) -> &mut T {
&mut self.configurer
}
}
impl<EM, OT, S, Z> CommandExecutor<EM, OT, S, StdCommandConfigurator, Z>
where
OT: MatchName + Debug + ObserversTuple<S>,
S: UsesInput,
{
pub fn from_cmd_with_file<P>(
cmd: &Command,
debug_child: bool,
observers: OT,
path: P,
) -> Result<Self, Error>
where
P: AsRef<Path>,
{
let mut command = clone_command(cmd);
if !debug_child {
command.stdout(Stdio::null());
command.stderr(Stdio::null());
}
command.stdin(Stdio::null());
let has_stdout_observer = observers.observes_stdout();
if has_stdout_observer {
command.stdout(Stdio::piped());
}
let has_stderr_observer = observers.observes_stderr();
if has_stderr_observer {
command.stderr(Stdio::piped());
}
Ok(Self {
observers,
configurer: StdCommandConfigurator {
input_location: InputLocation::File {
out_file: InputFile::create(path)?,
},
command,
debug_child,
has_stdout_observer,
has_stderr_observer,
},
phantom: PhantomData,
})
}
pub fn parse_afl_cmdline<IT, O>(
args: IT,
observers: OT,
debug_child: bool,
) -> Result<Self, Error>
where
IT: IntoIterator<Item = O>,
O: AsRef<OsStr>,
{
let mut atat_at = None;
let mut builder = CommandExecutorBuilder::new();
builder.debug_child(debug_child);
let afl_delim = OsStr::new("@@");
for (pos, arg) in args.into_iter().enumerate() {
if pos == 0 {
if arg.as_ref() == afl_delim {
return Err(Error::illegal_argument(
"The first argument must not be @@ but the program to execute",
));
}
builder.program(arg);
} else if arg.as_ref() == afl_delim {
if atat_at.is_some() {
return Err(Error::illegal_argument(
"Multiple @@ in afl commandline are not permitted",
));
}
atat_at = Some(pos);
builder.arg_input_file_std();
} else {
builder.arg(arg);
}
}
builder.build(observers)
}
}
#[cfg(all(feature = "std", unix))]
impl<EM, OT, S, T, Z> Executor<EM, Z> for CommandExecutor<EM, OT, S, T, Z>
where
EM: UsesState<State = S>,
S: UsesInput,
S::Input: HasTargetBytes,
T: CommandConfigurator + Debug,
OT: Debug + MatchName + ObserversTuple<S>,
Z: UsesState<State = S>,
{
fn run_target(
&mut self,
_fuzzer: &mut Z,
_state: &mut Self::State,
_mgr: &mut EM,
input: &Self::Input,
) -> Result<ExitKind, Error> {
use std::os::unix::prelude::ExitStatusExt;
use wait_timeout::ChildExt;
let mut child = self.configurer.spawn_child(input)?;
let res = match child
.wait_timeout(Duration::from_secs(5))
.expect("waiting on child failed")
.map(|status| status.signal())
{
Some(Some(9)) => Ok(ExitKind::Oom),
Some(Some(_)) => Ok(ExitKind::Crash),
Some(None) => Ok(ExitKind::Ok),
None => {
drop(child.kill());
drop(child.wait());
Ok(ExitKind::Timeout)
}
};
if self.observers.observes_stderr() {
let mut stderr = Vec::new();
child.stderr.as_mut().ok_or_else(|| {
Error::illegal_state(
"Observer tries to read stderr, but stderr was not `Stdio::pipe` in CommandExecutor",
)
})?.read_to_end(&mut stderr)?;
self.observers.observe_stderr(&stderr);
}
if self.observers.observes_stdout() {
let mut stdout = Vec::new();
child.stdout.as_mut().ok_or_else(|| {
Error::illegal_state(
"Observer tries to read stdout, but stdout was not `Stdio::pipe` in CommandExecutor",
)
})?.read_to_end(&mut stdout)?;
self.observers.observe_stdout(&stdout);
}
res
}
}
impl<EM, OT, S, T, Z> UsesState for CommandExecutor<EM, OT, S, T, Z>
where
S: UsesInput,
{
type State = S;
}
impl<EM, OT, S, T, Z> UsesObservers for CommandExecutor<EM, OT, S, T, Z>
where
OT: ObserversTuple<S>,
S: UsesInput,
{
type Observers = OT;
}
impl<EM, OT, S, T, Z> HasObservers for CommandExecutor<EM, OT, S, T, Z>
where
S: UsesInput,
T: Debug,
OT: ObserversTuple<S>,
{
fn observers(&self) -> &OT {
&self.observers
}
fn observers_mut(&mut self) -> &mut OT {
&mut self.observers
}
}
#[derive(Debug, Clone)]
pub struct CommandExecutorBuilder {
debug_child: bool,
program: Option<OsString>,
args: Vec<OsString>,
input_location: InputLocation,
cwd: Option<PathBuf>,
envs: Vec<(OsString, OsString)>,
}
impl Default for CommandExecutorBuilder {
fn default() -> Self {
Self::new()
}
}
impl CommandExecutorBuilder {
#[must_use]
fn new() -> CommandExecutorBuilder {
CommandExecutorBuilder {
program: None,
args: vec![],
input_location: InputLocation::StdIn,
cwd: None,
envs: vec![],
debug_child: false,
}
}
pub fn program<O>(&mut self, program: O) -> &mut Self
where
O: AsRef<OsStr>,
{
self.program = Some(program.as_ref().to_owned());
self
}
fn input(&mut self, input: InputLocation) -> &mut Self {
assert_eq!(
self.input_location,
InputLocation::StdIn,
"input location already set to non-stdin, cannot set it again"
);
self.input_location = input;
self
}
pub fn arg_input_arg(&mut self) -> &mut Self {
let argnum = self.args.len();
self.input(InputLocation::Arg { argnum });
self.arg("DUMMY");
self
}
pub fn arg_input_file_std(&mut self) -> &mut Self {
self.arg_input_file(INPUTFILE_STD);
self
}
pub fn arg_input_file<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
self.arg(path.as_ref());
let out_file_std = InputFile::create(path.as_ref()).unwrap();
self.input(InputLocation::File {
out_file: out_file_std,
});
self
}
pub fn arg<O: AsRef<OsStr>>(&mut self, arg: O) -> &mut CommandExecutorBuilder {
self.args.push(arg.as_ref().to_owned());
self
}
pub fn args<IT, O>(&mut self, args: IT) -> &mut CommandExecutorBuilder
where
IT: IntoIterator<Item = O>,
O: AsRef<OsStr>,
{
for arg in args {
self.arg(arg.as_ref());
}
self
}
pub fn envs<IT, K, V>(&mut self, vars: IT) -> &mut CommandExecutorBuilder
where
IT: IntoIterator<Item = (K, V)>,
K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
for (ref key, ref val) in vars {
self.env(key.as_ref(), val.as_ref());
}
self
}
pub fn env<K, V>(&mut self, key: K, val: V) -> &mut CommandExecutorBuilder
where
K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
self.envs
.push((key.as_ref().to_owned(), val.as_ref().to_owned()));
self
}
pub fn current_dir<P: AsRef<Path>>(&mut self, dir: P) -> &mut CommandExecutorBuilder {
self.cwd = Some(dir.as_ref().to_owned());
self
}
pub fn debug_child(&mut self, debug_child: bool) -> &mut CommandExecutorBuilder {
self.debug_child = debug_child;
self
}
pub fn build<EM, OT, S, Z>(
&self,
observers: OT,
) -> Result<CommandExecutor<EM, OT, S, StdCommandConfigurator, Z>, Error>
where
OT: Debug + MatchName + ObserversTuple<S>,
S: UsesInput,
{
let Some(program) = &self.program else {
return Err(Error::illegal_argument(
"CommandExecutor::builder: no program set!",
));
};
let mut command = Command::new(program);
match &self.input_location {
InputLocation::StdIn => {
command.stdin(Stdio::piped());
}
InputLocation::File { .. } | InputLocation::Arg { .. } => {
command.stdin(Stdio::null());
}
}
command.args(&self.args);
command.envs(
self.envs
.iter()
.map(|(k, v)| (k.as_os_str(), v.as_os_str())),
);
if let Some(cwd) = &self.cwd {
command.current_dir(cwd);
}
if !self.debug_child {
command.stdout(Stdio::null());
command.stderr(Stdio::null());
}
if observers.observes_stdout() {
command.stdout(Stdio::piped());
}
if observers.observes_stderr() {
command.stderr(Stdio::piped());
}
let configurator = StdCommandConfigurator {
debug_child: self.debug_child,
has_stdout_observer: observers.observes_stdout(),
has_stderr_observer: observers.observes_stderr(),
input_location: self.input_location.clone(),
command,
};
Ok(configurator.into_executor::<EM, OT, S, Z>(observers))
}
}
#[cfg_attr(all(feature = "std", unix), doc = " ```")]
#[cfg_attr(not(all(feature = "std", unix)), doc = " ```ignore")]
#[cfg(all(feature = "std", any(unix, doc)))]
pub trait CommandConfigurator: Sized + Debug {
fn spawn_child<I>(&mut self, input: &I) -> Result<Child, Error>
where
I: Input + HasTargetBytes;
fn into_executor<EM, OT, S, Z>(self, observers: OT) -> CommandExecutor<EM, OT, S, Self, Z>
where
OT: Debug + MatchName,
{
CommandExecutor {
observers,
configurer: self,
phantom: PhantomData,
}
}
}
#[cfg(test)]
mod tests {
use crate::{
events::SimpleEventManager,
executors::{
command::{CommandExecutor, InputLocation},
Executor,
},
inputs::BytesInput,
monitors::SimpleMonitor,
state::NopState,
NopFuzzer,
};
#[test]
#[cfg(unix)]
fn test_builder() {
let mut mgr = SimpleEventManager::new(SimpleMonitor::new(|status| {
println!("{status}");
}));
let mut executor = CommandExecutor::builder();
executor
.program("ls")
.input(InputLocation::Arg { argnum: 0 });
let executor = executor.build(());
let mut executor = executor.unwrap();
executor
.run_target(
&mut NopFuzzer::new(),
&mut NopState::new(),
&mut mgr,
&BytesInput::new(b"test".to_vec()),
)
.unwrap();
}
#[test]
#[cfg(unix)]
fn test_parse_afl_cmdline() {
use alloc::string::ToString;
let mut mgr = SimpleEventManager::new(SimpleMonitor::new(|status| {
println!("{status}");
}));
let mut executor =
CommandExecutor::parse_afl_cmdline(["file".to_string(), "@@".to_string()], (), true)
.unwrap();
executor
.run_target(
&mut NopFuzzer::new(),
&mut NopState::new(),
&mut mgr,
&BytesInput::new(b"test".to_vec()),
)
.unwrap();
}
}