Skip to content

Commit

Permalink
Implements strict ELF loader.
Browse files Browse the repository at this point in the history
  • Loading branch information
Lichtso committed Oct 11, 2024
1 parent d4800c3 commit 33f0193
Show file tree
Hide file tree
Showing 7 changed files with 209 additions and 20 deletions.
139 changes: 135 additions & 4 deletions src/elf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -378,10 +379,133 @@ impl<C: ContextObject> Executable<C> {
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<BuiltinProgram<C>>,
) -> Result<Self, ElfParserError> {
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::<usize>::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<BuiltinProgram<C>>,
Expand Down Expand Up @@ -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");
}
}
31 changes: 18 additions & 13 deletions tests/elfs/elf.ld
Original file line number Diff line number Diff line change
@@ -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*)
}
}
26 changes: 26 additions & 0 deletions tests/elfs/elf_sbpfv1.ld
Original file line number Diff line number Diff line change
@@ -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*)
}
}
9 changes: 6 additions & 3 deletions tests/elfs/elfs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 13 additions & 0 deletions tests/elfs/strict_header.rs
Original file line number Diff line number Diff line change
@@ -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) };
}
Binary file added tests/elfs/strict_header.so
Binary file not shown.
11 changes: 11 additions & 0 deletions tests/execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down

0 comments on commit 33f0193

Please sign in to comment.