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 7, 2024
1 parent c06f051 commit 57ed448
Show file tree
Hide file tree
Showing 5 changed files with 190 additions and 24 deletions.
167 changes: 157 additions & 10 deletions src/elf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ 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, PT_NULL, R_X86_64_32, R_X86_64_64, R_X86_64_NONE,
R_X86_64_RELATIVE, SHF_ALLOC, SHF_EXECINSTR, SHF_WRITE, SHT_NOBITS, SHT_NULL,
SHT_PROGBITS, SHT_SYMTAB, STT_FUNC,
},
types::{Elf64Phdr, Elf64Shdr, Elf64Word},
types::{Elf64Phdr, Elf64Shdr, Elf64Sym, Elf64Word},
Elf64, ElfParserError,
},
error::EbpfError,
Expand Down Expand Up @@ -375,25 +377,163 @@ impl<C: ContextObject> Executable<C> {
let bytes = if is_memory_aligned(bytes.as_ptr() as usize, HOST_ALIGN) {
bytes
} else {
// debug_assert!(false);
aligned = AlignedMemory::<{ HOST_ALIGN }>::from_slice(bytes);
aligned.as_slice()
};
Self::load_with_parser(&Elf64::parse(bytes)?, bytes, loader)
let elf = Elf64::parse(bytes)?;
if elf.file_header().e_flags == EF_SBPF_V2 {
Self::load_with_strict_parser(&elf, bytes, loader).map_err(|err| err.into())
} else {
Self::load_with_lenient_parser(&elf, bytes, loader)
}
}

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_PROGRAM_START), // byte code
(PT_LOAD, PF_R, ebpf::MM_PROGRAM_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
];
const EXPECTED_SECTION_HEADERS: [(u32, u64, u64); 6] = [
(SHT_NULL, 0, 0),
(
SHT_PROGBITS,
SHF_ALLOC | SHF_EXECINSTR,
ebpf::MM_PROGRAM_START,
), // .text
(SHT_PROGBITS, SHF_ALLOC, ebpf::MM_PROGRAM_START), // .rodata
(SHT_NOBITS, SHF_ALLOC | SHF_WRITE, ebpf::MM_STACK_START), // .stack
(SHT_NOBITS, SHF_ALLOC | SHF_WRITE, ebpf::MM_HEAP_START), // .heap
(SHT_SYMTAB, 0, 0), // .symtab
];
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()
|| elf.section_header_table().len() < EXPECTED_SECTION_HEADERS.len()
{
return Err(ElfParserError::InvalidFileHeader);
}
const REGION_MASK: u64 = (-(1i64 << 32)) as u64;
const ALIGNMENT_MASK: u64 = 0xF;
for (program_header, (p_type, p_flags, addr)) in elf
.program_header_table()
.iter()
.zip(EXPECTED_PROGRAM_HEADERS.iter())
{
if program_header.p_type != *p_type
|| program_header.p_flags != *p_flags
|| program_header.p_paddr != program_header.p_vaddr
|| program_header.p_vaddr & REGION_MASK != *addr
|| program_header.p_vaddr & ALIGNMENT_MASK != 0
|| program_header.p_memsz & REGION_MASK != 0
|| program_header.p_filesz > program_header.p_memsz
{
return Err(ElfParserError::InvalidProgramHeader);
}
}
for ((index, section_header), (s_type, s_flags, _sh_addr)) in elf
.section_header_table()
.iter()
.enumerate()
.zip(EXPECTED_SECTION_HEADERS.iter())
{
if section_header.sh_type != *s_type || section_header.sh_flags != *s_flags {
return Err(ElfParserError::InvalidSectionHeader);
}
if *s_type == SHT_NULL {
continue;
}
let program_header = &elf.program_header_table()[index.saturating_sub(1)];
if program_header.p_type != PT_NULL
&& (section_header.sh_addr != program_header.p_vaddr
|| section_header.sh_size != program_header.p_memsz)
{
return Err(ElfParserError::OutOfBounds);
}
}
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.slice_from_section_header::<Elf64Sym>(&elf.section_header_table()[5])?;
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;
function_registry
.register_function_hashed(*b"entrypoint", entry_pc)
.map_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_PROGRAM_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>>,
) -> Result<Self, ElfError> {
let mut elf_bytes = AlignedMemory::from_slice(bytes);
let config = loader.get_config();
let header = elf.file_header();
let sbpf_version = if header.e_flags == EF_SBPF_V2 {
SBPFVersion::V2
} else {
SBPFVersion::V1
};
let sbpf_version = SBPFVersion::V1;

Self::validate(config, elf, elf_bytes.as_slice())?;

Expand Down Expand Up @@ -1944,4 +2084,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: 17 additions & 14 deletions tests/elfs/elf.ld
Original file line number Diff line number Diff line change
@@ -1,26 +1,29 @@
PHDRS
{
text PT_LOAD ;
text PT_LOAD ;
rodata PT_LOAD ;
data PT_LOAD ;
dynamic PT_DYNAMIC ;
stack PT_GNU_STACK ;
heap PT_LOAD ;
other PT_NULL ;
}

SECTIONS
{
. = SIZEOF_HEADERS;
.text : { *(.text*) } :text
# . = SIZEOF_HEADERS;
.text 0x100000000 : { *(.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
.stack 0x200000000 : { *(.bss.stack*) } :stack
.heap 0x300000000 : { *(.bss.heap*) } :heap
.symtab : { *(.symtab) } :other
.shstrtab : { *(.shstrtab) } :other
.strtab : { *(.strtab) } :other
/DISCARD/ : {
*(.comment*)
*(.eh_frame*)
*(.gnu.hash*)
*(.hash*)
*(*hash*)
*(.bss*)
*(.data*)
*(.rel*)
*(.dyn*)
}
}
3 changes: 3 additions & 0 deletions tests/elfs/elfs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ LD_COMMON="$TOOLCHAIN/llvm/bin/ld.lld -z notext -shared --Bdynamic -entry entryp
LD="$LD_COMMON --section-start=.text=0x100000000"
LD_V1=$LD_COMMON

$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.

0 comments on commit 57ed448

Please sign in to comment.