From 3639ab890eb2776e745e70bafa28d823555290a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Mei=C3=9Fner?= Date: Tue, 6 Jul 2021 14:29:42 +0200 Subject: [PATCH] Fix ELF Symbol Parsing Performance (#176) * Cache parsed and hashed syscall names from ELF dynstrtab. * Keeps hashed syscall names around for the disassembler and the CLI tool. * Moves set_syscall_registry() as argument in the constructors of the Executable. * Moves all syscalls into the file src/syscalls.rs * Configurable enforcement of syscalls to be resolved when an ELF file is loaded. --- Cargo.lock | 6 +- Cargo.toml | 2 +- benches/elf_loader.rs | 20 +++-- benches/jit_compile.rs | 4 +- benches/vm_execution.rs | 17 ++-- cli/Cargo.lock | 6 +- cli/src/main.rs | 26 ++++--- examples/disassemble.rs | 5 +- examples/to_json.rs | 5 +- examples/uptime.rs | 20 ++--- src/assembler.rs | 18 +++-- src/disassembler.rs | 2 +- src/elf.rs | 167 +++++++++++++++++++++++++--------------- src/jit.rs | 31 ++++---- src/static_analysis.rs | 11 +-- src/syscalls.rs | 101 ++++++++++++++++++------ src/vm.rs | 42 ++++++---- test_utils/src/lib.rs | 101 +----------------------- tests/assembler.rs | 23 +++++- tests/disassembler.rs | 14 +++- tests/misc.rs | 30 ++++---- tests/ubpf_execution.rs | 101 +++++++++++++----------- tests/ubpf_verifier.rs | 19 ++++- 23 files changed, 428 insertions(+), 343 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a7d5550c..66ed1d99 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "ascii" version = "0.9.3" @@ -65,9 +67,9 @@ dependencies = [ [[package]] name = "goblin" -version = "0.3.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c69552f48b18aa6102ce0c82dd9bc9d3f8af5fc0a5797069b1b466b90570e39c" +checksum = "0b1800b95efee8ad4ef04517d4d69f8e209e763b1668f1179aeeedd0e454da55" dependencies = [ "log", "plain", diff --git a/Cargo.toml b/Cargo.toml index 39a1bf01..b0cea5fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ include = [ [dependencies] byteorder = "1.2" combine = "3.8.1" -goblin = "0.3.0" +goblin = "0.4.2" hash32 = "0.1.0" libc = "0.2" log = "0.4.2" diff --git a/benches/elf_loader.rs b/benches/elf_loader.rs index 6aa0be05..b2e75ba9 100644 --- a/benches/elf_loader.rs +++ b/benches/elf_loader.rs @@ -11,12 +11,20 @@ extern crate test; extern crate test_utils; use solana_rbpf::{ + syscalls::BpfSyscallU64, user_error::UserError, vm::{Config, DefaultInstructionMeter, Executable, SyscallObject, SyscallRegistry}, }; use std::{fs::File, io::Read}; use test::Bencher; -use test_utils::BpfSyscallU64; + +fn syscall_registry() -> SyscallRegistry { + let mut syscall_registry = SyscallRegistry::default(); + syscall_registry + .register_syscall_by_name::(b"log_64", BpfSyscallU64::call) + .unwrap(); + syscall_registry +} #[bench] fn bench_load_elf(bencher: &mut Bencher) { @@ -28,6 +36,7 @@ fn bench_load_elf(bencher: &mut Bencher) { &elf, None, Config::default(), + syscall_registry(), ) .unwrap() }); @@ -43,6 +52,7 @@ fn bench_load_elf_without_syscall(bencher: &mut Bencher) { &elf, None, Config::default(), + syscall_registry(), ) .unwrap(); executable @@ -55,17 +65,13 @@ fn bench_load_elf_with_syscall(bencher: &mut Bencher) { let mut elf = Vec::new(); file.read_to_end(&mut elf).unwrap(); bencher.iter(|| { - let mut executable = >::from_elf( + let executable = >::from_elf( &elf, None, Config::default(), + syscall_registry(), ) .unwrap(); - let mut syscall_registry = SyscallRegistry::default(); - syscall_registry - .register_syscall_by_name::(b"log_64", BpfSyscallU64::call) - .unwrap(); - executable.set_syscall_registry(syscall_registry); executable }); } diff --git a/benches/jit_compile.rs b/benches/jit_compile.rs index 86bec6f9..b9476aa1 100644 --- a/benches/jit_compile.rs +++ b/benches/jit_compile.rs @@ -11,7 +11,7 @@ extern crate test; use solana_rbpf::{ user_error::UserError, - vm::{Config, DefaultInstructionMeter, EbpfVm, Executable}, + vm::{Config, DefaultInstructionMeter, EbpfVm, Executable, SyscallRegistry}, }; use std::{fs::File, io::Read}; use test::Bencher; @@ -25,6 +25,7 @@ fn bench_init_vm(bencher: &mut Bencher) { &elf, None, Config::default(), + SyscallRegistry::default(), ) .unwrap(); bencher.iter(|| { @@ -43,6 +44,7 @@ fn bench_jit_compile(bencher: &mut Bencher) { &elf, None, Config::default(), + SyscallRegistry::default(), ) .unwrap(); bencher.iter(|| executable.jit_compile().unwrap()); diff --git a/benches/vm_execution.rs b/benches/vm_execution.rs index 78e0ceea..d2902375 100644 --- a/benches/vm_execution.rs +++ b/benches/vm_execution.rs @@ -11,7 +11,7 @@ extern crate test; use solana_rbpf::{ user_error::UserError, - vm::{Config, DefaultInstructionMeter, EbpfVm, Executable}, + vm::{Config, DefaultInstructionMeter, EbpfVm, Executable, SyscallRegistry}, }; use std::{fs::File, io::Read}; use test::Bencher; @@ -25,6 +25,7 @@ fn bench_init_interpreter_execution(bencher: &mut Bencher) { &elf, None, Config::default(), + SyscallRegistry::default(), ) .unwrap(); let mut vm = @@ -46,6 +47,7 @@ fn bench_init_jit_execution(bencher: &mut Bencher) { &elf, None, Config::default(), + SyscallRegistry::default(), ) .unwrap(); executable.jit_compile().unwrap(); @@ -65,11 +67,14 @@ fn bench_jit_vs_interpreter( instruction_meter: u64, mem: &mut [u8], ) { - let mut executable = solana_rbpf::assembler::assemble::< - UserError, - test_utils::TestInstructionMeter, - >(assembly, None, Config::default()) - .unwrap(); + let mut executable = + solana_rbpf::assembler::assemble::( + assembly, + None, + Config::default(), + SyscallRegistry::default(), + ) + .unwrap(); executable.jit_compile().unwrap(); let mut vm = EbpfVm::new(executable.as_ref(), mem, &[]).unwrap(); let interpreter_summary = bencher diff --git a/cli/Cargo.lock b/cli/Cargo.lock index c8d35538..86ff83f7 100644 --- a/cli/Cargo.lock +++ b/cli/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "ascii" version = "0.9.3" @@ -105,9 +107,9 @@ dependencies = [ [[package]] name = "goblin" -version = "0.3.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c69552f48b18aa6102ce0c82dd9bc9d3f8af5fc0a5797069b1b466b90570e39c" +checksum = "0b1800b95efee8ad4ef04517d4d69f8e209e763b1668f1179aeeedd0e454da55" dependencies = [ "log", "plain", diff --git a/cli/src/main.rs b/cli/src/main.rs index df4a2aa5..8d3e3617 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -4,12 +4,13 @@ use solana_rbpf::{ ebpf, memory_region::{MemoryMapping, MemoryRegion}, static_analysis::Analysis, + syscalls::Result, user_error::UserError, verifier::check, vm::{Config, DynamicAnalysis, EbpfVm, Executable, SyscallObject, SyscallRegistry}, }; use std::{fs::File, io::Read, path::Path}; -use test_utils::{Result, TestInstructionMeter}; +use test_utils::TestInstructionMeter; struct MockSyscall { name: String, @@ -122,6 +123,10 @@ fn main() { } else { None }; + let syscall_registry = SyscallRegistry::default(); + /*for hash in syscalls.keys() { + let _ = syscall_registry.register_syscall_by_hash(*hash, MockSyscall::call); + }*/ let mut executable = match matches.value_of("assembler") { Some(asm_file_name) => { let mut file = File::open(&Path::new(asm_file_name)).unwrap(); @@ -131,24 +136,23 @@ fn main() { std::str::from_utf8(source.as_slice()).unwrap(), verifier, config, + syscall_registry, ) } None => { let mut file = File::open(&Path::new(matches.value_of("elf").unwrap())).unwrap(); let mut elf = Vec::new(); file.read_to_end(&mut elf).unwrap(); - Executable::::from_elf(&elf, verifier, config) - .map_err(|err| format!("Executable constructor failed: {:?}", err)) + >::from_elf( + &elf, + verifier, + config, + syscall_registry, + ) + .map_err(|err| format!("Executable constructor failed: {:?}", err)) } } .unwrap(); - - let (syscalls, _functions) = executable.get_symbols(); - let mut syscall_registry = SyscallRegistry::default(); - for hash in syscalls.keys() { - let _ = syscall_registry.register_syscall_by_hash(*hash, MockSyscall::call); - } - executable.set_syscall_registry(syscall_registry); executable.jit_compile().unwrap(); let analysis = Analysis::from_executable(executable.as_ref()); @@ -192,7 +196,7 @@ fn main() { ]; let heap_region = MemoryRegion::new_from_slice(&heap, ebpf::MM_HEAP_START, 0, true); let mut vm = EbpfVm::new(executable.as_ref(), &mut mem, &[heap_region]).unwrap(); - for (hash, name) in &analysis.syscalls { + for (hash, name) in analysis.executable.get_syscall_symbols() { vm.bind_syscall_context_object(Box::new(MockSyscall { name: name.clone() }), Some(*hash)) .unwrap(); } diff --git a/examples/disassemble.rs b/examples/disassemble.rs index 7f4a7e69..745ea145 100644 --- a/examples/disassemble.rs +++ b/examples/disassemble.rs @@ -8,7 +8,7 @@ extern crate solana_rbpf; use solana_rbpf::{ static_analysis::Analysis, user_error::UserError, - vm::{Config, DefaultInstructionMeter, Executable}, + vm::{Config, DefaultInstructionMeter, Executable, SyscallRegistry}, }; use std::collections::BTreeMap; @@ -32,9 +32,10 @@ fn main() { ]; let executable = >::from_text_bytes( &program, - BTreeMap::new(), None, Config::default(), + SyscallRegistry::default(), + BTreeMap::default(), ) .unwrap(); let analysis = Analysis::from_executable(executable.as_ref()); diff --git a/examples/to_json.rs b/examples/to_json.rs index 685009a6..5fe8fbd1 100644 --- a/examples/to_json.rs +++ b/examples/to_json.rs @@ -15,7 +15,7 @@ use solana_rbpf::{ disassembler::disassemble_instruction, static_analysis::Analysis, user_error::UserError, - vm::{Config, DefaultInstructionMeter, Executable}, + vm::{Config, DefaultInstructionMeter, Executable, SyscallRegistry}, }; use std::collections::BTreeMap; // Turn a program into a JSON string. @@ -30,9 +30,10 @@ use std::collections::BTreeMap; fn to_json(program: &[u8]) -> String { let executable = >::from_text_bytes( &program, - BTreeMap::new(), None, Config::default(), + SyscallRegistry::default(), + BTreeMap::default(), ) .unwrap(); let analysis = Analysis::from_executable(executable.as_ref()); diff --git a/examples/uptime.rs b/examples/uptime.rs index ee2e46ad..7fb550b3 100644 --- a/examples/uptime.rs +++ b/examples/uptime.rs @@ -45,9 +45,10 @@ fn main() { // Create a VM: this one takes no data. Load prog1 in it. let executable = >::from_text_bytes( prog1, - BTreeMap::new(), None, Config::default(), + SyscallRegistry::default(), + BTreeMap::default(), ) .unwrap(); let mut vm = @@ -67,13 +68,6 @@ fn main() { // In the following example we use a syscall to get the elapsed time since boot time: we // reimplement uptime in eBPF, in Rust. Because why not. - let mut executable = >::from_text_bytes( - prog2, - BTreeMap::new(), - None, - Config::default(), - ) - .unwrap(); let mut syscall_registry = SyscallRegistry::default(); syscall_registry .register_syscall_by_hash::( @@ -81,7 +75,15 @@ fn main() { syscalls::BpfTimeGetNs::call, ) .unwrap(); - executable.set_syscall_registry(syscall_registry); + #[allow(unused_mut)] + let mut executable = >::from_text_bytes( + prog2, + None, + Config::default(), + syscall_registry, + BTreeMap::default(), + ) + .unwrap(); #[cfg(not(windows))] { executable.jit_compile().unwrap(); diff --git a/src/assembler.rs b/src/assembler.rs index 9727a141..c0e916ef 100644 --- a/src/assembler.rs +++ b/src/assembler.rs @@ -19,7 +19,7 @@ use crate::{ ebpf::{self, Insn}, elf::register_bpf_function, error::UserDefinedError, - vm::{Config, Executable, InstructionMeter, Verifier}, + vm::{Config, Executable, InstructionMeter, SyscallRegistry, Verifier}, }; use std::collections::{BTreeMap, HashMap}; @@ -178,7 +178,7 @@ fn insn(opc: u8, dst: i64, src: i64, off: i64, imm: i64) -> Result /// # Examples /// /// ``` -/// use solana_rbpf::{assembler::assemble, user_error::UserError, vm::{Config, DefaultInstructionMeter}}; +/// use solana_rbpf::{assembler::assemble, user_error::UserError, vm::{Config, DefaultInstructionMeter, SyscallRegistry}}; /// let executable = assemble::( /// "add64 r1, 0x605 /// mov64 r2, 0x32 @@ -187,7 +187,8 @@ fn insn(opc: u8, dst: i64, src: i64, off: i64, imm: i64) -> Result /// neg64 r2 /// exit", /// None, -/// Config::default() +/// Config::default(), +/// SyscallRegistry::default(), /// ).unwrap(); /// let program = executable.get_text_bytes().unwrap().1; /// println!("{:?}", program); @@ -214,6 +215,7 @@ pub fn assemble( src: &str, verifier: Option, config: Config, + syscall_registry: SyscallRegistry, ) -> Result>, String> { fn resolve_label( insn_ptr: usize, @@ -358,6 +360,12 @@ pub fn assemble( .map(|insn| insn.to_vec()) .flatten() .collect::>(); - >::from_text_bytes(&program, bpf_functions, verifier, config) - .map_err(|err| format!("Executable constructor {:?}", err)) + >::from_text_bytes( + &program, + verifier, + config, + syscall_registry, + bpf_functions, + ) + .map_err(|err| format!("Executable constructor {:?}", err)) } diff --git a/src/disassembler.rs b/src/disassembler.rs index 5b06190f..895caebc 100644 --- a/src/disassembler.rs +++ b/src/disassembler.rs @@ -225,7 +225,7 @@ pub fn disassemble_instruction(insn: & ebpf::JSLE_IMM => { name = "jsle"; desc = jmp_imm_str(name, insn, analysis); }, ebpf::JSLE_REG => { name = "jsle"; desc = jmp_reg_str(name, insn, analysis); }, ebpf::CALL_IMM => { - desc = if let Some(syscall_name) = analysis.syscalls.get(&(insn.imm as u32)) { + desc = if let Some(syscall_name) = analysis.executable.get_syscall_symbols().get(&(insn.imm as u32)) { name = "syscall"; format!("{} {}", name, syscall_name) } else { diff --git a/src/elf.rs b/src/elf.rs index 5875602e..50259820 100644 --- a/src/elf.rs +++ b/src/elf.rs @@ -207,8 +207,10 @@ pub struct EBpfElf { text_section_info: SectionInfo, /// Read-only section info ro_section_infos: Vec, - /// Call resolution map (pc, hash, name) + /// Call resolution map (hash, pc, name) bpf_functions: BTreeMap, + /// Syscall symbol map (hash, name) + syscall_symbols: BTreeMap, /// Syscall resolution map syscall_registry: SyscallRegistry, /// Compiled program and argument @@ -266,11 +268,6 @@ impl Executable for EBpfElf Option<&JitProgram> { self.compiled_program.as_ref() @@ -301,9 +298,8 @@ impl Executable for EBpfElf Executable for EBpfElf (BTreeMap, BTreeMap) { - let mut syscalls = BTreeMap::new(); + fn get_function_symbols(&self) -> BTreeMap { let mut bpf_functions = BTreeMap::new(); - if let Ok(elf) = Elf::parse(self.elf_bytes.as_slice()) { - for symbol in &elf.dynsyms { - if symbol.st_info != 0x10 { - continue; - } - let name = elf.dynstrtab.get(symbol.st_name).unwrap().unwrap(); - let hash = ebpf::hash_symbol_name(name.as_bytes()); - syscalls.insert(hash, name.to_string()); - } - } for (hash, (pc, name)) in self.bpf_functions.iter() { bpf_functions.insert(*pc, (*hash, name.clone())); } - (syscalls, bpf_functions) + bpf_functions + } + + /// Get syscalls symbols + fn get_syscall_symbols(&self) -> &BTreeMap { + &self.syscall_symbols } } @@ -342,6 +332,7 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EBpfElf { pub fn new_from_text_bytes( config: Config, text_bytes: &[u8], + syscall_registry: SyscallRegistry, bpf_functions: BTreeMap, ) -> Self { let elf_bytes = AlignedMemory::new_with_data(text_bytes, ebpf::HOST_ALIGN); @@ -358,13 +349,18 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EBpfElf { }, ro_section_infos: vec![], bpf_functions, - syscall_registry: SyscallRegistry::default(), + syscall_symbols: BTreeMap::default(), + syscall_registry, compiled_program: None, } } /// Fully loads an ELF, including validation and relocation - pub fn load(config: Config, bytes: &[u8]) -> Result { + pub fn load( + config: Config, + bytes: &[u8], + mut syscall_registry: SyscallRegistry, + ) -> Result { let elf = Elf::parse(bytes)?; let mut elf_bytes = AlignedMemory::new_with_data(bytes, ebpf::HOST_ALIGN); @@ -375,12 +371,11 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EBpfElf { let text_section_info = SectionInfo { name: elf .shdr_strtab - .get(text_section.sh_name) - .unwrap() + .get_at(text_section.sh_name) .unwrap() .to_string(), vaddr: text_section.sh_addr.saturating_add(ebpf::MM_PROGRAM_START), - offset_range: text_section.file_range(), + offset_range: text_section.file_range().unwrap_or_default(), }; if text_section_info.vaddr > ebpf::MM_STACK_START { return Err(ElfError::OutOfBounds); @@ -388,7 +383,15 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EBpfElf { // relocate symbols let mut bpf_functions = BTreeMap::default(); - Self::relocate(&mut bpf_functions, &elf, elf_bytes.as_slice_mut())?; + let mut syscall_symbols = BTreeMap::default(); + Self::relocate( + &config, + &mut bpf_functions, + &mut syscall_symbols, + &mut syscall_registry, + &elf, + elf_bytes.as_slice_mut(), + )?; // calculate entrypoint offset into the text section let offset = elf.header.e_entry - text_section.sh_addr; @@ -404,14 +407,14 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EBpfElf { .section_headers .iter() .filter_map(|section_header| { - if let Some(Ok(name)) = elf.shdr_strtab.get(section_header.sh_name) { + if let Some(name) = elf.shdr_strtab.get_at(section_header.sh_name) { if name == ".rodata" || name == ".data.rel.ro" || name == ".eh_frame" { return Some(SectionInfo { name: name.to_string(), vaddr: section_header .sh_addr .saturating_add(ebpf::MM_PROGRAM_START), - offset_range: section_header.file_range(), + offset_range: section_header.file_range().unwrap_or_default(), }); } } @@ -430,7 +433,8 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EBpfElf { text_section_info, ro_section_infos, bpf_functions, - syscall_registry: SyscallRegistry::default(), + syscall_symbols, + syscall_registry, compiled_program: None, }) } @@ -482,7 +486,7 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EBpfElf { } let num_text_sections = elf.section_headers.iter().fold(0, |count, section_header| { - if let Some(Ok(this_name)) = elf.shdr_strtab.get(section_header.sh_name) { + if let Some(this_name) = elf.shdr_strtab.get_at(section_header.sh_name) { if this_name == ".text" { return count + 1; } @@ -494,7 +498,7 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EBpfElf { } for section_header in elf.section_headers.iter() { - if let Some(Ok(this_name)) = elf.shdr_strtab.get(section_header.sh_name) { + if let Some(this_name) = elf.shdr_strtab.get_at(section_header.sh_name) { if this_name.starts_with(".bss") { return Err(ElfError::BssNotSupported); } @@ -525,7 +529,7 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EBpfElf { /// Get a section by name 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) { + if let Some(this_name) = elf.shdr_strtab.get_at(section_header.sh_name) { return this_name == name; } false @@ -537,7 +541,10 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EBpfElf { /// Relocates the ELF in-place fn relocate( + config: &Config, bpf_functions: &mut BTreeMap, + syscall_symbols: &mut BTreeMap, + syscall_registry: &mut SyscallRegistry, elf: &Elf, elf_bytes: &mut [u8], ) -> Result<(), ElfError> { @@ -547,10 +554,13 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EBpfElf { Self::fixup_relative_calls( bpf_functions, &mut elf_bytes - .get_mut(text_section.file_range()) + .get_mut(text_section.file_range().unwrap_or_default()) .ok_or(ElfError::OutOfBounds)?, )?; + let mut syscall_cache = BTreeMap::new(); + let text_section = Self::get_section(elf, ".text")?; + // Fixup all the relocations in the relocation section if exists for relocation in &elf.dynrels { let r_offset = relocation.r_offset as usize; @@ -577,13 +587,12 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EBpfElf { // 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); - // trace!( - // "Relocation section va {:#x} off {:#x} va {:#x} pa {:#x} va {:#x} pa {:#x}", - // section_infos[target_section].va, target_offset, relocation.addr, section_infos[target_section].bytes.as_ptr() as usize + target_offset, refd_va, refd_pa - // ); - // Write the physical address back into the target location - if text_section.file_range().contains(&r_offset) { + if text_section + .file_range() + .unwrap_or_default() + .contains(&r_offset) + { // Instruction lddw spans two instruction slots, split the // physical address into a high and low and write into both slot's imm field @@ -618,10 +627,8 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EBpfElf { .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))?; - let text_section = Self::get_section(elf, ".text")?; + .get_at(sym.st_name) + .ok_or(ElfError::UnknownSymbol(sym.st_name))?; let hash = if sym.is_function() && sym.st_value != 0 { // bpf call if !text_section.vm_range().contains(&(sym.st_value as usize)) { @@ -632,7 +639,20 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EBpfElf { register_bpf_function(bpf_functions, target_pc, name)? } else { // syscall - ebpf::hash_symbol_name(name.as_bytes()) + let hash = syscall_cache + .entry(sym.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, + )); + } + hash }; let mut checked_slice = elf_bytes .get_mut(imm_offset..imm_offset.saturating_add(BYTE_LENGTH_IMMEIDATE)) @@ -643,6 +663,12 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EBpfElf { } } + // Save hashed syscall names for debugging + *syscall_symbols = syscall_cache + .values() + .map(|(hash, name)| (*hash, name.to_string())) + .collect(); + // Register all known function names from the symbol table for symbol in &elf.syms { if symbol.st_info & 0xEF != 0x02 { @@ -657,9 +683,8 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EBpfElf { let target_pc = (symbol.st_value - text_section.sh_addr) as usize / ebpf::INSN_SIZE; let name = elf .strtab - .get(symbol.st_name) - .ok_or(ElfError::UnknownSymbol(symbol.st_name))? - .map_err(|_| ElfError::UnknownSymbol(symbol.st_name))?; + .get_at(symbol.st_name) + .ok_or(ElfError::UnknownSymbol(symbol.st_name))?; register_bpf_function(bpf_functions, target_pc, name)?; } @@ -685,12 +710,28 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EBpfElf { mod test { use super::*; use crate::{ - ebpf, elf::scroll::Pwrite, fuzz::fuzz, user_error::UserError, vm::DefaultInstructionMeter, + ebpf, + elf::scroll::Pwrite, + fuzz::fuzz, + syscalls::{BpfSyscallString, BpfSyscallU64}, + user_error::UserError, + vm::{DefaultInstructionMeter, SyscallObject}, }; use rand::{distributions::Uniform, Rng}; use std::{fs::File, io::Read}; type ElfExecutable = EBpfElf; + fn syscall_registry() -> SyscallRegistry { + let mut syscall_registry = SyscallRegistry::default(); + syscall_registry + .register_syscall_by_name(b"log", BpfSyscallString::call) + .unwrap(); + syscall_registry + .register_syscall_by_name(b"log_64", BpfSyscallU64::call) + .unwrap(); + syscall_registry + } + #[test] fn test_validate() { let mut file = File::open("tests/elfs/noop.so").expect("file open failed"); @@ -729,7 +770,8 @@ mod test { let mut elf_bytes = Vec::new(); file.read_to_end(&mut elf_bytes) .expect("failed to read elf file"); - ElfExecutable::load(Config::default(), &elf_bytes).expect("validation failed"); + ElfExecutable::load(Config::default(), &elf_bytes, syscall_registry()) + .expect("validation failed"); } #[test] @@ -738,7 +780,8 @@ mod test { let mut elf_bytes = Vec::new(); file.read_to_end(&mut elf_bytes) .expect("failed to read elf file"); - let elf = ElfExecutable::load(Config::default(), &elf_bytes).expect("validation failed"); + let elf = ElfExecutable::load(Config::default(), &elf_bytes, syscall_registry()) + .expect("validation failed"); let mut parsed_elf = Elf::parse(&elf_bytes).unwrap(); let initial_e_entry = parsed_elf.header.e_entry; let executable: &dyn Executable = &elf; @@ -752,7 +795,8 @@ mod test { parsed_elf.header.e_entry += 8; let mut elf_bytes = elf_bytes.clone(); elf_bytes.pwrite(parsed_elf.header, 0).unwrap(); - let elf = ElfExecutable::load(Config::default(), &elf_bytes).expect("validation failed"); + let elf = ElfExecutable::load(Config::default(), &elf_bytes, syscall_registry()) + .expect("validation failed"); let executable: &dyn Executable = &elf; assert_eq!( 1, @@ -766,7 +810,7 @@ mod test { elf_bytes.pwrite(parsed_elf.header, 0).unwrap(); assert_eq!( Err(ElfError::EntrypointOutOfBounds), - ElfExecutable::load(Config::default(), &elf_bytes) + ElfExecutable::load(Config::default(), &elf_bytes, syscall_registry()) ); parsed_elf.header.e_entry = std::u64::MAX; @@ -774,7 +818,7 @@ mod test { elf_bytes.pwrite(parsed_elf.header, 0).unwrap(); assert_eq!( Err(ElfError::EntrypointOutOfBounds), - ElfExecutable::load(Config::default(), &elf_bytes) + ElfExecutable::load(Config::default(), &elf_bytes, syscall_registry()) ); parsed_elf.header.e_entry = initial_e_entry + ebpf::INSN_SIZE as u64 + 1; @@ -782,13 +826,14 @@ mod test { elf_bytes.pwrite(parsed_elf.header, 0).unwrap(); assert_eq!( Err(ElfError::InvalidEntrypoint), - ElfExecutable::load(Config::default(), &elf_bytes) + ElfExecutable::load(Config::default(), &elf_bytes, syscall_registry()) ); parsed_elf.header.e_entry = initial_e_entry; let mut elf_bytes = elf_bytes; elf_bytes.pwrite(parsed_elf.header, 0).unwrap(); - let elf = ElfExecutable::load(Config::default(), &elf_bytes).expect("validation failed"); + let elf = ElfExecutable::load(Config::default(), &elf_bytes, syscall_registry()) + .expect("validation failed"); let executable: &dyn Executable = &elf; assert_eq!( 0, @@ -953,7 +998,7 @@ mod test { println!("random bytes"); for _ in 0..1_000 { let elf_bytes: Vec = (0..100).map(|_| rng.sample(&range)).collect(); - let _ = ElfExecutable::load(Config::default(), &elf_bytes); + let _ = ElfExecutable::load(Config::default(), &elf_bytes, SyscallRegistry::default()); } // Take a real elf and mangle it @@ -973,7 +1018,7 @@ mod test { 0..parsed_elf.header.e_ehsize as usize, 0..255, |bytes: &mut [u8]| { - let _ = ElfExecutable::load(Config::default(), bytes); + let _ = ElfExecutable::load(Config::default(), bytes, SyscallRegistry::default()); }, ); @@ -986,7 +1031,7 @@ mod test { parsed_elf.header.e_shoff as usize..elf_bytes.len(), 0..255, |bytes: &mut [u8]| { - let _ = ElfExecutable::load(Config::default(), bytes); + let _ = ElfExecutable::load(Config::default(), bytes, SyscallRegistry::default()); }, ); @@ -999,7 +1044,7 @@ mod test { 0..elf_bytes.len(), 0..255, |bytes: &mut [u8]| { - let _ = ElfExecutable::load(Config::default(), bytes); + let _ = ElfExecutable::load(Config::default(), bytes, SyscallRegistry::default()); }, ); } diff --git a/src/jit.rs b/src/jit.rs index c25cf99b..1bd6d6dd 100644 --- a/src/jit.rs +++ b/src/jit.rs @@ -23,7 +23,7 @@ use std::ops::{Index, IndexMut}; use rand::{rngs::ThreadRng, Rng}; use crate::{ - vm::{Config, Executable, ProgramResult, InstructionMeter, Tracer, DynTraitFatPointer, SYSCALL_CONTEXT_OBJECTS_OFFSET}, + vm::{Config, Executable, ProgramResult, InstructionMeter, Tracer, DynTraitFatPointer, SYSCALL_CONTEXT_OBJECTS_OFFSET, REPORT_UNRESOLVED_SYMBOL_INDEX}, ebpf::{self, INSN_SIZE, FIRST_SCRATCH_REG, SCRATCH_REGS, STACK_REG, MM_STACK_START}, error::{UserDefinedError, EbpfError}, memory_region::{AccessType, MemoryMapping}, @@ -1234,24 +1234,19 @@ impl JitCompiler { X86Instruction::store(OperandSize::S64, ARGUMENT_REGISTERS[0], RBP, X86IndirectAccess::Offset(slot_on_environment_stack(self, EnvironmentStackSlot::PrevInsnMeter))).emit(self)?; emit_undo_profile_instruction_count(self, 0)?; } + } else if let Some(target_pc) = executable.lookup_bpf_function(insn.imm as u32) { + emit_bpf_call(self, Value::Constant64(target_pc as i64, false), self.result.pc_section.len() - 1)?; } else { - match executable.lookup_bpf_function(insn.imm as u32) { - Some(target_pc) => { - emit_bpf_call(self, Value::Constant64(target_pc as i64, false), self.result.pc_section.len() - 1)?; - }, - None => { - // executable.report_unresolved_symbol(self.pc)?; - // Workaround for unresolved symbols in ELF: Report error at runtime instead of compiletime - let fat_ptr: DynTraitFatPointer = unsafe { std::mem::transmute(executable) }; - emit_rust_call(self, fat_ptr.vtable.methods[9], &[ - Argument { index: 2, value: Value::Constant64(self.pc as i64, false) }, - Argument { index: 1, value: Value::Constant64(fat_ptr.data as i64, false) }, - Argument { index: 0, value: Value::RegisterIndirect(RBP, slot_on_environment_stack(self, EnvironmentStackSlot::OptRetValPtr), false) }, - ], None, true)?; - X86Instruction::load_immediate(OperandSize::S64, R11, self.pc as i64).emit(self)?; - emit_jmp(self, TARGET_PC_SYSCALL_EXCEPTION)?; - }, - } + // executable.report_unresolved_symbol(self.pc)?; + // Workaround for unresolved symbols in ELF: Report error at runtime instead of compiletime + let fat_ptr: DynTraitFatPointer = unsafe { std::mem::transmute(executable) }; + emit_rust_call(self, fat_ptr.vtable.methods[REPORT_UNRESOLVED_SYMBOL_INDEX], &[ + Argument { index: 2, value: Value::Constant64(self.pc as i64, false) }, + Argument { index: 1, value: Value::Constant64(fat_ptr.data as i64, false) }, + Argument { index: 0, value: Value::RegisterIndirect(RBP, slot_on_environment_stack(self, EnvironmentStackSlot::OptRetValPtr), false) }, + ], None, true)?; + X86Instruction::load_immediate(OperandSize::S64, R11, self.pc as i64).emit(self)?; + emit_jmp(self, TARGET_PC_SYSCALL_EXCEPTION)?; } }, ebpf::CALL_REG => { diff --git a/src/static_analysis.rs b/src/static_analysis.rs index 55b0b977..4b03b713 100644 --- a/src/static_analysis.rs +++ b/src/static_analysis.rs @@ -96,8 +96,6 @@ pub struct Analysis<'a, E: UserDefinedError, I: InstructionMeter> { pub executable: &'a dyn Executable, /// Plain list of instructions as they occur in the executable pub instructions: Vec, - /// Syscalls used by the executable (available if debug symbols are not stripped) - pub syscalls: BTreeMap, /// Functions in the executable pub functions: BTreeMap, /// Nodes of the control-flow graph @@ -116,7 +114,7 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> Analysis<'a, E, I> { /// Analyze an executable statically pub fn from_executable(executable: &'a dyn Executable) -> Self { let (_program_vm_addr, program) = executable.get_text_bytes().unwrap(); - let (syscalls, functions) = executable.get_symbols(); + let functions = executable.get_function_symbols(); debug_assert!( program.len() % ebpf::INSN_SIZE == 0, "eBPF program length must be a multiple of {:?} octets is {:?}", @@ -140,7 +138,6 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> Analysis<'a, E, I> { let mut result = Self { executable, instructions, - syscalls, functions, cfg_nodes: BTreeMap::new(), topological_order: Vec::new(), @@ -189,7 +186,11 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> Analysis<'a, E, I> { let target_pc = (insn.ptr as isize + insn.off as isize + 1) as usize; match insn.opc { ebpf::CALL_IMM => { - if let Some(syscall_name) = self.syscalls.get(&(insn.imm as u32)) { + if let Some(syscall_name) = self + .executable + .get_syscall_symbols() + .get(&(insn.imm as u32)) + { if syscall_name == "abort" { self.cfg_nodes .entry(insn.ptr + 1) diff --git a/src/syscalls.rs b/src/syscalls.rs index b15e2c37..bdd69353 100644 --- a/src/syscalls.rs +++ b/src/syscalls.rs @@ -28,7 +28,8 @@ use crate::{ user_error::UserError, vm::SyscallObject, }; -use std::u64; +use libc::c_char; +use std::{slice::from_raw_parts, str::from_utf8, u64}; /// Return type of syscalls pub type Result = std::result::Result>; @@ -243,27 +244,6 @@ impl SyscallObject for BpfMemFrob { } } -// TODO: Try again when asm!() is available in stable Rust. -// #![feature(asm)] -// #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -// #[allow(unused_variables)] -// pub fn BpfMemFrob (ptr: u64, len: u64, arg3: u64, arg4: u64, arg5: u64) -> Result { -// unsafe { -// asm!( -// "mov $0xf0, %rax" -// ::: "mov $0xf1, %rcx" -// ::: "mov $0xf2, %rdx" -// ::: "mov $0xf3, %rsi" -// ::: "mov $0xf4, %rdi" -// ::: "mov $0xf5, %r8" -// ::: "mov $0xf6, %r9" -// ::: "mov $0xf7, %r10" -// ::: "mov $0xf8, %r11" -// ); -// } -// 0 -// } - /// Compute and return the square root of argument 1, cast as a float. Arguments 2 to 5 are /// unused. /// @@ -409,3 +389,80 @@ impl SyscallObject for BpfRand { *result = Result::Ok(n); } } + +/// Prints a NULL-terminated UTF-8 string. +pub struct BpfSyscallString {} +impl SyscallObject for BpfSyscallString { + fn call( + &mut self, + vm_addr: u64, + len: u64, + _arg3: u64, + _arg4: u64, + _arg5: u64, + memory_mapping: &MemoryMapping, + result: &mut Result, + ) { + let host_addr = question_mark!(memory_mapping.map(AccessType::Load, vm_addr, len), result); + let c_buf: *const c_char = host_addr as *const c_char; + unsafe { + for i in 0..len { + let c = std::ptr::read(c_buf.offset(i as isize)); + if c == 0 { + break; + } + } + let message = from_utf8(from_raw_parts(host_addr as *const u8, len as usize)) + .unwrap_or("Invalid UTF-8 String"); + println!("log: {}", message); + } + *result = Result::Ok(0); + } +} + +/// Prints the five arguments formated as u64 in decimal. +pub struct BpfSyscallU64 {} +impl SyscallObject for BpfSyscallU64 { + fn call( + &mut self, + arg1: u64, + arg2: u64, + arg3: u64, + arg4: u64, + arg5: u64, + memory_mapping: &MemoryMapping, + result: &mut Result, + ) { + println!( + "dump_64: {:#x}, {:#x}, {:#x}, {:#x}, {:#x}, {:?}", + arg1, arg2, arg3, arg4, arg5, memory_mapping as *const _ + ); + *result = Result::Ok(0); + } +} + +/// Excample of a syscall with internal state. +pub struct SyscallWithContext { + /// Mutable state + pub context: u64, +} +impl SyscallObject for SyscallWithContext { + fn call( + &mut self, + arg1: u64, + arg2: u64, + arg3: u64, + arg4: u64, + arg5: u64, + memory_mapping: &MemoryMapping, + result: &mut Result, + ) { + println!( + "SyscallWithContext: {:?}, {:#x}, {:#x}, {:#x}, {:#x}, {:#x}, {:?}", + self as *const _, arg1, arg2, arg3, arg4, arg5, memory_mapping as *const _ + ); + assert_eq!(self.context, 42); + self.context = 84; + *result = Result::Ok(0); + } +} diff --git a/src/vm.rs b/src/vm.rs index 6bc7d4fe..a25378c6 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -183,6 +183,8 @@ pub struct Config { pub enable_instruction_meter: bool, /// Enable instruction tracing pub enable_instruction_tracing: bool, + /// Reject ELF files containing syscalls which are not in the SyscallRegistry + pub reject_unresolved_syscalls: bool, /// Ratio of random no-ops per instruction in JIT (0.0 = OFF) pub noop_instruction_ratio: f64, /// Enable disinfection of immediate values and offsets provided by the user in JIT @@ -197,6 +199,7 @@ impl Default for Config { stack_frame_size: 4_096, enable_instruction_meter: true, enable_instruction_tracing: false, + reject_unresolved_syscalls: false, noop_instruction_ratio: 1.0 / 256.0, sanitize_user_provided_values: true, encrypt_environment_registers: true, @@ -218,18 +221,24 @@ pub trait Executable: Send + Sync { fn lookup_bpf_function(&self, hash: u32) -> Option; /// Get the syscall registry fn get_syscall_registry(&self) -> &SyscallRegistry; - /// Set (overwrite) the syscall registry - fn set_syscall_registry(&mut self, syscall_registry: SyscallRegistry); /// Get the JIT compiled program fn get_compiled_program(&self) -> Option<&JitProgram>; /// JIT compile the executable fn jit_compile(&mut self) -> Result<(), EbpfError>; /// Report information on a symbol that failed to be resolved fn report_unresolved_symbol(&self, insn_offset: usize) -> Result>; - /// Get syscalls and BPF functions (if debug symbols are not stripped) - fn get_symbols(&self) -> (BTreeMap, BTreeMap); + /// Get BPF functions + fn get_function_symbols(&self) -> BTreeMap; + /// Get syscalls symbols + fn get_syscall_symbols(&self) -> &BTreeMap; } +/// Index of report_unresolved_symbol in the Executable traits vtable +pub const REPORT_UNRESOLVED_SYMBOL_INDEX: usize = 8; + +/// The syscall_context_objects field stores some metadata in the front, thus the entries are shifted +pub const SYSCALL_CONTEXT_OBJECTS_OFFSET: usize = 6; + /// Static constructors for Executable impl dyn Executable { /// Creates a post relocaiton/fixup executable from an ELF file @@ -237,8 +246,9 @@ impl dyn Executable { elf_bytes: &[u8], verifier: Option, config: Config, + syscall_registry: SyscallRegistry, ) -> Result, EbpfError> { - let ebpf_elf = EBpfElf::load(config, elf_bytes)?; + let ebpf_elf = EBpfElf::load(config, elf_bytes, syscall_registry)?; let (_, bytes) = ebpf_elf.get_text_bytes()?; if let Some(verifier) = verifier { verifier(bytes)?; @@ -248,9 +258,10 @@ impl dyn Executable { /// Creates a post relocaiton/fixup executable from machine code pub fn from_text_bytes( text_bytes: &[u8], - bpf_functions: BTreeMap, verifier: Option, config: Config, + syscall_registry: SyscallRegistry, + bpf_functions: BTreeMap, ) -> Result, EbpfError> { if let Some(verifier) = verifier { verifier(text_bytes).map_err(EbpfError::VerifierError)?; @@ -258,6 +269,7 @@ impl dyn Executable { Ok(Box::new(EBpfElf::new_from_text_bytes( config, text_bytes, + syscall_registry, bpf_functions, ))) } @@ -410,15 +422,12 @@ macro_rules! translate_memory_access { }; } -/// The syscall_context_objects field also stores some metadata in the front, thus the entries are shifted -pub const SYSCALL_CONTEXT_OBJECTS_OFFSET: usize = 6; - /// A virtual machine to run eBPF program. /// /// # Examples /// /// ``` -/// use solana_rbpf::{ebpf, elf::register_bpf_function, vm::{Config, Executable, EbpfVm, DefaultInstructionMeter}, user_error::UserError}; +/// use solana_rbpf::{ebpf, elf::register_bpf_function, vm::{Config, Executable, EbpfVm, DefaultInstructionMeter, SyscallRegistry}, user_error::UserError}; /// /// let prog = &[ /// 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // exit @@ -430,7 +439,7 @@ pub const SYSCALL_CONTEXT_OBJECTS_OFFSET: usize = 6; /// // Instantiate a VM. /// let mut bpf_functions = std::collections::BTreeMap::new(); /// register_bpf_function(&mut bpf_functions, 0, "entrypoint").unwrap(); -/// let mut executable = >::from_text_bytes(prog, bpf_functions, None, Config::default()).unwrap(); +/// let mut executable = >::from_text_bytes(prog, None, Config::default(), SyscallRegistry::default(), bpf_functions).unwrap(); /// let mut vm = EbpfVm::::new(executable.as_ref(), mem, &[]).unwrap(); /// /// // Provide a reference to the packet data. @@ -457,7 +466,7 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EbpfVm<'a, E, I> { /// # Examples /// /// ``` - /// use solana_rbpf::{ebpf, elf::register_bpf_function, vm::{Config, Executable, EbpfVm, DefaultInstructionMeter}, user_error::UserError}; + /// use solana_rbpf::{ebpf, elf::register_bpf_function, vm::{Config, Executable, EbpfVm, DefaultInstructionMeter, SyscallRegistry}, user_error::UserError}; /// /// let prog = &[ /// 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // exit @@ -466,7 +475,7 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EbpfVm<'a, E, I> { /// // Instantiate a VM. /// let mut bpf_functions = std::collections::BTreeMap::new(); /// register_bpf_function(&mut bpf_functions, 0, "entrypoint").unwrap(); - /// let mut executable = >::from_text_bytes(prog, bpf_functions, None, Config::default()).unwrap(); + /// let mut executable = >::from_text_bytes(prog, None, Config::default(), SyscallRegistry::default(), bpf_functions).unwrap(); /// let mut vm = EbpfVm::::new(executable.as_ref(), &mut [], &[]).unwrap(); /// ``` pub fn new( @@ -574,8 +583,7 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EbpfVm<'a, E, I> { /// // Instantiate an Executable and VM /// let mut bpf_functions = std::collections::BTreeMap::new(); /// register_bpf_function(&mut bpf_functions, 0, "entrypoint").unwrap(); - /// let mut executable = >::from_text_bytes(prog, bpf_functions, None, Config::default()).unwrap(); - /// executable.set_syscall_registry(syscall_registry); + /// let mut executable = >::from_text_bytes(prog, None, Config::default(), syscall_registry, bpf_functions).unwrap(); /// let mut vm = EbpfVm::::new(executable.as_ref(), &mut [], &[]).unwrap(); /// // Bind a context object instance to the previously registered syscall /// vm.bind_syscall_context_object(Box::new(BpfTracePrintf {}), None); @@ -627,7 +635,7 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EbpfVm<'a, E, I> { /// # Examples /// /// ``` - /// use solana_rbpf::{ebpf, elf::register_bpf_function, vm::{Config, Executable, EbpfVm, DefaultInstructionMeter}, user_error::UserError}; + /// use solana_rbpf::{ebpf, elf::register_bpf_function, vm::{Config, Executable, EbpfVm, DefaultInstructionMeter, SyscallRegistry}, user_error::UserError}; /// /// let prog = &[ /// 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // exit @@ -639,7 +647,7 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EbpfVm<'a, E, I> { /// // Instantiate a VM. /// let mut bpf_functions = std::collections::BTreeMap::new(); /// register_bpf_function(&mut bpf_functions, 0, "entrypoint").unwrap(); - /// let mut executable = >::from_text_bytes(prog, bpf_functions, None, Config::default()).unwrap(); + /// let mut executable = >::from_text_bytes(prog, None, Config::default(), SyscallRegistry::default(), bpf_functions).unwrap(); /// let mut vm = EbpfVm::::new(executable.as_ref(), mem, &[]).unwrap(); /// /// // Provide a reference to the packet data. diff --git a/test_utils/src/lib.rs b/test_utils/src/lib.rs index 6e82fae5..e76684b0 100644 --- a/test_utils/src/lib.rs +++ b/test_utils/src/lib.rs @@ -11,15 +11,7 @@ extern crate libc; extern crate solana_rbpf; -use self::libc::c_char; -use solana_rbpf::{ - error::EbpfError, - memory_region::{AccessType, MemoryMapping}, - question_mark, - user_error::UserError, - vm::{InstructionMeter, SyscallObject}, -}; -use std::{slice::from_raw_parts, str::from_utf8}; +use solana_rbpf::vm::InstructionMeter; pub struct TestInstructionMeter { pub remaining: u64, @@ -34,97 +26,6 @@ impl InstructionMeter for TestInstructionMeter { } } -pub type Result = std::result::Result>; - -pub struct BpfTracePrintf {} -impl SyscallObject for BpfTracePrintf { - fn call( - &mut self, - _arg1: u64, - _arg2: u64, - _arg3: u64, - _arg4: u64, - _arg5: u64, - _memory_mapping: &MemoryMapping, - result: &mut Result, - ) { - *result = Result::Ok(0); - } -} - -pub struct BpfSyscallString {} -impl SyscallObject for BpfSyscallString { - fn call( - &mut self, - vm_addr: u64, - len: u64, - _arg3: u64, - _arg4: u64, - _arg5: u64, - memory_mapping: &MemoryMapping, - result: &mut Result, - ) { - let host_addr = question_mark!(memory_mapping.map(AccessType::Load, vm_addr, len), result); - let c_buf: *const c_char = host_addr as *const c_char; - unsafe { - for i in 0..len { - let c = std::ptr::read(c_buf.offset(i as isize)); - if c == 0 { - break; - } - } - let message = from_utf8(from_raw_parts(host_addr as *const u8, len as usize)) - .unwrap_or("Invalid UTF-8 String"); - println!("log: {}", message); - } - *result = Result::Ok(0); - } -} - -pub struct BpfSyscallU64 {} -impl SyscallObject for BpfSyscallU64 { - fn call( - &mut self, - arg1: u64, - arg2: u64, - arg3: u64, - arg4: u64, - arg5: u64, - memory_mapping: &MemoryMapping, - result: &mut Result, - ) { - println!( - "dump_64: {:#x}, {:#x}, {:#x}, {:#x}, {:#x}, {:?}", - arg1, arg2, arg3, arg4, arg5, memory_mapping as *const _ - ); - *result = Result::Ok(0); - } -} - -pub struct SyscallWithContext { - pub context: u64, -} -impl SyscallObject for SyscallWithContext { - fn call( - &mut self, - arg1: u64, - arg2: u64, - arg3: u64, - arg4: u64, - arg5: u64, - memory_mapping: &MemoryMapping, - result: &mut Result, - ) { - println!( - "SyscallWithContext: {:?}, {:#x}, {:#x}, {:#x}, {:#x}, {:#x}, {:?}", - self as *const _, arg1, arg2, arg3, arg4, arg5, memory_mapping as *const _ - ); - assert_eq!(self.context, 42); - self.context = 84; - *result = Result::Ok(0); - } -} - // Assembly code and data for tcp_sack testcases. pub const PROG_TCP_PORT_80: &str = " diff --git a/tests/assembler.rs b/tests/assembler.rs index 3af28376..4f7e3728 100644 --- a/tests/assembler.rs +++ b/tests/assembler.rs @@ -7,11 +7,21 @@ extern crate solana_rbpf; extern crate test_utils; -use solana_rbpf::{assembler::assemble, ebpf, user_error::UserError, vm::Config}; +use solana_rbpf::{ + assembler::assemble, + ebpf, + user_error::UserError, + vm::{Config, SyscallRegistry}, +}; use test_utils::{TestInstructionMeter, TCP_SACK_ASM, TCP_SACK_BIN}; fn asm(src: &str) -> Result, String> { - let executable = assemble::(src, None, Config::default())?; + let executable = assemble::( + src, + None, + Config::default(), + SyscallRegistry::default(), + )?; let (_program_vm_addr, program) = executable.get_text_bytes().unwrap(); Ok((0..program.len() / ebpf::INSN_SIZE) .map(|insn_ptr| ebpf::get_insn(program, insn_ptr)) @@ -564,8 +574,13 @@ fn test_large_immediate() { #[test] fn test_tcp_sack() { - let executable = - assemble::(TCP_SACK_ASM, None, Config::default()).unwrap(); + let executable = assemble::( + TCP_SACK_ASM, + None, + Config::default(), + SyscallRegistry::default(), + ) + .unwrap(); let (_program_vm_addr, program) = executable.get_text_bytes().unwrap(); assert_eq!(program, TCP_SACK_BIN.to_vec()); } diff --git a/tests/disassembler.rs b/tests/disassembler.rs index fa11afb8..7bc90143 100644 --- a/tests/disassembler.rs +++ b/tests/disassembler.rs @@ -8,7 +8,10 @@ extern crate solana_rbpf; use solana_rbpf::{ - assembler::assemble, static_analysis::Analysis, user_error::UserError, vm::Config, + assembler::assemble, + static_analysis::Analysis, + user_error::UserError, + vm::{Config, SyscallRegistry}, }; use test_utils::TestInstructionMeter; @@ -16,8 +19,13 @@ use test_utils::TestInstructionMeter; macro_rules! disasm { ($src:expr) => {{ let src = $src; - let executable = - assemble::(src, None, Config::default()).unwrap(); + let executable = assemble::( + src, + None, + Config::default(), + SyscallRegistry::default(), + ) + .unwrap(); let analysis = Analysis::from_executable(executable.as_ref()); let mut reasm = Vec::new(); analysis.disassemble(&mut reasm).unwrap(); diff --git a/tests/misc.rs b/tests/misc.rs index c01c09c4..376c01e0 100644 --- a/tests/misc.rs +++ b/tests/misc.rs @@ -23,12 +23,12 @@ extern crate test_utils; use solana_rbpf::{ fuzz::fuzz, + syscalls::{BpfSyscallString, BpfSyscallU64}, user_error::UserError, verifier::check, vm::{Config, DefaultInstructionMeter, EbpfVm, Executable, SyscallObject, SyscallRegistry}, }; use std::{fs::File, io::Read}; -use test_utils::{BpfSyscallString, BpfSyscallU64}; // The following two examples have been compiled from C with the following command: // @@ -111,21 +111,19 @@ fn test_fuzz_execute() { 0..elf.len(), 0..255, |bytes: &mut [u8]| { - if let Ok(mut executable) = - >::from_elf( - bytes, - Some(check), - Config::default(), - ) - { - let mut syscall_registry = SyscallRegistry::default(); - syscall_registry - .register_syscall_by_name::(b"log", BpfSyscallString::call) - .unwrap(); - syscall_registry - .register_syscall_by_name::(b"log_64", BpfSyscallU64::call) - .unwrap(); - executable.set_syscall_registry(syscall_registry); + let mut syscall_registry = SyscallRegistry::default(); + syscall_registry + .register_syscall_by_name::(b"log", BpfSyscallString::call) + .unwrap(); + syscall_registry + .register_syscall_by_name::(b"log_64", BpfSyscallU64::call) + .unwrap(); + if let Ok(executable) = >::from_elf( + bytes, + Some(check), + Config::default(), + syscall_registry, + ) { let mut vm = EbpfVm::::new( executable.as_ref(), &mut [], diff --git a/tests/ubpf_execution.rs b/tests/ubpf_execution.rs index 2299641a..74d8a6ec 100644 --- a/tests/ubpf_execution.rs +++ b/tests/ubpf_execution.rs @@ -17,18 +17,17 @@ use solana_rbpf::{ elf::ElfError, error::EbpfError, memory_region::AccessType, - syscalls, + syscalls::{self, Result}, user_error::UserError, vm::{Config, EbpfVm, Executable, SyscallObject, SyscallRegistry}, }; use std::{fs::File, io::Read}; use test_utils::{ - BpfSyscallString, BpfSyscallU64, BpfTracePrintf, Result, SyscallWithContext, TestInstructionMeter, PROG_TCP_PORT_80, TCP_SACK_ASM, TCP_SACK_MATCH, TCP_SACK_NOMATCH, }; macro_rules! test_interpreter_and_jit { - (register, $executable:expr, $syscall_registry:expr, $location:expr => $syscall_function:expr; $syscall_context_object:expr) => { + (register, $syscall_registry:expr, $location:expr => $syscall_function:expr; $syscall_context_object:expr) => { $syscall_registry.register_syscall_by_name::($location, $syscall_function).unwrap(); }; (bind, $vm:expr, $location:expr => $syscall_function:expr; $syscall_context_object:expr) => { @@ -36,9 +35,6 @@ macro_rules! test_interpreter_and_jit { }; ($executable:expr, $mem:tt, ($($location:expr => $syscall_function:expr; $syscall_context_object:expr),* $(,)?), $check:block, $expected_instruction_count:expr) => { let check_closure = $check; - let mut syscall_registry = SyscallRegistry::default(); - $(test_interpreter_and_jit!(register, $executable, syscall_registry, $location => $syscall_function; $syscall_context_object);)* - $executable.set_syscall_registry(syscall_registry); let (instruction_count_interpreter, _tracer_interpreter) = { let mut mem = $mem; let mut vm = EbpfVm::new($executable.as_ref(), &mut mem, &[]).unwrap(); @@ -87,7 +83,9 @@ macro_rules! test_interpreter_and_jit_asm { enable_instruction_tracing: true, ..Config::default() }; - let mut executable = assemble($source, None, config).unwrap(); + let mut syscall_registry = SyscallRegistry::default(); + $(test_interpreter_and_jit!(register, syscall_registry, $location => $syscall_function; $syscall_context_object);)* + let mut executable = assemble($source, None, config, syscall_registry).unwrap(); test_interpreter_and_jit!(executable, $mem, ($($location => $syscall_function; $syscall_context_object),*), $check, $expected_instruction_count); } }; @@ -104,7 +102,9 @@ macro_rules! test_interpreter_and_jit_elf { enable_instruction_tracing: true, ..Config::default() }; - let mut executable = >::from_elf(&elf, None, config).unwrap(); + let mut syscall_registry = SyscallRegistry::default(); + $(test_interpreter_and_jit!(register, syscall_registry, $location => $syscall_function; $syscall_context_object);)* + let mut executable = >::from_elf(&elf, None, config, syscall_registry).unwrap(); test_interpreter_and_jit!(executable, $mem, ($($location => $syscall_function; $syscall_context_object),*), $check, $expected_instruction_count); } }; @@ -2278,7 +2278,7 @@ fn test_relative_call() { "tests/elfs/relative_call.so", [1], ( - b"log" => BpfSyscallString::call; BpfSyscallString {}, + b"log" => syscalls::BpfSyscallString::call; syscalls::BpfSyscallString {}, ), { |_vm, res: Result| { res.unwrap() == 2 } }, 14 @@ -2291,7 +2291,7 @@ fn test_bpf_to_bpf_scratch_registers() { "tests/elfs/scratch_registers.so", [1], ( - b"log_64" => BpfSyscallU64::call; BpfSyscallU64 {}, + b"log_64" => syscalls::BpfSyscallU64::call; syscalls::BpfSyscallU64 {}, ), { |_vm, res: Result| { res.unwrap() == 112 } }, 41 @@ -2321,7 +2321,7 @@ fn test_syscall_parameter_on_stack() { exit", [], ( - b"BpfSyscallString" => BpfSyscallString::call; BpfSyscallString {}, + b"BpfSyscallString" => syscalls::BpfSyscallString::call; syscalls::BpfSyscallString {}, ), { |_vm, res: Result| { res.unwrap() == 0 } }, 6 @@ -2495,7 +2495,7 @@ fn test_bpf_to_bpf_depth() { "tests/elfs/multiple_file.so", [i as u8], ( - b"log" => BpfSyscallString::call; BpfSyscallString {}, + b"log" => syscalls::BpfSyscallString::call; syscalls::BpfSyscallString {}, ), { |_vm, res: Result| { res.unwrap() == 0 } }, if i == 0 { 4 } else { 3 + 10 * i as u64 } @@ -2510,7 +2510,7 @@ fn test_err_bpf_to_bpf_too_deep() { "tests/elfs/multiple_file.so", [config.max_call_depth as u8], ( - b"log" => BpfSyscallString::call; BpfSyscallString {}, + b"log" => syscalls::BpfSyscallString::call; syscalls::BpfSyscallString {}, ), { |_vm, res: Result| { @@ -2584,7 +2584,7 @@ fn test_err_syscall_string() { exit", [72, 101, 108, 108, 111], ( - b"BpfSyscallString" => BpfSyscallString::call; BpfSyscallString {}, + b"BpfSyscallString" => syscalls::BpfSyscallString::call; syscalls::BpfSyscallString {}, ), { |_vm, res: Result| { @@ -2608,7 +2608,7 @@ fn test_syscall_string() { exit", [72, 101, 108, 108, 111], ( - b"BpfSyscallString" => BpfSyscallString::call; BpfSyscallString {}, + b"BpfSyscallString" => syscalls::BpfSyscallString::call; syscalls::BpfSyscallString {}, ), { |_vm, res: Result| { res.unwrap() == 0 } }, 4 @@ -2629,7 +2629,7 @@ fn test_syscall() { exit", [], ( - b"BpfSyscallU64" => BpfSyscallU64::call; BpfSyscallU64 {}, + b"BpfSyscallU64" => syscalls::BpfSyscallU64::call; syscalls::BpfSyscallU64 {}, ), { |_vm, res: Result| { res.unwrap() == 0 } }, 8 @@ -2692,10 +2692,10 @@ fn test_syscall_with_context() { exit", [], ( - b"SyscallWithContext" => SyscallWithContext::call; SyscallWithContext { context: 42 }, + b"SyscallWithContext" => syscalls::SyscallWithContext::call; syscalls::SyscallWithContext { context: 42 }, ), { |vm: &EbpfVm, res: Result| { - let syscall_context_object = unsafe { &*(vm.get_syscall_context_object(SyscallWithContext::call as usize).unwrap() as *const SyscallWithContext) }; + let syscall_context_object = unsafe { &*(vm.get_syscall_context_object(syscalls::SyscallWithContext::call as usize).unwrap() as *const syscalls::SyscallWithContext) }; assert_eq!(syscall_context_object.context, 84); res.unwrap() == 0 }}, @@ -2711,8 +2711,8 @@ fn test_load_elf() { "tests/elfs/noop.so", [], ( - b"log" => BpfSyscallString::call; BpfSyscallString {}, - b"log_64" => BpfSyscallU64::call; BpfSyscallU64 {}, + b"log" => syscalls::BpfSyscallString::call; syscalls::BpfSyscallString {}, + b"log_64" => syscalls::BpfSyscallU64::call; syscalls::BpfSyscallU64 {}, ), { |_vm, res: Result| { res.unwrap() == 0 } }, 11 @@ -2725,7 +2725,7 @@ fn test_load_elf_empty_noro() { "tests/elfs/noro.so", [], ( - b"log_64" => BpfSyscallU64::call; BpfSyscallU64 {}, + b"log_64" => syscalls::BpfSyscallU64::call; syscalls::BpfSyscallU64 {}, ), { |_vm, res: Result| { res.unwrap() == 0 } }, 8 @@ -2738,7 +2738,7 @@ fn test_load_elf_empty_rodata() { "tests/elfs/empty_rodata.so", [], ( - b"log_64" => BpfSyscallU64::call; BpfSyscallU64 {}, + b"log_64" => syscalls::BpfSyscallU64::call; syscalls::BpfSyscallU64 {}, ), { |_vm, res: Result| { res.unwrap() == 0 } }, 8 @@ -2751,15 +2751,27 @@ fn test_custom_entrypoint() { let mut elf = Vec::new(); file.read_to_end(&mut elf).unwrap(); elf[24] = 80; // Move entrypoint to later in the text section - let mut executable = - >::from_elf(&elf, None, Config::default()) - .unwrap(); + let config = Config { + enable_instruction_tracing: true, + ..Config::default() + }; + let mut syscall_registry = SyscallRegistry::default(); + test_interpreter_and_jit!(register, syscall_registry, b"log" => syscalls::BpfSyscallString::call; syscalls::BpfSyscallString {}); + test_interpreter_and_jit!(register, syscall_registry, b"log_64" => syscalls::BpfSyscallU64::call; syscalls::BpfSyscallU64 {}); + #[allow(unused_mut)] + let mut executable = >::from_elf( + &elf, + None, + config, + syscall_registry, + ) + .unwrap(); test_interpreter_and_jit!( executable, [], ( - b"log" => BpfSyscallString::call; BpfSyscallString {}, - b"log_64" => BpfSyscallU64::call; BpfSyscallU64 {}, + b"log" => syscalls::BpfSyscallString::call; syscalls::BpfSyscallString {}, + b"log_64" => syscalls::BpfSyscallU64::call; syscalls::BpfSyscallU64 {}, ), { |_vm, res: Result| { res.unwrap() == 0 } }, 2 @@ -2864,7 +2876,7 @@ fn test_instruction_count_syscall() { exit", [72, 101, 108, 108, 111], ( - b"BpfSyscallString" => BpfSyscallString::call; BpfSyscallString {}, + b"BpfSyscallString" => syscalls::BpfSyscallString::call; syscalls::BpfSyscallString {}, ), { |_vm, res: Result| { res.unwrap() == 0 } }, 4 @@ -2881,7 +2893,7 @@ fn test_err_instruction_count_syscall_capped() { exit", [72, 101, 108, 108, 111], ( - b"BpfSyscallString" => BpfSyscallString::call; BpfSyscallString {}, + b"BpfSyscallString" => syscalls::BpfSyscallString::call; syscalls::BpfSyscallString {}, ), { |_vm, res: Result| { @@ -2939,7 +2951,7 @@ fn test_err_non_terminate_capped() { exit", [], ( - b"BpfTracePrintf" => BpfTracePrintf::call; BpfTracePrintf {}, + b"BpfTracePrintf" => syscalls::BpfTracePrintf::call; syscalls::BpfTracePrintf {}, ), { |_vm, res: Result| { @@ -2969,7 +2981,7 @@ fn test_err_non_terminating_capped() { exit", [], ( - b"BpfTracePrintf" => BpfTracePrintf::call; BpfTracePrintf {}, + b"BpfTracePrintf" => syscalls::BpfTracePrintf::call; syscalls::BpfTracePrintf {}, ), { |_vm, res: Result| { @@ -2997,7 +3009,7 @@ fn test_symbol_relocation() { exit", [72, 101, 108, 108, 111], ( - b"BpfSyscallString" => BpfSyscallString::call; BpfSyscallString {}, + b"BpfSyscallString" => syscalls::BpfSyscallString::call; syscalls::BpfSyscallString {}, ), { |_vm, res: Result| { res.unwrap() == 0 } }, 6 @@ -3042,16 +3054,17 @@ fn test_err_call_unresolved() { #[test] fn test_err_unresolved_elf() { - test_interpreter_and_jit_elf!( - "tests/elfs/unresolved_syscall.so", - [], - ( - 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) - }, - 9 + let mut syscall_registry = SyscallRegistry::default(); + test_interpreter_and_jit!(register, syscall_registry, b"log" => syscalls::BpfSyscallString::call; syscalls::BpfSyscallString {}); + let mut file = File::open("tests/elfs/unresolved_syscall.so").unwrap(); + let mut elf = Vec::new(); + file.read_to_end(&mut elf).unwrap(); + let config = Config { + reject_unresolved_syscalls: true, + ..Default::default() + }; + assert!( + matches!(>::from_elf(&elf, None, config, syscall_registry), Err(EbpfError::ElfError(ElfError::UnresolvedSymbol(symbol, pc, offset))) if symbol == "log_64" && pc == 550 && offset == 4168) ); } @@ -3276,19 +3289,19 @@ fn execute_generated_program(prog: &[u8]) -> bool { register_bpf_function(&mut bpf_functions, 0, "entrypoint").unwrap(); let executable = >::from_text_bytes( prog, - bpf_functions, Some(check), Config { enable_instruction_tracing: true, ..Config::default() }, + SyscallRegistry::default(), + bpf_functions, ); let mut executable = if let Ok(executable) = executable { executable } else { return false; }; - executable.set_syscall_registry(SyscallRegistry::default()); if executable.jit_compile().is_err() { return false; } diff --git a/tests/ubpf_verifier.rs b/tests/ubpf_verifier.rs index 107348e1..6c0c9194 100644 --- a/tests/ubpf_verifier.rs +++ b/tests/ubpf_verifier.rs @@ -27,7 +27,7 @@ use solana_rbpf::{ error::UserDefinedError, user_error::UserError, verifier::{check, VerifierError}, - vm::{Config, DefaultInstructionMeter, EbpfVm, Executable}, + vm::{Config, DefaultInstructionMeter, EbpfVm, Executable, SyscallRegistry}, }; use std::collections::BTreeMap; use thiserror::Error; @@ -48,6 +48,7 @@ fn test_verifier_success() { exit", Some(|_prog: &[u8]| Ok(())), Config::default(), + SyscallRegistry::default(), ) .unwrap(); let _vm = EbpfVm::::new(executable.as_ref(), &mut [], &[]) @@ -66,6 +67,7 @@ fn test_verifier_fail() { exit", Some(verifier_fail), Config::default(), + SyscallRegistry::default(), ) .unwrap(); } @@ -80,6 +82,7 @@ fn test_verifier_err_div_by_zero_imm() { exit", Some(check), Config::default(), + SyscallRegistry::default(), ) .unwrap(); } @@ -94,9 +97,10 @@ fn test_verifier_err_endian_size() { ]; let _ = >::from_text_bytes( prog, - BTreeMap::new(), Some(check), Config::default(), + SyscallRegistry::default(), + BTreeMap::default(), ) .unwrap(); } @@ -111,9 +115,10 @@ fn test_verifier_err_incomplete_lddw() { ]; let _ = >::from_text_bytes( prog, - BTreeMap::new(), Some(check), Config::default(), + SyscallRegistry::default(), + BTreeMap::default(), ) .unwrap(); } @@ -127,6 +132,7 @@ fn test_verifier_err_invalid_reg_dst() { exit", Some(check), Config::default(), + SyscallRegistry::default(), ) .unwrap(); } @@ -140,6 +146,7 @@ fn test_verifier_err_invalid_reg_src() { exit", Some(check), Config::default(), + SyscallRegistry::default(), ) .unwrap(); } @@ -154,6 +161,7 @@ fn test_verifier_err_jmp_lddw() { exit", Some(check), Config::default(), + SyscallRegistry::default(), ) .unwrap(); } @@ -167,6 +175,7 @@ fn test_verifier_err_jmp_out() { exit", Some(check), Config::default(), + SyscallRegistry::default(), ) .unwrap(); } @@ -180,9 +189,10 @@ fn test_verifier_err_unknown_opcode() { ]; let _ = >::from_text_bytes( prog, - BTreeMap::new(), Some(check), Config::default(), + SyscallRegistry::default(), + BTreeMap::default(), ) .unwrap(); } @@ -196,6 +206,7 @@ fn test_verifier_err_write_r10() { exit", Some(check), Config::default(), + SyscallRegistry::default(), ) .unwrap(); }