Skip to contentSkip to conversation

Fix sleepable context tracking for async callbacks

PATCH Applied Kumar Kartikeya Dwivedi 5 participants 10 messages Oct 7, 2025 – Oct 13, 2025

Subsystems (4)
lore
Share & tools
lore

Kumar Kartikeya Dwivedi

author maintainer
#0 lore raw

v2 cover Fix sleepable context tracking for async callbacks

Currently, asynchronous execution primitives set up their callback execution simulation using push_async_cb, which will end up inheriting the sleepable or non-sleepable bit from the program triggering the simulation of the callback. This is incorrect, as the actual execution context of the asynchronous callback has nothing to do with the program arming its execution.

This set fixes this oversight, and supplies a few test cases ensuring the correct behavior is tested across different types of primitives (i.e. timer, wq, task_work).

While looking at this bug, it was noticed that the GFP flag setting logic for storage_get helpers is also broken, hence fix it while we are at it.

PSA: These fixes and unit tests were primarily produced by prompting an AI assistant (Claude), and then modified in minor ways, in an exercise to understand how useful it can be at general kernel development tasks.

Changelog:
----------
v1 -> v2
v1: https://lore.kernel.org/bpf/[email protected]

* Squash fix for GFP flags into 1st commit. (Eduard)
* Add a commit refactoring func_atomic to non_sleepable, make it generic, also set for kfuncs in addition to helpers. (Eduard)
* Leave selftest as-is, coverage for global subprogs calling sleepable kfuncs or helpers is provided in rcu_read_lock.c.

Kumar Kartikeya Dwivedi (3):
bpf: Fix sleepable context for async callbacks
bpf: Refactor storage_get_func_atomic to generic non_sleepable flag selftests/bpf: Add tests for async cb context

 include/linux/bpf_verifier.h                  |   2 +-
 kernel/bpf/verifier.c                         |  54 ++++--
 .../selftests/bpf/prog_tests/verifier.c       |   2 +
 .../bpf/progs/verifier_async_cb_context.c     | 181 ++++++++++++++++++
 4 files changed, 221 insertions(+), 18 deletions(-)
 create mode 100644 tools/testing/selftests/bpf/progs/verifier_async_cb_context.c

base-commit: 0db4941d9dae159d887e7e2eac7e54e60c3aac87

Signature · 1 line
2.51.0
author maintainer
#1 lore raw

PATCH v2 1/3 bpf: Fix sleepable context for async callbacks

Fix the BPF verifier to correctly determine the sleepable context of async callbacks based on the async primitive type rather than the arming program's context.

The bug is in in_sleepable() which uses OR logic to check if the current execution context is sleepable. When a sleepable program arms a timer callback, the callback's state correctly has in_sleepable=false, but in_sleepable() would still return true due to env->prog->sleepable being true. This incorrectly allows sleepable helpers like bpf_copy_from_user() inside timer callbacks when armed from sleepable programs, even though timer callbacks always execute in non-sleepable context.

Fix in_sleepable() to rely solely on env->cur_state->in_sleepable, and initialize state->in_sleepable to env->prog->sleepable in do_check_common() for the main program entry. This ensures the sleepable context is properly tracked per verification state rather than being overridden by the program's sleepability.

The env->cur_state NULL check in in_sleepable() was only needed for do_misc_fixups() which runs after verification when env->cur_state is set to NULL. Update do_misc_fixups() to use env->prog->sleepable directly for the storage_get_function check, and remove the redundant NULL check from in_sleepable().

Introduce is_async_cb_sleepable() helper to explicitly determine async callback sleepability based on the primitive type:
- bpf_timer callbacks are never sleepable
- bpf_wq and bpf_task_work callbacks are always sleepable

Add verifier_bug() check to catch unhandled async callback types, ensuring future additions cannot be silently mishandled. Move the is_task_work_add_kfunc() forward declaration to the top alongside other callback-related helpers. We update push_async_cb() to adjust to the new changes.

At the same time, while simplifying in_sleepable(), we notice a problem in do_misc_fixups. Fix storage_get helpers to use GFP_ATOMIC when called from non-sleepable contexts within sleepable programs, such as bpf_timer callbacks.

Currently, the check in do_misc_fixups assumes that env->prog->sleepable, previously in_sleepable(env) which only resolved to this check before last commit, holds across the program's execution, but that is not true. Instead, the func_atomic bit must be set whenever we see the function being called in an atomic context. Previously, this is being done when the helper is invoked in atomic contexts in sleepable programs, we can simply just set the value to true without doing an in_sleepable() check.

We must also do a standalone in_sleepable() check to handle cases where the async callback itself is armed from a sleepable program, but is itself non-sleepable (e.g., timer callback) and invokes such a helper, thus needing the func_atomic bit to be true for the said call.

Adjust do_misc_fixups() to drop any checks regarding sleepable nature of the program, and just depend on the func_atomic bit to decide which GFP flag to pass.

