summaryrefslogtreecommitdiff
path: root/kernel
diff options
context:
space:
mode:
authorDaniel Borkmann <daniel@iogearbox.net>2026-09-25 19:52:31 +0200
committerAlexei Starovoitov <ast@kernel.org>2026-09-25 21:02:31 +0000
commit939bb6270ee32cfef0631b30ff07a9e0b8806e64 (patch)
tree52752d776ee30e1dcc675b6f3a32cd4b451ce3da /kernel
parent8608ec8e981974b18834e2d67ed39638a9ec2e63 (diff)
downloadlinux-939bb6270ee32cfef0631b30ff07a9e0b8806e64.tar.gz
linux-939bb6270ee32cfef0631b30ff07a9e0b8806e64.zip
bpf: Leave out the hidden exit edge of a subprogram without an exit
visit_abnormal_return_insn() gives tail_call and ld_{abs,ind} insns a second successor, the exit_idx of their subprogram, so that the hidden exit from the subprogram is part of the CFG. check_subprogs() only assigns exit_idx when it walks over a BPF_EXIT, and a subprogram whose last insn jumps back into itself never has one, so exit_idx stays zero and the edge points at insn 0 of the program: 0: r0 = 0 1: r0 = *(u8 *)skb[0] 2: goto -2 For a subprogram other than main, update_insn() then reads the liveness masks at a negative relative index. Such a subprogram can still load when it leaves through a bpf_throw(), so mark exit_idx as unset in that case and leave the hidden edge out, as there is no exit for it to reach. Fixes: e40f5a6bf88a ("bpf: correct stack liveness for tail calls") Fixes: ee861486e377 ("bpf: Fix ld_{abs,ind} failure path analysis in subprogs") Signed-off-by: Daniel Borkmann <daniel@iogearbox.net> Signed-off-by: Alexei Starovoitov <ast@kernel.org> Link: https://patch.msgid.link/20260925175244.1136329-5-daniel@iogearbox.net
Diffstat (limited to 'kernel')
-rw-r--r--kernel/bpf/cfg.c10
-rw-r--r--kernel/bpf/verifier.c1
2 files changed, 8 insertions, 3 deletions
diff --git a/kernel/bpf/cfg.c b/kernel/bpf/cfg.c
index 69e484b25b59..f3f545c53264 100644
--- a/kernel/bpf/cfg.c
+++ b/kernel/bpf/cfg.c
@@ -515,17 +515,21 @@ static int visit_abnormal_return_insn(struct bpf_verifier_env *env, int t)
{
struct bpf_subprog_info *subprog;
struct bpf_iarray *jt;
+ u32 n;
if (env->insn_aux_data[t].jt)
return 0;
- jt = bpf_iarray_realloc(NULL, 2);
+ /* A subprogram without an exit has no hidden exit edge to take. */
+ subprog = bpf_find_containing_subprog(env, t);
+ n = subprog->exit_idx == U32_MAX ? 1 : 2;
+ jt = bpf_iarray_realloc(NULL, n);
if (!jt)
return -ENOMEM;
- subprog = bpf_find_containing_subprog(env, t);
jt->items[0] = t + 1;
- jt->items[1] = subprog->exit_idx;
+ if (n == 2)
+ jt->items[1] = subprog->exit_idx;
env->insn_aux_data[t].jt = jt;
return 0;
}
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index cf9ff37cfdf2..107456fa1845 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -2470,6 +2470,7 @@ static int add_subprog(struct bpf_verifier_env *env, int off)
return -E2BIG;
}
/* determine subprog starts. The end is one before the next starts */
+ env->subprog_info[env->subprog_cnt].exit_idx = U32_MAX;
env->subprog_info[env->subprog_cnt++].start = off;
sort(env->subprog_info, env->subprog_cnt,
sizeof(env->subprog_info[0]), cmp_subprogs, NULL);