diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /toolkit/crashreporter/process_reader | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/crashreporter/process_reader')
5 files changed, 290 insertions, 0 deletions
diff --git a/toolkit/crashreporter/process_reader/Cargo.toml b/toolkit/crashreporter/process_reader/Cargo.toml new file mode 100644 index 0000000000..f84a00a29f --- /dev/null +++ b/toolkit/crashreporter/process_reader/Cargo.toml @@ -0,0 +1,23 @@ +[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.7", features = ["elf32", "elf64", "pe32", "pe64"] } +memoffset = "0.9" +mozilla-central-workspace-hack = { version = "0.1", optional = true } +thiserror = "1.0" + +[target."cfg(target_os = \"windows\")".dependencies] +[dependencies.windows-sys] +version = "0.52" +features = [ + "Win32_Foundation", + "Win32_System_Diagnostics_Debug", + "Win32_System_ProcessStatus", +] 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..1189ff144e --- /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 = windows_sys::Win32::Foundation::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..1dd1a13049 --- /dev/null +++ b/toolkit/crashreporter/process_reader/src/process_reader/windows.rs @@ -0,0 +1,218 @@ +/* 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 windows_sys::Win32::{ + Foundation::{FALSE, HMODULE, MAX_PATH}, + System::{ + Diagnostics::Debug::ReadProcessMemory, + ProcessStatus::{ + GetModuleBaseNameW, K32EnumProcessModules, K32GetModuleInformation, MODULEINFO, + }, + }, +}; + +use crate::{ + error::{ProcessReaderError, ReadError}, + ProcessHandle, ProcessReader, +}; + +impl ProcessReader { + pub fn new(process: ProcessHandle) -> Result<ProcessReader, ProcessReaderError> { + Ok(ProcessReader { process }) + } + + pub fn find_section( + &self, + module_name: &str, + section_name: &str, + ) -> Result<usize, ProcessReaderError> { + 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. + name.is_some_and(|name| name.eq_ignore_ascii_case(module_name)) + }) + .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<Vec<HMODULE>, ProcessReaderError> { + let mut module_num: usize = 100; + let mut required_buffer_size: u32 = 0; + let mut module_array = Vec::<HMODULE>::with_capacity(module_num); + + loop { + let buffer_size: u32 = (module_num * size_of::<HMODULE>()).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::<HMODULE>(); + + if res == 0 { + if required_buffer_size > buffer_size { + module_array = Vec::<HMODULE>::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<String> { + 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) }; + + 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<MODULEINFO> { + let mut info: MaybeUninit<MODULEINFO> = MaybeUninit::uninit(); + let res = unsafe { + K32GetModuleInformation( + self.process, + module, + info.as_mut_ptr(), + size_of::<MODULEINFO>() 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<usize, ProcessReaderError> { + // 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<T>(&self, src: usize) -> Result<MaybeUninit<T>, ReadError> { + let mut object = MaybeUninit::<T>::uninit(); + let res = unsafe { + ReadProcessMemory( + self.process, + src as _, + object.as_mut_ptr() as _, + size_of::<T>(), + null_mut(), + ) + }; + + if res != FALSE { + Ok(object) + } else { + Err(ReadError::ReadProcessMemoryError) + } + } + + pub fn copy_object<T>(&self, src: usize) -> Result<T, ReadError> { + let object = self.copy_object_shallow(src)?; + Ok(unsafe { object.assume_init() }) + } + + pub fn copy_array<T>(&self, src: usize, num: usize) -> Result<Vec<T>, ReadError> { + let num_of_bytes = num * size_of::<T>(); + let mut array: Vec<T> = 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) + } + } +} |