From 193bba036c9809aa94e47e4c827e331065b80aa5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Mei=C3=9Fner?= Date: Thu, 28 Sep 2023 13:41:39 +0200 Subject: [PATCH] Moves SBPFVersion, FunctionRegistry, BuiltinFunction and BuiltinProgram into a new file --- benches/elf_loader.rs | 5 +- benches/jit_compile.rs | 4 +- benches/memory_mapping.rs | 2 +- benches/vm_execution.rs | 5 +- cli/src/main.rs | 5 +- examples/disassemble.rs | 5 +- examples/to_json.rs | 5 +- fuzz/fuzz_targets/dumb.rs | 10 +- fuzz/fuzz_targets/smart.rs | 19 +- fuzz/fuzz_targets/smart_jit_diff.rs | 19 +- fuzz/fuzz_targets/smarter_jit_diff.rs | 26 +- fuzz/fuzz_targets/verify_semantic_aware.rs | 10 +- src/assembler.rs | 7 +- src/disassembler.rs | 4 +- src/elf.rs | 214 +-------------- src/jit.rs | 4 +- src/lib.rs | 1 + src/memory_region.rs | 2 +- src/program.rs | 289 +++++++++++++++++++++ src/syscalls.rs | 8 +- src/verifier.rs | 2 +- src/vm.rs | 93 +------ tests/assembler.rs | 6 +- tests/disassembler.rs | 4 +- tests/execution.rs | 7 +- tests/verifier.rs | 5 +- 26 files changed, 406 insertions(+), 355 deletions(-) create mode 100644 src/program.rs diff --git a/benches/elf_loader.rs b/benches/elf_loader.rs index 0ff3c84f..a8d2b1b8 100644 --- a/benches/elf_loader.rs +++ b/benches/elf_loader.rs @@ -11,9 +11,10 @@ extern crate test; extern crate test_utils; use solana_rbpf::{ - elf::{Executable, FunctionRegistry}, + elf::Executable, + program::{BuiltinFunction, BuiltinProgram, FunctionRegistry}, syscalls, - vm::{BuiltinFunction, BuiltinProgram, Config, TestContextObject}, + vm::{Config, TestContextObject}, }; use std::{fs::File, io::Read, sync::Arc}; use test::Bencher; diff --git a/benches/jit_compile.rs b/benches/jit_compile.rs index f4e572fb..c232bc9a 100644 --- a/benches/jit_compile.rs +++ b/benches/jit_compile.rs @@ -10,9 +10,7 @@ extern crate solana_rbpf; extern crate test; use solana_rbpf::{ - elf::Executable, - verifier::RequisiteVerifier, - vm::{BuiltinProgram, TestContextObject}, + elf::Executable, program::BuiltinProgram, verifier::RequisiteVerifier, vm::TestContextObject, }; use std::{fs::File, io::Read, sync::Arc}; use test::Bencher; diff --git a/benches/memory_mapping.rs b/benches/memory_mapping.rs index 2facf301..799db71d 100644 --- a/benches/memory_mapping.rs +++ b/benches/memory_mapping.rs @@ -12,10 +12,10 @@ extern crate test; use rand::{rngs::SmallRng, Rng, SeedableRng}; use solana_rbpf::{ - elf::SBPFVersion, memory_region::{ AccessType, AlignedMemoryMapping, MemoryRegion, MemoryState, UnalignedMemoryMapping, }, + program::SBPFVersion, vm::Config, }; use test::Bencher; diff --git a/benches/vm_execution.rs b/benches/vm_execution.rs index 965a7ef6..be651e6f 100644 --- a/benches/vm_execution.rs +++ b/benches/vm_execution.rs @@ -11,10 +11,11 @@ extern crate test; use solana_rbpf::{ ebpf, - elf::{Executable, FunctionRegistry}, + elf::Executable, memory_region::MemoryRegion, + program::{BuiltinProgram, FunctionRegistry}, verifier::RequisiteVerifier, - vm::{BuiltinProgram, Config, TestContextObject}, + vm::{Config, TestContextObject}, }; use std::{fs::File, io::Read, sync::Arc}; use test::Bencher; diff --git a/cli/src/main.rs b/cli/src/main.rs index 2de9c782..905028a1 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -3,11 +3,12 @@ use solana_rbpf::{ aligned_memory::AlignedMemory, assembler::assemble, ebpf, - elf::{Executable, FunctionRegistry}, + elf::Executable, memory_region::{MemoryMapping, MemoryRegion}, + program::{BuiltinProgram, FunctionRegistry}, static_analysis::Analysis, verifier::RequisiteVerifier, - vm::{BuiltinProgram, Config, DynamicAnalysis, EbpfVm, TestContextObject}, + vm::{Config, DynamicAnalysis, EbpfVm, TestContextObject}, }; use std::{fs::File, io::Read, path::Path, sync::Arc}; diff --git a/examples/disassemble.rs b/examples/disassemble.rs index 4417af4a..454fec2e 100644 --- a/examples/disassemble.rs +++ b/examples/disassemble.rs @@ -6,9 +6,10 @@ extern crate solana_rbpf; use solana_rbpf::{ - elf::{Executable, FunctionRegistry, SBPFVersion}, + elf::Executable, + program::{BuiltinProgram, FunctionRegistry, SBPFVersion}, static_analysis::Analysis, - vm::{BuiltinProgram, TestContextObject}, + vm::TestContextObject, }; use std::sync::Arc; diff --git a/examples/to_json.rs b/examples/to_json.rs index 89e9ec9d..0e2950aa 100644 --- a/examples/to_json.rs +++ b/examples/to_json.rs @@ -12,9 +12,10 @@ use std::path::PathBuf; extern crate solana_rbpf; use solana_rbpf::{ - elf::{Executable, FunctionRegistry, SBPFVersion}, + elf::Executable, + program::{BuiltinProgram, FunctionRegistry, SBPFVersion}, static_analysis::Analysis, - vm::{BuiltinProgram, TestContextObject}, + vm::TestContextObject, }; use std::sync::Arc; // Turn a program into a JSON string. diff --git a/fuzz/fuzz_targets/dumb.rs b/fuzz/fuzz_targets/dumb.rs index b0dab4e9..e8b17c2b 100644 --- a/fuzz/fuzz_targets/dumb.rs +++ b/fuzz/fuzz_targets/dumb.rs @@ -6,10 +6,11 @@ use libfuzzer_sys::fuzz_target; use solana_rbpf::{ ebpf, - elf::{Executable, FunctionRegistry, SBPFVersion}, + elf::Executable, memory_region::MemoryRegion, + program::{BuiltinProgram, FunctionRegistry, SBPFVersion}, verifier::{RequisiteVerifier, Verifier}, - vm::{BuiltinProgram, TestContextObject}, + vm::TestContextObject, }; use test_utils::create_vm; @@ -35,7 +36,10 @@ fuzz_target!(|data: DumbFuzzData| { let mut mem = data.mem; let executable = Executable::::from_text_bytes( &prog, - std::sync::Arc::new(BuiltinProgram::new_loader(config, FunctionRegistry::default())), + std::sync::Arc::new(BuiltinProgram::new_loader( + config, + FunctionRegistry::default(), + )), SBPFVersion::V2, function_registry, ) diff --git a/fuzz/fuzz_targets/smart.rs b/fuzz/fuzz_targets/smart.rs index d1a91a98..8ccb873e 100644 --- a/fuzz/fuzz_targets/smart.rs +++ b/fuzz/fuzz_targets/smart.rs @@ -7,11 +7,12 @@ use libfuzzer_sys::fuzz_target; use grammar_aware::*; use solana_rbpf::{ ebpf, - elf::{Executable, FunctionRegistry, SBPFVersion}, + elf::Executable, insn_builder::{Arch, IntoBytes}, memory_region::MemoryRegion, + program::{BuiltinProgram, FunctionRegistry, SBPFVersion}, verifier::{RequisiteVerifier, Verifier}, - vm::{BuiltinProgram, TestContextObject}, + vm::TestContextObject, }; use test_utils::create_vm; @@ -32,14 +33,24 @@ fuzz_target!(|data: FuzzData| { let prog = make_program(&data.prog, data.arch); let config = data.template.into(); let function_registry = FunctionRegistry::default(); - if RequisiteVerifier::verify(prog.into_bytes(), &config, &SBPFVersion::V2, &function_registry).is_err() { + if RequisiteVerifier::verify( + prog.into_bytes(), + &config, + &SBPFVersion::V2, + &function_registry, + ) + .is_err() + { // verify please return; } let mut mem = data.mem; let executable = Executable::::from_text_bytes( prog.into_bytes(), - std::sync::Arc::new(BuiltinProgram::new_loader(config, FunctionRegistry::default())), + std::sync::Arc::new(BuiltinProgram::new_loader( + config, + FunctionRegistry::default(), + )), SBPFVersion::V2, function_registry, ) diff --git a/fuzz/fuzz_targets/smart_jit_diff.rs b/fuzz/fuzz_targets/smart_jit_diff.rs index a6fc1e79..8e282065 100644 --- a/fuzz/fuzz_targets/smart_jit_diff.rs +++ b/fuzz/fuzz_targets/smart_jit_diff.rs @@ -5,11 +5,12 @@ use libfuzzer_sys::fuzz_target; use grammar_aware::*; use solana_rbpf::{ ebpf, - elf::{Executable, FunctionRegistry, SBPFVersion}, + elf::Executable, insn_builder::{Arch, Instruction, IntoBytes}, memory_region::MemoryRegion, + program::{BuiltinProgram, FunctionRegistry, SBPFVersion}, verifier::{RequisiteVerifier, Verifier}, - vm::{BuiltinProgram, TestContextObject}, + vm::TestContextObject, }; use test_utils::create_vm; @@ -39,7 +40,14 @@ fuzz_target!(|data: FuzzData| { .push(); let config = data.template.into(); let function_registry = FunctionRegistry::default(); - if RequisiteVerifier::verify(prog.into_bytes(), &config, &SBPFVersion::V2, &function_registry).is_err() { + if RequisiteVerifier::verify( + prog.into_bytes(), + &config, + &SBPFVersion::V2, + &function_registry, + ) + .is_err() + { // verify please return; } @@ -47,7 +55,10 @@ fuzz_target!(|data: FuzzData| { let mut jit_mem = data.mem; let mut executable = Executable::::from_text_bytes( prog.into_bytes(), - std::sync::Arc::new(BuiltinProgram::new_loader(config, FunctionRegistry::default())), + std::sync::Arc::new(BuiltinProgram::new_loader( + config, + FunctionRegistry::default(), + )), SBPFVersion::V2, function_registry, ) diff --git a/fuzz/fuzz_targets/smarter_jit_diff.rs b/fuzz/fuzz_targets/smarter_jit_diff.rs index 50c906a8..9f31e2c7 100644 --- a/fuzz/fuzz_targets/smarter_jit_diff.rs +++ b/fuzz/fuzz_targets/smarter_jit_diff.rs @@ -5,14 +5,13 @@ use libfuzzer_sys::fuzz_target; use semantic_aware::*; use solana_rbpf::{ ebpf, - elf::{Executable, FunctionRegistry, SBPFVersion}, + elf::Executable, insn_builder::IntoBytes, memory_region::MemoryRegion, + program::{BuiltinProgram, FunctionRegistry, SBPFVersion}, static_analysis::Analysis, verifier::{RequisiteVerifier, Verifier}, - vm::{ - BuiltinProgram, ContextObject, TestContextObject, - }, + vm::{ContextObject, TestContextObject}, }; use test_utils::create_vm; @@ -38,7 +37,14 @@ fuzz_target!(|data: FuzzData| { let prog = make_program(&data.prog); let config = data.template.into(); let function_registry = FunctionRegistry::default(); - if RequisiteVerifier::verify(prog.into_bytes(), &config, &SBPFVersion::V2, &function_registry).is_err() { + if RequisiteVerifier::verify( + prog.into_bytes(), + &config, + &SBPFVersion::V2, + &function_registry, + ) + .is_err() + { // verify please return; } @@ -46,7 +52,10 @@ fuzz_target!(|data: FuzzData| { let mut jit_mem = data.mem; let mut executable = Executable::::from_text_bytes( prog.into_bytes(), - std::sync::Arc::new(BuiltinProgram::new_loader(config, FunctionRegistry::default())), + std::sync::Arc::new(BuiltinProgram::new_loader( + config, + FunctionRegistry::default(), + )), SBPFVersion::V2, function_registry, ) @@ -82,8 +91,9 @@ fuzz_target!(|data: FuzzData| { let jit_res_str = format!("{:?}", jit_res); if interp_res_str != jit_res_str { // spot check: there's a meaningless bug where ExceededMaxInstructions is different due to jump calculations - if interp_res_str.contains("ExceededMaxInstructions") && - jit_res_str.contains("ExceededMaxInstructions") { + if interp_res_str.contains("ExceededMaxInstructions") + && jit_res_str.contains("ExceededMaxInstructions") + { return; } eprintln!("{:#?}", &data.prog); diff --git a/fuzz/fuzz_targets/verify_semantic_aware.rs b/fuzz/fuzz_targets/verify_semantic_aware.rs index 01308cea..c1e4e171 100644 --- a/fuzz/fuzz_targets/verify_semantic_aware.rs +++ b/fuzz/fuzz_targets/verify_semantic_aware.rs @@ -4,8 +4,8 @@ use libfuzzer_sys::fuzz_target; use semantic_aware::*; use solana_rbpf::{ - elf::{FunctionRegistry, SBPFVersion}, insn_builder::IntoBytes, + program::{FunctionRegistry, SBPFVersion}, verifier::{RequisiteVerifier, Verifier}, }; @@ -24,5 +24,11 @@ fuzz_target!(|data: FuzzData| { let prog = make_program(&data.prog); let config = data.template.into(); let function_registry = FunctionRegistry::default(); - RequisiteVerifier::verify(prog.into_bytes(), &config, &SBPFVersion::V2, &function_registry).unwrap(); + RequisiteVerifier::verify( + prog.into_bytes(), + &config, + &SBPFVersion::V2, + &function_registry, + ) + .unwrap(); }); diff --git a/src/assembler.rs b/src/assembler.rs index 764793d5..97e6721e 100644 --- a/src/assembler.rs +++ b/src/assembler.rs @@ -18,8 +18,9 @@ use crate::{ Statement, }, ebpf::{self, Insn}, - elf::{Executable, FunctionRegistry, SBPFVersion}, - vm::{BuiltinProgram, ContextObject}, + elf::Executable, + program::{BuiltinProgram, FunctionRegistry, SBPFVersion}, + vm::ContextObject, }; use std::{collections::HashMap, sync::Arc}; @@ -258,7 +259,7 @@ fn insn(opc: u8, dst: i64, src: i64, off: i64, imm: i64) -> Result /// # Examples /// /// ``` -/// use solana_rbpf::{assembler::assemble, vm::{Config, TestContextObject, BuiltinProgram}}; +/// use solana_rbpf::{assembler::assemble, program::BuiltinProgram, vm::{Config, TestContextObject}}; /// let executable = assemble::( /// "add64 r1, 0x605 /// mov64 r2, 0x32 diff --git a/src/disassembler.rs b/src/disassembler.rs index ad193770..97fabd04 100644 --- a/src/disassembler.rs +++ b/src/disassembler.rs @@ -10,9 +10,9 @@ use crate::{ ebpf, - elf::{FunctionRegistry, SBPFVersion}, + program::{BuiltinProgram, FunctionRegistry, SBPFVersion}, static_analysis::CfgNode, - vm::{BuiltinProgram, ContextObject}, + vm::ContextObject, }; use std::collections::BTreeMap; diff --git a/src/elf.rs b/src/elf.rs index d9160a96..16a8b3b9 100644 --- a/src/elf.rs +++ b/src/elf.rs @@ -19,21 +19,15 @@ use crate::{ }, error::EbpfError, memory_region::MemoryRegion, + program::{BuiltinProgram, FunctionRegistry, SBPFVersion}, verifier::Verifier, - vm::{BuiltinProgram, Config, ContextObject}, + vm::{Config, ContextObject}, }; #[cfg(all(feature = "jit", not(target_os = "windows"), target_arch = "x86_64"))] use crate::jit::{JitCompiler, JitProgram}; use byteorder::{ByteOrder, LittleEndian}; -use std::{ - collections::{btree_map::Entry, BTreeMap}, - fmt::Debug, - mem, - ops::Range, - str, - sync::Arc, -}; +use std::{collections::BTreeMap, fmt::Debug, mem, ops::Range, str, sync::Arc}; /// Error definitions #[derive(Debug, thiserror::Error, PartialEq, Eq)] @@ -239,205 +233,6 @@ pub(crate) enum Section { Borrowed(usize, Range), } -/// Defines a set of sbpf_version of an executable -#[derive(Debug, PartialEq, Eq, Clone)] -pub enum SBPFVersion { - /// The legacy format - V1, - /// The current format - V2, - /// The future format with BTF support - V3, -} - -impl SBPFVersion { - /// Enable the little-endian byte swap instructions - pub fn enable_le(&self) -> bool { - self == &SBPFVersion::V1 - } - - /// Enable the negation instruction - pub fn enable_neg(&self) -> bool { - self == &SBPFVersion::V1 - } - - /// Swaps the reg and imm operands of the subtraction instruction - pub fn swap_sub_reg_imm_operands(&self) -> bool { - self != &SBPFVersion::V1 - } - - /// Disable the only two slots long instruction: LD_DW_IMM - pub fn disable_lddw(&self) -> bool { - self != &SBPFVersion::V1 - } - - /// Enable the BPF_PQR instruction class - pub fn enable_pqr(&self) -> bool { - self != &SBPFVersion::V1 - } - - /// Use src reg instead of imm in callx - pub fn callx_uses_src_reg(&self) -> bool { - self != &SBPFVersion::V1 - } - - /// Ensure that rodata sections don't exceed their maximum allowed size and - /// overlap with the stack - pub fn reject_rodata_stack_overlap(&self) -> bool { - self != &SBPFVersion::V1 - } - - /// Allow sh_addr != sh_offset in elf sections. Used in V2 to align - /// section vaddrs to MM_PROGRAM_START. - pub fn enable_elf_vaddr(&self) -> bool { - self != &SBPFVersion::V1 - } - - /// Use dynamic stack frame sizes - pub fn dynamic_stack_frames(&self) -> bool { - self != &SBPFVersion::V1 - } - - /// Support syscalls via pseudo calls (insn.src = 0) - pub fn static_syscalls(&self) -> bool { - self != &SBPFVersion::V1 - } -} - -/// Holds the function symbols of an Executable -#[derive(Debug, PartialEq, Eq)] -pub struct FunctionRegistry { - pub(crate) map: BTreeMap, T)>, -} - -impl Default for FunctionRegistry { - fn default() -> Self { - Self { - map: BTreeMap::new(), - } - } -} - -impl FunctionRegistry { - /// Register a symbol with an explicit key - pub fn register_function( - &mut self, - key: u32, - name: impl Into>, - value: T, - ) -> Result<(), ElfError> { - match self.map.entry(key) { - Entry::Vacant(entry) => { - entry.insert((name.into(), value)); - } - Entry::Occupied(entry) => { - if entry.get().1 != value { - return Err(ElfError::SymbolHashCollision(key)); - } - } - } - Ok(()) - } - - /// Register a symbol with an implicit key - pub fn register_function_hashed( - &mut self, - name: impl Into>, - value: T, - ) -> Result { - let name = name.into(); - let key = ebpf::hash_symbol_name(name.as_slice()); - self.register_function(key, name, value)?; - Ok(key) - } - - /// Used for transitioning from SBPFv1 to SBPFv2 - fn register_function_hashed_legacy( - &mut self, - loader: &BuiltinProgram, - hash_symbol_name: bool, - name: impl Into>, - value: T, - ) -> Result - where - usize: From, - { - let name = name.into(); - let config = loader.get_config(); - let key = if hash_symbol_name { - let hash = if name == b"entrypoint" { - ebpf::hash_symbol_name(b"entrypoint") - } else { - ebpf::hash_symbol_name(&usize::from(value).to_le_bytes()) - }; - if config.external_internal_function_hash_collision - && loader.get_function_registry().lookup_by_key(hash).is_some() - { - return Err(ElfError::SymbolHashCollision(hash)); - } - hash - } else { - usize::from(value) as u32 - }; - self.register_function( - key, - if config.enable_symbol_and_section_labels || name == b"entrypoint" { - name - } else { - Vec::default() - }, - value, - )?; - Ok(key) - } - - /// Unregister a symbol again - pub fn unregister_function(&mut self, key: u32) { - self.map.remove(&key); - } - - /// Iterate over all keys - pub fn keys(&self) -> impl Iterator + '_ { - self.map.keys().cloned() - } - - /// Iterate over all entries - pub fn iter(&self) -> impl Iterator + '_ { - self.map - .iter() - .map(|(key, (name, value))| (*key, (name.as_slice(), *value))) - } - - /// Get a function by its key - pub fn lookup_by_key(&self, key: u32) -> Option<(&[u8], T)> { - // String::from_utf8_lossy(function_name).as_str() - self.map - .get(&key) - .map(|(function_name, value)| (function_name.as_slice(), *value)) - } - - /// Get a function by its name - pub fn lookup_by_name(&self, name: &[u8]) -> Option<(&[u8], T)> { - self.map - .values() - .find(|(function_name, _value)| function_name == name) - .map(|(function_name, value)| (function_name.as_slice(), *value)) - } - - /// Calculate memory size - pub fn mem_size(&self) -> usize { - mem::size_of::().saturating_add(self.map.iter().fold( - 0, - |state: usize, (_, (name, value))| { - state.saturating_add( - mem::size_of_val(value) - .saturating_add(mem::size_of_val(name).saturating_add(name.capacity())), - ) - }, - )) - } -} - /// Elf loader/relocator #[derive(Debug, PartialEq)] pub struct Executable { @@ -1407,8 +1202,9 @@ mod test { types::{Elf64Ehdr, Elf64Shdr}, }, fuzz::fuzz, + program::BuiltinFunction, syscalls, - vm::{BuiltinFunction, ProgramResult, TestContextObject}, + vm::{ProgramResult, TestContextObject}, }; use rand::{distributions::Uniform, Rng}; use std::{fs::File, io::Read}; diff --git a/src/jit.rs b/src/jit.rs index fc19038f..21bb3894 100644 --- a/src/jit.rs +++ b/src/jit.rs @@ -1636,9 +1636,9 @@ impl<'a, C: ContextObject> JitCompiler<'a, C> { mod tests { use super::*; use crate::{ - elf::{FunctionRegistry, SBPFVersion}, + program::{BuiltinFunction, BuiltinProgram, FunctionRegistry, SBPFVersion}, syscalls, - vm::{BuiltinFunction, BuiltinProgram, TestContextObject}, + vm::TestContextObject, }; use byteorder::{ByteOrder, LittleEndian}; use std::sync::Arc; diff --git a/src/lib.rs b/src/lib.rs index 1e90d310..291ed012 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,6 +42,7 @@ mod jit; #[cfg(feature = "jit")] mod memory_management; pub mod memory_region; +pub mod program; pub mod static_analysis; pub mod syscalls; pub mod verifier; diff --git a/src/memory_region.rs b/src/memory_region.rs index 0f493385..17c311de 100644 --- a/src/memory_region.rs +++ b/src/memory_region.rs @@ -3,8 +3,8 @@ use crate::{ aligned_memory::Pod, ebpf, - elf::SBPFVersion, error::EbpfError, + program::SBPFVersion, vm::{Config, ProgramResult}, }; use std::{ diff --git a/src/program.rs b/src/program.rs new file mode 100644 index 00000000..866ef887 --- /dev/null +++ b/src/program.rs @@ -0,0 +1,289 @@ +//! Common interface for built-in and user supplied programs +use { + crate::{ + ebpf, + elf::ElfError, + memory_region::MemoryMapping, + vm::{Config, ContextObject, ProgramResult}, + }, + std::collections::{btree_map::Entry, BTreeMap}, +}; + +/// Defines a set of sbpf_version of an executable +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum SBPFVersion { + /// The legacy format + V1, + /// The current format + V2, + /// The future format with BTF support + V3, +} + +impl SBPFVersion { + /// Enable the little-endian byte swap instructions + pub fn enable_le(&self) -> bool { + self == &SBPFVersion::V1 + } + + /// Enable the negation instruction + pub fn enable_neg(&self) -> bool { + self == &SBPFVersion::V1 + } + + /// Swaps the reg and imm operands of the subtraction instruction + pub fn swap_sub_reg_imm_operands(&self) -> bool { + self != &SBPFVersion::V1 + } + + /// Disable the only two slots long instruction: LD_DW_IMM + pub fn disable_lddw(&self) -> bool { + self != &SBPFVersion::V1 + } + + /// Enable the BPF_PQR instruction class + pub fn enable_pqr(&self) -> bool { + self != &SBPFVersion::V1 + } + + /// Use src reg instead of imm in callx + pub fn callx_uses_src_reg(&self) -> bool { + self != &SBPFVersion::V1 + } + + /// Ensure that rodata sections don't exceed their maximum allowed size and + /// overlap with the stack + pub fn reject_rodata_stack_overlap(&self) -> bool { + self != &SBPFVersion::V1 + } + + /// Allow sh_addr != sh_offset in elf sections. Used in V2 to align + /// section vaddrs to MM_PROGRAM_START. + pub fn enable_elf_vaddr(&self) -> bool { + self != &SBPFVersion::V1 + } + + /// Use dynamic stack frame sizes + pub fn dynamic_stack_frames(&self) -> bool { + self != &SBPFVersion::V1 + } + + /// Support syscalls via pseudo calls (insn.src = 0) + pub fn static_syscalls(&self) -> bool { + self != &SBPFVersion::V1 + } +} + +/// Holds the function symbols of an Executable +#[derive(Debug, PartialEq, Eq)] +pub struct FunctionRegistry { + pub(crate) map: BTreeMap, T)>, +} + +impl Default for FunctionRegistry { + fn default() -> Self { + Self { + map: BTreeMap::new(), + } + } +} + +impl FunctionRegistry { + /// Register a symbol with an explicit key + pub fn register_function( + &mut self, + key: u32, + name: impl Into>, + value: T, + ) -> Result<(), ElfError> { + match self.map.entry(key) { + Entry::Vacant(entry) => { + entry.insert((name.into(), value)); + } + Entry::Occupied(entry) => { + if entry.get().1 != value { + return Err(ElfError::SymbolHashCollision(key)); + } + } + } + Ok(()) + } + + /// Register a symbol with an implicit key + pub fn register_function_hashed( + &mut self, + name: impl Into>, + value: T, + ) -> Result { + let name = name.into(); + let key = ebpf::hash_symbol_name(name.as_slice()); + self.register_function(key, name, value)?; + Ok(key) + } + + /// Used for transitioning from SBPFv1 to SBPFv2 + pub(crate) fn register_function_hashed_legacy( + &mut self, + loader: &BuiltinProgram, + hash_symbol_name: bool, + name: impl Into>, + value: T, + ) -> Result + where + usize: From, + { + let name = name.into(); + let config = loader.get_config(); + let key = if hash_symbol_name { + let hash = if name == b"entrypoint" { + ebpf::hash_symbol_name(b"entrypoint") + } else { + ebpf::hash_symbol_name(&usize::from(value).to_le_bytes()) + }; + if config.external_internal_function_hash_collision + && loader.get_function_registry().lookup_by_key(hash).is_some() + { + return Err(ElfError::SymbolHashCollision(hash)); + } + hash + } else { + usize::from(value) as u32 + }; + self.register_function( + key, + if config.enable_symbol_and_section_labels || name == b"entrypoint" { + name + } else { + Vec::default() + }, + value, + )?; + Ok(key) + } + + /// Unregister a symbol again + pub fn unregister_function(&mut self, key: u32) { + self.map.remove(&key); + } + + /// Iterate over all keys + pub fn keys(&self) -> impl Iterator + '_ { + self.map.keys().cloned() + } + + /// Iterate over all entries + pub fn iter(&self) -> impl Iterator + '_ { + self.map + .iter() + .map(|(key, (name, value))| (*key, (name.as_slice(), *value))) + } + + /// Get a function by its key + pub fn lookup_by_key(&self, key: u32) -> Option<(&[u8], T)> { + // String::from_utf8_lossy(function_name).as_str() + self.map + .get(&key) + .map(|(function_name, value)| (function_name.as_slice(), *value)) + } + + /// Get a function by its name + pub fn lookup_by_name(&self, name: &[u8]) -> Option<(&[u8], T)> { + self.map + .values() + .find(|(function_name, _value)| function_name == name) + .map(|(function_name, value)| (function_name.as_slice(), *value)) + } + + /// Calculate memory size + pub fn mem_size(&self) -> usize { + std::mem::size_of::().saturating_add(self.map.iter().fold( + 0, + |state: usize, (_, (name, value))| { + state.saturating_add( + std::mem::size_of_val(value).saturating_add( + std::mem::size_of_val(name).saturating_add(name.capacity()), + ), + ) + }, + )) + } +} + +/// Syscall function without context +pub type BuiltinFunction = + fn(&mut C, u64, u64, u64, u64, u64, &mut MemoryMapping, &mut ProgramResult); + +/// Represents the interface to a fixed functionality program +#[derive(Eq)] +pub struct BuiltinProgram { + /// Holds the Config if this is a loader program + config: Option>, + /// Function pointers by symbol + functions: FunctionRegistry>, +} + +impl PartialEq for BuiltinProgram { + fn eq(&self, other: &Self) -> bool { + self.config.eq(&other.config) && self.functions.eq(&other.functions) + } +} + +impl BuiltinProgram { + /// Constructs a loader built-in program + pub fn new_loader(config: Config, functions: FunctionRegistry>) -> Self { + Self { + config: Some(Box::new(config)), + functions, + } + } + + /// Constructs a built-in program + pub fn new_builtin(functions: FunctionRegistry>) -> Self { + Self { + config: None, + functions, + } + } + + /// Constructs a mock loader built-in program + pub fn new_mock() -> Self { + Self { + config: Some(Box::default()), + functions: FunctionRegistry::default(), + } + } + + /// Get the configuration settings assuming this is a loader program + pub fn get_config(&self) -> &Config { + self.config.as_ref().unwrap() + } + + /// Get the function registry + pub fn get_function_registry(&self) -> &FunctionRegistry> { + &self.functions + } + + /// Calculate memory size + pub fn mem_size(&self) -> usize { + std::mem::size_of::() + .saturating_add(if self.config.is_some() { + std::mem::size_of::() + } else { + 0 + }) + .saturating_add(self.functions.mem_size()) + } +} + +impl std::fmt::Debug for BuiltinProgram { + fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { + writeln!(f, "{:?}", unsafe { + // `derive(Debug)` does not know that `C: ContextObject` does not need to implement `Debug` + std::mem::transmute::< + &FunctionRegistry>, + &FunctionRegistry>, + >(&self.functions) + })?; + Ok(()) + } +} diff --git a/src/syscalls.rs b/src/syscalls.rs index 8359f355..53aa35a0 100644 --- a/src/syscalls.rs +++ b/src/syscalls.rs @@ -56,7 +56,7 @@ pub const BPF_TRACE_PRINTK_IDX: u32 = 6; /// # Examples /// /// ``` -/// use solana_rbpf::{elf::SBPFVersion, memory_region::{MemoryRegion, MemoryMapping}, syscalls::bpf_trace_printf, vm::{Config, ProgramResult, TestContextObject}}; +/// use solana_rbpf::{program::SBPFVersion, memory_region::{MemoryRegion, MemoryMapping}, syscalls::bpf_trace_printf, vm::{Config, ProgramResult, TestContextObject}}; /// /// let mut result = ProgramResult::Ok(0); /// let config = Config::default(); @@ -119,7 +119,7 @@ pub fn bpf_trace_printf( /// # Examples /// /// ``` -/// use solana_rbpf::{elf::SBPFVersion, memory_region::{MemoryRegion, MemoryMapping}, syscalls::bpf_gather_bytes, vm::{Config, ProgramResult, TestContextObject}}; +/// use solana_rbpf::{program::SBPFVersion, memory_region::{MemoryRegion, MemoryMapping}, syscalls::bpf_gather_bytes, vm::{Config, ProgramResult, TestContextObject}}; /// /// let mut result = ProgramResult::Ok(0); /// let config = Config::default(); @@ -153,7 +153,7 @@ pub fn bpf_gather_bytes( /// # Examples /// /// ``` -/// use solana_rbpf::{elf::SBPFVersion, memory_region::{MemoryRegion, MemoryMapping}, syscalls::bpf_mem_frob, vm::{Config, ProgramResult, TestContextObject}}; +/// use solana_rbpf::{program::SBPFVersion, memory_region::{MemoryRegion, MemoryMapping}, syscalls::bpf_mem_frob, vm::{Config, ProgramResult, TestContextObject}}; /// /// let mut val = &mut [0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x22, 0x33]; /// let val_va = 0x100000000; @@ -194,7 +194,7 @@ pub fn bpf_mem_frob( /// # Examples /// /// ``` -/// use solana_rbpf::{elf::SBPFVersion, memory_region::{MemoryRegion, MemoryMapping}, syscalls::bpf_str_cmp, vm::{Config, ProgramResult, TestContextObject}}; +/// use solana_rbpf::{program::SBPFVersion, memory_region::{MemoryRegion, MemoryMapping}, syscalls::bpf_str_cmp, vm::{Config, ProgramResult, TestContextObject}}; /// /// let foo = "This is a string."; /// let bar = "This is another sting."; diff --git a/src/verifier.rs b/src/verifier.rs index b6502582..a3a14a81 100644 --- a/src/verifier.rs +++ b/src/verifier.rs @@ -25,7 +25,7 @@ use crate::{ ebpf, - elf::{FunctionRegistry, SBPFVersion}, + program::{FunctionRegistry, SBPFVersion}, vm::Config, }; use thiserror::Error; diff --git a/src/vm.rs b/src/vm.rs index 8fe6e1a6..cf30f1dc 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -14,13 +14,14 @@ use crate::{ ebpf, - elf::{Executable, FunctionRegistry, SBPFVersion}, + elf::Executable, error::EbpfError, interpreter::Interpreter, memory_region::MemoryMapping, + program::{BuiltinProgram, FunctionRegistry, SBPFVersion}, static_analysis::{Analysis, TraceLogEntry}, }; -use std::{collections::BTreeMap, fmt::Debug, mem, sync::Arc}; +use std::{collections::BTreeMap, fmt::Debug, sync::Arc}; /// Same as `Result` but provides a stable memory layout #[derive(Debug)] @@ -99,85 +100,6 @@ impl From> for StableResult { /// Return value of programs and syscalls pub type ProgramResult = StableResult>; -/// Syscall function without context -pub type BuiltinFunction = - fn(&mut C, u64, u64, u64, u64, u64, &mut MemoryMapping, &mut ProgramResult); - -/// Represents the interface to a fixed functionality program -#[derive(Eq)] -pub struct BuiltinProgram { - /// Holds the Config if this is a loader program - config: Option>, - /// Function pointers by symbol - functions: FunctionRegistry>, -} - -impl PartialEq for BuiltinProgram { - fn eq(&self, other: &Self) -> bool { - self.config.eq(&other.config) && self.functions.eq(&other.functions) - } -} - -impl BuiltinProgram { - /// Constructs a loader built-in program - pub fn new_loader(config: Config, functions: FunctionRegistry>) -> Self { - Self { - config: Some(Box::new(config)), - functions, - } - } - - /// Constructs a built-in program - pub fn new_builtin(functions: FunctionRegistry>) -> Self { - Self { - config: None, - functions, - } - } - - /// Constructs a mock loader built-in program - pub fn new_mock() -> Self { - Self { - config: Some(Box::default()), - functions: FunctionRegistry::default(), - } - } - - /// Get the configuration settings assuming this is a loader program - pub fn get_config(&self) -> &Config { - self.config.as_ref().unwrap() - } - - /// Get the function registry - pub fn get_function_registry(&self) -> &FunctionRegistry> { - &self.functions - } - - /// Calculate memory size - pub fn mem_size(&self) -> usize { - mem::size_of::() - + if self.config.is_some() { - mem::size_of::() - } else { - 0 - } - + self.functions.mem_size() - } -} - -impl Debug for BuiltinProgram { - fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { - writeln!(f, "{:?}", unsafe { - // `derive(Debug)` does not know that `C: ContextObject` does not need to implement `Debug` - std::mem::transmute::< - &FunctionRegistry>, - &FunctionRegistry>, - >(&self.functions) - })?; - Ok(()) - } -} - /// VM configuration settings #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Config { @@ -378,10 +300,11 @@ pub struct CallFrame { /// use solana_rbpf::{ /// aligned_memory::AlignedMemory, /// ebpf, -/// elf::{Executable, FunctionRegistry, SBPFVersion}, +/// elf::Executable, /// memory_region::{MemoryMapping, MemoryRegion}, -/// verifier::{RequisiteVerifier}, -/// vm::{BuiltinProgram, Config, EbpfVm, TestContextObject}, +/// program::{BuiltinProgram, FunctionRegistry, SBPFVersion}, +/// verifier::RequisiteVerifier, +/// vm::{Config, EbpfVm, TestContextObject}, /// }; /// /// let prog = &[ @@ -562,7 +485,7 @@ impl<'a, C: ContextObject> EbpfVm<'a, C> { #[cfg(test)] mod tests { use super::*; - use crate::syscalls; + use crate::{program::BuiltinFunction, syscalls}; #[test] fn test_program_result_is_stable() { diff --git a/tests/assembler.rs b/tests/assembler.rs index 9b9b5a9e..86f92921 100644 --- a/tests/assembler.rs +++ b/tests/assembler.rs @@ -8,11 +8,7 @@ extern crate solana_rbpf; extern crate test_utils; -use solana_rbpf::{ - assembler::assemble, - ebpf, - vm::{BuiltinProgram, TestContextObject}, -}; +use solana_rbpf::{assembler::assemble, ebpf, program::BuiltinProgram, vm::TestContextObject}; use std::sync::Arc; use test_utils::{TCP_SACK_ASM, TCP_SACK_BIN}; diff --git a/tests/disassembler.rs b/tests/disassembler.rs index 987e735d..a3080b31 100644 --- a/tests/disassembler.rs +++ b/tests/disassembler.rs @@ -9,9 +9,9 @@ extern crate solana_rbpf; use solana_rbpf::{ assembler::assemble, - elf::FunctionRegistry, + program::{BuiltinProgram, FunctionRegistry}, static_analysis::Analysis, - vm::{BuiltinProgram, Config, TestContextObject}, + vm::{Config, TestContextObject}, }; use std::sync::Arc; diff --git a/tests/execution.rs b/tests/execution.rs index 27444f21..bb9ff267 100644 --- a/tests/execution.rs +++ b/tests/execution.rs @@ -18,15 +18,14 @@ use rand::{rngs::SmallRng, RngCore, SeedableRng}; use solana_rbpf::{ assembler::assemble, ebpf, - elf::{Executable, FunctionRegistry, SBPFVersion}, + elf::Executable, error::EbpfError, memory_region::{AccessType, MemoryMapping, MemoryRegion}, + program::{BuiltinFunction, BuiltinProgram, FunctionRegistry, SBPFVersion}, static_analysis::Analysis, syscalls, verifier::RequisiteVerifier, - vm::{ - BuiltinFunction, BuiltinProgram, Config, ContextObject, ProgramResult, TestContextObject, - }, + vm::{Config, ContextObject, ProgramResult, TestContextObject}, }; use std::{fs::File, io::Read, sync::Arc}; use test_utils::{ diff --git a/tests/verifier.rs b/tests/verifier.rs index 856426e9..ff56b6d3 100644 --- a/tests/verifier.rs +++ b/tests/verifier.rs @@ -25,9 +25,10 @@ extern crate thiserror; use solana_rbpf::{ assembler::assemble, ebpf, - elf::{Executable, FunctionRegistry, SBPFVersion}, + elf::Executable, + program::{BuiltinProgram, FunctionRegistry, SBPFVersion}, verifier::{RequisiteVerifier, Verifier, VerifierError}, - vm::{BuiltinProgram, Config, TestContextObject}, + vm::{Config, TestContextObject}, }; use std::sync::Arc; use test_utils::{assert_error, create_vm};