Fixes 81f1d7a583fa ("bpf: wq: add bpf_wq_set_callback_impl")
Fixes b00fa38a9c1c ("bpf: Enable non-atomic allocations in local storage")
---
 kernel/bpf/verifier.c | 41 ++++++++++++++++++++++++++++++-----------
 1 file changed, 30 insertions(+), 11 deletions(-)
kernel/bpf/verifier.c +30 -11
index ff40e5e65c43..32123c4b041a 100644
@@ -515,6 +515,7 @@ static bool is_callback_calling_kfunc(u32 btf_id);
 static bool is_bpf_throw_kfunc(struct bpf_insn *insn);
 
 static bool is_bpf_wq_set_callback_impl_kfunc(u32 btf_id);
+static bool is_task_work_add_kfunc(u32 func_id);
 
 static bool is_sync_callback_calling_function(enum bpf_func_id func_id)
 {
@@ -547,6 +548,21 @@ static bool is_async_callback_calling_insn(struct bpf_insn *insn)
 	       (bpf_pseudo_kfunc_call(insn) && is_async_callback_calling_kfunc(insn->imm));
 }
 
+static bool is_async_cb_sleepable(struct bpf_verifier_env *env, struct bpf_insn *insn)
+{
+	/* bpf_timer callbacks are never sleepable. */
+	if (bpf_helper_call(insn) && insn->imm == BPF_FUNC_timer_set_callback)
+		return false;
+
+	/* bpf_wq and bpf_task_work callbacks are always sleepable. */
+	if (bpf_pseudo_kfunc_call(insn) && insn->off == 0 &&
+	    (is_bpf_wq_set_callback_impl_kfunc(insn->imm) || is_task_work_add_kfunc(insn->imm)))
+		return true;
+
+	verifier_bug(env, "unhandled async callback in is_async_cb_sleepable");
+	return false;
+}
+
 static bool is_may_goto_insn(struct bpf_insn *insn)
 {
 	return insn->code == (BPF_JMP | BPF_JCOND) && insn->src_reg == BPF_MAY_GOTO;
@@ -5826,8 +5842,7 @@ static int map_kptr_match_type(struct bpf_verifier_env *env,
 
 static bool in_sleepable(struct bpf_verifier_env *env)
 {
-	return env->prog->sleepable ||
-	       (env->cur_state && env->cur_state->in_sleepable);
+	return env->cur_state->in_sleepable;
 }
 
 /* The non-sleepable programs and sleepable programs with explicit bpf_rcu_read_lock()
@@ -10366,8 +10381,6 @@ typedef int (*set_callee_state_fn)(struct bpf_verifier_env *env,
 				   struct bpf_func_state *callee,
 				   int insn_idx);
 
-static bool is_task_work_add_kfunc(u32 func_id);
-
 static int set_callee_state(struct bpf_verifier_env *env,
 			    struct bpf_func_state *caller,
 			    struct bpf_func_state *callee, int insn_idx);
@@ -10586,8 +10599,7 @@ static int push_callback_call(struct bpf_verifier_env *env, struct bpf_insn *ins
 		env->subprog_info[subprog].is_async_cb = true;
 		async_cb = push_async_cb(env, env->subprog_info[subprog].start,
 					 insn_idx, subprog,
-					 is_bpf_wq_set_callback_impl_kfunc(insn->imm) ||
-					 is_task_work_add_kfunc(insn->imm));
+					 is_async_cb_sleepable(env, insn));
 		if (!async_cb)
 			return -EFAULT;
 		callee = async_cb->frame[0];
@@ -11426,7 +11438,7 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
 			return -EINVAL;
 		}
 
-		if (in_sleepable(env) && is_storage_get_function(func_id))
+		if (is_storage_get_function(func_id))
 			env->insn_aux_data[insn_idx].storage_get_func_atomic = true;
 	}
 
@@ -11437,7 +11449,7 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
 			return -EINVAL;
 		}
 
-		if (in_sleepable(env) && is_storage_get_function(func_id))
+		if (is_storage_get_function(func_id))
 			env->insn_aux_data[insn_idx].storage_get_func_atomic = true;
 	}
 
@@ -11448,10 +11460,17 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
 			return -EINVAL;
 		}
 
-		if (in_sleepable(env) && is_storage_get_function(func_id))
+		if (is_storage_get_function(func_id))
 			env->insn_aux_data[insn_idx].storage_get_func_atomic = true;
 	}
 
+	/*
+	 * Non-sleepable contexts in sleepable programs (e.g., timer callbacks)
+	 * are atomic and must use GFP_ATOMIC for storage_get helpers.
+	 */
+	if (!in_sleepable(env) && is_storage_get_function(func_id))
+		env->insn_aux_data[insn_idx].storage_get_func_atomic = true;
+
 	meta.func_id = func_id;
 	/* check args */
 	for (i = 0; i < MAX_BPF_FUNC_REG_ARGS; i++) {
@@ -22483,8 +22502,7 @@ static int do_misc_fixups(struct bpf_verifier_env *env)
 		}
 
 		if (is_storage_get_function(insn->imm)) {
-			if (!in_sleepable(env) ||
-			    env->insn_aux_data[i + delta].storage_get_func_atomic)
+			if (env->insn_aux_data[i + delta].storage_get_func_atomic)
 				insn_buf[0] = BPF_MOV64_IMM(BPF_REG_5, (__force __s32)GFP_ATOMIC);
 			else
 				insn_buf[0] = BPF_MOV64_IMM(BPF_REG_5, (__force __s32)GFP_KERNEL);
@@ -23154,6 +23172,7 @@ static int do_check_common(struct bpf_verifier_env *env, int subprog)
 	state->curframe = 0;
 	state->speculative = false;
 	state->branches = 1;
+	state->in_sleepable = env->prog->sleepable;
 	state->frame[0] = kzalloc(sizeof(struct bpf_func_state), GFP_KERNEL_ACCOUNT);
 	if (!state->frame[0]) {
 		kfree(state);
Signature · 1 line
2.51.0
author maintainer
#2 lore raw

PATCH v2 2/3 bpf: Refactor storage_get_func_atomic to generic non_sleepable flag

In reply to Kumar Kartikeya Dwivedi

Rename the storage_get_func_atomic flag to a more generic non_sleepable flag that tracks whether a helper or kfunc may be called from a non-sleepable context. This makes the flag more broadly applicable beyond just storage_get helpers. See [0] for more context.

The flag is now set unconditionally for all helpers and kfuncs when:
- RCU critical section is active.
- Preemption is disabled.
- IRQs are disabled.
- In a non-sleepable context within a sleepable program (e.g., timer callbacks), which is indicated by !in_sleepable().

Previously, the flag was only set for storage_get helpers in these contexts. With this change, it can be used by any code that needs to differentiate between sleepable and non-sleepable contexts at the per-instruction level.

The existing usage in do_misc_fixups() for storage_get helpers is preserved by checking is_storage_get_function() before using the flag.

[0]: https://lore.kernel.org/bpf/CAP01T76cbaNi4p-y8E0sjE2NXSra2S=Uja8G4hSQDu_SbXxREQ@mail.gmail.com

---
 include/linux/bpf_verifier.h |  2 +-
 kernel/bpf/verifier.c        | 33 +++++++++++++++++----------------
 2 files changed, 18 insertions(+), 17 deletions(-)
include/linux/bpf_verifier.h +1 -1
index 4c497e839526..b57222a25a4a 100644
@@ -548,7 +548,7 @@ struct bpf_insn_aux_data {
 	bool nospec_result; /* result is unsafe under speculation, nospec must follow */
 	bool zext_dst; /* this insn zero extends dst reg */
 	bool needs_zext; /* alu op needs to clear upper bits */
-	bool storage_get_func_atomic; /* bpf_*_storage_get() with atomic memory alloc */
+	bool non_sleepable; /* helper/kfunc may be called from non-sleepable context */
 	bool is_iter_next; /* bpf_iter_<type>_next() kfunc call */
 	bool call_with_percpu_alloc_ptr; /* {this,per}_cpu_ptr() with prog percpu alloc */
 	u8 alu_state; /* used in combination with alu_limit */
kernel/bpf/verifier.c +17 -16
index 32123c4b041a..85a953124412 100644
@@ -11371,6 +11371,15 @@ static int get_helper_proto(struct bpf_verifier_env *env, int func_id,
 	return *ptr && (*ptr)->func ? 0 : -EINVAL;
 }
 
+/* Check if we're in a sleepable context. */
+static inline bool in_sleepable_context(struct bpf_verifier_env *env)
+{
+	return !env->cur_state->active_rcu_lock &&
+	       !env->cur_state->active_preempt_locks &&
+	       !env->cur_state->active_irq_id &&
+	       in_sleepable(env);
+}
+
 static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
 			     int *insn_idx_p)
 {
@@ -11437,9 +11446,6 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
 				func_id_name(func_id), func_id);
 			return -EINVAL;
 		}
-
-		if (is_storage_get_function(func_id))
-			env->insn_aux_data[insn_idx].storage_get_func_atomic = true;
 	}
 
 	if (env->cur_state->active_preempt_locks) {
@@ -11448,9 +11454,6 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
 				func_id_name(func_id), func_id);
 			return -EINVAL;
 		}
-
-		if (is_storage_get_function(func_id))
-			env->insn_aux_data[insn_idx].storage_get_func_atomic = true;
 	}
 
 	if (env->cur_state->active_irq_id) {
@@ -11459,17 +11462,11 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
 				func_id_name(func_id), func_id);
 			return -EINVAL;
 		}
