From ccc88f75a69b20903aaa70b261b8e64b0ab72ccf Mon Sep 17 00:00:00 2001 From: Avgor46 Date: Wed, 13 Sep 2023 16:50:28 +0300 Subject: [PATCH] Fixes --- casr/src/analysis.rs | 136 ++++++++++++++++++++------------- casr/src/bin/casr-afl.rs | 31 ++++---- casr/src/bin/casr-cli.rs | 18 ++--- casr/src/bin/casr-libfuzzer.rs | 61 +++++++-------- casr/tests/tests.rs | 9 +-- 5 files changed, 141 insertions(+), 114 deletions(-) diff --git a/casr/src/analysis.rs b/casr/src/analysis.rs index dfc18778..cf2aa6a6 100644 --- a/casr/src/analysis.rs +++ b/casr/src/analysis.rs @@ -1,7 +1,9 @@ +//! Post-fuzzing analysis module use crate::util::{get_atheris_lib, get_path, initialize_dirs, log_progress}; use std::collections::HashMap; use std::fs; +use std::os::fd::AsFd; use std::path::{Path, PathBuf}; use std::process::Command; use std::sync::RwLock; @@ -21,7 +23,7 @@ pub struct CrashInfo { /// Input file argument index starting from argv\[1\], None for stdin. pub at_index: Option, /// ASAN. - pub is_asan: bool, + pub casr_tool: PathBuf, } impl<'a> CrashInfo { @@ -38,21 +40,22 @@ impl<'a> CrashInfo { /// * `envs` - environment variables for target pub fn run_casr>>( &self, - tool: &PathBuf, output_dir: T, timeout: u64, envs: &HashMap, ) -> Result<()> { + let tool = &self.casr_tool; + let tool_name = tool.file_name().unwrap().to_str().unwrap(); let mut args: Vec = vec!["-o".to_string()]; let (report_path, output_dir) = if let Some(out) = output_dir.into() { (out.join(self.path.file_name().unwrap()), out) } else { (self.path.clone(), self.path.parent().unwrap()) }; - if self.is_asan { - args.push(format!("{}.casrep", report_path.display())); - } else { + if tool_name.eq("casr-gdb") { args.push(format!("{}.gdb.casrep", report_path.display())); + } else { + args.push(format!("{}.casrep", report_path.display())); } args.push("--".to_string()); @@ -61,8 +64,8 @@ impl<'a> CrashInfo { let input = args[at_index + 3].replace("@@", self.path.to_str().unwrap()); args[at_index + 3] = input; } - let tool_name = tool.file_name().unwrap().to_str().unwrap(); if tool_name.ends_with("casr-python") { + // Put python3 after -- args.insert(3, "python3".to_string()); } if self.at_index.is_none() { @@ -128,40 +131,59 @@ impl<'a> CrashInfo { } } -/// Perform analysis +/// Perform crash analysis pipeline: Create, deduplicate and cluster CASR reports. /// /// # Arguments /// -/// `matches` - tool arguments +/// * `matches` - tool arguments /// -/// `crashes` - set of crashes, specified as a CrashInfo structure -/// -/// `tool` - tool that generates reports -/// -/// `gdb_argv` - arguments for casr-gdb -pub fn generate_reports( +/// * `crashes` - map of crashes, specified as a HashMap, where +/// key is crash input file name and value is CrashInfo structure +pub fn handle_crashes( matches: &clap::ArgMatches, crashes: &HashMap, - tool: &str, - gdb_argv: &Vec, ) -> Result<()> { - // Get tool paths - let casr_tool = get_path(tool)?; - let casr_gdb = get_path("casr-gdb")?; + // Get casr-cluster path let casr_cluster = get_path("casr-cluster")?; - // Get timeout - let timeout = if let Some(timeout) = matches.get_one::("timeout") { - *timeout + + let mut envs = HashMap::new(); + if crashes.is_empty() { + bail!("No crashes found"); } else { - 0 + let (_, crash_info) = crashes.iter().next().unwrap(); + if crash_info + .target_args + .iter() + .any(|x| x.contains("-detect_leaks=0")) + { + envs.insert( + "ASAN_OPTIONS".to_string(), + format!( + "{},detect_leaks=0", + std::env::var("ASAN_OPTIONS").unwrap_or(String::new()) + ), + ); + } + } + let envs = if crashes + .iter() + .next() + .unwrap() + .1 + .casr_tool + .ends_with("casr-python") + { + HashMap::from([("LD_PRELOAD".to_string(), get_atheris_lib()?)]) + } else { + HashMap::new() }; let output_dir = initialize_dirs(matches)?; - - let envs = if tool.eq("casr-python") { - HashMap::from([("LD_PRELOAD".to_string(), get_atheris_lib()?)]) + // Get timeout + let timeout = if let Some(timeout) = matches.get_one::("timeout") { + *timeout } else { - HashMap::new() + 0 }; // Get number of threads @@ -185,8 +207,7 @@ pub fn generate_reports( .join( || { crashes.par_iter().try_for_each(|(_, crash)| { - let tool = if crash.is_asan { &casr_tool } else { &casr_gdb }; - if let Err(e) = crash.run_casr(tool, output_dir.as_path(), timeout, &envs) { + if let Err(e) = crash.run_casr(output_dir.as_path(), timeout, &envs) { // Disable util::log_progress *counter.write().unwrap() = total; bail!(e); @@ -202,7 +223,7 @@ pub fn generate_reports( // Deduplicate reports. if output_dir.read_dir()?.count() < 2 { info!("There are less than 2 CASR reports, nothing to deduplicate."); - return summarize_results(output_dir, crashes, gdb_argv, num_of_threads, timeout); + return summarize_results(matches, crashes); } info!("Deduplicating CASR reports..."); let casr_cluster_d = Command::new(&casr_cluster) @@ -233,7 +254,7 @@ pub fn generate_reports( < 2 { info!("There are less than 2 CASR reports, nothing to cluster."); - return summarize_results(output_dir, crashes, gdb_argv, num_of_threads, timeout); + return summarize_results(matches, crashes); } info!("Clustering CASR reports..."); let casr_cluster_c = Command::new(&casr_cluster) @@ -261,33 +282,48 @@ pub fn generate_reports( } } - summarize_results(output_dir, crashes, gdb_argv, num_of_threads, timeout) + summarize_results(matches, crashes) } /// Copy crashes next to reports and print summary. /// Run casr-gdb on uninstrumented binary if specified in ARGS. +/// Print analysis statistic. /// /// # Arguments /// -/// * `dir` - directory with casr reports -/// -/// * `crashes` - crashes info -/// -/// * `gdb_args` - run casr-gdb on uninstrumented binary if specified +/// * `matches` - tool arguments /// -/// * `jobs` - number of threads for casr-gdb reports generation -/// -/// * `timeout` - target program timeout +/// * `crashes` - set of crashes, specified as a CrashInfo structure fn summarize_results( - dir: &Path, + matches: &clap::ArgMatches, crashes: &HashMap, - gdb_args: &Vec, - jobs: usize, - timeout: u64, ) -> 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 gdb_args = if let Some(argv) = matches.get_one::("casr-gdb-args") { + argv.split(' ') + .map(|x| x.to_string()) + .collect::>() + } else { + Vec::new() + }; + // Get timeout + let timeout = if let Some(timeout) = matches.get_one::("timeout") { + *timeout + } else { + 0 + }; + // Get number of threads + let jobs = if let Some(jobs) = matches.get_one::("jobs") { + *jobs as usize + } else { + std::cmp::max(1, num_cpus::get() / 2) + }; + if !gdb_args.is_empty() { let casr_gdb = get_path("casr-gdb")?; // Run casr-gdb on uninstrumented binary. @@ -326,14 +362,10 @@ fn summarize_results( path: crash.to_path_buf(), target_args: gdb_args.clone(), at_index, - is_asan: false, + casr_tool: casr_gdb.clone(), }) - .run_casr( - &casr_gdb, - None, - timeout, - &HashMap::new(), - ) { + .run_casr(None, timeout, &HashMap::new()) + { // Disable util::log_progress *counter.write().unwrap() = total; bail!(e); @@ -353,7 +385,7 @@ fn summarize_results( let status = Command::new(casr_cli) .arg(dir) .stderr(std::process::Stdio::inherit()) - .stdout(std::process::Stdio::inherit()) + .stdout(std::io::stderr().as_fd().try_clone_to_owned()?) .status() .with_context(|| "Couldn't launch casr-cli".to_string())?; diff --git a/casr/src/bin/casr-afl.rs b/casr/src/bin/casr-afl.rs index fc9e68d8..a9ee9e7b 100644 --- a/casr/src/bin/casr-afl.rs +++ b/casr/src/bin/casr-afl.rs @@ -1,4 +1,4 @@ -use casr::analysis::{generate_reports, CrashInfo}; +use casr::analysis::{handle_crashes, CrashInfo}; use casr::util; use anyhow::Result; @@ -82,24 +82,18 @@ fn main() -> Result<()> { .help("Do not cluster CASR reports") ) .arg( - Arg::new("ARGS") + Arg::new("casr-gdb-args") + .long("casr-gdb-args") .action(ArgAction::Set) - .required(false) - .num_args(1..) - .last(true) - .help("Add \"-- ./gdb_fuzz_target \" to generate additional crash reports with casr-gdb (e.g., test whether program crashes without sanitizers)"), + .help("Specify casr-gdb target arguments to generate casr reports for uninstrumented binary"), ) .get_matches(); // Init log. util::initialize_logging(&matches); - // Get optional gdb fuzz target args. - let gdb_argv: Vec = if let Some(argvs) = matches.get_many::("ARGS") { - argvs.cloned().collect() - } else { - Vec::new() - }; + let casr_san = util::get_path("casr-san")?; + let casr_gdb = util::get_path("casr-gdb")?; // Get all crashes. let mut crashes: HashMap = HashMap::new(); @@ -110,7 +104,10 @@ fn main() -> Result<()> { } // Get crashes from one node. - let mut crash_info = CrashInfo::default(); + let mut crash_info = casr::analysis::CrashInfo { + casr_tool: casr_gdb.clone(), + ..Default::default() + }; let cmdline_path = path.join("cmdline"); if let Ok(cmdline) = fs::read_to_string(&cmdline_path) { crash_info.target_args = cmdline.split_whitespace().map(|s| s.to_string()).collect(); @@ -123,7 +120,11 @@ fn main() -> Result<()> { if let Some(target) = crash_info.target_args.first() { match util::symbols_list(Path::new(target)) { - Ok(list) => crash_info.is_asan = list.contains("__asan"), + Ok(list) => { + if list.contains("__asan") { + crash_info.casr_tool = casr_san.clone() + } + } Err(e) => { error!("{e}"); continue; @@ -155,5 +156,5 @@ fn main() -> Result<()> { } // Generate reports - generate_reports(&matches, &crashes, "casr-san", &gdb_argv) + handle_crashes(&matches, &crashes) } diff --git a/casr/src/bin/casr-cli.rs b/casr/src/bin/casr-cli.rs index bd6c43b4..b921dda8 100644 --- a/casr/src/bin/casr-cli.rs +++ b/casr/src/bin/casr-cli.rs @@ -906,23 +906,23 @@ fn print_summary(dir: &Path, unique_crash_line: bool) { continue; } - eprintln!("==> <{}>", filename.magenta()); + println!("==> <{}>", filename.magenta()); for info in cluster_hash.values() { if ubsan { // /path/to/report.casrep: Description: crashline (path:line:column) - eprintln!("{}: {}", info.0.last().unwrap(), info.0[0]); + println!("{}: {}", info.0.last().unwrap(), info.0[0]); continue; } // Crash: /path/to/input or /path/to/report.casrep - eprintln!("{}: {}", "Crash".green(), info.0.last().unwrap()); + println!("{}: {}", "Crash".green(), info.0.last().unwrap()); // casrep: SeverityType: Description: crashline (path:line:column) or /path/to/report.casrep - eprintln!(" {}", info.0[0]); + println!(" {}", info.0[0]); if info.0.len() == 3 { // gdb.casrep: SeverityType: Description: crashline (path:line:column) or /path/to/report.casrep - eprintln!(" {}", info.0[1]); + println!(" {}", info.0[1]); } // Number of crashes with the same hash - eprintln!(" Similar crashes: {}", info.1); + println!(" Similar crashes: {}", info.1); } let mut classes = String::new(); cluster_classes.iter().for_each(|(class, number)| { @@ -933,7 +933,7 @@ fn print_summary(dir: &Path, unique_crash_line: bool) { ); }); if !ubsan { - eprintln!("Cluster summary ->{classes}"); + println!("Cluster summary ->{classes}"); } } let mut classes = String::new(); @@ -941,9 +941,9 @@ fn print_summary(dir: &Path, unique_crash_line: bool) { .iter() .for_each(|(class, number)| classes.push_str(format!(" {class}: {number}").as_str())); if classes.is_empty() { - eprintln!("{} -> {}", "SUMMARY".magenta(), "No crashes found".red()); + println!("{} -> {}", "SUMMARY".magenta(), "No crashes found".red()); } else { - eprintln!("{} ->{}", "SUMMARY".magenta(), classes); + println!("{} ->{}", "SUMMARY".magenta(), classes); } } diff --git a/casr/src/bin/casr-libfuzzer.rs b/casr/src/bin/casr-libfuzzer.rs index 3ec54487..190dc092 100644 --- a/casr/src/bin/casr-libfuzzer.rs +++ b/casr/src/bin/casr-libfuzzer.rs @@ -1,4 +1,4 @@ -use casr::analysis::{generate_reports, CrashInfo}; +use casr::analysis::{handle_crashes, CrashInfo}; use casr::util; use anyhow::{bail, Result}; @@ -106,35 +106,18 @@ fn main() -> Result<()> { let input_dir = matches.get_one::("input").unwrap().as_path(); // Get fuzz target args. - let argv: Vec<&str> = if let Some(argvs) = matches.get_many::("ARGS") { + let mut argv: Vec<&str> = if let Some(argvs) = matches.get_many::("ARGS") { argvs.map(|v| v.as_str()).collect() } else { bail!("Invalid fuzz target arguments"); }; - let at_index = argv - .iter() - .skip(1) - .position(|s| s.contains("@@")) - .map(|x| x + 1); + let at_index = if let Some(idx) = argv.iter().skip(1).position(|s| s.contains("@@")) { + idx + 1 + } else { + argv.push("@@"); + argv.len() - 1 + }; - // Get all crashes. - let crashes: HashMap = fs::read_dir(input_dir)? - .flatten() - .map(|p| p.path()) - .filter(|p| p.is_file()) - .map(|p| { - ( - p.file_name().unwrap().to_str().unwrap().to_string(), - CrashInfo { - path: p, - target_args: argv.iter().map(|x| x.to_string()).collect(), - at_index, - is_asan: true, - }, - ) - }) - .filter(|(fname, _)| fname.starts_with("crash-") || fname.starts_with("leak-")) - .collect(); let tool = if argv[0].ends_with(".py") { "casr-python" } else if argv[0].ends_with("jazzer") || argv[0].ends_with("java") { @@ -150,15 +133,27 @@ fn main() -> Result<()> { "casr-gdb" } }; + let tool = util::get_path(tool)?; - let gdb_argv = if let Some(argv) = matches.get_one::("casr-gdb-args") { - argv.split(' ') - .map(|x| x.to_string()) - .collect::>() - } else { - Vec::new() - }; + // Get all crashes. + let crashes: HashMap = fs::read_dir(input_dir)? + .flatten() + .map(|p| p.path()) + .filter(|p| p.is_file()) + .map(|p| { + ( + p.file_name().unwrap().to_str().unwrap().to_string(), + CrashInfo { + path: p, + target_args: argv.iter().map(|x| x.to_string()).collect(), + at_index: Some(at_index), + casr_tool: tool.clone(), + }, + ) + }) + .filter(|(fname, _)| fname.starts_with("crash-") || fname.starts_with("leak-")) + .collect(); // Generate reports - generate_reports(&matches, &crashes, tool, &gdb_argv) + handle_crashes(&matches, &crashes) } diff --git a/casr/tests/tests.rs b/casr/tests/tests.rs index a376b24d..ce7ddf16 100644 --- a/casr/tests/tests.rs +++ b/casr/tests/tests.rs @@ -3459,9 +3459,8 @@ fn test_casr_afl() { &paths[0], "-o", &paths[1], - "--", - "/tmp/load_sydr", - "@@", + "--casr-gdb-args", + "/tmp/load_sydr @@", ]) .env( "PATH", @@ -3647,7 +3646,7 @@ fn test_casr_libfuzzer() { .parent() .unwrap(); let mut cmd = Command::new(*EXE_CASR_LIBFUZZER.read().unwrap()); - cmd.args(["-i", &paths[0], "-o", &paths[1], "--", &paths[2], "@@"]) + cmd.args(["-i", &paths[0], "-o", &paths[1], "--", &paths[2]]) .env( "PATH", format!("{}:{}", bins.display(), std::env::var("PATH").unwrap()), @@ -3757,7 +3756,7 @@ fn test_casr_libfuzzer_atheris() { .parent() .unwrap(); let mut cmd = Command::new(*EXE_CASR_LIBFUZZER.read().unwrap()); - cmd.args(["-i", &paths[0], "-o", &paths[1], "--", &paths[2], "@@"]) + cmd.args(["-i", &paths[0], "-o", &paths[1], "--", &paths[2]]) .env( "PATH", format!("{}:{}", bins.display(), std::env::var("PATH").unwrap()),