diff --git a/src/elf.rs b/src/elf.rs index e314d7318..e723efbe2 100644 --- a/src/elf.rs +++ b/src/elf.rs @@ -396,11 +396,212 @@ impl Executable { 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>, + ) -> Result { + 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::() + ..mem::size_of::() + .saturating_mul(file_header.e_phnum as usize) + .saturating_add(mem::size_of::()); + 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::() as u64 + // file_header.e_shoff + // file_header.e_flags + || file_header.e_ehsize != mem::size_of::() as u16 + || file_header.e_phentsize != mem::size_of::() 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::() 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::(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::::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], diff --git a/src/elf_parser/mod.rs b/src/elf_parser/mod.rs index e6795c9ac..a588ba04c 100644 --- a/src/elf_parser/mod.rs +++ b/src/elf_parser/mod.rs @@ -573,7 +573,7 @@ impl<'a> Elf64<'a> { } /// Returns the `&[T]` contained at `bytes[range]` - fn slice_from_bytes( + pub fn slice_from_bytes( bytes: &[u8], range: Range, ) -> Result<&[T], ElfParserError> { diff --git a/src/program.rs b/src/program.rs index b4602e800..35f7ed497 100644 --- a/src/program.rs +++ b/src/program.rs @@ -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