-
-		if (is_storage_get_function(func_id))
-			env->insn_aux_data[insn_idx].storage_get_func_atomic = true;
 	}
 
-	/*
-	 * Non-sleepable contexts in sleepable programs (e.g., timer callbacks)
-	 * are atomic and must use GFP_ATOMIC for storage_get helpers.
-	 */
-	if (!in_sleepable(env) && is_storage_get_function(func_id))
-		env->insn_aux_data[insn_idx].storage_get_func_atomic = true;
+	/* Track non-sleepable context for helpers. */
+	if (!in_sleepable_context(env))
+		env->insn_aux_data[insn_idx].non_sleepable = true;
 
 	meta.func_id = func_id;
 	/* check args */
@@ -13880,6 +13877,10 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
 		return -EACCES;
 	}
 
+	/* Track non-sleepable context for kfuncs, same as for helpers. */
+	if (!in_sleepable_context(env))
+		insn_aux->non_sleepable = true;
+
 	/* Check the arguments */
 	err = check_kfunc_args(env, &meta, insn_idx);
 	if (err < 0)
@@ -22502,7 +22503,7 @@ static int do_misc_fixups(struct bpf_verifier_env *env)
 		}
 
 		if (is_storage_get_function(insn->imm)) {
-			if (env->insn_aux_data[i + delta].storage_get_func_atomic)
+			if (env->insn_aux_data[i + delta].non_sleepable)
 				insn_buf[0] = BPF_MOV64_IMM(BPF_REG_5, (__force __s32)GFP_ATOMIC);
 			else
 				insn_buf[0] = BPF_MOV64_IMM(BPF_REG_5, (__force __s32)GFP_KERNEL);
Signature · 1 line
2.51.0
maintainer Acked-by on v2 2/3
#3 lore raw

Re: [PATCH bpf-next v2 2/3] bpf: Refactor storage_get_func_atomic to generic non_sleepable flag

Quoted 17 lines · Kumar Kartikeya Dwivedi

Rename the storage_get_func_atomic flag to a more generic non_sleepable flag that tracks whether a helper or kfunc may be called from a non-sleepable context. This makes the flag more broadly applicable beyond just storage_get helpers. See [0] for more context.

The flag is now set unconditionally for all helpers and kfuncs when:
- RCU critical section is active.
- Preemption is disabled.
- IRQs are disabled.
- In a non-sleepable context within a sleepable program (e.g., timer callbacks), which is indicated by !in_sleepable().

Previously, the flag was only set for storage_get helpers in these contexts. With this change, it can be used by any code that needs to differentiate between sleepable and non-sleepable contexts at the per-instruction level.

Quoted 8 lines

The existing usage in do_misc_fixups() for storage_get helpers is preserved by checking is_storage_get_function() before using the flag.

[0]: https://lore.kernel.org/bpf/CAP01T76cbaNi4p-y8E0sjE2NXSra2S=Uja8G4hSQDu_SbXxREQ@mail.gmail.com

---

[...]

Acked-by on v2 2/3
#4 lore raw

Re: [PATCH bpf-next v2 2/3] bpf: Refactor storage_get_func_atomic to generic non_sleepable flag

In reply to Kumar Kartikeya Dwivedi
Quoted 20 lines · Kumar Kartikeya Dwivedi

Rename the storage_get_func_atomic flag to a more generic non_sleepable flag that tracks whether a helper or kfunc may be called from a non-sleepable context. This makes the flag more broadly applicable beyond just storage_get helpers. See [0] for more context.

The flag is now set unconditionally for all helpers and kfuncs when:
- RCU critical section is active.
- Preemption is disabled.
- IRQs are disabled.
- In a non-sleepable context within a sleepable program (e.g., timer callbacks), which is indicated by !in_sleepable().

Previously, the flag was only set for storage_get helpers in these contexts. With this change, it can be used by any code that needs to differentiate between sleepable and non-sleepable contexts at the per-instruction level.

The existing usage in do_misc_fixups() for storage_get helpers is preserved by checking is_storage_get_function() before using the flag.

Quoted 10 lines
   [0]: https://lore.kernel.org/bpf/CAP01T76cbaNi4p-y8E0sjE2NXSra2S=Uja8G4hSQDu_SbXxREQ@mail.gmail.com
---
  include/linux/bpf_verifier.h |  2 +-
  kernel/bpf/verifier.c        | 33 +++++++++++++++++----------------
  2 files changed, 18 insertions(+), 17 deletions(-)
author maintainer
#5 lore raw

PATCH v2 3/3 selftests/bpf: Add tests for async cb context

In reply to Kumar Kartikeya Dwivedi

Add tests to verify that async callback's sleepable attribute is correctly determined by the callback type, not the arming program's context, reflecting its true execution context.

Introduce verifier_async_cb_context.c with tests for all three async callback primitives: bpf_timer, bpf_wq, and bpf_task_work. Each primitive is tested when armed from both sleepable (lsm.s/file_open) and non-sleepable (fentry) programs.

Test coverage:
- bpf_timer callbacks: Verify they are never sleepable, even when armed from sleepable programs. Both tests should fail when attempting to use sleepable helper bpf_copy_from_user() in the callback.

- bpf_wq callbacks: Verify they are always sleepable, even when armed from non-sleepable programs. Both tests should succeed when using sleepable helpers in the callback.

- bpf_task_work callbacks: Verify they are always sleepable, even when armed from non-sleepable programs. Both tests should succeed when using sleepable helpers in the callback.

---
 .../selftests/bpf/prog_tests/verifier.c       |   2 +
 .../bpf/progs/verifier_async_cb_context.c     | 181 ++++++++++++++++++
 2 files changed, 183 insertions(+)
 create mode 100644 tools/testing/selftests/bpf/progs/verifier_async_cb_context.c
tools/testing/selftests/bpf/prog_tests/verifier.c +2 -0
index 28e81161e6fc..c0e8ffdaa484 100644
@@ -7,6 +7,7 @@
 #include "verifier_arena.skel.h"
 #include "verifier_arena_large.skel.h"
 #include "verifier_array_access.skel.h"
+#include "verifier_async_cb_context.skel.h"
 #include "verifier_basic_stack.skel.h"
 #include "verifier_bitfield_write.skel.h"
 #include "verifier_bounds.skel.h"
@@ -280,6 +281,7 @@ void test_verifier_array_access(void)
 		      verifier_array_access__elf_bytes,
 		      init_array_access_maps);
 }
