Skip to content

Commit 60d0ccb

Browse files
authored
Commandline args handling for binary only target on libafl_qemu (#3098)
* POC * POC * pin the obj * add map_input_to_memory * still trying to get things to work * Justfile * lol * nah bad idea * done * revert * revert * lol * Move to libafl_qemu * a * add * add * lol * clp * a * tmate * Thank you bash I love you * aaaaaaaa * a * bbb
1 parent 9195245 commit 60d0ccb

File tree

5 files changed

+233
-7
lines changed

5 files changed

+233
-7
lines changed

.gitignore

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ vendor
55

66
# cargo lockfiles except from binaries
77
**/Cargo.lock
8-
!fuzzers/**/Cargo.lock
9-
!utils/**/Cargo.lock
8+
# !fuzzers/**/Cargo.lock
9+
# !utils/**/Cargo.lock
1010

1111

1212
.DS_Store

fuzzers/inprocess/libfuzzer_stb_image/Justfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ fuzzer: cxx
2424
[macos]
2525
run: fuzzer
2626
#!/bin/bash
27-
./{{FUZZER}} &
27+
./{{FUZZER_NAME}} &
28+
./{{FUZZER_NAME}}
2829
sleep 0.2
2930

3031
[windows]

fuzzers/inprocess/libfuzzer_stb_image_sugar/Justfile

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ fuzzer: cxx
2424
[macos]
2525
run: fuzzer
2626
#!/bin/bash
27-
./{{FUZZER}} &
27+
./{{FUZZER_NAME}} &
2828
sleep 0.2
2929

3030
[windows]
@@ -37,9 +37,7 @@ test: fuzzer
3737
#!/bin/bash
3838
success=0
3939
rm -rf libafl_unix_shmem_server || true
40-
(timeout 5s ./{{FUZZER_NAME}} >fuzz_stdout.log 2>/dev/null || true) &
41-
sleep 0.2
42-
timeout 5s ./{{FUZZER_NAME}} >/dev/null 2>/dev/null || true
40+
timeout 5s ./{{FUZZER_NAME}} >fuzz_stdout.log
4341
while read -r line; do
4442
corpus_number=$(echo "$line" | cut -d' ' -f2)
4543
if (( corpus_number > 50 )); then

libafl_bolts/src/cargs.rs

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
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+
}

libafl_bolts/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,11 @@ pub mod subrange;
112112
#[cfg(any(feature = "xxh3", feature = "alloc"))]
113113
pub mod tuples;
114114

115+
#[cfg(all(feature = "std", unix))]
116+
pub mod cargs;
117+
#[cfg(all(feature = "std", unix))]
118+
pub use cargs::*;
119+
115120
/// The purpose of this module is to alleviate imports of the bolts by adding a glob import.
116121
#[cfg(feature = "prelude")]
117122
pub mod bolts_prelude {

0 commit comments

Comments
 (0)