#[cfg(feature = "casr")]
use alloc::string::ToString;
use alloc::{borrow::Cow, string::String, vec::Vec};
use core::fmt::Debug;
#[cfg(feature = "casr")]
use core::hash::{Hash, Hasher};
#[cfg(feature = "casr")]
use std::collections::hash_map::DefaultHasher;
use std::{
fs::{self, File},
io::Read,
path::Path,
process::ChildStderr,
};
use backtrace::Backtrace;
use libafl_bolts::{Named, ownedref::OwnedRefMut};
#[allow(unused_imports)] #[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_JAVA, 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_JAVA,
STACK_FRAME_FUNCTION_IGNORE_REGEXES_PYTHON, STACK_FRAME_FUNCTION_IGNORE_REGEXES_RUST,
},
init_ignored_frames,
stacktrace::{
Filter, ParseStacktrace, STACK_FRAME_FILEPATH_IGNORE_REGEXES,
STACK_FRAME_FUNCTION_IGNORE_REGEXES, Stacktrace, StacktraceEntry,
},
};
#[cfg(not(feature = "casr"))]
use regex::Regex;
use serde::{Deserialize, Serialize};
use super::ObserverWithHashField;
use crate::{Error, executors::ExitKind, observers::Observer};
#[cfg(not(feature = "casr"))]
#[must_use]
pub fn collect_backtrace() -> u64 {
let b = Backtrace::new_unresolved();
if b.frames().is_empty() {
return 0;
}
let mut hash = 0;
for frame in &b.frames()[1..] {
hash ^= frame.ip() as u64;
}
hash
}
#[cfg(feature = "casr")]
#[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().map_or_else(String::new, str::to_string);
}
if let Some(file) = symbol.filename() {
strace_entry.debug.file = file.to_string_lossy().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()
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub enum HarnessType {
InProcess,
Child,
External,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct BacktraceObserver<'a> {
observer_name: Cow<'static, str>,
hash: OwnedRefMut<'a, Option<u64>>,
harness_type: HarnessType,
}
impl<'a> BacktraceObserver<'a> {
#[cfg(not(feature = "casr"))]
#[must_use]
pub fn new<S>(
observer_name: S,
backtrace_hash: OwnedRefMut<'a, Option<u64>>,
harness_type: HarnessType,
) -> Self
where
S: Into<Cow<'static, str>>,
{
Self {
observer_name: observer_name.into(),
hash: backtrace_hash,
harness_type,
}
}
#[cfg(feature = "casr")]
#[must_use]
pub fn new<S>(
observer_name: S,
backtrace_hash: OwnedRefMut<'a, Option<u64>>,
harness_type: HarnessType,
) -> Self
where
S: Into<Cow<'static, str>>,
{
init_ignored_frames!("rust", "cpp", "go");
Self {
observer_name: observer_name.into(),
hash: backtrace_hash,
harness_type,
}
}
#[must_use]
pub fn owned<S>(observer_name: S, harness_type: HarnessType) -> Self
where
S: Into<Cow<'static, str>>,
{
Self::new(observer_name, OwnedRefMut::owned(None), harness_type)
}
fn update_hash(&mut self, hash: u64) {
*self.hash.as_mut() = Some(hash);
}
fn clear_hash(&mut self) {
*self.hash.as_mut() = None;
}
pub fn fill_external(&mut self, hash: u64, exit_kind: &ExitKind) {
if self.harness_type == HarnessType::External {
if *exit_kind == ExitKind::Crash {
self.update_hash(hash);
} else {
self.clear_hash();
}
}
}
}
impl ObserverWithHashField for BacktraceObserver<'_> {
fn hash(&self) -> Option<u64> {
*self.hash.as_ref()
}
}
impl<I, S> Observer<I, S> for BacktraceObserver<'_> {
fn post_exec(&mut self, _state: &mut S, _input: &I, exit_kind: &ExitKind) -> Result<(), Error> {
if self.harness_type == HarnessType::InProcess {
if *exit_kind == ExitKind::Crash {
self.update_hash(collect_backtrace());
} else {
self.clear_hash();
}
}
Ok(())
}
fn post_exec_child(
&mut self,
state: &mut S,
input: &I,
exit_kind: &ExitKind,
) -> Result<(), Error> {
self.post_exec(state, input, exit_kind)
}
}
impl Named for BacktraceObserver<'_> {
fn name(&self) -> &Cow<'static, str> {
&self.observer_name
}
}
pub static ASAN_LOG_PATH: &str = "./asanlog";
#[must_use]
pub fn get_asan_runtime_flags_with_log_path() -> String {
let mut flags = get_asan_runtime_flags();
flags.push_str(":log_path=");
flags.push_str(ASAN_LOG_PATH);
flags
}
#[must_use]
pub fn get_asan_runtime_flags() -> String {
let flags = [
"exitcode=0",
"abort_on_error=1",
"handle_abort=1",
"handle_segv=1",
"handle_sigbus=1",
"handle_sigill=1",
"handle_sigfpe=1",
];
flags.join(":")
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct AsanBacktraceObserver {
observer_name: Cow<'static, str>,
hash: Option<u64>,
}
impl AsanBacktraceObserver {
#[cfg(not(feature = "casr"))]
#[must_use]
pub fn new<S>(observer_name: S) -> Self
where
S: Into<Cow<'static, str>>,
{
Self {
observer_name: observer_name.into(),
hash: None,
}
}
#[cfg(feature = "casr")]
#[must_use]
pub fn new<S>(observer_name: S) -> Self
where
S: Into<Cow<'static, str>>,
{
init_ignored_frames!("rust", "cpp", "go");
Self {
observer_name: observer_name.into(),
hash: None,
}
}
pub fn parse_asan_output_from_childstderr(
&mut self,
stderr: &mut ChildStderr,
) -> Result<(), Error> {
let mut buf = Vec::new();
stderr.read_to_end(&mut buf)?;
self.parse_asan_output(&String::from_utf8_lossy(&buf));
Ok(())
}
pub fn parse_asan_output_from_asan_log_file(&mut self, pid: i32) -> Result<(), Error> {
let log_path = format!("{ASAN_LOG_PATH}.{pid}");
let mut asan_output = File::open(Path::new(&log_path))?;
let mut buf = String::new();
asan_output.read_to_string(&mut buf)?;
fs::remove_file(&log_path)?;
self.parse_asan_output(&buf);
Ok(())
}
#[cfg(not(feature = "casr"))]
pub fn parse_asan_output(&mut self, output: &str) {
let mut hash = 0;
let matcher = Regex::new("\\s*#[0-9]*\\s0x([0-9a-f]*)\\s.*").unwrap();
matcher.captures_iter(output).for_each(|m| {
let g = m.get(1).unwrap();
hash ^= u64::from_str_radix(g.as_str(), 16).unwrap();
});
self.update_hash(hash);
}
#[cfg(feature = "casr")]
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);
}
fn update_hash(&mut self, hash: u64) {
self.hash = Some(hash);
}
}
impl ObserverWithHashField for AsanBacktraceObserver {
fn hash(&self) -> Option<u64> {
self.hash
}
}
impl Default for AsanBacktraceObserver {
fn default() -> Self {
Self::new("AsanBacktraceObserver")
}
}
impl<I, S> Observer<I, S> for AsanBacktraceObserver {}
impl Named for AsanBacktraceObserver {
fn name(&self) -> &Cow<'static, str> {
&self.observer_name
}
}