Skip to content

Commit

Permalink
Support report accumulation for casr-libfuzzer and casr-afl (#223)
Browse files Browse the repository at this point in the history
  • Loading branch information
hkctkuy authored Jun 12, 2024
1 parent 5f871aa commit b1df8af
Show file tree
Hide file tree
Showing 8 changed files with 258 additions and 75 deletions.
2 changes: 1 addition & 1 deletion casr/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ tokio = { version = "1", features = ["rt", "macros"], optional = true }
toml = { version = "0.8", optional = true }
wait-timeout = "0.2"
which = "6.0"
copy_dir = "0.1"

libcasr = { path = "../libcasr", version = "2.12.0", features = ["serde", "exploitable"] }

Expand All @@ -52,4 +53,3 @@ required-features = ["dojo"]

[dev-dependencies]
lazy_static = "1.4"
copy_dir = "0.1.3"
9 changes: 9 additions & 0 deletions casr/src/bin/casr-afl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,15 @@ fn main() -> Result<()> {
.value_parser(clap::value_parser!(PathBuf))
.help("Output directory with triaged reports")
)
.arg(
Arg::new("join")
.long("join")
.env("CASR_PREV_CLUSTERS_DIR")
.action(ArgAction::Set)
.value_parser(clap::value_parser!(PathBuf))
.value_name("PREV_CLUSTERS_DIR")
.help("Use directory with previously triaged reports for new reports accumulation")
)
.arg(
Arg::new("force-remove")
.short('f')
Expand Down
6 changes: 3 additions & 3 deletions casr/src/bin/casr-cluster.rs
Original file line number Diff line number Diff line change
Expand Up @@ -762,11 +762,11 @@ fn main() -> Result<()> {
println!("Number of new clusters: {result}");
}
// Print crashline dedup summary
if before != 0 {
if dedup_crashlines {
println!("Number of reports before crashline deduplication in new clusters: {before}");
}
if before != after {
println!("Number of reports after crashline deduplication in new clusters: {after}");
} else {
println!("Number of reports in new clusters: {after}");
}
let sil = calc_avg_sil(paths[1], jobs)?;
println!("Cluster silhouette score: {sil}");
Expand Down
9 changes: 9 additions & 0 deletions casr/src/bin/casr-libfuzzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,15 @@ fn main() -> Result<()> {
.value_name("OUTPUT_DIR")
.help("Output directory with triaged reports")
)
.arg(
Arg::new("join")
.long("join")
.env("CASR_PREV_CLUSTERS_DIR")
.action(ArgAction::Set)
.value_parser(clap::value_parser!(PathBuf))
.value_name("PREV_CLUSTERS_DIR")
.help("Use directory with previously triaged reports for new reports accumulation")
)
.arg(
Arg::new("force-remove")
.short('f')
Expand Down
101 changes: 67 additions & 34 deletions casr/src/triage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,16 @@ pub fn fuzzing_crash_triage_pipeline(
bail!("No crashes found");
}

let accum_mode = matches.contains_id("join");

let output_dir = initialize_dirs(matches)?;

let casrep_dir = if accum_mode {
output_dir.join("casrep")
} else {
output_dir.to_path_buf()
};

// Get timeout
let timeout = *matches.get_one::<u64>("timeout").unwrap();

Expand Down Expand Up @@ -189,7 +197,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) {
if let Err(e) = crash.run_casr(casrep_dir.as_path(), timeout) {
// Disable util::log_progress
*counter.write().unwrap() = total;
bail!(e);
Expand All @@ -210,7 +218,7 @@ pub fn fuzzing_crash_triage_pipeline(
info!("Deduplicating CASR reports...");
let casr_cluster_d = Command::new(&casr_cluster)
.arg("-d")
.arg(output_dir.clone().into_os_string())
.arg(casrep_dir.clone().into_os_string())
.output()
.with_context(|| format!("Couldn't launch {casr_cluster:?}"))?;

Expand All @@ -230,41 +238,66 @@ pub fn fuzzing_crash_triage_pipeline(
}

if !matches.get_flag("no-cluster") {
if output_dir
.read_dir()?
.flatten()
.map(|e| e.path())
.filter(|e| e.extension().is_some() && e.extension().unwrap() == "casrep")
.count()
< 2
{
info!("There are less than 2 CASR reports, nothing to cluster.");
return summarize_results(matches, crashes, gdb_args);
}
info!("Clustering CASR reports...");
let casr_cluster_c = Command::new(&casr_cluster)
.arg("-c")
.arg(output_dir.clone().into_os_string())
.output()
.with_context(|| format!("Couldn't launch {casr_cluster:?}"))?;
if accum_mode {
info!("Accumulating CASR reports...");
let casr_cluster_u = Command::new(&casr_cluster)
.arg("-u")
.arg(casrep_dir.clone().into_os_string())
.arg(output_dir.clone().into_os_string())
.output()
.with_context(|| format!("Couldn't launch {casr_cluster:?}"))?;

if casr_cluster_u.status.success() {
info!(
"{}",
String::from_utf8_lossy(&casr_cluster_u.stdout).trim_end()
);
} else {
error!(
"{}",
String::from_utf8_lossy(&casr_cluster_u.stderr).trim_end()
);
}

if casr_cluster_c.status.success() {
info!(
"{}",
String::from_utf8_lossy(&casr_cluster_c.stdout).trim_end()
);
// Remove reports from deduplication phase. They are in clusters now.
fs::remove_dir_all(casrep_dir)?;
} else {
error!(
"{}",
String::from_utf8_lossy(&casr_cluster_c.stderr).trim_end()
);
}
if casrep_dir
.read_dir()?
.flatten()
.map(|e| e.path())
.filter(|e| e.extension().is_some() && e.extension().unwrap() == "casrep")
.count()
< 2
{
info!("There are less than 2 CASR reports, nothing to cluster.");
return summarize_results(matches, crashes, gdb_args);
}
info!("Clustering CASR reports...");
let casr_cluster_c = Command::new(&casr_cluster)
.arg("-c")
.arg(output_dir.clone().into_os_string())
.output()
.with_context(|| format!("Couldn't launch {casr_cluster:?}"))?;

if casr_cluster_c.status.success() {
info!(
"{}",
String::from_utf8_lossy(&casr_cluster_c.stdout).trim_end()
);
} else {
error!(
"{}",
String::from_utf8_lossy(&casr_cluster_c.stderr).trim_end()
);
}

// Remove reports from deduplication phase. They are in clusters now.
for casrep in fs::read_dir(output_dir)?.flatten().map(|e| e.path()) {
if let Some(ext) = casrep.extension() {
if ext == "casrep" {
let _ = fs::remove_file(casrep);
// Remove reports from deduplication phase. They are in clusters now.
for casrep in fs::read_dir(casrep_dir)?.flatten().map(|e| e.path()) {
if let Some(ext) = casrep.extension() {
if ext == "casrep" {
let _ = fs::remove_file(casrep);
}
}
}
}
Expand Down
27 changes: 17 additions & 10 deletions casr/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use libcasr::stacktrace::{

use anyhow::{bail, Context, Result};
use clap::ArgMatches;
use copy_dir::copy_dir;
use gdb_command::stacktrace::StacktraceExt;
use is_executable::IsExecutable;
use log::{info, warn};
Expand Down Expand Up @@ -316,28 +317,34 @@ pub fn get_atheris_lib() -> Result<String> {
pub fn initialize_dirs(matches: &clap::ArgMatches) -> Result<&PathBuf> {
// Get output dir
let output_dir = matches.get_one::<PathBuf>("output").unwrap();
if !output_dir.exists() {
fs::create_dir_all(output_dir).with_context(|| {
format!("Couldn't create output directory {}", output_dir.display())
})?;
} else if output_dir.read_dir()?.next().is_some() {
if output_dir.exists() && output_dir.read_dir()?.next().is_some() {
if matches.get_flag("force-remove") {
fs::remove_dir_all(output_dir)?;
fs::create_dir_all(output_dir).with_context(|| {
format!("Couldn't create output directory {}", output_dir.display())
})?;
} else {
bail!("Output directory is not empty.");
}
}

if let Some(join_dir) = matches.get_one::<PathBuf>("join") {
copy_dir(join_dir, output_dir)
.with_context(|| format!("Couldn't copy join directory {}", join_dir.display()))?;
// Get casrep dir
let casrep_dir = output_dir.join("casrep");
if !casrep_dir.exists() && fs::create_dir_all(&casrep_dir).is_err() {
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());
}

// Get oom dir
let oom_dir = output_dir.join("oom");
if fs::create_dir_all(&oom_dir).is_err() {
if !oom_dir.exists() && fs::create_dir_all(&oom_dir).is_err() {
bail!("Failed to create dir {}", &oom_dir.to_str().unwrap());
}
// Get timeout dir
let timeout_dir = output_dir.join("timeout");
if fs::create_dir_all(&timeout_dir).is_err() {
if !timeout_dir.exists() && fs::create_dir_all(&timeout_dir).is_err() {
bail!("Failed to create dir {}", &timeout_dir.to_str().unwrap());
}

Expand Down
Loading

0 comments on commit b1df8af

Please sign in to comment.