Skip to content

Commit

Permalink
[casr-afl][casr-libfuzzer] Support timeout (#117)
Browse files Browse the repository at this point in the history
  • Loading branch information
Ilya Yegorov authored Aug 17, 2023
1 parent fa4d5fc commit 0ca3fba
Show file tree
Hide file tree
Showing 9 changed files with 210 additions and 61 deletions.
58 changes: 47 additions & 11 deletions casr/src/bin/casr-afl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,14 @@ impl<'a> AflCrashInfo {
///
/// # Arguments
///
/// `output_dir` - save report to specified directory or use the same directory as crash
pub fn run_casr<T: Into<Option<&'a Path>>>(&self, output_dir: T) -> anyhow::Result<()> {
/// * `output_dir` - save report to specified directory or use the same directory as crash
///
/// * `timeout` - target program timeout (in seconds)
pub fn run_casr<T: Into<Option<&'a Path>>>(
&self,
output_dir: T,
timeout: u64,
) -> anyhow::Result<()> {
let mut args: Vec<String> = vec!["-o".to_string()];
let report_path = if let Some(out) = output_dir.into() {
out.join(self.path.file_name().unwrap())
Expand All @@ -50,6 +56,9 @@ impl<'a> AflCrashInfo {
args.push("--stdin".to_string());
args.push(self.path.to_str().unwrap().to_string());
}
if timeout != 0 {
args.append(&mut vec!["-t".to_string(), timeout.to_string()]);
}
args.push("--".to_string());
args.extend_from_slice(&self.target_args);
if let Some(at_index) = self.at_index {
Expand All @@ -61,9 +70,12 @@ impl<'a> AflCrashInfo {
let mut casr_cmd = Command::new(tool);
casr_cmd.args(&args);
debug!("{:?}", casr_cmd);

// Get output
let casr_output = casr_cmd
.output()
.with_context(|| format!("Couldn't launch {casr_cmd:?}"))?;

if !casr_output.status.success() {
let err = String::from_utf8_lossy(&casr_output.stderr);
if err.contains("Program terminated (no crash)") {
Expand Down Expand Up @@ -97,6 +109,15 @@ fn main() -> Result<()> {
.action(ArgAction::Set)
.help("Number of parallel jobs for generating CASR reports [default: half of cpu cores]")
.value_parser(clap::value_parser!(u32).range(1..)))
.arg(
Arg::new("timeout")
.short('t')
.long("timeout")
.action(ArgAction::Set)
.value_name("SECONDS")
.help("Timeout (in seconds) for target execution [default: disabled]")
.value_parser(clap::value_parser!(u64).range(1..))
)
.arg(
Arg::new("input")
.short('i')
Expand Down Expand Up @@ -149,6 +170,7 @@ fn main() -> Result<()> {
// Init log.
util::initialize_logging(&matches);

// Get output dir
let output_dir = matches.get_one::<PathBuf>("output").unwrap();
if !output_dir.exists() {
fs::create_dir_all(output_dir).with_context(|| {
Expand Down Expand Up @@ -228,6 +250,14 @@ fn main() -> Result<()> {
}
}

// Get timeout
let timeout = if let Some(timeout) = matches.get_one::<u64>("timeout") {
*timeout
} else {
0
};

// Get number of threads
let jobs = if let Some(jobs) = matches.get_one::<u32>("jobs") {
*jobs as usize
} else {
Expand All @@ -245,13 +275,13 @@ fn main() -> Result<()> {
custom_pool.install(|| {
crashes
.par_iter()
.try_for_each(|(_, crash)| crash.run_casr(output_dir.as_path()))
.try_for_each(|(_, crash)| crash.run_casr(output_dir.as_path(), timeout))
})?;

// 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);
return summarize_results(output_dir, &crashes, &gdb_argv, num_of_threads, timeout);
}
info!("Deduplicating CASR reports...");
let casr_cluster_d = Command::new("casr-cluster")
Expand Down Expand Up @@ -282,7 +312,7 @@ fn main() -> Result<()> {
< 2
{
info!("There are less than 2 CASR reports, nothing to cluster.");
return summarize_results(output_dir, &crashes, &gdb_argv, num_of_threads);
return summarize_results(output_dir, &crashes, &gdb_argv, num_of_threads, timeout);
}
info!("Clustering CASR reports...");
let casr_cluster_c = Command::new("casr-cluster")
Expand Down Expand Up @@ -310,23 +340,29 @@ fn main() -> Result<()> {
}
}

summarize_results(output_dir, &crashes, &gdb_argv, num_of_threads)
summarize_results(output_dir, &crashes, &gdb_argv, num_of_threads, timeout)
}

/// Copy crashes next to reports and print summary.
/// Run casr-gdb on uninstrumented binary if specified in ARGS.
///
/// # Arguments
///
/// `dir` - directory with casr reports
/// `crashes` - crashes info
/// `gdb_args` - run casr-gdb on uninstrumented binary if specified
/// `jobs` - number of threads for casr-gdb reports generation
/// * `dir` - directory with casr reports
///
/// * `crashes` - crashes info
///
/// * `gdb_args` - run casr-gdb on uninstrumented binary if specified
///
/// * `jobs` - number of threads for casr-gdb reports generation
///
/// * `timeout` - target program timeout
fn summarize_results(
dir: &Path,
crashes: &HashMap<String, AflCrashInfo>,
gdb_args: &Vec<String>,
jobs: usize,
timeout: u64,
) -> Result<()> {
// Copy crashes next to reports
copy_crashes(dir, crashes)?;
Expand Down Expand Up @@ -358,7 +394,7 @@ fn summarize_results(
at_index,
is_asan: false,
}
.run_casr(None)
.run_casr(None, timeout)
})
})?;
}
Expand Down
17 changes: 17 additions & 0 deletions casr/src/bin/casr-gdb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,15 @@ fn main() -> Result<()> {
.value_parser(clap::value_parser!(PathBuf))
.help("Stdin file for program"),
)
.arg(
Arg::new("timeout")
.short('t')
.long("timeout")
.action(ArgAction::Set)
.value_name("SECONDS")
.help("Timeout (in seconds) for target execution [default: disabled]")
.value_parser(clap::value_parser!(u64).range(1..))
)
.arg(
Arg::new("ignore")
.long("ignore")
Expand All @@ -84,6 +93,13 @@ fn main() -> Result<()> {
bail!("Wrong arguments for starting program");
};

// Get timeout
let timeout = if let Some(timeout) = matches.get_one::<u64>("timeout") {
*timeout
} else {
0
};

init_ignored_frames!("cpp", "rust");
if let Some(path) = matches.get_one::<PathBuf>("ignore") {
util::add_custom_ignored_frames(path)?;
Expand Down Expand Up @@ -161,6 +177,7 @@ fn main() -> Result<()> {
let exectype = ExecType::Local(argv.as_slice());
let mut gdb_command = GdbCommand::new(&exectype);
let gdb_command = gdb_command
.timeout(timeout)
.stdin(&stdin_file)
.r()
.bt()
Expand Down
22 changes: 18 additions & 4 deletions casr/src/bin/casr-java.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use libcasr::java::*;
use libcasr::report::CrashReport;
use libcasr::stacktrace::*;

use anyhow::{bail, Context, Result};
use anyhow::{bail, Result};
use clap::{Arg, ArgAction, ArgGroup};
use regex::Regex;
use std::path::PathBuf;
Expand Down Expand Up @@ -48,6 +48,15 @@ fn main() -> Result<()> {
.value_name("FILE")
.help("Stdin file for program"),
)
.arg(
Arg::new("timeout")
.short('t')
.long("timeout")
.action(ArgAction::Set)
.value_name("SECONDS")
.help("Timeout (in seconds) for target execution [default: disabled]")
.value_parser(clap::value_parser!(u64).range(1..))
)
.arg(
Arg::new("ignore")
.long("ignore")
Expand Down Expand Up @@ -90,6 +99,13 @@ fn main() -> Result<()> {
// Get stdin for target program.
let stdin_file = util::stdin_from_matches(&matches)?;

// Get timeout
let timeout = if let Some(timeout) = matches.get_one::<u64>("timeout") {
*timeout
} else {
0
};

// Run program.
let mut java_cmd = Command::new(argv[0]);
if let Some(ref file) = stdin_file {
Expand All @@ -98,9 +114,7 @@ fn main() -> Result<()> {
if argv.len() > 1 {
java_cmd.args(&argv[1..]);
}
let java_result = java_cmd
.output()
.with_context(|| "Couldn't run target program")?;
let java_result = util::get_output(&mut java_cmd, timeout, true)?;

let java_stderr = String::from_utf8_lossy(&java_result.stderr);

Expand Down
25 changes: 25 additions & 0 deletions casr/src/bin/casr-libfuzzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@ fn main() -> Result<()> {
.action(ArgAction::Set)
.help("Number of parallel jobs for generating CASR reports [default: half of cpu cores]")
.value_parser(clap::value_parser!(u32).range(1..)))
.arg(
Arg::new("timeout")
.short('t')
.long("timeout")
.action(ArgAction::Set)
.value_name("SECONDS")
.help("Timeout (in seconds) for target execution [default: disabled]")
.value_parser(clap::value_parser!(u64).range(1..))
)
.arg(
Arg::new("input")
.short('i')
Expand Down Expand Up @@ -83,8 +92,10 @@ fn main() -> Result<()> {
// Init log.
util::initialize_logging(&matches);

// Get input dir
let input_dir = matches.get_one::<PathBuf>("input").unwrap().as_path();

// Get output dir
let output_dir = matches.get_one::<PathBuf>("output").unwrap();
if !output_dir.exists() {
fs::create_dir_all(output_dir).with_context(|| {
Expand Down Expand Up @@ -134,6 +145,14 @@ fn main() -> Result<()> {
.filter(|(_, fname)| fname.starts_with("crash-") || fname.starts_with("leak-"))
.collect();

// Get timeout
let timeout = if let Some(timeout) = matches.get_one::<u64>("timeout") {
*timeout
} else {
0
};

// Get number of threads
let jobs = if let Some(jobs) = matches.get_one::<u32>("jobs") {
*jobs as usize
} else {
Expand All @@ -158,6 +177,9 @@ fn main() -> Result<()> {
custom_pool.install(|| {
crashes.par_iter().try_for_each(|(crash, fname)| {
let mut casr_cmd = Command::new(tool);
if timeout != 0 {
casr_cmd.args(["-t".to_string(), timeout.to_string()]);
}
casr_cmd.args([
"-o",
format!("{}.casrep", output_dir.join(fname).display()).as_str(),
Expand All @@ -170,9 +192,12 @@ fn main() -> Result<()> {
casr_cmd.args(argv.clone());
casr_cmd.arg(crash);
debug!("{:?}", casr_cmd);

// Get output
let casr_output = casr_cmd
.output()
.with_context(|| format!("Couldn't launch {casr_cmd:?}"))?;

if !casr_output.status.success() {
let err = String::from_utf8_lossy(&casr_output.stderr);
if err.contains("Program terminated (no crash)") {
Expand Down
22 changes: 18 additions & 4 deletions casr/src/bin/casr-python.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use libcasr::python::{PythonException, PythonStacktrace};
use libcasr::report::CrashReport;
use libcasr::stacktrace::*;

use anyhow::{bail, Context, Result};
use anyhow::{bail, Result};
use clap::{Arg, ArgAction, ArgGroup};
use regex::Regex;
use std::path::{Path, PathBuf};
Expand Down Expand Up @@ -47,6 +47,15 @@ fn main() -> Result<()> {
.value_name("FILE")
.help("Stdin file for program"),
)
.arg(
Arg::new("timeout")
.short('t')
.long("timeout")
.action(ArgAction::Set)
.value_name("SECONDS")
.help("Timeout (in seconds) for target execution [default: disabled]")
.value_parser(clap::value_parser!(u64).range(1..))
)
.arg(
Arg::new("ignore")
.long("ignore")
Expand Down Expand Up @@ -89,6 +98,13 @@ fn main() -> Result<()> {
// Get stdin for target program.
let stdin_file = util::stdin_from_matches(&matches)?;

// Get timeout
let timeout = if let Some(timeout) = matches.get_one::<u64>("timeout") {
*timeout
} else {
0
};

// Run program.
let mut python_cmd = Command::new(argv[0]);
if let Some(ref file) = stdin_file {
Expand All @@ -97,9 +113,7 @@ fn main() -> Result<()> {
if argv.len() > 1 {
python_cmd.args(&argv[1..]);
}
let python_result = python_cmd
.output()
.with_context(|| "Couldn't run target program")?;
let python_result = util::get_output(&mut python_cmd, timeout, true)?;

let python_stderr = String::from_utf8_lossy(&python_result.stderr);

Expand Down
Loading

0 comments on commit 0ca3fba

Please sign in to comment.