+void test_verifier_async_cb_context(void)    { RUN(verifier_async_cb_context); }
 
 static int init_value_ptr_arith_maps(struct bpf_object *obj)
 {
tools/testing/selftests/bpf/progs/verifier_async_cb_context.c new file +181 -0

181 lines not on this page · raw message

Signature · 1 line
2.51.0
#6 lore raw

Re: [PATCH bpf-next v2 3/3] selftests/bpf: Add tests for async cb context

Quoted 110 lines · Kumar Kartikeya Dwivedi

Add tests to verify that async callback's sleepable attribute is correctly determined by the callback type, not the arming program's context, reflecting its true execution context.

Introduce verifier_async_cb_context.c with tests for all three async callback primitives: bpf_timer, bpf_wq, and bpf_task_work. Each primitive is tested when armed from both sleepable (lsm.s/file_open) and non-sleepable (fentry) programs.

Test coverage:
- bpf_timer callbacks: Verify they are never sleepable, even when armed from sleepable programs. Both tests should fail when attempting to use sleepable helper bpf_copy_from_user() in the callback.

- bpf_wq callbacks: Verify they are always sleepable, even when armed from non-sleepable programs. Both tests should succeed when using sleepable helpers in the callback.

- bpf_task_work callbacks: Verify they are always sleepable, even when armed from non-sleepable programs. Both tests should succeed when using sleepable helpers in the callback.

---
 .../selftests/bpf/prog_tests/verifier.c       |   2 +
 .../bpf/progs/verifier_async_cb_context.c     | 181 ++++++++++++++++++
 2 files changed, 183 insertions(+)
 create mode 100644 tools/testing/selftests/bpf/progs/verifier_async_cb_context.c
tools/testing/selftests/bpf/prog_tests/verifier.c +2 -0
index 28e81161e6fc..c0e8ffdaa484 100644
@@ -7,6 +7,7 @@
 #include "verifier_arena.skel.h"
 #include "verifier_arena_large.skel.h"
 #include "verifier_array_access.skel.h"
+#include "verifier_async_cb_context.skel.h"
 #include "verifier_basic_stack.skel.h"
 #include "verifier_bitfield_write.skel.h"
 #include "verifier_bounds.skel.h"
@@ -280,6 +281,7 @@ void test_verifier_array_access(void)
                      verifier_array_access__elf_bytes,
                      init_array_access_maps);
 }
