From fc1f55870962eb5d46a4585aeea76d3877f60fd3 Mon Sep 17 00:00:00 2001 From: Chris Martin Date: Mon, 1 Jun 2026 13:18:23 -0400 Subject: [PATCH 1/2] Isolated Processes 7: Rewrite all backend functions in libc --- crates/linux/process-backend/src/lib.rs | 1 + crates/linux/process-backend/src/local/mod.rs | 396 +++++++++++++++++- .../src/local/module_reader.rs | 152 +++++++ .../linux/process-backend/src}/regs.rs | 0 src/linux/auxv/mod.rs | 12 +- src/linux/dumper_cpu_info/arm.rs | 5 +- src/linux/dumper_cpu_info/mod.rs | 10 +- src/linux/dumper_cpu_info/x86.rs | 13 +- src/linux/maps_reader.rs | 18 +- src/linux/minidump_writer/app_memory.rs | 10 +- src/linux/minidump_writer/errors.rs | 31 +- .../minidump_writer/handle_data_stream.rs | 9 +- .../memory_info_list_stream.rs | 6 +- src/linux/minidump_writer/mod.rs | 59 ++- .../minidump_writer/thread_list_stream.rs | 8 +- src/linux/mod.rs | 5 +- src/linux/module_reader.rs | 19 +- src/linux/process_inspection/mod.rs | 370 +++++++--------- src/linux/process_inspection/module_reader.rs | 64 --- .../process_inspection/process_reader.rs | 194 ++++----- src/linux/thread_info/mod.rs | 17 +- src/module_reader.rs | 3 + src/serializers.rs | 13 +- tests/linux_minidump_writer_soft_error.rs | 25 +- 24 files changed, 922 insertions(+), 518 deletions(-) create mode 100644 crates/linux/process-backend/src/local/module_reader.rs rename {src/linux/process_inspection => crates/linux/process-backend/src}/regs.rs (100%) delete mode 100644 src/linux/process_inspection/module_reader.rs diff --git a/crates/linux/process-backend/src/lib.rs b/crates/linux/process-backend/src/lib.rs index df95a2e5..00d93710 100644 --- a/crates/linux/process-backend/src/lib.rs +++ b/crates/linux/process-backend/src/lib.rs @@ -2,6 +2,7 @@ #![cfg(any(target_os = "linux", target_os = "android"))] pub mod local; +pub mod regs; /// This is the longest path length we guarantee we can handle, since we won't be able to allocate /// in the fork of the crashed process. We can increase if necessary. diff --git a/crates/linux/process-backend/src/local/mod.rs b/crates/linux/process-backend/src/local/mod.rs index 84fdc907..07bd4e7e 100644 --- a/crates/linux/process-backend/src/local/mod.rs +++ b/crates/linux/process-backend/src/local/mod.rs @@ -1,14 +1,26 @@ use { - core::{cell::RefCell, ffi::c_int}, + crate::regs::*, + core::{ + cell::RefCell, + ffi::{CStr, c_int, c_long, c_void}, + mem, ptr, + }, libc::pid_t, syscall_invoker::SyscallInvoker, }; -pub use error::Error; +pub use {error::Error, module_reader::MappedModuleMemoryReader}; mod error; +mod module_reader; mod syscall_invoker; +#[cfg(target_env = "gnu")] +type PtraceRequestType = core::ffi::c_uint; + +#[cfg(not(target_env = "gnu"))] +type PtraceRequestType = core::ffi::c_int; + #[derive(Debug)] pub struct Backend { pid: pid_t, @@ -34,6 +46,281 @@ impl Backend { Ok(()) } + pub fn suspend_thread(&self, tid: libc::pid_t) -> Result<(), Error> { + self.standard_syscall(|| unsafe { + ptrace(libc::PTRACE_ATTACH, tid, ptr::null_mut(), ptr::null_mut()) + }) + .map_err(Error::PtraceAttachFailed)?; + + loop { + let mut status = 0; + if let Err(e) = + self.standard_syscall(|| unsafe { libc::waitpid(tid, &mut status, libc::__WALL) }) + { + if e == libc::EINTR { + continue; + } + self.ptrace_detach(tid)?; + Err(Error::WaitPidFailed(e))?; + } + + if !libc::WIFSTOPPED(status) { + Err(Error::UnexpectedStatus(status))?; + } + + let signal = libc::WSTOPSIG(status); + + // Any signal will stop the thread, make sure it is SIGSTOP. Otherwise, this + // signal will be delivered after PTRACE_DETACH, and the thread will enter + // the "T (stopped)" state. + if signal == libc::SIGSTOP { + break; + } + + // Signals other than SIGSTOP that are received need to be reinjected, + // or they will otherwise get lost. + self.standard_syscall(|| unsafe { + ptrace(libc::PTRACE_CONT, tid, ptr::null_mut(), signal as *mut _) + }) + .map_err(|e| Error::ReinjectFailed(signal, e))?; + } + + Ok(()) + } + + pub fn resume_thread(&self, tid: libc::pid_t) -> Result<(), Error> { + self.ptrace_detach(tid) + } + + pub fn map_module_into_memory( + &self, + path: &CStr, + offset: u64, + ) -> Result { + MappedModuleMemoryReader::new(&mut self.syscall_invoker.borrow_mut(), path, offset) + } + + pub fn stat_file(&self, path: &CStr) -> Result { + let mut output = unsafe { mem::zeroed::() }; + self.standard_syscall(|| unsafe { libc::stat(path.as_ptr(), &mut output) }) + .map_err(Error::StatFailed)?; + Ok(output) + } + + pub fn read_file(&self, path: &CStr) -> Result { + self.open_file(path).map(FileReader) + } + + pub fn read_dir(&self, path: &CStr) -> Result { + self.special_syscall(|| unsafe { + let dirp = libc::opendir(path.as_ptr()); + if dirp.is_null() { + return Err(()); + } + Ok(dirp) + }) + .map(|dirp| DirReader { dirp, eof: false }) + .map_err(Error::OpenDirFailed) + } + + pub fn read_link(&self, path: &CStr, buf: &mut [u8]) -> Result { + let bytes_read = self + .standard_syscall(|| unsafe { + libc::readlink(path.as_ptr(), buf.as_mut_ptr().cast(), buf.len()) + }) + .map_err(Error::ReadLinkFailed)?; + + let bytes_read = usize::try_from(bytes_read).unwrap(); + if bytes_read == buf.len() { + Err(Error::BufferTooSmall)?; + } + + Ok(bytes_read) + } + + pub fn read_process_io_vec(&self, buf: &mut [u8], offset: usize) -> Result { + let mut local_iov = [libc::iovec { + iov_base: buf.as_mut_ptr().cast(), + iov_len: buf.len(), + }]; + + let mut remote_iov = [libc::iovec { + iov_base: offset as *mut _, + iov_len: buf.len(), + }]; + + let bytes_read = self + .standard_syscall(|| unsafe { + libc::process_vm_readv( + self.pid, + local_iov.as_mut_ptr(), + local_iov.len().try_into().unwrap(), + remote_iov.as_mut_ptr(), + remote_iov.len().try_into().unwrap(), + 0, + ) + }) + .map_err(Error::ProcessVmReadvFailed)?; + + Ok(usize::try_from(bytes_read).unwrap()) + } + + pub fn ptrace_peekdata(&self, offset: usize) -> Result<[u8; mem::size_of::()], Error> { + self.special_syscall(|| unsafe { + set_errno(0); + let rv = ptrace( + libc::PTRACE_PEEKDATA, + self.pid, + offset as *mut _, + ptr::null_mut(), + ); + if rv == -1 && errno() != 0 { + return Err(()); + } + Ok(rv.to_ne_bytes()) + }) + .map_err(Error::PtracePeekDataFailed) + } + + pub fn get_gen_regs(&self, tid: libc::pid_t) -> Result { + self.getregset(tid).or_else(|_| self.getregs(tid)) + } + + pub fn get_fp_regs(&self, tid: libc::pid_t) -> Result { + self.getfpregset(tid).or_else(|_| self.getfpregs(tid)) + } + + #[cfg(target_arch = "x86")] + pub fn get_fpx_regs(&self, tid: libc::pid_t) -> Result { + const PTRACE_GETFPXREGS: PtraceRequestType = 18; + unsafe { self.ptrace_getregs::(PTRACE_GETFPXREGS, tid) } + } + + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + pub fn ptrace_peekuser( + &self, + pid: libc::pid_t, + addr: usize, + ) -> Result<[u8; mem::size_of::()], Error> { + self.special_syscall(|| unsafe { + set_errno(0); + let rv = ptrace( + libc::PTRACE_PEEKUSER, + pid, + addr as *mut _, + core::ptr::null_mut(), + ); + if rv == -1 && errno() != 0 { + return Err(()); + } + Ok(rv.to_ne_bytes()) + }) + .map_err(Error::PtracePeekUserFailed) + } + + fn open_file(&self, path: &CStr) -> Result { + self.standard_syscall(|| unsafe { + libc::open(path.as_ptr(), libc::O_RDONLY | libc::O_CLOEXEC, 0) + }) + .map(OwnedFd) + .map_err(Error::OpenFileFailed) + } + + fn getregset(&self, _pid: libc::pid_t) -> Result { + #[cfg(target_arch = "arm")] + { + Err(Error::NotSupported) + } + #[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))] + { + const NT_PRSTATUS: usize = 1; + self.ptrace_getregset(NT_PRSTATUS, _pid) + } + } + + fn getregs(&self, pid: libc::pid_t) -> Result { + const PTRACE_GETREGS: PtraceRequestType = 12; + unsafe { self.ptrace_getregs::(PTRACE_GETREGS, pid) } + } + + fn getfpregset(&self, pid: libc::pid_t) -> Result { + #[cfg(target_arch = "arm")] + { + const NT_ARM_VFP: usize = 0x400; + self.ptrace_getregset(NT_ARM_VFP, pid) + } + #[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))] + { + const NT_PRFPREGSET: usize = 2; + self.ptrace_getregset(NT_PRFPREGSET, pid) + } + } + + fn getfpregs(&self, _pid: libc::pid_t) -> Result { + #[cfg(target_arch = "arm")] + { + Err(Error::NotSupported) + } + #[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))] + { + const PTRACE_GETFPREGS: PtraceRequestType = 14; + unsafe { self.ptrace_getregs::(PTRACE_GETFPREGS, _pid) } + } + } + + /// Safety: RequestType and T must agree on the size of the returned type + unsafe fn ptrace_getregs( + &self, + request: PtraceRequestType, + pid: libc::pid_t, + ) -> Result { + let mut output = mem::MaybeUninit::::uninit(); + self.standard_syscall(|| unsafe { + ptrace( + request, + pid, + core::ptr::null_mut(), + output.as_mut_ptr().cast(), + ) + }) + .map_err(Error::GetRegistersFailed)?; + Ok(unsafe { output.assume_init() }) + } + + fn ptrace_getregset(&self, regset_type: usize, pid: libc::pid_t) -> Result { + let mut output = mem::MaybeUninit::::uninit(); + let mut io = libc::iovec { + iov_base: output.as_mut_ptr().cast(), + iov_len: mem::size_of::(), + }; + + self.standard_syscall(|| unsafe { + ptrace( + libc::PTRACE_GETREGSET, + pid, + regset_type as *mut _, + (&raw mut io).cast(), + ) + }) + .map_err(Error::GetRegistersFailed)?; + + // PTRACE_GETREGSET returns the number of bytes actually read in iov_len. Need to ensure + // all bytes of T are actually initialized + if io.iov_len != mem::size_of::() { + Err(Error::GetRegistersFailed(libc::EINVAL))?; + } + + Ok(unsafe { output.assume_init() }) + } + + fn ptrace_detach(&self, tid: libc::pid_t) -> Result<(), Error> { + self.standard_syscall(|| unsafe { + ptrace(libc::PTRACE_DETACH, tid, ptr::null_mut(), ptr::null_mut()) + }) + .map_err(Error::PtraceDetachFailed)?; + Ok(()) + } + pub fn standard_syscall(&self, f: F) -> Result where F: FnOnce() -> T, @@ -57,10 +344,115 @@ impl Backend { } } +#[derive(Debug)] +pub struct FileReader(OwnedFd); + +impl FileReader { + pub fn read(&mut self, buf: &mut [u8]) -> Result { + let rv = unsafe { libc::read(self.0.0, buf.as_mut_ptr().cast(), buf.len()) }; + if rv == -1 { + return Err(Error::ReadFileFailed(errno())); + } + Ok(rv.try_into().unwrap()) + } + pub fn read_at(&self, buf: &mut [u8], offset: u64) -> Result { + let rv = unsafe { + libc::pread( + self.0.0, + buf.as_mut_ptr().cast(), + buf.len(), + offset.try_into().unwrap(), + ) + }; + if rv == -1 { + return Err(Error::ReadFileFailed(errno())); + } + Ok(rv.try_into().unwrap()) + } +} + +#[derive(Debug)] +pub struct DirReader { + dirp: *mut libc::DIR, + eof: bool, +} + +impl DirReader { + pub fn read_name(&mut self) -> Result, Error> { + if self.eof { + return Ok(None); + } + + loop { + set_errno(0); + let dirent = unsafe { libc::readdir(self.dirp) }; + if dirent.is_null() { + if errno() == 0 { + self.eof = true; + return Ok(None); + } + return Err(Error::ReadDirFailed(errno())); + } + + // The dirent structure is not guaranteed to be fully initialized, so it's only safe to + // read it through pointers + // + // SAFETY: the dirent structure is guaranteed to exist until we call readdir() again + // or closedir(), which we prevent by holding `&mut self` while `&[u8]` is alive. + let name_bytes = + unsafe { CStr::from_ptr((&raw const (*dirent).d_name).cast()).to_bytes() }; + + if name_bytes == b"." || name_bytes == b".." { + continue; + } + + return Ok(Some(name_bytes)); + } + } +} + +impl Drop for DirReader { + fn drop(&mut self) { + let rv = unsafe { libc::closedir(self.dirp) }; + if rv == -1 { + log::debug!("failed to close directory: {}", errno()); + } + } +} + +#[derive(Debug)] +struct OwnedFd(c_int); + +impl Drop for OwnedFd { + fn drop(&mut self) { + let rv = unsafe { libc::close(self.0) }; + if rv == -1 { + log::error!("failed to close file: {}", errno()); + } + } +} + +/// This is just a typesafe wrapper around ptrace(), which is vararg... But this is Rust, and +/// playing loosey-goosey with types is really more of a C thing ;) +unsafe fn ptrace( + request: PtraceRequestType, + pid: libc::pid_t, + addr: *mut c_void, + data: *mut c_void, +) -> c_long { + unsafe { libc::ptrace(request, pid, addr, data) } +} + fn errno() -> c_int { unsafe { *errno_location() } } +fn set_errno(value: c_int) { + unsafe { + *errno_location() = value; + } +} + #[cfg(target_os = "android")] fn errno_location() -> *mut c_int { unsafe { libc::__errno() } diff --git a/crates/linux/process-backend/src/local/module_reader.rs b/crates/linux/process-backend/src/local/module_reader.rs new file mode 100644 index 00000000..06f0012c --- /dev/null +++ b/crates/linux/process-backend/src/local/module_reader.rs @@ -0,0 +1,152 @@ +use { + super::{Error, OwnedFd, SyscallInvoker, errno}, + core::{ + ffi::{CStr, c_void}, + mem, ptr, + }, +}; + +#[derive(Debug)] +pub struct MappedModuleMemoryReader { + mapped: Mapped, + ptr: *mut u8, + len: usize, +} + +impl MappedModuleMemoryReader { + pub fn new( + syscall_invoker: &mut SyscallInvoker, + path: &CStr, + start_position: u64, + ) -> Result { + let fd = Self::open_file(syscall_invoker, path)?; + + // So far, we only ever map files from the start position to EOF - We never specify a + // max length anywhere. + let end_position = Self::get_file_size(syscall_invoker, &fd)?; + + if start_position > end_position { + Err(Error::StartPositionPastEnd)?; + } + + // a mmap() mapping must start on a page-aligned offset within the file + let page_size = Self::get_page_size(); + let offset_into_page = start_position % page_size; + let aligned_start_position = start_position - offset_into_page; + let mmap_length = usize::try_from(end_position - aligned_start_position) + .map_err(|_| Error::MappingTooLarge)?; + + let mapped = Self::map_memory(syscall_invoker, &fd, aligned_start_position, mmap_length)?; + + // Contrary to what you might expect, it's fine to close the fd once the mapping has + // been established + drop(fd); + + // Now that we have our page-aligned memory mapped, back-calculate the (ptr, len) pair + // for the actual slice the user asked for. + + let slice_offset_into_mapping = usize::try_from(offset_into_page).unwrap(); + let ptr = unsafe { mapped.ptr.cast::().add(slice_offset_into_mapping) }; + let len = mmap_length - slice_offset_into_mapping; + + Ok(MappedModuleMemoryReader { mapped, ptr, len }) + } + pub fn read(&self, offset: u64, length: u64) -> Result<&[u8], Error> { + (|| { + let offset = usize::try_from(offset).ok()?; + let length = usize::try_from(length).ok()?; + let end = offset.checked_add(length)?; + self.as_slice().get(offset..end) + })() + .ok_or(Error::IndexOutOfBounds) + } + pub fn len(&self) -> Result { + Ok(self.as_slice().len()) + } + pub fn is_empty(&self) -> Result { + self.len().map(|l| l == 0) + } + fn open_file(syscall_invoker: &mut SyscallInvoker, path: &CStr) -> Result { + syscall_invoker + .invoke_standard(|| unsafe { + libc::open(path.as_ptr(), libc::O_RDONLY | libc::O_CLOEXEC, 0) + }) + .map(OwnedFd) + .map_err(Error::OpenFileFailed) + } + fn get_file_size(syscall_invoker: &mut SyscallInvoker, fd: &OwnedFd) -> Result { + let mut stat: libc::stat = unsafe { mem::zeroed() }; + + syscall_invoker + .invoke_standard(|| unsafe { libc::fstat(fd.0, &mut stat) }) + .map_err(Error::StatFailed)?; + + Ok(u64::try_from(stat.st_size).unwrap()) + } + fn get_page_size() -> u64 { + let page_size = u64::try_from(unsafe { libc::sysconf(libc::_SC_PAGESIZE) }).unwrap(); + assert!(page_size > 0); + page_size + } + fn map_memory( + syscall_invoker: &mut SyscallInvoker, + fd: &OwnedFd, + page_aligned_start_position: u64, + len: usize, + ) -> Result { + // Linux requires the mapping length to be non-zero, even though we want to support + // zero-length mappings -- So we just make it a one-byte mapping (and ignore the byte). + let len = usize::max(len, 1); + + // Rust/LLVM cannot support a single object larger than `isize::MAX`, which is + // 2GiB on 32-bit systems. It is possible to map files larger than that, but there is a + // bunch of special handling that needs to be done to avoid accidentally telling LLVM that + // the mapped memory might be a single object with the same provenance. + // + // Luckily, we won't be accessing files that are larger than 2GiB, so we can skip all that + // nastiness by disallowing a mapping larger than `isize::MAX`. + + if len > isize::MAX as usize { + Err(Error::MappingTooLarge)?; + } + + syscall_invoker + .invoke(|| unsafe { + let ptr = libc::mmap( + ptr::null_mut(), + len, + libc::PROT_READ, + libc::MAP_SHARED, + fd.0, + page_aligned_start_position.try_into().unwrap(), + ); + if ptr == libc::MAP_FAILED { + return Err(()); + } + Ok(Mapped { ptr, len }) + }) + .map_err(Error::MMapfailed) + } + fn as_slice(&self) -> &[u8] { + // The compiler will warn that we're not using `mapped` at all, but technically this + // function does use it -- it just isn't captured by the semantics. This is basically + // a no-op just to show that we do, in fact, use it. + let _mapped_used = &self.mapped; + unsafe { core::slice::from_raw_parts(self.ptr, self.len) } + } +} + +#[derive(Debug)] +struct Mapped { + ptr: *mut c_void, + len: usize, +} + +impl Drop for Mapped { + fn drop(&mut self) { + let rv = unsafe { libc::munmap(self.ptr, self.len) }; + if rv == -1 { + log::error!("failed to unmap memory: {}", errno()); + } + } +} diff --git a/src/linux/process_inspection/regs.rs b/crates/linux/process-backend/src/regs.rs similarity index 100% rename from src/linux/process_inspection/regs.rs rename to crates/linux/process-backend/src/regs.rs diff --git a/src/linux/auxv/mod.rs b/src/linux/auxv/mod.rs index ade5448c..9eb0a897 100644 --- a/src/linux/auxv/mod.rs +++ b/src/linux/auxv/mod.rs @@ -1,6 +1,9 @@ use { self::reader::ProcfsAuxvIter, - super::{Pid, process_inspection::ProcessInspector}, + super::{ + Pid, + process_inspection::{self, ProcessInspector}, + }, crate::serializers::*, error_graph::WriteErrorList, failspot::failspot, @@ -144,12 +147,7 @@ impl AuxvDumpInfo { #[derive(Debug, Error, serde::Serialize)] pub enum AuxvError { #[error("Failed to open file {0}")] - OpenError( - String, - #[source] - #[serde(serialize_with = "serialize_io_error")] - std::io::Error, - ), + OpenError(String, #[source] process_inspection::Error), #[error("No auxv entry found for PID {0}")] NoAuxvEntryFound(Pid), #[error("Invalid auxv format (should not hit EOF before AT_NULL)")] diff --git a/src/linux/dumper_cpu_info/arm.rs b/src/linux/dumper_cpu_info/arm.rs index fedc9fb5..1c54b40d 100644 --- a/src/linux/dumper_cpu_info/arm.rs +++ b/src/linux/dumper_cpu_info/arm.rs @@ -13,7 +13,8 @@ type Result = std::result::Result; fn parse_cpus_from_sysfile(mut file: R) -> Result> { let mut res = HashSet::new(); let mut content = String::new(); - file.read_to_string(&mut content)?; + file.read_to_string(&mut content) + .map_err(CpuInfoError::FileIOError)?; // Expected format: comma-separated list of items, where each // item can be a decimal integer, or two decimal integers separated // by a dash. @@ -203,7 +204,7 @@ pub fn write_cpu_information( let mut elf_hwcaps = 0; for line in BufReader::new(cpuinfo_file).lines() { - let line = line?; + let line = line.map_err(CpuInfoError::FileIOError)?; // Expected format: + ':' // Note that: // - empty lines happen. diff --git a/src/linux/dumper_cpu_info/mod.rs b/src/linux/dumper_cpu_info/mod.rs index 6ef4993f..f0f1d1ec 100644 --- a/src/linux/dumper_cpu_info/mod.rs +++ b/src/linux/dumper_cpu_info/mod.rs @@ -1,5 +1,5 @@ use { - super::process_inspection::ProcessInspector, + super::process_inspection::{self, ProcessInspector}, crate::{minidump_format::PlatformId, serializers::*}, nix::sys::utsname::uname, }; @@ -26,9 +26,11 @@ pub use imp::write_cpu_information; #[derive(Debug, thiserror::Error, serde::Serialize)] pub enum CpuInfoError { - #[error("IO error for file /proc/cpuinfo")] - IOError( - #[from] + #[error("failed to read /proc/cpuinfo")] + ReadFileError(#[source] process_inspection::Error), + #[error("I/O error reading /proc/cpuinfo")] + FileIOError( + #[source] #[serde(serialize_with = "serialize_io_error")] std::io::Error, ), diff --git a/src/linux/dumper_cpu_info/x86.rs b/src/linux/dumper_cpu_info/x86.rs index e4b55a65..5bfb678d 100644 --- a/src/linux/dumper_cpu_info/x86.rs +++ b/src/linux/dumper_cpu_info/x86.rs @@ -45,16 +45,17 @@ pub fn write_cpu_information( MDCPUArchitecture::PROCESSOR_ARCHITECTURE_AMD64 } as u16; - failspot!( - CpuInfoFileOpen - bail(std::io::Error::other("test requested cpuinfo file failure")) - ); + if failspot!(CpuInfoFileOpen) { + process_inspector.fail_one_syscall_with(libc::EPERM); + } - let cpuinfo_file = process_inspector.read_file("/proc/cpuinfo")?; + let cpuinfo_file = process_inspector + .read_file("/proc/cpuinfo") + .map_err(CpuInfoError::ReadFileError)?; let mut vendor_id = String::new(); for line in BufReader::new(cpuinfo_file).lines() { - let line = line?; + let line = line.map_err(CpuInfoError::FileIOError)?; // Expected format: + ':' // Note that: // - empty lines happen. diff --git a/src/linux/maps_reader.rs b/src/linux/maps_reader.rs index 0e99ed0d..57986db1 100644 --- a/src/linux/maps_reader.rs +++ b/src/linux/maps_reader.rs @@ -1,6 +1,8 @@ use { super::{ - auxv::AuxvType, module_reader::ModuleReaderError, process_inspection::ProcessInspector, + auxv::AuxvType, + module_reader::ModuleReaderError, + process_inspection::{self, ProcessInspector}, serializers::*, }, crate::serializers::*, @@ -61,6 +63,8 @@ pub type MappingList = Vec; #[derive(thiserror::Error, Debug, serde::Serialize)] pub enum MapsReaderError { + #[error("failed to read /proc//maps")] + ReadFileFailed(#[source] process_inspection::Error), #[error("Couldn't parse as ELF file")] ELFParsingFailed( #[from] @@ -103,8 +107,6 @@ pub enum MapsReaderError { MmapSanityCheckFailed, #[error("Symlink does not match ({0} vs. {1})")] SymlinkError(std::path::PathBuf, std::path::PathBuf), - #[error("Mappings file missing for pid {pid} (is the process still alive?)")] - MappingFileMissing { pid: i32 }, #[error("Failed to parse memory maps file")] ParsingError( #[from] @@ -139,13 +141,9 @@ impl MappingInfo { linux_gate_loc: Option, ) -> Result> { let maps_path = format!("/proc/{}/maps", pid); - let maps_file = process_inspector.read_file(&maps_path).map_err(|e| { - if e.kind() == std::io::ErrorKind::NotFound { - MapsReaderError::MappingFileMissing { pid } - } else { - e.into() - } - })?; + let maps_file = process_inspector + .read_file(&maps_path) + .map_err(MapsReaderError::ReadFileFailed)?; let maps = MemoryMaps::from_read(maps_file)?; Self::aggregate(maps, linux_gate_loc) } diff --git a/src/linux/minidump_writer/app_memory.rs b/src/linux/minidump_writer/app_memory.rs index 2d9eaa0a..e4dbe19f 100644 --- a/src/linux/minidump_writer/app_memory.rs +++ b/src/linux/minidump_writer/app_memory.rs @@ -1,16 +1,8 @@ use super::*; -#[derive(Debug, Error, serde::Serialize)] -pub enum SectionAppMemoryError { - #[error("Failed to copy memory from process")] - CopyFromProcessError(#[from] CopyFromProcessError), - #[error("Failed to write to memory")] - MemoryWriterError(#[from] MemoryWriterError), -} - impl MinidumpWriter { /// Write application-provided memory regions. - pub fn write_app_memory(&mut self, buffer: &mut DumpBuf) -> Result<(), SectionAppMemoryError> { + pub fn write_app_memory(&mut self, buffer: &mut DumpBuf) -> Result<(), CopyFromProcessError> { let blamed_thread = self.blamed_thread; for app_memory in &self.app_memory { let data_copy = Self::copy_from_process( diff --git a/src/linux/minidump_writer/errors.rs b/src/linux/minidump_writer/errors.rs index 78df0fa4..e99428d2 100644 --- a/src/linux/minidump_writer/errors.rs +++ b/src/linux/minidump_writer/errors.rs @@ -5,7 +5,7 @@ use { dso_debug::SectionDsoDebugError, maps_reader::MapsReaderError, minidump_writer::{ - app_memory::SectionAppMemoryError, exception_stream::SectionExceptionStreamError, + exception_stream::SectionExceptionStreamError, handle_data_stream::SectionHandleDataStreamError, mappings::SectionMappingsError, memory_info_list_stream::SectionMemInfoListError, memory_list_stream::SectionMemListError, systeminfo_stream::SectionSystemInfoError, @@ -16,7 +16,10 @@ use { process_inspection, serializers::*, }, - crate::{dir_section::FileWriterError, mem_writer::MemoryWriterError, serializers::*}, + crate::{ + dir_section::FileWriterError, mem_writer::MemoryWriterError, + process_reader::CopyFromProcessError, serializers::*, + }, error_graph::ErrorList, procfs_core::ProcError, std::ffi::OsString, @@ -31,7 +34,7 @@ pub enum WriterError { #[error("Error during init phase")] InitError(#[from] InitError), #[error("Failed when writing section AppMemory")] - SectionAppMemoryError(#[from] SectionAppMemoryError), + SectionAppMemoryError(#[source] CopyFromProcessError), #[error("Failed when writing section ExceptionStream")] SectionExceptionStreamError(#[from] SectionExceptionStreamError), #[error("Failed when writing section HandleDataStream")] @@ -151,9 +154,9 @@ pub enum WriterError { std::num::TryFromIntError, ), #[error("failed to suspend thread")] - SuspendThreadFailed(#[source] process_inspection::SuspendResumeThreadError), + SuspendThreadFailed(#[source] process_inspection::Error), #[error("failed to resume thread")] - ResumeThreadFailed(#[source] process_inspection::SuspendResumeThreadError), + ResumeThreadFailed(#[source] process_inspection::Error), } #[derive(Debug, Error, serde::Serialize)] @@ -161,11 +164,7 @@ pub enum InitError { #[error("failed to read auxv")] ReadAuxvFailed(#[source] super::super::auxv::AuxvError), #[error("IO error reading /proc//task")] - ReadProcTaskFailed( - #[source] - #[serde(serialize_with = "serialize_io_error")] - std::io::Error, - ), + ReadProcTaskFailed(#[source] process_inspection::Error), #[cfg(target_os = "android")] #[error("Failed Android specific late init")] AndroidLateInitError(#[from] AndroidError), @@ -184,11 +183,7 @@ pub enum InitError { #[error("Failed filling missing Auxv info")] FillMissingAuxvInfoFailed(#[source] AuxvError), #[error("Failed reading proc/pid/task entry for process")] - ReadProcessThreadEntryFailed( - #[source] - #[serde(serialize_with = "serialize_io_error")] - std::io::Error, - ), + ReadProcessThreadEntryFailed(#[source] process_inspection::Error), #[error("Process task entry `{0:?}` could not be parsed as a TID")] ProcessTaskEntryNotTid(OsString), #[error("Failed to read thread name")] @@ -220,11 +215,7 @@ pub enum StopProcessError { #[error("Failed to stop the process")] Stop(#[source] process_inspection::Error), #[error("failed to open process file")] - ReadFileFailed( - #[source] - #[serde(serialize_with = "serialize_io_error")] - std::io::Error, - ), + ReadFileFailed(#[source] process_inspection::Error), #[error("Failed to get the process state")] State( #[from] diff --git a/src/linux/minidump_writer/handle_data_stream.rs b/src/linux/minidump_writer/handle_data_stream.rs index 80e2655b..b715bd11 100644 --- a/src/linux/minidump_writer/handle_data_stream.rs +++ b/src/linux/minidump_writer/handle_data_stream.rs @@ -1,6 +1,6 @@ use { super::*, - crate::mem_writer::MemoryWriter, + crate::{linux::process_inspection, mem_writer::MemoryWriter}, std::{ ffi::OsStr, mem, @@ -47,6 +47,8 @@ pub enum SectionHandleDataStreamError { #[serde(serialize_with = "serialize_io_error")] std::io::Error, ), + #[error("failed to read /proc//fd directory")] + ReadDirFailed(#[source] process_inspection::Error), #[error("Failed to write to memory")] MemoryWriterError(#[from] MemoryWriterError), #[error("Failed integer conversion")] @@ -63,7 +65,10 @@ impl MinidumpWriter { buffer: &mut DumpBuf, ) -> Result { let proc_fd_path = PathBuf::from(format!("/proc/{}/fd", self.process_id)); - let proc_fd_iter = self.process_inspector.read_dir(&proc_fd_path)?; + let proc_fd_iter = self + .process_inspector + .read_dir(&proc_fd_path) + .map_err(SectionHandleDataStreamError::ReadDirFailed)?; let descriptors: Vec<_> = proc_fd_iter .filter_map(|filename| filename.ok()) .filter_map(|filename| { diff --git a/src/linux/minidump_writer/memory_info_list_stream.rs b/src/linux/minidump_writer/memory_info_list_stream.rs index 94cf1c69..63fc56a0 100644 --- a/src/linux/minidump_writer/memory_info_list_stream.rs +++ b/src/linux/minidump_writer/memory_info_list_stream.rs @@ -9,11 +9,7 @@ pub enum SectionMemInfoListError { #[error("Failed to write to memory")] MemoryWriterError(#[from] MemoryWriterError), #[error("failed to open /proc//maps file")] - ReadFileFailed( - #[source] - #[serde(serialize_with = "serialize_io_error")] - std::io::Error, - ), + ReadFileFailed(#[source] process_inspection::Error), #[error("Failed to read from procfs")] ProcfsError( #[from] diff --git a/src/linux/minidump_writer/mod.rs b/src/linux/minidump_writer/mod.rs index 64ad7b4c..8cbd497b 100644 --- a/src/linux/minidump_writer/mod.rs +++ b/src/linux/minidump_writer/mod.rs @@ -7,7 +7,7 @@ use { dso_debug, dumper_cpu_info::CpuInfoError, maps_reader::{MappingInfo, MappingList, MapsReaderError}, - process_inspection::{ProcessInspector, process_reader::CopyFromProcessError}, + process_inspection::{self, ProcessInspector, process_reader::CopyFromProcessError}, serializers::*, thread_info::{ThreadInfo, ThreadInfoError}, }, @@ -349,7 +349,8 @@ impl MinidumpWriter { let dirent = self.write_mappings(buffer)?; dir_section.write_to_file(buffer, Some(dirent))?; - self.write_app_memory(buffer)?; + self.write_app_memory(buffer) + .map_err(WriterError::SectionAppMemoryError)?; dir_section.write_to_file(buffer, None)?; let dirent = self.write_memory_list_stream(buffer)?; @@ -666,20 +667,20 @@ impl MinidumpWriter { } }; + if failspot!(ThreadName) { + self.process_inspector.fail_one_syscall_with(libc::EPERM); + } + // Read the thread-name (if there is any) - let name_result = failspot!(if ThreadName { - Err(std::io::Error::other( - "testing requested failure reading thread name", - )) - } else { - self.process_inspector - .read_file(format!("/proc/{pid}/task/{tid}/comm")) - .and_then(|mut file| { - let mut s = String::new(); - file.read_to_string(&mut s)?; - Ok(s) - }) - }); + let name_result = self + .process_inspector + .read_file(format!("/proc/{pid}/task/{tid}/comm")) + .map_err(std::io::Error::other) + .and_then(|mut file| { + let mut s = String::new(); + file.read_to_string(&mut s)?; + Ok(s) + }); let name = match name_result { Ok(name) => Some(name.trim_end().to_string()), @@ -948,21 +949,12 @@ impl MinidumpWriter { #[inline] pub fn copy_from_process( process_inspector: &ProcessInspector, - pid: Pid, + _pid: Pid, src: usize, length: usize, ) -> Result, CopyFromProcessError> { - let length = std::num::NonZeroUsize::new(length).ok_or(CopyFromProcessError { - src, - child: pid, - offset: 0, - length, - // TODO: We should make copy_from_process also take a NonZero, - // as EINVAL could also come from the syscalls that actually read - // memory as well which could be confusing - source: nix::errno::Errno::EINVAL, - })?; - + let length = + std::num::NonZeroUsize::new(length).ok_or(CopyFromProcessError::InvalidArgument)?; let mem = process_inspector.process_reader(); mem.read_to_vec(src, length) } @@ -982,11 +974,14 @@ fn write_file( buffer: &mut DumpBuf, filename: &str, ) -> std::result::Result { - let content = process_inspector.read_file(filename).and_then(|mut file| { - let mut v = Vec::new(); - file.read_to_end(&mut v)?; - Ok(v) - })?; + let content = process_inspector + .read_file(filename) + .map_err(std::io::Error::other) + .and_then(|mut file| { + let mut v = Vec::new(); + file.read_to_end(&mut v)?; + Ok(v) + })?; let section = MemoryArrayWriter::write_bytes(buffer, &content); Ok(section.location()) diff --git a/src/linux/minidump_writer/thread_list_stream.rs b/src/linux/minidump_writer/thread_list_stream.rs index 3b44df12..a2702355 100644 --- a/src/linux/minidump_writer/thread_list_stream.rs +++ b/src/linux/minidump_writer/thread_list_stream.rs @@ -33,7 +33,7 @@ pub enum SectionThreadListError { std::num::TryFromIntError, ), #[error("Failed to copy memory from process")] - CopyFromProcessError(#[from] CopyFromProcessError), + CopyFromProcessError(#[source] CopyFromProcessError), #[error("Failed to get thread info")] ThreadInfoError(#[from] ThreadInfoError), #[error("Failed to write to memory buffer")] @@ -141,7 +141,8 @@ impl MinidumpWriter { thread.thread_id as i32, ip_memory_d.start_of_memory_range as _, ip_memory_d.memory.data_size as usize, - )?; + ) + .map_err(SectionThreadListError::CopyFromProcessError)?; let mem_section = MemoryArrayWriter::alloc_from_array(buffer, &memory_copy)?; ip_memory_d.memory = mem_section.location(); @@ -216,7 +217,8 @@ impl MinidumpWriter { thread.thread_id.try_into()?, valid_stack_ptr, stack_len, - )?; + ) + .map_err(SectionThreadListError::CopyFromProcessError)?; let stack_pointer_offset = stack_ptr.saturating_sub(valid_stack_ptr); if self.skip_stacks_if_mapping_unreferenced { if let Some(principal_mapping) = &self.principal_mapping { diff --git a/src/linux/mod.rs b/src/linux/mod.rs index 301dc754..1e3293f1 100644 --- a/src/linux/mod.rs +++ b/src/linux/mod.rs @@ -1,7 +1,10 @@ // `WriterError` is large and clippy doesn't like that, but not a huge deal atm #![allow(clippy::result_large_err)] -pub use {maps_reader::LINUX_GATE_LIBRARY_NAME, process_inspection::process_reader}; +pub use { + maps_reader::LINUX_GATE_LIBRARY_NAME, + process_inspection::{Error as BackendError, process_reader}, +}; pub mod app_memory; pub mod crash_context; diff --git a/src/linux/module_reader.rs b/src/linux/module_reader.rs index 84148127..58452846 100644 --- a/src/linux/module_reader.rs +++ b/src/linux/module_reader.rs @@ -1,5 +1,8 @@ use { - super::{process_inspection::ProcessInspector, serializers::*}, + super::{ + process_inspection::{self, ProcessInspector}, + serializers::*, + }, crate::module_reader::{ModuleMemoryReadError, ProcessModuleMemoryReader}, crate::{minidump_format::GUID, serializers::*}, goblin::{ @@ -19,6 +22,8 @@ const NOTE_SECTION_NAME: &[u8] = b".note.gnu.build-id\0"; #[derive(Debug, thiserror::Error, serde::Serialize)] pub enum ModuleReaderError { + #[error("failed to map modile into memory")] + MapModuleFailed(#[source] process_inspection::Error), #[error("failed to read module file ({path}): {error}")] MapFile { path: std::path::PathBuf, @@ -158,7 +163,9 @@ pub fn read_build_id_from_file( process_inspector: &ProcessInspector, path: &Path, ) -> Result, Error> { - let module_memory_reader = process_inspector.read_memory_mapped_module(path, 0)?; + let module_memory_reader = process_inspector + .map_module_into_memory(path, 0) + .map_err(Error::MapModuleFailed)?; read_build_id_from_module(module_memory_reader) } @@ -198,9 +205,13 @@ pub fn read_soname_from_file( return Err(Error::NotSafeToOpenMapping(path.as_os_str().to_os_string())); } - let module_memory_reader = process_inspector.read_memory_mapped_module(path, offset)?; + let module_memory_reader = process_inspector + .map_module_into_memory(path, offset) + .map_err(Error::MapModuleFailed)?; + + let memory_len = module_memory_reader.len().map_err(Error::MapModuleFailed)?; - if module_memory_reader.is_empty() || module_memory_reader.len() < elf::header::SELFMAG { + if memory_len < elf::header::SELFMAG { return Err(Error::MmapSanityCheckFailed); } diff --git a/src/linux/process_inspection/mod.rs b/src/linux/process_inspection/mod.rs index a843f373..c74ea597 100644 --- a/src/linux/process_inspection/mod.rs +++ b/src/linux/process_inspection/mod.rs @@ -1,41 +1,22 @@ use { - super::{ - Pid, maps_reader, - module_reader::{ModuleReaderError, ReadModuleMemory}, - serializers::{self, *}, - }, - crate::serializers::*, - core::{ffi::c_void, mem}, + crate::module_reader::{ModuleMemoryReadError, ReadError, ReadModuleMemory}, + core::ffi::c_int, failspot::failspot, - module_reader::MappedModuleMemoryReader, - nix::{ - errno::Errno, - sys::{ptrace, signal, wait}, - unistd::Pid as NixPid, - }, - process_backend::local, + process_backend::{MAX_PATH_LEN, local, regs::*}, process_reader::ProcessReader, - regs::*, std::{ + borrow::Cow, ffi::{CString, OsString}, - fs::{self, File}, - io::{self, Read}, + io, os::unix::ffi::OsStringExt, - path::{Path, PathBuf}, + path::PathBuf, rc::Rc, }, }; -pub mod process_reader; -pub mod regs; - -mod module_reader; - -#[cfg(target_env = "gnu")] -type PtraceRequestType = core::ffi::c_uint; +pub use process_backend::regs; -#[cfg(not(target_env = "gnu"))] -type PtraceRequestType = core::ffi::c_int; +pub mod process_reader; #[derive(Debug)] pub struct ProcessInspector { @@ -55,15 +36,13 @@ impl ProcessInspector { ProcessInspector { pid, - process_reader: ProcessReader::new(pid), + process_reader: ProcessReader::new(pid, Rc::clone(&backend)), backend, } } - pub fn process_reader(&self) -> &ProcessReader { &self.process_reader } - pub fn stop_process(&self) -> Result<(), Error> { failspot!(if StopProcess { return Err(Error::Local(local::Error::SigStopFailed(libc::EPERM))); @@ -80,90 +59,89 @@ impl ProcessInspector { } } - pub fn suspend_thread(&self, tid: libc::pid_t) -> Result<(), SuspendResumeThreadError> { - let tid = NixPid::from_raw(tid); - ptrace::attach(tid).map_err(SuspendResumeThreadError::PtraceAttachFailed)?; - loop { - match wait::waitpid(tid, Some(wait::WaitPidFlag::__WALL)) { - Ok(status) => { - let wait::WaitStatus::Stopped(_, signal) = status else { - return Err(SuspendResumeThreadError::UnexpectedStatus(status)); - }; - - // Any signal will stop the thread, make sure it is SIGSTOP. Otherwise, this - // signal will be delivered after PTRACE_DETACH, and the thread will enter - // the "T (stopped)" state. - if signal == signal::SIGSTOP { - break; - } - - // Signals other than SIGSTOP that are received need to be reinjected, - // or they will otherwise get lost. - ptrace::cont(tid, signal) - .map_err(|e| SuspendResumeThreadError::ReinjectFailed(e, signal))?; - } - Err(Errno::EINTR) => (), - Err(e) => { - ptrace_detach(tid).map_err(SuspendResumeThreadError::PtraceDetachFailed)?; - return Err(SuspendResumeThreadError::WaitPidFailed(e)); - } - } + pub fn suspend_thread(&self, tid: libc::pid_t) -> Result<(), Error> { + match &*self.backend { + Backend::Local(l) => l.suspend_thread(tid).map_err(Error::Local), } - Ok(()) } - pub fn resume_thread(&self, tid: libc::pid_t) -> Result<(), SuspendResumeThreadError> { - let tid = NixPid::from_raw(tid); - ptrace_detach(tid).map_err(SuspendResumeThreadError::PtraceDetachFailed) + pub fn resume_thread(&self, tid: libc::pid_t) -> Result<(), Error> { + match &*self.backend { + Backend::Local(l) => l.resume_thread(tid).map_err(Error::Local), + } } - pub fn read_memory_mapped_module( + pub fn map_module_into_memory( &self, - path: impl AsRef, + path: impl Into, offset: u64, - ) -> Result { - MappedModuleMemoryReader::new(path.as_ref(), offset) + ) -> Result { + let c_path = CString::new(path.into().into_os_string().into_vec()).unwrap(); + match &*self.backend { + Backend::Local(l) => l + .map_module_into_memory(&c_path, offset) + .map(MappedModuleMemoryReader::Local) + .map_err(Error::Local), + } } - pub fn stat_file(&self, path: impl Into) -> io::Result { + pub fn stat_file(&self, path: impl Into) -> Result { let c_path = CString::new(path.into().into_os_string().into_vec()).unwrap(); - - let mut output = unsafe { mem::zeroed::() }; - let rv = unsafe { libc::stat(c_path.as_ptr(), &mut output) }; - if rv == -1 { - return Err(io::Error::last_os_error()); + match &*self.backend { + Backend::Local(l) => l.stat_file(&c_path).map_err(Error::Local), } - Ok(output) } - pub fn read_file(&self, path: impl AsRef) -> io::Result { - File::open(path).map(FileReader) + pub fn read_file(&self, path: impl Into) -> Result { + let c_path = CString::new(path.into().into_os_string().into_vec()).unwrap(); + match &*self.backend { + Backend::Local(l) => l + .read_file(&c_path) + .map(FileReader::Local) + .map_err(Error::Local), + } } - pub fn read_dir(&self, path: impl AsRef) -> io::Result { - fs::read_dir(path).map(DirReader) + pub fn read_dir(&self, path: impl Into) -> Result { + let c_path = CString::new(path.into().into_os_string().into_vec()).unwrap(); + match &*self.backend { + Backend::Local(l) => l + .read_dir(&c_path) + .map(DirReader::Local) + .map_err(Error::Local), + } } - pub fn read_link(&self, path: impl AsRef) -> io::Result { - fs::read_link(path) - } + pub fn read_link(&self, path: impl Into) -> Result { + let c_path = CString::new(path.into().into_os_string().into_vec()).unwrap(); + + let mut buf = vec![0u8; MAX_PATH_LEN]; + + let len = match &*self.backend { + Backend::Local(l) => l.read_link(&c_path, &mut buf).map_err(Error::Local)?, + }; - pub fn path_exists(&self, path: impl AsRef) -> bool { - path.as_ref().exists() + buf.truncate(len); + Ok(PathBuf::from(OsString::from_vec(buf))) } - pub fn get_gen_regs(&self, tid: libc::pid_t) -> nix::Result { - getregset(tid).or_else(|_| getregs(tid)) + pub fn get_gen_regs(&self, tid: libc::pid_t) -> Result { + match &*self.backend { + Backend::Local(l) => l.get_gen_regs(tid).map_err(Error::Local), + } } - pub fn get_fp_regs(&self, tid: libc::pid_t) -> nix::Result { - getfpregset(tid).or_else(|_| getfpregs(tid)) + pub fn get_fp_regs(&self, tid: libc::pid_t) -> Result { + match &*self.backend { + Backend::Local(l) => l.get_fp_regs(tid).map_err(Error::Local), + } } #[cfg(target_arch = "x86")] - pub fn get_fpx_regs(&self, tid: libc::pid_t) -> nix::Result { - const PTRACE_GETFPXREGS: PtraceRequestType = 18; - unsafe { ptrace_getregs::(PTRACE_GETFPXREGS, tid) } + pub fn get_fpx_regs(&self, tid: libc::pid_t) -> Result { + match &*self.backend { + Backend::Local(l) => l.get_fpx_regs(tid).map_err(Error::Local), + } } #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] @@ -171,174 +149,118 @@ impl ProcessInspector { &self, pid: libc::pid_t, addr: usize, - ) -> nix::Result<[u8; mem::size_of::()]> { - // Since ptrace() is vararg, best to explicitly state arg types - let addr: *mut libc::c_void = addr as *mut libc::c_void; - let data: *mut libc::c_void = core::ptr::null_mut(); - Errno::set_raw(0); - let rv = unsafe { libc::ptrace(libc::PTRACE_PEEKUSER, pid, addr, data) }; - if rv == -1 && Errno::last_raw() != 0 { - Err(Errno::last()) - } else { - Ok(rv.to_ne_bytes()) + ) -> Result<[u8; core::mem::size_of::()], Error> { + match &*self.backend { + Backend::Local(l) => l.ptrace_peekuser(pid, addr).map_err(Error::Local), } } } #[derive(Debug)] -pub struct FileReader(File); +pub enum FileReader { + Local(local::FileReader), +} -impl Read for FileReader { +impl FileReader { + fn read_at(&self, buf: &mut [u8], offset: u64) -> Result { + match self { + Self::Local(l) => l.read_at(buf, offset).map_err(Error::Local), + } + } +} + +impl io::Read for FileReader { fn read(&mut self, buf: &mut [u8]) -> io::Result { - self.0.read(buf) + match self { + Self::Local(l) => l.read(buf).map_err(Error::Local), + } + .map_err(io::Error::other) } } #[derive(Debug)] -pub struct DirReader(fs::ReadDir); +pub enum DirReader { + Local(local::DirReader), +} impl Iterator for DirReader { - type Item = io::Result; + type Item = Result; fn next(&mut self) -> Option { - self.0 - .next() - .map(|result| result.map(|entry| entry.file_name())) + match self { + Self::Local(l) => match l.read_name().map_err(Error::Local) { + Ok(Some(name_bytes)) => Some(Ok(OsString::from_vec(name_bytes.to_vec()))), + Ok(None) => None, + Err(e) => Some(Err(e)), + }, + } } } -#[derive(Debug, thiserror::Error, serde::Serialize)] -pub enum SuspendResumeThreadError { - #[error("failed to attach to process")] - PtraceAttachFailed( - #[source] - #[serde(serialize_with = "serialize_nix_error")] - Errno, - ), - #[error("failed to detach from process")] - PtraceDetachFailed( - #[source] - #[serde(serialize_with = "serialize_nix_error")] - Errno, - ), - #[error("received an unexpected status: {0:?}")] - UnexpectedStatus(#[serde(serialize_with = "serialize_debug_string")] wait::WaitStatus), - #[error("failed to reinject irrelevant signal: {1:?}")] - ReinjectFailed( - #[source] - #[serde(serialize_with = "serialize_nix_error")] - Errno, - #[serde(serialize_with = "serialize_debug_string")] signal::Signal, - ), - #[error("failed waiting for process state to change")] - WaitPidFailed( - #[source] - #[serde(serialize_with = "serialize_nix_error")] - Errno, - ), -} - -fn getregset(_pid: libc::pid_t) -> nix::Result { - #[cfg(target_arch = "arm")] - { - Err(Errno::ENOTSUP) - } - #[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))] - { - const NT_PRSTATUS: usize = 1; - ptrace_getregset(NT_PRSTATUS, _pid) +#[doc(hidden)] +impl ProcessInspector { + pub fn force_pr_reset(&mut self) { + self.process_reader = ProcessReader::new(self.pid, Rc::clone(&self.backend)) } -} - -fn getregs(pid: libc::pid_t) -> nix::Result { - const PTRACE_GETREGS: PtraceRequestType = 12; - unsafe { ptrace_getregs::(PTRACE_GETREGS, pid) } -} - -fn getfpregset(pid: libc::pid_t) -> nix::Result { - #[cfg(target_arch = "arm")] - { - const NT_ARM_VFP: usize = 0x400; - ptrace_getregset(NT_ARM_VFP, pid) + pub fn force_pr_virtual_mem(&mut self) { + self.process_reader = ProcessReader::for_virtual_mem(self.pid, Rc::clone(&self.backend)) } - #[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))] - { - const NT_PRFPREGSET: usize = 2; - ptrace_getregset(NT_PRFPREGSET, pid) + pub fn force_pr_file(&mut self) -> Result<(), Error> { + self.process_reader = ProcessReader::for_file(self.pid, Rc::clone(&self.backend))?; + Ok(()) } -} - -fn getfpregs(_pid: libc::pid_t) -> nix::Result { - #[cfg(target_arch = "arm")] - { - Err(Errno::ENOTSUP) + pub fn force_pr_ptrace(&mut self) { + self.process_reader = ProcessReader::for_ptrace(self.pid, Rc::clone(&self.backend)); } - #[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))] - { - const PTRACE_GETFPREGS: PtraceRequestType = 14; - unsafe { ptrace_getregs::(PTRACE_GETFPREGS, _pid) } + pub fn fail_one_syscall_with(&self, errno: c_int) { + match &*self.backend { + Backend::Local(l) => l.fail_one_syscall_with(errno), + } } } -/// Safety: RequestType and T must agree on the size of the returned type -unsafe fn ptrace_getregs(request: PtraceRequestType, pid: libc::pid_t) -> nix::Result { - let mut output = mem::MaybeUninit::::uninit(); - - // Since ptrace() is vararg, best to explicitly state arg types - let addr: *mut c_void = core::ptr::null_mut(); - let data: *mut c_void = output.as_mut_ptr().cast(); - let res = unsafe { libc::ptrace(request, pid, addr, data) }; - Errno::result(res)?; - Ok(unsafe { output.assume_init() }) +#[derive(Debug)] +pub enum MappedModuleMemoryReader { + Local(local::MappedModuleMemoryReader), } -fn ptrace_getregset(regset_type: usize, pid: libc::pid_t) -> nix::Result { - let mut output = mem::MaybeUninit::::uninit(); - let mut io = libc::iovec { - iov_base: output.as_mut_ptr().cast(), - iov_len: mem::size_of::(), - }; - - // Since ptrace() is vararg, best to explicitly state arg types - let addr: *mut c_void = regset_type as *mut c_void; - let data: *mut c_void = (&raw mut io).cast(); - let res = unsafe { libc::ptrace(libc::PTRACE_GETREGSET, pid, addr, data) }; - Errno::result(res)?; - - // PTRACE_GETREGSET returns the number of bytes actually read in iov_len. Need to ensure - // all bytes of T are actually initialized - if io.iov_len != mem::size_of::() { - return Err(Errno::EINVAL); +impl MappedModuleMemoryReader { + pub fn read(&self, offset: u64, length: u64) -> Result<&[u8], Error> { + match self { + Self::Local(l) => l.read(offset, length).map_err(Error::Local), + } } - - Ok(unsafe { output.assume_init() }) -} - -fn ptrace_detach(tid: NixPid) -> Result<(), Errno> { - ptrace::detach(tid, None).or_else(|e| { - // errno is set to ESRCH if the pid no longer exists, but we don't want to error in that - // case. - if e == nix::Error::ESRCH { - Ok(()) - } else { - Err(e) + pub fn len(&self) -> Result { + match self { + Self::Local(l) => l.len().map_err(Error::Local), } - }) + } + pub fn is_empty(&self) -> Result { + match self { + Self::Local(l) => l.is_empty().map_err(Error::Local), + } + } } -#[doc(hidden)] -impl ProcessInspector { - pub fn force_pr_reset(&mut self) { - self.process_reader = ProcessReader::new(self.pid) +impl ReadModuleMemory for MappedModuleMemoryReader { + fn read(&self, offset: u64, length: u64) -> Result, ModuleMemoryReadError> { + self.read(offset, length) + .map(Cow::Borrowed) + .map_err(|e| ModuleMemoryReadError { + offset, + length, + start_address: None, + error: ReadError::PlatformSpecific(e), + }) } - pub fn force_pr_virtual_mem(&mut self) { - self.process_reader = ProcessReader::for_virtual_mem(self.pid) + fn absolute_to_relative(&self, addr: u64) -> Option { + Some(addr) } - pub fn force_pr_file(&mut self) -> std::io::Result<()> { - self.process_reader = ProcessReader::for_file(self.pid)?; - Ok(()) + /// Calculates the absolute address of the specified relative address + fn relative_to_absolute(&self, addr: u64) -> Option { + Some(addr) } - pub fn force_pr_ptrace(&mut self) { - self.process_reader = ProcessReader::for_ptrace(self.pid); + fn is_process_memory(&self) -> bool { + false } } diff --git a/src/linux/process_inspection/module_reader.rs b/src/linux/process_inspection/module_reader.rs deleted file mode 100644 index eea277c4..00000000 --- a/src/linux/process_inspection/module_reader.rs +++ /dev/null @@ -1,64 +0,0 @@ -use { - super::{ModuleReaderError as Error, ReadModuleMemory}, - crate::module_reader::ModuleMemoryReadError, - memmap2::{Mmap, MmapOptions}, - std::{borrow::Cow, fs::File, path::Path}, -}; - -pub struct MappedModuleMemoryReader(Mmap); - -impl MappedModuleMemoryReader { - pub fn new(path: &Path, offset: u64) -> Result { - File::open(path) - .map_err(Error::FileError) - .and_then(|file| unsafe { - MmapOptions::new() - .offset(offset) - .map(&file) - .map_err(Error::MapError) - }) - .map(Self) - } - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } - pub fn len(&self) -> usize { - self.0.len() - } -} - -impl ReadModuleMemory for MappedModuleMemoryReader { - fn read<'a>( - &'a self, - offset: u64, - length: u64, - ) -> Result, ModuleMemoryReadError> { - let inner = || { - use crate::module_reader::ReadError as E; - let offset = usize::try_from(offset).map_err(|_| E::Overflow)?; - let length = usize::try_from(length).map_err(|_| E::Overflow)?; - let end = offset.checked_add(length).ok_or(E::Overflow)?; - self.0 - .get(offset..end) - .map(Cow::Borrowed) - .ok_or(E::OutOfBounds) - }; - - inner().map_err(|error| ModuleMemoryReadError { - start_address: None, - offset, - length, - error, - }) - } - fn absolute_to_relative(&self, addr: u64) -> Option { - Some(addr) - } - /// Calculates the absolute address of the specified relative address - fn relative_to_absolute(&self, addr: u64) -> Option { - Some(addr) - } - fn is_process_memory(&self) -> bool { - false - } -} diff --git a/src/linux/process_inspection/process_reader.rs b/src/linux/process_inspection/process_reader.rs index 6e328582..a61e8376 100644 --- a/src/linux/process_inspection/process_reader.rs +++ b/src/linux/process_inspection/process_reader.rs @@ -1,6 +1,7 @@ use { - super::{Pid, serializers::*}, - std::sync::OnceLock, + super::{Backend, Error, FileReader}, + core::{ffi::c_long, mem}, + std::{ffi::CString, rc::Rc, sync::OnceLock}, }; #[derive(Debug)] @@ -15,7 +16,7 @@ enum Style { /// /// Available on basically all versions of Linux, but could fail if the process /// has insufficient privileges, ie ptrace - File(std::fs::File), + File(FileReader), /// Reads the memory with [ptrace (`PTRACE_PEEKDATA`)](https://man7.org/linux/man-pages/man2/ptrace.2.html) /// /// Reads data one word at a time, so slow, but fairly reliable, as long as @@ -23,36 +24,44 @@ enum Style { Ptrace, /// No methods succeeded, generally there isn't a case where failing a syscall /// will work if called again - Unavailable { - vmem: nix::Error, - file: nix::Error, - ptrace: nix::Error, - }, + Unavailable, } #[derive(Debug, thiserror::Error, serde::Serialize)] -#[error("Copy from process {child} failed (source {src}, offset: {offset}, length: {length})")] -pub struct CopyFromProcessError { - pub child: Pid, - pub src: usize, - pub offset: usize, - pub length: usize, - #[serde(serialize_with = "serialize_nix_error")] - pub source: nix::Error, +pub enum CopyFromProcessError { + #[error("Copy from process {child} failed (source {src}, offset: {offset}, length: {length})")] + StrategyFailed { + child: libc::pid_t, + src: usize, + offset: usize, + length: usize, + source: StrategyError, + }, + #[error("all strategies for reading a process failed")] + AllStrategiesFailed { + vmem: StrategyError, + file: StrategyError, + ptrace: (StrategyError, usize), + }, + #[error("process reading is unavailable")] + Unavailable, + #[error("an invalid argument was passed")] + InvalidArgument, } #[derive(Debug, thiserror::Error, serde::Serialize)] -pub enum FindModuleError { - #[error("Module not found")] - ModuleNotFound, - #[error("Failed to read process module mappings")] - MappingError(#[from] super::maps_reader::MapsReaderError), +pub enum StrategyError { + #[error(transparent)] + Backend(Error), + #[error("an unexpected end of file was reached")] + UnexpectedEof, } pub struct ProcessReader { /// The pid of the child to read - pid: nix::unistd::Pid, + pid: libc::pid_t, style: OnceLock