* [PATCH bpf-next v3 0/3] Fixes for tailcall with subprog and callback
@ 2026-07-16 12:01 Pu Lehui
2026-07-16 12:01 ` [PATCH bpf-next v3 1/3] bpf: Sync tail_call_reachable with callee state on entry Pu Lehui
` (3 more replies)
0 siblings, 4 replies; 5+ messages in thread
From: Pu Lehui @ 2026-07-16 12:01 UTC (permalink / raw)
To: bpf, linux-kernel, Eduard Zingerman, Björn Töpel,
Daniel Borkmann
Cc: Alexei Starovoitov, Andrii Nakryiko, Kumar Kartikeya Dwivedi,
Yonghong Song, Martin KaFai Lau, Song Liu, Jiri Olsa,
Emil Tsalapatis, Pu Lehui, Pu Lehui
From: Pu Lehui <pulehui@huawei.com>
This series fix two issues related to tailcall:
patch 1: Fix tail_call_reachable state leak across call branches.
patch 2: Reject callback subprogs invoke tailcall.
patch 3: testcases for patch 1 and 2.
v3:
- Fix tail_call_reachable state leak across call branches. (Sashiko)
- Add testcase for the above issue.
v2:
https://lore.kernel.org/bpf/20260714024459.420075-1-pulehui@huaweicloud.com
- Remove redantant testcase.
- Add ACK tag by Eduard.
v1:
https://lore.kernel.org/bpf/20260711104727.4023420-1-pulehui@huaweicloud.com
Pu Lehui (3):
bpf: Sync tail_call_reachable with callee state on entry
bpf: Reject callback subprogs invoke tailcall
selftests/bpf: Add testcases for callback with tailcall
kernel/bpf/verifier.c | 12 +--
.../selftests/bpf/prog_tests/tailcalls.c | 7 ++
.../selftests/bpf/progs/tailcall_callback.c | 81 +++++++++++++++++++
3 files changed, 92 insertions(+), 8 deletions(-)
create mode 100644 tools/testing/selftests/bpf/progs/tailcall_callback.c
--
2.34.1
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH bpf-next v3 1/3] bpf: Sync tail_call_reachable with callee state on entry
2026-07-16 12:01 [PATCH bpf-next v3 0/3] Fixes for tailcall with subprog and callback Pu Lehui
@ 2026-07-16 12:01 ` Pu Lehui
2026-07-16 12:01 ` [PATCH bpf-next v3 2/3] bpf: Reject callback subprogs invoke tailcall Pu Lehui
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Pu Lehui @ 2026-07-16 12:01 UTC (permalink / raw)
To: bpf, linux-kernel, Eduard Zingerman, Björn Töpel,
Daniel Borkmann
Cc: Alexei Starovoitov, Andrii Nakryiko, Kumar Kartikeya Dwivedi,
Yonghong Song, Martin KaFai Lau, Song Liu, Jiri Olsa,
Emil Tsalapatis, Pu Lehui, Pu Lehui
From: Pu Lehui <pulehui@huawei.com>
Currently in check_max_stack_depth_subprog, when the verifier enters a
new callee branch, the local tail_call_reachable is not properly
synchronized with the callee's state.
Consider a main prog branching into multiple subprogs:
subprog0 -> tailcall
main <
subprog1 -> subprog2
When the verifier finishes checking subprog0 and backtracks to main
prog, the local tail_call_reachable state is left as true. As it
proceeds to subprog1, this uncleared state leaks into the new branch,
falsely marking subprog1 and subprog2 as tailcall reachable.
Fix this by explicitly syncing tail_call_reachable with the callee's
has_tail_call state on entry. The caller's state is safely preserved and
restored via the existing backtracking logic.
Fixes: ebf7d1f508a7 ("bpf, x64: rework pro/epilogue and tailcall handling in JIT")
Reported-by: Sashiko <sashiko-bot@kernel.org>
Signed-off-by: Pu Lehui <pulehui@huawei.com>
---
kernel/bpf/verifier.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index de816063ae63..782d939c38cd 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -5263,8 +5263,8 @@ static int check_max_stack_depth_subprog(struct bpf_verifier_env *env, int idx,
if (!priv_stack_supported)
subprog[idx].priv_stack_mode = NO_PRIV_STACK;
- if (subprog[idx].has_tail_call)
- tail_call_reachable = true;
+ /* sync tail_call_reachable with callee state on entry */
+ tail_call_reachable = subprog[idx].has_tail_call;
frame = bpf_subprog_is_global(env, idx) ? 0 : frame + 1;
if (frame >= MAX_CALL_FRAMES) {
--
2.34.1
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH bpf-next v3 2/3] bpf: Reject callback subprogs invoke tailcall
2026-07-16 12:01 [PATCH bpf-next v3 0/3] Fixes for tailcall with subprog and callback Pu Lehui
2026-07-16 12:01 ` [PATCH bpf-next v3 1/3] bpf: Sync tail_call_reachable with callee state on entry Pu Lehui
@ 2026-07-16 12:01 ` Pu Lehui
2026-07-16 12:01 ` [PATCH bpf-next v3 3/3] selftests/bpf: Add testcases for callback with tailcall Pu Lehui
2026-07-17 0:50 ` [PATCH bpf-next v3 0/3] Fixes for tailcall with subprog and callback patchwork-bot+netdevbpf
3 siblings, 0 replies; 5+ messages in thread
From: Pu Lehui @ 2026-07-16 12:01 UTC (permalink / raw)
To: bpf, linux-kernel, Eduard Zingerman, Björn Töpel,
Daniel Borkmann
Cc: Alexei Starovoitov, Andrii Nakryiko, Kumar Kartikeya Dwivedi,
Yonghong Song, Martin KaFai Lau, Song Liu, Jiri Olsa,
Emil Tsalapatis, Pu Lehui, Pu Lehui
From: Pu Lehui <pulehui@huawei.com>
Some JIT compilers, such as x86_64, rely on a register to pass the TCC.
When subprograms of synchronous callback invoke tailcall, C helpers
invoking bpf callback clobber this register, and the corrupted TCC may
bypass the TCC limit, leading to infinite tailcall.
Fix this by rejecting tailcall inside all subprogs of sync callback.
This also cleanly consolidates the existing async and exception callback
checks into a single unified `is_cb` check.
Reported-by: Sashiko <sashiko-bot@kernel.org>
Reported-by: Björn Töpel <bjorn@kernel.org>
Acked-by: Eduard Zingerman <eddyz87@gmail.com>
Signed-off-by: Pu Lehui <pulehui@huawei.com>
---
kernel/bpf/verifier.c | 8 ++------
1 file changed, 2 insertions(+), 6 deletions(-)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 782d939c38cd..62d46b4c9962 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -5237,10 +5237,6 @@ static int check_max_stack_depth_subprog(struct bpf_verifier_env *env, int idx,
if (verifier_bug_if(sidx < 0, env, "callee not found at insn %d", next_insn))
return -EFAULT;
if (subprog[sidx].is_async_cb) {
- if (subprog[sidx].has_tail_call) {
- verifier_bug(env, "subprog has tail_call and async cb");
- return -EFAULT;
- }
/* async callbacks don't increase bpf prog stack size unless called directly */
if (!bpf_pseudo_call(insn + i))
continue;
@@ -5281,8 +5277,8 @@ static int check_max_stack_depth_subprog(struct bpf_verifier_env *env, int idx,
*/
if (tail_call_reachable) {
for (tmp = idx; tmp >= 0; tmp = dinfo[tmp].caller) {
- if (subprog[tmp].is_exception_cb) {
- verbose(env, "cannot tail call within exception cb\n");
+ if (subprog[tmp].is_cb) {
+ verbose(env, "cannot tail call within callback\n");
return -EINVAL;
}
if (subprog[tmp].stack_arg_cnt) {
--
2.34.1
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH bpf-next v3 3/3] selftests/bpf: Add testcases for callback with tailcall
2026-07-16 12:01 [PATCH bpf-next v3 0/3] Fixes for tailcall with subprog and callback Pu Lehui
2026-07-16 12:01 ` [PATCH bpf-next v3 1/3] bpf: Sync tail_call_reachable with callee state on entry Pu Lehui
2026-07-16 12:01 ` [PATCH bpf-next v3 2/3] bpf: Reject callback subprogs invoke tailcall Pu Lehui
@ 2026-07-16 12:01 ` Pu Lehui
2026-07-17 0:50 ` [PATCH bpf-next v3 0/3] Fixes for tailcall with subprog and callback patchwork-bot+netdevbpf
3 siblings, 0 replies; 5+ messages in thread
From: Pu Lehui @ 2026-07-16 12:01 UTC (permalink / raw)
To: bpf, linux-kernel, Eduard Zingerman, Björn Töpel,
Daniel Borkmann
Cc: Alexei Starovoitov, Andrii Nakryiko, Kumar Kartikeya Dwivedi,
Yonghong Song, Martin KaFai Lau, Song Liu, Jiri Olsa,
Emil Tsalapatis, Pu Lehui, Pu Lehui
From: Pu Lehui <pulehui@huawei.com>
Add 2 testcases for callback with tailcall:
1. failure case: callback->subprog->tailcall.
2. success case: subprog with tailcall do not affect
no-tailcall callback.
Signed-off-by: Pu Lehui <pulehui@huawei.com>
---
.../selftests/bpf/prog_tests/tailcalls.c | 7 ++
.../selftests/bpf/progs/tailcall_callback.c | 81 +++++++++++++++++++
2 files changed, 88 insertions(+)
create mode 100644 tools/testing/selftests/bpf/progs/tailcall_callback.c
diff --git a/tools/testing/selftests/bpf/prog_tests/tailcalls.c b/tools/testing/selftests/bpf/prog_tests/tailcalls.c
index a5a226d0104c..c66037162da5 100644
--- a/tools/testing/selftests/bpf/prog_tests/tailcalls.c
+++ b/tools/testing/selftests/bpf/prog_tests/tailcalls.c
@@ -12,6 +12,7 @@
#include "tailcall_cgrp_storage_no_storage.skel.h"
#include "tailcall_cgrp_storage.skel.h"
#include "tailcall_sleepable.skel.h"
+#include "tailcall_callback.skel.h"
/* test_tailcall_1 checks basic functionality by patching multiple locations
* in a single program for a single tail call slot with nop->jmp, jmp->nop
@@ -1901,6 +1902,11 @@ static void test_tailcall_sleepable(void)
tailcall_sleepable__destroy(skel);
}
+static void test_tailcall_callback(void)
+{
+ RUN_TESTS(tailcall_callback);
+}
+
void test_tailcalls(void)
{
if (test__start_subtest("tailcall_1"))
@@ -1967,4 +1973,5 @@ void test_tailcalls(void)
test_tailcall_cgrp_storage_no_storage_leaf();
if (test__start_subtest("tailcall_cgrp_storage_no_storage_bridge"))
test_tailcall_cgrp_storage_no_storage_bridge();
+ test_tailcall_callback();
}
diff --git a/tools/testing/selftests/bpf/progs/tailcall_callback.c b/tools/testing/selftests/bpf/progs/tailcall_callback.c
new file mode 100644
index 000000000000..c41632cf423b
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/tailcall_callback.c
@@ -0,0 +1,81 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+#include "bpf_test_utils.h"
+
+int classifier_0(struct __sk_buff *skb);
+
+struct {
+ __uint(type, BPF_MAP_TYPE_PROG_ARRAY);
+ __uint(max_entries, 1);
+ __uint(key_size, sizeof(__u32));
+ __array(values, void (void));
+} jmp_table SEC(".maps") = {
+ .values = {
+ [0] = (void *) &classifier_0,
+ },
+};
+
+__auxiliary
+SEC("tc")
+int classifier_0(struct __sk_buff *skb)
+{
+ return 0;
+}
+
+static __noinline
+int subprog_tail0(struct __sk_buff *skb)
+{
+ int ret = 0;
+
+ bpf_tail_call_static(skb, &jmp_table, 0);
+ barrier_var(ret);
+ return ret;
+}
+
+static __noinline
+int callback_loop(int index, void **cb_ctx)
+{
+ int ret;
+
+ ret = subprog_tail0(*cb_ctx);
+ barrier_var(ret);
+ return ret ? 1 : 0;
+}
+
+static __noinline
+int callback_empty(int index, void *data)
+{
+ return 0;
+}
+
+/* callback involving subprog with tail call is rejected */
+SEC("tc")
+__failure __msg("cannot tail call within callback")
+int tailcall_callback_1(struct __sk_buff *skb)
+{
+ clobber_regs_stack();
+
+ bpf_loop(1, callback_loop, &skb, 0);
+ return 0;
+}
+
+/* subprogs with tailcall do not affect no-tailcall callback */
+SEC("tc")
+__success
+__retval(0)
+int tailcall_callback_2(struct __sk_buff *skb)
+{
+ int ret;
+
+ clobber_regs_stack();
+
+ ret = subprog_tail0(skb);
+ __sink(ret);
+
+ bpf_loop(1, callback_empty, NULL, 0);
+ return 0;
+}
+
+char __license[] SEC("license") = "GPL";
--
2.34.1
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH bpf-next v3 0/3] Fixes for tailcall with subprog and callback
2026-07-16 12:01 [PATCH bpf-next v3 0/3] Fixes for tailcall with subprog and callback Pu Lehui
` (2 preceding siblings ...)
2026-07-16 12:01 ` [PATCH bpf-next v3 3/3] selftests/bpf: Add testcases for callback with tailcall Pu Lehui
@ 2026-07-17 0:50 ` patchwork-bot+netdevbpf
3 siblings, 0 replies; 5+ messages in thread
From: patchwork-bot+netdevbpf @ 2026-07-17 0:50 UTC (permalink / raw)
To: Pu Lehui
Cc: bpf, linux-kernel, eddyz87, bjorn, daniel, ast, andrii, memxor,
yonghong.song, martin.lau, song, jolsa, emil, pulehui
Hello:
This series was applied to bpf/bpf-next.git (master)
by Eduard Zingerman <eddyz87@gmail.com>:
On Thu, 16 Jul 2026 12:01:54 +0000 you wrote:
> From: Pu Lehui <pulehui@huawei.com>
>
> This series fix two issues related to tailcall:
>
> patch 1: Fix tail_call_reachable state leak across call branches.
> patch 2: Reject callback subprogs invoke tailcall.
> patch 3: testcases for patch 1 and 2.
>
> [...]
Here is the summary with links:
- [bpf-next,v3,1/3] bpf: Sync tail_call_reachable with callee state on entry
https://git.kernel.org/bpf/bpf-next/c/3513ea9dab6c
- [bpf-next,v3,2/3] bpf: Reject callback subprogs invoke tailcall
https://git.kernel.org/bpf/bpf-next/c/a41d0c30d764
- [bpf-next,v3,3/3] selftests/bpf: Add testcases for callback with tailcall
https://git.kernel.org/bpf/bpf-next/c/42bfd21a8b70
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-07-17 0:50 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-07-16 12:01 [PATCH bpf-next v3 0/3] Fixes for tailcall with subprog and callback Pu Lehui
2026-07-16 12:01 ` [PATCH bpf-next v3 1/3] bpf: Sync tail_call_reachable with callee state on entry Pu Lehui
2026-07-16 12:01 ` [PATCH bpf-next v3 2/3] bpf: Reject callback subprogs invoke tailcall Pu Lehui
2026-07-16 12:01 ` [PATCH bpf-next v3 3/3] selftests/bpf: Add testcases for callback with tailcall Pu Lehui
2026-07-17 0:50 ` [PATCH bpf-next v3 0/3] Fixes for tailcall with subprog and callback patchwork-bot+netdevbpf
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox