dynamic_loader_cache/
ld_so_1dot7.rs

1// Copyright 2024-2025 Koutheir Attouchi.
2// See the "LICENSE.txt" file at the top-level directory of this distribution.
3//
4// Licensed under the MIT license. This file may not be copied, modified,
5// or distributed except according to those terms.
6
7//! Cache of the GNU/Linux old dynamic loader.
8
9#[cfg(test)]
10mod tests;
11
12use core::ffi::c_uint;
13use core::iter::FusedIterator;
14use core::mem::{offset_of, size_of};
15use std::path::{Path, PathBuf};
16
17use nom::bytes::complete::{tag as nom_tag, take as nom_take};
18use nom::combinator::peek as nom_peek;
19use nom::number::complete::u32 as nom_u32;
20use nom::number::Endianness;
21use nom::sequence::preceded as nom_preceded;
22use static_assertions::assert_eq_size;
23
24use crate::utils::{map_file, CStringTable, HashTableIter};
25use crate::{CacheProvider, Error, Result};
26
27static CACHE_FILE_PATH: &str = "/etc/ld.so.cache";
28
29static MAGIC: &[u8] = b"ld.so-1.7.0";
30
31#[repr(C)]
32struct Header {
33    magic: [u8; 11],
34    padding: [u8; 1],
35    lib_count: c_uint,
36}
37
38#[repr(C)]
39struct Entry {
40    flags: u32,
41    key: u32,
42    value: u32,
43}
44
45#[derive(Debug)]
46struct ParsedHeader {
47    lib_count: usize,
48}
49
50impl ParsedHeader {
51    fn parse(path: &Path, bytes: &[u8]) -> Result<Self> {
52        let (_input, lib_count) =
53            Self::parse_fields(bytes).map_err(|r| Error::from_nom_parse(r, bytes, path))?;
54
55        let result = Self {
56            lib_count: usize::try_from(lib_count)?,
57        };
58
59        result.validate(path, bytes).map(|()| result)
60    }
61
62    fn parse_fields(bytes: &[u8]) -> nom::IResult<&[u8], u32> {
63        use nom::Parser;
64
65        assert_eq_size!(u32, c_uint);
66
67        nom_preceded(
68            nom_preceded(
69                nom_tag(MAGIC),
70                nom_take(offset_of!(Header, lib_count).saturating_sub(offset_of!(Header, padding))),
71            ),
72            nom_u32(Endianness::Native),
73        )
74        .parse(bytes)
75    }
76
77    fn validate(&self, path: &Path, bytes: &[u8]) -> Result<()> {
78        use nom::Parser;
79
80        let max_lib_count = bytes.len().saturating_sub(size_of::<Header>()) / size_of::<Entry>();
81
82        if self.lib_count > max_lib_count {
83            let r = nom::error::make_error(bytes, nom::error::ErrorKind::TooLarge);
84            return Err(Error::from_nom_parse(nom::Err::Error(r), bytes, path));
85        }
86
87        let min_size =
88            size_of::<Header>().saturating_add(size_of::<Entry>().saturating_mul(self.lib_count));
89
90        nom_peek(nom_take(min_size))
91            .parse(bytes)
92            .map(|_| ())
93            .map_err(|r| Error::from_nom_parse(r, bytes, path))
94    }
95}
96
97/// Cache of the GNU/Linux old dynamic loader.
98///
99/// This loads a dynamic loader cache file (*e.g.*, `/etc/ld.so.cache`),
100/// in the old `ld.so-1.7.0` format, for either 32-bits or 64-bits architectures,
101/// in either little-endian or big-endian byte order.
102#[derive(Debug)]
103pub struct Cache {
104    path: PathBuf,
105    map: memmap2::Mmap,
106    lib_count: usize,
107}
108
109impl Cache {
110    /// Create a cache that loads the file `/etc/ld.so.cache`.
111    pub fn load_default() -> Result<Self> {
112        Self::load(CACHE_FILE_PATH)
113    }
114
115    /// Create a cache that loads the specified cache file.
116    pub fn load(path: impl AsRef<Path>) -> Result<Self> {
117        let path = path.as_ref();
118        let map = map_file(path)?;
119        let header = ParsedHeader::parse(path, &map)?;
120
121        Ok(Self {
122            path: path.into(),
123            map,
124            lib_count: header.lib_count,
125        })
126    }
127
128    /// Return an iterator that returns cache entries.
129    pub fn iter(&self) -> Result<impl FusedIterator<Item = Result<crate::Entry<'_>>> + '_> {
130        Ok(HashTableIter::<{ size_of::<Entry>() }, _>::new(
131            &self.map[self.hash_table_range()],
132            CStringTable::new(&self.map[self.string_table_range()], &self.path),
133            self.next_hash_table_entry_parser(),
134        ))
135    }
136
137    fn hash_table_range(&self) -> core::ops::Range<usize> {
138        let size = size_of::<Entry>().saturating_mul(self.lib_count);
139        size_of::<Header>()..size_of::<Header>().saturating_add(size)
140    }
141
142    fn string_table_range(&self) -> core::ops::Range<usize> {
143        let size = size_of::<Entry>().saturating_mul(self.lib_count);
144        size_of::<Header>().saturating_add(size)..self.map.len()
145    }
146
147    fn next_hash_table_entry_parser(
148        &self,
149    ) -> impl nom::Parser<&[u8], Output = (u32, u32), Error = nom::error::Error<&[u8]>> {
150        (
151            nom_preceded(
152                nom_take(offset_of!(Entry, key)),
153                nom_u32(Endianness::Native),
154            ),
155            nom_u32(Endianness::Native),
156        )
157    }
158}
159
160impl CacheProvider for Cache {
161    fn entries_iter<'cache>(
162        &'cache self,
163    ) -> Result<Box<dyn FusedIterator<Item = Result<crate::Entry<'cache>>> + 'cache>> {
164        let iter = self.iter()?;
165        Ok(Box::new(iter))
166    }
167}