From 57ed4480cc4d973c3f16f366914f9be6c5a3158e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Mei=C3=9Fner?= Date: Fri, 26 Jan 2024 11:44:36 +0000 Subject: [PATCH] Implements strict ELF loader. --- src/elf.rs | 167 +++++++++++++++++++++++++++++++++--- tests/elfs/elf.ld | 31 ++++--- tests/elfs/elfs.sh | 3 + tests/elfs/strict_header.rs | 13 +++ tests/elfs/strict_header.so | Bin 0 -> 9088 bytes 5 files changed, 190 insertions(+), 24 deletions(-) create mode 100644 tests/elfs/strict_header.rs create mode 100644 tests/elfs/strict_header.so diff --git a/src/elf.rs b/src/elf.rs index 375c80404..fe583eac0 100644 --- a/src/elf.rs +++ b/src/elf.rs @@ -11,10 +11,12 @@ use crate::{ ebpf::{self, EF_SBPF_V2, HOST_ALIGN, INSN_SIZE}, elf_parser::{ consts::{ - ELFCLASS64, ELFDATA2LSB, ELFOSABI_NONE, EM_BPF, EM_SBPF, ET_DYN, R_X86_64_32, - R_X86_64_64, R_X86_64_NONE, R_X86_64_RELATIVE, + ELFCLASS64, ELFDATA2LSB, ELFOSABI_NONE, EM_BPF, EM_SBPF, ET_DYN, PF_R, PF_W, PF_X, + PT_GNU_STACK, PT_LOAD, PT_NULL, R_X86_64_32, R_X86_64_64, R_X86_64_NONE, + R_X86_64_RELATIVE, SHF_ALLOC, SHF_EXECINSTR, SHF_WRITE, SHT_NOBITS, SHT_NULL, + SHT_PROGBITS, SHT_SYMTAB, STT_FUNC, }, - types::{Elf64Phdr, Elf64Shdr, Elf64Word}, + types::{Elf64Phdr, Elf64Shdr, Elf64Sym, Elf64Word}, Elf64, ElfParserError, }, error::EbpfError, @@ -375,13 +377,155 @@ impl Executable { let bytes = if is_memory_aligned(bytes.as_ptr() as usize, HOST_ALIGN) { bytes } else { + // debug_assert!(false); aligned = AlignedMemory::<{ HOST_ALIGN }>::from_slice(bytes); aligned.as_slice() }; - Self::load_with_parser(&Elf64::parse(bytes)?, bytes, loader) + let elf = Elf64::parse(bytes)?; + if elf.file_header().e_flags == EF_SBPF_V2 { + Self::load_with_strict_parser(&elf, bytes, loader).map_err(|err| err.into()) + } else { + Self::load_with_lenient_parser(&elf, bytes, loader) + } + } + + fn load_with_strict_parser( + elf: &Elf64, + bytes: &[u8], + loader: Arc>, + ) -> Result { + const EXPECTED_PROGRAM_HEADERS: [(u32, u32, u64); 4] = [ + (PT_LOAD, PF_R | PF_X, ebpf::MM_PROGRAM_START), // byte code + (PT_LOAD, PF_R, ebpf::MM_PROGRAM_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 + ]; + const EXPECTED_SECTION_HEADERS: [(u32, u64, u64); 6] = [ + (SHT_NULL, 0, 0), + ( + SHT_PROGBITS, + SHF_ALLOC | SHF_EXECINSTR, + ebpf::MM_PROGRAM_START, + ), // .text + (SHT_PROGBITS, SHF_ALLOC, ebpf::MM_PROGRAM_START), // .rodata + (SHT_NOBITS, SHF_ALLOC | SHF_WRITE, ebpf::MM_STACK_START), // .stack + (SHT_NOBITS, SHF_ALLOC | SHF_WRITE, ebpf::MM_HEAP_START), // .heap + (SHT_SYMTAB, 0, 0), // .symtab + ]; + if elf.file_header().e_type != ET_DYN + || elf.file_header().e_ident.ei_osabi != 0x00 + || elf.file_header().e_ident.ei_abiversion != 0x00 + || elf.program_header_table().len() < EXPECTED_PROGRAM_HEADERS.len() + || elf.section_header_table().len() < EXPECTED_SECTION_HEADERS.len() + { + return Err(ElfParserError::InvalidFileHeader); + } + const REGION_MASK: u64 = (-(1i64 << 32)) as u64; + const ALIGNMENT_MASK: u64 = 0xF; + for (program_header, (p_type, p_flags, addr)) in elf + .program_header_table() + .iter() + .zip(EXPECTED_PROGRAM_HEADERS.iter()) + { + if program_header.p_type != *p_type + || program_header.p_flags != *p_flags + || program_header.p_paddr != program_header.p_vaddr + || program_header.p_vaddr & REGION_MASK != *addr + || program_header.p_vaddr & ALIGNMENT_MASK != 0 + || program_header.p_memsz & REGION_MASK != 0 + || program_header.p_filesz > program_header.p_memsz + { + return Err(ElfParserError::InvalidProgramHeader); + } + } + for ((index, section_header), (s_type, s_flags, _sh_addr)) in elf + .section_header_table() + .iter() + .enumerate() + .zip(EXPECTED_SECTION_HEADERS.iter()) + { + if section_header.sh_type != *s_type || section_header.sh_flags != *s_flags { + return Err(ElfParserError::InvalidSectionHeader); + } + if *s_type == SHT_NULL { + continue; + } + let program_header = &elf.program_header_table()[index.saturating_sub(1)]; + if program_header.p_type != PT_NULL + && (section_header.sh_addr != program_header.p_vaddr + || section_header.sh_size != program_header.p_memsz) + { + return Err(ElfParserError::OutOfBounds); + } + } + let config = loader.get_config(); + let text_section = &elf.section_header_table()[1]; + let rodata_section = &elf.section_header_table()[2]; + let symbol_table = + elf.slice_from_section_header::(&elf.section_header_table()[5])?; + let mut function_registry = FunctionRegistry::::default(); + for symbol in symbol_table { + if symbol.st_info & STT_FUNC == 0 { + continue; + } + if !text_section.vm_range().contains(&symbol.st_value) + || symbol.st_value.checked_rem(ebpf::INSN_SIZE as u64) != Some(0) + { + return Err(ElfParserError::OutOfBounds); + } + let target_pc = symbol + .st_value + .saturating_sub(text_section.sh_addr) + .checked_div(ebpf::INSN_SIZE as u64) + .unwrap_or_default() as usize; + function_registry + .register_function( + target_pc as u32, + if config.enable_symbol_and_section_labels { + elf.symbol_name(symbol.st_name as Elf64Word)? + } else { + &[] + }, + target_pc, + ) + .map_err(|_| ElfParserError::Overlap)?; + } + if !text_section.vm_range().contains(&elf.file_header().e_entry) + || elf + .file_header() + .e_entry + .checked_rem(ebpf::INSN_SIZE as u64) + != Some(0) + { + return Err(ElfParserError::OutOfBounds); + } + let entry_pc = elf + .file_header() + .e_entry + .saturating_sub(text_section.sh_addr) + .checked_div(ebpf::INSN_SIZE as u64) + .unwrap_or_default() as usize; + function_registry + .register_function_hashed(*b"entrypoint", entry_pc) + .map_err(|_| ElfParserError::InvalidFileHeader)?; + Ok(Self { + elf_bytes: AlignedMemory::from_slice(bytes), // TODO: borrow + sbpf_version: SBPFVersion::V2, + ro_section: Section::Borrowed( + rodata_section.sh_addr as usize, + rodata_section.file_range().unwrap_or_default(), + ), + text_section_vaddr: text_section.sh_addr, // ebpf::MM_PROGRAM_START, + text_section_range: text_section.file_range().unwrap_or_default(), + entry_pc, + function_registry, + loader, + #[cfg(all(feature = "jit", not(target_os = "windows"), target_arch = "x86_64"))] + compiled_program: None, + }) } - fn load_with_parser( + fn load_with_lenient_parser( elf: &Elf64, bytes: &[u8], loader: Arc>, @@ -389,11 +533,7 @@ impl Executable { let mut elf_bytes = AlignedMemory::from_slice(bytes); let config = loader.get_config(); let header = elf.file_header(); - let sbpf_version = if header.e_flags == EF_SBPF_V2 { - SBPFVersion::V2 - } else { - SBPFVersion::V1 - }; + let sbpf_version = SBPFVersion::V1; Self::validate(config, elf, elf_bytes.as_slice())?; @@ -1944,4 +2084,11 @@ mod test { SECTION_NAME_LENGTH_MAXIMUM ); } + + #[test] + fn test_strict_header() { + let elf_bytes = + std::fs::read("tests/elfs/strict_header.so").expect("failed to read elf file"); + ElfExecutable::load(&elf_bytes, loader()).expect("validation failed"); + } } diff --git a/tests/elfs/elf.ld b/tests/elfs/elf.ld index e01debd26..3bc23956c 100644 --- a/tests/elfs/elf.ld +++ b/tests/elfs/elf.ld @@ -1,26 +1,29 @@ PHDRS { - text PT_LOAD ; + text PT_LOAD ; rodata PT_LOAD ; - data PT_LOAD ; - dynamic PT_DYNAMIC ; + stack PT_GNU_STACK ; + heap PT_LOAD ; + other PT_NULL ; } SECTIONS { - . = SIZEOF_HEADERS; - .text : { *(.text*) } :text + # . = SIZEOF_HEADERS; + .text 0x100000000 : { *(.text*) } :text .rodata : { *(.rodata*) } :rodata - .data.rel.ro : { *(.data.rel.ro*) } :rodata - .dynamic : { *(.dynamic) } :dynamic - .dynsym : { *(.dynsym) } :data - .dynstr : { *(.dynstr) } :data - .rel.dyn : { *(.rel.dyn) } :data - .data : { *(.data*) } :data - .bss : { *(.bss*) } :data + .stack 0x200000000 : { *(.bss.stack*) } :stack + .heap 0x300000000 : { *(.bss.heap*) } :heap + .symtab : { *(.symtab) } :other + .shstrtab : { *(.shstrtab) } :other + .strtab : { *(.strtab) } :other /DISCARD/ : { + *(.comment*) *(.eh_frame*) - *(.gnu.hash*) - *(.hash*) + *(*hash*) + *(.bss*) + *(.data*) + *(.rel*) + *(.dyn*) } } diff --git a/tests/elfs/elfs.sh b/tests/elfs/elfs.sh index 5f02e69ba..f58537d8c 100755 --- a/tests/elfs/elfs.sh +++ b/tests/elfs/elfs.sh @@ -11,6 +11,9 @@ LD_COMMON="$TOOLCHAIN/llvm/bin/ld.lld -z notext -shared --Bdynamic -entry entryp LD="$LD_COMMON --section-start=.text=0x100000000" LD_V1=$LD_COMMON +$RC -o strict_header.o strict_header.rs +$LD -o strict_header.so strict_header.o + $RC_V1 -o relative_call.o relative_call.rs $LD_V1 -o relative_call_sbpfv1.so relative_call.o diff --git a/tests/elfs/strict_header.rs b/tests/elfs/strict_header.rs new file mode 100644 index 000000000..e90dd8b6d --- /dev/null +++ b/tests/elfs/strict_header.rs @@ -0,0 +1,13 @@ +#[link_section = ".bss.stack"] +pub static _STACK: [u8; 0x1000] = [0; 0x1000]; +#[link_section = ".bss.heap"] +pub static _HEAP: [u8; 0x1000] = [0; 0x1000]; + +static _VAL_A: u64 = 41; +static VAL_B: u64 = 42; +static _VAL_C: u64 = 43; + +#[no_mangle] +pub fn entrypoint() -> u64 { + return unsafe { core::ptr::read_volatile(&VAL_B) }; +} diff --git a/tests/elfs/strict_header.so b/tests/elfs/strict_header.so new file mode 100644 index 0000000000000000000000000000000000000000..48c8f3349c28c67f0d5d556ed6f7a147f4e4e429 GIT binary patch literal 9088 zcmeHNy^0h;5boXk%@hMMFla$U1c&+C*_rItV?^)}<;1|iVQy#IJ;b|XuMgble1Hos z@l8y80pG+2Ff!Dt?y7Yh7X%YQs$u%;s`{$8=G$nx>%821I2;TVk2STmw9valf%muk zLIdomTy4O-tu_^MHZXJ*CqH;ML`^Ca?UH0I3Wx%tfG8jehytR3 zC?E=m0-}H@APW3r1wO*}tA-lB0ph3Zg1&n{P+j*31w;W+Kok%KL;+Di6c7bO0Z~8{ z5Cud5QQ-em0M7!})z|O+H@gFl0o=4}hd=M z69<3ppuu({u zz<$2lOB3M_`8R>awNr)%?ctJ~_@3CO!u8)E|82l-?c`RM+OINp5cca2z`rX0v-Szx K7eeX3z5fsSiC`W8 literal 0 HcmV?d00001