From 2e05d703020f14e22ec4c486ee7b85c3109b899d Mon Sep 17 00:00:00 2001 From: Christopher Cole Date: Thu, 10 Nov 2022 13:31:20 -0800 Subject: [PATCH] Handle the case where ehdr.e_phnum > 0xffff If the number of segments is greater than or equal to PN_XNUM (0xffff), e_phnum is set to PN_XNUM, and the actual number of program header table entries is contained in the sh_info field of the section header at index 0. The phnum.m68k.so is a sample object file that tests this code path but then actually only has 1 segment - it just indirects phnum through shdr0. --- sample-objects/phnum.m68k.so | Bin 0 -> 128 bytes src/abi.rs | 7 +++++ src/elf_bytes.rs | 58 ++++++++++++++++++++++++++++++---- src/elf_stream.rs | 59 ++++++++++++++++++++++++++++++----- src/file.rs | 25 +-------------- 5 files changed, 111 insertions(+), 38 deletions(-) create mode 100644 sample-objects/phnum.m68k.so diff --git a/sample-objects/phnum.m68k.so b/sample-objects/phnum.m68k.so new file mode 100644 index 0000000000000000000000000000000000000000..5f32cfa33a5d470eb9a3dea22c5249e9780603a6 GIT binary patch literal 128 zcmb<-^>JflVq|~=W(F1@8zv9~WSfBHOc)gY|7XyE%fK~YFkpH?5^O*WG6Mz_fD8o& KCI*NtKn?)I{skHU literal 0 HcmV?d00001 diff --git a/src/abi.rs b/src/abi.rs index 1f7adb4..0eb0402 100644 --- a/src/abi.rs +++ b/src/abi.rs @@ -328,6 +328,13 @@ pub const EV_NONE: u8 = 0; /// Current version pub const EV_CURRENT: u8 = 1; +/// If the number of program headers is greater than or equal to PN_XNUM (0xffff), +/// this member has the value PN_XNUM (0xffff). The actual number of +/// program header table entries is contained in the sh_info field of the +/// section header at index 0. Otherwise, the sh_info member of the initial +/// section header entry contains the value zero. +pub const PN_XNUM: u16 = 0xffff; + /// PF_* define constants for the ELF Program Header's p_flags field. /// Represented as Elf32_Word in Elf32_Ehdr and Elf64_Word in Elf64_Ehdr which /// are both 4-byte unsigned integers with 4-byte alignment diff --git a/src/elf_bytes.rs b/src/elf_bytes.rs index 3e2760c..81035a2 100644 --- a/src/elf_bytes.rs +++ b/src/elf_bytes.rs @@ -112,13 +112,32 @@ fn find_phdrs<'data, E: EndianParse>( ehdr: &FileHeader, data: &'data [u8], ) -> Result>, ParseError> { - match ehdr.get_phdrs_data_range()? { - Some((start, end)) => { - let buf = data.get_bytes(start..end)?; - Ok(Some(SegmentTable::new(endian, ehdr.class, buf))) - } - None => Ok(None), + // It's Ok to have no program headers + if ehdr.e_phoff == 0 { + return Ok(None); } + + // If the number of segments is greater than or equal to PN_XNUM (0xffff), + // e_phnum is set to PN_XNUM, and the actual number of program header table + // entries is contained in the sh_info field of the section header at index 0. + let mut phnum = ehdr.e_phnum as usize; + if phnum == abi::PN_XNUM as usize { + let shoff: usize = ehdr.e_shoff.try_into()?; + let mut offset = shoff; + let shdr0 = SectionHeader::parse_at(endian, ehdr.class, &mut offset, data)?; + phnum = shdr0.sh_info.try_into()?; + } + + // Validate phentsize before trying to read the table so that we can error early for corrupted files + let entsize = ProgramHeader::validate_entsize(ehdr.class, ehdr.e_phentsize as usize)?; + + let phoff: usize = ehdr.e_phoff.try_into()?; + let size = entsize + .checked_mul(phnum) + .ok_or(ParseError::IntegerOverflow)?; + let end = phoff.checked_add(size).ok_or(ParseError::IntegerOverflow)?; + let buf = data.get_bytes(phoff..end)?; + Ok(Some(SegmentTable::new(endian, ehdr.class, buf))) } /// This struct collects the common sections found in ELF objects @@ -858,6 +877,33 @@ mod interface_tests { ); } + #[test] + fn segments_phnum_in_shdr0() { + let path = std::path::PathBuf::from("sample-objects/phnum.m68k.so"); + let file_data = std::fs::read(path).expect("Could not read file."); + let slice = file_data.as_slice(); + let file = ElfBytes::::minimal_parse(slice).expect("Open test1"); + + let segments: Vec = file + .segments() + .expect("File should have a segment table") + .iter() + .collect(); + assert_eq!( + segments[0], + ProgramHeader { + p_type: abi::PT_PHDR, + p_offset: 92, + p_vaddr: 0, + p_paddr: 0, + p_filesz: 32, + p_memsz: 32, + p_flags: 0x20003, + p_align: 0x40000, + } + ); + } + #[test] fn section_headers() { let path = std::path::PathBuf::from("sample-objects/basic.x86_64"); diff --git a/src/elf_stream.rs b/src/elf_stream.rs index f914949..5280817 100644 --- a/src/elf_stream.rs +++ b/src/elf_stream.rs @@ -80,15 +80,37 @@ fn parse_program_headers( ehdr: &FileHeader, reader: &mut CachingReader, ) -> Result, ParseError> { - match ehdr.get_phdrs_data_range()? { - Some((start, end)) => { - reader.load_bytes(start..end)?; - let buf = reader.get_bytes(start..end); - let phdrs_vec = SegmentTable::new(endian, ehdr.class, buf).iter().collect(); - Ok(phdrs_vec) - } - None => Ok(Vec::default()), + // It's Ok to have no program headers + if ehdr.e_phoff == 0 { + return Ok(Vec::default()); } + + // If the number of segments is greater than or equal to PN_XNUM (0xffff), + // e_phnum is set to PN_XNUM, and the actual number of program header table + // entries is contained in the sh_info field of the section header at index 0. + let mut phnum = ehdr.e_phnum as usize; + if phnum == abi::PN_XNUM as usize { + let shoff: usize = ehdr.e_shoff.try_into()?; + let end = shoff + .checked_add(SectionHeader::size_for(ehdr.class)) + .ok_or(ParseError::IntegerOverflow)?; + let data = reader.read_bytes(shoff, end)?; + let mut offset = 0; + let shdr0 = SectionHeader::parse_at(endian, ehdr.class, &mut offset, data)?; + phnum = shdr0.sh_info.try_into()?; + } + + // Validate phentsize before trying to read the table so that we can error early for corrupted files + let entsize = ProgramHeader::validate_entsize(ehdr.class, ehdr.e_phentsize as usize)?; + + let phoff: usize = ehdr.e_phoff.try_into()?; + let size = entsize + .checked_mul(phnum) + .ok_or(ParseError::IntegerOverflow)?; + let end = phoff.checked_add(size).ok_or(ParseError::IntegerOverflow)?; + let buf = reader.read_bytes(phoff, end)?; + let phdrs_vec = SegmentTable::new(endian, ehdr.class, buf).iter().collect(); + Ok(phdrs_vec) } impl ElfStream { @@ -825,6 +847,27 @@ mod interface_tests { ) } + #[test] + fn segments_phnum_in_shdr0() { + let path = std::path::PathBuf::from("sample-objects/phnum.m68k.so"); + let io = std::fs::File::open(path).expect("Could not open file."); + let file = ElfStream::::open_stream(io).expect("Open test1"); + + assert_eq!( + file.segments()[0], + ProgramHeader { + p_type: abi::PT_PHDR, + p_offset: 92, + p_vaddr: 0, + p_paddr: 0, + p_filesz: 32, + p_memsz: 32, + p_flags: 0x20003, + p_align: 0x40000, + } + ); + } + #[test] fn symbol_table() { let path = std::path::PathBuf::from("sample-objects/basic.x86_64"); diff --git a/src/file.rs b/src/file.rs index b664f0c..19b2714 100644 --- a/src/file.rs +++ b/src/file.rs @@ -1,8 +1,7 @@ //! Parsing the ELF File Header use crate::abi; use crate::endian::{AnyEndian, EndianParse}; -use crate::parse::{ParseAt, ParseError}; -use crate::segment::ProgramHeader; +use crate::parse::ParseError; /// Represents the ELF file data format (little-endian vs big-endian) #[derive(Debug, Copy, Clone, PartialEq, Eq)] @@ -185,28 +184,6 @@ impl FileHeader { e_shstrndx, }) } - - /// Calculate the (start, end) range in bytes for where the ProgramHeader table resides in - /// the ELF file containing this FileHeader. - /// - /// Returns Ok(None) if the file does not contain any ProgramHeaders. - /// Returns a ParseError if the range could not fit in the system's usize or encountered overflow - pub(crate) fn get_phdrs_data_range(self) -> Result, ParseError> { - if self.e_phnum == 0 { - return Ok(None); - } - - // Validate ph entsize. We do this when calculating the range before so that we can error - // early for corrupted files. - let entsize = ProgramHeader::validate_entsize(self.class, self.e_phentsize as usize)?; - - let start: usize = self.e_phoff.try_into()?; - let size = entsize - .checked_mul(self.e_phnum as usize) - .ok_or(ParseError::IntegerOverflow)?; - let end = start.checked_add(size).ok_or(ParseError::IntegerOverflow)?; - Ok(Some((start, end))) - } } #[cfg(test)]