From 694bdf112bf4e5263c24712942b7f314d142dd2a Mon Sep 17 00:00:00 2001 From: headshog <124502670+headshog@users.noreply.github.com> Date: Sun, 9 Feb 2025 20:08:24 +0300 Subject: [PATCH] Support vanilla AFl directory format for casr-afl (#240) --- README.md | 2 + casr/src/bin/casr-afl.rs | 31 +++++++--- casr/tests/tests.rs | 122 +++++++++++++++++++++++++++++++++++++-- docs/usage.md | 11 +++- 4 files changed, 151 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 4eb32073..2b4bc694 100644 --- a/README.md +++ b/README.md @@ -210,6 +210,8 @@ Triage crashes after Sharpfuzz fuzzing with casr-afl: $ casr-afl -i casr/tests/casr_tests/casrep/afl-out-sharpfuzz -o casr/tests/tmp_tests_casr/casr_afl_csharp_out $ # You may force your own run arguments using --ignore-cmdline $ casr-afl --ignore-cmdline -i casr/tests/casr_tests/casrep/afl-out-sharpfuzz -o casr/tests/tmp_tests_casr/casr_afl_csharp_out -- dotnet run --no-build --project /tmp/test_casr_afl_csharp/test_casr_afl_csharp.csproj @@ + $ # If you use vanilla AFL for fuzzing with Sharpfuzz, force your own run arguments via -- + $ casr-afl -i casr/tests/casr_tests/casrep/afl-out-sharpfuzz/afl_main-worker -o casr/tests/tmp_tests_casr/casr_afl_csharp_out -- dotnet run --no-build --project /tmp/test_casr_afl_csharp/test_casr_afl_csharp.csproj @@ Triage libFuzzer crashes with casr-libfuzzer: diff --git a/casr/src/bin/casr-afl.rs b/casr/src/bin/casr-afl.rs index 02a20cee..0ac2f01d 100644 --- a/casr/src/bin/casr-afl.rs +++ b/casr/src/bin/casr-afl.rs @@ -134,16 +134,26 @@ fn main() -> Result<()> { Vec::new() }; - if args.is_empty() && matches.get_flag("ignore-cmdline") { - bail!("ARGS is empty, but \"ignore-cmdline\" option is provided."); - } - let hint = matches.get_one::("hint").unwrap(); + let input = matches.get_one::("input").unwrap(); + let afl_dir = input.join("crashes").is_dir(); + let ignore_cmdline = matches.get_flag("ignore-cmdline") || afl_dir; + + if args.is_empty() && ignore_cmdline { + if afl_dir { + bail!("ARGS is empty, but vanilla AFL directory is given as input."); + } else { + bail!("ARGS is empty, but \"ignore-cmdline\" option is provided."); + } + } // Get all crashes. let mut crashes: HashMap = HashMap::new(); - for node_dir in fs::read_dir(matches.get_one::("input").unwrap())? { - let path = node_dir?.path(); + for node_dir in fs::read_dir(input)? { + let mut path = node_dir?.path(); + if afl_dir { + path.pop(); + } if !path.is_dir() { continue; } @@ -152,7 +162,7 @@ fn main() -> Result<()> { let mut crash_info = casr::triage::CrashInfo { ..Default::default() }; - crash_info.target_args = if matches.get_flag("ignore-cmdline") { + crash_info.target_args = if ignore_cmdline { args.clone() } else { let cmdline_path = path.join("cmdline"); @@ -217,9 +227,14 @@ fn main() -> Result<()> { info.path = crash.path(); crashes.insert(crash.file_name().into_string().unwrap(), info); } + + // We don't need to continue cycle for vanilla AFL directory + if afl_dir { + break; + } } - if matches.get_flag("ignore-cmdline") || !is_casr_gdb { + if ignore_cmdline || !is_casr_gdb { args = Vec::new(); } diff --git a/casr/tests/tests.rs b/casr/tests/tests.rs index 6f39105a..7b477dbb 100644 --- a/casr/tests/tests.rs +++ b/casr/tests/tests.rs @@ -6059,14 +6059,13 @@ fn test_casr_afl_csharp_ignore_cmd() { abs_path("tests/tmp_tests_casr/casr_afl_csharp_ignore_cmd_out"), abs_path("tests/casr_tests/csharp/test_casr_afl_csharp"), abs_path("tests/casr_tests/csharp/test_casr_afl_csharp_module"), - abs_path("tests/tmp_tests_casr/test_casr_afl_csharp"), - abs_path("tests/tmp_tests_casr/test_casr_afl_csharp_module"), + abs_path("/tmp/casr_afl_csharp_ignore_cmd/test_casr_afl_csharp"), + abs_path("/tmp/casr_afl_csharp_ignore_cmd/test_casr_afl_csharp_module"), ]; 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 _ = fs::create_dir_all("/tmp/casr_afl_csharp_ignore_cmd"); let _ = copy_dir(&paths[2], &paths[4]).unwrap(); let _ = copy_dir(&paths[3], &paths[5]).unwrap(); let Ok(dotnet_path) = which::which("dotnet") else { @@ -6160,8 +6159,119 @@ fn test_casr_afl_csharp_ignore_cmd() { } assert!(storage.values().all(|x| *x > 1)); - let _ = fs::remove_dir_all(&paths[4]); - let _ = fs::remove_dir_all(&paths[5]); + let _ = fs::remove_dir_all("/tmp/casr_afl_csharp_ignore_cmd"); +} + +#[test] +#[cfg(target_arch = "x86_64")] +fn test_casr_afl_csharp_vanilla_afl() { + use std::collections::HashMap; + + let paths = [ + abs_path("tests/casr_tests/casrep/afl-out-sharpfuzz/afl_main-worker"), + abs_path("tests/tmp_tests_casr/casr_afl_csharp_vanilla_afl_out"), + abs_path("tests/casr_tests/csharp/test_casr_afl_csharp"), + abs_path("tests/casr_tests/csharp/test_casr_afl_csharp_module"), + abs_path("/tmp/casr_afl_csharp_vanilla/test_casr_afl_csharp"), + abs_path("/tmp/casr_afl_csharp_vanilla/test_casr_afl_csharp_module"), + ]; + + let _ = fs::remove_dir_all(&paths[1]); + let _ = fs::create_dir(abs_path("tests/tmp_tests_casr")); + let _ = fs::create_dir_all("/tmp/casr_afl_csharp_vanilla"); + let _ = copy_dir(&paths[2], &paths[4]).unwrap(); + let _ = copy_dir(&paths[3], &paths[5]).unwrap(); + let Ok(dotnet_path) = which::which("dotnet") else { + panic!("No dotnet is found."); + }; + + let _ = Command::new(dotnet_path.to_str().unwrap()) + .args([ + "build", + &format!("{}/test_casr_afl_csharp.csproj", &paths[4]), + ]) + .output() + .expect("dotnet build crashed"); + + let bins = Path::new(*EXE_CASR_AFL.read().unwrap()).parent().unwrap(); + let mut output = Command::new(*EXE_CASR_AFL.read().unwrap()); + output + .args([ + "-i", + &paths[0], + "-o", + &paths[1], + "--", + (dotnet_path.to_str().unwrap()), + "run", + "--no-build", + "--project", + &format!("{}/test_casr_afl_csharp.csproj", &paths[4]), + "@@", + ]) + .env( + "PATH", + format!("{}:{}", bins.display(), std::env::var("PATH").unwrap()), + ); + + let output = output.output().expect("casr-afl crashed"); + + assert!( + output.status.success(), + "Stdout {}.\n Stderr: {}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + let res = String::from_utf8_lossy(&output.stderr); + + assert!(!res.is_empty()); + + let re = Regex::new(r"Number of reports after deduplication: (?P\d+)").unwrap(); + let unique_cnt = re + .captures(&res) + .unwrap() + .name("unique") + .map(|x| x.as_str()) + .unwrap() + .parse::() + .unwrap(); + + assert_eq!(unique_cnt, 3, "Invalid number of deduplicated reports"); + + let re = Regex::new(r"Number of clusters: (?P\d+)").unwrap(); + let clusters_cnt = re + .captures(&res) + .unwrap() + .name("clusters") + .map(|x| x.as_str()) + .unwrap() + .parse::() + .unwrap(); + + assert_eq!(clusters_cnt, 3, "Invalid number of clusters"); + + let mut storage: HashMap = HashMap::new(); + for entry in fs::read_dir(&paths[1]).unwrap() { + let e = entry.unwrap().path(); + let fname = e.file_name().unwrap().to_str().unwrap(); + if fname.starts_with("cl") && e.is_dir() { + for file in fs::read_dir(e).unwrap() { + let mut e = file.unwrap().path(); + if e.is_file() && e.extension().is_some() && e.extension().unwrap() == "casrep" { + e = e.with_extension(""); + } + let fname = e.file_name().unwrap().to_str().unwrap(); + if let Some(v) = storage.get_mut(fname) { + *v += 1; + } else { + storage.insert(fname.to_string(), 1); + } + } + } + } + + assert!(storage.values().all(|x| *x > 1)); + let _ = fs::remove_dir_all("/tmp/casr_afl_csharp_vanilla"); } #[test] diff --git a/docs/usage.md b/docs/usage.md index 0032d8f9..119af210 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -571,9 +571,18 @@ Sharpfuzz example (with --ignore-cmdline): $ dotnet publish /tmp/test_casr_afl_csharp/test_casr_afl_csharp.csproj -c Debug -o /tmp/test_casr_afl_csharp/bin $ casr-afl --ignore-cmdline -i casr/tests/casr_tests/casrep/afl-out-sharpfuzz -o casr/tests/tmp_tests_casr/casr_afl_csharp_out -- dotnet run --no-build --project /tmp/test_casr_afl_csharp/test_casr_afl_csharp.csproj @@ -**NOTE:** if you run `casr-afl` for Sharpfuzz pipeline using `--ignore-cmdline` with `dotnet run`, build +Sharpfuzz example (with vanilla AFL directory): + + $ cp -r casr/tests/casr_tests/csharp/test_casr_afl_csharp /tmp/test_casr_afl_csharp + $ cp -r casr/tests/casr_tests/csharp/test_casr_afl_csharp_module /tmp/test_casr_afl_csharp_module + $ dotnet publish /tmp/test_casr_afl_csharp/test_casr_afl_csharp.csproj -c Debug -o /tmp/test_casr_afl_csharp/bin + $ casr-afl -i casr/tests/casr_tests/casrep/afl-out-sharpfuzz/afl_main-worker -o casr/tests/tmp_tests_casr/casr_afl_csharp_out -- dotnet run --no-build --project /tmp/test_casr_afl_csharp/test_casr_afl_csharp.csproj @@ + +**NOTE 1:** if you run `casr-afl` for Sharpfuzz pipeline using `--ignore-cmdline` with `dotnet run`, build your project before (via `dotnet build` or `dotnet publish`) and specify `--no-build` option for `dotnet run`. +**NOTE 2:** if you run `casr-afl` for Sharpfuzz pipeline using vanilla AFL input directory, force your own run arguments via `-- `. + ## casr-libfuzzer Triage crashes found by libFuzzer based fuzzer