diff --git a/fuzzers/baby/backtrace_baby_fuzzers/forkserver_executor/src/main.rs b/fuzzers/baby/backtrace_baby_fuzzers/forkserver_executor/src/main.rs index 0c99d60f14..d50cf27be8 100644 --- a/fuzzers/baby/backtrace_baby_fuzzers/forkserver_executor/src/main.rs +++ b/fuzzers/baby/backtrace_baby_fuzzers/forkserver_executor/src/main.rs @@ -25,7 +25,7 @@ use libafl_bolts::{ rands::StdRand, shmem::{ShMem, ShMemProvider}, tuples::tuple_list, - AsSliceMut, + AsSliceMut, TargetArgs, }; pub fn main() { diff --git a/fuzzers/forkserver/forkserver_libafl_cc/src/main.rs b/fuzzers/forkserver/forkserver_libafl_cc/src/main.rs index 910345a4a9..9a7fa41581 100644 --- a/fuzzers/forkserver/forkserver_libafl_cc/src/main.rs +++ b/fuzzers/forkserver/forkserver_libafl_cc/src/main.rs @@ -22,7 +22,7 @@ use libafl_bolts::{ rands::StdRand, shmem::{ShMem, ShMemProvider, UnixShMemProvider}, tuples::{tuple_list, Handled, Merge}, - AsSliceMut, Truncate, + AsSliceMut, TargetArgs, Truncate, }; use libafl_targets::EDGES_MAP_DEFAULT_SIZE; use nix::sys::signal::Signal; diff --git a/fuzzers/forkserver/forkserver_simple/src/main.rs b/fuzzers/forkserver/forkserver_simple/src/main.rs index 1bb66514bf..13c6705139 100644 --- a/fuzzers/forkserver/forkserver_simple/src/main.rs +++ b/fuzzers/forkserver/forkserver_simple/src/main.rs @@ -19,7 +19,7 @@ use libafl::{ state::{HasCorpus, StdState}, }; use libafl_bolts::{ - AsSliceMut, Truncate, current_nanos, + AsSliceMut, TargetArgs, Truncate, current_nanos, rands::StdRand, shmem::{ShMem, ShMemProvider, UnixShMemProvider}, tuples::{Handled, Merge, tuple_list}, diff --git a/fuzzers/forkserver/fuzzbench_forkserver/src/main.rs b/fuzzers/forkserver/fuzzbench_forkserver/src/main.rs index c321c4cb0d..c1acaffaf8 100644 --- a/fuzzers/forkserver/fuzzbench_forkserver/src/main.rs +++ b/fuzzers/forkserver/fuzzbench_forkserver/src/main.rs @@ -38,7 +38,7 @@ use libafl_bolts::{ rands::StdRand, shmem::{ShMem, ShMemProvider, UnixShMemProvider}, tuples::{tuple_list, Merge}, - AsSliceMut, + AsSliceMut, TargetArgs, }; use libafl_targets::cmps::AFLppCmpLogMap; use nix::sys::signal::Signal; diff --git a/fuzzers/forkserver/fuzzbench_forkserver_cmplog/src/main.rs b/fuzzers/forkserver/fuzzbench_forkserver_cmplog/src/main.rs index 65b9e75c86..dd76c995d9 100644 --- a/fuzzers/forkserver/fuzzbench_forkserver_cmplog/src/main.rs +++ b/fuzzers/forkserver/fuzzbench_forkserver_cmplog/src/main.rs @@ -37,7 +37,7 @@ use libafl_bolts::{ rands::StdRand, shmem::{ShMem, ShMemProvider, UnixShMemProvider}, tuples::{tuple_list, Handled, Merge}, - AsSliceMut, + AsSliceMut, TargetArgs, }; use libafl_targets::{ cmps::{observers::AFLppCmpLogObserver, stages::AFLppCmplogTracingStage}, diff --git a/fuzzers/forkserver/fuzzbench_forkserver_sand/src/main.rs b/fuzzers/forkserver/fuzzbench_forkserver_sand/src/main.rs index 7e2b7bca99..e751bd770f 100644 --- a/fuzzers/forkserver/fuzzbench_forkserver_sand/src/main.rs +++ b/fuzzers/forkserver/fuzzbench_forkserver_sand/src/main.rs @@ -38,7 +38,7 @@ use libafl_bolts::{ rands::StdRand, shmem::{ShMem, ShMemProvider, UnixShMemProvider}, tuples::{tuple_list, Handled, Merge}, - AsSliceMut, + AsSliceMut, TargetArgs, }; use libafl_targets::cmps::AFLppCmpLogMap; use nix::sys::signal::Signal; diff --git a/fuzzers/forkserver/libafl-fuzz/src/fuzzer.rs b/fuzzers/forkserver/libafl-fuzz/src/fuzzer.rs index d36f5c8075..8f238a6c97 100644 --- a/fuzzers/forkserver/libafl-fuzz/src/fuzzer.rs +++ b/fuzzers/forkserver/libafl-fuzz/src/fuzzer.rs @@ -51,7 +51,7 @@ use libafl_bolts::{ rands::StdRand, shmem::{ShMem, ShMemProvider, UnixShMemProvider}, tuples::{tuple_list, Handled, Merge}, - AsSliceMut, + AsSliceMut, TargetArgs, }; #[cfg(feature = "nyx")] use libafl_nyx::{executor::NyxExecutor, helper::NyxHelper, settings::NyxSettings}; diff --git a/fuzzers/structure_aware/forkserver_simple_nautilus/src/main.rs b/fuzzers/structure_aware/forkserver_simple_nautilus/src/main.rs index c06997b2dc..c54b7265c4 100644 --- a/fuzzers/structure_aware/forkserver_simple_nautilus/src/main.rs +++ b/fuzzers/structure_aware/forkserver_simple_nautilus/src/main.rs @@ -29,7 +29,7 @@ use libafl_bolts::{ rands::StdRand, shmem::{ShMem, ShMemProvider, UnixShMemProvider}, tuples::{tuple_list, Handled}, - AsSliceMut, Truncate, + AsSliceMut, TargetArgs, Truncate, }; use nix::sys::signal::Signal; diff --git a/libafl/src/executors/command.rs b/libafl/src/executors/command.rs index 0580e2edad..0930b79a9b 100644 --- a/libafl/src/executors/command.rs +++ b/libafl/src/executors/command.rs @@ -23,8 +23,7 @@ use std::{ #[cfg(all(feature = "intel_pt", target_os = "linux"))] use libafl_bolts::core_affinity::CoreId; use libafl_bolts::{ - AsSlice, - fs::{InputFile, get_unique_std_input_file}, + AsSlice, InputLocation, TargetArgs, tuples::{Handle, MatchName, RefIndexable}, }; #[cfg(all(feature = "intel_pt", target_os = "linux"))] @@ -59,27 +58,6 @@ use crate::{ 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 /// Writes the input either to stdio or to a file /// Use [`CommandExecutor::builder()`] to use this configurator. @@ -530,6 +508,40 @@ pub struct CommandExecutorBuilder { timeout: Duration, } +impl TargetArgs for CommandExecutorBuilder { + fn arguments_ref(&self) -> &Vec { + &self.args + } + + fn arguments_mut(&mut self) -> &mut Vec { + &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 { + &self.program + } + + fn program_mut(&mut self) -> &mut Option { + &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 { fn default() -> Self { Self::new() @@ -553,40 +565,6 @@ impl CommandExecutorBuilder { } } - /// Set the binary to execute - /// This option is required. - pub fn program(&mut self, program: O) -> &mut Self - where - O: AsRef, - { - 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 pub fn stdout_observer(&mut self, stdout: Handle) -> &mut Self { self.stdout = Some(stdout); @@ -599,68 +577,6 @@ impl CommandExecutorBuilder { 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>(&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>(&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(&mut self, args: IT) -> &mut CommandExecutorBuilder - where - IT: IntoIterator, - O: AsRef, - { - for arg in args { - self.arg(arg.as_ref()); - } - self - } - - /// Adds a range of environment variables to the executed command. - pub fn envs(&mut self, vars: IT) -> &mut CommandExecutorBuilder - where - IT: IntoIterator, - K: AsRef, - V: AsRef, - { - 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(&mut self, key: K, val: V) -> &mut CommandExecutorBuilder - where - K: AsRef, - V: AsRef, - { - self.envs - .push((key.as_ref().to_owned(), val.as_ref().to_owned())); - self - } - /// Sets the working directory for the child process. pub fn current_dir>(&mut self, dir: P) -> &mut CommandExecutorBuilder { self.cwd = Some(dir.as_ref().to_owned()); @@ -865,6 +781,8 @@ fn waitpid_filtered(pid: Pid, options: Option) -> Result { arguments: Vec, envs: Vec<(OsString, OsString)>, debug_child: bool, - use_stdin: bool, uses_shmem_testcase: bool, is_persistent: bool, is_deferred_frksrv: bool, autotokens: Option<&'a mut Tokens>, - input_filename: Option, + input_location: InputLocation, shmem_provider: Option<&'a mut SP>, max_input_size: usize, min_input_size: usize, @@ -795,6 +793,43 @@ pub struct ForkserverExecutorBuilder<'a, TC, SP> { target_bytes_converter: TC, } +impl TargetArgs for ForkserverExecutorBuilder<'_, TC, SP> { + fn arguments_ref(&self) -> &Vec { + &self.arguments + } + fn arguments_mut(&mut self) -> &mut Vec { + &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 { + &self.program + } + + fn program_mut(&mut self) -> &mut Option { + &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> where SHM: ShMem, @@ -821,7 +856,7 @@ where "ForkserverExecutor: program: {:?}, arguments: {:?}, use_stdin: {:?}", target, self.arguments.clone(), - self.use_stdin + self.use_stdin() ); if self.uses_shmem_testcase && map.is_none() { @@ -887,7 +922,7 @@ where "ForkserverExecutor: program: {:?}, arguments: {:?}, use_stdin: {:?}, map_size: {:?}", target, self.arguments.clone(), - self.use_stdin, + self.use_stdin(), self.map_size ); @@ -933,16 +968,16 @@ where #[expect(clippy::pedantic)] fn build_helper(&mut self) -> Result<(Forkserver, InputFile, Option), Error> { - let input_filename = match &self.input_filename { - Some(name) => name.clone(), - None => { - self.use_stdin = true; - OsString::from(get_unique_std_input_file()) + let input_file = match &self.input_location { + InputLocation::StdIn => InputFile::create(OsString::from(get_unique_std_input_file()))?, + InputLocation::Arg { argnum: _ } => { + return Err(Error::illegal_argument( + "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 { None => None, Some(provider) => { @@ -966,7 +1001,7 @@ where self.arguments.clone(), self.envs.clone(), input_file.as_raw_fd(), - self.use_stdin, + self.use_stdin(), 0, self.is_persistent, self.is_deferred_frksrv, @@ -1226,94 +1261,6 @@ where 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(self, args: IT) -> Self - where - IT: IntoIterator, - O: AsRef, - { - 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(mut self, program: O) -> Self - where - O: AsRef, - { - 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(mut self, arg: O) -> Self - where - O: AsRef, - { - 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(mut self, args: IT) -> Self - where - IT: IntoIterator, - O: AsRef, - { - 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 #[must_use] pub fn max_input_size(mut self, size: usize) -> Self { @@ -1328,61 +1275,6 @@ where self } - /// Adds an environmental var to the harness's commandline - #[must_use] - pub fn env(mut self, key: K, val: V) -> Self - where - K: AsRef, - V: AsRef, - { - 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(mut self, vars: IT) -> Self - where - IT: IntoIterator, - K: AsRef, - V: AsRef, - { - 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>(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`. #[must_use] pub fn debug_child(mut self, debug_child: bool) -> Self { @@ -1453,12 +1345,11 @@ impl<'a> ForkserverExecutorBuilder<'a, NopTargetBytesConverter, Unix arguments: vec![], envs: vec![], debug_child: false, - use_stdin: false, uses_shmem_testcase: false, is_persistent: false, is_deferred_frksrv: false, autotokens: None, - input_filename: None, + input_location: InputLocation::StdIn, shmem_provider: None, map_size: None, max_input_size: MAX_INPUT_SIZE_DEFAULT, @@ -1487,12 +1378,11 @@ impl<'a, TC> ForkserverExecutorBuilder<'a, TC, UnixShMemProvider> { arguments: self.arguments, envs: self.envs, debug_child: self.debug_child, - use_stdin: self.use_stdin, uses_shmem_testcase: self.uses_shmem_testcase, is_persistent: self.is_persistent, is_deferred_frksrv: self.is_deferred_frksrv, autotokens: self.autotokens, - input_filename: self.input_filename, + input_location: InputLocation::StdIn, map_size: self.map_size, max_input_size: self.max_input_size, min_input_size: self.min_input_size, @@ -1520,12 +1410,11 @@ impl<'a, TC, SP> ForkserverExecutorBuilder<'a, TC, SP> { arguments: self.arguments, envs: self.envs, debug_child: self.debug_child, - use_stdin: self.use_stdin, uses_shmem_testcase: self.uses_shmem_testcase, is_persistent: self.is_persistent, is_deferred_frksrv: self.is_deferred_frksrv, autotokens: self.autotokens, - input_filename: self.input_filename, + input_location: InputLocation::StdIn, map_size: self.map_size, max_input_size: self.max_input_size, min_input_size: self.min_input_size, @@ -1600,7 +1489,7 @@ mod tests { use std::ffi::OsString; use libafl_bolts::{ - AsSliceMut, + AsSliceMut, TargetArgs, shmem::{ShMem, ShMemProvider, UnixShMemProvider}, tuples::tuple_list, }; diff --git a/libafl/src/observers/stdio.rs b/libafl/src/observers/stdio.rs index 2d3abc78bd..4ccc1dc501 100644 --- a/libafl/src/observers/stdio.rs +++ b/libafl/src/observers/stdio.rs @@ -36,6 +36,7 @@ use crate::{Error, observers::Observer}; /// }; /// use libafl_bolts::{ /// Named, current_nanos, +/// TargetArgs, /// rands::StdRand, /// tuples::{Handle, Handled, MatchNameRef, tuple_list}, /// }; diff --git a/libafl_bolts/src/argparse.rs b/libafl_bolts/src/argparse.rs new file mode 100644 index 0000000000..e5c021235f --- /dev/null +++ b/libafl_bolts/src/argparse.rs @@ -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, + input_location: InputLocation, + envs: Vec<(OsString, OsString)>, + args: Vec, +} + +impl TargetArgs for CMainArgsBuilder { + fn arguments_ref(&self) -> &Vec { + &self.args + } + + fn arguments_mut(&mut self) -> &mut Vec { + &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 { + &self.program + } + + fn program_mut(&mut self) -> &mut Option { + &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 { + let mut argv: Vec>> = 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>>, + 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() + } +} diff --git a/libafl_bolts/src/cargs.rs b/libafl_bolts/src/cargs.rs deleted file mode 100644 index a79b262e4c..0000000000 --- a/libafl_bolts/src/cargs.rs +++ /dev/null @@ -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, - input_filename: Option, - args: Vec, -} - -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(mut self, program: O) -> Self - where - O: AsRef, - { - 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(mut self, arg: O) -> Self - where - O: AsRef, - { - 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(mut self, args: IT) -> Self - where - IT: IntoIterator, - O: AsRef, - { - 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>(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(self, args: IT) -> Self - where - IT: IntoIterator, - O: AsRef, - { - 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 { - let mut argv: Vec>> = 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>>, - 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() - } -} diff --git a/libafl_bolts/src/lib.rs b/libafl_bolts/src/lib.rs index b972d85048..deb675f4d9 100644 --- a/libafl_bolts/src/lib.rs +++ b/libafl_bolts/src/lib.rs @@ -113,9 +113,14 @@ pub mod subrange; pub mod tuples; #[cfg(all(feature = "std", unix))] -pub mod cargs; +pub mod argparse; #[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. #[cfg(feature = "prelude")] diff --git a/libafl_bolts/src/target_args.rs b/libafl_bolts/src/target_args.rs new file mode 100644 index 0000000000..ec0b2ebdbb --- /dev/null +++ b/libafl_bolts/src/target_args.rs @@ -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; + /// Gets the mutable arguments + fn arguments_mut(&mut self) -> &mut Vec; + + /// Gets the main program + fn program_ref(&self) -> &Option; + /// Gets the mutable main program + fn program_mut(&mut self) -> &mut Option; + + /// 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(mut self, key: K, val: V) -> Self + where + K: AsRef, + V: AsRef, + { + 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(mut self, vars: IT) -> Self + where + IT: IntoIterator, + K: AsRef, + V: AsRef, + { + 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>(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(mut self, program: O) -> Self + where + O: AsRef, + { + *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(mut self, arg: O) -> Self + where + O: AsRef, + { + 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(mut self, args: IT) -> Self + where + IT: IntoIterator, + O: AsRef, + { + 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(self, args: IT) -> Self + where + IT: IntoIterator, + O: AsRef, + { + 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 + } +} diff --git a/libafl_sugar/src/forkserver.rs b/libafl_sugar/src/forkserver.rs index a6c71b8cc1..5fc5f0b914 100644 --- a/libafl_sugar/src/forkserver.rs +++ b/libafl_sugar/src/forkserver.rs @@ -24,7 +24,7 @@ use libafl::{ state::{HasCorpus, StdState}, }; use libafl_bolts::{ - AsSliceMut, + AsSliceMut, TargetArgs, core_affinity::Cores, nonzero, rands::StdRand,