Skip to content

Commit 1757eb9

Browse files
Added handling for tail call fallback return values, ensuring that all paths have an explicit return in the event that bpf_tail_call() fails. (#16)
Add relevant tests. Also: fix standalone IRTailCall codegen path — emit a context-aware fallback return after bpf_tail_call() to satisfy the eBPF verifier. Added regression test test_standalone_tail_call_fallback_xdp and included it in the suite. Ran tests/test_ebpf_c_codegen.exe (41 tests) — all passed.
1 parent 7b382a2 commit 1757eb9

2 files changed

Lines changed: 168 additions & 5 deletions

File tree

src/ebpf_c_codegen.ml

Lines changed: 28 additions & 5 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.
@@ -2021,7 +2031,8 @@ and generate_c_instruction ctx ir_instr =
20212031
(* Generate bpf_tail_call instruction *)
20222032
emit_line ctx (sprintf "/* Tail call to %s (index %d) */" name index);
20232033
emit_line ctx (sprintf "bpf_tail_call(ctx, &prog_array, %d);" index);
2024-
emit_line ctx "/* If tail call fails, continue execution */"
2034+
let fallback = get_tail_call_fallback_return ctx in
2035+
emit_line ctx (sprintf "return %s; /* tail call fallback */" fallback)
20252036

20262037
| IRMapLoad (map_val, key_val, dest_val, load_type) ->
20272038
generate_map_load ctx map_val key_val dest_val load_type
@@ -2200,13 +2211,19 @@ and generate_c_instruction ctx ir_instr =
22002211
let args_str = String.concat ", " (List.map (generate_c_value ctx) args) in
22012212
emit_line ctx (sprintf "/* Tail call to %s */" func_name);
22022213
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 */"
2214+
(* Fallback return: bpf_tail_call() may fail; verifier requires all
2215+
branches to have an explicit return. *)
2216+
let fallback = get_tail_call_fallback_return ctx in
2217+
emit_line ctx (sprintf "return %s; /* tail call fallback */" fallback)
22042218
| IRReturnTailCall (func_name, args, index) ->
22052219
(* Generate explicit tail call *)
22062220
let args_str = String.concat ", " (List.map (generate_c_value ctx) args) in
22072221
emit_line ctx (sprintf "/* Tail call to %s (index %d) */" func_name index);
22082222
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 */");
2223+
(* Fallback return: bpf_tail_call() may fail; verifier requires all
2224+
branches to have an explicit return. *)
2225+
let fallback = get_tail_call_fallback_return ctx in
2226+
emit_line ctx (sprintf "return %s; /* tail call fallback */" fallback));
22102227

22112228
decrease_indent ctx
22122229
| IRDefaultPattern ->
@@ -2223,13 +2240,19 @@ and generate_c_instruction ctx ir_instr =
22232240
let args_str = String.concat ", " (List.map (generate_c_value ctx) args) in
22242241
emit_line ctx (sprintf "/* Tail call to %s */" func_name);
22252242
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 */"
2243+
(* Fallback return: bpf_tail_call() may fail; verifier requires all
2244+
branches to have an explicit return. *)
2245+
let fallback = get_tail_call_fallback_return ctx in
2246+
emit_line ctx (sprintf "return %s; /* tail call fallback */" fallback)
22272247
| IRReturnTailCall (func_name, args, index) ->
22282248
(* Generate explicit tail call *)
22292249
let args_str = String.concat ", " (List.map (generate_c_value ctx) args) in
22302250
emit_line ctx (sprintf "/* Tail call to %s (index %d) */" func_name index);
22312251
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 */");
2252+
(* Fallback return: bpf_tail_call() may fail; verifier requires all
2253+
branches to have an explicit return. *)
2254+
let fallback = get_tail_call_fallback_return ctx in
2255+
emit_line ctx (sprintf "return %s; /* tail call fallback */" fallback));
22332256

22342257
decrease_indent ctx;
22352258
emit_line ctx "}"

tests/test_ebpf_c_codegen.ml

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1177,6 +1177,141 @@ let test_map_access_auto_deref_in_assignment () =
11771177
(* Without the fix this branch would fall through to the raw-pointer path *)
11781178
check bool "no raw pointer assignment" false (contains_substr output "count = map_ptr_0")
11791179

