Skip to content

Commit 356c9a9

Browse files
author
Pavel Nekrasov
committed
add casr-msan
1 parent 5c3d818 commit 356c9a9

File tree

10 files changed

+509
-4
lines changed

10 files changed

+509
-4
lines changed

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ CASR is maintained by:
2727

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

7677
* AddressSanitizer
78+
* MemorySanitizer
7779
* UndefinedBehaviorSanitizer
7880
* Gdb output
7981

@@ -145,6 +147,11 @@ Create report from AddressSanitizer output:
145147
$ clang++ -fsanitize=address -O0 -g casr/tests/casr_tests/test_asan_df.cpp -o test_asan_df
146148
$ casr-san -o asan.casrep -- ./test_asan_df
147149

150+
Create report from MemorySanitizer output:
151+
152+
$ clang++ -fsanitize=memory -O0 casr/tests/casr_tests/test_msan.cpp -o test_msan
153+
$ casr-msan -o msan.casrep -- ./test_msan
154+
148155
Create report from UndefinedBehaviorSanitizer output:
149156

150157
$ clang++ -fsanitize=undefined -O0 -g casr/tests/casr_tests/ubsan/test_ubsan.cpp -o test_ubsan

casr/src/bin/casr-cli.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,16 @@ fn build_tree_report(
411411
tree.expand_item(row);
412412
}
413413

