use std::fmt;
const MAGIC: &[u8; 4] = b"\x7fELF";
const LOAD: u64 = 1;
const ALLOCATED: u64 = 0x2;
const NO_BITS: u32 = 8;
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("isn't an ELF file this reads")]
NotElf,
#[error("ends before its {0}")]
Short(&'static str),
#[error("has a section only partly inside the segment that loads it")]
Straddles,
#[error("loads a section past the end of the address space")]
Wraps,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct Machine {
pub number: u16,
pub wide: bool,
}
impl Machine {
#[must_use]
pub const fn new(number: u16, wide: bool) -> Self {
Self { number, wide }
}
}
impl fmt::Display for Machine {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
formatter,
"ELF machine {} in {}-bit files",
self.number,
if self.wide { 64 } else { 32 }
)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct Load {
pub(crate) address: u64,
pub(crate) offset: u64,
pub(crate) length: u64,
}
pub(crate) struct Section {
pub(crate) kind: u32,
pub(crate) flags: u64,
pub(crate) offset: u64,
pub(crate) size: u64,
pub(crate) link: u64,
pub(crate) entry: u64,
}
pub(crate) struct File<'a> {
bytes: &'a [u8],
wide: bool,
big: bool,
}
impl<'a> File<'a> {
pub(crate) fn new(bytes: &'a [u8]) -> Result<Self, Error> {
let (Some(magic), Some(&class), Some(&order)) =
(bytes.get(..4), bytes.get(4), bytes.get(5))
else {
return Err(Error::NotElf);
};
if magic != MAGIC || !(1..=2).contains(&class) || !(1..=2).contains(&order) {
return Err(Error::NotElf);
}
Ok(Self {
bytes,
wide: class == 2,
big: order == 2,
})
}
pub(crate) fn bytes(
&self,
at: u64,
length: u64,
what: &'static str,
) -> Result<&'a [u8], Error> {
let start = usize::try_from(at).map_err(|_too_large| Error::Short(what))?;
let end = start
.checked_add(usize::try_from(length).map_err(|_too_large| Error::Short(what))?)
.ok_or(Error::Short(what))?;
self.bytes.get(start..end).ok_or(Error::Short(what))
}
fn number(&self, at: u64, width: u64, what: &'static str) -> Result<u64, Error> {
let bytes = self.bytes(at, width, what)?;
let fold = |value: u64, &byte: &u8| (value << 8) | u64::from(byte);
Ok(if self.big {
bytes.iter().fold(0, fold)
} else {
bytes.iter().rev().fold(0, fold)
})
}
fn word(&self, at: u64, what: &'static str) -> Result<u64, Error> {
self.number(at, if self.wide { 8 } else { 4 }, what)
}
fn entry(&self, table: u64, size: u64, index: u64, what: &'static str) -> Result<u64, Error> {
index
.checked_mul(size)
.and_then(|offset| table.checked_add(offset))
.filter(|&at| at <= self.bytes.len() as u64)
.ok_or(Error::Short(what))
}
pub(crate) fn machine(&self) -> Result<Machine, Error> {
let number = self.number(18, 2, "header")?;
Ok(Machine::new(
u16::try_from(number).expect("two bytes"),
self.wide,
))
}
pub(crate) fn big_endian(&self) -> bool {
self.big
}
pub(crate) fn sections(&self) -> Result<u64, Error> {
self.number(if self.wide { 60 } else { 48 }, 2, "header")
}
pub(crate) fn section(&self, index: u64) -> Result<Section, Error> {
let (table, size) = if self.wide {
(self.word(40, "header")?, self.number(58, 2, "header")?)
} else {
(self.word(32, "header")?, self.number(46, 2, "header")?)
};
let what = "section headers";
let at = self.entry(table, size, index, what)?;
let kind = u32::try_from(self.number(at + 4, 4, what)?)
.map_err(|_too_large| Error::Short(what))?;
Ok(if self.wide {
Section {
kind,
flags: self.word(at + 8, what)?,
offset: self.word(at + 24, what)?,
size: self.word(at + 32, what)?,
link: self.number(at + 40, 4, what)?,
entry: self.word(at + 56, what)?,
}
} else {
Section {
kind,
flags: self.word(at + 8, what)?,
offset: self.word(at + 16, what)?,
size: self.word(at + 20, what)?,
link: self.number(at + 24, 4, what)?,
entry: self.word(at + 36, what)?,
}
})
}
pub(crate) fn symbol(&self, at: u64) -> Result<(u64, u64, u8), Error> {
let what = "symbols";
let (value, info) = if self.wide {
(self.word(at + 8, what)?, at + 4)
} else {
(self.word(at + 4, what)?, at + 12)
};
Ok((
self.number(at, 4, what)?,
value,
self.bytes(info, 1, what)?[0],
))
}
pub(crate) fn loads(&self) -> Result<Vec<Load>, Error> {
let segments = self.segments()?;
let count = self.sections()?;
if count == 0 {
return Ok(segments);
}
let mut loads = Vec::new();
for index in 0..count {
let section = self.section(index)?;
if section.flags & ALLOCATED == 0 || section.kind == NO_BITS || section.size == 0 {
continue;
}
self.bytes(section.offset, section.size, "sections")?;
let end = section.offset + section.size;
let Some(segment) = segments.iter().find(|segment| {
segment.offset < end && section.offset < segment.offset + segment.length
}) else {
continue;
};
if section.offset < segment.offset || end > segment.offset + segment.length {
return Err(Error::Straddles);
}
loads.push(Load {
address: segment
.address
.checked_add(section.offset - segment.offset)
.ok_or(Error::Wraps)?,
offset: section.offset,
length: section.size,
});
}
Ok(loads)
}
fn segments(&self) -> Result<Vec<Load>, Error> {
let (table, size, count) = if self.wide {
(
self.word(32, "header")?,
self.number(54, 2, "header")?,
self.number(56, 2, "header")?,
)
} else {
(
self.word(28, "header")?,
self.number(42, 2, "header")?,
self.number(44, 2, "header")?,
)
};
let what = "program headers";
let mut segments = Vec::new();
for index in 0..count {
let at = self.entry(table, size, index, what)?;
let (offset, address, length) = if self.wide {
(
self.word(at + 8, what)?,
self.word(at + 24, what)?,
self.word(at + 32, what)?,
)
} else {
(
self.word(at + 4, what)?,
self.word(at + 12, what)?,
self.word(at + 16, what)?,
)
};
if self.number(at, 4, what)? == LOAD && length > 0 {
self.bytes(offset, length, "segments")?;
segments.push(Load {
address,
offset,
length,
});
}
}
Ok(segments)
}
}
#[cfg(test)]
pub(crate) fn built(
class: u8,
order: u8,
segments: &[(u64, u64, u64, &[u8], u64)],
sections: &[(u32, u64, usize, usize, usize)],
) -> Vec<u8> {
let (wide, big) = (class == 2, order == 2);
let put = |out: &mut Vec<u8>, value: u64, width: usize| {
let bytes = value.to_le_bytes();
let mut field = bytes[..width].to_vec();
if big {
field.reverse();
}
out.extend(field);
};
let word = if wide { 8 } else { 4 };
let header: usize = if wide { 64 } else { 52 };
let entry: usize = if wide { 56 } else { 32 };
let section_entry: usize = if wide { 64 } else { 40 };
let mut at = header + entry * segments.len();
let mut offsets = Vec::new();
for &(.., bytes, _) in segments {
offsets.push(at);
at += bytes.len();
}
let section_table = at;
let mut out = vec![0x7f, b'E', b'L', b'F', class, order];
out.resize(if wide { 32 } else { 28 }, 0);
put(&mut out, header as u64, word);
put(
&mut out,
if sections.is_empty() {
0
} else {
section_table as u64
},
word,
);
out.resize(if wide { 54 } else { 42 }, 0);
put(&mut out, entry as u64, 2);
put(&mut out, segments.len() as u64, 2);
put(&mut out, section_entry as u64, 2);
put(
&mut out,
if sections.is_empty() {
0
} else {
sections.len() as u64 + 1
},
2,
);
out.resize(header, 0);
for (&(kind, physical, virtual_, bytes, length), &offset) in segments.iter().zip(&offsets) {
put(&mut out, kind, 4);
if wide {
put(&mut out, 0, 4);
}
for value in [
offset as u64,
virtual_,
physical,
bytes.len() as u64,
length,
] {
put(&mut out, value, word);
}
if !wide {
put(&mut out, 0, 4);
}
put(&mut out, 0, word);
}
for &(.., bytes, _) in segments {
out.extend(bytes);
}
if !sections.is_empty() {
out.resize(out.len() + section_entry, 0);
}
for &(kind, flags, segment, start, size) in sections {
put(&mut out, 0, 4);
put(&mut out, kind.into(), 4);
put(&mut out, flags, word);
put(&mut out, 0, word);
put(&mut out, (offsets[segment] + start) as u64, word);
put(&mut out, size as u64, word);
put(&mut out, 0, 4);
put(&mut out, 0, 4);
put(&mut out, 0, word);
put(&mut out, 0, word);
}
out
}
#[cfg(test)]
mod tests {
use super::*;
const PROGRAM: u32 = 1;
fn stored(file: &[u8]) -> Vec<(u64, Vec<u8>)> {
let elf = File::new(file).unwrap();
elf.loads()
.unwrap()
.iter()
.map(|load| {
let bytes = elf.bytes(load.offset, load.length, "segments").unwrap();
(load.address, bytes.to_vec())
})
.collect()
}
#[test]
fn a_section_is_stored_where_its_segment_puts_it_and_nothing_outside_a_section_is() {
for class in [1, 2] {
for order in [1, 2] {
let file = built(
class,
order,
&[
(LOAD, 0x1000, 0x1000, &[0xE1, 0xE1, 1, 2, 3, 4], 6),
(LOAD, 0x1006, 0x8000, &[5, 6], 2),
],
&[
(PROGRAM, 0, 0, 0, 2),
(PROGRAM, ALLOCATED, 0, 2, 4),
(PROGRAM, ALLOCATED, 1, 0, 2),
(NO_BITS, ALLOCATED, 1, 2, 0x100),
],
);
assert_eq!(
stored(&file),
[(0x1002, vec![1, 2, 3, 4]), (0x1006, vec![5, 6])],
"class {class}, order {order}: the two bytes of the file's own in front of the code \
aren't stored, data that runs from 0x8000 is stored after the code, and neither \
zeroed memory nor a section the program doesn't occupy is"
);
}
}
}
#[test]
fn a_file_with_no_section_headers_stores_each_loaded_segments_bytes() {
const NOTE: u64 = 4;
for class in [1, 2] {
for order in [1, 2] {
let file = built(
class,
order,
&[
(LOAD, 0x1000, 0x1000, &[1, 2, 3, 4], 4),
(LOAD, 0x1004, 0x8000, &[5, 6], 2),
(LOAD, 0x8002, 0x8002, &[], 0x100),
(NOTE, 0x1006, 0x1006, &[7], 1),
],
&[],
);
assert_eq!(
stored(&file),
[(0x1000, vec![1, 2, 3, 4]), (0x1004, vec![5, 6])],
"class {class}, order {order}"
);
}
}
}
#[test]
fn a_section_partly_outside_its_segment_or_loaded_past_the_address_space_is_refused() {
let straddles = built(
1,
1,
&[(LOAD, 0x1000, 0x1000, &[1, 2, 3, 4], 4)],
&[(PROGRAM, ALLOCATED, 0, 2, 4)],
);
assert_eq!(
File::new(&straddles).unwrap().loads(),
Err(Error::Straddles),
"a section two bytes past the end of its segment's bytes"
);
let wraps = built(
2,
1,
&[(LOAD, u64::MAX - 1, 0x1000, &[1, 2, 3, 4], 4)],
&[(PROGRAM, ALLOCATED, 0, 2, 2)],
);
assert_eq!(File::new(&wraps).unwrap().loads(), Err(Error::Wraps));
}
#[test]
fn a_segment_or_a_section_that_runs_past_the_end_of_the_file_is_refused() {
let segment = built(1, 1, &[(LOAD, 0x1000, 0x1000, &[1, 2, 3, 4], 4)], &[]);
assert_eq!(
File::new(&segment[..segment.len() - 1]).unwrap().loads(),
Err(Error::Short("segments"))
);
let section = built(
1,
1,
&[(LOAD, 0x1000, 0x1000, &[1, 2, 3, 4], 4)],
&[(PROGRAM, ALLOCATED, 0, 0, 10_000)],
);
assert_eq!(
File::new(§ion).unwrap().loads(),
Err(Error::Short("sections"))
);
}
}