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+ }
0 commit comments