Skip to content

Commit

Permalink
Support getting the SONAME from program headers.
Browse files Browse the repository at this point in the history
  • Loading branch information
afranchuk committed Apr 30, 2024
1 parent f0b4480 commit 8938433
Show file tree
Hide file tree
Showing 2 changed files with 210 additions and 59 deletions.
15 changes: 13 additions & 2 deletions src/linux/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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\
Expand All @@ -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<Self>,
section: Box<Self>,
},
}
254 changes: 197 additions & 57 deletions src/linux/module_reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,14 @@ const NOTE_SECTION_NAME: &[u8] = b".note.gnu.build-id\0";
pub trait ModuleMemory {
type Memory: std::ops::Deref<Target = [u8]>;

/// Read memory from the module.
fn read_module_memory(&self, offset: u64, length: u64) -> std::io::Result<Self::Memory>;

/// Whether the module memory is from a module loaded in the address space of a program.
/// The default implementation assumes this to be true.
fn is_loaded_in_program(&self) -> bool {
true
}
}

impl<'a> ModuleMemory for &'a [u8] {
Expand All @@ -28,6 +35,22 @@ impl<'a> ModuleMemory for &'a [u8] {
}
}

/// Indicate that a ModuleMemory implementation is read directly from a file (the module is not
/// loaded by the dynamic linker).
pub struct ModuleMemoryFromFile<T>(T);

impl<T: ModuleMemory> ModuleMemory for ModuleMemoryFromFile<T> {
type Memory = T::Memory;

fn read_module_memory(&self, offset: u64, length: u64) -> std::io::Result<Self::Memory> {
self.0.read_module_memory(offset, length)
}

fn is_loaded_in_program(&self) -> bool {
false
}
}

fn read<T: ModuleMemory>(mem: &T, offset: u64, length: u64) -> Result<T::Memory, Error> {
mem.read_module_memory(offset, length)
.map_err(|error| Error::ReadModuleMemory {
Expand Down Expand Up @@ -73,7 +96,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 {
Expand Down Expand Up @@ -124,15 +147,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<elf::dynamic::Dyn, Error>;

fn next(&mut self) -> Option<Self::Item> {
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<Self, Error> {
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,
})
}
}

Expand All @@ -158,7 +224,41 @@ impl<T: ModuleMemory> ModuleReader<T> {
})
}

pub fn soname(&self) -> Result<String, Error> {
/// Read the SONAME using program headers to locate dynamic library information.
pub fn soname_from_program_headers(&self) -> Result<String, Error> {
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(addr), Some(size), Some(offset)) => {
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<String, Error> {
let section_headers = self.read_section_headers()?;

let dynamic_section_header = section_headers
Expand All @@ -180,50 +280,30 @@ impl<T: ModuleMemory> ModuleReader<T> {

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<Vec<u8>, 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;
Expand Down Expand Up @@ -272,6 +352,58 @@ impl<T: ModuleMemory> ModuleReader<T> {
Ok(build_id_from_bytes(&text_data))
}

fn read_segment(&self, header: &elf::ProgramHeader) -> Result<T::Memory, Error> {
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<String, Error> {
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<elf::ProgramHeaders, Error> {
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<elf::SectionHeaders, Error> {
if self.header.e_shoff == 0 {
return Err(Error::NoSections);
Expand Down Expand Up @@ -336,32 +468,32 @@ 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] = &[
const TINY_ELF: ModuleMemoryFromFile<&[u8]> = ModuleMemoryFromFile(&[
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,
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, 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,
Expand All @@ -374,11 +506,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,
Expand All @@ -387,12 +519,13 @@ 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]
fn build_id_program_headers() {
Expand Down Expand Up @@ -425,9 +558,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");
}
}

0 comments on commit 8938433

Please sign in to comment.