From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- .../crashreporter/mozannotation_server/Cargo.toml | 25 ++ .../mozannotation_server/cbindgen.toml | 15 + .../crashreporter/mozannotation_server/moz.build | 17 ++ .../mozannotation_server/src/errors.rs | 81 ++++++ .../crashreporter/mozannotation_server/src/lib.rs | 217 ++++++++++++++ .../mozannotation_server/src/process_reader.rs | 18 ++ .../src/process_reader/linux.rs | 312 +++++++++++++++++++++ .../src/process_reader/macos.rs | 214 ++++++++++++++ .../src/process_reader/windows.rs | 193 +++++++++++++ 9 files changed, 1092 insertions(+) create mode 100644 toolkit/crashreporter/mozannotation_server/Cargo.toml create mode 100644 toolkit/crashreporter/mozannotation_server/cbindgen.toml create mode 100644 toolkit/crashreporter/mozannotation_server/moz.build create mode 100644 toolkit/crashreporter/mozannotation_server/src/errors.rs create mode 100644 toolkit/crashreporter/mozannotation_server/src/lib.rs create mode 100644 toolkit/crashreporter/mozannotation_server/src/process_reader.rs create mode 100644 toolkit/crashreporter/mozannotation_server/src/process_reader/linux.rs create mode 100644 toolkit/crashreporter/mozannotation_server/src/process_reader/macos.rs create mode 100644 toolkit/crashreporter/mozannotation_server/src/process_reader/windows.rs (limited to 'toolkit/crashreporter/mozannotation_server') diff --git a/toolkit/crashreporter/mozannotation_server/Cargo.toml b/toolkit/crashreporter/mozannotation_server/Cargo.toml new file mode 100644 index 0000000000..747aff791e --- /dev/null +++ b/toolkit/crashreporter/mozannotation_server/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "mozannotation_server" +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.8" +mozannotation_client = { path = "../mozannotation_client/" } +nsstring = { path = "../../../xpcom/rust/nsstring/" } +thin-vec = { version = "0.2.7", features = ["gecko-ffi"] } +thiserror = "1.0.38" + +[target."cfg(any(target_os = \"linux\", target_os = \"android\"))".dependencies] +libc = "0.2" + +[target."cfg(target_os = \"windows\")".dependencies] +winapi = { version = "0.3", features = ["minwindef", "memoryapi", "psapi"] } + +[target."cfg(target_os = \"macos\")".dependencies] +mach2 = { version = "0.4" } diff --git a/toolkit/crashreporter/mozannotation_server/cbindgen.toml b/toolkit/crashreporter/mozannotation_server/cbindgen.toml new file mode 100644 index 0000000000..5329196262 --- /dev/null +++ b/toolkit/crashreporter/mozannotation_server/cbindgen.toml @@ -0,0 +1,15 @@ +header = """/* 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/. */""" +autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen. See RunCbindgen.py */ +""" +include_version = true +braces = "SameLine" +line_length = 100 +tab_width = 2 +language = "C++" +include_guard = "mozannotation_server_ffi_generated_h" +includes = ["nsString.h", "nsTArrayForwardDeclare.h"] + +[export.rename] +"ThinVec" = "nsTArray" diff --git a/toolkit/crashreporter/mozannotation_server/moz.build b/toolkit/crashreporter/mozannotation_server/moz.build new file mode 100644 index 0000000000..21762b95b1 --- /dev/null +++ b/toolkit/crashreporter/mozannotation_server/moz.build @@ -0,0 +1,17 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +if CONFIG["COMPILE_ENVIRONMENT"]: + # This tells mach to run cbindgen and that this header-file should be created + CbindgenHeader( + "mozannotation_server_ffi_generated.h", + inputs=["/toolkit/crashreporter/mozannotation_server"], + ) + + # This tells mach to copy that generated file to obj/dist/include/mozilla/toolkit/crashreporter + EXPORTS.mozilla.toolkit.crashreporter += [ + "!mozannotation_server_ffi_generated.h", + ] diff --git a/toolkit/crashreporter/mozannotation_server/src/errors.rs b/toolkit/crashreporter/mozannotation_server/src/errors.rs new file mode 100644 index 0000000000..037d432e5e --- /dev/null +++ b/toolkit/crashreporter/mozannotation_server/src/errors.rs @@ -0,0 +1,81 @@ +/* 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 RetrievalError { + #[error("The process handle/PID was invalid")] + InvalidProcessHandle, + #[error("Could not find the address of the annotations vector")] + AnnotationTableNotFound(#[from] FindAnnotationsAddressError), + #[error("Corrupt or wrong annotation table")] + InvalidAnnotationTable, + #[error("The data read from the target process is invalid")] + InvalidData, + #[cfg(any(target_os = "linux", target_os = "android"))] + #[error("Could not attach to the target process")] + AttachError(#[from] PtraceError), + #[error("Could not read from the target process address space")] + ReadFromProcessError(#[from] ReadError), + #[error("waitpid() failed when attaching to the process")] + WaitPidError, +} + +#[derive(Debug, Error)] +pub enum FindAnnotationsAddressError { + #[error("Could not convert address {0}")] + ConvertAddressError(#[from] std::num::TryFromIntError), + #[error("goblin failed to parse a module")] + GoblinError(#[from] goblin::error::Error), + #[error("Address was out of bounds")] + InvalidAddress, + #[error("IO error for file {0}")] + IOError(#[from] std::io::Error), + #[error("Could not find the address of the annotations vector")] + NotFound, + #[error("Could not parse address {0}")] + ParseAddressError(#[from] std::num::ParseIntError), + #[error("Could not parse a line in /proc//maps")] + ProcMapsParseError, + #[cfg(any(target_os = "linux", target_os = "android"))] + #[error("Program header was not found")] + ProgramHeaderNotFound, + #[cfg(target_os = "windows")] + #[error("Section was not found")] + SectionNotFound, + #[cfg(target_os = "windows")] + #[error("Cannot enumerate the target process's modules")] + EnumProcessModulesError, + #[error("Could not read memory from the target process")] + ReadError(#[from] ReadError), + #[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(any(target_os = "linux", target_os = "android"))] + #[error("ptrace-specific error")] + PtraceError(#[from] PtraceError), + #[cfg(target_os = "windows")] + #[error("ReadProcessMemory failed")] + ReadProcessMemoryError, + #[cfg(target_os = "macos")] + #[error("mach call failed")] + MachError, +} + +#[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/mozannotation_server/src/lib.rs b/toolkit/crashreporter/mozannotation_server/src/lib.rs new file mode 100644 index 0000000000..4ecb8ec919 --- /dev/null +++ b/toolkit/crashreporter/mozannotation_server/src/lib.rs @@ -0,0 +1,217 @@ +/* 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/. */ + +mod errors; +mod process_reader; + +use crate::errors::*; +use process_reader::ProcessReader; + +use mozannotation_client::{Annotation, AnnotationContents, AnnotationMutex}; +use nsstring::nsCString; +use std::cmp::min; +use std::iter::FromIterator; +use std::mem::size_of; +use std::ptr::null_mut; +use thin_vec::ThinVec; + +#[repr(C)] +pub enum AnnotationData { + Empty, + UsizeData(usize), + NSCStringData(nsCString), + ByteBuffer(ThinVec), +} + +#[repr(C)] +pub struct CAnnotation { + id: u32, + data: AnnotationData, +} + +#[cfg(target_os = "windows")] +type ProcessHandle = winapi::shared::ntdef::HANDLE; +#[cfg(any(target_os = "linux", target_os = "android"))] +type ProcessHandle = libc::pid_t; +#[cfg(any(target_os = "macos"))] +type ProcessHandle = mach2::mach_types::task_t; + +/// Return the annotations of a given process. +/// +/// This function will be exposed to C++ +#[no_mangle] +pub extern "C" fn mozannotation_retrieve( + process: usize, + max_annotations: usize, +) -> *mut ThinVec { + let result = retrieve_annotations(process as _, max_annotations); + match result { + // Leak the object as it will be owned by the C++ code from now on + Ok(annotations) => Box::into_raw(annotations) as *mut _, + Err(_) => null_mut(), + } +} + +/// Free the annotations returned by `mozannotation_retrieve()`. +/// +/// # Safety +/// +/// `ptr` must contain the value returned by a call to +/// `mozannotation_retrieve()` and be called only once. +#[no_mangle] +pub unsafe extern "C" fn mozannotation_free(ptr: *mut ThinVec) { + // The annotation vector will be automatically destroyed when the contents + // of this box are automatically dropped at the end of the function. + let _box = Box::from_raw(ptr); +} + +pub fn retrieve_annotations( + process: ProcessHandle, + max_annotations: usize, +) -> Result>, RetrievalError> { + let reader = ProcessReader::new(process)?; + let address = reader.find_annotations()?; + + let mut mutex = reader.copy_object_shallow::(address)?; + let mutex = unsafe { mutex.assume_init_mut() }; + + // TODO: we should clear the poison value here before getting the mutex + // contents. Right now we have to fail if the mutex was poisoned. + let annotation_table = mutex.get_mut().map_err(|_e| RetrievalError::InvalidData)?; + + if !annotation_table.verify() { + return Err(RetrievalError::InvalidAnnotationTable); + } + + let vec_pointer = annotation_table.get_ptr(); + let length = annotation_table.len(); + let mut annotations = ThinVec::::with_capacity(min(max_annotations, length)); + + for i in 0..length { + let annotation_address = unsafe { vec_pointer.add(i) }; + if let Ok(annotation) = read_annotation(&reader, annotation_address as usize) { + annotations.push(annotation); + } + } + + Ok(Box::new(annotations)) +} + +// Read an annotation from the given address +fn read_annotation(reader: &ProcessReader, address: usize) -> Result { + let raw_annotation = reader.copy_object::(address)?; + let mut annotation = CAnnotation { + id: raw_annotation.id, + data: AnnotationData::Empty, + }; + + match raw_annotation.contents { + AnnotationContents::Empty => {} + AnnotationContents::NSCString => { + let string = copy_nscstring(reader, raw_annotation.address)?; + annotation.data = AnnotationData::NSCStringData(string); + } + AnnotationContents::CString => { + let string = copy_null_terminated_string_pointer(reader, raw_annotation.address)?; + annotation.data = AnnotationData::NSCStringData(string); + } + AnnotationContents::CharBuffer => { + let string = copy_null_terminated_string(reader, raw_annotation.address)?; + annotation.data = AnnotationData::NSCStringData(string); + } + AnnotationContents::USize => { + let value = reader.copy_object::(raw_annotation.address)?; + annotation.data = AnnotationData::UsizeData(value); + } + AnnotationContents::ByteBuffer(size) => { + let value = copy_bytebuffer(reader, raw_annotation.address, size)?; + annotation.data = AnnotationData::ByteBuffer(value); + } + }; + + Ok(annotation) +} + +fn copy_null_terminated_string_pointer( + reader: &ProcessReader, + address: usize, +) -> Result { + let buffer_address = reader.copy_object::(address)?; + copy_null_terminated_string(reader, buffer_address) +} + +fn copy_null_terminated_string( + reader: &ProcessReader, + 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) = copy_null_terminated_string_word_by_word(reader, 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 = reader.copy_object::(address + length)?; + length += 1; + string.push(char); + + if char == 0 { + break; + } + } + + Ok(nsCString::from(&string[..length])) +} + +fn copy_null_terminated_string_word_by_word( + reader: &ProcessReader, + address: usize, +) -> Result { + const WORD_SIZE: usize = size_of::(); + let mut length = 0; + let mut string = Vec::::new(); + + loop { + let mut array = reader.copy_array::(address + length, WORD_SIZE)?; + let null_terminator = array.iter().position(|&e| e == 0); + length += null_terminator.unwrap_or(WORD_SIZE); + string.append(&mut array); + + if null_terminator.is_some() { + break; + } + } + + Ok(nsCString::from(&string[..length])) +} + +fn copy_nscstring(reader: &ProcessReader, address: usize) -> Result { + // HACK: This assumes the layout of the string + let length_address = address + size_of::(); + let length = reader.copy_object::(length_address)?; + + if length > 0 { + let data_address = reader.copy_object::(address)?; + reader + .copy_array::(data_address, length as _) + .map(nsCString::from) + } else { + Ok(nsCString::new()) + } +} + +fn copy_bytebuffer( + reader: &ProcessReader, + address: usize, + size: u32, +) -> Result, ReadError> { + let value = reader.copy_array::(address, size as _)?; + Ok(ThinVec::::from_iter(value.into_iter())) +} diff --git a/toolkit/crashreporter/mozannotation_server/src/process_reader.rs b/toolkit/crashreporter/mozannotation_server/src/process_reader.rs new file mode 100644 index 0000000000..b405b4b725 --- /dev/null +++ b/toolkit/crashreporter/mozannotation_server/src/process_reader.rs @@ -0,0 +1,18 @@ +/* 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 crate::ProcessHandle; + +pub struct ProcessReader { + process: ProcessHandle, +} + +#[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/mozannotation_server/src/process_reader/linux.rs b/toolkit/crashreporter/mozannotation_server/src/process_reader/linux.rs new file mode 100644 index 0000000000..db6dbd3df2 --- /dev/null +++ b/toolkit/crashreporter/mozannotation_server/src/process_reader/linux.rs @@ -0,0 +1,312 @@ +/* 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 mozannotation_client::MozAnnotationNote; +use std::{ + cmp::min, + fs::File, + io::{BufRead, BufReader, Error}, + mem::{size_of, MaybeUninit}, + ptr::null_mut, + slice, +}; + +use crate::{ + errors::{FindAnnotationsAddressError, PtraceError, ReadError, RetrievalError}, + ProcessHandle, +}; + +use super::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, +}; +use memoffset::offset_of; +use mozannotation_client::ANNOTATION_TYPE; + +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(RetrievalError::WaitPidError); + } + } + } else { + break; + } + } + + Ok(ProcessReader { process: pid }) + } + + pub fn find_annotations(&self) -> Result { + let maps_file = File::open(format!("/proc/{}/maps", self.process))?; + + BufReader::new(maps_file) + .lines() + .flatten() + .find_map(|line| self.find_annotations_in_module(&line).ok()) + .ok_or(FindAnnotationsAddressError::NotFound) + } + + fn find_annotations_in_module(&self, line: &str) -> Result { + parse_proc_maps_line(line).and_then(|module_address| { + 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_mozannotation_note(module_address, &elf) + .ok_or(FindAnnotationsAddressError::ProgramHeaderNotFound) + }) + } + + // Looks through the program headers for the note contained in the + // mozannotation_client crate. If the note is found return the address of the + // note's desc field as well as its contents. + fn find_mozannotation_note(&self, module_address: usize, elf: &Elf) -> Option { + 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 + // MozAnnotationNote structure. + if (program_header.p_type == PT_NOTE) + && ((program_header.p_flags & PF_R) != 0 + && (program_header.p_memsz as usize >= size_of::())) + { + // Iterate over the notes + let notes_address = module_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 == ANNOTATION_TYPE { + if let Ok(note) = self.copy_object::(note_address) { + let desc = note.desc; + let ehdr = (-note.ehdr) as usize; + let offset = desc + ehdr + - (offset_of!(MozAnnotationNote, ehdr) + - offset_of!(MozAnnotationNote, desc)); + + return usize::checked_add(module_address, offset); + } + } + + notes_offset += size_of::() + + (note.n_descsz as usize) + + (note.n_namesz as usize); + } else { + break; + } + } + } + } + + None + } + + 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 { + let mut splits = line.trim().splitn(6, ' '); + let address_str = splits + .next() + .ok_or(FindAnnotationsAddressError::ProcMapsParseError)?; + let _perms_str = splits + .next() + .ok_or(FindAnnotationsAddressError::ProcMapsParseError)?; + let _offset_str = splits + .next() + .ok_or(FindAnnotationsAddressError::ProcMapsParseError)?; + let _dev_str = splits + .next() + .ok_or(FindAnnotationsAddressError::ProcMapsParseError)?; + let _inode_str = splits + .next() + .ok_or(FindAnnotationsAddressError::ProcMapsParseError)?; + let _path_str = splits + .next() + .ok_or(FindAnnotationsAddressError::ProcMapsParseError)?; + + let address = get_proc_maps_address(address_str)?; + + Ok(address) +} + +fn get_proc_maps_address(addresses: &str) -> Result { + let begin = addresses + .split('-') + .next() + .ok_or(FindAnnotationsAddressError::ProcMapsParseError)?; + usize::from_str_radix(begin, 16).map_err(FindAnnotationsAddressError::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(target_os = "linux")] +type PTraceOperationNative = libc::c_uint; +#[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/mozannotation_server/src/process_reader/macos.rs b/toolkit/crashreporter/mozannotation_server/src/process_reader/macos.rs new file mode 100644 index 0000000000..52a3957ca9 --- /dev/null +++ b/toolkit/crashreporter/mozannotation_server/src/process_reader/macos.rs @@ -0,0 +1,214 @@ +/* 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::{ + errors::{FindAnnotationsAddressError, ReadError, RetrievalError}, + ProcessHandle, +}; + +use super::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"; +const MOZANNOTATION_SECTION: &[u8; 16] = b"mozannotation\0\0\0"; + +impl ProcessReader { + pub fn new(process: ProcessHandle) -> Result { + Ok(ProcessReader { process }) + } + + pub fn find_annotations(&self) -> Result { + let dyld_info = self.task_info()?; + if (dyld_info.all_image_info_format as u32) != TASK_DYLD_ALL_IMAGE_INFO_64 { + return Err(FindAnnotationsAddressError::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(FindAnnotationsAddressError::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_map(|image| self.find_annotations_in_image(image)) + .ok_or(FindAnnotationsAddressError::NotFound) + } + + 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(FindAnnotationsAddressError::TaskInfoError) + } + } + + fn find_annotations_in_image(&self, image: &ImageInfo) -> Option { + self.copy_object::(image.load_address as _) + .map_err(FindAnnotationsAddressError::from) + .and_then(|header| { + let image_address = image.load_address as usize; + let mut address = image_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_annotations_in_segment(address) { + return image_address + .checked_add(offset) + .ok_or(FindAnnotationsAddressError::InvalidAddress); + } + } + + address += command.cmdsize as usize; + } + } + + Err(FindAnnotationsAddressError::NotFound) + }) + .ok() + } + + fn find_annotations_in_segment( + &self, + segment_address: usize, + ) -> 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(MOZANNOTATION_SECTION) { + return Ok(section.offset as usize); + } + } + } + + Err(FindAnnotationsAddressError::InvalidAddress) + } + + 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/mozannotation_server/src/process_reader/windows.rs b/toolkit/crashreporter/mozannotation_server/src/process_reader/windows.rs new file mode 100644 index 0000000000..ffdcd95937 --- /dev/null +++ b/toolkit/crashreporter/mozannotation_server/src/process_reader/windows.rs @@ -0,0 +1,193 @@ +/* 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, + mem::{size_of, MaybeUninit}, + ptr::null_mut, +}; + +use winapi::{ + shared::{ + basetsd::SIZE_T, + minwindef::{DWORD, FALSE, HMODULE}, + }, + um::{ + memoryapi::ReadProcessMemory, + psapi::{K32EnumProcessModules, K32GetModuleInformation, MODULEINFO}, + }, +}; + +use crate::{ + errors::{FindAnnotationsAddressError, ReadError, RetrievalError}, + ProcessHandle, +}; + +use super::ProcessReader; + +impl ProcessReader { + pub fn new(process: ProcessHandle) -> Result { + Ok(ProcessReader { process }) + } + + pub fn find_annotations(&self) -> Result { + let modules = self.get_module_list()?; + + modules + .iter() + .find_map(|&module| { + self.get_module_info(module).and_then(|info| { + self.find_annotations_in_module( + info.lpBaseOfDll as usize, + info.SizeOfImage as usize, + ) + .ok() + }) + }) + .ok_or(FindAnnotationsAddressError::InvalidAddress) + } + + fn get_module_list(&self) -> Result, FindAnnotationsAddressError> { + let mut module_num: usize = 100; + let mut required_buffer_size: DWORD = 0; + let mut module_array = Vec::>::with_capacity(module_num); + + loop { + let buffer_size: DWORD = (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(FindAnnotationsAddressError::EnumProcessModulesError); + } + } else { + break; + } + } + + // SAFETY: module_array has been filled by K32EnumProcessModules() + let module_array: Vec = unsafe { + module_array.set_len(module_num); + std::mem::transmute(module_array) + }; + + Ok(module_array) + } + + 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 DWORD, + ) + }; + + if res == 0 { + None + } else { + let info = unsafe { info.assume_init() }; + Some(info) + } + } + + fn find_annotations_in_module( + &self, + 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(FindAnnotationsAddressError::ReadError( + 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(mozannotation_client::ANNOTATION_SECTION) { + let address = module_address.checked_add(section.virtual_address as usize); + return address.ok_or(FindAnnotationsAddressError::InvalidAddress); + } + } + + Err(FindAnnotationsAddressError::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::() as SIZE_T, + 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 as SIZE_T, + null_mut(), + ) + }; + + if res != FALSE { + unsafe { + array.set_len(num); + Ok(std::mem::transmute(array)) + } + } else { + Err(ReadError::ReadProcessMemoryError) + } + } +} -- cgit v1.2.3