diff --git a/benches/vm_execution.rs b/benches/vm_execution.rs index 4404c927..2fc2c370 100644 --- a/benches/vm_execution.rs +++ b/benches/vm_execution.rs @@ -145,7 +145,7 @@ fn bench_jit_vs_interpreter_address_translation(bencher: &mut Bencher) { mov r0, r1 and r0, 0xFFFFFF jlt r0, 0x20000, -5 - exit", + return", Config::default(), 655361, &mut [0; 0x20000], @@ -161,15 +161,15 @@ static ADDRESS_TRANSLATION_STACK_CODE: &str = " add r3, -1 ldxb r4, [r3] add r2, 1 - jlt r2, 0x10000, -8 - exit"; + jlt r2, 0x10000, -8"; #[cfg(all(feature = "jit", not(target_os = "windows"), target_arch = "x86_64"))] #[bench] fn bench_jit_vs_interpreter_address_translation_stack_fixed(bencher: &mut Bencher) { + let asm = format!("{ADDRESS_TRANSLATION_STACK_CODE}\nexit"); bench_jit_vs_interpreter( bencher, - ADDRESS_TRANSLATION_STACK_CODE, + asm.as_str(), Config { enabled_sbpf_versions: SBPFVersion::V1..=SBPFVersion::V1, ..Config::default() @@ -182,9 +182,10 @@ fn bench_jit_vs_interpreter_address_translation_stack_fixed(bencher: &mut Benche #[cfg(all(feature = "jit", not(target_os = "windows"), target_arch = "x86_64"))] #[bench] fn bench_jit_vs_interpreter_address_translation_stack_dynamic(bencher: &mut Bencher) { + let asm = format!("{ADDRESS_TRANSLATION_STACK_CODE}\nreturn"); bench_jit_vs_interpreter( bencher, - ADDRESS_TRANSLATION_STACK_CODE, + asm.as_str(), Config { enabled_sbpf_versions: SBPFVersion::V1..=SBPFVersion::V2, ..Config::default() @@ -204,7 +205,7 @@ fn bench_jit_vs_interpreter_empty_for_loop(bencher: &mut Bencher) { and r1, 1023 add r2, 1 jlt r2, 0x10000, -4 - exit", + return", Config::default(), 262145, &mut [0; 0], @@ -252,7 +253,7 @@ fn bench_jit_vs_interpreter_call_depth_dynamic(bencher: &mut Bencher) { mov r1, 18 call function_foo jlt r6, 1024, -4 - exit + return function_foo: add r11, -4 stw [r10-4], 0x11223344 @@ -262,7 +263,7 @@ fn bench_jit_vs_interpreter_call_depth_dynamic(bencher: &mut Bencher) { add r1, -1 call function_foo add r11, 4 - exit", + return", Config { enabled_sbpf_versions: SBPFVersion::V1..=SBPFVersion::V2, ..Config::default() diff --git a/src/assembler.rs b/src/assembler.rs index d3966ffb..7aa0cec7 100644 --- a/src/assembler.rs +++ b/src/assembler.rs @@ -113,6 +113,7 @@ fn make_instruction_map(sbpf_version: &SBPFVersion) -> HashMap( src: &str, loader: Arc>, ) -> Result, String> { - let sbpf_version = loader.get_config().enabled_sbpf_versions.end().clone(); + let sbpf_version = *loader.get_config().enabled_sbpf_versions.end(); let statements = parse(src)?; let instruction_map = make_instruction_map(&sbpf_version); diff --git a/src/disassembler.rs b/src/disassembler.rs index d96f762c..1d985fd7 100644 --- a/src/disassembler.rs +++ b/src/disassembler.rs @@ -284,6 +284,7 @@ pub fn disassemble_instruction( }, ebpf::CALL_REG => { name = "callx"; desc = format!("{} r{}", name, if sbpf_version.callx_uses_src_reg() { insn.src } else { insn.imm as u8 }); }, ebpf::EXIT => { name = "exit"; desc = name.to_string(); }, + ebpf::RETURN => { name = "return"; desc = name.to_string(); }, _ => { name = "unknown"; desc = format!("{} opcode={:#x}", name, insn.opc); }, }; diff --git a/src/ebpf.rs b/src/ebpf.rs index b15a2b7f..6608452b 100644 --- a/src/ebpf.rs +++ b/src/ebpf.rs @@ -480,8 +480,10 @@ pub const JSLE_REG: u8 = BPF_JMP | BPF_X | BPF_JSLE; pub const CALL_IMM: u8 = BPF_JMP | BPF_CALL; /// BPF opcode: tail call. pub const CALL_REG: u8 = BPF_JMP | BPF_X | BPF_CALL; -/// BPF opcode: `exit` /// `return r0`. +/// BPF opcode: `exit` /// `return r0`. /// Valid only for SBPFv1 pub const EXIT: u8 = BPF_JMP | BPF_EXIT; +/// BPF opcode: `return` /// `return r0`. /// Valid only for SBPFv2 +pub const RETURN: u8 = BPF_JMP | BPF_X | BPF_EXIT; // Used in JIT /// Mask to extract the operation class from an operation code. diff --git a/src/interpreter.rs b/src/interpreter.rs index 1ed54209..f296d2e9 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -566,7 +566,13 @@ impl<'a, 'b, C: ContextObject> Interpreter<'a, 'b, C> { } } - ebpf::EXIT => { + ebpf::EXIT + | ebpf::RETURN => { + if (insn.opc == ebpf::EXIT && self.executable.get_sbpf_version().static_syscalls()) + || (insn.opc == ebpf::RETURN && !self.executable.get_sbpf_version().static_syscalls()) { + throw_error!(self, EbpfError::UnsupportedInstruction); + } + if self.vm.call_depth == 0 { if config.enable_instruction_meter && self.vm.due_insn_count > self.vm.previous_instruction_meter { throw_error!(self, EbpfError::ExceededMaxInstructions); diff --git a/src/jit.rs b/src/jit.rs index fbcbb556..e014816a 100644 --- a/src/jit.rs +++ b/src/jit.rs @@ -745,7 +745,13 @@ impl<'a, C: ContextObject> JitCompiler<'a, C> { }; self.emit_internal_call(Value::Register(target_pc)); }, - ebpf::EXIT => { + ebpf::EXIT + | ebpf::RETURN => { + if (insn.opc == ebpf::EXIT && self.executable.get_sbpf_version().static_syscalls()) + || (insn.opc == ebpf::RETURN && !self.executable.get_sbpf_version().static_syscalls()) { + return Err(EbpfError::UnsupportedInstruction); + } + self.emit_validate_instruction_count(true, Some(self.pc)); let call_depth_access = X86IndirectAccess::Offset(self.slot_in_vm(RuntimeEnvironmentSlot::CallDepth)); @@ -772,7 +778,7 @@ impl<'a, C: ContextObject> JitCompiler<'a, C> { // and return self.emit_profile_instruction_count(false, Some(0)); self.emit_ins(X86Instruction::return_near()); - }, + } _ => return Err(EbpfError::UnsupportedInstruction), } @@ -1736,7 +1742,7 @@ mod tests { } fn create_mockup_executable(config: Config, program: &[u8]) -> Executable { - let sbpf_version = config.enabled_sbpf_versions.end().clone(); + let sbpf_version = *config.enabled_sbpf_versions.clone().end(); let mut function_registry = FunctionRegistry::>::default(); function_registry @@ -1769,7 +1775,7 @@ mod tests { let empty_program_machine_code_length = { let config = Config { noop_instruction_rate: 0, - enabled_sbpf_versions: sbpf_version.clone()..=sbpf_version.clone(), + enabled_sbpf_versions: sbpf_version..=sbpf_version, ..Config::default() }; let mut executable = create_mockup_executable(config, &prog[0..0]); @@ -1780,7 +1786,7 @@ mod tests { .machine_code_length() }; assert!(empty_program_machine_code_length <= MAX_EMPTY_PROGRAM_MACHINE_CODE_LENGTH); - empty_program_machine_code_length_per_version[sbpf_version.clone() as usize] = + empty_program_machine_code_length_per_version[sbpf_version as usize] = empty_program_machine_code_length; } @@ -1814,7 +1820,7 @@ mod tests { for sbpf_version in [SBPFVersion::V1, SBPFVersion::V2] { let empty_program_machine_code_length = - empty_program_machine_code_length_per_version[sbpf_version.clone() as usize]; + empty_program_machine_code_length_per_version[sbpf_version as usize]; for mut opcode in 0x00..=0xFF { let (registers, immediate) = match opcode { @@ -1841,7 +1847,7 @@ mod tests { } let config = Config { noop_instruction_rate: 0, - enabled_sbpf_versions: sbpf_version.clone()..=sbpf_version.clone(), + enabled_sbpf_versions: sbpf_version..=sbpf_version, ..Config::default() }; let mut executable = create_mockup_executable(config, &prog); diff --git a/src/program.rs b/src/program.rs index 59c68a76..1f817c02 100644 --- a/src/program.rs +++ b/src/program.rs @@ -9,7 +9,7 @@ use { }; /// Defines a set of sbpf_version of an executable -#[derive(Debug, PartialEq, PartialOrd, Eq, Clone)] +#[derive(Debug, PartialEq, PartialOrd, Eq, Clone, Copy)] pub enum SBPFVersion { /// The legacy format V1, diff --git a/src/verifier.rs b/src/verifier.rs index 779a7498..e9a28024 100644 --- a/src/verifier.rs +++ b/src/verifier.rs @@ -236,7 +236,7 @@ impl Verifier for RequisiteVerifier { function_range.end = *function_iter.peek().unwrap_or(&program_range.end); let insn = ebpf::get_insn(prog, function_range.end.saturating_sub(1)); match insn.opc { - ebpf::JA | ebpf::EXIT => {}, + ebpf::JA | ebpf::RETURN => {}, _ => return Err(VerifierError::InvalidFunction( function_range.end.saturating_sub(1), )), @@ -390,7 +390,8 @@ impl Verifier for RequisiteVerifier { ebpf::CALL_IMM if sbpf_version.static_syscalls() && insn.src == 0 => { check_call_target(insn.imm as u32, syscall_registry)?; }, ebpf::CALL_IMM => {}, ebpf::CALL_REG => { check_callx_register(&insn, insn_ptr, sbpf_version)?; }, - ebpf::EXIT => {}, + ebpf::EXIT if !sbpf_version.static_syscalls() => {}, + ebpf::RETURN if sbpf_version.static_syscalls() => {}, _ => { return Err(VerifierError::UnknownOpCode(insn.opc, insn_ptr)); diff --git a/src/vm.rs b/src/vm.rs index f4a9a15d..7bff55c4 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -243,7 +243,7 @@ pub struct CallFrame { /// }; /// /// let prog = &[ -/// 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // exit +/// 0x9d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // exit /// ]; /// let mem = &mut [ /// 0xaa, 0xbb, 0x11, 0x22, 0xcc, 0xdd diff --git a/test_utils/src/lib.rs b/test_utils/src/lib.rs index 360b7c7e..06d06c53 100644 --- a/test_utils/src/lib.rs +++ b/test_utils/src/lib.rs @@ -38,7 +38,7 @@ pub const PROG_TCP_PORT_80: &str = " ldxh r1, [r1+0x0] jne r1, 0x5000, +0x1 mov64 r0, 0x1 - exit"; + return"; pub const TCP_SACK_ASM: &str = " ldxb r2, [r1+12] @@ -84,7 +84,7 @@ pub const TCP_SACK_ASM: &str = " jsgt r2, r3, -18 ja +1 mov r0, 0x1 - exit"; + return"; pub const TCP_SACK_BIN: [u8; 352] = [ 0x2c, 0x12, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, // @@ -130,7 +130,7 @@ pub const TCP_SACK_BIN: [u8; 352] = [ 0x6d, 0x32, 0xee, 0xff, 0x00, 0x00, 0x00, 0x00, // 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xb7, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // - 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // + 0x9d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ]; pub const TCP_SACK_MATCH: [u8; 78] = [ diff --git a/tests/assembler.rs b/tests/assembler.rs index 2898f7da..55edfcbc 100644 --- a/tests/assembler.rs +++ b/tests/assembler.rs @@ -53,6 +53,11 @@ fn test_exit() { assert_eq!(asm("exit"), Ok(vec![insn(0, ebpf::EXIT, 0, 0, 0, 0)])); } +#[test] +fn test_return() { + assert_eq!(asm("return"), Ok(vec![insn(0, ebpf::RETURN, 0, 0, 0, 0)])); +} + // Example for InstructionType::AluBinary. #[test] fn test_add64() { diff --git a/tests/disassembler.rs b/tests/disassembler.rs index 6b3b25c3..d39aad25 100644 --- a/tests/disassembler.rs +++ b/tests/disassembler.rs @@ -45,6 +45,11 @@ fn test_exit() { disasm!("entrypoint:\n exit\n"); } +#[test] +fn test_return() { + disasm!("entrypoint:\n return\n"); +} + // Example for InstructionType::AluBinary. #[test] fn test_add64() { diff --git a/tests/execution.rs b/tests/execution.rs index 52a10116..bcd8242b 100644 --- a/tests/execution.rs +++ b/tests/execution.rs @@ -95,8 +95,8 @@ macro_rules! test_interpreter_and_jit { None ); match compilation_result { - Err(err) => assert_eq!( - format!("{:?}", err), + Err(_) => assert_eq!( + format!("{:?}", compilation_result), expected_result, "Unexpected result for JIT compilation" ), @@ -143,7 +143,7 @@ macro_rules! test_interpreter_and_jit { } macro_rules! test_interpreter_and_jit_asm { - ($source:tt, $config:expr, $mem:tt, ($($location:expr => $syscall_function:expr),* $(,)?), $context_object:expr, $expected_result:expr $(,)?) => { + ($source:expr, $config:expr, $mem:tt, ($($location:expr => $syscall_function:expr),* $(,)?), $context_object:expr, $expected_result:expr $(,)?) => { #[allow(unused_mut)] { let mut config = $config; @@ -155,7 +155,7 @@ macro_rules! test_interpreter_and_jit_asm { test_interpreter_and_jit!(executable, $mem, $context_object, $expected_result); } }; - ($source:tt, $mem:tt, ($($location:expr => $syscall_function:expr),* $(,)?), $context_object:expr, $expected_result:expr $(,)?) => { + ($source:expr, $mem:tt, ($($location:expr => $syscall_function:expr),* $(,)?), $context_object:expr, $expected_result:expr $(,)?) => { #[allow(unused_mut)] { test_interpreter_and_jit_asm!($source, Config::default(), $mem, ($($location => $syscall_function),*), $context_object, $expected_result); @@ -196,7 +196,7 @@ fn test_mov32_imm() { test_interpreter_and_jit_asm!( " mov32 r0, 1 - exit", + return", [], (), TestContextObject::new(2), @@ -205,7 +205,7 @@ fn test_mov32_imm() { test_interpreter_and_jit_asm!( " mov32 r0, -1 - exit", + return", [], (), TestContextObject::new(2), @@ -219,7 +219,7 @@ fn test_mov32_reg() { " mov32 r1, 1 mov32 r0, r1 - exit", + return", [], (), TestContextObject::new(3), @@ -229,7 +229,7 @@ fn test_mov32_reg() { " mov32 r1, -1 mov32 r0, r1 - exit", + return", [], (), TestContextObject::new(3), @@ -242,7 +242,7 @@ fn test_mov64_imm() { test_interpreter_and_jit_asm!( " mov64 r0, 1 - exit", + return", [], (), TestContextObject::new(2), @@ -251,7 +251,7 @@ fn test_mov64_imm() { test_interpreter_and_jit_asm!( " mov64 r0, -1 - exit", + return", [], (), TestContextObject::new(2), @@ -265,7 +265,7 @@ fn test_mov64_reg() { " mov64 r1, 1 mov64 r0, r1 - exit", + return", [], (), TestContextObject::new(3), @@ -275,7 +275,7 @@ fn test_mov64_reg() { " mov64 r1, -1 mov64 r0, r1 - exit", + return", [], (), TestContextObject::new(3), @@ -293,7 +293,7 @@ fn test_bounce() { mov r8, r7 mov r9, r8 mov r0, r9 - exit", + return", [], (), TestContextObject::new(7), @@ -309,7 +309,7 @@ fn test_add32() { mov32 r1, 2 add32 r0, 1 add32 r0, r1 - exit", + return", [], (), TestContextObject::new(5), @@ -339,7 +339,7 @@ fn test_alu32_arithmetic() { lmul32 r0, r3 udiv32 r0, 2 udiv32 r0, r4 - exit", + return", [], (), TestContextObject::new(19), @@ -369,7 +369,7 @@ fn test_alu64_arithmetic() { lmul r0, r3 udiv r0, 2 udiv r0, r4 - exit", + return", [], (), TestContextObject::new(19), @@ -422,7 +422,7 @@ fn test_lmul128() { rsh64 r4, 0x20 or64 r0, r4 stxdw [r1+0x0], r0 - exit", + return", [0; 16], (), TestContextObject::new(42), @@ -454,7 +454,7 @@ fn test_alu32_logic() { rsh32 r0, r7 xor32 r0, 0x03 xor32 r0, r2 - exit", + return", [], (), TestContextObject::new(21), @@ -488,7 +488,7 @@ fn test_alu64_logic() { rsh r0, r7 xor r0, 0x03 xor r0, r2 - exit", + return", [], (), TestContextObject::new(23), @@ -504,7 +504,7 @@ fn test_arsh32_high_shift() { mov32 r1, 0x00000001 hor64 r1, 0x00000001 arsh32 r0, r1 - exit", + return", [], (), TestContextObject::new(5), @@ -519,7 +519,7 @@ fn test_arsh32_imm() { mov32 r0, 0xf8 lsh32 r0, 28 arsh32 r0, 16 - exit", + return", [], (), TestContextObject::new(4), @@ -535,7 +535,7 @@ fn test_arsh32_reg() { mov32 r1, 16 lsh32 r0, 28 arsh32 r0, r1 - exit", + return", [], (), TestContextObject::new(5), @@ -552,7 +552,7 @@ fn test_arsh64() { arsh r0, 55 mov32 r1, 5 arsh r0, r1 - exit", + return", [], (), TestContextObject::new(6), @@ -567,7 +567,7 @@ fn test_lsh64_reg() { mov r0, 0x1 mov r7, 4 lsh r0, r7 - exit", + return", [], (), TestContextObject::new(4), @@ -582,7 +582,7 @@ fn test_rhs32_imm() { xor r0, r0 add r0, -1 rsh32 r0, 8 - exit", + return", [], (), TestContextObject::new(4), @@ -597,7 +597,7 @@ fn test_rsh64_reg() { mov r0, 0x10 mov r7, 4 rsh r0, r7 - exit", + return", [], (), TestContextObject::new(4), @@ -611,7 +611,7 @@ fn test_be16() { " ldxh r0, [r1] be16 r0 - exit", + return", [0x11, 0x22], (), TestContextObject::new(3), @@ -625,7 +625,7 @@ fn test_be16_high() { " ldxdw r0, [r1] be16 r0 - exit", + return", [0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88], (), TestContextObject::new(3), @@ -639,7 +639,7 @@ fn test_be32() { " ldxw r0, [r1] be32 r0 - exit", + return", [0x11, 0x22, 0x33, 0x44], (), TestContextObject::new(3), @@ -653,7 +653,7 @@ fn test_be32_high() { " ldxdw r0, [r1] be32 r0 - exit", + return", [0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88], (), TestContextObject::new(3), @@ -667,7 +667,7 @@ fn test_be64() { " ldxdw r0, [r1] be64 r0 - exit", + return", [0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88], (), TestContextObject::new(3), @@ -687,7 +687,7 @@ fn test_pqr() { prog[24] = ebpf::HOR64_IMM; prog[25] = 1; // dst = R1 prog[33] = 16; // src = R1 - prog[40] = ebpf::EXIT; + prog[40] = ebpf::RETURN; let loader = Arc::new(BuiltinProgram::new_mock()); for (opc, dst, src, expected_result) in [ (ebpf::UHMUL64_IMM, 13u64, 4u64, 0u64), @@ -841,7 +841,7 @@ fn test_pqr() { fn test_err_divide_by_zero() { let mut prog = [0; 24]; prog[0] = ebpf::MOV32_IMM; - prog[16] = ebpf::EXIT; + prog[16] = ebpf::RETURN; let loader = Arc::new(BuiltinProgram::new_mock()); for opc in [ ebpf::UDIV32_REG, @@ -882,7 +882,7 @@ fn test_err_divide_overflow() { LittleEndian::write_i32(&mut prog[20..], -1); prog[25] = 16; // src = R1 LittleEndian::write_i32(&mut prog[28..], -1); - prog[32] = ebpf::EXIT; + prog[32] = ebpf::RETURN; let loader = Arc::new(BuiltinProgram::new_mock()); for opc in [ ebpf::SDIV32_IMM, @@ -918,35 +918,40 @@ fn test_err_divide_overflow() { #[test] fn test_memory_instructions() { for sbpf_version in [SBPFVersion::V1, SBPFVersion::V2] { + let final_instr = if sbpf_version == SBPFVersion::V1 { + "exit" + } else { + "return" + }; + let config = Config { - enabled_sbpf_versions: sbpf_version.clone()..=sbpf_version, + enabled_sbpf_versions: sbpf_version..=sbpf_version, ..Config::default() }; + let asm = format!("ldxb r0, [r1+2]\n{final_instr}"); test_interpreter_and_jit_asm!( - " - ldxb r0, [r1+2] - exit", + asm.as_str(), config.clone(), [0xaa, 0xbb, 0x11, 0xcc, 0xdd], (), TestContextObject::new(2), ProgramResult::Ok(0x11), ); + + let asm = format!("ldxh r0, [r1+2]\n{final_instr}"); test_interpreter_and_jit_asm!( - " - ldxh r0, [r1+2] - exit", + asm.as_str(), config.clone(), [0xaa, 0xbb, 0x11, 0x22, 0xcc, 0xdd], (), TestContextObject::new(2), ProgramResult::Ok(0x2211), ); + + let asm = format!("ldxw r0, [r1+2]\n{final_instr}"); test_interpreter_and_jit_asm!( - " - ldxw r0, [r1+2] - exit", + asm.as_str(), config.clone(), [ 0xaa, 0xbb, 0x11, 0x22, 0x33, 0x44, 0xcc, 0xdd, // @@ -955,10 +960,10 @@ fn test_memory_instructions() { TestContextObject::new(2), ProgramResult::Ok(0x44332211), ); + + let asm = format!("ldxdw r0, [r1+2]\n{final_instr}"); test_interpreter_and_jit_asm!( - " - ldxdw r0, [r1+2] - exit", + asm.as_str(), config.clone(), [ 0xaa, 0xbb, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, // @@ -969,22 +974,29 @@ fn test_memory_instructions() { ProgramResult::Ok(0x8877665544332211), ); - test_interpreter_and_jit_asm!( + let asm = format!( " stb [r1+2], 0x11 ldxb r0, [r1+2] - exit", + {final_instr}" + ); + test_interpreter_and_jit_asm!( + asm.as_str(), config.clone(), [0xaa, 0xbb, 0xff, 0xcc, 0xdd], (), TestContextObject::new(3), ProgramResult::Ok(0x11), ); - test_interpreter_and_jit_asm!( + + let asm = format!( " sth [r1+2], 0x2211 ldxh r0, [r1+2] - exit", + {final_instr}" + ); + test_interpreter_and_jit_asm!( + asm.as_str(), config.clone(), [ 0xaa, 0xbb, 0xff, 0xff, 0xcc, 0xdd, // @@ -993,11 +1005,15 @@ fn test_memory_instructions() { TestContextObject::new(3), ProgramResult::Ok(0x2211), ); - test_interpreter_and_jit_asm!( + + let asm = format!( " stw [r1+2], 0x44332211 ldxw r0, [r1+2] - exit", + {final_instr}" + ); + test_interpreter_and_jit_asm!( + asm.as_str(), config.clone(), [ 0xaa, 0xbb, 0xff, 0xff, 0xff, 0xff, 0xcc, 0xdd, // @@ -1006,11 +1022,15 @@ fn test_memory_instructions() { TestContextObject::new(3), ProgramResult::Ok(0x44332211), ); - test_interpreter_and_jit_asm!( + + let asm = format!( " stdw [r1+2], 0x44332211 ldxdw r0, [r1+2] - exit", + {final_instr}" + ); + test_interpreter_and_jit_asm!( + asm.as_str(), config.clone(), [ 0xaa, 0xbb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // @@ -1021,12 +1041,15 @@ fn test_memory_instructions() { ProgramResult::Ok(0x44332211), ); - test_interpreter_and_jit_asm!( + let asm = format!( " mov32 r2, 0x11 stxb [r1+2], r2 ldxb r0, [r1+2] - exit", + {final_instr}" + ); + test_interpreter_and_jit_asm!( + asm.as_str(), config.clone(), [ 0xaa, 0xbb, 0xff, 0xcc, 0xdd, // @@ -1035,12 +1058,16 @@ fn test_memory_instructions() { TestContextObject::new(4), ProgramResult::Ok(0x11), ); - test_interpreter_and_jit_asm!( + + let asm = format!( " mov32 r2, 0x2211 stxh [r1+2], r2 ldxh r0, [r1+2] - exit", + {final_instr}" + ); + test_interpreter_and_jit_asm!( + asm.as_str(), config.clone(), [ 0xaa, 0xbb, 0xff, 0xff, 0xcc, 0xdd, // @@ -1049,12 +1076,16 @@ fn test_memory_instructions() { TestContextObject::new(4), ProgramResult::Ok(0x2211), ); - test_interpreter_and_jit_asm!( + + let asm = format!( " mov32 r2, 0x44332211 stxw [r1+2], r2 ldxw r0, [r1+2] - exit", + {final_instr}" + ); + test_interpreter_and_jit_asm!( + asm.as_str(), config.clone(), [ 0xaa, 0xbb, 0xff, 0xff, 0xff, 0xff, 0xcc, 0xdd, // @@ -1063,14 +1094,18 @@ fn test_memory_instructions() { TestContextObject::new(4), ProgramResult::Ok(0x44332211), ); - test_interpreter_and_jit_asm!( + + let asm = format!( " mov r2, -2005440939 lsh r2, 32 or r2, 0x44332211 stxdw [r1+2], r2 ldxdw r0, [r1+2] - exit", + {final_instr}" + ); + test_interpreter_and_jit_asm!( + asm.as_str(), config.clone(), [ 0xaa, 0xbb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // @@ -1089,7 +1124,7 @@ fn test_hor64() { " hor64 r0, 0x10203040 hor64 r0, 0x01020304 - exit", + return", [], (), TestContextObject::new(3), @@ -1104,7 +1139,7 @@ fn test_ldxh_same_reg() { mov r0, r1 sth [r0], 0x1234 ldxh r0, [r0] - exit", + return", [0xff, 0xff], (), TestContextObject::new(4), @@ -1117,7 +1152,7 @@ fn test_err_ldxdw_oob() { test_interpreter_and_jit_asm!( " ldxdw r0, [r1+6] - exit", + return", [ 0xaa, 0xbb, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, // 0x77, 0x88, 0xcc, 0xdd, // @@ -1138,7 +1173,7 @@ fn test_err_ldxdw_nomem() { test_interpreter_and_jit_asm!( " ldxdw r0, [r1+6] - exit", + return", [], (), TestContextObject::new(1), @@ -1185,7 +1220,7 @@ fn test_ldxb_all() { or r0, r7 or r0, r8 or r0, r9 - exit", + return", [ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // 0x08, 0x09, // @@ -1240,7 +1275,7 @@ fn test_ldxh_all() { or r0, r7 or r0, r8 or r0, r9 - exit", + return", [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, // 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x07, // @@ -1286,7 +1321,7 @@ fn test_ldxh_all2() { or r0, r7 or r0, r8 or r0, r9 - exit", + return", [ 0x00, 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, // 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x80, // @@ -1332,7 +1367,7 @@ fn test_ldxw_all() { or r0, r7 or r0, r8 or r0, r9 - exit", + return", [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, // 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, // @@ -1368,7 +1403,7 @@ fn test_stxb_all() { stxb [r1+7], r8 ldxdw r0, [r1] be64 r0 - exit", + return", [ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // ], @@ -1389,7 +1424,7 @@ fn test_stxb_all2() { stxb [r0+1], r9 ldxh r0, [r0] be16 r0 - exit", + return", [0xff, 0xff], (), TestContextObject::new(8), @@ -1421,7 +1456,7 @@ fn test_stxb_chain() { ldxb r1, [r0+8] stxb [r0+9], r1 ldxb r0, [r0+9] - exit", + return", [ 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x00, 0x00, // @@ -1436,54 +1471,98 @@ fn test_stxb_chain() { #[test] fn test_exit_capped() { - test_interpreter_and_jit_asm!( - " - exit", - [], - (), - TestContextObject::new(0), - ProgramResult::Err(EbpfError::ExceededMaxInstructions), - ); + for sbpf_version in [SBPFVersion::V1, SBPFVersion::V2] { + let asm = if sbpf_version == SBPFVersion::V1 { + "exit" + } else { + "return" + }; + let config = Config { + enabled_sbpf_versions: SBPFVersion::V1..=sbpf_version, + ..Config::default() + }; + test_interpreter_and_jit_asm!( + asm, + config, + [], + (), + TestContextObject::new(0), + ProgramResult::Err(EbpfError::ExceededMaxInstructions), + ); + } } #[test] fn test_exit_without_value() { - test_interpreter_and_jit_asm!( - " - exit", - [], - (), - TestContextObject::new(1), - ProgramResult::Ok(0x0), - ); + for sbpf_version in [SBPFVersion::V1, SBPFVersion::V2] { + let asm = if sbpf_version == SBPFVersion::V1 { + "exit" + } else { + "return" + }; + let config = Config { + enabled_sbpf_versions: SBPFVersion::V1..=sbpf_version, + ..Config::default() + }; + test_interpreter_and_jit_asm!( + asm, + config, + [], + (), + TestContextObject::new(1), + ProgramResult::Ok(0x0), + ); + } } #[test] fn test_exit() { - test_interpreter_and_jit_asm!( - " - mov r0, 0 - exit", - [], - (), - TestContextObject::new(2), - ProgramResult::Ok(0x0), - ); + for sbpf_version in [SBPFVersion::V1, SBPFVersion::V2] { + let final_instr = if sbpf_version == SBPFVersion::V1 { + "exit" + } else { + "return" + }; + let asm = format!("mov r0, 0\n{final_instr}\n"); + + let config = Config { + enabled_sbpf_versions: SBPFVersion::V1..=sbpf_version, + ..Config::default() + }; + test_interpreter_and_jit_asm!( + asm.as_str(), + config, + [], + (), + TestContextObject::new(2), + ProgramResult::Ok(0x0), + ); + } } #[test] fn test_early_exit() { - test_interpreter_and_jit_asm!( - " - mov r0, 3 - exit - mov r0, 4 - exit", - [], - (), - TestContextObject::new(2), - ProgramResult::Ok(0x3), - ); + for sbpf_version in [SBPFVersion::V1, SBPFVersion::V2] { + let final_instr = if sbpf_version == SBPFVersion::V1 { + "exit" + } else { + "return" + }; + let asm = format!("mov r0, 3\n{final_instr}\nmov r0, 4\n{final_instr}\n"); + + let config = Config { + enabled_sbpf_versions: SBPFVersion::V1..=sbpf_version, + ..Config::default() + }; + test_interpreter_and_jit_asm!( + asm.as_str(), + config, + [], + (), + TestContextObject::new(2), + ProgramResult::Ok(0x3), + ); + } } #[test] @@ -1493,7 +1572,7 @@ fn test_ja() { mov r0, 1 ja +1 mov r0, 2 - exit", + return", [], (), TestContextObject::new(3), @@ -1512,7 +1591,7 @@ fn test_jeq_imm() { mov32 r1, 0xb jeq r1, 0xb, +1 mov32 r0, 2 - exit", + return", [], (), TestContextObject::new(7), @@ -1532,7 +1611,7 @@ fn test_jeq_reg() { mov32 r1, 0xb jeq r1, r2, +1 mov32 r0, 2 - exit", + return", [], (), TestContextObject::new(8), @@ -1551,7 +1630,7 @@ fn test_jge_imm() { mov32 r1, 0xc jge r1, 0xb, +1 mov32 r0, 2 - exit", + return", [], (), TestContextObject::new(7), @@ -1571,7 +1650,7 @@ fn test_jge_reg() { mov32 r1, 0xb jge r1, r2, +1 mov32 r0, 2 - exit", + return", [], (), TestContextObject::new(8), @@ -1587,11 +1666,11 @@ fn test_jle_imm() { mov32 r1, 5 jle r1, 4, +1 jle r1, 6, +1 - exit + return jle r1, 5, +1 - exit + return mov32 r0, 1 - exit", + return", [], (), TestContextObject::new(7), @@ -1609,11 +1688,11 @@ fn test_jle_reg() { mov r3, 6 jle r1, r2, +2 jle r1, r1, +1 - exit + return jle r1, r3, +1 - exit + return mov r0, 1 - exit", + return", [], (), TestContextObject::new(9), @@ -1630,9 +1709,9 @@ fn test_jgt_imm() { jgt r1, 6, +2 jgt r1, 5, +1 jgt r1, 4, +1 - exit + return mov32 r0, 1 - exit", + return", [], (), TestContextObject::new(7), @@ -1651,9 +1730,9 @@ fn test_jgt_reg() { jgt r1, r2, +2 jgt r1, r1, +1 jgt r1, r3, +1 - exit + return mov r0, 1 - exit", + return", [], (), TestContextObject::new(9), @@ -1670,9 +1749,9 @@ fn test_jlt_imm() { jlt r1, 4, +2 jlt r1, 5, +1 jlt r1, 6, +1 - exit + return mov32 r0, 1 - exit", + return", [], (), TestContextObject::new(7), @@ -1691,9 +1770,9 @@ fn test_jlt_reg() { jlt r1, r2, +2 jlt r1, r1, +1 jlt r1, r3, +1 - exit + return mov r0, 1 - exit", + return", [], (), TestContextObject::new(9), @@ -1712,7 +1791,7 @@ fn test_jne_imm() { mov32 r1, 0xa jne r1, 0xb, +1 mov32 r0, 2 - exit", + return", [], (), TestContextObject::new(7), @@ -1732,7 +1811,7 @@ fn test_jne_reg() { mov32 r1, 0xa jne r1, r2, +1 mov32 r0, 2 - exit", + return", [], (), TestContextObject::new(8), @@ -1751,7 +1830,7 @@ fn test_jset_imm() { mov32 r1, 0x9 jset r1, 0x8, +1 mov32 r0, 2 - exit", + return", [], (), TestContextObject::new(7), @@ -1771,7 +1850,7 @@ fn test_jset_reg() { mov32 r1, 0x9 jset r1, r2, +1 mov32 r0, 2 - exit", + return", [], (), TestContextObject::new(8), @@ -1791,7 +1870,7 @@ fn test_jsge_imm() { mov r1, -1 jsge r1, -1, +1 mov32 r0, 2 - exit", + return", [], (), TestContextObject::new(8), @@ -1813,7 +1892,7 @@ fn test_jsge_reg() { mov r1, r2 jsge r1, r2, +1 mov32 r0, 2 - exit", + return", [], (), TestContextObject::new(10), @@ -1829,11 +1908,11 @@ fn test_jsle_imm() { mov r1, -2 jsle r1, -3, +1 jsle r1, -1, +1 - exit + return mov32 r0, 1 jsle r1, -2, +1 mov32 r0, 2 - exit", + return", [], (), TestContextObject::new(7), @@ -1851,12 +1930,12 @@ fn test_jsle_reg() { mov32 r3, 0 jsle r1, r2, +1 jsle r1, r3, +1 - exit + return mov32 r0, 1 mov r1, r2 jsle r1, r2, +1 mov32 r0, 2 - exit", + return", [], (), TestContextObject::new(10), @@ -1875,7 +1954,7 @@ fn test_jsgt_imm() { mov32 r1, 0 jsgt r1, -1, +1 mov32 r0, 2 - exit", + return", [], (), TestContextObject::new(7), @@ -1895,7 +1974,7 @@ fn test_jsgt_reg() { mov32 r1, 0 jsgt r1, r2, +1 mov32 r0, 2 - exit", + return", [], (), TestContextObject::new(8), @@ -1912,9 +1991,9 @@ fn test_jslt_imm() { jslt r1, -3, +2 jslt r1, -2, +1 jslt r1, -1, +1 - exit + return mov32 r0, 1 - exit", + return", [], (), TestContextObject::new(7), @@ -1933,9 +2012,9 @@ fn test_jslt_reg() { jslt r1, r1, +2 jslt r1, r2, +1 jslt r1, r3, +1 - exit + return mov32 r0, 1 - exit", + return", [], (), TestContextObject::new(9), @@ -1957,7 +2036,7 @@ fn test_stack1() { mov r2, r10 add r2, r1 ldxdw r0, [r2-16] - exit", + return", [], (), TestContextObject::new(9), @@ -1984,7 +2063,7 @@ fn test_stack2() { ldxb r5, [r10-1] syscall bpf_gather_bytes xor r0, 0x2a2a2a2a - exit", + return", [], ( "bpf_mem_frob" => syscalls::SyscallMemFrob::vm, @@ -2026,7 +2105,7 @@ fn test_string_stack() { mov r0, 0x1 jeq r1, r6, +1 mov r0, 0x0 - exit", + return", [], ( "bpf_str_cmp" => syscalls::SyscallStrCmp::vm, @@ -2050,7 +2129,7 @@ fn test_err_dynamic_stack_out_of_bound() { test_interpreter_and_jit_asm!( " stb [r10-0x3001], 0 - exit", + return", config.clone(), [], (), @@ -2067,7 +2146,7 @@ fn test_err_dynamic_stack_out_of_bound() { test_interpreter_and_jit_asm!( " stb [r10], 0 - exit", + return", config.clone(), [], (), @@ -2095,10 +2174,10 @@ fn test_err_dynamic_stack_ptr_overflow() { add r11, -0x7FFFFFFF add r11, -0x40005 call function_foo - exit + return function_foo: stb [r10], 0 - exit", + return", [], (), TestContextObject::new(7), @@ -2119,10 +2198,10 @@ fn test_dynamic_stack_frames_empty() { test_interpreter_and_jit_asm!( " call function_foo - exit + return function_foo: mov r0, r10 - exit", + return", config.clone(), [], (), @@ -2141,10 +2220,10 @@ fn test_dynamic_frame_ptr() { " add r11, -8 call function_foo - exit + return function_foo: mov r0, r10 - exit", + return", config.clone(), [], (), @@ -2159,9 +2238,9 @@ fn test_dynamic_frame_ptr() { add r11, -8 call function_foo mov r0, r10 - exit + return function_foo: - exit + return ", config.clone(), [], @@ -2185,17 +2264,27 @@ fn test_entrypoint_exit() { ..Config::default() }; - // This checks that when foo exits we don't stop execution even if the - // stack is empty (stack size and call depth are decoupled) - test_interpreter_and_jit_asm!( + let final_instr = if highest_sbpf_version == SBPFVersion::V1 { + "exit" + } else { + "return" + }; + + let asm = format!( " entrypoint: call function_foo mov r0, 42 - exit + {final_instr} function_foo: mov r0, 12 - exit", + {final_instr}" + ); + + // This checks that when foo exits we don't stop execution even if the + // stack is empty (stack size and call depth are decoupled) + test_interpreter_and_jit_asm!( + asm.as_str(), config, [], (), @@ -2214,18 +2303,29 @@ fn test_stack_call_depth_tracking() { ..Config::default() }; + let final_instr = if highest_sbpf_version == SBPFVersion::V1 { + "exit" + } else { + "return" + }; + // Given max_call_depth=2, make sure that two sibling calls don't // trigger CallDepthExceeded. In other words ensure that we correctly // pop frames in the interpreter and decrement // EnvironmentStackSlotDepth on ebpf::EXIT in the jit. - test_interpreter_and_jit_asm!( + + let asm = format!( " call function_foo call function_foo - exit + {final_instr} function_foo: - exit - ", + {final_instr} + " + ); + + test_interpreter_and_jit_asm!( + asm.as_str(), config.clone(), [], (), @@ -2234,17 +2334,22 @@ fn test_stack_call_depth_tracking() { ); // two nested calls should trigger CallDepthExceeded instead - test_interpreter_and_jit_asm!( + + let asm = format!( " entrypoint: call function_foo - exit + {final_instr} function_foo: call function_bar - exit + {final_instr} function_bar: - exit - ", + {final_instr} + " + ); + + test_interpreter_and_jit_asm!( + asm.as_str(), config, [], (), @@ -2261,7 +2366,7 @@ fn test_err_mem_access_out_of_bound() { prog[0] = ebpf::MOV32_IMM; prog[8] = ebpf::HOR64_IMM; prog[16] = ebpf::ST_1B_IMM; - prog[24] = ebpf::EXIT; + prog[24] = ebpf::RETURN; let loader = Arc::new(BuiltinProgram::new_mock()); for address in [0x2u64, 0x8002u64, 0x80000002u64, 0x8000000000000002u64] { LittleEndian::write_u32(&mut prog[4..], address as u32); @@ -2319,13 +2424,13 @@ fn test_bpf_to_bpf_scratch_registers() { add64 r0, r7 add64 r0, r8 add64 r0, r9 - exit + return function_foo: mov64 r6, 0x00 mov64 r7, 0x00 mov64 r8, 0x00 mov64 r9, 0x00 - exit", + return", [], (), TestContextObject::new(15), @@ -2342,7 +2447,7 @@ fn test_syscall_parameter_on_stack() { mov64 r2, 0x1 syscall bpf_syscall_string mov64 r0, 0x0 - exit", + return", [], ( "bpf_syscall_string" => syscalls::SyscallString::vm, @@ -2361,10 +2466,10 @@ fn test_callx() { lsh64 r8, 0x20 or64 r8, 0x30 callx r8 - exit + return function_foo: mov64 r0, 0x2A - exit", + return", [], (), TestContextObject::new(8), @@ -2381,9 +2486,9 @@ fn test_err_callx_unregistered() { lsh64 r8, 0x20 or64 r8, 0x30 callx r8 - exit + return mov64 r0, 0x2A - exit", + return", [], (), TestContextObject::new(6), @@ -2397,7 +2502,7 @@ fn test_err_callx_oob_low() { " mov64 r0, 0x3 callx r0 - exit", + return", [], (), TestContextObject::new(2), @@ -2413,7 +2518,7 @@ fn test_err_callx_oob_high() { lsh64 r0, 0x20 or64 r0, 0x3 callx r0 - exit", + return", [], (), TestContextObject::new(4), @@ -2444,12 +2549,12 @@ fn test_bpf_to_bpf_depth() { ldxb r1, [r1] add64 r1, -2 call function_foo - exit + return function_foo: jeq r1, 0, +2 add64 r1, -1 call function_foo - exit", + return", config.clone(), [max_call_depth as u8], (), @@ -2462,12 +2567,12 @@ fn test_bpf_to_bpf_depth() { ldxb r1, [r1] add64 r1, -2 call function_foo - exit + return function_foo: jeq r1, 0, +2 add64 r1, -1 call function_foo - exit", + return", config, [max_call_depth as u8 + 1], (), @@ -2489,7 +2594,7 @@ fn test_err_reg_stack_depth() { mov64 r0, 0x1 lsh64 r0, 0x20 callx r0 - exit", + return", config, [], (), @@ -2533,7 +2638,7 @@ fn test_err_syscall_string() { mov64 r1, 0x0 syscall bpf_syscall_string mov64 r0, 0x0 - exit", + return", [72, 101, 108, 108, 111], ( "bpf_syscall_string" => syscalls::SyscallString::vm, @@ -2550,7 +2655,7 @@ fn test_syscall_string() { mov64 r2, 0x5 syscall bpf_syscall_string mov64 r0, 0x0 - exit", + return", [72, 101, 108, 108, 111], ( "bpf_syscall_string" => syscalls::SyscallString::vm, @@ -2571,7 +2676,7 @@ fn test_syscall() { mov64 r5, 0xEE syscall bpf_syscall_u64 mov64 r0, 0x0 - exit", + return", [], ( "bpf_syscall_u64" => syscalls::SyscallU64::vm, @@ -2591,7 +2696,7 @@ fn test_call_gather_bytes() { mov r4, 4 mov r5, 5 syscall bpf_gather_bytes - exit", + return", [], ( "bpf_gather_bytes" => syscalls::SyscallGatherBytes::vm, @@ -2611,7 +2716,7 @@ fn test_call_memfrob() { syscall bpf_mem_frob ldxdw r0, [r6] be64 r0 - exit", + return", [ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // ], @@ -2660,7 +2765,7 @@ declare_builtin_function!( ldxb r2, [r1+1] ldxb r1, [r1] syscall nested_vm_syscall - exit", + return", Arc::new(loader), ) .unwrap(); @@ -2693,7 +2798,7 @@ fn test_tight_infinite_loop_conditional() { test_interpreter_and_jit_asm!( " jsge r0, r0, -1 - exit", + return", [], (), TestContextObject::new(4), @@ -2706,7 +2811,7 @@ fn test_tight_infinite_loop_unconditional() { test_interpreter_and_jit_asm!( " ja -1 - exit", + return", [], (), TestContextObject::new(4), @@ -2721,7 +2826,7 @@ fn test_tight_infinite_recursion() { entrypoint: mov64 r3, 0x41414141 call entrypoint - exit", + return", [], (), TestContextObject::new(4), @@ -2737,11 +2842,11 @@ fn test_tight_infinite_recursion_callx() { lsh64 r8, 0x20 or64 r8, 0x28 call function_foo - exit + return function_foo: mov64 r3, 0x41414141 callx r8 - exit", + return", [], (), TestContextObject::new(8), @@ -2756,7 +2861,7 @@ fn test_instruction_count_syscall() { mov64 r2, 0x5 syscall bpf_syscall_string mov64 r0, 0x0 - exit", + return", [72, 101, 108, 108, 111], ( "bpf_syscall_string" => syscalls::SyscallString::vm, @@ -2773,7 +2878,7 @@ fn test_err_instruction_count_syscall_capped() { mov64 r2, 0x5 syscall bpf_syscall_string mov64 r0, 0x0 - exit", + return", [72, 101, 108, 108, 111], ( "bpf_syscall_string" => syscalls::SyscallString::vm, @@ -2796,7 +2901,7 @@ fn test_non_terminate_early() { callx r6 add64 r6, 0x1 ja -0x8 - exit", + return", [], (), TestContextObject::new(7), @@ -2817,7 +2922,7 @@ fn test_err_non_terminate_capped() { syscall bpf_trace_printf add64 r6, 0x1 ja -0x8 - exit", + return", [], ( "bpf_trace_printf" => syscalls::SyscallTracePrintf::vm, @@ -2836,7 +2941,7 @@ fn test_err_non_terminate_capped() { syscall bpf_trace_printf add64 r6, 0x1 ja -0x8 - exit", + return", [], ( "bpf_trace_printf" => syscalls::SyscallTracePrintf::vm, @@ -2856,7 +2961,7 @@ fn test_err_capped_before_exception() { add64 r0, 0x0 udiv64 r1, r2 add64 r0, 0x0 - exit", + return", [], (), TestContextObject::new(4), @@ -2871,7 +2976,7 @@ fn test_err_capped_before_exception() { add64 r0, 0x0 callx r2 add64 r0, 0x0 - exit", + return", [], (), TestContextObject::new(4), @@ -2887,9 +2992,9 @@ fn test_err_exit_capped() { lsh64 r1, 0x20 or64 r1, 0x28 callx r1 - exit + return function_foo: - exit + return ", [], (), @@ -2902,10 +3007,10 @@ fn test_err_exit_capped() { lsh64 r1, 0x20 or64 r1, 0x28 callx r1 - exit + return function_foo: mov r0, r0 - exit + return ", [], (), @@ -2915,9 +3020,9 @@ fn test_err_exit_capped() { test_interpreter_and_jit_asm!( " call 1 - exit + return mov r0, r0 - exit + return ", [], (), @@ -2931,17 +3036,17 @@ fn test_far_jumps() { test_interpreter_and_jit_asm!( " call function_c - exit + return function_a: - exit + return function_b: .fill 1024, 0x0F - exit + return function_c: mov32 r1, 0x00000010 hor64 r1, 0x00000001 callx r1 - exit", + return", [], (), TestContextObject::new(7), @@ -2960,7 +3065,7 @@ fn test_symbol_relocation() { mov64 r2, 0x1 syscall bpf_syscall_string mov64 r0, 0x0 - exit", + return", [72, 101, 108, 108, 111], ( "bpf_syscall_string" => syscalls::SyscallString::vm, @@ -3149,7 +3254,7 @@ fn test_lmul_loop() { lmul r0, 0x7 add r1, -1 jne r1, 0x0, -3 - exit", + return", [], (), TestContextObject::new(37), @@ -3176,7 +3281,7 @@ fn test_prime() { sub r4, r3 mov r0, 0x0 jne r4, 0x0, -10 - exit", + return", [], (), TestContextObject::new(655), @@ -3201,7 +3306,7 @@ fn test_subnet() { and r1, 0xffffff jeq r1, 0x1a8c0, +1 mov r0, 0x0 - exit", + return", [ 0x00, 0x00, 0xc0, 0x9f, 0xa0, 0x97, 0x00, 0xa0, // 0xcc, 0x3b, 0xbf, 0xfa, 0x08, 0x00, 0x45, 0x10, // @@ -4046,3 +4151,31 @@ fn test_mod() { ProgramResult::Err(EbpfError::DivideByZero), ); } + +#[test] +fn test_invalid_exit_or_return() { + for sbpf_version in [SBPFVersion::V1, SBPFVersion::V2] { + let final_instr = if sbpf_version == SBPFVersion::V1 { + "return" + } else { + "exit" + }; + + let config = Config { + enabled_sbpf_versions: sbpf_version..=sbpf_version, + enable_instruction_tracing: true, + ..Config::default() + }; + let function_registry = FunctionRegistry::>::default(); + let loader = Arc::new(BuiltinProgram::new_loader(config, function_registry)); + let mut executable = assemble(final_instr, loader).unwrap(); + + test_interpreter_and_jit!( + false, + executable, + [], + TestContextObject::new(1), + ProgramResult::Err(EbpfError::UnsupportedInstruction), + ); + } +} diff --git a/tests/exercise_instructions.rs b/tests/exercise_instructions.rs index 659c5770..3ad5e8f1 100644 --- a/tests/exercise_instructions.rs +++ b/tests/exercise_instructions.rs @@ -513,6 +513,14 @@ fn test_ins(v1: bool, ins: String, prng: &mut SmallRng, cu: u64) { prng.fill_bytes(&mut input); + let mut config = Config::default(); + let final_instr = if v1 { + config.enabled_sbpf_versions = SBPFVersion::V1..=SBPFVersion::V1; + "exit" + } else { + "return" + }; + let asm = format!( " ldxdw r9, [r1+72] @@ -535,12 +543,8 @@ fn test_ins(v1: bool, ins: String, prng: &mut SmallRng, cu: u64) { xor64 r0, r7 xor64 r0, r8 xor64 r0, r9 - exit" + {final_instr}" ); - let mut config = Config::default(); - if v1 { - config.enabled_sbpf_versions = SBPFVersion::V1..=SBPFVersion::V1; - } test_interpreter_and_jit_asm!(asm.as_str(), config, input, (), TestContextObject::new(cu)); } diff --git a/tests/verifier.rs b/tests/verifier.rs index 5124e8b8..496e5aeb 100644 --- a/tests/verifier.rs +++ b/tests/verifier.rs @@ -109,7 +109,7 @@ fn test_verifier_err_div_by_zero_imm() { " mov32 r0, 1 udiv32 r0, 0 - exit", + return", Arc::new(BuiltinProgram::new_mock()), ) .unwrap(); @@ -122,7 +122,7 @@ fn test_verifier_err_endian_size() { let prog = &[ 0xdc, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, // 0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // - 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // + 0x9d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ]; let executable = Executable::::from_text_bytes( prog, @@ -160,7 +160,7 @@ fn test_verifier_err_invalid_reg_dst() { let executable = assemble::( " mov r11, 1 - exit", + return", Arc::new(BuiltinProgram::new_loader( Config { enabled_sbpf_versions: SBPFVersion::V1..=highest_sbpf_version, @@ -183,7 +183,7 @@ fn test_verifier_err_invalid_reg_src() { let executable = assemble::( " mov r0, r11 - exit", + return", Arc::new(BuiltinProgram::new_loader( Config { enabled_sbpf_versions: SBPFVersion::V1..=highest_sbpf_version, @@ -204,7 +204,7 @@ fn test_verifier_resize_stack_ptr_success() { " add r11, -1 add r11, 1 - exit", + return", Arc::new(BuiltinProgram::new_loader( Config { enable_stack_frame_gaps: false, @@ -224,7 +224,7 @@ fn test_verifier_err_jmp_lddw() { " ja +1 lddw r0, 0x1122334455667788 - exit", + return", Arc::new(BuiltinProgram::new_mock()), ) .unwrap(); @@ -265,7 +265,7 @@ fn test_verifier_err_jmp_out() { let executable = assemble::( " ja +2 - exit", + return", Arc::new(BuiltinProgram::new_mock()), ) .unwrap(); @@ -278,7 +278,7 @@ fn test_verifier_err_jmp_out_start() { let executable = assemble::( " ja -2 - exit", + return", Arc::new(BuiltinProgram::new_mock()), ) .unwrap(); @@ -290,7 +290,7 @@ fn test_verifier_err_jmp_out_start() { fn test_verifier_err_unknown_opcode() { let prog = &[ 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // - 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // + 0x9d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ]; let executable = Executable::::from_text_bytes( prog, @@ -307,7 +307,7 @@ fn test_verifier_err_unknown_opcode() { fn test_verifier_unknown_sycall() { let prog = &[ 0x85, 0x00, 0x00, 0x00, 0xfe, 0xc3, 0xf5, 0x6b, // call 0x6bf5c3fe - 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit + 0x9d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit ]; let executable = Executable::::from_text_bytes( prog, @@ -323,7 +323,7 @@ fn test_verifier_unknown_sycall() { fn test_verifier_known_syscall() { let prog = &[ 0x85, 0x00, 0x00, 0x00, 0xfe, 0xc3, 0xf5, 0x6b, // call 0x6bf5c3fe - 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit + 0x9d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // exit ]; let mut function_registry = FunctionRegistry::>::default(); function_registry @@ -348,7 +348,7 @@ fn test_verifier_err_write_r10() { let executable = assemble::( " mov r10, 1 - exit", + return", Arc::new(BuiltinProgram::new_mock()), ) .unwrap(); @@ -382,7 +382,7 @@ fn test_verifier_err_all_shift_overflows() { ]; for (overflowing_instruction, expected) in testcases { - let assembly = format!("\n{overflowing_instruction}\nexit"); + let assembly = format!("\n{overflowing_instruction}\nreturn"); let executable = assemble::(&assembly, Arc::new(BuiltinProgram::new_mock())).unwrap(); let result = executable.verify::(); @@ -404,12 +404,18 @@ fn test_sdiv_disabled() { for (opc, instruction) in instructions { for highest_sbpf_version in [SBPFVersion::V1, SBPFVersion::V2] { - let assembly = format!("\n{instruction}\nexit"); + let final_instr = match highest_sbpf_version { + SBPFVersion::V1 => "exit", + SBPFVersion::V2 => "return", + SBPFVersion::V3 => unreachable!("Version not tested"), + }; + + let assembly = format!("\n{instruction}\n{final_instr}"); let executable = assemble::( &assembly, Arc::new(BuiltinProgram::new_loader( Config { - enabled_sbpf_versions: SBPFVersion::V1..=highest_sbpf_version.clone(), + enabled_sbpf_versions: SBPFVersion::V1..=highest_sbpf_version, ..Config::default() }, FunctionRegistry::default(), @@ -425,3 +431,66 @@ fn test_sdiv_disabled() { } } } + +#[test] +fn return_instr() { + for sbpf_version in [SBPFVersion::V1, SBPFVersion::V2] { + let executable = assemble::( + " + mov r0, 2 + exit + return", + Arc::new(BuiltinProgram::new_loader( + Config { + enabled_sbpf_versions: sbpf_version..=sbpf_version, + ..Config::default() + }, + FunctionRegistry::default(), + )), + ) + .unwrap(); + let result = executable.verify::(); + if sbpf_version == SBPFVersion::V2 { + assert_error!(result, "VerifierError(UnknownOpCode(149, 1))"); + } else { + assert_error!(result, "VerifierError(UnknownOpCode(157, 2))"); + } + } +} + +#[test] +fn return_in_v2() { + let executable = assemble::( + " mov r0, 2 + return", + Arc::new(BuiltinProgram::new_loader( + Config { + enabled_sbpf_versions: SBPFVersion::V2..=SBPFVersion::V2, + ..Config::default() + }, + FunctionRegistry::default(), + )), + ) + .unwrap(); + let result = executable.verify::(); + assert!(result.is_ok()); +} + +#[test] +fn function_without_return() { + let executable = assemble::( + " mov r0, 2 + add64 r0, 5", + Arc::new(BuiltinProgram::new_loader( + Config { + enabled_sbpf_versions: SBPFVersion::V2..=SBPFVersion::V2, + ..Config::default() + }, + FunctionRegistry::default(), + )), + ) + .unwrap(); + let result = executable.verify::(); + std::println!("res: {:?}", result); + assert_error!(result, "VerifierError(InvalidFunction(1))"); +}