@@ -1146,6 +1146,120 @@ let test_global_map_redefinition_fix () =
11461146
11471147 ()
11481148
1149+ (* * Tests for tail call fallback return fix.
1150+ These tests verify that every match arm containing a tail call emits an
1151+ explicit fallback return statement so the eBPF verifier can confirm that
1152+ all code paths terminate, even when bpf_tail_call() fails at runtime. *)
1153+
1154+ (* * Unit test: IRReturnTailCall in a constant match arm generates a fallback
1155+ return statement with XDP_PASS when the function context is "xdp". *)
1156+ let test_tail_call_fallback_constant_arm_xdp () =
1157+ let ctx = create_c_context () in
1158+ ctx.current_function_context_type < - Some " xdp" ;
1159+
1160+ let matched_val = make_ir_value (IRVariable " protocol" ) IRU32 test_pos in
1161+ let ctx_arg = make_ir_value (IRVariable " ctx" )
1162+ (IRPointer (IRStruct (" xdp_md" , [] ), make_bounds_info () )) test_pos in
1163+ let arms = [
1164+ { match_pattern = IRConstantPattern
1165+ (make_ir_value (IRLiteral (IntLit (Signed64 6L , None ))) IRU32 test_pos);
1166+ return_action = IRReturnTailCall (" tcp_handler" , [ctx_arg], 1 );
1167+ arm_pos = test_pos };
1168+ { match_pattern = IRDefaultPattern ;
1169+ return_action = IRReturnValue
1170+ (make_ir_value (IRLiteral (IntLit (Signed64 0L , None ))) IRU32 test_pos);
1171+ arm_pos = test_pos };
1172+ ] in
1173+ let instr = make_ir_instruction (IRMatchReturn (matched_val, arms)) test_pos in
1174+ generate_c_instruction ctx instr;
1175+
1176+ let output = String. concat " \n " ctx.output_lines in
1177+ check bool " bpf_tail_call emitted for constant arm"
1178+ true (contains_substr output " bpf_tail_call(ctx, &prog_array, 1)" );
1179+ check bool " XDP_PASS fallback return present"
1180+ true (contains_substr output " return XDP_PASS; /* tail call fallback */" );
1181+ check bool " old continue-execution comment absent"
1182+ false (contains_substr output " If tail call fails, continue execution" );
1183+ ()
1184+
1185+ (* * Unit test: IRReturnTailCall in a default match arm also generates a
1186+ fallback return, and TC context gives TC_ACT_OK. *)
1187+ let test_tail_call_fallback_default_arm_tc () =
1188+ let ctx = create_c_context () in
1189+ ctx.current_function_context_type < - Some " tc" ;
1190+
1191+ let matched_val = make_ir_value (IRVariable " proto" ) IRU32 test_pos in
1192+ let ctx_arg = make_ir_value (IRVariable " ctx" )
1193+ (IRPointer (IRStruct (" __sk_buff" , [] ), make_bounds_info () )) test_pos in
1194+ let arms = [
1195+ { match_pattern = IRConstantPattern
1196+ (make_ir_value (IRLiteral (IntLit (Signed64 6L , None ))) IRU32 test_pos);
1197+ return_action = IRReturnValue
1198+ (make_ir_value (IRLiteral (IntLit (Signed64 0L , None ))) IRU32 test_pos);
1199+ arm_pos = test_pos };
1200+ { match_pattern = IRDefaultPattern ;
1201+ return_action = IRReturnTailCall (" default_tc_handler" , [ctx_arg], 2 );
1202+ arm_pos = test_pos };
1203+ ] in
1204+ let instr = make_ir_instruction (IRMatchReturn (matched_val, arms)) test_pos in
1205+ generate_c_instruction ctx instr;
1206+
1207+ let output = String. concat " \n " ctx.output_lines in
1208+ check bool " bpf_tail_call emitted for default arm"
1209+ true (contains_substr output " bpf_tail_call(ctx, &prog_array, 2)" );
1210+ check bool " TC_ACT_OK fallback return present for TC context"
1211+ true (contains_substr output " return TC_ACT_OK; /* tail call fallback */" );
1212+ check bool " XDP_PASS fallback NOT present in TC context"
1213+ false (contains_substr output " return XDP_PASS;" );
1214+ check bool " old continue-execution comment absent"
1215+ false (contains_substr output " If tail call fails, continue execution" );
1216+ ()
1217+
1218+ (* * Unit test: IRReturnCall (implicit tail call with index 0) in both constant
1219+ and default arms generates fallback returns. Generic context (None) uses
1220+ "return 0" as the fallback. *)
1221+ let test_return_call_fallback_generic_context () =
1222+ let ctx = create_c_context () in
1223+ (* current_function_context_type left as None -> generic fallback "0" *)
1224+
1225+ let matched_val = make_ir_value (IRVariable " key" ) IRU32 test_pos in
1226+ let ctx_arg = make_ir_value (IRVariable " ctx" )
1227+ (IRPointer (IRStruct (" generic_ctx" , [] ), make_bounds_info () )) test_pos in
1228+ let arms = [
1229+ { match_pattern = IRConstantPattern
1230+ (make_ir_value (IRLiteral (IntLit (Signed64 1L , None ))) IRU32 test_pos);
1231+ return_action = IRReturnCall (" handler_one" , [ctx_arg]);
1232+ arm_pos = test_pos };
1233+ { match_pattern = IRDefaultPattern ;
1234+ return_action = IRReturnCall (" handler_default" , [ctx_arg]);
1235+ arm_pos = test_pos };
1236+ ] in
1237+ let instr = make_ir_instruction (IRMatchReturn (matched_val, arms)) test_pos in
1238+ generate_c_instruction ctx instr;
1239+
1240+ let output = String. concat " \n " ctx.output_lines in
1241+ (* Both arms use IRReturnCall which maps to index 0 *)
1242+ let bpf_calls = ref 0 in
1243+ let search_start = ref 0 in
1244+ (try while true do
1245+ let pos = Str. search_forward (Str. regexp_string " bpf_tail_call(ctx, &prog_array, 0)" ) output ! search_start in
1246+ incr bpf_calls;
1247+ search_start := pos + 1
1248+ done with Not_found -> () );
1249+ check bool " bpf_tail_call emitted in constant arm (IRReturnCall)"
1250+ true (! bpf_calls > = 1 );
1251+ check bool " bpf_tail_call emitted in default arm (IRReturnCall)"
1252+ true (! bpf_calls > = 2 );
1253+ check bool " generic fallback return 0 present"
1254+ true (contains_substr output " return 0; /* tail call fallback */" );
1255+ check bool " XDP_PASS fallback NOT present for generic context"
1256+ false (contains_substr output " return XDP_PASS;" );
1257+ check bool " TC_ACT_OK fallback NOT present for generic context"
1258+ false (contains_substr output " return TC_ACT_OK;" );
1259+ check bool " old continue-execution comment absent"
1260+ false (contains_substr output " If tail call fails, continue execution" );
1261+ ()
1262+
11491263(* * Test suite definition *)
11501264let suite =
11511265 [
@@ -1191,6 +1305,10 @@ let suite =
11911305 (" eBPF function generation bug fix" , `Quick , test_ebpf_function_generation_bug_fix);
11921306 (* Test to prevent global variable map redefinition regression *)
11931307 (" Global map redefinition fix" , `Quick , test_global_map_redefinition_fix);
1308+ (* Tail call fallback return fix - verifier requires explicit return after bpf_tail_call() *)
1309+ (" Tail call fallback: constant arm XDP context" , `Quick , test_tail_call_fallback_constant_arm_xdp);
1310+ (" Tail call fallback: default arm TC context" , `Quick , test_tail_call_fallback_default_arm_tc);
1311+ (" Tail call fallback: IRReturnCall generic context" , `Quick , test_return_call_fallback_generic_context);
11941312 ]
11951313
11961314(* * Run all tests *)
0 commit comments