Skip to content

Commit

Permalink
Fixes for sydr-fuzz
Browse files Browse the repository at this point in the history
  • Loading branch information
Avgor46 committed Sep 5, 2023
1 parent 0f1ea92 commit 39462c5
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 58 deletions.
6 changes: 3 additions & 3 deletions casr/src/bin/casr-afl.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use casr::util::{self, find_asan_symbol, generate_reports, CrashInfo};
use casr::util::{self, generate_reports, CrashInfo};

use anyhow::Result;
use clap::{
Expand Down Expand Up @@ -120,8 +120,8 @@ fn main() -> Result<()> {
.map(|x| x + 1);

if let Some(target) = crash_info.target_args.first() {
match find_asan_symbol(Path::new(target)) {
Ok(flag) => crash_info.is_asan = flag,
match util::symbols_list(Path::new(target)) {
Ok(list) => crash_info.is_asan = list.contains("__asan"),
Err(e) => {
error!("{e}");
continue;
Expand Down
36 changes: 22 additions & 14 deletions casr/src/bin/casr-cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ use clap::{Arg, ArgAction};
use colored::Colorize;
use cursive::event::EventTrigger;
use cursive::View;
use log::info;
use regex::Regex;
use serde_json::Value;
use simplelog::*;
use std::collections::{BTreeMap, HashSet};
use std::fs;
use std::fs::{File, OpenOptions};
Expand Down Expand Up @@ -758,6 +760,17 @@ fn print_summary(dir: &Path, unique_crash_line: bool) {
// Line and column regex
let line_column = Regex::new(r"\d+:\d+$").unwrap();

// Initialize log
let _ = TermLogger::init(
LevelFilter::Info,
ConfigBuilder::new()
.set_time_offset_to_local()
.unwrap()
.build(),
TerminalMode::Stderr,
ColorChoice::Auto,
);

// Return true when crash should be omitted in summary because it has
// non-unique crash line
let mut skip_crash = |line: &str| {
Expand Down Expand Up @@ -906,23 +919,23 @@ fn print_summary(dir: &Path, unique_crash_line: bool) {
continue;
}

println!("==> <{}>", filename.magenta());
info!("==> <{}>", filename.magenta());
for info in cluster_hash.values() {
if ubsan {
// /path/to/report.casrep: Description: crashline (path:line:column)
println!("{}: {}", info.0.last().unwrap(), info.0[0]);
info!("{}: {}", info.0.last().unwrap(), info.0[0]);
continue;
}
// Crash: /path/to/input or /path/to/report.casrep
println!("{}: {}", "Crash".green(), info.0.last().unwrap());
info!("{}: {}", "Crash".green(), info.0.last().unwrap());
// casrep: SeverityType: Description: crashline (path:line:column) or /path/to/report.casrep
println!(" {}", info.0[0]);
info!(" {}", info.0[0]);
if info.0.len() == 3 {
// gdb.casrep: SeverityType: Description: crashline (path:line:column) or /path/to/report.casrep
println!(" {}", info.0[1]);
info!(" {}", info.0[1]);
}
// Number of crashes with the same hash
println!(" Similar crashes: {}", info.1);
info!(" Similar crashes: {}", info.1);
}
let mut classes = String::new();
cluster_classes.iter().for_each(|(class, number)| {
Expand All @@ -933,22 +946,17 @@ fn print_summary(dir: &Path, unique_crash_line: bool) {
);
});
if !ubsan {
println!("Cluster summary ->{classes}");
info!("Cluster summary ->{classes}");
}
}
let mut classes = String::new();
casr_classes
.iter()
.for_each(|(class, number)| classes.push_str(format!(" {class}: {number}").as_str()));
if classes.is_empty() {
println!("{} -> {}", "SUMMARY".magenta(), "No crashes found".red());
info!("{} -> {}", "SUMMARY".magenta(), "No crashes found".red());
} else {
println!("{} ->{}", "SUMMARY".magenta(), classes);
}

if !corrupted_reports.is_empty() {
println!("{} reports were found:", "Corrupted".red());
corrupted_reports.iter().for_each(|x| println!("{x}"));
info!("{} ->{}", "SUMMARY".magenta(), classes);
}
}

Expand Down
17 changes: 12 additions & 5 deletions casr/src/bin/casr-libfuzzer.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use casr::util::{self, find_asan_symbol, generate_reports, CrashInfo};
use casr::util::{self, generate_reports, CrashInfo};

use anyhow::{bail, Result};
use clap::{
Expand Down Expand Up @@ -37,7 +37,7 @@ fn main() -> Result<()> {
.action(ArgAction::Set)
.value_name("SECONDS")
.help("Timeout (in seconds) for target execution [default: disabled]")
.value_parser(clap::value_parser!(u64).range(1..))
.value_parser(clap::value_parser!(u64).range(0..))
)
.arg(
Arg::new("input")
Expand Down Expand Up @@ -87,6 +87,7 @@ fn main() -> Result<()> {
.long("gdb")
.action(ArgAction::Set)
.num_args(1..)
.value_terminator(";")
.help("Specify casr-gdb target arguments to add casr reports for uninstrumented binary"),
)
.arg(
Expand Down Expand Up @@ -138,10 +139,16 @@ fn main() -> Result<()> {
"casr-python"
} else if argv[0].ends_with("jazzer") || argv[0].ends_with("java") {
"casr-java"
} else if find_asan_symbol(Path::new(argv[0]))? || matches.get_flag("san-force") {
"casr-san"
} else {
"casr-gdb"
let sym_list = util::symbols_list(Path::new(argv[0]))?;
if sym_list.contains("__asan")
|| sym_list.contains("runtime.go")
|| matches.get_flag("san-force")
{
"casr-san"
} else {
"casr-gdb"
}
};

let gdb_argv = matches
Expand Down
88 changes: 73 additions & 15 deletions casr/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use anyhow::{bail, Context, Result};
use clap::ArgMatches;
use log::{debug, error, info, warn};
use simplelog::*;
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use std::fs::{self, OpenOptions};
use std::io::Write;
use std::io::{BufRead, BufReader};
Expand Down Expand Up @@ -273,6 +273,8 @@ pub fn get_output(command: &mut Command, timeout: u64, error_on_timeout: bool) -
// Else get output
if timeout != 0 {
let mut child = command
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.with_context(|| "Failed to start command: {command:?}")?;
if child
Expand All @@ -295,7 +297,16 @@ pub fn get_output(command: &mut Command, timeout: u64, error_on_timeout: bool) -
}
}

pub fn initialize_dirs(matches: &clap::ArgMatches) -> Result<(&PathBuf, PathBuf, PathBuf)> {
/// Create output, timeout and oom directories
///
/// # Arguments
///
/// `matches` - tool argumnets
///
/// Return value
///
/// Paths to (output, oom, timeout) directories
fn initialize_dirs(matches: &clap::ArgMatches) -> Result<(&PathBuf, PathBuf, PathBuf)> {
// Get output dir
let output_dir = matches.get_one::<PathBuf>("output").unwrap();
if !output_dir.exists() {
Expand Down Expand Up @@ -337,9 +348,13 @@ impl<'a> CrashInfo {
///
/// # Arguments
///
/// * `tool` - tool that generates reports
///
/// * `output_dir` - save report to specified directory or use the same directory as crash
///
/// * `timeout` - target program timeout (in seconds)
///
/// * `envs` - environment variables for target
pub fn run_casr<T: Into<Option<&'a Path>>>(
&self,
tool: &str,
Expand All @@ -359,10 +374,6 @@ impl<'a> CrashInfo {
args.push(format!("{}.gdb.casrep", report_path.display()));
}

if self.at_index.is_none() {
args.push("--stdin".to_string());
args.push(self.path.to_str().unwrap().to_string());
}
args.push("--".to_string());
args.extend_from_slice(&self.target_args);
if let Some(at_index) = self.at_index {
Expand All @@ -372,9 +383,13 @@ impl<'a> CrashInfo {
if tool.eq("casr-python") {
args.insert(3, "python3".to_string());
}
if self.at_index.is_none() {
args.insert(0, self.path.to_str().unwrap().to_string());
args.insert(0, "--stdin".to_string());
}
if timeout != 0 {
args.insert(3, timeout.to_string());
args.insert(3, "-t".to_string());
args.insert(0, timeout.to_string());
args.insert(0, "-t".to_string());
}

let tool = if self.is_asan { tool } else { "casr-gdb" };
Expand Down Expand Up @@ -422,7 +437,7 @@ impl<'a> CrashInfo {
error!("Error occurred while copying the file: {:?}", self.path);
}
} else if err.contains("Program terminated (no crash)") {
warn!("{}: no crash on input {}", tool, self.path.display());
warn!("{}: No crash on input {}", tool, self.path.display());
} else {
error!("{} for input: {}", err.trim(), self.path.display());
}
Expand All @@ -432,6 +447,17 @@ impl<'a> CrashInfo {
}
}

/// Perfofm analysis
///
/// # Arguments
///
/// `matches` - tool arguments
///
/// `crashes` - set of crashes, specified as a CrashInfo structure
///
/// `tool` - tool that generates reports
///
/// `gdb_argv` - arguments for casr-gdb
pub fn generate_reports(
matches: &clap::ArgMatches,
crashes: &HashMap<String, CrashInfo>,
Expand Down Expand Up @@ -664,7 +690,7 @@ fn summarize_results(
let oom_dir = dir.join("oom");
let oom_cnt = fs::read_dir(&oom_dir).unwrap().count();
if oom_cnt != 0 {
println!(
info!(
"{} out of memory seeds are saved to {:?}",
oom_cnt, &oom_dir
);
Expand All @@ -676,14 +702,27 @@ fn summarize_results(
let timeout_dir = dir.join("timeout");
let timeout_cnt = fs::read_dir(&timeout_dir).unwrap().count();
if timeout_cnt != 0 {
println!(
info!(
"{} timeout seeds are saved to {:?}",
timeout_cnt, &timeout_dir
);
} else {
fs::remove_dir_all(&timeout_dir)?;
}

// Check bad reports.
if let Ok(err_dir) = fs::read_dir(dir.join("clerr")) {
warn!(
"{} corrupted reports are saved to {:?}",
err_dir
.filter_map(|x| x.ok())
.filter_map(|x| x.file_name().into_string().ok())
.filter(|x| x.ends_with("casrep") && !x.ends_with("gdb.casrep"))
.count(),
&dir.join("clerr")
);
}

Ok(())
}

Expand All @@ -692,6 +731,7 @@ fn summarize_results(
/// # Arguments
///
/// `dir` - directory with casr reports
///
/// `crashes` - crashes info
fn copy_crashes(dir: &Path, crashes: &HashMap<String, CrashInfo>) -> Result<()> {
for e in fs::read_dir(dir)?.flatten().map(|x| x.path()) {
Expand All @@ -712,13 +752,30 @@ fn copy_crashes(dir: &Path, crashes: &HashMap<String, CrashInfo>) -> Result<()>
Ok(())
}

pub fn find_asan_symbol(path: &Path) -> Result<bool> {
/// Method checks whether binary file contains predefined symbols.
///
/// # Arguments
///
/// * `path` - path to binary to check.
pub fn symbols_list(path: &Path) -> Result<HashSet<&str>> {
let mut found_symbols = HashSet::new();
if let Ok(buffer) = fs::read(path) {
if let Ok(elf) = goblin::elf::Elf::parse(&buffer) {
let symbols = [
"__asan",
"__ubsan",
"__tsan",
"__msan",
"__llvm_profile",
"runtime.go",
];
for sym in elf.syms.iter() {
if let Some(name) = elf.strtab.get_at(sym.st_name) {
if name.contains("__asan") {
return Ok(true);
for symbol in symbols.iter() {
if name.contains(symbol) {
found_symbols.insert(*symbol);
break;
}
}
}
}
Expand All @@ -728,5 +785,6 @@ pub fn find_asan_symbol(path: &Path) -> Result<bool> {
} else {
bail!("Couldn't read fuzz target binary: {}.", path.display());
}
Ok(false)

Ok(found_symbols)
}
38 changes: 17 additions & 21 deletions casr/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3660,23 +3660,21 @@ fn test_casr_libfuzzer() {
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let out = String::from_utf8_lossy(&output.stdout);
let err = String::from_utf8_lossy(&output.stderr);

assert!(!out.is_empty());
assert!(!err.is_empty());

assert!(err.contains("casr-san: no crash on input"));
assert!(out.contains("1 out of memory seeds are saved to"));
assert!(out.contains("EXPLOITABLE"));
assert!(out.contains("NOT_EXPLOITABLE"));
assert!(out.contains("PROBABLY_EXPLOITABLE"));
assert!(out.contains("heap-buffer-overflow(read)"));
assert!(out.contains("heap-buffer-overflow(write)"));
assert!(out.contains("DestAvNearNull"));
assert!(out.contains("xml::serialization"));
assert!(out.contains("AbortSignal"));
assert!(out.contains("compound_document.hpp:83"));
assert!(err.contains("casr-san: No crash on input"));
assert!(err.contains("1 out of memory seeds are saved to"));
assert!(err.contains("EXPLOITABLE"));
assert!(err.contains("NOT_EXPLOITABLE"));
assert!(err.contains("PROBABLY_EXPLOITABLE"));
assert!(err.contains("heap-buffer-overflow(read)"));
assert!(err.contains("heap-buffer-overflow(write)"));
assert!(err.contains("DestAvNearNull"));
assert!(err.contains("xml::serialization"));
assert!(err.contains("AbortSignal"));
assert!(err.contains("compound_document.hpp:83"));

let re = Regex::new(r"Number of reports after deduplication: (?P<unique>\d+)").unwrap();
let unique_cnt = re
Expand Down Expand Up @@ -3772,18 +3770,16 @@ fn test_casr_libfuzzer_atheris() {
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let out = String::from_utf8_lossy(&output.stdout);
let err = String::from_utf8_lossy(&output.stderr);

assert!(!out.is_empty());
assert!(!err.is_empty());

assert!(out.contains("NOT_EXPLOITABLE"));
assert!(!out.contains("PROBABLY_EXPLOITABLE"));
assert!(out.contains("KeyError"));
assert!(out.contains("TypeError"));
assert!(out.contains("resolver.py"));
assert!(out.contains("constructor.py"));
assert!(err.contains("NOT_EXPLOITABLE"));
assert!(!err.contains("PROBABLY_EXPLOITABLE"));
assert!(err.contains("KeyError"));
assert!(err.contains("TypeError"));
assert!(err.contains("resolver.py"));
assert!(err.contains("constructor.py"));

let re = Regex::new(r"Number of reports after deduplication: (?P<unique>\d+)").unwrap();
let unique_cnt = re
Expand Down

0 comments on commit 39462c5

Please sign in to comment.