From a278fbb9533c5ed34965bc9cb502a244a9b95b3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Mei=C3=9Fner?= Date: Fri, 12 Feb 2021 19:40:42 +0100 Subject: [PATCH] Fix LDDW Related Bugs (#140) * Fixes placing the entrypoint in the middle of a "lddw" instruction * Fixes traces of jumps into the middle of "lddw" instructions. * Fixes jumps close to the end of the program landing in exception handlers if lddw instructions are present (in JIT compiler). Also updates test_large_program() accordingly. --- cli/src/main.rs | 4 +- src/disassembler.rs | 12 ++-- src/elf.rs | 120 ++++++++++++++++++++-------------------- src/error.rs | 6 +- src/jit.rs | 28 +++++----- src/verifier.rs | 22 ++++---- src/vm.rs | 5 +- tests/ubpf_execution.rs | 86 +++++++++++----------------- tests/ubpf_verifier.rs | 6 +- 9 files changed, 135 insertions(+), 154 deletions(-) diff --git a/cli/src/main.rs b/cli/src/main.rs index 58bab99d..3dd91ac1 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -2,7 +2,7 @@ use clap::{App, Arg}; use rustc_demangle::demangle; use solana_rbpf::{ assembler::assemble, - disassembler::{to_insn_vec, HLInsn}, + disassembler::{to_insn_vec, HlInsn}, ebpf, memory_region::{MemoryMapping, MemoryRegion}, user_error::UserError, @@ -63,7 +63,7 @@ macro_rules! resolve_label { } struct AnalysisResult { - instructions: Vec, + instructions: Vec, destinations: BTreeMap, sources: BTreeMap>, } diff --git a/src/disassembler.rs b/src/disassembler.rs index 436603e8..83e5c25c 100644 --- a/src/disassembler.rs +++ b/src/disassembler.rs @@ -82,7 +82,7 @@ fn jmp_reg_str(name: &str, insn: &ebpf::Insn) -> String { /// documentation about eBPF, or for a /// more concise version. #[derive(Debug, PartialEq)] -pub struct HLInsn { +pub struct HlInsn { /// Instruction pointer. pub ptr: usize, /// Operation code. @@ -102,7 +102,7 @@ pub struct HLInsn { pub imm: i64, } -/// Return a vector of `struct HLInsn` built from an eBPF program. +/// Return a vector of `struct HlInsn` built from an eBPF program. /// /// This is made public to provide a way to manipulate a program as a vector of instructions, in a /// high-level format, for example for dumping the program instruction after instruction with a @@ -133,7 +133,7 @@ pub struct HLInsn { /// /// let v = disassembler::to_insn_vec(prog); /// assert_eq!(v, vec![ -/// disassembler::HLInsn { +/// disassembler::HlInsn { /// ptr: 0, /// opc: 0x18, /// name: "lddw".to_string(), @@ -143,7 +143,7 @@ pub struct HLInsn { /// off: 0, /// imm: 0x1122334455667788 /// }, -/// disassembler::HLInsn { +/// disassembler::HlInsn { /// ptr: 2, /// opc: 0x95, /// name: "exit".to_string(), @@ -156,7 +156,7 @@ pub struct HLInsn { /// ]); /// ``` #[rustfmt::skip] -pub fn to_insn_vec(prog: &[u8]) -> Vec { +pub fn to_insn_vec(prog: &[u8]) -> Vec { debug_assert!(prog.len() % ebpf::INSN_SIZE == 0, "eBPF program length must be a multiple of {:?} octets is {:?}", ebpf::INSN_SIZE, prog.len()); if prog.is_empty() { @@ -301,7 +301,7 @@ pub fn to_insn_vec(prog: &[u8]) -> Vec { }, }; - res.push(HLInsn { + res.push(HlInsn { ptr, opc: insn.opc, name: name.to_string(), diff --git a/src/elf.rs b/src/elf.rs index 5aa409f0..bc1fda0b 100644 --- a/src/elf.rs +++ b/src/elf.rs @@ -24,7 +24,7 @@ use std::{collections::HashMap, fmt::Debug, mem, ops::Range, str}; /// Error definitions #[derive(Debug, thiserror::Error, PartialEq, Eq)] -pub enum ELFError { +pub enum ElfError { /// Failed to parse ELF file #[error("Failed to parse ELF file: {0}")] FailedToParse(String), @@ -54,7 +54,7 @@ pub enum ELFError { WrongEndianess, /// Incompatible ELF: wrong ABI #[error("Incompatible ELF: wrong ABI")] - WrongABI, + WrongAbi, /// Incompatible ELF: wrong mchine #[error("Incompatible ELF: wrong machine")] WrongMachine, @@ -66,7 +66,7 @@ pub enum ELFError { MultipleTextSections, /// .bss section mot supported #[error(".bss section not supported")] - BSSNotSupported, + BssNotSupported, /// Relocation failed, no loadable section contains virtual address #[error("Relocation failed, no loadable section contains virtual address {0:#x}")] AddressOutsideLoadableSection(u64), @@ -89,7 +89,7 @@ pub enum ELFError { #[error("Offset or value is out of bounds")] OutOfBounds, } -impl From for ELFError { +impl From for ElfError { fn from(error: GoblinError) -> Self { match error { GoblinError::Malformed(string) => Self::FailedToParse(format!("malformed: {}", string)), @@ -101,7 +101,7 @@ impl From for ELFError { } impl From for EbpfError { fn from(error: GoblinError) -> Self { - ELFError::from(error).into() + ElfError::from(error).into() } } @@ -128,9 +128,9 @@ const BYTE_LENGTH_IMMEIDATE: usize = 4; /// BPF relocation types. #[allow(non_camel_case_types)] #[derive(Debug, PartialEq, Copy, Clone)] -enum BPFRelocationType { +enum BpfRelocationType { /// No relocation, placeholder - R_BPF_NONE = 0, + R_Bpf_None = 0, /// 64 bit relocation of a ldxdw instruction. /// The ldxdw instruction occupies two instruction slots. The 64-bit address /// to load from is split into the 32-bit imm field of each slot. The first @@ -138,7 +138,7 @@ enum BPFRelocationType { /// as the file offset) of the location to load. Relocation involves calculating /// the post-load 64-bit physical address referenced by the imm field and writing /// that physical address back into the imm fields of the ldxdw instruction. - R_BPF_64_RELATIVE = 8, + R_Bpf_64_Relative = 8, /// Relocation of a call instruction. /// The existing imm field contains either an offset of the instruction to jump to /// (think local function call) or a special value of "-1". If -1 the symbol must @@ -149,14 +149,14 @@ enum BPFRelocationType { /// jump to. In the case of a local jump the hash is calculated using the current /// program counter and in the case of a symbol the hash is calculated using the /// name of the symbol. - R_BPF_64_32 = 10, + R_Bpf_64_32 = 10, } -impl BPFRelocationType { - fn from_x86_relocation_type(from: u32) -> Option { +impl BpfRelocationType { + fn from_x86_relocation_type(from: u32) -> Option { match from { - R_X86_64_NONE => Some(BPFRelocationType::R_BPF_NONE), - R_X86_64_RELATIVE => Some(BPFRelocationType::R_BPF_64_RELATIVE), - R_X86_64_32 => Some(BPFRelocationType::R_BPF_64_32), + R_X86_64_NONE => Some(BpfRelocationType::R_Bpf_None), + R_X86_64_RELATIVE => Some(BpfRelocationType::R_Bpf_64_Relative), + R_X86_64_32 => Some(BpfRelocationType::R_Bpf_64_32), _ => None, } } @@ -202,7 +202,7 @@ impl Executable for EBpfElf Executable for EBpfElf, EbpfError>>() @@ -266,24 +266,24 @@ impl Executable for EBpfElf EBpfElf { } /// Fully loads an ELF, including validation and relocation - pub fn load(config: Config, bytes: &[u8]) -> Result { + pub fn load(config: Config, bytes: &[u8]) -> Result { let elf = Elf::parse(bytes)?; let mut elf_bytes = bytes.to_vec(); Self::validate(&elf, &elf_bytes)?; @@ -354,7 +354,7 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EBpfElf { // 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 { - return Err(ELFError::InvalidEntrypoint); + return Err(ElfError::InvalidEntrypoint); } let entrypoint = offset as usize / ebpf::INSN_SIZE; @@ -404,13 +404,13 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EBpfElf { pub fn fixup_relative_calls( calls: &mut HashMap, elf_bytes: &mut [u8], - ) -> Result<(), ELFError> { + ) -> Result<(), ElfError> { for i in 0..elf_bytes.len() / ebpf::INSN_SIZE { let mut insn = ebpf::get_insn(elf_bytes, i); if insn.opc == 0x85 && insn.imm != -1 { let insn_idx = i as isize + 1 + insn.imm as isize; if insn_idx < 0 || insn_idx >= (elf_bytes.len() / ebpf::INSN_SIZE) as isize { - return Err(ELFError::RelativeJumpOutOfBounds( + return Err(ElfError::RelativeJumpOutOfBounds( i + ebpf::ELF_INSN_DUMP_OFFSET, )); } @@ -419,7 +419,7 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EBpfElf { LittleEndian::write_u64(&mut key, i as u64); let hash = ebpf::hash_symbol_name(&key); if calls.insert(hash, insn_idx as usize).is_some() { - return Err(ELFError::RelocationHashCollision( + return Err(ElfError::RelocationHashCollision( i + ebpf::ELF_INSN_DUMP_OFFSET, )); } @@ -427,7 +427,7 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EBpfElf { insn.imm = hash as i32; let checked_slice = elf_bytes .get_mut(i * ebpf::INSN_SIZE..(i * ebpf::INSN_SIZE) + ebpf::INSN_SIZE) - .ok_or(ELFError::OutOfBounds)?; + .ok_or(ElfError::OutOfBounds)?; checked_slice.copy_from_slice(&insn.to_vec()); } } @@ -435,21 +435,21 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EBpfElf { } /// Validates the ELF - pub fn validate(elf: &Elf, elf_bytes: &[u8]) -> Result<(), ELFError> { + pub fn validate(elf: &Elf, elf_bytes: &[u8]) -> Result<(), ElfError> { if elf.header.e_ident[EI_CLASS] != ELFCLASS64 { - return Err(ELFError::WrongClass); + return Err(ElfError::WrongClass); } if elf.header.e_ident[EI_DATA] != ELFDATA2LSB { - return Err(ELFError::WrongEndianess); + return Err(ElfError::WrongEndianess); } if elf.header.e_ident[EI_OSABI] != ELFOSABI_NONE { - return Err(ELFError::WrongABI); + return Err(ElfError::WrongAbi); } if elf.header.e_machine != EM_BPF { - return Err(ELFError::WrongMachine); + return Err(ElfError::WrongMachine); } if elf.header.e_type != ET_DYN { - return Err(ELFError::WrongType); + return Err(ElfError::WrongType); } let num_text_sections = elf.section_headers.iter().fold(0, |count, section_header| { @@ -461,13 +461,13 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EBpfElf { count }); if 1 != num_text_sections { - return Err(ELFError::MultipleTextSections); + return Err(ElfError::MultipleTextSections); } for section_header in elf.section_headers.iter() { if let Some(Ok(this_name)) = elf.shdr_strtab.get(section_header.sh_name) { if this_name == ".bss" { - return Err(ELFError::BSSNotSupported); + return Err(ElfError::BssNotSupported); } } } @@ -477,15 +477,15 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EBpfElf { let end = section_header .sh_offset .checked_add(section_header.sh_size) - .ok_or(ELFError::OutOfBounds)? as usize; - let _ = elf_bytes.get(start..end).ok_or(ELFError::OutOfBounds)?; + .ok_or(ElfError::OutOfBounds)? as usize; + let _ = elf_bytes.get(start..end).ok_or(ElfError::OutOfBounds)?; } let text_section = Self::get_section(elf, ".text")?; if !text_section .vm_range() .contains(&(elf.header.e_entry as usize)) { - return Err(ELFError::EntrypointOutOfBounds); + return Err(ElfError::EntrypointOutOfBounds); } Ok(()) @@ -494,7 +494,7 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EBpfElf { // Private functions /// Get a section by name - fn get_section(elf: &Elf, name: &str) -> Result { + fn get_section(elf: &Elf, name: &str) -> Result { match elf.section_headers.iter().find(|section_header| { if let Some(Ok(this_name)) = elf.shdr_strtab.get(section_header.sh_name) { return this_name == name; @@ -502,7 +502,7 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EBpfElf { false }) { Some(section) => Ok(section.clone()), - None => Err(ELFError::SectionNotFound(name.to_string())), + None => Err(ElfError::SectionNotFound(name.to_string())), } } @@ -511,7 +511,7 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EBpfElf { elf: &Elf, elf_bytes: &mut [u8], calls: &mut HashMap, - ) -> Result<(), ELFError> { + ) -> Result<(), ElfError> { let text_section = Self::get_section(elf, ".text")?; // Fixup all program counter relative call instructions @@ -519,7 +519,7 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EBpfElf { calls, &mut elf_bytes .get_mut(text_section.file_range()) - .ok_or(ELFError::OutOfBounds)?, + .ok_or(ElfError::OutOfBounds)?, )?; // Fixup all the relocations in the relocation section if exists @@ -528,8 +528,8 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EBpfElf { // Offset of the immediate field let imm_offset = r_offset.saturating_add(BYTE_OFFSET_IMMEDIATE); - match BPFRelocationType::from_x86_relocation_type(relocation.r_type) { - Some(BPFRelocationType::R_BPF_64_RELATIVE) => { + match BpfRelocationType::from_x86_relocation_type(relocation.r_type) { + Some(BpfRelocationType::R_Bpf_64_Relative) => { // Raw relocation between sections. The instruction being relocated contains // the virtual address that it needs turned into a physical address. Read it, // locate it in the ELF, convert to physical address @@ -538,11 +538,11 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EBpfElf { // address to convert to physical let checked_slice = elf_bytes .get(imm_offset..imm_offset.saturating_add(BYTE_LENGTH_IMMEIDATE)) - .ok_or(ELFError::OutOfBounds)?; + .ok_or(ElfError::OutOfBounds)?; let refd_va = LittleEndian::read_u32(&checked_slice) as u64; if refd_va == 0 { - return Err(ELFError::InvalidVirtualAddress(refd_va)); + return Err(ElfError::InvalidVirtualAddress(refd_va)); } // final "physical address" from the VM's perspetive is rooted at `MM_PROGRAM_START` @@ -560,7 +560,7 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EBpfElf { let mut checked_slice = elf_bytes .get_mut(imm_offset..imm_offset.saturating_add(BYTE_LENGTH_IMMEIDATE)) - .ok_or(ELFError::OutOfBounds)?; + .ok_or(ElfError::OutOfBounds)?; LittleEndian::write_u32(&mut checked_slice, (refd_pa & 0xFFFFFFFF) as u32); let mut checked_slice = elf_bytes .get_mut( @@ -568,17 +568,17 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EBpfElf { ..imm_offset .saturating_add(ebpf::INSN_SIZE + BYTE_LENGTH_IMMEIDATE), ) - .ok_or(ELFError::OutOfBounds)?; + .ok_or(ElfError::OutOfBounds)?; LittleEndian::write_u32(&mut checked_slice, (refd_pa >> 32) as u32); } else { // 64 bit memory location, write entire 64 bit physical address directly let mut checked_slice = elf_bytes .get_mut(r_offset..r_offset.saturating_add(mem::size_of::())) - .ok_or(ELFError::OutOfBounds)?; + .ok_or(ElfError::OutOfBounds)?; LittleEndian::write_u64(&mut checked_slice, refd_pa); } } - Some(BPFRelocationType::R_BPF_64_32) => { + Some(BpfRelocationType::R_Bpf_64_32) => { // The .text section has an unresolved call to symbol instruction // 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. @@ -586,21 +586,21 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EBpfElf { let sym = elf .dynsyms .get(relocation.r_sym) - .ok_or(ELFError::UnknownSymbol(relocation.r_sym))?; + .ok_or(ElfError::UnknownSymbol(relocation.r_sym))?; let name = elf .dynstrtab .get(sym.st_name) - .ok_or(ELFError::UnknownSymbol(sym.st_name))? - .map_err(|_| ELFError::UnknownSymbol(sym.st_name))?; + .ok_or(ElfError::UnknownSymbol(sym.st_name))? + .map_err(|_| ElfError::UnknownSymbol(sym.st_name))?; let hash = ebpf::hash_symbol_name(&name.as_bytes()); let mut checked_slice = elf_bytes .get_mut(imm_offset..imm_offset.saturating_add(BYTE_LENGTH_IMMEIDATE)) - .ok_or(ELFError::OutOfBounds)?; + .ok_or(ElfError::OutOfBounds)?; LittleEndian::write_u32(&mut checked_slice, hash); let text_section = Self::get_section(elf, ".text")?; if sym.is_function() && sym.st_value != 0 { if !text_section.vm_range().contains(&(sym.st_value as usize)) { - return Err(ELFError::OutOfBounds); + return Err(ElfError::OutOfBounds); } calls.insert( hash, @@ -608,7 +608,7 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EBpfElf { ); } } - _ => return Err(ELFError::UnknownRelocation(relocation.r_type)), + _ => return Err(ElfError::UnknownRelocation(relocation.r_type)), } } @@ -714,7 +714,7 @@ mod test { let mut elf_bytes = elf_bytes; elf_bytes.pwrite(parsed_elf.header, 0).unwrap(); assert_eq!( - Err(ELFError::EntrypointOutOfBounds), + Err(ElfError::EntrypointOutOfBounds), ElfExecutable::load(Config::default(), &elf_bytes) ); @@ -722,7 +722,7 @@ mod test { let mut elf_bytes = elf_bytes; elf_bytes.pwrite(parsed_elf.header, 0).unwrap(); assert_eq!( - Err(ELFError::EntrypointOutOfBounds), + Err(ElfError::EntrypointOutOfBounds), ElfExecutable::load(Config::default(), &elf_bytes) ); @@ -730,7 +730,7 @@ mod test { let mut elf_bytes = elf_bytes; elf_bytes.pwrite(parsed_elf.header, 0).unwrap(); assert_eq!( - Err(ELFError::InvalidEntrypoint), + Err(ElfError::InvalidEntrypoint), ElfExecutable::load(Config::default(), &elf_bytes) ); diff --git a/src/error.rs b/src/error.rs index 4dcdc690..b221a13a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -17,7 +17,7 @@ //! , or for a shorter version of //! the list of the operation codes: -use crate::{elf::ELFError, memory_region::AccessType}; +use crate::{elf::ElfError, memory_region::AccessType}; /// User defined errors must implement this trait pub trait UserDefinedError: 'static + std::error::Error {} @@ -30,7 +30,7 @@ pub enum EbpfError { UserError(#[from] E), /// ELF error #[error("ELF error: {0}")] - ELFError(#[from] ELFError), + ElfError(#[from] ElfError), /// Syscall was already registered before #[error("syscall #{0} was already registered before")] SycallAlreadyRegistered(usize), @@ -62,7 +62,7 @@ pub enum EbpfError { ExceededMaxInstructions(usize, u64), /// Program has not been JIT-compiled #[error("program has not been JIT-compiled")] - JITNotCompiled, + JitNotCompiled, /// Invalid virtual address #[error("invalid virtual address {0:x?}")] InvalidVirtualAddress(u64), diff --git a/src/jit.rs b/src/jit.rs index 3087413f..5f799f5e 100644 --- a/src/jit.rs +++ b/src/jit.rs @@ -124,20 +124,19 @@ impl JitProgram { } // Special values for target_pc in struct Jump -const TARGET_OFFSET: usize = ebpf::PROG_MAX_INSNS; -const TARGET_PC_TRACE: usize = TARGET_OFFSET + 1; -const TARGET_PC_TRANSLATE_PC: usize = TARGET_OFFSET + 2; -const TARGET_PC_TRANSLATE_PC_LOOP: usize = TARGET_OFFSET + 3; -const TARGET_PC_CALL_EXCEEDED_MAX_INSTRUCTIONS: usize = TARGET_OFFSET + 4; -const TARGET_PC_CALL_DEPTH_EXCEEDED: usize = TARGET_OFFSET + 5; -const TARGET_PC_CALL_OUTSIDE_TEXT_SEGMENT: usize = TARGET_OFFSET + 6; -const TARGET_PC_CALLX_UNSUPPORTED_INSTRUCTION: usize = TARGET_OFFSET + 7; -const TARGET_PC_CALL_UNSUPPORTED_INSTRUCTION: usize = TARGET_OFFSET + 8; -const TARGET_PC_DIV_BY_ZERO: usize = TARGET_OFFSET + 9; -const TARGET_PC_EXCEPTION_AT: usize = TARGET_OFFSET + 10; -const TARGET_PC_SYSCALL_EXCEPTION: usize = TARGET_OFFSET + 11; -const TARGET_PC_EXIT: usize = TARGET_OFFSET + 12; -const TARGET_PC_EPILOGUE: usize = TARGET_OFFSET + 13; +const TARGET_PC_TRACE: usize = std::usize::MAX - 13; +const TARGET_PC_TRANSLATE_PC: usize = std::usize::MAX - 12; +const TARGET_PC_TRANSLATE_PC_LOOP: usize = std::usize::MAX - 11; +const TARGET_PC_CALL_EXCEEDED_MAX_INSTRUCTIONS: usize = std::usize::MAX - 10; +const TARGET_PC_CALL_DEPTH_EXCEEDED: usize = std::usize::MAX - 9; +const TARGET_PC_CALL_OUTSIDE_TEXT_SEGMENT: usize = std::usize::MAX - 8; +const TARGET_PC_CALLX_UNSUPPORTED_INSTRUCTION: usize = std::usize::MAX - 7; +const TARGET_PC_CALL_UNSUPPORTED_INSTRUCTION: usize = std::usize::MAX - 6; +const TARGET_PC_DIV_BY_ZERO: usize = std::usize::MAX - 5; +const TARGET_PC_EXCEPTION_AT: usize = std::usize::MAX - 4; +const TARGET_PC_SYSCALL_EXCEPTION: usize = std::usize::MAX - 3; +const TARGET_PC_EXIT: usize = std::usize::MAX - 2; +const TARGET_PC_EPILOGUE: usize = std::usize::MAX - 1; #[derive(Copy, Clone)] enum OperandSize { @@ -1005,6 +1004,7 @@ impl JitCompiler { let entry = executable.get_entrypoint_instruction_offset().unwrap_or(0); if entry != 0 { emit_profile_instruction_count(self, Some(entry + 1))?; + emit_load_imm(self, R11, entry as i64)?; emit_jmp(self, entry)?; } diff --git a/src/verifier.rs b/src/verifier.rs index 68147eb3..9879daa5 100644 --- a/src/verifier.rs +++ b/src/verifier.rs @@ -41,24 +41,24 @@ pub enum VerifierError { /// DivisionByZero #[error("division by 0 (insn #{0})")] DivisionByZero(usize), - /// UnsupportedLEBEArgument + /// UnsupportedLeBeArgument #[error("unsupported argument for LE/BE (insn #{0})")] - UnsupportedLEBEArgument(usize), - /// LDDWCannotBeLast + UnsupportedLeBeArgument(usize), + /// LddwCannotBeLast #[error("LD_DW instruction cannot be last in program")] - LDDWCannotBeLast, - /// IncompleteLDDW + LddwCannotBeLast, + /// IncompleteLddw #[error("incomplete LD_DW instruction (insn #{0})")] - IncompleteLDDW(usize), + IncompleteLddw(usize), /// InfiniteLoop #[error("infinite loop (insn #{0})")] InfiniteLoop(usize), /// JumpOutOfCode #[error("jump out of code to #{0} (insn #{1})")] JumpOutOfCode(usize, usize), - /// JumpToMiddleOfLDDW + /// JumpToMiddleOfLddw #[error("jump to middle of LD_DW at #{0} (insn #{1})")] - JumpToMiddleOfLDDW(usize, usize), + JumpToMiddleOfLddw(usize, usize), /// InvalidSourceRegister #[error("invalid source register (insn #{0})")] InvalidSourceRegister(usize), @@ -114,7 +114,7 @@ fn check_imm_nonzero(insn: &ebpf::Insn, insn_ptr: usize) -> Result<(), VerifierE fn check_imm_endian(insn: &ebpf::Insn, insn_ptr: usize) -> Result<(), VerifierError> { match insn.imm { 16 | 32 | 64 => Ok(()), - _ => Err(VerifierError::UnsupportedLEBEArgument(insn_ptr)), + _ => Err(VerifierError::UnsupportedLeBeArgument(insn_ptr)), } } @@ -123,7 +123,7 @@ fn check_load_dw(prog: &[u8], insn_ptr: usize) -> Result<(), VerifierError> { // this function should be called only for LD_DW insn, that cannot be last in program. let next_insn = ebpf::get_insn(prog, insn_ptr + 1); if next_insn.opc != 0 { - return Err(VerifierError::IncompleteLDDW(insn_ptr)); + return Err(VerifierError::IncompleteLddw(insn_ptr)); } Ok(()) } @@ -143,7 +143,7 @@ fn check_jmp_offset(prog: &[u8], insn_ptr: usize) -> Result<(), VerifierError> { } let dst_insn = ebpf::get_insn(prog, dst_insn_ptr as usize); if dst_insn.opc == 0 { - return Err(VerifierError::JumpToMiddleOfLDDW( + return Err(VerifierError::JumpToMiddleOfLddw( dst_insn_ptr as usize, insn_ptr, )); diff --git a/src/vm.rs b/src/vm.rs index bd810da0..ff4d3ff6 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -283,9 +283,10 @@ impl Tracer { ) -> Result<(), std::fmt::Error> { let disassembled = disassembler::to_insn_vec(program); let mut pc_to_instruction_index = - vec![0usize; disassembled.last().map(|ins| ins.ptr + 1).unwrap_or(0)]; + vec![0usize; disassembled.last().map(|ins| ins.ptr + 2).unwrap_or(0)]; for index in 0..disassembled.len() { pc_to_instruction_index[disassembled[index].ptr] = index; + pc_to_instruction_index[disassembled[index].ptr + 1] = index; } for index in 0..self.log.len() { let entry = &self.log[index]; @@ -983,7 +984,7 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EbpfVm<'a, E, I> { let compiled_program = self .executable .get_compiled_program() - .ok_or(EbpfError::JITNotCompiled)?; + .ok_or(EbpfError::JitNotCompiled)?; unsafe { self.syscall_context_objects[SYSCALL_CONTEXT_OBJECTS_OFFSET - 1] = &mut self.tracer as *mut _ as *mut u8; diff --git a/tests/ubpf_execution.rs b/tests/ubpf_execution.rs index 0b0622f3..6f46a190 100644 --- a/tests/ubpf_execution.rs +++ b/tests/ubpf_execution.rs @@ -15,7 +15,7 @@ use rand::{rngs::SmallRng, RngCore, SeedableRng}; use solana_rbpf::{ assembler::assemble, ebpf::{self, hash_symbol_name}, - elf::ELFError, + elf::ElfError, error::EbpfError, memory_region::AccessType, syscalls, @@ -2870,7 +2870,7 @@ fn test_non_terminate_early() { { |_vm, res: Result| { matches!(res.unwrap_err(), - EbpfError::ELFError(ELFError::UnresolvedSymbol(a, b, c)) + EbpfError::ElfError(ElfError::UnresolvedSymbol(a, b, c)) if a == "Unknown" && b == 35 && c == 48 ) } @@ -2970,7 +2970,7 @@ fn test_err_symbol_unresolved() { [], (), { - |_vm, res: Result| matches!(res.unwrap_err(), EbpfError::ELFError(ELFError::UnresolvedSymbol(symbol, pc, offset)) if symbol == "Unknown" && pc == 29 && offset == 0) + |_vm, res: Result| matches!(res.unwrap_err(), EbpfError::ElfError(ElfError::UnresolvedSymbol(symbol, pc, offset)) if symbol == "Unknown" && pc == 29 && offset == 0) }, 1 ); @@ -2990,7 +2990,7 @@ fn test_err_call_unresolved() { [], (), { - |_vm, res: Result| matches!(res.unwrap_err(), EbpfError::ELFError(ELFError::UnresolvedSymbol(symbol, pc, offset)) if symbol == "Unknown" && pc == 34 && offset == 40) + |_vm, res: Result| matches!(res.unwrap_err(), EbpfError::ElfError(ElfError::UnresolvedSymbol(symbol, pc, offset)) if symbol == "Unknown" && pc == 34 && offset == 40) }, 6 ); @@ -3005,7 +3005,7 @@ fn test_err_unresolved_elf() { hash_symbol_name(b"log") => BpfSyscallString::call; BpfSyscallString {}, ), { - |_vm, res: Result| matches!(res.unwrap_err(), EbpfError::ELFError(ELFError::UnresolvedSymbol(symbol, pc, offset)) if symbol == "log_64" && pc == 550 && offset == 4168) + |_vm, res: Result| matches!(res.unwrap_err(), EbpfError::ElfError(ElfError::UnresolvedSymbol(symbol, pc, offset)) if symbol == "log_64" && pc == 550 && offset == 4168) }, 9 ); @@ -3221,74 +3221,54 @@ fn test_tcp_sack_nomatch() { #[test] fn test_large_program() { - fn write_insn(prog: &mut [u8], insn: usize, asm: &str) { - prog[insn * ebpf::INSN_SIZE..insn * ebpf::INSN_SIZE + ebpf::INSN_SIZE] - .copy_from_slice(&assemble(asm).unwrap()); + fn write_insn(prog: &mut [u8], index: usize, asm: &str) { + let insn = assemble(asm).unwrap(); + prog[index * ebpf::INSN_SIZE..index * ebpf::INSN_SIZE + insn.len()].copy_from_slice(&insn); } - let mut prog = vec![0; ebpf::PROG_MAX_INSNS * ebpf::INSN_SIZE]; - let mut add_insn = vec![0; ebpf::INSN_SIZE]; - write_insn(&mut add_insn, 0, "mov64 r0, 0"); - for insn in (0..(ebpf::PROG_MAX_INSNS - 1) * ebpf::INSN_SIZE).step_by(ebpf::INSN_SIZE) { - prog[insn..insn + ebpf::INSN_SIZE].copy_from_slice(&add_insn); + let mut prog = vec![0; (ebpf::PROG_MAX_INSNS * 2 - 1) * ebpf::INSN_SIZE]; + let mut insn = vec![0; ebpf::INSN_SIZE * 2]; + write_insn(&mut insn, 0, "lddw r0, 0"); + for index in (0..(ebpf::PROG_MAX_INSNS - 1) * ebpf::INSN_SIZE * 2).step_by(ebpf::INSN_SIZE * 2) + { + prog[index..index + ebpf::INSN_SIZE * 2].copy_from_slice(&insn); } - write_insn(&mut prog, ebpf::PROG_MAX_INSNS - 1, "exit"); + write_insn(&mut prog, ebpf::PROG_MAX_INSNS - 4, "ja 9"); + write_insn(&mut prog, ebpf::PROG_MAX_INSNS - 3, "mov r0, 0"); + write_insn(&mut prog, ebpf::PROG_MAX_INSNS * 2 - 2, "exit"); - { - // Test jumping to pc larger then i16 - write_insn(&mut prog, ebpf::PROG_MAX_INSNS - 2, "ja 0x0"); + let config = Config { + enable_instruction_tracing: true, + ..Config::default() + }; - let executable = Executable::::from_text_bytes( - &prog, - None, - Config::default(), - ) - .unwrap(); - let mut vm = - EbpfVm::::new(executable.as_ref(), &mut [], &[]) + #[allow(unused_mut)] + { + let mut executable = + Executable::::from_text_bytes(&prog, None, config) .unwrap(); - assert_eq!( - 0, - vm.execute_program_interpreted(&mut DefaultInstructionMeter {}) - .unwrap() + test_interpreter_and_jit!( + executable, + [], + (), + { |_vm, res: Result| res.unwrap() == 0x0 }, + ebpf::PROG_MAX_INSNS as u64 - 4 ); } - // reset program - write_insn(&mut prog, ebpf::PROG_MAX_INSNS - 2, "mov64 r0, 0"); { - // test program that is too large + // Test program that is too large prog.extend_from_slice(&assemble("exit").unwrap()); assert!( Executable::::from_text_bytes( &prog, Some(check), - Config::default() + config, ) .is_err() ); } - // reset program - prog.truncate(ebpf::PROG_MAX_INSNS * ebpf::INSN_SIZE); - - // verify program still works - #[allow(unused_mut)] - { - let mut executable = Executable::::from_text_bytes( - &prog, - None, - Config::default(), - ) - .unwrap(); - test_interpreter_and_jit!( - executable, - [], - (), - { |_vm, res: Result| res.unwrap() == 0x0 }, - ebpf::PROG_MAX_INSNS as u64 - ); - } } // Fuzzy diff --git a/tests/ubpf_verifier.rs b/tests/ubpf_verifier.rs index df863d7d..5c8a0a56 100644 --- a/tests/ubpf_verifier.rs +++ b/tests/ubpf_verifier.rs @@ -101,7 +101,7 @@ fn test_verifier_err_div_by_zero_imm() { } #[test] -#[should_panic(expected = "UnsupportedLEBEArgument(0)")] +#[should_panic(expected = "UnsupportedLeBeArgument(0)")] fn test_verifier_err_endian_size() { let prog = &[ 0xdc, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, // @@ -117,7 +117,7 @@ fn test_verifier_err_endian_size() { } #[test] -#[should_panic(expected = "IncompleteLDDW(0)")] +#[should_panic(expected = "IncompleteLddw(0)")] fn test_verifier_err_incomplete_lddw() { // Note: ubpf has test-err-incomplete-lddw2, which is the same let prog = &[ @@ -184,7 +184,7 @@ fn test_verifier_err_invalid_reg_src() { } #[test] -#[should_panic(expected = "JumpToMiddleOfLDDW(2, 0)")] +#[should_panic(expected = "JumpToMiddleOfLddw(2, 0)")] fn test_verifier_err_jmp_lddw() { let prog = assemble( "