From 33f0193dc6acbcd08ee97eabbb03b0adaa5e8ddf 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 | 139 ++++++++++++++++++++++++++++++++++-- tests/elfs/elf.ld | 31 ++++---- tests/elfs/elf_sbpfv1.ld | 26 +++++++ tests/elfs/elfs.sh | 9 ++- tests/elfs/strict_header.rs | 13 ++++ tests/elfs/strict_header.so | Bin 0 -> 13680 bytes tests/execution.rs | 11 +++ 7 files changed, 209 insertions(+), 20 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 d8951f3ac..5a3281577 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, @@ -378,10 +379,133 @@ 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 sbpf_version = if elf.file_header().e_flags == EF_SBPF_V2 { + SBPFVersion::V2 + } else { + SBPFVersion::V1 + }; + if !loader + .get_config() + .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 text_section = &elf.section_header_table()[1]; + let rodata_section = &elf.section_header_table()[2]; + let symbol_table = elf + .dynamic_symbol_table() + .ok_or(ElfParserError::InvalidFileHeader)?; + 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; + 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_section.sh_addr as usize, + rodata_section.file_range().unwrap_or_default(), + ), + text_section_vaddr: text_section.sh_addr, // ebpf::MM_RODATA_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>, @@ -1947,4 +2071,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..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 0000000000000000000000000000000000000000..55ed4eaac6cf741f1542a6b495d8805605e4a54a GIT binary patch literal 13680 zcmeI3&ubGw6vroNYt;(c3gSUzFM1HV>~4}xau6D8{DHM96+sV%Z8mFyBq_-X=Hx%% zAK*dnddtC!*Zw8Fh}U`&L|EUOc`t4^fgms4C8 z)N-n))CLWVJ=3tjY^aJ_f_g=rQK($Pp$k||7mHkMISzG;I5rtjIv2Wd>@+r@uw~to z?{Rnv{T7azlFwM(rS)JgEND@u9IQ-P$XMOweXs@R5gquoj_enjeE%>>{v=5xnz+fLO=)z0U;m+gn$qb0zyCt2mv7=1cbmzCUBJ&r_bU; zdo{DzHR|(m_=}nT?KE0|Bs+K=964nU`C|;9?|+H@ytzT3M+wk-?Kd>}o&dPi zH>~ood)ha4vFyNS=DO{;^)^=apdSWtpzL8BG+!&b-3s=U9gccIx6{;(;izkDJb$YqR{uEx=Mka*7=Q{r?t%oAgs7vDb p|CyZlyMp`7&VLhplf6?cnh&n&JWBbnnVtVG_-5&UkbeId{a*mfg$4is literal 0 HcmV?d00001 diff --git a/tests/execution.rs b/tests/execution.rs index 248b0c726..b358bfa1c 100644 --- a/tests/execution.rs +++ b/tests/execution.rs @@ -2718,6 +2718,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]