From: Daniel Borkmann <daniel@iogearbox.net>
To: ast@kernel.org
Cc: memxor@gmail.com, eddyz87@gmail.com, a.s.protopopov@gmail.com,
info@starlabs.sg, bpf@vger.kernel.org
Subject: [PATCH bpf 1/6] bpf: Avoid quadratic successor rescan in bpf_compute_scc
Date: Wed, 9 Sep 2026 22:40:30 +0200 [thread overview]
Message-ID: <20260909204035.24289-1-daniel@iogearbox.net> (raw)
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>
---
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.
+ *
+ * Re-folding the successors before that index would be a
+ * 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.
+ */
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].
+ */
+ 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;
}
--
2.43.0
next reply other threads:[~2026-09-09 20:40 UTC|newest]
Thread overview: 21+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-09 20:40 Daniel Borkmann [this message]
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 ` [PATCH bpf 1/6] bpf: Avoid quadratic successor rescan in bpf_compute_scc 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=20260909204035.24289-1-daniel@iogearbox.net \
--to=daniel@iogearbox.net \
--cc=a.s.protopopov@gmail.com \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=eddyz87@gmail.com \
--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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox