Misc bug fixes - part 1
Subsystems (4)
Kumar Kartikeya Dwivedi
maintainercover Misc bug fixes - part 1
A set of miscellaneous fixes for bugs reported by Nicholas. These are easy ones and should not require any major discussion, hence batched together. See commit logs and selftests for details.
Kumar Kartikeya Dwivedi (10):
bpf: Mark signal tracepoint siginfo arguments as scalar selftests/bpf: Cover signal tracepoint siginfo sentinels
bpf: Reject tail calls directly from callback frames selftests/bpf: Test direct tail calls from callbacks
bpf: Reject resilient lock operations in rbtree callbacks selftests/bpf: Reject resilient unlock in rbtree callback
bpf: Mark sched_process_wait argument as nullable selftests/bpf: Test sched_process_wait nullable argument
bpf: Mark syscall helpers as sleepable selftests/bpf: Check syscall helpers in timer callbacks
kernel/bpf/btf.c | 7 ++ kernel/bpf/syscall.c | 2 + kernel/bpf/verifier.c | 16 +++++ .../selftests/bpf/progs/raw_tp_null_fail.c | 53 +++++++++++++++ .../testing/selftests/bpf/progs/rbtree_fail.c | 29 +++++++++ .../selftests/bpf/progs/tailcall_callback.c | 16 +++++ .../bpf/progs/verifier_async_cb_context.c | 64 +++++++++++++++++++ 7 files changed, 187 insertions(+)
base-commit: ac0aaef0aa997fcdcb2458bd584539ba8608d33e
Signature · 1 line
2.53.0
Kumar Kartikeya Dwivedi
maintainerPATCH 1/10 bpf: Mark signal tracepoint siginfo arguments as scalar
The signal_generate and signal_deliver tracepoints declare their info argument as a struct kernel_siginfo pointer. btf_ctx_access() therefore treats it as a trusted pointer for tp_btf programs.
Signal delivery also uses SEND_SIG_NOINFO and SEND_SIG_PRIV as special values for this argument. Those values are zero and one respectively, and are not pointers. A tp_btf program can currently dereference either value and fault the kernel. In particular, signal_generate can run from timer interrupt context, turning the fault into a kernel panic.
Record both tracepoints in raw_tp_null_args[] and mark argument one as a non-pointer. This preserves scalar access to the cookie while rejecting direct and helper-mediated pointer use. Merely marking it nullable would not suffice because SEND_SIG_PRIV is nonzero.
--- kernel/bpf/btf.c | 3 +++ 1 file changed, 3 insertions(+)
kernel/bpf/btf.c +3 -0
@@ -6717,6 +6717,9 @@ static const struct bpf_raw_tp_null_args raw_tp_null_args[] = { { "rxrpc_resend", 0x10 }, { "rxrpc_tq", 0x10 }, { "rxrpc_client", 0x1 }, +/* signal */ + { "signal_generate", 0x20 }, + { "signal_deliver", 0x20 },/* skb */ {"kfree_skb", 0x1000}, /* sunrpc */
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH bpf v1 01/10] bpf: Mark signal tracepoint siginfo arguments as scalar
Is signal_deliver's 0x20 mask too strict for its actual call sites?
kernel/bpf/btf.c static const struct bpf_raw_tp_null_args raw_tp_null_args[] = { · line 6722
Quoted 11 lines
kernel/bpf/btf.c +3
-0@@ -6717,6 +6717,9 @@ static const struct bpf_raw_tp_null_args raw_tp_null_args[] = { { "rxrpc_resend", 0x10 }, { "rxrpc_tq", 0x10 }, { "rxrpc_client", 0x1 }, +/* signal */ + { "signal_generate", 0x20 }, + { "signal_deliver", 0x20 },
^^^^
Is signal_deliver's 0x20 mask too strict for its actual call sites?
Looking at the two call sites for trace_signal_deliver():
kernel/signal.c (do_coredump):
trace_signal_deliver(SIGKILL, SEND_SIG_NOINFO,
&sighand->action[SIGKILL-1]);
kernel/signal.c (get_signal):
trace_signal_deliver(signr, &ksig->info, ka);SEND_SIG_PRIV is never passed to this tracepoint. SEND_SIG_NOINFO is zero (NULL), and &ksig->info is always a real pointer filled by dequeue_synchronous_signal() or dequeue_signal() just above the call.
SEND_SIG_PRIV == 1 only reaches trace_signal_generate() from kernel/signal.c:__send_signal_locked().
If that's the case, would 0x10 (PTR_MAYBE_NULL) be both sufficient and less restrictive for signal_deliver? With 0x10 the verifier would still force a NULL check to catch SEND_SIG_NOINFO, but after the check a tp_btf program could read info->si_code and other fields. With 0x20 the register becomes SCALAR_VALUE immediately and check_mem_access() rejects all dereferencing with "R1 invalid mem access 'scalar'".
This table supports per-argument distinctions. The existing entry:
{ "cachefiles_lookup", 0x1 | 0x200 }
marks arg 0 nullable and arg 2 ERR_PTR rather than applying the same mask to both. The signal_generate justification holds (SEND_SIG_PRIV is nonzero so PTR_MAYBE_NULL alone doesn't work), but does the same reasoning apply to signal_deliver given that it never sees that sentinel?
Quoted 16 lines
commit
a481db8aa51a8682ee36d019234a31b254b3ecd5Author: Kumar Kartikeya Dwivedi <[email protected]> Date: Thu Sep 3 16:44:19 2026 +0200bpf: Mark signal tracepoint siginfo arguments as scalar
The
signal_generateandsignal_delivertracepoints declare their info argument as a structkernel_siginfopointer.btf_ctx_access()therefore treats it as a trusted pointer fortp_btfprograms.Signal delivery also uses
SEND_SIG_NOINFOandSEND_SIG_PRIVas special values for this argument. Those values are zero and one respectively, and are not pointers. Atp_btfprogram can currently dereference either value and fault the kernel. In particular,signal_generatecan run from timer interrupt context, turning the fault into a kernel panic.
Quoted 4 lines
Record both tracepoints in raw_tp_null_args[] and mark argument one as a non-pointer. This preserves scalar access to the cookie while rejecting direct and helper-mediated pointer use. Merely marking it nullable would not suffice because
SEND_SIG_PRIVis nonzero.
^^^^^^
Could this paragraph's wording be more precise about which parts are being preserved and which argument is being marked?
"This preserves scalar access to the cookie" - there doesn't appear to be a cookie anywhere in this change. Neither signal_generate nor signal_deliver has a cookie argument, and this isn't related to bpf_get_attach_cookie(). What's actually preserved is scalar access to the siginfo sentinel value itself (0 for SEND_SIG_NOINFO, 1 for SEND_SIG_PRIV), which is what a program can still read back. The term "cookie" has a specific meaning in BPF context, so this might mislead a reader trying to understand what the patch keeps working.
"mark argument one as a non-pointer" - 838a10bd2ebf documents the mask as "1st arg: 0x1 / 2nd arg: 0x10 / 3rd arg: 0x100", where the nibble position is the zero-indexed argument number. The mask 0x20 is nibble 1, meaning the second argument (info). Against the referenced commit's own wording, "argument one" could point at sig rather than info. Would "argument 1 (info)" or "the second argument" remove the off-by-one ambiguity?
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/33770534313
Re: [PATCH bpf v1 01/10] bpf: Mark signal tracepoint siginfo arguments as scalar
Quoted 31 lines · bot+bpf-ci
kernel/bpf/btf.c static const struct bpf_raw_tp_null_args raw_tp_null_args[] = { · line 6722
Quoted 11 lines
kernel/bpf/btf.c +3
-0@@ -6717,6 +6717,9 @@ static const struct bpf_raw_tp_null_args raw_tp_null_args[] = { { "rxrpc_resend", 0x10 }, { "rxrpc_tq", 0x10 }, { "rxrpc_client", 0x1 }, +/* signal */ + { "signal_generate", 0x20 }, + { "signal_deliver", 0x20 },^^^^Is signal_deliver's
0x20mask too strict for its actual call sites?Looking at the two call sites for
trace_signal_deliver():kernel/signal.c (do_coredump): trace_signal_deliver(SIGKILL, SEND_SIG_NOINFO, &sighand->action[SIGKILL-1]); kernel/signal.c (get_signal): trace_signal_deliver(signr, &ksig->info, ka);
SEND_SIG_PRIVis never passed to this tracepoint.SEND_SIG_NOINFOis zero (NULL), and&ksig->infois always a real pointer filled bydequeue_synchronous_signal()ordequeue_signal()just above the call.
SEND_SIG_PRIV== 1 only reachestrace_signal_generate()from kernel/signal.c:__send_signal_locked().
Quoted 6 lines
If that's the case, would
0x10(PTR_MAYBE_NULL) be both sufficient and less restrictive forsignal_deliver? With0x10the verifier would still force a NULL check to catchSEND_SIG_NOINFO, but after the check atp_btfprogram could readinfo->si_codeand other fields. With0x20the register becomesSCALAR_VALUEimmediately andcheck_mem_access()rejects all dereferencing with "R1 invalid mem access 'scalar'".
Though AI is correct today the callers of trace_signal_deliver() may change in the future without cc-ing bpf mailing list and raw_tp_null_args[] won't be correct anymore. It's better to be strict by default. Hence, I applied the series as-is.
Kumar Kartikeya Dwivedi
maintainerPATCH 2/10 selftests/bpf: Cover signal tracepoint siginfo sentinels
In reply to Kumar Kartikeya DwivediAdd load-only verifier coverage for the signal_generate and signal_deliver info arguments. The signal_generate case performs a NULL check before dereferencing info, ensuring that merely making it nullable cannot satisfy the test when the nonzero SEND_SIG_PRIV sentinel is used.
Both programs load successfully without the verifier fix, contrary to their expected-failure annotations. With the fix, info is a scalar and the attempted dereferences are rejected.
Also add success cases showing that plain raw tracepoint and tp_btf programs can continue to read and compare the context word as a scalar.
--- .../selftests/bpf/progs/raw_tp_null_fail.c | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+)
tools/testing/selftests/bpf/progs/raw_tp_null_fail.c +36 -0
@@ -22,3 +22,39 @@ int test_raw_tp_null_sched_pi_setprio_arg_2(void *ctx) { asm volatile("r1 = *(u64 *)(r1 +8); r1 = *(u64 *)(r1 +0);" ::: __clobber_all);return 0 ; } + +/* Plain raw tracepoint arguments remain scalar values. */ +SEC("raw_tp/signal_generate") +__success +int test_raw_tp_signal_generate_info_scalar (void *ctx) +{ + asm volatile("r1 = *(u64 *)(r1 +8); if r1 != 1 goto +0;" ::: __clobber_all); +return 0 ; +} + +/* tp_btf programs may inspect the sentinel as a scalar value. */ +SEC("tp_btf/signal_generate") +__success +int test_tp_btf_signal_generate_info_scalar (void *ctx) +{ + asm volatile("r1 = *(u64 *)(r1 +8); if r1 != 1 goto +0;" ::: __clobber_all); +return 0 ; +} + +/* SEND_SIG_PRIV is non-NULL, so a NULL check cannot make info safe. */ +SEC("tp_btf/signal_generate") +__failure __msg("R1 invalid mem access 'scalar'") +int test_tp_btf_signal_generate_info_no_deref(void *ctx) +{ + asmvolatile ("r1 = *(u64 *)(r1 +8); if r1 == 0 goto +1; " + "r1 = *(u32 *)(r1 +0);" ::: __clobber_all); +return 0 ; +} + +SEC("tp_btf/signal_deliver") +__failure __msg("R1 invalid mem access 'scalar'") +int test_tp_btf_signal_deliver_info_no_deref(void *ctx) +{ + asm volatile("r1 = *(u64 *)(r1 +8); r1 = *(u32 *)(r1 +0);" ::: __clobber_all); +return 0 ; +}
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH bpf v1 02/10] selftests/bpf: Cover signal tracepoint siginfo sentinels
This isn't a bug, but would it read more clearly to name the two expected-failure programs explicitly in the "Both programs load successfully" sentence?
Quoted 7 lines
selftests/bpf: Cover signal tracepoint siginfo sentinels
Add load-only verifier coverage for the
signal_generateandsignal_deliverinfo arguments. Thesignal_generatecase performs a NULL check before dereferencing info, ensuring that merely making it nullable cannot satisfy the test when the nonzeroSEND_SIG_PRIVsentinel is used.
Quoted 8 lines
Both programs load successfully without the verifier fix, contrary to their expected-failure annotations. With the fix, info is a scalar and the attempted dereferences are rejected.
Also add success cases showing that plain raw tracepoint and
tp_btfprograms can continue to read and compare the context word as a scalar.Signed-off-by Kumar Kartikeya Dwivedi <[email protected]>
This isn't a bug, but would it read more clearly to name the two expected-failure programs explicitly in the "Both programs load successfully" sentence?
The patch adds four programs total (two __success controls and two __failure cases), so "Both" has to be inferred as referring to the two __failure programs. Something like "without the verifier fix the two __failure programs load, so the tests fail; with the fix info is a scalar and both dereferences are rejected" would make the antecedent explicit.
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/33770534313
Kumar Kartikeya Dwivedi
maintainerPATCH 3/10 bpf: Reject tail calls directly from callback frames
In reply to Kumar Kartikeya DwivediA tail call from a non-zero frame is modeled as a return from that frame. The verifier makes R0 unknown and calls prepare_func_exit() for the taken branch.
When the current frame is a synchronous callback, prepare_func_exit() enforces the callback return-value contract and marks R0 precise. Since the tail-call path synthesized R0 rather than deriving it from an instruction, precision backtracking reaches the callback-calling instruction with R0 still requested and triggers the "callback unexpected regs" verifier bug. A CAP_BPF task can therefore cause a WARN and an -EFAULT BPF_PROG_LOAD.
Tail calls reachable from callbacks are already rejected later by check_max_stack_depth(). Reject a tail call made directly by a callback before constructing the inconsistent return state, using the existing diagnostic. Tail calls from ordinary subprograms keep their current behavior.
--- kernel/bpf/verifier.c | 11 +++++++++++ 1 file changed, 11 insertions(+)
kernel/bpf/verifier.c +11 -0
@@ -11228,6 +11228,17 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insnif (env->cur_state->curframe) {struct bpf_verifier_state *branch; +/* +* A taken tail call is modeled as a return from the current +* frame. A callback frame cannot be left that way because +* prepare_func_exit() would apply its return contract to the +* unknown R0 synthesized below. Stack-depth validation rejects +* this construct anyway. +*/ +if (cur_func (env)->in_callback_fn) { +verbose (env,"cannot tail call within callback\n" ); +return -EINVAL ; + }mark_reg_scratched (env,BPF_REG_0 ); branch =push_stack (env, env->insn_idx +1 , env->insn_idx, false);if (IS_ERR (branch))
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH bpf v1 03/10] bpf: Reject tail calls directly from callback frames
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] The patch prevents bpf_tail_call within a callback to fix the 'callback unexpected regs' verifier bug, but misses that BPF_LD_ABS and BPF_LD_IND trigger the exact same bug via the same prepare_func_exit() path. --
commit a25ccc5cdb72d9330414949c17ecac5156595510
Author: Kumar Kartikeya Dwivedi <[email protected]>
bpf: Reject tail calls directly from callback frames
A tail call from a non-zero frame is modeled as a return from that frame, synthesizing R0 and calling prepare_func_exit() for the taken branch. When the current frame is a synchronous callback, prepare_func_exit() enforces the callback return-value contract and marks R0 precise, triggering the "callback unexpected regs" verifier warning during precision backtracking. This patch rejects a tail call made directly by a callback before constructing the inconsistent return state.
Quoted 9 lines
kernel/bpf/verifier.c +1
-0@@ -11228,6 +11228,17 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insnif (env->cur_state->curframe) {struct bpf_verifier_state *branch; +/*
kernel/bpf/verifier.c static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn · line 11241
Quoted 10 lines
kernel/bpf/verifier.c +10
-0+* A taken tail call is modeled as a return from the current +* frame. A callback frame cannot be left that way because +* prepare_func_exit() would apply its return contract to the +* unknown R0 synthesized below. Stack-depth validation rejects +* this construct anyway. +*/ +if (cur_func (env)->in_callback_fn) { +verbose (env,"cannot tail call within callback\n" ); +return -EINVAL ; + }
[Severity: High]
This isn't a bug introduced by this patch, but doesn't BPF_LD_ABS inside a subprog trigger the exact same bug via the same prepare_func_exit() path?
Looking at check_ld_abs() in kernel/bpf/verifier.c:
if (env->cur_state->curframe) {
struct bpf_verifier_state *branch;
mark_reg_scratched(env, BPF_REG_0);
branch = push_stack(env, env->insn_idx + 1, env->insn_idx, false);
if (IS_ERR(branch))
return PTR_ERR(branch);
mark_reg_known_zero(env, regs, BPF_REG_0);
err = prepare_func_exit(env, &env->insn_idx);
if (err)
return err;
env->insn_idx--;
}If a CAP_BPF user loads a SCHED_CLS program (where BPF_LD_ABS is allowed), invokes bpf_loop(), passes PTR_TO_CTX through the stack to the callback_ctx argument, loads the context into R6 inside the callback, and executes BPF_LD_ABS, it appears check_ld_abs() will synthesize R0 and call prepare_func_exit().
Since the callback frame lacks instruction history for R0, won't precision backtracking fail and hit the exact same "callback unexpected regs" verifier warning? Does check_ld_abs() need a similar rejection for callbacks?
Quoted 3 lines
kernel/bpf/verifier.c +0
-0mark_reg_scratched (env,BPF_REG_0 ); branch =push_stack (env, env->insn_idx +1 , env->insn_idx, false);if (IS_ERR (branch))
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=3
Kumar Kartikeya Dwivedi
maintainerRe: [PATCH bpf v1 03/10] bpf: Reject tail calls directly from callback frames
Quoted 65 lines · sashiko-bot
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] The patch preventsbpf_tail_callwithin a callback to fix the 'callback unexpected regs' verifier bug, but misses thatBPF_LD_ABSandBPF_LD_INDtrigger the exact same bug via the sameprepare_func_exit()path. --commit
a25ccc5cdb72d9330414949c17ecac5156595510
Author: Kumar Kartikeya Dwivedi <[email protected]>bpf: Reject tail calls directly from callback frames
A tail call from a non-zero frame is modeled as a return from that frame, synthesizing R0 and calling
prepare_func_exit()for the taken branch. When the current frame is a synchronous callback,prepare_func_exit()enforces the callback return-value contract and marks R0 precise, triggering the "callback unexpected regs" verifier warning during precision backtracking. This patch rejects a tail call made directly by a callback before constructing the inconsistent return state.Quoted 9 lines
kernel/bpf/verifier.c +1
-0@@ -11228,6 +11228,17 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insnif (env->cur_state->curframe) {struct bpf_verifier_state *branch; +/* kernel/bpf/verifier.c static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn · line 11241
Quoted 10 lines
kernel/bpf/verifier.c +10
-0+* A taken tail call is modeled as a return from the current +* frame. A callback frame cannot be left that way because +* prepare_func_exit() would apply its return contract to the +* unknown R0 synthesized below. Stack-depth validation rejects +* this construct anyway. +*/ +if (cur_func (env)->in_callback_fn) { +verbose (env,"cannot tail call within callback\n" ); +return -EINVAL ; + }[Severity: High]
This isn't a bug introduced by this patch, but doesn'tBPF_LD_ABSinside a subprog trigger the exact same bug via the sameprepare_func_exit()path?Looking at
check_ld_abs()inkernel/bpf/verifier.c:if (env->cur_state->curframe) { struct bpf_verifier_state *branch; mark_reg_scratched(env, BPF_REG_0); branch = push_stack(env, env->insn_idx + 1, env->insn_idx, false); if (IS_ERR(branch)) return PTR_ERR(branch); mark_reg_known_zero(env, regs, BPF_REG_0); err = prepare_func_exit(env, &env->insn_idx); if (err) return err; env->insn_idx--; }If a
CAP_BPFuser loads aSCHED_CLSprogram (whereBPF_LD_ABSis allowed), invokesbpf_loop(), passesPTR_TO_CTXthrough the stack to thecallback_ctxargument, loads the context into R6 inside the callback, and executesBPF_LD_ABS, it appearscheck_ld_abs()will synthesize R0 and callprepare_func_exit().
Quoted 4 lines
Since the callback frame lacks instruction history for R0, won't precision backtracking fail and hit the exact same "callback unexpected regs" verifier warning? Does
check_ld_abs()need a similar rejection for callbacks?
Has to be fixed separately.
Quoted 3 lines
Quoted 3 lines
kernel/bpf/verifier.c +0
-0mark_reg_scratched (env,BPF_REG_0 ); branch =push_stack (env, env->insn_idx +1 , env->insn_idx, false);if (IS_ERR (branch))
Kumar Kartikeya Dwivedi
maintainerPATCH 4/10 selftests/bpf: Test direct tail calls from callbacks
In reply to Kumar Kartikeya Dwiveditailcall_callback tests a tail call one static subprogram below a callback. That reaches the later stack-depth rejection, but it does not exercise the tail-call helper while the current frame is itself a callback.
Add a callback that calls bpf_tail_call directly and expect the existing "cannot tail call within callback" diagnostic. On an affected kernel, the load instead reaches the "callback unexpected regs" verifier bug, so the expected message is absent and the test fails. The existing ordinary subprogram case remains a success control for legitimate tail calls.
--- .../selftests/bpf/progs/tailcall_callback.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+)
tools/testing/selftests/bpf/progs/tailcall_callback.c +16 -0
@@ -44,6 +44,13 @@ int callback_loop(int index, void **cb_ctx)return ret ?1 :0 ; } +static __noinline +int callback_tail(int index, void **cb_ctx) +{ +bpf_tail_call_static (*cb_ctx, &jmp_table,0 ); +return 0 ; +} +static __noinline int callback_empty(int index, void *data) {
@@ -78,4 +85,13 @@ int tailcall_callback_2(struct __sk_buff *skb)return 0 ; } +/* callback with a direct tail call is rejected without a verifier bug */ +SEC("tc") +__failure __msg("cannot tail call within callback") +int tailcall_callback_3(struct __sk_buff *skb) +{ +bpf_loop (1 , callback_tail, &skb,0 ); +return 0 ; +} + char __license[] SEC("license") = "GPL";
Signature · 1 line
2.53.0
Kumar Kartikeya Dwivedi
maintainerPATCH 5/10 bpf: Reject resilient lock operations in rbtree callbacks
In reply to Kumar Kartikeya Dwivedi__bpf_rbtree_add() keeps parent and link pointers live across calls to the program-supplied comparison callback. The verifier therefore requires the root's lock to remain held throughout the callback.
The helper path enforces this rule for bpf_spin_lock() and bpf_spin_unlock(), but the resilient lock kfunc argument path does not. Since resilient locks may protect BPF rbtree roots, a callback can release the root lock and let another CPU remove and free the node referenced by the in-progress tree walk. The walk then resumes using freed pointers.
Reject resilient lock kfuncs in an rbtree comparison callback, matching the existing policy for the spin lock helpers. Resilient-lock-protected trees remain valid when their comparison callbacks leave lock state alone.
--- kernel/bpf/verifier.c | 5 +++++ 1 file changed, 5 insertions(+)
kernel/bpf/verifier.c +5 -0
@@ -13241,6 +13241,11 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me {int flags =PROCESS_RES_LOCK ; +if (in_rbtree_lock_required_cb (env)) { +verbose (env,"can't res_spin_{lock,unlock} in rbtree cb\n" ); +return -EACCES ; + } +if (reg->type !=PTR_TO_MAP_VALUE && reg->type != (PTR_TO_BTF_ID |MEM_ALLOC )) {verbose (env,"%s doesn't point to map value or allocated object\n" ,reg_arg_name (env, argno));
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH bpf v1 05/10] bpf: Reject resilient lock operations in rbtree callbacks
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] The restriction against calling lock operations inside rbtree callbacks can be bypassed by nesting callbacks or calling subprograms, because in_rbtree_lock_required_cb() only checks the immediate top frame. --
commit c5914f67e15078bc12af13429620d2a2a4de2b11
Author: Kumar Kartikeya Dwivedi <[email protected]>
bpf: Reject resilient lock operations in rbtree callbacks
This patch attempts to enforce the root's lock requirement for resilient locks in rbtree callbacks. It aims to prevent use-after-free conditions when nodes are removed and freed by other CPUs during the in-progress tree walk.
kernel/bpf/verifier.c static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me · line 13244
Quoted 9 lines
kernel/bpf/verifier.c +1
-0@@ -13241,6 +13241,11 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me {int flags =PROCESS_RES_LOCK ; +if (in_rbtree_lock_required_cb (env)) {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[Severity: High]
This isn't a bug introduced by this patch, but does in_rbtree_lock_required_cb() fully protect against this bypass?
Looking at in_rbtree_lock_required_cb(), it only checks the immediate top frame:
kernel/bpf/verifier.c:in_rbtree_lock_required_cb() {
...
callee = state->frame[state->curframe];
if (!callee->in_callback_fn)
return false;
kfunc_btf_id = insn[callee->callsite].imm;
return is_rbtree_lock_required_kfunc(kfunc_btf_id);
}If a BPF program calls a custom rbtree comparison callback, and inside that callback it calls a static subprogram (or a nested callback like bpf_loop()), would state->curframe's in_callback_fn be false (or its kfunc_btf_id not match the rbtree add kfunc)?
Could a program use this nested frame to successfully unlock the resilient lock via a global map value (BPF_PSEUDO_MAP_VALUE), temporarily dropping the lock and allowing another CPU to concurrently remove and free the nodes being traversed by bpf_rbtree_add(), leading to a Use-After-Free?
Quoted 7 lines
kernel/bpf/verifier.c +4
-0+verbose (env,"can't res_spin_{lock,unlock} in rbtree cb\n" ); +return -EACCES ; + } +if (reg->type !=PTR_TO_MAP_VALUE && reg->type != (PTR_TO_BTF_ID |MEM_ALLOC )) {verbose (env,"%s doesn't point to map value or allocated object\n" ,reg_arg_name (env, argno));
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=5
Kumar Kartikeya Dwivedi
maintainerRe: [PATCH bpf v1 05/10] bpf: Reject resilient lock operations in rbtree callbacks
Quoted 50 lines · sashiko-bot
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] The restriction against calling lock operations inside rbtree callbacks can be bypassed by nesting callbacks or calling subprograms, becausein_rbtree_lock_required_cb()only checks the immediate top frame. --commit
c5914f67e15078bc12af13429620d2a2a4de2b11
Author: Kumar Kartikeya Dwivedi <[email protected]>bpf: Reject resilient lock operations in rbtree callbacks
This patch attempts to enforce the root's lock requirement for resilient locks in rbtree callbacks. It aims to prevent use-after-free conditions when nodes are removed and freed by other CPUs during the in-progress tree walk.
kernel/bpf/verifier.c static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me · line 13244
Quoted 9 lines
kernel/bpf/verifier.c +1
-0@@ -13241,6 +13241,11 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me {int flags =PROCESS_RES_LOCK ; +if (in_rbtree_lock_required_cb (env)) {^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^[Severity: High]
This isn't a bug introduced by this patch, but doesin_rbtree_lock_required_cb()fully protect against this bypass?Looking at
in_rbtree_lock_required_cb(), it only checks the immediate top frame:kernel/bpf/verifier.c:in_rbtree_lock_required_cb() { ... callee = state->frame[state->curframe]; if (!callee->in_callback_fn) return false; kfunc_btf_id = insn[callee->callsite].imm; return is_rbtree_lock_required_kfunc(kfunc_btf_id); }If a BPF program calls a custom rbtree comparison callback, and inside that callback it calls a static subprogram (or a nested callback like
bpf_loop()), would state->curframe'sin_callback_fnbe false (or itskfunc_btf_idnot match the rbtree add kfunc)?
Quoted 5 lines
Could a program use this nested frame to successfully unlock the resilient lock via a global map value (
BPF_PSEUDO_MAP_VALUE), temporarily dropping the lock and allowing another CPU to concurrently remove and free the nodes being traversed bybpf_rbtree_add(), leading to a Use-After-Free?
Separate bug, will be separate fix. Let's still add this one.
Quoted 7 lines
Quoted 7 lines
kernel/bpf/verifier.c +4
-0+verbose (env,"can't res_spin_{lock,unlock} in rbtree cb\n" ); +return -EACCES ; + } +if (reg->type !=PTR_TO_MAP_VALUE && reg->type != (PTR_TO_BTF_ID |MEM_ALLOC )) {verbose (env,"%s doesn't point to map value or allocated object\n" ,reg_arg_name (env, argno));
Kumar Kartikeya Dwivedi
maintainerPATCH 6/10 selftests/bpf: Reject resilient unlock in rbtree callback
In reply to Kumar Kartikeya DwivediAdd a load-only verifier regression for a resilient lock operation in an rbtree comparison callback. The program holds the rbtree's regular spin lock and a separate resilient lock, then releases the resilient lock from the callback. This isolates the missing kfunc policy check without running a concurrent tree mutation.
Release the resilient lock before the regular lock on the outer fall-through. The broken verifier therefore accepts the balanced program, while the fixed verifier rejects the resilient unlock specifically while verifying the callback.
--- .../testing/selftests/bpf/progs/rbtree_fail.c | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+)
tools/testing/selftests/bpf/progs/rbtree_fail.c +29 -0
@@ -16,6 +16,7 @@ struct node_data { private(A) struct bpf_spin_lock glock; private(A) struct bpf_rb_root groot __contains(node_data, node); private(A) struct bpf_rb_root groot2 __contains(node_data, node); +private(B) struct bpf_res_spin_lock res_glock;static bool less (struct bpf_rb_node *a,const struct bpf_rb_node *b) {
@@ -265,6 +266,12 @@ static bool less__bad_fn_call_first_unlock_after(struct bpf_rb_node *a, const streturn node_a->key < node_b->key; } +static bool less__bad_res_spin_unlock (struct bpf_rb_node *a,const struct bpf_rb_node *b) +{ +bpf_res_spin_unlock (&res_glock); +return false; +} +static __always_inline long add_with_cb (bool (cb )(struct bpf_rb_node *a,const struct bpf_rb_node *b)) {
@@ -301,4 +308,26 @@ long rbtree_api_add_bad_cb_bad_fn_call_first_unlock_after(void *ctx)return add_with_cb (less__bad_fn_call_first_unlock_after); } +SEC("?tc") +__failure __msg("can't res_spin_{lock,unlock} in rbtree cb") +long rbtree_api_add_bad_cb_res_spin_unlock(void *ctx) +{ +struct node_data *n; + + n =bpf_obj_new (typeof (*n)); +if (!n) +return 1 ; + +bpf_spin_lock (&glock); +if (bpf_res_spin_lock (&res_glock)) { +bpf_spin_unlock (&glock); +bpf_obj_drop (n); +return 1 ; + } +bpf_rbtree_add (&groot, &n->node, less__bad_res_spin_unlock); +bpf_res_spin_unlock (&res_glock); +bpf_spin_unlock (&glock); +return 0 ; +} + char _license[] SEC("license") = "GPL";
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH bpf v1 06/10] selftests/bpf: Reject resilient unlock in rbtree callback
The commit message states "The broken verifier therefore accepts the balanced program." Does the pre-fix verifier actually accept this program?
Quoted 6 lines
Add a load-only verifier regression for a resilient lock operation in an rbtree comparison callback. The program holds the rbtree's regular spin lock and a separate resilient lock, then releases the resilient lock from the callback. This isolates the missing kfunc policy check without running a concurrent tree mutation.
Quoted 6 lines
Release the resilient lock before the regular lock on the outer fall-through. The broken verifier therefore accepts the balanced program, while the fixed verifier rejects the resilient unlock specifically while verifying the callback.
Signed-off-by Kumar Kartikeya Dwivedi <[email protected]>
The commit message states "The broken verifier therefore accepts the balanced program." Does the pre-fix verifier actually accept this program?
Tracing through the verifier's behavior before the parent commit's fix: the added program structure is
bpf_spin_lock(&glock);
if (bpf_res_spin_lock(&res_glock)) { ... }
bpf_rbtree_add(&groot, &n->node, less__bad_res_spin_unlock);
bpf_res_spin_unlock(&res_glock);
bpf_spin_unlock(&glock);with the callback doing only bpf_res_spin_unlock(&res_glock).
Without the parent commit's check, the callback's unlock succeeds and the release is recorded in the verifier state, not in the callee frame. Locks live in struct bpf_verifier_state (state->refs, state->active_locks, acquire_lock_state() and release_lock_state() in verifier.c, and process_spin_lock()).
Nothing catches the imbalance at the callback boundary either:process_bpf_exit_full() passes check_lock = exception_exit || !env->cur_state->curframe, which is false for a callback frame. check_reference_leak() returns 0 immediately when cur_func(env)->frameno is non-zero.
prepare_func_exit() then does, for in_callback_fn:
if (in_callback_fn)
*insn_idx = callee->callsite;so the frame is popped and verification resumes at the bpf_rbtree_add callsite carrying the callback's state, in which res_glock is no longer held. is_state_visited() cannot prune that state against the pre-call state at the callsite, because refsafe() bails on old->active_locks != cur->active_locks (2 vs 1).
Verification therefore falls through past the callsite and reaches the outer bpf_res_spin_unlock(&res_glock), where process_spin_lock()'s find_lock_state(cur, REF_TYPE_RES_LOCK, reg->id, ptr) finds nothing and emits "bpf_res_spin_unlock of different lock" with -EINVAL.
The result is: pre-fix the load fails with "bpf_res_spin_unlock of different lock," while post-fix it fails with "can't res_spin_{lock,unlock} in rbtree cb."
The __failure __msg("can't res_spin_{lock,unlock} in rbtree cb") assertion does still discriminate a fixed kernel from a broken one, so the test itself is functional. However, should the changelog say the pre-fix verifier accepts this program when it actually rejects it with a different message?
A case that the pre-fix verifier would actually accept is one where the callback leaves the lock state balanced at exit (for example, unlock then re-lock the same resilient lock inside the callback), which is also closer to the use-after-free window the parent commit describes.
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/33770534313
Kumar Kartikeya Dwivedi
maintainerPATCH 7/10 bpf: Mark sched_process_wait argument as nullable
In reply to Kumar Kartikeya Dwivedido_wait() passes wo->wo_pid to the sched_process_wait tracepoint. kernel_wait4() leaves wo_pid NULL for wait4(-1), and kernel_waitid_prepare() does likewise for waitid(P_ALL).
btf_ctx_access() currently types argument 0 as PTR_TO_BTF_ID | PTR_TRUSTED. Without PTR_MAYBE_NULL, the verifier accepts an unchecked dereference. Trusted pointer loads have no fault protection, so a wait for any child can then cause a NULL pointer dereference in JITed BPF code.
Add sched_process_wait to raw_tp_null_args[] with argument 0 marked nullable. The verifier rejects an unchecked dereference while preserving access after the program checks the pointer for NULL.
--- kernel/bpf/btf.c | 4 ++++ 1 file changed, 4 insertions(+)
kernel/bpf/btf.c +4 -0
@@ -6657,6 +6657,10 @@ struct bpf_raw_tp_null_args {static const struct bpf_raw_tp_null_args raw_tp_null_args[] = {/* sched */ {"sched_pi_setprio" ,0x10 }, +/* +* do_wait() passes NULL for wait4(-1) and waitid(P_ALL). +*/ + {"sched_process_wait" ,0x1 },/* ... from sched_numa_pair_template event class */ {"sched_stick_numa" ,0x100 }, {"sched_swap_numa" ,0x100 },
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH bpf v1 07/10] bpf: Mark sched_process_wait argument as nullable
This isn't a bug, but would the comment read better as a single-line /* do_wait() passes NULL for wait4(-1) and waitid(P_ALL). */ to match the other comments in this table?
kernel/bpf/btf.c struct bpf_raw_tp_null_args { · line 6664
Quoted 13 lines
kernel/bpf/btf.c +4
-0@@ -6657,6 +6657,10 @@ struct bpf_raw_tp_null_args {static const struct bpf_raw_tp_null_args raw_tp_null_args[] = {/* sched */ {"sched_pi_setprio" ,0x10 }, +/* +* do_wait() passes NULL for wait4(-1) and waitid(P_ALL). +*/ + {"sched_process_wait" ,0x1 },/* ... from sched_numa_pair_template event class */
This isn't a bug, but would the comment read better as a single-line /* do_wait() passes NULL for wait4(-1) and waitid(P_ALL). */ to match the other comments in this table?
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/33770534313
Kumar Kartikeya Dwivedi
maintainerPATCH 8/10 selftests/bpf: Test sched_process_wait nullable argument
In reply to Kumar Kartikeya DwivediAdd a load-time verifier test that dereferences argument 0 of the sched_process_wait tp_btf program without checking it. The test expects the nullable-pointer diagnostic, so it is accepted unexpectedly before the fix and rejected as expected after it.
Add a successful control that checks the argument for NULL before the dereference. This ensures the nullable marking preserves legitimate access to the pid when the tracepoint supplies one.
--- .../selftests/bpf/progs/raw_tp_null_fail.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+)
tools/testing/selftests/bpf/progs/raw_tp_null_fail.c +17 -0
@@ -58,3 +58,20 @@ int test_tp_btf_signal_deliver_info_no_deref(void *ctx) asm volatile("r1 = *(u64 *)(r1 +8); r1 = *(u32 *)(r1 +0);" ::: __clobber_all);return 0 ; } + +SEC("tp_btf/sched_process_wait") +__failure __msg("R1 invalid mem access 'trusted_ptr_or_null_'") +int test_raw_tp_null_sched_process_wait_arg_1(void *ctx) +{ + asm volatile("r1 = *(u64 *)(r1 +0); r1 = *(u32 *)(r1 +0);" ::: __clobber_all); +return 0 ; +} + +SEC("tp_btf/sched_process_wait") +__success +int test_raw_tp_null_sched_process_wait_arg_1_checked (void *ctx) +{ + asmvolatile ("r1 = *(u64 *)(r1 +0); if r1 == 0 goto +1; " + "r1 = *(u32 *)(r1 +0);" ::: __clobber_all); +return 0 ; +}
Signature · 1 line
2.53.0
Kumar Kartikeya Dwivedi
maintainerPATCH 9/10 bpf: Mark syscall helpers as sleepable
In reply to Kumar Kartikeya Dwivedibpf_sys_bpf() executes the bpf(2) syscall body, which can take mutexes, allocate with GFP_KERNEL, and wait for an RCU grace period. bpf_sys_close() reaches close_fd() and filp_close(), which can sleep as well.
Both helpers are limited to BPF_PROG_TYPE_SYSCALL, whose main program is sleepable. That does not make every callback sleepable: a syscall program can register a bpf_timer callback, and the verifier checks that callback in a non-sleepable context while retaining the syscall helper set.
Without .might_sleep on the prototypes, such a callback can invoke bpf_sys_bpf() from hrtimer softirq context and trigger a scheduling-while-atomic failure. bpf_sys_close() is exposed through the same missing context check.
Set .might_sleep on both prototypes so the existing helper-context check rejects them from timer callbacks and other atomic regions. Calls from the sleepable main body remain valid.
--- kernel/bpf/syscall.c | 2 ++ 1 file changed, 2 insertions(+)
kernel/bpf/syscall.c +2 -0
@@ -6568,6 +6568,7 @@ EXPORT_SYMBOL_NS(kern_sys_bpf, "BPF_INTERNAL");static const struct bpf_func_proto bpf_sys_bpf_proto = { .func = bpf_sys_bpf, .gpl_only = false, + .might_sleep = true, .ret_type =RET_INTEGER , .arg1_type =ARG_ANYTHING , .arg2_type =ARG_PTR_TO_MEM |MEM_RDONLY ,
@@ -6593,6 +6594,7 @@ BPF_CALL_1(bpf_sys_close, u32, fd)static const struct bpf_func_proto bpf_sys_close_proto = { .func = bpf_sys_close, .gpl_only = false, + .might_sleep = true, .ret_type =RET_INTEGER , .arg1_type =ARG_ANYTHING , };
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH bpf v1 09/10] bpf: Mark syscall helpers as sleepable
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] bpf_btf_find_by_name_kind_proto lacks .might_sleep = true, allowing arbitrary FD injection via softirq. --
commit 1d16e876066d61c09bcc6698ca694cdf8336d52c
Author: Kumar Kartikeya Dwivedi <[email protected]>
bpf: Mark syscall helpers as sleepable
This commit adds the .might_sleep = true constraint to the bpf_sys_bpf and bpf_sys_close helper prototypes. This ensures the verifier rejects them from non-sleepable contexts, such as bpf_timer callbacks registered within a BPF_PROG_TYPE_SYSCALL program.
Quoted 15 lines
kernel/bpf/syscall.c +1
-0@@ -6568,6 +6568,7 @@ EXPORT_SYMBOL_NS(kern_sys_bpf, "BPF_INTERNAL");static const struct bpf_func_proto bpf_sys_bpf_proto = { .func = bpf_sys_bpf, .gpl_only = false, + .might_sleep = true, .ret_type =RET_INTEGER , .arg1_type =ARG_ANYTHING , .arg2_type =ARG_PTR_TO_MEM |MEM_RDONLY ,kernel/bpf/syscall.c +0
-0.arg3_type = ARG_MEM_SIZE, };
kernel/bpf/syscall.c BPF_CALL_1(bpf_sys_close, u32, fd) · line 6600
Quoted 7 lines
kernel/bpf/syscall.c +1
-0@@ -6593,6 +6594,7 @@ BPF_CALL_1(bpf_sys_close, u32, fd)static const struct bpf_func_proto bpf_sys_close_proto = { .func = bpf_sys_close, .gpl_only = false, + .might_sleep = true, .ret_type =RET_INTEGER , .arg1_type =ARG_ANYTHING , };
[Severity: High]
This isn't a bug introduced by this patch, but does bpf_btf_find_by_name_kind_proto also need to be marked as sleepable?
Looking at syscall_prog_func_proto() in kernel/bpf/syscall.c, bpf_btf_find_by_name_kind is also exposed to syscall programs:
case BPF_FUNC_btf_find_by_name_kind:
return &bpf_btf_find_by_name_kind_proto;Because bpf_btf_find_by_name_kind_proto in kernel/bpf/btf.c lacks the .might_sleep = true constraint, the verifier will permit its use inside a non-sleepable bpf_timer callback registered by a syscall program.
When the timer fires in softirq context, bpf_btf_find_by_name_kind() calls __btf_new_fd(). This attempts to allocate a file descriptor with GFP_KERNEL and injects the new file descriptor into the current->files struct of whatever arbitrary process was interrupted by the timer.
Should bpf_btf_find_by_name_kind_proto be updated as well to prevent this cross-process file descriptor injection and scheduling-while-atomic issue?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=9
Kumar Kartikeya Dwivedi
maintainerRe: [PATCH bpf v1 09/10] bpf: Mark syscall helpers as sleepable
Quoted 53 lines · sashiko-bot
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High]bpf_btf_find_by_name_kind_protolacks .might_sleep = true, allowing arbitrary FD injection via softirq. --commit
1d16e876066d61c09bcc6698ca694cdf8336d52c
Author: Kumar Kartikeya Dwivedi <[email protected]>bpf: Mark syscall helpers as sleepable
This commit adds the .might_sleep = true constraint to the
bpf_sys_bpfandbpf_sys_closehelper prototypes. This ensures the verifier rejects them from non-sleepable contexts, such asbpf_timercallbacks registered within aBPF_PROG_TYPE_SYSCALLprogram.Quoted 15 lines
kernel/bpf/syscall.c +1
-0@@ -6568,6 +6568,7 @@ EXPORT_SYMBOL_NS(kern_sys_bpf, "BPF_INTERNAL");static const struct bpf_func_proto bpf_sys_bpf_proto = { .func = bpf_sys_bpf, .gpl_only = false, + .might_sleep = true, .ret_type =RET_INTEGER , .arg1_type =ARG_ANYTHING , .arg2_type =ARG_PTR_TO_MEM |MEM_RDONLY ,kernel/bpf/syscall.c +0
-0.arg3_type = ARG_MEM_SIZE, };kernel/bpf/syscall.c BPF_CALL_1(bpf_sys_close, u32, fd) · line 6600
Quoted 7 lines
kernel/bpf/syscall.c +1
-0@@ -6593,6 +6594,7 @@ BPF_CALL_1(bpf_sys_close, u32, fd)static const struct bpf_func_proto bpf_sys_close_proto = { .func = bpf_sys_close, .gpl_only = false, + .might_sleep = true, .ret_type =RET_INTEGER , .arg1_type =ARG_ANYTHING , };[Severity: High]
This isn't a bug introduced by this patch, but doesbpf_btf_find_by_name_kind_protoalso need to be marked as sleepable?Looking at
syscall_prog_func_proto()inkernel/bpf/syscall.c,bpf_btf_find_by_name_kindis also exposed to syscall programs:case BPF_FUNC_btf_find_by_name_kind: return &bpf_btf_find_by_name_kind_proto;Because
bpf_btf_find_by_name_kind_protoinkernel/bpf/btf.clacks the .might_sleep = true constraint, the verifier will permit its use inside a non-sleepablebpf_timercallback registered by a syscall program.
Quoted 7 lines
When the timer fires in softirq context,
bpf_btf_find_by_name_kind()calls__btf_new_fd(). This attempts to allocate a file descriptor withGFP_KERNELand injects the new file descriptor into thecurrent->filesstruct of whatever arbitrary process was interrupted by the timer.Should
bpf_btf_find_by_name_kind_protobe updated as well to prevent this cross-process file descriptor injection and scheduling-while-atomic issue?
Yes, this also looks broken. Will fix after this set lands.
[email protected]
AI reviewRe: [PATCH bpf v1 09/10] bpf: Mark syscall helpers as sleepable
In reply to Kumar Kartikeya DwivediDoes bpf_btf_find_by_name_kind() need the same treatment?
kernel/bpf/syscall.c EXPORT_SYMBOL_NS(kern_sys_bpf, "BPF_INTERNAL"); · line 6574
Quoted 12 lines
kernel/bpf/syscall.c +1
-0@@ -6568,6 +6568,7 @@ EXPORT_SYMBOL_NS(kern_sys_bpf, "BPF_INTERNAL");static const struct bpf_func_proto bpf_sys_bpf_proto = { .func = bpf_sys_bpf, .gpl_only = false, + .might_sleep = true, .ret_type =RET_INTEGER , .arg1_type =ARG_ANYTHING , .arg2_type =ARG_PTR_TO_MEM |MEM_RDONLY ,
Does bpf_btf_find_by_name_kind() need the same treatment?
syscall_prog_func_proto() hands out four helpers to BPF_PROG_TYPE_SYSCALL. This patch marks two of them. bpf_btf_find_by_name_kind_proto is used by nothing else in the tree, so marking it has no collateral effect on other program types.
It can sleep on the module-BTF path:
bpf_btf_find_by_name_kind()
-> bpf_find_btf_id() /* module BTF hit */
-> __btf_new_fd()
-> anon_inode_getfd()
-> alloc_file_pseudo() /* kmem_cache_zalloc(filp_cachep, GFP_KERNEL) */
-> get_unused_fd_flags()
-> alloc_fd()
-> expand_files() /* kvmalloc(GFP_KERNEL_ACCOUNT) */bpf_get_btf_vmlinux() additionally takes mutex_lock(&btf_vmlinux_lock) (that branch is unreachable at run time because bpf_check() already populated btf_vmlinux, so the fd-allocation path is the load-bearing one).
Reachability is identical to the case the changelog describes for bpf_sys_bpf(): a SEC("syscall") program can call bpf_timer_set_callback(), and the verifier checks that callback in a non-sleepable context while retaining the syscall helper set. Without .might_sleep on the prototype, the callback can invoke bpf_btf_find_by_name_kind() from hrtimer softirq context and trigger the same scheduling-while-atomic failure.
The same issue applies to bpf_rcu_read_lock(), bpf_preempt_disable(), bpf_spin_lock(), and irq-disabled regions, which in_sleepable_context() also excludes.
The fourth helper, bpf_kallsyms_lookup_name(), is genuinely non-sleeping (kallsyms_lookup_name() operates under RCU), so leaving that one unmarked is correct.
kernel/bpf/syscall.c BPF_CALL_1(bpf_sys_close, u32, fd) · line 6600
Quoted 8 lines
kernel/bpf/syscall.c +1
-0@@ -6593,6 +6594,7 @@ BPF_CALL_1(bpf_sys_close, u32, fd)static const struct bpf_func_proto bpf_sys_close_proto = { .func = bpf_sys_close, .gpl_only = false, + .might_sleep = true, .ret_type =RET_INTEGER , .arg1_type =ARG_ANYTHING , };
A subsystem pattern flags this as potentially concerning: freplace (BPF_PROG_TYPE_EXT) programs that replace a global subprog of a syscall program can no longer call bpf_sys_bpf() or bpf_sys_close(), where they could before.
An EXT program is never sleepable: can_be_sleepable() does not list BPF_PROG_TYPE_EXT, and check_attach_btf_id() rejects 'prog->sleepable && !can_be_sleepable(prog)' with 'Program of this type cannot be sleepable'. Consequently do_check_common() sets state->in_sleepable = false for every EXT program.
At the same time check_attach_btf_id() sets env->ops = bpf_verifier_ops[tgt_prog->type] for EXT and resolve_prog_type() returns the saved SYSCALL type, so such an EXT program does get syscall_prog_func_proto() and previously loaded fine with a bpf_sys_bpf() call in it.
After this patch that same program is rejected with 'sleepable helper bpf_sys_bpf#166 in non-sleepable prog', even though the replaced subprog only ever runs from the sleepable BPF_PROG_TEST_RUN body.
Mitigating context: this limitation already exists for every other might_sleep helper (e.g. bpf_copy_from_user), no in-tree selftest does freplace on a SEC("syscall") program, and rejecting is the conservative direction. Worth a sentence in the changelog since it is a user-visible loadability change?
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/33770534313
Kumar Kartikeya Dwivedi
maintainerPATCH 10/10 selftests/bpf: Check syscall helpers in timer callbacks
In reply to Kumar Kartikeya DwivediA BPF_PROG_TYPE_SYSCALL program is sleepable, but its bpf_timer callbacks run in a non-sleepable hrtimer softirq context.
Add verifier cases that call bpf_sys_bpf() and bpf_sys_close() from timer callbacks. Without the syscall helper prototype annotations these programs load, so their failure expectations expose the bug.
Also add successful controls that call each helper from the syscall program main body, ensuring that the intended sleepable use remains accepted.
--- .../bpf/progs/verifier_async_cb_context.c | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+)
tools/testing/selftests/bpf/progs/verifier_async_cb_context.c +64 -0
@@ -62,6 +62,70 @@ int timer_sleepable_prog(void *ctx)return 0 ; } +static int timer_sys_bpf_cb (void *map,int *key,struct bpf_timer *timer) +{ +__u64 attr =0 ; + +bpf_sys_bpf (BPF_MAP_FREEZE , &attr,sizeof (attr)); +return 0 ; +} + +SEC("syscall") +__failure __msg("sleepable helper bpf_sys_bpf#{{[0-9]+}} in non-sleepable prog") +int timer_sys_bpf_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_sys_bpf_cb); +return 0 ; +} + +static int timer_sys_close_cb (void *map,int *key,struct bpf_timer *timer) +{ +bpf_sys_close (0 ); +return 0 ; +} + +SEC("syscall") +__failure __msg("sleepable helper bpf_sys_close#{{[0-9]+}} in non-sleepable prog") +int timer_sys_close_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_sys_close_cb); +return 0 ; +} + +SEC("syscall") +__success +int syscall_sys_bpf_prog (void *ctx) +{ +__u64 attr =0 ; + +bpf_sys_bpf (BPF_MAP_FREEZE , &attr,sizeof (attr)); +return 0 ; +} + +SEC("syscall") +__success +int syscall_sys_close_prog (void *ctx) +{ +bpf_sys_close (0 ); +return 0 ; +} +/* Workqueue tests */ struct wq_elem {
Signature · 1 line
2.53.0
This series was applied to bpf/bpf.git (master) by Alexei Starovoitov <[email protected]>:
Hello:
This series was applied to bpf/bpf.git (master)
by Alexei Starovoitov <[email protected]>:
Quoted 16 lines · On Thu, 3 Sep 2026 16:44:18 +0200 you wrote:
A set of miscellaneous fixes for bugs reported by Nicholas. These are easy ones and should not require any major discussion, hence batched together. See commit logs and selftests for details.
Kumar Kartikeya Dwivedi (10):
bpf: Mark signal tracepoint siginfo arguments as scalar selftests/bpf: Cover signal tracepoint siginfo sentinels
bpf: Reject tail calls directly from callback frames selftests/bpf: Test direct tail calls from callbacks
bpf: Reject resilient lock operations in rbtree callbacks selftests/bpf: Reject resilient unlock in rbtree callback
bpf: Marksched_process_waitargument as nullable selftests/bpf: Testsched_process_waitnullable argument
bpf: Mark syscall helpers as sleepable selftests/bpf: Check syscall helpers in timer callbacks
Quoted 1 line
[...]
Here is the summary with links:
- [bpf,v1,01/10] bpf: Mark signal tracepoint siginfo arguments as scalar
https://git.kernel.org/bpf/bpf/c/77515ab12e49 - [bpf,v1,02/10] selftests/bpf: Cover signal tracepoint siginfo sentinels https://git.kernel.org/bpf/bpf/c/d7719a1736e6 - [bpf,v1,03/10] bpf: Reject tail calls directly from callback frames https://git.kernel.org/bpf/bpf/c/266aa4ad0b2e - [bpf,v1,04/10] selftests/bpf: Test direct tail calls from callbacks https://git.kernel.org/bpf/bpf/c/d9ae3e4c7fb5 - [bpf,v1,05/10] bpf: Reject resilient lock operations in rbtree callbacks https://git.kernel.org/bpf/bpf/c/7b7b8b596010 - [bpf,v1,06/10] selftests/bpf: Reject resilient unlock in rbtree callback https://git.kernel.org/bpf/bpf/c/08b4dc83d981 - [bpf,v1,07/10] bpf: Mark sched_process_wait argument as nullable https://git.kernel.org/bpf/bpf/c/a453d6e3b8e8 - [bpf,v1,08/10] selftests/bpf: Test sched_process_wait nullable argument https://git.kernel.org/bpf/bpf/c/c1992ba73b03 - [bpf,v1,09/10] bpf: Mark syscall helpers as sleepable https://git.kernel.org/bpf/bpf/c/d05524794240 - [bpf,v1,10/10] selftests/bpf: Check syscall helpers in timer callbacks https://git.kernel.org/bpf/bpf/c/26a3a510cd34
You are awesome, thank you!
Signature · 2 lines
Deet-doot-dot, I am a bot. https://korg.docs.kernel.org/patchwork/pwbot.html