1use 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;
14const DYNSYM: u32 = 11;
15const STRTAB: u32 = 3;
16const DYNAMIC: u32 = 6;
17const VERDEF: u32 = 0x6fff_fffd;
18const VERSYM: u32 = 0x6fff_ffff;
19const TSV_HEADER: &str = "soname\tsymbol\tversion\tdefault\ttype\tbinding\tvisibility\tsection\tsection_alignment\tvalue\tsize\tsame_range\n";
20
21#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
23pub struct Symbol {
24 pub name: String,
26 pub version: Option<String>,
28 pub default_version: Option<bool>,
30 pub symbol_type: u8,
32 pub binding: u8,
34 pub visibility: u8,
36 pub section: u16,
38 pub section_alignment: u64,
40 pub value: u64,
42 pub size: u64,
44 pub same_range: Option<usize>,
46}
47
48#[derive(Clone, Debug, Eq, PartialEq)]
50pub struct Metadata {
51 pub soname: String,
53 pub symbols: Vec<Symbol>,
55}
56
57#[derive(Clone, Copy)]
58struct Section {
59 kind: u32,
60 offset: u64,
61 size: u64,
62 link: u32,
63 info: u32,
64 entry_size: u64,
65 alignment: u64,
66}
67
68struct Reader<'a>(&'a [u8]);
69impl<'a> Reader<'a> {
70 fn slice(&self, offset: u64, size: u64) -> Result<&'a [u8], String> {
71 let end = offset.checked_add(size).ok_or("ELF range overflow")?;
72 let start = usize::try_from(offset).map_err(|_| "ELF offset too large")?;
73 let end = usize::try_from(end).map_err(|_| "ELF range too large")?;
74 self.0
75 .get(start..end)
76 .ok_or_else(|| "ELF range outside input".into())
77 }
78 fn u16(&self, offset: u64) -> Result<u16, String> {
79 let mut bytes = [0; 2];
80 bytes.copy_from_slice(self.slice(offset, 2)?);
81 Ok(u16::from_le_bytes(bytes))
82 }
83 fn u32(&self, offset: u64) -> Result<u32, String> {
84 let mut bytes = [0; 4];
85 bytes.copy_from_slice(self.slice(offset, 4)?);
86 Ok(u32::from_le_bytes(bytes))
87 }
88 fn u64(&self, offset: u64) -> Result<u64, String> {
89 let mut bytes = [0; 8];
90 bytes.copy_from_slice(self.slice(offset, 8)?);
91 Ok(u64::from_le_bytes(bytes))
92 }
93 fn string(&self, offset: u32) -> Result<String, String> {
94 let tail = self
95 .0
96 .get(offset as usize..)
97 .ok_or("ELF string offset outside table")?;
98 let end = tail
99 .iter()
100 .position(|&byte| byte == 0)
101 .ok_or("unterminated ELF string")?;
102 let bytes = &tail[..end];
103 if bytes.is_empty() || bytes.len() > 1024 || !bytes.iter().all(u8::is_ascii_graphic) {
104 return Err("ELF names must be nonempty printable ASCII, at most 1024 bytes".into());
105 }
106 String::from_utf8(bytes.to_vec()).map_err(|_| "invalid ELF string".into())
107 }
108}
109
110fn one(sections: &[Section], kind: u32) -> Result<Option<(usize, Section)>, String> {
111 let mut found = sections
112 .iter()
113 .copied()
114 .enumerate()
115 .filter(|(_, s)| s.kind == kind);
116 let first = found.next();
117 if found.next().is_some() {
118 return Err(format!("multiple ELF sections of type {kind:#x}"));
119 }
120 Ok(first)
121}
122
123fn strings<'a>(r: &Reader<'a>, sections: &[Section], link: u32) -> Result<Reader<'a>, String> {
124 let section = sections
125 .get(link as usize)
126 .ok_or("invalid ELF string-table link")?;
127 if section.kind != STRTAB {
128 return Err("ELF link does not identify a string table".into());
129 }
130 let data = r.slice(section.offset, section.size)?;
131 if data.first() != Some(&0) {
132 return Err("ELF string table lacks initial NUL".into());
133 }
134 Ok(Reader(data))
135}
136
137fn versions(
138 r: &Reader<'_>,
139 section: Section,
140 names: &Reader<'_>,
141) -> Result<BTreeMap<u16, String>, String> {
142 let data = Reader(r.slice(section.offset, section.size)?);
143 if section.info == 0 || section.info > 4096 || u64::from(section.info) > section.size / 20 {
144 return Err("invalid ELF version-definition count".into());
145 }
146 let mut result = BTreeMap::new();
147 let mut offset = 0;
148 let mut name_bytes = 0;
149 for index in 0..section.info {
150 data.slice(offset, 20)?;
151 if data.u16(offset)? != 1 {
152 return Err("unsupported ELF version-definition revision".into());
153 }
154 let id = data.u16(offset + 4)?;
155 let count = data.u16(offset + 6)?;
156 let aux = u64::from(data.u32(offset + 12)?);
157 let next = u64::from(data.u32(offset + 16)?);
158 if id == 0 || id >= 0x8000 || count == 0 || count > 256 || aux < 20 {
159 return Err("invalid ELF version definition".into());
160 }
161 let mut auxiliary = offset + aux;
162 let mut version_name = None;
163 for item in 0..count {
164 data.slice(auxiliary, 8)?;
165 let name = names.string(data.u32(auxiliary)?)?;
166 name_bytes += name.len();
167 if name_bytes > 1024 * 1024 {
168 return Err("ELF version-name budget exceeded".into());
169 }
170 if item == 0 {
171 version_name = Some(name);
172 }
173 let advance = u64::from(data.u32(auxiliary + 4)?);
174 if item + 1 < count {
175 if advance < 8 {
176 return Err("invalid ELF version auxiliary chain".into());
177 }
178 auxiliary += advance;
179 } else if advance != 0 {
180 return Err("unterminated ELF version auxiliary chain".into());
181 }
182 }
183 let name = version_name.ok_or("missing ELF version name")?;
184 if result.insert(id, name).is_some() {
185 return Err("duplicate ELF version index".into());
186 }
187 if index + 1 < section.info {
188 if next < 20 || offset + next < auxiliary + 8 {
189 return Err("invalid ELF version-definition chain".into());
190 }
191 offset += next;
192 } else if next != 0 {
193 return Err("unterminated ELF version-definition chain".into());
194 }
195 }
196 Ok(result)
197}
198
199impl Metadata {
200 pub fn load(path: &Path) -> Result<Self, String> {
207 let file = File::open(path).map_err(|e| format!("{}: {e}", path.display()))?;
208 let mut bytes = Vec::new();
209 file.take(MAX_FILE_BYTES + 1)
210 .read_to_end(&mut bytes)
211 .map_err(|e| e.to_string())?;
212 Self::parse(&bytes).map_err(|e| format!("{}: {e}", path.display()))
213 }
214
215 pub fn parse(bytes: &[u8]) -> Result<Self, String> {
224 if bytes.len() as u64 > MAX_FILE_BYTES {
225 return Err("ELF exceeds 64 MiB input limit".into());
226 }
227 let r = Reader(bytes);
228 let header = r.slice(0, 64)?;
229 if &header[..7] != b"\x7fELF\x02\x01\x01" || !matches!(header[7], 0 | 3) || header[8] != 0 {
230 return Err("expected ELF64 little-endian System V/Linux input".into());
231 }
232 if r.u16(16)? != 3 || r.u16(18)? != 62 || r.u32(20)? != 1 || r.u16(52)? != 64 {
233 return Err("expected an x86_64 ELF shared object".into());
234 }
235 let offset = r.u64(40)?;
236 let count = r.u16(60)?;
237 if offset == 0 || count == 0 || r.u16(58)? != 64 {
238 return Err(
239 "ELF requires ordinary 64-byte section headers; extended/absent tables unsupported"
240 .into(),
241 );
242 }
243 r.slice(offset, u64::from(count) * 64)?;
244 let mut sections = Vec::new();
245 for index in 0..count {
246 let pos = offset + u64::from(index) * 64;
247 sections.push(Section {
248 kind: r.u32(pos + 4)?,
249 offset: r.u64(pos + 24)?,
250 size: r.u64(pos + 32)?,
251 link: r.u32(pos + 40)?,
252 info: r.u32(pos + 44)?,
253 entry_size: r.u64(pos + 56)?,
254 alignment: r.u64(pos + 48)?,
255 });
256 }
257 let (sym_index, sym_section) =
258 one(§ions, DYNSYM)?.ok_or("missing ELF dynamic symbols")?;
259 if sym_section.entry_size != 24 || sym_section.size == 0 || sym_section.size % 24 != 0 {
260 return Err("invalid ELF dynamic-symbol entry size".into());
261 }
262 let names = strings(&r, §ions, sym_section.link)?;
263 let symbols = Reader(r.slice(sym_section.offset, sym_section.size)?);
264 if symbols.slice(0, 24)?.iter().any(|&byte| byte != 0) {
265 return Err("ELF dynamic-symbol zero entry is not null".into());
266 }
267 let (_, dynamic) = one(§ions, DYNAMIC)?.ok_or("missing ELF dynamic section")?;
268 if dynamic.entry_size != 16 || dynamic.size % 16 != 0 {
269 return Err("invalid ELF dynamic entry size".into());
270 }
271 let dynamic_names = strings(&r, §ions, dynamic.link)?;
272 let dynamic_data = Reader(r.slice(dynamic.offset, dynamic.size)?);
273 let mut soname = None;
274 let mut terminated = false;
275 for pos in (0..dynamic.size).step_by(16) {
276 let tag = dynamic_data.u64(pos)?;
277 if tag == 0 {
278 terminated = true;
279 break;
280 }
281 if tag == 14 {
282 let name = u32::try_from(dynamic_data.u64(pos + 8)?)
283 .map_err(|_| "ELF SONAME offset too large")?;
284 if soname.replace(dynamic_names.string(name)?).is_some() {
285 return Err("duplicate ELF SONAME".into());
286 }
287 }
288 }
289 if !terminated {
290 return Err("unterminated ELF dynamic section".into());
291 }
292 let soname = soname.ok_or("ELF shared object lacks SONAME")?;
293 let symbol_count = sym_section.size / 24;
294 if symbol_count > 100_000 {
295 return Err("ELF dynamic-symbol count exceeds 100000".into());
296 }
297 let version_section = one(§ions, VERSYM)?;
298 let version_symbols = if let Some((_, section)) = version_section {
299 if section.link as usize != sym_index
300 || section.entry_size != 2
301 || section.size != symbol_count * 2
302 {
303 return Err("invalid ELF version-symbol table".into());
304 }
305 Some(Reader(r.slice(section.offset, section.size)?))
306 } else {
307 None
308 };
309 let version_names = if let Some((_, section)) = one(§ions, VERDEF)? {
310 if version_symbols.is_none() || section.link != sym_section.link {
311 return Err("invalid ELF version-definition link".into());
312 }
313 versions(&r, section, &names)?
314 } else {
315 BTreeMap::new()
316 };
317 let mut entries = Vec::new();
318 let mut keys = BTreeSet::new();
319 let mut defaults = BTreeSet::new();
320 let mut name_bytes = 0;
321 for index in 1..symbol_count {
322 let pos = index * 24;
323 let info = symbols.slice(pos + 4, 1)?[0];
324 let binding = info >> 4;
325 let section = symbols.u16(pos + 6)?;
326 if section == 0 || binding == 0 {
328 continue;
329 }
330 if !matches!(binding, 1 | 2 | 10) {
331 return Err(format!("unsupported ELF symbol binding {binding}"));
332 }
333 if section == 0xffff
334 || (section as usize >= sections.len() && !matches!(section, 0xfff1 | 0xfff2))
335 {
336 return Err("unsupported or invalid ELF symbol section index".into());
337 }
338 let visibility = symbols.slice(pos + 5, 1)?[0];
339 let section_alignment = sections
340 .get(section as usize)
341 .map_or(0, |section| section.alignment);
342 if section_alignment != 0 && !section_alignment.is_power_of_two() {
343 return Err("invalid ELF section alignment".into());
344 }
345 if visibility > 3 {
346 return Err("unsupported ELF symbol visibility bits".into());
347 }
348 let name = names.string(symbols.u32(pos)?)?;
349 let raw = match &version_symbols {
350 Some(table) => table.u16(index * 2)?,
351 None => 1,
352 };
353 let id = raw & 0x7fff;
354 if section == 0xfff1 && version_names.values().any(|version| version == &name) {
355 continue;
356 }
357 let (version, default_version) = if id > 1 {
358 (
359 Some(
360 version_names
361 .get(&id)
362 .ok_or("undefined ELF version-definition index")?
363 .clone(),
364 ),
365 Some(raw & 0x8000 == 0),
366 )
367 } else {
368 if raw & 0x8000 != 0 || id == 0 {
369 return Err("invalid global ELF symbol version index".into());
370 }
371 (None, None)
372 };
373 name_bytes += name.len() + version.as_ref().map_or(0, String::len);
374 if name_bytes > 8 * 1024 * 1024 {
375 return Err("ELF symbol-name budget exceeded".into());
376 }
377 if !keys.insert((name.clone(), version.clone(), default_version)) {
378 return Err("duplicate ELF symbol definition".into());
379 }
380 if default_version == Some(true) && !defaults.insert(name.clone()) {
381 return Err("multiple default ELF symbol versions".into());
382 }
383 entries.push(Symbol {
384 name,
385 version,
386 default_version,
387 symbol_type: info & 15,
388 binding,
389 visibility,
390 section,
391 section_alignment,
392 value: symbols.u64(pos + 8)?,
393 size: symbols.u64(pos + 16)?,
394 same_range: None,
395 });
396 }
397 entries.sort();
398 let mut ranges = BTreeMap::<_, Vec<usize>>::new();
399 for (index, symbol) in entries.iter().enumerate() {
400 if symbol.section < 0xff00 && symbol.size != 0 {
401 ranges
402 .entry((
403 symbol.section,
404 symbol.value,
405 symbol.symbol_type,
406 symbol.size,
407 ))
408 .or_default()
409 .push(index);
410 }
411 }
412 let mut group = 0;
413 for indexes in ranges.values().filter(|indexes| indexes.len() > 1) {
414 group += 1;
415 for &index in indexes {
416 entries[index].same_range = Some(group);
417 }
418 }
419 Ok(Self {
420 soname,
421 symbols: entries,
422 })
423 }
424
425 pub fn from_tsv(text: &str) -> Result<Vec<Self>, String> {
428 let mut lines = text.lines();
429 if lines.next() != Some(TSV_HEADER.trim_end()) || !text.ends_with('\n') {
430 return Err("not an ELF metadata table".into());
431 }
432 let mut libraries: Vec<Self> = Vec::new();
433 for line in lines {
434 let fields: Vec<&str> = line.split('\t').collect();
435 let [soname, name, version, default, kind, binding, visibility, section, alignment, value, size, group] =
436 fields[..]
437 else {
438 return Err(format!("malformed ELF metadata row {line:?}"));
439 };
440 let number = |text: &str| {
441 text.parse::<u64>()
442 .map_err(|_| format!("invalid number {text:?} in {line:?}"))
443 };
444 let named = |text: &str, names: &[(&str, u8)]| {
445 names
446 .iter()
447 .find(|(n, _)| *n == text)
448 .map(|&(_, v)| v)
449 .ok_or_else(|| format!("unsupported value {text:?} in {line:?}"))
450 };
451 let symbol = Symbol {
452 name: name.into(),
453 version: (version != "-").then(|| version.into()),
454 default_version: match default {
455 "yes" => Some(true),
456 "no" => Some(false),
457 "-" => None,
458 _ => return Err(format!("invalid default {default:?}")),
459 },
460 symbol_type: named(
461 kind,
462 &[
463 ("NOTYPE", 0),
464 ("OBJECT", 1),
465 ("FUNC", 2),
466 ("TLS", 6),
467 ("IFUNC", 10),
468 ],
469 )?,
470 binding: named(binding, &[("GLOBAL", 1), ("WEAK", 2), ("GNU_UNIQUE", 10)])?,
471 visibility: named(
472 visibility,
473 &[
474 ("DEFAULT", 0),
475 ("INTERNAL", 1),
476 ("HIDDEN", 2),
477 ("PROTECTED", 3),
478 ],
479 )?,
480 section: section
481 .parse()
482 .map_err(|_| format!("invalid section in {line:?}"))?,
483 section_alignment: number(alignment)?,
484 value: value
485 .strip_prefix("0x")
486 .and_then(|v| u64::from_str_radix(v, 16).ok())
487 .ok_or_else(|| format!("invalid value in {line:?}"))?,
488 size: number(size)?,
489 same_range: match group {
490 "-" => None,
491 g => Some(number(g)? as usize),
492 },
493 };
494 if soname.is_empty() || name.is_empty() {
495 return Err(format!("empty SONAME or name in {line:?}"));
496 }
497 match libraries.iter_mut().find(|m| m.soname == soname) {
498 Some(library) => library.symbols.push(symbol),
499 None => libraries.push(Self {
500 soname: soname.into(),
501 symbols: vec![symbol],
502 }),
503 }
504 }
505 Ok(libraries)
506 }
507
508 pub fn to_tsv(&self) -> String {
511 let mut text = String::from(TSV_HEADER);
512 for s in &self.symbols {
513 let kind = match s.symbol_type {
514 0 => "NOTYPE".into(),
515 1 => "OBJECT".into(),
516 2 => "FUNC".into(),
517 6 => "TLS".into(),
518 10 => "IFUNC".into(),
519 n => format!("TYPE_{n}"),
520 };
521 let binding = match s.binding {
522 1 => "GLOBAL",
523 2 => "WEAK",
524 10 => "GNU_UNIQUE",
525 _ => "UNKNOWN",
526 };
527 let visibility = match s.visibility {
528 0 => "DEFAULT",
529 1 => "INTERNAL",
530 2 => "HIDDEN",
531 3 => "PROTECTED",
532 _ => "UNKNOWN",
533 };
534 let default = match s.default_version {
535 Some(true) => "yes",
536 Some(false) => "no",
537 None => "-",
538 };
539 let group = s
540 .same_range
541 .map_or_else(|| "-".into(), |value| value.to_string());
542 writeln!(
543 text,
544 "{}\t{}\t{}\t{default}\t{kind}\t{binding}\t{visibility}\t{}\t{}\t0x{:x}\t{}\t{group}",
545 self.soname,
546 s.name,
547 s.version.as_deref().unwrap_or("-"),
548 s.section,
549 s.section_alignment,
550 s.value,
551 s.size
552 )
553 .expect("writing to a String cannot fail");
554 }
555 text
556 }
557}