From fbcef7ffcfaced2c5b3a2cdaeef04d81bdcf56b1 Mon Sep 17 00:00:00 2001 From: Emanuele Cesena Date: Wed, 11 Dec 2024 15:34:59 +0000 Subject: [PATCH] vm: support V3 and static syscalls in VmInterpHarness --- src/vm_interp.rs | 118 +++++++++++++++++++++++------------------------ 1 file changed, 58 insertions(+), 60 deletions(-) diff --git a/src/vm_interp.rs b/src/vm_interp.rs index 813f3eb..fd4de52 100644 --- a/src/vm_interp.rs +++ b/src/vm_interp.rs @@ -19,7 +19,7 @@ use solana_program_runtime::{ elf::Executable, error::{EbpfError, StableResult}, memory_region::{MemoryMapping, MemoryRegion}, - program::{BuiltinFunction, BuiltinProgram, FunctionRegistry, SBPFVersion}, + program::{BuiltinProgram, FunctionRegistry, SBPFVersion}, verifier::RequisiteVerifier, vm::{Config, ContextObject, EbpfVm, TestContextObject}, }, @@ -31,11 +31,11 @@ declare_builtin_function!( SyscallStub, fn rust( _invoke_context: &mut TestContextObject, - _hash_addr: u64, - _recovery_id_val: u64, - _signature_addr: u64, - _result_addr: u64, - _arg5: u64, + _r1: u64, + _r2: u64, + _r3: u64, + _r4: u64, + _r5: u64, _memory_mapping: &mut MemoryMapping, ) -> Result { // TODO: deduct CUs? @@ -117,44 +117,62 @@ pub fn execute_vm_interp(syscall_context: SyscallContext) -> Option SBPFVersion::V0, }; - // stub syscalls - let syscall_reg = unstubbed_runtime.get_function_registry(sbpf_version); - let mut stubbed_syscall_reg = FunctionRegistry::>::default(); - - for (key, (name, _)) in syscall_reg.iter() { - stubbed_syscall_reg - .register_function(key, name, SyscallStub::vm) - .unwrap(); - } - + let enable_stack_frame_gaps = (sbpf_version == SBPFVersion::V0) + || !feature_set.is_active(&bpf_account_data_direct_mapping::id()); let config = &Config { - aligned_memory_mapping: true, enabled_sbpf_versions: SBPFVersion::V0..=sbpf_version, - enable_stack_frame_gaps: !feature_set.is_active(&bpf_account_data_direct_mapping::id()), + enable_stack_frame_gaps, enable_instruction_tracing: true, ..Config::default() }; - let program_runtime_environment_v1 = - BuiltinProgram::new_loader(config.clone(), stubbed_syscall_reg); - let loader = std::sync::Arc::new(program_runtime_environment_v1); + let mut loader = BuiltinProgram::new_loader_with_dense_registration(config.clone()); + + // Stub syscalls + // Note: unstubbed_runtime is "v1", so syscalls are only registered for version < V3, + // i.e. unstubbed_runtime.get_function_registry(sbpf_version) does NOT work. + let syscall_reg = unstubbed_runtime.get_function_registry(SBPFVersion::V0); + for (j, (_key, (name, _func))) in syscall_reg.iter().enumerate() { + loader + .register_function( + std::str::from_utf8(name).unwrap(), + j as u32, + SyscallStub::vm, + ) + .unwrap(); + } + let loader = std::sync::Arc::new(loader); - // Setup TestContextObject - let mut context_obj = TestContextObject::new(instr_ctx.cu_avail); + let function_registry = setup_internal_fn_registry(&vm_ctx); + let mut executable = + Executable::from_text_bytes(&vm_ctx.rodata, loader, sbpf_version, function_registry) + .unwrap(); - // setup memory - if vm_ctx.heap_max as usize > HEAP_MAX { - return None; + if executable.verify::().is_err() { + return Some(SyscallEffects { + error: -2, + ..Default::default() + }); } - let function_registry = setup_internal_fn_registry(&vm_ctx); + if !USE_INTERPRETER && executable.jit_compile().is_err() { + return Some(SyscallEffects { + error: -3, + ..Default::default() + }); + } + + // Setup TestContextObject + let mut context_obj = TestContextObject::new(instr_ctx.cu_avail); + // setup memory + let heap_max = (vm_ctx.heap_max as usize).min(HEAP_MAX); let syscall_inv = syscall_context.syscall_invocation.unwrap(); let mut mempool = VmMemoryPool::new(); let rodata = AlignedMemory::::from(&vm_ctx.rodata); let mut stack = mempool.get_stack(STACK_SIZE); - let mut heap = AlignedMemory::::from(&vec![0; vm_ctx.heap_max as usize]); + let mut heap = AlignedMemory::::from(&vec![0; heap_max]); let mut regions = vec![ MemoryRegion::new_readonly(rodata.as_slice(), ebpf::MM_RODATA_START), @@ -183,8 +201,8 @@ pub fn execute_vm_interp(syscall_context: SyscallContext) -> Option Option().is_err() { - return Some(SyscallEffects { - error: -2, - ..Default::default() - }); - } - - if executable.jit_compile().is_err() { - return Some(SyscallEffects { - error: -3, - ..Default::default() - }); - } - let (_, result) = vm.execute_program( &executable, USE_INTERPRETER, /* use JIT for fuzzing, interpreter for debugging */ @@ -322,10 +322,12 @@ fn setup_internal_fn_registry(vm_ctx: &VmContext) -> FunctionRegistry { let max_pc = vm_ctx.rodata.len() / 8; // register entry point + let entry_pc = (vm_ctx.entry_pc as usize).min(max_pc - 1); let _ = fn_reg.register_function( - ebpf::hash_symbol_name(b"entrypoint"), + // entry_pc as u32, + entry_pc as u32, b"entrypoint", - vm_ctx.entry_pc as usize, + entry_pc, ); let call_whitelist = &vm_ctx.call_whitelist; @@ -336,11 +338,7 @@ fn setup_internal_fn_registry(vm_ctx: &VmContext) -> FunctionRegistry { // ignore invalid pc, i.e. assume the test was set up correctly. // registering fn beyond max_pc segfaults inside the JIT. if pc < max_pc { - let _ = fn_reg.register_function( - ebpf::hash_symbol_name(&u64::to_le_bytes(pc as u64)), - b"fn", - pc, - ); + let _ = fn_reg.register_function(pc as u32, b"fn", pc); } } } @@ -367,13 +365,13 @@ fn process_result( which hashes the PC of the target function into the instruction immediate. The interpreter fuzzer uses this. */ - let pc = vm.registers[11]; - let insn = ebpf::get_insn_unchecked(executable.get_text_bytes().1, pc as usize); + let bytes = executable.get_text_bytes().1; + let insn_sz = bytes.len() / ebpf::INSN_SIZE; + let pc = (vm.registers[11] as usize).min(insn_sz - 1); + let insn = ebpf::get_insn_unchecked(bytes, pc); if insn.opc == ebpf::CALL_IMM { let pchash = insn.imm as u32; - if pchash_inverse(pchash) - > (executable.get_text_bytes().1.len() / ebpf::INSN_SIZE) as u32 - { + if pchash_inverse(pchash) > (insn_sz) as u32 { // need to simulate pushing a stack frame vm.call_depth += 1; EbpfError::CallOutsideTextSegment