Introduce HasAflStyleTargetArguments and restore parse_afl_cmdline for CommandExecutor (#3125)

* Introduce HasAflStyleTargetArguments and restore parse_afl_cmdline for CommandExecutor

* clippy

* Fix imports

* Move envs to the shared trait

* Move to a standalone file

* Format

* Gate via std and unix

* Fix

* clippy

* move InputLocation to afl_args

* clippy

* Fix

* fmt

* Fix more

* Shall last missing

* Rename to AflTargetArgs

* Fmt

* move AflTargetArgs to libafl_bolts and adapt CMainArgsBuilder to use it

* use_stdin is no longer needed

* leaveout

* Fix again

* Renaming

* Leave out lib.rs

* Leave out fmt

---------

Co-authored-by: Dongjia "toka" Zhang <tokazerkje@outlook.com>
This commit is contained in:
lazymio 2025-04-08 19:19:44 +08:00 committed by GitHub
parent a7d735c1de
commit fa8a576ef0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 467 additions and 520 deletions

View File

@ -25,7 +25,7 @@ use libafl_bolts::{
rands::StdRand, rands::StdRand,
shmem::{ShMem, ShMemProvider}, shmem::{ShMem, ShMemProvider},
tuples::tuple_list, tuples::tuple_list,
AsSliceMut, AsSliceMut, TargetArgs,
}; };
pub fn main() { pub fn main() {

View File

@ -22,7 +22,7 @@ use libafl_bolts::{
rands::StdRand, rands::StdRand,
shmem::{ShMem, ShMemProvider, UnixShMemProvider}, shmem::{ShMem, ShMemProvider, UnixShMemProvider},
tuples::{tuple_list, Handled, Merge}, tuples::{tuple_list, Handled, Merge},
AsSliceMut, Truncate, AsSliceMut, TargetArgs, Truncate,
}; };
use libafl_targets::EDGES_MAP_DEFAULT_SIZE; use libafl_targets::EDGES_MAP_DEFAULT_SIZE;
use nix::sys::signal::Signal; use nix::sys::signal::Signal;

View File

@ -19,7 +19,7 @@ use libafl::{
state::{HasCorpus, StdState}, state::{HasCorpus, StdState},
}; };
use libafl_bolts::{ use libafl_bolts::{
AsSliceMut, Truncate, current_nanos, AsSliceMut, TargetArgs, Truncate, current_nanos,
rands::StdRand, rands::StdRand,
shmem::{ShMem, ShMemProvider, UnixShMemProvider}, shmem::{ShMem, ShMemProvider, UnixShMemProvider},
tuples::{Handled, Merge, tuple_list}, tuples::{Handled, Merge, tuple_list},

View File

@ -38,7 +38,7 @@ use libafl_bolts::{
rands::StdRand, rands::StdRand,
shmem::{ShMem, ShMemProvider, UnixShMemProvider}, shmem::{ShMem, ShMemProvider, UnixShMemProvider},
tuples::{tuple_list, Merge}, tuples::{tuple_list, Merge},
AsSliceMut, AsSliceMut, TargetArgs,
}; };
use libafl_targets::cmps::AFLppCmpLogMap; use libafl_targets::cmps::AFLppCmpLogMap;
use nix::sys::signal::Signal; use nix::sys::signal::Signal;

View File

@ -37,7 +37,7 @@ use libafl_bolts::{
rands::StdRand, rands::StdRand,
shmem::{ShMem, ShMemProvider, UnixShMemProvider}, shmem::{ShMem, ShMemProvider, UnixShMemProvider},
tuples::{tuple_list, Handled, Merge}, tuples::{tuple_list, Handled, Merge},
AsSliceMut, AsSliceMut, TargetArgs,
}; };
use libafl_targets::{ use libafl_targets::{
cmps::{observers::AFLppCmpLogObserver, stages::AFLppCmplogTracingStage}, cmps::{observers::AFLppCmpLogObserver, stages::AFLppCmplogTracingStage},

View File

@ -38,7 +38,7 @@ use libafl_bolts::{
rands::StdRand, rands::StdRand,
shmem::{ShMem, ShMemProvider, UnixShMemProvider}, shmem::{ShMem, ShMemProvider, UnixShMemProvider},
tuples::{tuple_list, Handled, Merge}, tuples::{tuple_list, Handled, Merge},
AsSliceMut, AsSliceMut, TargetArgs,
}; };
use libafl_targets::cmps::AFLppCmpLogMap; use libafl_targets::cmps::AFLppCmpLogMap;
use nix::sys::signal::Signal; use nix::sys::signal::Signal;

View File

@ -51,7 +51,7 @@ use libafl_bolts::{
rands::StdRand, rands::StdRand,
shmem::{ShMem, ShMemProvider, UnixShMemProvider}, shmem::{ShMem, ShMemProvider, UnixShMemProvider},
tuples::{tuple_list, Handled, Merge}, tuples::{tuple_list, Handled, Merge},
AsSliceMut, AsSliceMut, TargetArgs,
}; };
#[cfg(feature = "nyx")] #[cfg(feature = "nyx")]
use libafl_nyx::{executor::NyxExecutor, helper::NyxHelper, settings::NyxSettings}; use libafl_nyx::{executor::NyxExecutor, helper::NyxHelper, settings::NyxSettings};

View File

