From def92d1b8e9d373e2f6f27c366d578d97d8960c6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 15 May 2024 05:34:50 +0200 Subject: Merging upstream version 126.0. Signed-off-by: Daniel Baumann --- toolkit/crashreporter/process_reader/Cargo.toml | 14 +- toolkit/crashreporter/process_reader/src/error.rs | 42 ++- toolkit/crashreporter/process_reader/src/lib.rs | 72 ++++- .../crashreporter/process_reader/src/platform.rs | 12 + .../process_reader/src/platform/linux.rs | 322 +++++++++++++++++++++ .../process_reader/src/platform/macos.rs | 225 ++++++++++++++ .../process_reader/src/platform/windows.rs | 201 +++++++++++++ .../process_reader/src/process_reader.rs | 6 - .../process_reader/src/process_reader/windows.rs | 218 -------------- 9 files changed, 879 insertions(+), 233 deletions(-) create mode 100644 toolkit/crashreporter/process_reader/src/platform.rs create mode 100644 toolkit/crashreporter/process_reader/src/platform/linux.rs create mode 100644 toolkit/crashreporter/process_reader/src/platform/macos.rs create mode 100644 toolkit/crashreporter/process_reader/src/platform/windows.rs delete mode 100644 toolkit/crashreporter/process_reader/src/process_reader.rs delete mode 100644 toolkit/crashreporter/process_reader/src/process_reader/windows.rs (limited to 'toolkit/crashreporter/process_reader') diff --git a/toolkit/crashreporter/process_reader/Cargo.toml b/toolkit/crashreporter/process_reader/Cargo.toml index f84a00a29f..5fb20cf95a 100644 --- a/toolkit/crashreporter/process_reader/Cargo.toml +++ b/toolkit/crashreporter/process_reader/Cargo.toml @@ -10,14 +10,18 @@ license = "MPL-2.0" [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(any(target_os = \"linux\", target_os = \"android\"))".dependencies] +libc = "0.2" + [target."cfg(target_os = \"windows\")".dependencies] -[dependencies.windows-sys] -version = "0.52" -features = [ +windows-sys = { version = "0.52", features = [ "Win32_Foundation", "Win32_System_Diagnostics_Debug", "Win32_System_ProcessStatus", -] +] } +mozilla-central-workspace-hack = { version = "0.1", optional = true } + +[target."cfg(target_os = \"macos\")".dependencies] +mach2 = { version = "0.4" } diff --git a/toolkit/crashreporter/process_reader/src/error.rs b/toolkit/crashreporter/process_reader/src/error.rs index af4d803a7a..2c681f78fb 100644 --- a/toolkit/crashreporter/process_reader/src/error.rs +++ b/toolkit/crashreporter/process_reader/src/error.rs @@ -8,6 +8,8 @@ use thiserror::Error; pub enum ProcessReaderError { #[error("Could not convert address {0}")] ConvertAddressError(#[from] std::num::TryFromIntError), + #[error("Could not parse address {0}")] + ParseAddressError(#[from] std::num::ParseIntError), #[cfg(target_os = "windows")] #[error("Cannot enumerate the target process's modules")] EnumProcessModulesError, @@ -17,14 +19,52 @@ pub enum ProcessReaderError { InvalidAddress, #[error("Could not read from the target process address space")] ReadFromProcessError(#[from] ReadError), - #[cfg(target_os = "windows")] + #[cfg(any(target_os = "windows", target_os = "macos"))] #[error("Section was not found")] SectionNotFound, + #[cfg(any(target_os = "linux", target_os = "android"))] + #[error("Could not attach to the target process")] + AttachError(#[from] PtraceError), + #[cfg(any(target_os = "linux", target_os = "android"))] + #[error("Note not found")] + NoteNotFound, + #[cfg(any(target_os = "linux", target_os = "android"))] + #[error("waitpid() failed when attaching to the process")] + WaitPidError, + #[cfg(any(target_os = "linux", target_os = "android"))] + #[error("Could not parse a line in /proc//maps")] + ProcMapsParseError, + #[error("Module not found")] + ModuleNotFound, + #[cfg(any(target_os = "linux", target_os = "android"))] + #[error("IO error for file {0}")] + IOError(#[from] std::io::Error), + #[cfg(target_os = "macos")] + #[error("Failure when requesting the task information")] + TaskInfoError, + #[cfg(target_os = "macos")] + #[error("The task dyld information format is unknown or invalid")] + ImageFormatError, } #[derive(Debug, Error)] pub enum ReadError { + #[cfg(target_os = "macos")] + #[error("mach call failed")] + MachError, + #[cfg(any(target_os = "linux", target_os = "android"))] + #[error("ptrace-specific error")] + PtraceError(#[from] PtraceError), #[cfg(target_os = "windows")] #[error("ReadProcessMemory failed")] ReadProcessMemoryError, } + +#[cfg(any(target_os = "linux", target_os = "android"))] +#[derive(Debug, Error)] +pub enum PtraceError { + #[error("Could not read from the target process address space")] + ReadError(#[source] std::io::Error), + #[error("Could not trace the process")] + TraceError(#[source] std::io::Error), +} diff --git a/toolkit/crashreporter/process_reader/src/lib.rs b/toolkit/crashreporter/process_reader/src/lib.rs index 1189ff144e..09936e46f1 100644 --- a/toolkit/crashreporter/process_reader/src/lib.rs +++ b/toolkit/crashreporter/process_reader/src/lib.rs @@ -2,12 +2,78 @@ * 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::{ffi::CString, mem::size_of}; + +use error::ReadError; + +pub mod error; +mod platform; + #[cfg(target_os = "windows")] -type ProcessHandle = windows_sys::Win32::Foundation::HANDLE; +pub type ProcessHandle = windows_sys::Win32::Foundation::HANDLE; + +#[cfg(any(target_os = "linux", target_os = "android"))] +pub type ProcessHandle = libc::pid_t; + +#[cfg(any(target_os = "macos"))] +pub type ProcessHandle = mach2::mach_types::task_t; pub struct ProcessReader { process: ProcessHandle, } -mod error; -mod process_reader; +impl ProcessReader { + pub fn copy_null_terminated_string(&self, address: usize) -> Result { + // Try copying the string word-by-word first, this is considerably + // faster than one byte at a time. + if let Ok(string) = self.copy_null_terminated_string_word_by_word(address) { + return Ok(string); + } + + // Reading the string one word at a time failed, let's try again one + // byte at a time. It's slow but it might work in situations where the + // string alignment causes word-by-word access to straddle page + // boundaries. + let mut length = 0; + let mut string = Vec::::new(); + + loop { + let char = self.copy_object::(address + length)?; + length += 1; + string.push(char); + + if char == 0 { + break; + } + } + + // SAFETY: If we reach this point we've read at least one byte and we + // know that the last one we read is nul. + Ok(unsafe { CString::from_vec_with_nul_unchecked(string) }) + } + + fn copy_null_terminated_string_word_by_word( + &self, + address: usize, + ) -> Result { + const WORD_SIZE: usize = size_of::(); + let mut length = 0; + let mut string = Vec::::new(); + + loop { + let array = self.copy_array::(address + length, WORD_SIZE)?; + let null_terminator = array.iter().position(|&e| e == 0); + length += null_terminator.unwrap_or(WORD_SIZE); + string.extend(array.into_iter()); + + if null_terminator.is_some() { + string.truncate(length + 1); + break; + } + } + + // SAFETY: If we reach this point we've read at least one byte and we + // know that the last one we read is nul. + Ok(unsafe { CString::from_vec_with_nul_unchecked(string) }) + } +} diff --git a/toolkit/crashreporter/process_reader/src/platform.rs b/toolkit/crashreporter/process_reader/src/platform.rs new file mode 100644 index 0000000000..0ac354cd66 --- /dev/null +++ b/toolkit/crashreporter/process_reader/src/platform.rs @@ -0,0 +1,12 @@ +/* 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; + +#[cfg(any(target_os = "android", target_os = "linux"))] +mod linux; + +#[cfg(target_os = "macos")] +mod macos; diff --git a/toolkit/crashreporter/process_reader/src/platform/linux.rs b/toolkit/crashreporter/process_reader/src/platform/linux.rs new file mode 100644 index 0000000000..4a87e5480b --- /dev/null +++ b/toolkit/crashreporter/process_reader/src/platform/linux.rs @@ -0,0 +1,322 @@ +/* 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::{ + cmp::min, + fs::File, + io::{BufRead, BufReader, Error}, + mem::{size_of, MaybeUninit}, + ptr::null_mut, + slice, +}; + +use crate::{ + error::{ProcessReaderError, PtraceError, ReadError}, + ProcessHandle, ProcessReader, +}; + +use goblin::elf::{ + self, + program_header::{PF_R, PT_NOTE}, + Elf, ProgramHeader, +}; +use libc::{ + c_int, c_long, c_void, pid_t, ptrace, waitpid, EINTR, PTRACE_ATTACH, PTRACE_DETACH, + PTRACE_PEEKDATA, __WALL, +}; + +impl ProcessReader { + pub fn new(process: ProcessHandle) -> Result { + let pid: pid_t = process; + + ptrace_attach(pid)?; + + let mut status: i32 = 0; + + loop { + let res = unsafe { waitpid(pid, &mut status as *mut _, __WALL) }; + if res < 0 { + match get_errno() { + EINTR => continue, + _ => { + ptrace_detach(pid)?; + return Err(ProcessReaderError::WaitPidError); + } + } + } else { + break; + } + } + + Ok(ProcessReader { process: pid }) + } + + pub fn find_module(&self, module_name: &str) -> Result { + let maps_file = File::open(format!("/proc/{}/maps", self.process))?; + + BufReader::new(maps_file) + .lines() + .flatten() + .map(|line| parse_proc_maps_line(&line)) + .filter_map(Result::ok) + .find_map(|(name, address)| { + if name.is_some_and(|name| name.eq(module_name)) { + Some(address) + } else { + None + } + }) + .ok_or(ProcessReaderError::ModuleNotFound) + } + + pub fn find_program_note( + &self, + module_address: usize, + note_type: u32, + note_size: usize, + ) -> Result { + let header_bytes = self.copy_array(module_address, size_of::())?; + let elf_header = Elf::parse_header(&header_bytes)?; + + let program_header_bytes = self.copy_array( + module_address + (elf_header.e_phoff as usize), + (elf_header.e_phnum as usize) * (elf_header.e_phentsize as usize), + )?; + + let mut elf = Elf::lazy_parse(elf_header)?; + let context = goblin::container::Ctx { + container: elf.header.container()?, + le: elf.header.endianness()?, + }; + + elf.program_headers = ProgramHeader::parse( + &program_header_bytes, + 0, + elf_header.e_phnum as usize, + context, + )?; + + self.find_note_in_headers(&elf, module_address, note_type, note_size) + } + + fn find_note_in_headers( + &self, + elf: &Elf, + address: usize, + note_type: u32, + note_size: usize, + ) -> Result { + for program_header in elf.program_headers.iter() { + // We're looking for a note in the program headers, it needs to be + // readable and it needs to be at least as large as the + // requested size. + if (program_header.p_type == PT_NOTE) + && ((program_header.p_flags & PF_R) != 0 + && (program_header.p_memsz as usize >= note_size)) + { + // Iterate over the notes + let notes_address = address + program_header.p_offset as usize; + let mut notes_offset = 0; + let notes_size = program_header.p_memsz as usize; + while notes_offset < notes_size { + let note_address = notes_address + notes_offset; + if let Ok(note) = self.copy_object::(note_address) { + if note.n_type == note_type { + return Ok(note_address); + } + + notes_offset += size_of::() + + (note.n_descsz as usize) + + (note.n_namesz as usize); + } else { + break; + } + } + } + } + + Err(ProcessReaderError::NoteNotFound) + } + + pub fn copy_object_shallow(&self, src: usize) -> Result, ReadError> { + let data = self.copy_array(src, size_of::())?; + let mut object = MaybeUninit::::uninit(); + let uninitialized_object = uninit_as_bytes_mut(&mut object); + + for (index, &value) in data.iter().enumerate() { + uninitialized_object[index].write(value); + } + + Ok(object) + } + + pub fn copy_object(&self, src: usize) -> Result { + self.copy_object_shallow(src) + .map(|object| unsafe { object.assume_init() }) + } + + pub fn copy_array(&self, src: usize, num: usize) -> Result, ReadError> { + let mut array = Vec::>::with_capacity(num); + let num_bytes = num * size_of::(); + let mut array_buffer = array.as_mut_ptr() as *mut u8; + let mut index = 0; + + while index < num_bytes { + let word = ptrace_read(self.process, src + index)?; + let len = min(size_of::(), num_bytes - index); + let word_as_bytes = word.to_ne_bytes(); + for &byte in word_as_bytes.iter().take(len) { + unsafe { + array_buffer.write(byte); + array_buffer = array_buffer.add(1); + } + } + + index += size_of::(); + } + + unsafe { + array.set_len(num); + Ok(std::mem::transmute(array)) + } + } +} + +impl Drop for ProcessReader { + fn drop(&mut self) { + let _ignored = ptrace_detach(self.process); + } +} + +fn parse_proc_maps_line(line: &str) -> Result<(Option, usize), ProcessReaderError> { + let mut splits = line.trim().splitn(6, ' '); + let address_str = splits + .next() + .ok_or(ProcessReaderError::ProcMapsParseError)?; + let _perms_str = splits + .next() + .ok_or(ProcessReaderError::ProcMapsParseError)?; + let _offset_str = splits + .next() + .ok_or(ProcessReaderError::ProcMapsParseError)?; + let _dev_str = splits + .next() + .ok_or(ProcessReaderError::ProcMapsParseError)?; + let _inode_str = splits + .next() + .ok_or(ProcessReaderError::ProcMapsParseError)?; + let path_str = splits + .next() + .ok_or(ProcessReaderError::ProcMapsParseError)?; + + let address = get_proc_maps_address(address_str)?; + + // Note that we don't care if the mapped file has been deleted because + // we're reading everything from memory. + let name = path_str + .trim_end_matches(" (deleted)") + .rsplit('/') + .next() + .map(String::from); + + Ok((name, address)) +} + +fn get_proc_maps_address(addresses: &str) -> Result { + let begin = addresses + .split('-') + .next() + .ok_or(ProcessReaderError::ProcMapsParseError)?; + usize::from_str_radix(begin, 16).map_err(ProcessReaderError::from) +} + +fn uninit_as_bytes_mut(elem: &mut MaybeUninit) -> &mut [MaybeUninit] { + // SAFETY: MaybeUninit is always valid, even for padding bytes + unsafe { slice::from_raw_parts_mut(elem.as_mut_ptr() as *mut MaybeUninit, size_of::()) } +} + +/*********************************************************************** + ***** libc helpers ***** + ***********************************************************************/ + +fn get_errno() -> c_int { + #[cfg(target_os = "linux")] + unsafe { + *libc::__errno_location() + } + #[cfg(target_os = "android")] + unsafe { + *libc::__errno() + } +} + +fn clear_errno() { + #[cfg(target_os = "linux")] + unsafe { + *libc::__errno_location() = 0; + } + #[cfg(target_os = "android")] + unsafe { + *libc::__errno() = 0; + } +} + +#[derive(Clone, Copy)] +enum PTraceOperation { + Attach, + Detach, + PeekData, +} + +#[cfg(all(target_os = "linux", target_env = "gnu"))] +type PTraceOperationNative = libc::c_uint; +#[cfg(all(target_os = "linux", target_env = "musl"))] +type PTraceOperationNative = libc::c_int; +#[cfg(target_os = "android")] +type PTraceOperationNative = c_int; + +impl From for PTraceOperationNative { + fn from(val: PTraceOperation) -> Self { + match val { + PTraceOperation::Attach => PTRACE_ATTACH, + PTraceOperation::Detach => PTRACE_DETACH, + PTraceOperation::PeekData => PTRACE_PEEKDATA, + } + } +} + +fn ptrace_attach(pid: pid_t) -> Result<(), PtraceError> { + ptrace_helper(pid, PTraceOperation::Attach, 0).map(|_r| ()) +} + +fn ptrace_detach(pid: pid_t) -> Result<(), PtraceError> { + ptrace_helper(pid, PTraceOperation::Detach, 0).map(|_r| ()) +} + +fn ptrace_read(pid: libc::pid_t, addr: usize) -> Result { + ptrace_helper(pid, PTraceOperation::PeekData, addr) +} + +fn ptrace_helper(pid: pid_t, op: PTraceOperation, addr: usize) -> Result { + clear_errno(); + let result = unsafe { ptrace(op.into(), pid, addr, null_mut::()) }; + + if result == -1 { + let errno = get_errno(); + if errno != 0 { + let error = match op { + PTraceOperation::Attach => PtraceError::TraceError(Error::from_raw_os_error(errno)), + PTraceOperation::Detach => PtraceError::TraceError(Error::from_raw_os_error(errno)), + PTraceOperation::PeekData => { + PtraceError::ReadError(Error::from_raw_os_error(errno)) + } + }; + Err(error) + } else { + Ok(result) + } + } else { + Ok(result) + } +} diff --git a/toolkit/crashreporter/process_reader/src/platform/macos.rs b/toolkit/crashreporter/process_reader/src/platform/macos.rs new file mode 100644 index 0000000000..a0dfa2b8fd --- /dev/null +++ b/toolkit/crashreporter/process_reader/src/platform/macos.rs @@ -0,0 +1,225 @@ +/* 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 goblin::mach::{ + header::{Header64, MH_DYLIB, MH_EXECUTE, MH_MAGIC_64}, + load_command::{LoadCommandHeader, Section64, SegmentCommand64, LC_SEGMENT_64}, +}; +use mach2::{ + kern_return::KERN_SUCCESS, + task::task_info, + task_info::{task_dyld_info, TASK_DYLD_ALL_IMAGE_INFO_64, TASK_DYLD_INFO}, + vm::mach_vm_read_overwrite, +}; +use std::mem::{size_of, MaybeUninit}; + +use crate::{ + error::{ProcessReaderError, ReadError}, + ProcessHandle, ProcessReader, +}; + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +struct AllImagesInfo { + // VERSION 1 + pub version: u32, + /// The number of [`ImageInfo`] structs at that following address + info_array_count: u32, + /// The address in the process where the array of [`ImageInfo`] structs is + info_array_addr: u64, + /// A function pointer, unused + _notification: u64, + /// Unused + _process_detached_from_shared_region: bool, + // VERSION 2 + lib_system_initialized: bool, + // Note that crashpad adds a 32-bit int here to get proper alignment when + // building on 32-bit targets...but we explicitly don't care about 32-bit + // targets since Apple doesn't + pub dyld_image_load_address: u64, +} + +/// `dyld_image_info` from +#[repr(C)] +#[derive(Debug, Clone, Copy)] +struct ImageInfo { + /// The address in the process where the image is loaded + pub load_address: u64, + /// The address in the process where the image's file path can be read + pub file_path: u64, + /// Timestamp for when the image's file was last modified + pub file_mod_date: u64, +} + +const DATA_SEGMENT: &[u8; 16] = b"__DATA\0\0\0\0\0\0\0\0\0\0"; + +impl ProcessReader { + pub fn new(process: ProcessHandle) -> Result { + Ok(ProcessReader { process }) + } + + pub fn find_module(&self, module_name: &str) -> Result { + let dyld_info = self.task_info()?; + if (dyld_info.all_image_info_format as u32) != TASK_DYLD_ALL_IMAGE_INFO_64 { + return Err(ProcessReaderError::ImageFormatError); + } + + let all_image_info_size = dyld_info.all_image_info_size; + let all_image_info_addr = dyld_info.all_image_info_addr; + if (all_image_info_size as usize) < size_of::() { + return Err(ProcessReaderError::ImageFormatError); + } + + let all_images_info = self.copy_object::(all_image_info_addr as _)?; + + // Load the images + let images = self.copy_array::( + all_images_info.info_array_addr as _, + all_images_info.info_array_count as _, + )?; + + images + .iter() + .find(|&image| { + let image_path = self.copy_null_terminated_string(image.file_path as usize); + + if let Ok(image_path) = image_path { + if let Some(image_name) = image_path.into_bytes().rsplit(|&b| b == b'/').next() + { + image_name.eq(module_name.as_bytes()) + } else { + false + } + } else { + false + } + }) + .map(|image| image.load_address as usize) + .ok_or(ProcessReaderError::ModuleNotFound) + } + + pub fn find_section( + &self, + module_address: usize, + section_name: &[u8; 16], + ) -> Result { + let header = self.copy_object::(module_address)?; + let mut address = module_address + size_of::(); + + if header.magic == MH_MAGIC_64 + && (header.filetype == MH_EXECUTE || header.filetype == MH_DYLIB) + { + let end_of_commands = address + (header.sizeofcmds as usize); + + while address < end_of_commands { + let command = self.copy_object::(address)?; + + if command.cmd == LC_SEGMENT_64 { + if let Ok(offset) = self.find_section_in_segment(address, section_name) { + return module_address + .checked_add(offset) + .ok_or(ProcessReaderError::InvalidAddress); + } + } + + address += command.cmdsize as usize; + } + } + + Err(ProcessReaderError::SectionNotFound) + } + + fn find_section_in_segment( + &self, + segment_address: usize, + section_name: &[u8; 16], + ) -> Result { + let segment = self.copy_object::(segment_address)?; + + if segment.segname.eq(DATA_SEGMENT) { + let sections_addr = segment_address + size_of::(); + let sections = self.copy_array::(sections_addr, segment.nsects as usize)?; + for section in §ions { + if section.sectname.eq(section_name) { + return Ok(section.offset as usize); + } + } + } + + Err(ProcessReaderError::SectionNotFound) + } + + fn task_info(&self) -> Result { + let mut info = std::mem::MaybeUninit::::uninit(); + let mut count = (std::mem::size_of::() / std::mem::size_of::()) as u32; + + let res = unsafe { + task_info( + self.process, + TASK_DYLD_INFO, + info.as_mut_ptr().cast(), + &mut count, + ) + }; + + if res == KERN_SUCCESS { + // SAFETY: this will be initialized if the call succeeded + unsafe { Ok(info.assume_init()) } + } else { + Err(ProcessReaderError::TaskInfoError) + } + } + + pub fn copy_object_shallow(&self, src: usize) -> Result, ReadError> { + let mut object = MaybeUninit::::uninit(); + let mut size: u64 = 0; + let res = unsafe { + mach_vm_read_overwrite( + self.process, + src as u64, + size_of::() as u64, + object.as_mut_ptr() as _, + &mut size as _, + ) + }; + + if res == KERN_SUCCESS { + Ok(object) + } else { + Err(ReadError::MachError) + } + } + + 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 mut array: Vec> = Vec::with_capacity(num); + let mut size: u64 = 0; + let res = unsafe { + mach_vm_read_overwrite( + self.process, + src as u64, + (num * size_of::()) as u64, + array.as_mut_ptr() as _, + &mut size as _, + ) + }; + + if res == KERN_SUCCESS { + unsafe { + array.set_len(num); + Ok(std::mem::transmute(array)) + } + } else { + Err(ReadError::MachError) + } + } +} + +impl Drop for ProcessReader { + fn drop(&mut self) {} +} diff --git a/toolkit/crashreporter/process_reader/src/platform/windows.rs b/toolkit/crashreporter/process_reader/src/platform/windows.rs new file mode 100644 index 0000000000..2d8c63444e --- /dev/null +++ b/toolkit/crashreporter/process_reader/src/platform/windows.rs @@ -0,0 +1,201 @@ +/* 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::{ + K32EnumProcessModules, K32GetModuleBaseNameW, K32GetModuleInformation, MODULEINFO, + }, + }, +}; + +use crate::{ + error::{ProcessReaderError, ReadError}, + ProcessHandle, ProcessReader, +}; + +impl ProcessReader { + pub fn new(process: ProcessHandle) -> Result { + Ok(ProcessReader { process }) + } + + pub fn find_module(&self, module_name: &str) -> Result { + let modules = self.get_module_list()?; + + let module = modules.iter().find_map(|&module| { + let name = self.get_module_name(module); + // Crude way of mimicking Windows lower-case comparisons but + // sufficient for our use-cases. + if name.is_some_and(|name| name.eq_ignore_ascii_case(module_name)) { + self.get_module_info(module) + .map(|module| module.lpBaseOfDll as usize) + } else { + None + } + }); + + module.ok_or(ProcessReaderError::ModuleNotFound) + } + + pub fn find_section( + &self, + module_address: usize, + section_name: &[u8; 8], + ) -> 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. + const PAGE_SIZE: usize = 4096; + let bytes = self.copy_array(module_address as _, PAGE_SIZE)?; + 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) { + let address = module_address.checked_add(section.virtual_address as usize); + return address.ok_or(ProcessReaderError::InvalidAddress); + } + } + + Err(ProcessReaderError::SectionNotFound) + } + + 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 { + K32GetModuleBaseNameW(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 { + 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) + } + } + + 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) + } + } +} diff --git a/toolkit/crashreporter/process_reader/src/process_reader.rs b/toolkit/crashreporter/process_reader/src/process_reader.rs deleted file mode 100644 index 1473aafa09..0000000000 --- a/toolkit/crashreporter/process_reader/src/process_reader.rs +++ /dev/null @@ -1,6 +0,0 @@ -/* 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 deleted file mode 100644 index 1dd1a13049..0000000000 --- a/toolkit/crashreporter/process_reader/src/process_reader/windows.rs +++ /dev/null @@ -1,218 +0,0 @@ -/* 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 { - 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. - 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, 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) }; - - 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