All of lore.kernel.org
 help / color / mirror / Atom feed
From: Eduard Zingerman <eddyz87@gmail.com>
To: Daniel Borkmann <daniel@iogearbox.net>, ast@kernel.org
Cc: memxor@gmail.com, a.s.protopopov@gmail.com, info@starlabs.sg,
	 bpf@vger.kernel.org
Subject: Re: [PATCH bpf 1/6] bpf: Avoid quadratic successor rescan in bpf_compute_scc
Date: Thu, 10 Sep 2026 11:54:40 -0700	[thread overview]
Message-ID: <c90de371d062295815dfff1bdeca4b15adb4a57e.camel@gmail.com> (raw)
In-Reply-To: <20260909204035.24289-1-daniel@iogearbox.net>

On Wed, 2026-09-09 at 22:40 +0200, Daniel Borkmann wrote:
> The iterative Tarjan DFS in bpf_compute_scc() emulates recursion with an
> explicit 'dfs' stack: when a successor has not been visited yet, the
> successor is pushed and the walk restarts at the top of the loop. On the
> way back to a vertex the successor scan starts over at index zero, so a
> vertex with k successors rescans up to k successors on each of its up to k
> descents, i.e. O(k^2) work.
> 
> For ordinary instructions k <= 2 and this is irrelevant. For a gotox the
> successors are the jump table of the containing subprogram, whose size is
> bounded only by the max_entries of the insn_array map, so k can reach the
> 1M instruction complexity limit. Loading such a program keeps a CPU busy
> in the loop for a very long time before verification even begins.
> 
> Record in 'dfs_pos' the successor index each frame stopped at and resume
> the scan there. Each edge is therefore examined a bounded number of times
> and the walk becomes linear in the number of edges.
> 
> Fixes: 493d9e0d6083 ("bpf, x86: add support for indirect jumps")
> Reported-by: STAR Labs SG <info@starlabs.sg>
> Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
> ---

Acked-by: Eduard Zingerman <eddyz87@gmail.com>

The change looks correct, but I'd cleanup the comments a bit,
to me these look too LLM-ish.

