selftests/bpf: Verify is_extended with multiple freplace links

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>
Acked-by: Jiri Olsa <jolsa@kernel.org>
Link: https://lore.kernel.org/bpf/20260924023737.1140521-3-chenyuan_fl@163.com
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
This commit is contained in:
Yuan Chen 2026-09-24 10:37:37 +08:00 committed by Kumar Kartikeya Dwivedi
parent c43c348a84
commit 01d37848bf
No known key found for this signature in database
GPG Key ID: 472D377B63542F83
2 changed files with 101 additions and 0 deletions

View File

@ -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"))

View File

@ -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";