From 90ddfd0b1758c5b1fd29b1441f6aec88049a420b 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 | 198 +++++++++++++++++++++++++++++++++--- src/elf_parser/mod.rs | 8 ++ tests/elfs/elf.ld | 35 ++++--- tests/elfs/elf_sbpfv1.ld | 26 +++++ tests/elfs/elfs.sh | 9 +- tests/elfs/strict_header.rs | 13 +++ tests/elfs/strict_header.so | Bin 0 -> 13688 bytes tests/execution.rs | 11 ++ 8 files changed, 269 insertions(+), 31 deletions(-) create mode 100644 tests/elfs/elf_sbpfv1.ld 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 287f53e7..202ad0cd 100644 --- a/src/elf.rs +++ b/src/elf.rs @@ -11,10 +11,11 @@ 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, R_X86_64_32, R_X86_64_64, R_X86_64_NONE, R_X86_64_RELATIVE, + STT_FUNC, }, - types::{Elf64Phdr, Elf64Shdr, Elf64Word}, + types::{Elf64Phdr, Elf64Shdr, Elf64Sym, Elf64Word}, Elf64, ElfParserError, }, error::EbpfError, @@ -375,17 +376,173 @@ impl Executable { // The new parser creates references from the input byte slice, so // it must be properly aligned. We assume that HOST_ALIGN is a // multiple of the ELF "natural" alignment. See test_load_unaligned. - let aligned; - let bytes = if is_memory_aligned(bytes.as_ptr() as usize, HOST_ALIGN) { - bytes + let aligned_memory = (!is_memory_aligned(bytes.as_ptr() as usize, HOST_ALIGN)) + .then(|| AlignedMemory::<{ HOST_ALIGN }>::from_slice(bytes)); + let aligned_bytes = aligned_memory + .as_ref() + .map(|aligned_memory| aligned_memory.as_slice()) + .unwrap_or(bytes); + let elf = Elf64::parse(aligned_bytes)?; + + let enabled_sbpf_versions = loader.get_config().enabled_sbpf_versions.clone(); + let sbpf_version = if *enabled_sbpf_versions.end() == SBPFVersion::V1 { + // Emulates a bug in the version dispatcher until we enable the first other version + if elf.file_header().e_flags == 0x20 { + SBPFVersion::V2 + } else { + SBPFVersion::V1 + } + } else { + match elf.file_header().e_flags { + 0 => SBPFVersion::V1, + EF_SBPF_V2 => SBPFVersion::V2, + _ => SBPFVersion::V3, + } + }; + if !enabled_sbpf_versions.contains(&sbpf_version) { + return Err(ElfError::UnsupportedSBPFVersion); + } + + let mut elf = if sbpf_version == SBPFVersion::V1 { + Self::load_with_lenient_parser(&elf, aligned_bytes, loader)? } else { - aligned = AlignedMemory::<{ HOST_ALIGN }>::from_slice(bytes); - aligned.as_slice() + let elf: Result = + Self::load_with_strict_parser(&elf, loader).map_err(|err| err.into()); + let mut elf = elf?; + elf.elf_bytes = aligned_memory.unwrap_or_else(|| AlignedMemory::from_slice(bytes)); + elf }; - Self::load_with_parser(&Elf64::parse(bytes)?, bytes, loader) + elf.sbpf_version = sbpf_version; + Ok(elf) } - fn load_with_parser( + fn load_with_strict_parser( + elf: &Elf64, + loader: Arc>, + ) -> Result { + const EXPECTED_PROGRAM_HEADERS: [(u32, u32, u64); 5] = [ + (PT_LOAD, PF_R | 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_LOAD, PF_R, 0xFFFFFFFF00000000), // dynamic symbol table + ]; + + let file_header = elf.file_header(); + if 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 + || elf.program_header_table().len() < EXPECTED_PROGRAM_HEADERS.len() + { + return Err(ElfParserError::InvalidFileHeader); + } + for (program_header, (p_type, p_flags, p_vaddr)) in elf + .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_paddr != *p_vaddr + || program_header.p_vaddr != *p_vaddr + || program_header.p_memsz >= ebpf::MM_REGION_SIZE + || program_header.p_filesz != p_filesz + { + return Err(ElfParserError::InvalidProgramHeader); + } + } + let config = loader.get_config(); + let bytecode_header = &elf.program_header_table()[0]; + let rodata_header = &elf.program_header_table()[1]; + let dynamic_symbol_table: &[Elf64Sym] = + elf.slice_from_program_header(&elf.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_size == 0 { + return Err(ElfParserError::InvalidSize); + } + if symbol.st_value != expected_symbol_address { + return Err(ElfParserError::SectionNotInOrder); + } + if symbol.st_value.checked_rem(ebpf::INSN_SIZE as u64) != Some(0) { + return Err(ElfParserError::InvalidAlignment); + } + expected_symbol_address = symbol.st_value.saturating_add(symbol.st_size); + let target_pc = symbol + .st_value + .saturating_sub(bytecode_header.p_vaddr) + .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, + ) + .unwrap(); + } + if expected_symbol_address != bytecode_header.vm_range().end { + return Err(ElfParserError::OutOfBounds); + } + if expected_symbol_address.checked_rem(ebpf::INSN_SIZE as u64) != Some(0) { + return Err(ElfParserError::InvalidAlignment); + } + if !bytecode_header + .vm_range() + .contains(&elf.file_header().e_entry) + { + return Err(ElfParserError::InvalidFileHeader); + } + if elf + .file_header() + .e_entry + .checked_rem(ebpf::INSN_SIZE as u64) + != Some(0) + { + return Err(ElfParserError::InvalidFileHeader); + } + let entry_pc = elf + .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); + } + Ok(Self { + elf_bytes: AlignedMemory::with_capacity(0), // Is set in Self::load() + sbpf_version: SBPFVersion::V1, // Is set in Self::load() + ro_section: Section::Borrowed( + rodata_header.p_vaddr as usize, + rodata_header.file_range().unwrap_or_default(), + ), + text_section_vaddr: bytecode_header.p_vaddr, + text_section_range: bytecode_header.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_lenient_parser( elf: &Elf64, bytes: &[u8], loader: Arc>, @@ -462,7 +619,7 @@ impl Executable { Ok(Self { elf_bytes, - sbpf_version, + sbpf_version: SBPFVersion::V1, // Is set in Self::load() ro_section, text_section_vaddr, text_section_range: text_section.file_range().unwrap_or_default(), @@ -1941,14 +2098,29 @@ mod test { #[test] fn test_long_section_name() { + let loader = BuiltinProgram::new_loader( + Config { + enabled_sbpf_versions: SBPFVersion::V1..=SBPFVersion::V1, + reject_broken_elfs: true, + ..Config::default() + }, + FunctionRegistry::default(), + ); let elf_bytes = std::fs::read("tests/elfs/long_section_name.so").unwrap(); assert_error!( - Elf64::parse(&elf_bytes), - "StringTooLong({:?}, {})", + ElfExecutable::load(&elf_bytes, Arc::new(loader)), + "FailedToParse(\"Section or symbol name `{}` is longer than `{}` bytes\")", ".bss.__rust_no_alloc_shim_is_unstable" .get(0..SECTION_NAME_LENGTH_MAXIMUM) .unwrap(), 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/src/elf_parser/mod.rs b/src/elf_parser/mod.rs index 12e13766..385a551a 100644 --- a/src/elf_parser/mod.rs +++ b/src/elf_parser/mod.rs @@ -63,6 +63,14 @@ pub enum ElfParserError { } impl Elf64Phdr { + /// Returns the byte range the section spans in the file. + pub fn file_range(&self) -> Option> { + (self.p_type == PT_LOAD).then(|| { + let offset = self.p_offset as usize; + offset..offset.saturating_add(self.p_filesz as usize) + }) + } + /// Returns the segment virtual address range. pub fn vm_range(&self) -> Range { let addr = self.p_vaddr; diff --git a/tests/elfs/elf.ld b/tests/elfs/elf.ld index e01debd2..d57647af 100644 --- a/tests/elfs/elf.ld +++ b/tests/elfs/elf.ld @@ -1,26 +1,31 @@ PHDRS { - text PT_LOAD ; + text PT_LOAD ; rodata PT_LOAD ; - data PT_LOAD ; - dynamic PT_DYNAMIC ; + stack PT_GNU_STACK ; + heap PT_LOAD ; + dynsym PT_LOAD ; + other PT_NULL ; } SECTIONS { - . = SIZEOF_HEADERS; - .text : { *(.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 + .text 0x000000000 : { *(.text*) } :text + .rodata 0x100000000 : { *(.rodata*) } :rodata + .bss.stack 0x200000000 : { *(.bss.stack*) } :stack + .bss.heap 0x300000000 : { *(.bss.heap*) } :heap + .dynsym 0xFFFFFFFF00000000 : { *(.dynsym) } :dynsym + .dynstr : { *(.dynstr) } :other + .dynamic : { *(.dynamic) } :other + .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/elf_sbpfv1.ld b/tests/elfs/elf_sbpfv1.ld new file mode 100644 index 00000000..859ab669 --- /dev/null +++ b/tests/elfs/elf_sbpfv1.ld @@ -0,0 +1,26 @@ +PHDRS +{ + text PT_LOAD ; + rodata PT_LOAD ; + data PT_LOAD ; + dynamic PT_DYNAMIC ; +} + +SECTIONS +{ + . = SIZEOF_HEADERS; + .text : { *(.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 + /DISCARD/ : { + *(.eh_frame*) + *(.gnu.hash*) + *(.hash*) + } +} diff --git a/tests/elfs/elfs.sh b/tests/elfs/elfs.sh index 5f02e69b..f8cfc974 100755 --- a/tests/elfs/elfs.sh +++ b/tests/elfs/elfs.sh @@ -7,9 +7,12 @@ TOOLCHAIN=../../../agave/sdk/sbf/dependencies/platform-tools RC_COMMON="$TOOLCHAIN/rust/bin/rustc --target sbf-solana-solana --crate-type lib -C panic=abort -C opt-level=2" RC="$RC_COMMON -C target_cpu=sbfv2" RC_V1="$RC_COMMON -C target_cpu=generic" -LD_COMMON="$TOOLCHAIN/llvm/bin/ld.lld -z notext -shared --Bdynamic -entry entrypoint --script elf.ld" -LD="$LD_COMMON --section-start=.text=0x100000000" -LD_V1=$LD_COMMON +LD_COMMON="$TOOLCHAIN/llvm/bin/ld.lld -z notext -shared --Bdynamic -entry entrypoint" +LD="$LD_COMMON --script elf.ld" +LD_V1="$LD_COMMON --script elf_sbpfv1.ld" + +$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 00000000..e90dd8b6 --- /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..02051c6e92ea5d4550e374a6ff971682be87f265 GIT binary patch literal 13688 zcmeHOL2DCH5PnT-qgJq15b+>6hzHSSvq?6|K}f6-#afk$q6fn!n>9h2mShEc67QZo zdCV zOp6!31kNcDv`oXy)3{)c8Dt*8(jtc4R$@v};*d9owVefH_X35rqj1OsCGYy5tz}MXLTjI0N zOH(yfKow90Q~^~$6;K6K0aZX1Pz6*0Rp7uC_{{$x|0aa{e$kcqaNw16LsdW(Pz6*0 zRX`O`1yli5Kow90Q~^~$6*w#fE*(~3t*Z*C0;+&2pbDr0s(>n>3aA3AfGVI0r~-$s z0L}s|*%JY-m6CKc7^jo1&5hAGwaj_u?HPIUPe~l(5+ffgFsF0#Kik_Ip!gVqJEPAr zI48fr{#izQ*MNLEPk)^G6O3mV{})hy&iu0H`m?zgX)OXhxa1d=8FWwqY zx7Hn|6N}OMsBb)C)2K%{oTd}!IW#mM_0ulp$AgJit2HW7(kw?wzgMZo^(*~nFFfCL zpRATE|3uavwpY5>%JpG0s8-{!nbaFW5{E(3F_|yK?!2(O_Mp9dn-Ux4APEz{Q4PX4 ztVcm-uPWV}o%THnP6h)%DOW1Zsvib^)TH3+k5}7wZY^8U4q(8$#K+zV$?nJRtCZ!) zm-jAoksRdnH4aN-Zc5hO6`#*PNnzmR4hpic*Ur5b?gKo`?(JMVo})3JZ};*!XrKHG z6o%hcwtxG*6aWAK literal 0 HcmV?d00001 diff --git a/tests/execution.rs b/tests/execution.rs index 760078d7..9b35a5cf 100644 --- a/tests/execution.rs +++ b/tests/execution.rs @@ -2717,6 +2717,17 @@ fn test_nested_vm_syscall() { assert_error!(result, "CallDepthExceeded"); } +#[test] +fn test_static_syscall() { + test_interpreter_and_jit_elf!( + "tests/elfs/strict_header.so", + [], + (), + TestContextObject::new(4), + ProgramResult::Ok(42), + ); +} + // Instruction Meter Limit #[test]