From: sashiko-bot@kernel.org
To: "Alexei Starovoitov" <alexei.starovoitov@gmail.com>, bpf@vger.kernel.org
Subject: Re: [PATCH bpf-next v3 21/45] bpf: Verify static subprogs on their own
Date: Fri, 06 Nov 2026 04:53:21 +0000 [thread overview]
Message-ID: <sashiko-outbox-200776@kernel.org> (raw)
In-Reply-To: <20261106035111.3315285-22-alexei.starovoitov@gmail.com>
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] bpf: bpf_flow_return() drops speculative state flag on callee exit
- [Medium] bpf: call_func_on_its_own() loses async callback context in frame[0]
- [Medium] bpf: add_insns_of_callees() double-counts insns and misses callx edges
- [Medium] bpf: bpf_flow_return() lazy fallback leaves stale post-call states
--
commit 34bdfd07d4f78d7029c50c8aec27497f8117e123
Author: Alexei Starovoitov <ast@kernel.org>
bpf: Verify static subprogs on their own
This commit verifies eligible static subprograms in a single standalone frame
rather than in the call frames of their callers, joining states at the
subprogram entry and applying exit states to all waiting call sites.
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 103f368035638..b61166f670945 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
[ ... ]
> @@ -11840,6 +11843,180 @@ static void account_current_path(struct bpf_verifier_env *env);
[ ... ]
> +static bool func_is_on_its_own(struct bpf_verifier_env *env, struct bpf_verifier_state *state,
> + int subprog)
> +{
> + struct bpf_func_state *caller = state->frame[state->curframe];
> + struct bpf_subprog_info *sub = &env->subprog_info[subprog];
> + u16 live = env->insn_aux_data[sub->start].live_regs_before;
> + u32 i;
> +
> + if (sub->changes_pkt_data || sub->has_tail_call || sub->has_ld_abs ||
> + sub->is_cb || sub->is_async_cb || sub->is_exception_cb)
> + return false;
> + if (state->speculative)
> + return false;
> + if (sub->releases_inherited &&
> + (state->acquired_refs || state->active_rcu_locks || state->active_preempt_locks))
> + return false;
[Severity: Medium]
What happens if a static subprogram verified on its own has multiple exit
paths, or is called from two sites whose entries are merged, where one exit
leaves caller-held references, locks, or packet data intact while a later
exit releases an inherited resource or changes packet data?
In kernel/bpf/flow.c:flow_apply_exit(), each exit state is applied to waiting
callers as it arrives:
err = bpf_copy_verifier_state(&st, &w->st);
if (!err)
err = bpf_flow_return(env, &st, &x->st, call_idx);
if (!err)
err = bpf_flow_deliver(env, &st, call_idx + 1, call_idx);
bpf_free_verifier_state(&st, false);
if (err != 1)
return err;
When an earlier non-releasing exit reaches flow_apply_exit() (or is applied
via flow_do_united() when two entry states are joined by flow_merge()), a
post-call state is delivered to call_idx + 1 while w remains on entry->calls.
When a subsequent exit reaches bpf_flow_return() and sets
sub->releases_inherited = true (or sub->changes_pkt_data = true), returning 1
re-queues w at call_idx for in-frame verification, but any post-call states
already delivered to call_idx + 1 from earlier exits are not retracted. Can
those stale post-call states continue executing in the caller and trigger
false check_resource_leak() failures at program exit?
[ ... ]
> +static int call_func_on_its_own(struct bpf_verifier_env *env, int subprog, int insn_idx)
> +{
> + u16 live = env->insn_aux_data[env->subprog_info[subprog].start].live_regs_before;
> + struct bpf_verifier_state *state = env->cur_state;
> + struct bpf_func_state *caller = state->frame[state->curframe], *callee;
> + struct bpf_verifier_state entry = {};
> + int i, err;
> +
> + callee = kzalloc_obj(*callee, GFP_KERNEL_ACCOUNT);
> + if (!callee)
> + return -ENOMEM;
> + entry.frame[0] = callee;
> + entry.in_sleepable = state->in_sleepable;
> + entry.callers_unknown = state->callers_unknown;
> + init_func_state(env, callee, BPF_MAIN_FUNC, 0, subprog);
> + /* the frame that says so is not there when the function is verified */
> + callee->in_rcu_callback_fn = in_rcu_callback(env);
[Severity: Medium]
As reported by Sashiko on v2 [1], when a non-sleepable async callback
scheduled from a global subprogram verified with env->no_rcu_read_lock == true
calls a static subprogram verified on its own, call_func_on_its_own() replaces
entry.frame[0] with callee and only sets callee->in_rcu_callback_fn.
Because push_async_cb() sets async_cb->frame[0]->in_async_callback_fn = true
and async_cb->frame[0]->callback_ret_range rather than in_rcu_callback_fn,
has_rcu_read_lock() relies on state->frame[0]->in_async_callback_fn:
if (state->active_rcu_locks || in_rcu_callback(env))
return true;
if (in_sleepable(env))
return false;
return !env->no_rcu_read_lock || state->frame[0]->in_async_callback_fn;
And check_return_code() (which check_kfunc_call() invokes for bpf_throw()
during do_check_all()) also checks frame->in_async_callback_fn and
frame->callback_ret_range on env->cur_state->frame[0].
Should call_func_on_its_own() also preserve state->frame[0]->in_async_callback_fn
and state->frame[0]->callback_ret_range on callee so RCU read-lock tracking
and async callback return-range checks are not lost inside the standalone
callee frame?
[ ... ]
> +/* The current state is at exit of a function that is verified on its own. */
> +static int exit_func_on_its_own(struct bpf_verifier_env *env)
> +{
> + struct bpf_verifier_state *state = env->cur_state;
> + struct bpf_func_state *callee = state->frame[0];
> + u32 i, nregs;
> + int err;
> +
> + nregs = bpf_ret_reg_pair(env, callee->subprogno) ? 2 : 1;
> + for (i = 0; i < nregs; i++) {
> + if (callee->regs[ret_regs[i]].type != PTR_TO_STACK)
> + continue;
> + verbose(env, "cannot return stack pointer to the caller\n");
> + return -EINVAL;
> + }
> + state->unwound |= bpf_is_unwind_exit(&env->prog->insnsi[env->insn_idx]);
> +
> + /* Invalidate callee-local dynptrs and their slices before the frame goes away. */
> + err = destroy_dynptrs_in_stack_slots(env, callee, 0,
> + callee->allocated_stack / BPF_REG_SIZE - 1);
> + if (err)
> + return err;
> +
> + account_current_path(env);
> + err = bpf_flow_exit(env, state, env->insn_idx);
> + return err ?: PROCESS_BPF_EXIT;
> +}
[Severity: High]
As reported by bot+bpf-ci@kernel.org on v2 [2], when speculation mitigation is
active (!env->bypass_spec_v1 or !env->bypass_spec_v4), a static subprogram
entered with state->speculative == false can spawn speculative paths inside
the callee via check_cond_jmp_op(), sanitize_ptr_alu(), or
check_unwind_goto().
When such a speculative path reaches BPF_EXIT, process_bpf_exit_full() routes
it through exit_func_on_its_own() -> bpf_flow_exit() -> flow_apply_exit() ->
bpf_flow_return(). In bpf_flow_return(), caller->unwound is copied from
exit->unwound, but exit->speculative is not propagated to caller->speculative:
if (exit->unwound && !bpf_is_unwind_goto(&env->prog->insnsi[call_idx + 1])) {
verbose(env, "unwind_exit, but no unwind_goto after the call at insn %d\n",
call_idx);
return -EINVAL;
}
caller->unwound = exit->unwound;
In addition, in kernel/bpf/flow.c:flow_merge(), a non-speculative state t->st
can subsume a speculative state s->st via flow_within() ->
bpf_state_subsumes() and unite their flow_entry sets via flow_unite().
Could this cause speculative return states to resume in the caller as
non-speculative architectural paths (marking dead instructions as seen in
sanitize_mark_insn_seen() and failing the state->speculative check on
recoverable errors in do_check_block()) or cross-unite distinct call entries?
[ ... ]
> @@ -23452,6 +23641,37 @@ static int do_check_all(struct bpf_verifier_env *env)
> }
> }
>
> +/*
> + * What it took to verify a static subprog on its own is not in insns_total of its callers.
> + * Add it, once per caller. The main prog and global subprogs have all of it already.
> + */
> +static void add_insns_of_callees(struct bpf_verifier_env *env)
> +{
> + struct bpf_subprog_info *si = env->subprog_info;
> + struct bpf_insn *insn = env->prog->insnsi;
> + int *added, i, j, s, c;
> +
> + added = kvcalloc(env->subprog_cnt, sizeof(*added), GFP_KERNEL_ACCOUNT);
> + if (!added)
> + return;
> + /* callees first */
> + for (i = 0; i < env->subprog_cnt; i++) {
> + s = env->subprog_topo_order[i];
> + if (!s || bpf_subprog_is_global(env, s))
> + continue;
> + for (j = si[s].start; j < si[s + 1].start; j++) {
> + if (!bpf_pseudo_call(&insn[j]))
> + continue;
> + c = bpf_find_subprog(env, j + insn[j].imm + 1);
> + if (c <= 0 || c == s || !si[c].on_its_own || added[c] == s)
> + continue;
> + added[c] = s;
> + si[s].insns_total += si[c].insns_total;
> + }
> + }
> + kvfree(added);
> +}
[Severity: Medium]
As reported by bot+bpf-ci@kernel.org on v2 [3], if a static subprogram c is
verified in-frame for one call from s (or falls back to in-frame verification
when bpf_flow_return() returns 1, or across a do_check_all() restart where
on_its_own is not cleared) and is also verified on its own for another call,
prepare_func_exit() -> account_processed_insns() has already added the
in-frame instructions of c into both si[c].insns_total and si[s].insns_total.
When add_insns_of_callees() then adds the full si[c].insns_total to
si[s].insns_total, won't the in-frame portion be counted twice in
si[s].insns_total?
Also, since check_func_callx() calls check_static_func_call() and can verify
indirect targets on their own via call_func_on_its_own(), does
add_insns_of_callees() need to account for callx edges in env->callx_edges in
addition to bpf_pseudo_call() instructions?
[1] https://lore.kernel.org/r/20261105204513.2230344-22-alexei.starovoitov@gmail.com
[2] https://lore.kernel.org/r/fbc6a76bd8cb0b115a602fe1f8064612f09afac8924a23ae5d64cf4e7fe68b43@mail.kernel.org
[3] https://lore.kernel.org/r/fbc6a76bd8cb0b115a602fe1f8064612f09afac8924a23ae5d64cf4e7fe68b43@mail.kernel.org
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261106035111.3315285-1-alexei.starovoitov@gmail.com?part=21
next prev parent reply other threads:[~2026-11-06 4:53 UTC|newest]
Thread overview: 77+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-06 3:50 [PATCH bpf-next v3 00/45] bpf: A verifier that joins states Alexei Starovoitov
2026-11-06 3:50 ` [PATCH bpf-next v3 01/45] bpf: Add may_goto to the loops that the verifier marks Alexei Starovoitov
2026-11-06 4:22 ` sashiko-bot
2026-11-06 4:38 ` bot+bpf-ci
2026-11-06 3:50 ` [PATCH bpf-next v3 02/45] bpf: Limit depth and time of recursive subprogs at run time Alexei Starovoitov
2026-11-06 3:50 ` [PATCH bpf-next v3 03/45] bpf: Add union and widening of the range of a register Alexei Starovoitov
2026-11-06 3:50 ` [PATCH bpf-next v3 04/45] bpf: Add join of registers and stack slots Alexei Starovoitov
2026-11-06 4:39 ` sashiko-bot
2026-11-06 3:50 ` [PATCH bpf-next v3 05/45] bpf: Add join of verifier states Alexei Starovoitov
2026-11-06 4:25 ` sashiko-bot
2026-11-06 4:38 ` bot+bpf-ci
2026-11-06 3:50 ` [PATCH bpf-next v3 06/45] bpf: Tell which arguments of a call a join must leave alone Alexei Starovoitov
2026-11-06 3:50 ` [PATCH bpf-next v3 07/45] bpf: Add the data structures of the main pass over the control flow graph Alexei Starovoitov
2026-11-06 4:24 ` bot+bpf-ci
2026-11-06 3:50 ` [PATCH bpf-next v3 08/45] bpf: Order the insns, find what stops a loop and where may_goto can go Alexei Starovoitov
2026-11-06 3:50 ` [PATCH bpf-next v3 09/45] bpf: Find the registers that hold a number or a pointer to arena Alexei Starovoitov
2026-11-06 3:50 ` [PATCH bpf-next v3 10/45] bpf: Find the registers and stack slots that later checks depend on Alexei Starovoitov
2026-11-06 4:08 ` sashiko-bot
2026-11-06 3:50 ` [PATCH bpf-next v3 11/45] bpf: Add the sets of states at an insn and the entries of functions Alexei Starovoitov
2026-11-06 3:50 ` [PATCH bpf-next v3 12/45] bpf: Compare states of different iterations of a loop Alexei Starovoitov
2026-11-06 3:50 ` [PATCH bpf-next v3 13/45] bpf: Decide which states are not joined Alexei Starovoitov
2026-11-06 4:38 ` bot+bpf-ci
2026-11-06 3:50 ` [PATCH bpf-next v3 14/45] bpf: Merge the state that arrives into the set at an insn Alexei Starovoitov
2026-11-06 4:24 ` bot+bpf-ci
2026-11-06 3:50 ` [PATCH bpf-next v3 15/45] bpf: Move states along edges, into loops and subprogs and out of them Alexei Starovoitov
2026-11-06 3:50 ` [PATCH bpf-next v3 16/45] bpf: Make the state after a call from the caller's state and an exit state Alexei Starovoitov
2026-11-06 4:34 ` sashiko-bot
2026-11-06 3:50 ` [PATCH bpf-next v3 17/45] bpf: Enter and leave loops in the main pass, build flow.c Alexei Starovoitov
2026-11-06 4:38 ` bot+bpf-ci
2026-11-06 3:50 ` [PATCH bpf-next v3 18/45] bpf: Tell the passes over the graph where an added may_goto goes Alexei Starovoitov
2026-11-06 4:26 ` sashiko-bot
2026-11-06 3:50 ` [PATCH bpf-next v3 19/45] bpf: Verify programs as a fixpoint over the control flow graph Alexei Starovoitov
2026-11-06 4:38 ` bot+bpf-ci
2026-11-06 4:55 ` sashiko-bot
2026-11-06 3:50 ` [PATCH bpf-next v3 20/45] bpf: Keep the count of a loop when a register has no closed form Alexei Starovoitov
2026-11-06 4:38 ` bot+bpf-ci
2026-11-06 3:50 ` [PATCH bpf-next v3 21/45] bpf: Verify static subprogs on their own Alexei Starovoitov
2026-11-06 4:50 ` bot+bpf-ci
2026-11-06 4:53 ` sashiko-bot [this message]
2026-11-06 3:50 ` [PATCH bpf-next v3 22/45] bpf: Allow recursive subprogs Alexei Starovoitov
2026-11-06 4:38 ` bot+bpf-ci
2026-11-06 5:09 ` sashiko-bot
2026-11-06 3:50 ` [PATCH bpf-next v3 23/45] bpf: Remove is_state_visited(), bpf_compute_scc() and iter.depth Alexei Starovoitov
2026-11-06 3:50 ` [PATCH bpf-next v3 24/45] bpf: Remove precision tracking Alexei Starovoitov
2026-11-06 4:24 ` bot+bpf-ci
2026-11-06 3:50 ` [PATCH bpf-next v3 25/45] bpf: Remove prune points, levels of state comparison and pop_stack() Alexei Starovoitov
2026-11-06 3:50 ` [PATCH bpf-next v3 26/45] bpf: Update comments about paths, pruning and explored states Alexei Starovoitov
2026-11-06 4:19 ` sashiko-bot
2026-11-06 4:24 ` bot+bpf-ci
2026-11-06 3:50 ` [PATCH bpf-next v3 27/45] bpf: Track a pointer to one of a set of functions Alexei Starovoitov
2026-11-06 4:49 ` sashiko-bot
2026-11-06 3:50 ` [PATCH bpf-next v3 28/45] bpf: callx reads what the subprogs whose address is taken read Alexei Starovoitov
2026-11-06 3:50 ` [PATCH bpf-next v3 29/45] bpf: Don't take what bpf_core_cast() returns for a number Alexei Starovoitov
2026-11-06 3:50 ` [PATCH bpf-next v3 30/45] bpf: Don't widen away a register that only the loop sets Alexei Starovoitov
2026-11-06 3:50 ` [PATCH bpf-next v3 31/45] bpf: A base that nothing is known about can be the context Alexei Starovoitov
2026-11-06 3:50 ` [PATCH bpf-next v3 32/45] bpf: Keep the frames of the caller for a subprog that loads data_end Alexei Starovoitov
2026-11-06 4:24 ` bot+bpf-ci
2026-11-06 3:50 ` [PATCH bpf-next v3 33/45] bpf: Keep the frames of the caller for ctx of struct_ops with a reference Alexei Starovoitov
2026-11-06 4:24 ` bot+bpf-ci
2026-11-06 3:50 ` [PATCH bpf-next v3 34/45] bpf: R0 is live at the exit that an added may_goto goes to Alexei Starovoitov
2026-11-06 3:50 ` [PATCH bpf-next v3 35/45] bpf: Keep the step of a number in the union of two ranges Alexei Starovoitov
2026-11-06 3:51 ` [PATCH bpf-next v3 36/45] bpf: Keep the first iteration of a counted loop apart Alexei Starovoitov
2026-11-06 4:38 ` bot+bpf-ci
2026-11-06 3:51 ` [PATCH bpf-next v3 37/45] bpf: What a call returns in each iteration is like what the loop loads Alexei Starovoitov
2026-11-06 3:51 ` [PATCH bpf-next v3 38/45] bpf: Find where the jumps of a loop stop a number Alexei Starovoitov
2026-11-06 3:51 ` [PATCH bpf-next v3 39/45] selftests/bpf: Adjust tests to log lines that are gone Alexei Starovoitov
2026-11-06 4:38 ` bot+bpf-ci
2026-11-06 3:51 ` [PATCH bpf-next v3 40/45] selftests/bpf: Adjust tests to new verdicts, messages and order Alexei Starovoitov
2026-11-06 4:25 ` bot+bpf-ci
2026-11-06 4:34 ` sashiko-bot
2026-11-06 3:51 ` [PATCH bpf-next v3 41/45] selftests/bpf: Test states that are not joined Alexei Starovoitov
2026-11-06 3:51 ` [PATCH bpf-next v3 42/45] selftests/bpf: Test static subprogs that are verified on their own Alexei Starovoitov
2026-11-06 3:51 ` [PATCH bpf-next v3 43/45] selftests/bpf: Test loops that are counted, walked or widened Alexei Starovoitov
2026-11-06 3:51 ` [PATCH bpf-next v3 44/45] selftests/bpf: Test progs that run out of budget Alexei Starovoitov
2026-11-06 3:51 ` [PATCH bpf-next v3 45/45] selftests/bpf: Test callx of one of a set of functions Alexei Starovoitov
2026-11-06 4:52 ` sashiko-bot
2026-11-06 18:08 ` [PATCH bpf-next v3 00/45] bpf: A verifier that joins states Eduard Zingerman
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=sashiko-outbox-200776@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=alexei.starovoitov@gmail.com \
--cc=bpf@vger.kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.