Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature - Strict ELF parser #605

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
202 changes: 188 additions & 14 deletions src/elf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -116,7 +117,7 @@ impl From<ElfParserError> for ElfError {
| ElfParserError::InvalidString
| ElfParserError::InvalidSize
| ElfParserError::Overlap
| ElfParserError::SectionNotInOrder
| ElfParserError::NotInOrder
| ElfParserError::NoSectionNameStringTable
| ElfParserError::InvalidDynamicSectionTable
| ElfParserError::InvalidRelocationTable
Expand Down Expand Up @@ -375,17 +376,175 @@ impl<C: ContextObject> Executable<C> {
// 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, ElfError> =
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<BuiltinProgram<C>>,
) -> Result<Self, ElfParserError> {
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::<usize>::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<BuiltinProgram<C>>,
Expand Down Expand Up @@ -462,7 +621,7 @@ impl<C: ContextObject> Executable<C> {

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(),
Expand Down Expand Up @@ -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");
}
}
51 changes: 25 additions & 26 deletions src/elf_parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<Range<usize>> {
(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<Elf64Addr> {
let addr = self.p_vaddr;
Expand Down Expand Up @@ -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;
Expand All @@ -222,12 +222,12 @@ impl<'a> Elf64<'a> {
check_that_there_is_no_overlap(&section_range, &program_header_table_range)?;
check_that_there_is_no_overlap(&section_range, &section_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)
Expand All @@ -238,7 +238,7 @@ impl<'a> Elf64<'a> {
})
.transpose()?;

let mut parser = Self {
let parser = Self {
elf_bytes,
file_header,
program_header_table,
Expand All @@ -252,9 +252,6 @@ impl<'a> Elf64<'a> {
dynamic_symbol_names_section_header: None,
};

parser.parse_sections()?;
parser.parse_dynamic()?;

Ok(parser)
}

Expand Down Expand Up @@ -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,)*) => {
Expand Down Expand Up @@ -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
Expand Down
Loading