+void test_verifier_async_cb_context(void)    { RUN(verifier_async_cb_context); }
 
 static int init_value_ptr_arith_maps(struct bpf_object *obj)
 {
tools/testing/selftests/bpf/progs/verifier_async_cb_context.c new file +53 -0
new file mode 100644
index 000000000000..96ff6749168b
@@ -0,0 +1,181 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "bpf_misc.h"
+#include "bpf_experimental.h"
+
+char _license[] SEC("license") = "GPL";
+
+/* Timer tests */
+
+struct timer_elem {
+       struct bpf_timer t;
+};
+
+struct {
+       __uint(type, BPF_MAP_TYPE_ARRAY);
+       __uint(max_entries, 1);
+       __type(key, int);
+       __type(value, struct timer_elem);
+} timer_map SEC(".maps");
+
+static int timer_cb(void *map, int *key, struct bpf_timer *timer)
+{
+       u32 data;
+       /* Timer callbacks are never sleepable, even from non-sleepable programs */
+       bpf_copy_from_user(&data, sizeof(data), NULL);
+       return 0;
+}
+
+SEC("fentry/bpf_fentry_test1")
+__failure __msg("helper call might sleep in a non-sleepable prog")
+int timer_non_sleepable_prog(void *ctx)
+{
+       struct timer_elem *val;
+       int key = 0;
+
+       val = bpf_map_lookup_elem(&timer_map, &key);
+       if (!val)
+               return 0;
+
+       bpf_timer_init(&val->t, &timer_map, 0);
+       bpf_timer_set_callback(&val->t, timer_cb);
+       return 0;
+}
+
+SEC("lsm.s/file_open")
+__failure __msg("helper call might sleep in a non-sleepable prog")
+int timer_sleepable_prog(void *ctx)
+{
+       struct timer_elem *val;

tools/testing/selftests/bpf/progs/verifier_async_cb_context.c line 63

Quoted 10 lines
tools/testing/selftests/bpf/progs/verifier_async_cb_context.c new file +10 -0
+       int key = 0;
+
+       val = bpf_map_lookup_elem(&timer_map, &key);
+       if (!val)
+               return 0;
+
+       bpf_timer_init(&val->t, &timer_map, 0);
+       bpf_timer_set_callback(&val->t, timer_cb);
+       return 0;
+}

can you please also add (as a follow up is fine) this case:

static noinline int mixed_mode_subprog(void)

{
    /* use one of is_storage_get_function() helpers here */
}

static int timer_cb(void *map, int *key, struct bpf_timer *timer)

{
    mixed_mode_subprog();
    return 0;
}

SEC("lsm.s/file_open") /* sleepable entry program */

int sleepable(void *ctx) {
    ...
    bpf_timer_set_callback(&val->t, timer_cb);

    /* !!! important to call it here */
    mixed_mode_subprog();
}

Idea being that we have a subprog that is called in both sleepable and non-sleepable modes. It should be rejected.

Let's do the same the other way, when the entry program is non-sleepable but an async callback is sleepable, ok?

Note also that noinline part is important, we need to make sure it doesn't get inline anywhere. If necessary, we can do __weak __hidden to make this happen.

[...]

author maintainer
#7 lore raw

Re: [PATCH bpf-next v2 3/3] selftests/bpf: Add tests for async cb context

Quoted 148 lines · Andrii Nakryiko
Quoted 110 lines · Kumar Kartikeya Dwivedi

Add tests to verify that async callback's sleepable attribute is correctly determined by the callback type, not the arming program's context, reflecting its true execution context.

Introduce verifier_async_cb_context.c with tests for all three async callback primitives: bpf_timer, bpf_wq, and bpf_task_work. Each primitive is tested when armed from both sleepable (lsm.s/file_open) and non-sleepable (fentry) programs.

Test coverage:
- bpf_timer callbacks: Verify they are never sleepable, even when armed from sleepable programs. Both tests should fail when attempting to use sleepable helper bpf_copy_from_user() in the callback.

- bpf_wq callbacks: Verify they are always sleepable, even when armed from non-sleepable programs. Both tests should succeed when using sleepable helpers in the callback.

- bpf_task_work callbacks: Verify they are always sleepable, even when armed from non-sleepable programs. Both tests should succeed when using sleepable helpers in the callback.

---
 .../selftests/bpf/prog_tests/verifier.c       |   2 +
 .../bpf/progs/verifier_async_cb_context.c     | 181 ++++++++++++++++++
 2 files changed, 183 insertions(+)
 create mode 100644 tools/testing/selftests/bpf/progs/verifier_async_cb_context.c
tools/testing/selftests/bpf/prog_tests/verifier.c +2 -0
index 28e81161e6fc..c0e8ffdaa484 100644
@@ -7,6 +7,7 @@
 #include "verifier_arena.skel.h"
 #include "verifier_arena_large.skel.h"
 #include "verifier_array_access.skel.h"
+#include "verifier_async_cb_context.skel.h"
 #include "verifier_basic_stack.skel.h"
 #include "verifier_bitfield_write.skel.h"
 #include "verifier_bounds.skel.h"
@@ -280,6 +281,7 @@ void test_verifier_array_access(void)
                      verifier_array_access__elf_bytes,
                      init_array_access_maps);
 }
