Skip to content

Commit 3de3887

Browse files
committed
bpf: properly verify tail call behavior
A successful ebpf tail call does not return to the caller, but to the caller-of-the-caller, often just finishing the ebpf program altogether. Any restrictions that the verifier needs to take into account - notably the fact that the tail call might have modified packet pointers - are to be checked on the caller-of-the-caller. Checking it on the caller made the verifier refuse perfectly fine programs that would use the packet pointers after a tail call, which is no problem as this code is only executed if the tail call was unsuccessful, i.e. nothing happened. This patch simulates the behavior of a tail call in the verifier. A conditional jump to the code after the tail call is added for the case of an unsucessful tail call, and a return to the caller is simulated for a successful tail call. For the successful case we assume that the tail call returns an int, as tail calls are currently only allowed in functions that return and int. We always assume that the tail call modified the packet pointers, as we do not know what the tail call did. For the unsuccessful case we know nothing happened, so we do not need to add new constraints. Some test are added, notably one corner case found by Eduard Zingerman. Fixes: 1a4607f ("bpf: consider that tail calls invalidate packet pointers") Link: https://lore.kernel.org/bpf/20251029105828.1488347-1-martin.teichmann@xfel.eu/ Signed-off-by: Martin Teichmann <martin.teichmann@xfel.eu>
1 parent 8b55cb4 commit 3de3887

File tree

2 files changed

+59
-5
lines changed

2 files changed

+59
-5
lines changed

kernel/bpf/verifier.c

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10974,6 +10974,10 @@ static int prepare_func_exit(struct bpf_verifier_env *env, int *insn_idx)
1097410974
bool in_callback_fn;
1097510975
int err;
1097610976

10977+
err = bpf_update_live_stack(env);
10978+
if (err)
10979+
return err;
10980+
1097710981
callee = state->frame[state->curframe];
1097810982
r0 = &callee->regs[BPF_REG_0];
1097910983
if (r0->type == PTR_TO_STACK) {
@@ -11884,6 +11888,24 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
1188411888
env->prog->call_get_func_ip = true;
1188511889
}
1188611890

11891+
if (func_id == BPF_FUNC_tail_call) {
11892+
if (env->cur_state->curframe) {
11893+
struct bpf_verifier_state *branch;
11894+
mark_reg_scratched(env, BPF_REG_0);
11895+
branch = push_stack(env, env->insn_idx + 1, env->insn_idx, false);
11896+
if (!branch)
11897+
return -ENOMEM;
11898+
clear_all_pkt_pointers(env);
11899+
mark_reg_unknown(env, regs, BPF_REG_0);
11900+
err = prepare_func_exit(env, &env->insn_idx);
11901+
if (err)
11902+
return err;
11903+
env->insn_idx--;
11904+
} else {
11905+
changes_data = false;
11906+
}
11907+
}
11908+
1188711909
if (changes_data)
1188811910
clear_all_pkt_pointers(env);
1188911911
return 0;
@@ -19782,9 +19804,6 @@ static int process_bpf_exit_full(struct bpf_verifier_env *env,
1978219804
return PROCESS_BPF_EXIT;
1978319805

1978419806
if (env->cur_state->curframe) {
19785-
err = bpf_update_live_stack(env);
19786-
if (err)
19787-
return err;
1978819807
/* exit from nested function */
1978919808
err = prepare_func_exit(env, &env->insn_idx);
1979019809
if (err)

tools/testing/selftests/bpf/progs/verifier_sock.c

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1117,10 +1117,17 @@ int tail_call(struct __sk_buff *sk)
11171117
return 0;
11181118
}
11191119

1120-
/* Tail calls invalidate packet pointers. */
1120+
static __noinline
1121+
int static_tail_call(struct __sk_buff *sk)
1122+
{
1123+
bpf_tail_call_static(sk, &jmp_table, 0);
1124+
return 0;
1125+
}
1126+
1127+
/* Tail calls in sub-programs invalidate packet pointers. */
11211128
SEC("tc")
11221129
__failure __msg("invalid mem access")
1123-
int invalidate_pkt_pointers_by_tail_call(struct __sk_buff *sk)
1130+
int invalidate_pkt_pointers_by_global_tail_call(struct __sk_buff *sk)
11241131
{
11251132
int *p = (void *)(long)sk->data;
11261133

@@ -1131,4 +1138,32 @@ int invalidate_pkt_pointers_by_tail_call(struct __sk_buff *sk)
11311138
return TCX_PASS;
11321139
}
11331140

1141+
/* Tail calls in static sub-programs invalidate packet pointers. */
1142+
SEC("tc")
1143+
__failure __msg("invalid mem access")
1144+
int invalidate_pkt_pointers_by_static_tail_call(struct __sk_buff *sk)
1145+
{
1146+
int *p = (void *)(long)sk->data;
1147+
1148+
if ((void *)(p + 1) > (void *)(long)sk->data_end)
1149+
return TCX_DROP;
1150+
static_tail_call(sk);
1151+
*p = 42; /* this is unsafe */
1152+
return TCX_PASS;
1153+
}
1154+
1155+
/* Direct tail calls do not invalidate packet pointers. */
1156+
SEC("tc")
1157+
__success
1158+
int invalidate_pkt_pointers_by_tail_call(struct __sk_buff *sk)
1159+
{
1160+
int *p = (void *)(long)sk->data;
1161+
1162+
if ((void *)(p + 1) > (void *)(long)sk->data_end)
1163+
return TCX_DROP;
1164+
bpf_tail_call_static(sk, &jmp_table, 0);
1165+
*p = 42; /* this is NOT unsafe: tail calls don't return */
1166+
return TCX_PASS;
1167+
}
1168+
11341169
char _license[] SEC("license") = "GPL";

0 commit comments

Comments
 (0)