@ -29,7 +29,7 @@ use libafl_bolts::{
rands::StdRand, rands::StdRand,
shmem::{ShMem, ShMemProvider, UnixShMemProvider}, shmem::{ShMem, ShMemProvider, UnixShMemProvider},
tuples::{tuple_list, Handled}, tuples::{tuple_list, Handled},
AsSliceMut, Truncate, AsSliceMut, TargetArgs, Truncate,
}; };
use nix::sys::signal::Signal; use nix::sys::signal::Signal;

View File

@ -23,8 +23,7 @@ use std::{
#[cfg(all(feature = "intel_pt", target_os = "linux"))] #[cfg(all(feature = "intel_pt", target_os = "linux"))]
use libafl_bolts::core_affinity::CoreId; use libafl_bolts::core_affinity::CoreId;
use libafl_bolts::{ use libafl_bolts::{
AsSlice, AsSlice, InputLocation, TargetArgs,
fs::{InputFile, get_unique_std_input_file},
tuples::{Handle, MatchName, RefIndexable}, tuples::{Handle, MatchName, RefIndexable},
}; };
#[cfg(all(feature = "intel_pt", target_os = "linux"))] #[cfg(all(feature = "intel_pt", target_os = "linux"))]
@ -59,27 +58,6 @@ use crate::{
std::borrow::ToOwned, std::borrow::ToOwned,
}; };
/// How to deliver input to an external program
/// `StdIn`: The target reads from stdin
/// `File`: The target reads from the specified [`InputFile`]
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub enum InputLocation {
/// Mutate a commandline argument to deliver an input
Arg {
/// The offset of the argument to mutate
argnum: usize,
},
/// Deliver input via `StdIn`
#[default]
StdIn,
/// Deliver the input via the specified [`InputFile`]
/// You can use specify [`InputFile::create(INPUTFILE_STD)`] to use a default filename.
File {
/// The file to write input to. The target should read input from this location.
out_file: InputFile,
},
}
/// 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. /// Use [`CommandExecutor::builder()`] to use this configurator.
@ -530,6 +508,40 @@ pub struct CommandExecutorBuilder {
timeout: Duration, timeout: Duration,
} }
impl TargetArgs for CommandExecutorBuilder {
fn arguments_ref(&self) -> &Vec<OsString> {
&self.args
}
fn arguments_mut(&mut self) -> &mut Vec<OsString> {
&mut self.args
}
fn envs_ref(&self) -> &Vec<(OsString, OsString)> {
&self.envs
}
fn envs_mut(&mut self) -> &mut Vec<(OsString, OsString)> {
&mut self.envs
}
fn program_ref(&self) -> &Option<OsString> {
&self.program
}
fn program_mut(&mut self) -> &mut Option<OsString> {
&mut self.program
}
fn input_location_ref(&self) -> &InputLocation {
&self.input_location
}
fn input_location_mut(&mut self) -> &mut InputLocation {
&mut self.input_location
}
}
impl Default for CommandExecutorBuilder { impl Default for CommandExecutorBuilder {
fn default() -> Self { fn default() -> Self {
Self::new() Self::new()
@ -553,40 +565,6 @@ impl CommandExecutorBuilder {
} }
} }
/// Set the binary to execute
/// This option is required.
pub fn program<O>(&mut self, program: O) -> &mut Self
where
O: AsRef<OsStr>,
{
self.program = Some(program.as_ref().to_owned());
self
}
/// Set the input mode and location.
/// This option is mandatory, if not set, the `build` method will error.
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 = 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 });
// Placeholder arg that gets replaced with the input name later.
self.arg("PLACEHOLDER");
self
}
/// Sets the stdout observer /// Sets the stdout observer
pub fn stdout_observer(&mut self, stdout: Handle<StdOutObserver>) -> &mut Self { pub fn stdout_observer(&mut self, stdout: Handle<StdOutObserver>) -> &mut Self {
self.stdout = Some(stdout); self.stdout = Some(stdout);
@ -599,68 +577,6 @@ impl CommandExecutorBuilder {
self 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(get_unique_std_input_file());
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 = InputFile::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 {
self.args.push(arg.as_ref().to_owned());
self
}
/// Adds a range of arguments to the program's commandline.
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
}
/// Adds a range of environment variables to the executed command.
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
}
/// Adds an environment variable to the executed command.
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
}
/// Sets the working directory for the child process. /// Sets the working directory for the child process.
pub fn current_dir<P: AsRef<Path>>(&mut self, dir: P) -> &mut CommandExecutorBuilder { pub fn current_dir<P: AsRef<Path>>(&mut self, dir: P) -> &mut CommandExecutorBuilder {
self.cwd = Some(dir.as_ref().to_owned()); self.cwd = Some(dir.as_ref().to_owned());
@ -865,6 +781,8 @@ fn waitpid_filtered(pid: Pid, options: Option<WaitPidFlag>) -> Result<WaitStatus
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use libafl_bolts::TargetArgs;
use crate::{ use crate::{
events::SimpleEventManager, events::SimpleEventManager,
executors::{ executors::{
@ -885,8 +803,7 @@ mod tests {
log::info!("{status}"); log::info!("{status}");
})); }));
let mut executor = CommandExecutor::builder(); let executor = CommandExecutor::builder()
executor
.program("ls") .program("ls")
.input(InputLocation::Arg { argnum: 0 }); .input(InputLocation::Arg { argnum: 0 });
let executor = executor.build(()); let executor = executor.build(());

View File

@ -1,6 +1,6 @@
//! Expose an `Executor` based on a `Forkserver` in order to execute AFL/AFL++ binaries //! Expose an `Executor` based on a `Forkserver` in order to execute AFL/AFL++ binaries
use alloc::{borrow::ToOwned, string::ToString, vec::Vec}; use alloc::{string::ToString, vec::Vec};
use core::{ use core::{
fmt::{self, Debug, Formatter}, fmt::{self, Debug, Formatter},
marker::PhantomData, marker::PhantomData,
@ -8,18 +8,17 @@ use core::{
}; };
use std::{ use std::{
env, env,
ffi::{OsStr, OsString}, ffi::OsString,
io::{self, ErrorKind, Read, Write}, io::{self, ErrorKind, Read, Write},
os::{ os::{
fd::{AsRawFd, BorrowedFd}, fd::{AsRawFd, BorrowedFd},
unix::{io::RawFd, process::CommandExt}, unix::{io::RawFd, process::CommandExt},
}, },
path::Path,
process::{Child, Command, Stdio}, process::{Child, Command, Stdio},
}; };
use libafl_bolts::{ use libafl_bolts::{
AsSlice, AsSliceMut, Truncate, AsSlice, AsSliceMut, InputLocation, TargetArgs, Truncate,
fs::{InputFile, get_unique_std_input_file}, fs::{InputFile, get_unique_std_input_file},
os::{dup2, pipes::Pipe}, os::{dup2, pipes::Pipe},
ownedref::OwnedSlice, ownedref::OwnedSlice,
@ -777,12 +776,11 @@ pub struct ForkserverExecutorBuilder<'a, TC, SP> {
arguments: Vec<OsString>, arguments: Vec<OsString>,
envs: Vec<(OsString, OsString)>, envs: Vec<(OsString, OsString)>,
debug_child: bool, debug_child: bool,
use_stdin: bool,
uses_shmem_testcase: bool, uses_shmem_testcase: bool,
is_persistent: bool, is_persistent: bool,
is_deferred_frksrv: bool, is_deferred_frksrv: bool,
autotokens: Option<&'a mut Tokens>, autotokens: Option<&'a mut Tokens>,
input_filename: Option<OsString>, input_location: InputLocation,
shmem_provider: Option<&'a mut SP>, shmem_provider: Option<&'a mut SP>,
max_input_size: usize, max_input_size: usize,
min_input_size: usize, min_input_size: usize,
@ -795,6 +793,43 @@ pub struct ForkserverExecutorBuilder<'a, TC, SP> {
target_bytes_converter: TC, target_bytes_converter: TC,
} }
impl<TC, SP> TargetArgs for ForkserverExecutorBuilder<'_, TC, SP> {
fn arguments_ref(&self) -> &Vec<OsString> {
&self.arguments
}
fn arguments_mut(&mut self) -> &mut Vec<OsString> {
&mut self.arguments
}
fn envs_ref(&self) -> &Vec<(OsString, OsString)> {
&self.envs
}
fn envs_mut(&mut self) -> &mut Vec<(OsString, OsString)> {
&mut self.envs
}
fn program_ref(&self) -> &Option<OsString> {
&self.program
}
fn program_mut(&mut self) -> &mut Option<OsString> {
&mut self.program
}
fn input_location_ref(&self) -> &InputLocation {
&self.input_location
}
fn input_location_mut(&mut self) -> &mut InputLocation {
&mut self.input_location
}
fn arg_input_arg(self) -> Self {
panic!("ForkserverExecutor doesn't support mutating arguments")
}
}
impl<'a, TC, SHM, SP> ForkserverExecutorBuilder<'a, TC, SP> impl<'a, TC, SHM, SP> ForkserverExecutorBuilder<'a, TC, SP>
where where
SHM: ShMem, SHM: ShMem,
@ -821,7 +856,7 @@ where
"ForkserverExecutor: program: {:?}, arguments: {:?}, use_stdin: {:?}", "ForkserverExecutor: program: {:?}, arguments: {:?}, use_stdin: {:?}",
target, target,
self.arguments.clone(), self.arguments.clone(),
self.use_stdin self.use_stdin()
); );
if self.uses_shmem_testcase && map.is_none() { if self.uses_shmem_testcase && map.is_none() {
@ -887,7 +922,7 @@ where
"ForkserverExecutor: program: {:?}, arguments: {:?}, use_stdin: {:?}, map_size: {:?}", "ForkserverExecutor: program: {:?}, arguments: {:?}, use_stdin: {:?}, map_size: {:?}",
target, target,
self.arguments.clone(), self.arguments.clone(),
self.use_stdin, self.use_stdin(),
self.map_size self.map_size
); );
@ -933,16 +968,16 @@ where
#[expect(clippy::pedantic)] #[expect(clippy::pedantic)]
fn build_helper(&mut self) -> Result<(Forkserver, InputFile, Option<SHM>), Error> { fn build_helper(&mut self) -> Result<(Forkserver, InputFile, Option<SHM>), Error> {
let input_filename = match &self.input_filename { let input_file = match &self.input_location {
Some(name) => name.clone(), InputLocation::StdIn => InputFile::create(OsString::from(get_unique_std_input_file()))?,
None => { InputLocation::Arg { argnum: _ } => {
self.use_stdin = true; return Err(Error::illegal_argument(
OsString::from(get_unique_std_input_file()) "forkserver doesn't support argument mutation",
));
} }
InputLocation::File { out_file } => out_file.clone(),
}; };
let input_file = InputFile::create(input_filename)?;
let map = match &mut self.shmem_provider { let map = match &mut self.shmem_provider {
None => None, None => None,
Some(provider) => { Some(provider) => {
@ -966,7 +1001,7 @@ where
self.arguments.clone(), self.arguments.clone(),
self.envs.clone(), self.envs.clone(),
input_file.as_raw_fd(), input_file.as_raw_fd(),
self.use_stdin, self.use_stdin(),
0, 0,
self.is_persistent, self.is_persistent,
self.is_deferred_frksrv, self.is_deferred_frksrv,
@ -1226,94 +1261,6 @@ where
self self
} }
#[must_use]
/// Parse afl style command line
///
/// Replaces `@@` with the path to the input file generated by the fuzzer. If `@@` is omitted,
/// `stdin` is used to pass the test case instead.
///
/// Interprets the first argument as the path to the program as long as it is not set yet.
/// You have to omit the program path in case you have set it already. Otherwise
/// it will be interpreted as a regular argument, leading to probably unintended results.
pub fn parse_afl_cmdline<IT, O>(self, args: IT) -> Self
where
IT: IntoIterator<Item = O>,
O: AsRef<OsStr>,
{
let mut moved = self;
let mut use_arg_0_as_program = false;
if moved.program.is_none() {
use_arg_0_as_program = true;
}
for item in args {
if use_arg_0_as_program {
moved = moved.program(item);
// After the program has been set, unset `use_arg_0_as_program` to treat all
// subsequent arguments as regular arguments
use_arg_0_as_program = false;
} else if item.as_ref() == "@@" {
match &moved.input_filename.clone() {
Some(name) => {
// If the input file name has been modified, use this one
moved = moved.arg_input_file(name);
}
_ => {
moved = moved.arg_input_file_std();
}
}
} else {
moved = moved.arg(item);
}
}
// If we have not set an input file, use stdin as it is AFLs default
moved.use_stdin = moved.input_filename.is_none();
moved
}
/// The harness
#[must_use]
pub fn program<O>(mut self, program: O) -> Self
where
O: AsRef<OsStr>,
{
self.program = Some(program.as_ref().to_owned());
self
}
/// Adds an argument to the harness's commandline
///
/// You may want to use `parse_afl_cmdline` if you're going to pass `@@`
/// represents the input file generated by the fuzzer (similar to the `afl-fuzz` command line).
#[must_use]
pub fn arg<O>(mut self, arg: O) -> Self
where
O: AsRef<OsStr>,
{
self.arguments.push(arg.as_ref().to_owned());
self
}
/// Adds arguments to the harness's commandline
///
/// You may want to use `parse_afl_cmdline` if you're going to pass `@@`
/// represents the input file generated by the fuzzer (similar to the `afl-fuzz` command line).
#[must_use]
pub fn args<IT, O>(mut self, args: IT) -> Self
where
IT: IntoIterator<Item = O>,
O: AsRef<OsStr>,
{
let mut res = vec![];
for arg in args {
res.push(arg.as_ref().to_owned());
}
self.arguments.append(&mut res);
self
}
/// Set the max input size /// Set the max input size
#[must_use] #[must_use]
pub fn max_input_size(mut self, size: usize) -> Self { pub fn max_input_size(mut self, size: usize) -> Self {
@ -1328,61 +1275,6 @@ where
self self
} }
/// Adds an environmental var to the harness's commandline
#[must_use]
pub fn env<K, V>(mut self, key: K, val: V) -> Self
where
K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
self.envs
.push((key.as_ref().to_owned(), val.as_ref().to_owned()));
self
}
/// Adds environmental vars to the harness's commandline
#[must_use]
pub fn envs<IT, K, V>(mut self, vars: IT) -> Self
where
IT: IntoIterator<Item = (K, V)>,
K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
let mut res = vec![];
for (ref key, ref val) in vars {
res.push((key.as_ref().to_owned(), val.as_ref().to_owned()));
}
self.envs.append(&mut res);
self
}
/// Place the input at this position and set the filename for the input.
///
/// Note: If you use this, you should ensure that there is only one instance using this
/// file at any given time.
#[must_use]
pub fn arg_input_file<P: AsRef<Path>>(self, path: P) -> Self {
let mut moved = self.arg(path.as_ref());
let path_as_string = path.as_ref().as_os_str().to_os_string();
assert!(
// It's only save to set the input_filename, if it does not overwrite an existing one.
(moved.input_filename.is_none() || moved.input_filename.unwrap() == path_as_string),
"Already specified an input file under a different name. This is not supported"
);
moved.input_filename = Some(path_as_string);
moved
}
/// Place the input at this position and set the default filename for the input.
#[must_use]
/// The filename includes the PID of the fuzzer to ensure that no two fuzzers write to the same file
pub fn arg_input_file_std(self) -> Self {
self.arg_input_file(get_unique_std_input_file())
}
/// If `debug_child` is set, the child will print to `stdout`/`stderr`. /// If `debug_child` is set, the child will print to `stdout`/`stderr`.
#[must_use] #[must_use]
pub fn debug_child(mut self, debug_child: bool) -> Self { pub fn debug_child(mut self, debug_child: bool) -> Self {
@ -1453,12 +1345,11 @@ impl<'a> ForkserverExecutorBuilder<'a, NopTargetBytesConverter<BytesInput>, Unix
arguments: vec![], arguments: vec![],
envs: vec![], envs: vec![],
debug_child: false, debug_child: false,
use_stdin: false,
uses_shmem_testcase: false, uses_shmem_testcase: false,
is_persistent: false, is_persistent: false,
is_deferred_frksrv: false, is_deferred_frksrv: false,
autotokens: None, autotokens: None,
input_filename: None, input_location: InputLocation::StdIn,
shmem_provider: None, shmem_provider: None,
map_size: None, map_size: None,
max_input_size: MAX_INPUT_SIZE_DEFAULT, max_input_size: MAX_INPUT_SIZE_DEFAULT,
@ -1487,12 +1378,11 @@ impl<'a, TC> ForkserverExecutorBuilder<'a, TC, UnixShMemProvider> {
arguments: self.arguments, arguments: self.arguments,
envs: self.envs, envs: self.envs,
debug_child: self.debug_child, debug_child: self.debug_child,
use_stdin: self.use_stdin,
uses_shmem_testcase: self.uses_shmem_testcase, uses_shmem_testcase: self.uses_shmem_testcase,
is_persistent: self.is_persistent, is_persistent: self.is_persistent,
is_deferred_frksrv: self.is_deferred_frksrv, is_deferred_frksrv: self.is_deferred_frksrv,
autotokens: self.autotokens, autotokens: self.autotokens,
input_filename: self.input_filename, input_location: InputLocation::StdIn,
map_size: self.map_size, map_size: self.map_size,
max_input_size: self.max_input_size, max_input_size: self.max_input_size,
min_input_size: self.min_input_size, min_input_size: self.min_input_size,
@ -1520,12 +1410,11 @@ impl<'a, TC, SP> ForkserverExecutorBuilder<'a, TC, SP> {
arguments: self.arguments, arguments: self.arguments,
envs: self.envs, envs: self.envs,
debug_child: self.debug_child, debug_child: self.debug_child,
use_stdin: self.use_stdin,
uses_shmem_testcase: self.uses_shmem_testcase, uses_shmem_testcase: self.uses_shmem_testcase,
is_persistent: self.is_persistent, is_persistent: self.is_persistent,
is_deferred_frksrv: self.is_deferred_frksrv, is_deferred_frksrv: self.is_deferred_frksrv,
autotokens: self.autotokens, autotokens: self.autotokens,
input_filename: self.input_filename, input_location: InputLocation::StdIn,
map_size: self.map_size, map_size: self.map_size,
max_input_size: self.max_input_size, max_input_size: self.max_input_size,
min_input_size: self.min_input_size, min_input_size: self.min_input_size,
@ -1600,7 +1489,7 @@ mod tests {
use std::ffi::OsString; use std::ffi::OsString;
use libafl_bolts::{ use libafl_bolts::{
AsSliceMut, AsSliceMut, TargetArgs,
shmem::{ShMem, ShMemProvider, UnixShMemProvider}, shmem::{ShMem, ShMemProvider, UnixShMemProvider},
tuples::tuple_list, tuples::tuple_list,
}; };

View File

@ -36,6 +36,7 @@ use crate::{Error, observers::Observer};
/// }; /// };
/// use libafl_bolts::{ /// use libafl_bolts::{
/// Named, current_nanos, /// Named, current_nanos,
/// TargetArgs,
/// rands::StdRand, /// rands::StdRand,
/// tuples::{Handle, Handled, MatchNameRef, tuple_list}, /// tuples::{Handle, Handled, MatchNameRef, tuple_list},
/// }; /// };

View File

@ -0,0 +1,137 @@
//! Parse command line argument like AFL, then put it in a C-compatible way
use alloc::{boxed::Box, ffi::CString, vec::Vec};
use core::{
ffi::{c_char, c_int},
pin::Pin,
};
use std::{ffi::OsString, os::unix::ffi::OsStrExt};
use crate::{Error, InputLocation, TargetArgs};
/// For creating an C-compatible argument
#[derive(Debug)]
pub struct CMainArgsBuilder {
program: Option<OsString>,
input_location: InputLocation,
envs: Vec<(OsString, OsString)>,
args: Vec<OsString>,
}
impl TargetArgs for CMainArgsBuilder {
fn arguments_ref(&self) -> &Vec<OsString> {
&self.args
}
fn arguments_mut(&mut self) -> &mut Vec<OsString> {
&mut self.args
}
fn input_location_ref(&self) -> &InputLocation {
&self.input_location
}
fn input_location_mut(&mut self) -> &mut InputLocation {
&mut self.input_location
}
fn envs_ref(&self) -> &Vec<(OsString, OsString)> {
&self.envs
}
fn envs_mut(&mut self) -> &mut Vec<(OsString, OsString)> {
&mut self.envs
}
fn program_ref(&self) -> &Option<OsString> {
&self.program
}
fn program_mut(&mut self) -> &mut Option<OsString> {
&mut self.program
}
}
impl Default for CMainArgsBuilder {
fn default() -> Self {
Self::new()
}
}
impl CMainArgsBuilder {
/// Constructor
#[must_use]
pub fn new() -> Self {
Self {
program: None,
input_location: InputLocation::StdIn,
envs: Vec::new(),
args: Vec::new(),
}
}
/// Build it
pub fn build(&self) -> Result<CMainArgs, Error> {
let mut argv: Vec<Pin<Box<CString>>> = Vec::new();
if let Some(program) = &self.program {
argv.push(Box::pin(CString::new(program.as_bytes()).unwrap()));
} else {
return Err(Error::illegal_argument("Program not specified"));
}
for args in &self.args {
argv.push(Box::pin(CString::new(args.as_bytes()).unwrap()));
}
let mut argv_ptr: Vec<*const c_char> = argv.iter().map(|arg| arg.as_ptr()).collect();
argv_ptr.push(core::ptr::null());
Ok(CMainArgs {
use_stdin: self.use_stdin(),
argv,
argv_ptr,
})
}
}
/// For creating an C-compatible argument
#[derive(Debug)]
#[allow(dead_code)]
pub struct CMainArgs {
use_stdin: bool,
/// This guys have to sit here, else Rust will free them
argv: Vec<Pin<Box<CString>>>,
argv_ptr: Vec<*const c_char>,
}
// From https://gist.github.com/TrinityCoder/793c097b5a4ab25b8fabf5cd67e92f05
impl CMainArgs {
/// If stdin is used for this or no
#[must_use]
pub fn use_stdin(&self) -> bool {
self.use_stdin
}
/// Returns the C language's `argv` (`*const *const c_char`).
#[must_use]
pub fn argv(&self) -> *const *const c_char {
// println!("{:#?}", self.argv_ptr);
self.argv_ptr.as_ptr()
}
/// Returns the C language's `argv[0]` (`*const c_char`).
/// On x64 you would pass this to Rsi before starting emulation
/// Like: `qemu.write_reg(Regs::Rsi, main_args.argv() as u64).unwrap();`
#[must_use]
pub fn argv0(&self) -> *const c_char {
self.argv_ptr[0]
}
/// Gets total number of args.
/// On x64 you would pass this to Rdi before starting emulation
/// Like: `qemu.write_reg(Regs::Rdi, main_args.argc() as u64).unwrap();`
#[must_use]
pub fn argc(&self) -> c_int {
(self.argv_ptr.len() - 1).try_into().unwrap()
}
}

View File

@ -1,222 +0,0 @@
//! Parse command line argument like AFL, then put it in a C-compatible way
use alloc::{borrow::ToOwned, boxed::Box, ffi::CString, vec::Vec};
use core::{
ffi::{c_char, c_int},
pin::Pin,
};
use std::{
ffi::{OsStr, OsString},
os::unix::ffi::OsStrExt,
path::Path,
};
use crate::{Error, fs::get_unique_std_input_file};
/// For creating an C-compatible argument
#[derive(Debug)]
pub struct CMainArgsBuilder {
use_stdin: bool,
program: Option<OsString>,
input_filename: Option<OsString>,
args: Vec<OsString>,
}
impl Default for CMainArgsBuilder {
fn default() -> Self {
Self::new()
}
}
impl CMainArgsBuilder {
/// Constructor
#[must_use]
pub fn new() -> Self {
Self {
program: None,
use_stdin: false,
input_filename: None,
args: Vec::new(),
}
}
/// The harness
#[must_use]
pub fn program<O>(mut self, program: O) -> Self
where
O: AsRef<OsStr>,
{
self.program = Some(program.as_ref().to_owned());
self
}
/// Adds an argument to the harness's commandline
///
/// You may want to use `parse_afl_cmdline` if you're going to pass `@@`
/// represents the input file generated by the fuzzer (similar to the `afl-fuzz` command line).
#[must_use]
pub fn arg<O>(mut self, arg: O) -> Self
where
O: AsRef<OsStr>,
{
self.args.push(arg.as_ref().to_owned());
self
}
/// Adds arguments to the harness's commandline
///
/// You may want to use `parse_afl_cmdline` if you're going to pass `@@`
/// represents the input file generated by the fuzzer (similar to the `afl-fuzz` command line).
#[must_use]
pub fn args<IT, O>(mut self, args: IT) -> Self
where
IT: IntoIterator<Item = O>,
O: AsRef<OsStr>,
{
let mut res = vec![];
for arg in args {
res.push(arg.as_ref().to_owned());
}
self.args.append(&mut res);
self
}
/// Place the input at this position and set the filename for the input.
///
/// Note: If you use this, you should ensure that there is only one instance using this
/// file at any given time.
#[must_use]
pub fn arg_input_file<P: AsRef<Path>>(self, path: P) -> Self {
let mut moved = self.arg(path.as_ref());
let path_as_string = path.as_ref().as_os_str().to_os_string();
assert!(
// It's only save to set the input_filename, if it does not overwrite an existing one.
(moved.input_filename.is_none() || moved.input_filename.unwrap() == path_as_string),
"Already specified an input file under a different name. This is not supported"
);
moved.input_filename = Some(path_as_string);
moved
}
/// Place the input at this position and set the default filename for the input.
#[must_use]
/// The filename includes the PID of the fuzzer to ensure that no two fuzzers write to the same file
pub fn arg_input_file_std(self) -> Self {
self.arg_input_file(get_unique_std_input_file())
}
#[must_use]
/// Parse afl style command line
///
/// Replaces `@@` with the path to the input file generated by the fuzzer. If `@@` is omitted,
/// `stdin` is used to pass the test case instead.
///
/// Interprets the first argument as the path to the program as long as it is not set yet.
/// You have to omit the program path in case you have set it already. Otherwise
/// it will be interpreted as a regular argument, leading to probably unintended results.
pub fn parse_afl_cmdline<IT, O>(self, args: IT) -> Self
where
IT: IntoIterator<Item = O>,
O: AsRef<OsStr>,
{
let mut moved = self;
let mut use_arg_0_as_program = false;
if moved.program.is_none() {
use_arg_0_as_program = true;
}
for item in args {
if use_arg_0_as_program {
moved = moved.program(item);
// After the program has been set, unset `use_arg_0_as_program` to treat all
// subsequent arguments as regular arguments
use_arg_0_as_program = false;
} else if item.as_ref() == "@@" {
match &moved.input_filename.clone() {
Some(name) => {
// If the input file name has been modified, use this one
moved = moved.arg_input_file(name);
}
_ => {
moved = moved.arg_input_file_std();
}
}
} else {
moved = moved.arg(item);
}
}
// If we have not set an input file, use stdin as it is AFLs default
moved.use_stdin = moved.input_filename.is_none();
moved
}
/// Build it
pub fn build(&self) -> Result<CMainArgs, Error> {
let mut argv: Vec<Pin<Box<CString>>> = Vec::new();
if let Some(program) = &self.program {
argv.push(Box::pin(CString::new(program.as_bytes()).unwrap()));
} else {
return Err(Error::illegal_argument("Program not specified"));
}
for args in &self.args {
argv.push(Box::pin(CString::new(args.as_bytes()).unwrap()));
}
let mut argv_ptr: Vec<*const c_char> = argv.iter().map(|arg| arg.as_ptr()).collect();
argv_ptr.push(core::ptr::null());
Ok(CMainArgs {
use_stdin: self.use_stdin,
argv,
argv_ptr,
})
}
}
/// For creating an C-compatible argument
#[derive(Debug)]
#[allow(dead_code)]
pub struct CMainArgs {
use_stdin: bool,
/// This guys have to sit here, else Rust will free them
argv: Vec<Pin<Box<CString>>>,
argv_ptr: Vec<*const c_char>,
}
// From https://gist.github.com/TrinityCoder/793c097b5a4ab25b8fabf5cd67e92f05
impl CMainArgs {
/// If stdin is used for this or no
#[must_use]
pub fn use_stdin(&self) -> bool {
self.use_stdin
}
/// Returns the C language's `argv` (`*const *const c_char`).
#[must_use]
pub fn argv(&self) -> *const *const c_char {
// println!("{:#?}", self.argv_ptr);
self.argv_ptr.as_ptr()
}
/// Returns the C language's `argv[0]` (`*const c_char`).
/// On x64 you would pass this to Rsi before starting emulation
/// Like: `qemu.write_reg(Regs::Rsi, main_args.argv() as u64).unwrap();`
#[must_use]
pub fn argv0(&self) -> *const c_char {
self.argv_ptr[0]
}
/// Gets total number of args.
/// On x64 you would pass this to Rdi before starting emulation
/// Like: `qemu.write_reg(Regs::Rdi, main_args.argc() as u64).unwrap();`
#[must_use]
pub fn argc(&self) -> c_int {
(self.argv_ptr.len() - 1).try_into().unwrap()
}
}

View File

@ -113,9 +113,14 @@ pub mod subrange;
pub mod tuples; pub mod tuples;
#[cfg(all(feature = "std", unix))] #[cfg(all(feature = "std", unix))]
pub mod cargs; pub mod argparse;
#[cfg(all(feature = "std", unix))] #[cfg(all(feature = "std", unix))]
pub use cargs::*; pub use argparse::*;
#[cfg(all(feature = "std", unix))]
pub mod target_args;
#[cfg(all(feature = "std", unix))]
pub use target_args::*;
/// The purpose of this module is to alleviate imports of the bolts by adding a glob import. /// The purpose of this module is to alleviate imports of the bolts by adding a glob import.
#[cfg(feature = "prelude")] #[cfg(feature = "prelude")]

View File

@ -0,0 +1,220 @@
//! Shared implementation of afl style arguments
use alloc::{borrow::ToOwned, vec::Vec};
use std::{
ffi::{OsStr, OsString},
path::Path,
};
use crate::fs::{InputFile, get_unique_std_input_file};
/// How to deliver input to an external program
/// `StdIn`: The target reads from stdin
/// `File`: The target reads from the specified [`InputFile`]
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub enum InputLocation {
/// Mutate a commandline argument to deliver an input
Arg {
/// The offset of the argument to mutate
argnum: usize,
},
/// Deliver input via `StdIn`
#[default]
StdIn,
/// Deliver the input via the specified [`InputFile`]
/// You can use specify [`InputFile::create(INPUTFILE_STD)`] to use a default filename.
File {
/// The file to write input to. The target should read input from this location.
out_file: InputFile,
},
}
/// The main implementation trait of afl style arguments handling
pub trait TargetArgs: Sized {
/// Gets the arguments
fn arguments_ref(&self) -> &Vec<OsString>;
/// Gets the mutable arguments
fn arguments_mut(&mut self) -> &mut Vec<OsString>;
/// Gets the main program
fn program_ref(&self) -> &Option<OsString>;
/// Gets the mutable main program
fn program_mut(&mut self) -> &mut Option<OsString>;
/// Gets the input file
fn input_location_ref(&self) -> &InputLocation;
/// Gets the mutable input file
fn input_location_mut(&mut self) -> &mut InputLocation;
/// Get the environments
fn envs_ref(&self) -> &Vec<(OsString, OsString)>;
/// Get the mutable environments
fn envs_mut(&mut self) -> &mut Vec<(OsString, OsString)>;
/// Adds an environmental var to the harness's commandline
#[must_use]
fn env<K, V>(mut self, key: K, val: V) -> Self
where
K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
self.envs_mut()
.push((key.as_ref().to_owned(), val.as_ref().to_owned()));
self
}
/// Adds environmental vars to the harness's commandline
#[must_use]
fn envs<IT, K, V>(mut self, vars: IT) -> Self
where
IT: IntoIterator<Item = (K, V)>,
K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
let mut res = vec![];
for (ref key, ref val) in vars {
res.push((key.as_ref().to_owned(), val.as_ref().to_owned()));
}
self.envs_mut().append(&mut res);
self
}
/// If use stdin
#[must_use]
fn use_stdin(&self) -> bool {
matches!(self.input_location_ref(), InputLocation::StdIn)
}
/// Set input
#[must_use]
fn input(mut self, input: InputLocation) -> Self {
*self.input_location_mut() = 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.
#[must_use]
fn arg_input_arg(mut self) -> Self {
let argnum = self.arguments_ref().len();
self = self.input(InputLocation::Arg { argnum });
// Placeholder arg that gets replaced with the input name later.
self = self.arg("PLACEHOLDER");
self
}
/// Place the input at this position and set the filename for the input.
///
/// Note: If you use this, you should ensure that there is only one instance using this
/// file at any given time.
#[must_use]
fn arg_input_file<P: AsRef<Path>>(self, path: P) -> Self {
let mut moved = self.arg(path.as_ref());
assert!(
match moved.input_location_ref() {
InputLocation::File { out_file } => out_file.path.as_path() == path.as_ref(),
InputLocation::StdIn => true,
InputLocation::Arg { argnum: _ } => false,
},
"Already specified an input file under a different name. This is not supported"
);
let out_file = InputFile::create(path).unwrap();
moved = moved.input(InputLocation::File { out_file });
moved
}
/// Place the input at this position and set the default filename for the input.
#[must_use]
/// The filename includes the PID of the fuzzer to ensure that no two fuzzers write to the same file
fn arg_input_file_std(self) -> Self {
self.arg_input_file(get_unique_std_input_file())
}
/// The harness
#[must_use]
fn program<O>(mut self, program: O) -> Self
where
O: AsRef<OsStr>,
{
*self.program_mut() = Some(program.as_ref().to_owned());
self
}
/// Adds an argument to the harness's commandline
///
/// You may want to use `parse_afl_cmdline` if you're going to pass `@@`
/// represents the input file generated by the fuzzer (similar to the `afl-fuzz` command line).
#[must_use]
fn arg<O>(mut self, arg: O) -> Self
where
O: AsRef<OsStr>,
{
self.arguments_mut().push(arg.as_ref().to_owned());
self
}
/// Adds arguments to the harness's commandline
///
/// You may want to use `parse_afl_cmdline` if you're going to pass `@@`
/// represents the input file generated by the fuzzer (similar to the `afl-fuzz` command line).
#[must_use]
fn args<IT, O>(mut self, args: IT) -> Self
where
IT: IntoIterator<Item = O>,
O: AsRef<OsStr>,
{
let mut res = vec![];
for arg in args {
res.push(arg.as_ref().to_owned());
}
self.arguments_mut().append(&mut res);
self
}
#[must_use]
/// Parse afl style command line
///
/// Replaces `@@` with the path to the input file generated by the fuzzer. If `@@` is omitted,
/// `stdin` is used to pass the test case instead.
///
/// Interprets the first argument as the path to the program as long as it is not set yet.
/// You have to omit the program path in case you have set it already. Otherwise
/// it will be interpreted as a regular argument, leading to probably unintended results.
fn parse_afl_cmdline<IT, O>(self, args: IT) -> Self
where
IT: IntoIterator<Item = O>,
O: AsRef<OsStr>,
{
let mut moved = self;
let mut use_arg_0_as_program = false;
if moved.program_ref().is_none() {
use_arg_0_as_program = true;
}
for item in args {
if use_arg_0_as_program {
moved = moved.program(item);
// After the program has been set, unset `use_arg_0_as_program` to treat all
// subsequent arguments as regular arguments
use_arg_0_as_program = false;
} else if item.as_ref() == "@@" {
match moved.input_location_ref().clone() {
InputLocation::File { out_file } => {
// If the input file name has been modified, use this one
moved = moved.arg_input_file(&out_file.path);
}
_ => {
moved = moved.arg_input_file_std();
}
}
} else {
moved = moved.arg(item);
}
}
// If we have not set an input file, use stdin as it is AFLs default
moved
}
}

View File

@ -24,7 +24,7 @@ use libafl::{
state::{HasCorpus, StdState}, state::{HasCorpus, StdState},
}; };
use libafl_bolts::{ use libafl_bolts::{
AsSliceMut, AsSliceMut, TargetArgs,
core_affinity::Cores, core_affinity::Cores,
nonzero, nonzero,
rands::StdRand, rands::StdRand,