diff options
Diffstat (limited to 'toolkit/crashreporter/mozwer-rust/lib.rs')
-rw-r--r-- | toolkit/crashreporter/mozwer-rust/lib.rs | 315 |
1 files changed, 152 insertions, 163 deletions
diff --git a/toolkit/crashreporter/mozwer-rust/lib.rs b/toolkit/crashreporter/mozwer-rust/lib.rs index fab6c4f85c..16889a0cce 100644 --- a/toolkit/crashreporter/mozwer-rust/lib.rs +++ b/toolkit/crashreporter/mozwer-rust/lib.rs @@ -7,17 +7,18 @@ extern crate winapi; use ini::Ini; use libc::time; +use process_reader::ProcessReader; 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::fs::{read_to_string, DirBuilder, File}; use std::io::{BufRead, BufReader, Write}; -use std::mem::{size_of, zeroed}; +use std::mem::{size_of, transmute, zeroed}; use std::os::windows::ffi::{OsStrExt, OsStringExt}; -use std::os::windows::io::AsRawHandle; +use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle, RawHandle}; use std::path::{Path, PathBuf}; -use std::ptr::{addr_of_mut, null, null_mut}; +use std::ptr::{addr_of, null, null_mut}; use std::slice::from_raw_parts; use uuid::Uuid; use winapi::shared::basetsd::{SIZE_T, ULONG_PTR}; @@ -26,17 +27,21 @@ use winapi::shared::minwindef::{ }; use winapi::shared::ntdef::{NTSTATUS, STRING, UNICODE_STRING}; use winapi::shared::ntstatus::STATUS_SUCCESS; -use winapi::shared::winerror::{E_UNEXPECTED, S_OK}; +use winapi::shared::winerror::{ERROR_INSUFFICIENT_BUFFER, ERROR_SUCCESS, E_UNEXPECTED, S_OK}; use winapi::um::combaseapi::CoTaskMemFree; +use winapi::um::errhandlingapi::{GetLastError, SetLastError}; use winapi::um::handleapi::CloseHandle; use winapi::um::knownfolders::FOLDERID_RoamingAppData; -use winapi::um::memoryapi::{ReadProcessMemory, WriteProcessMemory}; +use winapi::um::memoryapi::{VirtualAllocEx, VirtualFreeEx, WriteProcessMemory}; use winapi::um::minwinbase::LPTHREAD_START_ROUTINE; use winapi::um::processthreadsapi::{ CreateProcessW, CreateRemoteThread, GetProcessId, GetProcessTimes, GetThreadId, OpenProcess, - TerminateProcess, PROCESS_INFORMATION, STARTUPINFOW, + OpenProcessToken, TerminateProcess, PROCESS_INFORMATION, STARTUPINFOW, }; use winapi::um::psapi::K32GetModuleFileNameExW; +use winapi::um::securitybaseapi::{ + GetSidSubAuthority, GetSidSubAuthorityCount, GetTokenInformation, IsTokenRestricted, +}; use winapi::um::shlobj::SHGetKnownFolderPath; use winapi::um::synchapi::WaitForSingleObject; use winapi::um::winbase::{ @@ -44,33 +49,22 @@ use winapi::um::winbase::{ 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, + TokenIntegrityLevel, VerSetConditionMask, CONTEXT, DWORDLONG, EXCEPTION_POINTERS, + EXCEPTION_RECORD, HANDLE, HRESULT, LIST_ENTRY, LPOSVERSIONINFOEXW, MEM_COMMIT, MEM_RELEASE, + MEM_RESERVE, OSVERSIONINFOEXW, PAGE_READWRITE, PCWSTR, PEXCEPTION_POINTERS, PROCESS_ALL_ACCESS, + PVOID, PWSTR, SECURITY_MANDATORY_MEDIUM_RID, TOKEN_MANDATORY_LABEL, TOKEN_QUERY, + 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. */ + * nsExceptionHandler.h. 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 @@ -141,108 +135,95 @@ fn out_of_process_exception_event_callback( 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 process_type: u32 = (context as usize).try_into().map_err(|_| ())?; 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); + let crash_report = CrashReport::new(&application_info, startup_time); crash_report.write_minidump(exception_information)?; - if wer_data.process_type == MAIN_PROCESS_TYPE { - handle_main_process_crash(crash_report, process, &application_info) + if process_type == MAIN_PROCESS_TYPE { + match is_sandboxed_process(process) { + Ok(false) => handle_main_process_crash(crash_report, &application_info), + _ => { + // The parent process should never be sandboxed, bail out so the + // process which is impersonating it gets killed right away. Also + // bail out if is_sandboxed_process() failed while checking. + Ok(()) + } + } } else { handle_child_process_crash(crash_report, process) } } +fn get_parent_process(process: HANDLE) -> Result<HANDLE, ()> { + let pbi = get_process_basic_information(process)?; + get_process_handle(pbi.InheritedFromUniqueProcessId as u32) +} + 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, - ); + launch_crash_reporter_client(&application_information.install_path, &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(), - ) + let parent_process = get_parent_process(child_process)?; + let process_reader = ProcessReader::new(parent_process).map_err(|_e| ())?; + let wer_notify_proc = process_reader + .find_section("xul.dll", "mozwerpt") + .map_err(|_e| ())?; + let wer_notify_proc = unsafe { transmute::<_, LPTHREAD_START_ROUTINE>(wer_notify_proc) }; + + let wer_data = WindowsErrorReportingData { + child_pid: get_process_id(child_process)?, + minidump_name: crash_report.get_minidump_name(), }; - - bool_ok_or_err(res, data) + let address = copy_object_into_process(parent_process, wer_data)?; + notify_main_process(parent_process, wer_notify_proc, address) } -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( +fn copy_object_into_process<T>(process: HANDLE, data: T) -> Result<*mut T, ()> { + let address = unsafe { + VirtualAllocEx( process, - data_ptr, - array.as_mut_ptr() as *mut _, - size, null_mut(), + size_of::<T>(), + MEM_RESERVE | MEM_COMMIT, + PAGE_READWRITE, ) }; - bool_ok_or_err(res, array) -} + if address.is_null() { + return Err(()); + } -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, + address, + addr_of!(data) as *const _, + size_of::<T>(), null_mut(), ) }; - bool_ok_or_err(res, ()) + if res == 0 { + unsafe { VirtualFreeEx(process, address as *mut _, 0, MEM_RELEASE) }; + Err(()) + } else { + Ok(address as *mut T) + } } fn notify_main_process( process: HANDLE, wer_notify_proc: LPTHREAD_START_ROUTINE, - data_ptr: *mut WindowsErrorReportingData, + address: *mut WindowsErrorReportingData, ) -> Result<(), ()> { let thread = unsafe { CreateRemoteThread( @@ -250,24 +231,29 @@ fn notify_main_process( null_mut(), 0, wer_notify_proc, - data_ptr as LPVOID, + address as LPVOID, 0, null_mut(), ) }; if thread == null_mut() { + unsafe { VirtualFreeEx(process, address as *mut _, 0, MEM_RELEASE) }; return Err(()); } + // From this point on the memory pointed to by address is owned by the + // thread we've created in the main process, so we don't free it. + + let thread = unsafe { OwnedHandle::from_raw_handle(thread as RawHandle) }; + // Don't wait forever as we want the process to get killed eventually - let res = unsafe { WaitForSingleObject(thread, 5000) }; + let res = unsafe { WaitForSingleObject(thread.as_raw_handle() as HANDLE, 5000) }; if res != WAIT_OBJECT_0 { return Err(()); } - let res = unsafe { CloseHandle(thread) }; - bool_ok_or_err(res, ()) + Ok(()) } fn get_startup_time(process: HANDLE) -> Result<u64, ()> { @@ -294,24 +280,6 @@ fn get_startup_time(process: HANDLE) -> Result<u64, ()> { 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(()), @@ -328,11 +296,7 @@ fn get_process_handle(pid: DWORD) -> Result<HANDLE, ()> { } } -fn launch_crash_reporter_client( - install_path: &Path, - environment: &mut Vec<u16>, - crash_report: &CrashReport, -) { +fn launch_crash_reporter_client(install_path: &Path, crash_report: &CrashReport) { // Prepare the command line let client_path = install_path.join("crashreporter.exe"); @@ -357,7 +321,7 @@ fn launch_crash_reporter_client( null_mut(), FALSE, NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT, - environment.as_mut_ptr() as *mut _, + null_mut(), null_mut(), &mut si, &mut pi, @@ -422,8 +386,6 @@ struct Annotations { BuildID: String, CrashTime: String, InstallTime: String, - #[serde(skip_serializing_if = "Option::is_none")] - OOMAllocationSize: Option<String>, ProductID: String, ProductName: String, ReleaseChannel: String, @@ -443,18 +405,11 @@ impl Annotations { 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, @@ -487,7 +442,8 @@ impl ApplicationInformation { let install_time = ApplicationInformation::get_install_time( &crash_reports_dir, &application_data.build_id, - )?; + ) + .unwrap_or("0".to_string()); Ok(ApplicationInformation { install_path, @@ -571,15 +527,10 @@ struct CrashReport { 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 { + fn new(application_information: &ApplicationInformation, startup_time: u64) -> CrashReport { let uuid = Uuid::new_v4() .as_hyphenated() .encode_lower(&mut Uuid::encode_buffer()) @@ -592,7 +543,6 @@ impl CrashReport { application_information.install_time.clone(), crash_time, startup_time, - oom_allocation_size, ); CrashReport { uuid, @@ -600,7 +550,6 @@ impl CrashReport { release_channel: application_information.release_channel.clone(), annotations, crash_time, - oom_allocation_size, } } @@ -656,6 +605,12 @@ impl CrashReport { &self, exception_information: PWER_RUNTIME_EXCEPTION_INFORMATION, ) -> Result<(), ()> { + // Make sure that the target directory is present + DirBuilder::new() + .recursive(true) + .create(self.get_pending_path()) + .map_err(|_e| ())?; + 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(); @@ -692,6 +647,12 @@ impl CrashReport { } fn write_event_file(&self) -> Result<(), ()> { + // Make that the target directory is present + DirBuilder::new() + .recursive(true) + .create(self.get_events_path()) + .map_err(|_e| ())?; + 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| ())?; @@ -732,28 +693,7 @@ fn bool_ok_or_err<T>(res: BOOL, data: T) -> Result<T, ()> { } } -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, ()> { +fn get_process_basic_information(process: HANDLE) -> Result<PROCESS_BASIC_INFORMATION, ()> { let mut pbi: PROCESS_BASIC_INFORMATION = unsafe { zeroed() }; let mut length: ULONG = 0; let result = unsafe { @@ -770,11 +710,59 @@ fn read_user_process_parameters(process: HANDLE) -> Result<RTL_USER_PROCESS_PARA return Err(()); } - // Read the process environment block - let peb: PEB = read_from_process(process, pbi.PebBaseAddress)?; + Ok(pbi) +} + +fn is_sandboxed_process(process: HANDLE) -> Result<bool, ()> { + let mut token: HANDLE = null_mut(); + let res = unsafe { OpenProcessToken(process, TOKEN_QUERY, &mut token as *mut _) }; + + if res != TRUE { + return Err(()); + } + + let is_restricted = unsafe { IsTokenRestricted(token) } != FALSE; + + unsafe { SetLastError(ERROR_SUCCESS) }; + let mut buffer_size: DWORD = 0; + let res = unsafe { + GetTokenInformation( + token, + TokenIntegrityLevel, + null_mut(), + 0, + &mut buffer_size as *mut _, + ) + }; + + if (res != FALSE) || (unsafe { GetLastError() } != ERROR_INSUFFICIENT_BUFFER) { + return Err(()); + } + + let mut buffer: Vec<u8> = vec![Default::default(); buffer_size as usize]; + let res = unsafe { + GetTokenInformation( + token, + TokenIntegrityLevel, + buffer.as_mut_ptr() as *mut _, + buffer_size, + &mut buffer_size as *mut _, + ) + }; + + if res != TRUE { + return Err(()); + } + + let token_mandatory_label = &unsafe { *(buffer.as_ptr() as *const TOKEN_MANDATORY_LABEL) }; + let sid = token_mandatory_label.Label.Sid; + // We're not checking for errors in the following two calls because these + // functions can only fail if provided with an invalid SID and we know the + // one we obtained from `GetTokenInformation()` is valid. + let sid_subauthority_count = unsafe { *GetSidSubAuthorityCount(sid) - 1u8 }; + let integrity_level = unsafe { *GetSidSubAuthority(sid, sid_subauthority_count.into()) }; - // Read the user process parameters - read_from_process::<RTL_USER_PROCESS_PARAMETERS>(process, peb.ProcessParameters) + Ok((integrity_level < SECURITY_MANDATORY_MEDIUM_RID as u32) || is_restricted) } /****************************************************************************** @@ -979,11 +967,12 @@ STRUCT! {#[allow(non_snake_case)] struct PEB { pub type PPEB = *mut PEB; STRUCT! {#[allow(non_snake_case)] struct PROCESS_BASIC_INFORMATION { - Reserved1: PVOID, + ExitStatus: NTSTATUS, PebBaseAddress: PPEB, - Reserved2: [PVOID; 2], + AffinityMask: SIZE_T, + BasePriority: DWORD, UniqueProcessId: ULONG_PTR, - Reserved3: PVOID, + InheritedFromUniqueProcessId: ULONG_PTR, }} ENUM! {enum PROCESSINFOCLASS { |