use crate::{
ParseDynamicError, Result,
elf::{ElfLayout, ElfSymbol, ElfWord, SymbolLookup, SymbolTableView},
memory::{ImageMemory, MappedView, RegionAccess, VmAddr, VmOffset},
segment::ElfSegments,
};
use core::mem::size_of;
#[repr(C)]
#[derive(Clone, Copy)]
struct ElfGnuHeader {
nbucket: u32,
symbias: u32,
nbloom: u32,
nshift: u32,
}
impl ElfGnuHeader {
#[inline]
fn from_bytes(bytes: [u8; size_of::<Self>()]) -> Self {
let [
b0,
b1,
b2,
b3,
s0,
s1,
s2,
s3,
l0,
l1,
l2,
l3,
h0,
h1,
h2,
h3,
] = bytes;
Self {
nbucket: u32::from_ne_bytes([b0, b1, b2, b3]),
symbias: u32::from_ne_bytes([s0, s1, s2, s3]),
nbloom: u32::from_ne_bytes([l0, l1, l2, l3]),
nshift: u32::from_ne_bytes([h0, h1, h2, h3]),
}
}
}
pub(crate) struct ElfGnuHash<L: ElfLayout> {
header: ElfGnuHeader,
blooms: MappedView<L::Word>,
buckets: MappedView<u32>,
chains: MappedView<u32>,
}
impl<L: ElfLayout> Clone for ElfGnuHash<L> {
#[inline]
fn clone(&self) -> Self {
Self {
header: self.header,
blooms: self.blooms.clone(),
buckets: self.buckets.clone(),
chains: self.chains.clone(),
}
}
}
impl<L: ElfLayout> ElfGnuHash<L> {
#[inline]
pub(crate) fn hash(name: &[u8]) -> u64 {
let mut hash = 5381u32;
for byte in name {
hash = hash.wrapping_mul(33).wrapping_add(u32::from(*byte));
}
u64::from(hash)
}
#[inline]
pub(crate) fn parse<R: RegionAccess>(segments: &ElfSegments<R>, addr: VmAddr) -> Result<Self> {
const HEADER_SIZE: usize = size_of::<ElfGnuHeader>();
let start = addr
.checked_offset_from(segments.base())
.ok_or(ParseDynamicError::AddressOverflow)?;
let mut bytes = [0u8; HEADER_SIZE];
segments.read_bytes(addr, &mut bytes)?;
let header = ElfGnuHeader::from_bytes(bytes);
if header.nbloom == 0 {
return Err(ParseDynamicError::EmptyHashTable {
table: "DT_GNU_HASH bloom filter",
}
.into());
}
if header.nbucket == 0 {
return Err(ParseDynamicError::EmptyHashTable {
table: "DT_GNU_HASH bucket table",
}
.into());
}
let bloom_size = (header.nbloom as usize)
.checked_mul(size_of::<L::Word>())
.ok_or(ParseDynamicError::AddressOverflow)?;
let bucket_size = (header.nbucket as usize)
.checked_mul(size_of::<u32>())
.ok_or(ParseDynamicError::AddressOverflow)?;
let blooms_off = start
.checked_add(HEADER_SIZE)
.ok_or(ParseDynamicError::AddressOverflow)?;
let buckets_off = blooms_off
.checked_add(bloom_size)
.ok_or(ParseDynamicError::AddressOverflow)?;
let chains_off = buckets_off
.checked_add(bucket_size)
.ok_or(ParseDynamicError::AddressOverflow)?;
let blooms = segments.read_view(blooms_off, bloom_size).ok_or(
ParseDynamicError::MalformedHashTable {
detail: "DT_GNU_HASH bloom filter size is malformed",
},
)?;
let buckets = segments.read_view(buckets_off, bucket_size).ok_or(
ParseDynamicError::MalformedHashTable {
detail: "DT_GNU_HASH bucket table size is malformed",
},
)?;
let chain_count = Self::count_chain_entries(segments, chains_off, &header, &buckets)?;
let chain_size = chain_count
.checked_mul(size_of::<u32>())
.ok_or(ParseDynamicError::AddressOverflow)?;
let chains = segments.read_view(chains_off, chain_size).ok_or(
ParseDynamicError::MalformedHashTable {
detail: "DT_GNU_HASH chain table size is malformed",
},
)?;
Ok(Self {
header,
blooms,
buckets,
chains,
})
}
fn count_chain_entries<R: RegionAccess>(
segments: &ElfSegments<R>,
chains_off: VmOffset,
header: &ElfGnuHeader,
buckets: &MappedView<u32>,
) -> Result<usize> {
let Some(nsym) = buckets
.as_slice()
.iter()
.copied()
.max()
.map(|idx| idx as usize)
else {
return Ok(0);
};
if nsym == 0 {
return Ok(0);
}
let symbias = header.symbias as usize;
if nsym < symbias {
return Err(ParseDynamicError::GnuHashBucketBeforeSymbolBias {
bucket_symbol: nsym,
symbol_bias: symbias,
}
.into());
}
let mut idx = nsym - symbias;
loop {
let offset = idx
.checked_mul(size_of::<u32>())
.ok_or(ParseDynamicError::AddressOverflow)?;
let offset = chains_off
.checked_add(offset)
.ok_or(ParseDynamicError::AddressOverflow)?;
let mut bytes = [0u8; size_of::<u32>()];
segments.read_bytes(segments.base() + offset, &mut bytes)?;
let value = u32::from_ne_bytes(bytes);
if value & 1 != 0 {
return Ok(idx
.checked_add(1)
.ok_or(ParseDynamicError::AddressOverflow)?);
}
idx = idx
.checked_add(1)
.ok_or(ParseDynamicError::AddressOverflow)?;
}
}
}
impl<L: ElfLayout> ElfGnuHash<L> {
pub(crate) fn count_syms(&self) -> usize {
let mut nsym = 0;
let buckets = self.buckets.as_slice();
let chains = self.chains.as_slice();
for bucket in buckets {
nsym = nsym.max(*bucket as usize);
}
if nsym > 0 {
let mut idx = nsym.saturating_sub(self.header.symbias as usize);
while chains.get(idx).is_some_and(|chain| chain & 1 == 0) {
nsym += 1;
idx += 1;
}
}
nsym + 1
}
pub(crate) fn lookup<'sym, H>(
&self,
table: SymbolTableView<'sym, L, H>,
lookup: &mut SymbolLookup<'_>,
) -> Option<&'sym ElfSymbol<L>> {
let hash = lookup.gnu_hash();
let word_bits = L::Word::BITS;
let fofs = hash as usize / word_bits;
let fmask = 1u64 << (hash as usize % word_bits);
let blooms = self.blooms.as_slice();
let buckets = self.buckets.as_slice();
let chains = self.chains.as_slice();
let bloom_idx = fofs & (self.header.nbloom - 1) as usize;
let filter = blooms.get(bloom_idx)?.to_u64();
if filter & fmask == 0 {
return None;
}
let filter2 = filter >> ((hash >> self.header.nshift) as usize % word_bits);
if filter2 & 1 == 0 {
return None;
}
let table_start_idx = self.header.symbias as usize;
let chain_start_idx =
*buckets.get((hash as usize) % self.header.nbucket as usize)? as usize;
if chain_start_idx == 0 {
return None;
}
#[cfg(feature = "version")]
let mut dynsym_idx = chain_start_idx;
let mut chain_idx = chain_start_idx.checked_sub(table_start_idx)?;
let mut cur_symbol_idx = chain_start_idx;
loop {
let chain_hash = *chains.get(chain_idx)?;
if hash | 1 == chain_hash | 1 {
let cur_symbol = table.symbols().get(cur_symbol_idx)?;
let sym_name = table.strtab.get_str(cur_symbol.st_name());
#[cfg(feature = "version")]
if sym_name == lookup.name() && table.check_match(dynsym_idx, lookup.version()) {
return Some(cur_symbol);
}
#[cfg(not(feature = "version"))]
if sym_name == lookup.name() {
return Some(cur_symbol);
}
}
if chain_hash & 1 != 0 {
break;
}
chain_idx += 1;
cur_symbol_idx += 1;
#[cfg(feature = "version")]
{
dynsym_idx += 1;
}
}
None
}
}