From nobody Thu Apr 9 14:56:47 2026 Received: from out-174.mta0.migadu.com (out-174.mta0.migadu.com [91.218.175.174]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A4BF03FFAD6 for ; Mon, 2 Mar 2026 15:05:36 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=91.218.175.174 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772463938; cv=none; b=Op/fed3TfaWeTL+aFUVAaK6tda/gducf3f2lDVRJMvXk3Rp3oFjQQRBiwf1HaGDVGmY1rAOVb2kJxhCL7DtdkzPplyjwTJPMoONJyRl2JyrJyRMmLBw7ZTalGRnIKJLmbHFUdEm3waQ9XGZlwt3z+/L9hGwaRyOMGjskOdd6jL4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772463938; c=relaxed/simple; bh=P0lp57O3xVx8qjH4XeIzmvz7D2tk1xI95GIqlyzq810=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=L3NAkmfDuyl3lC5z0kjrfBcDpjCmUe9jMmfLxHclMGrLLolpz+IhFHNV6Ky6zpXHe8aPPZCqpF0IWXu/tuEGyWVb9RnjfRxLHjTRgcQ8+Prk0NR8WUy82C9J6AGhUqvg2rQrpccBlzx6OWomtomycFGaBE9HSsy5wOs8+ycjh+U= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev; spf=pass smtp.mailfrom=linux.dev; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b=JwqosRZB; arc=none smtp.client-ip=91.218.175.174 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.dev Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.dev Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linux.dev header.i=@linux.dev header.b="JwqosRZB" X-Report-Abuse: Please report any abuse attempt to abuse@migadu.com and include these headers. DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.dev; s=key1; t=1772463933; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=ALIJ7LIlV+z/PhSVVXXCJAIsp2KMkktx7GQtroRZcKI=; b=JwqosRZB5VmSP5OzFL5hsD6xn2q0e2+39jB5C8TkyomR4KeQzDC2sDvOVth+pLi8AVd9A9 XSVJ/wGNPtwRBgIzXWXKKR5Bc571A7ETNikDOrvA7nWTuFoyCvzJpnZ1c7M9x+Sz0JXqif /cx3VW2V4Oz93SfKnNU8UfrdnvCfq/A= From: Leon Hwang To: bpf@vger.kernel.org Cc: Alexei Starovoitov , Daniel Borkmann , Andrii Nakryiko , Martin KaFai Lau , Eduard Zingerman , Song Liu , Yonghong Song , John Fastabend , KP Singh , Stanislav Fomichev , Hao Luo , Jiri Olsa , Shuah Khan , Feng Yang , Leon Hwang , Menglong Dong , Puranjay Mohan , =?UTF-8?q?Bj=C3=B6rn=20T=C3=B6pel?= , Pu Lehui , linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, netdev@vger.kernel.org, kernel-patches-bot@fb.com Subject: [PATCH bpf-next v2 6/6] selftests/bpf: Add tests to verify prog_array map compatibility Date: Mon, 2 Mar 2026 23:03:42 +0800 Message-ID: <20260302150342.55709-7-leon.hwang@linux.dev> In-Reply-To: <20260302150342.55709-1-leon.hwang@linux.dev> References: <20260302150342.55709-1-leon.hwang@linux.dev> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Migadu-Flow: FLOW_OUT Content-Type: text/plain; charset="utf-8" Add tests to verify the following tail call restrictions: * !kprobe_write_ctx progs are not compatible with kprobe_write_ctx progs. * !call_get_func_ip progs are not compatible with call_get_func_ip progs. * !call_session_cookie progs are not compatible with call_session_cookie progs. For kprobe_write_ctx, call_get_func_ip, and call_session_cookie, a prog_array map cannot be shared between progs with different values. Signed-off-by: Leon Hwang --- .../selftests/bpf/prog_tests/tailcalls.c | 319 ++++++++++++++++++ .../bpf/progs/tailcall_map_compatible.c | 103 ++++++ 2 files changed, 422 insertions(+) create mode 100644 tools/testing/selftests/bpf/progs/tailcall_map_compatib= le.c diff --git a/tools/testing/selftests/bpf/prog_tests/tailcalls.c b/tools/tes= ting/selftests/bpf/prog_tests/tailcalls.c index 7d534fde0af9..1063e73ecffa 100644 --- a/tools/testing/selftests/bpf/prog_tests/tailcalls.c +++ b/tools/testing/selftests/bpf/prog_tests/tailcalls.c @@ -9,6 +9,7 @@ #include "tc_bpf2bpf.skel.h" #include "tailcall_fail.skel.h" #include "tailcall_sleepable.skel.h" +#include "tailcall_map_compatible.skel.h" =20 /* test_tailcall_1 checks basic functionality by patching multiple locatio= ns * in a single program for a single tail call slot with nop->jmp, jmp->nop @@ -1725,6 +1726,312 @@ static void test_tailcall_sleepable(void) tailcall_sleepable__destroy(skel); } =20 +#ifdef __x86_64__ +/* uprobe attach point */ +static noinline int trigger_uprobe_fn(int a) +{ + asm volatile ("" : "+r"(a)); + return a; +} + +static void test_map_compatible_update_kprobe_write_ctx(void) +{ + struct bpf_program *dummy, *kprobe, *fsession; + struct tailcall_map_compatible *skel; + struct bpf_link *link =3D NULL; + int err, prog_fd, key =3D 0; + struct bpf_map *map; + LIBBPF_OPTS(bpf_kprobe_opts, kprobe_opts); + LIBBPF_OPTS(bpf_uprobe_opts, uprobe_opts); + LIBBPF_OPTS(bpf_test_run_opts, topts); + + skel =3D tailcall_map_compatible__open(); + if (!ASSERT_OK_PTR(skel, "tailcall_map_compatible__open")) + return; + + dummy =3D skel->progs.dummy_kprobe; + bpf_program__set_autoload(dummy, true); + + kprobe =3D skel->progs.kprobe; + bpf_program__set_autoload(kprobe, true); + + fsession =3D skel->progs.fsession_tailcall; + bpf_program__set_autoload(fsession, true); + + skel->bss->data =3D 0xdeadbeef; + + err =3D tailcall_map_compatible__load(skel); + if (!ASSERT_OK(err, "tailcall_map_compatible__load")) + goto out; + + prog_fd =3D bpf_program__fd(kprobe); + map =3D skel->maps.prog_array_dummy; + err =3D bpf_map_update_elem(bpf_map__fd(map), &key, &prog_fd, BPF_ANY); + ASSERT_ERR(err, "bpf_map_update_elem kprobe"); + + skel->links.dummy_kprobe =3D bpf_program__attach_kprobe_opts(dummy, "bpf_= fentry_test1", + &kprobe_opts); + if (!ASSERT_OK_PTR(skel->links.dummy_kprobe, "bpf_program__attach_kprobe_= opts")) + goto out; + + skel->links.fsession_tailcall =3D bpf_program__attach_trace(fsession); + if (!ASSERT_OK_PTR(skel->links.fsession_tailcall, "bpf_program__attach_tr= ace")) + goto out; + + err =3D bpf_prog_test_run_opts(bpf_program__fd(fsession), &topts); + ASSERT_OK(err, "bpf_prog_test_run_opts fsession"); + + ASSERT_EQ(topts.retval, 0, "dummy retval"); + ASSERT_EQ(skel->bss->dummy_run, 1, "dummy_run"); + ASSERT_EQ(skel->bss->data, 0xdeadbeef, "data"); + + err =3D bpf_map_delete_elem(bpf_map__fd(map), &key); + ASSERT_TRUE(!err || err =3D=3D -ENOENT, "bpf_map_delete_elem"); + + uprobe_opts.func_name =3D "trigger_uprobe_fn"; + link =3D bpf_program__attach_uprobe_opts(kprobe, 0, "/proc/self/exe", 0, = &uprobe_opts); + if (!ASSERT_OK_PTR(link, "bpf_program__attach_uprobe_opts")) + goto out; + + prog_fd =3D bpf_program__fd(dummy); + map =3D skel->maps.prog_array_kprobe; + err =3D bpf_map_update_elem(bpf_map__fd(map), &key, &prog_fd, BPF_ANY); + ASSERT_OK(err, "bpf_map_update_elem dummy"); + + ASSERT_EQ(trigger_uprobe_fn(1), 0, "trigger_uprobe_fn retval"); /* modifi= ed by uprobe */ + + ASSERT_EQ(topts.retval, 0, "dummy retval"); + ASSERT_EQ(skel->bss->dummy_run, 2, "dummy_run"); + ASSERT_EQ(skel->bss->data, 0, "data"); + +out: + bpf_link__destroy(link); + tailcall_map_compatible__destroy(skel); +} +#else +static void test_map_compatible_update_kprobe_write_ctx(void) +{ + test__skip(); +} +#endif + +static void test_map_compatible_update_get_func_ip(void) +{ + struct tailcall_map_compatible *skel; + struct bpf_program *dummy, *fentry; + struct bpf_link *link =3D NULL; + int err, prog_fd, key =3D 0; + struct bpf_map *map; + __u64 func_ip; + LIBBPF_OPTS(bpf_test_run_opts, topts); + + skel =3D tailcall_map_compatible__open(); + if (!ASSERT_OK_PTR(skel, "tailcall_map_compatible__open")) + return; + + dummy =3D skel->progs.dummy_fentry; + bpf_program__set_autoload(dummy, true); + + fentry =3D skel->progs.fentry; + bpf_program__set_autoload(fentry, true); + + err =3D tailcall_map_compatible__load(skel); + if (!ASSERT_OK(err, "tailcall_map_compatible__load")) + goto out; + + link =3D bpf_program__attach_trace(fentry); + if (!ASSERT_OK_PTR(link, "bpf_program__attach_trace fentry")) + goto out; + + err =3D bpf_prog_test_run_opts(bpf_program__fd(fentry), &topts); + if (!ASSERT_OK(err, "bpf_prog_test_run_opts fentry")) + goto out; + + ASSERT_EQ(topts.retval, 0, "fentry retval"); + ASSERT_EQ(skel->bss->dummy_run, 0, "dummy_run"); + ASSERT_NEQ(skel->bss->data, 0, "data"); + func_ip =3D skel->bss->data; + + skel->bss->data =3D 0xdeadbeef; + + err =3D bpf_link__destroy(link); + link =3D NULL; + if (!ASSERT_OK(err, "bpf_link__destroy")) + goto out; + + prog_fd =3D bpf_program__fd(fentry); + map =3D skel->maps.prog_array_dummy; + err =3D bpf_map_update_elem(bpf_map__fd(map), &key, &prog_fd, BPF_ANY); + ASSERT_ERR(err, "bpf_map_update_elem fentry"); + + link =3D bpf_program__attach_trace(dummy); + if (!ASSERT_OK_PTR(link, "bpf_program__attach_trace dummy")) + goto out; + + err =3D bpf_prog_test_run_opts(bpf_program__fd(dummy), &topts); + if (!ASSERT_OK(err, "bpf_prog_test_run_opts dummy")) + goto out; + + ASSERT_EQ(topts.retval, 0, "dummy retval"); + ASSERT_EQ(skel->bss->dummy_run, 1, "dummy_run"); + ASSERT_EQ(skel->bss->data, 0xdeadbeef, "data"); + ASSERT_NEQ(skel->bss->data, func_ip, "data func_ip"); + + err =3D bpf_link__destroy(link); + link =3D NULL; + if (!ASSERT_OK(err, "bpf_link__destroy")) + goto out; + + err =3D bpf_map_delete_elem(bpf_map__fd(map), &key); + ASSERT_TRUE(!err || err =3D=3D -ENOENT, "bpf_map_delete_elem"); + + prog_fd =3D bpf_program__fd(dummy); + map =3D skel->maps.prog_array_tracing; + err =3D bpf_map_update_elem(bpf_map__fd(map), &key, &prog_fd, BPF_ANY); + ASSERT_OK(err, "bpf_map_update_elem dummy"); + + link =3D bpf_program__attach_trace(fentry); + if (!ASSERT_OK_PTR(link, "bpf_program__attach_trace fentry")) + goto out; + + err =3D bpf_prog_test_run_opts(bpf_program__fd(fentry), &topts); + if (!ASSERT_OK(err, "bpf_prog_test_run_opts fentry")) + goto out; + + ASSERT_EQ(topts.retval, 0, "fentry retval"); + ASSERT_EQ(skel->bss->dummy_run, 2, "dummy_run"); + ASSERT_EQ(skel->bss->data, func_ip, "data"); + +out: + bpf_link__destroy(link); + tailcall_map_compatible__destroy(skel); +} + +static void test_map_compatible_update_session_cookie(void) +{ + struct tailcall_map_compatible *skel; + struct bpf_program *dummy, *fsession; + struct bpf_link *link =3D NULL; + int err, prog_fd, key =3D 0; + struct bpf_map *map; + LIBBPF_OPTS(bpf_test_run_opts, topts); + + skel =3D tailcall_map_compatible__open(); + if (!ASSERT_OK_PTR(skel, "tailcall_map_compatible__open")) + return; + + dummy =3D skel->progs.dummy_fsession; + bpf_program__set_autoload(dummy, true); + + fsession =3D skel->progs.fsession_cookie; + bpf_program__set_autoload(fsession, true); + + skel->bss->data =3D 0xdeadbeef; + + err =3D tailcall_map_compatible__load(skel); + if (err =3D=3D -EOPNOTSUPP) { + test__skip(); + goto out; + } + if (!ASSERT_OK(err, "tailcall_map_compatible__load")) + goto out; + + prog_fd =3D bpf_program__fd(fsession); + map =3D skel->maps.prog_array_dummy; + err =3D bpf_map_update_elem(bpf_map__fd(map), &key, &prog_fd, BPF_ANY); + ASSERT_ERR(err, "bpf_map_update_elem fsession"); + + link =3D bpf_program__attach_trace(dummy); + if (!ASSERT_OK_PTR(link, "bpf_program__attach_trace dummy")) + goto out; + + err =3D bpf_prog_test_run_opts(bpf_program__fd(dummy), &topts); + ASSERT_OK(err, "bpf_prog_test_run_opts dummy"); + + ASSERT_EQ(topts.retval, 0, "dummy retval"); + ASSERT_EQ(skel->bss->dummy_run, 2, "dummy_run"); + ASSERT_EQ(skel->bss->data, 0xdeadbeef, "data"); + + err =3D bpf_link__destroy(link); + link =3D NULL; + if (!ASSERT_OK(err, "bpf_link__destroy")) + goto out; + + err =3D bpf_map_delete_elem(bpf_map__fd(map), &key); + ASSERT_TRUE(!err || err =3D=3D -ENOENT, "bpf_map_delete_elem"); + + prog_fd =3D bpf_program__fd(dummy); + map =3D skel->maps.prog_array_tracing; + err =3D bpf_map_update_elem(bpf_map__fd(map), &key, &prog_fd, BPF_ANY); + ASSERT_OK(err, "bpf_map_update_elem dummy"); + + link =3D bpf_program__attach_trace(fsession); + if (!ASSERT_OK_PTR(link, "bpf_program__attach_trace fsession")) + goto out; + + err =3D bpf_prog_test_run_opts(bpf_program__fd(fsession), &topts); + if (!ASSERT_OK(err, "bpf_prog_test_run_opts fsession")) + goto out; + + ASSERT_EQ(topts.retval, 0, "fsession retval"); + ASSERT_EQ(skel->bss->dummy_run, 4, "dummy_run"); + ASSERT_EQ(skel->bss->data, 0, "data"); + +out: + bpf_link__destroy(link); + tailcall_map_compatible__destroy(skel); +} + +static void test_map_compatible_init(const char *prog1, const char *prog2) +{ + struct tailcall_map_compatible *skel; + struct bpf_program *p1, *p2; + int err; + + skel =3D tailcall_map_compatible__open(); + if (!ASSERT_OK_PTR(skel, "tailcall_map_compatible__open")) + return; + + p1 =3D bpf_object__find_program_by_name(skel->obj, prog1); + if (!ASSERT_OK_PTR(p1, "bpf_object__find_program_by_name prog1")) + goto out; + bpf_program__set_autoload(p1, true); + + p2 =3D bpf_object__find_program_by_name(skel->obj, prog2); + if (!ASSERT_OK_PTR(p2, "bpf_object__find_program_by_name prog2")) + goto out; + bpf_program__set_autoload(p2, true); + + err =3D tailcall_map_compatible__load(skel); + if (err =3D=3D -EOPNOTSUPP) { + test__skip(); + goto out; + } + ASSERT_ERR(err, "tailcall_map_compatible__load"); + +out: + tailcall_map_compatible__destroy(skel); +} + +static void test_map_compatible_init_kprobe_write_ctx(void) +{ +#ifdef __x86_64__ + test_map_compatible_init("kprobe", "kprobe_tailcall"); +#else + test__skip(); +#endif +} + +static void test_map_compatible_init_call_get_func_ip(void) +{ + test_map_compatible_init("fentry", "fentry_tailcall"); +} + +static void test_map_compatible_init_call_session_cookie(void) +{ + test_map_compatible_init("fsession_cookie", "fsession_tailcall"); +} + void test_tailcalls(void) { if (test__start_subtest("tailcall_1")) @@ -1781,4 +2088,16 @@ void test_tailcalls(void) test_tailcall_failure(); if (test__start_subtest("tailcall_sleepable")) test_tailcall_sleepable(); + if (test__start_subtest("map_compatible/update/kprobe_write_ctx")) + test_map_compatible_update_kprobe_write_ctx(); + if (test__start_subtest("map_compatible/update/get_func_ip")) + test_map_compatible_update_get_func_ip(); + if (test__start_subtest("map_compatible/update/session_cookie")) + test_map_compatible_update_session_cookie(); + if (test__start_subtest("map_compatible/init/kprobe_write_ctx")) + test_map_compatible_init_kprobe_write_ctx(); + if (test__start_subtest("map_compatible/init/call_get_func_ip")) + test_map_compatible_init_call_get_func_ip(); + if (test__start_subtest("map_compatible/init/call_session_cookie")) + test_map_compatible_init_call_session_cookie(); } diff --git a/tools/testing/selftests/bpf/progs/tailcall_map_compatible.c b/= tools/testing/selftests/bpf/progs/tailcall_map_compatible.c new file mode 100644 index 000000000000..991b799c89ac --- /dev/null +++ b/tools/testing/selftests/bpf/progs/tailcall_map_compatible.c @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include + +char _license[] SEC("license") =3D "GPL"; + +int dummy_run; +u64 data; + +struct { + __uint(type, BPF_MAP_TYPE_PROG_ARRAY); + __uint(max_entries, 1); + __uint(key_size, sizeof(__u32)); + __uint(value_size, sizeof(__u32)); +} prog_array_dummy SEC(".maps"); + +#if defined(__TARGET_ARCH_x86) +SEC("?kprobe") +int dummy_kprobe(void *ctx) +{ + dummy_run++; + bpf_tail_call_static(ctx, &prog_array_dummy, 0); + return 0; +} + +struct { + __uint(type, BPF_MAP_TYPE_PROG_ARRAY); + __uint(max_entries, 1); + __uint(key_size, sizeof(__u32)); + __uint(value_size, sizeof(__u32)); +} prog_array_kprobe SEC(".maps"); + +SEC("?kprobe") +int kprobe(struct pt_regs *regs) +{ + data =3D regs->di =3D 0; + bpf_tail_call_static(regs, &prog_array_kprobe, 0); + return 0; +} + +SEC("?kprobe") +int kprobe_tailcall(struct pt_regs *regs) +{ + bpf_tail_call_static(regs, &prog_array_kprobe, 0); + return 0; +} +#endif + +SEC("?fentry/bpf_fentry_test1") +int dummy_fentry(void *ctx) +{ + dummy_run++; + bpf_tail_call_static(ctx, &prog_array_dummy, 0); + return 0; +} + +struct { + __uint(type, BPF_MAP_TYPE_PROG_ARRAY); + __uint(max_entries, 1); + __uint(key_size, sizeof(__u32)); + __uint(value_size, sizeof(__u32)); +} prog_array_tracing SEC(".maps"); + +SEC("?fentry/bpf_fentry_test1") +int BPF_PROG(fentry) +{ + data =3D bpf_get_func_ip(ctx); + bpf_tail_call_static(ctx, &prog_array_tracing, 0); + return 0; +} + +SEC("?fentry/bpf_fentry_test1") +int BPF_PROG(fentry_tailcall) +{ + bpf_tail_call_static(ctx, &prog_array_tracing, 0); + return 0; +} + +SEC("?fsession/bpf_fentry_test2") +int dummy_fsession(void *ctx) +{ + dummy_run++; + bpf_tail_call_static(ctx, &prog_array_dummy, 0); + return 0; +} + +SEC("?fsession/bpf_fentry_test2") +int BPF_PROG(fsession_cookie) +{ + u64 *cookie =3D bpf_session_cookie(ctx); + + data =3D *cookie =3D 0; + bpf_tail_call_static(ctx, &prog_array_tracing, 0); + return 0; +} + +SEC("?fsession/bpf_fentry_test2") +int BPF_PROG(fsession_tailcall) +{ + bpf_tail_call_static(ctx, &prog_array_tracing, 0); + return 0; +} --=20 2.52.0