Skip to content

Add support MemorySanitizer for casr-san #249

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Feb 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ crashes.
It can analyze crashes from different sources:

* AddressSanitizer
* MemorySanitizer
* UndefinedBehaviorSanitizer
* Gdb output

Expand Down Expand Up @@ -145,6 +146,11 @@ Create report from AddressSanitizer output:
$ clang++ -fsanitize=address -O0 -g casr/tests/casr_tests/test_asan_df.cpp -o test_asan_df
$ casr-san -o asan.casrep -- ./test_asan_df

Create report from MemorySanitizer output:

$ clang++ -fsanitize=memory -O0 -g casr/tests/casr_tests/test_msan.cpp -o test_msan
$ casr-san -o msan.casrep -- ./test_msan

Create report from UndefinedBehaviorSanitizer output:

$ clang++ -fsanitize=undefined -O0 -g casr/tests/casr_tests/ubsan/test_ubsan.cpp -o test_ubsan
Expand Down
14 changes: 14 additions & 0 deletions casr/src/bin/casr-cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,16 @@
tree.expand_item(row);
}

if !report.msan_report.is_empty() {
row = tree
.insert_container_item("MsanReport".to_string(), Placement::After, row)
.unwrap();
report.msan_report.iter().for_each(|e| {
tree.insert_item(e.clone(), Placement::LastChild, row);
});
tree.expand_item(row);
}

Check warning on line 422 in casr/src/bin/casr-cli.rs

View check run for this annotation

Codecov / codecov/patch

casr/src/bin/casr-cli.rs#L414-L422

Added lines #L414 - L422 were not covered by tests

if !report.ubsan_report.is_empty() {
row = tree
.insert_container_item("UbsanReport".to_string(), Placement::After, row)
Expand Down Expand Up @@ -656,6 +666,10 @@
select.add_item("AsanReport", report.asan_report.join("\n"));
}

if !report.msan_report.is_empty() {
select.add_item("MsanReport", report.msan_report.join("\n"));
}

Check warning on line 671 in casr/src/bin/casr-cli.rs

View check run for this annotation

Codecov / codecov/patch

casr/src/bin/casr-cli.rs#L669-L671

Added lines #L669 - L671 were not covered by tests

