1use std::path::Path;
9
10fn read_u16(b: &[u8], is_le: bool) -> u16 {
13 if is_le {
14 u16::from_le_bytes([b[0], b[1]])
15 } else {
16 u16::from_be_bytes([b[0], b[1]])
17 }
18}
19
20fn read_u32(b: &[u8], is_le: bool) -> u32 {
22 if is_le {
23 u32::from_le_bytes([b[0], b[1], b[2], b[3]])
24 } else {
25 u32::from_be_bytes([b[0], b[1], b[2], b[3]])
26 }
27}
28
29fn read_u64(b: &[u8], is_le: bool) -> u64 {
31 if is_le {
32 u64::from_le_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]])
33 } else {
34 u64::from_be_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]])
35 }
36}
37
38fn ph_locator(hdr: &[u8], is_64bit: bool, is_le: bool) -> (u64, u16, u16) {
44 if is_64bit {
45 (
47 read_u64(&hdr[32..40], is_le),
48 read_u16(&hdr[54..56], is_le),
49 read_u16(&hdr[56..58], is_le),
50 )
51 } else {
52 (
54 read_u32(&hdr[28..32], is_le) as u64,
55 read_u16(&hdr[42..44], is_le),
56 read_u16(&hdr[44..46], is_le),
57 )
58 }
59}
60
61pub fn is_dynamically_linked_bytes(bytes: &[u8]) -> bool {
73 if bytes.len() < 6 || &bytes[0..4] != b"\x7fELF" {
75 return false;
76 }
77 let is_64bit = bytes[4] == 2;
78 let is_le = bytes[5] == 1; let min_len = if is_64bit { 58 } else { 46 };
84 if bytes.len() < min_len {
85 return false;
86 }
87
88 let (ph_offset, ph_entry_size, ph_count) = ph_locator(bytes, is_64bit, is_le);
89 if ph_count == 0 || ph_entry_size == 0 {
90 return false;
91 }
92 let (ph_offset, ph_entry_size) = (ph_offset as usize, ph_entry_size as usize);
93
94 const PT_INTERP: u32 = 3;
95 for i in 0..ph_count as usize {
96 let field = i
102 .checked_mul(ph_entry_size)
103 .and_then(|rel| rel.checked_add(ph_offset))
104 .and_then(|start| start.checked_add(4).map(|end| start..end))
105 .and_then(|range| bytes.get(range));
106 let Some(field) = field else {
107 return false;
108 };
109 if read_u32(field, is_le) == PT_INTERP {
110 return true;
111 }
112 }
113 false
114}
115
116pub fn is_dynamically_linked(path: &Path) -> std::io::Result<bool> {
131 use std::io::Read;
132 let mut file = match std::fs::File::open(path) {
133 Ok(f) => f,
134 Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(false),
139 Err(e) => return Err(e),
140 };
141
142 let mut buf = [0u8; 64];
147 let n = file.read(&mut buf)?;
148
149 if &buf[0..4] != b"\x7fELF" {
152 return Ok(false);
153 }
154
155 let is_64bit = buf[4] == 2;
156 let is_le = buf[5] == 1; let min_len = if is_64bit { 58 } else { 46 };
165 if n < min_len {
166 return Ok(false);
167 }
168
169 let (ph_offset, ph_entry_size, ph_count) = ph_locator(&buf, is_64bit, is_le);
171 let ph_entry_size = ph_entry_size as u64;
172
173 if ph_count == 0 || ph_entry_size == 0 {
174 return Ok(false);
175 }
176
177 let total_size = ph_entry_size * ph_count as u64;
181 let mut ph_buf = vec![0u8; total_size as usize];
182 use std::io::Seek;
183 file.seek(std::io::SeekFrom::Start(ph_offset))?;
184 file.read_exact(&mut ph_buf)?;
185
186 const PT_INTERP: u32 = 3;
188 for i in 0..ph_count as usize {
189 let entry_start = i * ph_entry_size as usize;
190 let p_type = read_u32(&ph_buf[entry_start..entry_start + 4], is_le);
191 if p_type == PT_INTERP {
192 return Ok(true);
193 }
194 }
195
196 Ok(false)
197}
198
199#[cfg(test)]
200mod tests {
201 use super::{is_dynamically_linked, is_dynamically_linked_bytes};
202
203 fn elf64_one_phdr(p_type: u32) -> Vec<u8> {
207 let phoff: u64 = 64;
208 let phentsize: u16 = 56;
209 let phnum: u16 = 1;
210 let mut bytes = Vec::with_capacity(64 + phentsize as usize);
211 bytes.extend_from_slice(b"\x7fELF");
212 bytes.push(2); bytes.push(1); bytes.push(1); bytes.extend_from_slice(&[0u8; 9]);
216 bytes.extend_from_slice(&[0u8; 2]); bytes.extend_from_slice(&[0u8; 2]); bytes.extend_from_slice(&[0u8; 4]); bytes.extend_from_slice(&[0u8; 8]); bytes.extend_from_slice(&phoff.to_le_bytes()); bytes.extend_from_slice(&[0u8; 8]); bytes.extend_from_slice(&[0u8; 4]); bytes.extend_from_slice(&[0u8; 2]); bytes.extend_from_slice(&phentsize.to_le_bytes()); bytes.extend_from_slice(&phnum.to_le_bytes()); bytes.extend_from_slice(&[0u8; 6]); bytes.extend_from_slice(&p_type.to_le_bytes()); bytes.extend_from_slice(&vec![0u8; phentsize as usize - 4]);
229 bytes
230 }
231
232 #[test]
235 fn bytes_probe_detects_dynamic_and_static() {
236 assert!(is_dynamically_linked_bytes(&elf64_one_phdr(3))); assert!(!is_dynamically_linked_bytes(&elf64_one_phdr(1))); }
239
240 #[test]
242 fn bytes_probe_rejects_non_elf_and_truncated() {
243 assert!(!is_dynamically_linked_bytes(b"not an ELF"));
244 assert!(!is_dynamically_linked_bytes(b""));
245 let mut short = elf64_one_phdr(3);
247 short.truncate(66); assert!(!is_dynamically_linked_bytes(&short));
249 }
250
251 #[test]
255 fn bytes_probe_absurd_ph_offset_does_not_panic() {
256 let mut evil = elf64_one_phdr(3);
257 evil[32..40].copy_from_slice(&u64::MAX.to_le_bytes());
259 assert!(!is_dynamically_linked_bytes(&evil));
260 }
261
262 #[test]
265 fn missing_file_returns_false() {
266 assert!(
267 !is_dynamically_linked(std::path::Path::new(
268 "/nonexistent/path/to/binary/that/cannot/exist"
269 ))
270 .expect("a missing file is Ok(false), not an error")
271 );
272 }
273
274 #[test]
276 fn short_file_returns_false() {
277 let tmp = tempfile::tempdir().unwrap();
278 let p = tmp.path().join("tiny");
279 std::fs::write(&p, b"abc").unwrap();
280 assert!(!is_dynamically_linked(&p).unwrap());
281 }
282
283 #[test]
288 #[cfg(unix)]
289 fn unreadable_path_is_error() {
290 let tmp = tempfile::tempdir().unwrap();
291 assert!(is_dynamically_linked(tmp.path()).is_err());
292 }
293
294 #[test]
296 fn non_elf_returns_false() {
297 let tmp = tempfile::tempdir().unwrap();
298 let p = tmp.path().join("not-elf");
299 let bytes: Vec<u8> = (0..64u8).collect();
301 std::fs::write(&p, bytes).unwrap();
302 assert!(!is_dynamically_linked(&p).unwrap());
303 }
304
305 #[test]
308 fn elf64_with_pt_interp_returns_true() {
309 let tmp = tempfile::tempdir().unwrap();
310 let p = tmp.path().join("elf64-dyn");
311 let phoff: u64 = 64;
313 let phentsize: u16 = 56;
314 let phnum: u16 = 1;
315 let mut bytes = Vec::with_capacity(64 + phentsize as usize);
316 bytes.extend_from_slice(b"\x7fELF"); bytes.push(2); bytes.push(1); bytes.push(1); bytes.extend_from_slice(&[0u8; 9]); bytes.extend_from_slice(&[0u8; 2]); bytes.extend_from_slice(&[0u8; 2]); bytes.extend_from_slice(&[0u8; 4]); bytes.extend_from_slice(&[0u8; 8]); bytes.extend_from_slice(&phoff.to_le_bytes()); bytes.extend_from_slice(&[0u8; 8]); bytes.extend_from_slice(&[0u8; 4]); bytes.extend_from_slice(&[0u8; 2]); bytes.extend_from_slice(&phentsize.to_le_bytes()); bytes.extend_from_slice(&phnum.to_le_bytes()); bytes.extend_from_slice(&[0u8; 6]); debug_assert_eq!(bytes.len(), 64);
333 bytes.extend_from_slice(&3u32.to_le_bytes());
335 bytes.extend_from_slice(&vec![0u8; phentsize as usize - 4]);
336 std::fs::write(&p, &bytes).unwrap();
337 assert!(
338 is_dynamically_linked(&p).unwrap(),
339 "PT_INTERP must be detected"
340 );
341 }
342
343 #[test]
346 fn elf64_without_pt_interp_returns_false() {
347 let tmp = tempfile::tempdir().unwrap();
348 let p = tmp.path().join("elf64-static");
349 let phoff: u64 = 64;
350 let phentsize: u16 = 56;
351 let phnum: u16 = 1;
352 let mut bytes = Vec::with_capacity(64 + phentsize as usize);
353 bytes.extend_from_slice(b"\x7fELF");
354 bytes.push(2);
355 bytes.push(1);
356 bytes.push(1);
357 bytes.extend_from_slice(&[0u8; 9]);
358 bytes.extend_from_slice(&[0u8; 2]);
359 bytes.extend_from_slice(&[0u8; 2]);
360 bytes.extend_from_slice(&[0u8; 4]);
361 bytes.extend_from_slice(&[0u8; 8]);
362 bytes.extend_from_slice(&phoff.to_le_bytes());
363 bytes.extend_from_slice(&[0u8; 8]);
364 bytes.extend_from_slice(&[0u8; 4]);
365 bytes.extend_from_slice(&[0u8; 2]);
366 bytes.extend_from_slice(&phentsize.to_le_bytes());
367 bytes.extend_from_slice(&phnum.to_le_bytes());
368 bytes.extend_from_slice(&[0u8; 6]);
369 debug_assert_eq!(bytes.len(), 64);
370 bytes.extend_from_slice(&1u32.to_le_bytes());
372 bytes.extend_from_slice(&vec![0u8; phentsize as usize - 4]);
373 std::fs::write(&p, &bytes).unwrap();
374 assert!(!is_dynamically_linked(&p).unwrap());
375 }
376
377 #[test]
382 fn elf32_with_pt_interp_returns_true() {
383 let tmp = tempfile::tempdir().unwrap();
384 let p = tmp.path().join("elf32-dyn");
385 let phoff: u32 = 52;
388 let phentsize: u16 = 32;
389 let phnum: u16 = 1;
390 let mut bytes = Vec::with_capacity(52 + phentsize as usize);
391 bytes.extend_from_slice(b"\x7fELF"); bytes.push(1); bytes.push(1); bytes.push(1); bytes.extend_from_slice(&[0u8; 9]); bytes.extend_from_slice(&[0u8; 2]); bytes.extend_from_slice(&[0u8; 2]); bytes.extend_from_slice(&[0u8; 4]); bytes.extend_from_slice(&[0u8; 4]); bytes.extend_from_slice(&phoff.to_le_bytes()); bytes.extend_from_slice(&[0u8; 4]); bytes.extend_from_slice(&[0u8; 4]); bytes.extend_from_slice(&[0u8; 2]); bytes.extend_from_slice(&phentsize.to_le_bytes()); bytes.extend_from_slice(&phnum.to_le_bytes()); bytes.extend_from_slice(&[0u8; 6]); debug_assert_eq!(bytes.len(), 52);
408 bytes.extend_from_slice(&3u32.to_le_bytes()); bytes.extend_from_slice(&vec![0u8; phentsize as usize - 4]);
410 std::fs::write(&p, &bytes).unwrap();
411 assert!(is_dynamically_linked(&p).unwrap());
412 }
413
414 #[test]
417 fn elf64_big_endian_with_pt_interp_returns_true() {
418 let tmp = tempfile::tempdir().unwrap();
419 let p = tmp.path().join("elf64-be-dyn");
420 let phoff: u64 = 64;
421 let phentsize: u16 = 56;
422 let phnum: u16 = 1;
423 let mut bytes = Vec::with_capacity(64 + phentsize as usize);
424 bytes.extend_from_slice(b"\x7fELF");
425 bytes.push(2);
426 bytes.push(2); bytes.push(1);
428 bytes.extend_from_slice(&[0u8; 9]);
429 bytes.extend_from_slice(&[0u8; 2]);
430 bytes.extend_from_slice(&[0u8; 2]);
431 bytes.extend_from_slice(&[0u8; 4]);
432 bytes.extend_from_slice(&[0u8; 8]);
433 bytes.extend_from_slice(&phoff.to_be_bytes());
434 bytes.extend_from_slice(&[0u8; 8]);
435 bytes.extend_from_slice(&[0u8; 4]);
436 bytes.extend_from_slice(&[0u8; 2]);
437 bytes.extend_from_slice(&phentsize.to_be_bytes());
438 bytes.extend_from_slice(&phnum.to_be_bytes());
439 bytes.extend_from_slice(&[0u8; 6]);
440 debug_assert_eq!(bytes.len(), 64);
441 bytes.extend_from_slice(&3u32.to_be_bytes());
442 bytes.extend_from_slice(&vec![0u8; phentsize as usize - 4]);
443 std::fs::write(&p, &bytes).unwrap();
444 assert!(is_dynamically_linked(&p).unwrap());
445 }
446}