BUG: workqueue leaked atomic, lock or RCU: kworker/u8:NUM[NUM] (3)

Status: moderation: reported on 2026/11/11 22:05
Subsystems: rds
Labels: prio:high
[Documentation on labels]
Reported-by: syzbot+321e79e7af2b316fb087@syzkaller.appspotmail.com
First crash: 4d01h, last: 1d00h
✨ AI-generated root cause summary:

In process_one_work(), lockdep_start_depth is recorded as lockdep_depth(current) prior to running a work item. For BH workqueue items (such as tcp_tsq_workfn), execution runs in softirq context borrowed by whatever thread happens to service softirqs (here, rds_send_worker, which already held 6 locks, making lockdep_start_depth=6). If debug_locks is disabled during execution (e.g. via debug_locks_off()), subsequent evaluations of lockdep_depth(current) return 0 due to its definition (debug_locks ? (tsk)->lockdep_depth : 0). Because process_one_work() does not check whether debug_locks is still enabled before comparing lockdep_depth(current) != lockdep_start_depth, it falsely reports that the work item leaked locks (lock=6->0).

▶ ▼ ✨ Full AI root cause analysis
Based on kernel commit d48723711bae:

Technical Root Cause Analysis

1. Relevant Subsystems, Functions, and Files


2. Background and Mechanism

BH Workqueues and Context Borrowing

In Linux kernels supporting Bottom Half (BH) workqueues, work items queued on system_bh_wq (such as TCP Small Queues work tcp_tsq_workfn) are serviced in softirq context via tasklet_action() -> workqueue_softirq_action() -> bh_worker().

Whenever bottom halves are enabled (for example, in read_unlock_bh(&sk->sk_callback_lock) inside rds_tcp_write_space()), pending softirqs can be processed synchronously in the context of the running thread. In this trace, the running thread is kworker/u8:15 executing rds_send_worker(). Prior to servicing the softirq, rds_send_worker() had already acquired 6 locks (e.g., workqueue tracking maps, RDS connection mutexes/spinlocks, socket locks).

Lockdep Tracking in process_one_work()

When bh_worker() invokes process_one_work(worker, work) to run tcp_tsq_workfn, process_one_work() performs lock debugging checks introduced for BH workqueues (commit c35aea39d1e1):

/* kernel/workqueue.c:3466 */
rcu_start_depth = rcu_preempt_depth();
lockdep_start_depth = lockdep_depth(current);
...
worker->current_func(work);
...
if (unlikely((worker->task && in_atomic()) ||
	     lockdep_depth(current) != lockdep_start_depth ||
	     rcu_preempt_depth() != rcu_start_depth)) {
	pr_err("BUG: workqueue leaked atomic, lock or RCU: %s[%d]\n"
	       "     preempt=0x%08x lock=%d->%d RCU=%d->%d workfn=%ps\n",
	       current->comm, task_pid_nr(current), preempt_count(),
	       lockdep_start_depth, lockdep_depth(current),
	       rcu_start_depth, rcu_preempt_depth(),
	       worker->current_func);
	debug_show_held_locks(current);
	dump_stack();
}
The Discrepancy (lock=6->0)
  1. Before worker->current_func(work) is called, debug_locks is 1. Since current already holds 6 locks from the outer rds_send_worker() context, lockdep_depth(current) evaluates to 6. Thus: $$\text{lockdep_start_depth} = 6$$
  2. While tcp_tsq_workfn() is running (or due to an external condition such as an unhandled lockdep error, warning, or kernel taint/softlockup), debug_locks_off() is called, setting debug_locks = 0.
  3. In include/linux/lockdep.h:
    #define lockdep_depth(tsk) (debug_locks ? (tsk)->lockdep_depth : 0)
    
    When debug_locks is 0, lockdep_depth(current) unconditionally returns 0.
  4. When process_one_work() checks whether locks were leaked:
    lockdep_depth(current) != lockdep_start_depth  // evaluates to 0 != 6 -> true
    
    Because process_one_work() does not verify whether debug_locks is still active, it mistakes the global disabling of lockdep for an unlock imbalance (lock=6->0), prints the false-positive splat:
    BUG: workqueue leaked atomic, lock or RCU: kworker/u8:15[1533]
         preempt=0x00000000 lock=6->0 RCU=2->2 workfn=tcp_tsq_workfn
    
    and subsequently calls debug_show_held_locks(current), which notes that lockdep was turned off:
    INFO: lockdep is turned off.
    

3. Invariant Violated

