Skip to content

Commit

Permalink
Adds Executable::load_with_strict_parser().
Browse files Browse the repository at this point in the history
  • Loading branch information
Lichtso committed Nov 15, 2024
1 parent 29b7d22 commit 709fde2
Show file tree
Hide file tree
Showing 3 changed files with 208 additions and 2 deletions.
203 changes: 202 additions & 1 deletion src/elf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -396,11 +396,212 @@ impl<C: ContextObject> Executable<C> {
return Err(ElfError::UnsupportedSBPFVersion);
}

let mut executable = Self::load_with_lenient_parser(bytes, loader)?;
let mut executable = if sbpf_version.enable_stricter_elf_headers() {
Self::load_with_strict_parser(bytes, loader)?
} else {
Self::load_with_lenient_parser(bytes, loader)?
};
executable.sbpf_version = sbpf_version;
Ok(executable)
}

/// Loads an ELF without relocation
fn load_with_strict_parser(
bytes: &[u8],
loader: Arc<BuiltinProgram<C>>,
) -> Result<Self, ElfParserError> {
use crate::elf_parser::{
consts::{
ELFMAG, EV_CURRENT, PF_R, PF_W, PF_X, PT_GNU_STACK, PT_LOAD, PT_NULL, SHN_UNDEF,
STT_FUNC,
},
types::{Elf64Ehdr, Elf64Shdr, Elf64Sym},
};

let aligned_memory = AlignedMemory::<{ HOST_ALIGN }>::from_slice(bytes);
let elf_bytes = aligned_memory.as_slice();

let (file_header_range, file_header) = Elf64::parse_file_header(elf_bytes)?;
let program_header_table_range = mem::size_of::<Elf64Ehdr>()
..mem::size_of::<Elf64Phdr>()
.saturating_mul(file_header.e_phnum as usize)
.saturating_add(mem::size_of::<Elf64Ehdr>());
if file_header.e_ident.ei_mag != ELFMAG
|| file_header.e_ident.ei_class != ELFCLASS64
|| file_header.e_ident.ei_data != ELFDATA2LSB
|| file_header.e_ident.ei_version != EV_CURRENT as u8
|| file_header.e_ident.ei_osabi != ELFOSABI_NONE
|| file_header.e_ident.ei_abiversion != 0x00
|| file_header.e_ident.ei_pad != [0x00; 7]
|| file_header.e_type != ET_DYN
|| file_header.e_machine != EM_SBPF
|| file_header.e_version != EV_CURRENT
// file_header.e_entry
|| file_header.e_phoff != mem::size_of::<Elf64Ehdr>() as u64
// file_header.e_shoff
// file_header.e_flags
|| file_header.e_ehsize != mem::size_of::<Elf64Ehdr>() as u16
|| file_header.e_phentsize != mem::size_of::<Elf64Phdr>() as u16
|| file_header.e_phnum < EXPECTED_PROGRAM_HEADERS.len() as u16
|| program_header_table_range.end >= elf_bytes.len()
|| file_header.e_shentsize != mem::size_of::<Elf64Shdr>() as u16
// file_header.e_shnum
|| file_header.e_shstrndx >= file_header.e_shnum
{
return Err(ElfParserError::InvalidFileHeader);
}

const EXPECTED_PROGRAM_HEADERS: [(u32, u32, u64); 5] = [
(PT_LOAD, PF_X, ebpf::MM_BYTECODE_START), // byte code
(PT_LOAD, PF_R, ebpf::MM_RODATA_START), // read only data
(PT_GNU_STACK, PF_R | PF_W, ebpf::MM_STACK_START), // stack
(PT_LOAD, PF_R | PF_W, ebpf::MM_HEAP_START), // heap
(PT_NULL, 0, 0xFFFFFFFF00000000), // dynamic symbol table
];
let program_header_table =
Elf64::slice_from_bytes::<Elf64Phdr>(elf_bytes, program_header_table_range.clone())?;
for (program_header, (p_type, p_flags, p_vaddr)) in program_header_table
.iter()
.zip(EXPECTED_PROGRAM_HEADERS.iter())
{
let p_filesz = if (*p_flags & PF_W) != 0 {
0
} else {
program_header.p_memsz
};
if program_header.p_type != *p_type
|| program_header.p_flags != *p_flags
|| program_header.p_offset < program_header_table_range.end as u64
|| program_header.p_offset >= elf_bytes.len() as u64
|| program_header.p_offset.checked_rem(ebpf::INSN_SIZE as u64) != Some(0)
|| program_header.p_vaddr != *p_vaddr
|| program_header.p_paddr != *p_vaddr
|| program_header.p_filesz != p_filesz
|| program_header.p_filesz
> (elf_bytes.len() as u64).saturating_sub(program_header.p_offset)
|| program_header.p_memsz >= ebpf::MM_REGION_SIZE
{
return Err(ElfParserError::InvalidProgramHeader);
}
}

let config = loader.get_config();
let symbol_names_section_header = if config.enable_symbol_and_section_labels {
let (_section_header_table_range, section_header_table) =
Elf64::parse_section_header_table(
elf_bytes,
file_header_range.clone(),
file_header,
program_header_table_range.clone(),
)?;
let section_names_section_header = (file_header.e_shstrndx != SHN_UNDEF)
.then(|| {
section_header_table
.get(file_header.e_shstrndx as usize)
.ok_or(ElfParserError::OutOfBounds)
})
.transpose()?
.ok_or(ElfParserError::NoSectionNameStringTable)?;
let mut symbol_names_section_header = None;
for section_header in section_header_table.iter() {
let section_name = Elf64::get_string_in_section(
elf_bytes,
section_names_section_header,
section_header.sh_name,
64,
)?;
if section_name == b".dynstr" {
symbol_names_section_header = Some(section_header);
}
}
symbol_names_section_header
} else {
None
};
let bytecode_header = &program_header_table[0];
let rodata_header = &program_header_table[1];
let dynamic_symbol_table: &[Elf64Sym] =
Elf64::slice_from_program_header(elf_bytes, &program_header_table[4])?;
let mut function_registry = FunctionRegistry::<usize>::default();
let mut expected_symbol_address = bytecode_header.p_vaddr;
for symbol in dynamic_symbol_table {
if symbol.st_info & STT_FUNC == 0 {
continue;
}
if symbol.st_value != expected_symbol_address {
return Err(ElfParserError::OutOfBounds);
}
if symbol.st_size == 0 || symbol.st_size.checked_rem(ebpf::INSN_SIZE as u64) != Some(0)
{
return Err(ElfParserError::InvalidSize);
}
if symbol.st_size
> bytecode_header
.vm_range()
.end
.saturating_sub(symbol.st_value)
{
return Err(ElfParserError::OutOfBounds);
}
let target_pc = symbol
.st_value
.saturating_sub(bytecode_header.p_vaddr)
.checked_div(ebpf::INSN_SIZE as u64)
.unwrap_or_default() as usize;
let name = if config.enable_symbol_and_section_labels {
Elf64::get_string_in_section(
elf_bytes,
symbol_names_section_header
.as_ref()
.ok_or(ElfParserError::NoStringTable)?,
symbol.st_name as Elf64Word,
u8::MAX as usize,
)?
} else {
&[]
};
function_registry
.register_function(target_pc as u32, name, target_pc)
.unwrap();
expected_symbol_address = symbol.st_value.saturating_add(symbol.st_size);
}
if expected_symbol_address != bytecode_header.vm_range().end {
return Err(ElfParserError::OutOfBounds);
}
if !bytecode_header.vm_range().contains(&file_header.e_entry)
|| file_header.e_entry.checked_rem(ebpf::INSN_SIZE as u64) != Some(0)
{
return Err(ElfParserError::InvalidFileHeader);
}
let entry_pc = file_header
.e_entry
.saturating_sub(bytecode_header.p_vaddr)
.checked_div(ebpf::INSN_SIZE as u64)
.unwrap_or_default() as usize;
if function_registry.lookup_by_key(entry_pc as u32).is_none() {
return Err(ElfParserError::InvalidFileHeader);
}

let text_section_vaddr = bytecode_header.p_vaddr;
let text_section_range = bytecode_header.file_range().unwrap_or_default();
let ro_section = Section::Borrowed(
rodata_header.p_vaddr as usize,
rodata_header.file_range().unwrap_or_default(),
);
Ok(Self {
elf_bytes: aligned_memory,
sbpf_version: SBPFVersion::Reserved, // Is set in Self::load()
ro_section,
text_section_vaddr,
text_section_range,
entry_pc,
function_registry,
loader,
#[cfg(all(feature = "jit", not(target_os = "windows"), target_arch = "x86_64"))]
compiled_program: None,
})
}

/// Loads an ELF with relocation
fn load_with_lenient_parser(
bytes: &[u8],
Expand Down
2 changes: 1 addition & 1 deletion src/elf_parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -573,7 +573,7 @@ impl<'a> Elf64<'a> {
}

/// Returns the `&[T]` contained at `bytes[range]`
fn slice_from_bytes<T: 'static>(
pub fn slice_from_bytes<T: 'static>(
bytes: &[u8],
range: Range<usize>,
) -> Result<&[T], ElfParserError> {
Expand Down
5 changes: 5 additions & 0 deletions src/program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ impl SBPFVersion {
pub fn move_memory_instruction_classes(self) -> bool {
self != SBPFVersion::V1
}

/// Constrain ELF format to ignore section headers and relocations
pub fn enable_stricter_elf_headers(self) -> bool {
self != SBPFVersion::V1
}
}

/// Holds the function symbols of an Executable
Expand Down

0 comments on commit 709fde2

Please sign in to comment.