1use bstr::{BStr, BString, ByteSlice};
6use ebpf::{EbpfInstruction, EbpfMapType, MapFlags, MapSchema};
7use num_derive::FromPrimitive;
8use starnix_ext::map_ext::EntryExt;
9use std::collections::HashMap;
10use std::io::Read;
11use std::{fs, io, mem};
12use thiserror::Error;
13use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
14
15#[derive(KnownLayout, FromBytes, IntoBytes, Immutable, Eq, PartialEq, Default, Clone)]
16#[repr(C)]
17pub struct ElfIdent {
18 pub magic: [u8; 4],
19 pub class: u8,
20 pub data: u8,
21 pub version: u8,
22 pub osabi: u8,
23 pub abiversion: u8,
24 pub pad: [u8; 7],
25}
26
27#[derive(KnownLayout, FromBytes, IntoBytes, Immutable, Eq, PartialEq, Default, Clone)]
28#[repr(C)]
29pub struct Elf64FileHeader {
30 pub ident: ElfIdent,
31 pub elf_type: u16,
32 pub machine: u16,
33 pub version: u32,
34 pub entry: usize,
35 pub phoff: usize,
36 pub shoff: usize,
37 pub flags: u32,
38 pub ehsize: u16,
39 pub phentsize: u16,
40 pub phnum: u16,
41 pub shentsize: u16,
42 pub shnum: u16,
43 pub shstrndx: u16,
44}
45
46const EM_BPF: u16 = 247;
47
48#[derive(KnownLayout, FromBytes, Immutable, IntoBytes, Eq, PartialEq, Default, Clone, Debug)]
49#[repr(C)]
50pub struct Elf64SectionHeader {
51 pub name: u32,
52 pub type_: u32,
53 pub flags: u64,
54 pub addr: usize,
55 pub offset: usize,
56 pub size: u64,
57 pub link: u32,
58 pub info: u32,
59 pub addralign: u64,
60 pub entsize: u64,
61}
62
63#[derive(Debug, FromPrimitive, Eq, PartialEq, Copy, Clone)]
64#[repr(u8)]
65pub enum Elf64SectionType {
66 Null = 0,
67 Progbits = 1,
68 Symtab = 2,
69 Strtab = 3,
70 Rela = 4,
71 NoBits = 8,
73}
74
75#[derive(KnownLayout, FromBytes, Immutable, IntoBytes, Debug, Eq, PartialEq, Copy, Clone)]
76#[repr(C)]
77pub struct Elf64Symbol {
78 name: u32,
79 info: u8,
80 other: u8,
81 shndx: u16,
82 value: usize,
83 size: u64,
84}
85
86#[derive(KnownLayout, FromBytes, Immutable, IntoBytes, Debug, Eq, PartialEq, Copy, Clone)]
87#[repr(C)]
88struct Elf64_Rel {
89 offset: usize,
90 info: u64,
91}
92
93#[derive(KnownLayout, FromBytes, Immutable, IntoBytes, Debug, Eq, PartialEq, Copy, Clone)]
95#[repr(C)]
96struct bpf_map_def {
97 map_type: EbpfMapType,
98 key_size: u32,
99 value_size: u32,
100 max_entries: u32,
101 flags: u32,
102}
103
104#[derive(Debug)]
105pub struct MapDefinition {
106 pub name: Option<BString>,
108 pub schema: MapSchema,
109}
110
111impl MapDefinition {
112 pub fn name(&self) -> String {
113 self.name
114 .as_ref()
115 .map(|s| s.to_str_lossy().to_string())
116 .unwrap_or_else(|| "<unnamed>".to_owned())
117 }
118}
119
120#[derive(Debug)]
121pub struct ProgramDefinition {
122 pub code: Vec<EbpfInstruction>,
123 pub maps: Vec<MapDefinition>,
124}
125
126#[derive(Error, Debug)]
127pub enum Error {
128 #[error("{}", _0)]
129 IoError(#[from] io::Error),
130 #[error("Invalid ELF file: {}", _0)]
131 ElfParse(&'static str),
132 #[error("Can't find section {:?}", _0)]
133 ElfMissingSection(BString),
134 #[error("InvalidStringIndex {}", _0)]
135 ElfInvalidStringIndex(usize),
136 #[error("InvalidSymbolIndex {}", _0)]
137 ElfInvalidSymIndex(usize),
138 #[error("Can't find function named: {}", _0)]
139 InvalidProgramName(String),
140 #[error("File is not compiled for eBPF")]
141 InvalidArch,
142}
143
144#[derive(Debug)]
145struct ElfFileContents {
146 data: Vec<u8>,
147}
148
149impl ElfFileContents {
150 fn new(data: Vec<u8>) -> Self {
151 Self { data }
152 }
153
154 fn header(&self) -> Result<&Elf64FileHeader, Error> {
155 let (header, _) = Elf64FileHeader::ref_from_prefix(&self.data)
156 .map_err(|_| Error::ElfParse("Failed to load header"))?;
157 Ok(header)
158 }
159
160 fn sections(&self) -> Result<&[Elf64SectionHeader], Error> {
161 let header = self.header()?;
162 let sections_start = header.shoff as usize;
163 let sections_end = header.shoff + header.shnum as usize * header.shentsize as usize;
164 let (sections, _) = <[Elf64SectionHeader]>::ref_from_prefix(
165 &self
166 .data
167 .get(sections_start..sections_end)
168 .ok_or_else(|| Error::ElfParse("Invalid sections header"))?,
169 )
170 .map_err(|_| Error::ElfParse("Failed to load ELF sections"))?;
171 Ok(sections)
172 }
173
174 fn find_section<F>(&self, pred: F) -> Result<Option<&Elf64SectionHeader>, Error>
175 where
176 F: Fn(&Elf64SectionHeader) -> bool,
177 {
178 let sections = self.sections()?;
179 Ok(sections.iter().find(|s| pred(s)))
180 }
181
182 fn get_section_data(&self, section: &Elf64SectionHeader) -> Result<&[u8], Error> {
183 let section_start = section.offset as usize;
184 let section_end = section_start + section.size as usize;
185 self.data
186 .get(section_start..section_end)
187 .ok_or_else(|| Error::ElfParse("Invalid ELF section location"))
188 }
189}
190
191#[derive(Debug)]
192struct StringsSection {
193 data: Vec<u8>,
194}
195
196impl StringsSection {
197 fn get(&self, index: u32) -> Result<Option<&BStr>, Error> {
198 let index = index as usize;
199 if index >= self.data.len() {
200 return Err(Error::ElfInvalidStringIndex(index));
201 }
202 if index == 0 {
203 return Ok(None);
204 }
205 let end = index + self.data[index..].iter().position(|c| *c == 0).unwrap();
206 Ok(Some(<&BStr>::from(&self.data[index..end])))
207 }
208}
209
210#[derive(Debug)]
211struct SymbolInfo<'a> {
212 name: Option<&'a BStr>,
213 section: &'a Elf64SectionHeader,
214 data: &'a [u8],
215 offset: usize,
216}
217
218#[derive(Debug)]
219pub struct ElfFile {
220 contents: ElfFileContents,
221 strings: StringsSection,
222 symbols_header: Elf64SectionHeader,
223}
224
225impl ElfFile {
226 pub fn new(path: &str) -> Result<Self, Error> {
227 let mut data = Vec::new();
228 let mut file = fs::File::open(path)?;
229 file.read_to_end(&mut data)?;
230 let contents = ElfFileContents::new(data);
231
232 let strings = contents
233 .find_section(|s| s.type_ == Elf64SectionType::Strtab as u32)?
234 .ok_or_else(|| Error::ElfParse("Symbols section not found"))?;
235 let strings = contents.get_section_data(strings)?.to_vec();
236 let strings = StringsSection { data: strings };
237
238 let symbols_header = contents
239 .find_section(|s| s.type_ == Elf64SectionType::Symtab as u32)?
240 .ok_or_else(|| Error::ElfParse("Symbols section not found"))?
241 .clone();
242
243 Ok(Self { contents, strings, symbols_header })
244 }
245
246 fn get_section(&self, name: &BStr) -> Result<&[u8], Error> {
247 let header = self
248 .contents
249 .find_section(|s| self.strings.get(s.name).unwrap_or(None) == Some(name))?
250 .ok_or_else(|| Error::ElfMissingSection(name.to_owned()))?;
251 self.contents.get_section_data(header)
252 }
253
254 fn symbols(&self) -> Result<&[Elf64Symbol], Error> {
255 <[Elf64Symbol]>::ref_from_bytes(self.contents.get_section_data(&self.symbols_header)?)
256 .map_err(|_| Error::ElfParse("Invalid ELF symbols table"))
257 }
258
259 fn get_symbol_info(&self, sym: &Elf64Symbol) -> Result<SymbolInfo<'_>, Error> {
260 let sections = self.contents.sections()?;
261 let section = sections
262 .get(sym.shndx as usize)
263 .ok_or_else(|| Error::ElfParse("Invalid section index"))?;
264 let section_data = self.contents.get_section_data(section)?;
265 let offset = sym.value;
266 let end = sym.value + sym.size as usize;
267 let data = section_data
268 .get(offset..end)
269 .ok_or_else(|| Error::ElfParse("Invalid symbol location"))?;
270 let name = self.strings.get(sym.name)?;
271 Ok(SymbolInfo { name, section, data, offset })
272 }
273
274 fn symbol_by_name(
275 &self,
276 name: &BStr,
277 section_name: &BStr,
278 ) -> Result<Option<SymbolInfo<'_>>, Error> {
279 for sym in self
280 .symbols()?
281 .iter()
282 .filter(|s| self.strings.get(s.name).unwrap_or(None) == Some(name))
283 {
284 let info = self.get_symbol_info(sym)?;
285 if self.strings.get(info.section.name)? == Some(section_name) {
286 return Ok(Some(info));
287 }
288 }
289 Ok(None)
290 }
291
292 fn symbol_by_index(&self, index: usize) -> Result<SymbolInfo<'_>, Error> {
293 let sym = self.symbols()?.get(index).ok_or_else(|| Error::ElfInvalidSymIndex(index))?;
294 self.get_symbol_info(sym)
295 }
296}
297
298pub fn load_ebpf_program(
299 path: &str,
300 section_name: &str,
301 program_name: &str,
302) -> Result<ProgramDefinition, Error> {
303 let elf_file = ElfFile::new(path)?;
304 load_ebpf_program_from_file(&elf_file, section_name, program_name)
305}
306
307pub fn load_ebpf_program_from_file(
308 elf_file: &ElfFile,
309 section_name: &str,
310 program_name: &str,
311) -> Result<ProgramDefinition, Error> {
312 if elf_file.contents.header()?.machine != EM_BPF {
313 return Err(Error::InvalidArch);
314 }
315
316 let prog_sym = elf_file
317 .symbol_by_name(program_name.into(), BStr::new(section_name))?
318 .ok_or_else(|| Error::InvalidProgramName(program_name.to_owned()))?;
319 let mut code = <[EbpfInstruction]>::ref_from_bytes(prog_sym.data)
320 .map_err(|_| Error::ElfParse("Failed to load program instructions"))?
321 .to_vec();
322
323 let mut maps = vec![];
326 let mut map_indices = HashMap::new();
327
328 let rel_table_section_name = format!(".rel{}", section_name);
329 let rel_table = match elf_file.get_section(BStr::new(&rel_table_section_name)) {
330 Ok(r) => Some(r),
331 Err(Error::ElfMissingSection(_)) => None,
332 Err(e) => return Err(e),
333 };
334 if let Some(rel_table) = rel_table {
335 let rel_entries = <[Elf64_Rel]>::ref_from_bytes(rel_table)
336 .map_err(|_| Error::ElfParse("Failed to parse .rel section"))?;
337 for rel in rel_entries.iter().filter(|rel| {
338 rel.offset >= prog_sym.offset && rel.offset < prog_sym.offset + prog_sym.data.len()
339 }) {
340 let offset = rel.offset - prog_sym.offset;
341 if offset % mem::size_of::<EbpfInstruction>() != 0 {
342 return Err(Error::ElfParse("Invalid relocation offset"));
343 }
344 let pc = offset / mem::size_of::<EbpfInstruction>();
345 let sym_index = (rel.info >> 32) as usize;
346 let sym = elf_file.symbol_by_index(sym_index)?;
347
348 let is_from_map_section = match sym {
350 SymbolInfo { name: Some(_), section: Elf64SectionHeader { type_, .. }, .. }
351 if *type_ == Elf64SectionType::Progbits as u32 =>
352 {
353 code[pc].set_src_reg(ebpf::BPF_PSEUDO_MAP_IDX);
354 true
355 }
356 SymbolInfo { name: None, section: Elf64SectionHeader { type_, .. }, .. }
357 if *type_ == Elf64SectionType::NoBits as u32 =>
358 {
359 let offset = code[pc].imm();
360 code[pc].set_src_reg(ebpf::BPF_PSEUDO_MAP_IDX_VALUE);
361 code[pc + 1].set_imm(offset);
362 false
363 }
364 _ => return Err(Error::ElfParse("Invalid map symbol")),
365 };
366
367 let map_index = map_indices.entry(sym_index).or_insert_with_fallible(|| {
370 let schema = if is_from_map_section {
371 let Ok((def, _)) = bpf_map_def::ref_from_prefix(sym.data) else {
372 return Err(Error::ElfParse("Failed to load map definition"));
373 };
374 MapSchema {
375 map_type: def.map_type,
376 key_size: def.key_size,
377 value_size: def.value_size,
378 max_entries: def.max_entries,
379 flags: MapFlags::from_bits_truncate(def.flags),
380 }
381 } else {
382 MapSchema {
383 map_type: linux_uapi::bpf_map_type_BPF_MAP_TYPE_ARRAY,
384 key_size: 4,
385 value_size: sym.section.size as u32,
386 max_entries: 1,
387 flags: MapFlags::empty(),
388 }
389 };
390 maps.push(MapDefinition { name: sym.name.map(|x| x.to_owned()), schema });
391 Ok(maps.len() - 1)
392 })?;
393
394 code[pc].set_imm(*map_index as i32);
395 }
396 }
397
398 Ok(ProgramDefinition { code, maps })
399}
400
401#[cfg(test)]
402mod test {
403 use ebpf_api::{AttachType, ProgramType};
404
405 use super::*;
406
407 #[test]
408 fn test_load_ebpf_program() {
409 let ProgramDefinition { code, maps } =
410 load_ebpf_program("/pkg/data/loader_test_prog.o", ".text", "test_prog")
411 .expect("Failed to load program");
412
413 let mut names = maps.iter().map(|m| m.name.clone()).collect::<Vec<_>>();
415 names.sort();
416 assert_eq!(
417 &names,
418 &[None, BStr::new("array").to_owned().into(), BStr::new("hashmap").to_owned().into()]
419 );
420
421 let maps_schema = maps.iter().map(|m| m.schema).collect();
423 let calling_context = ProgramType::SocketFilter
424 .create_calling_context(AttachType::Unspecified, maps_schema)
425 .unwrap();
426
427 ebpf::verify_program(code, calling_context, &mut ebpf::NullVerifierLogger)
428 .expect("Failed to verify loaded program");
429 }
430}