process_one_work() assumes that lockdep_depth(current) accurately reflects the number of held locks across the invocation of worker->current_func(). This invariant is violated when debug_locks transitions from 1 to 0 during execution, which collapses lockdep_depth() to 0 and triggers a spurious leak report.

▶ ▼ ✨ AI Jobs (2)
ID Workflow Result Correct Bug Created Started Finished Revision
7c663950 repro-c 💥 BUG: workqueue leaked atomic, lock or RCU: kworker/u8:NUM[NUM] (3) 2026/11/10 06:09 2026/11/10 06:09 2026/11/10 13:16 01625235
240cff46 assessment-security DenialOfService: ❌ Exploitable: ❌ FilesystemTrigger: ❌ FuzzingArtifact: ❌ MemoryCorruption: ❌ NetworkNeedsSetup: ❌ NetworkTrigger: ❌ PeripheralTrigger: ❌ RemoteTrigger: ❌ Unprivileged: ✅ UserNamespace: ✅ VMGuestTrigger: ❌ VMHostTrigger: ❌ ❓ BUG: workqueue leaked atomic, lock or RCU: kworker/u8:NUM[NUM] (3) 2026/11/07 21:09 2026/11/07 21:10 2026/11/07 21:53 5cecaf1f
▶ ▼ Similar bugs (2)
Kernel Title Rank 🛈 Repro Cause bisect Fix bisect Count Last Reported Patched Status
upstream BUG: workqueue leaked atomic, lock or RCU: kworker/u8:NUM[NUM] batman -1 1 491d 487d 0/29 auto-obsoleted due to no activity on 2025/10/06 01:42
upstream BUG: workqueue leaked atomic, lock or RCU: kworker/u8:NUM[NUM] (2) prio:low arm -1 2 135d 199d 0/29 auto-obsoleted due to no activity on 2026/09/27 10:48

Sample crash report:
BUG: workqueue leaked atomic, lock or RCU: kworker/u8:1[14]
     preempt=0x00000000 lock=5->0 RCU=2->2 workfn=usb_giveback_urb_bh
INFO: lockdep is turned off.
CPU: 0 UID: 0 PID: 14 Comm: kworker/u8:1 Not tainted syzkaller #0 PREEMPT_{RT,(full)} 
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 11/06/2026
Workqueue: events_unbound macvlan_process_broadcast
Call Trace:
 <TASK>
 dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
 process_one_work kernel/workqueue.c:3516 [inline]
 process_scheduled_works+0x12c2/0x1630 kernel/workqueue.c:3578
 bh_worker+0x4b3/0xbb0 kernel/workqueue.c:3885
 tasklet_action+0xc/0x70 kernel/softirq.c:1022
 handle_softirqs+0x1d4/0x6c0 kernel/softirq.c:669
 __do_softirq kernel/softirq.c:703 [inline]
 __local_bh_enable_ip+0x16f/0x2b0 kernel/softirq.c:325
 local_bh_enable include/linux/bottom_half.h:33 [inline]
 spin_unlock_bh include/linux/spinlock_rt.h:122 [inline]
 macvlan_process_broadcast+0x26e/0x6a0 drivers/net/macvlan.c:338
 process_one_work kernel/workqueue.c:3495 [inline]
 process_scheduled_works+0xc3d/0x1630 kernel/workqueue.c:3578
 worker_thread+0xa47/0xfb0 kernel/workqueue.c:3659
 kthread+0x38b/0x470 kernel/kthread.c:436
 ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158
 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
 </TASK>

Crashes (3):
Time Kernel Commit Syzkaller Config Log Report Syz repro C repro VM info Assets (help?) Manager Title
2026/11/10 22:06 upstream 067c5c24cef2 d3056657 .config console log report info [disk image] [vmlinux] [kernel image] ci-upstream-kasan-gce-smack-root BUG: workqueue leaked atomic, lock or RCU: kworker/u8:NUM[NUM]
2026/11/07 21:09 upstream d48723711bae 5cecaf1f .config console log report info [disk image] [vmlinux] [kernel image] ci-upstream-kasan-gce-smack-root BUG: workqueue leaked atomic, lock or RCU: kworker/u8:NUM[NUM]
2026/11/09 17:22 linux-next 630059a5a632 5cecaf1f .config console log report info [disk image] [vmlinux] [kernel image] ci-upstream-rust-kasan-gce BUG: workqueue leaked atomic, lock or RCU: kworker/u8:NUM[NUM]
* Struck through repros no longer work on HEAD.