diff --git a/src/agent/onefuzz-agent/src/local/cmd.rs b/src/agent/onefuzz-agent/src/local/cmd.rs index b496e0ada..04f90d02c 100644 --- a/src/agent/onefuzz-agent/src/local/cmd.rs +++ b/src/agent/onefuzz-agent/src/local/cmd.rs @@ -10,7 +10,7 @@ use tokio::time::timeout; use crate::local::{ common::add_common_config, generic_analysis, generic_crash_report, generic_generator, libfuzzer, libfuzzer_coverage, libfuzzer_crash_report, libfuzzer_fuzz, libfuzzer_merge, - libfuzzer_test_input, radamsa, test_input, + libfuzzer_regression, libfuzzer_test_input, radamsa, test_input, }; const RADAMSA: &str = "radamsa"; @@ -20,6 +20,7 @@ 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 LIBFUZZER_REGRESSION: &str = "libfuzzer-regression"; const GENERIC_CRASH_REPORT: &str = "crash-report"; const GENERIC_GENERATOR: &str = "generator"; const GENERIC_ANALYSIS: &str = "analysis"; @@ -43,6 +44,7 @@ pub async fn run(args: &clap::ArgMatches<'_>) -> Result<()> { (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, + (LIBFUZZER_REGRESSION, Some(sub)) => libfuzzer_regression::run(sub).await, _ => { anyhow::bail!("missing subcommand\nUSAGE: {}", args.usage()); } @@ -78,6 +80,9 @@ pub fn args(name: &str) -> App<'static, 'static> { LIBFUZZER_COVERAGE, ))) .subcommand(add_common_config(libfuzzer_merge::args(LIBFUZZER_MERGE))) + .subcommand(add_common_config(libfuzzer_regression::args( + LIBFUZZER_REGRESSION, + ))) .subcommand(add_common_config(libfuzzer_crash_report::args( LIBFUZZER_CRASH_REPORT, ))) diff --git a/src/agent/onefuzz-agent/src/local/common.rs b/src/agent/onefuzz-agent/src/local/common.rs index f1e371e72..4b7b87f12 100644 --- a/src/agent/onefuzz-agent/src/local/common.rs +++ b/src/agent/onefuzz-agent/src/local/common.rs @@ -33,6 +33,7 @@ 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 REGRESSION_REPORTS_DIR: &str = "regression_reports_dir"; pub const TARGET_EXE: &str = "target_exe"; pub const TARGET_ENV: &str = "target_env"; diff --git a/src/agent/onefuzz-agent/src/local/libfuzzer.rs b/src/agent/onefuzz-agent/src/local/libfuzzer.rs index cb3aaf48a..510b9f08b 100644 --- a/src/agent/onefuzz-agent/src/local/libfuzzer.rs +++ b/src/agent/onefuzz-agent/src/local/libfuzzer.rs @@ -5,17 +5,20 @@ use crate::{ local::{ common::{ build_common_config, wait_for_dir, DirectoryMonitorQueue, ANALYZER_EXE, COVERAGE_DIR, - UNIQUE_REPORTS_DIR, + REGRESSION_REPORTS_DIR, UNIQUE_REPORTS_DIR, }, generic_analysis::{build_analysis_config, build_shared_args as build_analysis_args}, libfuzzer_coverage::{build_coverage_config, build_shared_args as build_coverage_args}, libfuzzer_crash_report::{build_report_config, build_shared_args as build_crash_args}, libfuzzer_fuzz::{build_fuzz_config, build_shared_args as build_fuzz_args}, + libfuzzer_regression::{ + build_regression_config, build_shared_args as build_regression_args, + }, }, tasks::{ analysis::generic::run as run_analysis, config::CommonConfig, coverage::libfuzzer_coverage::CoverageTask, fuzz::libfuzzer_fuzz::LibFuzzerFuzzTask, - report::libfuzzer_report::ReportTask, + regression::libfuzzer::LibFuzzerRegressionTask, report::libfuzzer_report::ReportTask, }, }; use anyhow::Result; @@ -87,7 +90,7 @@ pub async fn run(args: &clap::ArgMatches<'_>) -> Result<()> { Some(analysis_input_monitor.queue_client), CommonConfig { task_id: Uuid::new_v4(), - ..common + ..common.clone() }, )?; let analysis_task = spawn(async move { run_analysis(analysis_config).await }); @@ -96,6 +99,19 @@ pub async fn run(args: &clap::ArgMatches<'_>) -> Result<()> { task_handles.push(analysis_input_monitor.handle); } + if args.is_present(REGRESSION_REPORTS_DIR) { + let regression_config = build_regression_config( + args, + CommonConfig { + task_id: Uuid::new_v4(), + ..common + }, + )?; + let regression = LibFuzzerRegressionTask::new(regression_config); + let regression_task = spawn(async move { regression.run().await }); + task_handles.push(regression_task); + } + try_wait_all_join_handles(task_handles).await?; Ok(()) @@ -110,6 +126,7 @@ pub fn args(name: &'static str) -> App<'static, 'static> { build_crash_args(), build_analysis_args(false), build_coverage_args(true), + build_regression_args(false), ] { for arg in args { if used.contains(arg.b.name) { diff --git a/src/agent/onefuzz-agent/src/local/libfuzzer_regression.rs b/src/agent/onefuzz-agent/src/local/libfuzzer_regression.rs new file mode 100644 index 000000000..11d22d6d2 --- /dev/null +++ b/src/agent/onefuzz-agent/src/local/libfuzzer_regression.rs @@ -0,0 +1,138 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::{ + local::common::{ + build_common_config, get_cmd_arg, get_cmd_env, get_cmd_exe, get_synced_dir, CmdType, + CHECK_FUZZER_HELP, CHECK_RETRY_COUNT, COVERAGE_DIR, CRASHES_DIR, NO_REPRO_DIR, + REGRESSION_REPORTS_DIR, REPORTS_DIR, TARGET_ENV, TARGET_EXE, TARGET_OPTIONS, + TARGET_TIMEOUT, UNIQUE_REPORTS_DIR, + }, + tasks::{ + config::CommonConfig, + regression::libfuzzer::{Config, LibFuzzerRegressionTask}, + }, +}; +use anyhow::Result; +use clap::{App, Arg, SubCommand}; + +const REPORT_NAMES: &str = "report_names"; + +pub fn build_regression_config( + args: &clap::ArgMatches<'_>, + common: CommonConfig, +) -> Result { + let target_exe = get_cmd_exe(CmdType::Target, args)?.into(); + let target_env = get_cmd_env(CmdType::Target, args)?; + let target_options = get_cmd_arg(CmdType::Target, args); + let target_timeout = value_t!(args, TARGET_TIMEOUT, u64).ok(); + let crashes = get_synced_dir(CRASHES_DIR, common.job_id, common.task_id, args)?; + let regression_reports = + get_synced_dir(REGRESSION_REPORTS_DIR, common.job_id, common.task_id, args)?; + let check_retry_count = value_t!(args, CHECK_RETRY_COUNT, u64)?; + + let reports = get_synced_dir(REPORTS_DIR, common.job_id, common.task_id, args).ok(); + let no_repro = get_synced_dir(NO_REPRO_DIR, common.job_id, common.task_id, args).ok(); + let unique_reports = + get_synced_dir(UNIQUE_REPORTS_DIR, common.job_id, common.task_id, args).ok(); + + let report_list = if args.is_present(REPORT_NAMES) { + Some(values_t!(args, REPORT_NAMES, String)?) + } else { + None + }; + + let check_fuzzer_help = args.is_present(CHECK_FUZZER_HELP); + + let config = Config { + target_exe, + target_env, + target_options, + target_timeout, + check_fuzzer_help, + check_retry_count, + crashes, + regression_reports, + reports, + no_repro, + unique_reports, + readonly_inputs: None, + report_list, + minimized_stack_depth: None, + common, + }; + Ok(config) +} + +pub async fn run(args: &clap::ArgMatches<'_>) -> Result<()> { + let common = build_common_config(args, true)?; + let config = build_regression_config(args, common)?; + LibFuzzerRegressionTask::new(config).run().await +} + +pub fn build_shared_args(local_job: bool) -> Vec> { + let mut args = vec![ + Arg::with_name(TARGET_EXE) + .long(TARGET_EXE) + .takes_value(true) + .required(true), + Arg::with_name(TARGET_ENV) + .long(TARGET_ENV) + .takes_value(true) + .multiple(true), + Arg::with_name(TARGET_OPTIONS) + .long(TARGET_OPTIONS) + .takes_value(true) + .value_delimiter(" ") + .help("Use a quoted string with space separation to denote multiple arguments"), + Arg::with_name(COVERAGE_DIR) + .takes_value(true) + .required(!local_job) + .long(COVERAGE_DIR), + Arg::with_name(CHECK_FUZZER_HELP) + .takes_value(false) + .long(CHECK_FUZZER_HELP), + Arg::with_name(TARGET_TIMEOUT) + .takes_value(true) + .long(TARGET_TIMEOUT), + Arg::with_name(CRASHES_DIR) + .long(CRASHES_DIR) + .takes_value(true) + .required(true), + Arg::with_name(REGRESSION_REPORTS_DIR) + .long(REGRESSION_REPORTS_DIR) + .takes_value(true) + .required(local_job), + Arg::with_name(REPORTS_DIR) + .long(REPORTS_DIR) + .takes_value(true) + .required(false), + Arg::with_name(NO_REPRO_DIR) + .long(NO_REPRO_DIR) + .takes_value(true) + .required(false), + Arg::with_name(UNIQUE_REPORTS_DIR) + .long(UNIQUE_REPORTS_DIR) + .takes_value(true) + .required(true), + Arg::with_name(CHECK_RETRY_COUNT) + .takes_value(true) + .long(CHECK_RETRY_COUNT) + .default_value("0"), + ]; + if local_job { + args.push( + Arg::with_name(REPORT_NAMES) + .long(REPORT_NAMES) + .takes_value(true) + .multiple(true), + ) + } + args +} + +pub fn args(name: &'static str) -> App<'static, 'static> { + SubCommand::with_name(name) + .about("execute a local-only libfuzzer regression task") + .args(&build_shared_args(false)) +} diff --git a/src/agent/onefuzz-agent/src/local/mod.rs b/src/agent/onefuzz-agent/src/local/mod.rs index f404f30eb..f1e7e555a 100644 --- a/src/agent/onefuzz-agent/src/local/mod.rs +++ b/src/agent/onefuzz-agent/src/local/mod.rs @@ -11,6 +11,7 @@ pub mod libfuzzer_coverage; pub mod libfuzzer_crash_report; pub mod libfuzzer_fuzz; pub mod libfuzzer_merge; +pub mod libfuzzer_regression; pub mod libfuzzer_test_input; pub mod radamsa; pub mod test_input; diff --git a/src/agent/onefuzz-agent/src/tasks/regression/common.rs b/src/agent/onefuzz-agent/src/tasks/regression/common.rs index 49b1c4dee..4cc124afc 100644 --- a/src/agent/onefuzz-agent/src/tasks/regression/common.rs +++ b/src/agent/onefuzz-agent/src/tasks/regression/common.rs @@ -32,7 +32,9 @@ pub async fn run( readonly_inputs: &Option, handler: &impl RegressionHandler, ) -> Result<()> { - info!("Starting generic regression task"); + info!("starting regression task"); + regression_reports.init().await?; + handle_crash_reports( handler, crashes, @@ -41,7 +43,8 @@ pub async fn run( ®ression_reports, &heartbeat_client, ) - .await?; + .await + .context("handling crash reports")?; if let Some(readonly_inputs) = &readonly_inputs { handle_inputs( @@ -50,9 +53,11 @@ pub async fn run( ®ression_reports, &heartbeat_client, ) - .await?; + .await + .context("handling inputs")?; } + info!("regression task stopped"); Ok(()) } diff --git a/src/agent/onefuzz-agent/src/tasks/regression/libfuzzer.rs b/src/agent/onefuzz-agent/src/tasks/regression/libfuzzer.rs index e6cb26bb1..e6bbb8838 100644 --- a/src/agent/onefuzz-agent/src/tasks/regression/libfuzzer.rs +++ b/src/agent/onefuzz-agent/src/tasks/regression/libfuzzer.rs @@ -7,7 +7,7 @@ use crate::tasks::{ utils::default_bool_true, }; -use anyhow::Result; +use anyhow::{Context, Result}; use reqwest::Url; use super::common::{self, RegressionHandler}; @@ -78,8 +78,6 @@ impl LibFuzzerRegressionTask { } pub async fn run(&self) -> Result<()> { - info!("Starting libfuzzer regression task"); - let mut report_dirs = vec![]; for dir in &[ &self.config.reports, @@ -101,7 +99,8 @@ impl LibFuzzerRegressionTask { &self.config.readonly_inputs, self, ) - .await?; + .await + .context("libfuzzer regression")?; Ok(()) } }