From af9e9e46df70e81b8d5275748863c3a4007faa5e Mon Sep 17 00:00:00 2001 From: Ilya Yegorov Date: Thu, 28 Nov 2024 14:20:35 +0300 Subject: [PATCH] [casr-afl][casr-libfuzzer] Add hints (#230) --- .github/workflows/coverage.yaml | 4 ++-- .github/workflows/riscv64.yml | 3 ++- README.md | 2 +- casr/src/bin/casr-afl.rs | 21 +++++++++++++++++---- casr/src/bin/casr-cli.rs | 2 +- casr/src/bin/casr-libfuzzer.rs | 32 +++++++++++++++++++++++++------- casr/src/util.rs | 2 +- casr/tests/tests.rs | 5 +++++ docs/usage.md | 6 ++++++ libcasr/src/cluster.rs | 8 ++------ 10 files changed, 62 insertions(+), 23 deletions(-) diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml index 12f23c35..7fae8fa4 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yaml @@ -38,7 +38,7 @@ jobs: - name: Build and Run Tests env: CARGO_INCREMENTAL: 0 - RUSTFLAGS: '-Cinstrument-coverage -Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort' + RUSTFLAGS: '-Cinstrument-coverage -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort' RUSTDOCFLAGS: '-Cpanic=abort' LLVM_PROFILE_FILE: 'casr-%p-%m.profraw' run: | @@ -61,6 +61,6 @@ jobs: --excl-stop '^}$' \ -t lcov - name: Upload Coverage Reports to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v5 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/riscv64.yml b/.github/workflows/riscv64.yml index bc315e2e..9cee8adb 100644 --- a/.github/workflows/riscv64.yml +++ b/.github/workflows/riscv64.yml @@ -25,7 +25,8 @@ jobs: install: | export CARGO_TERM_COLOR=always export CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse - apt-get update && apt-get install -y gdb pip curl python3.10-dev clang llvm build-essential + apt-get update \ + && apt-get install -y gdb pip curl python3-dev clang llvm build-essential curl https://sh.rustup.rs -o rustup.sh && chmod +x rustup.sh && \ ./rustup.sh -y && rm rustup.sh run: | diff --git a/README.md b/README.md index f670157c..a6d6f6d4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ [![Crates.io](https://img.shields.io/crates/v/casr)](https://crates.io/crates/casr) [![Documentation](https://docs.rs/libcasr/badge.svg)](https://docs.rs/libcasr) -[![codecov](https://codecov.io/github/ispras/casr/graph/badge.svg?token=D9VY1WRWA7)](https://codecov.io/github/ispras/casr) +[![codecov](https://codecov.io/github/ispras/casr/graph/badge.svg?token=D9VY1WRWA7)](https://app.codecov.io/github/ispras/casr) [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://github.com/ispras/casr/blob/master/LICENSE) [![amd64](https://github.com/ispras/casr/actions/workflows/amd64.yml/badge.svg?branch=master)](https://github.com/ispras/casr/actions/workflows/amd64.yml) diff --git a/casr/src/bin/casr-afl.rs b/casr/src/bin/casr-afl.rs index b201e046..02a20cee 100644 --- a/casr/src/bin/casr-afl.rs +++ b/casr/src/bin/casr-afl.rs @@ -103,6 +103,15 @@ fn main() -> Result<()> { .long("no-cluster") .help("Do not cluster CASR reports") ) + .arg( + Arg::new("hint") + .long("hint") + .value_name("HINT") + .action(ArgAction::Set) + .default_value("auto") + .value_parser(["auto", "gdb", "san", "csharp"]) + .help("Hint to force run casr-HINT tool to analyze crashes") + ) .arg( Arg::new("ARGS") .action(ArgAction::Set) @@ -129,6 +138,8 @@ fn main() -> Result<()> { bail!("ARGS is empty, but \"ignore-cmdline\" option is provided."); } + let hint = matches.get_one::("hint").unwrap(); + // Get all crashes. let mut crashes: HashMap = HashMap::new(); for node_dir in fs::read_dir(matches.get_one::("input").unwrap())? { @@ -152,9 +163,11 @@ fn main() -> Result<()> { continue; } }; - crash_info.casr_tool = if !crash_info.target_args.is_empty() - && (crash_info.target_args[0].ends_with("dotnet") - || crash_info.target_args[0].ends_with("mono")) + crash_info.casr_tool = if hint == "csharp" + || hint == "auto" + && !crash_info.target_args.is_empty() + && (crash_info.target_args[0].ends_with("dotnet") + || crash_info.target_args[0].ends_with("mono")) { is_casr_gdb = false; util::get_path("casr-csharp")? @@ -169,7 +182,7 @@ fn main() -> Result<()> { .map(|x| x + 1); // When we triage crashes for binaries, use casr-san. - if is_casr_gdb { + if hint == "san" || hint == "auto" && is_casr_gdb { if let Some(target) = crash_info.target_args.first() { match util::symbols_list(Path::new(target)) { Ok(list) => { diff --git a/casr/src/bin/casr-cli.rs b/casr/src/bin/casr-cli.rs index 4229804f..2676c0b4 100644 --- a/casr/src/bin/casr-cli.rs +++ b/casr/src/bin/casr-cli.rs @@ -153,7 +153,7 @@ fn main() -> Result<()> { )); if !report.package.is_empty() && !report.package_version.is_empty() { - header_string.append(&format!( + header_string.append(format!( " from {} {}", report.package, report.package_version )); diff --git a/casr/src/bin/casr-libfuzzer.rs b/casr/src/bin/casr-libfuzzer.rs index 4e6d0581..dd34b100 100644 --- a/casr/src/bin/casr-libfuzzer.rs +++ b/casr/src/bin/casr-libfuzzer.rs @@ -102,6 +102,15 @@ fn main() -> Result<()> { .action(ArgAction::Set) .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("hint") + .long("hint") + .value_name("HINT") + .action(ArgAction::Set) + .default_value("auto") + .value_parser(["auto", "gdb", "java", "js", "python", "san"]) + .help("Hint to force run casr-HINT tool to analyze crashes") + ) .arg( Arg::new("ARGS") .action(ArgAction::Set) @@ -132,22 +141,31 @@ fn main() -> Result<()> { Vec::new() }; + // Get hint + let hint = matches.get_one::("hint").unwrap(); + // Get tool. let mut envs = HashMap::new(); - let tool = if argv[0].ends_with(".py") { + let tool = if hint == "python" || hint == "auto" && argv[0].ends_with(".py") { envs.insert("LD_PRELOAD".to_string(), util::get_atheris_lib()?); "casr-python" - } else if argv[0].ends_with("jazzer") || argv[0].ends_with("java") { + } else if hint == "java" + || hint == "auto" && (argv[0].ends_with("jazzer") || argv[0].ends_with("java")) + { "casr-java" - } else if argv[0].ends_with(".js") - || argv[0].ends_with("node") - || argv.len() > 1 && argv[0].ends_with("npx") && argv[1] == "jazzer" - || argv[0].ends_with("jsfuzz") + } else if hint == "js" + || hint == "auto" + && (argv[0].ends_with(".js") + || argv[0].ends_with("node") + || argv.len() > 1 && argv[0].ends_with("npx") && argv[1] == "jazzer" + || argv[0].ends_with("jsfuzz")) { "casr-js" } else { let sym_list = util::symbols_list(Path::new(argv[0]))?; - if sym_list.contains("__asan") || sym_list.contains("runtime.go") { + if hint == "san" + || hint == "auto" && (sym_list.contains("__asan") || sym_list.contains("runtime.go")) + { "casr-san" } else { "casr-gdb" diff --git a/casr/src/util.rs b/casr/src/util.rs index 7d5f6c5f..d17cc2b7 100644 --- a/casr/src/util.rs +++ b/casr/src/util.rs @@ -334,7 +334,7 @@ pub fn initialize_dirs(matches: &clap::ArgMatches) -> Result<&PathBuf> { bail!("Failed to create dir {}", &casrep_dir.to_str().unwrap()); } } else if !output_dir.exists() && fs::create_dir_all(output_dir).is_err() { - format!("Couldn't create output directory {}", output_dir.display()); + bail!("Couldn't create output directory {}", output_dir.display()); } // Get oom dir diff --git a/casr/tests/tests.rs b/casr/tests/tests.rs index 05517799..1e7fe468 100644 --- a/casr/tests/tests.rs +++ b/casr/tests/tests.rs @@ -4683,6 +4683,7 @@ fn test_casr_python_call_san_df() { let work_dir = abs_path("tests/casr_tests/python"); let test_dir = abs_path("tests/tmp_tests_casr/test_casr_python_call_san_df"); + let _ = std::fs::remove_dir_all(&test_dir); let _ = copy_dir(work_dir, &test_dir).unwrap(); let paths = [ @@ -5924,6 +5925,8 @@ fn test_casr_afl_csharp() { ]; let _ = fs::remove_dir_all(&paths[1]); + let _ = fs::remove_dir_all(&paths[4]); + let _ = fs::remove_dir_all(&paths[5]); let _ = fs::create_dir(abs_path("tests/tmp_tests_casr")); let _ = copy_dir(&paths[2], &paths[4]).unwrap(); let _ = copy_dir(&paths[3], &paths[5]).unwrap(); @@ -6024,6 +6027,8 @@ fn test_casr_afl_csharp_ignore_cmd() { ]; let _ = fs::remove_dir_all(&paths[1]); + let _ = fs::remove_dir_all(&paths[4]); + let _ = fs::remove_dir_all(&paths[5]); let _ = fs::create_dir(abs_path("tests/tmp_tests_casr")); let _ = copy_dir(&paths[2], &paths[4]).unwrap(); let _ = copy_dir(&paths[3], &paths[5]).unwrap(); diff --git a/docs/usage.md b/docs/usage.md index 6599858f..be98355f 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -463,6 +463,9 @@ Triage crashes found by AFL++/Sharpfuzz --ignore-cmdline Force usage to run target instead of searching for cmdline files in AFL fuzzing directory --no-cluster Do not cluster CASR reports + --hint Hint to force run casr-HINT tool to analyze crashes + [default: auto] [possible values: auto, gdb, san, + csharp] -h, --help Print help -V, --version Print version @@ -603,6 +606,9 @@ Triage crashes found by libFuzzer based fuzzer Add "--casr-gdb-args './gdb_fuzz_target '" to generate additional crash reports with casr-gdb (e.g., test whether program crashes without sanitizers) + --hint + Hint to force run casr-HINT tool to analyze crashes [default: auto] [possible + values: auto, gdb, java, js, python, san] -h, --help Print help -V, --version diff --git a/libcasr/src/cluster.rs b/libcasr/src/cluster.rs index 9f65d983..c14b329b 100644 --- a/libcasr/src/cluster.rs +++ b/libcasr/src/cluster.rs @@ -197,16 +197,12 @@ impl Cluster { /// NOTE: Result also can be interpreted as diameter of cluster merge result pub fn dist_rep(cluster: &Cluster, report: &ReportInfo) -> f64 { let (_, (trace, _)) = report; - if let Some(max) = cluster + cluster .stacktraces() .iter() .map(|s| 1.0 - similarity(s, trace)) .max_by(|a, b| a.total_cmp(b)) - { - max - } else { - 0f64 - } + .unwrap_or(0f64) } }