CASR deduplication for StacktraceObservers (#1184)
* Implement CasrAsanBacktraceObserver for dedupe crashes using libCASR and ASAN reports. * Use casr observer with forkserver executor * Add casr deduplication for AsanBacktraceObserver * Add casr deduplication for BacktraceObserver * Add Stacktrace filtering * Move init_ignored_frames to constructors * Add go ignore regexps for BacktraceObservers --------- Co-authored-by: Dongjia "toka" Zhang <tokazerkje@outlook.com>
This commit is contained in:
parent
ccd1211cd2
commit
1bd21509af
@ -1,6 +1,6 @@
|
|||||||
# Backtrace baby fuzzers
|
# Backtrace baby fuzzers
|
||||||
|
|
||||||
The projects contained in this directory are simple fuzzers derived from the original baby_fuzzer examples, whose purpose is to show how to use a `BacktraceObserver` or an `ASANObserver` to dedupe crashes and other necessary components for this feature.
|
The projects contained in this directory are simple fuzzers derived from the original baby_fuzzer examples, whose purpose is to show how to use a `BacktraceObserver` or an `ASANObserver` to dedupe crashes and other necessary components for this feature. To use `casr` deduplication for `BacktraceObserver` or `ASANObserver` build LibAFL with `casr` feature.
|
||||||
|
|
||||||
The examples cover:
|
The examples cover:
|
||||||
|
|
||||||
|
@ -32,6 +32,7 @@ cmin = ["z3"] # corpus minimisation
|
|||||||
corpus_btreemap = [] # Switches from HashMap to BTreeMap for CorpusId
|
corpus_btreemap = [] # Switches from HashMap to BTreeMap for CorpusId
|
||||||
gzip = ["miniz_oxide"] # Enables gzip compression in certain parts of the lib
|
gzip = ["miniz_oxide"] # Enables gzip compression in certain parts of the lib
|
||||||
regex = ["std", "dep:regex"] # enables the NaiveTokenizer and StacktraceObserver
|
regex = ["std", "dep:regex"] # enables the NaiveTokenizer and StacktraceObserver
|
||||||
|
casr = ["libcasr", "std", "regex"] # enables deduplication based on libcasr for StacktraceObserver
|
||||||
|
|
||||||
# features hiding dependencies licensed under GPL
|
# features hiding dependencies licensed under GPL
|
||||||
gpl = []
|
gpl = []
|
||||||
@ -103,6 +104,7 @@ z3 = { version = "0.11", features = ["static-link-z3"], optional = true } # for
|
|||||||
pyo3 = { version = "0.17", optional = true, features = ["serde", "macros"] }
|
pyo3 = { version = "0.17", optional = true, features = ["serde", "macros"] }
|
||||||
concat-idents = { version = "1.1.3", optional = true }
|
concat-idents = { version = "1.1.3", optional = true }
|
||||||
|
|
||||||
|
libcasr = { version = "2.5", optional = true}
|
||||||
|
|
||||||
# AGPL
|
# AGPL
|
||||||
# !!! this create requires nightly
|
# !!! this create requires nightly
|
||||||
|
@ -1,6 +1,14 @@
|
|||||||
//! the ``StacktraceObserver`` looks up the stacktrace on the execution thread and computes a hash for it for dedupe
|
//! the ``StacktraceObserver`` looks up the stacktrace on the execution thread and computes a hash for it for dedupe
|
||||||
|
|
||||||
use alloc::string::{String, ToString};
|
use alloc::{
|
||||||
|
string::{String, ToString},
|
||||||
|
vec::Vec,
|
||||||
|
};
|
||||||
|
#[cfg(feature = "casr")]
|
||||||
|
use std::{
|
||||||
|
collections::hash_map::DefaultHasher,
|
||||||
|
hash::{Hash, Hasher},
|
||||||
|
};
|
||||||
use std::{
|
use std::{
|
||||||
fmt::Debug,
|
fmt::Debug,
|
||||||
fs::{self, File},
|
fs::{self, File},
|
||||||
@ -10,6 +18,22 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use backtrace::Backtrace;
|
use backtrace::Backtrace;
|
||||||
|
#[cfg(feature = "casr")]
|
||||||
|
use libcasr::{
|
||||||
|
asan::AsanStacktrace,
|
||||||
|
constants::{
|
||||||
|
STACK_FRAME_FILEPATH_IGNORE_REGEXES_CPP, STACK_FRAME_FILEPATH_IGNORE_REGEXES_GO,
|
||||||
|
STACK_FRAME_FILEPATH_IGNORE_REGEXES_PYTHON, STACK_FRAME_FILEPATH_IGNORE_REGEXES_RUST,
|
||||||
|
STACK_FRAME_FUNCTION_IGNORE_REGEXES_CPP, STACK_FRAME_FUNCTION_IGNORE_REGEXES_GO,
|
||||||
|
STACK_FRAME_FUNCTION_IGNORE_REGEXES_PYTHON, STACK_FRAME_FUNCTION_IGNORE_REGEXES_RUST,
|
||||||
|
},
|
||||||
|
init_ignored_frames,
|
||||||
|
stacktrace::{
|
||||||
|
Filter, ParseStacktrace, Stacktrace, StacktraceEntry, STACK_FRAME_FILEPATH_IGNORE_REGEXES,
|
||||||
|
STACK_FRAME_FUNCTION_IGNORE_REGEXES,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
#[cfg(not(feature = "casr"))]
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@ -22,6 +46,7 @@ use crate::{
|
|||||||
Error,
|
Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(not(feature = "casr"))]
|
||||||
/// Collects the backtrace via [`Backtrace`] and [`Debug`]
|
/// Collects the backtrace via [`Backtrace`] and [`Debug`]
|
||||||
/// ([`Debug`] is currently used for dev purposes, symbols hash will be used eventually)
|
/// ([`Debug`] is currently used for dev purposes, symbols hash will be used eventually)
|
||||||
#[must_use]
|
#[must_use]
|
||||||
@ -45,6 +70,40 @@ pub fn collect_backtrace() -> u64 {
|
|||||||
hash
|
hash
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "casr")]
|
||||||
|
/// Collects the backtrace via [`Backtrace`]
|
||||||
|
#[must_use]
|
||||||
|
pub fn collect_backtrace() -> u64 {
|
||||||
|
let mut b = Backtrace::new_unresolved();
|
||||||
|
if b.frames().is_empty() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
b.resolve();
|
||||||
|
let mut strace = Stacktrace::new();
|
||||||
|
for frame in &b.frames()[1..] {
|
||||||
|
let mut strace_entry = StacktraceEntry::default();
|
||||||
|
let symbols = frame.symbols();
|
||||||
|
if symbols.len() > 1 {
|
||||||
|
let symbol = &symbols[0];
|
||||||
|
if let Some(name) = symbol.name() {
|
||||||
|
strace_entry.function = name.as_str().unwrap_or("").to_string();
|
||||||
|
}
|
||||||
|
if let Some(file) = symbol.filename() {
|
||||||
|
strace_entry.debug.file = file.to_str().unwrap_or("").to_string();
|
||||||
|
}
|
||||||
|
strace_entry.debug.line = u64::from(symbol.lineno().unwrap_or(0));
|
||||||
|
strace_entry.debug.column = u64::from(symbol.colno().unwrap_or(0));
|
||||||
|
}
|
||||||
|
strace_entry.address = frame.ip() as u64;
|
||||||
|
strace.push(strace_entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
strace.filter();
|
||||||
|
let mut s = DefaultHasher::new();
|
||||||
|
strace.hash(&mut s);
|
||||||
|
s.finish()
|
||||||
|
}
|
||||||
|
|
||||||
/// An enum encoding the types of harnesses
|
/// An enum encoding the types of harnesses
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum HarnessType {
|
pub enum HarnessType {
|
||||||
@ -66,6 +125,7 @@ pub struct BacktraceObserver<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> BacktraceObserver<'a> {
|
impl<'a> BacktraceObserver<'a> {
|
||||||
|
#[cfg(not(feature = "casr"))]
|
||||||
/// Creates a new [`BacktraceObserver`] with the given name.
|
/// Creates a new [`BacktraceObserver`] with the given name.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
@ -80,6 +140,22 @@ impl<'a> BacktraceObserver<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "casr")]
|
||||||
|
/// Creates a new [`BacktraceObserver`] with the given name.
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(
|
||||||
|
observer_name: &str,
|
||||||
|
backtrace_hash: &'a mut Option<u64>,
|
||||||
|
harness_type: HarnessType,
|
||||||
|
) -> Self {
|
||||||
|
init_ignored_frames!("rust", "cpp", "go");
|
||||||
|
Self {
|
||||||
|
observer_name: observer_name.to_string(),
|
||||||
|
hash: OwnedRefMut::Ref(backtrace_hash),
|
||||||
|
harness_type,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Updates the hash value of this observer.
|
/// Updates the hash value of this observer.
|
||||||
fn update_hash(&mut self, hash: u64) {
|
fn update_hash(&mut self, hash: u64) {
|
||||||
*self.hash.as_mut() = Some(hash);
|
*self.hash.as_mut() = Some(hash);
|
||||||
@ -189,6 +265,7 @@ pub struct AsanBacktraceObserver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AsanBacktraceObserver {
|
impl AsanBacktraceObserver {
|
||||||
|
#[cfg(not(feature = "casr"))]
|
||||||
/// Creates a new [`BacktraceObserver`] with the given name.
|
/// Creates a new [`BacktraceObserver`] with the given name.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new(observer_name: &str) -> Self {
|
pub fn new(observer_name: &str) -> Self {
|
||||||
@ -198,14 +275,25 @@ impl AsanBacktraceObserver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "casr")]
|
||||||
|
/// Creates a new [`BacktraceObserver`] with the given name.
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(observer_name: &str) -> Self {
|
||||||
|
init_ignored_frames!("rust", "cpp", "go");
|
||||||
|
Self {
|
||||||
|
observer_name: observer_name.to_string(),
|
||||||
|
hash: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// read ASAN output from the child stderr and parse it.
|
/// read ASAN output from the child stderr and parse it.
|
||||||
pub fn parse_asan_output_from_childstderr(
|
pub fn parse_asan_output_from_childstderr(
|
||||||
&mut self,
|
&mut self,
|
||||||
stderr: &mut ChildStderr,
|
stderr: &mut ChildStderr,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let mut buf = String::new();
|
let mut buf = Vec::new();
|
||||||
stderr.read_to_string(&mut buf)?;
|
stderr.read_to_end(&mut buf)?;
|
||||||
self.parse_asan_output(&buf);
|
self.parse_asan_output(&String::from_utf8_lossy(&buf));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,6 +310,7 @@ impl AsanBacktraceObserver {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "casr"))]
|
||||||
/// parse ASAN error output emited by the target command and compute the hash
|
/// parse ASAN error output emited by the target command and compute the hash
|
||||||
pub fn parse_asan_output(&mut self, output: &str) {
|
pub fn parse_asan_output(&mut self, output: &str) {
|
||||||
let mut hash = 0;
|
let mut hash = 0;
|
||||||
@ -233,6 +322,21 @@ impl AsanBacktraceObserver {
|
|||||||
self.update_hash(hash);
|
self.update_hash(hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "casr")]
|
||||||
|
/// parse ASAN error output emited by the target command and compute the hash
|
||||||
|
pub fn parse_asan_output(&mut self, output: &str) {
|
||||||
|
let mut hash = 0;
|
||||||
|
if let Ok(st_vec) = AsanStacktrace::extract_stacktrace(output) {
|
||||||
|
if let Ok(mut stacktrace) = AsanStacktrace::parse_stacktrace(&st_vec) {
|
||||||
|
stacktrace.filter();
|
||||||
|
let mut s = DefaultHasher::new();
|
||||||
|
stacktrace.hash(&mut s);
|
||||||
|
hash = s.finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.update_hash(hash);
|
||||||
|
}
|
||||||
|
|
||||||
/// Updates the hash value of this observer.
|
/// Updates the hash value of this observer.
|
||||||
fn update_hash(&mut self, hash: u64) {
|
fn update_hash(&mut self, hash: u64) {
|
||||||
self.hash = Some(hash);
|
self.hash = Some(hash);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user