+void test_verifier_async_cb_context(void)    { RUN(verifier_async_cb_context); }
 
 static int init_value_ptr_arith_maps(struct bpf_object *obj)
 {
tools/testing/selftests/bpf/progs/verifier_async_cb_context.c new file +53 -0
new file mode 100644
index 000000000000..96ff6749168b
@@ -0,0 +1,181 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "bpf_misc.h"
+#include "bpf_experimental.h"
+
+char _license[] SEC("license") = "GPL";
+
+/* Timer tests */
+
+struct timer_elem {
+       struct bpf_timer t;
+};
+
+struct {
+       __uint(type, BPF_MAP_TYPE_ARRAY);
+       __uint(max_entries, 1);
+       __type(key, int);
+       __type(value, struct timer_elem);
+} timer_map SEC(".maps");
+
+static int timer_cb(void *map, int *key, struct bpf_timer *timer)
+{
+       u32 data;
+       /* Timer callbacks are never sleepable, even from non-sleepable programs */
+       bpf_copy_from_user(&data, sizeof(data), NULL);
+       return 0;
+}
+
+SEC("fentry/bpf_fentry_test1")
+__failure __msg("helper call might sleep in a non-sleepable prog")
+int timer_non_sleepable_prog(void *ctx)
+{
+       struct timer_elem *val;
+       int key = 0;
+
+       val = bpf_map_lookup_elem(&timer_map, &key);
+       if (!val)
+               return 0;
+
+       bpf_timer_init(&val->t, &timer_map, 0);
+       bpf_timer_set_callback(&val->t, timer_cb);
+       return 0;
+}
+
+SEC("lsm.s/file_open")
+__failure __msg("helper call might sleep in a non-sleepable prog")
+int timer_sleepable_prog(void *ctx)
+{
+       struct timer_elem *val;

tools/testing/selftests/bpf/progs/verifier_async_cb_context.c line 63

Quoted 10 lines
tools/testing/selftests/bpf/progs/verifier_async_cb_context.c new file +10 -0
+       int key = 0;
+
+       val = bpf_map_lookup_elem(&timer_map, &key);
+       if (!val)
+               return 0;
+
+       bpf_timer_init(&val->t, &timer_map, 0);
+       bpf_timer_set_callback(&val->t, timer_cb);
+       return 0;
+}

can you please also add (as a follow up is fine) this case:

static noinline int mixed_mode_subprog(void)

{
    /* use one of is_storage_get_function() helpers here */
}

static int timer_cb(void *map, int *key, struct bpf_timer *timer)

{
    mixed_mode_subprog();
    return 0;
}

SEC("lsm.s/file_open") /* sleepable entry program */

int sleepable(void *ctx) {
    ...
    bpf_timer_set_callback(&val->t, timer_cb);

    /* !!! important to call it here */
    mixed_mode_subprog();
}

Idea being that we have a subprog that is called in both sleepable and non-sleepable modes. It should be rejected.

Quoted 7 lines

Let's do the same the other way, when the entry program is non-sleepable but an async callback is sleepable, ok?

Note also that noinline part is important, we need to make sure it doesn't get inline anywhere. If necessary, we can do __weak __hidden to make this happen.

I didn't get why it needs to be rejected.
That seems like something that is bound to trip up users. Shouldn't we instead allow this, but choose the most restrictive context when setting any necessary flags etc.?
E.g. if it's both sleepable and non-sleepable, we should simply always do GFP_ATOMIC.

Quoted 2 lines

[...]

#8 lore raw

Re: [PATCH bpf-next v2 3/3] selftests/bpf: Add tests for async cb context

Quoted 158 lines · Kumar Kartikeya Dwivedi
Quoted 148 lines · Andrii Nakryiko
Quoted 110 lines · Kumar Kartikeya Dwivedi

Add tests to verify that async callback's sleepable attribute is correctly determined by the callback type, not the arming program's context, reflecting its true execution context.

Introduce verifier_async_cb_context.c with tests for all three async callback primitives: bpf_timer, bpf_wq, and bpf_task_work. Each primitive is tested when armed from both sleepable (lsm.s/file_open) and non-sleepable (fentry) programs.

Test coverage:
- bpf_timer callbacks: Verify they are never sleepable, even when armed from sleepable programs. Both tests should fail when attempting to use sleepable helper bpf_copy_from_user() in the callback.

- bpf_wq callbacks: Verify they are always sleepable, even when armed from non-sleepable programs. Both tests should succeed when using sleepable helpers in the callback.

- bpf_task_work callbacks: Verify they are always sleepable, even when armed from non-sleepable programs. Both tests should succeed when using sleepable helpers in the callback.

---
 .../selftests/bpf/prog_tests/verifier.c       |   2 +
 .../bpf/progs/verifier_async_cb_context.c     | 181 ++++++++++++++++++
 2 files changed, 183 insertions(+)
 create mode 100644 tools/testing/selftests/bpf/progs/verifier_async_cb_context.c
tools/testing/selftests/bpf/prog_tests/verifier.c +2 -0
index 28e81161e6fc..c0e8ffdaa484 100644
@@ -7,6 +7,7 @@
 #include "verifier_arena.skel.h"
 #include "verifier_arena_large.skel.h"
 #include "verifier_array_access.skel.h"
+#include "verifier_async_cb_context.skel.h"
 #include "verifier_basic_stack.skel.h"
 #include "verifier_bitfield_write.skel.h"
 #include "verifier_bounds.skel.h"
@@ -280,6 +281,7 @@ void test_verifier_array_access(void)
                      verifier_array_access__elf_bytes,
                      init_array_access_maps);
 }
