/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use crash_helper_client::report_external_exception; use ini::Ini; use libc::time; use serde::Serialize; use serde_json::ser::to_writer; use std::convert::TryInto; use std::ffi::{c_void, OsString}; use std::fs::{DirBuilder, 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::{
AsHandle, AsRawHandle, BorrowedHandle, FromRawHandle, OwnedHandle, RawHandle,
}; use std::path::{Path, PathBuf}; use std::ptr::{null, null_mut}; use std::slice::from_raw_parts; use uuid::Uuid; use windows_sys::core::{BOOL, HRESULT, PWSTR}; use windows_sys::Wdk::System::Threading::{NtQueryInformationProcess, ProcessBasicInformation}; use windows_sys::Win32::Foundation::WIN32_ERROR; use windows_sys::Win32::{
Foundation::{
CloseHandle, GetLastError, SetLastError, ERROR_INSUFFICIENT_BUFFER, ERROR_SUCCESS,
EXCEPTION_BREAKPOINT, E_UNEXPECTED, FALSE, FILETIME, HANDLE, HWND, LPARAM, MAX_PATH,
STATUS_SUCCESS, S_OK, TRUE,
},
Security::{
GetSidSubAuthority, GetSidSubAuthorityCount, GetTokenInformation, IsTokenRestricted,
TokenIntegrityLevel, TOKEN_MANDATORY_LABEL, TOKEN_QUERY,
},
System::Com::CoTaskMemFree,
System::Diagnostics::Debug::{
GetThreadContext, MiniDumpWithFullMemoryInfo, MiniDumpWithIndirectlyReferencedMemory,
MiniDumpWithProcessThreadData, MiniDumpWithUnloadedModules, MiniDumpWriteDump,
EXCEPTION_POINTERS, MINIDUMP_EXCEPTION_INFORMATION, MINIDUMP_TYPE,
},
System::ErrorReporting::WER_RUNTIME_EXCEPTION_INFORMATION,
System::ProcessStatus::K32GetModuleFileNameExW,
System::SystemInformation::{
VerSetConditionMask, VerifyVersionInfoW, OSVERSIONINFOEXW, VER_MAJORVERSION,
VER_MINORVERSION, VER_SERVICEPACKMAJOR, VER_SERVICEPACKMINOR,
},
System::SystemServices::{SECURITY_MANDATORY_MEDIUM_RID, VER_GREATER_EQUAL},
System::Threading::{
CreateProcessW, GetProcessId, GetProcessTimes, GetThreadId, OpenProcess, OpenProcessToken,
OpenThread, TerminateProcess, CREATE_NO_WINDOW, CREATE_UNICODE_ENVIRONMENT,
NORMAL_PRIORITY_CLASS, PROCESS_ALL_ACCESS, PROCESS_BASIC_INFORMATION, PROCESS_INFORMATION,
STARTUPINFOW, THREAD_GET_CONTEXT,
},
UI::Shell::{FOLDERID_RoamingAppData, SHGetKnownFolderPath},
UI::WindowsAndMessaging::{EnumWindows, GetWindowThreadProcessId, IsHungAppWindow},
};
type DWORD = u32; type ULONG = u32; type DWORDLONG = u64; type LPVOID = *mut c_void; type PVOID = LPVOID; type PBOOL = *mut BOOL; type PDWORD = *mut DWORD; #[allow(non_camel_case_types)] type PWER_RUNTIME_EXCEPTION_INFORMATION = *mut WER_RUNTIME_EXCEPTION_INFORMATION;
// This value comes from GeckoProcessTypes.h static MAIN_PROCESS_TYPE: u32 = 0;
/// # Safety /// /// The parameters must point to valid objects, the Windows Error Reporting /// service guarantees this. #[no_mangle] pubunsafeextern"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(_) => { // SAFETY: `b_ownership_claimed` is guaranteed to point to a valid // block of memory and `hProcess` is a valid process handle. 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,
}
}
/// # Safety /// /// The parameters must point to valid objects, the Windows Error Reporting /// service guarantees this. #[no_mangle] pubunsafeextern"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 { // SAFETY: `*b_is_custom_debugger` is guaranteed to point to a valid block // of memory. unsafe {
*b_is_custom_debugger = FALSE;
}
S_OK
}
type Result<T> = std::result::Result<T, ()>;
fn out_of_process_exception_event_callback(
context: PVOID,
exception_information: PWER_RUNTIME_EXCEPTION_INFORMATION,
) -> Result<()> { // SAFETY: `exception_information` is guaranteed to point to an object of // type `WER_RUNTIME_EXCEPTION_INFORMATION`. let exception_information = unsafe { &mut *exception_information }; // SAFETY: The `hProcess` field is guaranteed to be a valid process handle let process = unsafe { BorrowedHandle::borrow_raw(exception_information.hProcess as RawHandle) }; let is_fatal = exception_information.bIsFatal.to_bool(); letmut is_ui_hang = false; if !is_fatal { 'hang: { // Check whether this error is a hang. A hang always results in an EXCEPTION_BREAKPOINT. // Hangs may have an hThread/context that is unrelated to the hanging thread, so we get // it by searching for process windows that are hung. if exception_information.exceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT { iflet Ok(thread_id) = find_hung_window_thread(process) { // SAFETY: This is always safe to call. // // In the case of a hang, change the crashing thread to be the one that created // the hung window. // // This is all best-effort, so don't return errors (just fall through to the // Ok return). let thread_handle = unsafe { OpenThread(THREAD_GET_CONTEXT, FALSE, thread_id) }; // SAFETY: `thread_handle` is guaranteed to be valid and the // `context` parameter points to an object on the stack. if !thread_handle.is_null()
&& unsafe {
GetThreadContext(thread_handle, &mut exception_information.context)
}
.to_bool()
{
exception_information.hThread = thread_handle; break'hang;
}
}
}
// A non-fatal but non-hang exception should not do anything else. return Ok(());
}
is_ui_hang = true;
}
let process_type: u32 = (context as usize).try_into().map_err(|_| ())?; if process_type == MAIN_PROCESS_TYPE { match is_sandboxed_process(process) {
Ok(false) => { let application_info = ApplicationInformation::from_process(process)?; let startup_time = get_startup_time(process)?; let crash_report = CrashReport::new(&application_info, startup_time, is_ui_hang);
crash_report.write_minidump(exception_information)?;
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(exception_information)
}
}
/// Find whether the given process has a hung window, and return the thread id related to the /// window. fn find_hung_window_thread(process: BorrowedHandle) -> Result<DWORD> { let process_id = get_process_id(process)?;
unsafeextern"system"fn enum_window_callback(wnd: HWND, data: LPARAM) -> BOOL { let data = &mut *(data as *mut WindowSearch); letmut window_proc_id = DWORD::default(); let thread_id = GetWindowThreadProcessId(wnd, &mut window_proc_id); if thread_id != 0 && window_proc_id == data.process_id && IsHungAppWindow(wnd).to_bool() {
data.ui_thread_id = Some(thread_id); FALSE
} else { TRUE
}
}
// SAFETY: All the pointers going into this call point to stack-allocated // variables and `enum_window_callback` is guaranteed to be a valid // function pointer // // Disregard the return value, we are trying for best-effort service (it's okay if ui_thread_id // is never set). unsafe { EnumWindows(Some(enum_window_callback), &mut search as*mut _ as LPARAM) };
search.ui_thread_id.ok_or(())
}
fn get_parent_process(process: BorrowedHandle) -> Result<OwnedHandle> { let pbi = get_process_basic_information(process)?;
get_process_handle(pbi.InheritedFromUniqueProcessId as u32)
}
fn handle_child_process_crash(
exception_information: PWER_RUNTIME_EXCEPTION_INFORMATION,
) -> Result<()> { // SAFETY: The `hProcess` field is guaranteed to contain a valid handle. let process = unsafe { BorrowedHandle::borrow_raw((*exception_information).hProcess as RawHandle) }; // SAFETY: The `hThread` field is guaranteed to contain a valid handle let thread = unsafe { BorrowedHandle::borrow_raw((*exception_information).hThread as RawHandle) }; let parent_process = get_parent_process(process)?; let parent_pid = get_process_id(parent_process.as_handle())?;
let process = process.try_clone_to_owned().map_err(|_e| ())?; let thread = thread.try_clone_to_owned().map_err(|_e| ())?;
// SAFETY: All the pointers going into this function are guaranteed to be // valid and will only be read from. unsafe {
report_external_exception(
parent_pid,
process,
thread,
&raw mut (*exception_information).exceptionRecord,
&raw mut (*exception_information).context,
);
}
// SAFETY: All the pointers going into this call point to stack-allocated // variables and `process` is guaranteed to be a valid handle. unsafe { if GetProcessTimes(
process.as_raw_handle() as HANDLE,
&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_process_id(process: BorrowedHandle) -> Result<DWORD> { // SAFETY: `process` is guaranteed to be a valid handle. matchunsafe { GetProcessId(process.as_raw_handle() as HANDLE) } { 0 => Err(()),
pid => Ok(pid),
}
}
fn get_process_handle(pid: DWORD) -> Result<OwnedHandle> { // SAFETY: This is always safe to call. let handle = unsafe { OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid) }; if !handle.is_null() { // SAFETY: `handle` is guaranteed to contain a valid handle here
Ok(unsafe { OwnedHandle::from_raw_handle(handle as RawHandle) })
} else {
Err(())
}
}
fn launch_crash_reporter_client(install_path: &Path, crash_report: &CrashReport) { // Prepare the command line let client_path = install_path.join("crashreporter.exe");
// SAFETY: A zero-initialized `PROCESS_INFORMATION` structure is valid. letmut pi = unsafe { zeroed::<PROCESS_INFORMATION>() }; let si = STARTUPINFOW {
cb: size_of::<STARTUPINFOW>().try_into().unwrap(), // SAFETY: A zero-initialized `STARTUPINFOW` structure is valid.
..unsafe { zeroed() }
};
// SAFETY: `cmd_line` is guaranteed to point to a valid command-line // buffer which will be kept alive for the duration of the call. // `si` and `pi` point to stack-allocated objects. After a successful // call `pi.hProcess and pi.hThread` are guaranteed to contain valid // handles. unsafe { if CreateProcessW(
null_mut(),
cmd_line.as_mut_ptr(),
null_mut(),
null_mut(), FALSE,
NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT,
null_mut(),
null_mut(),
&si,
&mut pi,
) != 0
{
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
}
}
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();
/// 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: u64,
}
impl ApplicationInformation { fn from_process(process: BorrowedHandle) -> Result<ApplicationInformation> { let exe_path = ApplicationInformation::get_application_path(process)?; let install_path = exe_path.parent().ok_or(())?.to_path_buf(); 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 =
crash_helper_common::ApplicationInfo::get_install_time(Some(exe_path)).unwrap_or(0);
fn get_application_path(process: BorrowedHandle) -> Result<PathBuf> { letmut path: [u16; MAX_PATH as usize + 1] = [0; MAX_PATH as usize + 1]; // SAFETY: The `process` handle is guaranteed to be valid and the // `path` pointer is allocated on the stack just above. unsafe { let res = K32GetModuleFileNameExW(
process.as_raw_handle() as HANDLE,
std::ptr::null_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(std::result::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> { letmut psz_path: PWSTR = null_mut(); // SAFETY: `psz_path` points to a stack-allocated variable. unsafe { let res = SHGetKnownFolderPath(
&FOLDERID_RoamingAppData as *const _, 0,
std::ptr::null_mut(),
&mut psz_path as *mut _,
);
if res == S_OK { letmut 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 _); letmut path = PathBuf::from(str); iflet Some(vendor) = &application_data.vendor {
path.push(vendor);
}
path.push(&application_data.name);
path.push("Crash Reports");
Ok(path)
} else {
Err(())
}
}
}
}
fn get_minidump_type(&self) -> MINIDUMP_TYPE { letmut minidump_type = MiniDumpWithFullMemoryInfo | MiniDumpWithUnloadedModules; ifself.is_nightly() { // This is Nightly only because this doubles the size of minidumps based // on the experimental data.
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 |= MiniDumpWithIndirectlyReferencedMemory
}
}
minidump_type
}
fn write_minidump(
&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(); // SAFETY: The `hProcess` field is guaranteed to contain a valid handle. let process = unsafe { BorrowedHandle::borrow_raw((*exception_information).hProcess as RawHandle) };
// SAFETY: We control all the pointers going into the // `MiniDumpWriteDump()` call and all the handles involved are also // guaranteed to be valid. unsafe { letmut exception_pointers = EXCEPTION_POINTERS {
ExceptionRecord: &mut ((*exception_information).exceptionRecord),
ContextRecord: &mut ((*exception_information).context),
};
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| ())?;
fn get_process_basic_information(process: BorrowedHandle) -> Result<PROCESS_BASIC_INFORMATION> { // SAFETY: A zero-initialized `PROCESS_BASIC_INFORMATION` structure is // considered valid. letmut pbi: PROCESS_BASIC_INFORMATION = unsafe { zeroed() }; letmut length: ULONG = 0; // SAFETY: All the pointers going into this call point to stack-allocated // variables and `process` is guaranteed to be a valid handle. let result = unsafe {
NtQueryInformationProcess(
process.as_raw_handle() as HANDLE,
ProcessBasicInformation,
&mut pbi as *mut _ as _,
size_of::<PROCESS_BASIC_INFORMATION>().try_into().unwrap(),
&mut length,
)
};
if result != STATUS_SUCCESS { return Err(());
}
Ok(pbi)
}
fn is_sandboxed_process(process: BorrowedHandle) -> Result<bool> { letmut token: HANDLE = null_mut(); // SAFETY: All the pointers going into this call point to stack-allocated // variables and `process` is guaranteed to be a valid handle. let res = unsafe {
OpenProcessToken(
process.as_raw_handle() as HANDLE,
TOKEN_QUERY,
&mut token as *mut _,
)
};
if res != TRUE { return Err(());
}
// SAFETY: We've just checked that `OpenProcessToken` returned a valid token handle. let token = unsafe { OwnedHandle::from_raw_handle(token as RawHandle) }; // SAFETY: `token` is guaranteed to be a valid handle. let is_restricted = unsafe { IsTokenRestricted(token.as_raw_handle() as HANDLE) } != FALSE;
set_last_error(ERROR_SUCCESS); letmut buffer_size: DWORD = 0; // SAFETY: All the pointers going into this call point to stack-allocated // variables and `token` is guaranteed to be a valid handle. let res = unsafe {
GetTokenInformation(
token.as_raw_handle() as HANDLE,
TokenIntegrityLevel,
null_mut(), 0,
&mut buffer_size as *mut _,
)
};
letmut buffer: Vec<u8> = vec![Default::default(); buffer_size as usize]; // SAFETY: All the pointers going into this call point to memory blocks we // own and `token` is guaranteed to be a valid handle. let res = unsafe {
GetTokenInformation(
token.as_raw_handle() as HANDLE,
TokenIntegrityLevel,
buffer.as_mut_ptr() as *mut _,
buffer_size,
&mut buffer_size as *mut _,
)
};
if res != TRUE { return Err(());
}
// SAFETY: `buffer` is guaranteed to be valid and point to an object of the // appropriate size. let token_mandatory_label = &unsafe { *(buffer.as_ptr() as *constTOKEN_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.
// SAFETY: `sid` is a valid pointer let sid_subauthority_count = unsafe { *GetSidSubAuthorityCount(sid) - 1u8 }; // SAFETY: `sid` is a valid pointer let integrity_level = unsafe { *GetSidSubAuthority(sid, sid_subauthority_count.into()) };
Ok((integrity_level < SECURITY_MANDATORY_MEDIUM_RID as u32) || is_restricted)
}
fn get_last_error() -> WIN32_ERROR { // SAFETY: This is always safe to call unsafe { GetLastError() }
}
fn set_last_error(error: WIN32_ERROR) { // SAFETY: This is always safe to call unsafe { SetLastError(error) }
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.19 Sekunden
(vorverarbeitet am 2026-08-25)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.