Skip to content

Conversation

@kernel-patches-daemon-bpf
Copy link

Pull request for series with
subject: bpf: properly verify tail call behavior
version: 6
url: https://patchwork.kernel.org/project/netdevbpf/list/?series=1025417

@kernel-patches-daemon-bpf
Copy link
Author

Upstream branch: d6ec090
series: https://patchwork.kernel.org/project/netdevbpf/list/?series=1025417
version: 6

tecki and others added 4 commits November 19, 2025 08:23
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.

This approach also allows to check other problems that may occur with
tail calls, namely we are now able to check that precision is properly
propagated into subprograms using tail calls, as well as checking the
live slots in such a subprogram.

Fixes: 1a4607f ("bpf: consider that tail calls invalidate packet pointers")
Link: https://lore.kernel.org/bpf/[email protected]/
Signed-off-by: Martin Teichmann <[email protected]>
Acked-by: Eduard Zingerman <[email protected]>
Three tests are added:

- invalidate_pkt_pointers_by_tail_call checks that one can use the
  packet pointer after a tail call. This was originally possible
  and also poses not problems, but was made impossible by 1a4607f.

- invalidate_pkt_pointers_by_static_tail_call tests a corner case
  found by Eduard Zingerman during the discussion of the original fix,
  which was broken in that fix.

- subprog_result_tail_call tests that precision propagation works
  correctly across tail calls. This did not work before.

Signed-off-by: Martin Teichmann <[email protected]>
Acked-by: Eduard Zingerman <[email protected]>
This updates bpf_insn_successors() reflecting that control flow might
jump over the instructions between tail call and function exit, verifier
might assume that some writes to parent stack always happen, which is
not the case.

Signed-off-by: Eduard Zingerman <[email protected]>
Signed-off-by: Martin Teichmann <[email protected]>
A new test is added: caller_stack_write_tail_call tests that the live
stack is correctly tracked for a tail call.

Signed-off-by: Eduard Zingerman <[email protected]>
Signed-off-by: Martin Teichmann <[email protected]>
@kernel-patches-daemon-bpf
Copy link
Author

Upstream branch: d6ec090
series: https://patchwork.kernel.org/project/netdevbpf/list/?series=1025417
version: 6

@kernel-patches-review-bot
Copy link

commit be6ba7d2aa8d20944132ee54031b553c34d385db
Author: Eduard Zingerman <[email protected]>

bpf: correct stack liveness for tail calls

This patch updates bpf_insn_successors() to handle tail calls by creating
a jump table that reflects control flow can skip instructions between the
tail call and function exit when the tail call succeeds.

> diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
> index 5441341f1..8d0b60fa5 100644
> --- a/include/linux/bpf_verifier.h
> +++ b/include/linux/bpf_verifier.h

[ ... ]

> @@ -652,6 +652,7 @@ struct bpf_subprog_info {
>  	u32 start; /* insn idx of function entry point */
>  	u32 linfo_idx; /* The idx to the main_prog->aux->linfo */
>  	u32 postorder_start; /* The idx to the env->cfg.insn_postorder */
> +	u32 exit_idx; /* Index of one of the BPF_EXIT instructions in this subprogram */
>  	u16 stack_depth; /* max. stack depth used by this function */

[ ... ]

> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 117a2b1cf..f564150ec 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -3555,8 +3555,12 @@ static int check_subprogs(struct bpf_verifier_env *env)
>  			subprog[cur_subprog].has_ld_abs = true;
>  		if (BPF_CLASS(code) != BPF_JMP && BPF_CLASS(code) != BPF_JMP32)
>  			goto next;
> -		if (BPF_OP(code) == BPF_EXIT || BPF_OP(code) == BPF_CALL)
> +		if (BPF_OP(code) == BPF_CALL)
>  			goto next;
> +		if (BPF_OP(code) == BPF_EXIT) {
> +			subprog[cur_subprog].exit_idx = i;
> +			goto next;
> +		}

[ ... ]

> +static int visit_tailcall_insn(struct bpf_verifier_env *env, int t)
> +{
> +	static struct bpf_subprog_info *subprog;
> +	struct bpf_iarray *jt;
> +
> +	if (env->insn_aux_data[t].jt)
> +		return 0;
> +
> +	jt = iarray_realloc(NULL, 2);
> +	if (!jt)
> +		return -ENOMEM;
> +
> +	subprog = bpf_find_containing_subprog(env, t);
> +	jt->items[0] = t + 1;
> +	jt->items[1] = subprog->exit_idx;
                        ^^^^^^^^^^^^^^^^^^

Can a subprogram with a tail_call have no BPF_EXIT instruction? The
verifier allows subprograms to end with an unconditional jump instead
of BPF_EXIT (see check_subprogs() which accepts BPF_JA as the last
instruction). In check_subprogs(), exit_idx is only set when a BPF_EXIT
is encountered. For subprograms ending with only jumps (like infinite
loops with tail_calls), exit_idx remains 0 from the kvzalloc() in
bpf_check().

If visit_tailcall_insn() uses exit_idx when it's 0, the jump table will
incorrectly point to instruction 0 instead of the actual exit, potentially
causing incorrect liveness analysis for stack slots.

> +	env->insn_aux_data[t].jt = jt;
> +	return 0;
> +}



AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

In-Reply-To-Subject: bpf: correct stack liveness for tail calls
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/19508226347

@kernel-patches-daemon-bpf
Copy link
Author

Forwarding comment 3553616554 via email
In-Reply-To: [email protected]
Patch: https://patchwork.kernel.org/project/netdevbpf/patch/[email protected]/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants