Skip to content
This repository was archived by the owner on Jan 10, 2025. It is now read-only.

Commit 48bf08a

Browse files
committed
Implements strict ELF loader.
1 parent 21dd18d commit 48bf08a

File tree

7 files changed

+205
-20
lines changed

7 files changed

+205
-20
lines changed

src/elf.rs

Lines changed: 131 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ use crate::{
1111
ebpf::{self, EF_SBPF_V2, HOST_ALIGN, INSN_SIZE},
1212
elf_parser::{
1313
consts::{
14-
ELFCLASS64, ELFDATA2LSB, ELFOSABI_NONE, EM_BPF, EM_SBPF, ET_DYN, R_X86_64_32,
15-
R_X86_64_64, R_X86_64_NONE, R_X86_64_RELATIVE,
14+
ELFCLASS64, ELFDATA2LSB, ELFOSABI_NONE, EM_BPF, EM_SBPF, ET_DYN, PF_R, PF_W, PF_X,
15+
PT_GNU_STACK, PT_LOAD, R_X86_64_32, R_X86_64_64, R_X86_64_NONE, R_X86_64_RELATIVE,
16+
STT_FUNC,
1617
},
1718
types::{Elf64Phdr, Elf64Shdr, Elf64Word},
1819
Elf64, ElfParserError,
@@ -378,10 +379,129 @@ impl<C: ContextObject> Executable<C> {
378379
aligned = AlignedMemory::<{ HOST_ALIGN }>::from_slice(bytes);
379380
aligned.as_slice()
380381
};
381-
Self::load_with_parser(&Elf64::parse(bytes)?, bytes, loader)
382+
let elf = Elf64::parse(bytes)?;
383+
let sbpf_version = if elf.file_header().e_flags == EF_SBPF_V2 {
384+
SBPFVersion::V2
385+
} else {
386+
SBPFVersion::V1
387+
};
388+
if !loader
389+
.get_config()
390+
.enabled_sbpf_versions
391+
.contains(&sbpf_version)
392+
{
393+
return Err(ElfError::UnsupportedSBPFVersion);
394+
}
395+
if sbpf_version == SBPFVersion::V1 {
396+
Self::load_with_lenient_parser(&elf, bytes, loader)
397+
} else {
398+
Self::load_with_strict_parser(&elf, bytes, loader).map_err(|err| err.into())
399+
}
400+
}
401+
402+
fn load_with_strict_parser(
403+
elf: &Elf64,
404+
bytes: &[u8],
405+
loader: Arc<BuiltinProgram<C>>,
406+
) -> Result<Self, ElfParserError> {
407+
const EXPECTED_PROGRAM_HEADERS: [(u32, u32, u64); 4] = [
408+
(PT_LOAD, PF_R | PF_X, 0), // byte code
409+
(PT_LOAD, PF_R, ebpf::MM_RODATA_START), // read only data
410+
(PT_GNU_STACK, PF_R | PF_W, ebpf::MM_STACK_START), // stack
411+
(PT_LOAD, PF_R | PF_W, ebpf::MM_HEAP_START), // heap
412+
];
413+
if elf.file_header().e_type != ET_DYN
414+
|| elf.file_header().e_ident.ei_osabi != 0x00
415+
|| elf.file_header().e_ident.ei_abiversion != 0x00
416+
|| elf.program_header_table().len() < EXPECTED_PROGRAM_HEADERS.len()
417+
{
418+
return Err(ElfParserError::InvalidFileHeader);
419+
}
420+
const REGION_MASK: u64 = (-(1i64 << 32)) as u64;
421+
for (program_header, (p_type, p_flags, p_vaddr)) in elf
422+
.program_header_table()
423+
.iter()
424+
.zip(EXPECTED_PROGRAM_HEADERS.iter())
425+
{
426+
if program_header.p_type != *p_type
427+
|| program_header.p_flags != *p_flags
428+
|| program_header.p_paddr != *p_vaddr
429+
|| program_header.p_vaddr != *p_vaddr
430+
|| program_header.p_memsz & REGION_MASK != 0
431+
|| program_header.p_filesz > program_header.p_memsz
432+
{
433+
return Err(ElfParserError::InvalidProgramHeader);
434+
}
435+
}
436+
let config = loader.get_config();
437+
let text_section = &elf.section_header_table()[1];
438+
let rodata_section = &elf.section_header_table()[2];
439+
let symbol_table = elf
440+
.dynamic_symbol_table()
441+
.ok_or(ElfParserError::InvalidFileHeader)?;
442+
let mut function_registry = FunctionRegistry::<usize>::default();
443+
for symbol in symbol_table {
444+
if symbol.st_info & STT_FUNC == 0 {
445+
continue;
446+
}
447+
if !text_section.vm_range().contains(&symbol.st_value)
448+
|| symbol.st_value.checked_rem(ebpf::INSN_SIZE as u64) != Some(0)
449+
{
450+
return Err(ElfParserError::OutOfBounds);
451+
}
452+
let target_pc = symbol
453+
.st_value
454+
.saturating_sub(text_section.sh_addr)
455+
.checked_div(ebpf::INSN_SIZE as u64)
456+
.unwrap_or_default() as usize;
457+
function_registry
458+
.register_function(
459+
target_pc as u32,
460+
if config.enable_symbol_and_section_labels {
461+
elf.symbol_name(symbol.st_name as Elf64Word)?
462+
} else {
463+
&[]
464+
},
465+
target_pc,
466+
)
467+
.map_err(|_| ElfParserError::Overlap)?;
468+
}
469+
if !text_section.vm_range().contains(&elf.file_header().e_entry)
470+
|| elf
471+
.file_header()
472+
.e_entry
473+
.checked_rem(ebpf::INSN_SIZE as u64)
474+
!= Some(0)
475+
{
476+
return Err(ElfParserError::OutOfBounds);
477+
}
478+
let entry_pc = elf
479+
.file_header()
480+
.e_entry
481+
.saturating_sub(text_section.sh_addr)
482+
.checked_div(ebpf::INSN_SIZE as u64)
483+
.unwrap_or_default() as usize;
484+
if function_registry.lookup_by_key(entry_pc as u32).is_none() {
485+
return Err(ElfParserError::InvalidFileHeader);
486+
}
487+
Ok(Self {
488+
elf_bytes: AlignedMemory::from_slice(bytes), // TODO: borrow
489+
sbpf_version: SBPFVersion::V2,
490+
ro_section: Section::Borrowed(
491+
rodata_section.sh_addr as usize,
492+
rodata_section.file_range().unwrap_or_default(),
493+
),
494+
text_section_vaddr: text_section.sh_addr, // ebpf::MM_RODATA_START,
495+
text_section_range: text_section.file_range().unwrap_or_default(),
496+
entry_pc,
497+
function_registry,
498+
loader,
499+
#[cfg(all(feature = "jit", not(target_os = "windows"), target_arch = "x86_64"))]
500+
compiled_program: None,
501+
})
382502
}
383503

384-
fn load_with_parser(
504+
fn load_with_lenient_parser(
385505
elf: &Elf64,
386506
bytes: &[u8],
387507
loader: Arc<BuiltinProgram<C>>,
@@ -1947,4 +2067,11 @@ mod test {
19472067
SECTION_NAME_LENGTH_MAXIMUM
19482068
);
19492069
}
2070+
2071+
#[test]
2072+
fn test_strict_header() {
2073+
let elf_bytes =
2074+
std::fs::read("tests/elfs/strict_header.so").expect("failed to read elf file");
2075+
ElfExecutable::load(&elf_bytes, loader()).expect("validation failed");
2076+
}
19502077
}

tests/elfs/elf.ld

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,31 @@
11
PHDRS
22
{
3-
text PT_LOAD ;
3+
text PT_LOAD ;
44
rodata PT_LOAD ;
5-
data PT_LOAD ;
5+
stack PT_GNU_STACK ;
6+
heap PT_LOAD ;
67
dynamic PT_DYNAMIC ;
8+
other PT_NULL ;
79
}
810

911
SECTIONS
1012
{
11-
. = SIZEOF_HEADERS;
12-
.text : { *(.text*) } :text
13-
.rodata : { *(.rodata*) } :rodata
14-
.data.rel.ro : { *(.data.rel.ro*) } :rodata
13+
.text 0x000000000 : { *(.text*) } :text
14+
.rodata 0x100000000 : { *(.rodata*) } :rodata
15+
.stack 0x200000000 : { *(.bss.stack*) } :stack
16+
.heap 0x300000000 : { *(.bss.heap*) } :heap
1517
.dynamic : { *(.dynamic) } :dynamic
16-
.dynsym : { *(.dynsym) } :data
17-
.dynstr : { *(.dynstr) } :data
18-
.rel.dyn : { *(.rel.dyn) } :data
19-
.data : { *(.data*) } :data
20-
.bss : { *(.bss*) } :data
18+
.dynsym : { *(.dynsym) } :dynamic
19+
.dynstr : { *(.dynstr) } :dynamic
20+
.symtab : { *(.symtab) } :other
21+
.shstrtab : { *(.shstrtab) } :other
22+
.strtab : { *(.strtab) } :other
2123
/DISCARD/ : {
24+
*(.comment*)
2225
*(.eh_frame*)
23-
*(.gnu.hash*)
24-
*(.hash*)
26+
*(*hash*)
27+
*(.bss*)
28+
*(.data*)
29+
*(.rel.dyn*)
2530
}
2631
}

tests/elfs/elf_sbpfv1.ld

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
PHDRS
2+
{
3+
text PT_LOAD ;
4+
rodata PT_LOAD ;
5+
data PT_LOAD ;
6+
dynamic PT_DYNAMIC ;
7+
}
8+
9+
SECTIONS
10+
{
11+
. = SIZEOF_HEADERS;
12+
.text : { *(.text*) } :text
13+
.rodata : { *(.rodata*) } :rodata
14+
.data.rel.ro : { *(.data.rel.ro*) } :rodata
15+
.dynamic : { *(.dynamic) } :dynamic
16+
.dynsym : { *(.dynsym) } :data
17+
.dynstr : { *(.dynstr) } :data
18+
.rel.dyn : { *(.rel.dyn) } :data
19+
.data : { *(.data*) } :data
20+
.bss : { *(.bss*) } :data
21+
/DISCARD/ : {
22+
*(.eh_frame*)
23+
*(.gnu.hash*)
24+
*(.hash*)
25+
}
26+
}

tests/elfs/elfs.sh

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,12 @@ TOOLCHAIN=../../../agave/sdk/sbf/dependencies/platform-tools
77
RC_COMMON="$TOOLCHAIN/rust/bin/rustc --target sbf-solana-solana --crate-type lib -C panic=abort -C opt-level=2"
88
RC="$RC_COMMON -C target_cpu=sbfv2"
99
RC_V1="$RC_COMMON -C target_cpu=generic"
10-
LD_COMMON="$TOOLCHAIN/llvm/bin/ld.lld -z notext -shared --Bdynamic -entry entrypoint --script elf.ld"
11-
LD="$LD_COMMON --section-start=.text=0x100000000"
12-
LD_V1=$LD_COMMON
10+
LD_COMMON="$TOOLCHAIN/llvm/bin/ld.lld -z notext -shared --Bdynamic -entry entrypoint"
11+
LD="$LD_COMMON --script elf.ld"
12+
LD_V1="$LD_COMMON --script elf_v1.ld"
13+
14+
$RC -o strict_header.o strict_header.rs
15+
$LD -o strict_header.so strict_header.o
1316

1417
$RC_V1 -o relative_call.o relative_call.rs
1518
$LD_V1 -o relative_call_sbpfv1.so relative_call.o

tests/elfs/strict_header.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#[link_section = ".bss.stack"]
2+
pub static _STACK: [u8; 0x1000] = [0; 0x1000];
3+
#[link_section = ".bss.heap"]
4+
pub static _HEAP: [u8; 0x1000] = [0; 0x1000];
5+
6+
static _VAL_A: u64 = 41;
7+
static VAL_B: u64 = 42;
8+
static _VAL_C: u64 = 43;
9+
10+
#[no_mangle]
11+
pub fn entrypoint() -> u64 {
12+
return unsafe { core::ptr::read_volatile(&VAL_B) };
13+
}

tests/elfs/strict_header.so

13.4 KB
Binary file not shown.

tests/execution.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2718,6 +2718,17 @@ fn test_nested_vm_syscall() {
27182718
assert_error!(result, "CallDepthExceeded");
27192719
}
27202720

2721+
#[test]
2722+
fn test_static_syscall() {
2723+
test_interpreter_and_jit_elf!(
2724+
"tests/elfs/strict_header.so",
2725+
[],
2726+
(),
2727+
TestContextObject::new(4),
2728+
ProgramResult::Ok(42),
2729+
);
2730+
}
2731+
27212732
// Instruction Meter Limit
27222733

27232734
#[test]

0 commit comments

Comments
 (0)