use crate::{
AlignedBytes, ParseDynamicError, ParsePhdrError, Result,
arch::NativeArch,
elf::{
ElfDyn, ElfDynamicTag, ElfHeader, ElfLayout, ElfPhdr, ElfProgramType, ElfSectionFlags,
ElfSectionId, ElfSectionType, ElfShdr, ElfStringTable, NativeElfLayout,
parse_dynamic_entries,
},
input::{ElfReader, ElfReaderExt, Path, PathBuf},
loader::ScanBuilder,
memory::MappedView,
relocation::RelocationArch,
};
use alloc::{boxed::Box, vec::Vec};
use core::{fmt, mem::size_of, num::NonZeroUsize};
struct DynamicScanParts {
strtab: Box<[u8]>,
needed_libs: Box<[usize]>,
soname: Option<usize>,
rpath: Option<usize>,
runpath: Option<usize>,
}
impl DynamicScanParts {
fn new<L: ElfLayout>(object: &dyn ElfReader, phdrs: &[ElfPhdr<L>]) -> Result<Self> {
let dynamic_phdr = phdrs
.iter()
.find(|phdr| phdr.program_type() == ElfProgramType::DYNAMIC)
.ok_or(ParsePhdrError::MissingDynamicSection)?;
let dynamic_entry_size = size_of::<ElfDyn<L>>();
if dynamic_phdr.p_filesz() % dynamic_entry_size != 0 {
return Err(ParseDynamicError::InvalidDynamicTableSize {
size: dynamic_phdr.p_filesz(),
entry_size: dynamic_entry_size,
}
.into());
}
let dyns = object.read_to_vec::<ElfDyn<L>>(
dynamic_phdr.p_offset(),
dynamic_phdr.p_filesz() / dynamic_entry_size,
)?;
let parsed = parse_dynamic_entries(
dyns.into_iter()
.map(|dynamic| (dynamic.tag(), dynamic.value())),
);
let strtab_vaddr =
NonZeroUsize::new(parsed.strtab_off).ok_or(ParseDynamicError::AddressOverflow)?;
let strtab_file_off = vaddr_to_file_offset(strtab_vaddr.get(), phdrs)?;
let strtab_size = parsed
.strtab_size
.ok_or(ParseDynamicError::MissingRequiredTag {
tag: ElfDynamicTag::STRSZ,
})?
.get();
let strtab = object.read_to_vec(strtab_file_off, strtab_size)?;
let needed_libs = parsed
.needed_libs
.into_iter()
.map(|offset| offset.get())
.collect::<Vec<_>>()
.into_boxed_slice();
let soname = parsed.soname_off.map(|offset| offset.get());
let rpath = parsed.rpath_off.map(|offset| offset.get());
let runpath = parsed.runpath_off.map(|offset| offset.get());
Ok(Self {
strtab: strtab.into_boxed_slice(),
needed_libs,
soname,
rpath,
runpath,
})
}
}
struct SectionTable<L: ElfLayout = NativeElfLayout> {
sections: Box<[ElfShdr<L>]>,
shstrtab: Box<[u8]>,
}
impl<L: ElfLayout> SectionTable<L> {
fn new(object: &dyn ElfReader, ehdr: &ElfHeader<L>) -> Result<Option<Self>> {
if ehdr.e_shnum() == 0 {
return Ok(None);
}
let Some((start, _)) = ehdr.checked_shdr_layout(object.len())? else {
return Ok(None);
};
let shdrs = object.read_to_vec::<ElfShdr<L>>(start, ehdr.e_shnum())?;
let shstrndx = ehdr.e_shstrndx();
let shstrtab = match shdrs.get(shstrndx) {
Some(shdr) if shdr.section_type() != ElfSectionType::NOBITS => {
object.read_to_vec(shdr.sh_offset(), shdr.sh_size())?
}
_ => return Ok(None),
};
Ok(Some(SectionTable {
sections: shdrs.into_boxed_slice(),
shstrtab: shstrtab.into_boxed_slice(),
}))
}
#[inline]
fn shstrtab(&self) -> ElfStringTable {
let bytes =
unsafe { core::slice::from_raw_parts(self.shstrtab.as_ptr(), self.shstrtab.len()) };
ElfStringTable::new(MappedView::from_slice(bytes))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ModuleCapability {
Opaque,
SectionData,
SectionReorderable,
}
impl ModuleCapability {
#[inline]
pub const fn has_section_data(self) -> bool {
!matches!(self, Self::Opaque)
}
#[inline]
pub const fn supports_reorder_repair(self) -> bool {
matches!(self, Self::SectionReorderable)
}
}
pub struct ScannedDynamic<Arch: RelocationArch = NativeArch> {
path: PathBuf,
ehdr: ElfHeader<Arch::Layout>,
phdrs: Box<[ElfPhdr<Arch::Layout>]>,
interp: Option<Box<[u8]>>,
_strtab_bytes: Box<[u8]>,
strtab: ElfStringTable,
section_table: Option<SectionTable<Arch::Layout>>,
soname: Option<usize>,
rpath: Option<usize>,
runpath: Option<usize>,
needed_libs: Box<[usize]>,
capability: ModuleCapability,
reader: Box<dyn ElfReader + 'static>,
}
pub struct ScannedExec<Arch: RelocationArch = NativeArch> {
path: PathBuf,
ehdr: ElfHeader<Arch::Layout>,
phdrs: Box<[ElfPhdr<Arch::Layout>]>,
interp: Option<Box<[u8]>>,
section_table: Option<SectionTable<Arch::Layout>>,
#[allow(dead_code)]
reader: Box<dyn ElfReader + 'static>,
}
#[derive(Debug)]
pub enum ScannedElf<Arch: RelocationArch = NativeArch> {
Dynamic(ScannedDynamic<Arch>),
StaticExec(ScannedExec<Arch>),
}
pub(crate) struct ScannedDynamicLoadParts<Arch: RelocationArch = NativeArch> {
pub(crate) ehdr: ElfHeader<Arch::Layout>,
pub(crate) phdrs: Box<[ElfPhdr<Arch::Layout>]>,
pub(crate) reader: Box<dyn ElfReader + 'static>,
}
pub struct ScannedSection<'a, L: ElfLayout = NativeElfLayout> {
id: ElfSectionId,
name: &'a str,
header: &'a ElfShdr<L>,
}
impl<L: ElfLayout> Copy for ScannedSection<'_, L> {}
impl<L: ElfLayout> Clone for ScannedSection<'_, L> {
#[inline]
fn clone(&self) -> Self {
*self
}
}
pub struct ScannedSections<'a, L: ElfLayout = NativeElfLayout> {
sections: &'a [ElfShdr<L>],
shstrtab: ElfStringTable,
index: usize,
}
impl<'a, L: ElfLayout> ScannedSections<'a, L> {
#[inline]
fn new(sections: &'a [ElfShdr<L>], shstrtab: ElfStringTable) -> Self {
Self {
sections,
shstrtab,
index: 0,
}
}
#[inline]
fn empty() -> Self {
Self::new(&[], ElfStringTable::new(MappedView::empty()))
}
#[inline]
fn section_name(&self, section: &ElfShdr<L>) -> &'a str {
self.shstrtab.get_str(section.sh_name() as usize)
}
}
impl<'a, L: ElfLayout> Iterator for ScannedSections<'a, L> {
type Item = ScannedSection<'a, L>;
fn next(&mut self) -> Option<Self::Item> {
let header = self.sections.get(self.index)?;
let id = ElfSectionId::new(self.index);
self.index += 1;
Some(ScannedSection::new(id, self.section_name(header), header))
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self.sections.len().saturating_sub(self.index);
(remaining, Some(remaining))
}
}
impl<L: ElfLayout> ExactSizeIterator for ScannedSections<'_, L> {}
impl<'a, L: ElfLayout> ScannedSection<'a, L> {
#[inline]
fn new(id: ElfSectionId, name: &'a str, header: &'a ElfShdr<L>) -> Self {
Self { id, name, header }
}
#[inline]
pub const fn id(&self) -> ElfSectionId {
self.id
}
#[inline]
pub fn name(&self) -> &'a str {
self.name
}
#[inline]
pub fn header(&self) -> &'a ElfShdr<L> {
self.header
}
#[inline]
pub fn section_type(&self) -> ElfSectionType {
self.header.section_type()
}
#[inline]
pub fn flags(&self) -> ElfSectionFlags {
self.header.flags()
}
#[inline]
pub fn address(&self) -> usize {
self.header.sh_addr()
}
#[inline]
pub fn file_offset(&self) -> usize {
self.header.sh_offset()
}
#[inline]
pub fn size(&self) -> usize {
self.header.sh_size()
}
#[inline]
pub fn alignment(&self) -> usize {
self.header.sh_addralign()
}
#[inline]
pub fn is_allocated(&self) -> bool {
self.flags().contains(ElfSectionFlags::ALLOC)
}
#[inline]
pub fn is_writable(&self) -> bool {
self.flags().contains(ElfSectionFlags::WRITE)
}
#[inline]
pub fn is_executable(&self) -> bool {
self.flags().contains(ElfSectionFlags::EXECINSTR)
}
#[inline]
pub fn is_tls(&self) -> bool {
self.flags().contains(ElfSectionFlags::TLS)
}
#[inline]
pub fn is_nobits(&self) -> bool {
self.section_type() == ElfSectionType::NOBITS
}
#[inline]
pub fn is_relocation_section(&self) -> bool {
matches!(
self.section_type(),
ElfSectionType::REL | ElfSectionType::RELA
)
}
#[inline]
pub fn linked_section_id(&self) -> Option<ElfSectionId> {
(self.header.sh_link() != 0).then_some(ElfSectionId::new(self.header.sh_link() as usize))
}
#[inline]
pub fn info_section_id(&self) -> Option<ElfSectionId> {
(self.header.sh_info() != 0).then_some(ElfSectionId::new(self.header.sh_info() as usize))
}
}
impl<'a, L: ElfLayout> fmt::Debug for ScannedSection<'a, L> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ScannedSection")
.field("id", &self.id)
.field("name", &self.name)
.field("type", &self.section_type())
.field("size", &self.size())
.field("align", &self.alignment())
.finish()
}
}
impl<Arch: RelocationArch> fmt::Debug for ScannedDynamic<Arch> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ScannedDynamic")
.field("path", &self.path)
.field("soname", &self.soname())
.field("needed_libs", &self.needed_libs().collect::<Vec<_>>())
.field(
"sections",
&self
.section_table
.as_ref()
.map_or(0, |table| table.sections.len()),
)
.field("capability", &self.capability)
.finish()
}
}
impl<Arch: RelocationArch> fmt::Debug for ScannedExec<Arch> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ScannedExec")
.field("path", &self.path)
.field(
"sections",
&self
.section_table
.as_ref()
.map_or(0, |table| table.sections.len()),
)
.finish()
}
}
impl<Arch: RelocationArch> ScannedElf<Arch> {
#[inline]
pub fn path(&self) -> &Path {
match self {
Self::Dynamic(image) => image.path(),
Self::StaticExec(image) => image.path(),
}
}
#[inline]
pub fn name(&self) -> &str {
match self {
Self::Dynamic(image) => image.name(),
Self::StaticExec(image) => image.name(),
}
}
#[inline]
pub fn ehdr(&self) -> &ElfHeader<Arch::Layout> {
match self {
Self::Dynamic(image) => image.ehdr(),
Self::StaticExec(image) => image.ehdr(),
}
}
#[inline]
pub fn phdrs(&self) -> &[ElfPhdr<Arch::Layout>] {
match self {
Self::Dynamic(image) => image.phdrs(),
Self::StaticExec(image) => image.phdrs(),
}
}
#[inline]
pub fn interp(&self) -> Option<&str> {
match self {
Self::Dynamic(image) => image.interp(),
Self::StaticExec(image) => image.interp(),
}
}
#[inline]
pub fn as_dynamic(&self) -> Option<&ScannedDynamic<Arch>> {
match self {
Self::Dynamic(image) => Some(image),
Self::StaticExec(_) => None,
}
}
#[inline]
pub fn as_static_exec(&self) -> Option<&ScannedExec<Arch>> {
match self {
Self::Dynamic(_) => None,
Self::StaticExec(image) => Some(image),
}
}
#[inline]
pub fn into_dynamic(self) -> Option<ScannedDynamic<Arch>> {
match self {
Self::Dynamic(image) => Some(image),
Self::StaticExec(_) => None,
}
}
#[inline]
pub fn into_static_exec(self) -> Option<ScannedExec<Arch>> {
match self {
Self::Dynamic(_) => None,
Self::StaticExec(image) => Some(image),
}
}
#[inline]
pub fn has_sections(&self) -> bool {
match self {
Self::Dynamic(image) => image.has_sections(),
Self::StaticExec(image) => image.has_sections(),
}
}
#[inline]
pub fn section_headers(&self) -> Option<&[ElfShdr<Arch::Layout>]> {
match self {
Self::Dynamic(image) => image.section_headers(),
Self::StaticExec(image) => image.section_headers(),
}
}
#[inline]
pub fn section(&self, id: ElfSectionId) -> Option<ScannedSection<'_, Arch::Layout>> {
match self {
Self::Dynamic(image) => image.section(id),
Self::StaticExec(image) => image.section(id),
}
}
#[inline]
pub fn sections(&self) -> ScannedSections<'_, Arch::Layout> {
match self {
Self::Dynamic(image) => image.sections(),
Self::StaticExec(image) => image.sections(),
}
}
#[inline]
pub fn alloc_sections(&self) -> impl Iterator<Item = ScannedSection<'_, Arch::Layout>> {
self.sections().filter(|section| section.is_allocated())
}
}
impl<Arch: RelocationArch> ScannedDynamic<Arch> {
pub(crate) fn from_builder(builder: ScanBuilder<Arch::Layout>) -> Result<Self> {
let ScanBuilder {
path,
ehdr,
phdrs,
mut reader,
} = builder;
let interp = read_interp(reader.as_mut(), &phdrs)?;
let DynamicScanParts {
strtab,
needed_libs,
soname,
rpath,
runpath,
} = DynamicScanParts::new(reader.as_mut(), &phdrs)?;
let section_table = SectionTable::new(reader.as_mut(), &ehdr)?;
let strtab_bytes = unsafe { core::slice::from_raw_parts(strtab.as_ptr(), strtab.len()) };
let strtab_view = ElfStringTable::new(MappedView::from_slice(strtab_bytes));
let capability = section_table
.as_ref()
.map_or(ModuleCapability::Opaque, |table| {
classify_module_capability(&table.sections)
});
Ok(Self {
path,
ehdr,
phdrs,
interp,
_strtab_bytes: strtab,
strtab: strtab_view,
section_table,
soname,
rpath,
runpath,
needed_libs,
capability,
reader,
})
}
#[inline]
pub fn path(&self) -> &Path {
self.path.as_path()
}
#[inline]
pub fn name(&self) -> &str {
self.soname().unwrap_or_else(|| self.path().file_name())
}
#[inline]
pub fn ehdr(&self) -> &ElfHeader<Arch::Layout> {
&self.ehdr
}
#[inline]
pub fn phdrs(&self) -> &[ElfPhdr<Arch::Layout>] {
&self.phdrs
}
#[inline]
pub fn interp(&self) -> Option<&str> {
self.interp.as_deref().and_then(|bytes| {
let end = bytes
.iter()
.position(|byte| *byte == 0)
.unwrap_or(bytes.len());
core::str::from_utf8(&bytes[..end]).ok()
})
}
#[inline]
pub fn rpath(&self) -> Option<&str> {
self.rpath.map(|offset| self.strtab.get_str(offset))
}
#[inline]
pub fn runpath(&self) -> Option<&str> {
self.runpath.map(|offset| self.strtab.get_str(offset))
}
#[inline]
pub fn soname(&self) -> Option<&str> {
self.soname.map(|offset| self.strtab.get_str(offset))
}
#[inline]
pub fn needed_lib(&self, index: usize) -> Option<&str> {
self.needed_libs
.get(index)
.map(|offset| self.strtab.get_str(*offset))
}
#[inline]
pub fn needed_libs(&self) -> impl ExactSizeIterator<Item = &str> + '_ {
self.needed_libs
.iter()
.map(|offset| self.strtab.get_str(*offset))
}
#[inline]
pub const fn capability(&self) -> ModuleCapability {
self.capability
}
#[inline]
pub fn has_sections(&self) -> bool {
self.section_table.is_some()
}
#[inline]
pub fn section_headers(&self) -> Option<&[ElfShdr<Arch::Layout>]> {
self.section_table
.as_ref()
.map(|table| table.sections.as_ref())
}
#[inline]
pub fn section(&self, id: ElfSectionId) -> Option<ScannedSection<'_, Arch::Layout>> {
let section_table = self.section_table.as_ref()?;
let shstrtab = section_table.shstrtab();
let header = section_table.sections.get(id.index())?;
Some(ScannedSection::new(
id,
shstrtab.get_str(header.sh_name() as usize),
header,
))
}
#[inline]
pub fn sections(&self) -> ScannedSections<'_, Arch::Layout> {
self.section_table
.as_ref()
.map_or_else(ScannedSections::empty, |table| {
ScannedSections::new(table.sections.as_ref(), table.shstrtab())
})
}
#[inline]
pub fn alloc_sections(&self) -> impl Iterator<Item = ScannedSection<'_, Arch::Layout>> {
self.sections().filter(|section| section.is_allocated())
}
#[inline]
pub fn relocation_sections(&self) -> impl Iterator<Item = ScannedSection<'_, Arch::Layout>> {
self.sections()
.filter(|section| section.is_relocation_section())
}
pub(crate) fn section_data(&self, id: ElfSectionId) -> Result<Option<AlignedBytes>> {
let Some(section) = self.section(id) else {
return Ok(None);
};
if section.is_nobits() {
return Ok(Some(
AlignedBytes::with_len(section.size()).expect("failed to allocate section bytes"),
));
}
Ok(Some(
self.read_bytes(section.file_offset(), section.size())?,
))
}
#[inline]
fn read_bytes(&self, offset: usize, len: usize) -> Result<AlignedBytes> {
let mut bytes = AlignedBytes::with_len(len).ok_or(ParseDynamicError::AddressOverflow)?;
self.reader.read(bytes.as_mut(), offset)?;
Ok(bytes)
}
pub(crate) fn into_load_parts(self) -> ScannedDynamicLoadParts<Arch> {
let Self {
ehdr,
phdrs,
reader,
..
} = self;
ScannedDynamicLoadParts {
ehdr,
phdrs,
reader,
}
}
}
impl<Arch: RelocationArch> ScannedExec<Arch> {
pub(crate) fn from_builder(builder: ScanBuilder<Arch::Layout>) -> Result<Self> {
let ScanBuilder {
path,
ehdr,
phdrs,
mut reader,
} = builder;
let interp = read_interp(reader.as_mut(), &phdrs)?;
let section_table = SectionTable::new(reader.as_mut(), &ehdr)?;
Ok(Self {
path,
ehdr,
phdrs,
interp,
section_table,
reader,
})
}
#[inline]
pub fn path(&self) -> &Path {
self.path.as_path()
}
#[inline]
pub fn name(&self) -> &str {
self.path().file_name()
}
#[inline]
pub fn ehdr(&self) -> &ElfHeader<Arch::Layout> {
&self.ehdr
}
#[inline]
pub fn phdrs(&self) -> &[ElfPhdr<Arch::Layout>] {
&self.phdrs
}
#[inline]
pub fn interp(&self) -> Option<&str> {
self.interp.as_deref().and_then(|bytes| {
let end = bytes
.iter()
.position(|byte| *byte == 0)
.unwrap_or(bytes.len());
core::str::from_utf8(&bytes[..end]).ok()
})
}
#[inline]
pub fn has_sections(&self) -> bool {
self.section_table.is_some()
}
#[inline]
pub fn section_headers(&self) -> Option<&[ElfShdr<Arch::Layout>]> {
self.section_table
.as_ref()
.map(|table| table.sections.as_ref())
}
#[inline]
pub fn section(&self, id: ElfSectionId) -> Option<ScannedSection<'_, Arch::Layout>> {
let section_table = self.section_table.as_ref()?;
let shstrtab = section_table.shstrtab();
let header = section_table.sections.get(id.index())?;
Some(ScannedSection::new(
id,
shstrtab.get_str(header.sh_name() as usize),
header,
))
}
#[inline]
pub fn sections(&self) -> ScannedSections<'_, Arch::Layout> {
self.section_table
.as_ref()
.map_or_else(ScannedSections::empty, |table| {
ScannedSections::new(table.sections.as_ref(), table.shstrtab())
})
}
#[inline]
pub fn alloc_sections(&self) -> impl Iterator<Item = ScannedSection<'_, Arch::Layout>> {
self.sections().filter(|section| section.is_allocated())
}
}
fn classify_module_capability<L: ElfLayout>(sections: &[ElfShdr<L>]) -> ModuleCapability {
for section in sections {
if !matches!(
section.section_type(),
ElfSectionType::REL | ElfSectionType::RELA
) {
continue;
}
if !section.flags().contains(ElfSectionFlags::ALLOC)
&& section.sh_info() != 0
&& section.sh_link() != 0
{
return ModuleCapability::SectionReorderable;
}
}
ModuleCapability::SectionData
}
fn read_interp<L: ElfLayout>(
object: &dyn ElfReader,
phdrs: &[ElfPhdr<L>],
) -> Result<Option<Box<[u8]>>> {
let Some(interp) = phdrs
.iter()
.find(|phdr| phdr.program_type() == ElfProgramType::INTERP)
else {
return Ok(None);
};
let bytes = object.read_to_vec(interp.p_offset(), interp.p_filesz())?;
Ok(Some(bytes.into_boxed_slice()))
}
fn vaddr_to_file_offset<L: ElfLayout>(vaddr: usize, phdrs: &[ElfPhdr<L>]) -> Result<usize> {
for phdr in phdrs
.iter()
.filter(|phdr| phdr.program_type() == ElfProgramType::LOAD)
{
let seg_start = phdr.p_vaddr().get();
let seg_end = seg_start
.checked_add(phdr.p_filesz())
.ok_or(ParseDynamicError::AddressOverflow)?;
if seg_start <= vaddr && vaddr < seg_end {
return phdr
.p_offset()
.checked_add(vaddr - seg_start)
.ok_or(ParseDynamicError::AddressOverflow.into());
}
}
Err(ParsePhdrError::malformed("virtual address is not covered by a PT_LOAD segment").into())
}