diff --git a/src/bin/test.rs b/src/bin/test.rs index 7051ee16..3db0c3c8 100644 --- a/src/bin/test.rs +++ b/src/bin/test.rs @@ -12,7 +12,6 @@ mod linux { minidump_writer::{ LINUX_GATE_LIBRARY_NAME, minidump_writer::{MinidumpWriter, MinidumpWriterConfig}, - module_reader, }, nix::{ sys::mman::{MapFlags, ProtFlags, mmap_anonymous}, @@ -191,7 +190,7 @@ mod linux { } } let idx = found_exe.unwrap(); - let module_reader::BuildId(id) = dumper.from_process_memory_for_index(idx)?; + let id = dumper.build_id_from_process_memory_for_index(idx)?; drop(dumper); @@ -228,19 +227,17 @@ mod linux { fn test_linux_gate_mapping_id() -> Result<()> { let ppid = getppid().as_raw(); - let dumper = fail_on_soft_error!( + let mut dumper = fail_on_soft_error!( soft_errors, MinidumpWriterConfig::new(ppid, ppid).build_for_testing(&mut soft_errors)? ); let mut found_linux_gate = false; - for mapping in dumper.mappings.clone() { - if mapping.name == Some(LINUX_GATE_LIBRARY_NAME.into()) { + for idx in 0..dumper.mappings.len() { + if dumper.mappings[idx].name == Some(LINUX_GATE_LIBRARY_NAME.into()) { found_linux_gate = true; - let module_reader::BuildId(id) = MinidumpWriter::from_process_memory_for_mapping( - &dumper.process_inspector, - &mapping, - )?; + let id = dumper.build_id_from_process_memory_for_index(idx)?; + test!(!id.is_empty(), "id-vec is empty"); test!(id.iter().any(|&x| x > 0), "all id elements are 0"); drop(dumper); diff --git a/src/linux/maps_reader.rs b/src/linux/maps_reader.rs index ee3a302c..0e99ed0d 100644 --- a/src/linux/maps_reader.rs +++ b/src/linux/maps_reader.rs @@ -1,19 +1,19 @@ use { - super::{auxv::AuxvType, module_reader::ModuleReaderError, serializers::*}, + super::{ + auxv::AuxvType, module_reader::ModuleReaderError, process_inspection::ProcessInspector, + serializers::*, + }, crate::serializers::*, byteorder::{NativeEndian, ReadBytesExt}, - goblin::elf, - memmap2::{Mmap, MmapOptions}, procfs_core::{ FromRead, process::{MMPermissions, MMapPath, MemoryMaps}, }, std::{ ffi::{OsStr, OsString}, - fs::File, mem::size_of, os::unix::ffi::{OsStrExt, OsStringExt}, - path::PathBuf, + path::{Path, PathBuf}, }, }; @@ -67,6 +67,10 @@ pub enum MapsReaderError { #[serde(serialize_with = "serialize_goblin_error")] goblin::error::Error, ), + #[error("error reading soname from file")] + ReadSoNameFromFileFailed(#[source] ModuleReaderError), + #[error("failed to memory map file")] + MemoryMapFileFailed(#[source] ModuleReaderError), #[error("No soname found (filename: {})", .0.to_string_lossy())] NoSoName(OsString, #[source] ModuleReaderError), @@ -129,9 +133,13 @@ fn sanitize_path(pathname: OsString) -> OsString { impl MappingInfo { /// Get the mappings for the given process. - pub fn for_pid(pid: i32, linux_gate_loc: Option) -> Result> { + pub fn for_pid( + process_inspector: &ProcessInspector, + pid: i32, + linux_gate_loc: Option, + ) -> Result> { let maps_path = format!("/proc/{}/maps", pid); - let maps_file = File::open(&maps_path).map_err(|e| { + let maps_file = process_inspector.read_file(&maps_path).map_err(|e| { if e.kind() == std::io::ErrorKind::NotFound { MapsReaderError::MappingFileMissing { pid } } else { @@ -257,28 +265,6 @@ impl MappingInfo { Ok(infos) } - pub fn get_mmap(name: &Option, offset: usize) -> Result { - if !MappingInfo::is_mapped_file_safe_to_open(name) { - return Err(MapsReaderError::NotSafeToOpenMapping( - name.clone().unwrap_or_default(), - )); - } - - // Not doing this as root_prefix is always "" at the moment - // if (!dumper.GetMappingAbsolutePath(mapping, filename)) - let filename = name.clone().unwrap_or_default(); - let mapped_file = unsafe { - MmapOptions::new() - .offset(offset.try_into()?) // try_into() to work for both 32 and 64 bit - .map(&File::open(filename)?)? - }; - - if mapped_file.is_empty() || mapped_file.len() < elf::header::SELFMAG { - return Err(MapsReaderError::MmapSanityCheckFailed); - } - Ok(mapped_file) - } - pub fn stack_has_pointer_to_mapping(&self, stack_copy: &[u8], sp_offset: usize) -> bool { // Loop over all stack words that would have been on the stack in // the target process (i.e. are word aligned, and at addresses >= @@ -312,29 +298,12 @@ impl MappingInfo { false } - pub fn is_mapped_file_safe_to_open(name: &Option) -> bool { - // It is unsafe to attempt to open a mapped file that lives under /dev, - // because the semantics of the open may be driver-specific so we'd risk - // hanging the crash dumper. And a file in /dev/ almost certainly has no - // ELF file identifier anyways. - if let Some(name) = name - && name.as_bytes().starts_with(b"/dev/") - { - return false; - } - true - } - /// Find the shared object name (SONAME) by examining the ELF information /// for the mapping. - fn so_name(&self) -> Result { - use super::module_reader::{ReadFromModule, SoName}; - - let mapped_file = MappingInfo::get_mmap(&self.name, self.offset)?; - Ok(SoName::read_from_module((&*mapped_file).into()) - .map_err(|e| MapsReaderError::NoSoName(self.name.clone().unwrap_or_default(), e))? - .0 - .to_string()) + fn so_name(&self, process_inspector: &ProcessInspector) -> Result { + let path = Path::new(self.name.as_deref().unwrap_or_default()); + super::module_reader::read_soname_from_file(process_inspector, path, self.offset) + .map_err(MapsReaderError::ReadSoNameFromFileFailed) } #[inline] @@ -344,6 +313,7 @@ impl MappingInfo { pub fn get_mapping_effective_path_name_and_version( &self, + process_inspector: &ProcessInspector, soname: Option, ) -> Result<(PathBuf, String, Option)> { let mut file_path = PathBuf::from(self.name.clone().unwrap_or_default()); @@ -354,7 +324,7 @@ impl MappingInfo { // filesystem name of the module. // Just use the filesystem name if no SONAME is present. - let Some(file_name) = soname.or_else(|| self.so_name().ok()) else { + let Some(file_name) = soname.or_else(|| self.so_name(process_inspector).ok()) else { // file_path := /path/to/libname.so // file_name := libname.so let file_name = file_path @@ -760,8 +730,10 @@ a4840000-a4873000 rw-p 09021000 08:12 393449 /data/app/org.mozilla.firefox-1 ); assert_eq!(mappings.len(), 1); + let process_inspector = ProcessInspector::local(0); + let (file_path, file_name, _version) = mappings[0] - .get_mapping_effective_path_name_and_version(None) + .get_mapping_effective_path_name_and_version(&process_inspector, None) .expect("Couldn't get effective name for mapping"); assert_eq!(file_name, "libmozgtk.so"); assert_eq!( diff --git a/src/linux/minidump_writer/mappings.rs b/src/linux/minidump_writer/mappings.rs index b12e2511..a31c69e2 100644 --- a/src/linux/minidump_writer/mappings.rs +++ b/src/linux/minidump_writer/mappings.rs @@ -1,10 +1,4 @@ -use super::{ - super::{ - maps_reader::MappingInfo, - module_reader::{BuildId, ReadFromModule, SoName}, - }, - *, -}; +use super::{super::maps_reader::MappingInfo, *}; #[derive(Debug, Error, serde::Serialize)] pub enum SectionMappingsError { @@ -37,8 +31,8 @@ impl MinidumpWriter { continue; } log::debug!("retrieving build id for {:?}", &self.mappings[map_idx]); - let BuildId(identifier) = self - .from_process_memory_for_index(map_idx) + let identifier = self + .build_id_from_process_memory_for_index(map_idx) .or_else(|e| { // If the mapping has an associated name that is a file, try to read the build id // from the file. If there is no note segment with the build id in @@ -48,7 +42,7 @@ impl MinidumpWriter { let path = std::path::Path::new(&path); if path.exists() { log::debug!("failed to get build id from process memory ({e}), attempting to retrieve from {}", path.display()); - return BuildId::read_from_file(path) + return module_reader::read_build_id_from_file(&self.process_inspector, path) .map_err(errors::WriterError::ModuleReaderError); } log::debug!( @@ -60,7 +54,7 @@ impl MinidumpWriter { }) .unwrap_or_else(|e| { log::warn!("failed to get build id for mapping: {e}"); - BuildId(Vec::new()) + Vec::new() }); // If the identifier is all 0, its an uninteresting mapping (bmc#1676109) @@ -70,19 +64,28 @@ impl MinidumpWriter { // SONAME should always be accessible through program headers alone, so we don't really // need to fall back to trying to read from the mapping file. - let soname = self - .from_process_memory_for_index(map_idx) - .ok() - .map(|SoName(n)| n); - - let module = fill_raw_module(buffer, &self.mappings[map_idx], &identifier, soname)?; + let soname = self.soname_from_process_memory_for_index(map_idx).ok(); + + let module = fill_raw_module( + &self.process_inspector, + buffer, + &self.mappings[map_idx], + &identifier, + soname, + )?; modules.push(module); } // Next write all the mappings provided by the caller for user in &self.user_mapping_list { // GUID was provided by caller. - let module = fill_raw_module(buffer, &user.mapping, &user.identifier, None)?; + let module = fill_raw_module( + &self.process_inspector, + buffer, + &user.mapping, + &user.identifier, + None, + )?; modules.push(module); } @@ -102,6 +105,7 @@ impl MinidumpWriter { } } fn fill_raw_module( + process_inspector: &ProcessInspector, buffer: &mut DumpBuf, mapping: &MappingInfo, identifier: &[u8], @@ -127,7 +131,7 @@ fn fill_raw_module( }; let (file_path, _, so_version) = mapping - .get_mapping_effective_path_name_and_version(soname) + .get_mapping_effective_path_name_and_version(process_inspector, soname) .map_err(|e| SectionMappingsError::GetEffectivePathError(mapping.clone(), e))?; let name_header = write_string_to_location(buffer, file_path.to_string_lossy().as_ref())?; diff --git a/src/linux/minidump_writer/mod.rs b/src/linux/minidump_writer/mod.rs index edfba4bf..8206c4cb 100644 --- a/src/linux/minidump_writer/mod.rs +++ b/src/linux/minidump_writer/mod.rs @@ -733,8 +733,12 @@ impl MinidumpWriter { // case its entry when creating the list of mappings. // See http://www.trilithium.com/johan/2005/08/linux-gate/ for more // information. - self.mappings = MappingInfo::for_pid(self.process_id, self.auxv.get_linux_gate_address()) - .map_err(InitError::AggregateMappingsFailed)?; + self.mappings = MappingInfo::for_pid( + &self.process_inspector, + self.process_id, + self.auxv.get_linux_gate_address(), + ) + .map_err(InitError::AggregateMappingsFailed)?; // Although the initial executable is usually the first mapping, it's not // guaranteed (see http://crosbug.com/25355); therefore, try to use the @@ -945,23 +949,28 @@ impl MinidumpWriter { }) } - pub fn from_process_memory_for_index( + pub fn build_id_from_process_memory_for_index( &mut self, idx: usize, - ) -> Result { - assert!(idx < self.mappings.len()); - - Self::from_process_memory_for_mapping(&self.process_inspector, &self.mappings[idx]) + ) -> Result, WriterError> { + let reader = self.process_inspector.process_reader(); + module_reader::read_build_id_from_module(module_reader::ProcessModuleMemoryReader::new( + reader, + self.mappings[idx].start_address, + )) + .map_err(WriterError::ModuleReaderError) } - pub fn from_process_memory_for_mapping( - process_inspector: &ProcessInspector, - mapping: &MappingInfo, - ) -> Result { - let reader = process_inspector.process_reader(); - Ok(T::read_from_module( - module_reader::ModuleMemory::from_process(reader, mapping.start_address), - )?) + pub fn soname_from_process_memory_for_index( + &mut self, + idx: usize, + ) -> Result { + let reader = self.process_inspector.process_reader(); + module_reader::read_soname_from_module(module_reader::ProcessModuleMemoryReader::new( + reader, + self.mappings[idx].start_address, + )) + .map_err(WriterError::ModuleReaderError) } /// Copies a block of bytes from the target process, returning the heap diff --git a/src/linux/module_reader.rs b/src/linux/module_reader.rs index c0f7aef2..84148127 100644 --- a/src/linux/module_reader.rs +++ b/src/linux/module_reader.rs @@ -1,12 +1,16 @@ use { - super::serializers::*, - crate::module_reader::{ModuleMemory, ModuleMemoryReadError}, + super::{process_inspection::ProcessInspector, serializers::*}, + crate::module_reader::{ModuleMemoryReadError, ProcessModuleMemoryReader}, crate::{minidump_format::GUID, serializers::*}, goblin::{ container::{Container, Ctx, Endian}, elf, }, - std::{borrow::Cow, ffi::CStr}, + std::{ + borrow::Cow, + ffi::{CStr, OsString}, + path::Path, + }, }; type Error = ModuleReaderError; @@ -70,12 +74,28 @@ pub enum ModuleReaderError { program_headers: Box, section: Box, }, -} - -impl crate::module_reader::ModuleMemory<'_> { - pub fn read_from_module(self) -> Result { - T::read_from_module(self) - } + #[error("Not safe to open mapping {}", .0.to_string_lossy())] + NotSafeToOpenMapping(OsString), + #[error("Mmapped file empty or not an ELF file")] + MmapSanityCheckFailed, + #[error("Linux gate location doesn't fit in the required integer type")] + LinuxGateNotConvertable( + #[source] + #[serde(skip)] + std::num::TryFromIntError, + ), + #[error("IO Error")] + FileError( + #[source] + #[serde(serialize_with = "serialize_io_error")] + std::io::Error, + ), + #[error("failed to map file")] + MapError( + #[source] + #[serde(serialize_with = "serialize_io_error")] + std::io::Error, + ), } #[inline] @@ -105,11 +125,11 @@ fn build_id_from_bytes(data: &[u8]) -> Vec { } // `name` should be null-terminated -fn section_header_with_name<'sc>( +fn section_header_with_name<'sc, MM: ReadModuleMemory>( section_headers: &'sc elf::SectionHeaders, strtab_index: usize, name: &[u8], - module_memory: &ModuleMemory<'_>, + module_memory: &MM, ) -> Result, Error> { let strtab_section_header = section_headers .get(strtab_index) @@ -134,46 +154,73 @@ fn section_header_with_name<'sc>( Ok(None) } -/// Types which can be read from ModuleMemory. -pub trait ReadFromModule: Sized { - fn read_from_module(module_memory: ModuleMemory<'_>) -> Result; +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)?; + read_build_id_from_module(module_memory_reader) +} - fn read_from_file(path: &std::path::Path) -> Result { - let map = std::fs::File::open(path) - // Safety: the file is an executable binary (very likely read-only), and won't be changed. - .and_then(|file| unsafe { memmap2::Mmap::map(&file) }) - .map_err(|error| Error::MapFile { - path: path.to_owned(), - error, - })?; - Self::read_from_module(ModuleMemory::Slice(&map)) - } +pub fn read_build_id_from_module(module_memory: impl ReadModuleMemory) -> Result, Error> { + let reader = ModuleReader::new(module_memory)?; + let program_headers = match reader.build_id_from_program_headers() { + Ok(v) => return Ok(v), + Err(e) => Box::new(e), + }; + let section = match reader.build_id_from_section() { + Ok(v) => return Ok(v), + Err(e) => Box::new(e), + }; + let generated = match reader.build_id_generate_from_text() { + Ok(v) => return Ok(v), + Err(e) => Box::new(e), + }; + Err(Error::NoBuildId { + program_headers, + section, + generated, + }) } -/// The module build id. -pub struct BuildId(pub Vec); +pub fn read_soname_from_file( + process_inspector: &ProcessInspector, + path: &Path, + offset: usize, +) -> Result { + let offset = u64::try_from(offset).map_err(Error::LinuxGateNotConvertable)?; + + // It is unsafe to attempt to open a mapped file that lives under /dev, + // because the semantics of the open may be driver-specific so we'd risk + // hanging the crash dumper. And a file in /dev/ almost certainly has no + // ELF file identifier anyways. + if path.starts_with("/dev/") { + return Err(Error::NotSafeToOpenMapping(path.as_os_str().to_os_string())); + } -impl ReadFromModule for BuildId { - fn read_from_module(module_memory: ModuleMemory<'_>) -> Result { - let 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), - }; - let section = match reader.build_id_from_section() { - Ok(v) => return Ok(BuildId(v)), - Err(e) => Box::new(e), - }; - let generated = match reader.build_id_generate_from_text() { - Ok(v) => return Ok(BuildId(v)), - Err(e) => Box::new(e), - }; - Err(Error::NoBuildId { - program_headers, - section, - generated, - }) + let module_memory_reader = process_inspector.read_memory_mapped_module(path, offset)?; + + if module_memory_reader.is_empty() || module_memory_reader.len() < elf::header::SELFMAG { + return Err(Error::MmapSanityCheckFailed); } + + read_soname_from_module(module_memory_reader) +} + +pub fn read_soname_from_module(module_memory: impl ReadModuleMemory) -> Result { + let reader = ModuleReader::new(module_memory)?; + let program_headers = match reader.soname_from_program_headers() { + Ok(v) => return Ok(v), + Err(e) => Box::new(e), + }; + let section = match reader.soname_from_sections() { + Ok(v) => return Ok(v), + Err(e) => Box::new(e), + }; + Err(Error::NoSoName { + program_headers, + section, + }) } struct DynIter<'a> { @@ -209,36 +256,14 @@ impl Iterator for DynIter<'_> { } } -/// The module SONAME. -#[derive(Default, Clone, Debug)] -pub struct SoName(pub String); - -impl ReadFromModule for SoName { - fn read_from_module(module_memory: ModuleMemory<'_>) -> Result { - let 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), - }; - let section = match reader.soname_from_sections() { - Ok(v) => return Ok(SoName(v)), - Err(e) => Box::new(e), - }; - Err(Error::NoSoName { - program_headers, - section, - }) - } -} - -pub struct ModuleReader<'buf> { - module_memory: ModuleMemory<'buf>, +pub struct ModuleReader { + module_memory: MM, header: elf::Header, context: Ctx, } -impl<'buf> ModuleReader<'buf> { - pub fn new(module_memory: ModuleMemory<'buf>) -> Result { +impl ModuleReader { + pub fn new(module_memory: MM) -> Result, Error> { // 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. @@ -428,7 +453,7 @@ impl<'buf> ModuleReader<'buf> { Ok(build_id_from_bytes(&text_data)) } - fn read_segment(&self, header: &elf::ProgramHeader) -> Result, Error> { + fn read_segment<'a>(&'a self, header: &elf::ProgramHeader) -> Result, Error> { let (offset, size) = if self.module_memory.is_process_memory() { (header.p_vaddr, header.p_memsz) } else { @@ -543,6 +568,31 @@ impl<'buf> ModuleReader<'buf> { } } +pub trait ReadModuleMemory { + fn read<'a>(&'a self, offset: u64, length: u64) + -> Result, ModuleMemoryReadError>; + fn absolute_to_relative(&self, addr: u64) -> Option; + /// Calculates the absolute address of the specified relative address + fn relative_to_absolute(&self, addr: u64) -> Option; + fn is_process_memory(&self) -> bool; +} + +impl<'a> ReadModuleMemory for ProcessModuleMemoryReader<'a> { + fn read(&self, offset: u64, length: u64) -> Result, ModuleMemoryReadError> { + self.read(offset, length) + } + fn absolute_to_relative(&self, addr: u64) -> Option { + addr.checked_sub(self.start_address) + } + /// Calculates the absolute address of the specified relative address + fn relative_to_absolute(&self, addr: u64) -> Option { + self.start_address.checked_add(addr) + } + fn is_process_memory(&self) -> bool { + true + } +} + #[cfg(test)] mod test { use super::*; @@ -621,7 +671,7 @@ mod test { #[test] fn build_id_program_headers() { - let reader = ModuleReader::new(TINY_ELF.into()).unwrap(); + let reader = ModuleReader::new(SliceModuleMemoryReader(TINY_ELF)).unwrap(); let id = reader.build_id_from_program_headers().unwrap(); assert_eq!( id, @@ -631,7 +681,7 @@ mod test { #[test] fn build_id_section() { - let reader = ModuleReader::new(TINY_ELF.into()).unwrap(); + let reader = ModuleReader::new(SliceModuleMemoryReader(TINY_ELF)).unwrap(); let id = reader.build_id_from_section().unwrap(); assert_eq!( id, @@ -641,7 +691,7 @@ mod test { #[test] fn build_id_text_hash() { - let reader = ModuleReader::new(TINY_ELF.into()).unwrap(); + let reader = ModuleReader::new(SliceModuleMemoryReader(TINY_ELF)).unwrap(); let id = reader.build_id_generate_from_text().unwrap(); assert_eq!( id, @@ -653,15 +703,53 @@ mod test { #[test] fn soname_program_headers() { - let reader = ModuleReader::new(TINY_ELF.into()).unwrap(); + let reader = ModuleReader::new(SliceModuleMemoryReader(TINY_ELF)).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.into()).unwrap(); + let reader = ModuleReader::new(SliceModuleMemoryReader(TINY_ELF)).unwrap(); let soname = reader.soname_from_sections().unwrap(); assert_eq!(soname, "libfoo.so.1"); } + + pub struct SliceModuleMemoryReader<'a>(pub &'a [u8]); + + impl<'a> ReadModuleMemory for SliceModuleMemoryReader<'a> { + fn read<'b>( + &'b 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/mod.rs b/src/linux/process_inspection/mod.rs index 11cbc311..a190a6c7 100644 --- a/src/linux/process_inspection/mod.rs +++ b/src/linux/process_inspection/mod.rs @@ -1,6 +1,11 @@ use { - super::{Pid, maps_reader, serializers}, + super::{ + Pid, maps_reader, + module_reader::{ModuleReaderError, ReadModuleMemory}, + serializers, + }, core::{ffi::c_void, mem}, + module_reader::MappedModuleMemoryReader, nix::errno::Errno, process_reader::ProcessReader, regs::*, @@ -14,6 +19,8 @@ use { pub mod process_reader; pub mod regs; +mod module_reader; + #[cfg(target_env = "gnu")] type PtraceRequestType = core::ffi::c_uint; @@ -38,6 +45,14 @@ impl ProcessInspector { &self.process_reader } + pub fn read_memory_mapped_module( + &self, + path: impl AsRef, + offset: u64, + ) -> Result { + MappedModuleMemoryReader::new(path.as_ref(), offset) + } + pub fn read_file(&self, path: impl AsRef) -> io::Result { File::open(path) } diff --git a/src/linux/process_inspection/module_reader.rs b/src/linux/process_inspection/module_reader.rs new file mode 100644 index 00000000..eea277c4 --- /dev/null +++ b/src/linux/process_inspection/module_reader.rs @@ -0,0 +1,64 @@ +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 82cfb0d0..6e328582 100644 --- a/src/linux/process_inspection/process_reader.rs +++ b/src/linux/process_inspection/process_reader.rs @@ -1,12 +1,8 @@ use { - super::{Pid, maps_reader::MappingInfo, serializers::*}, - crate::module_reader::ModuleMemory, + super::{Pid, serializers::*}, std::sync::OnceLock, }; -#[cfg(target_os = "android")] -use super::super::module_reader::SoName; - #[derive(Debug)] enum Style { /// Uses [`process_vm_readv`](https://linux.die.net/man/2/process_vm_readv) @@ -233,32 +229,4 @@ impl ProcessReader { Ok(dst.len()) } - - /// Find the address at which a module with the given name is loaded in the process. - pub fn find_module(&self, module_name: &str) -> Result, FindModuleError> { - MappingInfo::for_pid(self.pid.as_raw(), None)? - .into_iter() - .find_map(|m| { - let mmem = ModuleMemory::from_process(self, m.start_address); - let name = m.name.as_ref().and_then(|s| s.to_str())?; - if name == module_name { - return Some(mmem); - } - // Check whether the SO_NAME matches the module name. - // - // For now, only check the SO_NAME of Android APKS, because libraries may be mapped - // directly from within an APK. See bug 1982902. - #[cfg(target_os = "android")] - if name.ends_with(".apk") { - if let Ok(SoName(so_name)) = mmem.read_from_module() { - if so_name == name { - return Some(mmem); - } - } - } - - None - }) - .ok_or(FindModuleError::ModuleNotFound) - } } diff --git a/src/mac/module_reader.rs b/src/mac/module_reader.rs index 4d9c5405..21d31ca5 100644 --- a/src/mac/module_reader.rs +++ b/src/mac/module_reader.rs @@ -1,4 +1,4 @@ -use crate::module_reader::{ModuleMemory, ModuleMemoryReadError}; +use crate::module_reader::{ModuleMemoryReadError, ProcessModuleMemoryReader}; use crate::serializers::*; use goblin::container::{Container, Ctx, Endian}; use goblin::mach::{ @@ -11,7 +11,7 @@ use scroll::ctx::{SizeWith, TryFromCtx}; const DATA_SEGMENT: &[u8; 16] = b"__DATA\0\0\0\0\0\0\0\0\0\0"; pub struct ModuleReader<'a> { - module_memory: ModuleMemory<'a>, + module_memory: ProcessModuleMemoryReader<'a>, header: Header, context: Ctx, } @@ -58,7 +58,7 @@ macro_rules! read_scroll_array { }}; } -impl ModuleMemory<'_> { +impl ProcessModuleMemoryReader<'_> { /// Read an array of values using scroll traits. /// /// See `read_scroll` for an explanation of the return form. @@ -94,7 +94,7 @@ impl ModuleMemory<'_> { } impl<'a> ModuleReader<'a> { - pub fn new(module_memory: ModuleMemory<'a>) -> Result { + pub fn new(module_memory: ProcessModuleMemoryReader<'a>) -> Result { let header_size = Header::size_with(&Ctx::new(Container::Big, Endian::default())); let header_data = module_memory.read(0, header_size as u64)?; let (_, ctx) = mach::parse_magic_and_ctx(&header_data, 0)?; diff --git a/src/mac/process_reader.rs b/src/mac/process_reader.rs index 800ff557..978b4cf6 100644 --- a/src/mac/process_reader.rs +++ b/src/mac/process_reader.rs @@ -2,14 +2,16 @@ * 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::module_reader::ModuleMemory; -use mach2::{ - kern_return::KERN_SUCCESS, - task::task_info, - task_info::{TASK_DYLD_ALL_IMAGE_INFO_64, TASK_DYLD_INFO, task_dyld_info}, - vm::mach_vm_read_overwrite, +use { + crate::module_reader::ProcessModuleMemoryReader, + mach2::{ + kern_return::KERN_SUCCESS, + task::task_info, + task_info::{TASK_DYLD_ALL_IMAGE_INFO_64, TASK_DYLD_INFO, task_dyld_info}, + vm::mach_vm_read_overwrite, + }, + std::mem::{MaybeUninit, size_of}, }; -use std::mem::{MaybeUninit, size_of}; pub type ProcessHandle = mach2::mach_types::task_t; @@ -100,7 +102,10 @@ impl ProcessReader { } } - pub fn find_module(&self, module_name: &str) -> Result, FindModuleError> { + pub fn find_module( + &self, + module_name: &str, + ) -> Result, FindModuleError> { let dyld_info = self.task_info()?; if (dyld_info.all_image_info_format as u32) != TASK_DYLD_ALL_IMAGE_INFO_64 { return Err(FindModuleError::ImageFormatInvalid); @@ -143,7 +148,7 @@ impl ProcessReader { false } }) - .map(|image| ModuleMemory::from_process(self, image.load_address as usize)) + .map(|image| ProcessModuleMemoryReader::new(self, image.load_address as usize)) .ok_or(FindModuleError::ModuleNotFound) } diff --git a/src/module_reader.rs b/src/module_reader.rs index 26f92a9f..aeff6719 100644 --- a/src/module_reader.rs +++ b/src/module_reader.rs @@ -13,20 +13,40 @@ pub use crate::windows::module_reader::*; #[cfg(target_os = "macos")] pub use crate::mac::module_reader::*; -/// Module memory, which may either be represented by a slice or by an offset into a process's -/// address space. -#[derive(Clone, Copy)] -pub enum ModuleMemory<'a> { - Slice(&'a [u8]), - Process { - reader: &'a ProcessReader, - start_address: u64, - }, +pub struct ProcessModuleMemoryReader<'a> { + pub(super) reader: &'a ProcessReader, + pub(super) start_address: u64, } -impl<'buf> From<&'buf [u8]> for ModuleMemory<'buf> { - fn from(value: &'buf [u8]) -> Self { - Self::Slice(value) +impl<'a> ProcessModuleMemoryReader<'a> { + pub fn new(reader: &'a ProcessReader, start_address: usize) -> Self { + Self { + reader, + start_address: start_address as u64, + } + } + pub fn read(&self, offset: u64, length: u64) -> Result, ModuleMemoryReadError> { + let inner = || { + let address = self + .start_address + .checked_add(offset) + .ok_or(ReadError::Overflow)?; + let address = usize::try_from(address).map_err(|_| ReadError::Overflow)?; + let length = usize::try_from(length).map_err(|_| ReadError::Overflow)?; + let length = + std::num::NonZeroUsize::new(length).ok_or(ReadError::ZeroLengthProcessRead)?; + self.reader + .read_to_vec(address, length) + .map(Cow::Owned) + .map_err(ReadError::CopyError) + }; + + inner().map_err(|error| ModuleMemoryReadError { + start_address: Some(self.start_address), + offset, + length, + error, + }) } } @@ -35,11 +55,11 @@ impl<'buf> From<&'buf [u8]> for ModuleMemory<'buf> { .start_address.map(|s| format!(" (module start address {s:#x})")).unwrap_or_default() )] pub struct ModuleMemoryReadError { - offset: u64, - length: u64, - start_address: Option, + pub offset: u64, + pub length: u64, + pub start_address: Option, #[source] - error: ReadError, + pub error: ReadError, } #[derive(Debug, thiserror::Error, serde::Serialize)] @@ -53,72 +73,3 @@ pub enum ReadError { #[error(transparent)] CopyError(#[from] CopyFromProcessError), } - -impl<'a> ModuleMemory<'a> { - pub fn from_process(reader: &'a ProcessReader, start_address: usize) -> Self { - Self::Process { - reader, - start_address: start_address as u64, - } - } - - #[inline] - pub fn read(&self, offset: u64, length: u64) -> Result, ModuleMemoryReadError> { - let error = move |start_address, error| ModuleMemoryReadError { - start_address, - offset, - length, - error, - }; - - match self { - Self::Process { - reader, - start_address, - } => { - let error = |e| error(Some(*start_address), e); - let len = std::num::NonZeroUsize::new(length as usize) - .ok_or_else(|| error(ReadError::ZeroLengthProcessRead))?; - let proc_offset = start_address - .checked_add(offset) - .ok_or_else(|| error(ReadError::Overflow))?; - reader - .read_to_vec(proc_offset as _, len) - .map(Cow::Owned) - .map_err(|err| error(err.into())) - } - Self::Slice(s) => { - let error = |e| error(None, e); - let end = offset - .checked_add(length) - .ok_or_else(|| error(ReadError::Overflow))?; - s.get(offset as usize..end as usize) - .map(Cow::Borrowed) - .ok_or_else(|| error(ReadError::OutOfBounds)) - } - } - } - - /// Calculates the relative address of the specified absolute address - #[inline] - pub fn absolute_to_relative(&self, addr: u64) -> Option { - let Self::Process { start_address, .. } = self else { - return Some(addr); - }; - addr.checked_sub(*start_address) - } - - /// Calculates the absolute address of the specified relative address - #[inline] - pub fn relative_to_absolute(&self, addr: u64) -> Option { - let Self::Process { start_address, .. } = self else { - return Some(addr); - }; - start_address.checked_add(addr) - } - - #[inline] - pub fn is_process_memory(&self) -> bool { - matches!(self, Self::Process { .. }) - } -} diff --git a/src/windows/module_reader.rs b/src/windows/module_reader.rs index 69fc65fa..8bc15e81 100644 --- a/src/windows/module_reader.rs +++ b/src/windows/module_reader.rs @@ -1,6 +1,6 @@ use { crate::{ - module_reader::{ModuleMemory, ModuleMemoryReadError}, + module_reader::{ModuleMemoryReadError, ProcessModuleMemoryReader}, serializers::*, }, goblin::pe::header::Header, @@ -24,7 +24,7 @@ pub enum ModuleReaderError { } impl<'a> ModuleReader<'a> { - pub fn new(module_memory: ModuleMemory<'a>) -> Result { + pub fn new(module_memory: ProcessModuleMemoryReader<'a>) -> 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 diff --git a/src/windows/process_reader.rs b/src/windows/process_reader.rs index c8db14e9..8fc5889d 100644 --- a/src/windows/process_reader.rs +++ b/src/windows/process_reader.rs @@ -1,6 +1,6 @@ use { super::ffi::{self, HMODULE}, - crate::{module_reader::ModuleMemory, serializers::serialize_io_error}, + crate::{module_reader::ProcessModuleMemoryReader, serializers::serialize_io_error}, std::{ convert::TryInto, ffi::OsString, @@ -64,7 +64,10 @@ impl ProcessReader { } } - pub fn find_module(&self, module_name: &str) -> Result, FindModuleError> { + pub fn find_module( + &self, + module_name: &str, + ) -> Result, FindModuleError> { let modules = self.get_module_list()?; let module = modules.iter().find_map(|&module| { @@ -80,7 +83,7 @@ impl ProcessReader { }); module - .map(|m| ModuleMemory::from_process(self, m)) + .map(|m| ProcessModuleMemoryReader::new(self, m)) .ok_or(FindModuleError::ModuleNotFound) } diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 7ad470c3..bf6368b1 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -143,3 +143,53 @@ pub fn assert_soft_errors_in_minidump<'a, 'b, T, I>( ); } } + +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(unused)] +pub use linux::*; + +#[cfg(any(target_os = "linux", target_os = "android"))] +#[allow(unused)] +mod linux { + use { + minidump_writer::module_reader::{self, ModuleMemoryReadError, ReadModuleMemory}, + std::borrow::Cow, + }; + pub struct SliceModuleMemoryReader<'a>(pub &'a [u8]); + + impl<'a> ReadModuleMemory for SliceModuleMemoryReader<'a> { + fn read<'b>( + &'b self, + offset: u64, + length: u64, + ) -> Result, ModuleMemoryReadError> { + let inner = || { + use 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/tests/linux_minidump_writer.rs b/tests/linux_minidump_writer.rs index 31fedc2c..a1cf216f 100644 --- a/tests/linux_minidump_writer.rs +++ b/tests/linux_minidump_writer.rs @@ -11,7 +11,7 @@ use { crash_context::CrashContext, maps_reader::{MappingEntry, MappingInfo, SystemMappingInfo}, minidump_writer::{MinidumpWriter, MinidumpWriterConfig, errors::WriterError}, - module_reader::{BuildId, ReadFromModule}, + module_reader::{self}, }, nix::{errno::Errno, sys::signal::Signal}, procfs_core::process::MMPermissions, @@ -724,8 +724,9 @@ fn with_deleted_binary() { let pid = child.id() as i32; - let BuildId(mut build_id) = - BuildId::read_from_module(mem_slice.as_slice().into()).expect("Failed to get build_id"); + let mut build_id = + module_reader::read_build_id_from_module(SliceModuleMemoryReader(mem_slice.as_slice())) + .expect("Failed to get build_id"); std::fs::remove_file(&binary_copy).expect("Failed to remove binary"); diff --git a/tests/linux_module_reader.rs b/tests/linux_module_reader.rs index 1140fc35..578c61aa 100644 --- a/tests/linux_module_reader.rs +++ b/tests/linux_module_reader.rs @@ -1,6 +1,8 @@ #![cfg(target_os = "linux")] -use minidump_writer::module_reader::*; +use {common::*, minidump_writer::module_reader::*}; + +mod common; /// This is a small (but valid) 64-bit little-endian elf executable with the following layout: /// * ELF header @@ -22,21 +24,21 @@ const TINY_ELF: &[u8] = include_bytes!("tiny.elf"); #[test] fn build_id_program_headers() { - let reader = ModuleReader::new(TINY_ELF.into()).unwrap(); + let reader = ModuleReader::new(SliceModuleMemoryReader(TINY_ELF)).unwrap(); let id = reader.build_id_from_program_headers().unwrap(); assert_eq!(id, &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]); } #[test] fn build_id_section() { - let reader = ModuleReader::new(TINY_ELF.into()).unwrap(); + let reader = ModuleReader::new(SliceModuleMemoryReader(TINY_ELF)).unwrap(); let id = reader.build_id_from_section().unwrap(); assert_eq!(id, &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]); } #[test] fn build_id_text_hash() { - let reader = ModuleReader::new(TINY_ELF.into()).unwrap(); + let reader = ModuleReader::new(SliceModuleMemoryReader(TINY_ELF)).unwrap(); let id = reader.build_id_generate_from_text().unwrap(); assert_eq!( id, @@ -48,14 +50,14 @@ fn build_id_text_hash() { #[test] fn soname_program_headers() { - let reader = ModuleReader::new(TINY_ELF.into()).unwrap(); + let reader = ModuleReader::new(SliceModuleMemoryReader(TINY_ELF)).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.into()).unwrap(); + let reader = ModuleReader::new(SliceModuleMemoryReader(TINY_ELF)).unwrap(); let soname = reader.soname_from_sections().unwrap(); assert_eq!(soname, "libfoo.so.1"); }