+void test_verifier_async_cb_context(void)    { RUN(verifier_async_cb_context); }
 
 static int init_value_ptr_arith_maps(struct bpf_object *obj)
 {
tools/testing/selftests/bpf/progs/verifier_async_cb_context.c new file +53 -0
new file mode 100644
index 000000000000..96ff6749168b
@@ -0,0 +1,181 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "bpf_misc.h"
+#include "bpf_experimental.h"
+
+char _license[] SEC("license") = "GPL";
+
+/* Timer tests */
+
+struct timer_elem {
+       struct bpf_timer t;
+};
+
+struct {
+       __uint(type, BPF_MAP_TYPE_ARRAY);
+       __uint(max_entries, 1);
+       __type(key, int);
+       __type(value, struct timer_elem);
+} timer_map SEC(".maps");
+
+static int timer_cb(void *map, int *key, struct bpf_timer *timer)
+{
+       u32 data;
+       /* Timer callbacks are never sleepable, even from non-sleepable programs */
+       bpf_copy_from_user(&data, sizeof(data), NULL);
+       return 0;
+}
+
+SEC("fentry/bpf_fentry_test1")
+__failure __msg("helper call might sleep in a non-sleepable prog")
+int timer_non_sleepable_prog(void *ctx)
+{
+       struct timer_elem *val;
+       int key = 0;
+
+       val = bpf_map_lookup_elem(&timer_map, &key);
+       if (!val)
+               return 0;
+
+       bpf_timer_init(&val->t, &timer_map, 0);
+       bpf_timer_set_callback(&val->t, timer_cb);
+       return 0;
+}
+
+SEC("lsm.s/file_open")
+__failure __msg("helper call might sleep in a non-sleepable prog")
+int timer_sleepable_prog(void *ctx)
+{
+       struct timer_elem *val;

tools/testing/selftests/bpf/progs/verifier_async_cb_context.c line 63

Quoted 10 lines
tools/testing/selftests/bpf/progs/verifier_async_cb_context.c new file +10 -0
+       int key = 0;
+
+       val = bpf_map_lookup_elem(&timer_map, &key);
+       if (!val)
+               return 0;
+
+       bpf_timer_init(&val->t, &timer_map, 0);
+       bpf_timer_set_callback(&val->t, timer_cb);
+       return 0;
+}

can you please also add (as a follow up is fine) this case:

static noinline int mixed_mode_subprog(void)

{
    /* use one of is_storage_get_function() helpers here */
}

static int timer_cb(void *map, int *key, struct bpf_timer *timer)

{
    mixed_mode_subprog();
    return 0;
}

SEC("lsm.s/file_open") /* sleepable entry program */

int sleepable(void *ctx) {
    ...
    bpf_timer_set_callback(&val->t, timer_cb);

    /* !!! important to call it here */
    mixed_mode_subprog();
}

Idea being that we have a subprog that is called in both sleepable and non-sleepable modes. It should be rejected.

Quoted 7 lines

Let's do the same the other way, when the entry program is non-sleepable but an async callback is sleepable, ok?

Note also that noinline part is important, we need to make sure it doesn't get inline anywhere. If necessary, we can do __weak __hidden to make this happen.

Quoted 7 lines

I didn't get why it needs to be rejected.
That seems like something that is bound to trip up users. Shouldn't we instead allow this, but choose the most restrictive context when setting any necessary flags etc.?
E.g. if it's both sleepable and non-sleepable, we should simply always do GFP_ATOMIC.

Most restrictive context differs based on exactly what functionality is being used.

E.g., for KF_RCU the most conservative approach is to assume sleepable context and require explicit bpf_rcu_read_{lock,unlock}().

For memory allocation flags, the most conservative approach would be to assume non-sleepable and use GFP_ATOMIC.

It might be ok to mix sleepable and non-sleepable treatment for different instructions within the same subprog, but that seems like a bit of a hairy approach.

Either way, let's add tests and make sure they don't do the wrong thing. Whether we reject such subprogs or manage to wrangle them to be always correct and safe regardless of sleepable/non-sleepable context, that's something to think through and decide.

Does it make sense?

Quoted 2 lines
Quoted 2 lines

[...]

#9 lore raw
In reply to Kumar Kartikeya Dwivedi
This series was applied to bpf/bpf-next.git (master) by Alexei Starovoitov <[email protected]>:

Hello:

This series was applied to bpf/bpf-next.git (master) by Alexei Starovoitov <[email protected]>:

Quoted 8 lines · On Tue, 7 Oct 2025 22:03:46 +0000 you wrote:

Currently, asynchronous execution primitives set up their callback execution simulation using push_async_cb, which will end up inheriting the sleepable or non-sleepable bit from the program triggering the simulation of the callback. This is incorrect, as the actual execution context of the asynchronous callback has nothing to do with the program arming its execution.

[...]

Here is the summary with links:
- [bpf-next,v2,1/3] bpf: Fix sleepable context for async callbacks

    https://git.kernel.org/bpf/bpf-next/c/469d638d1520
  - [bpf-next,v2,2/3] bpf: Refactor storage_get_func_atomic to generic non_sleepable flag
    https://git.kernel.org/bpf/bpf-next/c/f233d4855918
  - [bpf-next,v2,3/3] selftests/bpf: Add tests for async cb context
    https://git.kernel.org/bpf/bpf-next/c/5b1b5d380ac7

You are awesome, thank you!

Signature · 2 lines
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html