-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Pavel Nekrasov
committed
Feb 24, 2025
1 parent
5c3d818
commit 356c9a9
Showing
10 changed files
with
509 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,290 @@ | ||
use casr::util; | ||
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); | ||
} | ||
} |
Oops, something went wrong.