diff --git a/src/elf.rs b/src/elf.rs index 287f53e7..ddbe99ae 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, @@ -116,7 +117,7 @@ impl From for ElfError { | ElfParserError::InvalidString | ElfParserError::InvalidSize | ElfParserError::Overlap - | ElfParserError::SectionNotInOrder + | ElfParserError::NotInOrder | ElfParserError::NoSectionNameStringTable | ElfParserError::InvalidDynamicSectionTable | ElfParserError::InvalidRelocationTable @@ -375,17 +376,175 @@ 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 mut 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 { + elf.parse_sections()?; + elf.parse_dynamic()?; + 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::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::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 +621,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 +2100,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..d936d882 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; @@ -186,28 +194,20 @@ impl<'a> Elf64<'a> { .filter(|section_header| section_header.sh_type == SHT_NULL) .ok_or(ElfParserError::InvalidSectionHeader)?; - let mut prev_program_header: Option<&Elf64Phdr> = None; + let mut vaddr = 0usize; for program_header in program_header_table { if program_header.p_type != PT_LOAD { continue; } - - if let Some(prev_program_header) = prev_program_header { - // program headers must be ascending - if program_header.p_vaddr < prev_program_header.p_vaddr { - return Err(ElfParserError::InvalidProgramHeader); - } + if (program_header.p_vaddr as usize) < vaddr { + return Err(ElfParserError::InvalidProgramHeader); } - - if program_header + vaddr = program_header .p_offset - .err_checked_add(program_header.p_filesz)? as usize - > elf_bytes.len() - { + .err_checked_add(program_header.p_filesz)? as usize; + if vaddr > elf_bytes.len() { return Err(ElfParserError::OutOfBounds); } - - prev_program_header = Some(program_header) } let mut offset = 0usize; @@ -222,12 +222,12 @@ 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() { + offset = section_range.end; + if offset > elf_bytes.len() { return Err(ElfParserError::OutOfBounds); } - offset = section_range.end; } let section_names_section_header = (file_header.e_shstrndx != SHN_UNDEF) @@ -238,7 +238,7 @@ impl<'a> Elf64<'a> { }) .transpose()?; - let mut parser = Self { + let parser = Self { elf_bytes, file_header, program_header_table, @@ -252,9 +252,6 @@ impl<'a> Elf64<'a> { dynamic_symbol_names_section_header: None, }; - parser.parse_sections()?; - parser.parse_dynamic()?; - Ok(parser) } @@ -283,7 +280,8 @@ impl<'a> Elf64<'a> { self.dynamic_relocations_table } - fn parse_sections(&mut self) -> Result<(), ElfParserError> { + /// Parses the section header table. + pub fn parse_sections(&mut self) -> Result<(), ElfParserError> { macro_rules! section_header_by_name { ($self:expr, $section_header:expr, $section_name:expr, $($name:literal => $field:ident,)*) => { @@ -318,7 +316,8 @@ impl<'a> Elf64<'a> { Ok(()) } - fn parse_dynamic(&mut self) -> Result<(), ElfParserError> { + /// Parses the dynamic section. + pub fn parse_dynamic(&mut self) -> Result<(), ElfParserError> { let mut dynamic_table: Option<&[Elf64Dyn]> = None; // try to parse PT_DYNAMIC 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 00000000..02051c6e Binary files /dev/null and b/tests/elfs/strict_header.so differ 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]