From 37600eee3587d5111c0de31ea042a4c189e14513 Mon Sep 17 00:00:00 2001 From: Jack May Date: Mon, 6 Jun 2022 10:19:32 -0700 Subject: [PATCH] Verifier is not optional (#322) --- benches/elf_loader.rs | 3 - benches/jit_compile.rs | 25 +- benches/vm_execution.rs | 37 ++- cli/src/main.rs | 43 ++- examples/disassemble.rs | 2 - examples/to_json.rs | 2 - fuzz/Cargo.lock | 9 + fuzz/Cargo.toml | 1 + fuzz/fuzz_targets/dumb.rs | 16 +- fuzz/fuzz_targets/smart.rs | 16 +- fuzz/fuzz_targets/smart_jit_diff.rs | 24 +- fuzz/fuzz_targets/smarter_jit_diff.rs | 36 ++- fuzz/fuzz_targets/verify_semantic_aware.rs | 4 +- src/assembler.rs | 8 +- src/elf.rs | 10 +- src/interpreter.rs | 24 +- src/jit.rs | 1 - src/verifier.rs | 330 +++++++++++---------- src/vm.rs | 120 +++++--- test_utils/src/lib.rs | 10 + tests/assembler.rs | 3 - tests/disassembler.rs | 10 +- tests/misc.rs | 36 ++- tests/ubpf_execution.rs | 91 ++++-- tests/ubpf_verifier.rs | 192 ++++++++---- 25 files changed, 613 insertions(+), 440 deletions(-) diff --git a/benches/elf_loader.rs b/benches/elf_loader.rs index 77e075fd..64e52f96 100644 --- a/benches/elf_loader.rs +++ b/benches/elf_loader.rs @@ -39,7 +39,6 @@ fn bench_load_elf(bencher: &mut Bencher) { bencher.iter(|| { Executable::::from_elf( &elf, - None, Config::default(), syscall_registry(), ) @@ -55,7 +54,6 @@ fn bench_load_elf_without_syscall(bencher: &mut Bencher) { bencher.iter(|| { let executable = Executable::::from_elf( &elf, - None, Config::default(), syscall_registry(), ) @@ -72,7 +70,6 @@ fn bench_load_elf_with_syscall(bencher: &mut Bencher) { bencher.iter(|| { let executable = Executable::::from_elf( &elf, - None, Config::default(), syscall_registry(), ) diff --git a/benches/jit_compile.rs b/benches/jit_compile.rs index d6a1cfce..9d0106a1 100644 --- a/benches/jit_compile.rs +++ b/benches/jit_compile.rs @@ -12,10 +12,11 @@ extern crate test; use solana_rbpf::{ elf::Executable, user_error::UserError, - vm::{Config, EbpfVm, SyscallRegistry, TestInstructionMeter}, + vm::{Config, EbpfVm, SyscallRegistry, TestInstructionMeter, VerifiedExecutable}, }; use std::{fs::File, io::Read}; use test::Bencher; +use test_utils::TautologyVerifier; #[bench] fn bench_init_vm(bencher: &mut Bencher) { @@ -24,14 +25,16 @@ fn bench_init_vm(bencher: &mut Bencher) { file.read_to_end(&mut elf).unwrap(); let executable = Executable::::from_elf( &elf, - None, Config::default(), SyscallRegistry::default(), ) .unwrap(); - bencher.iter(|| { - EbpfVm::::new(&executable, &mut [], Vec::new()).unwrap() - }); + let verified_executable = + VerifiedExecutable::::from_executable( + executable, + ) + .unwrap(); + bencher.iter(|| EbpfVm::new(&verified_executable, &mut [], Vec::new()).unwrap()); } #[cfg(not(windows))] @@ -40,14 +43,16 @@ fn bench_jit_compile(bencher: &mut Bencher) { let mut file = File::open("tests/elfs/pass_stack_reference.so").unwrap(); let mut elf = Vec::new(); file.read_to_end(&mut elf).unwrap(); - let mut executable = Executable::::from_elf( + let executable = Executable::::from_elf( &elf, - None, Config::default(), SyscallRegistry::default(), ) .unwrap(); - bencher.iter(|| { - Executable::::jit_compile(&mut executable).unwrap() - }); + let mut verified_executable = + VerifiedExecutable::::from_executable( + executable, + ) + .unwrap(); + bencher.iter(|| verified_executable.jit_compile().unwrap()); } diff --git a/benches/vm_execution.rs b/benches/vm_execution.rs index b29cae30..4f82d749 100644 --- a/benches/vm_execution.rs +++ b/benches/vm_execution.rs @@ -14,10 +14,11 @@ use solana_rbpf::{ elf::Executable, memory_region::MemoryRegion, user_error::UserError, - vm::{Config, EbpfVm, SyscallRegistry, TestInstructionMeter}, + vm::{Config, EbpfVm, SyscallRegistry, TestInstructionMeter, VerifiedExecutable}, }; use std::{fs::File, io::Read}; use test::Bencher; +use test_utils::TautologyVerifier; #[bench] fn bench_init_interpreter_execution(bencher: &mut Bencher) { @@ -26,13 +27,16 @@ fn bench_init_interpreter_execution(bencher: &mut Bencher) { file.read_to_end(&mut elf).unwrap(); let executable = Executable::::from_elf( &elf, - None, Config::default(), SyscallRegistry::default(), ) .unwrap(); - let mut vm = - EbpfVm::::new(&executable, &mut [], Vec::new()).unwrap(); + let verified_executable = + VerifiedExecutable::::from_executable( + executable, + ) + .unwrap(); + let mut vm = EbpfVm::new(&verified_executable, &mut [], Vec::new()).unwrap(); bencher.iter(|| { vm.execute_program_interpreted(&mut TestInstructionMeter { remaining: 29 }) .unwrap() @@ -45,16 +49,19 @@ fn bench_init_jit_execution(bencher: &mut Bencher) { let mut file = File::open("tests/elfs/pass_stack_reference.so").unwrap(); let mut elf = Vec::new(); file.read_to_end(&mut elf).unwrap(); - let mut executable = Executable::::from_elf( + let executable = Executable::::from_elf( &elf, - None, Config::default(), SyscallRegistry::default(), ) .unwrap(); - Executable::::jit_compile(&mut executable).unwrap(); - let mut vm = - EbpfVm::::new(&executable, &mut [], Vec::new()).unwrap(); + let mut verified_executable = + VerifiedExecutable::::from_executable( + executable, + ) + .unwrap(); + verified_executable.jit_compile().unwrap(); + let mut vm = EbpfVm::new(&verified_executable, &mut [], Vec::new()).unwrap(); bencher.iter(|| { vm.execute_program_jit(&mut TestInstructionMeter { remaining: 29 }) .unwrap() @@ -69,16 +76,20 @@ fn bench_jit_vs_interpreter( instruction_meter: u64, mem: &mut [u8], ) { - let mut executable = solana_rbpf::assembler::assemble::( + let executable = solana_rbpf::assembler::assemble::( assembly, - None, config, SyscallRegistry::default(), ) .unwrap(); - Executable::::jit_compile(&mut executable).unwrap(); + let mut verified_executable = + VerifiedExecutable::::from_executable( + executable, + ) + .unwrap(); + verified_executable.jit_compile().unwrap(); let mem_region = MemoryRegion::new_writable(mem, ebpf::MM_INPUT_START); - let mut vm = EbpfVm::new(&executable, &mut [], vec![mem_region]).unwrap(); + let mut vm = EbpfVm::new(&verified_executable, &mut [], vec![mem_region]).unwrap(); let interpreter_summary = bencher .bench(|bencher| { bencher.iter(|| { diff --git a/cli/src/main.rs b/cli/src/main.rs index 87e1eb6d..cb2c378d 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -7,8 +7,11 @@ use solana_rbpf::{ static_analysis::Analysis, syscalls::Result, user_error::UserError, - verifier::check, - vm::{Config, DynamicAnalysis, EbpfVm, SyscallObject, SyscallRegistry, TestInstructionMeter}, + verifier::RequisiteVerifier, + vm::{ + Config, DynamicAnalysis, EbpfVm, SyscallObject, SyscallRegistry, TestInstructionMeter, + VerifiedExecutable, + }, }; use std::{fs::File, io::Read, path::Path}; @@ -106,12 +109,6 @@ fn main() { .short('p') .long("prof"), ) - .arg( - Arg::new("verify") - .about("Run the verifier before execution or disassembly") - .short('v') - .long("veri"), - ) .get_matches(); let config = Config { @@ -119,21 +116,14 @@ fn main() { enable_symbol_and_section_labels: true, ..Config::default() }; - let verifier: Option fn(&'r [u8], &Config) -> std::result::Result<_, _>> = - if matches.is_present("verify") { - Some(check) - } else { - None - }; let syscall_registry = SyscallRegistry::default(); - let mut executable = match matches.value_of("assembler") { + let executable = match matches.value_of("assembler") { Some(asm_file_name) => { let mut file = File::open(&Path::new(asm_file_name)).unwrap(); let mut source = Vec::new(); file.read_to_end(&mut source).unwrap(); assemble::( std::str::from_utf8(source.as_slice()).unwrap(), - verifier, config, syscall_registry, ) @@ -142,17 +132,18 @@ fn main() { let mut file = File::open(&Path::new(matches.value_of("elf").unwrap())).unwrap(); let mut elf = Vec::new(); file.read_to_end(&mut elf).unwrap(); - Executable::::from_elf( - &elf, - verifier, - config, - syscall_registry, - ) - .map_err(|err| format!("Executable constructor failed: {:?}", err)) + Executable::::from_elf(&elf, config, syscall_registry) + .map_err(|err| format!("Executable constructor failed: {:?}", err)) } } .unwrap(); + let mut verified_executable = + VerifiedExecutable::::from_executable( + executable, + ) + .unwrap(); + let mut mem = match matches.value_of("input").unwrap().parse::() { Ok(allocate) => vec![0u8; allocate], Err(_) => { @@ -178,17 +169,17 @@ fn main() { .unwrap() ]; if matches.value_of("use") == Some("jit") { - Executable::::jit_compile(&mut executable).unwrap(); + verified_executable.jit_compile().unwrap(); } let mem_region = MemoryRegion::new_writable(&mut mem, ebpf::MM_INPUT_START); - let mut vm = EbpfVm::new(&executable, &mut heap, vec![mem_region]).unwrap(); + let mut vm = EbpfVm::new(&verified_executable, &mut heap, vec![mem_region]).unwrap(); let analysis = if matches.value_of("use") == Some("cfg") || matches.value_of("use") == Some("disassembler") || matches.is_present("trace") || matches.is_present("profile") { - Some(Analysis::from_executable(&executable).unwrap()) + Some(Analysis::from_executable(verified_executable.get_executable()).unwrap()) } else { None }; diff --git a/examples/disassemble.rs b/examples/disassemble.rs index b3992215..3ff1eed1 100644 --- a/examples/disassemble.rs +++ b/examples/disassemble.rs @@ -9,7 +9,6 @@ use solana_rbpf::{ elf::Executable, static_analysis::Analysis, user_error::UserError, - verifier::check, vm::{Config, SyscallRegistry, TestInstructionMeter}, }; use std::collections::BTreeMap; @@ -34,7 +33,6 @@ fn main() { ]; let executable = Executable::::from_text_bytes( &program, - Some(check), Config::default(), SyscallRegistry::default(), BTreeMap::default(), diff --git a/examples/to_json.rs b/examples/to_json.rs index badf8861..a0aee5aa 100644 --- a/examples/to_json.rs +++ b/examples/to_json.rs @@ -16,7 +16,6 @@ use solana_rbpf::{ elf::Executable, static_analysis::Analysis, user_error::UserError, - verifier::check, vm::{Config, SyscallRegistry, TestInstructionMeter}, }; use std::collections::BTreeMap; @@ -32,7 +31,6 @@ use std::collections::BTreeMap; fn to_json(program: &[u8]) -> String { let executable = Executable::::from_text_bytes( &program, - Some(check), Config::default(), SyscallRegistry::default(), BTreeMap::default(), diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 39650fe9..cbcd515d 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -371,6 +371,7 @@ dependencies = [ "num-traits", "rayon", "solana_rbpf", + "test_utils", ] [[package]] @@ -384,6 +385,14 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "test_utils" +version = "0.2.30" +dependencies = [ + "libc", + "solana_rbpf", +] + [[package]] name = "thiserror" version = "1.0.30" diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 723d9579..f986f2dd 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -13,6 +13,7 @@ arbitrary = { version = "1.0", features = ["derive"] } libfuzzer-sys = "0.4" num-traits = "0.2" rayon = "1.5" +test_utils = { path = "../test_utils/" } [dependencies.solana_rbpf] path = ".." diff --git a/fuzz/fuzz_targets/dumb.rs b/fuzz/fuzz_targets/dumb.rs index 7d6d7dc3..ad276d1e 100644 --- a/fuzz/fuzz_targets/dumb.rs +++ b/fuzz/fuzz_targets/dumb.rs @@ -11,9 +11,10 @@ use solana_rbpf::{ elf::{register_bpf_function, Executable}, memory_region::MemoryRegion, user_error::UserError, - verifier::check, - vm::{EbpfVm, SyscallRegistry, TestInstructionMeter}, + verifier::{RequisiteVerifier, Verifier}, + vm::{EbpfVm, SyscallRegistry, TestInstructionMeter, VerifiedExecutable}, }; +use test_utils::TautologyVerifier; use crate::common::ConfigTemplate; @@ -29,7 +30,7 @@ struct DumbFuzzData { fuzz_target!(|data: DumbFuzzData| { let prog = data.prog; let config = data.template.into(); - if check(&prog, &config).is_err() { + if RequisiteVerifier::verify(&prog, &config).is_err() { // verify please return; } @@ -39,15 +40,18 @@ fuzz_target!(|data: DumbFuzzData| { register_bpf_function(&config, &mut bpf_functions, ®istry, 0, "entrypoint").unwrap(); let executable = Executable::::from_text_bytes( &prog, - None, config, SyscallRegistry::default(), bpf_functions, ) .unwrap(); + let verified_executable = + VerifiedExecutable::::from_executable( + executable, + ) + .unwrap(); let mem_region = MemoryRegion::new_writable(&mut mem, ebpf::MM_INPUT_START); - let mut vm = - EbpfVm::::new(&executable, &mut [], vec![mem_region]).unwrap(); + let mut vm = EbpfVm::new(&verified_executable, &mut [], vec![mem_region]).unwrap(); drop(black_box(vm.execute_program_interpreted( &mut TestInstructionMeter { remaining: 1024 }, diff --git a/fuzz/fuzz_targets/smart.rs b/fuzz/fuzz_targets/smart.rs index 5e8a0702..2702c35b 100644 --- a/fuzz/fuzz_targets/smart.rs +++ b/fuzz/fuzz_targets/smart.rs @@ -13,9 +13,10 @@ use solana_rbpf::{ insn_builder::{Arch, IntoBytes}, memory_region::MemoryRegion, user_error::UserError, - verifier::check, - vm::{EbpfVm, SyscallRegistry, TestInstructionMeter}, + verifier::{RequisiteVerifier, Verifier}, + vm::{EbpfVm, SyscallRegistry, TestInstructionMeter, VerifiedExecutable}, }; +use test_utils::TautologyVerifier; use crate::common::ConfigTemplate; @@ -33,7 +34,7 @@ struct FuzzData { fuzz_target!(|data: FuzzData| { let prog = make_program(&data.prog, data.arch); let config = data.template.into(); - if check(prog.into_bytes(), &config).is_err() { + if RequisiteVerifier::verify(prog.into_bytes(), &config).is_err() { // verify please return; } @@ -43,15 +44,18 @@ fuzz_target!(|data: FuzzData| { register_bpf_function(&config, &mut bpf_functions, ®istry, 0, "entrypoint").unwrap(); let executable = Executable::::from_text_bytes( prog.into_bytes(), - None, config, SyscallRegistry::default(), bpf_functions, ) .unwrap(); + let verified_executable = + VerifiedExecutable::::from_executable( + executable, + ) + .unwrap(); let mem_region = MemoryRegion::new_writable(&mut mem, ebpf::MM_INPUT_START); - let mut vm = - EbpfVm::::new(&executable, &mut [], vec![mem_region]).unwrap(); + let mut vm = EbpfVm::new(&verified_executable, &mut [], vec![mem_region]).unwrap(); drop(black_box(vm.execute_program_interpreted( &mut TestInstructionMeter { remaining: 1 << 16 }, diff --git a/fuzz/fuzz_targets/smart_jit_diff.rs b/fuzz/fuzz_targets/smart_jit_diff.rs index 863cf8a3..6ae5a428 100644 --- a/fuzz/fuzz_targets/smart_jit_diff.rs +++ b/fuzz/fuzz_targets/smart_jit_diff.rs @@ -11,9 +11,10 @@ use solana_rbpf::{ insn_builder::{Arch, Instruction, IntoBytes}, memory_region::MemoryRegion, user_error::UserError, - verifier::check, - vm::{EbpfVm, SyscallRegistry, TestInstructionMeter}, + verifier::{RequisiteVerifier, Verifier}, + vm::{EbpfVm, SyscallRegistry, TestInstructionMeter, VerifiedExecutable}, }; +use test_utils::TautologyVerifier; use crate::common::ConfigTemplate; @@ -40,7 +41,7 @@ fuzz_target!(|data: FuzzData| { .set_imm(data.exit_imm) .push(); let config = data.template.into(); - if check(prog.into_bytes(), &config).is_err() { + if RequisiteVerifier::verify(prog.into_bytes(), &config).is_err() { // verify please return; } @@ -49,23 +50,24 @@ fuzz_target!(|data: FuzzData| { let registry = SyscallRegistry::default(); let mut bpf_functions = BTreeMap::new(); register_bpf_function(&config, &mut bpf_functions, ®istry, 0, "entrypoint").unwrap(); - let mut executable = Executable::::from_text_bytes( + let executable = Executable::::from_text_bytes( prog.into_bytes(), - None, config, SyscallRegistry::default(), bpf_functions, ) .unwrap(); - if Executable::jit_compile(&mut executable).is_ok() { + let mut verified_executable = + VerifiedExecutable::::from_executable( + executable, + ) + .unwrap(); + if verified_executable.jit_compile().is_ok() { let interp_mem_region = MemoryRegion::new_writable(&mut interp_mem, ebpf::MM_INPUT_START); let mut interp_vm = - EbpfVm::::new(&executable, &mut [], vec![interp_mem_region]) - .unwrap(); + EbpfVm::new(&verified_executable, &mut [], vec![interp_mem_region]).unwrap(); let jit_mem_region = MemoryRegion::new_writable(&mut jit_mem, ebpf::MM_INPUT_START); - let mut jit_vm = - EbpfVm::::new(&executable, &mut [], vec![jit_mem_region]) - .unwrap(); + let mut jit_vm = EbpfVm::new(&verified_executable, &mut [], vec![jit_mem_region]).unwrap(); let mut interp_meter = TestInstructionMeter { remaining: 1 << 16 }; let interp_res = interp_vm.execute_program_interpreted(&mut interp_meter); diff --git a/fuzz/fuzz_targets/smarter_jit_diff.rs b/fuzz/fuzz_targets/smarter_jit_diff.rs index 750d92b1..30636dd2 100644 --- a/fuzz/fuzz_targets/smarter_jit_diff.rs +++ b/fuzz/fuzz_targets/smarter_jit_diff.rs @@ -13,9 +13,10 @@ use solana_rbpf::{ memory_region::MemoryRegion, static_analysis::Analysis, user_error::UserError, - verifier::check, - vm::{EbpfVm, InstructionMeter, SyscallRegistry, TestInstructionMeter}, + verifier::{RequisiteVerifier, Verifier}, + vm::{EbpfVm, InstructionMeter, SyscallRegistry, TestInstructionMeter, VerifiedExecutable}, }; +use test_utils::TautologyVerifier; use crate::common::ConfigTemplate; @@ -29,8 +30,10 @@ struct FuzzData { mem: Vec, } -fn dump_insns(executable: &Executable) { - let analysis = Analysis::from_executable(executable).unwrap(); +fn dump_insns( + verified_executable: &VerifiedExecutable, +) { + let analysis = Analysis::from_executable(verified_executable.get_executable()).unwrap(); eprint!("Using the following disassembly"); analysis.disassemble(&mut std::io::stderr().lock()).unwrap(); } @@ -38,7 +41,7 @@ fn dump_insns(executable: &Executable< fuzz_target!(|data: FuzzData| { let prog = make_program(&data.prog); let config = data.template.into(); - if check(prog.into_bytes(), &config).is_err() { + if RequisiteVerifier::verify(prog.into_bytes(), &config).is_err() { // verify please return; } @@ -47,23 +50,24 @@ fuzz_target!(|data: FuzzData| { let registry = SyscallRegistry::default(); let mut bpf_functions = BTreeMap::new(); register_bpf_function(&config, &mut bpf_functions, ®istry, 0, "entrypoint").unwrap(); - let mut executable = Executable::::from_text_bytes( + let executable = Executable::::from_text_bytes( prog.into_bytes(), - None, config, SyscallRegistry::default(), bpf_functions, ) .unwrap(); - if Executable::jit_compile(&mut executable).is_ok() { + let mut verified_executable = + VerifiedExecutable::::from_executable( + executable, + ) + .unwrap(); + if verified_executable.jit_compile().is_ok() { let interp_mem_region = MemoryRegion::new_writable(&mut interp_mem, ebpf::MM_INPUT_START); let mut interp_vm = - EbpfVm::::new(&executable, &mut [], vec![interp_mem_region]) - .unwrap(); + EbpfVm::new(&verified_executable, &mut [], vec![interp_mem_region]).unwrap(); let jit_mem_region = MemoryRegion::new_writable(&mut jit_mem, ebpf::MM_INPUT_START); - let mut jit_vm = - EbpfVm::::new(&executable, &mut [], vec![jit_mem_region]) - .unwrap(); + let mut jit_vm = EbpfVm::new(&verified_executable, &mut [], vec![jit_mem_region]).unwrap(); let mut interp_meter = TestInstructionMeter { remaining: 1 << 16 }; let interp_res = interp_vm.execute_program_interpreted(&mut interp_meter); @@ -82,20 +86,20 @@ fuzz_target!(|data: FuzzData| { } } eprintln!("{:#?}", &data.prog); - dump_insns(&executable); + dump_insns(&verified_executable); panic!("Expected {:?}, but got {:?}", interp_res, jit_res); } if interp_res.is_ok() { // we know jit res must be ok if interp res is by this point if interp_meter.remaining != jit_meter.remaining { - dump_insns(&executable); + dump_insns(&verified_executable); panic!( "Expected {} insts remaining, but got {}", interp_meter.remaining, jit_meter.remaining ); } if interp_mem != jit_mem { - dump_insns(&executable); + dump_insns(&verified_executable); panic!( "Expected different memory. From interpreter: {:?}\nFrom JIT: {:?}", interp_mem, jit_mem diff --git a/fuzz/fuzz_targets/verify_semantic_aware.rs b/fuzz/fuzz_targets/verify_semantic_aware.rs index f20b81ed..f9fd5390 100644 --- a/fuzz/fuzz_targets/verify_semantic_aware.rs +++ b/fuzz/fuzz_targets/verify_semantic_aware.rs @@ -4,7 +4,7 @@ use libfuzzer_sys::fuzz_target; use semantic_aware::*; use solana_rbpf::insn_builder::IntoBytes; -use solana_rbpf::verifier::check; +use solana_rbpf::verifier::{RequisiteVerifier, Verifier}; use crate::common::ConfigTemplate; @@ -20,5 +20,5 @@ struct FuzzData { fuzz_target!(|data: FuzzData| { let prog = make_program(&data.prog); let config = data.template.into(); - check(prog.into_bytes(), &config).unwrap(); + RequisiteVerifier::verify(prog.into_bytes(), &config).unwrap(); }); diff --git a/src/assembler.rs b/src/assembler.rs index 70622cf0..a0f40708 100644 --- a/src/assembler.rs +++ b/src/assembler.rs @@ -20,7 +20,7 @@ use crate::{ ebpf::{self, Insn}, elf::{register_bpf_function, Executable}, error::UserDefinedError, - vm::{Config, InstructionMeter, SyscallRegistry, Verifier}, + vm::{Config, InstructionMeter, SyscallRegistry}, }; use std::{ collections::{BTreeMap, HashMap}, @@ -186,7 +186,7 @@ fn insn(opc: u8, dst: i64, src: i64, off: i64, imm: i64) -> Result /// # Examples /// /// ``` -/// use solana_rbpf::{assembler::assemble, user_error::UserError, vm::{Config, TestInstructionMeter, SyscallRegistry}, verifier::check}; +/// use solana_rbpf::{assembler::assemble, user_error::UserError, vm::{Config, TestInstructionMeter, SyscallRegistry}}; /// let executable = assemble::( /// "add64 r1, 0x605 /// mov64 r2, 0x32 @@ -194,7 +194,6 @@ fn insn(opc: u8, dst: i64, src: i64, off: i64, imm: i64) -> Result /// be16 r0 /// neg64 r2 /// exit", -/// Some(check), /// Config::default(), /// SyscallRegistry::default(), /// ).unwrap(); @@ -221,7 +220,6 @@ fn insn(opc: u8, dst: i64, src: i64, off: i64, imm: i64) -> Result /// ``` pub fn assemble( src: &str, - verifier: Option, config: Config, syscall_registry: SyscallRegistry, ) -> Result>>, String> { @@ -389,6 +387,6 @@ pub fn assemble( .iter() .flat_map(|insn| insn.to_vec()) .collect::>(); - Executable::::from_text_bytes(&program, verifier, config, syscall_registry, bpf_functions) + Executable::::from_text_bytes(&program, config, syscall_registry, bpf_functions) .map_err(|err| format!("Executable constructor {:?}", err)) } diff --git a/src/elf.rs b/src/elf.rs index 2ded30d8..d01e26f8 100644 --- a/src/elf.rs +++ b/src/elf.rs @@ -2086,13 +2086,9 @@ mod test { let mut elf_bytes = Vec::new(); file.read_to_end(&mut elf_bytes) .expect("failed to read elf file"); - let mut executable = ElfExecutable::from_elf( - &elf_bytes, - Some(crate::verifier::check), - Config::default(), - syscall_registry(), - ) - .expect("validation failed"); + let mut executable = + ElfExecutable::from_elf(&elf_bytes, Config::default(), syscall_registry()) + .expect("validation failed"); { Executable::jit_compile(&mut executable).unwrap(); } diff --git a/src/interpreter.rs b/src/interpreter.rs index 23302dd2..9740b4e9 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -18,6 +18,7 @@ use crate::{ error::{EbpfError, UserDefinedError}, memory_region::AccessType, user_error::UserError, + verifier::Verifier, vm::{ EbpfVm, InstructionMeter, ProgramResult, SyscallFunction, SYSCALL_CONTEXT_OBJECTS_OFFSET, }, @@ -57,9 +58,8 @@ macro_rules! translate_memory_access { } /// State of an interpreter -pub struct Interpreter<'a, 'b, E: UserDefinedError, I: InstructionMeter> { - vm: &'a mut EbpfVm<'b, E, I>, - +pub struct Interpreter<'a, 'b, V: Verifier, E: UserDefinedError, I: InstructionMeter> { + vm: &'a mut EbpfVm<'b, V, E, I>, instruction_meter: &'a mut I, pub(crate) initial_insn_count: u64, remaining_insn_count: u64, @@ -71,13 +71,14 @@ pub struct Interpreter<'a, 'b, E: UserDefinedError, I: InstructionMeter> { pub pc: usize, } -impl<'a, 'b, E: UserDefinedError, I: InstructionMeter> Interpreter<'a, 'b, E, I> { +impl<'a, 'b, V: Verifier, E: UserDefinedError, I: InstructionMeter> Interpreter<'a, 'b, V, E, I> { /// Creates a new interpreter state pub fn new( - vm: &'a mut EbpfVm<'b, E, I>, + vm: &'a mut EbpfVm<'b, V, E, I>, instruction_meter: &'a mut I, ) -> Result> { - let initial_insn_count = if vm.executable.get_config().enable_instruction_meter { + let executable = vm.verified_executable.get_executable(); + let initial_insn_count = if executable.get_config().enable_instruction_meter { instruction_meter.get_remaining() } else { 0 @@ -96,7 +97,7 @@ impl<'a, 'b, E: UserDefinedError, I: InstructionMeter> Interpreter<'a, 'b, E, I> 0, vm.stack.get_frame_ptr(), ]; - let pc = vm.executable.get_entrypoint_instruction_offset()?; + let pc = executable.get_entrypoint_instruction_offset()?; Ok(Self { vm, instruction_meter, @@ -130,7 +131,8 @@ impl<'a, 'b, E: UserDefinedError, I: InstructionMeter> Interpreter<'a, 'b, E, I> /// Advances the interpreter state by one instruction #[rustfmt::skip] pub fn step(&mut self) -> Result, EbpfError> { - let config = &self.vm.executable.get_config(); + let executable = self.vm.verified_executable.get_executable(); + let config = &executable.get_config(); let mut instruction_width = 1; self.due_insn_count += 1; @@ -451,7 +453,7 @@ impl<'a, 'b, E: UserDefinedError, I: InstructionMeter> Interpreter<'a, 'b, E, I> }; if syscalls { - if let Some(syscall) = self.vm.executable.get_syscall_registry().lookup_syscall(insn.imm as u32) { + if let Some(syscall) = executable.get_syscall_registry().lookup_syscall(insn.imm as u32) { resolved = true; if config.enable_instruction_meter { @@ -477,7 +479,7 @@ impl<'a, 'b, E: UserDefinedError, I: InstructionMeter> Interpreter<'a, 'b, E, I> } if calls { - if let Some(target_pc) = self.vm.executable.lookup_bpf_function(insn.imm as u32) { + if let Some(target_pc) = executable.lookup_bpf_function(insn.imm as u32) { resolved = true; // make BPF to BPF call @@ -491,7 +493,7 @@ impl<'a, 'b, E: UserDefinedError, I: InstructionMeter> Interpreter<'a, 'b, E, I> if config.disable_unresolved_symbols_at_runtime { return Err(EbpfError::UnsupportedInstruction(pc + ebpf::ELF_INSN_DUMP_OFFSET)); } else { - self.vm.executable.report_unresolved_symbol(pc)?; + executable.report_unresolved_symbol(pc)?; } } } diff --git a/src/jit.rs b/src/jit.rs index a3c69421..3eeadace 100644 --- a/src/jit.rs +++ b/src/jit.rs @@ -1821,7 +1821,6 @@ mod tests { bpf_functions.insert(0xFFFFFFFF, (8, "foo".to_string())); Executable::::from_text_bytes( program, - None, config, syscall_registry, bpf_functions, diff --git a/src/verifier.rs b/src/verifier.rs index 7952b22c..528e1a31 100644 --- a/src/verifier.rs +++ b/src/verifier.rs @@ -79,6 +79,19 @@ pub enum VerifierError { InvalidRegister(usize), } +/// eBPF Verifier +pub trait Verifier { + /// eBPF verification function that returns an error if the program does not meet its requirements. + /// + /// Some examples of things the verifier may reject the program for: + /// + /// - Program does not terminate. + /// - Unknown instructions. + /// - Bad formed instruction. + /// - Unknown eBPF syscall index. + fn verify(prog: &[u8], config: &Config) -> Result<(), VerifierError>; +} + fn adj_insn_ptr(insn_ptr: usize) -> usize { insn_ptr + ebpf::ELF_INSN_DUMP_OFFSET } @@ -190,163 +203,176 @@ fn check_imm_register( Ok(()) } -/// Check the program against the verifier's rules -#[rustfmt::skip] -pub fn check(prog: &[u8], config: &Config) -> Result<(), VerifierError> { - check_prog_len(prog)?; - - let mut insn_ptr: usize = 0; - while (insn_ptr + 1) * ebpf::INSN_SIZE <= prog.len() { - let insn = ebpf::get_insn(prog, insn_ptr); - let mut store = false; - - match insn.opc { - ebpf::LD_ABS_B - | ebpf::LD_ABS_H - | ebpf::LD_ABS_W - | ebpf::LD_ABS_DW - | ebpf::LD_IND_B - | ebpf::LD_IND_H - | ebpf::LD_IND_W - | ebpf::LD_IND_DW if config.disable_deprecated_load_instructions => { - return Err(VerifierError::UnknownOpCode(insn.opc, adj_insn_ptr(insn_ptr))); - }, - - // BPF_LD class - ebpf::LD_ABS_B => {}, - ebpf::LD_ABS_H => {}, - ebpf::LD_ABS_W => {}, - ebpf::LD_ABS_DW => {}, - ebpf::LD_IND_B => {}, - ebpf::LD_IND_H => {}, - ebpf::LD_IND_W => {}, - ebpf::LD_IND_DW => {}, - - ebpf::LD_DW_IMM => { - check_load_dw(prog, insn_ptr)?; - insn_ptr += 1; - }, - - // BPF_LDX class - ebpf::LD_B_REG => {}, - ebpf::LD_H_REG => {}, - ebpf::LD_W_REG => {}, - ebpf::LD_DW_REG => {}, - - // BPF_ST class - ebpf::ST_B_IMM => store = true, - ebpf::ST_H_IMM => store = true, - ebpf::ST_W_IMM => store = true, - ebpf::ST_DW_IMM => store = true, - - // BPF_STX class - ebpf::ST_B_REG => store = true, - ebpf::ST_H_REG => store = true, - ebpf::ST_W_REG => store = true, - ebpf::ST_DW_REG => store = true, - - // BPF_ALU class - ebpf::ADD32_IMM => {}, - ebpf::ADD32_REG => {}, - ebpf::SUB32_IMM => {}, - ebpf::SUB32_REG => {}, - ebpf::MUL32_IMM => {}, - 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::OR32_IMM => {}, - ebpf::OR32_REG => {}, - ebpf::AND32_IMM => {}, - ebpf::AND32_REG => {}, - ebpf::LSH32_IMM => { check_imm_shift(&insn, insn_ptr, 32)?; }, - ebpf::LSH32_REG => {}, - ebpf::RSH32_IMM => { check_imm_shift(&insn, insn_ptr, 32)?; }, - ebpf::RSH32_REG => {}, - ebpf::NEG32 => {}, - ebpf::MOD32_IMM => { check_imm_nonzero(&insn, insn_ptr)?; }, - ebpf::MOD32_REG => {}, - ebpf::XOR32_IMM => {}, - ebpf::XOR32_REG => {}, - ebpf::MOV32_IMM => {}, - ebpf::MOV32_REG => {}, - ebpf::ARSH32_IMM => { check_imm_shift(&insn, insn_ptr, 32)?; }, - ebpf::ARSH32_REG => {}, - ebpf::LE => { check_imm_endian(&insn, insn_ptr)?; }, - ebpf::BE => { check_imm_endian(&insn, insn_ptr)?; }, - - // BPF_ALU64 class - ebpf::ADD64_IMM => {}, - ebpf::ADD64_REG => {}, - ebpf::SUB64_IMM => {}, - ebpf::SUB64_REG => {}, - ebpf::MUL64_IMM => {}, - 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::OR64_IMM => {}, - ebpf::OR64_REG => {}, - ebpf::AND64_IMM => {}, - ebpf::AND64_REG => {}, - ebpf::LSH64_IMM => { check_imm_shift(&insn, insn_ptr, 64)?; }, - ebpf::LSH64_REG => {}, - ebpf::RSH64_IMM => { check_imm_shift(&insn, insn_ptr, 64)?; }, - ebpf::RSH64_REG => {}, - ebpf::NEG64 => {}, - ebpf::MOD64_IMM => { check_imm_nonzero(&insn, insn_ptr)?; }, - ebpf::MOD64_REG => {}, - ebpf::XOR64_IMM => {}, - ebpf::XOR64_REG => {}, - ebpf::MOV64_IMM => {}, - ebpf::MOV64_REG => {}, - ebpf::ARSH64_IMM => { check_imm_shift(&insn, insn_ptr, 64)?; }, - ebpf::ARSH64_REG => {}, - - // BPF_JMP class - ebpf::JA => { check_jmp_offset(prog, insn_ptr)?; }, - ebpf::JEQ_IMM => { check_jmp_offset(prog, insn_ptr)?; }, - ebpf::JEQ_REG => { check_jmp_offset(prog, insn_ptr)?; }, - ebpf::JGT_IMM => { check_jmp_offset(prog, insn_ptr)?; }, - ebpf::JGT_REG => { check_jmp_offset(prog, insn_ptr)?; }, - ebpf::JGE_IMM => { check_jmp_offset(prog, insn_ptr)?; }, - ebpf::JGE_REG => { check_jmp_offset(prog, insn_ptr)?; }, - ebpf::JLT_IMM => { check_jmp_offset(prog, insn_ptr)?; }, - ebpf::JLT_REG => { check_jmp_offset(prog, insn_ptr)?; }, - ebpf::JLE_IMM => { check_jmp_offset(prog, insn_ptr)?; }, - ebpf::JLE_REG => { check_jmp_offset(prog, insn_ptr)?; }, - ebpf::JSET_IMM => { check_jmp_offset(prog, insn_ptr)?; }, - ebpf::JSET_REG => { check_jmp_offset(prog, insn_ptr)?; }, - ebpf::JNE_IMM => { check_jmp_offset(prog, insn_ptr)?; }, - ebpf::JNE_REG => { check_jmp_offset(prog, insn_ptr)?; }, - ebpf::JSGT_IMM => { check_jmp_offset(prog, insn_ptr)?; }, - ebpf::JSGT_REG => { check_jmp_offset(prog, insn_ptr)?; }, - ebpf::JSGE_IMM => { check_jmp_offset(prog, insn_ptr)?; }, - ebpf::JSGE_REG => { check_jmp_offset(prog, insn_ptr)?; }, - ebpf::JSLT_IMM => { check_jmp_offset(prog, insn_ptr)?; }, - ebpf::JSLT_REG => { check_jmp_offset(prog, insn_ptr)?; }, - ebpf::JSLE_IMM => { check_jmp_offset(prog, insn_ptr)?; }, - ebpf::JSLE_REG => { check_jmp_offset(prog, insn_ptr)?; }, - ebpf::CALL_IMM => {}, - ebpf::CALL_REG => { check_imm_register(&insn, insn_ptr, config)?; }, - ebpf::EXIT => {}, - - _ => { - return Err(VerifierError::UnknownOpCode(insn.opc, adj_insn_ptr(insn_ptr))); +/// Mandatory verifier for solana programs to run on-chain +#[derive(Debug)] +pub struct RequisiteVerifier {} +impl Verifier for RequisiteVerifier { + /// Check the program against the verifier's rules + #[rustfmt::skip] + fn verify(prog: &[u8], config: &Config) -> Result<(), VerifierError> { + check_prog_len(prog)?; + + let mut insn_ptr: usize = 0; + while (insn_ptr + 1) * ebpf::INSN_SIZE <= prog.len() { + let insn = ebpf::get_insn(prog, insn_ptr); + let mut store = false; + + match insn.opc { + ebpf::LD_ABS_B + | ebpf::LD_ABS_H + | ebpf::LD_ABS_W + | ebpf::LD_ABS_DW + | ebpf::LD_IND_B + | ebpf::LD_IND_H + | ebpf::LD_IND_W + | ebpf::LD_IND_DW if config.disable_deprecated_load_instructions => { + return Err(VerifierError::UnknownOpCode(insn.opc, adj_insn_ptr(insn_ptr))); + }, + + // BPF_LD class + ebpf::LD_ABS_B => {}, + ebpf::LD_ABS_H => {}, + ebpf::LD_ABS_W => {}, + ebpf::LD_ABS_DW => {}, + ebpf::LD_IND_B => {}, + ebpf::LD_IND_H => {}, + ebpf::LD_IND_W => {}, + ebpf::LD_IND_DW => {}, + + ebpf::LD_DW_IMM => { + check_load_dw(prog, insn_ptr)?; + insn_ptr += 1; + }, + + // BPF_LDX class + ebpf::LD_B_REG => {}, + ebpf::LD_H_REG => {}, + ebpf::LD_W_REG => {}, + ebpf::LD_DW_REG => {}, + + // BPF_ST class + ebpf::ST_B_IMM => store = true, + ebpf::ST_H_IMM => store = true, + ebpf::ST_W_IMM => store = true, + ebpf::ST_DW_IMM => store = true, + + // BPF_STX class + ebpf::ST_B_REG => store = true, + ebpf::ST_H_REG => store = true, + ebpf::ST_W_REG => store = true, + ebpf::ST_DW_REG => store = true, + + // BPF_ALU class + ebpf::ADD32_IMM => {}, + ebpf::ADD32_REG => {}, + ebpf::SUB32_IMM => {}, + ebpf::SUB32_REG => {}, + ebpf::MUL32_IMM => {}, + 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::OR32_IMM => {}, + ebpf::OR32_REG => {}, + ebpf::AND32_IMM => {}, + ebpf::AND32_REG => {}, + ebpf::LSH32_IMM => { check_imm_shift(&insn, insn_ptr, 32)?; }, + ebpf::LSH32_REG => {}, + ebpf::RSH32_IMM => { check_imm_shift(&insn, insn_ptr, 32)?; }, + ebpf::RSH32_REG => {}, + ebpf::NEG32 => {}, + ebpf::MOD32_IMM => { check_imm_nonzero(&insn, insn_ptr)?; }, + ebpf::MOD32_REG => {}, + ebpf::XOR32_IMM => {}, + ebpf::XOR32_REG => {}, + ebpf::MOV32_IMM => {}, + ebpf::MOV32_REG => {}, + ebpf::ARSH32_IMM => { check_imm_shift(&insn, insn_ptr, 32)?; }, + ebpf::ARSH32_REG => {}, + ebpf::LE => { check_imm_endian(&insn, insn_ptr)?; }, + ebpf::BE => { check_imm_endian(&insn, insn_ptr)?; }, + + // BPF_ALU64 class + ebpf::ADD64_IMM => {}, + ebpf::ADD64_REG => {}, + ebpf::SUB64_IMM => {}, + ebpf::SUB64_REG => {}, + ebpf::MUL64_IMM => {}, + 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::OR64_IMM => {}, + ebpf::OR64_REG => {}, + ebpf::AND64_IMM => {}, + ebpf::AND64_REG => {}, + ebpf::LSH64_IMM => { check_imm_shift(&insn, insn_ptr, 64)?; }, + ebpf::LSH64_REG => {}, + ebpf::RSH64_IMM => { check_imm_shift(&insn, insn_ptr, 64)?; }, + ebpf::RSH64_REG => {}, + ebpf::NEG64 => {}, + ebpf::MOD64_IMM => { check_imm_nonzero(&insn, insn_ptr)?; }, + ebpf::MOD64_REG => {}, + ebpf::XOR64_IMM => {}, + ebpf::XOR64_REG => {}, + ebpf::MOV64_IMM => {}, + ebpf::MOV64_REG => {}, + ebpf::ARSH64_IMM => { check_imm_shift(&insn, insn_ptr, 64)?; }, + ebpf::ARSH64_REG => {}, + + // BPF_JMP class + ebpf::JA => { check_jmp_offset(prog, insn_ptr)?; }, + ebpf::JEQ_IMM => { check_jmp_offset(prog, insn_ptr)?; }, + ebpf::JEQ_REG => { check_jmp_offset(prog, insn_ptr)?; }, + ebpf::JGT_IMM => { check_jmp_offset(prog, insn_ptr)?; }, + ebpf::JGT_REG => { check_jmp_offset(prog, insn_ptr)?; }, + ebpf::JGE_IMM => { check_jmp_offset(prog, insn_ptr)?; }, + ebpf::JGE_REG => { check_jmp_offset(prog, insn_ptr)?; }, + ebpf::JLT_IMM => { check_jmp_offset(prog, insn_ptr)?; }, + ebpf::JLT_REG => { check_jmp_offset(prog, insn_ptr)?; }, + ebpf::JLE_IMM => { check_jmp_offset(prog, insn_ptr)?; }, + ebpf::JLE_REG => { check_jmp_offset(prog, insn_ptr)?; }, + ebpf::JSET_IMM => { check_jmp_offset(prog, insn_ptr)?; }, + ebpf::JSET_REG => { check_jmp_offset(prog, insn_ptr)?; }, + ebpf::JNE_IMM => { check_jmp_offset(prog, insn_ptr)?; }, + ebpf::JNE_REG => { check_jmp_offset(prog, insn_ptr)?; }, + ebpf::JSGT_IMM => { check_jmp_offset(prog, insn_ptr)?; }, + ebpf::JSGT_REG => { check_jmp_offset(prog, insn_ptr)?; }, + ebpf::JSGE_IMM => { check_jmp_offset(prog, insn_ptr)?; }, + ebpf::JSGE_REG => { check_jmp_offset(prog, insn_ptr)?; }, + ebpf::JSLT_IMM => { check_jmp_offset(prog, insn_ptr)?; }, + ebpf::JSLT_REG => { check_jmp_offset(prog, insn_ptr)?; }, + ebpf::JSLE_IMM => { check_jmp_offset(prog, insn_ptr)?; }, + ebpf::JSLE_REG => { check_jmp_offset(prog, insn_ptr)?; }, + ebpf::CALL_IMM => {}, + ebpf::CALL_REG => { check_imm_register(&insn, insn_ptr, config)?; }, + ebpf::EXIT => {}, + + _ => { + return Err(VerifierError::UnknownOpCode(insn.opc, adj_insn_ptr(insn_ptr))); + } } + + check_registers(&insn, store, insn_ptr, config.dynamic_stack_frames)?; + + insn_ptr += 1; } - check_registers(&insn, store, insn_ptr, config.dynamic_stack_frames)?; + // insn_ptr should now be equal to number of instructions. + if insn_ptr != prog.len() / ebpf::INSN_SIZE { + return Err(VerifierError::JumpOutOfCode(adj_insn_ptr(insn_ptr), adj_insn_ptr(insn_ptr))); + } - insn_ptr += 1; + Ok(()) } +} - // insn_ptr should now be equal to number of instructions. - if insn_ptr != prog.len() / ebpf::INSN_SIZE { - return Err(VerifierError::JumpOutOfCode(adj_insn_ptr(insn_ptr), adj_insn_ptr(insn_ptr))); +// Warning: For test purposes only +pub(crate) struct TautologyVerifier {} +impl Verifier for TautologyVerifier { + fn verify(_prog: &[u8], _config: &Config) -> std::result::Result<(), VerifierError> { + Ok(()) } - - Ok(()) } diff --git a/src/vm.rs b/src/vm.rs index c9041e93..20e4651f 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -22,26 +22,17 @@ use crate::{ jit::JitProgramArgument, memory_region::{MemoryMapping, MemoryRegion}, static_analysis::Analysis, - verifier::VerifierError, + verifier::Verifier, }; use std::{ collections::{BTreeMap, HashMap}, fmt::Debug, + marker::PhantomData, mem, pin::Pin, u32, }; -/// eBPF verification function that returns an error if the program does not meet its requirements. -/// -/// Some examples of things the verifier may reject the program for: -/// -/// - Program does not terminate. -/// - Unknown instructions. -/// - Bad formed instruction. -/// - Unknown eBPF syscall index. -pub type Verifier = fn(prog: &[u8], config: &Config) -> Result<(), VerifierError>; - /// Return value of programs and syscalls pub type ProgramResult = Result>; @@ -276,30 +267,22 @@ pub const SYSCALL_CONTEXT_OBJECTS_OFFSET: usize = 4; /// Static constructors for Executable impl Executable { - /// Creates a verified executable from an ELF file + /// Creates an executable from an ELF file pub fn from_elf( elf_bytes: &[u8], - verifier: Option, config: Config, syscall_registry: SyscallRegistry, ) -> Result>, EbpfError> { let executable = Executable::load(config, elf_bytes, syscall_registry)?; - if let Some(verifier) = verifier { - verifier(executable.get_text_bytes().1, executable.get_config())?; - } Ok(Pin::new(Box::new(executable))) } - /// Creates a verified executable from machine code + /// Creates an executable from machine code pub fn from_text_bytes( text_bytes: &[u8], - verifier: Option, config: Config, syscall_registry: SyscallRegistry, bpf_functions: BTreeMap, ) -> Result>, EbpfError> { - if let Some(verifier) = verifier { - verifier(text_bytes, &config).map_err(EbpfError::VerifierError)?; - } Ok(Pin::new(Box::new(Executable::new_from_text_bytes( config, text_bytes, @@ -309,6 +292,35 @@ impl Executable { } } +/// Verified executable +#[derive(Debug, PartialEq)] +#[repr(transparent)] +pub struct VerifiedExecutable { + executable: Pin>>, + _verifier: PhantomData, +} + +impl VerifiedExecutable { + /// Verify an executable + pub fn from_executable(executable: Pin>>) -> Result> { + ::verify(executable.get_text_bytes().1, executable.get_config())?; + Ok(VerifiedExecutable { + executable, + _verifier: PhantomData, + }) + } + + /// JIT compile the executable + pub fn jit_compile(&mut self) -> Result<(), EbpfError> { + Executable::::jit_compile(&mut self.executable) + } + + /// Get a reference to the underlying executable + pub fn get_executable(&self) -> &Executable { + &self.executable + } +} + /// Instruction meter pub trait InstructionMeter { /// Consume instructions @@ -437,7 +449,7 @@ impl Tracer { /// # Examples /// /// ``` -/// use solana_rbpf::{ebpf, elf::{Executable, register_bpf_function}, memory_region::MemoryRegion, vm::{Config, EbpfVm, TestInstructionMeter, SyscallRegistry}, verifier::check, user_error::UserError}; +/// use solana_rbpf::{ebpf, elf::{Executable, register_bpf_function}, memory_region::MemoryRegion, vm::{Config, EbpfVm, TestInstructionMeter, SyscallRegistry, VerifiedExecutable}, user_error::UserError, verifier::RequisiteVerifier}; /// /// let prog = &[ /// 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // exit @@ -451,16 +463,17 @@ impl Tracer { /// let mut bpf_functions = std::collections::BTreeMap::new(); /// let syscall_registry = SyscallRegistry::default(); /// register_bpf_function(&config, &mut bpf_functions, &syscall_registry, 0, "entrypoint").unwrap(); -/// let mut executable = Executable::::from_text_bytes(prog, Some(check), config, syscall_registry, bpf_functions).unwrap(); +/// let mut executable = Executable::::from_text_bytes(prog, config, syscall_registry, bpf_functions).unwrap(); /// let mem_region = MemoryRegion::new_writable(mem, ebpf::MM_INPUT_START); -/// let mut vm = EbpfVm::::new(&executable, &mut [], vec![mem_region]).unwrap(); +/// let verified_executable = VerifiedExecutable::::from_executable(executable).unwrap(); +/// let mut vm = EbpfVm::new(&verified_executable, &mut [], vec![mem_region]).unwrap(); /// /// // Provide a reference to the packet data. /// let res = vm.execute_program_interpreted(&mut TestInstructionMeter { remaining: 1 }).unwrap(); /// assert_eq!(res, 0); /// ``` -pub struct EbpfVm<'a, E: UserDefinedError, I: InstructionMeter> { - pub(crate) executable: &'a Executable, +pub struct EbpfVm<'a, V: Verifier, E: UserDefinedError, I: InstructionMeter> { + pub(crate) verified_executable: &'a VerifiedExecutable, pub(crate) program: &'a [u8], pub(crate) program_vm_addr: u64, pub(crate) memory_mapping: MemoryMapping<'a>, @@ -471,14 +484,14 @@ pub struct EbpfVm<'a, E: UserDefinedError, I: InstructionMeter> { total_insn_count: u64, } -impl<'a, E: UserDefinedError, I: InstructionMeter> EbpfVm<'a, E, I> { +impl<'a, V: Verifier, E: UserDefinedError, I: InstructionMeter> EbpfVm<'a, V, E, I> { /// Create a new virtual machine instance, and load an eBPF program into that instance. /// When attempting to load the program, it passes through a simple verifier. /// /// # Examples /// /// ``` - /// use solana_rbpf::{ebpf, elf::{Executable, register_bpf_function}, vm::{Config, EbpfVm, TestInstructionMeter, SyscallRegistry}, verifier::check, user_error::UserError}; + /// use solana_rbpf::{ebpf, elf::{Executable, register_bpf_function}, vm::{Config, EbpfVm, TestInstructionMeter, SyscallRegistry, VerifiedExecutable}, user_error::UserError, verifier::RequisiteVerifier}; /// /// let prog = &[ /// 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // exit @@ -489,19 +502,21 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EbpfVm<'a, E, I> { /// let mut bpf_functions = std::collections::BTreeMap::new(); /// let syscall_registry = SyscallRegistry::default(); /// register_bpf_function(&config, &mut bpf_functions, &syscall_registry, 0, "entrypoint").unwrap(); - /// let mut executable = Executable::::from_text_bytes(prog, Some(check), config, syscall_registry, bpf_functions).unwrap(); - /// let mut vm = EbpfVm::::new(&executable, &mut [], Vec::new()).unwrap(); + /// let mut executable = Executable::::from_text_bytes(prog, config, syscall_registry, bpf_functions).unwrap(); + /// let verified_executable = VerifiedExecutable::::from_executable(executable).unwrap(); + /// let mut vm = EbpfVm::new(&verified_executable, &mut [], Vec::new()).unwrap(); /// ``` pub fn new( - executable: &'a Pin>>, + verified_executable: &'a VerifiedExecutable, heap_region: &mut [u8], additional_regions: Vec, - ) -> Result, EbpfError> { + ) -> Result, EbpfError> { + let executable = verified_executable.get_executable(); let config = executable.get_config(); let mut stack = CallFrames::new(config); let regions: Vec = vec![ MemoryRegion::new_readonly(&[], 0), - executable.get_ro_region(), + verified_executable.get_executable().get_ro_region(), stack.get_memory_region(), MemoryRegion::new_writable(heap_region, ebpf::MM_HEAP_START), ] @@ -511,7 +526,7 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EbpfVm<'a, E, I> { let (program_vm_addr, program) = executable.get_text_bytes(); let number_of_syscalls = executable.get_syscall_registry().get_number_of_syscalls(); let mut vm = EbpfVm { - executable, + verified_executable, program, program_vm_addr, memory_mapping: MemoryMapping::new(regions, config)?, @@ -554,7 +569,7 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EbpfVm<'a, E, I> { /// # Examples /// /// ``` - /// use solana_rbpf::{ebpf, elf::{Executable, register_bpf_function}, vm::{Config, EbpfVm, SyscallObject, SyscallRegistry, TestInstructionMeter}, verifier::check, syscalls::BpfTracePrintf, user_error::UserError}; + /// use solana_rbpf::{ebpf, elf::{Executable, register_bpf_function}, vm::{Config, EbpfVm, SyscallObject, SyscallRegistry, TestInstructionMeter, VerifiedExecutable}, syscalls::BpfTracePrintf, user_error::UserError, verifier::RequisiteVerifier}; /// /// // This program was compiled with clang, from a C program containing the following single /// // instruction: `return bpf_trace_printk("foo %c %c %c\n", 10, 1, 2, 3);` @@ -580,8 +595,9 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EbpfVm<'a, E, I> { /// let config = Config::default(); /// let mut bpf_functions = std::collections::BTreeMap::new(); /// register_bpf_function(&config, &mut bpf_functions, &syscall_registry, 0, "entrypoint").unwrap(); - /// let mut executable = Executable::::from_text_bytes(prog, Some(check), config, syscall_registry, bpf_functions).unwrap(); - /// let mut vm = EbpfVm::::new(&executable, &mut [], Vec::new()).unwrap(); + /// let mut executable = Executable::::from_text_bytes(prog, config, syscall_registry, bpf_functions).unwrap(); + /// let verified_executable = VerifiedExecutable::::from_executable(executable).unwrap(); + /// let mut vm = EbpfVm::new(&verified_executable, &mut [], Vec::new()).unwrap(); /// // Bind a context object instance to the previously registered syscall /// vm.bind_syscall_context_objects(0); /// ``` @@ -589,7 +605,10 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EbpfVm<'a, E, I> { &mut self, syscall_context: C, ) -> Result<(), EbpfError> { - let syscall_registry = self.executable.get_syscall_registry(); + let syscall_registry = self + .verified_executable + .get_executable() + .get_syscall_registry(); for syscall in syscall_registry.entries.values() { let syscall_object_init_fn: SyscallInit = @@ -618,7 +637,8 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EbpfVm<'a, E, I> { /// Lookup a syscall context object by its function pointer. Used for testing and validation. pub fn get_syscall_context_object(&self, syscall_function: usize) -> Option<*mut u8> { - self.executable + self.verified_executable + .get_executable() .get_syscall_registry() .lookup_context_object_slot(syscall_function as u64) .map(|slot| self.syscall_context_objects[SYSCALL_CONTEXT_OBJECTS_OFFSET + slot]) @@ -632,7 +652,7 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EbpfVm<'a, E, I> { /// # Examples /// /// ``` - /// use solana_rbpf::{ebpf, elf::{Executable, register_bpf_function}, memory_region::MemoryRegion, vm::{Config, EbpfVm, TestInstructionMeter, SyscallRegistry}, verifier::check, user_error::UserError}; + /// use solana_rbpf::{ebpf, elf::{Executable, register_bpf_function}, memory_region::MemoryRegion, vm::{Config, EbpfVm, TestInstructionMeter, SyscallRegistry, VerifiedExecutable}, user_error::UserError, verifier::RequisiteVerifier}; /// /// let prog = &[ /// 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // exit @@ -646,9 +666,10 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EbpfVm<'a, E, I> { /// let mut bpf_functions = std::collections::BTreeMap::new(); /// let syscall_registry = SyscallRegistry::default(); /// register_bpf_function(&config, &mut bpf_functions, &syscall_registry, 0, "entrypoint").unwrap(); - /// let mut executable = Executable::::from_text_bytes(prog, Some(check), config, syscall_registry, bpf_functions).unwrap(); + /// let mut executable = Executable::::from_text_bytes(prog, config, syscall_registry, bpf_functions).unwrap(); + /// let verified_executable = VerifiedExecutable::::from_executable(executable).unwrap(); /// let mem_region = MemoryRegion::new_writable(mem, ebpf::MM_INPUT_START); - /// let mut vm = EbpfVm::::new(&executable, &mut [], vec![mem_region]).unwrap(); + /// let mut vm = EbpfVm::new(&verified_executable, &mut [], vec![mem_region]).unwrap(); /// /// // Provide a reference to the packet data. /// let res = vm.execute_program_interpreted(&mut TestInstructionMeter { remaining: 1 }).unwrap(); @@ -663,7 +684,12 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EbpfVm<'a, E, I> { } (interpreter.initial_insn_count, interpreter.due_insn_count) }; - if self.executable.get_config().enable_instruction_meter { + if self + .verified_executable + .get_executable() + .get_config() + .enable_instruction_meter + { instruction_meter.consume(due_insn_count); self.total_insn_count = initial_insn_count - instruction_meter.get_remaining(); } @@ -679,14 +705,14 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EbpfVm<'a, E, I> { /// the program works with the interpreter before running the JIT-compiled version of it. /// pub fn execute_program_jit(&mut self, instruction_meter: &mut I) -> ProgramResult { - let initial_insn_count = if self.executable.get_config().enable_instruction_meter { + let executable = self.verified_executable.get_executable(); + let initial_insn_count = if executable.get_config().enable_instruction_meter { instruction_meter.get_remaining() } else { 0 }; let result: ProgramResult = Ok(0); - let compiled_program = self - .executable + let compiled_program = executable .get_compiled_program() .ok_or(EbpfError::JitNotCompiled)?; let instruction_meter_final = unsafe { @@ -700,7 +726,7 @@ impl<'a, E: UserDefinedError, I: InstructionMeter> EbpfVm<'a, E, I> { ) .max(0) as u64 }; - if self.executable.get_config().enable_instruction_meter { + if executable.get_config().enable_instruction_meter { let remaining_insn_count = instruction_meter.get_remaining(); let due_insn_count = remaining_insn_count - instruction_meter_final; instruction_meter.consume(due_insn_count); diff --git a/test_utils/src/lib.rs b/test_utils/src/lib.rs index 770a0f15..49e71f3d 100644 --- a/test_utils/src/lib.rs +++ b/test_utils/src/lib.rs @@ -151,3 +151,13 @@ pub const TCP_SACK_NOMATCH: [u8; 66] = [ 0x08, 0x0a, 0x00, 0x17, 0x95, 0x6f, 0x8d, 0x9d, // 0x9e, 0x27, // ]; + +pub struct TautologyVerifier {} +impl solana_rbpf::verifier::Verifier for TautologyVerifier { + fn verify( + _prog: &[u8], + _config: &solana_rbpf::vm::Config, + ) -> std::result::Result<(), solana_rbpf::verifier::VerifierError> { + Ok(()) + } +} diff --git a/tests/assembler.rs b/tests/assembler.rs index 1ad63c49..4da3b329 100644 --- a/tests/assembler.rs +++ b/tests/assembler.rs @@ -12,7 +12,6 @@ use solana_rbpf::{ assembler::assemble, ebpf, user_error::UserError, - verifier::check, vm::{Config, SyscallRegistry, TestInstructionMeter}, }; use test_utils::{TCP_SACK_ASM, TCP_SACK_BIN}; @@ -20,7 +19,6 @@ use test_utils::{TCP_SACK_ASM, TCP_SACK_BIN}; fn asm(src: &str) -> Result, String> { let executable = assemble::( src, - None, Config::default(), SyscallRegistry::default(), )?; @@ -578,7 +576,6 @@ fn test_large_immediate() { fn test_tcp_sack() { let executable = assemble::( TCP_SACK_ASM, - Some(check), Config::default(), SyscallRegistry::default(), ) diff --git a/tests/disassembler.rs b/tests/disassembler.rs index 208047ea..bcb1c5ee 100644 --- a/tests/disassembler.rs +++ b/tests/disassembler.rs @@ -22,13 +22,9 @@ macro_rules! disasm { enable_symbol_and_section_labels: true, ..Config::default() }; - let executable = assemble::( - src, - None, - config, - SyscallRegistry::default(), - ) - .unwrap(); + let executable = + assemble::(src, config, SyscallRegistry::default()) + .unwrap(); let analysis = Analysis::from_executable(&executable).unwrap(); let mut reasm = Vec::new(); analysis.disassemble(&mut reasm).unwrap(); diff --git a/tests/misc.rs b/tests/misc.rs index 8b2b3831..c4e24dfb 100644 --- a/tests/misc.rs +++ b/tests/misc.rs @@ -26,8 +26,10 @@ use solana_rbpf::{ fuzz::fuzz, syscalls::{BpfSyscallContext, BpfSyscallString, BpfSyscallU64}, user_error::UserError, - verifier::check, - vm::{Config, EbpfVm, SyscallObject, SyscallRegistry, TestInstructionMeter}, + verifier::RequisiteVerifier, + vm::{ + Config, EbpfVm, SyscallObject, SyscallRegistry, TestInstructionMeter, VerifiedExecutable, + }, }; use std::{fs::File, io::Read}; @@ -129,21 +131,27 @@ fn test_fuzz_execute() { .unwrap(); if let Ok(executable) = Executable::::from_elf( bytes, - Some(check), Config::default(), syscall_registry, ) { - let mut vm = EbpfVm::::new( - &executable, - &mut [], - Vec::new(), - ) - .unwrap(); - vm.bind_syscall_context_objects(0).unwrap(); - vm.bind_syscall_context_objects(0).unwrap(); - let _ = vm.execute_program_interpreted(&mut TestInstructionMeter { - remaining: 1_000_000, - }); + if let Ok(verified_executable) = VerifiedExecutable::< + RequisiteVerifier, + UserError, + TestInstructionMeter, + >::from_executable(executable) + { + let mut vm = EbpfVm::::new( + &verified_executable, + &mut [], + Vec::new(), + ) + .unwrap(); + vm.bind_syscall_context_objects(0).unwrap(); + vm.bind_syscall_context_objects(0).unwrap(); + let _ = vm.execute_program_interpreted(&mut TestInstructionMeter { + remaining: 1_000_000, + }); + } } }, ); diff --git a/tests/ubpf_execution.rs b/tests/ubpf_execution.rs index e536c2d3..1b6a82e5 100644 --- a/tests/ubpf_execution.rs +++ b/tests/ubpf_execution.rs @@ -22,8 +22,10 @@ use solana_rbpf::{ memory_region::{AccessType, MemoryMapping, MemoryRegion}, syscalls::{self, BpfSyscallContext, Result}, user_error::UserError, - verifier::check, - vm::{Config, EbpfVm, SyscallObject, SyscallRegistry, TestInstructionMeter}, + verifier::RequisiteVerifier, + vm::{ + Config, EbpfVm, SyscallObject, SyscallRegistry, TestInstructionMeter, VerifiedExecutable, + }, }; use std::{collections::BTreeMap, fs::File, io::Read}; use test_utils::{PROG_TCP_PORT_80, TCP_SACK_ASM, TCP_SACK_MATCH, TCP_SACK_NOMATCH}; @@ -40,10 +42,18 @@ macro_rules! test_interpreter_and_jit { ($executable:expr, $mem:tt, $syscall_context:expr, $check:block, $expected_instruction_count:expr) => { #[allow(unused_mut)] let mut check_closure = $check; + #[allow(unused_mut)] + let mut verified_executable = VerifiedExecutable::< + RequisiteVerifier, + UserError, + TestInstructionMeter, + >::from_executable($executable) + .unwrap(); let (instruction_count_interpreter, _tracer_interpreter) = { let mut mem = $mem; let mem_region = MemoryRegion::new_writable(&mut mem, ebpf::MM_INPUT_START); - let mut vm = EbpfVm::new(&$executable, &mut [], vec![mem_region]).unwrap(); + + let mut vm = EbpfVm::new(&verified_executable, &mut [], vec![mem_region]).unwrap(); test_interpreter_and_jit!(bind, vm, $syscall_context); let result = vm.execute_program_interpreted(&mut TestInstructionMeter { remaining: $expected_instruction_count, @@ -55,11 +65,10 @@ macro_rules! test_interpreter_and_jit { { #[allow(unused_mut)] let mut check_closure = $check; - let compilation_result = - Executable::::jit_compile(&mut $executable); + let compilation_result = verified_executable.jit_compile(); let mut mem = $mem; let mem_region = MemoryRegion::new_writable(&mut mem, ebpf::MM_INPUT_START); - let mut vm = EbpfVm::new(&$executable, &mut [], vec![mem_region]).unwrap(); + let mut vm = EbpfVm::new(&verified_executable, &mut [], vec![mem_region]).unwrap(); match compilation_result { Err(err) => assert!(check_closure(&vm, Err(err))), Ok(()) => { @@ -71,9 +80,10 @@ macro_rules! test_interpreter_and_jit { if !check_closure(&vm, result) || !solana_rbpf::vm::Tracer::compare(&_tracer_interpreter, tracer_jit) { - let analysis = - solana_rbpf::static_analysis::Analysis::from_executable(&$executable) - .unwrap(); + let analysis = solana_rbpf::static_analysis::Analysis::from_executable( + verified_executable.get_executable(), + ) + .unwrap(); let stdout = std::io::stdout(); _tracer_interpreter .write(&mut stdout.lock(), &analysis) @@ -81,14 +91,22 @@ macro_rules! test_interpreter_and_jit { tracer_jit.write(&mut stdout.lock(), &analysis).unwrap(); panic!(); } - if $executable.get_config().enable_instruction_meter { + if verified_executable + .get_executable() + .get_config() + .enable_instruction_meter + { let instruction_count_jit = vm.get_total_instruction_count(); assert_eq!(instruction_count_interpreter, instruction_count_jit); } } } } - if $executable.get_config().enable_instruction_meter { + if verified_executable + .get_executable() + .get_config() + .enable_instruction_meter + { assert_eq!(instruction_count_interpreter, $expected_instruction_count); } }; @@ -100,7 +118,7 @@ macro_rules! test_interpreter_and_jit_asm { { let mut syscall_registry = SyscallRegistry::default(); $(test_interpreter_and_jit!(register, syscall_registry, $location => $syscall_init; $syscall_function);)* - let mut executable = assemble($source, Some(check), $config, syscall_registry).unwrap(); + let mut executable = assemble($source, $config, syscall_registry).unwrap(); test_interpreter_and_jit!(executable, $mem, $syscall_context, $check, $expected_instruction_count); } }; @@ -126,7 +144,7 @@ macro_rules! test_interpreter_and_jit_elf { { let mut syscall_registry = SyscallRegistry::default(); $(test_interpreter_and_jit!(register, syscall_registry, $location => $syscall_init; $syscall_function);)* - let mut executable = Executable::::from_elf(&elf, Some(check), $config, syscall_registry).unwrap(); + let mut executable = Executable::::from_elf(&elf, $config, syscall_registry).unwrap(); test_interpreter_and_jit!(executable, $mem, $syscall_context, $check, $expected_instruction_count); } }; @@ -2891,7 +2909,6 @@ fn test_err_mem_access_out_of_bound() { #[allow(unused_mut)] let mut executable = Executable::::from_text_bytes( &prog, - Some(check), config, syscall_registry, bpf_functions, @@ -3372,7 +3389,7 @@ fn test_syscall_with_context() { b"SyscallWithContext" => syscalls::SyscallWithContext::init::< syscalls::BpfSyscallContext, UserError>; syscalls::SyscallWithContext::call ), 42, - { |vm: &EbpfVm, res: Result| { + { |vm: &EbpfVm, res: Result| { let syscall_context_object = unsafe { &*(vm.get_syscall_context_object(syscalls::SyscallWithContext::call as usize).unwrap() as *const syscalls::SyscallWithContext) }; assert_eq!(syscall_context_object.context, 84); res.unwrap() == 0 @@ -3416,7 +3433,6 @@ impl SyscallObject for NestedVmSyscall { ldxb r1, [r1] syscall NestedVmSyscall exit", - Some(check), Config::default(), syscall_registry, ) @@ -3552,13 +3568,9 @@ fn test_custom_entrypoint() { let mut syscall_registry = SyscallRegistry::default(); test_interpreter_and_jit!(register, syscall_registry, b"log_64" => syscalls::BpfSyscallU64::init::; syscalls::BpfSyscallU64::call); #[allow(unused_mut)] - let mut executable = Executable::::from_elf( - &elf, - Some(check), - config, - syscall_registry, - ) - .unwrap(); + let mut executable = + Executable::::from_elf(&elf, config, syscall_registry) + .unwrap(); test_interpreter_and_jit!( executable, [], @@ -3981,7 +3993,7 @@ fn test_err_unresolved_elf() { ..Config::default() }; assert!( - matches!(Executable::::from_elf(&elf, Some(check), config, syscall_registry), Err(EbpfError::ElfError(ElfError::UnresolvedSymbol(symbol, pc, offset))) if symbol == "log_64" && pc == 550 && offset == 4168) + matches!(Executable::::from_elf(&elf, config, syscall_registry), Err(EbpfError::ElfError(ElfError::UnresolvedSymbol(symbol, pc, offset))) if symbol == "log_64" && pc == 550 && offset == 4168) ); } @@ -4382,23 +4394,32 @@ fn execute_generated_program(prog: &[u8]) -> bool { .unwrap(); let executable = Executable::::from_text_bytes( prog, - Some(check), config, syscall_registry, bpf_functions, ); - let mut executable = if let Ok(executable) = executable { + let executable = if let Ok(executable) = executable { executable } else { return false; }; - if Executable::::jit_compile(&mut executable).is_err() { + let verified_executable = VerifiedExecutable::< + solana_rbpf::verifier::RequisiteVerifier, + UserError, + TestInstructionMeter, + >::from_executable(executable); + let mut verified_executable = if let Ok(verified_executable) = verified_executable { + verified_executable + } else { + return false; + }; + if verified_executable.jit_compile().is_err() { return false; } let (instruction_count_interpreter, tracer_interpreter, result_interpreter) = { let mut mem = vec![0u8; mem_size]; let mem_region = MemoryRegion::new_writable(&mut mem, ebpf::MM_INPUT_START); - let mut vm = EbpfVm::new(&executable, &mut [], vec![mem_region]).unwrap(); + let mut vm = EbpfVm::new(&verified_executable, &mut [], vec![mem_region]).unwrap(); let result_interpreter = vm.execute_program_interpreted(&mut TestInstructionMeter { remaining: max_instruction_count, }); @@ -4411,7 +4432,7 @@ fn execute_generated_program(prog: &[u8]) -> bool { }; let mut mem = vec![0u8; mem_size]; let mem_region = MemoryRegion::new_writable(&mut mem, ebpf::MM_INPUT_START); - let mut vm = EbpfVm::new(&executable, &mut [], vec![mem_region]).unwrap(); + let mut vm = EbpfVm::new(&verified_executable, &mut [], vec![mem_region]).unwrap(); let result_jit = vm.execute_program_jit(&mut TestInstructionMeter { remaining: max_instruction_count, }); @@ -4419,8 +4440,10 @@ fn execute_generated_program(prog: &[u8]) -> bool { if result_interpreter != result_jit || !solana_rbpf::vm::Tracer::compare(&tracer_interpreter, tracer_jit) { - let analysis = - solana_rbpf::static_analysis::Analysis::from_executable(&executable).unwrap(); + let analysis = solana_rbpf::static_analysis::Analysis::from_executable( + verified_executable.get_executable(), + ) + .unwrap(); println!("result_interpreter={:?}", result_interpreter); println!("result_jit={:?}", result_jit); let stdout = std::io::stdout(); @@ -4430,7 +4453,11 @@ fn execute_generated_program(prog: &[u8]) -> bool { tracer_jit.write(&mut stdout.lock(), &analysis).unwrap(); panic!(); } - if executable.get_config().enable_instruction_meter { + if verified_executable + .get_executable() + .get_config() + .enable_instruction_meter + { let instruction_count_jit = vm.get_total_instruction_count(); assert_eq!(instruction_count_interpreter, instruction_count_jit); } diff --git a/tests/ubpf_verifier.rs b/tests/ubpf_verifier.rs index 15aeb59e..d367d685 100644 --- a/tests/ubpf_verifier.rs +++ b/tests/ubpf_verifier.rs @@ -28,10 +28,11 @@ use solana_rbpf::{ elf::Executable, error::UserDefinedError, user_error::UserError, - verifier::{check, VerifierError}, - vm::{Config, EbpfVm, SyscallRegistry, TestInstructionMeter}, + verifier::{RequisiteVerifier, Verifier, VerifierError}, + vm::{Config, EbpfVm, SyscallRegistry, TestInstructionMeter, VerifiedExecutable}, }; use std::collections::BTreeMap; +use test_utils::TautologyVerifier; use thiserror::Error; /// Error definitions @@ -42,51 +43,72 @@ pub enum VerifierTestError { } impl UserDefinedError for VerifierTestError {} +struct ContradictionVerifier {} +impl Verifier for ContradictionVerifier { + fn verify(_prog: &[u8], _config: &Config) -> std::result::Result<(), VerifierError> { + Err(VerifierError::NoProgram) + } +} + #[test] fn test_verifier_success() { let executable = assemble::( " mov32 r0, 0xBEE exit", - Some(|_prog: &[u8], _config: &Config| Ok(())), Config::default(), SyscallRegistry::default(), ) .unwrap(); - let _vm = - EbpfVm::::new(&executable, &mut [], Vec::new()).unwrap(); + let verified_executable = + VerifiedExecutable::::from_executable( + executable, + ) + .unwrap(); + let _vm = EbpfVm::::new( + &verified_executable, + &mut [], + Vec::new(), + ) + .unwrap(); } #[test] #[should_panic(expected = "NoProgram")] fn test_verifier_fail() { - fn verifier_fail(_prog: &[u8], _config: &Config) -> Result<(), VerifierError> { - Err(VerifierError::NoProgram) - } - let _executable = assemble::( + let executable = assemble::( " mov32 r0, 0xBEE exit", - Some(verifier_fail), Config::default(), SyscallRegistry::default(), ) .unwrap(); + let _verified_executable = VerifiedExecutable::< + ContradictionVerifier, + UserError, + TestInstructionMeter, + >::from_executable(executable) + .unwrap(); } #[test] #[should_panic(expected = "DivisionByZero(30)")] fn test_verifier_err_div_by_zero_imm() { - let _executable = assemble::( + let executable = assemble::( " mov32 r0, 1 div32 r0, 0 exit", - Some(check), Config::default(), SyscallRegistry::default(), ) .unwrap(); + let _verified_executable = + VerifiedExecutable::::from_executable( + executable, + ) + .unwrap(); } #[test] @@ -97,14 +119,18 @@ fn test_verifier_err_endian_size() { 0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ]; - let _ = Executable::::from_text_bytes( + let executable = Executable::::from_text_bytes( prog, - Some(check), Config::default(), SyscallRegistry::default(), BTreeMap::default(), ) .unwrap(); + let _verified_executable = + VerifiedExecutable::::from_executable( + executable, + ) + .unwrap(); } #[test] @@ -115,14 +141,18 @@ fn test_verifier_err_incomplete_lddw() { 0x18, 0x00, 0x00, 0x00, 0x88, 0x77, 0x66, 0x55, // 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ]; - let _ = Executable::::from_text_bytes( + let executable = Executable::::from_text_bytes( prog, - Some(check), Config::default(), SyscallRegistry::default(), BTreeMap::default(), ) .unwrap(); + let _verified_executable = + VerifiedExecutable::::from_executable( + executable, + ) + .unwrap(); } #[test] @@ -130,19 +160,22 @@ 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] { + let executable = assemble::( + " + mov r11, 1 + exit", + Config { + dynamic_stack_frames, + ..Config::default() + }, + SyscallRegistry::default(), + ) + .unwrap(); + let result = VerifiedExecutable::::from_executable(executable) + .map_err(|err| format!("Executable constructor {:?}", err)); + assert_eq!( - assemble::( - " - mov r11, 1 - exit", - Some(check), - Config { - dynamic_stack_frames, - ..Config::default() - }, - SyscallRegistry::default(), - ) - .unwrap_err(), + result.unwrap_err(), "Executable constructor VerifierError(InvalidDestinationRegister(29))" ); } @@ -153,19 +186,22 @@ 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] { + let executable = assemble::( + " + mov r0, r11 + exit", + Config { + dynamic_stack_frames, + ..Config::default() + }, + SyscallRegistry::default(), + ) + .unwrap(); + let result = VerifiedExecutable::::from_executable(executable) + .map_err(|err| format!("Executable constructor {:?}", err)); + assert_eq!( - assemble::( - " - mov r0, r11 - exit", - Some(check), - Config { - dynamic_stack_frames, - ..Config::default() - }, - SyscallRegistry::default(), - ) - .unwrap_err(), + result.unwrap_err(), "Executable constructor VerifierError(InvalidSourceRegister(29))" ); } @@ -173,12 +209,11 @@ fn test_verifier_err_invalid_reg_src() { #[test] fn test_verifier_resize_stack_ptr_success() { - let _executable = assemble::( + let executable = assemble::( " sub r11, 1 add r11, 1 exit", - Some(check), Config { dynamic_stack_frames: true, enable_stack_frame_gaps: false, @@ -187,49 +222,66 @@ fn test_verifier_resize_stack_ptr_success() { SyscallRegistry::default(), ) .unwrap(); + let _verified_executable = + VerifiedExecutable::::from_executable( + executable, + ) + .unwrap(); } #[test] #[should_panic(expected = "JumpToMiddleOfLDDW(2, 29)")] fn test_verifier_err_jmp_lddw() { - let _executable = assemble::( + let executable = assemble::( " ja +1 lddw r0, 0x1122334455667788 exit", - Some(check), Config::default(), SyscallRegistry::default(), ) .unwrap(); + let _verified_executable = + VerifiedExecutable::::from_executable( + executable, + ) + .unwrap(); } #[test] #[should_panic(expected = "JumpOutOfCode(3, 29)")] -fn test_verifier_err_jmp_out_end() { - let _executable = assemble::( +fn test_verifier_err_jmp_out() { + let executable = assemble::( " ja +2 exit", - Some(check), Config::default(), SyscallRegistry::default(), ) .unwrap(); + let _verified_executable = + VerifiedExecutable::::from_executable( + executable, + ) + .unwrap(); } #[test] #[should_panic(expected = "JumpOutOfCode(18446744073709551615, 29)")] fn test_verifier_err_jmp_out_start() { - let _executable = assemble::( + let executable = assemble::( " ja -2 exit", - Some(check), Config::default(), SyscallRegistry::default(), ) .unwrap(); + let _verified_executable = + VerifiedExecutable::::from_executable( + executable, + ) + .unwrap(); } #[test] @@ -239,28 +291,36 @@ fn test_verifier_err_unknown_opcode() { 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ]; - let _ = Executable::::from_text_bytes( + let executable = Executable::::from_text_bytes( prog, - Some(check), Config::default(), SyscallRegistry::default(), BTreeMap::default(), ) .unwrap(); + let _verified_executable = + VerifiedExecutable::::from_executable( + executable, + ) + .unwrap(); } #[test] #[should_panic(expected = "CannotWriteR10(29)")] fn test_verifier_err_write_r10() { - let _executable = assemble::( + let executable = assemble::( " mov r10, 1 exit", - Some(check), Config::default(), SyscallRegistry::default(), ) .unwrap(); + let _verified_executable = + VerifiedExecutable::::from_executable( + executable, + ) + .unwrap(); } #[test] @@ -291,12 +351,14 @@ fn test_verifier_err_all_shift_overflows() { for (overflowing_instruction, expected) in testcases { let assembly = format!("\n{}\nexit", overflowing_instruction); - let result = assemble::( + let executable = assemble::( &assembly, - Some(check), Config::default(), SyscallRegistry::default(), - ); + ) + .unwrap(); + let result = VerifiedExecutable::::from_executable(executable) + .map_err(|err| format!("Executable constructor {:?}", err)); match expected { Ok(()) => assert!(result.is_ok()), Err(overflow_msg) => match result { @@ -326,16 +388,17 @@ fn test_verifier_err_ldabs_ldind_disabled() { for (opc, instruction) in instructions { for disable_deprecated_load_instructions in [true, false] { let assembly = format!("\n{}\nexit", instruction); - let result = assemble::( + let executable = assemble::( &assembly, - Some(check), Config { disable_deprecated_load_instructions, ..Config::default() }, SyscallRegistry::default(), - ); - + ) + .unwrap(); + let result = VerifiedExecutable::::from_executable(executable) + .map_err(|err| format!("Executable constructor {:?}", err)); if disable_deprecated_load_instructions { assert_eq!( result.unwrap_err(), @@ -364,16 +427,17 @@ fn test_sdiv_disabled() { for (opc, instruction) in instructions { for enable_sdiv in [true, false] { let assembly = format!("\n{}\nexit", instruction); - let result = assemble::( + let executable = assemble::( &assembly, - Some(check), Config { enable_sdiv, ..Config::default() }, SyscallRegistry::default(), - ); - + ) + .unwrap(); + let result = VerifiedExecutable::::from_executable(executable) + .map_err(|err| format!("Executable constructor {:?}", err)); if enable_sdiv { assert!(result.is_ok()); } else {