diff options
| author | David Lönnhager <david.l@mullvad.net> | 2019-11-19 16:57:54 +0100 |
|---|---|---|
| committer | David Lönnhager <david.l@mullvad.net> | 2019-11-21 11:11:54 +0100 |
| commit | b4b5fd35fe85ef75628c88bbe1064345c5c70d86 (patch) | |
| tree | 72c985415071b624f82d4e1ebe6e6b4f3c62bbe3 | |
| parent | 2431762f3beae66fc264315f410b31918323f099 (diff) | |
| download | mullvadvpn-b4b5fd35fe85ef75628c88bbe1064345c5c70d86.tar.xz mullvadvpn-b4b5fd35fe85ef75628c88bbe1064345c5c70d86.zip | |
Log unhandled SEH exceptions on Windows
| -rw-r--r-- | mullvad-daemon/Cargo.toml | 2 | ||||
| -rw-r--r-- | mullvad-daemon/src/main.rs | 4 | ||||
| -rw-r--r-- | mullvad-daemon/src/windows_exception_logging.rs | 151 |
3 files changed, 156 insertions, 1 deletions
diff --git a/mullvad-daemon/Cargo.toml b/mullvad-daemon/Cargo.toml index 5eeb095d93..f32a16f745 100644 --- a/mullvad-daemon/Cargo.toml +++ b/mullvad-daemon/Cargo.toml @@ -56,7 +56,7 @@ simple-signal = "1.1" [target.'cfg(windows)'.dependencies] ctrlc = "3.0" windows-service = { git = "https://github.com/mullvad/windows-service-rs.git", rev = "48d755b0afbf259ba547d4defc3e9340d1436cf6" } -winapi = "0.3" +winapi = { version = "0.3", features = ["errhandlingapi", "handleapi", "libloaderapi", "synchapi", "tlhelp32", "winbase", "winerror", "winuser"] } [target.'cfg(windows)'.build-dependencies] winres = "0.1" diff --git a/mullvad-daemon/src/main.rs b/mullvad-daemon/src/main.rs index 26e463ef70..ea44a51656 100644 --- a/mullvad-daemon/src/main.rs +++ b/mullvad-daemon/src/main.rs @@ -17,6 +17,8 @@ mod cli; mod shutdown; #[cfg(windows)] mod system_service; +#[cfg(windows)] +mod windows_exception_logging; const DAEMON_LOG_FILENAME: &str = "daemon.log"; @@ -48,6 +50,8 @@ fn init_logging(config: &cli::Config) -> Result<Option<PathBuf>, String> { ) .map_err(|e| e.display_chain_with_msg("Unable to initialize logger"))?; log_panics::init(); + #[cfg(windows)] + windows_exception_logging::enable(); version::log_version(); if let Some(ref log_dir) = log_dir { info!("Logging to {}", log_dir.display()); diff --git a/mullvad-daemon/src/windows_exception_logging.rs b/mullvad-daemon/src/windows_exception_logging.rs new file mode 100644 index 0000000000..7fb84f1b97 --- /dev/null +++ b/mullvad-daemon/src/windows_exception_logging.rs @@ -0,0 +1,151 @@ +use std::{ffi::CStr, mem, os::raw::c_char}; + +use winapi::{ + ctypes::c_void, + shared::{ + minwindef::{BYTE, DWORD, FALSE}, + winerror::ERROR_NO_MORE_FILES, + }, + um::{ + errhandlingapi::SetUnhandledExceptionFilter, + handleapi::{CloseHandle, INVALID_HANDLE_VALUE}, + tlhelp32::{ + CreateToolhelp32Snapshot, Module32First, Module32Next, MODULEENTRY32, TH32CS_SNAPMODULE, + }, + winnt::{EXCEPTION_POINTERS, EXCEPTION_RECORD, HANDLE, LONG}, + }, + vc::excpt::EXCEPTION_EXECUTE_HANDLER, +}; + +/// Enable logging of unhandled SEH exceptions. +pub fn enable() { + unsafe { SetUnhandledExceptionFilter(Some(logging_exception_filter)) }; +} + +extern "system" fn logging_exception_filter(info: *mut EXCEPTION_POINTERS) -> LONG { + // TODO: output the error constant's name instead of its numeric value + // SAFETY: Windows gives us valid pointers + let info: &EXCEPTION_POINTERS = unsafe { &*info }; + let record: &EXCEPTION_RECORD = unsafe { &*info.ExceptionRecord }; + + match find_address_module(record.ExceptionAddress) { + Some(mod_info) => log::error!( + "Unhandled exception at {:#x?} in {}: {:#x?}", + record.ExceptionAddress as usize - mod_info.base_address as usize, + mod_info.name, + record.ExceptionCode, + ), + None => log::error!( + "Unhandled exception at {:#x?}: {:#x?}", + record.ExceptionAddress, + record.ExceptionCode + ), + } + + EXCEPTION_EXECUTE_HANDLER +} + +/// Return module info for the current process and given memory address. +fn find_address_module(address: *mut c_void) -> Option<ModuleInfo> { + let snap = + ProcessSnapshot::new(TH32CS_SNAPMODULE, 0).expect("could not create process snapshot"); + + for module in snap.modules() { + let module_end_address = unsafe { module.base_address.offset(module.size as isize) }; + if (address as *const BYTE) >= module.base_address + && (address as *const BYTE) < module_end_address + { + return Some(module); + } + } + + None +} + +struct ModuleInfo { + name: String, + base_address: *const BYTE, + size: usize, +} + +struct ProcessSnapshot { + handle: HANDLE, +} + +impl ProcessSnapshot { + fn new(flags: DWORD, process_id: DWORD) -> std::io::Result<ProcessSnapshot> { + let snap = unsafe { CreateToolhelp32Snapshot(flags, process_id) }; + + if snap == INVALID_HANDLE_VALUE { + Err(std::io::Error::last_os_error()) + } else { + Ok(ProcessSnapshot { handle: snap }) + } + } + + fn handle(&self) -> HANDLE { + self.handle + } + + fn modules(&self) -> ProcessSnapshotModules<'_> { + let mut entry: MODULEENTRY32 = unsafe { mem::zeroed() }; + entry.dwSize = mem::size_of::<MODULEENTRY32>() as u32; + + ProcessSnapshotModules { + snapshot: self, + iter_started: false, + temp_entry: entry, + } + } +} + +impl Drop for ProcessSnapshot { + fn drop(&mut self) { + unsafe { + CloseHandle(self.handle); + } + } +} + +struct ProcessSnapshotModules<'a> { + snapshot: &'a ProcessSnapshot, + iter_started: bool, + temp_entry: MODULEENTRY32, +} + +impl Iterator for ProcessSnapshotModules<'_> { + type Item = ModuleInfo; + + fn next(&mut self) -> Option<ModuleInfo> { + if self.iter_started { + if unsafe { Module32Next(self.snapshot.handle(), &mut self.temp_entry) } == FALSE { + let last_error = std::io::Error::last_os_error(); + + return if last_error.raw_os_error().unwrap() as u32 == ERROR_NO_MORE_FILES { + None + } else { + panic!( + "Windows error during ProcessSnapshot iteration: {}", + last_error + ) + }; + } + } else { + if unsafe { Module32First(self.snapshot.handle(), &mut self.temp_entry) } == FALSE { + let last_error = std::io::Error::last_os_error(); + panic!( + "Windows error during ProcessSnapshot iteration: {}", + last_error + ); + } + self.iter_started = true; + } + + let cstr = unsafe { CStr::from_ptr(&self.temp_entry.szModule[0] as *const c_char) }; + Some(ModuleInfo { + name: cstr.to_string_lossy().into_owned(), + base_address: self.temp_entry.modBaseAddr, + size: self.temp_entry.modBaseSize as usize, + }) + } +} |
