diff --git a/src/elf.rs b/src/elf.rs index 0c84da4c0..8dcb308a3 100644 --- a/src/elf.rs +++ b/src/elf.rs @@ -11,8 +11,9 @@ 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}, Elf64, ElfParserError, @@ -116,7 +117,7 @@ impl From for ElfError { | ElfParserError::InvalidString | ElfParserError::InvalidSize | ElfParserError::Overlap - | ElfParserError::SectionNotInOrder + | ElfParserError::NotInOrder | ElfParserError::NoSectionNameStringTable | ElfParserError::InvalidDynamicSectionTable | ElfParserError::InvalidRelocationTable @@ -382,10 +383,156 @@ impl Executable { aligned = AlignedMemory::<{ HOST_ALIGN }>::from_slice(bytes); aligned.as_slice() }; - Self::load_with_parser(&Elf64::parse(bytes)?, bytes, loader) + let elf = Elf64::parse(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); + } + if sbpf_version == SBPFVersion::V1 { + Self::load_with_lenient_parser(&elf, bytes, loader) + } else { + Self::load_with_strict_parser(&elf, bytes, loader).map_err(|err| err.into()) + } + } + + 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_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 + ]; + 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() + { + 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 symbol_table = elf + .dynamic_symbol_table() + .ok_or(ElfParserError::InvalidFileHeader)?; + let mut function_registry = FunctionRegistry::::default(); + let mut expected_symbol_address = bytecode_header.p_vaddr; + for symbol in 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::NotInOrder); + } + 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::OutOfBounds); + } + if elf + .file_header() + .e_entry + .checked_rem(ebpf::INSN_SIZE as u64) + != Some(0) + { + return Err(ElfParserError::InvalidAlignment); + } + 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::from_slice(bytes), // TODO: borrow + sbpf_version: SBPFVersion::V2, + 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_parser( + fn load_with_lenient_parser( elf: &Elf64, bytes: &[u8], loader: Arc>, @@ -1951,4 +2098,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/src/elf_parser/mod.rs b/src/elf_parser/mod.rs index 12e13766e..90dc99828 100644 --- a/src/elf_parser/mod.rs +++ b/src/elf_parser/mod.rs @@ -39,9 +39,9 @@ pub enum ElfParserError { /// Headers, tables or sections do overlap in the file #[error("values overlap")] Overlap, - /// Sections are not sorted in ascending order - #[error("sections not in ascending order")] - SectionNotInOrder, + /// Tables or sections are not sorted in ascending order + #[error("values not in ascending order")] + NotInOrder, /// No section name string table present in the file #[error("no section name string table found")] NoSectionNameStringTable, @@ -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; @@ -222,7 +230,7 @@ impl<'a> Elf64<'a> { check_that_there_is_no_overlap(§ion_range, &program_header_table_range)?; check_that_there_is_no_overlap(§ion_range, §ion_header_table_range)?; if section_range.start < offset { - return Err(ElfParserError::SectionNotInOrder); + return Err(ElfParserError::NotInOrder); } if section_range.end > elf_bytes.len() { return Err(ElfParserError::OutOfBounds); diff --git a/tests/elfs/elf.ld b/tests/elfs/elf.ld index e01debd26..02b15b20a 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 ; + stack PT_GNU_STACK ; + heap PT_LOAD ; dynamic PT_DYNAMIC ; + other PT_NULL ; } SECTIONS { - . = SIZEOF_HEADERS; - .text : { *(.text*) } :text - .rodata : { *(.rodata*) } :rodata - .data.rel.ro : { *(.data.rel.ro*) } :rodata + .text 0x000000000 : { *(.text*) } :text + .rodata 0x100000000 : { *(.rodata*) } :rodata + .stack 0x200000000 : { *(.bss.stack*) } :stack + .heap 0x300000000 : { *(.bss.heap*) } :heap .dynamic : { *(.dynamic) } :dynamic - .dynsym : { *(.dynsym) } :data - .dynstr : { *(.dynstr) } :data - .rel.dyn : { *(.rel.dyn) } :data - .data : { *(.data*) } :data - .bss : { *(.bss*) } :data + .dynsym : { *(.dynsym) } :dynamic + .dynstr : { *(.dynstr) } :dynamic + .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 000000000..859ab6693 --- /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 5f02e69ba..4a87c30bc 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_v1.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 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 000000000..55ed4eaac Binary files /dev/null and b/tests/elfs/strict_header.so differ diff --git a/tests/execution.rs b/tests/execution.rs index 760078d70..9b35a5cf9 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]