diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-21 11:44:51 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-21 11:44:51 +0000 |
commit | 9e3c08db40b8916968b9f30096c7be3f00ce9647 (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /toolkit/crashreporter/mozwer-rust/lib.rs | |
parent | Initial commit. (diff) | |
download | thunderbird-9e3c08db40b8916968b9f30096c7be3f00ce9647.tar.xz thunderbird-9e3c08db40b8916968b9f30096c7be3f00ce9647.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/crashreporter/mozwer-rust/lib.rs')
-rw-r--r-- | toolkit/crashreporter/mozwer-rust/lib.rs | 1005 |
1 files changed, 1005 insertions, 0 deletions
diff --git a/toolkit/crashreporter/mozwer-rust/lib.rs b/toolkit/crashreporter/mozwer-rust/lib.rs new file mode 100644 index 0000000000..fab6c4f85c --- /dev/null +++ b/toolkit/crashreporter/mozwer-rust/lib.rs @@ -0,0 +1,1005 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +extern crate ini; +extern crate winapi; + +use ini::Ini; +use libc::time; +use serde::Serialize; +use serde_json::ser::to_writer; +use std::convert::TryInto; +use std::ffi::OsString; +use std::fs::{read_to_string, File}; +use std::io::{BufRead, BufReader, Write}; +use std::mem::{size_of, zeroed}; +use std::os::windows::ffi::{OsStrExt, OsStringExt}; +use std::os::windows::io::AsRawHandle; +use std::path::{Path, PathBuf}; +use std::ptr::{addr_of_mut, null, null_mut}; +use std::slice::from_raw_parts; +use uuid::Uuid; +use winapi::shared::basetsd::{SIZE_T, ULONG_PTR}; +use winapi::shared::minwindef::{ + BOOL, BYTE, DWORD, FALSE, FILETIME, LPVOID, MAX_PATH, PBOOL, PDWORD, PULONG, TRUE, ULONG, WORD, +}; +use winapi::shared::ntdef::{NTSTATUS, STRING, UNICODE_STRING}; +use winapi::shared::ntstatus::STATUS_SUCCESS; +use winapi::shared::winerror::{E_UNEXPECTED, S_OK}; +use winapi::um::combaseapi::CoTaskMemFree; +use winapi::um::handleapi::CloseHandle; +use winapi::um::knownfolders::FOLDERID_RoamingAppData; +use winapi::um::memoryapi::{ReadProcessMemory, WriteProcessMemory}; +use winapi::um::minwinbase::LPTHREAD_START_ROUTINE; +use winapi::um::processthreadsapi::{ + CreateProcessW, CreateRemoteThread, GetProcessId, GetProcessTimes, GetThreadId, OpenProcess, + TerminateProcess, PROCESS_INFORMATION, STARTUPINFOW, +}; +use winapi::um::psapi::K32GetModuleFileNameExW; +use winapi::um::shlobj::SHGetKnownFolderPath; +use winapi::um::synchapi::WaitForSingleObject; +use winapi::um::winbase::{ + VerifyVersionInfoW, CREATE_NO_WINDOW, CREATE_UNICODE_ENVIRONMENT, NORMAL_PRIORITY_CLASS, + WAIT_OBJECT_0, +}; +use winapi::um::winnt::{ + VerSetConditionMask, CONTEXT, DWORDLONG, EXCEPTION_POINTERS, EXCEPTION_RECORD, HANDLE, HRESULT, + LIST_ENTRY, LPOSVERSIONINFOEXW, OSVERSIONINFOEXW, PCWSTR, PEXCEPTION_POINTERS, + PROCESS_ALL_ACCESS, PVOID, PWSTR, VER_GREATER_EQUAL, VER_MAJORVERSION, VER_MINORVERSION, + VER_SERVICEPACKMAJOR, VER_SERVICEPACKMINOR, +}; +use winapi::STRUCT; + +/* The following struct must be kept in sync with the identically named one in + * nsExceptionHandler.h. There is one copy of this structure for every child + * process and they are all stored within the main process'. WER will use it to + * communicate with the main process when a child process is encountered. */ +#[repr(C)] +struct WindowsErrorReportingData { + wer_notify_proc: LPTHREAD_START_ROUTINE, + child_pid: DWORD, + minidump_name: [u8; 40], + oom_allocation_size: usize, +} + +/* The following struct must be kept in sync with the identically named one in + * nsExceptionHandler.h. A copy of this is stored in every process and a pointer + * to it is passed to the runtime exception module. We will read it to gather + * information about the crashed process. */ +#[repr(C)] +struct InProcessWindowsErrorReportingData { + process_type: u32, + oom_allocation_size_ptr: *mut usize, +} + +// This value comes from GeckoProcessTypes.h +static MAIN_PROCESS_TYPE: u32 = 0; + +#[no_mangle] +pub extern "C" fn OutOfProcessExceptionEventCallback( + context: PVOID, + exception_information: PWER_RUNTIME_EXCEPTION_INFORMATION, + b_ownership_claimed: PBOOL, + _wsz_event_name: PWSTR, + _pch_size: PDWORD, + _dw_signature_count: PDWORD, +) -> HRESULT { + let result = out_of_process_exception_event_callback(context, exception_information); + + match result { + Ok(_) => { + unsafe { + // Inform WER that we claim ownership of this crash + *b_ownership_claimed = TRUE; + // Make sure that the process shuts down + TerminateProcess((*exception_information).hProcess, 1); + } + S_OK + } + Err(_) => E_UNEXPECTED, + } +} + +#[no_mangle] +pub extern "C" fn OutOfProcessExceptionEventSignatureCallback( + _context: PVOID, + _exception_information: PWER_RUNTIME_EXCEPTION_INFORMATION, + _w_index: DWORD, + _wsz_name: PWSTR, + _ch_name: PDWORD, + _wsz_value: PWSTR, + _ch_value: PDWORD, +) -> HRESULT { + S_OK +} + +#[no_mangle] +pub extern "C" fn 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, +) -> HRESULT { + unsafe { + *b_is_custom_debugger = FALSE; + } + + S_OK +} + +fn out_of_process_exception_event_callback( + context: PVOID, + exception_information: PWER_RUNTIME_EXCEPTION_INFORMATION, +) -> Result<(), ()> { + let is_fatal = unsafe { (*exception_information).bIsFatal } != FALSE; + if !is_fatal { + return Ok(()); + } + + let process = unsafe { (*exception_information).hProcess }; + let application_info = ApplicationInformation::from_process(process)?; + let wer_data = read_from_process::<InProcessWindowsErrorReportingData>( + process, + context as *mut InProcessWindowsErrorReportingData, + )?; + let process = unsafe { (*exception_information).hProcess }; + let startup_time = get_startup_time(process)?; + let oom_allocation_size = get_oom_allocation_size(process, &wer_data); + let crash_report = CrashReport::new(&application_info, startup_time, oom_allocation_size); + crash_report.write_minidump(exception_information)?; + if wer_data.process_type == MAIN_PROCESS_TYPE { + handle_main_process_crash(crash_report, process, &application_info) + } else { + handle_child_process_crash(crash_report, process) + } +} + +fn handle_main_process_crash( + crash_report: CrashReport, + process: HANDLE, + application_information: &ApplicationInformation, +) -> Result<(), ()> { + crash_report.write_extra_file()?; + crash_report.write_event_file()?; + + let mut environment = read_environment_block(process)?; + launch_crash_reporter_client( + &application_information.install_path, + &mut environment, + &crash_report, + ); + + Ok(()) +} + +fn handle_child_process_crash(crash_report: CrashReport, child_process: HANDLE) -> Result<(), ()> { + let command_line = read_command_line(child_process)?; + let (parent_pid, data_ptr) = parse_child_data(&command_line)?; + + let parent_process = get_process_handle(parent_pid)?; + let mut wer_data: WindowsErrorReportingData = read_from_process(parent_process, data_ptr)?; + wer_data.child_pid = get_process_id(child_process)?; + wer_data.minidump_name = crash_report.get_minidump_name(); + wer_data.oom_allocation_size = crash_report.oom_allocation_size; + let wer_notify_proc = wer_data.wer_notify_proc; + write_to_process(parent_process, wer_data, data_ptr)?; + notify_main_process(parent_process, wer_notify_proc, data_ptr) +} + +fn read_from_process<T>(process: HANDLE, data_ptr: *mut T) -> Result<T, ()> { + let mut data: T = unsafe { zeroed() }; + let res = unsafe { + ReadProcessMemory( + process, + data_ptr as *mut _, + addr_of_mut!(data) as *mut _, + size_of::<T>() as SIZE_T, + null_mut(), + ) + }; + + bool_ok_or_err(res, data) +} + +fn read_array_from_process<T: Clone + Default>( + process: HANDLE, + data_ptr: LPVOID, + count: usize, +) -> Result<Vec<T>, ()> { + let mut array = vec![Default::default(); count]; + let size = size_of::<T>() as SIZE_T; + let size = size.checked_mul(count).ok_or(())?; + let res = unsafe { + ReadProcessMemory( + process, + data_ptr, + array.as_mut_ptr() as *mut _, + size, + null_mut(), + ) + }; + + bool_ok_or_err(res, array) +} + +fn write_to_process<T>(process: HANDLE, mut data: T, data_ptr: *mut T) -> Result<(), ()> { + let res = unsafe { + WriteProcessMemory( + process, + data_ptr as LPVOID, + addr_of_mut!(data) as *mut _, + size_of::<T>() as SIZE_T, + null_mut(), + ) + }; + + bool_ok_or_err(res, ()) +} + +fn notify_main_process( + process: HANDLE, + wer_notify_proc: LPTHREAD_START_ROUTINE, + data_ptr: *mut WindowsErrorReportingData, +) -> Result<(), ()> { + let thread = unsafe { + CreateRemoteThread( + process, + null_mut(), + 0, + wer_notify_proc, + data_ptr as LPVOID, + 0, + null_mut(), + ) + }; + + if thread == null_mut() { + return Err(()); + } + + // Don't wait forever as we want the process to get killed eventually + let res = unsafe { WaitForSingleObject(thread, 5000) }; + if res != WAIT_OBJECT_0 { + return Err(()); + } + + let res = unsafe { CloseHandle(thread) }; + bool_ok_or_err(res, ()) +} + +fn get_startup_time(process: HANDLE) -> Result<u64, ()> { + let mut create_time: FILETIME = Default::default(); + let mut exit_time: FILETIME = Default::default(); + let mut kernel_time: FILETIME = Default::default(); + let mut user_time: FILETIME = Default::default(); + unsafe { + if GetProcessTimes( + process, + &mut create_time as *mut _, + &mut exit_time as *mut _, + &mut kernel_time as *mut _, + &mut user_time as *mut _, + ) == 0 + { + return Err(()); + } + } + let start_time_in_ticks = + ((create_time.dwHighDateTime as u64) << 32) + create_time.dwLowDateTime as u64; + let windows_tick: u64 = 10000000; + let sec_to_unix_epoch = 11644473600; + Ok((start_time_in_ticks / windows_tick) - sec_to_unix_epoch) +} + +fn get_oom_allocation_size( + process: HANDLE, + wer_data: &InProcessWindowsErrorReportingData, +) -> usize { + read_from_process(process, wer_data.oom_allocation_size_ptr).unwrap_or(0) +} + +fn parse_child_data(command_line: &str) -> Result<(DWORD, *mut WindowsErrorReportingData), ()> { + let mut itr = command_line.rsplit(' '); + let address = itr.nth(1).ok_or(())?; + let address = usize::from_str_radix(address, 16).map_err(|_err| (()))?; + let address = address as *mut WindowsErrorReportingData; + let parent_pid = itr.nth(2).ok_or(())?; + let parent_pid = u32::from_str_radix(parent_pid, 10).map_err(|_err| (()))?; + + Ok((parent_pid, address)) +} + +fn get_process_id(process: HANDLE) -> Result<DWORD, ()> { + match unsafe { GetProcessId(process) } { + 0 => Err(()), + pid => Ok(pid), + } +} + +fn get_process_handle(pid: DWORD) -> Result<HANDLE, ()> { + let handle = unsafe { OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid) }; + if handle != null_mut() { + Ok(handle) + } else { + Err(()) + } +} + +fn launch_crash_reporter_client( + install_path: &Path, + environment: &mut Vec<u16>, + crash_report: &CrashReport, +) { + // Prepare the command line + let client_path = install_path.join("crashreporter.exe"); + + let mut cmd_line = OsString::from("\""); + cmd_line.push(client_path); + cmd_line.push("\" \""); + cmd_line.push(crash_report.get_minidump_path()); + cmd_line.push("\"\0"); + let mut cmd_line: Vec<u16> = cmd_line.encode_wide().collect(); + + let mut pi: PROCESS_INFORMATION = Default::default(); + let mut si = STARTUPINFOW { + cb: size_of::<STARTUPINFOW>().try_into().unwrap(), + ..Default::default() + }; + + unsafe { + if CreateProcessW( + null_mut(), + cmd_line.as_mut_ptr(), + null_mut(), + null_mut(), + FALSE, + NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT, + environment.as_mut_ptr() as *mut _, + null_mut(), + &mut si, + &mut pi, + ) != 0 + { + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } + } +} + +#[derive(Debug)] +struct ApplicationData { + vendor: Option<String>, + name: String, + version: String, + build_id: String, + product_id: String, + server_url: String, +} + +impl ApplicationData { + fn load_from_disk(install_path: &Path) -> Result<ApplicationData, ()> { + let ini_path = ApplicationData::get_path(install_path); + let conf = Ini::load_from_file(ini_path).map_err(|_e| ())?; + + // Parse the "App" section + let app_section = conf.section(Some("App")).ok_or(())?; + let vendor = app_section.get("Vendor").map(|s| s.to_owned()); + let name = app_section.get("Name").ok_or(())?.to_owned(); + let version = app_section.get("Version").ok_or(())?.to_owned(); + let build_id = app_section.get("BuildID").ok_or(())?.to_owned(); + let product_id = app_section.get("ID").ok_or(())?.to_owned(); + + // Parse the "Crash Reporter" section + let crash_reporter_section = conf.section(Some("Crash Reporter")).ok_or(())?; + let server_url = crash_reporter_section + .get("ServerURL") + .ok_or(())? + .to_owned(); + + // InstallTime<build_id> + + Ok(ApplicationData { + vendor, + name, + version, + build_id, + product_id, + server_url, + }) + } + + fn get_path(install_path: &Path) -> PathBuf { + install_path.join("application.ini") + } +} + +#[derive(Serialize)] +#[allow(non_snake_case)] +struct Annotations { + BuildID: String, + CrashTime: String, + InstallTime: String, + #[serde(skip_serializing_if = "Option::is_none")] + OOMAllocationSize: Option<String>, + ProductID: String, + ProductName: String, + ReleaseChannel: String, + ServerURL: String, + StartupTime: String, + UptimeTS: String, + #[serde(skip_serializing_if = "Option::is_none")] + Vendor: Option<String>, + Version: String, + WindowsErrorReporting: String, +} + +impl Annotations { + fn from_application_data( + application_data: &ApplicationData, + release_channel: String, + install_time: String, + crash_time: u64, + startup_time: u64, + oom_allocation_size: usize, + ) -> Annotations { + let oom_allocation_size = if oom_allocation_size != 0 { + Some(oom_allocation_size.to_string()) + } else { + None + }; + Annotations { + BuildID: application_data.build_id.clone(), + CrashTime: crash_time.to_string(), + InstallTime: install_time, + OOMAllocationSize: oom_allocation_size, + ProductID: application_data.product_id.clone(), + ProductName: application_data.name.clone(), + ReleaseChannel: release_channel, + ServerURL: application_data.server_url.clone(), + StartupTime: startup_time.to_string(), + UptimeTS: (crash_time - startup_time).to_string() + ".0", + Vendor: application_data.vendor.clone(), + Version: application_data.version.clone(), + WindowsErrorReporting: "1".to_string(), + } + } +} + +/// Encapsulates the information about the application that crashed. This includes the install path as well as version information +struct ApplicationInformation { + install_path: PathBuf, + application_data: ApplicationData, + release_channel: String, + crash_reports_dir: PathBuf, + install_time: String, +} + +impl ApplicationInformation { + fn from_process(process: HANDLE) -> Result<ApplicationInformation, ()> { + let mut install_path = ApplicationInformation::get_application_path(process)?; + install_path.pop(); + let application_data = ApplicationData::load_from_disk(install_path.as_ref())?; + let release_channel = ApplicationInformation::get_release_channel(install_path.as_ref())?; + let crash_reports_dir = ApplicationInformation::get_crash_reports_dir(&application_data)?; + let install_time = ApplicationInformation::get_install_time( + &crash_reports_dir, + &application_data.build_id, + )?; + + Ok(ApplicationInformation { + install_path, + application_data, + release_channel, + crash_reports_dir, + install_time, + }) + } + + fn get_application_path(process: HANDLE) -> Result<PathBuf, ()> { + let mut path: [u16; MAX_PATH + 1] = [0; MAX_PATH + 1]; + unsafe { + let res = K32GetModuleFileNameExW( + process, + null_mut(), + (&mut path).as_mut_ptr(), + (MAX_PATH + 1) as DWORD, + ); + + if res == 0 { + return Err(()); + } + + let application_path = PathBuf::from(OsString::from_wide(&path[0..res as usize])); + Ok(application_path) + } + } + + fn get_release_channel(install_path: &Path) -> Result<String, ()> { + let channel_prefs = + File::open(install_path.join("defaults/pref/channel-prefs.js")).map_err(|_e| ())?; + let lines = BufReader::new(channel_prefs).lines(); + let line = lines + .filter_map(Result::ok) + .find(|line| line.contains("app.update.channel")) + .ok_or(())?; + line.split("\"").nth(3).map(|s| s.to_string()).ok_or(()) + } + + fn get_crash_reports_dir(application_data: &ApplicationData) -> Result<PathBuf, ()> { + let mut psz_path: PWSTR = null_mut(); + unsafe { + let res = SHGetKnownFolderPath( + &FOLDERID_RoamingAppData as *const _, + 0, + null_mut(), + &mut psz_path as *mut _, + ); + + if res == S_OK { + let mut len = 0; + while psz_path.offset(len).read() != 0 { + len += 1; + } + let str = OsString::from_wide(from_raw_parts(psz_path, len as usize)); + CoTaskMemFree(psz_path as _); + let mut path = PathBuf::from(str); + if let Some(vendor) = &application_data.vendor { + path.push(vendor); + } + path.push(&application_data.name); + path.push("Crash Reports"); + Ok(path) + } else { + Err(()) + } + } + } + + fn get_install_time(crash_reports_path: &Path, build_id: &str) -> Result<String, ()> { + let file_name = "InstallTime".to_owned() + build_id; + let file_path = crash_reports_path.join(file_name); + read_to_string(file_path).map_err(|_e| ()) + } +} + +struct CrashReport { + uuid: String, + crash_reports_path: PathBuf, + release_channel: String, + annotations: Annotations, + crash_time: u64, + oom_allocation_size: usize, +} + +impl CrashReport { + fn new( + application_information: &ApplicationInformation, + startup_time: u64, + oom_allocation_size: usize, + ) -> CrashReport { + let uuid = Uuid::new_v4() + .as_hyphenated() + .encode_lower(&mut Uuid::encode_buffer()) + .to_owned(); + let crash_reports_path = application_information.crash_reports_dir.clone(); + let crash_time: u64 = unsafe { time(null_mut()) as u64 }; + let annotations = Annotations::from_application_data( + &application_information.application_data, + application_information.release_channel.clone(), + application_information.install_time.clone(), + crash_time, + startup_time, + oom_allocation_size, + ); + CrashReport { + uuid, + crash_reports_path, + release_channel: application_information.release_channel.clone(), + annotations, + crash_time, + oom_allocation_size, + } + } + + fn is_nightly(&self) -> bool { + self.release_channel == "nightly" || self.release_channel == "default" + } + + fn get_minidump_type(&self) -> MINIDUMP_TYPE { + let mut minidump_type = MiniDumpWithFullMemoryInfo | MiniDumpWithUnloadedModules; + if self.is_nightly() { + // This is Nightly only because this doubles the size of minidumps based + // on the experimental data. + minidump_type = minidump_type | MiniDumpWithProcessThreadData; + + // dbghelp.dll on Win7 can't handle overlapping memory regions so we only + // enable this feature on Win8 or later. + if is_windows8_or_later() { + // This allows us to examine heap objects referenced from stack objects + // at the cost of further doubling the size of minidumps. + minidump_type = minidump_type | MiniDumpWithIndirectlyReferencedMemory + } + } + minidump_type + } + + fn get_pending_path(&self) -> PathBuf { + self.crash_reports_path.join("pending") + } + + fn get_events_path(&self) -> PathBuf { + self.crash_reports_path.join("events") + } + + fn get_minidump_path(&self) -> PathBuf { + self.get_pending_path().join(self.uuid.to_string() + ".dmp") + } + + fn get_minidump_name(&self) -> [u8; 40] { + let bytes = (self.uuid.to_string() + ".dmp").into_bytes(); + bytes[0..40].try_into().unwrap() + } + + fn get_extra_file_path(&self) -> PathBuf { + self.get_pending_path() + .join(self.uuid.to_string() + ".extra") + } + + fn get_event_file_path(&self) -> PathBuf { + self.get_events_path().join(self.uuid.to_string()) + } + + fn write_minidump( + &self, + exception_information: PWER_RUNTIME_EXCEPTION_INFORMATION, + ) -> Result<(), ()> { + let minidump_path = self.get_minidump_path(); + let minidump_file = File::create(minidump_path).map_err(|_e| ())?; + let minidump_type: MINIDUMP_TYPE = self.get_minidump_type(); + + unsafe { + let mut exception_pointers = EXCEPTION_POINTERS { + ExceptionRecord: &mut ((*exception_information).exceptionRecord), + ContextRecord: &mut ((*exception_information).context), + }; + + let mut exception = MINIDUMP_EXCEPTION_INFORMATION { + ThreadId: GetThreadId((*exception_information).hThread), + ExceptionPointers: &mut exception_pointers, + ClientPointers: FALSE, + }; + + let res = MiniDumpWriteDump( + (*exception_information).hProcess, + get_process_id((*exception_information).hProcess)?, + minidump_file.as_raw_handle() as _, + minidump_type, + &mut exception, + /* userStream */ null(), + /* callback */ null(), + ); + + bool_ok_or_err(res, ()) + } + } + + fn write_extra_file(&self) -> Result<(), ()> { + let extra_file = File::create(self.get_extra_file_path()).map_err(|_e| ())?; + to_writer(extra_file, &self.annotations).map_err(|_e| ()) + } + + fn write_event_file(&self) -> Result<(), ()> { + let mut event_file = File::create(self.get_event_file_path()).map_err(|_e| ())?; + writeln!(event_file, "crash.main.3").map_err(|_e| ())?; + writeln!(event_file, "{}", self.crash_time).map_err(|_e| ())?; + writeln!(event_file, "{}", self.uuid).map_err(|_e| ())?; + to_writer(event_file, &self.annotations).map_err(|_e| ()) + } +} + +fn is_windows8_or_later() -> bool { + let mut info = OSVERSIONINFOEXW { + dwOSVersionInfoSize: size_of::<OSVERSIONINFOEXW>().try_into().unwrap(), + dwMajorVersion: 6, + dwMinorVersion: 2, + ..Default::default() + }; + + unsafe { + let mut mask: DWORDLONG = 0; + mask = VerSetConditionMask(mask, VER_MAJORVERSION, VER_GREATER_EQUAL); + mask = VerSetConditionMask(mask, VER_MINORVERSION, VER_GREATER_EQUAL); + mask = VerSetConditionMask(mask, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); + mask = VerSetConditionMask(mask, VER_SERVICEPACKMINOR, VER_GREATER_EQUAL); + + let res = VerifyVersionInfoW( + &mut info as LPOSVERSIONINFOEXW, + VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR, + mask, + ); + + res != FALSE + } +} + +fn bool_ok_or_err<T>(res: BOOL, data: T) -> Result<T, ()> { + match res { + FALSE => Err(()), + _ => Ok(data), + } +} + +fn read_environment_block(process: HANDLE) -> Result<Vec<u16>, ()> { + let upp = read_user_process_parameters(process)?; + + // Read the environment + let buffer = upp.Environment; + let length = upp.EnvironmentSize; + let count = length as usize / 2; + read_array_from_process::<u16>(process, buffer, count) +} + +fn read_command_line(process: HANDLE) -> Result<String, ()> { + let upp = read_user_process_parameters(process)?; + + // Read the command-line + let buffer = upp.CommandLine.Buffer; + let length = upp.CommandLine.Length; + let count = (length as usize) / 2; + let command_line = read_array_from_process::<u16>(process, buffer as *mut _, count)?; + String::from_utf16(&command_line).map_err(|_err| ()) +} + +fn read_user_process_parameters(process: HANDLE) -> Result<RTL_USER_PROCESS_PARAMETERS, ()> { + let mut pbi: PROCESS_BASIC_INFORMATION = unsafe { zeroed() }; + let mut length: ULONG = 0; + let result = unsafe { + NtQueryInformationProcess( + process, + ProcessBasicInformation, + &mut pbi as *mut _ as _, + size_of::<PROCESS_BASIC_INFORMATION>().try_into().unwrap(), + &mut length, + ) + }; + + if result != STATUS_SUCCESS { + return Err(()); + } + + // Read the process environment block + let peb: PEB = read_from_process(process, pbi.PebBaseAddress)?; + + // Read the user process parameters + read_from_process::<RTL_USER_PROCESS_PARAMETERS>(process, peb.ProcessParameters) +} + +/****************************************************************************** + * The stuff below should be migrated to the winapi crate, see bug 1696414 * + ******************************************************************************/ + +// we can't use winapi's ENUM macro directly because it doesn't support +// attributes, so let's define this one here until we migrate this code +macro_rules! ENUM { + {enum $name:ident { $($variant:ident = $value:expr,)+ }} => { + #[allow(non_camel_case_types)] pub type $name = u32; + $(#[allow(non_upper_case_globals)] pub const $variant: $name = $value;)+ + }; +} + +// winapi doesn't export the FN macro, so we duplicate it here +macro_rules! FN { + (stdcall $func:ident($($t:ty,)*) -> $ret:ty) => ( + #[allow(non_camel_case_types)] pub type $func = Option<unsafe extern "system" fn($($t,)*) -> $ret>; + ); + (stdcall $func:ident($($p:ident: $t:ty,)*) -> $ret:ty) => ( + #[allow(non_camel_case_types)] pub type $func = Option<unsafe extern "system" fn($($p: $t,)*) -> $ret>; + ); +} + +// From um/WerApi.h + +STRUCT! {#[allow(non_snake_case)] struct WER_RUNTIME_EXCEPTION_INFORMATION +{ + dwSize: DWORD, + hProcess: HANDLE, + hThread: HANDLE, + exceptionRecord: EXCEPTION_RECORD, + context: CONTEXT, + pwszReportId: PCWSTR, + bIsFatal: BOOL, + dwReserved: DWORD, +}} + +#[allow(non_camel_case_types)] +pub type PWER_RUNTIME_EXCEPTION_INFORMATION = *mut WER_RUNTIME_EXCEPTION_INFORMATION; + +// From minidumpapiset.hProcess + +STRUCT! {#[allow(non_snake_case)] #[repr(packed(4))] struct MINIDUMP_EXCEPTION_INFORMATION { + ThreadId: DWORD, + ExceptionPointers: PEXCEPTION_POINTERS, + ClientPointers: BOOL, +}} + +#[allow(non_camel_case_types)] +pub type PMINIDUMP_EXCEPTION_INFORMATION = *mut MINIDUMP_EXCEPTION_INFORMATION; + +ENUM! { enum MINIDUMP_TYPE { + MiniDumpNormal = 0x00000000, + MiniDumpWithDataSegs = 0x00000001, + MiniDumpWithFullMemory = 0x00000002, + MiniDumpWithHandleData = 0x00000004, + MiniDumpFilterMemory = 0x00000008, + MiniDumpScanMemory = 0x00000010, + MiniDumpWithUnloadedModules = 0x00000020, + MiniDumpWithIndirectlyReferencedMemory = 0x00000040, + MiniDumpFilterModulePaths = 0x00000080, + MiniDumpWithProcessThreadData = 0x00000100, + MiniDumpWithPrivateReadWriteMemory = 0x00000200, + MiniDumpWithoutOptionalData = 0x00000400, + MiniDumpWithFullMemoryInfo = 0x00000800, + MiniDumpWithThreadInfo = 0x00001000, + MiniDumpWithCodeSegs = 0x00002000, + MiniDumpWithoutAuxiliaryState = 0x00004000, + MiniDumpWithFullAuxiliaryState = 0x00008000, + MiniDumpWithPrivateWriteCopyMemory = 0x00010000, + MiniDumpIgnoreInaccessibleMemory = 0x00020000, + MiniDumpWithTokenInformation = 0x00040000, + MiniDumpWithModuleHeaders = 0x00080000, + MiniDumpFilterTriage = 0x00100000, + MiniDumpWithAvxXStateContext = 0x00200000, + MiniDumpWithIptTrace = 0x00400000, + MiniDumpScanInaccessiblePartialPages = 0x00800000, + MiniDumpValidTypeFlags = 0x00ffffff, +}} + +// We don't actually need the following three structs so we use placeholders +STRUCT! {#[allow(non_snake_case)] struct MINIDUMP_CALLBACK_INPUT { + dummy: u32, +}} + +#[allow(non_camel_case_types)] +pub type PMINIDUMP_CALLBACK_INPUT = *const MINIDUMP_CALLBACK_INPUT; + +STRUCT! {#[allow(non_snake_case)] struct MINIDUMP_USER_STREAM_INFORMATION { + dummy: u32, +}} + +#[allow(non_camel_case_types)] +pub type PMINIDUMP_USER_STREAM_INFORMATION = *const MINIDUMP_USER_STREAM_INFORMATION; + +STRUCT! {#[allow(non_snake_case)] struct MINIDUMP_CALLBACK_OUTPUT { + dummy: u32, +}} + +#[allow(non_camel_case_types)] +pub type PMINIDUMP_CALLBACK_OUTPUT = *const MINIDUMP_CALLBACK_OUTPUT; + +// MiniDumpWriteDump() function and structs +FN! {stdcall MINIDUMP_CALLBACK_ROUTINE( +CallbackParam: PVOID, +CallbackInput: PMINIDUMP_CALLBACK_INPUT, +CallbackOutput: PMINIDUMP_CALLBACK_OUTPUT, +) -> BOOL} + +STRUCT! {#[allow(non_snake_case)] #[repr(packed(4))] struct MINIDUMP_CALLBACK_INFORMATION { + CallbackRoutine: MINIDUMP_CALLBACK_ROUTINE, + CallbackParam: PVOID, +}} + +#[allow(non_camel_case_types)] +pub type PMINIDUMP_CALLBACK_INFORMATION = *const MINIDUMP_CALLBACK_INFORMATION; + +extern "system" { + pub fn MiniDumpWriteDump( + hProcess: HANDLE, + ProcessId: DWORD, + hFile: HANDLE, + DumpType: MINIDUMP_TYPE, + Exceptionparam: PMINIDUMP_EXCEPTION_INFORMATION, + UserStreamParam: PMINIDUMP_USER_STREAM_INFORMATION, + CallbackParam: PMINIDUMP_CALLBACK_INFORMATION, + ) -> BOOL; +} + +// From um/winternl.h + +STRUCT! {#[allow(non_snake_case)] struct PEB_LDR_DATA { + Reserved1: [BYTE; 8], + Reserved2: [PVOID; 3], + InMemoryOrderModuleList: LIST_ENTRY, +}} + +#[allow(non_camel_case_types)] +pub type PPEB_LDR_DATA = *mut PEB_LDR_DATA; + +STRUCT! {#[allow(non_snake_case)] struct RTL_DRIVE_LETTER_CURDIR { + Flags: WORD, + Length: WORD, + TimeStamp: ULONG, + DosPath: STRING, +}} + +STRUCT! {#[allow(non_snake_case)] struct RTL_USER_PROCESS_PARAMETERS { + Reserved1: [BYTE; 16], + Reserved2: [PVOID; 10], + ImagePathName: UNICODE_STRING, + CommandLine: UNICODE_STRING, + // Everything below this point is undocumented + Environment: PVOID, + StartingX: ULONG, + StartingY: ULONG, + CountX: ULONG, + CountY: ULONG, + CountCharsX: ULONG, + CountCharsY: ULONG, + FillAttribute: ULONG, + WindowFlags: ULONG, + ShowWindowFlags: ULONG, + WindowTitle: UNICODE_STRING, + DesktopInfo: UNICODE_STRING, + ShellInfo: UNICODE_STRING, + RuntimeData: UNICODE_STRING, + CurrentDirectores: [RTL_DRIVE_LETTER_CURDIR; 32], + EnvironmentSize: ULONG, +}} + +#[allow(non_camel_case_types)] +pub type PRTL_USER_PROCESS_PARAMETERS = *mut RTL_USER_PROCESS_PARAMETERS; + +FN! {stdcall PPS_POST_PROCESS_INIT_ROUTINE() -> ()} + +STRUCT! {#[allow(non_snake_case)] struct PEB { + Reserved1: [BYTE; 2], + BeingDebugged: BYTE, + Reserved2: [BYTE; 1], + Reserved3: [PVOID; 2], + Ldr: PPEB_LDR_DATA, + ProcessParameters: PRTL_USER_PROCESS_PARAMETERS, + Reserved4: [PVOID; 3], + AtlThunkSListPtr: PVOID, + Reserved5: PVOID, + Reserved6: ULONG, + Reserved7: PVOID, + Reserved8: ULONG, + AtlThunkSListPtr32: ULONG, + Reserved9: [PVOID; 45], + Reserved10: [BYTE; 96], + PostProcessInitRoutine: PPS_POST_PROCESS_INIT_ROUTINE, + Reserved11: [BYTE; 128], + Reserved12: [PVOID; 1], + SessionId: ULONG, +}} + +#[allow(non_camel_case_types)] +pub type PPEB = *mut PEB; + +STRUCT! {#[allow(non_snake_case)] struct PROCESS_BASIC_INFORMATION { + Reserved1: PVOID, + PebBaseAddress: PPEB, + Reserved2: [PVOID; 2], + UniqueProcessId: ULONG_PTR, + Reserved3: PVOID, +}} + +ENUM! {enum PROCESSINFOCLASS { + ProcessBasicInformation = 0, + ProcessDebugPort = 7, + ProcessWow64Information = 26, + ProcessImageFileName = 27, + ProcessBreakOnTermination = 29, +}} + +extern "system" { + pub fn NtQueryInformationProcess( + ProcessHandle: HANDLE, + ProcessInformationClass: PROCESSINFOCLASS, + ProcessInformation: PVOID, + ProcessInformationLength: ULONG, + ReturnLength: PULONG, + ) -> NTSTATUS; +} |