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;
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 pub default_version: Option<bool>,
25 pub symbol_type: u8,
26 pub binding: u8,
27 pub visibility: u8,
28 pub section: u16,
29 pub section_alignment: u64,
31 pub value: u64,
32 pub size: u64,
33 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(§ions, 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, §ions, 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(§ions, 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, §ions, 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(§ions, 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(§ions, 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 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}