Skip to main content

libc_abi_tools/
elf.rs

1//! Bounded ELF64 x86_64 dynamic-symbol evidence. No alias semantics are inferred.
2//!
3//! Layout follows the System V ABI and GNU version-definition extensions.
4use std::{
5    collections::{BTreeMap, BTreeSet},
6    fmt::Write as _,
7    fs::File,
8    io::Read,
9    path::Path,
10};
11
12pub const MAX_FILE_BYTES: u64 = 64 * 1024 * 1024;
13const DYNSYM: u32 = 11;
14const STRTAB: u32 = 3;
15const DYNAMIC: u32 = 6;
16const VERDEF: u32 = 0x6fff_fffd;
17const VERSYM: u32 = 0x6fff_ffff;
18
19#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
20pub struct Symbol {
21    pub name: String,
22    pub version: Option<String>,
23    /// None for unversioned symbols; false for GNU nondefault versions.
24    pub default_version: Option<bool>,
25    pub symbol_type: u8,
26    pub binding: u8,
27    pub visibility: u8,
28    pub section: u16,
29    /// Declared alignment of the defining section; zero for special indexes.
30    pub section_alignment: u64,
31    pub value: u64,
32    pub size: u64,
33    /// Same section/value/type/size in this file, not a proven semantic alias.
34    pub same_range: Option<usize>,
35}
36
37#[derive(Clone, Debug, Eq, PartialEq)]
38pub struct Metadata {
39    pub soname: String,
40    pub symbols: Vec<Symbol>,
41}
42
43#[derive(Clone, Copy)]
44struct Section {
45    kind: u32,
46    offset: u64,
47    size: u64,
48    link: u32,
49    info: u32,
50    entry_size: u64,
51    alignment: u64,
52}
53
54struct Reader<'a>(&'a [u8]);
55impl<'a> Reader<'a> {
56    fn slice(&self, offset: u64, size: u64) -> Result<&'a [u8], String> {
57        let end = offset.checked_add(size).ok_or("ELF range overflow")?;
58        let start = usize::try_from(offset).map_err(|_| "ELF offset too large")?;
59        let end = usize::try_from(end).map_err(|_| "ELF range too large")?;
60        self.0
61            .get(start..end)
62            .ok_or_else(|| "ELF range outside input".into())
63    }
64    fn u16(&self, offset: u64) -> Result<u16, String> {
65        let mut bytes = [0; 2];
66        bytes.copy_from_slice(self.slice(offset, 2)?);
67        Ok(u16::from_le_bytes(bytes))
68    }
69    fn u32(&self, offset: u64) -> Result<u32, String> {
70        let mut bytes = [0; 4];
71        bytes.copy_from_slice(self.slice(offset, 4)?);
72        Ok(u32::from_le_bytes(bytes))
73    }
74    fn u64(&self, offset: u64) -> Result<u64, String> {
75        let mut bytes = [0; 8];
76        bytes.copy_from_slice(self.slice(offset, 8)?);
77        Ok(u64::from_le_bytes(bytes))
78    }
79    fn string(&self, offset: u32) -> Result<String, String> {
80        let tail = self
81            .0
82            .get(offset as usize..)
83            .ok_or("ELF string offset outside table")?;
84        let end = tail
85            .iter()
86            .position(|&byte| byte == 0)
87            .ok_or("unterminated ELF string")?;
88        let bytes = &tail[..end];
89        if bytes.is_empty() || bytes.len() > 1024 || !bytes.iter().all(u8::is_ascii_graphic) {
90            return Err("ELF names must be nonempty printable ASCII, at most 1024 bytes".into());
91        }
92        String::from_utf8(bytes.to_vec()).map_err(|_| "invalid ELF string".into())
93    }
94}
95
96fn one(sections: &[Section], kind: u32) -> Result<Option<(usize, Section)>, String> {
97    let mut found = sections
98        .iter()
99        .copied()
100        .enumerate()
101        .filter(|(_, s)| s.kind == kind);
102    let first = found.next();
103    if found.next().is_some() {
104        return Err(format!("multiple ELF sections of type {kind:#x}"));
105    }
106    Ok(first)
107}
108
109fn strings<'a>(r: &Reader<'a>, sections: &[Section], link: u32) -> Result<Reader<'a>, String> {
110    let section = sections
111        .get(link as usize)
112        .ok_or("invalid ELF string-table link")?;
113    if section.kind != STRTAB {
114        return Err("ELF link does not identify a string table".into());
115    }
116    let data = r.slice(section.offset, section.size)?;
117    if data.first() != Some(&0) {
118        return Err("ELF string table lacks initial NUL".into());
119    }
120    Ok(Reader(data))
121}
122
123fn versions(
124    r: &Reader<'_>,
125    section: Section,
126    names: &Reader<'_>,
127) -> Result<BTreeMap<u16, String>, String> {
128    let data = Reader(r.slice(section.offset, section.size)?);
129    if section.info == 0 || section.info > 4096 || u64::from(section.info) > section.size / 20 {
130        return Err("invalid ELF version-definition count".into());
131    }
132    let mut result = BTreeMap::new();
133    let mut offset = 0;
134    let mut name_bytes = 0;
135    for index in 0..section.info {
136        data.slice(offset, 20)?;
137        if data.u16(offset)? != 1 {
138            return Err("unsupported ELF version-definition revision".into());
139        }
140        let id = data.u16(offset + 4)?;
141        let count = data.u16(offset + 6)?;
142        let aux = u64::from(data.u32(offset + 12)?);
143        let next = u64::from(data.u32(offset + 16)?);
144        if id == 0 || id >= 0x8000 || count == 0 || count > 256 || aux < 20 {
145            return Err("invalid ELF version definition".into());
146        }
147        let mut auxiliary = offset + aux;
148        let mut version_name = None;
149        for item in 0..count {
150            data.slice(auxiliary, 8)?;
151            let name = names.string(data.u32(auxiliary)?)?;
152            name_bytes += name.len();
153            if name_bytes > 1024 * 1024 {
154                return Err("ELF version-name budget exceeded".into());
155            }
156            if item == 0 {
157                version_name = Some(name);
158            }
159            let advance = u64::from(data.u32(auxiliary + 4)?);
160            if item + 1 < count {
161                if advance < 8 {
162                    return Err("invalid ELF version auxiliary chain".into());
163                }
164                auxiliary += advance;
165            } else if advance != 0 {
166                return Err("unterminated ELF version auxiliary chain".into());
167            }
168        }
169        let name = version_name.ok_or("missing ELF version name")?;
170        if result.insert(id, name).is_some() {
171            return Err("duplicate ELF version index".into());
172        }
173        if index + 1 < section.info {
174            if next < 20 || offset + next < auxiliary + 8 {
175                return Err("invalid ELF version-definition chain".into());
176            }
177            offset += next;
178        } else if next != 0 {
179            return Err("unterminated ELF version-definition chain".into());
180        }
181    }
182    Ok(result)
183}
184
185impl Metadata {
186    pub fn load(path: &Path) -> Result<Self, String> {
187        let file = File::open(path).map_err(|e| format!("{}: {e}", path.display()))?;
188        let mut bytes = Vec::new();
189        file.take(MAX_FILE_BYTES + 1)
190            .read_to_end(&mut bytes)
191            .map_err(|e| e.to_string())?;
192        Self::parse(&bytes).map_err(|e| format!("{}: {e}", path.display()))
193    }
194
195    pub fn parse(bytes: &[u8]) -> Result<Self, String> {
196        if bytes.len() as u64 > MAX_FILE_BYTES {
197            return Err("ELF exceeds 64 MiB input limit".into());
198        }
199        let r = Reader(bytes);
200        let header = r.slice(0, 64)?;
201        if &header[..7] != b"\x7fELF\x02\x01\x01" || !matches!(header[7], 0 | 3) || header[8] != 0 {
202            return Err("expected ELF64 little-endian System V/Linux input".into());
203        }
204        if r.u16(16)? != 3 || r.u16(18)? != 62 || r.u32(20)? != 1 || r.u16(52)? != 64 {
205            return Err("expected an x86_64 ELF shared object".into());
206        }
207        let offset = r.u64(40)?;
208        let count = r.u16(60)?;
209        if offset == 0 || count == 0 || r.u16(58)? != 64 {
210            return Err(
211                "ELF requires ordinary 64-byte section headers; extended/absent tables unsupported"
212                    .into(),
213            );
214        }
215        r.slice(offset, u64::from(count) * 64)?;
216        let mut sections = Vec::new();
217        for index in 0..count {
218            let pos = offset + u64::from(index) * 64;
219            sections.push(Section {
220                kind: r.u32(pos + 4)?,
221                offset: r.u64(pos + 24)?,
222                size: r.u64(pos + 32)?,
223                link: r.u32(pos + 40)?,
224                info: r.u32(pos + 44)?,
225                entry_size: r.u64(pos + 56)?,
226                alignment: r.u64(pos + 48)?,
227            });
228        }
229        let (sym_index, sym_section) =
230            one(&sections, DYNSYM)?.ok_or("missing ELF dynamic symbols")?;
231        if sym_section.entry_size != 24 || sym_section.size == 0 || sym_section.size % 24 != 0 {
232            return Err("invalid ELF dynamic-symbol entry size".into());
233        }
234        let names = strings(&r, &sections, sym_section.link)?;
235        let symbols = Reader(r.slice(sym_section.offset, sym_section.size)?);
236        if symbols.slice(0, 24)?.iter().any(|&byte| byte != 0) {
237            return Err("ELF dynamic-symbol zero entry is not null".into());
238        }
239        let (_, dynamic) = one(&sections, DYNAMIC)?.ok_or("missing ELF dynamic section")?;
240        if dynamic.entry_size != 16 || dynamic.size % 16 != 0 {
241            return Err("invalid ELF dynamic entry size".into());
242        }
243        let dynamic_names = strings(&r, &sections, dynamic.link)?;
244        let dynamic_data = Reader(r.slice(dynamic.offset, dynamic.size)?);
245        let mut soname = None;
246        let mut terminated = false;
247        for pos in (0..dynamic.size).step_by(16) {
248            let tag = dynamic_data.u64(pos)?;
249            if tag == 0 {
250                terminated = true;
251                break;
252            }
253            if tag == 14 {
254                let name = u32::try_from(dynamic_data.u64(pos + 8)?)
255                    .map_err(|_| "ELF SONAME offset too large")?;
256                if soname.replace(dynamic_names.string(name)?).is_some() {
257                    return Err("duplicate ELF SONAME".into());
258                }
259            }
260        }
261        if !terminated {
262            return Err("unterminated ELF dynamic section".into());
263        }
264        let soname = soname.ok_or("ELF shared object lacks SONAME")?;
265        let symbol_count = sym_section.size / 24;
266        if symbol_count > 100_000 {
267            return Err("ELF dynamic-symbol count exceeds 100000".into());
268        }
269        let version_section = one(&sections, VERSYM)?;
270        let version_symbols = if let Some((_, section)) = version_section {
271            if section.link as usize != sym_index
272                || section.entry_size != 2
273                || section.size != symbol_count * 2
274            {
275                return Err("invalid ELF version-symbol table".into());
276            }
277            Some(Reader(r.slice(section.offset, section.size)?))
278        } else {
279            None
280        };
281        let version_names = if let Some((_, section)) = one(&sections, VERDEF)? {
282            if version_symbols.is_none() || section.link != sym_section.link {
283                return Err("invalid ELF version-definition link".into());
284            }
285            versions(&r, section, &names)?
286        } else {
287            BTreeMap::new()
288        };
289        let mut entries = Vec::new();
290        let mut keys = BTreeSet::new();
291        let mut defaults = BTreeSet::new();
292        let mut name_bytes = 0;
293        for index in 1..symbol_count {
294            let pos = index * 24;
295            let info = symbols.slice(pos + 4, 1)?[0];
296            let binding = info >> 4;
297            let section = symbols.u16(pos + 6)?;
298            // Imported or local symbols are not definitions supplied by this DSO.
299            if section == 0 || binding == 0 {
300                continue;
301            }
302            if !matches!(binding, 1 | 2 | 10) {
303                return Err(format!("unsupported ELF symbol binding {binding}"));
304            }
305            if section == 0xffff
306                || (section as usize >= sections.len() && !matches!(section, 0xfff1 | 0xfff2))
307            {
308                return Err("unsupported or invalid ELF symbol section index".into());
309            }
310            let visibility = symbols.slice(pos + 5, 1)?[0];
311            let section_alignment = sections
312                .get(section as usize)
313                .map_or(0, |section| section.alignment);
314            if section_alignment != 0 && !section_alignment.is_power_of_two() {
315                return Err("invalid ELF section alignment".into());
316            }
317            if visibility > 3 {
318                return Err("unsupported ELF symbol visibility bits".into());
319            }
320            let name = names.string(symbols.u32(pos)?)?;
321            let raw = match &version_symbols {
322                Some(table) => table.u16(index * 2)?,
323                None => 1,
324            };
325            let id = raw & 0x7fff;
326            if section == 0xfff1 && version_names.values().any(|version| version == &name) {
327                continue;
328            }
329            let (version, default_version) = if id > 1 {
330                (
331                    Some(
332                        version_names
333                            .get(&id)
334                            .ok_or("undefined ELF version-definition index")?
335                            .clone(),
336                    ),
337                    Some(raw & 0x8000 == 0),
338                )
339            } else {
340                if raw & 0x8000 != 0 || id == 0 {
341                    return Err("invalid global ELF symbol version index".into());
342                }
343                (None, None)
344            };
345            name_bytes += name.len() + version.as_ref().map_or(0, String::len);
346            if name_bytes > 8 * 1024 * 1024 {
347                return Err("ELF symbol-name budget exceeded".into());
348            }
349            if !keys.insert((name.clone(), version.clone(), default_version)) {
350                return Err("duplicate ELF symbol definition".into());
351            }
352            if default_version == Some(true) && !defaults.insert(name.clone()) {
353                return Err("multiple default ELF symbol versions".into());
354            }
355            entries.push(Symbol {
356                name,
357                version,
358                default_version,
359                symbol_type: info & 15,
360                binding,
361                visibility,
362                section,
363                section_alignment,
364                value: symbols.u64(pos + 8)?,
365                size: symbols.u64(pos + 16)?,
366                same_range: None,
367            });
368        }
369        entries.sort();
370        let mut ranges = BTreeMap::<_, Vec<usize>>::new();
371        for (index, symbol) in entries.iter().enumerate() {
372            if symbol.section < 0xff00 && symbol.size != 0 {
373                ranges
374                    .entry((
375                        symbol.section,
376                        symbol.value,
377                        symbol.symbol_type,
378                        symbol.size,
379                    ))
380                    .or_default()
381                    .push(index);
382            }
383        }
384        let mut group = 0;
385        for indexes in ranges.values().filter(|indexes| indexes.len() > 1) {
386            group += 1;
387            for &index in indexes {
388                entries[index].same_range = Some(group);
389            }
390        }
391        Ok(Self {
392            soname,
393            symbols: entries,
394        })
395    }
396
397    pub fn to_tsv(&self) -> String {
398        let mut text = String::from("soname\tsymbol\tversion\tdefault\ttype\tbinding\tvisibility\tsection\tsection_alignment\tvalue\tsize\tsame_range\n");
399        for s in &self.symbols {
400            let kind = match s.symbol_type {
401                0 => "NOTYPE".into(),
402                1 => "OBJECT".into(),
403                2 => "FUNC".into(),
404                6 => "TLS".into(),
405                10 => "IFUNC".into(),
406                n => format!("TYPE_{n}"),
407            };
408            let binding = match s.binding {
409                1 => "GLOBAL",
410                2 => "WEAK",
411                10 => "GNU_UNIQUE",
412                _ => "UNKNOWN",
413            };
414            let visibility = match s.visibility {
415                0 => "DEFAULT",
416                1 => "INTERNAL",
417                2 => "HIDDEN",
418                3 => "PROTECTED",
419                _ => "UNKNOWN",
420            };
421            let default = match s.default_version {
422                Some(true) => "yes",
423                Some(false) => "no",
424                None => "-",
425            };
426            let group = s
427                .same_range
428                .map_or_else(|| "-".into(), |value| value.to_string());
429            writeln!(
430                text,
431                "{}\t{}\t{}\t{default}\t{kind}\t{binding}\t{visibility}\t{}\t{}\t0x{:x}\t{}\t{group}",
432                self.soname,
433                s.name,
434                s.version.as_deref().unwrap_or("-"),
435                s.section,
436                s.section_alignment,
437                s.value,
438                s.size
439            )
440            .expect("writing to a String cannot fail");
441        }
442        text
443    }
444}