provide parsed call stack details asan logs (#591)

For a given entry in a call stack, this parses out the following: line, function name, function offset, source file name, source file line, module path, and module offset.

Additionally, this provides a code-generated libclusterfuzz port of the regular expressions used for stack minimization.

For an example of the minimization, instead of:
```json
[
"#0 0x56512a9c1418 in __sanitizer_print_stack_trace /b/s/w/ir/cache/builder/src/third_party/llvm/compiler-rt/lib/asan/asan_stack.cpp:86:3",
"#1 0x56512aaaa42d in fuzzer::PrintStackTrace() third_party/libFuzzer/src/FuzzerUtil.cpp:205:5",
"#2 0x56512aa6a85e in fuzzer::Fuzzer::CrashCallback() third_party/libFuzzer/src/FuzzerLoop.cpp:232:3",
"#3 0x56512aa6a7df in fuzzer::Fuzzer::StaticCrashSignalCallback() third_party/libFuzzer/src/FuzzerLoop.cpp:203:6",
"#4 0x56512aaab948 in fuzzer::CrashHandler(int, siginfo_t*, void*) third_party/libFuzzer/src/FuzzerUtilPosix.cpp:46:3",
"#5 0x7f1ee3f0188f  (/lib/x86_64-linux-gnu/libpthread.so.0+0x1288f)",
"#6 0x56512a9e5aa1 in Json::OurReader::parse(char const*, char const*, Json::Value&, bool) third_party/jsoncpp/source/src/lib_json/json_reader.cpp:1062:10",
"#7 0x56512a9eedb4 in Json::OurCharReader::parse(char const*, char const*, Json::Value*, std::__Cr::basic_string<char, std::__Cr::char_traits<char>, std::__Cr::allocator<char> >*) third_party/jsoncpp/source/src/lib_json/json_reader.cpp:1899:23",
"#8 0x56512a9e03a3 in LLVMFuzzerTestOneInput third_party/jsoncpp/fuzzers/json_fuzzer.cc:39:24",
"#9 0x56512aa6d0cf in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) third_party/libFuzzer/src/FuzzerLoop.cpp:556:15",
"#10 0x56512aa3b7da in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) third_party/libFuzzer/src/FuzzerDriver.cpp:292:6",
"#11 0x56512aa4108a in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) third_party/libFuzzer/src/FuzzerDriver.cpp:774:9","#12 0x56512aa821ac in main third_party/libFuzzer/src/FuzzerMain.cpp:19:10",
"#13 0x7f1ee3361b96 in __libc_start_main /build/glibc-OTsEL5/glibc-2.27/csu/../csu/libc-start.c:310",
]
```

The minimized call stack is:
```json
[
"Json::OurReader::parse(char const*, char const*, Json::Value&, bool)", 
"Json::OurCharReader::parse(char const*, char const*, Json::Value*, std::__Cr::basic_string<char, std::__Cr::char_traits<char>, std::__Cr::allocator<char> >*)",
"json_fuzzer.cc"
]
```

This also provides a naïve function name list, which comes close to Clusterfuzz's function identification.

This would result in:
```json
[
    "Json::OurReader::parse",
    "Json::OurCharReader::parse",
    "json_fuzzer.cc"
]
```

Lastly, for our `stack hash` functionality used by the crash reporting task, those now provide the ability to specify the number of frames to include when building the hash.
This commit is contained in:
bmc-msft
2021-03-18 13:25:12 -04:00
committed by GitHub
parent 1174162af9
commit 34b2a739cb
52 changed files with 3386 additions and 351 deletions

View File

@ -33,6 +33,7 @@ onefuzz = { path = "../onefuzz" }
storage-queue = { path = "../storage-queue" }
reqwest-retry = { path = "../reqwest-retry" }
onefuzz-telemetry = { path = "../onefuzz-telemetry" }
stacktrace-parser = { path = "../stacktrace-parser" }
path-absolutize = "3.0.6"
[dev-dependencies]

View File

@ -58,6 +58,7 @@ pub fn build_report_config(
check_retry_count,
check_queue,
crashes,
minimized_stack_depth: None,
input_queue,
no_repro,
reports,

View File

@ -50,6 +50,7 @@ pub fn build_report_config(
target_timeout,
check_retry_count,
check_fuzzer_help,
minimized_stack_depth: None,
input_queue,
check_queue,
crashes,

View File

@ -3,17 +3,17 @@
use anyhow::{Context, Result};
use futures::StreamExt;
use onefuzz::{asan::AsanLog, blob::BlobUrl, monitor::DirectoryMonitor, syncdir::SyncedDir};
use onefuzz::{blob::BlobUrl, monitor::DirectoryMonitor, syncdir::SyncedDir};
use onefuzz_telemetry::{
Event::{new_report, new_unable_to_reproduce, new_unique_report},
EventData,
};
use serde::{Deserialize, Serialize};
use stacktrace_parser::CrashLog;
use std::path::{Path, PathBuf};
use uuid::Uuid;
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Deserialize, Serialize, Default)]
pub struct CrashReport {
pub input_sha256: String,
@ -27,9 +27,18 @@ pub struct CrashReport {
pub crash_site: String,
pub call_stack: Vec<String>,
pub call_stack_sha256: String,
#[serde(default)]
pub minimized_stack: Vec<String>,
#[serde(default)]
pub minimized_stack_sha256: Option<String>,
#[serde(default)]
pub minimized_stack_function_names: Vec<String>,
#[serde(default)]
pub minimized_stack_function_names_sha256: Option<String>,
pub asan_log: Option<String>,
pub task_id: Uuid,
@ -123,24 +132,42 @@ impl From<BlobUrl> for InputBlob {
impl CrashReport {
pub fn new(
asan_log: AsanLog,
crash_log: CrashLog,
task_id: Uuid,
job_id: Uuid,
executable: impl Into<PathBuf>,
input_blob: Option<InputBlob>,
input_sha256: String,
minimized_stack_depth: Option<usize>,
) -> Self {
let call_stack_sha256 = crash_log.call_stack_sha256();
let minimized_stack_sha256 = if crash_log.minimized_stack.is_empty() {
None
} else {
Some(crash_log.minimized_stack_sha256(minimized_stack_depth))
};
let minimized_stack_function_names_sha256 =
if crash_log.minimized_stack_function_names.is_empty() {
None
} else {
Some(crash_log.minimized_stack_function_names_sha256(minimized_stack_depth))
};
Self {
input_sha256,
input_blob,
executable: executable.into(),
crash_type: asan_log.fault_type().into(),
crash_site: asan_log.summary().into(),
call_stack: asan_log.call_stack().to_vec(),
call_stack_sha256: asan_log.call_stack_sha256(),
asan_log: Some(asan_log.text().to_string()),
scariness_score: asan_log.scariness_score(),
scariness_description: asan_log.scariness_description().to_owned(),
crash_type: crash_log.fault_type,
crash_site: crash_log.summary,
call_stack_sha256,
minimized_stack: crash_log.minimized_stack,
minimized_stack_sha256,
minimized_stack_function_names: crash_log.minimized_stack_function_names,
minimized_stack_function_names_sha256,
call_stack: crash_log.call_stack,
asan_log: Some(crash_log.text),
scariness_score: crash_log.scariness_score,
scariness_description: crash_log.scariness_description,
task_id,
job_id,
}

View File

@ -47,6 +47,9 @@ pub struct Config {
#[serde(default = "default_bool_true")]
pub check_queue: bool,
#[serde(default)]
pub minimized_stack_depth: Option<usize>,
#[serde(flatten)]
pub common: CommonConfig,
}
@ -132,6 +135,7 @@ impl<'a> GenericReportProcessor<'a> {
&self.config.target_exe,
input_blob,
input_sha256,
self.config.minimized_stack_depth,
);
Ok(CrashTestResult::CrashReport(crash_report))
} else if let Some(crash) = test_report.crash {
@ -144,11 +148,9 @@ impl<'a> GenericReportProcessor<'a> {
crash_type: crash.crash_type,
crash_site: crash.crash_site,
call_stack_sha256,
asan_log: None,
scariness_score: None,
scariness_description: None,
task_id,
job_id,
..Default::default()
};
Ok(CrashTestResult::CrashReport(crash_report))

View File

@ -36,6 +36,9 @@ pub struct Config {
#[serde(default)]
pub check_retry_count: u64,
#[serde(default)]
pub minimized_stack_depth: Option<usize>,
#[serde(default = "default_bool_true")]
pub check_queue: bool,
@ -140,6 +143,7 @@ impl AsanProcessor {
&self.config.target_exe,
input_blob,
input_sha256,
self.config.minimized_stack_depth,
);
Ok(CrashTestResult::CrashReport(crash_report))
}