bpf: Fix exception exit lock checking for subprogs
Subsystems (4)
Ihor Solodrai
reviewerPATCH v2 1/2 bpf: Fix exception exit lock checking for subprogs
process_bpf_exit_full() passes check_lock = !curframe to check_resource_leak(), which is false in cases when bpf_throw() is called from a static subprog. This makes check_resource_leak() to skip validation of active_rcu_locks, active_preempt_locks, and active_irq_id on exception exits from subprogs.
At runtime bpf_throw() unwinds the stack via ORC without releasing any user-acquired locks, which may cause various issues as the result.
Fix by setting check_lock = true for exception exits regardless of curframe, since exceptions bypass all intermediate frame cleanup. Update the error message prefix to "bpf_throw" for exception exits to distinguish them from normal BPF_EXIT.
Fix reject_subprog_with_rcu_read_lock test which was previously passing for the wrong reason. Test program returned directly from the subprog call without closing the RCU section, so the error was triggered by the unclosed RCU lock on normal exit, not by bpf_throw. Update __msg annotations for affected tests to match the new "bpf_throw" error prefix.
The spin_lock case is not affected because they are already checked [1] at the call site in do_check_insn() before bpf_throw can run.
---
v1->v2:
* fix reject_subprog_with_rcu_read_lock test (Kumar)
* add test case with irq lock (Kumar)
* remove reject_subprog_throw_rcu_lock test from patch #2, since it's essentially a duplicate now
v1: https://lore.kernel.org/bpf/[email protected]/
This bug was discovered by an AI bot. You can find the original AI-generated investigation here:
https://github.com/theihor/bpf/commits/ai-tinkering-20260310-0745/
--- kernel/bpf/verifier.c | 3 ++- tools/testing/selftests/bpf/progs/exceptions_fail.c | 9 ++++++--- 2 files changed, 8 insertions(+), 4 deletions(-)
kernel/bpf/verifier.c +2 -1
@@ -20911,7 +20911,8 @@ static int process_bpf_exit_full(struct bpf_verifier_env *env,* state when it exits. */ int err =check_resource_leak (env, exception_exit, - !env->cur_state->curframe, + exception_exit || !env->cur_state->curframe, + exception_exit ?"bpf_throw" :"BPF_EXIT instruction in main prog" );if (err)return err;
tools/testing/selftests/bpf/progs/exceptions_fail.c +6 -3
@@ -8,6 +8,7 @@#include "bpf_experimental.h" extern void bpf_rcu_read_lock(void) __ksym; +extern void bpf_rcu_read_unlock(void) __ksym;#define private (name) SEC(".bss." #name) __hidden __attribute__((aligned(8)))
@@ -131,7 +132,7 @@ int reject_subprog_with_lock(void *ctx) } SEC("?tc") -__failure __msg("BPF_EXIT instruction in main prog cannot be used inside bpf_rcu_read_lock-ed region") +__failure __msg("bpf_throw cannot be used inside bpf_rcu_read_lock-ed region") int reject_with_rcu_read_lock(void *ctx) {bpf_rcu_read_lock ();
@@ -147,11 +148,13 @@ __noinline static int throwing_subprog(struct __sk_buff *ctx) } SEC("?tc") -__failure __msg("BPF_EXIT instruction in main prog cannot be used inside bpf_rcu_read_lock-ed region") +__failure __msg("bpf_throw cannot be used inside bpf_rcu_read_lock-ed region") int reject_subprog_with_rcu_read_lock(void *ctx) {bpf_rcu_read_lock (); -return throwing_subprog (ctx); +throwing_subprog (ctx); +bpf_rcu_read_unlock (); +return 0 ; }static bool rbless (struct bpf_rb_node *n1,const struct bpf_rb_node *n2)
Signature · 1 line
2.53.0
Ihor Solodrai
reviewerPATCH v2 2/2 selftests/bpf: Add tests for bpf_throw lock leak from subprogs
Add test cases to ensure the verifier correctly rejects bpf_throw from subprogs when RCU, preempt, or IRQ locks are held:
* reject_subprog_rcu_lock_throw: subprog acquires bpf_rcu_read_lock and then calls bpf_throw
* reject_subprog_throw_preempt_lock: always-throwing subprog called while caller holds bpf_preempt_disable
* reject_subprog_throw_irq_lock: always-throwing subprog called while caller holds bpf_local_irq_save
--- .../selftests/bpf/progs/exceptions_fail.c | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+)
tools/testing/selftests/bpf/progs/exceptions_fail.c +47 -0
@@ -9,6 +9,10 @@ extern void bpf_rcu_read_lock(void) __ksym; extern void bpf_rcu_read_unlock(void) __ksym; +extern void bpf_preempt_disable(void) __ksym; +extern void bpf_preempt_enable(void) __ksym; +extern void bpf_local_irq_save(unsigned long *) __ksym; +extern void bpf_local_irq_restore(unsigned long *) __ksym;#define private (name) SEC(".bss." #name) __hidden __attribute__((aligned(8)))
@@ -349,4 +353,47 @@ int reject_exception_throw_cb_diff(struct __sk_buff *ctx)return 0 ; } +__noinline static int always_throws(void) +{ +bpf_throw (0 ); +return 0 ; +} + +__noinline static int rcu_lock_then_throw(void) +{ +bpf_rcu_read_lock (); +bpf_throw (0 ); +return 0 ; +} + +SEC("?tc") +__failure __msg("bpf_throw cannot be used inside bpf_rcu_read_lock-ed region") +int reject_subprog_rcu_lock_throw(void *ctx) +{ +rcu_lock_then_throw (); +return 0 ; +} + +SEC("?tc") +__failure __msg("bpf_throw cannot be used inside bpf_preempt_disable-ed region") +int reject_subprog_throw_preempt_lock(void *ctx) +{ +bpf_preempt_disable (); +always_throws (); +bpf_preempt_enable (); +return 0 ; +} + +SEC("?tc") +__failure __msg("bpf_throw cannot be used inside bpf_local_irq_save-ed region") +int reject_subprog_throw_irq_lock(void *ctx) +{ +unsigned long flags; + +bpf_local_irq_save (&flags); +always_throws (); +bpf_local_irq_restore (&flags); +return 0 ; +} + char _license[] SEC("license") = "GPL";
Signature · 1 line
2.53.0
Re: [PATCH bpf v2 2/2] selftests/bpf: Add tests for bpf_throw lock leak from subprogs
Quoted 10 lines · Ihor Solodrai
Add test cases to ensure the verifier correctly rejects
bpf_throwfrom subprogs when RCU, preempt, or IRQ locks are held:*
reject_subprog_rcu_lock_throw: subprog acquiresbpf_rcu_read_lockand then callsbpf_throw
*reject_subprog_throw_preempt_lock: always-throwing subprog called while caller holdsbpf_preempt_disable
*reject_subprog_throw_irq_lock: always-throwing subprog called while caller holdsbpf_local_irq_save
Quoted 2 lines
Assisted-by Claude:claude-opus-4-6Signed-off-by Ihor Solodrai <[email protected]>
Re: [PATCH bpf v2 2/2] selftests/bpf: Add tests for bpf_throw lock leak from subprogs
In reply to Ihor SolodraiQuoted 11 lines · Ihor Solodrai
Add test cases to ensure the verifier correctly rejects
bpf_throwfrom subprogs when RCU, preempt, or IRQ locks are held:*
reject_subprog_rcu_lock_throw: subprog acquiresbpf_rcu_read_lockand then callsbpf_throw
*reject_subprog_throw_preempt_lock: always-throwing subprog called while caller holdsbpf_preempt_disable
*reject_subprog_throw_irq_lock: always-throwing subprog called while caller holdsbpf_local_irq_save
Quoted 3 lines
Assisted-by Claude:claude-opus-4-6Signed-off-by Ihor Solodrai <[email protected]>---
Quoted 1 line
[...]
Quoted 21 lines · Ihor Solodrai
process_bpf_exit_full()passescheck_lock= !curframe tocheck_resource_leak(), which is false in cases whenbpf_throw()is called from a static subprog. This makescheck_resource_leak()to skip validation ofactive_rcu_locks,active_preempt_locks, andactive_irq_idon exception exits from subprogs.At runtime
bpf_throw()unwinds the stack via ORC without releasing any user-acquired locks, which may cause various issues as the result.Fix by setting
check_lock= true for exception exits regardless of curframe, since exceptions bypass all intermediate frame cleanup. Update the error message prefix to "bpf_throw" for exception exits to distinguish them from normalBPF_EXIT.Fix
reject_subprog_with_rcu_read_locktest which was previously passing for the wrong reason. Test program returned directly from the subprog call without closing the RCU section, so the error was triggered by the unclosed RCU lock on normal exit, not bybpf_throw. Update__msgannotations for affected tests to match the new "bpf_throw" error prefix.
Quoted 8 lines
The
spin_lockcase is not affected because they are already checked [1] at the call site indo_check_insn()beforebpf_throwcan run.Assisted-by Claude:claude-opus-4-6Fixes f18b03fabaa9 ("bpf: Implement BPF exceptions")Signed-off-by Ihor Solodrai <[email protected]>
Quoted 25 lines · Ihor Solodrai
process_bpf_exit_full()passescheck_lock= !curframe tocheck_resource_leak(), which is false in cases whenbpf_throw()is called from a static subprog. This makescheck_resource_leak()to skip validation ofactive_rcu_locks,active_preempt_locks, andactive_irq_idon exception exits from subprogs.At runtime
bpf_throw()unwinds the stack via ORC without releasing any user-acquired locks, which may cause various issues as the result.Fix by setting
check_lock= true for exception exits regardless of curframe, since exceptions bypass all intermediate frame cleanup. Update the error message prefix to "bpf_throw" for exception exits to distinguish them from normalBPF_EXIT.Fix
reject_subprog_with_rcu_read_locktest which was previously passing for the wrong reason. Test program returned directly from the subprog call without closing the RCU section, so the error was triggered by the unclosed RCU lock on normal exit, not bybpf_throw. Update__msgannotations for affected tests to match the new "bpf_throw" error prefix.The
spin_lockcase is not affected because they are already checked [1] at the call site indo_check_insn()beforebpf_throwcan run.
Quoted 7 lines
Assisted-by Claude:claude-opus-4-6Fixes f18b03fabaa9 ("bpf: Implement BPF exceptions")Signed-off-by Ihor Solodrai <[email protected]>---
Quoted 1 line
[...]
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 10 lines · On Thu, 19 Mar 2026 17:08:08 -0700 you wrote:
process_bpf_exit_full()passescheck_lock= !curframe tocheck_resource_leak(), which is false in cases whenbpf_throw()is called from a static subprog. This makescheck_resource_leak()to skip validation ofactive_rcu_locks,active_preempt_locks, andactive_irq_idon exception exits from subprogs.At runtime
bpf_throw()unwinds the stack via ORC without releasing any user-acquired locks, which may cause various issues as the result.[...]
Here is the summary with links:
- [bpf,v2,1/2] bpf: Fix exception exit lock checking for subprogs
https://git.kernel.org/bpf/bpf/c/6c2128505f61 - [bpf,v2,2/2] selftests/bpf: Add tests for bpf_throw lock leak from subprogs https://git.kernel.org/bpf/bpf/c/a1e5c46eaed3
You are awesome, thank you!
Signature · 2 lines
Deet-doot-dot, I am a bot. https://korg.docs.kernel.org/patchwork/pwbot.html