libafl/observers/
stacktrace.rs

1//! the ``StacktraceObserver`` looks up the stacktrace on the execution thread and computes a hash for it for dedupe
2
3#[cfg(feature = "casr")]
4use alloc::string::ToString;
5use alloc::{borrow::Cow, string::String, vec::Vec};
6use core::fmt::Debug;
7#[cfg(feature = "casr")]
8use core::hash::{Hash, Hasher};
9#[cfg(feature = "casr")]
10use std::collections::hash_map::DefaultHasher;
11use std::{
12    fs::{self, File},
13    io::Read,
14    path::Path,
15    process::ChildStderr,
16};
17
18use backtrace::Backtrace;
19use libafl_bolts::{Named, ownedref::OwnedRefMut};
20#[allow(unused_imports)] // expect breaks here for some reason
21#[cfg(feature = "casr")]
22use libcasr::{
23    asan::AsanStacktrace,
24    constants::{
25        STACK_FRAME_FILEPATH_IGNORE_REGEXES_CPP, STACK_FRAME_FILEPATH_IGNORE_REGEXES_GO,
26        STACK_FRAME_FILEPATH_IGNORE_REGEXES_JAVA, STACK_FRAME_FILEPATH_IGNORE_REGEXES_PYTHON,
27        STACK_FRAME_FILEPATH_IGNORE_REGEXES_RUST, STACK_FRAME_FUNCTION_IGNORE_REGEXES_CPP,
28        STACK_FRAME_FUNCTION_IGNORE_REGEXES_GO, STACK_FRAME_FUNCTION_IGNORE_REGEXES_JAVA,
29        STACK_FRAME_FUNCTION_IGNORE_REGEXES_PYTHON, STACK_FRAME_FUNCTION_IGNORE_REGEXES_RUST,
30    },
31    init_ignored_frames,
32    stacktrace::{
33        Filter, ParseStacktrace, STACK_FRAME_FILEPATH_IGNORE_REGEXES,
34        STACK_FRAME_FUNCTION_IGNORE_REGEXES, Stacktrace, StacktraceEntry,
35    },
36};
37#[cfg(not(feature = "casr"))]
38use regex::Regex;
39use serde::{Deserialize, Serialize};
40
41use super::ObserverWithHashField;
42use crate::{Error, executors::ExitKind, observers::Observer};
43
44#[cfg(not(feature = "casr"))]
45/// Collects the backtrace via [`Backtrace`] and [`Debug`]
46/// ([`Debug`] is currently used for dev purposes, symbols hash will be used eventually)
47#[must_use]
48pub fn collect_backtrace() -> u64 {
49    let b = Backtrace::new_unresolved();
50    if b.frames().is_empty() {
51        return 0;
52    }
53    let mut hash = 0;
54    for frame in &b.frames()[1..] {
55        hash ^= frame.ip() as u64;
56    }
57    // will use symbols later
58    // let trace = format!("{:?}", b);
59    // log::trace!("{}", trace);
60    // log::info!(
61    //     "backtrace collected with hash={} at pid={}",
62    //     hash,
63    //     std::process::id()
64    // );
65    hash
66}
67
68#[cfg(feature = "casr")]
69/// Collects the backtrace via [`Backtrace`]
70#[must_use]
71pub fn collect_backtrace() -> u64 {
72    let mut b = Backtrace::new_unresolved();
73    if b.frames().is_empty() {
74        return 0;
75    }
76    b.resolve();
77    let mut strace = Stacktrace::new();
78    for frame in &b.frames()[1..] {
79        let mut strace_entry = StacktraceEntry::default();
80        let symbols = frame.symbols();
81        if symbols.len() > 1 {
82            let symbol = &symbols[0];
83            if let Some(name) = symbol.name() {
84                strace_entry.function = name.as_str().map_or_else(String::new, str::to_string);
85            }
86            if let Some(file) = symbol.filename() {
87                strace_entry.debug.file = file.to_string_lossy().to_string();
88            }
89            strace_entry.debug.line = u64::from(symbol.lineno().unwrap_or(0));
90            strace_entry.debug.column = u64::from(symbol.colno().unwrap_or(0));
91        }
92        strace_entry.address = frame.ip() as u64;
93        strace.push(strace_entry);
94    }
95
96    strace.filter();
97    let mut s = DefaultHasher::new();
98    strace.hash(&mut s);
99    s.finish()
100}
101
102/// An enum encoding the types of harnesses
103#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
104pub enum HarnessType {
105    /// Harness type when the target is in the same process
106    InProcess,
107    /// Harness type when the target is a child process
108    Child,
109    /// Harness type with an external component filling the backtrace hash (e.g. `CrashBacktraceCollector` in `libafl_qemu`)
110    External,
111}
112
113/// An observer looking at the backtrace after the harness crashes
114#[derive(Serialize, Deserialize, Debug)]
115pub struct BacktraceObserver<'a> {
116    observer_name: Cow<'static, str>,
117    hash: OwnedRefMut<'a, Option<u64>>,
118    harness_type: HarnessType,
119}
120
121impl<'a> BacktraceObserver<'a> {
122    #[cfg(not(feature = "casr"))]
123    /// Creates a new [`BacktraceObserver`] with the given name.
124    #[must_use]
125    pub fn new<S>(
126        observer_name: S,
127        backtrace_hash: OwnedRefMut<'a, Option<u64>>,
128        harness_type: HarnessType,
129    ) -> Self
130    where
131        S: Into<Cow<'static, str>>,
132    {
133        Self {
134            observer_name: observer_name.into(),
135            hash: backtrace_hash,
136            harness_type,
137        }
138    }
139
140    #[cfg(feature = "casr")]
141    /// Creates a new [`BacktraceObserver`] with the given name.
142    #[must_use]
143    pub fn new<S>(
144        observer_name: S,
145        backtrace_hash: OwnedRefMut<'a, Option<u64>>,
146        harness_type: HarnessType,
147    ) -> Self
148    where
149        S: Into<Cow<'static, str>>,
150    {
151        init_ignored_frames!("rust", "cpp", "go");
152        Self {
153            observer_name: observer_name.into(),
154            hash: backtrace_hash,
155            harness_type,
156        }
157    }
158
159    /// Creates a new [`BacktraceObserver`] with the given name, owning a new `backtrace_hash` variable.
160    #[must_use]
161    pub fn owned<S>(observer_name: S, harness_type: HarnessType) -> Self
162    where
163        S: Into<Cow<'static, str>>,
164    {
165        Self::new(observer_name, OwnedRefMut::owned(None), harness_type)
166    }
167
168    /// Updates the hash value of this observer.
169    fn update_hash(&mut self, hash: u64) {
170        *self.hash.as_mut() = Some(hash);
171    }
172
173    /// Clears the current hash value (sets it to `None`)
174    fn clear_hash(&mut self) {
175        *self.hash.as_mut() = None;
176    }
177
178    /// Fill the hash value if the harness type is external
179    pub fn fill_external(&mut self, hash: u64, exit_kind: &ExitKind) {
180        if self.harness_type == HarnessType::External {
181            if *exit_kind == ExitKind::Crash {
182                self.update_hash(hash);
183            } else {
184                self.clear_hash();
185            }
186        }
187    }
188}
189
190impl ObserverWithHashField for BacktraceObserver<'_> {
191    /// Gets the hash value of this observer.
192    fn hash(&self) -> Option<u64> {
193        *self.hash.as_ref()
194    }
195}
196
197impl<I, S> Observer<I, S> for BacktraceObserver<'_> {
198    fn post_exec(&mut self, _state: &mut S, _input: &I, exit_kind: &ExitKind) -> Result<(), Error> {
199        if self.harness_type == HarnessType::InProcess {
200            if *exit_kind == ExitKind::Crash {
201                self.update_hash(collect_backtrace());
202            } else {
203                self.clear_hash();
204            }
205        }
206        Ok(())
207    }
208
209    fn post_exec_child(
210        &mut self,
211        state: &mut S,
212        input: &I,
213        exit_kind: &ExitKind,
214    ) -> Result<(), Error> {
215        self.post_exec(state, input, exit_kind)
216    }
217}
218
219impl Named for BacktraceObserver<'_> {
220    fn name(&self) -> &Cow<'static, str> {
221        &self.observer_name
222    }
223}
224
225/// static variable of ASAN log path
226pub static ASAN_LOG_PATH: &str = "./asanlog"; // TODO make it unique
227
228/// returns the recommended ASAN runtime flags to capture the backtrace correctly with `log_path` set
229#[must_use]
230pub fn get_asan_runtime_flags_with_log_path() -> String {
231    let mut flags = get_asan_runtime_flags();
232    flags.push_str(":log_path=");
233    flags.push_str(ASAN_LOG_PATH);
234    flags
235}
236
237/// returns the recommended ASAN runtime flags to capture the backtrace correctly
238#[must_use]
239pub fn get_asan_runtime_flags() -> String {
240    let flags = [
241        "exitcode=0",
242        "abort_on_error=1",
243        "handle_abort=1",
244        "handle_segv=1",
245        "handle_sigbus=1",
246        "handle_sigill=1",
247        "handle_sigfpe=1",
248    ];
249
250    flags.join(":")
251}
252
253/// An observer looking at the backtrace of target command using ASAN output. This observer is only compatible with a `ForkserverExecutor`.
254#[derive(Serialize, Deserialize, Debug, Clone)]
255pub struct AsanBacktraceObserver {
256    observer_name: Cow<'static, str>,
257    hash: Option<u64>,
258}
259
260impl AsanBacktraceObserver {
261    #[cfg(not(feature = "casr"))]
262    /// Creates a new [`BacktraceObserver`] with the given name.
263    #[must_use]
264    pub fn new<S>(observer_name: S) -> Self
265    where
266        S: Into<Cow<'static, str>>,
267    {
268        Self {
269            observer_name: observer_name.into(),
270            hash: None,
271        }
272    }
273
274    #[cfg(feature = "casr")]
275    /// Creates a new [`BacktraceObserver`] with the given name.
276    #[must_use]
277    pub fn new<S>(observer_name: S) -> Self
278    where
279        S: Into<Cow<'static, str>>,
280    {
281        init_ignored_frames!("rust", "cpp", "go");
282        Self {
283            observer_name: observer_name.into(),
284            hash: None,
285        }
286    }
287
288    /// read ASAN output from the child stderr and parse it.
289    pub fn parse_asan_output_from_childstderr(
290        &mut self,
291        stderr: &mut ChildStderr,
292    ) -> Result<(), Error> {
293        let mut buf = Vec::new();
294        stderr.read_to_end(&mut buf)?;
295        self.parse_asan_output(&String::from_utf8_lossy(&buf));
296        Ok(())
297    }
298
299    /// read ASAN output from the log file and parse it.
300    pub fn parse_asan_output_from_asan_log_file(&mut self, pid: i32) -> Result<(), Error> {
301        let log_path = format!("{ASAN_LOG_PATH}.{pid}");
302        let mut asan_output = File::open(Path::new(&log_path))?;
303
304        let mut buf = String::new();
305        asan_output.read_to_string(&mut buf)?;
306        fs::remove_file(&log_path)?;
307
308        self.parse_asan_output(&buf);
309        Ok(())
310    }
311
312    #[cfg(not(feature = "casr"))]
313    /// parse ASAN error output emited by the target command and compute the hash
314    pub fn parse_asan_output(&mut self, output: &str) {
315        let mut hash = 0;
316        let matcher = Regex::new("\\s*#[0-9]*\\s0x([0-9a-f]*)\\s.*").unwrap();
317        matcher.captures_iter(output).for_each(|m| {
318            let g = m.get(1).unwrap();
319            hash ^= u64::from_str_radix(g.as_str(), 16).unwrap();
320        });
321        self.update_hash(hash);
322    }
323
324    #[cfg(feature = "casr")]
325    /// parse ASAN error output emited by the target command and compute the hash
326    pub fn parse_asan_output(&mut self, output: &str) {
327        let mut hash = 0;
328        if let Ok(st_vec) = AsanStacktrace::extract_stacktrace(output) {
329            if let Ok(mut stacktrace) = AsanStacktrace::parse_stacktrace(&st_vec) {
330                stacktrace.filter();
331                let mut s = DefaultHasher::new();
332                stacktrace.hash(&mut s);
333                hash = s.finish();
334            }
335        }
336        self.update_hash(hash);
337    }
338
339    /// Updates the hash value of this observer.
340    fn update_hash(&mut self, hash: u64) {
341        self.hash = Some(hash);
342    }
343}
344
345impl ObserverWithHashField for AsanBacktraceObserver {
346    /// Gets the hash value of this observer.
347    fn hash(&self) -> Option<u64> {
348        self.hash
349    }
350}
351
352impl Default for AsanBacktraceObserver {
353    fn default() -> Self {
354        Self::new("AsanBacktraceObserver")
355    }
356}
357
358impl<I, S> Observer<I, S> for AsanBacktraceObserver {}
359
360impl Named for AsanBacktraceObserver {
361    fn name(&self) -> &Cow<'static, str> {
362        &self.observer_name
363    }
364}