Skip to content

Commit a1ca8a9

Browse files
committed
Added handling for tail call fallback return values, ensuring that all paths have an explicit return in the event that bpf_tail_call() fails.
Add relevant tests.
1 parent f980559 commit a1ca8a9

2 files changed

Lines changed: 144 additions & 4 deletions

File tree

src/ebpf_c_codegen.ml

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,16 @@ let create_c_context () = {
163163
dynptr_backed_pointers = Hashtbl.create 32;
164164
}
165165

166+
(** Get the appropriate fallback return value when bpf_tail_call() fails.
167+
bpf_tail_call() is not guaranteed to succeed; when it fails execution
168+
continues past the call site. Every arm that uses a tail call must have
169+
an explicit return so the eBPF verifier can confirm all paths exit. *)
170+
let get_tail_call_fallback_return ctx =
171+
match ctx.current_function_context_type with
172+
| Some "xdp" -> "XDP_PASS"
173+
| Some "tc" -> "TC_ACT_OK"
174+
| _ -> "0"
175+
166176
(** Helper functions for code generation *)
167177

168178
(** Calculate the size of a type for dynptr field assignment operations.
@@ -2200,13 +2210,19 @@ and generate_c_instruction ctx ir_instr =
22002210
let args_str = String.concat ", " (List.map (generate_c_value ctx) args) in
22012211
emit_line ctx (sprintf "/* Tail call to %s */" func_name);
22022212
emit_line ctx (sprintf "bpf_tail_call(ctx, &prog_array, 0); /* %s(%s) */" func_name args_str);
2203-
emit_line ctx "/* If tail call fails, continue execution */"
2213+
(* Fallback return: bpf_tail_call() may fail; verifier requires all
2214+
branches to have an explicit return. *)
2215+
let fallback = get_tail_call_fallback_return ctx in
2216+
emit_line ctx (sprintf "return %s; /* tail call fallback */" fallback)
22042217
| IRReturnTailCall (func_name, args, index) ->
22052218
(* Generate explicit tail call *)
22062219
let args_str = String.concat ", " (List.map (generate_c_value ctx) args) in
22072220
emit_line ctx (sprintf "/* Tail call to %s (index %d) */" func_name index);
22082221
emit_line ctx (sprintf "bpf_tail_call(ctx, &prog_array, %d); /* %s(%s) */" index func_name args_str);
2209-
emit_line ctx "/* If tail call fails, continue execution */");
2222+
(* Fallback return: bpf_tail_call() may fail; verifier requires all
2223+
branches to have an explicit return. *)
2224+
let fallback = get_tail_call_fallback_return ctx in
2225+
emit_line ctx (sprintf "return %s; /* tail call fallback */" fallback));
22102226

22112227
decrease_indent ctx
22122228
| IRDefaultPattern ->
@@ -2223,13 +2239,19 @@ and generate_c_instruction ctx ir_instr =
22232239
let args_str = String.concat ", " (List.map (generate_c_value ctx) args) in
22242240
emit_line ctx (sprintf "/* Tail call to %s */" func_name);
22252241
emit_line ctx (sprintf "bpf_tail_call(ctx, &prog_array, 0); /* %s(%s) */" func_name args_str);
2226-
emit_line ctx "/* If tail call fails, continue execution */"
2242+
(* Fallback return: bpf_tail_call() may fail; verifier requires all
2243+
branches to have an explicit return. *)
2244+
let fallback = get_tail_call_fallback_return ctx in
2245+
emit_line ctx (sprintf "return %s; /* tail call fallback */" fallback)
22272246
| IRReturnTailCall (func_name, args, index) ->
22282247
(* Generate explicit tail call *)
22292248
let args_str = String.concat ", " (List.map (generate_c_value ctx) args) in
22302249
emit_line ctx (sprintf "/* Tail call to %s (index %d) */" func_name index);
22312250
emit_line ctx (sprintf "bpf_tail_call(ctx, &prog_array, %d); /* %s(%s) */" index func_name args_str);
2232-
emit_line ctx "/* If tail call fails, continue execution */");
2251+
(* Fallback return: bpf_tail_call() may fail; verifier requires all
2252+
branches to have an explicit return. *)
2253+
let fallback = get_tail_call_fallback_return ctx in
2254+
emit_line ctx (sprintf "return %s; /* tail call fallback */" fallback));
22332255

22342256
decrease_indent ctx;
22352257
emit_line ctx "}"

tests/test_ebpf_c_codegen.ml

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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 *)
11501264
let 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

Comments
 (0)