From 3de6aeb0343051f53fbf4efeeaa915232913e6a8 Mon Sep 17 00:00:00 2001 From: Jake Shadle Date: Mon, 12 Aug 2024 10:04:36 +0200 Subject: [PATCH] Fixup rebase --- src/bin/test.rs | 2 +- src/linux/auxv/mod.rs | 2 +- src/linux/errors.rs | 4 +- src/linux/maps_reader.rs | 2 +- src/linux/mem_reader.rs | 2 +- src/linux/module_reader.rs | 234 ++++++++++++++++----------------- src/linux/ptrace_dumper.rs | 26 ++-- src/linux/sections/mappings.rs | 2 +- tests/linux_minidump_writer.rs | 2 +- 9 files changed, 138 insertions(+), 138 deletions(-) diff --git a/src/bin/test.rs b/src/bin/test.rs index aeb13ef0..e8dbb412 100644 --- a/src/bin/test.rs +++ b/src/bin/test.rs @@ -139,7 +139,7 @@ mod linux { found_linux_gate = true; dumper.suspend_threads()?; let module_reader::BuildId(id) = - dumper.from_process_memory_for_mapping(&mapping)?; + PtraceDumper::from_process_memory_for_mapping(&mapping, ppid)?; test!(!id.is_empty(), "id-vec is empty")?; test!(id.iter().any(|&x| x > 0), "all id elements are 0")?; dumper.resume_threads()?; diff --git a/src/linux/auxv/mod.rs b/src/linux/auxv/mod.rs index c8ee248a..403ab114 100644 --- a/src/linux/auxv/mod.rs +++ b/src/linux/auxv/mod.rs @@ -1,6 +1,6 @@ pub use reader::ProcfsAuxvIter; use { - crate::linux::thread_info::Pid, + crate::Pid, std::{fs::File, io::BufReader}, thiserror::Error, }; diff --git a/src/linux/errors.rs b/src/linux/errors.rs index e8f652a3..f8a19cc8 100644 --- a/src/linux/errors.rs +++ b/src/linux/errors.rs @@ -9,7 +9,7 @@ use thiserror::Error; #[derive(Debug, Error)] pub enum InitError { #[error("failed to read auxv")] - ReadAuxvFailed(AuxvError), + ReadAuxvFailed(crate::auxv::AuxvError), #[error("IO error for file {0}")] IOError(String, #[source] std::io::Error), #[error("crash thread does not reference principal mapping")] @@ -18,6 +18,8 @@ pub enum InitError { AndroidLateInitError(#[from] AndroidError), #[error("Failed to read the page size")] PageSizeError(#[from] Errno), + #[error("Ptrace does not function within the same process")] + CannotPtraceSameProcess, } #[derive(Error, Debug)] diff --git a/src/linux/maps_reader.rs b/src/linux/maps_reader.rs index 8b89e873..e023a21a 100644 --- a/src/linux/maps_reader.rs +++ b/src/linux/maps_reader.rs @@ -259,7 +259,7 @@ impl MappingInfo { use super::module_reader::{ReadFromModule, SoName}; let mapped_file = MappingInfo::get_mmap(&self.name, self.offset)?; - Ok(SoName::read_from_module(&*mapped_file) + Ok(SoName::read_from_module((&*mapped_file).into()) .map_err(|e| MapsReaderError::NoSoName(self.name.clone().unwrap_or_default(), e))? .0 .to_string()) diff --git a/src/linux/mem_reader.rs b/src/linux/mem_reader.rs index e3f9cdc1..8691d51c 100644 --- a/src/linux/mem_reader.rs +++ b/src/linux/mem_reader.rs @@ -12,7 +12,7 @@ enum Style { /// Reads the memory from `/proc//mem` /// /// Available on basically all versions of Linux, but could fail if the process - /// has insufficient priveleges, ie ptrace + /// has insufficient privileges, ie ptrace File(std::fs::File), /// Reads the memory with [ptrace (`PTRACE_PEEKDATA`)](https://man7.org/linux/man-pages/man2/ptrace.2.html) /// diff --git a/src/linux/module_reader.rs b/src/linux/module_reader.rs index a3c9a9ac..03adc9b1 100644 --- a/src/linux/module_reader.rs +++ b/src/linux/module_reader.rs @@ -1,71 +1,87 @@ use crate::errors::ModuleReaderError as Error; +use crate::mem_reader::MemReader; use crate::minidump_format::GUID; use goblin::{ container::{Container, Ctx, Endian}, elf, }; -use std::ffi::CStr; +use std::{borrow::Cow, ffi::CStr}; -const NOTE_SECTION_NAME: &[u8] = b".note.gnu.build-id\0"; +type Buf<'buf> = Cow<'buf, [u8]>; -pub trait ModuleMemory { - type Memory: std::ops::Deref; +const NOTE_SECTION_NAME: &[u8] = b".note.gnu.build-id\0"; - /// Read memory from the module. - fn read_module_memory(&self, offset: u64, length: u64) -> std::io::Result; +pub struct ProcessReader { + inner: MemReader, + start_address: u64, +} - /// The base address of the module in memory, if loaded in the address space of a program. - /// The default implementation returns None. - fn base_address(&self) -> Option { - None +impl ProcessReader { + pub fn new(pid: i32, start_address: usize) -> Self { + Self { + inner: MemReader::new(pid), + start_address: start_address as u64, + } } +} - /// Whether the module memory is from a module loaded in the address space of a program. - /// The default implementation assumes this to be true if a base address is provided. - fn is_loaded_in_program(&self) -> bool { - self.base_address().is_some() - } +pub enum ProcessMemory<'buf> { + Slice(&'buf [u8]), + Process(ProcessReader), } -impl<'a> ModuleMemory for &'a [u8] { - type Memory = Self; - - fn read_module_memory(&self, offset: u64, length: u64) -> std::io::Result { - self.get(offset as usize..(offset + length) as usize) - .ok_or_else(|| { - std::io::Error::new( - std::io::ErrorKind::UnexpectedEof, - format!("{} out of bounds", offset + length), - ) - }) +impl<'buf> From<&'buf [u8]> for ProcessMemory<'buf> { + fn from(value: &'buf [u8]) -> Self { + Self::Slice(value) } } -/// Indicate that a ModuleMemory implementation is read from the address space of a program with -/// the given base address. -pub struct ModuleMemoryAtAddress(pub T, pub u64); - -impl ModuleMemory for ModuleMemoryAtAddress { - type Memory = T::Memory; +impl<'buf> From for ProcessMemory<'buf> { + fn from(value: ProcessReader) -> Self { + Self::Process(value) + } +} - fn read_module_memory(&self, offset: u64, length: u64) -> std::io::Result { - self.0.read_module_memory(offset, length) +impl<'buf> ProcessMemory<'buf> { + #[inline] + fn read(&mut self, offset: u64, length: u64) -> Result, Error> { + match self { + Self::Process(pr) => pr + .inner + .read_to_vec((pr.start_address + offset) as _, length as _) + .map(Cow::Owned) + .map_err(|err| Error::ReadModuleMemory { + offset, + length, + error: err.source, + }), + Self::Slice(s) => s + .get(offset as usize..(offset + length) as usize) + .map(Cow::Borrowed) + .ok_or_else(|| Error::ReadModuleMemory { + offset, + length, + error: nix::Error::EACCES, + }), + } } - fn base_address(&self) -> Option { - Some(self.1) + /// Calculates the absolute address of the specified relative address + #[inline] + fn absolute(&self, addr: u64) -> u64 { + let Self::Process(pr) = self else { + return addr; + }; + addr.checked_sub(pr.start_address).unwrap_or(addr) } -} -fn read(mem: &T, offset: u64, length: u64) -> Result { - mem.read_module_memory(offset, length) - .map_err(|error| Error::ReadModuleMemory { - offset, - length, - error, - }) + #[inline] + fn is_process_memory(&self) -> bool { + matches!(self, Self::Process(_)) + } } +#[inline] fn is_executable_section(header: &elf::SectionHeader) -> bool { header.sh_type == elf::section_header::SHT_PROGBITS && header.sh_flags & u64::from(elf::section_header::SHF_ALLOC) != 0 @@ -92,12 +108,12 @@ fn build_id_from_bytes(data: &[u8]) -> Vec { } // `name` should be null-terminated -fn section_header_with_name<'a>( - section_headers: &'a elf::SectionHeaders, +fn section_header_with_name<'sc>( + section_headers: &'sc elf::SectionHeaders, strtab_index: usize, name: &[u8], - module_memory: &impl ModuleMemory, -) -> Result, Error> { + module_memory: &mut ProcessMemory<'_>, +) -> Result, Error> { let strtab_section_header = section_headers.get(strtab_index).ok_or(Error::NoStrTab)?; for header in section_headers { let sh_name = header.sh_name as u64; @@ -109,11 +125,7 @@ fn section_header_with_name<'a>( // This can't be a match. continue; } - let n = read( - module_memory, - strtab_section_header.sh_offset + sh_name, - name.len() as u64, - )?; + let n = module_memory.read(strtab_section_header.sh_offset + sh_name, name.len() as u64)?; if name == &*n { return Ok(Some(header)); } @@ -123,16 +135,15 @@ fn section_header_with_name<'a>( /// Types which can be read from an `impl ModuleMemory`. pub trait ReadFromModule: Sized { - fn read_from_module(module_memory: impl ModuleMemory) -> Result; + fn read_from_module(module_memory: ProcessMemory<'_>) -> Result; } /// The module build id. -#[derive(Default, Clone, Debug)] pub struct BuildId(pub Vec); impl ReadFromModule for BuildId { - fn read_from_module(module_memory: impl ModuleMemory) -> Result { - let reader = ModuleReader::new(module_memory)?; + fn read_from_module(module_memory: ProcessMemory<'_>) -> Result { + let mut reader = ModuleReader::new(module_memory)?; let program_headers = match reader.build_id_from_program_headers() { Ok(v) => return Ok(BuildId(v)), Err(e) => Box::new(e), @@ -191,8 +202,8 @@ impl<'a> Iterator for DynIter<'a> { pub struct SoName(pub String); impl ReadFromModule for SoName { - fn read_from_module(module_memory: impl ModuleMemory) -> Result { - let reader = ModuleReader::new(module_memory)?; + fn read_from_module(module_memory: ProcessMemory<'_>) -> Result { + let mut reader = ModuleReader::new(module_memory)?; let program_headers = match reader.soname_from_program_headers() { Ok(v) => return Ok(SoName(v)), Err(e) => Box::new(e), @@ -208,22 +219,23 @@ impl ReadFromModule for SoName { } } -pub struct ModuleReader { - module_memory: T, +pub struct ModuleReader<'buf> { + module_memory: ProcessMemory<'buf>, header: elf::Header, context: Ctx, } -impl ModuleReader { - pub fn new(module_memory: T) -> Result { +impl<'buf> ModuleReader<'buf> { + pub fn new(mut module_memory: ProcessMemory<'buf>) -> Result { // We could use `Ctx::default()` (which defaults to the native system), however to be extra // permissive we'll just use a 64-bit ("Big") context which would result in the largest // possible header size. let header_size = elf::Header::size(Ctx::new(Container::Big, Endian::default())); - let header_data = read(&module_memory, 0, header_size as u64)?; + let header_data = module_memory.read(0, header_size as u64)?; let header = elf::Elf::parse_header(&header_data)?; let context = Ctx::new(header.container()?, header.endianness()?); - Ok(ModuleReader { + + Ok(Self { module_memory, header, context, @@ -231,7 +243,7 @@ impl ModuleReader { } /// Read the SONAME using program headers to locate dynamic library information. - pub fn soname_from_program_headers(&self) -> Result { + pub fn soname_from_program_headers(&mut self) -> Result { let program_headers = self.read_program_headers()?; let dynamic_segment_header = program_headers @@ -239,12 +251,12 @@ impl ModuleReader { .find(|h| h.p_type == elf::program_header::PT_DYNAMIC) .ok_or(Error::NoDynamicSection)?; - let dynamic_section: &[u8] = &self.read_segment(dynamic_segment_header)?; + let dynamic_section = self.read_segment(dynamic_segment_header)?; let mut soname_strtab_offset = None; let mut strtab_addr = None; let mut strtab_size = None; - for dyn_ in DynIter::new(dynamic_section, self.context) { + for dyn_ in DynIter::new(&dynamic_section, self.context) { let dyn_ = dyn_?; match dyn_.d_tag { elf::dynamic::DT_SONAME => soname_strtab_offset = Some(dyn_.d_val), @@ -257,22 +269,15 @@ impl ModuleReader { match (strtab_addr, strtab_size, soname_strtab_offset) { (None, _, _) | (_, None, _) => Err(Error::NoDynStrSection), (_, _, None) => Err(Error::NoSoNameEntry), - (Some(mut addr), Some(size), Some(offset)) => { - if self.module_memory.is_loaded_in_program() { - if let Some(base) = self.module_memory.base_address() { - // If loaded in memory, the address will be altered to be absolute. - if let Some(r) = addr.checked_sub(base) { - addr = r; - } - } - } - self.read_name_from_strtab(addr, size, offset) + (Some(addr), Some(size), Some(offset)) => { + // If loaded in memory, the address will be altered to be absolute. + self.read_name_from_strtab(self.module_memory.absolute(addr), size, offset) } } } /// Read the SONAME using section headers to locate dynamic library information. - pub fn soname_from_sections(&self) -> Result { + pub fn soname_from_sections(&mut self) -> Result { let section_headers = self.read_section_headers()?; let dynamic_section_header = section_headers @@ -287,18 +292,17 @@ impl ModuleReader { §ion_headers, self.header.e_shstrndx as usize, b".dynstr\0", - &self.module_memory, + &mut self.module_memory, )? .ok_or(Error::NoDynStrSection)?, }; - let dynamic_section: &[u8] = &read( - &self.module_memory, + let dynamic_section = self.module_memory.read( self.section_offset(dynamic_section_header), dynamic_section_header.sh_size, )?; - for dyn_ in DynIter::new(dynamic_section, self.context) { + for dyn_ in DynIter::new(&dynamic_section, self.context) { let dyn_ = dyn_?; if dyn_.d_tag == elf::dynamic::DT_SONAME { let name_offset = dyn_.d_val; @@ -316,7 +320,7 @@ impl ModuleReader { } /// Read the build id from a program header note. - pub fn build_id_from_program_headers(&self) -> Result, Error> { + pub fn build_id_from_program_headers(&mut self) -> Result, Error> { let program_headers = self.read_program_headers()?; for header in program_headers { if header.p_type != elf::program_header::PT_NOTE { @@ -332,14 +336,14 @@ impl ModuleReader { } /// Read the build id from a notes section. - pub fn build_id_from_section(&self) -> Result, Error> { + pub fn build_id_from_section(&mut self) -> Result, Error> { let section_headers = self.read_section_headers()?; let header = section_header_with_name( §ion_headers, self.header.e_shstrndx as usize, NOTE_SECTION_NAME, - &self.module_memory, + &mut self.module_memory, )? .ok_or(Error::NoSectionNote)?; @@ -351,7 +355,7 @@ impl ModuleReader { } /// Generate a build id by hashing the first page of the text section. - pub fn build_id_generate_from_text(&self) -> Result, Error> { + pub fn build_id_generate_from_text(&mut self) -> Result, Error> { let Some(text_header) = self .read_section_headers()? .into_iter() @@ -362,50 +366,47 @@ impl ModuleReader { // Take at most one page of the text section (we assume page size is 4096 bytes). let len = std::cmp::min(4096, text_header.sh_size); - let text_data = read(&self.module_memory, text_header.sh_offset, len)?; + let text_data = self.module_memory.read(text_header.sh_offset, len)?; Ok(build_id_from_bytes(&text_data)) } - fn read_segment(&self, header: &elf::ProgramHeader) -> Result { - let (offset, size) = if self.module_memory.is_loaded_in_program() { + fn read_segment(&mut self, header: &elf::ProgramHeader) -> Result, Error> { + let (offset, size) = if self.module_memory.is_process_memory() { (header.p_vaddr, header.p_memsz) } else { (header.p_offset, header.p_filesz) }; - read(&self.module_memory, offset, size) + self.module_memory.read(offset, size) } fn read_name_from_strtab( - &self, + &mut self, strtab_offset: u64, strtab_size: u64, name_offset: u64, ) -> Result { - let name = read( - &self.module_memory, - strtab_offset + name_offset, - strtab_size - name_offset, - )?; + let name = self + .module_memory + .read(strtab_offset + name_offset, strtab_size - name_offset)?; return CStr::from_bytes_until_nul(&name) .map(|s| s.to_string_lossy().into_owned()) .map_err(|_| Error::StrTabNoNulByte); } fn section_offset(&self, header: &elf::SectionHeader) -> u64 { - if self.module_memory.is_loaded_in_program() { + if self.module_memory.is_process_memory() { header.sh_addr } else { header.sh_offset } } - fn read_program_headers(&self) -> Result { + fn read_program_headers(&mut self) -> Result { if self.header.e_phoff == 0 { return Err(Error::NoProgramHeaders); } - let program_headers_data = read( - &self.module_memory, + let program_headers_data = self.module_memory.read( self.header.e_phoff, self.header.e_phentsize as u64 * self.header.e_phnum as u64, )?; @@ -418,23 +419,18 @@ impl ModuleReader { Ok(program_headers) } - fn read_section_headers(&self) -> Result { + fn read_section_headers(&mut self) -> Result { if self.header.e_shoff == 0 { return Err(Error::NoSections); } - // FIXME Until a version following goblin 0.8.0 is published (with - // `SectionHeader::parse_from`), we read one extra byte preceding the sections so that - // `SectionHeader::parse` doesn't return immediately due to a 0 offset. - - let section_headers_data = read( - &self.module_memory, - self.header.e_shoff - 1, - self.header.e_shentsize as u64 * self.header.e_shnum as u64 + 1, + let section_headers_data = self.module_memory.read( + self.header.e_shoff, + self.header.e_shentsize as u64 * self.header.e_shnum as u64, )?; - let section_headers = elf::SectionHeader::parse( + let section_headers = elf::SectionHeader::parse_from( §ion_headers_data, - 1, + 0, self.header.e_shnum as usize, self.context, )?; @@ -442,12 +438,12 @@ impl ModuleReader { } fn find_build_id_note( - &self, + &mut self, offset: u64, size: u64, alignment: u64, ) -> Result>, Error> { - let notes = read(&self.module_memory, offset, size)?; + let notes = self.module_memory.read(offset, size)?; for note in (elf::note::NoteDataIterator { data: ¬es, // Note that `NoteDataIterator::size` is poorly named, it is actually an end offset. In @@ -543,7 +539,7 @@ mod test { #[test] fn build_id_program_headers() { - let reader = ModuleReader::new(TINY_ELF).unwrap(); + let mut reader = ModuleReader::new(TINY_ELF.into()).unwrap(); let id = reader.build_id_from_program_headers().unwrap(); assert_eq!( id, @@ -553,7 +549,7 @@ mod test { #[test] fn build_id_section() { - let reader = ModuleReader::new(TINY_ELF).unwrap(); + let mut reader = ModuleReader::new(TINY_ELF.into()).unwrap(); let id = reader.build_id_from_section().unwrap(); assert_eq!( id, @@ -563,7 +559,7 @@ mod test { #[test] fn build_id_text_hash() { - let reader = ModuleReader::new(TINY_ELF).unwrap(); + let mut reader = ModuleReader::new(TINY_ELF.into()).unwrap(); let id = reader.build_id_generate_from_text().unwrap(); assert_eq!( id, @@ -573,14 +569,14 @@ mod test { #[test] fn soname_program_headers() { - let reader = ModuleReader::new(TINY_ELF).unwrap(); + let mut reader = ModuleReader::new(TINY_ELF.into()).unwrap(); let soname = reader.soname_from_program_headers().unwrap(); assert_eq!(soname, "libfoo.so.1"); } #[test] fn soname_section() { - let reader = ModuleReader::new(TINY_ELF).unwrap(); + let mut reader = ModuleReader::new(TINY_ELF.into()).unwrap(); let soname = reader.soname_from_sections().unwrap(); assert_eq!(soname, "libfoo.so.1"); } diff --git a/src/linux/ptrace_dumper.rs b/src/linux/ptrace_dumper.rs index dec0f968..40bb1800 100644 --- a/src/linux/ptrace_dumper.rs +++ b/src/linux/ptrace_dumper.rs @@ -4,6 +4,7 @@ use crate::linux::{ auxv::AuxvDumpInfo, errors::{DumperError, InitError, ThreadInfoError}, maps_reader::MappingInfo, + module_reader, thread_info::ThreadInfo, Pid, }; @@ -18,8 +19,6 @@ use procfs_core::{ FromRead, ProcError, }; use std::{ - collections::HashMap, - io::BufReader, path, result::Result, time::{Duration, Instant}, @@ -88,9 +87,12 @@ fn ptrace_detach(child: Pid) -> Result<(), DumperError> { } impl PtraceDumper { - /// Constructs a dumper for extracting information of a given process - /// with a process ID of |pid|. - pub fn new(pid: Pid, stop_timeout: Duration) -> Result { + /// Constructs a dumper for extracting information from the specified process id + pub fn new(pid: Pid, stop_timeout: Duration, auxv: AuxvDumpInfo) -> Result { + if pid == std::process::id() as _ { + return Err(InitError::CannotPtraceSameProcess); + } + let mut dumper = Self { pid, threads_suspended: false, @@ -512,20 +514,20 @@ impl PtraceDumper { } pub fn from_process_memory_for_index( - &self, + &mut self, idx: usize, ) -> Result { assert!(idx < self.mappings.len()); - self.from_process_memory_for_mapping(&self.mappings[idx]) + Self::from_process_memory_for_mapping(&self.mappings[idx], self.pid) } - pub fn elf_identifier_for_mapping( - mapping: &mut MappingInfo, + pub fn from_process_memory_for_mapping( + mapping: &MappingInfo, pid: Pid, - ) -> Result, DumperError> { - Ok(build_id_reader::read_build_id( - build_id_reader::ProcessReader::new(pid, mapping.start_address), + ) -> Result { + Ok(T::read_from_module( + module_reader::ProcessReader::new(pid, mapping.start_address).into(), )?) } } diff --git a/src/linux/sections/mappings.rs b/src/linux/sections/mappings.rs index 2c45d1e4..c0172084 100644 --- a/src/linux/sections/mappings.rs +++ b/src/linux/sections/mappings.rs @@ -26,7 +26,7 @@ pub fn write( } let BuildId(identifier) = dumper .from_process_memory_for_index(map_idx) - .unwrap_or_default(); + .unwrap_or_else(|_| BuildId(Vec::new())); // If the identifier is all 0, its an uninteresting mapping (bmc#1676109) if identifier.is_empty() || identifier.iter().all(|&x| x == 0) { diff --git a/tests/linux_minidump_writer.rs b/tests/linux_minidump_writer.rs index dfe64a8a..7af91544 100644 --- a/tests/linux_minidump_writer.rs +++ b/tests/linux_minidump_writer.rs @@ -706,7 +706,7 @@ fn with_deleted_binary() { let pid = child.id() as i32; let BuildId(mut build_id) = - BuildId::read_from_module(mem_slice.as_slice()).expect("Failed to get build_id"); + BuildId::read_from_module(mem_slice.as_slice().into()).expect("Failed to get build_id"); std::fs::remove_file(&binary_copy).expect("Failed to remove binary");