Skip to content
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

Add casr-msan #248

Closed
wants to merge 1 commit into from
Closed
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
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ CASR is maintained by:

CASR is a set of tools that allows you to collect crash reports in different
ways. Use `casr-core` binary to deal with coredumps. Use `casr-san` to analyze
ASAN reports or `casr-ubsan` to analyze UBSAN reports. Try `casr-gdb` to get
ASAN reports or `casr-msan` to analyze
MSAN reports or `casr-ubsan` to analyze UBSAN reports. Try `casr-gdb` to get
reports from gdb. Use `casr-python` to analyze python reports and get report
from [Atheris](https://github.com/google/atheris). Use `casr-java` to analyze
java reports and get report from
Expand Down Expand Up @@ -74,6 +75,7 @@ crashes.
It can analyze crashes from different sources:

* AddressSanitizer
* MemorySanitizer
* UndefinedBehaviorSanitizer
* Gdb output

Expand Down Expand Up @@ -145,6 +147,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 casr/tests/casr_tests/test_msan.cpp -o test_msan
$ casr-msan -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 @@ fn build_tree_report(
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);
}

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 @@ fn build_slider_report(
select.add_item("AsanReport", report.asan_report.join("\n"));
}

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

if !report.ubsan_report.is_empty() {
select.add_item("UbsanReport", report.ubsan_report.join("\n"));
}
Expand Down
290 changes: 290 additions & 0 deletions casr/src/bin/casr-msan.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
use casr::util;
Copy link
Collaborator

@SweetVishnya SweetVishnya Feb 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file looks like mostly a copy-paste of casr-san.rs. We may just add MemorySanitizer support to casr-san, as it does not state any exact sanitizer in its name.

use libcasr::{
msan::{MsanStacktrace, MsanContext},
constants::{
SIGINFO_SIGABRT, SIGINFO_SIGBUS, SIGINFO_SIGILL, SIGINFO_SIGSEGV, SIGINFO_SIGSYS,
SIGINFO_SIGTRAP,
},
cpp::CppException,
exception::Exception,
execution_class::*,
gdb::*,
init_ignored_frames,
report::CrashReport,
severity::Severity,
stacktrace::*,
};

use anyhow::{bail, Context, Result};
use clap::{Arg, ArgAction, ArgGroup};
use gdb_command::mappings::{MappedFiles, MappedFilesExt};
use gdb_command::stacktrace::StacktraceExt;
use gdb_command::*;
use regex::Regex;

use std::env;
use std::os::unix::process::{CommandExt, ExitStatusExt};
use std::path::PathBuf;
use std::process::Command;