1180+
(** Tests for tail call fallback return fix.
1181+
These tests verify that every match arm containing a tail call emits an
1182+
explicit fallback return statement so the eBPF verifier can confirm that
1183+
all code paths terminate, even when bpf_tail_call() fails at runtime. *)
1184+
1185+
(** Unit test: standalone IRTailCall also generates a fallback return and no
1186+
longer emits the legacy continue-execution comment. *)
1187+
let test_standalone_tail_call_fallback_xdp () =
1188+
let ctx = create_c_context () in
1189+
ctx.current_function_context_type <- Some "xdp";
1190+
1191+
let ctx_arg = make_ir_value (IRVariable "ctx")
1192+
(IRPointer (IRStruct ("xdp_md", []), make_bounds_info ())) test_pos in
1193+
let instr = make_ir_instruction
1194+
(IRTailCall ("tcp_handler", [ctx_arg], 7)) test_pos in
1195+
generate_c_instruction ctx instr;
1196+
1197+
let output = String.concat "\n" ctx.output_lines in
1198+
check bool "standalone bpf_tail_call emitted"
1199+
true (contains_substr output "bpf_tail_call(ctx, &prog_array, 7);");
1200+
check bool "standalone XDP fallback return present"
1201+
true (contains_substr output "return XDP_PASS; /* tail call fallback */");
1202+
check bool "standalone old continue-execution comment absent"
1203+
false (contains_substr output "If tail call fails, continue execution");
1204+
()
1205+
1206+
(** Unit test: IRReturnTailCall in a constant match arm generates a fallback
1207+
return statement with XDP_PASS when the function context is "xdp". *)
1208+
let test_tail_call_fallback_constant_arm_xdp () =
1209+
let ctx = create_c_context () in
1210+
ctx.current_function_context_type <- Some "xdp";
1211+
1212+
let matched_val = make_ir_value (IRVariable "protocol") IRU32 test_pos in
1213+
let ctx_arg = make_ir_value (IRVariable "ctx")
1214+
(IRPointer (IRStruct ("xdp_md", []), make_bounds_info ())) test_pos in
1215+
let arms = [
1216+
{ match_pattern = IRConstantPattern
1217+
(make_ir_value (IRLiteral (IntLit (Signed64 6L, None))) IRU32 test_pos);
1218+
return_action = IRReturnTailCall ("tcp_handler", [ctx_arg], 1);
1219+
arm_pos = test_pos };
1220+
{ match_pattern = IRDefaultPattern;
1221+
return_action = IRReturnValue
1222+
(make_ir_value (IRLiteral (IntLit (Signed64 0L, None))) IRU32 test_pos);
1223+
arm_pos = test_pos };
1224+
] in
1225+
let instr = make_ir_instruction (IRMatchReturn (matched_val, arms)) test_pos in
1226+
generate_c_instruction ctx instr;
1227+
1228+
let output = String.concat "\n" ctx.output_lines in
1229+
check bool "bpf_tail_call emitted for constant arm"
1230+
true (contains_substr output "bpf_tail_call(ctx, &prog_array, 1)");
1231+
check bool "XDP_PASS fallback return present"
1232+
true (contains_substr output "return XDP_PASS; /* tail call fallback */");
1233+
check bool "old continue-execution comment absent"
1234+
false (contains_substr output "If tail call fails, continue execution");
1235+
()
1236+
1237+
(** Unit test: IRReturnTailCall in a default match arm also generates a
1238+
fallback return, and TC context gives TC_ACT_OK. *)
1239+
let test_tail_call_fallback_default_arm_tc () =
1240+
let ctx = create_c_context () in
1241+
ctx.current_function_context_type <- Some "tc";
1242+
1243+
let matched_val = make_ir_value (IRVariable "proto") IRU32 test_pos in
1244+
let ctx_arg = make_ir_value (IRVariable "ctx")
1245+
(IRPointer (IRStruct ("__sk_buff", []), make_bounds_info ())) test_pos in
1246+
let arms = [
1247+
{ match_pattern = IRConstantPattern
1248+
(make_ir_value (IRLiteral (IntLit (Signed64 6L, None))) IRU32 test_pos);
1249+
return_action = IRReturnValue
1250+
(make_ir_value (IRLiteral (IntLit (Signed64 0L, None))) IRU32 test_pos);
1251+
arm_pos = test_pos };
1252+
{ match_pattern = IRDefaultPattern;
1253+
return_action = IRReturnTailCall ("default_tc_handler", [ctx_arg], 2);
1254+
arm_pos = test_pos };
1255+
] in
1256+
let instr = make_ir_instruction (IRMatchReturn (matched_val, arms)) test_pos in
1257+
generate_c_instruction ctx instr;
1258+
1259+
let output = String.concat "\n" ctx.output_lines in
1260+
check bool "bpf_tail_call emitted for default arm"
1261+
true (contains_substr output "bpf_tail_call(ctx, &prog_array, 2)");
1262+
check bool "TC_ACT_OK fallback return present for TC context"
1263+
true (contains_substr output "return TC_ACT_OK; /* tail call fallback */");
1264+
check bool "XDP_PASS fallback NOT present in TC context"
1265+
false (contains_substr output "return XDP_PASS;");
1266+
check bool "old continue-execution comment absent"
1267+
false (contains_substr output "If tail call fails, continue execution");
1268+
()
1269+
1270+
(** Unit test: IRReturnCall (implicit tail call with index 0) in both constant
1271+
and default arms generates fallback returns. Generic context (None) uses
1272+
"return 0" as the fallback. *)
1273+
let test_return_call_fallback_generic_context () =
1274+
let ctx = create_c_context () in
1275+
(* current_function_context_type left as None -> generic fallback "0" *)
1276+
1277+
let matched_val = make_ir_value (IRVariable "key") IRU32 test_pos in
1278+
let ctx_arg = make_ir_value (IRVariable "ctx")
1279+
(IRPointer (IRStruct ("generic_ctx", []), make_bounds_info ())) test_pos in
1280+
let arms = [
1281+
{ match_pattern = IRConstantPattern
1282+
(make_ir_value (IRLiteral (IntLit (Signed64 1L, None))) IRU32 test_pos);
1283+
return_action = IRReturnCall ("handler_one", [ctx_arg]);
1284+
arm_pos = test_pos };
1285+
{ match_pattern = IRDefaultPattern;
1286+
return_action = IRReturnCall ("handler_default", [ctx_arg]);
1287+
arm_pos = test_pos };
1288+
] in
1289+
let instr = make_ir_instruction (IRMatchReturn (matched_val, arms)) test_pos in
1290+
generate_c_instruction ctx instr;
1291+
1292+
let output = String.concat "\n" ctx.output_lines in
1293+
(* Both arms use IRReturnCall which maps to index 0 *)
1294+
let bpf_calls = ref 0 in
1295+
let search_start = ref 0 in
1296+
(try while true do
1297+
let pos = Str.search_forward (Str.regexp_string "bpf_tail_call(ctx, &prog_array, 0)") output !search_start in
1298+
incr bpf_calls;
1299+
search_start := pos + 1
1300+
done with Not_found -> ());
1301+
check bool "bpf_tail_call emitted in constant arm (IRReturnCall)"
1302+
true (!bpf_calls >= 1);
1303+
check bool "bpf_tail_call emitted in default arm (IRReturnCall)"
1304+
true (!bpf_calls >= 2);
1305+
check bool "generic fallback return 0 present"
1306+
true (contains_substr output "return 0; /* tail call fallback */");
1307+
check bool "XDP_PASS fallback NOT present for generic context"
1308+
false (contains_substr output "return XDP_PASS;");
1309+
check bool "TC_ACT_OK fallback NOT present for generic context"
1310+
false (contains_substr output "return TC_ACT_OK;");
1311+
check bool "old continue-execution comment absent"
1312+
false (contains_substr output "If tail call fails, continue execution");
1313+
()
1314+
11801315
(** Test suite definition *)
11811316
let suite =
11821317
[
@@ -1224,6 +1359,11 @@ let suite =
12241359
("Global map redefinition fix", `Quick, test_global_map_redefinition_fix);
12251360
(* Coverage for IRMapAccess auto-dereference path in generate_assignment *)
12261361
("Map access auto-deref in assignment", `Quick, test_map_access_auto_deref_in_assignment);
1362+
(* Tail call fallback return fix - verifier requires explicit return after bpf_tail_call() *)
1363+
("Tail call fallback: standalone XDP context", `Quick, test_standalone_tail_call_fallback_xdp);
1364+
("Tail call fallback: constant arm XDP context", `Quick, test_tail_call_fallback_constant_arm_xdp);
1365+
("Tail call fallback: default arm TC context", `Quick, test_tail_call_fallback_default_arm_tc);
1366+
("Tail call fallback: IRReturnCall generic context", `Quick, test_return_call_fallback_generic_context);
12271367
]
12281368

12291369
(** Run all tests *)

0 commit comments

Comments
 (0)