From 0c2a2e8772f56affdbbe0d1478c774fe5bc91218 Mon Sep 17 00:00:00 2001 From: Alex Franchuk Date: Tue, 30 Apr 2024 15:29:52 -0400 Subject: [PATCH] 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..f12f0e2d 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 {