From e56f764fce53e74a29c572a93646f89594d49baa Mon Sep 17 00:00:00 2001 From: Alex Franchuk Date: Fri, 19 Apr 2024 11:41:45 -0400 Subject: [PATCH 1/4] Read ELF SONAMEs directly from process memory when possible. When this fails, it falls back to the previous behavior of trying to open the mapped file name directly. Closes #79. --- src/bin/test.rs | 8 +- src/linux.rs | 2 +- src/linux/errors.rs | 16 ++- src/linux/maps_reader.rs | 18 +-- .../{build_id_reader.rs => module_reader.rs} | 136 +++++++++++++----- src/linux/ptrace_dumper.rs | 38 ++--- src/linux/sections/mappings.rs | 18 ++- tests/linux_minidump_writer.rs | 5 +- 8 files changed, 164 insertions(+), 77 deletions(-) rename src/linux/{build_id_reader.rs => module_reader.rs} (75%) diff --git a/src/bin/test.rs b/src/bin/test.rs index 77a54081..d8ac8b60 100644 --- a/src/bin/test.rs +++ b/src/bin/test.rs @@ -9,6 +9,7 @@ mod linux { use super::*; use minidump_writer::{ minidump_writer::STOP_TIMEOUT, + module_reader, ptrace_dumper::{PtraceDumper, AT_SYSINFO_EHDR}, LINUX_GATE_LIBRARY_NAME, }; @@ -100,7 +101,7 @@ mod linux { } } let idx = found_exe.unwrap(); - let id = dumper.elf_identifier_for_mapping_index(idx)?; + let module_reader::BuildId(id) = dumper.from_process_memory_for_index(idx)?; dumper.resume_threads()?; assert!(!id.is_empty()); assert!(id.iter().any(|&x| x > 0)); @@ -133,11 +134,12 @@ mod linux { let ppid = getppid().as_raw(); let mut dumper = PtraceDumper::new(ppid, STOP_TIMEOUT)?; let mut found_linux_gate = false; - for mut mapping in dumper.mappings.clone() { + for mapping in dumper.mappings.clone() { if mapping.name == Some(LINUX_GATE_LIBRARY_NAME.into()) { found_linux_gate = true; dumper.suspend_threads()?; - let id = PtraceDumper::elf_identifier_for_mapping(&mut mapping, ppid)?; + let module_reader::BuildId(id) = + dumper.from_process_memory_for_mapping(&mapping)?; 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.rs b/src/linux.rs index 31e21c76..0b68c125 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -5,13 +5,13 @@ mod android; pub mod app_memory; pub(crate) mod auxv_reader; -pub mod build_id_reader; pub mod crash_context; mod dso_debug; mod dumper_cpu_info; pub mod errors; pub mod maps_reader; pub mod minidump_writer; +pub mod module_reader; pub mod ptrace_dumper; pub(crate) mod sections; pub mod thread_info; diff --git a/src/linux/errors.rs b/src/linux/errors.rs index faf79f4d..1112c13a 100644 --- a/src/linux/errors.rs +++ b/src/linux/errors.rs @@ -26,7 +26,7 @@ pub enum MapsReaderError { #[error("Couldn't parse as ELF file")] ELFParsingFailed(#[from] goblin::error::Error), #[error("No soname found (filename: {})", .0.to_string_lossy())] - NoSoName(OsString), + NoSoName(OsString, #[source] ModuleReaderError), // parse_from_line() #[error("Map entry malformed: No {0} found")] @@ -115,8 +115,8 @@ pub enum DumperError { TryFromSliceError(#[from] std::array::TryFromSliceError), #[error("Couldn't parse as ELF file")] ELFParsingFailed(#[from] goblin::error::Error), - #[error("No build-id found")] - NoBuildIDFound(#[from] BuildIdReaderError), + #[error("Could not read value from module")] + ModuleReaderError(#[from] ModuleReaderError), #[error("Not safe to open mapping: {}", .0.to_string_lossy())] NotSafeToOpenMapping(OsString), #[error("Failed integer conversion")] @@ -250,7 +250,7 @@ pub enum WriterError { } #[derive(Debug, Error)] -pub enum BuildIdReaderError { +pub enum ModuleReaderError { #[error("failed to read module memory: {length} bytes at {offset}: {error}")] ReadModuleMemory { offset: u64, @@ -276,9 +276,15 @@ pub enum BuildIdReaderError { ... from sections: {section}\n\ ... from the text section: {section}" )] - Aggregate { + NoBuildId { program_headers: Box, section: Box, generated: Box, }, + #[error("a string in the strtab did not have a terminating nul byte")] + StrTabNoNulByte, + #[error("no SONAME found in dynamic linking information")] + NoSoNameEntry, + #[error("no dynamic linking information section")] + NoDynamicSection, } diff --git a/src/linux/maps_reader.rs b/src/linux/maps_reader.rs index eecabae8..66fa2f46 100644 --- a/src/linux/maps_reader.rs +++ b/src/linux/maps_reader.rs @@ -256,14 +256,13 @@ impl MappingInfo { /// Find the shared object name (SONAME) by examining the ELF information /// for the mapping. fn so_name(&self) -> Result { - let mapped_file = MappingInfo::get_mmap(&self.name, self.offset)?; - - let elf_obj = elf::Elf::parse(&mapped_file)?; + use super::module_reader::{ReadFromModule, SoName}; - let soname = elf_obj.soname.ok_or_else(|| { - MapsReaderError::NoSoName(self.name.clone().unwrap_or_else(|| "None".into())) - })?; - Ok(soname.to_string()) + let mapped_file = MappingInfo::get_mmap(&self.name, self.offset)?; + Ok(SoName::read_from_module(&*mapped_file) + .map_err(|e| MapsReaderError::NoSoName(self.name.clone().unwrap_or_default(), e))? + .0 + .to_string()) } #[inline] @@ -273,6 +272,7 @@ impl MappingInfo { pub fn get_mapping_effective_path_name_and_version( &self, + soname: Option, ) -> Result<(PathBuf, String, Option)> { let mut file_path = PathBuf::from(self.name.clone().unwrap_or_default()); @@ -282,7 +282,7 @@ impl MappingInfo { // filesystem name of the module. // Just use the filesystem name if no SONAME is present. - let Ok(file_name) = self.so_name() else { + let Some(file_name) = soname.or_else(|| self.so_name().ok()) else { // file_path := /path/to/libname.so // file_name := libname.so let file_name = file_path @@ -689,7 +689,7 @@ a4840000-a4873000 rw-p 09021000 08:12 393449 /data/app/org.mozilla.firefox-1 assert_eq!(mappings.len(), 1); let (file_path, file_name, _version) = mappings[0] - .get_mapping_effective_path_name_and_version() + .get_mapping_effective_path_name_and_version(None) .expect("Couldn't get effective name for mapping"); assert_eq!(file_name, "libmozgtk.so"); assert_eq!(file_path, PathBuf::from("/home/martin/Documents/mozilla/devel/mozilla-central/obj/widget/gtk/mozgtk/gtk3/libmozgtk.so")); diff --git a/src/linux/build_id_reader.rs b/src/linux/module_reader.rs similarity index 75% rename from src/linux/build_id_reader.rs rename to src/linux/module_reader.rs index 94288b24..c0a5ee1e 100644 --- a/src/linux/build_id_reader.rs +++ b/src/linux/module_reader.rs @@ -1,9 +1,10 @@ -use crate::errors::BuildIdReaderError as Error; +use crate::errors::ModuleReaderError as Error; use crate::minidump_format::GUID; use goblin::{ container::{Container, Ctx, Endian}, elf, }; +use std::ffi::CStr; const NOTE_SECTION_NAME: &[u8] = b".note.gnu.build-id\0"; @@ -61,34 +62,57 @@ fn build_id_from_bytes(data: &[u8]) -> Vec { ) } -pub fn read_build_id(module_memory: impl ModuleMemory) -> Result, Error> { - let reader = ElfBuildIdReader::new(module_memory)?; - let program_headers = match reader.read_from_program_headers() { - Ok(v) => return Ok(v), - Err(e) => Box::new(e), - }; - let section = match reader.read_from_section() { - Ok(v) => return Ok(v), - Err(e) => Box::new(e), - }; - let generated = match reader.generate_from_text() { - Ok(v) => return Ok(v), - Err(e) => Box::new(e), - }; - Err(Error::Aggregate { - program_headers, - section, - generated, - }) +/// Types which can be read from an `impl ModuleMemory`. +pub trait ReadFromModule: Sized { + fn read_from_module(module_memory: impl ModuleMemory) -> Result; } -pub struct ElfBuildIdReader { +/// 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)?; + 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, + }) + } +} + +/// The module SONAME. +#[derive(Default, Clone, Debug)] +pub struct SoName(pub String); + +impl ReadFromModule for SoName { + fn read_from_module(module_memory: impl ModuleMemory) -> Result { + ModuleReader::new(module_memory) + .and_then(|r| r.soname()) + .map(SoName) + } +} + +pub struct ModuleReader { module_memory: T, header: elf::Header, context: Ctx, } -impl ElfBuildIdReader { +impl ModuleReader { pub fn new(module_memory: T) -> 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 @@ -97,15 +121,57 @@ impl ElfBuildIdReader { let header_data = read(&module_memory, 0, header_size as u64)?; let header = elf::Elf::parse_header(&header_data)?; let context = Ctx::new(header.container()?, header.endianness()?); - Ok(ElfBuildIdReader { + Ok(ModuleReader { module_memory, header, context, }) } + pub fn soname(&self) -> Result { + let section_headers = self.read_section_headers()?; + + let strtab_section_header = section_headers + .get(self.header.e_shstrndx as usize) + .ok_or(Error::NoStrTab)?; + + let dynamic_section_header = section_headers + .iter() + .find(|h| h.sh_type == elf::section_header::SHT_DYNAMIC) + .ok_or(Error::NoDynamicSection)?; + + let dynamic_section: &[u8] = &read( + &self.module_memory, + dynamic_section_header.sh_offset, + dynamic_section_header.sh_size, + )?; + + let mut offset = 0; + loop { + use scroll::Pread; + let dyn_: elf::dynamic::Dyn = dynamic_section.gread_with(&mut offset, self.context)?; + if dyn_.d_tag == elf::dynamic::DT_SONAME { + let strtab_offset = dyn_.d_val; + if strtab_offset < strtab_section_header.sh_size { + let name = read( + &self.module_memory, + strtab_section_header.sh_offset + strtab_offset, + strtab_section_header.sh_size - strtab_offset, + )?; + return CStr::from_bytes_until_nul(&name) + .map(|s| s.to_string_lossy().into_owned()) + .map_err(|_| Error::StrTabNoNulByte); + } + } + if dyn_.d_tag == elf::dynamic::DT_NULL { + break; + } + } + Err(Error::NoSoNameEntry) + } + /// Read the build id from a program header note. - pub fn read_from_program_headers(&self) -> Result, Error> { + pub fn build_id_from_program_headers(&self) -> Result, Error> { if self.header.e_phoff == 0 { return Err(Error::NoProgramHeaderNote); } @@ -134,7 +200,7 @@ impl ElfBuildIdReader { } /// Read the build id from a notes section. - pub fn read_from_section(&self) -> Result, Error> { + pub fn build_id_from_section(&self) -> Result, Error> { let section_headers = self.read_section_headers()?; let strtab_section_header = section_headers @@ -173,7 +239,7 @@ impl ElfBuildIdReader { } /// Generate a build id by hashing the first page of the text section. - pub fn generate_from_text(&self) -> Result, Error> { + pub fn build_id_generate_from_text(&self) -> Result, Error> { let Some(text_header) = self .read_section_headers()? .into_iter() @@ -288,9 +354,9 @@ mod test { ]; #[test] - fn program_headers() { - let reader = ElfBuildIdReader::new(TINY_ELF).unwrap(); - let id = reader.read_from_program_headers().unwrap(); + fn build_id_program_headers() { + let reader = ModuleReader::new(TINY_ELF).unwrap(); + let id = reader.build_id_from_program_headers().unwrap(); assert_eq!( id, vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] @@ -298,9 +364,9 @@ mod test { } #[test] - fn section() { - let reader = ElfBuildIdReader::new(TINY_ELF).unwrap(); - let id = reader.read_from_section().unwrap(); + fn build_id_section() { + let reader = ModuleReader::new(TINY_ELF).unwrap(); + let id = reader.build_id_from_section().unwrap(); assert_eq!( id, vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] @@ -308,9 +374,9 @@ mod test { } #[test] - fn text_hash() { - let reader = ElfBuildIdReader::new(TINY_ELF).unwrap(); - let id = reader.generate_from_text().unwrap(); + fn build_id_text_hash() { + let reader = ModuleReader::new(TINY_ELF).unwrap(); + let id = reader.build_id_generate_from_text().unwrap(); assert_eq!( id, vec![0x6a, 0x3c, 0x58, 0x31, 0xff, 0x0f, 0x05, 0, 0, 0, 0, 0, 0, 0, 0, 0] diff --git a/src/linux/ptrace_dumper.rs b/src/linux/ptrace_dumper.rs index bcb0a8e4..d05e53ca 100644 --- a/src/linux/ptrace_dumper.rs +++ b/src/linux/ptrace_dumper.rs @@ -2,9 +2,9 @@ use crate::linux::android::late_process_mappings; use crate::linux::{ auxv_reader::{AuxvType, ProcfsAuxvIter}, - build_id_reader, errors::{DumperError, InitError, ThreadInfoError}, maps_reader::MappingInfo, + module_reader, thread_info::{Pid, ThreadInfo}, }; #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] @@ -557,28 +557,35 @@ impl PtraceDumper { }) } - pub fn elf_identifier_for_mapping_index(&mut self, idx: usize) -> Result, DumperError> { + pub fn from_process_memory_for_index( + &self, + idx: usize, + ) -> Result { assert!(idx < self.mappings.len()); - Self::elf_identifier_for_mapping(&mut self.mappings[idx], self.pid) + self.from_process_memory_for_mapping(&self.mappings[idx]) } - pub fn elf_identifier_for_mapping( - mapping: &mut MappingInfo, - pid: Pid, - ) -> Result, DumperError> { - let result = if pid == std::process::id().try_into()? { + pub fn from_process_memory_for_mapping( + &self, + mapping: &MappingInfo, + ) -> Result { + if std::process::id() + .try_into() + .map(|v: Pid| v == self.pid) + .unwrap_or(false) + { let mem_slice = unsafe { std::slice::from_raw_parts(mapping.start_address as *const u8, mapping.size) }; - build_id_reader::read_build_id(mem_slice) + T::read_from_module(mem_slice) } else { - struct ProcessReader { + struct ProcessModuleMemory { pid: Pid, start_address: u64, } - impl build_id_reader::ModuleMemory for ProcessReader { + impl module_reader::ModuleMemory for ProcessModuleMemory { type Memory = Vec; fn read_module_memory( @@ -596,12 +603,11 @@ impl PtraceDumper { } } - build_id_reader::read_build_id(ProcessReader { - pid, + T::read_from_module(ProcessModuleMemory { + pid: self.pid, start_address: mapping.start_address as u64, }) - }; - - result.map_err(|e| e.into()) + } + .map_err(|e| e.into()) } } diff --git a/src/linux/sections/mappings.rs b/src/linux/sections/mappings.rs index 9012ae35..2c45d1e4 100644 --- a/src/linux/sections/mappings.rs +++ b/src/linux/sections/mappings.rs @@ -1,5 +1,6 @@ use super::*; use crate::linux::maps_reader::MappingInfo; +use crate::linux::module_reader::{BuildId, SoName}; /// Write information about the mappings in effect. Because we are using the /// minidump format, the information about the mappings is pretty limited. @@ -23,9 +24,8 @@ pub fn write( { continue; } - // Note: elf_identifier_for_mapping_index() can manipulate the |mapping.name|. - let identifier = dumper - .elf_identifier_for_mapping_index(map_idx) + let BuildId(identifier) = dumper + .from_process_memory_for_index(map_idx) .unwrap_or_default(); // If the identifier is all 0, its an uninteresting mapping (bmc#1676109) @@ -33,14 +33,19 @@ pub fn write( continue; } - let module = fill_raw_module(buffer, &dumper.mappings[map_idx], &identifier)?; + let soname = dumper + .from_process_memory_for_index(map_idx) + .ok() + .map(|SoName(n)| n); + + let module = fill_raw_module(buffer, &dumper.mappings[map_idx], &identifier, soname)?; modules.push(module); } // Next write all the mappings provided by the caller for user in &config.user_mapping_list { // GUID was provided by caller. - let module = fill_raw_module(buffer, &user.mapping, &user.identifier)?; + let module = fill_raw_module(buffer, &user.mapping, &user.identifier, None)?; modules.push(module); } @@ -63,6 +68,7 @@ fn fill_raw_module( buffer: &mut DumpBuf, mapping: &MappingInfo, identifier: &[u8], + soname: Option, ) -> Result { let cv_record = if identifier.is_empty() { // Just zeroes @@ -84,7 +90,7 @@ fn fill_raw_module( }; let (file_path, _, so_version) = mapping - .get_mapping_effective_path_name_and_version() + .get_mapping_effective_path_name_and_version(soname) .map_err(|e| errors::SectionMappingsError::GetEffectivePathError(mapping.clone(), e))?; let name_header = write_string_to_location(buffer, file_path.to_string_lossy().as_ref())?; diff --git a/tests/linux_minidump_writer.rs b/tests/linux_minidump_writer.rs index 6cf75e4b..5983e12b 100644 --- a/tests/linux_minidump_writer.rs +++ b/tests/linux_minidump_writer.rs @@ -5,11 +5,11 @@ use minidump::*; use minidump_common::format::{GUID, MINIDUMP_STREAM_TYPE::*}; use minidump_writer::{ app_memory::AppMemory, - build_id_reader::read_build_id, crash_context::CrashContext, errors::*, maps_reader::{MappingEntry, MappingInfo, SystemMappingInfo}, minidump_writer::MinidumpWriter, + module_reader::{BuildId, ReadFromModule}, ptrace_dumper::PtraceDumper, thread_info::Pid, }; @@ -705,7 +705,8 @@ fn with_deleted_binary() { let pid = child.id() as i32; - let mut build_id = read_build_id(mem_slice.as_slice()).expect("Failed to get build_id"); + let BuildId(mut build_id) = + BuildId::read_from_module(mem_slice.as_slice()).expect("Failed to get build_id"); std::fs::remove_file(&binary_copy).expect("Failed to remove binary"); From f0b448059042f8f5aa47e4a418978da7f1eea908 Mon Sep 17 00:00:00 2001 From: Alex Franchuk Date: Mon, 22 Apr 2024 15:43:53 -0400 Subject: [PATCH 2/4] Add test for SONAME and fix the implementation. --- src/linux/errors.rs | 2 + src/linux/module_reader.rs | 174 +++++++++++++++++++++++-------------- 2 files changed, 113 insertions(+), 63 deletions(-) diff --git a/src/linux/errors.rs b/src/linux/errors.rs index 1112c13a..6e8c4aea 100644 --- a/src/linux/errors.rs +++ b/src/linux/errors.rs @@ -281,6 +281,8 @@ pub enum ModuleReaderError { section: Box, generated: Box, }, + #[error("no dynamic string table section")] + NoDynStrSection, #[error("a string in the strtab did not have a terminating nul byte")] StrTabNoNulByte, #[error("no SONAME found in dynamic linking information")] diff --git a/src/linux/module_reader.rs b/src/linux/module_reader.rs index c0a5ee1e..671d0074 100644 --- a/src/linux/module_reader.rs +++ b/src/linux/module_reader.rs @@ -62,6 +62,36 @@ fn build_id_from_bytes(data: &[u8]) -> Vec { ) } +// `name` should be null-terminated +fn section_header_with_name<'a>( + section_headers: &'a elf::SectionHeaders, + strtab_index: usize, + name: &[u8], + module_memory: &impl ModuleMemory, +) -> 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; + if sh_name >= strtab_section_header.sh_size { + log::warn!("invalid sh_name offset"); + continue; + } + if sh_name + name.len() as u64 >= strtab_section_header.sh_size { + // This can't be a match. + continue; + } + let n = read( + module_memory, + strtab_section_header.sh_offset + sh_name, + name.len() as u64, + )?; + if name == &*n { + return Ok(Some(header)); + } + } + Ok(None) +} + /// Types which can be read from an `impl ModuleMemory`. pub trait ReadFromModule: Sized { fn read_from_module(module_memory: impl ModuleMemory) -> Result; @@ -131,15 +161,23 @@ impl ModuleReader { pub fn soname(&self) -> Result { let section_headers = self.read_section_headers()?; - let strtab_section_header = section_headers - .get(self.header.e_shstrndx as usize) - .ok_or(Error::NoStrTab)?; - let dynamic_section_header = section_headers .iter() .find(|h| h.sh_type == elf::section_header::SHT_DYNAMIC) .ok_or(Error::NoDynamicSection)?; + let dynstr_section_header = + match section_headers.get(dynamic_section_header.sh_link as usize) { + Some(header) if header.sh_type == elf::section_header::SHT_STRTAB => header, + _ => section_header_with_name( + §ion_headers, + self.header.e_shstrndx as usize, + b".dynstr\0", + &self.module_memory, + )? + .ok_or(Error::NoDynStrSection)?, + }; + let dynamic_section: &[u8] = &read( &self.module_memory, dynamic_section_header.sh_offset, @@ -152,11 +190,11 @@ impl ModuleReader { let dyn_: elf::dynamic::Dyn = dynamic_section.gread_with(&mut offset, self.context)?; if dyn_.d_tag == elf::dynamic::DT_SONAME { let strtab_offset = dyn_.d_val; - if strtab_offset < strtab_section_header.sh_size { + if strtab_offset < dynstr_section_header.sh_size { let name = read( &self.module_memory, - strtab_section_header.sh_offset + strtab_offset, - strtab_section_header.sh_size - strtab_offset, + dynstr_section_header.sh_offset + strtab_offset, + dynstr_section_header.sh_size - strtab_offset, )?; return CStr::from_bytes_until_nul(&name) .map(|s| s.to_string_lossy().into_owned()) @@ -203,39 +241,19 @@ impl ModuleReader { pub fn build_id_from_section(&self) -> Result, Error> { let section_headers = self.read_section_headers()?; - let strtab_section_header = section_headers - .get(self.header.e_shstrndx as usize) - .ok_or(Error::NoStrTab)?; + let header = section_header_with_name( + §ion_headers, + self.header.e_shstrndx as usize, + NOTE_SECTION_NAME, + &self.module_memory, + )? + .ok_or(Error::NoSectionNote)?; - for header in §ion_headers { - let sh_name = header.sh_name as u64; - if sh_name >= strtab_section_header.sh_size { - log::warn!("invalid sh_name offset"); - continue; - } - if sh_name + NOTE_SECTION_NAME.len() as u64 >= strtab_section_header.sh_size { - // This can't be a match. - continue; - } - let name = read( - &self.module_memory, - strtab_section_header.sh_offset + sh_name, - NOTE_SECTION_NAME.len() as u64, - )?; - if NOTE_SECTION_NAME == &*name { - return match self.find_build_id_note( - header.sh_offset, - header.sh_size, - header.sh_addralign, - ) { - Ok(Some(v)) => Ok(v), - Ok(None) => Err(Error::NoSectionNote), - Err(e) => Err(e), - }; - } + match self.find_build_id_note(header.sh_offset, header.sh_size, header.sh_addralign) { + Ok(Some(v)) => Ok(v), + Ok(None) => Err(Error::NoSectionNote), + Err(e) => Err(e), } - - Err(Error::NoSectionNote) } /// Generate a build id by hashing the first page of the text section. @@ -309,48 +327,71 @@ mod test { /// * ELF header /// * program header: text segment /// * program header: note + /// * program header: dynamic /// * section header: null /// * section header: .text /// * section header: .note.gnu.build-id /// * section header: .shstrtab + /// * section header: .dynamic + /// * section header: .dynstr /// * note header (build id note) /// * shstrtab + /// * dynamic (SONAME) + /// * dynstr (SONAME string = libfoo.so.1) /// * program (calls exit(0)) const TINY_ELF: &[u8] = &[ 0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x02, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00, 0xf4, 0x01, 0x40, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x38, 0x00, 0x02, 0x00, 0x40, 0x00, - 0x04, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0xf4, 0x01, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0xf4, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00, 0xea, 0x02, 0x40, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe8, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x38, 0x00, 0x03, 0x00, 0x40, 0x00, + 0x06, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0xea, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xea, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0xb0, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x68, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xbd, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbd, 0x02, 0x40, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xea, 0x02, 0x40, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xea, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0xf4, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf4, 0x01, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0xb0, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb0, 0x01, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x68, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0x01, 0x40, 0x00, 0x00, 0x00, - 0x00, 0x00, 0xd0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, - 0x00, 0x10, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x47, 0x4e, 0x55, 0x00, 0x01, 0x02, - 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x00, - 0x2e, 0x74, 0x65, 0x78, 0x74, 0x00, 0x2e, 0x6e, 0x6f, 0x74, 0x65, 0x2e, 0x67, 0x6e, 0x75, - 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2d, 0x69, 0x64, 0x00, 0x2e, 0x73, 0x68, 0x73, 0x74, - 0x72, 0x74, 0x61, 0x62, 0x00, 0x6a, 0x3c, 0x58, 0x31, 0xff, 0x0f, 0x05, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbd, 0x02, 0x40, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xbd, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdd, 0x02, + 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdd, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x47, 0x4e, + 0x55, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, + 0x0e, 0x0f, 0x10, 0x00, 0x2e, 0x74, 0x65, 0x78, 0x74, 0x00, 0x2e, 0x6e, 0x6f, 0x74, 0x65, + 0x2e, 0x67, 0x6e, 0x75, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2d, 0x69, 0x64, 0x00, 0x2e, + 0x73, 0x68, 0x73, 0x74, 0x72, 0x74, 0x61, 0x62, 0x00, 0x2e, 0x64, 0x79, 0x6e, 0x61, 0x6d, + 0x69, 0x63, 0x00, 0x2e, 0x64, 0x79, 0x6e, 0x73, 0x74, 0x72, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6c, + 0x69, 0x62, 0x66, 0x6f, 0x6f, 0x2e, 0x73, 0x6f, 0x2e, 0x31, 0x00, 0x6a, 0x3c, 0x58, 0x31, + 0xff, 0x0f, 0x05, 0x66, 0x6f, 0x6f, 0x2e, 0x73, 0x6f, 0x2e, 0x31, 0x00, 0x6a, 0x3c, 0x58, + 0x31, 0xff, 0x0f, 0x05, 0x05, ]; #[test] @@ -382,4 +423,11 @@ mod test { vec![0x6a, 0x3c, 0x58, 0x31, 0xff, 0x0f, 0x05, 0, 0, 0, 0, 0, 0, 0, 0, 0] ); } + + #[test] + fn soname() { + let reader = ModuleReader::new(TINY_ELF).unwrap(); + let soname = reader.soname().unwrap(); + assert_eq!(soname, "libfoo.so.1"); + } } From cfe66155ec78b9a40a5e080f623d12d254c5154d Mon Sep 17 00:00:00 2001 From: Alex Franchuk Date: Tue, 30 Apr 2024 15:29:52 -0400 Subject: [PATCH 3/4] Support getting the SONAME from program headers. --- src/linux/errors.rs | 15 ++- src/linux/module_reader.rs | 264 +++++++++++++++++++++++++++++-------- src/linux/ptrace_dumper.rs | 9 +- 3 files changed, 230 insertions(+), 58 deletions(-) diff --git a/src/linux/errors.rs b/src/linux/errors.rs index 6e8c4aea..29486e47 100644 --- a/src/linux/errors.rs +++ b/src/linux/errors.rs @@ -266,9 +266,11 @@ pub enum ModuleReaderError { NoStrTab, #[error("no build id note sections")] NoSectionNote, - #[error("the ELF file contains no sections")] + #[error("the ELF data contains no program headers")] + NoProgramHeaders, + #[error("the ELF data contains no sections")] NoSections, - #[error("the ELF file does not have a .text section from which to generate a build id")] + #[error("the ELF data does not have a .text section from which to generate a build id")] NoTextSection, #[error( "failed to calculate build id\n\ @@ -289,4 +291,13 @@ pub enum ModuleReaderError { NoSoNameEntry, #[error("no dynamic linking information section")] NoDynamicSection, + #[error( + "failed to retrieve soname\n\ + ... from program headers: {program_headers}\n\ + ... from sections: {section}" + )] + NoSoName { + program_headers: Box, + section: Box, + }, } diff --git a/src/linux/module_reader.rs b/src/linux/module_reader.rs index 671d0074..a3c9a9ac 100644 --- a/src/linux/module_reader.rs +++ b/src/linux/module_reader.rs @@ -11,7 +11,20 @@ const NOTE_SECTION_NAME: &[u8] = b".note.gnu.build-id\0"; pub trait ModuleMemory { type Memory: std::ops::Deref; + /// Read memory from the module. fn read_module_memory(&self, offset: u64, length: u64) -> std::io::Result; + + /// 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 + } + + /// 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() + } } impl<'a> ModuleMemory for &'a [u8] { @@ -28,6 +41,22 @@ impl<'a> ModuleMemory for &'a [u8] { } } +/// 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; + + fn read_module_memory(&self, offset: u64, length: u64) -> std::io::Result { + self.0.read_module_memory(offset, length) + } + + fn base_address(&self) -> Option { + Some(self.1) + } +} + fn read(mem: &T, offset: u64, length: u64) -> Result { mem.read_module_memory(offset, length) .map_err(|error| Error::ReadModuleMemory { @@ -73,7 +102,7 @@ fn section_header_with_name<'a>( for header in section_headers { let sh_name = header.sh_name as u64; if sh_name >= strtab_section_header.sh_size { - log::warn!("invalid sh_name offset"); + log::warn!("invalid sh_name offset for {:?}", name); continue; } if sh_name + name.len() as u64 >= strtab_section_header.sh_size { @@ -124,15 +153,58 @@ impl ReadFromModule for BuildId { } } +struct DynIter<'a> { + data: &'a [u8], + offset: usize, + ctx: Ctx, +} + +impl<'a> DynIter<'a> { + pub fn new(data: &'a [u8], ctx: Ctx) -> Self { + DynIter { + data, + offset: 0, + ctx, + } + } +} + +impl<'a> Iterator for DynIter<'a> { + type Item = Result; + + fn next(&mut self) -> Option { + use scroll::Pread; + let dyn_: elf::dynamic::Dyn = match self.data.gread_with(&mut self.offset, self.ctx) { + Ok(v) => v, + Err(e) => return Some(Err(e.into())), + }; + if dyn_.d_tag == elf::dynamic::DT_NULL { + None + } else { + Some(Ok(dyn_)) + } + } +} + /// The module SONAME. #[derive(Default, Clone, Debug)] pub struct SoName(pub String); impl ReadFromModule for SoName { fn read_from_module(module_memory: impl ModuleMemory) -> Result { - ModuleReader::new(module_memory) - .and_then(|r| r.soname()) - .map(SoName) + 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, + }) } } @@ -158,7 +230,49 @@ impl ModuleReader { }) } - pub fn soname(&self) -> Result { + /// Read the SONAME using program headers to locate dynamic library information. + pub fn soname_from_program_headers(&self) -> Result { + let program_headers = self.read_program_headers()?; + + let dynamic_segment_header = program_headers + .iter() + .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 mut soname_strtab_offset = None; + let mut strtab_addr = None; + let mut strtab_size = None; + 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), + elf::dynamic::DT_STRTAB => strtab_addr = Some(dyn_.d_val), + elf::dynamic::DT_STRSZ => strtab_size = Some(dyn_.d_val), + _ => (), + } + } + + 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) + } + } + } + + /// Read the SONAME using section headers to locate dynamic library information. + pub fn soname_from_sections(&self) -> Result { let section_headers = self.read_section_headers()?; let dynamic_section_header = section_headers @@ -180,50 +294,30 @@ impl ModuleReader { let dynamic_section: &[u8] = &read( &self.module_memory, - dynamic_section_header.sh_offset, + self.section_offset(dynamic_section_header), dynamic_section_header.sh_size, )?; - let mut offset = 0; - loop { - use scroll::Pread; - let dyn_: elf::dynamic::Dyn = dynamic_section.gread_with(&mut offset, self.context)?; + for dyn_ in DynIter::new(dynamic_section, self.context) { + let dyn_ = dyn_?; if dyn_.d_tag == elf::dynamic::DT_SONAME { - let strtab_offset = dyn_.d_val; - if strtab_offset < dynstr_section_header.sh_size { - let name = read( - &self.module_memory, - dynstr_section_header.sh_offset + strtab_offset, - dynstr_section_header.sh_size - strtab_offset, - )?; - return CStr::from_bytes_until_nul(&name) - .map(|s| s.to_string_lossy().into_owned()) - .map_err(|_| Error::StrTabNoNulByte); + let name_offset = dyn_.d_val; + if name_offset < dynstr_section_header.sh_size { + return self.read_name_from_strtab( + self.section_offset(dynstr_section_header), + dynstr_section_header.sh_size, + name_offset, + ); } } - if dyn_.d_tag == elf::dynamic::DT_NULL { - break; - } } + Err(Error::NoSoNameEntry) } /// Read the build id from a program header note. pub fn build_id_from_program_headers(&self) -> Result, Error> { - if self.header.e_phoff == 0 { - return Err(Error::NoProgramHeaderNote); - } - let program_headers_data = read( - &self.module_memory, - self.header.e_phoff, - self.header.e_phentsize as u64 * self.header.e_phnum as u64, - )?; - let program_headers = elf::ProgramHeader::parse( - &program_headers_data, - 0, - self.header.e_phnum as usize, - self.context, - )?; + let program_headers = self.read_program_headers()?; for header in program_headers { if header.p_type != elf::program_header::PT_NOTE { continue; @@ -272,6 +366,58 @@ impl ModuleReader { 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() { + (header.p_vaddr, header.p_memsz) + } else { + (header.p_offset, header.p_filesz) + }; + + read(&self.module_memory, offset, size) + } + + fn read_name_from_strtab( + &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, + )?; + 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() { + header.sh_addr + } else { + header.sh_offset + } + } + + fn read_program_headers(&self) -> Result { + if self.header.e_phoff == 0 { + return Err(Error::NoProgramHeaders); + } + let program_headers_data = read( + &self.module_memory, + self.header.e_phoff, + self.header.e_phentsize as u64 * self.header.e_phnum as u64, + )?; + let program_headers = elf::ProgramHeader::parse( + &program_headers_data, + 0, + self.header.e_phnum as usize, + self.context, + )?; + Ok(program_headers) + } + fn read_section_headers(&self) -> Result { if self.header.e_shoff == 0 { return Err(Error::NoSections); @@ -336,16 +482,16 @@ mod test { /// * section header: .dynstr /// * note header (build id note) /// * shstrtab - /// * dynamic (SONAME) + /// * dynamic (SONAME/STRTAB/STRSZ) /// * dynstr (SONAME string = libfoo.so.1) /// * program (calls exit(0)) const TINY_ELF: &[u8] = &[ 0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x02, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00, 0xea, 0x02, 0x40, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, 0x03, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x38, 0x00, 0x03, 0x00, 0x40, 0x00, - 0x06, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0xea, 0x02, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0xea, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x0a, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x03, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -353,15 +499,15 @@ mod test { 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbd, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbd, 0x02, 0x40, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xea, 0x02, 0x40, - 0x00, 0x00, 0x00, 0x00, 0x00, 0xea, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x03, 0x40, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -374,11 +520,11 @@ mod test { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbd, 0x02, 0x40, 0x00, 0x00, 0x00, - 0x00, 0x00, 0xbd, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xbd, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, - 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdd, 0x02, - 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdd, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0x02, + 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x47, 0x4e, @@ -387,11 +533,12 @@ mod test { 0x2e, 0x67, 0x6e, 0x75, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2d, 0x69, 0x64, 0x00, 0x2e, 0x73, 0x68, 0x73, 0x74, 0x72, 0x74, 0x61, 0x62, 0x00, 0x2e, 0x64, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x00, 0x2e, 0x64, 0x79, 0x6e, 0x73, 0x74, 0x72, 0x00, 0x0e, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6c, - 0x69, 0x62, 0x66, 0x6f, 0x6f, 0x2e, 0x73, 0x6f, 0x2e, 0x31, 0x00, 0x6a, 0x3c, 0x58, 0x31, - 0xff, 0x0f, 0x05, 0x66, 0x6f, 0x6f, 0x2e, 0x73, 0x6f, 0x2e, 0x31, 0x00, 0x6a, 0x3c, 0x58, - 0x31, 0xff, 0x0f, 0x05, 0x05, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x6c, 0x69, 0x62, 0x66, 0x6f, 0x6f, 0x2e, 0x73, 0x6f, 0x2e, 0x31, 0x00, 0x6a, 0x3c, + 0x58, 0x31, 0xff, 0x0f, 0x05, ]; #[test] @@ -425,9 +572,16 @@ mod test { } #[test] - fn soname() { + fn soname_program_headers() { + let reader = ModuleReader::new(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).unwrap(); - let soname = reader.soname().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 d05e53ca..4db81d3d 100644 --- a/src/linux/ptrace_dumper.rs +++ b/src/linux/ptrace_dumper.rs @@ -578,7 +578,10 @@ impl PtraceDumper { let mem_slice = unsafe { std::slice::from_raw_parts(mapping.start_address as *const u8, mapping.size) }; - T::read_from_module(mem_slice) + T::read_from_module(module_reader::ModuleMemoryAtAddress( + mem_slice, + mapping.start_address as u64, + )) } else { struct ProcessModuleMemory { pid: Pid, @@ -601,6 +604,10 @@ impl PtraceDumper { ) .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e)) } + + fn base_address(&self) -> Option { + Some(self.start_address) + } } T::read_from_module(ProcessModuleMemory { From cfdbc8f7ffd8133819ea33530f1fc79118bc1896 Mon Sep 17 00:00:00 2001 From: Alex Franchuk Date: Thu, 30 May 2024 14:16:30 -0400 Subject: [PATCH 4/4] Ensure build ids are created in android binaries. --- .cargo/config.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.cargo/config.toml b/.cargo/config.toml index ee35b173..e32d5cfd 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -11,4 +11,6 @@ # ] [target.x86_64-linux-android] linker = "x86_64-linux-android30-clang" +# By default the linker _doesn't_ generate a build-id, however we want one for our tests. +rustflags = ["-C", "link-args=-Wl,--build-id=sha1"] runner = [".cargo/android-runner.sh"]