From a5f28e22cc28ca53d092f6545bc924ee43a8bbe4 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Mon, 8 Apr 2024 17:18:54 +0200 Subject: Merging upstream version 115.9.0esr. Signed-off-by: Daniel Baumann --- toolkit/crashreporter/client/crashreporter.cpp | 33 ++- toolkit/crashreporter/client/moz.build | 1 + toolkit/crashreporter/docs/index.rst | 5 +- toolkit/crashreporter/mozwer-rust/Cargo.toml | 1 + toolkit/crashreporter/mozwer-rust/lib.rs | 315 ++++++++++----------- toolkit/crashreporter/mozwer-rust/moz.build | 1 + toolkit/crashreporter/nsExceptionHandler.cpp | 27 +- toolkit/crashreporter/nsExceptionHandler.h | 13 +- toolkit/crashreporter/process_reader/Cargo.toml | 18 ++ toolkit/crashreporter/process_reader/src/error.rs | 30 ++ toolkit/crashreporter/process_reader/src/lib.rs | 13 + .../process_reader/src/process_reader.rs | 6 + .../process_reader/src/process_reader/windows.rs | 226 +++++++++++++++ 13 files changed, 490 insertions(+), 199 deletions(-) create mode 100644 toolkit/crashreporter/process_reader/Cargo.toml create mode 100644 toolkit/crashreporter/process_reader/src/error.rs create mode 100644 toolkit/crashreporter/process_reader/src/lib.rs create mode 100644 toolkit/crashreporter/process_reader/src/process_reader.rs create mode 100644 toolkit/crashreporter/process_reader/src/process_reader/windows.rs (limited to 'toolkit/crashreporter') diff --git a/toolkit/crashreporter/client/crashreporter.cpp b/toolkit/crashreporter/client/crashreporter.cpp index 4426e6446c..a14ea14fbc 100644 --- a/toolkit/crashreporter/client/crashreporter.cpp +++ b/toolkit/crashreporter/client/crashreporter.cpp @@ -753,16 +753,31 @@ int main(int argc, char** argv) { vector restartArgs; - ostringstream paramName; - int i = 0; - paramName << "MOZ_CRASHREPORTER_RESTART_ARG_" << i++; - const char* param = getenv(paramName.str().c_str()); - while (param && *param) { - restartArgs.push_back(param); - - paramName.str(""); + if (!extraData.isMember("WindowsErrorReporting")) { + // We relaunch the application associated with the client, but only when + // we encountered a crash caught by the exception handler. Crashes handled + // by WER are prevented from directly restarting the application. + string programPath = GetProgramPath(MOZ_APP_NAME); +#ifndef XP_WIN + const char* moz_app_launcher = getenv("MOZ_APP_LAUNCHER"); + if (moz_app_launcher) { + programPath = moz_app_launcher; + } +#endif // XP_WIN + + restartArgs.push_back(programPath); + + ostringstream paramName; + int i = 1; paramName << "MOZ_CRASHREPORTER_RESTART_ARG_" << i++; - param = getenv(paramName.str().c_str()); + const char* param = getenv(paramName.str().c_str()); + while (param && *param) { + restartArgs.push_back(param); + + paramName.str(""); + paramName << "MOZ_CRASHREPORTER_RESTART_ARG_" << i++; + param = getenv(paramName.str().c_str()); + } } // allow override of the server url via environment variable diff --git a/toolkit/crashreporter/client/moz.build b/toolkit/crashreporter/client/moz.build index f678ca1cd6..82e19b8637 100644 --- a/toolkit/crashreporter/client/moz.build +++ b/toolkit/crashreporter/client/moz.build @@ -85,6 +85,7 @@ if CONFIG["OS_ARCH"] == "Linux" or CONFIG["OS_ARCH"] == "SunOS": "Throbber-small.gif", ] +DEFINES["MOZ_APP_NAME"] = '"%s"' % CONFIG["MOZ_APP_NAME"] DEFINES["BIN_SUFFIX"] = '"%s"' % CONFIG["BIN_SUFFIX"] RCINCLUDE = "crashreporter.rc" diff --git a/toolkit/crashreporter/docs/index.rst b/toolkit/crashreporter/docs/index.rst index 57b4c3a5e5..f2444a75c6 100644 --- a/toolkit/crashreporter/docs/index.rst +++ b/toolkit/crashreporter/docs/index.rst @@ -248,8 +248,9 @@ Environment variables used internally - ``MOZ_CRASHREPORTER_PING_DIRECTORY`` - Path of the directory holding the pending crash ping files. - ``MOZ_CRASHREPORTER_RESTART_ARG_`` - Each of these variable specifies one - of the arguments that had been passed to the application, the crash reporter - client uses them for restarting it. + of the arguments that had been passed to the application, starting with the + first after the executable, the crash reporter client uses them for restarting + it. - ``MOZ_CRASHREPORTER_RESTART_XUL_APP_FILE`` - If a XUL app file was specified when starting the app it has to be stored in this variable so that the crash reporter client can restart the application. diff --git a/toolkit/crashreporter/mozwer-rust/Cargo.toml b/toolkit/crashreporter/mozwer-rust/Cargo.toml index 6b960d827d..ae45d20360 100644 --- a/toolkit/crashreporter/mozwer-rust/Cargo.toml +++ b/toolkit/crashreporter/mozwer-rust/Cargo.toml @@ -8,6 +8,7 @@ license = "MPL-2.0" [dependencies] libc = "0.2.0" mozilla-central-workspace-hack = { path = "../../../build/workspace-hack" } +process_reader = { path = "../process_reader/" } rust-ini = "0.10" serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0" } 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::( - 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 { + 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(process: HANDLE, data_ptr: *mut T) -> Result { - let mut data: T = unsafe { zeroed() }; - let res = unsafe { - ReadProcessMemory( - process, - data_ptr as *mut _, - addr_of_mut!(data) as *mut _, - size_of::() 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( - process: HANDLE, - data_ptr: LPVOID, - count: usize, -) -> Result, ()> { - let mut array = vec![Default::default(); count]; - let size = size_of::() as SIZE_T; - let size = size.checked_mul(count).ok_or(())?; - let res = unsafe { - ReadProcessMemory( +fn copy_object_into_process(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::(), + MEM_RESERVE | MEM_COMMIT, + PAGE_READWRITE, ) }; - bool_ok_or_err(res, array) -} + if address.is_null() { + return Err(()); + } -fn write_to_process(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::() as SIZE_T, + address, + addr_of!(data) as *const _, + size_of::(), 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 { @@ -294,24 +280,6 @@ fn get_startup_time(process: HANDLE) -> Result { 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 { match unsafe { GetProcessId(process) } { 0 => Err(()), @@ -328,11 +296,7 @@ fn get_process_handle(pid: DWORD) -> Result { } } -fn launch_crash_reporter_client( - install_path: &Path, - environment: &mut Vec, - 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, 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(res: BOOL, data: T) -> Result { } } -fn read_environment_block(process: HANDLE) -> Result, ()> { - 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::(process, buffer, count) -} - -fn read_command_line(process: HANDLE) -> Result { - 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::(process, buffer as *mut _, count)?; - String::from_utf16(&command_line).map_err(|_err| ()) -} - -fn read_user_process_parameters(process: HANDLE) -> Result { +fn get_process_basic_information(process: HANDLE) -> Result { 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 Result { + 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 = 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::(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 { diff --git a/toolkit/crashreporter/mozwer-rust/moz.build b/toolkit/crashreporter/mozwer-rust/moz.build index dc37e8cced..a9b049ecfe 100644 --- a/toolkit/crashreporter/mozwer-rust/moz.build +++ b/toolkit/crashreporter/mozwer-rust/moz.build @@ -5,6 +5,7 @@ OS_LIBS += [ "kernel32", "ntdll", "ole32", + "psapi", "shell32", "userenv", "ws2_32", diff --git a/toolkit/crashreporter/nsExceptionHandler.cpp b/toolkit/crashreporter/nsExceptionHandler.cpp index 534f5ab48c..dacc71bd18 100644 --- a/toolkit/crashreporter/nsExceptionHandler.cpp +++ b/toolkit/crashreporter/nsExceptionHandler.cpp @@ -2551,17 +2551,11 @@ nsresult SetRestartArgs(int argc, char** argv) { int i; nsAutoCString envVar; char* env; - char* argv0 = getenv("MOZ_APP_LAUNCHER"); - for (i = 0; i < argc; i++) { + for (i = 1; i < argc; i++) { envVar = "MOZ_CRASHREPORTER_RESTART_ARG_"; envVar.AppendInt(i); envVar += "="; - if (argv0 && i == 0) { - // Is there a request to suppress default binary launcher? - envVar += argv0; - } else { - envVar += argv[i]; - } + envVar += argv[i]; // PR_SetEnv() wants the string to be available for the lifetime // of the app, so dup it here. This conversion is not lossy. @@ -3609,10 +3603,20 @@ bool FinalizeOrphanedMinidump(uint32_t aChildPid, GeckoProcessType aType, // Function invoked by the WER runtime exception handler running in an // external process. This function isn't used anywhere inside Gecko directly // but rather invoked via CreateRemoteThread() in the main process. -DWORD WINAPI WerNotifyProc(LPVOID aParameter) { + +// Store this global in a section called mozwerpt where we can find it by just +// looking at the program headers. +# pragma section("mozwerpt", read, executable, shared) + +__declspec(allocate("mozwerpt")) MOZ_EXPORT DWORD WINAPI + WerNotifyProc(LPVOID aParameter) { const WindowsErrorReportingData* werData = static_cast(aParameter); + auto freeParameterOnExit = MakeScopeExit([&aParameter] { + VirtualFree(aParameter, sizeof(WindowsErrorReportingData), MEM_RELEASE); + }); + // Hold the mutex until the current dump request is complete, to // prevent UnsetExceptionHandler() from pulling the rug out from // under us. @@ -3639,11 +3643,6 @@ DWORD WINAPI WerNotifyProc(LPVOID aParameter) { pd->sequence = ++crashSequence; pd->annotations = MakeUnique(); (*pd->annotations)[Annotation::WindowsErrorReporting] = "1"_ns; - if (werData->mOOMAllocationSize > 0) { - char buffer[32] = {}; - XP_STOA(werData->mOOMAllocationSize, buffer); - (*pd->annotations)[Annotation::OOMAllocationSize] = buffer; - } PopulateContentProcessAnnotations(*(pd->annotations)); } diff --git a/toolkit/crashreporter/nsExceptionHandler.h b/toolkit/crashreporter/nsExceptionHandler.h index 1212ae2e51..d79996244a 100644 --- a/toolkit/crashreporter/nsExceptionHandler.h +++ b/toolkit/crashreporter/nsExceptionHandler.h @@ -173,24 +173,20 @@ nsresult AppendObjCExceptionInfoToAppNotes(void* inException); nsresult GetSubmitReports(bool* aSubmitReport); nsresult SetSubmitReports(bool aSubmitReport); -// Out-of-process crash reporter API. - #ifdef XP_WIN // This data is stored in the parent process, there is one copy for each child // process. The mChildPid and mMinidumpFile fields are filled by the WER runtime // exception module when the associated child process crashes. struct WindowsErrorReportingData { - // Points to the WerNotifyProc function. - LPTHREAD_START_ROUTINE mWerNotifyProc; // PID of the child process that crashed. DWORD mChildPid; // Filename of the generated minidump; this is not a 0-terminated string char mMinidumpFile[40]; - // OOM allocation size for the crash (ignore if zero) - size_t mOOMAllocationSize; }; #endif // XP_WIN +// Out-of-process crash reporter API. + // Initializes out-of-process crash reporting. This method must be called // before the platform-specific notification pipe APIs are called. If called // from off the main thread, this method will synchronously proxy to the main @@ -319,11 +315,6 @@ bool CreateNotificationPipeForChild(int* childCrashFd, int* childCrashRemapFd); #endif // XP_WIN -// Windows Error Reporting helper -#if defined(XP_WIN) -DWORD WINAPI WerNotifyProc(LPVOID aParameter); -#endif - // Child-side API bool SetRemoteExceptionHandler( const char* aCrashPipe = nullptr, diff --git a/toolkit/crashreporter/process_reader/Cargo.toml b/toolkit/crashreporter/process_reader/Cargo.toml new file mode 100644 index 0000000000..ee85951d5b --- /dev/null +++ b/toolkit/crashreporter/process_reader/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "process_reader" +version = "0.1.0" +authors = ["Gabriele Svelto"] +edition = "2018" +license = "MPL-2.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +goblin = { version = "0.6", features = ["elf32", "elf64", "pe32", "pe64"] } +memoffset = "0.8" +mozilla-central-workspace-hack = { path = "../../../build/workspace-hack" } +thiserror = "1.0" + +[target."cfg(target_os = \"windows\")".dependencies] +[dependencies.winapi] +version = "0.3" diff --git a/toolkit/crashreporter/process_reader/src/error.rs b/toolkit/crashreporter/process_reader/src/error.rs new file mode 100644 index 0000000000..af4d803a7a --- /dev/null +++ b/toolkit/crashreporter/process_reader/src/error.rs @@ -0,0 +1,30 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ + +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum ProcessReaderError { + #[error("Could not convert address {0}")] + ConvertAddressError(#[from] std::num::TryFromIntError), + #[cfg(target_os = "windows")] + #[error("Cannot enumerate the target process's modules")] + EnumProcessModulesError, + #[error("goblin failed to parse a module")] + GoblinError(#[from] goblin::error::Error), + #[error("Address was out of bounds")] + InvalidAddress, + #[error("Could not read from the target process address space")] + ReadFromProcessError(#[from] ReadError), + #[cfg(target_os = "windows")] + #[error("Section was not found")] + SectionNotFound, +} + +#[derive(Debug, Error)] +pub enum ReadError { + #[cfg(target_os = "windows")] + #[error("ReadProcessMemory failed")] + ReadProcessMemoryError, +} diff --git a/toolkit/crashreporter/process_reader/src/lib.rs b/toolkit/crashreporter/process_reader/src/lib.rs new file mode 100644 index 0000000000..20bdad5207 --- /dev/null +++ b/toolkit/crashreporter/process_reader/src/lib.rs @@ -0,0 +1,13 @@ +/* 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/. */ + +#[cfg(target_os = "windows")] +type ProcessHandle = winapi::um::winnt::HANDLE; + +pub struct ProcessReader { + process: ProcessHandle, +} + +mod error; +mod process_reader; diff --git a/toolkit/crashreporter/process_reader/src/process_reader.rs b/toolkit/crashreporter/process_reader/src/process_reader.rs new file mode 100644 index 0000000000..1473aafa09 --- /dev/null +++ b/toolkit/crashreporter/process_reader/src/process_reader.rs @@ -0,0 +1,6 @@ +/* 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/. */ + +#[cfg(target_os = "windows")] +mod windows; diff --git a/toolkit/crashreporter/process_reader/src/process_reader/windows.rs b/toolkit/crashreporter/process_reader/src/process_reader/windows.rs new file mode 100644 index 0000000000..c3d44c1a16 --- /dev/null +++ b/toolkit/crashreporter/process_reader/src/process_reader/windows.rs @@ -0,0 +1,226 @@ +/* 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/. */ + +use std::{ + convert::TryInto, + ffi::OsString, + mem::{size_of, MaybeUninit}, + os::windows::ffi::OsStringExt, + ptr::null_mut, +}; + +use winapi::{ + shared::minwindef::{FALSE, HMODULE, MAX_PATH}, + um::{ + memoryapi::ReadProcessMemory, + psapi::{GetModuleBaseNameW, K32EnumProcessModules, K32GetModuleInformation, MODULEINFO}, + }, +}; + +use crate::{ + error::{ProcessReaderError, ReadError}, + ProcessHandle, ProcessReader, +}; + +impl ProcessReader { + pub fn new(process: ProcessHandle) -> Result { + Ok(ProcessReader { process }) + } + + pub fn find_section( + &self, + module_name: &str, + section_name: &str, + ) -> Result { + let modules = self.get_module_list()?; + + modules + .iter() + .filter(|&&module| { + let name = self.get_module_name(module); + // Crude way of mimicking Windows lower-case comparisons but + // sufficient for our use-cases. + if let Some(name) = name { + name.eq_ignore_ascii_case(module_name) + } else { + false + } + }) + .find_map(|&module| { + self.get_module_info(module).and_then(|info| { + self.find_section_in_module( + section_name, + info.lpBaseOfDll as usize, + info.SizeOfImage as usize, + ) + .ok() + }) + }) + .ok_or(ProcessReaderError::InvalidAddress) + } + + fn get_module_list(&self) -> Result, ProcessReaderError> { + let mut module_num: usize = 100; + let mut required_buffer_size: u32 = 0; + let mut module_array = Vec::::with_capacity(module_num); + + loop { + let buffer_size: u32 = (module_num * size_of::()).try_into()?; + let res = unsafe { + K32EnumProcessModules( + self.process, + module_array.as_mut_ptr() as *mut _, + buffer_size, + &mut required_buffer_size as *mut _, + ) + }; + + module_num = required_buffer_size as usize / size_of::(); + + if res == 0 { + if required_buffer_size > buffer_size { + module_array = Vec::::with_capacity(module_num); + } else { + return Err(ProcessReaderError::EnumProcessModulesError); + } + } else { + break; + } + } + + // SAFETY: module_array has been filled by K32EnumProcessModules() + unsafe { + module_array.set_len(module_num); + }; + + Ok(module_array) + } + + fn get_module_name(&self, module: HMODULE) -> Option { + let mut path: [u16; MAX_PATH as usize] = [0; MAX_PATH as usize]; + let res = unsafe { + GetModuleBaseNameW( + self.process, + module, + (&mut path).as_mut_ptr(), + MAX_PATH.try_into().unwrap(), + ) + }; + + if res == 0 { + None + } else { + let name = OsString::from_wide(&path[0..res as usize]); + let name = name.to_str()?; + Some(name.to_string()) + } + } + + fn get_module_info(&self, module: HMODULE) -> Option { + let mut info: MaybeUninit = MaybeUninit::uninit(); + let res = unsafe { + K32GetModuleInformation( + self.process, + module, + info.as_mut_ptr(), + size_of::() as u32, + ) + }; + + if res == 0 { + None + } else { + let info = unsafe { info.assume_init() }; + Some(info) + } + } + + fn find_section_in_module( + &self, + section_name: &str, + module_address: usize, + size: usize, + ) -> Result { + // We read only the first page from the module, this should be more than + // enough to read the header and section list. In the future we might do + // this incrementally but for now goblin requires an array to parse + // so we can't do it just yet. + let page_size = 4096; + if size < page_size { + // Don't try to read from the target module if it's too small + return Err(ProcessReaderError::ReadFromProcessError( + ReadError::ReadProcessMemoryError, + )); + } + + let bytes = self.copy_array(module_address as _, 4096)?; + let header = goblin::pe::header::Header::parse(&bytes)?; + + // Skip the PE header so we can parse the sections + let optional_header_offset = header.dos_header.pe_pointer as usize + + goblin::pe::header::SIZEOF_PE_MAGIC + + goblin::pe::header::SIZEOF_COFF_HEADER; + let offset = + &mut (optional_header_offset + header.coff_header.size_of_optional_header as usize); + + let sections = header.coff_header.sections(&bytes, offset)?; + + for section in sections { + if section.name.eq(section_name.as_bytes()) { + let address = module_address.checked_add(section.virtual_address as usize); + return address.ok_or(ProcessReaderError::InvalidAddress); + } + } + + Err(ProcessReaderError::SectionNotFound) + } + + pub fn copy_object_shallow(&self, src: usize) -> Result, ReadError> { + let mut object = MaybeUninit::::uninit(); + let res = unsafe { + ReadProcessMemory( + self.process, + src as _, + object.as_mut_ptr() as _, + size_of::(), + null_mut(), + ) + }; + + if res != FALSE { + Ok(object) + } else { + Err(ReadError::ReadProcessMemoryError) + } + } + + pub fn copy_object(&self, src: usize) -> Result { + let object = self.copy_object_shallow(src)?; + Ok(unsafe { object.assume_init() }) + } + + pub fn copy_array(&self, src: usize, num: usize) -> Result, ReadError> { + let num_of_bytes = num * size_of::(); + let mut array: Vec = Vec::with_capacity(num); + let res = unsafe { + ReadProcessMemory( + self.process, + src as _, + array.as_mut_ptr() as _, + num_of_bytes, + null_mut(), + ) + }; + + if res != FALSE { + unsafe { + array.set_len(num); + } + + Ok(array) + } else { + Err(ReadError::ReadProcessMemoryError) + } + } +} -- cgit v1.2.3