dynamic_loader_cache/
ld_elf_so_hints.rs#[cfg(test)]
mod tests;
use alloc::borrow::Cow;
use alloc::rc::Rc;
use core::iter::FusedIterator;
use core::mem::{offset_of, size_of};
use std::fs::read_dir;
use std::path::Path;
use nom::branch::alt as nom_alt;
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, terminated as nom_terminated};
use crate::utils::{map_file, path_from_bytes};
use crate::{CacheProvider, Error, Result};
pub(crate) static CACHE_FILE_PATHS: &[&str] =
&["/var/run/ld-elf.so.hints", "/var/run/ld-elf32.so.hints"];
const MAGIC: u32 = 0x74_6e_68_45;
const MAGIC_LE32: [u8; 4] = MAGIC.to_le_bytes();
const MAGIC_BE32: [u8; 4] = MAGIC.to_be_bytes();
const VERSION: u32 = 1_u32;
#[repr(C)]
struct Header {
magic: u32,
version: u32,
string_table_offset: u32,
string_table_size: u32,
dir_list_offset: u32,
dir_list_size: u32,
spare: [u32; 26],
}
#[derive(Debug)]
struct ParsedHeader {
string_table_offset: usize,
string_table_size: usize,
dir_list_offset: usize,
dir_list_size: usize,
}
impl ParsedHeader {
fn parse(path: &Path, bytes: &[u8]) -> Result<Self> {
let (input, byte_order) =
Self::parse_byte_order(bytes).map_err(|r| Error::from_nom_parse(r, bytes, path))?;
let (_input, fields) = Self::parse_fields(input, byte_order)
.map_err(|r| Error::from_nom_parse(r, input, path))?;
let result = Self {
string_table_offset: usize::try_from(fields.0)?,
string_table_size: usize::try_from(fields.1)?,
dir_list_offset: usize::try_from(fields.2)?,
dir_list_size: usize::try_from(fields.3)?,
};
result.validate(path, bytes).map(|()| result)
}
fn parse_byte_order(bytes: &[u8]) -> nom::IResult<&[u8], Endianness> {
use nom::Parser;
nom_alt((
nom_tag(&MAGIC_LE32[..]).map(|_| Endianness::Little),
nom_tag(&MAGIC_BE32[..]).map(|_| Endianness::Big),
))
.parse(bytes)
}
fn parse_fields(
bytes: &[u8],
byte_order: Endianness,
) -> nom::IResult<&[u8], (u32, u32, u32, u32)> {
use nom::Parser;
let version_bytes = match byte_order {
Endianness::Big => VERSION.to_be_bytes(),
Endianness::Little => VERSION.to_le_bytes(),
Endianness::Native => VERSION.to_ne_bytes(),
};
let mut parser = (
nom_preceded(nom_tag(&version_bytes[..]), nom_u32(byte_order)),
nom_u32(byte_order),
nom_u32(byte_order),
nom_terminated(
nom_u32(byte_order),
nom_take(size_of::<Header>().saturating_sub(offset_of!(Header, spare))),
),
);
parser.parse(bytes)
}
fn validate(&self, path: &Path, bytes: &[u8]) -> Result<()> {
use nom::Parser;
let size_after_string_table = bytes.len().saturating_sub(self.string_table_offset);
let max_dir_list_size = size_after_string_table
.saturating_sub(self.dir_list_offset)
.saturating_sub(1);
if self.string_table_size > size_after_string_table
|| self.dir_list_offset > size_after_string_table
|| self.dir_list_size > max_dir_list_size
{
let r = nom::error::make_error(bytes, nom::error::ErrorKind::TooLarge);
return Err(Error::from_nom_parse(nom::Err::Error(r), bytes, path));
}
let string_table_end = self
.string_table_offset
.saturating_add(self.string_table_size);
let dir_list_end = self
.string_table_offset
.saturating_add(self.dir_list_offset)
.saturating_add(self.dir_list_size)
.saturating_add(1);
let min_size = string_table_end.max(dir_list_end);
nom_peek(nom_take(min_size))
.parse(bytes)
.map(|_| ())
.map_err(|r| Error::from_nom_parse(r, bytes, path))
}
}
#[derive(Debug)]
pub struct Cache {
map: memmap2::Mmap,
dir_list_offset: usize,
dir_list_size: usize,
}
impl Cache {
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)?;
let dir_list_offset = header
.string_table_offset
.saturating_add(header.dir_list_offset);
Ok(Self {
map,
dir_list_offset,
dir_list_size: header.dir_list_size,
})
}
pub fn iter(&self) -> Result<impl FusedIterator<Item = Result<crate::Entry<'_>>> + '_> {
let bytes = &self.map[self.dir_list_range()];
let iter = bytes
.split(|&b| b == b':')
.map(path_from_bytes)
.filter_map(Result::ok)
.map(Rc::new)
.filter_map(|path| {
read_dir(path.as_ref().as_ref())
.ok()
.map(move |dirs| dirs.map(move |entries| (Rc::clone(&path), entries)))
})
.flatten()
.map(|(path, entry)| match entry {
Ok(entry) => Ok(crate::Entry {
file_name: Cow::Owned(entry.file_name()),
full_path: Cow::Owned(entry.path()),
}),
Err(source) => {
let path = path.as_ref().as_ref().into();
Err(Error::ReadDir { path, source })
}
});
Ok(iter)
}
fn dir_list_range(&self) -> core::ops::Range<usize> {
let end = self.dir_list_offset.saturating_add(self.dir_list_size);
self.dir_list_offset..end
}
}
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))
}
}