Since bpf_get_func_ip() is supported for fsession intuitively, add fsession to the verbose log in check_get_func_ip(). No functional change intended. Signed-off-by: Leon Hwang --- kernel/bpf/verifier.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 1153a828ce8d..9e9c04e08fba 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -11463,7 +11463,7 @@ static int check_get_func_ip(struct bpf_verifier_env *env) if (type == BPF_PROG_TYPE_TRACING) { if (!bpf_prog_has_trampoline(env->prog)) { - verbose(env, "func %s#%d supported only for fentry/fexit/fmod_ret programs\n", + verbose(env, "func %s#%d supported only for fentry/fexit/fsession/fmod_ret programs\n", func_id_name(func_id), func_id); return -ENOTSUPP; } -- 2.52.0 uprobe programs that can modify pt_regs require different runtime assumptions than pt_regs-read-only uprobe programs. Mixing both in one prog_array can make owner expectations diverge from callee behavior. Reject the combination of !kprobe_write_ctx progs with kprobe_write_ctx progs in __bpf_prog_map_compatible() to address the issue. Fixes: 7384893d970e ("bpf: Allow uprobe program to change context registers") Signed-off-by: Leon Hwang --- include/linux/bpf.h | 7 ++++--- kernel/bpf/core.c | 3 +++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/include/linux/bpf.h b/include/linux/bpf.h index b78b53198a2e..2a2f6448a5fb 100644 --- a/include/linux/bpf.h +++ b/include/linux/bpf.h @@ -285,9 +285,10 @@ struct bpf_list_node_kern { */ struct bpf_map_owner { enum bpf_prog_type type; - bool jited; - bool xdp_has_frags; - bool sleepable; + u32 jited:1, + xdp_has_frags:1, + sleepable:1, + kprobe_write_ctx:1; u64 storage_cookie[MAX_BPF_CGROUP_STORAGE_TYPE]; const struct btf_type *attach_func_proto; enum bpf_attach_type expected_attach_type; diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c index 3ece2da55625..f99a901b5e48 100644 --- a/kernel/bpf/core.c +++ b/kernel/bpf/core.c @@ -2402,6 +2402,7 @@ static bool __bpf_prog_map_compatible(struct bpf_map *map, map->owner->jited = fp->jited; map->owner->xdp_has_frags = aux->xdp_has_frags; map->owner->sleepable = fp->sleepable; + map->owner->kprobe_write_ctx = aux->kprobe_write_ctx; map->owner->expected_attach_type = fp->expected_attach_type; map->owner->attach_func_proto = aux->attach_func_proto; for_each_cgroup_storage_type(i) { @@ -2415,6 +2416,8 @@ static bool __bpf_prog_map_compatible(struct bpf_map *map, map->owner->jited == fp->jited && map->owner->xdp_has_frags == aux->xdp_has_frags && map->owner->sleepable == fp->sleepable; + if (ret && (!map->owner->kprobe_write_ctx && aux->kprobe_write_ctx)) + ret = false; if (ret && map->map_type == BPF_MAP_TYPE_PROG_ARRAY && map->owner->expected_attach_type != fp->expected_attach_type) -- 2.52.0 Trampoline-based tracing programs that call bpf_get_func_ip() rely on func_ip stored on stack. Mixing them with tracing programs that do not share this requirement creates asymmetric expectations in tail calls. Reject the combination of !call_get_func_ip progs with call_get_func_ip progs in __bpf_prog_map_compatible() to address the issue. Fixes: 1e37392cccde ("bpf: Enable BPF_TRAMP_F_IP_ARG for trampolines with call_get_func_ip") Signed-off-by: Leon Hwang --- include/linux/bpf.h | 3 ++- kernel/bpf/core.c | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/include/linux/bpf.h b/include/linux/bpf.h index 2a2f6448a5fb..65793fd146c5 100644 --- a/include/linux/bpf.h +++ b/include/linux/bpf.h @@ -288,7 +288,8 @@ struct bpf_map_owner { u32 jited:1, xdp_has_frags:1, sleepable:1, - kprobe_write_ctx:1; + kprobe_write_ctx:1, + call_get_func_ip:1; u64 storage_cookie[MAX_BPF_CGROUP_STORAGE_TYPE]; const struct btf_type *attach_func_proto; enum bpf_attach_type expected_attach_type; diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c index f99a901b5e48..01fce3fba0be 100644 --- a/kernel/bpf/core.c +++ b/kernel/bpf/core.c @@ -2403,6 +2403,7 @@ static bool __bpf_prog_map_compatible(struct bpf_map *map, map->owner->xdp_has_frags = aux->xdp_has_frags; map->owner->sleepable = fp->sleepable; map->owner->kprobe_write_ctx = aux->kprobe_write_ctx; + map->owner->call_get_func_ip = fp->call_get_func_ip; map->owner->expected_attach_type = fp->expected_attach_type; map->owner->attach_func_proto = aux->attach_func_proto; for_each_cgroup_storage_type(i) { @@ -2418,6 +2419,9 @@ static bool __bpf_prog_map_compatible(struct bpf_map *map, map->owner->sleepable == fp->sleepable; if (ret && (!map->owner->kprobe_write_ctx && aux->kprobe_write_ctx)) ret = false; + if (ret && (!map->owner->call_get_func_ip && fp->call_get_func_ip && + prog_type == BPF_PROG_TYPE_TRACING)) + ret = false; if (ret && map->map_type == BPF_MAP_TYPE_PROG_ARRAY && map->owner->expected_attach_type != fp->expected_attach_type) -- 2.52.0 bpf_session_cookie() depends on consistent session metadata stored on stack for fsession programs. Mixing fsession programs that do and do not rely on these helpers in tail calls can violate that runtime contract. Disallow the combination of !call_session_cookie progs and call_session_cookie progs in __bpf_prog_map_compatible() to address the issue. Fixes: eeee4239dbb1 ("bpf: support fsession for bpf_session_cookie") Signed-off-by: Leon Hwang --- include/linux/bpf.h | 3 ++- kernel/bpf/core.c | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/include/linux/bpf.h b/include/linux/bpf.h index 65793fd146c5..c74db70f9be1 100644 --- a/include/linux/bpf.h +++ b/include/linux/bpf.h @@ -289,7 +289,8 @@ struct bpf_map_owner { xdp_has_frags:1, sleepable:1, kprobe_write_ctx:1, - call_get_func_ip:1; + call_get_func_ip:1, + call_session_cookie:1; u64 storage_cookie[MAX_BPF_CGROUP_STORAGE_TYPE]; const struct btf_type *attach_func_proto; enum bpf_attach_type expected_attach_type; diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c index 01fce3fba0be..904a8dbfd56f 100644 --- a/kernel/bpf/core.c +++ b/kernel/bpf/core.c @@ -2404,6 +2404,7 @@ static bool __bpf_prog_map_compatible(struct bpf_map *map, map->owner->sleepable = fp->sleepable; map->owner->kprobe_write_ctx = aux->kprobe_write_ctx; map->owner->call_get_func_ip = fp->call_get_func_ip; + map->owner->call_session_cookie = fp->call_session_cookie; map->owner->expected_attach_type = fp->expected_attach_type; map->owner->attach_func_proto = aux->attach_func_proto; for_each_cgroup_storage_type(i) { @@ -2422,6 +2423,9 @@ static bool __bpf_prog_map_compatible(struct bpf_map *map, if (ret && (!map->owner->call_get_func_ip && fp->call_get_func_ip && prog_type == BPF_PROG_TYPE_TRACING)) ret = false; + if (ret && (!map->owner->call_session_cookie && fp->call_session_cookie && + prog_type == BPF_PROG_TYPE_TRACING)) + ret = false; if (ret && map->map_type == BPF_MAP_TYPE_PROG_ARRAY && map->owner->expected_attach_type != fp->expected_attach_type) -- 2.52.0 bpf_session_is_return() depends on consistent session metadata stored on stack for fsession programs. Mixing fsession programs that do and do not rely on these helpers in tail calls can violate that runtime contract. Disallow the combination of !call_session_is_return progs and call_session_is_return progs in __bpf_prog_map_compatible() to address the issue. Fixes: 27d89baa6da8 ("bpf: support fsession for bpf_session_is_return") Signed-off-by: Leon Hwang --- include/linux/bpf.h | 4 +++- kernel/bpf/core.c | 4 ++++ kernel/bpf/verifier.c | 2 ++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/include/linux/bpf.h b/include/linux/bpf.h index c74db70f9be1..6be5f81b61e7 100644 --- a/include/linux/bpf.h +++ b/include/linux/bpf.h @@ -290,7 +290,8 @@ struct bpf_map_owner { sleepable:1, kprobe_write_ctx:1, call_get_func_ip:1, - call_session_cookie:1; + call_session_cookie:1, + call_session_is_return:1; u64 storage_cookie[MAX_BPF_CGROUP_STORAGE_TYPE]; const struct btf_type *attach_func_proto; enum bpf_attach_type expected_attach_type; @@ -1699,6 +1700,7 @@ struct bpf_prog_aux { bool changes_pkt_data; bool might_sleep; bool kprobe_write_ctx; + bool call_session_is_return; /* Do we call bpf_session_is_return */ 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 */ struct bpf_arena *arena; diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c index 904a8dbfd56f..44aeb49b2d1b 100644 --- a/kernel/bpf/core.c +++ b/kernel/bpf/core.c @@ -2405,6 +2405,7 @@ static bool __bpf_prog_map_compatible(struct bpf_map *map, map->owner->kprobe_write_ctx = aux->kprobe_write_ctx; map->owner->call_get_func_ip = fp->call_get_func_ip; map->owner->call_session_cookie = fp->call_session_cookie; + map->owner->call_session_is_return = aux->call_session_is_return; map->owner->expected_attach_type = fp->expected_attach_type; map->owner->attach_func_proto = aux->attach_func_proto; for_each_cgroup_storage_type(i) { @@ -2426,6 +2427,9 @@ static bool __bpf_prog_map_compatible(struct bpf_map *map, if (ret && (!map->owner->call_session_cookie && fp->call_session_cookie && prog_type == BPF_PROG_TYPE_TRACING)) ret = false; + if (ret && (!map->owner->call_session_is_return && aux->call_session_is_return && + prog_type == BPF_PROG_TYPE_TRACING)) + ret = false; if (ret && map->map_type == BPF_MAP_TYPE_PROG_ARRAY && map->owner->expected_attach_type != fp->expected_attach_type) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 9e9c04e08fba..919075ee3479 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -14409,6 +14409,8 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn, if (meta.func_id == special_kfunc_list[KF_bpf_session_cookie]) env->prog->call_session_cookie = true; + if (meta.func_id == special_kfunc_list[KF_bpf_session_is_return]) + env->prog->aux->call_session_is_return = true; return 0; } -- 2.52.0 Add prog_array coverage for kprobe programs with and without kprobe_write_ctx requirements. The test verifies one-way enforcement: !kprobe_write_ctx programs cannot tail-call kprobe_write_ctx programs. Assisted-by: Codex:gpt-5.3-codex Signed-off-by: Leon Hwang --- .../selftests/bpf/prog_tests/attach_probe.c | 44 +++++++++++++++++++ .../selftests/bpf/progs/kprobe_write_ctx.c | 6 +++ 2 files changed, 50 insertions(+) diff --git a/tools/testing/selftests/bpf/prog_tests/attach_probe.c b/tools/testing/selftests/bpf/prog_tests/attach_probe.c index 9e77e5da7097..a9444db92246 100644 --- a/tools/testing/selftests/bpf/prog_tests/attach_probe.c +++ b/tools/testing/selftests/bpf/prog_tests/attach_probe.c @@ -220,11 +220,53 @@ static void test_attach_kprobe_write_ctx(void) kprobe_write_ctx__destroy(skel); } + +static void test_kprobe_write_ctx_prog_array_compat(void) +{ + __u32 key = 0, read_prog_fd, write_prog_fd; + struct kprobe_write_ctx *skel = NULL; + int map_fd = -1, err; + + skel = kprobe_write_ctx__open_and_load(); + if (!ASSERT_OK_PTR(skel, "kprobe_write_ctx__open_and_load")) + return; + + map_fd = bpf_map_create(BPF_MAP_TYPE_PROG_ARRAY, NULL, sizeof(key), + sizeof(__u32), 1, NULL); + if (!ASSERT_GE(map_fd, 0, "bpf_map_create")) + goto cleanup; + + read_prog_fd = bpf_program__fd(skel->progs.kprobe_read_ctx); + write_prog_fd = bpf_program__fd(skel->progs.kprobe_write_ctx); + if (!ASSERT_GE(read_prog_fd, 0, "read_prog_fd")) + goto cleanup; + if (!ASSERT_GE(write_prog_fd, 0, "write_prog_fd")) + goto cleanup; + + err = bpf_map_update_elem(map_fd, &key, &read_prog_fd, BPF_ANY); + if (!ASSERT_OK(err, "bpf_map_update_elem success")) + goto cleanup; + + err = bpf_map_update_elem(map_fd, &key, &write_prog_fd, BPF_ANY); + if (!ASSERT_ERR(err, "bpf_map_update_elem failure")) + goto cleanup; + ASSERT_EQ(errno, EINVAL, "bpf_map_update_elem errno"); + +cleanup: + if (map_fd >= 0) + close(map_fd); + kprobe_write_ctx__destroy(skel); +} #else static void test_attach_kprobe_write_ctx(void) { test__skip(); } + +static void test_kprobe_write_ctx_prog_array_compat(void) +{ + test__skip(); +} #endif static void test_attach_probe_auto(struct test_attach_probe *skel) @@ -434,6 +476,8 @@ void test_attach_probe(void) test_attach_kprobe_long_event_name(); if (test__start_subtest("kprobe-write-ctx")) test_attach_kprobe_write_ctx(); + if (test__start_subtest("kprobe-write-ctx-prog-array-compat")) + test_kprobe_write_ctx_prog_array_compat(); cleanup: test_attach_probe__destroy(skel); diff --git a/tools/testing/selftests/bpf/progs/kprobe_write_ctx.c b/tools/testing/selftests/bpf/progs/kprobe_write_ctx.c index f77aef0474d3..09e0c487bde5 100644 --- a/tools/testing/selftests/bpf/progs/kprobe_write_ctx.c +++ b/tools/testing/selftests/bpf/progs/kprobe_write_ctx.c @@ -13,6 +13,12 @@ int kprobe_write_ctx(struct pt_regs *ctx) return 0; } +SEC("kprobe") +int kprobe_read_ctx(struct pt_regs *ctx) +{ + return 0; +} + SEC("kprobe.multi") int kprobe_multi_write_ctx(struct pt_regs *ctx) { -- 2.52.0 Add prog_array coverage for tracing programs that differ in bpf_get_func_ip() dependency. The test verifies one-way enforcement: !call_get_func_ip programs cannot tail-call call_get_func_ip programs. Assisted-by: Codex:gpt-5.3-codex Signed-off-by: Leon Hwang --- .../bpf/prog_tests/get_func_ip_test.c | 46 +++++++++++++++++++ .../selftests/bpf/progs/get_func_ip_test.c | 6 +++ 2 files changed, 52 insertions(+) diff --git a/tools/testing/selftests/bpf/prog_tests/get_func_ip_test.c b/tools/testing/selftests/bpf/prog_tests/get_func_ip_test.c index 7772a0f288d3..670457d4e159 100644 --- a/tools/testing/selftests/bpf/prog_tests/get_func_ip_test.c +++ b/tools/testing/selftests/bpf/prog_tests/get_func_ip_test.c @@ -139,3 +139,49 @@ void test_get_func_ip_test(void) test_function_entry(); test_function_body(); } + +static void test_prog_array_call_get_func_ip_compat(void) +{ + __u32 key = 0, with_get_func_ip_fd, without_get_func_ip_fd; + struct get_func_ip_test *skel = NULL; + int map_fd = -1, err; + + skel = get_func_ip_test__open(); + if (!ASSERT_OK_PTR(skel, "get_func_ip_test__open")) + return; + + err = get_func_ip_test__load(skel); + if (!ASSERT_OK(err, "get_func_ip_test__load")) + goto cleanup; + + with_get_func_ip_fd = bpf_program__fd(skel->progs.test1); + without_get_func_ip_fd = bpf_program__fd(skel->progs.test1_dummy); + if (!ASSERT_GE(with_get_func_ip_fd, 0, "with_get_func_ip_fd")) + goto cleanup; + if (!ASSERT_GE(without_get_func_ip_fd, 0, "without_get_func_ip_fd")) + goto cleanup; + + map_fd = bpf_map_create(BPF_MAP_TYPE_PROG_ARRAY, NULL, sizeof(key), + sizeof(__u32), 1, NULL); + if (!ASSERT_GE(map_fd, 0, "bpf_map_create")) + goto cleanup; + + err = bpf_map_update_elem(map_fd, &key, &without_get_func_ip_fd, BPF_ANY); + if (!ASSERT_OK(err, "bpf_map_update_elem success")) + goto cleanup; + + err = bpf_map_update_elem(map_fd, &key, &with_get_func_ip_fd, BPF_ANY); + if (!ASSERT_ERR(err, "bpf_map_update_elem failure")) + goto cleanup; + ASSERT_EQ(errno, EINVAL, "bpf_map_update_elem errno"); + +cleanup: + if (map_fd >= 0) + close(map_fd); + get_func_ip_test__destroy(skel); +} + +void test_get_func_ip_test_prog_map_compatible(void) +{ + test_prog_array_call_get_func_ip_compat(); +} diff --git a/tools/testing/selftests/bpf/progs/get_func_ip_test.c b/tools/testing/selftests/bpf/progs/get_func_ip_test.c index 45eaa54d1ac7..6a7d306af96e 100644 --- a/tools/testing/selftests/bpf/progs/get_func_ip_test.c +++ b/tools/testing/selftests/bpf/progs/get_func_ip_test.c @@ -32,6 +32,12 @@ int BPF_PROG(test1, int a) return 0; } +SEC("fentry/bpf_fentry_test1") +int BPF_PROG(test1_dummy, int a) +{ + return 0; +} + __u64 test2_result = 0; SEC("fexit/bpf_fentry_test2") int BPF_PROG(test2, int a) -- 2.52.0 Add prog_array coverage for tracing programs that differ in bpf_session_cookie() / bpf_session_is_return() usage. The test verifies one-way enforcement: !call_session_* programs cannot tail-call call_session_* programs. Assisted-by: Codex:gpt-5.3-codex Signed-off-by: Leon Hwang --- .../selftests/bpf/prog_tests/fsession_test.c | 47 +++++++++++++++++++ .../selftests/bpf/progs/fsession_test.c | 6 +++ 2 files changed, 53 insertions(+) diff --git a/tools/testing/selftests/bpf/prog_tests/fsession_test.c b/tools/testing/selftests/bpf/prog_tests/fsession_test.c index a299aeb8cc2e..304986544a8d 100644 --- a/tools/testing/selftests/bpf/prog_tests/fsession_test.c +++ b/tools/testing/selftests/bpf/prog_tests/fsession_test.c @@ -129,6 +129,51 @@ static void test_fsession_cookie(void) fsession_test__destroy(skel); } +static void test_fsession_prog_array_session_kfunc_compat(void) +{ + struct fsession_test *skel = NULL; + __u32 key = 0, session_kfunc_prog_fd, no_session_kfunc_prog_fd; + int map_fd = -1, err; + + skel = fsession_test__open(); + if (!ASSERT_OK_PTR(skel, "fsession_test__open")) + goto cleanup; + + err = fsession_test__load(skel); + if (err == -EOPNOTSUPP) { + test__skip(); + goto cleanup; + } + if (!ASSERT_OK(err, "fsession_test__load")) + goto cleanup; + + session_kfunc_prog_fd = bpf_program__fd(skel->progs.test9); + no_session_kfunc_prog_fd = bpf_program__fd(skel->progs.test12); + if (!ASSERT_GE(session_kfunc_prog_fd, 0, "session_kfunc_prog_fd")) + goto cleanup; + if (!ASSERT_GE(no_session_kfunc_prog_fd, 0, "no_session_kfunc_prog_fd")) + goto cleanup; + + map_fd = bpf_map_create(BPF_MAP_TYPE_PROG_ARRAY, NULL, sizeof(key), + sizeof(__u32), 1, NULL); + if (!ASSERT_GE(map_fd, 0, "bpf_map_create")) + goto cleanup; + + err = bpf_map_update_elem(map_fd, &key, &no_session_kfunc_prog_fd, BPF_ANY); + if (!ASSERT_OK(err, "bpf_map_update_elem success")) + goto cleanup; + + err = bpf_map_update_elem(map_fd, &key, &session_kfunc_prog_fd, BPF_ANY); + if (!ASSERT_ERR(err, "bpf_map_update_elem failure")) + goto cleanup; + ASSERT_EQ(errno, EINVAL, "bpf_map_update_elem errno"); + +cleanup: + if (map_fd >= 0) + close(map_fd); + fsession_test__destroy(skel); +} + void test_fsession_test(void) { if (test__start_subtest("fsession_test")) @@ -137,4 +182,6 @@ void test_fsession_test(void) test_fsession_reattach(); if (test__start_subtest("fsession_cookie")) test_fsession_cookie(); + if (test__start_subtest("fsession_prog_array_session_kfunc_compat")) + test_fsession_prog_array_session_kfunc_compat(); } diff --git a/tools/testing/selftests/bpf/progs/fsession_test.c b/tools/testing/selftests/bpf/progs/fsession_test.c index 86e8a2fe467e..1e6be28bcc3d 100644 --- a/tools/testing/selftests/bpf/progs/fsession_test.c +++ b/tools/testing/selftests/bpf/progs/fsession_test.c @@ -177,3 +177,9 @@ int BPF_PROG(test11, int a) test11_result = a == 1; return 0; } + +SEC("fsession/bpf_fentry_test1") +int BPF_PROG(test12, int a, int ret) +{ + return 0; +} -- 2.52.0