|
| 1 | +//! Parse command line argument like AFL, then put it in a C-compatible way |
| 2 | +use alloc::{borrow::ToOwned, boxed::Box, ffi::CString, vec::Vec}; |
| 3 | +use core::{ |
| 4 | + ffi::{c_char, c_int}, |
| 5 | + pin::Pin, |
| 6 | +}; |
| 7 | +use std::{ |
| 8 | + ffi::{OsStr, OsString}, |
| 9 | + os::unix::ffi::OsStrExt, |
| 10 | + path::Path, |
| 11 | +}; |
| 12 | + |
| 13 | +use crate::{Error, fs::get_unique_std_input_file}; |
| 14 | + |
| 15 | +/// For creating an C-compatible argument |
| 16 | +#[derive(Debug)] |
| 17 | +pub struct CMainArgsBuilder { |
| 18 | + use_stdin: bool, |
| 19 | + program: Option<OsString>, |
| 20 | + input_filename: Option<OsString>, |
| 21 | + args: Vec<OsString>, |
| 22 | +} |
| 23 | + |
| 24 | +impl Default for CMainArgsBuilder { |
| 25 | + fn default() -> Self { |
| 26 | + Self::new() |
| 27 | + } |
| 28 | +} |
| 29 | + |
| 30 | +impl CMainArgsBuilder { |
| 31 | + /// Constructor |
| 32 | + #[must_use] |
| 33 | + pub fn new() -> Self { |
| 34 | + Self { |
| 35 | + program: None, |
| 36 | + use_stdin: false, |
| 37 | + input_filename: None, |
| 38 | + args: Vec::new(), |
| 39 | + } |
| 40 | + } |
| 41 | + |
| 42 | + /// The harness |
| 43 | + #[must_use] |
| 44 | + pub fn program<O>(mut self, program: O) -> Self |
| 45 | + where |
| 46 | + O: AsRef<OsStr>, |
| 47 | + { |
| 48 | + self.program = Some(program.as_ref().to_owned()); |
| 49 | + self |
| 50 | + } |
| 51 | + |
| 52 | + /// Adds an argument to the harness's commandline |
| 53 | + /// |
| 54 | + /// You may want to use `parse_afl_cmdline` if you're going to pass `@@` |
| 55 | + /// represents the input file generated by the fuzzer (similar to the `afl-fuzz` command line). |
| 56 | + #[must_use] |
| 57 | + pub fn arg<O>(mut self, arg: O) -> Self |
| 58 | + where |
| 59 | + O: AsRef<OsStr>, |
| 60 | + { |
| 61 | + self.args.push(arg.as_ref().to_owned()); |
| 62 | + self |
| 63 | + } |
| 64 | + |
| 65 | + /// Adds arguments to the harness's commandline |
| 66 | + /// |
| 67 | + /// You may want to use `parse_afl_cmdline` if you're going to pass `@@` |
| 68 | + /// represents the input file generated by the fuzzer (similar to the `afl-fuzz` command line). |
| 69 | + #[must_use] |
| 70 | + pub fn args<IT, O>(mut self, args: IT) -> Self |
| 71 | + where |
| 72 | + IT: IntoIterator<Item = O>, |
| 73 | + O: AsRef<OsStr>, |
| 74 | + { |
| 75 | + let mut res = vec![]; |
| 76 | + for arg in args { |
| 77 | + res.push(arg.as_ref().to_owned()); |
| 78 | + } |
| 79 | + self.args.append(&mut res); |
| 80 | + self |
| 81 | + } |
| 82 | + |
| 83 | + /// Place the input at this position and set the filename for the input. |
| 84 | + /// |
| 85 | + /// Note: If you use this, you should ensure that there is only one instance using this |
| 86 | + /// file at any given time. |
| 87 | + #[must_use] |
| 88 | + pub fn arg_input_file<P: AsRef<Path>>(self, path: P) -> Self { |
| 89 | + let mut moved = self.arg(path.as_ref()); |
| 90 | + |
| 91 | + let path_as_string = path.as_ref().as_os_str().to_os_string(); |
| 92 | + |
| 93 | + assert!( |
| 94 | + // It's only save to set the input_filename, if it does not overwrite an existing one. |
| 95 | + (moved.input_filename.is_none() || moved.input_filename.unwrap() == path_as_string), |
| 96 | + "Already specified an input file under a different name. This is not supported" |
| 97 | + ); |
| 98 | + |
| 99 | + moved.input_filename = Some(path_as_string); |
| 100 | + moved |
| 101 | + } |
| 102 | + |
| 103 | + /// Place the input at this position and set the default filename for the input. |
| 104 | + #[must_use] |
| 105 | + /// The filename includes the PID of the fuzzer to ensure that no two fuzzers write to the same file |
| 106 | + pub fn arg_input_file_std(self) -> Self { |
| 107 | + self.arg_input_file(get_unique_std_input_file()) |
| 108 | + } |
| 109 | + |
| 110 | + #[must_use] |
| 111 | + /// Parse afl style command line |
| 112 | + /// |
| 113 | + /// Replaces `@@` with the path to the input file generated by the fuzzer. If `@@` is omitted, |
| 114 | + /// `stdin` is used to pass the test case instead. |
| 115 | + /// |
| 116 | + /// Interprets the first argument as the path to the program as long as it is not set yet. |
| 117 | + /// You have to omit the program path in case you have set it already. Otherwise |
| 118 | + /// it will be interpreted as a regular argument, leading to probably unintended results. |
| 119 | + pub fn parse_afl_cmdline<IT, O>(self, args: IT) -> Self |
| 120 | + where |
| 121 | + IT: IntoIterator<Item = O>, |
| 122 | + O: AsRef<OsStr>, |
| 123 | + { |
| 124 | + let mut moved = self; |
| 125 | + |
| 126 | + let mut use_arg_0_as_program = false; |
| 127 | + if moved.program.is_none() { |
| 128 | + use_arg_0_as_program = true; |
| 129 | + } |
| 130 | + |
| 131 | + for item in args { |
| 132 | + if use_arg_0_as_program { |
| 133 | + moved = moved.program(item); |
| 134 | + // After the program has been set, unset `use_arg_0_as_program` to treat all |
| 135 | + // subsequent arguments as regular arguments |
| 136 | + use_arg_0_as_program = false; |
| 137 | + } else if item.as_ref() == "@@" { |
| 138 | + match &moved.input_filename.clone() { |
| 139 | + Some(name) => { |
| 140 | + // If the input file name has been modified, use this one |
| 141 | + moved = moved.arg_input_file(name); |
| 142 | + } |
| 143 | + _ => { |
| 144 | + moved = moved.arg_input_file_std(); |
| 145 | + } |
| 146 | + } |
| 147 | + } else { |
| 148 | + moved = moved.arg(item); |
| 149 | + } |
| 150 | + } |
| 151 | + |
| 152 | + // If we have not set an input file, use stdin as it is AFLs default |
| 153 | + moved.use_stdin = moved.input_filename.is_none(); |
| 154 | + moved |
| 155 | + } |
| 156 | + |
| 157 | + /// Build it |
| 158 | + pub fn build(&self) -> Result<CMainArgs, Error> { |
| 159 | + let mut argv: Vec<Pin<Box<CString>>> = Vec::new(); |
| 160 | + |
| 161 | + if let Some(program) = &self.program { |
| 162 | + argv.push(Box::pin(CString::new(program.as_bytes()).unwrap())); |
| 163 | + } else { |
| 164 | + return Err(Error::illegal_argument("Program not specified")); |
| 165 | + } |
| 166 | + |
| 167 | + for args in &self.args { |
| 168 | + argv.push(Box::pin(CString::new(args.as_bytes()).unwrap())); |
| 169 | + } |
| 170 | + |
| 171 | + let mut argv_ptr: Vec<*const c_char> = argv.iter().map(|arg| arg.as_ptr()).collect(); |
| 172 | + argv_ptr.push(core::ptr::null()); |
| 173 | + |
| 174 | + Ok(CMainArgs { |
| 175 | + use_stdin: self.use_stdin, |
| 176 | + argv, |
| 177 | + argv_ptr, |
| 178 | + }) |
| 179 | + } |
| 180 | +} |
| 181 | + |
| 182 | +/// For creating an C-compatible argument |
| 183 | +#[derive(Debug)] |
| 184 | +#[allow(dead_code)] |
| 185 | +pub struct CMainArgs { |
| 186 | + use_stdin: bool, |
| 187 | + /// This guys have to sit here, else Rust will free them |
| 188 | + argv: Vec<Pin<Box<CString>>>, |
| 189 | + argv_ptr: Vec<*const c_char>, |
| 190 | +} |
| 191 | + |
| 192 | +// From https://gist.github.com/TrinityCoder/793c097b5a4ab25b8fabf5cd67e92f05 |
| 193 | +impl CMainArgs { |
| 194 | + /// If stdin is used for this or no |
| 195 | + #[must_use] |
| 196 | + pub fn use_stdin(&self) -> bool { |
| 197 | + self.use_stdin |
| 198 | + } |
| 199 | + |
| 200 | + /// Returns the C language's `argv` (`*const *const c_char`). |
| 201 | + #[must_use] |
| 202 | + pub fn argv(&self) -> *const *const c_char { |
| 203 | + // println!("{:#?}", self.argv_ptr); |
| 204 | + self.argv_ptr.as_ptr() |
| 205 | + } |
| 206 | + |
| 207 | + /// Returns the C language's `argv[0]` (`*const c_char`). |
| 208 | + /// On x64 you would pass this to Rsi before starting emulation |
| 209 | + /// Like: `qemu.write_reg(Regs::Rsi, main_args.argv() as u64).unwrap();` |
| 210 | + #[must_use] |
| 211 | + pub fn argv0(&self) -> *const c_char { |
| 212 | + self.argv_ptr[0] |
| 213 | + } |
| 214 | + |
| 215 | + /// Gets total number of args. |
| 216 | + /// On x64 you would pass this to Rdi before starting emulation |
| 217 | + /// Like: `qemu.write_reg(Regs::Rdi, main_args.argc() as u64).unwrap();` |
| 218 | + #[must_use] |
| 219 | + pub fn argc(&self) -> c_int { |
| 220 | + (self.argv_ptr.len() - 1).try_into().unwrap() |
| 221 | + } |
| 222 | +} |
0 commit comments