>  kernel/bpf/cfg.c | 41 +++++++++++++++++++++++++++++++++++------
>  1 file changed, 35 insertions(+), 6 deletions(-)
> 
> diff --git a/kernel/bpf/cfg.c b/kernel/bpf/cfg.c
> index 842c7d1eabcc..081f7003eae6 100644
> --- a/kernel/bpf/cfg.c
> +++ b/kernel/bpf/cfg.c
> @@ -749,7 +749,7 @@ int bpf_compute_scc(struct bpf_verifier_env *env)
>  	struct bpf_insn_aux_data *aux = env->insn_aux_data;
>  	const u32 insn_cnt = env->prog->len;
>  	int stack_sz, dfs_sz, err = 0;
> -	u32 *stack, *pre, *low, *dfs;
> +	u32 *stack, *pre, *low, *dfs, *dfs_pos;
>  	u32 i, j, t, w;
>  	u32 next_preorder_num;
>  	u32 next_scc_id;
> @@ -762,13 +762,16 @@ int bpf_compute_scc(struct bpf_verifier_env *env)
>  	 * - 'stack' accumulates vertices in DFS order, see invariant comment below;
>  	 * - 'pre[t] == p' => preorder number of vertex 't' is 'p';
>  	 * - 'low[t] == n' => smallest preorder number of the vertex reachable from 't' is 'n';
> -	 * - 'dfs' DFS traversal stack, used to emulate explicit recursion.
> +	 * - 'dfs' DFS traversal stack, used to emulate explicit recursion;
> +	 * - 'dfs_pos[k] == j' => the frame 'dfs[k]' resumes visiting its
> +	 *   successors at index 'j'.
>  	 */
>  	stack = kvcalloc(insn_cnt, sizeof(int), GFP_KERNEL_ACCOUNT);
>  	pre = kvcalloc(insn_cnt, sizeof(int), GFP_KERNEL_ACCOUNT);
>  	low = kvcalloc(insn_cnt, sizeof(int), GFP_KERNEL_ACCOUNT);
>  	dfs = kvcalloc(insn_cnt, sizeof(*dfs), GFP_KERNEL_ACCOUNT);
> -	if (!stack || !pre || !low || !dfs) {
> +	dfs_pos = kvcalloc(insn_cnt, sizeof(*dfs_pos), GFP_KERNEL_ACCOUNT);
> +	if (!stack || !pre || !low || !dfs || !dfs_pos) {
>  		err = -ENOMEM;
>  		goto exit;
>  	}
> @@ -851,6 +854,7 @@ int bpf_compute_scc(struct bpf_verifier_env *env)
>  		stack_sz = 0;
>  		dfs_sz = 1;
>  		dfs[0] = i;
> +		dfs_pos[0] = 0;
>  dfs_continue:
>  		while (dfs_sz) {
>  			w = dfs[dfs_sz - 1];
> @@ -860,13 +864,37 @@ int bpf_compute_scc(struct bpf_verifier_env *env)
>  				next_preorder_num++;
>  				stack[stack_sz++] = w;
>  			}
> -			/* Visit 'w' successors */
> +			/*
> +			 * Visit 'w' successors, resuming at the successor this
> +			 * frame last descended into. Restarting the scan at zero
> +			 * on every return to 'w' would examine each successor
> +			 * once per descent, i.e. quadratic in the number of
> +			 * successors, which for a gotox is the size of the jump
> +			 * table.

Please drop the paragraph above, this fact was already stated in the
commit message.

> +			 *
> +			 * Re-folding the successors before that index would be a

I think "visiting" is a better term in this context.

> +			 * no-op. Such a successor 's' has 'pre[s] != 0' by then,
> +			 * so it is never pushed onto 'dfs' again, and low[s] can
> +			 * only decrease while 's' is the top of 'dfs'. If 's' is
> +			 * still on 'dfs' it sits below 'w' and cannot become the
> +			 * top before 'w' is popped; otherwise the only remaining
> +			 * write to low[s] is the pop of its SCC, setting it to
> +			 * NOT_ON_STACK, for which the min below is a no-op.
> +			 */

Tbh, I find this paragraph extremely hard to parse.
Why not simply /* Visit the next 'w' successor */ ?

>  			succ = bpf_insn_successors(env, w);
> -			for (j = 0; j < succ->cnt; ++j) {
> +			for (j = dfs_pos[dfs_sz - 1]; j < succ->cnt; ++j) {
>  				if (pre[succ->items[j]]) {
>  					low[w] = min(low[w], low[succ->items[j]]);
>  				} else {
> -					dfs[dfs_sz++] = succ->items[j];
> +					/*
> +					 * Resume at 'j', not 'j + 1': the successor
> +					 * is revisited once its DFS completes, to
> +					 * fold its low[] into low[w].
> +					 */

/*
 * Once DFS for succ->items[j] is complete, the pre[succ->items[j]] would be non-zero,
 * hence resuming at 'j' allows to follow the update low[w] = min(...) branch above.
 */

> +					dfs_pos[dfs_sz - 1] = j;
> +					dfs_pos[dfs_sz] = 0;
> +					dfs[dfs_sz] = succ->items[j];
> +					dfs_sz++;
>  					goto dfs_continue;
>  				}
>  			}
> @@ -916,5 +944,6 @@ int bpf_compute_scc(struct bpf_verifier_env *env)
>  	kvfree(pre);
>  	kvfree(low);
>  	kvfree(dfs);
> +	kvfree(dfs_pos);
>  	return err;
>  }

      parent reply	other threads:[~2026-09-10 18:54 UTC|newest]

Thread overview: 21+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-09 20:40 [PATCH bpf 1/6] bpf: Avoid quadratic successor rescan in bpf_compute_scc Daniel Borkmann
2026-09-09 20:40 ` [PATCH bpf 2/6] bpf: Bound the number of indirect jump edges in a program Daniel Borkmann
2026-09-09 20:57   ` sashiko-bot
2026-09-10 11:15     ` Daniel Borkmann
2026-09-10 11:44   ` Anton Protopopov
2026-09-09 20:40 ` [PATCH bpf 3/6] bpf: Cache the jump table of a subprogram during CFG discovery Daniel Borkmann
2026-09-09 21:34   ` bot+bpf-ci
2026-09-10 11:21     ` Daniel Borkmann
2026-09-10 11:46   ` Anton Protopopov
2026-09-10 21:02   ` Eduard Zingerman
2026-09-09 20:40 ` [PATCH bpf 4/6] bpf: Reject indirect jumps that leave their subprogram Daniel Borkmann
2026-09-09 21:50   ` bot+bpf-ci
2026-09-10 12:10   ` Anton Protopopov
2026-09-10 19:37   ` Eduard Zingerman
2026-09-09 20:40 ` [PATCH bpf 5/6] selftests/bpf: Add tests for the indirect jump edge limit Daniel Borkmann
2026-09-09 21:34   ` bot+bpf-ci
2026-09-10 12:14   ` Anton Protopopov
2026-09-09 20:40 ` [PATCH bpf 6/6] selftests/bpf: Add tests for indirect jumps across subprograms Daniel Borkmann
2026-09-09 21:34   ` bot+bpf-ci
2026-09-10 12:22   ` Anton Protopopov
2026-09-10 18:54 ` Eduard Zingerman [this message]

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=c90de371d062295815dfff1bdeca4b15adb4a57e.camel@gmail.com \
    --to=eddyz87@gmail.com \
    --cc=a.s.protopopov@gmail.com \
    --cc=ast@kernel.org \
    --cc=bpf@vger.kernel.org \
    --cc=daniel@iogearbox.net \
    --cc=info@starlabs.sg \
    --cc=memxor@gmail.com \
    /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.