Skip to content

Commit b1df8af

Browse files
authored
Support report accumulation for casr-libfuzzer and casr-afl (#223)
1 parent 5f871aa commit b1df8af

File tree

8 files changed

+258
-75
lines changed

8 files changed

+258
-75
lines changed

casr/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ tokio = { version = "1", features = ["rt", "macros"], optional = true }
3737
toml = { version = "0.8", optional = true }
3838
wait-timeout = "0.2"
3939
which = "6.0"
40+
copy_dir = "0.1"
4041

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

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

5354
[dev-dependencies]
5455
lazy_static = "1.4"
55-
copy_dir = "0.1.3"

casr/src/bin/casr-afl.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,15 @@ fn main() -> Result<()> {
7575
.value_parser(clap::value_parser!(PathBuf))
7676
.help("Output directory with triaged reports")
7777
)
78+
.arg(
79+
Arg::new("join")
80+
.long("join")
81+
.env("CASR_PREV_CLUSTERS_DIR")
82+
.action(ArgAction::Set)
83+
.value_parser(clap::value_parser!(PathBuf))
84+
.value_name("PREV_CLUSTERS_DIR")
85+
.help("Use directory with previously triaged reports for new reports accumulation")
86+
)
7887
.arg(
7988
Arg::new("force-remove")
8089
.short('f')

casr/src/bin/casr-cluster.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -762,11 +762,11 @@ fn main() -> Result<()> {
762762
println!("Number of new clusters: {result}");
763763
}
764764
// Print crashline dedup summary
765-
if before != 0 {
765+
if dedup_crashlines {
766766
println!("Number of reports before crashline deduplication in new clusters: {before}");
767-
}
768-
if before != after {
769767
println!("Number of reports after crashline deduplication in new clusters: {after}");
768+
} else {
769+
println!("Number of reports in new clusters: {after}");
770770
}
771771
let sil = calc_avg_sil(paths[1], jobs)?;
772772
println!("Cluster silhouette score: {sil}");

casr/src/bin/casr-libfuzzer.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,15 @@ fn main() -> Result<()> {
7474
.value_name("OUTPUT_DIR")
7575
.help("Output directory with triaged reports")
7676
)
77+
.arg(
78+
Arg::new("join")
79+
.long("join")
80+
.env("CASR_PREV_CLUSTERS_DIR")
81+
.action(ArgAction::Set)
82+
.value_parser(clap::value_parser!(PathBuf))
83+
.value_name("PREV_CLUSTERS_DIR")
84+
.help("Use directory with previously triaged reports for new reports accumulation")
85+
)
7786
.arg(
7887
Arg::new("force-remove")
7988
.short('f')

casr/src/triage.rs

Lines changed: 67 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,16 @@ pub fn fuzzing_crash_triage_pipeline(
159159
bail!("No crashes found");
160160
}
161161

162+
let accum_mode = matches.contains_id("join");
163+
162164
let output_dir = initialize_dirs(matches)?;
163165

166+
let casrep_dir = if accum_mode {
167+
output_dir.join("casrep")
168+
} else {
169+
output_dir.to_path_buf()
170+
};
171+
164172
// Get timeout
165173
let timeout = *matches.get_one::<u64>("timeout").unwrap();
166174

@@ -189,7 +197,7 @@ pub fn fuzzing_crash_triage_pipeline(
189197
.join(
190198
|| {
191199
crashes.par_iter().try_for_each(|(_, crash)| {
192-
if let Err(e) = crash.run_casr(output_dir.as_path(), timeout) {
200+
if let Err(e) = crash.run_casr(casrep_dir.as_path(), timeout) {
193201
// Disable util::log_progress
194202
*counter.write().unwrap() = total;
195203
bail!(e);
@@ -210,7 +218,7 @@ pub fn fuzzing_crash_triage_pipeline(
210218
info!("Deduplicating CASR reports...");
211219
let casr_cluster_d = Command::new(&casr_cluster)
212220
.arg("-d")
213-
.arg(output_dir.clone().into_os_string())
221+
.arg(casrep_dir.clone().into_os_string())
214222
.output()
215223
.with_context(|| format!("Couldn't launch {casr_cluster:?}"))?;
216224

@@ -230,41 +238,66 @@ pub fn fuzzing_crash_triage_pipeline(
230238
}
231239

232240
if !matches.get_flag("no-cluster") {
233-
if output_dir
234-
.read_dir()?
235-
.flatten()
236-
.map(|e| e.path())
237-
.filter(|e| e.extension().is_some() && e.extension().unwrap() == "casrep")
238-
.count()
239-
< 2
240-
{
241-
info!("There are less than 2 CASR reports, nothing to cluster.");
242-
return summarize_results(matches, crashes, gdb_args);
243-
}
244-
info!("Clustering CASR reports...");
245-
let casr_cluster_c = Command::new(&casr_cluster)
246-
.arg("-c")
247-
.arg(output_dir.clone().into_os_string())
248-
.output()
249-
.with_context(|| format!("Couldn't launch {casr_cluster:?}"))?;
241+
if accum_mode {
242+
info!("Accumulating CASR reports...");
243+
let casr_cluster_u = Command::new(&casr_cluster)
244+
.arg("-u")
245+
.arg(casrep_dir.clone().into_os_string())
246+
.arg(output_dir.clone().into_os_string())
247+
.output()
248+
.with_context(|| format!("Couldn't launch {casr_cluster:?}"))?;
249+
250+
if casr_cluster_u.status.success() {
251+
info!(
252+
"{}",
253+
String::from_utf8_lossy(&casr_cluster_u.stdout).trim_end()
254+
);
255+
} else {
256+
error!(
257+
"{}",
258+
String::from_utf8_lossy(&casr_cluster_u.stderr).trim_end()
259+
);
260+
}
250261

251-
if casr_cluster_c.status.success() {
252-
info!(
253-
"{}",
254-
String::from_utf8_lossy(&casr_cluster_c.stdout).trim_end()
255-
);
262+
// Remove reports from deduplication phase. They are in clusters now.
263+
fs::remove_dir_all(casrep_dir)?;
256264
} else {
257-
error!(
258-
"{}",
259-
String::from_utf8_lossy(&casr_cluster_c.stderr).trim_end()
260-
);
261-
}
265+
if casrep_dir
266+
.read_dir()?
267+
.flatten()
268+
.map(|e| e.path())
269+
.filter(|e| e.extension().is_some() && e.extension().unwrap() == "casrep")
270+
.count()
271+
< 2
272+
{
273+
info!("There are less than 2 CASR reports, nothing to cluster.");
274+
return summarize_results(matches, crashes, gdb_args);
275+
}
276+
info!("Clustering CASR reports...");
277+
let casr_cluster_c = Command::new(&casr_cluster)
278+
.arg("-c")
279+
.arg(output_dir.clone().into_os_string())
280+
.output()
281+
.with_context(|| format!("Couldn't launch {casr_cluster:?}"))?;
282+
283+
if casr_cluster_c.status.success() {
284+
info!(
285+
"{}",
286+
String::from_utf8_lossy(&casr_cluster_c.stdout).trim_end()
287+
);
288+
} else {
289+
error!(
290+
"{}",
291+
String::from_utf8_lossy(&casr_cluster_c.stderr).trim_end()
292+
);
293+
}
262294

263-
// Remove reports from deduplication phase. They are in clusters now.
264-
for casrep in fs::read_dir(output_dir)?.flatten().map(|e| e.path()) {
265-
if let Some(ext) = casrep.extension() {
266-
if ext == "casrep" {
267-
let _ = fs::remove_file(casrep);
295+
// Remove reports from deduplication phase. They are in clusters now.
296+
for casrep in fs::read_dir(casrep_dir)?.flatten().map(|e| e.path()) {
297+
if let Some(ext) = casrep.extension() {
298+
if ext == "casrep" {
299+
let _ = fs::remove_file(casrep);
300+
}
268301
}
269302
}
270303
}

casr/src/util.rs

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use libcasr::stacktrace::{
99

1010
use anyhow::{bail, Context, Result};
1111
use clap::ArgMatches;
12+
use copy_dir::copy_dir;
1213
use gdb_command::stacktrace::StacktraceExt;
1314
use is_executable::IsExecutable;
1415
use log::{info, warn};
@@ -316,28 +317,34 @@ pub fn get_atheris_lib() -> Result<String> {
316317
pub fn initialize_dirs(matches: &clap::ArgMatches) -> Result<&PathBuf> {
317318
// Get output dir
318319
let output_dir = matches.get_one::<PathBuf>("output").unwrap();
319-
if !output_dir.exists() {
320-
fs::create_dir_all(output_dir).with_context(|| {
321-
format!("Couldn't create output directory {}", output_dir.display())
322-
})?;
323-
} else if output_dir.read_dir()?.next().is_some() {
320+
if output_dir.exists() && output_dir.read_dir()?.next().is_some() {
324321
if matches.get_flag("force-remove") {
325322
fs::remove_dir_all(output_dir)?;
326-
fs::create_dir_all(output_dir).with_context(|| {
327-
format!("Couldn't create output directory {}", output_dir.display())
328-
})?;
329323
} else {
330324
bail!("Output directory is not empty.");
331325
}
332326
}
327+
328+
if let Some(join_dir) = matches.get_one::<PathBuf>("join") {
329+
copy_dir(join_dir, output_dir)
330+
.with_context(|| format!("Couldn't copy join directory {}", join_dir.display()))?;
331+
// Get casrep dir
332+
let casrep_dir = output_dir.join("casrep");
333+
if !casrep_dir.exists() && fs::create_dir_all(&casrep_dir).is_err() {
334+
bail!("Failed to create dir {}", &casrep_dir.to_str().unwrap());
335+
}
336+
} else if !output_dir.exists() && fs::create_dir_all(output_dir).is_err() {
337+
format!("Couldn't create output directory {}", output_dir.display());
338+
}
339+
333340
// Get oom dir
334341
let oom_dir = output_dir.join("oom");
335-
if fs::create_dir_all(&oom_dir).is_err() {
342+
if !oom_dir.exists() && fs::create_dir_all(&oom_dir).is_err() {
336343
bail!("Failed to create dir {}", &oom_dir.to_str().unwrap());
337344
}
338345
// Get timeout dir
339346
let timeout_dir = output_dir.join("timeout");
340-
if fs::create_dir_all(&timeout_dir).is_err() {
347+
if !timeout_dir.exists() && fs::create_dir_all(&timeout_dir).is_err() {
341348
bail!("Failed to create dir {}", &timeout_dir.to_str().unwrap());
342349
}
343350

0 commit comments

Comments
 (0)