dynamic_loader_cache/
ld_so_1dot7.rs#[cfg(test)]
mod tests;
use core::ffi::c_uint;
use core::iter::FusedIterator;
use core::mem::{offset_of, size_of};
use std::path::{Path, PathBuf};
use nom::bytes::complete::{tag as nom_tag, take as nom_take};
use nom::combinator::peek as nom_peek;
use nom::number::complete::u32 as nom_u32;
use nom::number::Endianness;
use nom::sequence::preceded as nom_preceded;
use static_assertions::assert_eq_size;
use crate::utils::{map_file, CStringTable, HashTableIter};
use crate::{CacheProvider, Error, Result};
static CACHE_FILE_PATH: &str = "/etc/ld.so.cache";
static MAGIC: &[u8] = b"ld.so-1.7.0";
#[repr(C)]
struct Header {
magic: [u8; 11],
padding: [u8; 1],
lib_count: c_uint,
}
#[repr(C)]
struct Entry {
flags: u32,
key: u32,
value: u32,
}
#[derive(Debug)]
struct ParsedHeader {
lib_count: usize,
}
impl ParsedHeader {
fn parse(path: &Path, bytes: &[u8]) -> Result<Self> {
let (_input, lib_count) =
Self::parse_fields(bytes).map_err(|r| Error::from_nom_parse(r, bytes, path))?;
let result = Self {
lib_count: usize::try_from(lib_count)?,
};
result.validate(path, bytes).map(|()| result)
}
fn parse_fields(bytes: &[u8]) -> nom::IResult<&[u8], u32> {
use nom::Parser;
assert_eq_size!(u32, c_uint);
nom_preceded(
nom_preceded(
nom_tag(MAGIC),
nom_take(offset_of!(Header, lib_count).saturating_sub(offset_of!(Header, padding))),
),
nom_u32(Endianness::Native),
)
.parse(bytes)
}
fn validate(&self, path: &Path, bytes: &[u8]) -> Result<()> {
use nom::Parser;
let max_lib_count = bytes.len().saturating_sub(size_of::<Header>()) / size_of::<Entry>();
if self.lib_count > max_lib_count {
let r = nom::error::make_error(bytes, nom::error::ErrorKind::TooLarge);
return Err(Error::from_nom_parse(nom::Err::Error(r), bytes, path));
}
let min_size =
size_of::<Header>().saturating_add(size_of::<Entry>().saturating_mul(self.lib_count));
nom_peek(nom_take(min_size))
.parse(bytes)
.map(|_| ())
.map_err(|r| Error::from_nom_parse(r, bytes, path))
}
}
#[derive(Debug)]
pub struct Cache {
path: PathBuf,
map: memmap2::Mmap,
lib_count: usize,
}
impl Cache {
pub fn load_default() -> Result<Self> {
Self::load(CACHE_FILE_PATH)
}
pub fn load(path: impl AsRef<Path>) -> Result<Self> {
let path = path.as_ref();
let map = map_file(path)?;
let header = ParsedHeader::parse(path, &map)?;
Ok(Self {
path: path.into(),
map,
lib_count: header.lib_count,
})
}
pub fn iter(&self) -> Result<impl FusedIterator<Item = Result<crate::Entry<'_>>> + '_> {
Ok(HashTableIter::<{ size_of::<Entry>() }, _>::new(
&self.map[self.hash_table_range()],
CStringTable::new(&self.map[self.string_table_range()], &self.path),
self.next_hash_table_entry_parser(),
))
}
fn hash_table_range(&self) -> core::ops::Range<usize> {
let size = size_of::<Entry>().saturating_mul(self.lib_count);
size_of::<Header>()..size_of::<Header>().saturating_add(size)
}
fn string_table_range(&self) -> core::ops::Range<usize> {
let size = size_of::<Entry>().saturating_mul(self.lib_count);
size_of::<Header>().saturating_add(size)..self.map.len()
}
fn next_hash_table_entry_parser(
&self,
) -> impl nom::Parser<&[u8], Output = (u32, u32), Error = nom::error::Error<&[u8]>> {
(
nom_preceded(
nom_take(offset_of!(Entry, key)),
nom_u32(Endianness::Native),
),
nom_u32(Endianness::Native),
)
}
}
impl CacheProvider for Cache {
fn entries_iter<'cache>(
&'cache self,
) -> Result<Box<dyn FusedIterator<Item = Result<crate::Entry<'cache>>> + 'cache>> {
let iter = self.iter()?;
Ok(Box::new(iter))
}
}