diff --git a/benches/memory_mapping.rs b/benches/memory_mapping.rs index 28aaf145..2facf301 100644 --- a/benches/memory_mapping.rs +++ b/benches/memory_mapping.rs @@ -12,6 +12,7 @@ extern crate test; use rand::{rngs::SmallRng, Rng, SeedableRng}; use solana_rbpf::{ + elf::SBPFVersion, memory_region::{ AccessType, AlignedMemoryMapping, MemoryRegion, MemoryState, UnalignedMemoryMapping, }, @@ -71,7 +72,8 @@ macro_rules! bench_gapped_randomized_access_with_1024_entries { MemoryState::Readable, )]; let config = Config::default(); - let memory_mapping = $mem::new(memory_regions, &config).unwrap(); + let memory_mapping = + $mem::new(memory_regions, &config, &SBPFVersion::V2).unwrap(); let mut prng = new_prng!(); bencher.iter(|| { assert!(memory_mapping @@ -110,7 +112,7 @@ macro_rules! bench_randomized_access_with_0001_entry { let content = vec![0; 1024 * 2]; let memory_regions = vec![MemoryRegion::new_readonly(&content[..], 0x100000000)]; let config = Config::default(); - let memory_mapping = $mem::new(memory_regions, &config).unwrap(); + let memory_mapping = $mem::new(memory_regions, &config, &SBPFVersion::V2).unwrap(); let mut prng = new_prng!(); bencher.iter(|| { let _ = memory_mapping.map( @@ -145,7 +147,7 @@ macro_rules! bench_randomized_access_with_n_entries { let (memory_regions, end_address) = generate_memory_regions($n, MemoryState::Readable, Some(&mut prng)); let config = Config::default(); - let memory_mapping = $mem::new(memory_regions, &config).unwrap(); + let memory_mapping = $mem::new(memory_regions, &config, &SBPFVersion::V2).unwrap(); bencher.iter(|| { let _ = memory_mapping.map( AccessType::Load, @@ -195,7 +197,7 @@ macro_rules! bench_randomized_mapping_with_n_entries { let (memory_regions, _end_address) = generate_memory_regions($n, MemoryState::Readable, Some(&mut prng)); let config = Config::default(); - let memory_mapping = $mem::new(memory_regions, &config).unwrap(); + let memory_mapping = $mem::new(memory_regions, &config, &SBPFVersion::V2).unwrap(); bencher.iter(|| { let _ = memory_mapping.map(AccessType::Load, 0x100000000, 1, 0); }); @@ -244,7 +246,7 @@ macro_rules! bench_mapping_with_n_entries { let (memory_regions, _end_address) = generate_memory_regions($n, MemoryState::Readable, None); let config = Config::default(); - let memory_mapping = $mem::new(memory_regions, &config).unwrap(); + let memory_mapping = $mem::new(memory_regions, &config, &SBPFVersion::V2).unwrap(); bencher.iter(|| { let _ = memory_mapping.map(AccessType::Load, 0x100000000, 1, 0); }); @@ -302,6 +304,7 @@ fn do_bench_mapping_operation(bencher: &mut Bencher, op: MemoryOperation, vm_add MemoryRegion::new_writable(&mut mem2, 0x100000000 + 8), ], &config, + &SBPFVersion::V2, ) .unwrap(); diff --git a/benches/vm_execution.rs b/benches/vm_execution.rs index 6b370148..a9dd9288 100644 --- a/benches/vm_execution.rs +++ b/benches/vm_execution.rs @@ -171,7 +171,7 @@ fn bench_jit_vs_interpreter_address_translation_stack_fixed(bencher: &mut Benche bencher, ADDRESS_TRANSLATION_STACK_CODE, Config { - dynamic_stack_frames: false, + enable_sbpf_v2: false, ..Config::default() }, 524289, @@ -186,7 +186,7 @@ fn bench_jit_vs_interpreter_address_translation_stack_dynamic(bencher: &mut Benc bencher, ADDRESS_TRANSLATION_STACK_CODE, Config { - dynamic_stack_frames: true, + enable_sbpf_v2: true, ..Config::default() }, 524289, @@ -233,7 +233,7 @@ fn bench_jit_vs_interpreter_call_depth_fixed(bencher: &mut Bencher) { call function_foo exit", Config { - dynamic_stack_frames: false, + enable_sbpf_v2: false, ..Config::default() }, 137218, @@ -264,7 +264,7 @@ fn bench_jit_vs_interpreter_call_depth_dynamic(bencher: &mut Bencher) { add r11, 4 exit", Config { - dynamic_stack_frames: true, + enable_sbpf_v2: true, ..Config::default() }, 176130, diff --git a/cli/src/main.rs b/cli/src/main.rs index 1bb7d61c..3041fe56 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -12,13 +12,13 @@ use solana_rbpf::{ use std::{fs::File, io::Read, path::Path, sync::Arc}; fn main() { - let matches = App::new("Solana RBPF CLI") + let matches = App::new("Solana BPF CLI") .version(crate_version!()) .author("Solana Maintainers ") - .about("CLI to test and analyze eBPF programs") + .about("CLI to test and analyze Solana BPF programs") .arg( Arg::new("assembler") - .about("Assemble and load eBPF executable") + .about("Assemble and load Solana BPF executable") .short('a') .long("asm") .value_name("FILE") @@ -27,7 +27,7 @@ fn main() { ) .arg( Arg::new("elf") - .about("Load ELF as eBPF executable") + .about("Load ELF as Solana BPF executable") .short('e') .long("elf") .value_name("FILE") @@ -139,6 +139,7 @@ fn main() { .unwrap(), ); let config = verified_executable.get_config(); + let sbpf_version = verified_executable.get_sbpf_version(); let mut stack = AlignedMemory::<{ ebpf::HOST_ALIGN }>::zero_filled(config.stack_size()); let stack_len = stack.len(); let mut heap = AlignedMemory::<{ ebpf::HOST_ALIGN }>::zero_filled( @@ -153,7 +154,7 @@ fn main() { MemoryRegion::new_writable_gapped( stack.as_slice_mut(), ebpf::MM_STACK_START, - if !config.dynamic_stack_frames && config.enable_stack_frame_gaps { + if !sbpf_version.dynamic_stack_frames() && config.enable_stack_frame_gaps { config.stack_frame_size as u64 } else { 0 @@ -163,7 +164,7 @@ fn main() { MemoryRegion::new_writable(&mut mem, ebpf::MM_INPUT_START), ]; - let memory_mapping = MemoryMapping::new(regions, config).unwrap(); + let memory_mapping = MemoryMapping::new(regions, config, sbpf_version).unwrap(); let mut vm = EbpfVm::new( &verified_executable, diff --git a/examples/disassemble.rs b/examples/disassemble.rs index 778fadea..02134b09 100644 --- a/examples/disassemble.rs +++ b/examples/disassemble.rs @@ -6,7 +6,7 @@ extern crate solana_rbpf; use solana_rbpf::{ - elf::Executable, + elf::{Executable, SBPFVersion}, static_analysis::Analysis, verifier::TautologyVerifier, vm::{BuiltinProgram, FunctionRegistry, TestContextObject}, @@ -35,6 +35,7 @@ fn main() { let executable = Executable::::from_text_bytes( program, loader, + SBPFVersion::V2, FunctionRegistry::default(), ) .unwrap(); diff --git a/examples/to_json.rs b/examples/to_json.rs index 05de67d9..3938470c 100644 --- a/examples/to_json.rs +++ b/examples/to_json.rs @@ -12,7 +12,7 @@ use std::path::PathBuf; extern crate solana_rbpf; use solana_rbpf::{ - elf::Executable, + elf::{Executable, SBPFVersion}, static_analysis::Analysis, verifier::TautologyVerifier, vm::{BuiltinProgram, FunctionRegistry, TestContextObject}, @@ -31,6 +31,7 @@ fn to_json(program: &[u8]) -> String { let executable = Executable::::from_text_bytes( program, Arc::new(BuiltinProgram::default()), + SBPFVersion::V2, FunctionRegistry::default(), ) .unwrap(); diff --git a/fuzz/fuzz_targets/common.rs b/fuzz/fuzz_targets/common.rs index a13359ca..00722552 100644 --- a/fuzz/fuzz_targets/common.rs +++ b/fuzz/fuzz_targets/common.rs @@ -14,8 +14,6 @@ pub struct ConfigTemplate { sanitize_user_provided_values: bool, encrypt_environment_registers: bool, reject_callx_r10: bool, - dynamic_stack_frames: bool, - enable_sdiv: bool, optimize_rodata: bool, } @@ -31,8 +29,6 @@ impl<'a> Arbitrary<'a> for ConfigTemplate { sanitize_user_provided_values: bools & (1 << 3) != 0, encrypt_environment_registers: bools & (1 << 4) != 0, reject_callx_r10: bools & (1 << 6) != 0, - dynamic_stack_frames: bools & (1 << 7) != 0, - enable_sdiv: bools & (1 << 8) != 0, optimize_rodata: bools & (1 << 9) != 0, }) } @@ -57,8 +53,6 @@ impl From for Config { sanitize_user_provided_values, encrypt_environment_registers, reject_callx_r10, - dynamic_stack_frames, - enable_sdiv, optimize_rodata, } => Config { max_call_depth, @@ -74,8 +68,6 @@ impl From for Config { 0 }, reject_callx_r10, - dynamic_stack_frames, - enable_sdiv, optimize_rodata, ..Default::default() }, diff --git a/fuzz/fuzz_targets/dumb.rs b/fuzz/fuzz_targets/dumb.rs index 84e83600..c7176eaa 100644 --- a/fuzz/fuzz_targets/dumb.rs +++ b/fuzz/fuzz_targets/dumb.rs @@ -6,7 +6,7 @@ use libfuzzer_sys::fuzz_target; use solana_rbpf::{ ebpf, - elf::Executable, + elf::{Executable, SBPFVersion}, memory_region::MemoryRegion, verifier::{RequisiteVerifier, TautologyVerifier, Verifier}, vm::{BuiltinProgram, FunctionRegistry, TestContextObject}, @@ -28,7 +28,7 @@ fuzz_target!(|data: DumbFuzzData| { let prog = data.prog; let config = data.template.into(); let function_registry = FunctionRegistry::default(); - if RequisiteVerifier::verify(&prog, &config, &function_registry).is_err() { + if RequisiteVerifier::verify(&prog, &config, &SBPFVersion::V2, &function_registry).is_err() { // verify please return; } @@ -36,6 +36,7 @@ fuzz_target!(|data: DumbFuzzData| { let executable = Executable::::from_text_bytes( &prog, std::sync::Arc::new(BuiltinProgram::new_loader(config)), + SBPFVersion::V2, function_registry, ) .unwrap(); diff --git a/fuzz/fuzz_targets/smart.rs b/fuzz/fuzz_targets/smart.rs index 0d9729bc..f55dc7af 100644 --- a/fuzz/fuzz_targets/smart.rs +++ b/fuzz/fuzz_targets/smart.rs @@ -7,7 +7,7 @@ use libfuzzer_sys::fuzz_target; use grammar_aware::*; use solana_rbpf::{ ebpf, - elf::Executable, + elf::{Executable, SBPFVersion}, insn_builder::{Arch, IntoBytes}, memory_region::MemoryRegion, verifier::{RequisiteVerifier, TautologyVerifier, Verifier}, @@ -32,7 +32,7 @@ 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, &function_registry).is_err() { + if RequisiteVerifier::verify(prog.into_bytes(), &config, &SBPFVersion::V2, &function_registry).is_err() { // verify please return; } @@ -40,6 +40,7 @@ fuzz_target!(|data: FuzzData| { let executable = Executable::::from_text_bytes( prog.into_bytes(), std::sync::Arc::new(BuiltinProgram::new_loader(config)), + SBPFVersion::V2, function_registry, ) .unwrap(); diff --git a/fuzz/fuzz_targets/smart_jit_diff.rs b/fuzz/fuzz_targets/smart_jit_diff.rs index 1b0bbd19..d7fcd12f 100644 --- a/fuzz/fuzz_targets/smart_jit_diff.rs +++ b/fuzz/fuzz_targets/smart_jit_diff.rs @@ -5,7 +5,7 @@ use libfuzzer_sys::fuzz_target; use grammar_aware::*; use solana_rbpf::{ ebpf, - elf::Executable, + elf::{Executable, SBPFVersion}, insn_builder::{Arch, Instruction, IntoBytes}, memory_region::MemoryRegion, verifier::{RequisiteVerifier, TautologyVerifier, Verifier}, @@ -39,7 +39,7 @@ fuzz_target!(|data: FuzzData| { .push(); let config = data.template.into(); let function_registry = FunctionRegistry::default(); - if RequisiteVerifier::verify(prog.into_bytes(), &config, &function_registry).is_err() { + if RequisiteVerifier::verify(prog.into_bytes(), &config, &SBPFVersion::V2, &function_registry).is_err() { // verify please return; } @@ -48,6 +48,7 @@ fuzz_target!(|data: FuzzData| { let mut executable = Executable::::from_text_bytes( prog.into_bytes(), std::sync::Arc::new(BuiltinProgram::new_loader(config)), + SBPFVersion::V2, function_registry, ) .unwrap(); diff --git a/fuzz/fuzz_targets/smarter_jit_diff.rs b/fuzz/fuzz_targets/smarter_jit_diff.rs index 8325d5ae..64aa93c6 100644 --- a/fuzz/fuzz_targets/smarter_jit_diff.rs +++ b/fuzz/fuzz_targets/smarter_jit_diff.rs @@ -5,7 +5,7 @@ use libfuzzer_sys::fuzz_target; use semantic_aware::*; use solana_rbpf::{ ebpf, - elf::Executable, + elf::{Executable, SBPFVersion}, insn_builder::IntoBytes, memory_region::MemoryRegion, static_analysis::Analysis, @@ -38,7 +38,7 @@ 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, &function_registry).is_err() { + if RequisiteVerifier::verify(prog.into_bytes(), &config, &SBPFVersion::V2, &function_registry).is_err() { // verify please return; } @@ -47,6 +47,7 @@ fuzz_target!(|data: FuzzData| { let mut executable = Executable::::from_text_bytes( prog.into_bytes(), std::sync::Arc::new(BuiltinProgram::new_loader(config)), + SBPFVersion::V2, function_registry, ) .unwrap(); diff --git a/fuzz/fuzz_targets/verify_semantic_aware.rs b/fuzz/fuzz_targets/verify_semantic_aware.rs index d41cfb12..3438017b 100644 --- a/fuzz/fuzz_targets/verify_semantic_aware.rs +++ b/fuzz/fuzz_targets/verify_semantic_aware.rs @@ -4,6 +4,7 @@ use libfuzzer_sys::fuzz_target; use semantic_aware::*; use solana_rbpf::{ + elf::SBPFVersion, insn_builder::IntoBytes, verifier::{RequisiteVerifier, Verifier}, vm::FunctionRegistry, @@ -24,5 +25,5 @@ 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, &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 1edf2c84..07985d1f 100644 --- a/src/assembler.rs +++ b/src/assembler.rs @@ -18,7 +18,7 @@ use crate::{ Statement, }, ebpf::{self, Insn}, - elf::{register_internal_function, Executable}, + elf::{register_internal_function, Executable, SBPFVersion}, verifier::TautologyVerifier, vm::{BuiltinProgram, ContextObject, FunctionRegistry}, }; @@ -218,6 +218,11 @@ pub fn assemble( src: &str, loader: Arc>, ) -> Result, String> { + let sbpf_version = if loader.get_config().enable_sbpf_v2 { + SBPFVersion::V2 + } else { + SBPFVersion::V1 + }; fn resolve_label( insn_ptr: usize, labels: &HashMap<&str, usize>, @@ -242,6 +247,7 @@ pub fn assemble( register_internal_function( &mut function_registry, &loader, + &SBPFVersion::V2, insn_ptr, name.as_bytes(), ) @@ -295,6 +301,7 @@ pub fn assemble( register_internal_function( &mut function_registry, &loader, + &SBPFVersion::V2, target_pc as usize, label.as_bytes(), ) @@ -358,6 +365,11 @@ pub fn assemble( .iter() .flat_map(|insn| insn.to_vec()) .collect::>(); - Executable::::from_text_bytes(&program, loader, function_registry) - .map_err(|err| format!("Executable constructor {err:?}")) + Executable::::from_text_bytes( + &program, + loader, + sbpf_version, + function_registry, + ) + .map_err(|err| format!("Executable constructor {err:?}")) } diff --git a/src/disassembler.rs b/src/disassembler.rs index 93991dde..86f4804c 100644 --- a/src/disassembler.rs +++ b/src/disassembler.rs @@ -8,9 +8,12 @@ //! Functions in this module are used to handle eBPF programs with a higher level representation, //! for example to disassemble the code into a human-readable format. -use crate::ebpf; -use crate::static_analysis::CfgNode; -use crate::vm::{BuiltinProgram, ContextObject, FunctionRegistry}; +use crate::{ + ebpf, + elf::SBPFVersion, + static_analysis::CfgNode, + vm::{BuiltinProgram, ContextObject, FunctionRegistry}, +}; use std::collections::BTreeMap; fn resolve_label(cfg_nodes: &BTreeMap, pc: usize) -> &str { @@ -122,6 +125,7 @@ pub fn disassemble_instruction( cfg_nodes: &BTreeMap, function_registry: &FunctionRegistry, loader: &BuiltinProgram, + sbpf_version: &SBPFVersion, ) -> String { let name; let desc; @@ -248,7 +252,7 @@ pub fn disassemble_instruction( ebpf::JSLE_REG => { name = "jsle"; desc = jmp_reg_str(name, insn, cfg_nodes); }, ebpf::CALL_IMM => { let mut function_name = None; - if loader.get_config().static_syscalls { + if sbpf_version.static_syscalls() { if insn.src != 0 { function_name = Some(resolve_label(cfg_nodes, insn.imm as usize)); } diff --git a/src/ebpf.rs b/src/ebpf.rs index d1c1c5a6..52e2fe3b 100644 --- a/src/ebpf.rs +++ b/src/ebpf.rs @@ -22,8 +22,8 @@ use byteorder::{ByteOrder, LittleEndian}; use hash32::{Hash, Hasher, Murmur3Hasher}; use std::fmt; -/// SBF version flag -pub const EF_SBF_V2: u32 = 0x20; +/// Solana BPF version flag +pub const EF_SBPF_V2: u32 = 0x20; /// Maximum number of instructions in an eBPF program. pub const PROG_MAX_INSNS: usize = 65_536; /// Size of an eBPF instructions, in bytes. diff --git a/src/elf.rs b/src/elf.rs index 44676aec..3e2deee5 100644 --- a/src/elf.rs +++ b/src/elf.rs @@ -8,10 +8,10 @@ use crate::{ aligned_memory::{is_memory_aligned, AlignedMemory}, - ebpf::{self, EF_SBF_V2, HOST_ALIGN, INSN_SIZE}, + ebpf::{self, EF_SBPF_V2, HOST_ALIGN, INSN_SIZE}, elf_parser::{ consts::{ - ELFCLASS64, ELFDATA2LSB, ELFOSABI_NONE, EM_BPF, EM_SBF, ET_DYN, R_X86_64_32, + ELFCLASS64, ELFDATA2LSB, ELFOSABI_NONE, EM_BPF, EM_SBPF, ET_DYN, R_X86_64_32, R_X86_64_64, R_X86_64_NONE, R_X86_64_RELATIVE, }, types::Elf64Word, @@ -105,9 +105,9 @@ pub enum ElfError { /// Offset or value is out of bounds #[error("Offset or value is out of bounds")] ValueOutOfBounds, - /// Detected capabilities required by the executable which are not enabled - #[error("Detected capabilities required by the executable which are not enabled")] - UnsupportedExecutableCapabilities, + /// Detected sbpf_version required by the executable which are not enabled + #[error("Detected sbpf_version required by the executable which are not enabled")] + UnsupportedSBPFVersion, /// Invalid program header #[error("Invalid ELF program header")] InvalidProgramHeader, @@ -128,11 +128,12 @@ pub fn hash_internal_function(pc: usize, name: &[u8]) -> u32 { pub fn register_internal_function( function_registry: &mut FunctionRegistry, loader: &BuiltinProgram, + sbpf_version: &SBPFVersion, pc: usize, name: &[u8], ) -> Result { let config = loader.get_config(); - let key = if config.static_syscalls { + let key = if sbpf_version.static_syscalls() { // With static_syscalls normal function calls and syscalls are differentiated in the ISA. // Thus, we don't need to hash them here anymore and collisions are gone as well. pc as u32 @@ -260,6 +261,46 @@ 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 native signed division + pub fn enable_sdiv(&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 + } +} + /// Elf loader/relocator #[derive(Debug, PartialEq)] pub struct Executable { @@ -267,6 +308,8 @@ pub struct Executable { _verifier: PhantomData, /// Loaded and executable elf elf_bytes: AlignedMemory<{ HOST_ALIGN }>, + /// Required SBPF capabilities + sbpf_version: SBPFVersion, /// Read-only section ro_section: Section, /// Text section info @@ -288,6 +331,11 @@ impl Executable { self.loader.get_config() } + /// Get the executable sbpf_version + pub fn get_sbpf_version(&self) -> &SBPFVersion { + &self.sbpf_version + } + /// Get the .text section virtual address and bytes pub fn get_text_bytes(&self) -> (u64, &[u8]) { let (ro_offset, ro_section) = match &self.ro_section { @@ -355,6 +403,7 @@ impl Executable { ::verify( executable.get_text_bytes().1, executable.get_config(), + executable.get_sbpf_version(), executable.get_function_registry(), )?; Ok(unsafe { @@ -379,6 +428,7 @@ impl Executable { pub fn new_from_text_bytes( text_bytes: &[u8], loader: Arc>, + sbpf_version: SBPFVersion, mut function_registry: FunctionRegistry, ) -> Result { let elf_bytes = AlignedMemory::from_slice(text_bytes); @@ -390,12 +440,19 @@ impl Executable { { *pc } else { - register_internal_function(&mut function_registry, &loader, 0, b"entrypoint")?; + register_internal_function( + &mut function_registry, + &loader, + &sbpf_version, + 0, + b"entrypoint", + )?; 0 }; Ok(Self { _verifier: PhantomData, elf_bytes, + sbpf_version, ro_section: Section::Borrowed(0, 0..text_bytes.len()), text_section_info: SectionInfo { name: if enable_symbol_and_section_labels { @@ -440,6 +497,12 @@ impl Executable { ) -> Result { let mut elf_bytes = AlignedMemory::from_slice(bytes); let config = loader.get_config(); + let header = elf.header(); + let sbpf_version = if header.e_flags == EF_SBPF_V2 { + SBPFVersion::V2 + } else { + SBPFVersion::V1 + }; Self::validate(config, elf, elf_bytes.as_slice())?; @@ -454,7 +517,9 @@ impl Executable { } else { String::default() }, - vaddr: if config.enable_elf_vaddr && text_section.sh_addr() >= ebpf::MM_PROGRAM_START { + vaddr: if sbpf_version.enable_elf_vaddr() + && text_section.sh_addr() >= ebpf::MM_PROGRAM_START + { text_section.sh_addr() } else { text_section @@ -463,7 +528,7 @@ impl Executable { }, offset_range: text_section.file_range().unwrap_or_default(), }; - let vaddr_end = if config.reject_rodata_stack_overlap { + let vaddr_end = if sbpf_version.reject_rodata_stack_overlap() { text_section_info .vaddr .saturating_add(text_section.sh_size()) @@ -471,7 +536,7 @@ impl Executable { text_section_info.vaddr }; if (config.reject_broken_elfs - && !config.enable_elf_vaddr + && !sbpf_version.enable_elf_vaddr() && text_section.sh_addr() != text_section.sh_offset()) || vaddr_end > ebpf::MM_STACK_START { @@ -488,15 +553,21 @@ impl Executable { )?; // calculate entrypoint offset into the text section - let offset = elf.header().e_entry.saturating_sub(text_section.sh_addr()); + let offset = header.e_entry.saturating_sub(text_section.sh_addr()); if offset.checked_rem(ebpf::INSN_SIZE as u64) != Some(0) { return Err(ElfError::InvalidEntrypoint); } let entry_pc = if let Some(entry_pc) = (offset as usize).checked_div(ebpf::INSN_SIZE) { - if !config.static_syscalls { + if !sbpf_version.static_syscalls() { function_registry.remove(&ebpf::hash_symbol_name(b"entrypoint")); } - register_internal_function(&mut function_registry, &loader, entry_pc, b"entrypoint")?; + register_internal_function( + &mut function_registry, + &loader, + &sbpf_version, + entry_pc, + b"entrypoint", + )?; entry_pc } else { return Err(ElfError::InvalidEntrypoint); @@ -504,6 +575,7 @@ impl Executable { let ro_section = Self::parse_ro_sections( config, + &sbpf_version, elf.section_headers() .map(|s| (elf.section_name(s.sh_name()), s)), elf_bytes.as_slice(), @@ -512,6 +584,7 @@ impl Executable { Ok(Self { _verifier: PhantomData, elf_bytes, + sbpf_version, ro_section, text_section_info, entry_pc, @@ -563,6 +636,7 @@ impl Executable { pub fn fixup_relative_calls( function_registry: &mut FunctionRegistry, loader: &BuiltinProgram, + sbpf_version: &SBPFVersion, elf_bytes: &mut [u8], ) -> Result<(), ElfError> { let config = loader.get_config(); @@ -574,7 +648,7 @@ impl Executable { let mut insn = ebpf::get_insn(elf_bytes, i); if insn.opc == ebpf::CALL_IMM && insn.imm != -1 - && !(config.static_syscalls && insn.src == 0) + && !(sbpf_version.static_syscalls() && insn.src == 0) { let target_pc = (i as isize) .saturating_add(1) @@ -593,6 +667,7 @@ impl Executable { let key = register_internal_function( function_registry, loader, + sbpf_version, target_pc as usize, name.as_bytes(), )?; @@ -623,26 +698,26 @@ impl Executable { if header.e_ident.ei_osabi != ELFOSABI_NONE { return Err(ElfError::WrongAbi); } - if header.e_machine != EM_BPF && (!config.new_elf_parser || header.e_machine != EM_SBF) { + if header.e_machine != EM_BPF && (!config.new_elf_parser || header.e_machine != EM_SBPF) { return Err(ElfError::WrongMachine); } if header.e_type != ET_DYN { return Err(ElfError::WrongType); } - if header.e_flags == EF_SBF_V2 { - if !config.dynamic_stack_frames { - return Err(ElfError::UnsupportedExecutableCapabilities); + let sbpf_version = if header.e_flags == EF_SBPF_V2 { + if !config.enable_sbpf_v2 { + return Err(ElfError::UnsupportedSBPFVersion); } - } else if config.dynamic_stack_frames - && config.enable_elf_vaddr - && config.reject_rodata_stack_overlap - && config.static_syscalls - { - return Err(ElfError::UnsupportedExecutableCapabilities); - } + SBPFVersion::V2 + } else { + if !config.enable_sbpf_v1 { + return Err(ElfError::UnsupportedSBPFVersion); + } + SBPFVersion::V1 + }; - if config.enable_elf_vaddr { + if sbpf_version.enable_elf_vaddr() { // This is needed to avoid an overflow error in header.vm_range() as // used by relocate(). See https://github.com/m4b/goblin/pull/306. // @@ -717,6 +792,7 @@ impl Executable { S: IntoIterator, &'a T)>, >( config: &Config, + sbpf_version: &SBPFVersion, sections: S, elf_bytes: &[u8], ) -> Result { @@ -727,7 +803,7 @@ impl Executable { // the aggregated section length, not including gaps between sections let mut ro_fill_length = 0usize; let mut invalid_offsets = false; - // when config.enable_elf_vaddr=true, we allow section_addr != sh_offset + // when sbpf_version.enable_elf_vaddr()=true, we allow section_addr != sh_offset // if section_addr - sh_offset is constant across all sections. That is, // we allow sections to be translated by a fixed virtual offset. let mut addr_file_offset = None; @@ -759,16 +835,16 @@ impl Executable { // sh_offset handling: // - // If config.enable_elf_vaddr=true, we allow section_addr > + // If sbpf_version.enable_elf_vaddr()=true, we allow section_addr > // sh_offset, if section_addr - sh_offset is constant across all // sections. That is, we allow the linker to align rodata to a // positive base address (MM_PROGRAM_START) as long as the mapping // to sh_offset(s) stays linear. // - // If config.enable_elf_vaddr=false, section_addr must match + // If sbpf_version.enable_elf_vaddr()=false, section_addr must match // sh_offset for backwards compatibility if !invalid_offsets { - if config.enable_elf_vaddr { + if sbpf_version.enable_elf_vaddr() { if section_addr < section_header.sh_offset() { invalid_offsets = true; } else { @@ -786,13 +862,13 @@ impl Executable { } } - let mut vaddr_end = if config.enable_elf_vaddr && section_addr >= ebpf::MM_PROGRAM_START - { - section_addr - } else { - section_addr.saturating_add(ebpf::MM_PROGRAM_START) - }; - if config.reject_rodata_stack_overlap { + let mut vaddr_end = + if sbpf_version.enable_elf_vaddr() && section_addr >= ebpf::MM_PROGRAM_START { + section_addr + } else { + section_addr.saturating_add(ebpf::MM_PROGRAM_START) + }; + if sbpf_version.reject_rodata_stack_overlap() { vaddr_end = vaddr_end.saturating_add(section_header.sh_size()); } if (config.reject_broken_elfs && invalid_offsets) || vaddr_end > ebpf::MM_STACK_START { @@ -824,7 +900,7 @@ impl Executable { // Read only sections are grouped together with no intermixed non-ro // sections. We can borrow. - // When config.enable_elf_vaddr=true, section addresses and their + // When sbpf_version.enable_elf_vaddr()=true, section addresses and their // corresponding buffer offsets can be translated by a constant // amount. Subtract the constant to get buffer positions. let buf_offset_start = @@ -892,11 +968,17 @@ impl Executable { ) -> Result<(), ElfError> { let mut syscall_cache = BTreeMap::new(); let text_section = elf.section(b".text")?; + let sbpf_version = if elf.header().e_flags == EF_SBPF_V2 { + SBPFVersion::V2 + } else { + SBPFVersion::V1 + }; // Fixup all program counter relative call instructions Self::fixup_relative_calls( function_registry, loader, + &sbpf_version, elf_bytes .get_mut(text_section.file_range().unwrap_or_default()) .ok_or(ElfError::ValueOutOfBounds)?, @@ -909,10 +991,10 @@ impl Executable { for relocation in elf.dynamic_relocations() { let mut r_offset = relocation.r_offset() as usize; - // When config.enable_elf_vaddr=true, we allow section.sh_addr != + // When sbpf_version.enable_elf_vaddr()=true, we allow section.sh_addr != // section.sh_offset so we need to bring r_offset to the correct // byte offset. - if config.enable_elf_vaddr { + if sbpf_version.enable_elf_vaddr() { match program_header { Some(header) if header.vm_range().contains(&(r_offset as u64)) => {} _ => { @@ -934,7 +1016,7 @@ impl Executable { .file_range() .unwrap_or_default() .contains(&r_offset) - || elf.header().e_flags != EF_SBF_V2 + || sbpf_version == SBPFVersion::V1 { r_offset.saturating_add(BYTE_OFFSET_IMMEDIATE) } else { @@ -968,7 +1050,7 @@ impl Executable { .file_range() .unwrap_or_default() .contains(&r_offset) - || elf.header().e_flags != EF_SBF_V2 + || sbpf_version == SBPFVersion::V1 { let imm_low_offset = imm_offset; let imm_high_offset = imm_low_offset.saturating_add(INSN_SIZE); @@ -1074,7 +1156,7 @@ impl Executable { refd_addr.checked_shr(32).unwrap_or_default() as u32, ); } else { - let refd_addr = if elf.header().e_flags == EF_SBF_V2 { + let refd_addr = if sbpf_version != SBPFVersion::V1 { // We're relocating an address inside a data section (eg .rodata). The // address is encoded as a simple u64. @@ -1131,7 +1213,13 @@ impl Executable { as usize) .checked_div(ebpf::INSN_SIZE) .unwrap_or_default(); - register_internal_function(function_registry, loader, target_pc, name)? + register_internal_function( + function_registry, + loader, + &sbpf_version, + target_pc, + name, + )? } else { // Else it's a syscall let hash = *syscall_cache @@ -1176,7 +1264,13 @@ impl Executable { let name = elf .symbol_name(symbol.st_name() as Elf64Word) .ok_or_else(|| ElfError::UnknownSymbol(symbol.st_name() as usize))?; - register_internal_function(function_registry, loader, target_pc, name)?; + register_internal_function( + function_registry, + loader, + &sbpf_version, + target_pc, + name, + )?; } } @@ -1400,7 +1494,6 @@ mod test { fn test_fixup_relative_calls_back() { let mut function_registry = FunctionRegistry::default(); let loader = BuiltinProgram::new_loader(Config { - static_syscalls: false, enable_symbol_and_section_labels: true, ..Config::default() }); @@ -1415,7 +1508,13 @@ mod test { 0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x85, 0x10, 0x00, 0x00, 0xfe, 0xff, 0xff, 0xff]; - ElfExecutable::fixup_relative_calls(&mut function_registry, &loader, &mut prog).unwrap(); + ElfExecutable::fixup_relative_calls( + &mut function_registry, + &loader, + &SBPFVersion::V1, + &mut prog, + ) + .unwrap(); let name = "function_4".to_string(); let hash = hash_internal_function(4, name.as_bytes()); let insn = ebpf::Insn { @@ -1431,7 +1530,13 @@ mod test { // call +6 let mut function_registry = FunctionRegistry::default(); prog.splice(44.., vec![0xfa, 0xff, 0xff, 0xff]); - ElfExecutable::fixup_relative_calls(&mut function_registry, &loader, &mut prog).unwrap(); + ElfExecutable::fixup_relative_calls( + &mut function_registry, + &loader, + &SBPFVersion::V1, + &mut prog, + ) + .unwrap(); let name = "function_0".to_string(); let hash = hash_internal_function(0, name.as_bytes()); let insn = ebpf::Insn { @@ -1449,7 +1554,6 @@ mod test { fn test_fixup_relative_calls_forward() { let mut function_registry = FunctionRegistry::default(); let loader = BuiltinProgram::new_loader(Config { - static_syscalls: false, enable_symbol_and_section_labels: true, ..Config::default() }); @@ -1464,7 +1568,13 @@ mod test { 0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; - ElfExecutable::fixup_relative_calls(&mut function_registry, &loader, &mut prog).unwrap(); + ElfExecutable::fixup_relative_calls( + &mut function_registry, + &loader, + &SBPFVersion::V1, + &mut prog, + ) + .unwrap(); let name = "function_1".to_string(); let hash = hash_internal_function(1, name.as_bytes()); let insn = ebpf::Insn { @@ -1480,7 +1590,13 @@ mod test { // call +4 let mut function_registry = FunctionRegistry::default(); prog.splice(4..8, vec![0x04, 0x00, 0x00, 0x00]); - ElfExecutable::fixup_relative_calls(&mut function_registry, &loader, &mut prog).unwrap(); + ElfExecutable::fixup_relative_calls( + &mut function_registry, + &loader, + &SBPFVersion::V1, + &mut prog, + ) + .unwrap(); let name = "function_5".to_string(); let hash = hash_internal_function(5, name.as_bytes()); let insn = ebpf::Insn { @@ -1512,7 +1628,13 @@ mod test { 0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; - ElfExecutable::fixup_relative_calls(&mut function_registry, &loader, &mut prog).unwrap(); + ElfExecutable::fixup_relative_calls( + &mut function_registry, + &loader, + &SBPFVersion::V2, + &mut prog, + ) + .unwrap(); let name = "function_1".to_string(); let hash = hash_internal_function(1, name.as_bytes()); let insn = ebpf::Insn { @@ -1544,7 +1666,13 @@ mod test { 0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x85, 0x10, 0x00, 0x00, 0xf9, 0xff, 0xff, 0xff]; - ElfExecutable::fixup_relative_calls(&mut function_registry, &loader, &mut prog).unwrap(); + ElfExecutable::fixup_relative_calls( + &mut function_registry, + &loader, + &SBPFVersion::V2, + &mut prog, + ) + .unwrap(); let name = "function_4".to_string(); let hash = hash_internal_function(4, name.as_bytes()); let insn = ebpf::Insn { @@ -1653,6 +1781,7 @@ mod test { assert!(matches!( ElfExecutable::parse_ro_sections( &config, + &SBPFVersion::V2, sections, &elf_bytes, ), @@ -1679,6 +1808,7 @@ mod test { assert!(matches!( ElfExecutable::parse_ro_sections( &config, + &SBPFVersion::V2, sections, &elf_bytes, ), @@ -1690,7 +1820,7 @@ mod test { fn test_sh_offset_not_same_as_vaddr() { let config = Config { reject_broken_elfs: true, - enable_elf_vaddr: false, + enable_sbpf_v2: false, ..Config::default() }; let elf_bytes = [0u8; 512]; @@ -1699,13 +1829,19 @@ mod test { { let sections: [(Option<&[u8]>, &Elf64Shdr); 1] = [(Some(b".text"), &s1)]; - assert!(ElfExecutable::parse_ro_sections(&config, sections, &elf_bytes).is_ok()); + assert!(ElfExecutable::parse_ro_sections( + &config, + &SBPFVersion::V1, + sections, + &elf_bytes + ) + .is_ok()); } s1.sh_offset = 0; let sections: [(Option<&[u8]>, &Elf64Shdr); 1] = [(Some(b".text"), &s1)]; assert_eq!( - ElfExecutable::parse_ro_sections(&config, sections, &elf_bytes), + ElfExecutable::parse_ro_sections(&config, &SBPFVersion::V1, sections, &elf_bytes), Err(ElfError::ValueOutOfBounds) ); } @@ -1726,7 +1862,7 @@ mod test { let sections: [(Option<&[u8]>, &Elf64Shdr); 2] = [(Some(b".text"), &s1), (Some(b".rodata"), &s2)]; assert_eq!( - ElfExecutable::parse_ro_sections(&config, sections, &elf_bytes,), + ElfExecutable::parse_ro_sections(&config, &SBPFVersion::V2, sections, &elf_bytes,), Err(ElfError::ValueOutOfBounds) ); } @@ -1750,7 +1886,7 @@ mod test { let sections: [(Option<&[u8]>, &Elf64Shdr); 2] = [(Some(b".text"), &s1), (Some(b".rodata"), &s2)]; assert_eq!( - ElfExecutable::parse_ro_sections(&config, sections, &elf_bytes,), + ElfExecutable::parse_ro_sections(&config, &SBPFVersion::V2, sections, &elf_bytes,), Err(ElfError::ValueOutOfBounds) ); } @@ -1772,7 +1908,7 @@ mod test { let sections: [(Option<&[u8]>, &Elf64Shdr); 2] = [(Some(b".text"), &s1), (Some(b".rodata"), &s2)]; assert_eq!( - ElfExecutable::parse_ro_sections(&config, sections, &elf_bytes,), + ElfExecutable::parse_ro_sections(&config, &SBPFVersion::V2, sections, &elf_bytes,), Ok(Section::Borrowed(10, 100..120)) ); } @@ -1792,7 +1928,9 @@ mod test { (Some(b".dynamic"), &s2), (Some(b".rodata"), &s3), ]; - let ro_section = ElfExecutable::parse_ro_sections(&config, sections, &elf_bytes).unwrap(); + let ro_section = + ElfExecutable::parse_ro_sections(&config, &SBPFVersion::V2, sections, &elf_bytes) + .unwrap(); let ro_region = get_ro_region(&ro_section, &elf_bytes); let owned_section = match &ro_section { Section::Owned(_offset, data) => data.as_slice(), @@ -1831,7 +1969,9 @@ mod test { (Some(b".dynamic"), &s2), (Some(b".rodata"), &s3), ]; - let ro_section = ElfExecutable::parse_ro_sections(&config, sections, &elf_bytes).unwrap(); + let ro_section = + ElfExecutable::parse_ro_sections(&config, &SBPFVersion::V2, sections, &elf_bytes) + .unwrap(); let ro_region = get_ro_region(&ro_section, &elf_bytes); let owned_section = match &ro_section { Section::Owned(_offset, data) => data.as_slice(), @@ -1869,7 +2009,9 @@ mod test { (Some(b".dynamic"), &s2), (Some(b".rodata"), &s3), ]; - let ro_section = ElfExecutable::parse_ro_sections(&config, sections, &elf_bytes).unwrap(); + let ro_section = + ElfExecutable::parse_ro_sections(&config, &SBPFVersion::V2, sections, &elf_bytes) + .unwrap(); let owned_section = match &ro_section { Section::Owned(_offset, data) => data.as_slice(), _ => panic!(), @@ -1927,6 +2069,7 @@ mod test { assert!(matches!( ElfExecutable::parse_ro_sections( &config, + &SBPFVersion::V2, sections, &elf_bytes, ), @@ -1951,7 +2094,7 @@ mod test { (Some(b".dynamic"), &s4), ]; assert_eq!( - ElfExecutable::parse_ro_sections(&config, sections, &elf_bytes,), + ElfExecutable::parse_ro_sections(&config, &SBPFVersion::V2, sections, &elf_bytes,), Ok(Section::Borrowed(20, 20..50)) ); } @@ -1970,7 +2113,9 @@ mod test { (Some(b".rodata"), &s2), (Some(b".dynamic"), &s3), ]; - let ro_section = ElfExecutable::parse_ro_sections(&config, sections, &elf_bytes).unwrap(); + let ro_section = + ElfExecutable::parse_ro_sections(&config, &SBPFVersion::V2, sections, &elf_bytes) + .unwrap(); let ro_region = get_ro_region(&ro_section, &elf_bytes); // s1 starts at sh_addr=0 so [0..s2.sh_addr + s2.sh_size] is the valid @@ -2001,7 +2146,9 @@ mod test { (Some(b".text"), &s2), (Some(b".rodata"), &s3), ]; - let ro_section = ElfExecutable::parse_ro_sections(&config, sections, &elf_bytes).unwrap(); + let ro_section = + ElfExecutable::parse_ro_sections(&config, &SBPFVersion::V2, sections, &elf_bytes) + .unwrap(); let ro_region = get_ro_region(&ro_section, &elf_bytes); // s2 starts at sh_addr=10 so [0..10] is not mappable @@ -2040,8 +2187,7 @@ mod test { #[test] fn test_reject_rodata_stack_overlap() { let config = Config { - enable_elf_vaddr: true, - reject_rodata_stack_overlap: true, + enable_sbpf_v2: true, ..Config::default() }; let elf_bytes = [0u8; 512]; @@ -2050,20 +2196,26 @@ mod test { let mut s1 = new_section(ebpf::MM_STACK_START - 10, 10); s1.sh_offset = 0; let sections: [(Option<&[u8]>, &Elf64Shdr); 1] = [(Some(b".text"), &s1)]; - assert!(ElfExecutable::parse_ro_sections(&config, sections, &elf_bytes).is_ok()); + assert!( + ElfExecutable::parse_ro_sections(&config, &SBPFVersion::V2, sections, &elf_bytes) + .is_ok() + ); // no overlap let mut s1 = new_section(ebpf::MM_STACK_START, 0); s1.sh_offset = 0; let sections: [(Option<&[u8]>, &Elf64Shdr); 1] = [(Some(b".text"), &s1)]; - assert!(ElfExecutable::parse_ro_sections(&config, sections, &elf_bytes).is_ok()); + assert!( + ElfExecutable::parse_ro_sections(&config, &SBPFVersion::V2, sections, &elf_bytes) + .is_ok() + ); // overlap let mut s1 = new_section(ebpf::MM_STACK_START, 1); s1.sh_offset = 0; let sections: [(Option<&[u8]>, &Elf64Shdr); 1] = [(Some(b".text"), &s1)]; assert_eq!( - ElfExecutable::parse_ro_sections(&config, sections, &elf_bytes), + ElfExecutable::parse_ro_sections(&config, &SBPFVersion::V2, sections, &elf_bytes), Err(ElfError::ValueOutOfBounds) ); @@ -2072,7 +2224,7 @@ mod test { s1.sh_offset = 0; let sections: [(Option<&[u8]>, &Elf64Shdr); 1] = [(Some(b".text"), &s1)]; assert_eq!( - ElfExecutable::parse_ro_sections(&config, sections, &elf_bytes), + ElfExecutable::parse_ro_sections(&config, &SBPFVersion::V2, sections, &elf_bytes), Err(ElfError::ValueOutOfBounds) ); } @@ -2093,21 +2245,6 @@ mod test { ElfExecutable::load(&elf_bytes, loader()).expect("validation failed"); } - #[test] - #[should_panic(expected = r#"validation failed: RelativeJumpOutOfBounds(29)"#)] - fn test_static_syscall_disabled() { - let loader = BuiltinProgram::new_loader(Config { - static_syscalls: false, - ..Config::default() - }); - let elf_bytes = - std::fs::read("tests/elfs/syscall_static_unknown.so").expect("failed to read elf file"); - - // when config.static_syscalls=false, all CALL_IMMs are treated as relative - // calls for backwards compatibility - ElfExecutable::load(&elf_bytes, Arc::new(loader)).expect("validation failed"); - } - #[test] #[should_panic(expected = "validation failed: InvalidProgramHeader")] fn test_program_headers_overflow() { diff --git a/src/elf_parser/consts.rs b/src/elf_parser/consts.rs index c3db8f98..2af2d637 100644 --- a/src/elf_parser/consts.rs +++ b/src/elf_parser/consts.rs @@ -16,7 +16,7 @@ pub const EI_OSABI: u8 = 7; pub const ELFOSABI_NONE: u8 = 0; pub const EM_BPF: Elf64Half = 247; -pub const EM_SBF: Elf64Half = 263; +pub const EM_SBPF: Elf64Half = 263; pub const ET_NONE: Elf64Half = 0; pub const ET_REL: Elf64Half = 1; diff --git a/src/interpreter.rs b/src/interpreter.rs index 2f3b63cc..07e83d42 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -144,7 +144,7 @@ impl<'a, 'b, V: Verifier, C: ContextObject> Interpreter<'a, 'b, V, C> { ); } - if !config.dynamic_stack_frames { + if !self.vm.executable.get_sbpf_version().dynamic_stack_frames() { // With fixed frames we start the new frame at the next fixed offset let stack_frame_size = config.stack_frame_size * if config.enable_stack_frame_gaps { 2 } else { 1 }; @@ -181,7 +181,7 @@ impl<'a, 'b, V: Verifier, C: ContextObject> Interpreter<'a, 'b, V, C> { } match insn.opc { - _ if dst == STACK_PTR_REG && config.dynamic_stack_frames => { + _ if dst == STACK_PTR_REG && self.vm.executable.get_sbpf_version().dynamic_stack_frames() => { // Let the stack overflow. For legitimate programs, this is a nearly // impossible condition to hit since programs are metered and we already // enforce a maximum call depth. For programs that intentionally mess @@ -420,7 +420,7 @@ impl<'a, 'b, V: Verifier, C: ContextObject> Interpreter<'a, 'b, V, C> { if !self.check_pc(pc) { return false; } - if config.static_syscalls && self.vm.executable.lookup_internal_function(self.pc as u32).is_none() { + if self.vm.executable.get_sbpf_version().static_syscalls() && self.vm.executable.lookup_internal_function(self.pc as u32).is_none() { self.due_insn_count += 1; throw_error!(self, EbpfError::UnsupportedInstruction(self.pc + ebpf::ELF_INSN_DUMP_OFFSET)); } @@ -430,7 +430,7 @@ impl<'a, 'b, V: Verifier, C: ContextObject> Interpreter<'a, 'b, V, C> { // changed after the program has been verified. ebpf::CALL_IMM => { let mut resolved = false; - let (external, internal) = if config.static_syscalls { + let (external, internal) = if self.vm.executable.get_sbpf_version().static_syscalls() { (insn.src == 0, insn.src != 0) } else { (true, true) @@ -500,7 +500,7 @@ impl<'a, 'b, V: Verifier, C: ContextObject> Interpreter<'a, 'b, V, C> { self.reg[ebpf::FIRST_SCRATCH_REG ..ebpf::FIRST_SCRATCH_REG + ebpf::SCRATCH_REGS] .copy_from_slice(&frame.caller_saved_registers); - if !config.dynamic_stack_frames { + if !self.vm.executable.get_sbpf_version().dynamic_stack_frames() { let stack_frame_size = config.stack_frame_size * if config.enable_stack_frame_gaps { 2 } else { 1 }; self.vm.env.stack_pointer -= stack_frame_size as u64; diff --git a/src/jit.rs b/src/jit.rs index bc53d25c..049d001d 100644 --- a/src/jit.rs +++ b/src/jit.rs @@ -392,7 +392,7 @@ impl<'a, V: Verifier, C: ContextObject> JitCompiler<'a, V, C> { let target_pc = (self.pc as isize + insn.off as isize + 1) as usize; match insn.opc { - _ if insn.dst == STACK_PTR_REG as u8 && self.config.dynamic_stack_frames => { + _ if insn.dst == STACK_PTR_REG as u8 && self.executable.get_sbpf_version().dynamic_stack_frames() => { let stack_ptr_access = X86IndirectAccess::Offset(self.slot_on_environment_stack(RuntimeEnvironmentSlot::StackPointer)); match insn.opc { ebpf::SUB64_IMM => self.emit_ins(X86Instruction::alu(OperandSize::S64, 0x81, 5, RBP, insn.imm, Some(stack_ptr_access))), @@ -592,7 +592,7 @@ impl<'a, V: Verifier, C: ContextObject> JitCompiler<'a, V, C> { // For JIT, external functions MUST be registered at compile time. let mut resolved = false; - let (external, internal) = if self.config.static_syscalls { + let (external, internal) = if self.executable.get_sbpf_version().static_syscalls() { (insn.src == 0, insn.src != 0) } else { (true, true) @@ -639,7 +639,7 @@ impl<'a, V: Verifier, C: ContextObject> JitCompiler<'a, V, C> { self.emit_ins(X86Instruction::alu(OperandSize::S64, 0x81, 5, REGISTER_MAP[FRAME_PTR_REG], 1, None)); self.emit_ins(X86Instruction::store(OperandSize::S64, REGISTER_MAP[FRAME_PTR_REG], RBP, call_depth_access)); - if !self.config.dynamic_stack_frames { + if !self.executable.get_sbpf_version().dynamic_stack_frames() { let stack_pointer_access = X86IndirectAccess::Offset(self.slot_on_environment_stack(RuntimeEnvironmentSlot::StackPointer)); let stack_frame_size = self.config.stack_frame_size as i64 * if self.config.enable_stack_frame_gaps { 2 } else { 1 }; self.emit_ins(X86Instruction::alu(OperandSize::S64, 0x81, 5, RBP, stack_frame_size, Some(stack_pointer_access))); // env.stack_pointer -= stack_frame_size; @@ -1391,7 +1391,7 @@ impl<'a, V: Verifier, C: ContextObject> JitCompiler<'a, V, C> { // Setup the frame pointer for the new frame. What we do depends on whether we're using dynamic or fixed frames. let stack_pointer_access = X86IndirectAccess::Offset(self.slot_on_environment_stack(RuntimeEnvironmentSlot::StackPointer)); - if !self.config.dynamic_stack_frames { + if !self.executable.get_sbpf_version().dynamic_stack_frames() { // With fixed frames we start the new frame at the next fixed offset let stack_frame_size = self.config.stack_frame_size as i64 * if self.config.enable_stack_frame_gaps { 2 } else { 1 }; self.emit_ins(X86Instruction::alu(OperandSize::S64, 0x81, 0, RBP, stack_frame_size, Some(stack_pointer_access))); // env.stack_pointer += stack_frame_size; @@ -1530,7 +1530,7 @@ impl<'a, V: Verifier, C: ContextObject> JitCompiler<'a, V, C> { } // There is no `VerifierError::JumpToMiddleOfLDDW` for `call imm` so patch it here let call_unsupported_instruction = self.anchors[ANCHOR_CALL_UNSUPPORTED_INSTRUCTION] as usize; - if self.config.static_syscalls { + if self.executable.get_sbpf_version().static_syscalls() { let mut prev_pc = 0; for current_pc in self.executable.get_function_registry().keys() { if *current_pc as usize >= self.result.pc_section.len() { @@ -1552,6 +1552,7 @@ impl<'a, V: Verifier, C: ContextObject> JitCompiler<'a, V, C> { mod tests { use super::*; use crate::{ + elf::SBPFVersion, syscalls, verifier::TautologyVerifier, vm::{BuiltinProgram, FunctionRegistry, TestContextObject}, @@ -1572,7 +1573,7 @@ mod tests { stopwatch_numerator: 0, stopwatch_denominator: 0, program_result: ProgramResult::Ok(0), - memory_mapping: MemoryMapping::new(Vec::new(), &config).unwrap(), + memory_mapping: MemoryMapping::new(Vec::new(), &config, &SBPFVersion::V2).unwrap(), call_frames: Vec::new(), }; @@ -1614,6 +1615,7 @@ mod tests { Executable::::from_text_bytes( program, Arc::new(loader), + SBPFVersion::V2, function_registry, ) .unwrap() diff --git a/src/memory_region.rs b/src/memory_region.rs index 2d3c116a..fc664918 100644 --- a/src/memory_region.rs +++ b/src/memory_region.rs @@ -3,6 +3,7 @@ use crate::{ aligned_memory::Pod, ebpf, + elf::SBPFVersion, error::EbpfError, vm::{Config, ProgramResult}, }; @@ -188,6 +189,8 @@ pub struct UnalignedMemoryMapping<'a> { cache: UnsafeCell, /// VM configuration config: &'a Config, + /// Executable sbpf_version + sbpf_version: &'a SBPFVersion, /// CoW callback cow_cb: Option, } @@ -239,6 +242,7 @@ impl<'a> UnalignedMemoryMapping<'a> { mut regions: Vec, cow_cb: Option, config: &'a Config, + sbpf_version: &'a SBPFVersion, ) -> Result { regions.sort(); for index in 1..regions.len() { @@ -257,6 +261,7 @@ impl<'a> UnalignedMemoryMapping<'a> { region_addresses: vec![0; regions.len()].into_boxed_slice(), cache: UnsafeCell::new(MappingCache::new()), config, + sbpf_version, cow_cb, }; result.construct_eytzinger_order(&mut regions, 0, 0); @@ -264,8 +269,12 @@ impl<'a> UnalignedMemoryMapping<'a> { } /// Creates a new UnalignedMemoryMapping structure from the given regions - pub fn new(regions: Vec, config: &'a Config) -> Result { - Self::new_internal(regions, None, config) + pub fn new( + regions: Vec, + config: &'a Config, + sbpf_version: &'a SBPFVersion, + ) -> Result { + Self::new_internal(regions, None, config, sbpf_version) } /// Creates a new UnalignedMemoryMapping from the given regions. @@ -275,8 +284,9 @@ impl<'a> UnalignedMemoryMapping<'a> { regions: Vec, cow_cb: MemoryCowCallback, config: &'a Config, + sbpf_version: &'a SBPFVersion, ) -> Result { - Self::new_internal(regions, Some(cow_cb), config) + Self::new_internal(regions, Some(cow_cb), config, sbpf_version) } #[allow(clippy::arithmetic_side_effects)] @@ -319,7 +329,16 @@ impl<'a> UnalignedMemoryMapping<'a> { let region = match self.find_region(cache, vm_addr) { Some(res) => res, - None => return generate_access_violation(self.config, access_type, vm_addr, len, pc), + None => { + return generate_access_violation( + self.config, + self.sbpf_version, + access_type, + vm_addr, + len, + pc, + ) + } }; if access_type == AccessType::Load || ensure_writable_region(region, &self.cow_cb) { @@ -328,7 +347,14 @@ impl<'a> UnalignedMemoryMapping<'a> { } } - generate_access_violation(self.config, access_type, vm_addr, len, pc) + generate_access_violation( + self.config, + self.sbpf_version, + access_type, + vm_addr, + len, + pc, + ) } /// Loads `size_of::()` bytes from the given address. @@ -357,7 +383,14 @@ impl<'a> UnalignedMemoryMapping<'a> { region } None => { - return generate_access_violation(self.config, AccessType::Load, vm_addr, len, pc) + return generate_access_violation( + self.config, + self.sbpf_version, + AccessType::Load, + vm_addr, + len, + pc, + ) } }; @@ -396,6 +429,7 @@ impl<'a> UnalignedMemoryMapping<'a> { generate_access_violation( self.config, + self.sbpf_version, AccessType::Load, initial_vm_addr, initial_len, @@ -431,7 +465,14 @@ impl<'a> UnalignedMemoryMapping<'a> { region } _ => { - return generate_access_violation(self.config, AccessType::Store, vm_addr, len, pc) + return generate_access_violation( + self.config, + self.sbpf_version, + AccessType::Store, + vm_addr, + len, + pc, + ) } }; @@ -469,6 +510,7 @@ impl<'a> UnalignedMemoryMapping<'a> { generate_access_violation( self.config, + self.sbpf_version, AccessType::Store, initial_vm_addr, initial_len, @@ -494,7 +536,10 @@ impl<'a> UnalignedMemoryMapping<'a> { return Ok(region); } } - Err(generate_access_violation(self.config, access_type, vm_addr, 0, 0).unwrap_err()) + Err( + generate_access_violation(self.config, self.sbpf_version, access_type, vm_addr, 0, 0) + .unwrap_err(), + ) } /// Returns the `MemoryRegion`s in this mapping @@ -520,6 +565,8 @@ pub struct AlignedMemoryMapping<'a> { regions: Box<[MemoryRegion]>, /// VM configuration config: &'a Config, + /// Executable sbpf_version + sbpf_version: &'a SBPFVersion, /// CoW callback cow_cb: Option, } @@ -546,6 +593,7 @@ impl<'a> AlignedMemoryMapping<'a> { mut regions: Vec, cow_cb: Option, config: &'a Config, + sbpf_version: &'a SBPFVersion, ) -> Result { regions.insert(0, MemoryRegion::new_readonly(&[], 0)); regions.sort(); @@ -562,13 +610,18 @@ impl<'a> AlignedMemoryMapping<'a> { Ok(Self { regions: regions.into_boxed_slice(), config, + sbpf_version, cow_cb, }) } /// Creates a new MemoryMapping structure from the given regions - pub fn new(regions: Vec, config: &'a Config) -> Result { - Self::new_internal(regions, None, config) + pub fn new( + regions: Vec, + config: &'a Config, + sbpf_version: &'a SBPFVersion, + ) -> Result { + Self::new_internal(regions, None, config, sbpf_version) } /// Creates a new MemoryMapping structure from the given regions. @@ -578,8 +631,9 @@ impl<'a> AlignedMemoryMapping<'a> { regions: Vec, cow_cb: MemoryCowCallback, config: &'a Config, + sbpf_version: &'a SBPFVersion, ) -> Result { - Self::new_internal(regions, Some(cow_cb), config) + Self::new_internal(regions, Some(cow_cb), config, sbpf_version) } /// Given a list of regions translate from virtual machine to host address @@ -595,7 +649,14 @@ impl<'a> AlignedMemoryMapping<'a> { } } } - generate_access_violation(self.config, access_type, vm_addr, len, pc) + generate_access_violation( + self.config, + self.sbpf_version, + access_type, + vm_addr, + len, + pc, + ) } /// Loads `size_of::()` bytes from the given address. @@ -651,7 +712,10 @@ impl<'a> AlignedMemoryMapping<'a> { return Ok(region); } } - Err(generate_access_violation(self.config, access_type, vm_addr, 0, 0).unwrap_err()) + Err( + generate_access_violation(self.config, self.sbpf_version, access_type, vm_addr, 0, 0) + .unwrap_err(), + ) } /// Returns the `MemoryRegion`s in this mapping @@ -702,11 +766,15 @@ impl<'a> MemoryMapping<'a> { /// /// Uses aligned or unaligned memory mapping depending on the value of /// `config.aligned_memory_mapping=true`. - pub fn new(regions: Vec, config: &'a Config) -> Result { + pub fn new( + regions: Vec, + config: &'a Config, + sbpf_version: &'a SBPFVersion, + ) -> Result { if config.aligned_memory_mapping { - AlignedMemoryMapping::new(regions, config).map(MemoryMapping::Aligned) + AlignedMemoryMapping::new(regions, config, sbpf_version).map(MemoryMapping::Aligned) } else { - UnalignedMemoryMapping::new(regions, config).map(MemoryMapping::Unaligned) + UnalignedMemoryMapping::new(regions, config, sbpf_version).map(MemoryMapping::Unaligned) } } @@ -718,11 +786,13 @@ impl<'a> MemoryMapping<'a> { regions: Vec, cow_cb: MemoryCowCallback, config: &'a Config, + sbpf_version: &'a SBPFVersion, ) -> Result { if config.aligned_memory_mapping { - AlignedMemoryMapping::new_with_cow(regions, cow_cb, config).map(MemoryMapping::Aligned) + AlignedMemoryMapping::new_with_cow(regions, cow_cb, config, sbpf_version) + .map(MemoryMapping::Aligned) } else { - UnalignedMemoryMapping::new_with_cow(regions, cow_cb, config) + UnalignedMemoryMapping::new_with_cow(regions, cow_cb, config, sbpf_version) .map(MemoryMapping::Unaligned) } } @@ -818,6 +888,7 @@ fn ensure_writable_region(region: &MemoryRegion, cow_cb: &Option(ebpf::MM_INPUT_START, 0).unwrap(), 0xff); @@ -1477,6 +1561,7 @@ mod test { MemoryRegion::new_readonly(&mem2, ebpf::MM_INPUT_START + 4), ], &config, + &SBPFVersion::V2, ) .unwrap(); assert_eq!( @@ -1504,6 +1589,7 @@ mod test { MemoryRegion::new_readonly(&mem2, ebpf::MM_INPUT_START + mem1.len() as u64), ], &config, + &SBPFVersion::V2, ) .unwrap(); m.store(0x11223344, ebpf::MM_INPUT_START, 0).unwrap(); @@ -1521,6 +1607,7 @@ mod test { MemoryRegion::new_readonly(&mem2, ebpf::MM_INPUT_START + mem1.len() as u64), ], &config, + &SBPFVersion::V2, ) .unwrap(); @@ -1594,6 +1681,7 @@ mod test { MemoryRegion::new_readonly(&mem2, ebpf::MM_STACK_START), ], &config, + &SBPFVersion::V2, ) .unwrap(); @@ -1650,6 +1738,7 @@ mod test { Ok(c.borrow().as_slice().as_ptr() as u64) }), &config, + &SBPFVersion::V2, ) .unwrap(); @@ -1684,6 +1773,7 @@ mod test { Ok(c.borrow().as_slice().as_ptr() as u64) }), &config, + &SBPFVersion::V2, ) .unwrap(); @@ -1729,6 +1819,7 @@ mod test { Ok(c.borrow().as_slice().as_ptr() as u64) }), &config, + &SBPFVersion::V2, ) .unwrap(); @@ -1748,6 +1839,7 @@ mod test { vec![MemoryRegion::new_cow(&original, ebpf::MM_PROGRAM_START, 42)], Box::new(|_| Err(())), &config, + &SBPFVersion::V2, ) .unwrap(); @@ -1765,6 +1857,7 @@ mod test { vec![MemoryRegion::new_cow(&original, ebpf::MM_PROGRAM_START, 42)], Box::new(|_| Err(())), &config, + &SBPFVersion::V2, ) .unwrap(); diff --git a/src/static_analysis.rs b/src/static_analysis.rs index 61ca75c6..fb0a44cd 100644 --- a/src/static_analysis.rs +++ b/src/static_analysis.rs @@ -454,6 +454,7 @@ impl<'a> Analysis<'a> { &self.cfg_nodes, self.executable.get_function_registry(), self.executable.get_loader(), + self.executable.get_sbpf_version(), ) } diff --git a/src/syscalls.rs b/src/syscalls.rs index 2741937b..8359f355 100644 --- a/src/syscalls.rs +++ b/src/syscalls.rs @@ -56,13 +56,11 @@ pub const BPF_TRACE_PRINTK_IDX: u32 = 6; /// # Examples /// /// ``` -/// use solana_rbpf::syscalls::bpf_trace_printf; -/// use solana_rbpf::memory_region::{MemoryRegion, MemoryMapping}; -/// use solana_rbpf::vm::{Config, ProgramResult, TestContextObject}; +/// use solana_rbpf::{elf::SBPFVersion, memory_region::{MemoryRegion, MemoryMapping}, syscalls::bpf_trace_printf, vm::{Config, ProgramResult, TestContextObject}}; /// /// let mut result = ProgramResult::Ok(0); /// let config = Config::default(); -/// let mut memory_mapping = MemoryMapping::new(vec![], &config).unwrap(); +/// let mut memory_mapping = MemoryMapping::new(vec![], &config, &SBPFVersion::V2).unwrap(); /// bpf_trace_printf(&mut TestContextObject::default(), 0, 0, 1, 15, 32, &mut memory_mapping, &mut result); /// assert_eq!(result.unwrap() as usize, "bpf_trace_printf: 0x1, 0xf, 0x20\n".len()); /// ``` @@ -121,13 +119,11 @@ pub fn bpf_trace_printf( /// # Examples /// /// ``` -/// use solana_rbpf::syscalls::bpf_gather_bytes; -/// use solana_rbpf::memory_region::{MemoryRegion, MemoryMapping}; -/// use solana_rbpf::vm::{Config, ProgramResult, TestContextObject}; +/// use solana_rbpf::{elf::SBPFVersion, memory_region::{MemoryRegion, MemoryMapping}, syscalls::bpf_gather_bytes, vm::{Config, ProgramResult, TestContextObject}}; /// /// let mut result = ProgramResult::Ok(0); /// let config = Config::default(); -/// let mut memory_mapping = MemoryMapping::new(vec![], &config).unwrap(); +/// let mut memory_mapping = MemoryMapping::new(vec![], &config, &SBPFVersion::V2).unwrap(); /// bpf_gather_bytes(&mut TestContextObject::default(), 0x11, 0x22, 0x33, 0x44, 0x55, &mut memory_mapping, &mut result); /// assert_eq!(result.unwrap(), 0x1122334455); /// ``` @@ -157,16 +153,14 @@ pub fn bpf_gather_bytes( /// # Examples /// /// ``` -/// use solana_rbpf::syscalls::bpf_mem_frob; -/// use solana_rbpf::memory_region::{MemoryRegion, MemoryMapping}; -/// use solana_rbpf::vm::{Config, ProgramResult, TestContextObject}; +/// use solana_rbpf::{elf::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; /// /// let mut result = ProgramResult::Ok(0); /// let config = Config::default(); -/// let mut memory_mapping = MemoryMapping::new(vec![MemoryRegion::new_writable(val, val_va)], &config).unwrap(); +/// let mut memory_mapping = MemoryMapping::new(vec![MemoryRegion::new_writable(val, val_va)], &config, &SBPFVersion::V2).unwrap(); /// bpf_mem_frob(&mut TestContextObject::default(), val_va, 8, 0, 0, 0, &mut memory_mapping, &mut result); /// assert_eq!(val, &[0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x3b, 0x08, 0x19]); /// bpf_mem_frob(&mut TestContextObject::default(), val_va, 8, 0, 0, 0, &mut memory_mapping, &mut result); @@ -200,9 +194,7 @@ pub fn bpf_mem_frob( /// # Examples /// /// ``` -/// use solana_rbpf::syscalls::bpf_str_cmp; -/// use solana_rbpf::memory_region::{MemoryRegion, MemoryMapping}; -/// use solana_rbpf::vm::{Config, ProgramResult, TestContextObject}; +/// use solana_rbpf::{elf::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."; @@ -211,11 +203,11 @@ pub fn bpf_mem_frob( /// /// let mut result = ProgramResult::Ok(0); /// let config = Config::default(); -/// let mut memory_mapping = MemoryMapping::new(vec![MemoryRegion::new_readonly(foo.as_bytes(), va_foo)], &config).unwrap(); +/// let mut memory_mapping = MemoryMapping::new(vec![MemoryRegion::new_readonly(foo.as_bytes(), va_foo)], &config, &SBPFVersion::V2).unwrap(); /// bpf_str_cmp(&mut TestContextObject::default(), va_foo, va_foo, 0, 0, 0, &mut memory_mapping, &mut result); /// assert!(result.unwrap() == 0); /// let mut result = ProgramResult::Ok(0); -/// let mut memory_mapping = MemoryMapping::new(vec![MemoryRegion::new_readonly(foo.as_bytes(), va_foo), MemoryRegion::new_readonly(bar.as_bytes(), va_bar)], &config).unwrap(); +/// let mut memory_mapping = MemoryMapping::new(vec![MemoryRegion::new_readonly(foo.as_bytes(), va_foo), MemoryRegion::new_readonly(bar.as_bytes(), va_bar)], &config, &SBPFVersion::V2).unwrap(); /// bpf_str_cmp(&mut TestContextObject::default(), va_foo, va_bar, 0, 0, 0, &mut memory_mapping, &mut result); /// assert!(result.unwrap() != 0); /// ``` diff --git a/src/verifier.rs b/src/verifier.rs index 55f849bd..b0485b03 100644 --- a/src/verifier.rs +++ b/src/verifier.rs @@ -25,6 +25,7 @@ use crate::{ ebpf, + elf::SBPFVersion, vm::{Config, FunctionRegistry}, }; use thiserror::Error; @@ -98,6 +99,7 @@ pub trait Verifier { fn verify( prog: &[u8], config: &Config, + sbpf_version: &SBPFVersion, function_registry: &FunctionRegistry, ) -> Result<(), VerifierError>; } @@ -172,7 +174,7 @@ fn check_registers( insn: &ebpf::Insn, store: bool, insn_ptr: usize, - enable_stack_ptr: bool, + sbpf_version: &SBPFVersion, ) -> Result<(), VerifierError> { if insn.src > 10 { return Err(VerifierError::InvalidSourceRegister(adj_insn_ptr(insn_ptr))); @@ -181,7 +183,8 @@ fn check_registers( match (insn.dst, store) { (0..=9, _) | (10, true) => Ok(()), (11, _) - if enable_stack_ptr && (insn.opc == ebpf::SUB64_IMM || insn.opc == ebpf::ADD64_IMM) => + if sbpf_version.dynamic_stack_frames() + && (insn.opc == ebpf::SUB64_IMM || insn.opc == ebpf::ADD64_IMM) => { Ok(()) } @@ -223,7 +226,7 @@ pub struct RequisiteVerifier {} impl Verifier for RequisiteVerifier { /// Check the program against the verifier's rules #[rustfmt::skip] - fn verify(prog: &[u8], config: &Config, function_registry: &FunctionRegistry) -> Result<(), VerifierError> { + fn verify(prog: &[u8], config: &Config, sbpf_version: &SBPFVersion, function_registry: &FunctionRegistry) -> Result<(), VerifierError> { check_prog_len(prog)?; let program_range = 0..prog.len() / ebpf::INSN_SIZE; @@ -234,7 +237,7 @@ impl Verifier for RequisiteVerifier { let insn = ebpf::get_insn(prog, insn_ptr); let mut store = false; - if config.static_syscalls && function_iter.peek() == Some(&insn_ptr) { + if sbpf_version.static_syscalls() && function_iter.peek() == Some(&insn_ptr) { function_range.start = function_iter.next().unwrap_or(0); function_range.end = *function_iter.peek().unwrap_or(&program_range.end); if insn.opc == 0 { @@ -284,8 +287,8 @@ impl Verifier for RequisiteVerifier { ebpf::MUL32_REG => {}, ebpf::DIV32_IMM => { check_imm_nonzero(&insn, insn_ptr)?; }, ebpf::DIV32_REG => {}, - ebpf::SDIV32_IMM if config.enable_sdiv => { check_imm_nonzero(&insn, insn_ptr)?; }, - ebpf::SDIV32_REG if config.enable_sdiv => {}, + ebpf::SDIV32_IMM if sbpf_version.enable_sdiv() => { check_imm_nonzero(&insn, insn_ptr)?; }, + ebpf::SDIV32_REG if sbpf_version.enable_sdiv() => {}, ebpf::OR32_IMM => {}, ebpf::OR32_REG => {}, ebpf::AND32_IMM => {}, @@ -315,8 +318,8 @@ impl Verifier for RequisiteVerifier { ebpf::MUL64_REG => {}, ebpf::DIV64_IMM => { check_imm_nonzero(&insn, insn_ptr)?; }, ebpf::DIV64_REG => {}, - ebpf::SDIV64_IMM if config.enable_sdiv => { check_imm_nonzero(&insn, insn_ptr)?; }, - ebpf::SDIV64_REG if config.enable_sdiv => {}, + ebpf::SDIV64_IMM if sbpf_version.enable_sdiv() => { check_imm_nonzero(&insn, insn_ptr)?; }, + ebpf::SDIV64_REG if sbpf_version.enable_sdiv() => {}, ebpf::OR64_IMM => {}, ebpf::OR64_REG => {}, ebpf::AND64_IMM => {}, @@ -359,7 +362,7 @@ impl Verifier for RequisiteVerifier { ebpf::JSLT_REG => { check_jmp_offset(prog, insn_ptr, &function_range)?; }, ebpf::JSLE_IMM => { check_jmp_offset(prog, insn_ptr, &function_range)?; }, ebpf::JSLE_REG => { check_jmp_offset(prog, insn_ptr, &function_range)?; }, - ebpf::CALL_IMM if config.static_syscalls && insn.src != 0 => { check_jmp_offset(prog, insn_ptr, &program_range)?; }, + ebpf::CALL_IMM if sbpf_version.static_syscalls() && insn.src != 0 => { check_jmp_offset(prog, insn_ptr, &program_range)?; }, ebpf::CALL_IMM => {}, ebpf::CALL_REG => { check_imm_register(&insn, insn_ptr, config)?; }, ebpf::EXIT => {}, @@ -369,7 +372,7 @@ impl Verifier for RequisiteVerifier { } } - check_registers(&insn, store, insn_ptr, config.dynamic_stack_frames)?; + check_registers(&insn, store, insn_ptr, sbpf_version)?; insn_ptr += 1; } @@ -390,6 +393,7 @@ impl Verifier for TautologyVerifier { fn verify( _prog: &[u8], _config: &Config, + _sbpf_version: &SBPFVersion, _function_registry: &FunctionRegistry, ) -> std::result::Result<(), VerifierError> { Ok(()) diff --git a/src/vm.rs b/src/vm.rs index 9257dee8..4d9c0397 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -14,7 +14,7 @@ use crate::{ ebpf, - elf::Executable, + elf::{Executable, SBPFVersion}, error::EbpfError, interpreter::Interpreter, memory_region::MemoryMapping, @@ -228,24 +228,16 @@ pub struct Config { pub external_internal_function_hash_collision: bool, /// Have the verifier reject "callx r10" pub reject_callx_r10: bool, - /// Use dynamic stack frame sizes - pub dynamic_stack_frames: bool, - /// Enable native signed division - pub enable_sdiv: bool, /// Avoid copying read only sections when possible pub optimize_rodata: bool, - /// Support syscalls via pseudo calls (insn.src = 0) - pub static_syscalls: bool, - /// Allow sh_addr != sh_offset in elf sections. Used in SBFv2 to align - /// section vaddrs to MM_PROGRAM_START. - pub enable_elf_vaddr: bool, /// Use the new ELF parser pub new_elf_parser: bool, - /// Ensure that rodata sections don't exceed their maximum allowed size and - /// overlap with the stack - pub reject_rodata_stack_overlap: bool, /// Use aligned memory mapping pub aligned_memory_mapping: bool, + /// Allow ExecutableCapability::V1 + pub enable_sbpf_v1: bool, + /// Allow ExecutableCapability::V2 + pub enable_sbpf_v2: bool, } impl Config { @@ -273,14 +265,11 @@ impl Default for Config { >> PROGRAM_ENVIRONMENT_KEY_SHIFT, external_internal_function_hash_collision: true, reject_callx_r10: true, - dynamic_stack_frames: true, - enable_sdiv: true, optimize_rodata: true, - static_syscalls: true, - enable_elf_vaddr: true, new_elf_parser: true, - reject_rodata_stack_overlap: true, aligned_memory_mapping: true, + enable_sbpf_v1: true, + enable_sbpf_v2: true, } } } @@ -296,9 +285,10 @@ impl Executable { pub fn from_text_bytes( text_bytes: &[u8], loader: Arc>, + sbpf_version: SBPFVersion, function_registry: FunctionRegistry, ) -> Result { - Executable::new_from_text_bytes(text_bytes, loader, function_registry) + Executable::new_from_text_bytes(text_bytes, loader, sbpf_version, function_registry) .map_err(EbpfError::ElfError) } } @@ -418,7 +408,7 @@ pub struct RuntimeEnvironment<'a, C: ContextObject> { /// /// The stack pointer isn't exposed as an actual register. Only sub and add /// instructions (typically generated by the LLVM backend) are allowed to - /// access it when config.dynamic_stack_frames=true. Its value is only + /// access it when sbpf_version.dynamic_stack_frames()=true. Its value is only /// stored here and therefore the register is not tracked in REGISTER_MAP. pub stack_pointer: u64, /// Pointer to ContextObject @@ -445,7 +435,7 @@ pub struct RuntimeEnvironment<'a, C: ContextObject> { /// use solana_rbpf::{ /// aligned_memory::AlignedMemory, /// ebpf, -/// elf::Executable, +/// elf::{Executable, SBPFVersion}, /// memory_region::{MemoryMapping, MemoryRegion}, /// verifier::{TautologyVerifier, RequisiteVerifier}, /// vm::{BuiltinProgram, Config, EbpfVm, FunctionRegistry, TestContextObject}, @@ -460,7 +450,7 @@ pub struct RuntimeEnvironment<'a, C: ContextObject> { /// /// let loader = std::sync::Arc::new(BuiltinProgram::new_loader(Config::default())); /// let function_registry = FunctionRegistry::default(); -/// let mut executable = Executable::::from_text_bytes(prog, loader, function_registry).unwrap(); +/// let mut executable = Executable::::from_text_bytes(prog, loader, SBPFVersion::V2, function_registry).unwrap(); /// let verified_executable = Executable::::verified(executable).unwrap(); /// let mut context_object = TestContextObject::new(1); /// let config = verified_executable.get_config(); @@ -471,20 +461,15 @@ pub struct RuntimeEnvironment<'a, C: ContextObject> { /// /// let regions: Vec = vec![ /// verified_executable.get_ro_region(), -/// MemoryRegion::new_writable_gapped( +/// MemoryRegion::new_writable( /// stack.as_slice_mut(), /// ebpf::MM_STACK_START, -/// if !config.dynamic_stack_frames && config.enable_stack_frame_gaps { -/// config.stack_frame_size as u64 -/// } else { -/// 0 -/// }, /// ), /// MemoryRegion::new_writable(heap.as_slice_mut(), ebpf::MM_HEAP_START), /// MemoryRegion::new_writable(mem, ebpf::MM_INPUT_START), /// ]; /// -/// let memory_mapping = MemoryMapping::new(regions, config).unwrap(); +/// let memory_mapping = MemoryMapping::new(regions, config, verified_executable.get_sbpf_version()).unwrap(); /// /// let mut vm = EbpfVm::new(&verified_executable, &mut context_object, memory_mapping, stack_len); /// @@ -510,7 +495,10 @@ impl<'a, V: Verifier, C: ContextObject> EbpfVm<'a, V, C> { stack_len: usize, ) -> EbpfVm<'a, V, C> { let config = executable.get_config(); - let stack_pointer = ebpf::MM_STACK_START.saturating_add(if config.dynamic_stack_frames { + let stack_pointer = ebpf::MM_STACK_START.saturating_add(if executable + .get_sbpf_version() + .dynamic_stack_frames() + { // the stack is fully descending, frames start as empty and change size anytime r11 is modified stack_len } else { diff --git a/test_utils/src/lib.rs b/test_utils/src/lib.rs index d9f449d4..e45340de 100644 --- a/test_utils/src/lib.rs +++ b/test_utils/src/lib.rs @@ -167,12 +167,13 @@ pub fn create_memory_mapping<'a, V: Verifier, C: ContextObject>( cow_cb: Option, ) -> Result, EbpfError> { let config = executable.get_config(); + let sbpf_version = executable.get_sbpf_version(); let regions: Vec = vec![ executable.get_ro_region(), MemoryRegion::new_writable_gapped( stack.as_slice_mut(), ebpf::MM_STACK_START, - if !config.dynamic_stack_frames && config.enable_stack_frame_gaps { + if !sbpf_version.dynamic_stack_frames() && config.enable_stack_frame_gaps { config.stack_frame_size as u64 } else { 0 @@ -185,9 +186,9 @@ pub fn create_memory_mapping<'a, V: Verifier, C: ContextObject>( .collect(); Ok(if let Some(cow_cb) = cow_cb { - MemoryMapping::new_with_cow(regions, cow_cb, config)? + MemoryMapping::new_with_cow(regions, cow_cb, config, sbpf_version)? } else { - MemoryMapping::new(regions, config)? + MemoryMapping::new(regions, config, sbpf_version)? }) } diff --git a/tests/execution.rs b/tests/execution.rs index be3092fb..ea71c6d3 100644 --- a/tests/execution.rs +++ b/tests/execution.rs @@ -18,7 +18,7 @@ use rand::{rngs::SmallRng, RngCore, SeedableRng}; use solana_rbpf::{ assembler::assemble, ebpf, - elf::Executable, + elf::{Executable, SBPFVersion}, error::EbpfError, memory_region::{AccessType, MemoryMapping, MemoryRegion}, static_analysis::Analysis, @@ -2302,7 +2302,7 @@ fn test_string_stack() { #[test] fn test_err_fixed_stack_out_of_bound() { let config = Config { - dynamic_stack_frames: false, + enable_sbpf_v2: false, max_call_depth: 3, ..Config::default() }; @@ -2327,7 +2327,7 @@ fn test_err_fixed_stack_out_of_bound() { #[test] fn test_err_dynamic_stack_out_of_bound() { let config = Config { - dynamic_stack_frames: true, + enable_sbpf_v2: true, max_call_depth: 3, ..Config::default() }; @@ -2373,10 +2373,7 @@ fn test_err_dynamic_stack_out_of_bound() { #[test] fn test_err_dynamic_stack_ptr_overflow() { - let config = Config { - dynamic_stack_frames: true, - ..Config::default() - }; + let config = Config::default(); // See the comment in CallFrames::resize_stack() for the reason why it's // safe to let the stack pointer overflow @@ -2410,10 +2407,7 @@ fn test_err_dynamic_stack_ptr_overflow() { #[test] fn test_dynamic_stack_frames_empty() { - let config = Config { - dynamic_stack_frames: true, - ..Config::default() - }; + let config = Config::default(); // Check that unless explicitly resized the stack doesn't grow test_interpreter_and_jit_asm!( @@ -2433,10 +2427,7 @@ fn test_dynamic_stack_frames_empty() { #[test] fn test_dynamic_frame_ptr() { - let config = Config { - dynamic_stack_frames: true, - ..Config::default() - }; + let config = Config::default(); // Check that upon entering a function (foo) the frame pointer is advanced // to the top of the stack @@ -2482,9 +2473,9 @@ fn test_entrypoint_exit() { // can't infer anything from the stack size so we track call depth // explicitly. Make sure exit still works with both fixed and dynamic // frames. - for dynamic_stack_frames in [false, true] { + for enable_sbpf_v2 in [false, true] { let config = Config { - dynamic_stack_frames, + enable_sbpf_v2, ..Config::default() }; @@ -2510,9 +2501,9 @@ fn test_entrypoint_exit() { #[test] fn test_stack_call_depth_tracking() { - for dynamic_stack_frames in [false, true] { + for enable_sbpf_v2 in [false, true] { let config = Config { - dynamic_stack_frames, + enable_sbpf_v2, max_call_depth: 2, ..Config::default() }; @@ -2575,6 +2566,7 @@ fn test_err_mem_access_out_of_bound() { let mut executable = Executable::::from_text_bytes( &prog, loader.clone(), + SBPFVersion::V2, FunctionRegistry::default(), ) .unwrap(); @@ -2748,7 +2740,7 @@ fn test_err_static_jmp_lddw() { #[test] fn test_err_dynamic_jmp_lddw() { let config = Config { - static_syscalls: false, + enable_sbpf_v2: false, ..Config::default() }; test_interpreter_and_jit_asm!( @@ -3033,7 +3025,7 @@ fn nested_vm_syscall( fn test_nested_vm_syscall() { let config = Config::default(); let mut context_object = TestContextObject::default(); - let mut memory_mapping = MemoryMapping::new(vec![], &config).unwrap(); + let mut memory_mapping = MemoryMapping::new(vec![], &config, &SBPFVersion::V2).unwrap(); let mut result = ProgramResult::Ok(0); nested_vm_syscall( &mut context_object, @@ -3821,6 +3813,7 @@ fn execute_generated_program(prog: &[u8]) -> bool { enable_instruction_tracing: true, ..Config::default() })), + SBPFVersion::V2, FunctionRegistry::default(), ); let executable = if let Ok(executable) = executable { diff --git a/tests/verifier.rs b/tests/verifier.rs index 09a7b040..7629eeda 100644 --- a/tests/verifier.rs +++ b/tests/verifier.rs @@ -25,7 +25,7 @@ extern crate thiserror; use solana_rbpf::{ assembler::assemble, ebpf, - elf::Executable, + elf::{Executable, SBPFVersion}, verifier::{RequisiteVerifier, TautologyVerifier, Verifier, VerifierError}, vm::{BuiltinProgram, Config, FunctionRegistry, TestContextObject}, }; @@ -45,6 +45,7 @@ impl Verifier for ContradictionVerifier { fn verify( _prog: &[u8], _config: &Config, + _sbpf_version: &SBPFVersion, _function_registry: &FunctionRegistry, ) -> std::result::Result<(), VerifierError> { Err(VerifierError::NoProgram) @@ -113,6 +114,7 @@ fn test_verifier_err_endian_size() { let executable = Executable::::from_text_bytes( prog, Arc::new(BuiltinProgram::new_loader(Config::default())), + SBPFVersion::V2, FunctionRegistry::default(), ) .unwrap(); @@ -131,6 +133,7 @@ fn test_verifier_err_incomplete_lddw() { let executable = Executable::::from_text_bytes( prog, Arc::new(BuiltinProgram::new_loader(Config::default())), + SBPFVersion::V2, FunctionRegistry::default(), ) .unwrap(); @@ -140,15 +143,15 @@ fn test_verifier_err_incomplete_lddw() { #[test] fn test_verifier_err_invalid_reg_dst() { - // r11 is disabled when dynamic_stack_frames=false, and only sub and add are - // allowed when dynamic_stack_frames=true - for dynamic_stack_frames in [false, true] { + // r11 is disabled when sbpf_version.dynamic_stack_frames()=false, and only sub and add are + // allowed when sbpf_version.dynamic_stack_frames()=true + for enable_sbpf_v2 in [false, true] { let executable = assemble::( " mov r11, 1 exit", Arc::new(BuiltinProgram::new_loader(Config { - dynamic_stack_frames, + enable_sbpf_v2, ..Config::default() })), ) @@ -165,15 +168,15 @@ fn test_verifier_err_invalid_reg_dst() { #[test] fn test_verifier_err_invalid_reg_src() { - // r11 is disabled when dynamic_stack_frames=false, and only sub and add are - // allowed when dynamic_stack_frames=true - for dynamic_stack_frames in [false, true] { + // r11 is disabled when sbpf_version.dynamic_stack_frames()=false, and only sub and add are + // allowed when sbpf_version.dynamic_stack_frames()=true + for enable_sbpf_v2 in [false, true] { let executable = assemble::( " mov r0, r11 exit", Arc::new(BuiltinProgram::new_loader(Config { - dynamic_stack_frames, + enable_sbpf_v2, ..Config::default() })), ) @@ -288,6 +291,7 @@ fn test_verifier_err_unknown_opcode() { let executable = Executable::::from_text_bytes( prog, Arc::new(BuiltinProgram::new_loader(Config::default())), + SBPFVersion::V2, FunctionRegistry::default(), ) .unwrap(); @@ -367,19 +371,19 @@ fn test_sdiv_disabled() { ]; for (opc, instruction) in instructions { - for enable_sdiv in [true, false] { + for enable_sbpf_v2 in [true, false] { let assembly = format!("\n{instruction}\nexit"); let executable = assemble::( &assembly, Arc::new(BuiltinProgram::new_loader(Config { - enable_sdiv, + enable_sbpf_v2, ..Config::default() })), ) .unwrap(); let result = Executable::::verified(executable) .map_err(|err| format!("Executable constructor {err:?}")); - if enable_sdiv { + if enable_sbpf_v2 { assert!(result.is_ok()); } else { assert_eq!(