1use crate::sys::Result;
4
5const PT_INTERP: u32 = 3;
7const MAX_PHNUM: usize = 512;
9
10#[derive(Clone, Debug, PartialEq, Eq)]
12pub struct ElfInfo {
13 pub interp: Option<Vec<u8>>,
15 pub machine: u16,
17}
18
19#[derive(Clone, Debug, PartialEq, Eq)]
21pub enum FileKind {
22 Elf64(ElfInfo),
24 Elf32(ElfInfo),
26 Shebang(Vec<u8>),
28 Other,
30}
31
32pub fn classify(head: &[u8], read_at: &mut dyn FnMut(u64, usize) -> Result<Vec<u8>>) -> FileKind {
45 if head.starts_with(b"#!") {
46 let line = &head[2..];
47 let end = line
48 .iter()
49 .position(|&b| b == b'\n')
50 .unwrap_or(line.len().min(255));
51 return FileKind::Shebang(line[..end.min(255)].to_vec());
52 }
53 if head.len() < 64 || &head[..4] != b"\x7fELF" {
54 return FileKind::Other;
55 }
56 let is64 = match head[4] {
57 1 => false,
58 2 => true,
59 _ => return FileKind::Other,
60 };
61 if head[5] != 1 {
62 return FileKind::Other;
64 }
65 let machine = u16::from_le_bytes(head[18..20].try_into().unwrap());
66 let wrap = |info: ElfInfo| {
67 if is64 {
68 FileKind::Elf64(info)
69 } else {
70 FileKind::Elf32(info)
71 }
72 };
73 let (phoff, phentsize, phnum, min_ent, off_field, sz_field, width) = if is64 {
76 (
77 u64::from_le_bytes(head[32..40].try_into().unwrap()),
78 u16::from_le_bytes(head[54..56].try_into().unwrap()) as usize,
79 u16::from_le_bytes(head[56..58].try_into().unwrap()) as usize,
80 56,
81 8,
82 32,
83 8,
84 )
85 } else {
86 (
87 u32::from_le_bytes(head[28..32].try_into().unwrap()) as u64,
88 u16::from_le_bytes(head[42..44].try_into().unwrap()) as usize,
89 u16::from_le_bytes(head[44..46].try_into().unwrap()) as usize,
90 32,
91 4,
92 16,
93 4,
94 )
95 };
96 if phentsize < min_ent || phnum == 0 || phnum > MAX_PHNUM {
97 return wrap(ElfInfo {
98 interp: None,
99 machine,
100 });
101 }
102 let table = match read_at(phoff, phentsize * phnum) {
103 Ok(t) if t.len() == phentsize * phnum => t,
104 _ => return FileKind::Other,
105 };
106 let field = |ph: &[u8], at: usize| -> u64 {
107 if width == 8 {
108 u64::from_le_bytes(ph[at..at + 8].try_into().unwrap())
109 } else {
110 u32::from_le_bytes(ph[at..at + 4].try_into().unwrap()) as u64
111 }
112 };
113 for ph in table.chunks_exact(phentsize) {
114 let p_type = u32::from_le_bytes(ph[0..4].try_into().unwrap());
115 if p_type != PT_INTERP {
116 continue;
117 }
118 let offset = field(ph, off_field);
119 let filesz = field(ph, sz_field) as usize;
120 if filesz == 0 || filesz > 4096 {
121 return FileKind::Other;
122 }
123 let mut interp = match read_at(offset, filesz) {
124 Ok(i) => i,
125 Err(_) => return FileKind::Other,
126 };
127 while interp.last() == Some(&0) {
128 interp.pop();
129 }
130 return wrap(ElfInfo {
131 interp: Some(interp),
132 machine,
133 });
134 }
135 wrap(ElfInfo {
136 interp: None,
137 machine,
138 })
139}
140
141pub fn classify_file(host_path: &[u8]) -> Result<FileKind> {
149 use std::io::{Read, Seek, SeekFrom};
150 use std::os::unix::ffi::OsStrExt;
151 let path = std::path::Path::new(std::ffi::OsStr::from_bytes(host_path));
152 let mut f = std::fs::File::open(path).map_err(|e| e.raw_os_error().unwrap_or(libc::EIO))?;
153 if f.metadata().map(|m| m.is_dir()).unwrap_or(false) {
154 return Err(libc::EACCES);
155 }
156 let mut head = vec![0u8; 4096];
157 let mut got = 0;
158 while got < head.len() {
159 match f.read(&mut head[got..]) {
160 Ok(0) => break,
161 Ok(n) => got += n,
162 Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
163 Err(e) => return Err(e.raw_os_error().unwrap_or(libc::EIO)),
164 }
165 }
166 head.truncate(got);
167 let mut read_at = |off: u64, len: usize| -> Result<Vec<u8>> {
168 f.seek(SeekFrom::Start(off))
169 .map_err(|e| e.raw_os_error().unwrap_or(libc::EIO))?;
170 let mut buf = vec![0u8; len];
171 f.read_exact(&mut buf)
172 .map_err(|e| e.raw_os_error().unwrap_or(libc::EIO))?;
173 Ok(buf)
174 };
175 Ok(classify(&head, &mut read_at))
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181
182 fn fake_elf(interp: &[u8]) -> Vec<u8> {
184 let mut f = vec![0u8; 64];
185 f[..4].copy_from_slice(b"\x7fELF");
186 f[4] = 2;
187 f[5] = 1;
188 f[18..20].copy_from_slice(&62u16.to_le_bytes());
189 f[32..40].copy_from_slice(&64u64.to_le_bytes()); f[54..56].copy_from_slice(&56u16.to_le_bytes()); f[56..58].copy_from_slice(&1u16.to_le_bytes()); let mut ph = vec![0u8; 56];
193 ph[0..4].copy_from_slice(&PT_INTERP.to_le_bytes());
194 ph[8..16].copy_from_slice(&120u64.to_le_bytes()); ph[32..40].copy_from_slice(&((interp.len() + 1) as u64).to_le_bytes());
196 f.extend_from_slice(&ph);
197 f.extend_from_slice(interp);
198 f.push(0);
199 f
200 }
201
202 fn fake_elf32(interp: &[u8]) -> Vec<u8> {
204 let mut f = vec![0u8; 52];
205 f[..4].copy_from_slice(b"\x7fELF");
206 f[4] = 1;
207 f[5] = 1;
208 f[28..32].copy_from_slice(&64u32.to_le_bytes()); f[42..44].copy_from_slice(&32u16.to_le_bytes()); f[44..46].copy_from_slice(&1u16.to_le_bytes()); f.resize(64, 0);
212 let mut ph = vec![0u8; 32];
213 ph[0..4].copy_from_slice(&PT_INTERP.to_le_bytes());
214 ph[4..8].copy_from_slice(&96u32.to_le_bytes()); ph[16..20].copy_from_slice(&((interp.len() + 1) as u32).to_le_bytes());
216 f.extend_from_slice(&ph);
217 f.extend_from_slice(interp);
218 f.push(0);
219 f
220 }
221
222 #[test]
223 fn finds_interp_elf32() {
224 let file = fake_elf32(b"/lib/ld-linux.so.2");
225 let mut read_at = |off: u64, len: usize| Ok(file[off as usize..off as usize + len].to_vec());
226 assert_eq!(
227 classify(&file[..64], &mut read_at),
228 FileKind::Elf32(ElfInfo {
229 interp: Some(b"/lib/ld-linux.so.2".to_vec()),
230 machine: 0,
231 })
232 );
233 }
234
235 #[test]
236 fn finds_interp() {
237 let file = fake_elf(b"/lib64/ld-linux-x86-64.so.2");
238 let mut read_at = |off: u64, len: usize| Ok(file[off as usize..off as usize + len].to_vec());
239 assert_eq!(
240 classify(&file[..64], &mut read_at),
241 FileKind::Elf64(ElfInfo {
242 interp: Some(b"/lib64/ld-linux-x86-64.so.2".to_vec()),
243 machine: 62,
244 })
245 );
246 }
247
248 #[test]
249 fn classifies_others() {
250 let mut nop = |_: u64, _: usize| Err(libc::EIO);
251 assert_eq!(
252 classify(b"#!/bin/sh -e\necho", &mut nop),
253 FileKind::Shebang(b"/bin/sh -e".to_vec())
254 );
255 assert_eq!(classify(b"MZ..", &mut nop), FileKind::Other);
256 let mut e32 = vec![0u8; 64];
257 e32[..4].copy_from_slice(b"\x7fELF");
258 e32[4] = 1;
259 e32[5] = 1;
260 assert_eq!(
262 classify(&e32, &mut nop),
263 FileKind::Elf32(ElfInfo {
264 interp: None,
265 machine: 0
266 })
267 );
268 }
269
270 #[test]
271 fn real_binary_has_interp() {
272 if let Ok(FileKind::Elf64(info)) = classify_file(b"/bin/ls") {
273 assert!(info.interp.map(|i| i.contains(&b'/')).unwrap_or(true));
274 }
275 }
276}