1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4 
5 extern crate ini;
6 extern crate winapi;
7 
8 use ini::Ini;
9 use libc::time;
10 use serde::Serialize;
11 use serde_json::ser::to_writer;
12 use std::convert::TryInto;
13 use std::ffi::OsString;
14 use std::fs::{read_to_string, File};
15 use std::io::{BufRead, BufReader, Write};
16 use std::mem::{size_of, zeroed};
17 use std::os::windows::ffi::{OsStrExt, OsStringExt};
18 use std::os::windows::io::AsRawHandle;
19 use std::path::{Path, PathBuf};
20 use std::ptr::{addr_of_mut, null, null_mut};
21 use std::slice::from_raw_parts;
22 use uuid::Uuid;
23 use winapi::shared::basetsd::{SIZE_T, ULONG_PTR};
24 use winapi::shared::minwindef::{
25     BOOL, BYTE, DWORD, FALSE, FILETIME, LPVOID, MAX_PATH, PBOOL, PDWORD, PULONG, TRUE, ULONG, WORD,
26 };
27 use winapi::shared::ntdef::{NTSTATUS, STRING, UNICODE_STRING};
28 use winapi::shared::ntstatus::STATUS_SUCCESS;
29 use winapi::shared::winerror::{E_UNEXPECTED, S_OK};
30 use winapi::um::combaseapi::CoTaskMemFree;
31 use winapi::um::handleapi::CloseHandle;
32 use winapi::um::knownfolders::FOLDERID_RoamingAppData;
33 use winapi::um::memoryapi::{ReadProcessMemory, WriteProcessMemory};
34 use winapi::um::minwinbase::LPTHREAD_START_ROUTINE;
35 use winapi::um::processthreadsapi::{
36     CreateProcessW, CreateRemoteThread, GetProcessId, GetProcessTimes, GetThreadId, OpenProcess,
37     TerminateProcess, PROCESS_INFORMATION, STARTUPINFOW,
38 };
39 use winapi::um::psapi::K32GetModuleFileNameExW;
40 use winapi::um::shlobj::SHGetKnownFolderPath;
41 use winapi::um::synchapi::WaitForSingleObject;
42 use winapi::um::winbase::{
43     VerifyVersionInfoW, CREATE_NO_WINDOW, CREATE_UNICODE_ENVIRONMENT, NORMAL_PRIORITY_CLASS,
44     WAIT_OBJECT_0,
45 };
46 use winapi::um::winnt::{
47     VerSetConditionMask, CONTEXT, DWORDLONG, EXCEPTION_POINTERS, EXCEPTION_RECORD, HANDLE, HRESULT,
48     LIST_ENTRY, LPOSVERSIONINFOEXW, OSVERSIONINFOEXW, PCWSTR, PEXCEPTION_POINTERS,
49     PROCESS_ALL_ACCESS, PVOID, PWSTR, VER_GREATER_EQUAL, VER_MAJORVERSION, VER_MINORVERSION,
50     VER_SERVICEPACKMAJOR, VER_SERVICEPACKMINOR,
51 };
52 use winapi::STRUCT;
53 
54 /* The following struct must be kept in sync with the identically named one in
55  * nsExceptionHandler.h. There is one copy of this structure for every child
56  * process and they are all stored within the main process'. WER will use it to
57  * communicate with the main process when a child process is encountered. */
58 #[repr(C)]
59 struct WindowsErrorReportingData {
60     wer_notify_proc: LPTHREAD_START_ROUTINE,
61     child_pid: DWORD,
62     minidump_name: [u8; 40],
63     oom_allocation_size: usize,
64 }
65 
66 /* The following struct must be kept in sync with the identically named one in
67  * nsExceptionHandler.h. A copy of this is stored in every process and a pointer
68  * to it is passed to the runtime exception module. We will read it to gather
69  * information about the crashed process. */
70 #[repr(C)]
71 struct InProcessWindowsErrorReportingData {
72     process_type: u32,
73     oom_allocation_size_ptr: *mut usize,
74 }
75 
76 // This value comes from GeckoProcessTypes.h
77 static MAIN_PROCESS_TYPE: u32 = 0;
78 
79 #[no_mangle]
OutOfProcessExceptionEventCallback( context: PVOID, exception_information: PWER_RUNTIME_EXCEPTION_INFORMATION, b_ownership_claimed: PBOOL, _wsz_event_name: PWSTR, _pch_size: PDWORD, _dw_signature_count: PDWORD, ) -> HRESULT80 pub extern "C" fn OutOfProcessExceptionEventCallback(
81     context: PVOID,
82     exception_information: PWER_RUNTIME_EXCEPTION_INFORMATION,
83     b_ownership_claimed: PBOOL,
84     _wsz_event_name: PWSTR,
85     _pch_size: PDWORD,
86     _dw_signature_count: PDWORD,
87 ) -> HRESULT {
88     let result = out_of_process_exception_event_callback(context, exception_information);
89 
90     match result {
91         Ok(_) => {
92             unsafe {
93                 // Inform WER that we claim ownership of this crash
94                 *b_ownership_claimed = TRUE;
95                 // Make sure that the process shuts down
96                 TerminateProcess((*exception_information).hProcess, 1);
97             }
98             S_OK
99         }
100         Err(_) => E_UNEXPECTED,
101     }
102 }
103 
104 #[no_mangle]
OutOfProcessExceptionEventSignatureCallback( _context: PVOID, _exception_information: PWER_RUNTIME_EXCEPTION_INFORMATION, _w_index: DWORD, _wsz_name: PWSTR, _ch_name: PDWORD, _wsz_value: PWSTR, _ch_value: PDWORD, ) -> HRESULT105 pub extern "C" fn OutOfProcessExceptionEventSignatureCallback(
106     _context: PVOID,
107     _exception_information: PWER_RUNTIME_EXCEPTION_INFORMATION,
108     _w_index: DWORD,
109     _wsz_name: PWSTR,
110     _ch_name: PDWORD,
111     _wsz_value: PWSTR,
112     _ch_value: PDWORD,
113 ) -> HRESULT {
114     S_OK
115 }
116 
117 #[no_mangle]
OutOfProcessExceptionEventDebuggerLaunchCallback( _context: PVOID, _exception_information: PWER_RUNTIME_EXCEPTION_INFORMATION, b_is_custom_debugger: PBOOL, _wsz_debugger_launch: PWSTR, _ch_debugger_launch: PDWORD, _b_is_debugger_autolaunch: PBOOL, ) -> HRESULT118 pub extern "C" fn OutOfProcessExceptionEventDebuggerLaunchCallback(
119     _context: PVOID,
120     _exception_information: PWER_RUNTIME_EXCEPTION_INFORMATION,
121     b_is_custom_debugger: PBOOL,
122     _wsz_debugger_launch: PWSTR,
123     _ch_debugger_launch: PDWORD,
124     _b_is_debugger_autolaunch: PBOOL,
125 ) -> HRESULT {
126     unsafe {
127         *b_is_custom_debugger = FALSE;
128     }
129 
130     S_OK
131 }
132 
out_of_process_exception_event_callback( context: PVOID, exception_information: PWER_RUNTIME_EXCEPTION_INFORMATION, ) -> Result<(), ()>133 fn out_of_process_exception_event_callback(
134     context: PVOID,
135     exception_information: PWER_RUNTIME_EXCEPTION_INFORMATION,
136 ) -> Result<(), ()> {
137     let is_fatal = unsafe { (*exception_information).bIsFatal } != FALSE;
138     if !is_fatal {
139         return Ok(());
140     }
141 
142     let process = unsafe { (*exception_information).hProcess };
143     let application_info = ApplicationInformation::from_process(process)?;
144     let wer_data = read_from_process::<InProcessWindowsErrorReportingData>(
145         process,
146         context as *mut InProcessWindowsErrorReportingData,
147     )?;
148     let process = unsafe { (*exception_information).hProcess };
149     let startup_time = get_startup_time(process)?;
150     let oom_allocation_size = get_oom_allocation_size(process, &wer_data);
151     let crash_report = CrashReport::new(&application_info, startup_time, oom_allocation_size);
152     crash_report.write_minidump(exception_information)?;
153     if wer_data.process_type == MAIN_PROCESS_TYPE {
154         handle_main_process_crash(crash_report, process, &application_info)
155     } else {
156         handle_child_process_crash(crash_report, process)
157     }
158 }
159 
handle_main_process_crash( crash_report: CrashReport, process: HANDLE, application_information: &ApplicationInformation, ) -> Result<(), ()>160 fn handle_main_process_crash(
161     crash_report: CrashReport,
162     process: HANDLE,
163     application_information: &ApplicationInformation,
164 ) -> Result<(), ()> {
165     crash_report.write_extra_file()?;
166     crash_report.write_event_file()?;
167 
168     let mut environment = read_environment_block(process)?;
169     launch_crash_reporter_client(
170         &application_information.install_path,
171         &mut environment,
172         &crash_report,
173     );
174 
175     Ok(())
176 }
177 
handle_child_process_crash(crash_report: CrashReport, child_process: HANDLE) -> Result<(), ()>178 fn handle_child_process_crash(crash_report: CrashReport, child_process: HANDLE) -> Result<(), ()> {
179     let command_line = read_command_line(child_process)?;
180     let (parent_pid, data_ptr) = parse_child_data(&command_line)?;
181 
182     let parent_process = get_process_handle(parent_pid)?;
183     let mut wer_data: WindowsErrorReportingData = read_from_process(parent_process, data_ptr)?;
184     wer_data.child_pid = get_process_id(child_process)?;
185     wer_data.minidump_name = crash_report.get_minidump_name();
186     wer_data.oom_allocation_size = crash_report.oom_allocation_size;
187     let wer_notify_proc = wer_data.wer_notify_proc;
188     write_to_process(parent_process, wer_data, data_ptr)?;
189     notify_main_process(parent_process, wer_notify_proc, data_ptr)
190 }
191 
read_from_process<T>(process: HANDLE, data_ptr: *mut T) -> Result<T, ()>192 fn read_from_process<T>(process: HANDLE, data_ptr: *mut T) -> Result<T, ()> {
193     let mut data: T = unsafe { zeroed() };
194     let res = unsafe {
195         ReadProcessMemory(
196             process,
197             data_ptr as *mut _,
198             addr_of_mut!(data) as *mut _,
199             size_of::<T>() as SIZE_T,
200             null_mut(),
201         )
202     };
203 
204     bool_ok_or_err(res, data)
205 }
206 
read_array_from_process<T: Clone + Default>( process: HANDLE, data_ptr: LPVOID, count: usize, ) -> Result<Vec<T>, ()>207 fn read_array_from_process<T: Clone + Default>(
208     process: HANDLE,
209     data_ptr: LPVOID,
210     count: usize,
211 ) -> Result<Vec<T>, ()> {
212     let mut array = vec![Default::default(); count];
213     let size = size_of::<T>() as SIZE_T;
214     let size = size.checked_mul(count).ok_or(())?;
215     let res = unsafe {
216         ReadProcessMemory(
217             process,
218             data_ptr,
219             array.as_mut_ptr() as *mut _,
220             size,
221             null_mut(),
222         )
223     };
224 
225     bool_ok_or_err(res, array)
226 }
227 
write_to_process<T>(process: HANDLE, mut data: T, data_ptr: *mut T) -> Result<(), ()>228 fn write_to_process<T>(process: HANDLE, mut data: T, data_ptr: *mut T) -> Result<(), ()> {
229     let res = unsafe {
230         WriteProcessMemory(
231             process,
232             data_ptr as LPVOID,
233             addr_of_mut!(data) as *mut _,
234             size_of::<T>() as SIZE_T,
235             null_mut(),
236         )
237     };
238 
239     bool_ok_or_err(res, ())
240 }
241 
notify_main_process( process: HANDLE, wer_notify_proc: LPTHREAD_START_ROUTINE, data_ptr: *mut WindowsErrorReportingData, ) -> Result<(), ()>242 fn notify_main_process(
243     process: HANDLE,
244     wer_notify_proc: LPTHREAD_START_ROUTINE,
245     data_ptr: *mut WindowsErrorReportingData,
246 ) -> Result<(), ()> {
247     let thread = unsafe {
248         CreateRemoteThread(
249             process,
250             null_mut(),
251             0,
252             wer_notify_proc,
253             data_ptr as LPVOID,
254             0,
255             null_mut(),
256         )
257     };
258 
259     if thread == null_mut() {
260         return Err(());
261     }
262 
263     // Don't wait forever as we want the process to get killed eventually
264     let res = unsafe { WaitForSingleObject(thread, 5000) };
265     if res != WAIT_OBJECT_0 {
266         return Err(());
267     }
268 
269     let res = unsafe { CloseHandle(thread) };
270     bool_ok_or_err(res, ())
271 }
272 
get_startup_time(process: HANDLE) -> Result<u64, ()>273 fn get_startup_time(process: HANDLE) -> Result<u64, ()> {
274     let mut create_time: FILETIME = Default::default();
275     let mut exit_time: FILETIME = Default::default();
276     let mut kernel_time: FILETIME = Default::default();
277     let mut user_time: FILETIME = Default::default();
278     unsafe {
279         if GetProcessTimes(
280             process,
281             &mut create_time as *mut _,
282             &mut exit_time as *mut _,
283             &mut kernel_time as *mut _,
284             &mut user_time as *mut _,
285         ) == 0
286         {
287             return Err(());
288         }
289     }
290     let start_time_in_ticks =
291         ((create_time.dwHighDateTime as u64) << 32) + create_time.dwLowDateTime as u64;
292     let windows_tick: u64 = 10000000;
293     let sec_to_unix_epoch = 11644473600;
294     Ok((start_time_in_ticks / windows_tick) - sec_to_unix_epoch)
295 }
296 
get_oom_allocation_size( process: HANDLE, wer_data: &InProcessWindowsErrorReportingData, ) -> usize297 fn get_oom_allocation_size(
298     process: HANDLE,
299     wer_data: &InProcessWindowsErrorReportingData,
300 ) -> usize {
301     read_from_process(process, wer_data.oom_allocation_size_ptr).unwrap_or(0)
302 }
303 
parse_child_data(command_line: &str) -> Result<(DWORD, *mut WindowsErrorReportingData), ()>304 fn parse_child_data(command_line: &str) -> Result<(DWORD, *mut WindowsErrorReportingData), ()> {
305     let mut itr = command_line.rsplit(' ');
306     let address = itr.nth(1).ok_or(())?;
307     let address = usize::from_str_radix(address, 16).map_err(|_err| (()))?;
308     let address = address as *mut WindowsErrorReportingData;
309     let parent_pid = itr.nth(2).ok_or(())?;
310     let parent_pid = u32::from_str_radix(parent_pid, 10).map_err(|_err| (()))?;
311 
312     Ok((parent_pid, address))
313 }
314 
get_process_id(process: HANDLE) -> Result<DWORD, ()>315 fn get_process_id(process: HANDLE) -> Result<DWORD, ()> {
316     match unsafe { GetProcessId(process) } {
317         0 => Err(()),
318         pid => Ok(pid),
319     }
320 }
321 
get_process_handle(pid: DWORD) -> Result<HANDLE, ()>322 fn get_process_handle(pid: DWORD) -> Result<HANDLE, ()> {
323     let handle = unsafe { OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid) };
324     if handle != null_mut() {
325         Ok(handle)
326     } else {
327         Err(())
328     }
329 }
330 
launch_crash_reporter_client( install_path: &Path, environment: &mut Vec<u16>, crash_report: &CrashReport, )331 fn launch_crash_reporter_client(
332     install_path: &Path,
333     environment: &mut Vec<u16>,
334     crash_report: &CrashReport,
335 ) {
336     // Prepare the command line
337     let client_path = install_path.join("crashreporter.exe");
338 
339     let mut cmd_line = OsString::from("\"");
340     cmd_line.push(client_path);
341     cmd_line.push("\" \"");
342     cmd_line.push(crash_report.get_minidump_path());
343     cmd_line.push("\"\0");
344     let mut cmd_line: Vec<u16> = cmd_line.encode_wide().collect();
345 
346     let mut pi: PROCESS_INFORMATION = Default::default();
347     let mut si = STARTUPINFOW {
348         cb: size_of::<STARTUPINFOW>().try_into().unwrap(),
349         ..Default::default()
350     };
351 
352     unsafe {
353         if CreateProcessW(
354             null_mut(),
355             cmd_line.as_mut_ptr(),
356             null_mut(),
357             null_mut(),
358             FALSE,
359             NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT,
360             environment.as_mut_ptr() as *mut _,
361             null_mut(),
362             &mut si,
363             &mut pi,
364         ) != 0
365         {
366             CloseHandle(pi.hProcess);
367             CloseHandle(pi.hThread);
368         }
369     }
370 }
371 
372 #[derive(Debug)]
373 struct ApplicationData {
374     vendor: Option<String>,
375     name: String,
376     version: String,
377     build_id: String,
378     product_id: String,
379     server_url: String,
380 }
381 
382 impl ApplicationData {
load_from_disk(install_path: &Path) -> Result<ApplicationData, ()>383     fn load_from_disk(install_path: &Path) -> Result<ApplicationData, ()> {
384         let ini_path = ApplicationData::get_path(install_path);
385         let conf = Ini::load_from_file(ini_path).map_err(|_e| ())?;
386 
387         // Parse the "App" section
388         let app_section = conf.section(Some("App")).ok_or(())?;
389         let vendor = app_section.get("Vendor").map(|s| s.to_owned());
390         let name = app_section.get("Name").ok_or(())?.to_owned();
391         let version = app_section.get("Version").ok_or(())?.to_owned();
392         let build_id = app_section.get("BuildID").ok_or(())?.to_owned();
393         let product_id = app_section.get("ID").ok_or(())?.to_owned();
394 
395         // Parse the "Crash Reporter" section
396         let crash_reporter_section = conf.section(Some("Crash Reporter")).ok_or(())?;
397         let server_url = crash_reporter_section
398             .get("ServerURL")
399             .ok_or(())?
400             .to_owned();
401 
402         // InstallTime<build_id>
403 
404         Ok(ApplicationData {
405             vendor,
406             name,
407             version,
408             build_id,
409             product_id,
410             server_url,
411         })
412     }
413 
get_path(install_path: &Path) -> PathBuf414     fn get_path(install_path: &Path) -> PathBuf {
415         install_path.join("application.ini")
416     }
417 }
418 
419 #[derive(Serialize)]
420 #[allow(non_snake_case)]
421 struct Annotations {
422     BuildID: String,
423     CrashTime: String,
424     InstallTime: String,
425     #[serde(skip_serializing_if = "Option::is_none")]
426     OOMAllocationSize: Option<String>,
427     ProductID: String,
428     ProductName: String,
429     ReleaseChannel: String,
430     ServerURL: String,
431     StartupTime: String,
432     UptimeTS: String,
433     #[serde(skip_serializing_if = "Option::is_none")]
434     Vendor: Option<String>,
435     Version: String,
436     WindowsErrorReporting: String,
437 }
438 
439 impl Annotations {
from_application_data( application_data: &ApplicationData, release_channel: String, install_time: String, crash_time: u64, startup_time: u64, oom_allocation_size: usize, ) -> Annotations440     fn from_application_data(
441         application_data: &ApplicationData,
442         release_channel: String,
443         install_time: String,
444         crash_time: u64,
445         startup_time: u64,
446         oom_allocation_size: usize,
447     ) -> Annotations {
448         let oom_allocation_size = if oom_allocation_size != 0 {
449             Some(oom_allocation_size.to_string())
450         } else {
451             None
452         };
453         Annotations {
454             BuildID: application_data.build_id.clone(),
455             CrashTime: crash_time.to_string(),
456             InstallTime: install_time,
457             OOMAllocationSize: oom_allocation_size,
458             ProductID: application_data.product_id.clone(),
459             ProductName: application_data.name.clone(),
460             ReleaseChannel: release_channel,
461             ServerURL: application_data.server_url.clone(),
462             StartupTime: startup_time.to_string(),
463             UptimeTS: (crash_time - startup_time).to_string() + ".0",
464             Vendor: application_data.vendor.clone(),
465             Version: application_data.version.clone(),
466             WindowsErrorReporting: "1".to_string(),
467         }
468     }
469 }
470 
471 /// Encapsulates the information about the application that crashed. This includes the install path as well as version information
472 struct ApplicationInformation {
473     install_path: PathBuf,
474     application_data: ApplicationData,
475     release_channel: String,
476     crash_reports_dir: PathBuf,
477     install_time: String,
478 }
479 
480 impl ApplicationInformation {
from_process(process: HANDLE) -> Result<ApplicationInformation, ()>481     fn from_process(process: HANDLE) -> Result<ApplicationInformation, ()> {
482         let mut install_path = ApplicationInformation::get_application_path(process)?;
483         install_path.pop();
484         let application_data = ApplicationData::load_from_disk(install_path.as_ref())?;
485         let release_channel = ApplicationInformation::get_release_channel(install_path.as_ref())?;
486         let crash_reports_dir = ApplicationInformation::get_crash_reports_dir(&application_data)?;
487         let install_time = ApplicationInformation::get_install_time(
488             &crash_reports_dir,
489             &application_data.build_id,
490         )?;
491 
492         Ok(ApplicationInformation {
493             install_path,
494             application_data,
495             release_channel,
496             crash_reports_dir,
497             install_time,
498         })
499     }
500 
get_application_path(process: HANDLE) -> Result<PathBuf, ()>501     fn get_application_path(process: HANDLE) -> Result<PathBuf, ()> {
502         let mut path: [u16; MAX_PATH + 1] = [0; MAX_PATH + 1];
503         unsafe {
504             let res = K32GetModuleFileNameExW(
505                 process,
506                 null_mut(),
507                 (&mut path).as_mut_ptr(),
508                 (MAX_PATH + 1) as DWORD,
509             );
510 
511             if res == 0 {
512                 return Err(());
513             }
514 
515             let application_path = PathBuf::from(OsString::from_wide(&path[0..res as usize]));
516             Ok(application_path)
517         }
518     }
519 
get_release_channel(install_path: &Path) -> Result<String, ()>520     fn get_release_channel(install_path: &Path) -> Result<String, ()> {
521         let channel_prefs =
522             File::open(install_path.join("defaults/pref/channel-prefs.js")).map_err(|_e| ())?;
523         let lines = BufReader::new(channel_prefs).lines();
524         let line = lines
525             .filter_map(Result::ok)
526             .find(|line| line.contains("app.update.channel"))
527             .ok_or(())?;
528         line.split("\"").nth(3).map(|s| s.to_string()).ok_or(())
529     }
530 
get_crash_reports_dir(application_data: &ApplicationData) -> Result<PathBuf, ()>531     fn get_crash_reports_dir(application_data: &ApplicationData) -> Result<PathBuf, ()> {
532         let mut psz_path: PWSTR = null_mut();
533         unsafe {
534             let res = SHGetKnownFolderPath(
535                 &FOLDERID_RoamingAppData as *const _,
536                 0,
537                 null_mut(),
538                 &mut psz_path as *mut _,
539             );
540 
541             if res == S_OK {
542                 let mut len = 0;
543                 while psz_path.offset(len).read() != 0 {
544                     len += 1;
545                 }
546                 let str = OsString::from_wide(from_raw_parts(psz_path, len as usize));
547                 CoTaskMemFree(psz_path as _);
548                 let mut path = PathBuf::from(str);
549                 if let Some(vendor) = &application_data.vendor {
550                     path.push(vendor);
551                 }
552                 path.push(&application_data.name);
553                 path.push("Crash Reports");
554                 Ok(path)
555             } else {
556                 Err(())
557             }
558         }
559     }
560 
get_install_time(crash_reports_path: &Path, build_id: &str) -> Result<String, ()>561     fn get_install_time(crash_reports_path: &Path, build_id: &str) -> Result<String, ()> {
562         let file_name = "InstallTime".to_owned() + build_id;
563         let file_path = crash_reports_path.join(file_name);
564         read_to_string(file_path).map_err(|_e| ())
565     }
566 }
567 
568 struct CrashReport {
569     uuid: String,
570     crash_reports_path: PathBuf,
571     release_channel: String,
572     annotations: Annotations,
573     crash_time: u64,
574     oom_allocation_size: usize,
575 }
576 
577 impl CrashReport {
new( application_information: &ApplicationInformation, startup_time: u64, oom_allocation_size: usize, ) -> CrashReport578     fn new(
579         application_information: &ApplicationInformation,
580         startup_time: u64,
581         oom_allocation_size: usize,
582     ) -> CrashReport {
583         let uuid = Uuid::new_v4()
584             .to_hyphenated()
585             .encode_lower(&mut Uuid::encode_buffer())
586             .to_owned();
587         let crash_reports_path = application_information.crash_reports_dir.clone();
588         let crash_time: u64 = unsafe { time(null_mut()) as u64 };
589         let annotations = Annotations::from_application_data(
590             &application_information.application_data,
591             application_information.release_channel.clone(),
592             application_information.install_time.clone(),
593             crash_time,
594             startup_time,
595             oom_allocation_size,
596         );
597         CrashReport {
598             uuid,
599             crash_reports_path,
600             release_channel: application_information.release_channel.clone(),
601             annotations,
602             crash_time,
603             oom_allocation_size,
604         }
605     }
606 
is_nightly(&self) -> bool607     fn is_nightly(&self) -> bool {
608         self.release_channel == "nightly" || self.release_channel == "default"
609     }
610 
get_minidump_type(&self) -> MINIDUMP_TYPE611     fn get_minidump_type(&self) -> MINIDUMP_TYPE {
612         let mut minidump_type = MiniDumpWithFullMemoryInfo | MiniDumpWithUnloadedModules;
613         if self.is_nightly() {
614             // This is Nightly only because this doubles the size of minidumps based
615             // on the experimental data.
616             minidump_type = minidump_type | MiniDumpWithProcessThreadData;
617 
618             // dbghelp.dll on Win7 can't handle overlapping memory regions so we only
619             // enable this feature on Win8 or later.
620             if is_windows8_or_later() {
621                 // This allows us to examine heap objects referenced from stack objects
622                 // at the cost of further doubling the size of minidumps.
623                 minidump_type = minidump_type | MiniDumpWithIndirectlyReferencedMemory
624             }
625         }
626         minidump_type
627     }
628 
get_pending_path(&self) -> PathBuf629     fn get_pending_path(&self) -> PathBuf {
630         self.crash_reports_path.join("pending")
631     }
632 
get_events_path(&self) -> PathBuf633     fn get_events_path(&self) -> PathBuf {
634         self.crash_reports_path.join("events")
635     }
636 
get_minidump_path(&self) -> PathBuf637     fn get_minidump_path(&self) -> PathBuf {
638         self.get_pending_path().join(self.uuid.to_string() + ".dmp")
639     }
640 
get_minidump_name(&self) -> [u8; 40]641     fn get_minidump_name(&self) -> [u8; 40] {
642         let bytes = (self.uuid.to_string() + ".dmp").into_bytes();
643         bytes[0..40].try_into().unwrap()
644     }
645 
get_extra_file_path(&self) -> PathBuf646     fn get_extra_file_path(&self) -> PathBuf {
647         self.get_pending_path()
648             .join(self.uuid.to_string() + ".extra")
649     }
650 
get_event_file_path(&self) -> PathBuf651     fn get_event_file_path(&self) -> PathBuf {
652         self.get_events_path().join(self.uuid.to_string())
653     }
654 
write_minidump( &self, exception_information: PWER_RUNTIME_EXCEPTION_INFORMATION, ) -> Result<(), ()>655     fn write_minidump(
656         &self,
657         exception_information: PWER_RUNTIME_EXCEPTION_INFORMATION,
658     ) -> Result<(), ()> {
659         let minidump_path = self.get_minidump_path();
660         let minidump_file = File::create(minidump_path).map_err(|_e| ())?;
661         let minidump_type: MINIDUMP_TYPE = self.get_minidump_type();
662 
663         unsafe {
664             let mut exception_pointers = EXCEPTION_POINTERS {
665                 ExceptionRecord: &mut ((*exception_information).exceptionRecord),
666                 ContextRecord: &mut ((*exception_information).context),
667             };
668 
669             let mut exception = MINIDUMP_EXCEPTION_INFORMATION {
670                 ThreadId: GetThreadId((*exception_information).hThread),
671                 ExceptionPointers: &mut exception_pointers,
672                 ClientPointers: FALSE,
673             };
674 
675             let res = MiniDumpWriteDump(
676                 (*exception_information).hProcess,
677                 get_process_id((*exception_information).hProcess)?,
678                 minidump_file.as_raw_handle() as _,
679                 minidump_type,
680                 &mut exception,
681                 /* userStream */ null(),
682                 /* callback */ null(),
683             );
684 
685             bool_ok_or_err(res, ())
686         }
687     }
688 
write_extra_file(&self) -> Result<(), ()>689     fn write_extra_file(&self) -> Result<(), ()> {
690         let extra_file = File::create(self.get_extra_file_path()).map_err(|_e| ())?;
691         to_writer(extra_file, &self.annotations).map_err(|_e| ())
692     }
693 
write_event_file(&self) -> Result<(), ()>694     fn write_event_file(&self) -> Result<(), ()> {
695         let mut event_file = File::create(self.get_event_file_path()).map_err(|_e| ())?;
696         writeln!(event_file, "crash.main.3").map_err(|_e| ())?;
697         writeln!(event_file, "{}", self.crash_time).map_err(|_e| ())?;
698         writeln!(event_file, "{}", self.uuid).map_err(|_e| ())?;
699         to_writer(event_file, &self.annotations).map_err(|_e| ())
700     }
701 }
702 
is_windows8_or_later() -> bool703 fn is_windows8_or_later() -> bool {
704     let mut info = OSVERSIONINFOEXW {
705         dwOSVersionInfoSize: size_of::<OSVERSIONINFOEXW>().try_into().unwrap(),
706         dwMajorVersion: 6,
707         dwMinorVersion: 2,
708         ..Default::default()
709     };
710 
711     unsafe {
712         let mut mask: DWORDLONG = 0;
713         mask = VerSetConditionMask(mask, VER_MAJORVERSION, VER_GREATER_EQUAL);
714         mask = VerSetConditionMask(mask, VER_MINORVERSION, VER_GREATER_EQUAL);
715         mask = VerSetConditionMask(mask, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);
716         mask = VerSetConditionMask(mask, VER_SERVICEPACKMINOR, VER_GREATER_EQUAL);
717 
718         let res = VerifyVersionInfoW(
719             &mut info as LPOSVERSIONINFOEXW,
720             VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR,
721             mask,
722         );
723 
724         res != FALSE
725     }
726 }
727 
bool_ok_or_err<T>(res: BOOL, data: T) -> Result<T, ()>728 fn bool_ok_or_err<T>(res: BOOL, data: T) -> Result<T, ()> {
729     match res {
730         FALSE => Err(()),
731         _ => Ok(data),
732     }
733 }
734 
read_environment_block(process: HANDLE) -> Result<Vec<u16>, ()>735 fn read_environment_block(process: HANDLE) -> Result<Vec<u16>, ()> {
736     let upp = read_user_process_parameters(process)?;
737 
738     // Read the environment
739     let buffer = upp.Environment;
740     let length = upp.EnvironmentSize;
741     let count = length as usize / 2;
742     read_array_from_process::<u16>(process, buffer, count)
743 }
744 
read_command_line(process: HANDLE) -> Result<String, ()>745 fn read_command_line(process: HANDLE) -> Result<String, ()> {
746     let upp = read_user_process_parameters(process)?;
747 
748     // Read the command-line
749     let buffer = upp.CommandLine.Buffer;
750     let length = upp.CommandLine.Length;
751     let count = (length as usize) / 2;
752     let command_line = read_array_from_process::<u16>(process, buffer as *mut _, count)?;
753     String::from_utf16(&command_line).map_err(|_err| ())
754 }
755 
read_user_process_parameters(process: HANDLE) -> Result<RTL_USER_PROCESS_PARAMETERS, ()>756 fn read_user_process_parameters(process: HANDLE) -> Result<RTL_USER_PROCESS_PARAMETERS, ()> {
757     let mut pbi: PROCESS_BASIC_INFORMATION = unsafe { zeroed() };
758     let mut length: ULONG = 0;
759     let result = unsafe {
760         NtQueryInformationProcess(
761             process,
762             ProcessBasicInformation,
763             &mut pbi as *mut _ as _,
764             size_of::<PROCESS_BASIC_INFORMATION>().try_into().unwrap(),
765             &mut length,
766         )
767     };
768 
769     if result != STATUS_SUCCESS {
770         return Err(());
771     }
772 
773     // Read the process environment block
774     let peb: PEB = read_from_process(process, pbi.PebBaseAddress)?;
775 
776     // Read the user process parameters
777     read_from_process::<RTL_USER_PROCESS_PARAMETERS>(process, peb.ProcessParameters)
778 }
779 
780 /******************************************************************************
781  * The stuff below should be migrated to the winapi crate, see bug 1696414    *
782  ******************************************************************************/
783 
784 // we can't use winapi's ENUM macro directly because it doesn't support
785 // attributes, so let's define this one here until we migrate this code
786 macro_rules! ENUM {
787     {enum $name:ident { $($variant:ident = $value:expr,)+ }} => {
788         #[allow(non_camel_case_types)] pub type $name = u32;
789         $(#[allow(non_upper_case_globals)] pub const $variant: $name = $value;)+
790     };
791 }
792 
793 // winapi doesn't export the FN macro, so we duplicate it here
794 macro_rules! FN {
795     (stdcall $func:ident($($t:ty,)*) -> $ret:ty) => (
796         #[allow(non_camel_case_types)] pub type $func = Option<unsafe extern "system" fn($($t,)*) -> $ret>;
797     );
798     (stdcall $func:ident($($p:ident: $t:ty,)*) -> $ret:ty) => (
799         #[allow(non_camel_case_types)] pub type $func = Option<unsafe extern "system" fn($($p: $t,)*) -> $ret>;
800     );
801 }
802 
803 // From um/WerApi.h
804 
805 STRUCT! {#[allow(non_snake_case)] struct WER_RUNTIME_EXCEPTION_INFORMATION
806 {
807     dwSize: DWORD,
808     hProcess: HANDLE,
809     hThread: HANDLE,
810     exceptionRecord: EXCEPTION_RECORD,
811     context: CONTEXT,
812     pwszReportId: PCWSTR,
813     bIsFatal: BOOL,
814     dwReserved: DWORD,
815 }}
816 
817 #[allow(non_camel_case_types)]
818 pub type PWER_RUNTIME_EXCEPTION_INFORMATION = *mut WER_RUNTIME_EXCEPTION_INFORMATION;
819 
820 // From minidumpapiset.hProcess
821 
822 STRUCT! {#[allow(non_snake_case)] #[repr(packed(4))] struct MINIDUMP_EXCEPTION_INFORMATION {
823     ThreadId: DWORD,
824     ExceptionPointers: PEXCEPTION_POINTERS,
825     ClientPointers: BOOL,
826 }}
827 
828 #[allow(non_camel_case_types)]
829 pub type PMINIDUMP_EXCEPTION_INFORMATION = *mut MINIDUMP_EXCEPTION_INFORMATION;
830 
831 ENUM! { enum MINIDUMP_TYPE {
832     MiniDumpNormal                         = 0x00000000,
833     MiniDumpWithDataSegs                   = 0x00000001,
834     MiniDumpWithFullMemory                 = 0x00000002,
835     MiniDumpWithHandleData                 = 0x00000004,
836     MiniDumpFilterMemory                   = 0x00000008,
837     MiniDumpScanMemory                     = 0x00000010,
838     MiniDumpWithUnloadedModules            = 0x00000020,
839     MiniDumpWithIndirectlyReferencedMemory = 0x00000040,
840     MiniDumpFilterModulePaths              = 0x00000080,
841     MiniDumpWithProcessThreadData          = 0x00000100,
842     MiniDumpWithPrivateReadWriteMemory     = 0x00000200,
843     MiniDumpWithoutOptionalData            = 0x00000400,
844     MiniDumpWithFullMemoryInfo             = 0x00000800,
845     MiniDumpWithThreadInfo                 = 0x00001000,
846     MiniDumpWithCodeSegs                   = 0x00002000,
847     MiniDumpWithoutAuxiliaryState          = 0x00004000,
848     MiniDumpWithFullAuxiliaryState         = 0x00008000,
849     MiniDumpWithPrivateWriteCopyMemory     = 0x00010000,
850     MiniDumpIgnoreInaccessibleMemory       = 0x00020000,
851     MiniDumpWithTokenInformation           = 0x00040000,
852     MiniDumpWithModuleHeaders              = 0x00080000,
853     MiniDumpFilterTriage                   = 0x00100000,
854     MiniDumpWithAvxXStateContext           = 0x00200000,
855     MiniDumpWithIptTrace                   = 0x00400000,
856     MiniDumpScanInaccessiblePartialPages   = 0x00800000,
857     MiniDumpValidTypeFlags                 = 0x00ffffff,
858 }}
859 
860 // We don't actually need the following three structs so we use placeholders
861 STRUCT! {#[allow(non_snake_case)] struct MINIDUMP_CALLBACK_INPUT {
862     dummy: u32,
863 }}
864 
865 #[allow(non_camel_case_types)]
866 pub type PMINIDUMP_CALLBACK_INPUT = *const MINIDUMP_CALLBACK_INPUT;
867 
868 STRUCT! {#[allow(non_snake_case)] struct MINIDUMP_USER_STREAM_INFORMATION {
869     dummy: u32,
870 }}
871 
872 #[allow(non_camel_case_types)]
873 pub type PMINIDUMP_USER_STREAM_INFORMATION = *const MINIDUMP_USER_STREAM_INFORMATION;
874 
875 STRUCT! {#[allow(non_snake_case)] struct MINIDUMP_CALLBACK_OUTPUT {
876     dummy: u32,
877 }}
878 
879 #[allow(non_camel_case_types)]
880 pub type PMINIDUMP_CALLBACK_OUTPUT = *const MINIDUMP_CALLBACK_OUTPUT;
881 
882 // MiniDumpWriteDump() function and structs
883 FN! {stdcall MINIDUMP_CALLBACK_ROUTINE(
884 CallbackParam: PVOID,
885 CallbackInput: PMINIDUMP_CALLBACK_INPUT,
886 CallbackOutput: PMINIDUMP_CALLBACK_OUTPUT,
887 ) -> BOOL}
888 
889 STRUCT! {#[allow(non_snake_case)] #[repr(packed(4))] struct MINIDUMP_CALLBACK_INFORMATION {
890     CallbackRoutine: MINIDUMP_CALLBACK_ROUTINE,
891     CallbackParam: PVOID,
892 }}
893 
894 #[allow(non_camel_case_types)]
895 pub type PMINIDUMP_CALLBACK_INFORMATION = *const MINIDUMP_CALLBACK_INFORMATION;
896 
897 extern "system" {
MiniDumpWriteDump( hProcess: HANDLE, ProcessId: DWORD, hFile: HANDLE, DumpType: MINIDUMP_TYPE, Exceptionparam: PMINIDUMP_EXCEPTION_INFORMATION, UserStreamParam: PMINIDUMP_USER_STREAM_INFORMATION, CallbackParam: PMINIDUMP_CALLBACK_INFORMATION, ) -> BOOL898     pub fn MiniDumpWriteDump(
899         hProcess: HANDLE,
900         ProcessId: DWORD,
901         hFile: HANDLE,
902         DumpType: MINIDUMP_TYPE,
903         Exceptionparam: PMINIDUMP_EXCEPTION_INFORMATION,
904         UserStreamParam: PMINIDUMP_USER_STREAM_INFORMATION,
905         CallbackParam: PMINIDUMP_CALLBACK_INFORMATION,
906     ) -> BOOL;
907 }
908 
909 // From um/winternl.h
910 
911 STRUCT! {#[allow(non_snake_case)] struct PEB_LDR_DATA {
912     Reserved1: [BYTE; 8],
913     Reserved2: [PVOID; 3],
914     InMemoryOrderModuleList: LIST_ENTRY,
915 }}
916 
917 #[allow(non_camel_case_types)]
918 pub type PPEB_LDR_DATA = *mut PEB_LDR_DATA;
919 
920 STRUCT! {#[allow(non_snake_case)] struct RTL_DRIVE_LETTER_CURDIR {
921     Flags: WORD,
922     Length: WORD,
923     TimeStamp: ULONG,
924     DosPath: STRING,
925 }}
926 
927 STRUCT! {#[allow(non_snake_case)] struct RTL_USER_PROCESS_PARAMETERS {
928     Reserved1: [BYTE; 16],
929     Reserved2: [PVOID; 10],
930     ImagePathName: UNICODE_STRING,
931     CommandLine: UNICODE_STRING,
932     // Everything below this point is undocumented
933     Environment: PVOID,
934     StartingX: ULONG,
935     StartingY: ULONG,
936     CountX: ULONG,
937     CountY: ULONG,
938     CountCharsX: ULONG,
939     CountCharsY: ULONG,
940     FillAttribute: ULONG,
941     WindowFlags: ULONG,
942     ShowWindowFlags: ULONG,
943     WindowTitle: UNICODE_STRING,
944     DesktopInfo: UNICODE_STRING,
945     ShellInfo: UNICODE_STRING,
946     RuntimeData: UNICODE_STRING,
947     CurrentDirectores: [RTL_DRIVE_LETTER_CURDIR; 32],
948     EnvironmentSize: ULONG,
949 }}
950 
951 #[allow(non_camel_case_types)]
952 pub type PRTL_USER_PROCESS_PARAMETERS = *mut RTL_USER_PROCESS_PARAMETERS;
953 
954 FN! {stdcall PPS_POST_PROCESS_INIT_ROUTINE() -> ()}
955 
956 STRUCT! {#[allow(non_snake_case)] struct PEB {
957     Reserved1: [BYTE; 2],
958     BeingDebugged: BYTE,
959     Reserved2: [BYTE; 1],
960     Reserved3: [PVOID; 2],
961     Ldr: PPEB_LDR_DATA,
962     ProcessParameters: PRTL_USER_PROCESS_PARAMETERS,
963     Reserved4: [PVOID; 3],
964     AtlThunkSListPtr: PVOID,
965     Reserved5: PVOID,
966     Reserved6: ULONG,
967     Reserved7: PVOID,
968     Reserved8: ULONG,
969     AtlThunkSListPtr32: ULONG,
970     Reserved9: [PVOID; 45],
971     Reserved10: [BYTE; 96],
972     PostProcessInitRoutine: PPS_POST_PROCESS_INIT_ROUTINE,
973     Reserved11: [BYTE; 128],
974     Reserved12: [PVOID; 1],
975     SessionId: ULONG,
976 }}
977 
978 #[allow(non_camel_case_types)]
979 pub type PPEB = *mut PEB;
980 
981 STRUCT! {#[allow(non_snake_case)] struct PROCESS_BASIC_INFORMATION {
982     Reserved1: PVOID,
983     PebBaseAddress: PPEB,
984     Reserved2: [PVOID; 2],
985     UniqueProcessId: ULONG_PTR,
986     Reserved3: PVOID,
987 }}
988 
989 ENUM! {enum PROCESSINFOCLASS {
990     ProcessBasicInformation = 0,
991     ProcessDebugPort = 7,
992     ProcessWow64Information = 26,
993     ProcessImageFileName = 27,
994     ProcessBreakOnTermination = 29,
995 }}
996 
997 extern "system" {
NtQueryInformationProcess( ProcessHandle: HANDLE, ProcessInformationClass: PROCESSINFOCLASS, ProcessInformation: PVOID, ProcessInformationLength: ULONG, ReturnLength: PULONG, ) -> NTSTATUS998     pub fn NtQueryInformationProcess(
999         ProcessHandle: HANDLE,
1000         ProcessInformationClass: PROCESSINFOCLASS,
1001         ProcessInformation: PVOID,
1002         ProcessInformationLength: ULONG,
1003         ReturnLength: PULONG,
1004     ) -> NTSTATUS;
1005 }
1006