diff --git a/casr/src/bin/casr-afl.rs b/casr/src/bin/casr-afl.rs index ec7e3b3d..44514962 100644 --- a/casr/src/bin/casr-afl.rs +++ b/casr/src/bin/casr-afl.rs @@ -157,6 +157,12 @@ fn main() -> Result<()> { } } + let gdb_args = if let Some(argv) = matches.get_many::("ARGS") { + argv.cloned().collect() + } else { + Vec::new() + }; + // Generate reports - fuzzing_crash_triage_pipeline(&matches, &crashes) + fuzzing_crash_triage_pipeline(&matches, &crashes, &gdb_args) } diff --git a/casr/src/bin/casr-gdb.rs b/casr/src/bin/casr-gdb.rs index 1acafe50..40273231 100644 --- a/casr/src/bin/casr-gdb.rs +++ b/casr/src/bin/casr-gdb.rs @@ -65,9 +65,10 @@ fn main() -> Result<()> { .short('t') .long("timeout") .action(ArgAction::Set) + .default_value("0") .value_name("SECONDS") - .help("Timeout (in seconds) for target execution [default: disabled]") - .value_parser(clap::value_parser!(u64).range(1..)) + .help("Timeout (in seconds) for target execution, 0 value means that timeout is disabled") + .value_parser(clap::value_parser!(u64).range(0..)) ) .arg( Arg::new("ignore") diff --git a/casr/src/bin/casr-java.rs b/casr/src/bin/casr-java.rs index 62149551..049bda84 100644 --- a/casr/src/bin/casr-java.rs +++ b/casr/src/bin/casr-java.rs @@ -53,9 +53,10 @@ fn main() -> Result<()> { .short('t') .long("timeout") .action(ArgAction::Set) + .default_value("0") .value_name("SECONDS") - .help("Timeout (in seconds) for target execution [default: disabled]") - .value_parser(clap::value_parser!(u64).range(1..)) + .help("Timeout (in seconds) for target execution, 0 value means that timeout is disabled") + .value_parser(clap::value_parser!(u64).range(0..)) ) .arg( Arg::new("ignore") @@ -149,7 +150,7 @@ fn main() -> Result<()> { report.execution_class = exception; } } else { - // Call sub tool + // Call casr-san return util::call_casr_san(&matches, &argv, "casr-java"); } diff --git a/casr/src/bin/casr-libfuzzer.rs b/casr/src/bin/casr-libfuzzer.rs index 77b061ca..023b5099 100644 --- a/casr/src/bin/casr-libfuzzer.rs +++ b/casr/src/bin/casr-libfuzzer.rs @@ -84,7 +84,7 @@ fn main() -> Result<()> { Arg::new("casr-gdb-args") .long("casr-gdb-args") .action(ArgAction::Set) - .help("Specify casr-gdb target arguments to add casr reports for non-instrumented binary"), + .help("Add \"--casr-gdb-args \'./gdb_fuzz_target \'\" to generate additional crash reports with casr-gdb (e.g., test whether program crashes without sanitizers)"), ) .arg( Arg::new("ARGS") @@ -148,6 +148,12 @@ fn main() -> Result<()> { }) .collect(); + let gdb_args = if let Some(argv) = matches.get_one::("casr-gdb-args") { + shell_words::split(argv)? + } else { + Vec::new() + }; + // Generate reports - fuzzing_crash_triage_pipeline(&matches, &crashes) + fuzzing_crash_triage_pipeline(&matches, &crashes, &gdb_args) } diff --git a/casr/src/bin/casr-python.rs b/casr/src/bin/casr-python.rs index a6502e0f..a9824308 100644 --- a/casr/src/bin/casr-python.rs +++ b/casr/src/bin/casr-python.rs @@ -52,9 +52,10 @@ fn main() -> Result<()> { .short('t') .long("timeout") .action(ArgAction::Set) + .default_value("0") .value_name("SECONDS") - .help("Timeout (in seconds) for target execution [default: disabled]") - .value_parser(clap::value_parser!(u64).range(1..)) + .help("Timeout (in seconds) for target execution, 0 value means that timeout is disabled") + .value_parser(clap::value_parser!(u64).range(0..)) ) .arg( Arg::new("ignore") @@ -152,7 +153,7 @@ fn main() -> Result<()> { } } } else { - // Call sub tool + // Call casr-san return util::call_casr_san(&matches, &argv, "casr-python"); } } else if let Some(report_start) = python_stderr_list @@ -174,7 +175,7 @@ fn main() -> Result<()> { report.execution_class = exception; } } else { - // Call sub tool + // Call casr-san return util::call_casr_san(&matches, &argv, "casr-python"); } diff --git a/casr/src/bin/casr-san.rs b/casr/src/bin/casr-san.rs index c4a591d6..89635da6 100644 --- a/casr/src/bin/casr-san.rs +++ b/casr/src/bin/casr-san.rs @@ -65,9 +65,10 @@ fn main() -> Result<()> { .short('t') .long("timeout") .action(ArgAction::Set) + .default_value("0") .value_name("SECONDS") - .help("Timeout (in seconds) for target execution [default: disabled]") - .value_parser(clap::value_parser!(u64).range(1..)) + .help("Timeout (in seconds) for target execution, 0 value means that timeout is disabled") + .value_parser(clap::value_parser!(u64).range(0..)) ) .arg( Arg::new("ignore") diff --git a/casr/src/bin/casr-ubsan.rs b/casr/src/bin/casr-ubsan.rs index ea07c1a8..1212eff4 100644 --- a/casr/src/bin/casr-ubsan.rs +++ b/casr/src/bin/casr-ubsan.rs @@ -235,9 +235,10 @@ fn main() -> Result<()> { .short('t') .long("timeout") .action(ArgAction::Set) + .default_value("0") .value_name("SECONDS") - .help("Timeout (in seconds) for target execution [default: disabled]") - .value_parser(clap::value_parser!(u64).range(1..)) + .help("Timeout (in seconds) for target execution, 0 value means that timeout is disabled") + .value_parser(clap::value_parser!(u64).range(0..)) ) .arg( Arg::new("input") diff --git a/casr/src/triage.rs b/casr/src/triage.rs index c33c44f3..bebd166f 100644 --- a/casr/src/triage.rs +++ b/casr/src/triage.rs @@ -35,14 +35,7 @@ impl<'a> CrashInfo { /// * `output_dir` - save report to specified directory or use the same directory as crash /// /// * `timeout` - target program timeout (in seconds) - /// - /// * `envs` - environment variables for target - pub fn run_casr>>( - &self, - output_dir: T, - timeout: u64, - envs: &HashMap, - ) -> Result<()> { + pub fn run_casr>>(&self, output_dir: T, timeout: u64) -> Result<()> { let tool = &self.casr_tool; let tool_name = tool.file_name().unwrap().to_str().unwrap(); let mut args: Vec = vec!["-o".to_string()]; @@ -77,7 +70,26 @@ impl<'a> CrashInfo { let mut casr_cmd = Command::new(tool); casr_cmd.args(&args); - casr_cmd.envs(envs); + + // Add envs + if self + .target_args + .iter() + .any(|x| x.contains("-detect_leaks=0")) + { + let asan_options = std::env::var("ASAN_OPTIONS").unwrap_or(String::new()); + casr_cmd.env( + "ASAN_OPTIONS", + if asan_options.is_empty() { + "detect_leaks=0".to_string() + } else { + format!("{asan_options},detect_leaks=0",) + }, + ); + } + if self.casr_tool.ends_with("casr-python") { + casr_cmd.env("LD_PRELOAD", get_atheris_lib()?); + } debug!("{:?}", casr_cmd); @@ -137,36 +149,19 @@ impl<'a> CrashInfo { /// /// * `crashes` - map of crashes, specified as a HashMap, where /// key is crash input file name and value is CrashInfo structure +/// +/// * `gdb_args` - casr-gdb target arguments. If they are empty, casr-gdb won't be launched. pub fn fuzzing_crash_triage_pipeline( matches: &clap::ArgMatches, crashes: &HashMap, + gdb_args: &Vec, ) -> Result<()> { // Get casr-cluster path let casr_cluster = get_path("casr-cluster")?; - let mut envs = HashMap::new(); if crashes.is_empty() { bail!("No crashes found"); } - let crash_info = crashes.values().next().unwrap(); - if crash_info - .target_args - .iter() - .any(|x| x.contains("-detect_leaks=0")) - { - let asan_options = std::env::var("ASAN_OPTIONS").unwrap_or(String::new()); - envs.insert( - "ASAN_OPTIONS".to_string(), - if asan_options.is_empty() { - "detect_leaks=0".to_string() - } else { - format!("{asan_options},detect_leaks=0",) - }, - ); - } - if crash_info.casr_tool.ends_with("casr-python") { - envs.insert("LD_PRELOAD".to_string(), get_atheris_lib()?); - } let output_dir = initialize_dirs(matches)?; // Get timeout @@ -201,7 +196,7 @@ pub fn fuzzing_crash_triage_pipeline( .join( || { crashes.par_iter().try_for_each(|(_, crash)| { - if let Err(e) = crash.run_casr(output_dir.as_path(), timeout, &envs) { + if let Err(e) = crash.run_casr(output_dir.as_path(), timeout) { // Disable util::log_progress *counter.write().unwrap() = total; bail!(e); @@ -217,7 +212,7 @@ pub fn fuzzing_crash_triage_pipeline( // Deduplicate reports. if output_dir.read_dir()?.count() < 2 { info!("There are less than 2 CASR reports, nothing to deduplicate."); - return summarize_results(matches, crashes); + return summarize_results(matches, crashes, gdb_args); } info!("Deduplicating CASR reports..."); let casr_cluster_d = Command::new(&casr_cluster) @@ -248,7 +243,7 @@ pub fn fuzzing_crash_triage_pipeline( < 2 { info!("There are less than 2 CASR reports, nothing to cluster."); - return summarize_results(matches, crashes); + return summarize_results(matches, crashes, gdb_args); } info!("Clustering CASR reports..."); let casr_cluster_c = Command::new(&casr_cluster) @@ -276,7 +271,7 @@ pub fn fuzzing_crash_triage_pipeline( } } - summarize_results(matches, crashes) + summarize_results(matches, crashes, gdb_args) } /// Copy crashes next to reports and print summary. @@ -288,35 +283,18 @@ pub fn fuzzing_crash_triage_pipeline( /// * `matches` - tool arguments /// /// * `crashes` - set of crashes, specified as a CrashInfo structure +/// +/// * `gdb_args` - casr-gdb target arguments. If they are empty, casr-gdb won't be launched. fn summarize_results( matches: &clap::ArgMatches, crashes: &HashMap, + gdb_args: &Vec, ) -> Result<()> { // Get output dir let dir = matches.get_one::("output").unwrap(); // Copy crashes next to reports copy_crashes(dir, crashes)?; - // Get casr-gdb args - let tool = std::env::current_exe()? - .file_name() - .unwrap() - .to_str() - .unwrap() - .to_string(); - let gdb_args = if tool.eq("casr-libfuzzer") { - // casr-libfuzzer case - if let Some(argv) = matches.get_one::("casr-gdb-args") { - shell_words::split(argv)? - } else { - Vec::new() - } - } else if let Some(argv) = matches.get_many::("ARGS") { - // casr-afl case - argv.cloned().collect() - } else { - Vec::new() - }; // Get timeout let timeout = if let Some(timeout) = matches.get_one::("timeout") { *timeout @@ -337,7 +315,7 @@ fn summarize_results( .into_iter() .filter_entry(|e| { let name = e.file_name().to_str().unwrap(); - !name.eq("clerr") && !name.eq("oom") && !name.eq("timeout") + !name.eq("oom") && !name.eq("timeout") }) .flatten() .map(|e| e.into_path()) @@ -345,43 +323,45 @@ fn summarize_results( .filter(|e| e.extension().is_none() || e.extension().unwrap() != "casrep") .filter(|e| !Path::new(format!("{}.gdb.casrep", e.display()).as_str()).exists()) .collect(); - let num_of_threads = jobs.min(crashes.len()).max(1) + 1; - info!("casr-gdb: adding crash reports..."); - info!("Using {} threads", num_of_threads - 1); - let counter = RwLock::new(0_usize); - let total = crashes.len(); - let custom_pool = rayon::ThreadPoolBuilder::new() - .num_threads(num_of_threads) - .build() - .unwrap(); - let at_index = gdb_args - .iter() - .skip(1) - .position(|s| s.contains("@@")) - .map(|x| x + 1); - custom_pool - .join( - || { - crashes.par_iter().try_for_each(|crash| { - if let Err(e) = (CrashInfo { - path: crash.to_path_buf(), - target_args: gdb_args.clone(), - at_index, - casr_tool: casr_gdb.clone(), + if !crashes.is_empty() { + let num_of_threads = jobs.min(crashes.len()) + 1; + info!("casr-gdb: adding crash reports..."); + info!("Using {} threads", num_of_threads - 1); + let counter = RwLock::new(0_usize); + let total = crashes.len(); + let custom_pool = rayon::ThreadPoolBuilder::new() + .num_threads(num_of_threads) + .build() + .unwrap(); + let at_index = gdb_args + .iter() + .skip(1) + .position(|s| s.contains("@@")) + .map(|x| x + 1); + custom_pool + .join( + || { + crashes.par_iter().try_for_each(|crash| { + if let Err(e) = (CrashInfo { + path: crash.to_path_buf(), + target_args: gdb_args.clone(), + at_index, + casr_tool: casr_gdb.clone(), + }) + .run_casr(None, timeout) + { + // Disable util::log_progress + *counter.write().unwrap() = total; + bail!(e); + }; + *counter.write().unwrap() += 1; + Ok::<(), anyhow::Error>(()) }) - .run_casr(None, timeout, &HashMap::new()) - { - // Disable util::log_progress - *counter.write().unwrap() = total; - bail!(e); - }; - *counter.write().unwrap() += 1; - Ok::<(), anyhow::Error>(()) - }) - }, - || log_progress(&counter, total), - ) - .0?; + }, + || log_progress(&counter, total), + ) + .0?; + } } let casr_cli = get_path("casr-cli")?; diff --git a/docs/usage.md b/docs/usage.md index 1afab91b..2902a56d 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -25,15 +25,16 @@ Create CASR reports (.casrep) from gdb execution [ARGS]... Add "-- ./binary " to run executable Options: - -o, --output Path to save report. Path can be a directory, then report name is - generated - --stdout Print CASR report to stdout - --stdin Stdin file for program - -t, --timeout Timeout (in seconds) for target execution [default: disabled] - --ignore File with regular expressions for functions and file paths that - should be ignored - -h, --help Print help - -V, --version Print version + -o, --output Path to save report. Path can be a directory, then report name + is generated + --stdout Print CASR report to stdout + --stdin Stdin file for program + -t, --timeout Timeout (in seconds) for target execution, 0 value means that + timeout is disabled [default: 0] + --ignore File with regular expressions for functions and file paths that + should be ignored + -h, --help Print help + -V, --version Print version Example: @@ -49,15 +50,16 @@ Create CASR reports (.casrep) from AddressSanitizer reports [ARGS]... Add "-- ./binary " to run executable Options: - -o, --output Path to save report. Path can be a directory, then report name is - generated - --stdout Print CASR report to stdout - --stdin Stdin file for program - -t, --timeout Timeout (in seconds) for target execution [default: disabled] - --ignore File with regular expressions for functions and file paths that - should be ignored - -h, --help Print help - -V, --version Print version + -o, --output Path to save report. Path can be a directory, then report name + is generated + --stdout Print CASR report to stdout + --stdin Stdin file for program + -t, --timeout Timeout (in seconds) for target execution, 0 value means that + timeout is disabled [default: 0] + --ignore File with regular expressions for functions and file paths that + should be ignored + -h, --help Print help + -V, --version Print version Compile binary with ASAN: @@ -84,8 +86,8 @@ Triage errors found by UndefinedBehaviorSanitizer and create CASR reports (.casr debug] -j, --jobs Number of parallel jobs for generating CASR reports [default: half of cpu cores] - -t, --timeout Timeout (in seconds) for target execution [default: - disabled] + -t, --timeout Timeout (in seconds) for target execution, 0 value means + that timeout is disabled [default: 0] -i, --input ... Target input directory list -o, --output Output directory with triaged reports -h, --help Print help @@ -120,7 +122,8 @@ Create CASR reports (.casrep) from python reports is generated --stdout Print CASR report to stdout --stdin Stdin file for program - -t, --timeout Timeout (in seconds) for target execution [default: disabled] + -t, --timeout Timeout (in seconds) for target execution, 0 value means that + timeout is disabled [default: 0] --ignore File with regular expressions for functions and file paths that should be ignored -h, --help Print help @@ -144,7 +147,8 @@ Create CASR reports (.casrep) from java reports is generated --stdout Print CASR report to stdout --stdin Stdin file for program - -t, --timeout Timeout (in seconds) for target execution [default: disabled] + -t, --timeout Timeout (in seconds) for target execution, 0 value means that + timeout is disabled [default: 0] --ignore File with regular expressions for functions and file paths that should be ignored -h, --help Print help @@ -464,8 +468,9 @@ Triage crashes found by libFuzzer based fuzzer (C/C++/go-fuzz/Atheris/Jazzer) --no-cluster Do not cluster CASR reports --casr-gdb-args - Specify casr-gdb target arguments to add casr reports for non-instrumented - binary + Add "--casr-gdb-args './gdb_fuzz_target '" to generate additional + crash reports with casr-gdb (e.g., test whether program crashes without + sanitizers) -h, --help Print help -V, --version @@ -481,6 +486,11 @@ libFuzzer example: $ casr-libfuzzer -i casr/tests/casr_tests/casrep/libfuzzer_crashes_xlnt -o casr/tests/tmp_tests_casr/casr_libfuzzer_out -- casr/tests/casr_tests/bin/load_fuzzer +You may also run `casr-libfuzzer` with additional report generation for uninstrumented +binary with `casr-gdb`: + + $ casr-libfuzzer -i casr/tests/casr_tests/casrep/libfuzzer_crashes_xlnt -o casr/tests/tmp_tests_casr/casr_libfuzzer_out --casr-gdb-args 'casr/tests/casr_tests/bin/load_sydr @@' -- casr/tests/casr_tests/bin/load_fuzzer + Atheris example: $ unzip casr/tests/casr_tests/python/ruamel.zip