Skip to main content

ebpf_loader/
loader.rs

1// Copyright 2025 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use 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    // As Progbits, but requires a array map to store the data
72    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/// Must match `struct bpf_map_def` in `bpf_helpers.h`
94#[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    // The name is missing for array maps that are defined in the bss section.
107    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    // Walk through the relocation table to update all map references
324    // in the program while building the maps list.
325    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            // Determine whether the map is found the map section or bss
349            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            // Insert map index to the code. The actual map address is inserted
368            // later, when the program is linked.
369            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        // Verify that all maps were loaded.
414        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        // Check that the program passes the verifier.
422        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}