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.
This commit is contained in:
Christopher Cole 2022-11-10 13:31:20 -08:00
parent c5fba3b0ab
commit 2e05d70302
No known key found for this signature in database
GPG Key ID: 0AC856975983E9DB
5 changed files with 111 additions and 38 deletions

Binary file not shown.

View File

@ -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

View File

@ -112,14 +112,33 @@ fn find_phdrs<'data, E: EndianParse>(
ehdr: &FileHeader,
data: &'data [u8],
) -> Result<Option<SegmentTable<'data, E>>, ParseError> {
match ehdr.get_phdrs_data_range()? {
Some((start, end)) => {
let buf = data.get_bytes(start..end)?;
// 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)))
}
None => Ok(None),
}
}
/// This struct collects the common sections found in ELF objects
#[derive(Default)]
@ -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::<AnyEndian>::minimal_parse(slice).expect("Open test1");
let segments: Vec<ProgramHeader> = 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");

View File

@ -80,16 +80,38 @@ fn parse_program_headers<E: EndianParse, S: Read + Seek>(
ehdr: &FileHeader,
reader: &mut CachingReader<S>,
) -> Result<Vec<ProgramHeader>, ParseError> {
match ehdr.get_phdrs_data_range()? {
Some((start, end)) => {
reader.load_bytes(start..end)?;
let buf = reader.get_bytes(start..end);
// 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)
}
None => Ok(Vec::default()),
}
}
impl<E: EndianParse, S: std::io::Read + std::io::Seek> ElfStream<E, S> {
/// Do a minimal amount of parsing work to open an [ElfStream] handle from a Read+Seek containing an ELF object.
@ -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::<AnyEndian, _>::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");

View File

@ -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<Option<(usize, usize)>, 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)]