* [PATCH bpf-next 1/2] bpf: Keep target extended until its last freplace link detaches
2026-09-23 9:06 [PATCH bpf-next 0/2] bpf: Keep target extended until its last freplace link detaches chenyuan_fl
@ 2026-09-23 9:06 ` chenyuan_fl
2026-09-23 9:54 ` Leon Hwang
` (2 more replies)
2026-09-23 9:06 ` [PATCH bpf-next 2/2] selftests/bpf: Verify is_extended with multiple freplace links chenyuan_fl
1 sibling, 3 replies; 6+ messages in thread
From: chenyuan_fl @ 2026-09-23 9:06 UTC (permalink / raw)
To: bpf
Cc: linux-kernel, Alexei Starovoitov, Alexei Starovoitov,
Daniel Borkmann, Andrii Nakryiko, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Martin KaFai Lau, Song Liu,
Yonghong Song, Jiri Olsa, Ihor Solodrai, Yuan Chen
From: Yuan Chen <chenyuan@kylinos.cn>
The is_extended / prog_array_member_cnt protocol introduced by commit
d6083f040d5d ("bpf: Prevent tailcall infinite loop caused by freplace")
keeps a prog extended by a freplace program out of prog_array maps, and
vice versa: once a tail call re-enters an extended subprogram, its
tail_call_cnt resets on every execution and the loop never terminates.
But is_extended is a plain boolean, while one target prog can carry
several freplace links at the same time, one on its entry and one on a
global subprogram. __bpf_trampoline_unlink_prog() cleared is_extended
whenever *any* freplace link detached, so detaching one of two links
re-armed the unbounded loop through the remaining one.
Replace the is_extended boolean with a count of the freplace links
attached to each target prog, so the target stays extended until its
last link detaches. Also rename bpf_freplace_check_tgt_prog() to
bpf_freplace_link_tgt_prog(), as the helper has never been a pure
check: it reserves the target prog on success.
Fixes: d6083f040d5d ("bpf: Prevent tailcall infinite loop caused by freplace")
Signed-off-by: Yuan Chen <chenyuan@kylinos.cn>
---
include/linux/bpf.h | 4 ++--
kernel/bpf/arraymap.c | 2 +-
kernel/bpf/trampoline.c | 10 ++++++----
3 files changed, 9 insertions(+), 7 deletions(-)
diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index 73bacfc6444d..fc9011b8446d 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -1773,7 +1773,6 @@ struct bpf_prog_aux {
bool xdp_has_frags;
bool exception_cb;
bool exception_boundary;
- bool is_extended; /* true if extended by freplace program */
bool jits_use_priv_stack;
bool priv_stack_requested;
bool changes_pkt_data;
@@ -1785,7 +1784,8 @@ struct bpf_prog_aux {
u8 verdict;
} sig;
u64 prog_array_member_cnt; /* counts how many times as member of prog_array */
- struct mutex ext_mutex; /* mutex for is_extended and prog_array_member_cnt */
+ u64 freplace_link_cnt; /* counts freplace links extending this prog */
+ struct mutex ext_mutex; /* mutex for freplace_link_cnt and prog_array_member_cnt */
struct bpf_arena *arena;
void (*recursion_detected)(struct bpf_prog *prog); /* callback if recursion is detected */
/* BTF_KIND_FUNC_PROTO for valid attach_btf_id */
diff --git a/kernel/bpf/arraymap.c b/kernel/bpf/arraymap.c
index 248b4818178c..3bcff6a0430d 100644
--- a/kernel/bpf/arraymap.c
+++ b/kernel/bpf/arraymap.c
@@ -974,7 +974,7 @@ static void *prog_fd_array_get_ptr(struct bpf_map *map,
}
mutex_lock(&prog->aux->ext_mutex);
- is_extended = prog->aux->is_extended;
+ is_extended = prog->aux->freplace_link_cnt > 0;
if (!is_extended)
prog->aux->prog_array_member_cnt++;
mutex_unlock(&prog->aux->ext_mutex);
diff --git a/kernel/bpf/trampoline.c b/kernel/bpf/trampoline.c
index ed7999ad6c66..d18a913bc80f 100644
--- a/kernel/bpf/trampoline.c
+++ b/kernel/bpf/trampoline.c
@@ -813,7 +813,7 @@ static enum bpf_tramp_prog_type bpf_attach_type_to_tramp(struct bpf_prog *prog)
}
}
-static int bpf_freplace_check_tgt_prog(struct bpf_prog *tgt_prog)
+static int bpf_freplace_link_tgt_prog(struct bpf_prog *tgt_prog)
{
struct bpf_prog_aux *aux = tgt_prog->aux;
@@ -827,7 +827,7 @@ static int bpf_freplace_check_tgt_prog(struct bpf_prog *tgt_prog)
*/
return -EBUSY;
- aux->is_extended = true;
+ aux->freplace_link_cnt++;
return 0;
}
@@ -933,7 +933,7 @@ static int __bpf_trampoline_link_prog(struct bpf_tramp_node *node,
/* Cannot attach extension if fentry/fexit are in use. */
if (cnt)
return -EBUSY;
- err = bpf_freplace_check_tgt_prog(tgt_prog);
+ err = bpf_freplace_link_tgt_prog(tgt_prog);
if (err)
return err;
tr->extension_prog = node->link->prog;
@@ -979,7 +979,9 @@ static int __bpf_trampoline_unlink_prog(struct bpf_tramp_node *node,
tr->extension_prog->bpf_func, NULL);
tr->extension_prog = NULL;
guard(mutex)(&tgt_prog->aux->ext_mutex);
- tgt_prog->aux->is_extended = false;
+ if (WARN_ON_ONCE(!tgt_prog->aux->freplace_link_cnt))
+ return err;
+ tgt_prog->aux->freplace_link_cnt--;
return err;
}
bpf_trampoline_remove_prog(tr, node);
--
2.54.0
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH bpf-next 2/2] selftests/bpf: Verify is_extended with multiple freplace links
2026-09-23 9:06 [PATCH bpf-next 0/2] bpf: Keep target extended until its last freplace link detaches chenyuan_fl
2026-09-23 9:06 ` [PATCH bpf-next 1/2] " chenyuan_fl
@ 2026-09-23 9:06 ` chenyuan_fl
1 sibling, 0 replies; 6+ messages in thread
From: chenyuan_fl @ 2026-09-23 9:06 UTC (permalink / raw)
To: bpf
Cc: linux-kernel, Alexei Starovoitov, Alexei Starovoitov,
Daniel Borkmann, Andrii Nakryiko, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Martin KaFai Lau, Song Liu,
Yonghong Song, Jiri Olsa, Ihor Solodrai, Yuan Chen
From: Yuan Chen <chenyuan@kylinos.cn>
Extend tc_bpf2bpf with two freplace links, one on entry_tc and one on
subprog_tc, and detach the one on the entry: while subprog_tc is still
extended, updating entry_tc into a prog_array map must keep failing
with -EBUSY, and succeed again once the last link detaches. The test
asserts the rejection instead of running the prog, as the update
succeeds and the prog loops unbounded on an unfixed kernel.
Signed-off-by: Yuan Chen <chenyuan@kylinos.cn>
---
.../selftests/bpf/prog_tests/tailcalls.c | 74 +++++++++++++++++++
.../bpf/progs/tailcall_freplace_multi.c | 27 +++++++
2 files changed, 101 insertions(+)
create mode 100644 tools/testing/selftests/bpf/progs/tailcall_freplace_multi.c
diff --git a/tools/testing/selftests/bpf/prog_tests/tailcalls.c b/tools/testing/selftests/bpf/prog_tests/tailcalls.c
index c5c9d6c359bb..aefb46778307 100644
--- a/tools/testing/selftests/bpf/prog_tests/tailcalls.c
+++ b/tools/testing/selftests/bpf/prog_tests/tailcalls.c
@@ -6,6 +6,7 @@
#include "tailcall_bpf2bpf_hierarchy2.skel.h"
#include "tailcall_bpf2bpf_hierarchy3.skel.h"
#include "tailcall_freplace.skel.h"
+#include "tailcall_freplace_multi.skel.h"
#include "tc_bpf2bpf.skel.h"
#include "tailcall_fail.skel.h"
#include "tailcall_cgrp_storage_owner.skel.h"
@@ -1655,6 +1656,77 @@ static void test_tailcall_bpf2bpf_freplace(void)
tc_bpf2bpf__destroy(tc_skel);
}
+static void test_tailcall_freplace_multi(void)
+{
+ struct tailcall_freplace_multi *freplace_skel = NULL;
+ struct bpf_link *link_subprog = NULL, *link_entry = NULL;
+ struct tc_bpf2bpf *tc_skel = NULL;
+ int tc_prog_fd, map_fd, key = 0, err;
+
+ tc_skel = tc_bpf2bpf__open_and_load();
+ if (!ASSERT_OK_PTR(tc_skel, "tc_bpf2bpf__open_and_load"))
+ return;
+
+ tc_prog_fd = bpf_program__fd(tc_skel->progs.entry_tc);
+
+ freplace_skel = tailcall_freplace_multi__open();
+ if (!ASSERT_OK_PTR(freplace_skel, "tailcall_freplace_multi__open"))
+ goto out;
+
+ err = bpf_program__set_attach_target(freplace_skel->progs.subprog_freplace,
+ tc_prog_fd, "subprog_tc");
+ if (!ASSERT_OK(err, "set_attach_target subprog_tc"))
+ goto out;
+
+ err = bpf_program__set_attach_target(freplace_skel->progs.entry_freplace,
+ tc_prog_fd, "entry_tc");
+ if (!ASSERT_OK(err, "set_attach_target entry_tc"))
+ goto out;
+
+ err = tailcall_freplace_multi__load(freplace_skel);
+ if (!ASSERT_OK(err, "tailcall_freplace_multi__load"))
+ goto out;
+
+ map_fd = bpf_map__fd(freplace_skel->maps.jmp_table);
+
+ link_subprog = bpf_program__attach_freplace(freplace_skel->progs.subprog_freplace,
+ tc_prog_fd, "subprog_tc");
+ if (!ASSERT_OK_PTR(link_subprog, "attach_freplace subprog_tc"))
+ goto out;
+
+ link_entry = bpf_program__attach_freplace(freplace_skel->progs.entry_freplace,
+ tc_prog_fd, "entry_tc");
+ if (!ASSERT_OK_PTR(link_entry, "attach_freplace entry_tc"))
+ goto out;
+
+ err = bpf_map_update_elem(map_fd, &key, &tc_prog_fd, BPF_ANY);
+ if (!ASSERT_ERR(err, "update jmp_table with extended prog"))
+ goto out;
+
+ err = bpf_link__destroy(link_entry);
+ link_entry = NULL;
+ if (!ASSERT_OK(err, "destroy entry link"))
+ goto out;
+
+ err = bpf_map_update_elem(map_fd, &key, &tc_prog_fd, BPF_ANY);
+ if (!ASSERT_ERR(err, "update jmp_table with still extended prog"))
+ goto out;
+
+ err = bpf_link__destroy(link_subprog);
+ link_subprog = NULL;
+ if (!ASSERT_OK(err, "destroy subprog link"))
+ goto out;
+
+ err = bpf_map_update_elem(map_fd, &key, &tc_prog_fd, BPF_ANY);
+ ASSERT_OK(err, "update jmp_table");
+
+out:
+ bpf_link__destroy(link_subprog);
+ bpf_link__destroy(link_entry);
+ tailcall_freplace_multi__destroy(freplace_skel);
+ tc_bpf2bpf__destroy(tc_skel);
+}
+
static void test_tailcall_failure()
{
RUN_TESTS(tailcall_fail);
@@ -2005,6 +2077,8 @@ void test_tailcalls(void)
test_tailcall_freplace();
if (test__start_subtest("tailcall_bpf2bpf_freplace"))
test_tailcall_bpf2bpf_freplace();
+ if (test__start_subtest("tailcall_freplace_multi"))
+ test_tailcall_freplace_multi();
if (test__start_subtest("tailcall_failure"))
test_tailcall_failure();
if (test__start_subtest("tailcall_sleepable"))
diff --git a/tools/testing/selftests/bpf/progs/tailcall_freplace_multi.c b/tools/testing/selftests/bpf/progs/tailcall_freplace_multi.c
new file mode 100644
index 000000000000..ca764411dc0b
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/tailcall_freplace_multi.c
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 KylinSoft Corporation. */
+
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+
+struct {
+ __uint(type, BPF_MAP_TYPE_PROG_ARRAY);
+ __uint(max_entries, 1);
+ __uint(key_size, sizeof(__u32));
+ __uint(value_size, sizeof(__u32));
+} jmp_table SEC(".maps");
+
+SEC("freplace")
+int subprog_freplace(struct __sk_buff *skb)
+{
+ bpf_tail_call_static(skb, &jmp_table, 0);
+ return 0;
+}
+
+SEC("freplace")
+int entry_freplace(struct __sk_buff *skb)
+{
+ return 0;
+}
+
+char __license[] SEC("license") = "GPL";
--
2.54.0
^ permalink raw reply [flat|nested] 6+ messages in thread