fn main() -> Result<()> {
let matches = clap::Command::new("casr-msan")
.version(clap::crate_version!())
.about("Create CASR reports (.casrep) from MemorySanitizer reports")
.term_width(90)
.arg(
Arg::new("output")
.short('o')
.long("output")
.action(ArgAction::Set)
.value_name("REPORT")
.value_parser(clap::value_parser!(PathBuf))
.help(
"Path to save report. Path can be a directory, then report name is generated",
),
)
.arg(
Arg::new("stdout")
.action(ArgAction::SetTrue)
.long("stdout")
.help("Print CASR report to stdout"),
)
.group(
ArgGroup::new("out")
.args(["stdout", "output"])
.required(true),
)
.arg(
Arg::new("stdin")
.long("stdin")
.action(ArgAction::Set)
.value_name("FILE")
.value_parser(clap::value_parser!(PathBuf))
.help("Stdin file for program"),
)
.arg(
Arg::new("timeout")
.short('t')
.long("timeout")
.action(ArgAction::Set)
.default_value("0")
.value_name("SECONDS")
.help("Timeout (in seconds) for target execution, 0 value means that timeout is disabled")
.value_parser(clap::value_parser!(u64))
)
.arg(
Arg::new("ignore")
.long("ignore")
.action(ArgAction::Set)
.value_name("FILE")
.value_parser(clap::value_parser!(PathBuf))
.help("File with regular expressions for functions and file paths that should be ignored"),
)
.arg(
Arg::new("strip-path")
.long("strip-path")
.env("CASR_STRIP_PATH")
.action(ArgAction::Set)
.value_name("PREFIX")
.help("Path prefix to strip from stacktrace and crash line"),
)
.arg(
Arg::new("ARGS")
.action(ArgAction::Set)
.num_args(1..)
.last(true)
.required(true)
.help("Add \"-- ./binary <arguments>\" to run executable"),
)
.get_matches();

// Get program args.
let argv: Vec<&str> = if let Some(argvs) = matches.get_many::<String>("ARGS") {
argvs.map(|s| s.as_str()).collect()
} else {
bail!("Wrong arguments for starting program");
};

init_ignored_frames!("cpp");

if let Some(path) = matches.get_one::<PathBuf>("ignore") {
util::add_custom_ignored_frames(path)?;
}
// Get stdin for target program.
let stdin_file = util::stdin_from_matches(&matches)?;

// Get timeout
let timeout = *matches.get_one::<u64>("timeout").unwrap();

// Set rss limit.
if let Ok(msan_options_str) = env::var("MSAN_OPTIONS") {
let mut msan_options = msan_options_str.clone();
if !msan_options_str.contains("hard_rss_limit_mb") {
msan_options = [msan_options.as_str(), "hard_rss_limit_mb=2048"].join(",");
}
if msan_options.starts_with(',') {
msan_options.remove(0);
}
msan_options = msan_options.replace("symbolize=0", "symbolize=1");
unsafe {
std::env::set_var("MSAN_OPTIONS", msan_options);
}
} else {
unsafe {
std::env::set_var("MSAN_OPTIONS", "hard_rss_limit_mb=2048");
}
}

// Run program with sanitizers.
let mut sanitizers_cmd = Command::new(argv[0]);
if let Some(ref file) = stdin_file {
sanitizers_cmd.stdin(std::fs::File::open(file).unwrap());
}
if argv.len() > 1 {
sanitizers_cmd.args(&argv[1..]);
}
#[cfg(target_os = "macos")]
{
sanitizers_cmd.env("DYLD_NO_PIE", "1");
}
#[cfg(target_os = "linux")]
{
use linux_personality::{Personality, personality};

unsafe {
sanitizers_cmd.pre_exec(|| {
if personality(Personality::ADDR_NO_RANDOMIZE).is_err() {
panic!("Cannot set personality");
}
Ok(())
})
};
}
let sanitizers_result = util::get_output(&mut sanitizers_cmd, timeout, true)?;
let sanitizers_stderr = String::from_utf8_lossy(&sanitizers_result.stderr);

if sanitizers_stderr.contains("Cannot set personality") {
bail!("Cannot set personality (if you are running docker, allow personality syscall in your seccomp profile)");
}

// Create report.
let mut report = CrashReport::new();
report.executable_path = argv[0].to_string();
report.proc_cmdline = argv.join(" ");
let _ = report.add_os_info();
let _ = report.add_proc_environ();
if let Some(mut file_path) = stdin_file.clone() {
file_path = file_path.canonicalize().unwrap_or(file_path);
report.stdin = file_path.display().to_string();
}

let stacktrace: Stacktrace;

// Get MASAN report.
let msan_stderr_list: Vec<String> = sanitizers_stderr
.split('\n')
.map(|l| l.trim_end().to_string())
.collect();
let rmsan_start =
Regex::new(r"==\d+==\s*WARNING: MemorySanitizer:").unwrap();
if let Some(report_start) = msan_stderr_list
.iter()
.position(|line| rmsan_start.is_match(line))
{
// Set MASAN report in casr report.
let report_end = msan_stderr_list.iter().rposition(|s| !s.is_empty()).unwrap() + 1;
report.msan_report = Vec::from(&msan_stderr_list[report_start..report_end]);
let context = MsanContext(report.msan_report.clone());
let severity = context.severity();
if let Ok(severity) = severity {
report.execution_class = severity;
} else {
eprintln!("Couldn't estimate severity. {}", severity.err().unwrap());
}
report.stacktrace = MsanStacktrace::extract_stacktrace(&report.msan_report.join("\n"))?;
} else {
// Get termination signal.
if let Some(signal) = sanitizers_result.status.signal() {
// Get stack trace and mappings from gdb.
match signal as u32 {
SIGINFO_SIGILL | SIGINFO_SIGSYS => {
report.execution_class = ExecutionClass::find("BadInstruction").unwrap();
}
SIGINFO_SIGTRAP => {
report.execution_class = ExecutionClass::find("TrapSignal").unwrap();
}
SIGINFO_SIGABRT => {
report.execution_class = ExecutionClass::find("AbortSignal").unwrap();
}
SIGINFO_SIGBUS | SIGINFO_SIGSEGV => {
eprintln!("Segmentation fault occurred, but there is not enough information available to determine \
exploitability. Try using casr-gdb instead.");
report.execution_class = ExecutionClass::find("AccessViolation").unwrap();
}
_ => {
// "Undefined" is by default in report.
}
}

// Get stack trace and mappings from gdb.
let gdb_result = GdbCommand::new(&ExecType::Local(&argv))
.timeout(timeout)
.stdin(&stdin_file)
.r()
.bt()
.mappings()
.launch()
.with_context(|| "Unable to get results from gdb")?;

let frame = Regex::new(r"^ *#[0-9]+").unwrap();
report.stacktrace = gdb_result[0]
.split('\n')
.filter(|x| frame.is_match(x))
.map(|x| x.to_string())
.collect::<Vec<String>>();
report.proc_maps = gdb_result[1]
.split('\n')
.skip(4)
.map(|x| x.to_string())
.collect::<Vec<String>>();
} else {
// Normal termination.
bail!("Program terminated (no crash)");
}
}

// Get stacktrace to find crash line.
stacktrace = if !report.msan_report.is_empty() {
MsanStacktrace::parse_stacktrace(&report.stacktrace)?
} else {
let mut parsed_stacktrace = GdbStacktrace::parse_stacktrace(&report.stacktrace)?;
if let Ok(mfiles) = MappedFiles::from_gdb(report.proc_maps.join("\n")) {
parsed_stacktrace.compute_module_offsets(&mfiles);
}
parsed_stacktrace
};

// Check for exceptions
if let Some(class) = [CppException::parse_exception]
.iter()
.find_map(|parse| parse(&sanitizers_stderr))
{
report.execution_class = class;
}

// Get crash line.
if let Ok(crash_line) = stacktrace.crash_line() {
report.crashline = crash_line.to_string();
if let CrashLine::Source(debug) = crash_line {
if let Some(sources) = CrashReport::sources(&debug) {
report.source = sources;
}
}
}

if let Some(path) = matches.get_one::<String>("strip-path") {
util::strip_paths(&mut report, &stacktrace, path);
}

util::output_report(&report, &matches, &argv)
}
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");
}
}
Loading
Loading