Subject: [PATCH bpf-next 5/8] bpf: Disallow !call_session_is_return progs tail-calling call_session_is_return progs

From: Leon Hwang <[email-masked]>
Date: Tue, 24 Feb 2026 23:40:21 +0800

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 <[email-masked]>
---
 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