blob: afe559e0460a0b110aa0afce8c8a6946668f65ad [file] [log] [blame] [edit]
// Copyright 2025 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use bstr::{BStr, BString, ByteSlice};
use ebpf::{EbpfInstruction, EbpfMapType, MapFlags, MapSchema};
use num_derive::FromPrimitive;
use starnix_ext::map_ext::EntryExt;
use std::collections::HashMap;
use std::io::Read;
use std::{fs, io, mem};
use thiserror::Error;
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
#[derive(KnownLayout, FromBytes, IntoBytes, Immutable, Eq, PartialEq, Default, Clone)]
#[repr(C)]
pub struct ElfIdent {
pub magic: [u8; 4],
pub class: u8,
pub data: u8,
pub version: u8,
pub osabi: u8,
pub abiversion: u8,
pub pad: [u8; 7],
}
#[derive(KnownLayout, FromBytes, IntoBytes, Immutable, Eq, PartialEq, Default, Clone)]
#[repr(C)]
pub struct Elf64FileHeader {
pub ident: ElfIdent,
pub elf_type: u16,
pub machine: u16,
pub version: u32,
pub entry: usize,
pub phoff: usize,
pub shoff: usize,
pub flags: u32,
pub ehsize: u16,
pub phentsize: u16,
pub phnum: u16,
pub shentsize: u16,
pub shnum: u16,
pub shstrndx: u16,
}
const EM_BPF: u16 = 247;
#[derive(KnownLayout, FromBytes, Immutable, IntoBytes, Eq, PartialEq, Default, Clone, Debug)]
#[repr(C)]
pub struct Elf64SectionHeader {
pub name: u32,
pub type_: u32,
pub flags: u64,
pub addr: usize,
pub offset: usize,
pub size: u64,
pub link: u32,
pub info: u32,
pub addralign: u64,
pub entsize: u64,
}
#[derive(Debug, FromPrimitive, Eq, PartialEq, Copy, Clone)]
#[repr(u8)]
pub enum Elf64SectionType {
Null = 0,
Progbits = 1,
Symtab = 2,
Strtab = 3,
Rela = 4,
// As Progbits, but requires a array map to store the data
NoBits = 8,
}
#[derive(KnownLayout, FromBytes, Immutable, IntoBytes, Debug, Eq, PartialEq, Copy, Clone)]
#[repr(C)]
pub struct Elf64Symbol {
name: u32,
info: u8,
other: u8,
shndx: u16,
value: usize,
size: u64,
}
#[derive(KnownLayout, FromBytes, Immutable, IntoBytes, Debug, Eq, PartialEq, Copy, Clone)]
#[repr(C)]
struct Elf64_Rel {
offset: usize,
info: u64,
}
/// Must match `struct bpf_map_def` in `bpf_helpers.h`
#[derive(KnownLayout, FromBytes, Immutable, IntoBytes, Debug, Eq, PartialEq, Copy, Clone)]
#[repr(C)]
struct bpf_map_def {
map_type: EbpfMapType,
key_size: u32,
value_size: u32,
max_entries: u32,
flags: u32,
}
#[derive(Debug)]
pub struct MapDefinition {
// The name is missing for array maps that are defined in the bss section.
pub name: Option<BString>,
pub schema: MapSchema,
}
impl MapDefinition {
pub fn name(&self) -> String {
self.name
.as_ref()
.map(|s| s.to_str_lossy().to_string())
.unwrap_or_else(|| "<unnamed>".to_owned())
}
}
#[derive(Debug)]
pub struct ProgramDefinition {
pub code: Vec<EbpfInstruction>,
pub maps: Vec<MapDefinition>,
}
#[derive(Error, Debug)]
pub enum Error {
#[error("{}", _0)]
IoError(#[from] io::Error),
#[error("Invalid ELF file: {}", _0)]
ElfParse(&'static str),
#[error("Can't find section {:?}", _0)]
ElfMissingSection(BString),
#[error("InvalidStringIndex {}", _0)]
ElfInvalidStringIndex(usize),
#[error("InvalidSymbolIndex {}", _0)]
ElfInvalidSymIndex(usize),
#[error("Can't find function named: {}", _0)]
InvalidProgramName(String),
#[error("File is not compiled for eBPF")]
InvalidArch,
}
#[derive(Debug)]
struct ElfFileContents {
data: Vec<u8>,
}
impl ElfFileContents {
fn new(data: Vec<u8>) -> Self {
Self { data }
}
fn header(&self) -> Result<&Elf64FileHeader, Error> {
let (header, _) = Elf64FileHeader::ref_from_prefix(&self.data)
.map_err(|_| Error::ElfParse("Failed to load header"))?;
Ok(header)
}
fn sections(&self) -> Result<&[Elf64SectionHeader], Error> {
let header = self.header()?;
let sections_start = header.shoff as usize;
let sections_end = header.shoff + header.shnum as usize * header.shentsize as usize;
let (sections, _) = <[Elf64SectionHeader]>::ref_from_prefix(
&self
.data
.get(sections_start..sections_end)
.ok_or_else(|| Error::ElfParse("Invalid sections header"))?,
)
.map_err(|_| Error::ElfParse("Failed to load ELF sections"))?;
Ok(sections)
}
fn find_section<F>(&self, pred: F) -> Result<Option<&Elf64SectionHeader>, Error>
where
F: Fn(&Elf64SectionHeader) -> bool,
{
let sections = self.sections()?;
Ok(sections.iter().find(|s| pred(s)))
}
fn get_section_data(&self, section: &Elf64SectionHeader) -> Result<&[u8], Error> {
let section_start = section.offset as usize;
let section_end = section_start + section.size as usize;
self.data
.get(section_start..section_end)
.ok_or_else(|| Error::ElfParse("Invalid ELF section location"))
}
}
#[derive(Debug)]
struct StringsSection {
data: Vec<u8>,
}
impl StringsSection {
fn get(&self, index: u32) -> Result<Option<&BStr>, Error> {
let index = index as usize;
if index >= self.data.len() {
return Err(Error::ElfInvalidStringIndex(index));
}
if index == 0 {
return Ok(None);
}
let end = index + self.data[index..].iter().position(|c| *c == 0).unwrap();
Ok(Some(<&BStr>::from(&self.data[index..end])))
}
}
#[derive(Debug)]
struct SymbolInfo<'a> {
name: Option<&'a BStr>,
section: &'a Elf64SectionHeader,
data: &'a [u8],
offset: usize,
}
#[derive(Debug)]
pub struct ElfFile {
contents: ElfFileContents,
strings: StringsSection,
symbols_header: Elf64SectionHeader,
}
impl ElfFile {
pub fn new(path: &str) -> Result<Self, Error> {
let mut data = Vec::new();
let mut file = fs::File::open(path)?;
file.read_to_end(&mut data)?;
let contents = ElfFileContents::new(data);
let strings = contents
.find_section(|s| s.type_ == Elf64SectionType::Strtab as u32)?
.ok_or_else(|| Error::ElfParse("Symbols section not found"))?;
let strings = contents.get_section_data(strings)?.to_vec();
let strings = StringsSection { data: strings };
let symbols_header = contents
.find_section(|s| s.type_ == Elf64SectionType::Symtab as u32)?
.ok_or_else(|| Error::ElfParse("Symbols section not found"))?
.clone();
Ok(Self { contents, strings, symbols_header })
}
fn get_section(&self, name: &BStr) -> Result<&[u8], Error> {
let header = self
.contents
.find_section(|s| self.strings.get(s.name).unwrap_or(None) == Some(name))?
.ok_or_else(|| Error::ElfMissingSection(name.to_owned()))?;
self.contents.get_section_data(header)
}
fn symbols(&self) -> Result<&[Elf64Symbol], Error> {
<[Elf64Symbol]>::ref_from_bytes(self.contents.get_section_data(&self.symbols_header)?)
.map_err(|_| Error::ElfParse("Invalid ELF symbols table"))
}
fn get_symbol_info(&self, sym: &Elf64Symbol) -> Result<SymbolInfo<'_>, Error> {
let sections = self.contents.sections()?;
let section = sections
.get(sym.shndx as usize)
.ok_or_else(|| Error::ElfParse("Invalid section index"))?;
let section_data = self.contents.get_section_data(section)?;
let offset = sym.value;
let end = sym.value + sym.size as usize;
let data = section_data
.get(offset..end)
.ok_or_else(|| Error::ElfParse("Invalid symbol location"))?;
let name = self.strings.get(sym.name)?;
Ok(SymbolInfo { name, section, data, offset })
}
fn symbol_by_name(
&self,
name: &BStr,
section_name: &BStr,
) -> Result<Option<SymbolInfo<'_>>, Error> {
for sym in self
.symbols()?
.iter()
.filter(|s| self.strings.get(s.name).unwrap_or(None) == Some(name))
{
let info = self.get_symbol_info(sym)?;
if self.strings.get(info.section.name)? == Some(section_name) {
return Ok(Some(info));
}
}
Ok(None)
}
fn symbol_by_index(&self, index: usize) -> Result<SymbolInfo<'_>, Error> {
let sym = self.symbols()?.get(index).ok_or_else(|| Error::ElfInvalidSymIndex(index))?;
self.get_symbol_info(sym)
}
}
pub fn load_ebpf_program(
path: &str,
section_name: &str,
program_name: &str,
) -> Result<ProgramDefinition, Error> {
let elf_file = ElfFile::new(path)?;
load_ebpf_program_from_file(&elf_file, section_name, program_name)
}
pub fn load_ebpf_program_from_file(
elf_file: &ElfFile,
section_name: &str,
program_name: &str,
) -> Result<ProgramDefinition, Error> {
if elf_file.contents.header()?.machine != EM_BPF {
return Err(Error::InvalidArch);
}
let prog_sym = elf_file
.symbol_by_name(program_name.into(), BStr::new(section_name))?
.ok_or_else(|| Error::InvalidProgramName(program_name.to_owned()))?;
let mut code = <[EbpfInstruction]>::ref_from_bytes(prog_sym.data)
.map_err(|_| Error::ElfParse("Failed to load program instructions"))?
.to_vec();
// Walk through the relocation table to update all map references
// in the program while building the maps list.
let mut maps = vec![];
let mut map_indices = HashMap::new();
let rel_table_section_name = format!(".rel{}", section_name);
let rel_table = match elf_file.get_section(BStr::new(&rel_table_section_name)) {
Ok(r) => Some(r),
Err(Error::ElfMissingSection(_)) => None,
Err(e) => return Err(e),
};
if let Some(rel_table) = rel_table {
let rel_entries = <[Elf64_Rel]>::ref_from_bytes(rel_table)
.map_err(|_| Error::ElfParse("Failed to parse .rel section"))?;
for rel in rel_entries.iter().filter(|rel| {
rel.offset >= prog_sym.offset && rel.offset < prog_sym.offset + prog_sym.data.len()
}) {
let offset = rel.offset - prog_sym.offset;
if offset % mem::size_of::<EbpfInstruction>() != 0 {
return Err(Error::ElfParse("Invalid relocation offset"));
}
let pc = offset / mem::size_of::<EbpfInstruction>();
let sym_index = (rel.info >> 32) as usize;
let sym = elf_file.symbol_by_index(sym_index)?;
// Determine whether the map is found the map section or bss
let is_from_map_section = match sym {
SymbolInfo { name: Some(_), section: Elf64SectionHeader { type_, .. }, .. }
if *type_ == Elf64SectionType::Progbits as u32 =>
{
code[pc].set_src_reg(ebpf::BPF_PSEUDO_MAP_IDX);
true
}
SymbolInfo { name: None, section: Elf64SectionHeader { type_, .. }, .. }
if *type_ == Elf64SectionType::NoBits as u32 =>
{
let offset = code[pc].imm();
code[pc].set_src_reg(ebpf::BPF_PSEUDO_MAP_IDX_VALUE);
code[pc + 1].set_imm(offset);
false
}
_ => return Err(Error::ElfParse("Invalid map symbol")),
};
// Insert map index to the code. The actual map address is inserted
// later, when the program is linked.
let map_index = map_indices.entry(sym_index).or_insert_with_fallible(|| {
let schema = if is_from_map_section {
let Ok((def, _)) = bpf_map_def::ref_from_prefix(sym.data) else {
return Err(Error::ElfParse("Failed to load map definition"));
};
MapSchema {
map_type: def.map_type,
key_size: def.key_size,
value_size: def.value_size,
max_entries: def.max_entries,
flags: MapFlags::from_bits_truncate(def.flags),
}
} else {
MapSchema {
map_type: linux_uapi::bpf_map_type_BPF_MAP_TYPE_ARRAY,
key_size: 4,
value_size: sym.section.size as u32,
max_entries: 1,
flags: MapFlags::empty(),
}
};
maps.push(MapDefinition { name: sym.name.map(|x| x.to_owned()), schema });
Ok(maps.len() - 1)
})?;
code[pc].set_imm(*map_index as i32);
}
}
Ok(ProgramDefinition { code, maps })
}
#[cfg(test)]
mod test {
use ebpf_api::{AttachType, ProgramType};
use super::*;
#[test]
fn test_load_ebpf_program() {
let ProgramDefinition { code, maps } =
load_ebpf_program("/pkg/data/loader_test_prog.o", ".text", "test_prog")
.expect("Failed to load program");
// Verify that all maps were loaded.
let mut names = maps.iter().map(|m| m.name.clone()).collect::<Vec<_>>();
names.sort();
assert_eq!(
&names,
&[None, BStr::new("array").to_owned().into(), BStr::new("hashmap").to_owned().into()]
);
// Check that the program passes the verifier.
let maps_schema = maps.iter().map(|m| m.schema).collect();
let calling_context = ProgramType::SocketFilter
.create_calling_context(AttachType::Unspecified, maps_schema)
.unwrap();
ebpf::verify_program(code, calling_context, &mut ebpf::NullVerifierLogger)
.expect("Failed to verify loaded program");
}
}