if !report.ubsan_report.is_empty() {
select.add_item("UbsanReport", report.ubsan_report.join("\n"));
}
Expand Down
5 changes: 4 additions & 1 deletion casr/src/bin/casr-python.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,10 @@ fn main() -> Result<()> {
let python_stderr_list: Vec<String> =
python_stderr.split('\n').map(|l| l.to_string()).collect();

let re = Regex::new(r"==\d+==\s*ERROR: (LeakSanitizer|AddressSanitizer|libFuzzer):").unwrap();
let re = Regex::new(
r"==\d+==\s*(ERROR: (LeakSanitizer|AddressSanitizer|libFuzzer)|WARNING: MemorySanitizer): ",
)
.unwrap();
if python_stderr_list.iter().any(|line| re.is_match(line)) {
let python_stdout = String::from_utf8_lossy(&python_result.stdout);
let python_stdout_list: Vec<String> =
Expand Down
29 changes: 19 additions & 10 deletions casr/src/bin/casr-san.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,28 +211,37 @@
.map(|l| l.trim_end().to_string())
.collect();
} else {
// Get ASAN report.
// Get ASAN or MSAN report.
let san_stderr_list: Vec<String> = sanitizers_stderr
.split('\n')
.map(|l| l.trim_end().to_string())
.collect();
let rasan_start =
Regex::new(r"==\d+==\s*ERROR: (LeakSanitizer|AddressSanitizer|libFuzzer):").unwrap();
let rmsan_start = Regex::new(r"==\d+==\s*WARNING: MemorySanitizer:").unwrap();
if let Some(report_start) = san_stderr_list
.iter()
.position(|line| rasan_start.is_match(line))
.position(|line| rasan_start.is_match(line) || rmsan_start.is_match(line))
{
// Set ASAN report in casr report.
let report_end = san_stderr_list.iter().rposition(|s| !s.is_empty()).unwrap() + 1;
report.asan_report = Vec::from(&san_stderr_list[report_start..report_end]);
let context = AsanContext(report.asan_report.clone());
let severity = context.severity();
if let Ok(severity) = severity {
let report_slice = &san_stderr_list[report_start..report_end];

match rasan_start.is_match(&san_stderr_list[report_start]) {
true => report.asan_report = report_slice.to_vec(),
false => report.msan_report = report_slice.to_vec(),
}

let context = AsanContext(report_slice.to_vec());
if let Ok(severity) = context.severity() {
report.execution_class = severity;
} else {
eprintln!("Couldn't estimate severity. {}", severity.err().unwrap());
eprintln!(
"Couldn't estimate severity. {}",
context.severity().err().unwrap()
);

Check warning on line 241 in casr/src/bin/casr-san.rs

View check run for this annotation

Codecov / codecov/patch

casr/src/bin/casr-san.rs#L238-L241

Added lines #L238 - L241 were not covered by tests
}
report.stacktrace = AsanStacktrace::extract_stacktrace(&report.asan_report.join("\n"))?;

report.stacktrace = AsanStacktrace::extract_stacktrace(&report_slice.join("\n"))?;
} else {
// Get termination signal.
if let Some(signal) = sanitizers_result.status.signal() {
Expand Down Expand Up @@ -287,7 +296,7 @@
}

// Get stacktrace to find crash line.
stacktrace = if !report.asan_report.is_empty() {
stacktrace = if !report.asan_report.is_empty() || !report.msan_report.is_empty() {
AsanStacktrace::parse_stacktrace(&report.stacktrace)?
} else {
let mut parsed_stacktrace = GdbStacktrace::parse_stacktrace(&report.stacktrace)?;
Expand Down
15 changes: 15 additions & 0 deletions casr/tests/casr_tests/test_msan.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#include <stdio.h>

void set_val(bool &b, const int val) {
if (val > 1) {
b = false;
}
}

int main(const int argc, const char *[]) {
bool b;
set_val(b, argc);
if (b) {
printf("value set\n");
}
}
69 changes: 67 additions & 2 deletions casr/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3079,8 +3079,6 @@ fn test_casr_san() {
} else {
panic!("Couldn't parse json report file.");
}

let _ = std::fs::remove_file(&paths[1]);
// Test casr-san stdin
let paths = [
abs_path("tests/casr_tests/test_asan_stdin.cpp"),
Expand Down Expand Up @@ -3238,6 +3236,73 @@ fn test_casr_san() {
panic!("Couldn't parse json report file.");
}

#[test]
#[cfg(target_arch = "x86_64")]
fn test_casr_san_msan() {
let paths = [
abs_path("tests/casr_tests/test_msan.cpp"),
abs_path("tests/tmp_tests_casr/test_msan"),
];

let clang = Command::new("bash")
.arg("-c")
.arg(format!(
"clang++ -fsanitize=memory -O0 {} -o {}",
&paths[0], &paths[1]
))
.status()
.expect("failed to execute clang++");

assert!(clang.success());

let output = Command::new(*EXE_CASR_SAN)
.args(["--stdout", "--", &paths[1]])
.output()
.expect("failed to start casr-san");

assert!(
output.status.success(),
"Stdout: {}\n. Stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);

let report: Result<Value, _> = serde_json::from_slice(&output.stdout);
if let Ok(report) = report {
let severity_type = report["CrashSeverity"]["Type"].as_str().unwrap();
let severity_desc = report["CrashSeverity"]["ShortDescription"]
.as_str()
.unwrap()
.to_string();
let stacktrace = report["Stacktrace"]
.as_array()
.unwrap()
.iter()
.map(|x| x.to_string())
.collect::<Vec<String>>();

assert!(stacktrace.len() > 2);
assert!(stacktrace[0].contains("in main"));
assert_eq!(severity_type, "NOT_EXPLOITABLE");
assert_eq!(severity_desc, "use-of-uninitialized-value");
assert!(
report["CrashLine"]
.as_str()
.unwrap()
.eq("tests/casr_tests/test_msan.cpp:12:9")
// We build a test on ubuntu18 and run it on ubuntu20.
// Debug information is broken.
|| report["CrashLine"]
.as_str()
.unwrap()
.contains("test_msan+0x") // We can't hardcode the offset because we rebuild tests every time.
);
} else {
panic!("Couldn't parse json report file.");
}
let _ = std::fs::remove_file(&paths[1]);
}

#[test]
#[cfg(target_arch = "x86_64")]
fn test_casr_san_segf_near_null() {
Expand Down
1 change: 1 addition & 0 deletions docs/classes.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,4 @@ Lists of classes:
28. **out-of-memory**. The target has exceeded the memory limit.
29. **fuzz target exited**. Fuzz target exited.
30. **timeout**. Timeout after several seconds.
31. **use-of-uninitialized-value**. The target attempted to access memory that was not initialized.
2 changes: 2 additions & 0 deletions libcasr/src/asan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@ impl Severity for AsanContext {
}
if asan_report[0].contains("LeakSanitizer") {
ExecutionClass::find("memory-leaks")
} else if asan_report[0].contains("MemorySanitizer") {
ExecutionClass::find("use-of-uninitialized-value")
} else {
let summary =
Regex::new(r"SUMMARY: *(AddressSanitizer|libFuzzer): ([A-Za-z_\-\(\)]+)").unwrap();
Expand Down
8 changes: 7 additions & 1 deletion libcasr/src/execution_class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ pub struct ExecutionClass {
/// Instances of `ExecutionClass` structure.
/// Add new classes to the end of array.
/// TODO: Think about adding some ID for array element.
pub const CLASSES: &[(&str, &str, &str, &str); 74] = &[
pub const CLASSES: &[(&str, &str, &str, &str); 75] = &[
(
"EXPLOITABLE",
"SegFaultOnPc",
Expand Down Expand Up @@ -485,6 +485,12 @@ pub const CLASSES: &[(&str, &str, &str, &str); 74] = &[
"Attempt to overwrite constant input",
"Fuzz target overwrites its constant input.",
),
(
"NOT_EXPLOITABLE",
"use-of-uninitialized-value",
"Use of uninitialized value",
"The target attempted to access memory that was not initialized.",
),
];

impl ExecutionClass {
Expand Down
1 change: 1 addition & 0 deletions libcasr/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//! It can analyze crashes from different sources:
//!
//! * AddressSanitizer
//! * MemorySanitizer
//! * UndefinedBehaviorSanitizer
//! * Gdb output
//!
Expand Down
20 changes: 19 additions & 1 deletion libcasr/src/report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,13 @@ pub struct CrashReport {
)]
#[cfg_attr(feature = "serde", serde(default))]
pub asan_report: Vec<String>,
/// Msan report.
#[cfg_attr(
feature = "serde",
serde(rename(serialize = "MsanReport", deserialize = "MsanReport"))
)]
#[cfg_attr(feature = "serde", serde(default))]
pub msan_report: Vec<String>,
/// Ubsan report.
#[cfg_attr(
feature = "serde",
Expand Down Expand Up @@ -584,7 +591,7 @@ impl CrashReport {
/// Filter frames from the stack trace that are not related to analyzed code containing crash
/// and return it as `Stacktrace` struct
pub fn filtered_stacktrace(&self) -> Result<Stacktrace> {
let mut rawtrace = if !self.asan_report.is_empty() {
let mut rawtrace = if !self.asan_report.is_empty() || !self.msan_report.is_empty() {
AsanStacktrace::parse_stacktrace(&self.stacktrace)?
} else if !self.python_report.is_empty() {
PythonStacktrace::parse_stacktrace(&self.stacktrace)?
Expand Down Expand Up @@ -743,6 +750,12 @@ impl fmt::Display for CrashReport {
report += &(self.asan_report.join("\n") + "\n");
}

// MSANreport
if !self.msan_report.is_empty() {
report += "\n===MsanReport===\n";
report += &(self.msan_report.join("\n") + "\n");
}

// UBSANreport
if !self.ubsan_report.is_empty() {
report += "\n===UbsanReport===\n";
Expand Down Expand Up @@ -892,6 +905,8 @@ mod tests {
"==363912==ERROR: AddressSanitizer: SEGV on unknown address 0xffffffffffffffe0 (pc 0x0000004ca0e0 bp 0x7fffffff9980 sp 0x7fffffff9928 T0)".to_string(),
"==363912==The signal is caused by a READ memory access.".to_string(),
];
report.msan_report =
vec!["==26629==WARNING: MemorySanitizer: use-of-uninitialized-value".to_string()];
report.ubsan_report = vec![
"/home/hkctkuy/github/casr/casr/tests/tmp_tests_casr/test_casr_ubsan/test_ubsan.cpp:4:29: runtime error: signed integer overflow: 65535 * 32769 cannot be represented in type 'int'".to_string(),
"SUMMARY: UndefinedBehaviorSanitizer: signed-integer-overflow /home/hkctkuy/github/casr/casr/tests/tmp_tests_casr/test_casr_ubsan/test_ubsan.cpp:4:29 in".to_string(),
Expand Down Expand Up @@ -986,6 +1001,9 @@ mod tests {
"==363912==ERROR: AddressSanitizer: SEGV on unknown address 0xffffffffffffffe0 (pc 0x0000004ca0e0 bp 0x7fffffff9980 sp 0x7fffffff9928 T0)".to_string(),
"==363912==The signal is caused by a READ memory access.".to_string(),
"".to_string(),
"===MsanReport===".to_string(),
"==26629==WARNING: MemorySanitizer: use-of-uninitialized-value".to_string(),
"".to_string(),
"===UbsanReport===".to_string(),
"/home/hkctkuy/github/casr/casr/tests/tmp_tests_casr/test_casr_ubsan/test_ubsan.cpp:4:29: runtime error: signed integer overflow: 65535 * 32769 cannot be represented in type 'int'".to_string(),
"SUMMARY: UndefinedBehaviorSanitizer: signed-integer-overflow /home/hkctkuy/github/casr/casr/tests/tmp_tests_casr/test_casr_ubsan/test_ubsan.cpp:4:29 in".to_string(),
Expand Down
Loading