#![forbid(unsafe_code)]
#![forbid(clippy::arithmetic_side_effects)]
#![forbid(clippy::cast_possible_truncation)]
#![forbid(clippy::cast_possible_wrap)]
#![forbid(clippy::cast_sign_loss)]
use std::{
convert::TryInto,
fmt,
io::{self, ErrorKind, Read, Seek, SeekFrom},
os::fd::AsFd,
};
use memchr::arch::all::{is_equal, is_prefix};
use nix::errno::Errno;
use crate::{fs::safe_open_file, XPath};
const ELF_MAGIC: &[u8] = b"\x7FELF";
const EI_CLASS: usize = 4;
const EI_DATA: usize = 5;
const EI_VERSION: usize = 6;
const ELFCLASS32: u8 = 1;
const ELFCLASS64: u8 = 2;
const ET_NONE: u16 = 0;
const ET_EXEC: u16 = 2;
const ET_DYN: u16 = 3;
const ET_REL: u16 = 1;
const ET_CORE: u16 = 4;
const ET_LOPROC: u16 = 0xff00;
const ET_HIPROC: u16 = 0xffff;
const PT_DYNAMIC: u32 = 2;
const PT_INTERP: u32 = 3;
const PT_GNU_STACK: u32 = 0x6474e551;
const PF_X: u32 = 0x1;
const ELFDATA2LSB: u8 = 1;
const ELFDATA2MSB: u8 = 2;
const EV_CURRENT: u8 = 1;
const MAX_PROGRAM_HEADERS: usize = 0x0001_0000;
const MAX_PHENT_SIZE: usize = 1024;
const MAX_DYNAMIC_SECTION_SIZE: u64 = 16 * 1024 * 1024;
const DT_FLAGS_1: u64 = 0x6fff_fffb;
const DF_1_PIE: u64 = 0x0800_0000;
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum ExecutableFile {
Elf {
elf_type: ElfType,
file_type: ElfFileType,
linking_type: Option<LinkingType>,
pie: bool,
xs: bool,
},
Script,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum ElfType {
Elf32,
Elf64,
}
#[cfg(target_pointer_width = "32")]
pub const ELFTYPE_NATIVE: ElfType = ElfType::Elf32;
#[cfg(target_pointer_width = "64")]
pub const ELFTYPE_NATIVE: ElfType = ElfType::Elf64;
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum ElfFileType {
None,
Executable,
Library,
Relocatable,
Core,
ProcessorSpecific,
Unknown,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum LinkingType {
Static,
Dynamic,
}
#[derive(Debug)]
pub enum ElfError {
BadMagic,
Malformed,
IoError(io::Error),
}
impl From<u16> for ElfFileType {
fn from(e_type: u16) -> Self {
match e_type {
ET_NONE => Self::None,
ET_EXEC => Self::Executable,
ET_DYN => Self::Library,
ET_REL => Self::Relocatable,
ET_CORE => Self::Core,
ET_LOPROC..=ET_HIPROC => Self::ProcessorSpecific,
_ => Self::Unknown,
}
}
}
impl fmt::Display for ElfError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::BadMagic => write!(f, "Invalid ELF magic number"),
Self::Malformed => write!(f, "Malformed ELF header"),
Self::IoError(e) => write!(f, "I/O error: {e}"),
}
}
}
impl From<io::Error> for ElfError {
fn from(err: io::Error) -> Self {
Self::IoError(err)
}
}
impl From<ElfError> for Errno {
fn from(err: ElfError) -> Self {
match err {
ElfError::BadMagic => Self::EINVAL,
ElfError::Malformed => Self::EACCES,
ElfError::IoError(e) => Self::from_raw(e.raw_os_error().unwrap_or(Self::EIO as i32)),
}
}
}
impl From<Errno> for ElfError {
fn from(errno: Errno) -> Self {
Self::IoError(io::Error::from_raw_os_error(errno as i32))
}
}
impl fmt::Display for ExecutableFile {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Elf {
elf_type,
file_type,
linking_type,
pie,
xs,
} => {
let pie = if *pie { "-pie" } else { "" };
let xs = if *xs { "-xs" } else { "" };
if let Some(linking_type) = linking_type {
write!(f, "ELF:{file_type}{elf_type}-{linking_type}{pie}{xs}")
} else {
write!(f, "ELF:{file_type}{elf_type}{pie}{xs}")
}
}
Self::Script => write!(f, "SCRIPT"),
}
}
}
impl fmt::Display for ElfType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Elf32 => write!(f, "32"),
Self::Elf64 => write!(f, "64"),
}
}
}
impl fmt::Display for ElfFileType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::None => write!(f, "none"),
Self::Executable => write!(f, "exe"),
Self::Library => write!(f, "lib"),
Self::Relocatable => write!(f, "rel"),
Self::Core => write!(f, "core"),
Self::ProcessorSpecific => write!(f, "proc"),
Self::Unknown => write!(f, "reserved"),
}
}
}
impl fmt::Display for LinkingType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Static => write!(f, "static"),
Self::Dynamic => write!(f, "dynamic"),
}
}
}
impl ExecutableFile {
pub fn is_script_file<Fd: AsFd>(fd: Fd, path: &XPath) -> Result<bool, ElfError> {
let (mut file, _) = safe_open_file(fd, path)?;
let mut hashbang = [0u8; 2];
file.read_exact(&mut hashbang)?;
Ok(&hashbang == b"#!")
}
pub fn is_elf_file<R: Read>(mut reader: R) -> Result<bool, ElfError> {
let mut magic = [0u8; 4];
let mut nread = 0;
while nread < 4 {
match reader.read(&mut magic[nread..]) {
Ok(0) => {
return Ok(false);
}
Ok(n) => nread = nread.checked_add(n).ok_or(Errno::EOVERFLOW)?,
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue,
Err(e) => return Err(e.into()),
}
}
Ok(is_equal(&magic, ELF_MAGIC))
}
pub fn is_valid_elf_file<R: Read>(mut reader: R) -> Result<bool, ElfError> {
let mut ident = [0u8; 16]; let mut nread = 0;
while nread < 16 {
match reader.read(&mut ident[nread..]) {
Ok(0) => {
return Ok(false);
}
Ok(n) => nread = nread.checked_add(n).ok_or(Errno::EOVERFLOW)?,
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue,
Err(e) => return Err(e.into()),
}
}
if !is_equal(&ident[0..4], ELF_MAGIC) {
return Ok(false);
}
if ident[EI_VERSION] != EV_CURRENT {
return Ok(false);
}
if !matches!(ident[EI_DATA], ELFDATA2LSB | ELFDATA2MSB) {
return Ok(false);
}
if !matches!(ident[EI_CLASS], ELFCLASS64 | ELFCLASS32) {
return Ok(false);
}
Ok(true)
}
#[expect(clippy::cognitive_complexity)]
pub fn parse<R: Read + Seek>(
mut reader: R,
check_linking: bool,
) -> Result<ExecutableFile, ElfError> {
let mut header = [0u8; 64];
let mut bytes_read = 0;
while bytes_read < header.len() {
match reader.read(&mut header[bytes_read..]) {
Ok(0) => break,
Ok(n) => bytes_read = bytes_read.checked_add(n).ok_or(Errno::EOVERFLOW)?,
Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
Err(e) => return Err(ElfError::IoError(e)),
}
}
match bytes_read {
0 => return Err(ElfError::BadMagic),
1 => {
if header[0] == b'#' {
return Ok(ExecutableFile::Script);
} else {
return Err(ElfError::BadMagic);
}
}
2..=3 => {
if is_prefix(&header, b"#!") {
return Ok(ExecutableFile::Script);
} else {
return Err(ElfError::BadMagic);
}
}
4..=63 => {
if is_prefix(&header, b"#!") {
return Ok(ExecutableFile::Script);
} else if !is_equal(&header[0..4], ELF_MAGIC) {
return Err(ElfError::BadMagic);
} else {
return Err(ElfError::Malformed);
}
}
_ => {
if is_prefix(&header, b"#!") {
return Ok(ExecutableFile::Script);
} else if !is_equal(&header[0..4], ELF_MAGIC) {
return Err(ElfError::BadMagic);
}
}
}
let is_big_endian = match header.get(EI_DATA) {
Some(&ELFDATA2LSB) => false,
Some(&ELFDATA2MSB) => true,
_ => return Err(ElfError::Malformed),
};
let elf_type = match header.get(EI_CLASS) {
Some(&ELFCLASS32) => ElfType::Elf32,
Some(&ELFCLASS64) => ElfType::Elf64,
_ => return Err(ElfError::Malformed),
};
let mut file_type = if is_big_endian {
read_u16_be(header.get(16..18).ok_or(ElfError::Malformed)?)
} else {
read_u16_le(header.get(16..18).ok_or(ElfError::Malformed)?)
}
.map(ElfFileType::from)?;
let mut dynamic = false;
let mut pie = false;
let mut xs = !(cfg!(target_arch = "powerpc64") && elf_type == ElfType::Elf64);
if check_linking && matches!(file_type, ElfFileType::Executable | ElfFileType::Library) {
let (phoff_offset, phnum_offset, phentsize_offset) = if elf_type == ElfType::Elf64 {
(32usize, 56usize, 54usize)
} else {
(28usize, 44usize, 42usize)
};
let phoff = if elf_type == ElfType::Elf64 {
if is_big_endian {
read_u64_be(
header
.get(
phoff_offset
..phoff_offset.checked_add(8).ok_or(Errno::EOVERFLOW)?,
)
.ok_or(ElfError::Malformed)?,
)?
} else {
read_u64_le(
header
.get(
phoff_offset
..phoff_offset.checked_add(8).ok_or(Errno::EOVERFLOW)?,
)
.ok_or(ElfError::Malformed)?,
)?
}
} else {
if is_big_endian {
read_u32_be(
header
.get(
phoff_offset
..phoff_offset.checked_add(4).ok_or(Errno::EOVERFLOW)?,
)
.ok_or(ElfError::Malformed)?,
)?
} else {
read_u32_le(
header
.get(
phoff_offset
..phoff_offset.checked_add(4).ok_or(Errno::EOVERFLOW)?,
)
.ok_or(ElfError::Malformed)?,
)?
}
.into()
};
let phnum = if is_big_endian {
read_u16_be(
header
.get(phnum_offset..phnum_offset.checked_add(2).ok_or(Errno::EOVERFLOW)?)
.ok_or(ElfError::Malformed)?,
)?
} else {
read_u16_le(
header
.get(phnum_offset..phnum_offset.checked_add(2).ok_or(Errno::EOVERFLOW)?)
.ok_or(ElfError::Malformed)?,
)?
} as usize;
let phentsize = if is_big_endian {
read_u16_be(
header
.get(
phentsize_offset
..phentsize_offset.checked_add(2).ok_or(Errno::EOVERFLOW)?,
)
.ok_or(ElfError::Malformed)?,
)?
} else {
read_u16_le(
header
.get(
phentsize_offset
..phentsize_offset.checked_add(2).ok_or(Errno::EOVERFLOW)?,
)
.ok_or(ElfError::Malformed)?,
)?
} as usize;
if phnum == 0
|| phnum > MAX_PROGRAM_HEADERS
|| phentsize == 0
|| phentsize > MAX_PHENT_SIZE
{
return Err(ElfError::Malformed);
}
let total_size = phnum.checked_mul(phentsize).ok_or(Errno::EOVERFLOW)?;
let mut phdrs = Vec::new();
phdrs.try_reserve(total_size).or(Err(Errno::ENOMEM))?;
phdrs.resize(total_size, 0);
reader.seek(SeekFrom::Start(phoff))?;
reader.read_exact(&mut phdrs)?;
let mut seen_interp = false;
for i in 0..phnum {
let offset = i.checked_mul(phentsize).ok_or(Errno::EOVERFLOW)?;
let end = offset.checked_add(4).ok_or(Errno::EOVERFLOW)?;
if end > phdrs.len() || offset >= phdrs.len() {
break;
}
let p_type = if is_big_endian {
read_u32_be(&phdrs[offset..end])?
} else {
read_u32_le(&phdrs[offset..end])?
};
match p_type {
PT_INTERP if !seen_interp => {
file_type = ElfFileType::Executable;
dynamic = true;
seen_interp = true;
}
PT_GNU_STACK => {
let flags_offset = if elf_type == ElfType::Elf64 {
offset.checked_add(4).ok_or(Errno::EOVERFLOW)?
} else {
offset.checked_add(24).ok_or(Errno::EOVERFLOW)?
};
let flags_end = flags_offset.checked_add(4).ok_or(Errno::EOVERFLOW)?;
if flags_end > phdrs.len() || flags_offset >= phdrs.len() {
break;
}
let p_flags = if is_big_endian {
read_u32_be(&phdrs[flags_offset..flags_end])?
} else {
read_u32_le(&phdrs[flags_offset..flags_end])?
};
xs = p_flags & PF_X != 0;
}
_ => continue,
}
}
if let Some((dynamic_section, dynamic_size)) = read_dynamic_section(
&mut reader,
&phdrs,
elf_type,
is_big_endian,
phnum,
phentsize,
)? {
pie = is_pie(&dynamic_section, dynamic_size, elf_type, is_big_endian)?;
if pie {
file_type = ElfFileType::Executable;
}
}
}
let linking_type = if file_type == ElfFileType::Executable {
if dynamic {
Some(LinkingType::Dynamic)
} else {
Some(LinkingType::Static)
}
} else {
None
};
Ok(ExecutableFile::Elf {
elf_type,
file_type,
linking_type,
pie,
xs,
})
}
}
fn is_pie(
dynamic_section: &[u8],
dynamic_size: usize,
elf_type: ElfType,
is_big_endian: bool,
) -> Result<bool, ElfError> {
let entry_size = match elf_type {
ElfType::Elf32 => 8,
ElfType::Elf64 => 16,
};
for i in (0..dynamic_size).step_by(entry_size) {
let j = i.checked_add(entry_size / 2).ok_or(Errno::EOVERFLOW)?;
if j > dynamic_size || i >= dynamic_size {
break;
}
#[expect(clippy::collapsible_else_if)]
let d_tag = if is_big_endian {
if elf_type == ElfType::Elf64 {
read_u64_be(&dynamic_section[i..j])?
} else {
read_u32_be(&dynamic_section[i..j])?.into()
}
} else {
if elf_type == ElfType::Elf64 {
read_u64_le(&dynamic_section[i..j])?
} else {
read_u32_le(&dynamic_section[i..j])?.into()
}
};
if d_tag == DT_FLAGS_1 {
let k = i.checked_add(entry_size).ok_or(Errno::EOVERFLOW)?;
if k > dynamic_size || j >= dynamic_size {
break;
}
#[expect(clippy::collapsible_else_if)]
let d_val = if is_big_endian {
if elf_type == ElfType::Elf64 {
read_u64_be(&dynamic_section[j..k])?
} else {
read_u32_be(&dynamic_section[j..k])?.into()
}
} else {
if elf_type == ElfType::Elf64 {
read_u64_le(&dynamic_section[j..k])?
} else {
read_u32_le(&dynamic_section[j..k])?.into()
}
};
return Ok(d_val & DF_1_PIE != 0);
}
}
Ok(false)
}
#[expect(clippy::cognitive_complexity)]
#[expect(clippy::type_complexity)]
fn read_dynamic_section<R: Read + Seek>(
reader: &mut R,
phdrs: &[u8],
elf_type: ElfType,
is_big_endian: bool,
phnum: usize,
phentsize: usize,
) -> Result<Option<(Vec<u8>, usize)>, ElfError> {
for i in 0..phnum {
let offset = i.checked_mul(phentsize).ok_or(Errno::EOVERFLOW)?;
let end = offset.checked_add(4).ok_or(Errno::EOVERFLOW)?;
if end > phdrs.len() || offset >= phdrs.len() {
break;
}
let p_type = if is_big_endian {
read_u32_be(&phdrs[offset..end])?
} else {
read_u32_le(&phdrs[offset..end])?
};
if p_type == PT_DYNAMIC {
let p_offset = if elf_type == ElfType::Elf64 {
let offset_dyn_min = offset.checked_add(8).ok_or(Errno::EOVERFLOW)?;
let offset_dyn_max = offset.checked_add(16).ok_or(Errno::EOVERFLOW)?;
if offset_dyn_max > phdrs.len() || offset_dyn_min >= phdrs.len() {
break;
}
if is_big_endian {
read_u64_be(&phdrs[offset_dyn_min..offset_dyn_max])?
} else {
read_u64_le(&phdrs[offset_dyn_min..offset_dyn_max])?
}
} else {
let offset_dyn_min = offset.checked_add(4).ok_or(Errno::EOVERFLOW)?;
let offset_dyn_max = offset.checked_add(8).ok_or(Errno::EOVERFLOW)?;
if offset_dyn_max > phdrs.len() || offset_dyn_min >= phdrs.len() {
break;
}
if is_big_endian {
read_u32_be(&phdrs[offset_dyn_min..offset_dyn_max])?.into()
} else {
read_u32_le(&phdrs[offset_dyn_min..offset_dyn_max])?.into()
}
};
let p_filesz = if elf_type == ElfType::Elf64 {
let offset_filesz_min = offset.checked_add(32).ok_or(Errno::EOVERFLOW)?;
let offset_filesz_max = offset.checked_add(40).ok_or(Errno::EOVERFLOW)?;
if offset_filesz_max > phdrs.len() || offset_filesz_min >= phdrs.len() {
break;
}
if is_big_endian {
read_u64_be(&phdrs[offset_filesz_min..offset_filesz_max])?
} else {
read_u64_le(&phdrs[offset_filesz_min..offset_filesz_max])?
}
} else {
let offset_filesz_min = offset.checked_add(16).ok_or(Errno::EOVERFLOW)?;
let offset_filesz_max = offset.checked_add(20).ok_or(Errno::EOVERFLOW)?;
if offset_filesz_max > phdrs.len() || offset_filesz_min >= phdrs.len() {
break;
}
if is_big_endian {
read_u32_be(&phdrs[offset_filesz_min..offset_filesz_max])?.into()
} else {
read_u32_le(&phdrs[offset_filesz_min..offset_filesz_max])?.into()
}
};
if p_filesz > MAX_DYNAMIC_SECTION_SIZE {
return Err(ElfError::Malformed);
}
let file_size = reader.seek(SeekFrom::End(0))?;
if p_offset > file_size || p_offset.saturating_add(p_filesz) > file_size {
return Err(ElfError::Malformed);
}
reader.seek(SeekFrom::Start(p_offset))?;
let mut dynamic_section = Vec::new();
let p_filesz = usize::try_from(p_filesz).or(Err(ElfError::Malformed))?;
dynamic_section
.try_reserve(p_filesz)
.or(Err(Errno::ENOMEM))?;
dynamic_section.resize(p_filesz, 0);
reader.read_exact(&mut dynamic_section)?;
return Ok(Some((dynamic_section, p_filesz)));
}
}
Ok(None)
}
fn read_u16_be(bytes: &[u8]) -> Result<u16, ElfError> {
let arr: [u8; 2] = bytes.try_into().or(Err(ElfError::Malformed))?;
Ok(u16::from_be_bytes(arr))
}
fn read_u16_le(bytes: &[u8]) -> Result<u16, ElfError> {
let arr: [u8; 2] = bytes.try_into().or(Err(ElfError::Malformed))?;
Ok(u16::from_le_bytes(arr))
}
fn read_u32_be(bytes: &[u8]) -> Result<u32, ElfError> {
let arr: [u8; 4] = bytes.try_into().or(Err(ElfError::Malformed))?;
Ok(u32::from_be_bytes(arr))
}
fn read_u32_le(bytes: &[u8]) -> Result<u32, ElfError> {
let arr: [u8; 4] = bytes.try_into().or(Err(ElfError::Malformed))?;
Ok(u32::from_le_bytes(arr))
}
fn read_u64_be(bytes: &[u8]) -> Result<u64, ElfError> {
let arr: [u8; 8] = bytes.try_into().or(Err(ElfError::Malformed))?;
Ok(u64::from_be_bytes(arr))
}
fn read_u64_le(bytes: &[u8]) -> Result<u64, ElfError> {
let arr: [u8; 8] = bytes.try_into().or(Err(ElfError::Malformed))?;
Ok(u64::from_le_bytes(arr))
}