#[cfg(feature = "frida_cli")]
use alloc::{boxed::Box, string::ToString};
use alloc::{string::String, vec::Vec};
#[cfg(feature = "frida_cli")]
use std::error;
use std::{net::SocketAddr, path::PathBuf, time::Duration};
use clap::{Command, CommandFactory, Parser};
use serde::{Deserialize, Serialize};
use super::core_affinity::Cores;
use crate::Error;
fn parse_timeout(src: &str) -> Result<Duration, Error> {
Ok(Duration::from_millis(src.parse()?))
}
#[cfg(feature = "frida_cli")]
fn parse_instrumentation_location(
location: &str,
) -> Result<(String, usize), Box<dyn error::Error + Send + Sync + 'static>> {
let pos = location
.find('@')
.ok_or("Expected an '@' in location specifier")?;
let (module, offset) = location.split_at(pos);
Ok((
module.to_string(),
usize::from_str_radix(
offset
.get(1..)
.ok_or("index out of range")?
.trim_start_matches("0x"),
16,
)?,
))
}
#[derive(Parser, Clone, Debug, Serialize, Deserialize)]
#[clap(
arg_required_else_help(true),
subcommand_precedence_over_arg(true),
args_conflicts_with_subcommands(true)
)]
#[allow(clippy::struct_excessive_bools)]
pub struct FuzzerOptions {
#[clap(short, long, default_value = "1000", parse(try_from_str = parse_timeout), help_heading = "Fuzz Options")]
pub timeout: Duration,
#[clap(short, long)]
pub verbose: bool,
#[clap(short, long, default_value = "/dev/null")]
pub stdout: String,
#[clap(long, default_value = "default configuration")]
pub configuration: String,
#[clap(short = 'A', long, help_heading = "Fuzz Options")]
pub asan: bool,
#[cfg(feature = "frida_cli")]
#[clap(long, default_value = "0", parse(try_from_str = Cores::from_cmdline), help_heading = "ASAN Options")]
pub asan_cores: Cores,
#[clap(short = 'I', long, help_heading = "Fuzz Options", default_value = "0")]
pub iterations: usize,
#[clap(short = 'H', long, parse(from_os_str), help_heading = "Fuzz Options")]
pub harness: Option<PathBuf>,
#[cfg(not(feature = "qemu_cli"))]
#[clap(last = true, name = "HARNESS_ARGS")]
pub harness_args: Vec<String>,
#[cfg(feature = "frida_cli")]
#[clap(
short = 'F',
long,
default_value = "LLVMFuzzerTestOneInput",
help_heading = "Frida Options"
)]
pub harness_function: String,
#[cfg(feature = "frida_cli")]
#[clap(short, long, help_heading = "Frida Options")]
pub libs_to_instrument: Vec<String>,
#[cfg_attr(
feature = "frida_cli",
clap(short = 'C', long, help_heading = "Frida Options")
)]
#[cfg_attr(
not(feature = "frida_cli"),
clap(short = 'C', long, help_heading = "Fuzz Options")
)]
pub cmplog: bool,
#[cfg(feature = "frida_cli")]
#[clap(long, default_value = "0", parse(try_from_str = Cores::from_cmdline), help_heading = "Frida Options")]
pub cmplog_cores: Cores,
#[cfg(feature = "frida_cli")]
#[clap(short, long, help_heading = "ASAN Options")]
pub detect_leaks: bool,
#[cfg(feature = "frida_cli")]
#[clap(long, help_heading = "ASAN Options")]
pub continue_on_error: bool,
#[cfg(feature = "frida_cli")]
#[clap(long, help_heading = "ASAN Options")]
pub allocation_backtraces: bool,
#[cfg(feature = "frida_cli")]
#[clap(
short,
long,
default_value = "1073741824", // 1_usize << 30
help_heading = "ASAN Options"
)]
pub max_allocation: usize,
#[cfg(feature = "frida_cli")]
#[clap(
short = 'M',
long,
default_value = "4294967296", // 1_usize << 32
help_heading = "ASAN Options"
)]
pub max_total_allocation: usize,
#[cfg(feature = "frida_cli")]
#[clap(long, help_heading = "ASAN Options")]
pub max_allocation_panics: bool,
#[cfg(feature = "frida_cli")]
#[clap(long, help_heading = "Frida Options")]
pub disable_coverage: bool,
#[cfg(feature = "frida_cli")]
#[clap(long, help_heading = "Frida Options")]
pub drcov: bool,
#[cfg(feature = "frida_cli")]
#[clap(short = 'D', long, help_heading = "Frida Options", parse(try_from_str = parse_instrumentation_location), multiple_occurrences = true)]
pub dont_instrument: Vec<(String, usize)>,
#[cfg(feature = "qemu_cli")]
#[clap(last = true)]
pub qemu_args: Vec<String>,
#[clap(
short = 'x',
long,
multiple_values = true,
parse(from_os_str),
help_heading = "Fuzz Options"
)]
pub tokens: Vec<PathBuf>,
#[clap(
short,
long,
default_values = &["corpus/"],
multiple_values = true,
parse(from_os_str),
help_heading = "Corpus Options"
)]
pub input: Vec<PathBuf>,
#[clap(
short,
long,
default_value = "solutions/",
parse(from_os_str),
help_heading = "Corpus Options"
)]
pub output: PathBuf,
#[clap(short = 'c', long, default_value = "0", parse(try_from_str = Cores::from_cmdline))]
pub cores: Cores,
#[clap(short = 'p', long, default_value = "1337", name = "PORT")]
pub broker_port: u16,
#[clap(short = 'a', long, parse(try_from_str), name = "REMOTE")]
pub remote_broker_addr: Option<SocketAddr>,
#[clap(short, long, parse(from_os_str), help_heading = "Replay Options")]
pub replay: Option<PathBuf>,
#[clap(
short = 'R',
long,
default_missing_value = "1",
min_values = 0,
help_heading = "Replay Options",
requires = "replay"
)]
pub repeat: Option<usize>,
}
impl FuzzerOptions {
#[must_use]
pub fn with_subcommand(mode: Command) -> Command {
let command: Command = Self::command();
command.subcommand(mode)
}
}
#[must_use]
pub fn parse_args() -> FuzzerOptions {
FuzzerOptions::parse()
}
#[cfg(all(
test,
any(feature = "cli", feature = "qemu_cli", feature = "frida_cli")
))]
mod tests {
#[cfg(feature = "frida_cli")]
use alloc::string::String;
use super::*;
#[test]
#[cfg(feature = "qemu_cli")]
fn standard_option_with_trailing_variable_length_args_collected() {
let parsed = FuzzerOptions::parse_from([
"some-command",
"--broker-port",
"1336",
"-i",
"corpus-1",
"-i",
"corpus-2",
"--",
"-L",
"qemu-bound",
]);
assert_eq!(parsed.qemu_args, ["-L", "qemu-bound"]);
assert_eq!(parsed.broker_port, 1336);
}
#[test]
#[cfg(feature = "frida_cli")]
fn parse_instrumentation_location_fails_without_at_symbol() {
parse_instrumentation_location("mod_name0x12345").unwrap_err();
}
#[test]
#[cfg(feature = "frida_cli")]
fn parse_instrumentation_location_failes_without_address() {
parse_instrumentation_location("mod_name@").unwrap_err();
}
#[test]
#[cfg(feature = "frida_cli")]
fn parse_instrumentation_location_succeeds_without_0x() {
assert_eq!(
parse_instrumentation_location("mod_name@12345").unwrap(),
(String::from("mod_name"), 74565)
);
}
#[test]
#[cfg(feature = "frida_cli")]
fn parse_instrumentation_location_succeeds_with_0x() {
assert_eq!(
parse_instrumentation_location("mod_name@0x12345").unwrap(),
(String::from("mod_name"), 74565)
);
}
#[test]
#[cfg(feature = "cli")]
fn parse_timeout_gives_correct_values() {
assert_eq!(parse_timeout("1525").unwrap(), Duration::from_millis(1525));
}
}