414+
if !report.msan_report.is_empty() {
415+
row = tree
416+
.insert_container_item("MsanReport".to_string(), Placement::After, row)
417+
.unwrap();
418+
report.msan_report.iter().for_each(|e| {
419+
tree.insert_item(e.clone(), Placement::LastChild, row);
420+
});
421+
tree.expand_item(row);
422+
}
423+
414424
if !report.ubsan_report.is_empty() {
415425
row = tree
416426
.insert_container_item("UbsanReport".to_string(), Placement::After, row)
@@ -656,6 +666,10 @@ fn build_slider_report(
656666
select.add_item("AsanReport", report.asan_report.join("\n"));
657667
}
658668

669+
if !report.msan_report.is_empty() {
670+
select.add_item("MsanReport", report.msan_report.join("\n"));
671+
}
672+
659673
if !report.ubsan_report.is_empty() {
660674
select.add_item("UbsanReport", report.ubsan_report.join("\n"));
661675
}

casr/src/bin/casr-msan.rs

Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
use casr::util;
2+
use libcasr::{
3+
msan::{MsanStacktrace, MsanContext},
4+
constants::{
5+
SIGINFO_SIGABRT, SIGINFO_SIGBUS, SIGINFO_SIGILL, SIGINFO_SIGSEGV, SIGINFO_SIGSYS,
6+
SIGINFO_SIGTRAP,
7+
},
8+
cpp::CppException,
9+
exception::Exception,
10+
execution_class::*,
11+
gdb::*,
12+
init_ignored_frames,
13+
report::CrashReport,
14+
severity::Severity,
15+
stacktrace::*,
16+
};
17+
18+
use anyhow::{bail, Context, Result};
19+
use clap::{Arg, ArgAction, ArgGroup};
20+
use gdb_command::mappings::{MappedFiles, MappedFilesExt};
21+
use gdb_command::stacktrace::StacktraceExt;
22+
use gdb_command::*;
23+
use regex::Regex;
24+
25+
use std::env;
26+
use std::os::unix::process::{CommandExt, ExitStatusExt};
27+
use std::path::PathBuf;
28+
use std::process::Command;
29+
30+
fn main() -> Result<()> {
31+
let matches = clap::Command::new("casr-msan")
32+
.version(clap::crate_version!())
33+
.about("Create CASR reports (.casrep) from MemorySanitizer reports")
34+
.term_width(90)
35+
.arg(
36+
Arg::new("output")
37+
.short('o')
38+
.long("output")
39+
.action(ArgAction::Set)
40+
.value_name("REPORT")
41+
.value_parser(clap::value_parser!(PathBuf))
42+
.help(
43+
"Path to save report. Path can be a directory, then report name is generated",
44+
),
45+
)
46+
.arg(
47+
Arg::new("stdout")
48+
.action(ArgAction::SetTrue)
49+
.long("stdout")
50+
.help("Print CASR report to stdout"),
51+
)
52+
.group(
53+
ArgGroup::new("out")
54+
.args(["stdout", "output"])
55+
.required(true),
56+
)
57+
.arg(
58+
Arg::new("stdin")
59+
.long("stdin")
60+
.action(ArgAction::Set)
61+
.value_name("FILE")
62+
.value_parser(clap::value_parser!(PathBuf))
63+
.help("Stdin file for program"),
64+
)
65+
.arg(
66+
Arg::new("timeout")
67+
.short('t')
68+
.long("timeout")
69+
.action(ArgAction::Set)
70+
.default_value("0")
71+
.value_name("SECONDS")
72+
.help("Timeout (in seconds) for target execution, 0 value means that timeout is disabled")
73+
.value_parser(clap::value_parser!(u64))
74+
)
75+
.arg(
76+
Arg::new("ignore")
77+
.long("ignore")
78+
.action(ArgAction::Set)
79+
.value_name("FILE")
80+
.value_parser(clap::value_parser!(PathBuf))
81+
.help("File with regular expressions for functions and file paths that should be ignored"),
82+
)
83+
.arg(
84+
Arg::new("strip-path")
85+
.long("strip-path")
86+
.env("CASR_STRIP_PATH")
87+
.action(ArgAction::Set)
88+
.value_name("PREFIX")
89+
.help("Path prefix to strip from stacktrace and crash line"),
90+
)
91+
.arg(
92+
Arg::new("ARGS")
93+
.action(ArgAction::Set)
94+
.num_args(1..)
95+
.last(true)
96+
.required(true)
97+
.help("Add \"-- ./binary <arguments>\" to run executable"),
98+
)
99+
.get_matches();
100+
101+
// Get program args.
102+
let argv: Vec<&str> = if let Some(argvs) = matches.get_many::<String>("ARGS") {
103+
argvs.map(|s| s.as_str()).collect()
104+
} else {
105+
bail!("Wrong arguments for starting program");
106+
};
107+
108+
init_ignored_frames!("cpp");
109+
110+
if let Some(path) = matches.get_one::<PathBuf>("ignore") {
111+
util::add_custom_ignored_frames(path)?;
112+
}
113+
// Get stdin for target program.
114+
let stdin_file = util::stdin_from_matches(&matches)?;
115+
116+
// Get timeout
117+
let timeout = *matches.get_one::<u64>("timeout").unwrap();
118+
119+
// Set rss limit.
120+
if let Ok(msan_options_str) = env::var("MSAN_OPTIONS") {
121+
let mut msan_options = msan_options_str.clone();
122+
if !msan_options_str.contains("hard_rss_limit_mb") {
123+
msan_options = [msan_options.as_str(), "hard_rss_limit_mb=2048"].join(",");
124+
}
125+
if msan_options.starts_with(',') {
126+
msan_options.remove(0);
127+
}
128+
msan_options = msan_options.replace("symbolize=0", "symbolize=1");
129+
unsafe {
130+
std::env::set_var("MSAN_OPTIONS", msan_options);
131+
}
132+
} else {
133+
unsafe {
134+
std::env::set_var("MSAN_OPTIONS", "hard_rss_limit_mb=2048");
135+
}
136+
}
137+
138+
// Run program with sanitizers.
139+
let mut sanitizers_cmd = Command::new(argv[0]);
140+
if let Some(ref file) = stdin_file {
141+
sanitizers_cmd.stdin(std::fs::File::open(file).unwrap());
142+
}
143+
if argv.len() > 1 {
144+
sanitizers_cmd.args(&argv[1..]);
145+
}
146+
#[cfg(target_os = "macos")]
147+
{
148+
sanitizers_cmd.env("DYLD_NO_PIE", "1");
149+
}
150+
#[cfg(target_os = "linux")]
151+
{
152+
use linux_personality::{Personality, personality};
153+
154+
unsafe {
155+
sanitizers_cmd.pre_exec(|| {
156+
if personality(Personality::ADDR_NO_RANDOMIZE).is_err() {
157+
panic!("Cannot set personality");
158+
}
159+
Ok(())
160+
})
161+
};
162+
}
163+
let sanitizers_result = util::get_output(&mut sanitizers_cmd, timeout, true)?;
164+
let sanitizers_stderr = String::from_utf8_lossy(&sanitizers_result.stderr);
165+
166+
if sanitizers_stderr.contains("Cannot set personality") {
167+
bail!("Cannot set personality (if you are running docker, allow personality syscall in your seccomp profile)");
168+
}
169+
170+
// Create report.
171+
let mut report = CrashReport::new();
172+
report.executable_path = argv[0].to_string();
173+
report.proc_cmdline = argv.join(" ");
174+
let _ = report.add_os_info();
175+
let _ = report.add_proc_environ();
176+
if let Some(mut file_path) = stdin_file.clone() {
177+
file_path = file_path.canonicalize().unwrap_or(file_path);
178+
report.stdin = file_path.display().to_string();
179+
}
180+
181+
let stacktrace: Stacktrace;
182+
183+
// Get MASAN report.
184+
let msan_stderr_list: Vec<String> = sanitizers_stderr
185+
.split('\n')
186+
.map(|l| l.trim_end().to_string())
187+
.collect();
188+
let rmsan_start =
189+
Regex::new(r"==\d+==\s*WARNING: MemorySanitizer:").unwrap();
190+
if let Some(report_start) = msan_stderr_list
191+
.iter()
192+
.position(|line| rmsan_start.is_match(line))
193+
{
194+
// Set MASAN report in casr report.
195+
let report_end = msan_stderr_list.iter().rposition(|s| !s.is_empty()).unwrap() + 1;
196+
report.msan_report = Vec::from(&msan_stderr_list[report_start..report_end]);
197+
let context = MsanContext(report.msan_report.clone());
198+
let severity = context.severity();
199+
if let Ok(severity) = severity {
200+
report.execution_class = severity;
201+
} else {
202+
eprintln!("Couldn't estimate severity. {}", severity.err().unwrap());
203+
}
204+
report.stacktrace = MsanStacktrace::extract_stacktrace(&report.msan_report.join("\n"))?;
205+
} else {
206+
// Get termination signal.
207+
if let Some(signal) = sanitizers_result.status.signal() {
208+
// Get stack trace and mappings from gdb.
209+
match signal as u32 {
210+
SIGINFO_SIGILL | SIGINFO_SIGSYS => {
211+
report.execution_class = ExecutionClass::find("BadInstruction").unwrap();
212+
}
213+
SIGINFO_SIGTRAP => {
214+
report.execution_class = ExecutionClass::find("TrapSignal").unwrap();
215+
}
216+
SIGINFO_SIGABRT => {
217+
report.execution_class = ExecutionClass::find("AbortSignal").unwrap();
218+
}
219+
SIGINFO_SIGBUS | SIGINFO_SIGSEGV => {
220+
eprintln!("Segmentation fault occurred, but there is not enough information available to determine \
221+
exploitability. Try using casr-gdb instead.");
222+
report.execution_class = ExecutionClass::find("AccessViolation").unwrap();
223+
}
224+
_ => {
225+
// "Undefined" is by default in report.
226+
}
227+
}
228+
229+
// Get stack trace and mappings from gdb.
230+
let gdb_result = GdbCommand::new(&ExecType::Local(&argv))
231+
.timeout(timeout)
232+
.stdin(&stdin_file)
233+
.r()
234+
.bt()
235+
.mappings()
236+
.launch()
237+
.with_context(|| "Unable to get results from gdb")?;
238+
239+
let frame = Regex::new(r"^ *#[0-9]+").unwrap();
240+
report.stacktrace = gdb_result[0]
241+
.split('\n')
242+
.filter(|x| frame.is_match(x))
243+
.map(|x| x.to_string())
244+
.collect::<Vec<String>>();
245+
report.proc_maps = gdb_result[1]
246+
.split('\n')
247+
.skip(4)
248+
.map(|x| x.to_string())
249+
.collect::<Vec<String>>();
250+
} else {
251+
// Normal termination.
252+
bail!("Program terminated (no crash)");
253+
}
254+
}
255+
256+
// Get stacktrace to find crash line.
257+
stacktrace = if !report.msan_report.is_empty() {
258+
MsanStacktrace::parse_stacktrace(&report.stacktrace)?
259+
} else {
260+
let mut parsed_stacktrace = GdbStacktrace::parse_stacktrace(&report.stacktrace)?;
261+
if let Ok(mfiles) = MappedFiles::from_gdb(report.proc_maps.join("\n")) {
262+
parsed_stacktrace.compute_module_offsets(&mfiles);
263+
}
264+
parsed_stacktrace
265+
};
266+
267+
// Check for exceptions
268+
if let Some(class) = [CppException::parse_exception]
269+
.iter()
270+
.find_map(|parse| parse(&sanitizers_stderr))
271+
{
272+
report.execution_class = class;
273+
}
274+
275+
// Get crash line.
276+
if let Ok(crash_line) = stacktrace.crash_line() {
277+
report.crashline = crash_line.to_string();
278+
if let CrashLine::Source(debug) = crash_line {
279+
if let Some(sources) = CrashReport::sources(&debug) {
280+
report.source = sources;
281+
}
282+
}
283+
}
284+
285+
if let Some(path) = matches.get_one::<String>("strip-path") {
286+
util::strip_paths(&mut report, &stacktrace, path);
287+
}
288+
289+
util::output_report(&report, &matches, &argv)
290+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#include <stdio.h>
2+
3+
void set_val(bool &b, const int val) {
4+
if (val > 1) {
5+
b = false;
6+
}
7+
}
8+
9+
int main(const int argc, const char *[]) {
10+
bool b;
11+
set_val(b, argc);
12+
if (b) {
13+
printf("value set\n");
14+
}
15+
}

0 commit comments

Comments
 (0)