Skip to content

Commit

Permalink
Fix: Unchecked integer arithmetic in elf.rs (#236)
Browse files Browse the repository at this point in the history
* Guards all integer_arithmetic in elf.rs

* Spelling
  • Loading branch information
Lichtso authored Dec 7, 2021
1 parent 6a97477 commit 849c7c2
Showing 1 changed file with 97 additions and 61 deletions.
158 changes: 97 additions & 61 deletions src/elf.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#![allow(clippy::integer_arithmetic)]
//! This module relocates a BPF ELF
// Note: Typically ELF shared objects are loaded using the program headers and
Expand Down Expand Up @@ -157,7 +156,7 @@ pub fn register_bpf_function(
/// Byte offset of the immediate field in the instruction
const BYTE_OFFSET_IMMEDIATE: usize = 4;
/// Byte length of the immediate field
const BYTE_LENGTH_IMMEIDATE: usize = 4;
const BYTE_LENGTH_IMMEDIATE: usize = 4;

/// BPF relocation types.
#[allow(non_camel_case_types)]
Expand Down Expand Up @@ -243,10 +242,14 @@ impl<E: UserDefinedError, I: InstructionMeter> Executable<E, I> {

/// Get the .text section virtual address and bytes
pub fn get_text_bytes(&self) -> (u64, &[u8]) {
let offset = (self.text_section_info.vaddr - ebpf::MM_PROGRAM_START) as usize;
let offset = (self
.text_section_info
.vaddr
.saturating_sub(ebpf::MM_PROGRAM_START)) as usize;
(
self.text_section_info.vaddr,
&self.ro_section[offset..offset + self.text_section_info.offset_range.len()],
&self.ro_section
[offset..offset.saturating_add(self.text_section_info.offset_range.len())],
)
}

Expand Down Expand Up @@ -312,7 +315,10 @@ impl<E: UserDefinedError, I: InstructionMeter> Executable<E, I> {
}
Err(ElfError::UnresolvedSymbol(
name.to_string(),
file_offset / ebpf::INSN_SIZE + ebpf::ELF_INSN_DUMP_OFFSET,
file_offset
.checked_div(ebpf::INSN_SIZE)
.and_then(|offset| offset.checked_add(ebpf::ELF_INSN_DUMP_OFFSET))
.unwrap_or(ebpf::ELF_INSN_DUMP_OFFSET),
file_offset,
)
.into())
Expand Down Expand Up @@ -408,21 +414,25 @@ impl<E: UserDefinedError, I: InstructionMeter> Executable<E, I> {
)?;

// calculate entrypoint offset into the text section
let offset = elf.header.e_entry - text_section.sh_addr;
if offset % ebpf::INSN_SIZE as u64 != 0 {
let offset = elf.header.e_entry.saturating_sub(text_section.sh_addr);
if offset.checked_rem(ebpf::INSN_SIZE as u64) != Some(0) {
return Err(ElfError::InvalidEntrypoint);
}
if let Some(entrypoint) = (offset as usize).checked_div(ebpf::INSN_SIZE) {
bpf_functions.remove(&ebpf::hash_symbol_name(b"entrypoint"));
register_bpf_function(
&mut bpf_functions,
entrypoint,
"entrypoint",
config.enable_symbol_and_section_labels,
)?;
} else {
return Err(ElfError::InvalidEntrypoint);
}
let entrypoint = offset as usize / ebpf::INSN_SIZE;
bpf_functions.remove(&ebpf::hash_symbol_name(b"entrypoint"));
register_bpf_function(
&mut bpf_functions,
entrypoint,
"entrypoint",
config.enable_symbol_and_section_labels,
)?;

// concatenate the read-only sections into one
let mut ro_length = text_section.sh_addr as usize + text_section_info.offset_range.len();
let mut ro_length =
(text_section.sh_addr as usize).saturating_add(text_section_info.offset_range.len());
let ro_slices = elf
.section_headers
.iter()
Expand All @@ -446,21 +456,25 @@ impl<E: UserDefinedError, I: InstructionMeter> Executable<E, I> {
.as_slice()
.get(section_header.file_range().unwrap_or_default())
.ok_or(ElfError::ValueOutOfBounds)?;
ro_length = ro_length.max(section_header.sh_addr as usize + slice.len());
ro_length =
ro_length.max((section_header.sh_addr as usize).saturating_add(slice.len()));
Ok((section_header.sh_addr as usize, slice))
})
.collect::<Result<Vec<_>, ElfError>>()?;
if ro_length > elf_bytes.len() {
return Err(ElfError::ValueOutOfBounds);
}
let mut ro_section = vec![0; ro_length];
ro_section[text_section.sh_addr as usize
..text_section.sh_addr as usize + text_section_info.offset_range.len()]
..(text_section.sh_addr as usize).saturating_add(text_section_info.offset_range.len())]
.copy_from_slice(
elf_bytes
.as_slice()
.get(text_section_info.offset_range.clone())
.ok_or(ElfError::ValueOutOfBounds)?,
);
for (offset, slice) in ro_slices.iter() {
ro_section[*offset..*offset + slice.len()].copy_from_slice(slice);
ro_section[*offset..offset.saturating_add(slice.len())].copy_from_slice(slice);
}

Ok(Self {
Expand All @@ -483,13 +497,19 @@ impl<E: UserDefinedError, I: InstructionMeter> Executable<E, I> {
bpf_functions: &mut BTreeMap<u32, (usize, String)>,
elf_bytes: &mut [u8],
) -> Result<(), ElfError> {
for i in 0..elf_bytes.len() / ebpf::INSN_SIZE {
let instruction_count = elf_bytes
.len()
.checked_div(ebpf::INSN_SIZE)
.ok_or(ElfError::ValueOutOfBounds)?;
for i in 0..instruction_count {
let mut insn = ebpf::get_insn(elf_bytes, i);
if insn.opc == ebpf::CALL_IMM && insn.imm != -1 {
let target_pc = i as isize + 1 + insn.imm as isize;
if target_pc < 0 || target_pc >= (elf_bytes.len() / ebpf::INSN_SIZE) as isize {
let target_pc = (i as isize)
.saturating_add(1)
.saturating_add(insn.imm as isize);
if target_pc < 0 || target_pc >= instruction_count as isize {
return Err(ElfError::RelativeJumpOutOfBounds(
i + ebpf::ELF_INSN_DUMP_OFFSET,
i.saturating_add(ebpf::ELF_INSN_DUMP_OFFSET),
));
}
let name = format!("function_{}", target_pc);
Expand All @@ -500,8 +520,9 @@ impl<E: UserDefinedError, I: InstructionMeter> Executable<E, I> {
enable_symbol_and_section_labels,
)?;
insn.imm = hash as i64;
let offset = i.saturating_mul(ebpf::INSN_SIZE);
let checked_slice = elf_bytes
.get_mut(i * ebpf::INSN_SIZE..(i * ebpf::INSN_SIZE) + ebpf::INSN_SIZE)
.get_mut(offset..offset.saturating_add(ebpf::INSN_SIZE))
.ok_or(ElfError::ValueOutOfBounds)?;
checked_slice.copy_from_slice(&insn.to_vec());
}
Expand All @@ -527,14 +548,17 @@ impl<E: UserDefinedError, I: InstructionMeter> Executable<E, I> {
return Err(ElfError::WrongType);
}

let num_text_sections = elf.section_headers.iter().fold(0, |count, section_header| {
if let Some(this_name) = elf.shdr_strtab.get_at(section_header.sh_name) {
if this_name == ".text" {
return count + 1;
}
}
count
});
let num_text_sections =
elf.section_headers
.iter()
.fold(0, |count: usize, section_header| {
if let Some(this_name) = elf.shdr_strtab.get_at(section_header.sh_name) {
if this_name == ".text" {
return count.saturating_add(1);
}
}
count
});
if 1 != num_text_sections {
return Err(ElfError::NotOneTextSection);
}
Expand Down Expand Up @@ -617,30 +641,30 @@ impl<E: UserDefinedError, I: InstructionMeter> Executable<E, I> {
// Read the instruction's immediate field which contains virtual
// address to convert to physical
let checked_slice = elf_bytes
.get(imm_offset..imm_offset.saturating_add(BYTE_LENGTH_IMMEIDATE))
.get(imm_offset..imm_offset.saturating_add(BYTE_LENGTH_IMMEDIATE))
.ok_or(ElfError::ValueOutOfBounds)?;
let refd_va = LittleEndian::read_u32(checked_slice) as u64;
// final "physical address" from the VM's perspetive is rooted at `MM_PROGRAM_START`
let refd_pa = ebpf::MM_PROGRAM_START.saturating_add(refd_va);

// The .text section has an unresolved load symbol instruction.
let sym = elf
let symbol = elf
.dynsyms
.get(relocation.r_sym)
.ok_or(ElfError::UnknownSymbol(relocation.r_sym))?;
let addr = (sym.st_value + refd_pa) as u64;
let addr = symbol.st_value.saturating_add(refd_pa) as u64;
let checked_slice = elf_bytes
.get_mut(imm_offset..imm_offset.saturating_add(BYTE_LENGTH_IMMEIDATE))
.get_mut(imm_offset..imm_offset.saturating_add(BYTE_LENGTH_IMMEDIATE))
.ok_or(ElfError::ValueOutOfBounds)?;
LittleEndian::write_u32(checked_slice, (addr & 0xFFFFFFFF) as u32);
let file_offset = imm_offset.saturating_add(ebpf::INSN_SIZE);
let checked_slice = elf_bytes
.get_mut(
imm_offset.saturating_add(ebpf::INSN_SIZE)
..imm_offset
.saturating_add(ebpf::INSN_SIZE + BYTE_LENGTH_IMMEIDATE),
)
.get_mut(file_offset..file_offset.saturating_add(BYTE_LENGTH_IMMEDIATE))
.ok_or(ElfError::ValueOutOfBounds)?;
LittleEndian::write_u32(checked_slice, (addr >> 32) as u32);
LittleEndian::write_u32(
checked_slice,
addr.checked_shr(32).unwrap_or_default() as u32,
);
}
Some(BpfRelocationType::R_Bpf_64_Relative) => {
// Raw relocation between sections. The instruction being relocated contains
Expand All @@ -650,7 +674,7 @@ impl<E: UserDefinedError, I: InstructionMeter> Executable<E, I> {
// Read the instruction's immediate field which contains virtual
// address to convert to physical
let checked_slice = elf_bytes
.get(imm_offset..imm_offset.saturating_add(BYTE_LENGTH_IMMEIDATE))
.get(imm_offset..imm_offset.saturating_add(BYTE_LENGTH_IMMEDIATE))
.ok_or(ElfError::ValueOutOfBounds)?;
let refd_va = LittleEndian::read_u32(checked_slice) as u64;

Expand All @@ -671,17 +695,17 @@ impl<E: UserDefinedError, I: InstructionMeter> Executable<E, I> {
// physical address into a high and low and write into both slot's imm field

let checked_slice = elf_bytes
.get_mut(imm_offset..imm_offset.saturating_add(BYTE_LENGTH_IMMEIDATE))
.get_mut(imm_offset..imm_offset.saturating_add(BYTE_LENGTH_IMMEDIATE))
.ok_or(ElfError::ValueOutOfBounds)?;
LittleEndian::write_u32(checked_slice, (refd_pa & 0xFFFFFFFF) as u32);
let file_offset = imm_offset.saturating_add(ebpf::INSN_SIZE);
let checked_slice = elf_bytes
.get_mut(
imm_offset.saturating_add(ebpf::INSN_SIZE)
..imm_offset
.saturating_add(ebpf::INSN_SIZE + BYTE_LENGTH_IMMEIDATE),
)
.get_mut(file_offset..file_offset.saturating_add(BYTE_LENGTH_IMMEDIATE))
.ok_or(ElfError::ValueOutOfBounds)?;
LittleEndian::write_u32(checked_slice, (refd_pa >> 32) as u32);
LittleEndian::write_u32(
checked_slice,
refd_pa.checked_shr(32).unwrap_or_default() as u32,
);
} else {
// 64 bit memory location, write entire 64 bit physical address directly
let checked_slice = elf_bytes
Expand All @@ -695,21 +719,26 @@ impl<E: UserDefinedError, I: InstructionMeter> Executable<E, I> {
// Hash the symbol name and stick it into the call instruction's imm
// field. Later that hash will be used to look up the function location.

let sym = elf
let symbol = elf
.dynsyms
.get(relocation.r_sym)
.ok_or(ElfError::UnknownSymbol(relocation.r_sym))?;
let name = elf
.dynstrtab
.get_at(sym.st_name)
.ok_or(ElfError::UnknownSymbol(sym.st_name))?;
let hash = if sym.is_function() && sym.st_value != 0 {
.get_at(symbol.st_name)
.ok_or(ElfError::UnknownSymbol(symbol.st_name))?;
let hash = if symbol.is_function() && symbol.st_value != 0 {
// bpf call
if !text_section.vm_range().contains(&(sym.st_value as usize)) {
if !text_section
.vm_range()
.contains(&(symbol.st_value as usize))
{
return Err(ElfError::ValueOutOfBounds);
}
let target_pc =
(sym.st_value - text_section.sh_addr) as usize / ebpf::INSN_SIZE;
let target_pc = (symbol.st_value.saturating_sub(text_section.sh_addr)
as usize)
.checked_div(ebpf::INSN_SIZE)
.unwrap_or_default();
register_bpf_function(
bpf_functions,
target_pc,
Expand All @@ -719,22 +748,27 @@ impl<E: UserDefinedError, I: InstructionMeter> Executable<E, I> {
} else {
// syscall
let hash = syscall_cache
.entry(sym.st_name)
.entry(symbol.st_name)
.or_insert_with(|| (ebpf::hash_symbol_name(name.as_bytes()), name))
.0;
if config.reject_unresolved_syscalls
&& syscall_registry.lookup_syscall(hash).is_none()
{
return Err(ElfError::UnresolvedSymbol(
name.to_string(),
r_offset / ebpf::INSN_SIZE + ebpf::ELF_INSN_DUMP_OFFSET,
r_offset
.checked_div(ebpf::INSN_SIZE)
.and_then(|offset| {
offset.checked_add(ebpf::ELF_INSN_DUMP_OFFSET)
})
.unwrap_or(ebpf::ELF_INSN_DUMP_OFFSET),
r_offset,
));
}
hash
};
let checked_slice = elf_bytes
.get_mut(imm_offset..imm_offset.saturating_add(BYTE_LENGTH_IMMEIDATE))
.get_mut(imm_offset..imm_offset.saturating_add(BYTE_LENGTH_IMMEDIATE))
.ok_or(ElfError::ValueOutOfBounds)?;
LittleEndian::write_u32(checked_slice, hash);
}
Expand All @@ -760,7 +794,9 @@ impl<E: UserDefinedError, I: InstructionMeter> Executable<E, I> {
{
return Err(ElfError::ValueOutOfBounds);
}
let target_pc = (symbol.st_value - text_section.sh_addr) as usize / ebpf::INSN_SIZE;
let target_pc = (symbol.st_value.saturating_sub(text_section.sh_addr) as usize)
.checked_div(ebpf::INSN_SIZE)
.unwrap_or_default();
let name = elf
.strtab
.get_at(symbol.st_name)
Expand Down

0 comments on commit 849c7c2

Please sign in to comment.