diff --git a/src/agent/onefuzz-agent/src/local/cmd.rs b/src/agent/onefuzz-agent/src/local/cmd.rs index c20a5bca7..624edd820 100644 --- a/src/agent/onefuzz-agent/src/local/cmd.rs +++ b/src/agent/onefuzz-agent/src/local/cmd.rs @@ -7,7 +7,7 @@ use clap::{App, SubCommand}; use crate::local::{ common::add_common_config, generic_analysis, generic_crash_report, generic_generator, libfuzzer, libfuzzer_coverage, libfuzzer_crash_report, libfuzzer_fuzz, libfuzzer_merge, - radamsa, + libfuzzer_test_input, radamsa, test_input, }; const RADAMSA: &str = "radamsa"; @@ -16,9 +16,11 @@ const LIBFUZZER_FUZZ: &str = "libfuzzer-fuzz"; const LIBFUZZER_CRASH_REPORT: &str = "libfuzzer-crash-report"; const LIBFUZZER_COVERAGE: &str = "libfuzzer-coverage"; const LIBFUZZER_MERGE: &str = "libfuzzer-merge"; +const LIBFUZZER_TEST_INPUT: &str = "libfuzzer-test-input"; const GENERIC_CRASH_REPORT: &str = "generic-crash-report"; const GENERIC_GENERATOR: &str = "generic-generator"; const GENERIC_ANALYSIS: &str = "generic-analysis"; +const GENERIC_TEST_INPUT: &str = "generic-test-input"; pub async fn run(args: &clap::ArgMatches<'_>) -> Result<()> { match args.subcommand() { @@ -31,6 +33,8 @@ pub async fn run(args: &clap::ArgMatches<'_>) -> Result<()> { (GENERIC_ANALYSIS, Some(sub)) => generic_analysis::run(sub).await, (GENERIC_CRASH_REPORT, Some(sub)) => generic_crash_report::run(sub).await, (GENERIC_GENERATOR, Some(sub)) => generic_generator::run(sub).await, + (GENERIC_TEST_INPUT, Some(sub)) => test_input::run(sub).await, + (LIBFUZZER_TEST_INPUT, Some(sub)) => libfuzzer_test_input::run(sub).await, _ => { anyhow::bail!("missing subcommand\nUSAGE: {}", args.usage()); } @@ -57,4 +61,8 @@ pub fn args(name: &str) -> App<'static, 'static> { GENERIC_GENERATOR, ))) .subcommand(add_common_config(generic_analysis::args(GENERIC_ANALYSIS))) + .subcommand(add_common_config(test_input::args(GENERIC_TEST_INPUT))) + .subcommand(add_common_config(libfuzzer_test_input::args( + LIBFUZZER_TEST_INPUT, + ))) } diff --git a/src/agent/onefuzz-agent/src/local/common.rs b/src/agent/onefuzz-agent/src/local/common.rs index 68c4a5505..8ddf416e8 100644 --- a/src/agent/onefuzz-agent/src/local/common.rs +++ b/src/agent/onefuzz-agent/src/local/common.rs @@ -32,6 +32,7 @@ pub const CHECK_ASAN_LOG: &str = "check_asan_log"; pub const TOOLS_DIR: &str = "tools_dir"; pub const RENAME_OUTPUT: &str = "rename_output"; pub const CHECK_FUZZER_HELP: &str = "check_fuzzer_help"; +pub const DISABLE_CHECK_DEBUGGER: &str = "disable_check_debugger"; pub const TARGET_EXE: &str = "target_exe"; pub const TARGET_ENV: &str = "target_env"; @@ -176,9 +177,19 @@ pub fn get_synced_dir( }) } -pub fn build_common_config(args: &ArgMatches<'_>) -> Result { +// NOTE: generate_task_id is intended to change the default behavior for local +// fuzzing tasks from generating random task id to using UUID::nil(). This +// enables making the one-shot crash report generation, which isn't really a task, +// consistent across multiple runs. +pub fn build_common_config(args: &ArgMatches<'_>, generate_task_id: bool) -> Result { let job_id = get_uuid("job_id", args).unwrap_or_else(|_| Uuid::nil()); - let task_id = get_uuid("task_id", args).unwrap_or_else(|_| Uuid::new_v4()); + let task_id = get_uuid("task_id", args).unwrap_or_else(|_| { + if generate_task_id { + Uuid::new_v4() + } else { + Uuid::nil() + } + }); let instance_id = get_uuid("instance_id", args).unwrap_or_else(|_| Uuid::nil()); let setup_dir = if args.is_present(SETUP_DIR) { diff --git a/src/agent/onefuzz-agent/src/local/generic_analysis.rs b/src/agent/onefuzz-agent/src/local/generic_analysis.rs index 6cd1976db..0cefd32dc 100644 --- a/src/agent/onefuzz-agent/src/local/generic_analysis.rs +++ b/src/agent/onefuzz-agent/src/local/generic_analysis.rs @@ -55,7 +55,7 @@ pub fn build_analysis_config( } pub async fn run(args: &clap::ArgMatches<'_>) -> Result<()> { - let common = build_common_config(args)?; + let common = build_common_config(args, true)?; let config = build_analysis_config(args, None, common)?; run_analysis(config).await } diff --git a/src/agent/onefuzz-agent/src/local/generic_crash_report.rs b/src/agent/onefuzz-agent/src/local/generic_crash_report.rs index b2977d414..80bb42815 100644 --- a/src/agent/onefuzz-agent/src/local/generic_crash_report.rs +++ b/src/agent/onefuzz-agent/src/local/generic_crash_report.rs @@ -4,8 +4,9 @@ use crate::{ local::common::{ build_common_config, get_cmd_arg, get_cmd_env, get_cmd_exe, get_synced_dir, CmdType, - CHECK_ASAN_LOG, CHECK_RETRY_COUNT, CRASHES_DIR, DISABLE_CHECK_QUEUE, NO_REPRO_DIR, - REPORTS_DIR, TARGET_ENV, TARGET_EXE, TARGET_OPTIONS, TARGET_TIMEOUT, UNIQUE_REPORTS_DIR, + CHECK_ASAN_LOG, CHECK_RETRY_COUNT, CRASHES_DIR, DISABLE_CHECK_DEBUGGER, + DISABLE_CHECK_QUEUE, NO_REPRO_DIR, REPORTS_DIR, TARGET_ENV, TARGET_EXE, TARGET_OPTIONS, + TARGET_TIMEOUT, UNIQUE_REPORTS_DIR, }, tasks::{ config::CommonConfig, @@ -46,7 +47,7 @@ pub fn build_report_config( let check_retry_count = value_t!(args, CHECK_RETRY_COUNT, u64)?; let check_queue = !args.is_present(DISABLE_CHECK_QUEUE); let check_asan_log = args.is_present(CHECK_ASAN_LOG); - let check_debugger = !args.is_present("disable_check_debugger"); + let check_debugger = !args.is_present(DISABLE_CHECK_DEBUGGER); let config = Config { target_exe, @@ -70,7 +71,7 @@ pub fn build_report_config( } pub async fn run(args: &clap::ArgMatches<'_>) -> Result<()> { - let common = build_common_config(args)?; + let common = build_common_config(args, true)?; let config = build_report_config(args, None, common)?; ReportTask::new(config).managed_run().await } @@ -121,9 +122,9 @@ pub fn build_shared_args() -> Vec> { Arg::with_name(CHECK_ASAN_LOG) .takes_value(false) .long(CHECK_ASAN_LOG), - Arg::with_name("disable_check_debugger") + Arg::with_name(DISABLE_CHECK_DEBUGGER) .takes_value(false) - .long("disable_check_debugger"), + .long(DISABLE_CHECK_DEBUGGER), ] } diff --git a/src/agent/onefuzz-agent/src/local/generic_generator.rs b/src/agent/onefuzz-agent/src/local/generic_generator.rs index 90c0d42a1..18b043115 100644 --- a/src/agent/onefuzz-agent/src/local/generic_generator.rs +++ b/src/agent/onefuzz-agent/src/local/generic_generator.rs @@ -4,9 +4,9 @@ use crate::{ local::common::{ build_common_config, get_cmd_arg, get_cmd_env, get_cmd_exe, get_synced_dir, - get_synced_dirs, CmdType, CHECK_ASAN_LOG, CHECK_RETRY_COUNT, CRASHES_DIR, GENERATOR_ENV, - GENERATOR_EXE, GENERATOR_OPTIONS, READONLY_INPUTS, RENAME_OUTPUT, TARGET_ENV, TARGET_EXE, - TARGET_OPTIONS, TARGET_TIMEOUT, TOOLS_DIR, + get_synced_dirs, CmdType, CHECK_ASAN_LOG, CHECK_RETRY_COUNT, CRASHES_DIR, + DISABLE_CHECK_DEBUGGER, GENERATOR_ENV, GENERATOR_EXE, GENERATOR_OPTIONS, READONLY_INPUTS, + RENAME_OUTPUT, TARGET_ENV, TARGET_EXE, TARGET_OPTIONS, TARGET_TIMEOUT, TOOLS_DIR, }, tasks::{ config::CommonConfig, @@ -29,7 +29,7 @@ pub fn build_fuzz_config(args: &clap::ArgMatches<'_>, common: CommonConfig) -> R let rename_output = args.is_present(RENAME_OUTPUT); let check_asan_log = args.is_present(CHECK_ASAN_LOG); - let check_debugger = !args.is_present("disable_check_debugger"); + let check_debugger = !args.is_present(DISABLE_CHECK_DEBUGGER); let check_retry_count = value_t!(args, CHECK_RETRY_COUNT, u64)?; let target_timeout = Some(value_t!(args, TARGET_TIMEOUT, u64)?); @@ -60,7 +60,7 @@ pub fn build_fuzz_config(args: &clap::ArgMatches<'_>, common: CommonConfig) -> R } pub async fn run(args: &clap::ArgMatches<'_>) -> Result<()> { - let common = build_common_config(args)?; + let common = build_common_config(args, true)?; let config = build_fuzz_config(args, common)?; GeneratorTask::new(config).run().await } @@ -120,9 +120,9 @@ pub fn build_shared_args() -> Vec> { .takes_value(true) .long(TARGET_TIMEOUT) .default_value("30"), - Arg::with_name("disable_check_debugger") + Arg::with_name(DISABLE_CHECK_DEBUGGER) .takes_value(false) - .long("disable_check_debugger"), + .long(DISABLE_CHECK_DEBUGGER), ] } diff --git a/src/agent/onefuzz-agent/src/local/libfuzzer.rs b/src/agent/onefuzz-agent/src/local/libfuzzer.rs index ae3596735..74d5074af 100644 --- a/src/agent/onefuzz-agent/src/local/libfuzzer.rs +++ b/src/agent/onefuzz-agent/src/local/libfuzzer.rs @@ -27,7 +27,7 @@ use tokio::task::spawn; use uuid::Uuid; pub async fn run(args: &clap::ArgMatches<'_>) -> Result<()> { - let common = build_common_config(args)?; + let common = build_common_config(args, true)?; let fuzz_config = build_fuzz_config(args, common.clone())?; let crash_dir = fuzz_config .crashes diff --git a/src/agent/onefuzz-agent/src/local/libfuzzer_coverage.rs b/src/agent/onefuzz-agent/src/local/libfuzzer_coverage.rs index 9ae385f92..317b4e410 100644 --- a/src/agent/onefuzz-agent/src/local/libfuzzer_coverage.rs +++ b/src/agent/onefuzz-agent/src/local/libfuzzer_coverage.rs @@ -55,7 +55,7 @@ pub fn build_coverage_config( } pub async fn run(args: &clap::ArgMatches<'_>) -> Result<()> { - let common = build_common_config(args)?; + let common = build_common_config(args, true)?; let config = build_coverage_config(args, false, None, common)?; let mut task = CoverageTask::new(config); diff --git a/src/agent/onefuzz-agent/src/local/libfuzzer_crash_report.rs b/src/agent/onefuzz-agent/src/local/libfuzzer_crash_report.rs index dd962f4d4..fd8ea8bad 100644 --- a/src/agent/onefuzz-agent/src/local/libfuzzer_crash_report.rs +++ b/src/agent/onefuzz-agent/src/local/libfuzzer_crash_report.rs @@ -63,7 +63,7 @@ pub fn build_report_config( } pub async fn run(args: &clap::ArgMatches<'_>) -> Result<()> { - let common = build_common_config(args)?; + let common = build_common_config(args, true)?; let config = build_report_config(args, None, common)?; ReportTask::new(config).managed_run().await } diff --git a/src/agent/onefuzz-agent/src/local/libfuzzer_fuzz.rs b/src/agent/onefuzz-agent/src/local/libfuzzer_fuzz.rs index 8882ed6fc..4ef11de64 100644 --- a/src/agent/onefuzz-agent/src/local/libfuzzer_fuzz.rs +++ b/src/agent/onefuzz-agent/src/local/libfuzzer_fuzz.rs @@ -50,7 +50,7 @@ pub fn build_fuzz_config(args: &clap::ArgMatches<'_>, common: CommonConfig) -> R } pub async fn run(args: &clap::ArgMatches<'_>) -> Result<()> { - let common = build_common_config(args)?; + let common = build_common_config(args, true)?; let config = build_fuzz_config(args, common)?; LibFuzzerFuzzTask::new(config)?.run().await } diff --git a/src/agent/onefuzz-agent/src/local/libfuzzer_merge.rs b/src/agent/onefuzz-agent/src/local/libfuzzer_merge.rs index 4d0dcc31d..43acc8ee9 100644 --- a/src/agent/onefuzz-agent/src/local/libfuzzer_merge.rs +++ b/src/agent/onefuzz-agent/src/local/libfuzzer_merge.rs @@ -45,7 +45,7 @@ pub fn build_merge_config( } pub async fn run(args: &clap::ArgMatches<'_>) -> Result<()> { - let common = build_common_config(args)?; + let common = build_common_config(args, true)?; let config = build_merge_config(args, None, common)?; spawn(std::sync::Arc::new(config)).await } diff --git a/src/agent/onefuzz-agent/src/local/libfuzzer_test_input.rs b/src/agent/onefuzz-agent/src/local/libfuzzer_test_input.rs new file mode 100644 index 000000000..67e3df326 --- /dev/null +++ b/src/agent/onefuzz-agent/src/local/libfuzzer_test_input.rs @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::{ + local::common::{ + build_common_config, get_cmd_arg, get_cmd_env, CmdType, CHECK_RETRY_COUNT, TARGET_ENV, + TARGET_EXE, TARGET_OPTIONS, TARGET_TIMEOUT, + }, + tasks::report::libfuzzer_report::{test_input, TestInputArgs}, +}; +use anyhow::Result; +use clap::{App, Arg, SubCommand}; +use std::path::PathBuf; + +pub async fn run(args: &clap::ArgMatches<'_>) -> Result<()> { + let common = build_common_config(args, true)?; + + let target_exe = value_t!(args, TARGET_EXE, PathBuf)?; + let target_env = get_cmd_env(CmdType::Target, args)?; + let target_options = get_cmd_arg(CmdType::Target, args); + let input = value_t!(args, "input", PathBuf)?; + let target_timeout = value_t!(args, TARGET_TIMEOUT, u64).ok(); + let check_retry_count = value_t!(args, CHECK_RETRY_COUNT, u64)?; + + let config = TestInputArgs { + target_exe: &target_exe.as_path(), + target_env: &target_env, + target_options: &target_options, + input_url: None, + input: input.as_path(), + job_id: common.job_id, + task_id: common.task_id, + target_timeout, + check_retry_count, + setup_dir: &common.setup_dir, + minimized_stack_depth: None, + }; + + let result = test_input(config).await?; + println!("{}", serde_json::to_string_pretty(&result)?); + Ok(()) +} + +pub fn build_shared_args() -> Vec> { + vec![ + Arg::with_name(TARGET_EXE).takes_value(true).required(true), + Arg::with_name("input").takes_value(true).required(true), + Arg::with_name(TARGET_ENV) + .long(TARGET_ENV) + .takes_value(true) + .multiple(true), + Arg::with_name(TARGET_OPTIONS) + .default_value("{input}") + .long(TARGET_OPTIONS) + .takes_value(true) + .value_delimiter(" ") + .help("Use a quoted string with space separation to denote multiple arguments"), + Arg::with_name(TARGET_TIMEOUT) + .takes_value(true) + .long(TARGET_TIMEOUT), + Arg::with_name(CHECK_RETRY_COUNT) + .takes_value(true) + .long(CHECK_RETRY_COUNT) + .default_value("0"), + ] +} + +pub fn args(name: &'static str) -> App<'static, 'static> { + SubCommand::with_name(name) + .about("test a libfuzzer application with a specific input") + .args(&build_shared_args()) +} diff --git a/src/agent/onefuzz-agent/src/local/mod.rs b/src/agent/onefuzz-agent/src/local/mod.rs index 4ed5c9bdd..f404f30eb 100644 --- a/src/agent/onefuzz-agent/src/local/mod.rs +++ b/src/agent/onefuzz-agent/src/local/mod.rs @@ -11,4 +11,6 @@ pub mod libfuzzer_coverage; pub mod libfuzzer_crash_report; pub mod libfuzzer_fuzz; pub mod libfuzzer_merge; +pub mod libfuzzer_test_input; pub mod radamsa; +pub mod test_input; diff --git a/src/agent/onefuzz-agent/src/local/radamsa.rs b/src/agent/onefuzz-agent/src/local/radamsa.rs index b2a2ffb41..96c339099 100644 --- a/src/agent/onefuzz-agent/src/local/radamsa.rs +++ b/src/agent/onefuzz-agent/src/local/radamsa.rs @@ -17,7 +17,7 @@ use tokio::task::spawn; use uuid::Uuid; pub async fn run(args: &clap::ArgMatches<'_>) -> Result<()> { - let common = build_common_config(args)?; + let common = build_common_config(args, true)?; let fuzz_config = build_fuzz_config(args, common.clone())?; let crash_dir = fuzz_config .crashes diff --git a/src/agent/onefuzz-agent/src/local/test_input.rs b/src/agent/onefuzz-agent/src/local/test_input.rs new file mode 100644 index 000000000..1103a409d --- /dev/null +++ b/src/agent/onefuzz-agent/src/local/test_input.rs @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::{ + local::common::{ + build_common_config, get_cmd_arg, get_cmd_env, CmdType, CHECK_ASAN_LOG, CHECK_RETRY_COUNT, + DISABLE_CHECK_DEBUGGER, TARGET_ENV, TARGET_EXE, TARGET_OPTIONS, TARGET_TIMEOUT, + }, + tasks::report::generic::{test_input, TestInputArgs}, +}; +use anyhow::Result; +use clap::{App, Arg, SubCommand}; +use std::path::PathBuf; + +pub async fn run(args: &clap::ArgMatches<'_>) -> Result<()> { + let common = build_common_config(args, false)?; + + let target_exe = value_t!(args, TARGET_EXE, PathBuf)?; + let target_env = get_cmd_env(CmdType::Target, args)?; + let target_options = get_cmd_arg(CmdType::Target, args); + let input = value_t!(args, "input", PathBuf)?; + let target_timeout = value_t!(args, TARGET_TIMEOUT, u64).ok(); + let check_retry_count = value_t!(args, CHECK_RETRY_COUNT, u64)?; + let check_asan_log = args.is_present(CHECK_ASAN_LOG); + let check_debugger = !args.is_present(DISABLE_CHECK_DEBUGGER); + + let config = TestInputArgs { + target_exe: &target_exe.as_path(), + target_env: &target_env, + target_options: &target_options, + input_url: None, + input: input.as_path(), + job_id: common.job_id, + task_id: common.task_id, + target_timeout, + check_retry_count, + setup_dir: &common.setup_dir, + minimized_stack_depth: None, + check_asan_log, + check_debugger, + }; + + let result = test_input(config).await?; + println!("{}", serde_json::to_string_pretty(&result)?); + Ok(()) +} + +pub fn build_shared_args() -> Vec> { + vec![ + Arg::with_name(TARGET_EXE).takes_value(true).required(true), + Arg::with_name("input").takes_value(true).required(true), + Arg::with_name(TARGET_ENV) + .long(TARGET_ENV) + .takes_value(true) + .multiple(true), + Arg::with_name(TARGET_OPTIONS) + .default_value("{input}") + .long(TARGET_OPTIONS) + .takes_value(true) + .value_delimiter(" ") + .help("Use a quoted string with space separation to denote multiple arguments"), + Arg::with_name(TARGET_TIMEOUT) + .takes_value(true) + .long(TARGET_TIMEOUT), + Arg::with_name(CHECK_RETRY_COUNT) + .takes_value(true) + .long(CHECK_RETRY_COUNT) + .default_value("0"), + Arg::with_name(CHECK_ASAN_LOG) + .takes_value(false) + .long(CHECK_ASAN_LOG), + Arg::with_name(DISABLE_CHECK_DEBUGGER) + .takes_value(false) + .long("disable_check_debugger"), + ] +} + +pub fn args(name: &'static str) -> App<'static, 'static> { + SubCommand::with_name(name) + .about("test an application with a specific input") + .args(&build_shared_args()) +} diff --git a/src/agent/onefuzz-agent/src/tasks/report/crash_report.rs b/src/agent/onefuzz-agent/src/tasks/report/crash_report.rs index 6f3f907c5..127487c6a 100644 --- a/src/agent/onefuzz-agent/src/tasks/report/crash_report.rs +++ b/src/agent/onefuzz-agent/src/tasks/report/crash_report.rs @@ -32,24 +32,27 @@ pub struct CrashReport { pub call_stack: Vec, pub call_stack_sha256: String, - #[serde(default)] + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub minimized_stack: Vec, - #[serde(default)] + #[serde(default, skip_serializing_if = "Option::is_none")] pub minimized_stack_sha256: Option, - #[serde(default)] + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub minimized_stack_function_names: Vec, - #[serde(default)] + #[serde(default, skip_serializing_if = "Option::is_none")] pub minimized_stack_function_names_sha256: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub asan_log: Option, pub task_id: Uuid, pub job_id: Uuid, + #[serde(skip_serializing_if = "Option::is_none")] pub scariness_score: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub scariness_description: Option, } @@ -62,6 +65,7 @@ pub struct NoCrash { pub task_id: Uuid, pub job_id: Uuid, pub tries: u64, + #[serde(skip_serializing_if = "Option::is_none")] pub error: Option, }