syzbot
WARNING in __batadv_dat_purge
Status:
moderation: reported on 2026/11/10 03:11
Subsystems:
batman
Labels:
prio:low
[Documentation on labels]
Reported-by: syzbot+aaff888db153ef05f947@syzkaller.appspotmail.com
First crash: 1h47m, last: 1h47m
✨ AI-generated root cause summary:
Under PREEMPT_RT kernels, __local_bh_enable_ip() enforces that hard IRQs are enabled using lockdep_assert_irqs_enabled(). When batadv_dat_purge() invokes __batadv_dat_purge(), spin_unlock_bh() calls local_bh_enable(), which trips kernel/softirq.c:289 because lockdep's per-CPU hardirqs_enabled tracker was 0 despite CPU hardware interrupts being enabled (EFLAGS.IF == 1). The lock and bottom-half operations in __batadv_dat_purge() and its callee batadv_dat_entry_put() are properly balanced, indicating lockdep's per-CPU IRQ tracking state became desynchronized from the CPU's hardware interrupt state.
▶
▼
✨ Full AI root cause analysis
Technical Root Cause and Crash Mechanism
1. Context and Involved Components
- Source Files & Functions:
- Configuration:
CONFIG_PREEMPT_RT enabled with CONFIG_LOCKDEP and CONFIG_PROVE_LOCKING enabled (CONFIG_PREEMPT_RT_NEEDS_BH_LOCK is not set).
2. Triggering Sequence and Violation
- In
batadv_dat_purge() (invoked as a delayed work item in the bat_events workqueue), __batadv_dat_purge() iterates over the hash table buckets of bat_priv->dat.hash:
for (i = 0; i < bat_priv->dat.hash->size; i++) {
head = &bat_priv->dat.hash->table[i];
list_lock = &bat_priv->dat.hash->list_locks[i];
spin_lock_bh(list_lock);
hlist_for_each_entry_safe(dat_entry, node_tmp, head, hash_entry) {
if (to_purge && !to_purge(dat_entry))
continue;
hlist_del_rcu(&dat_entry->hash_entry);
batadv_dat_entry_put(dat_entry);
}
spin_unlock_bh(list_lock);
}
- Under
PREEMPT_RT, spin_unlock_bh(list_lock) in include/linux/spinlock_rt.h expands to:
static __always_inline void spin_unlock_bh(spinlock_t *lock)
{
rt_spin_unlock(lock);
local_bh_enable();
}
local_bh_enable() calls __local_bh_enable_ip(_THIS_IP_, SOFTIRQ_DISABLE_OFFSET). At the entry of __local_bh_enable_ip() (kernel/softirq.c:289):
void __local_bh_enable_ip(unsigned long ip, unsigned int cnt)
{
bool preempt_on = preemptible();
unsigned long flags;
u32 pending;
int curcnt;
WARN_ON_ONCE(in_hardirq());
lockdep_assert_irqs_enabled();
where lockdep_assert_irqs_enabled() evaluates:
WARN_ON_ONCE(__lockdep_enabled && !this_cpu_read(hardirqs_enabled));
- As revealed in the crash register dump,
EFLAGS: 00010246 has bit 9 (IF, Interrupt Flag) set to 1, meaning hardware interrupts are indeed active on the CPU. However, lockdep's per-CPU tracking variable hardirqs_enabled read 0 (!this_cpu_read(hardirqs_enabled) was true), triggering the WARN_ON_ONCE.
3. Subsystem and Object Verification
- In
__batadv_dat_purge(), spin_lock_bh() and spin_unlock_bh() are strictly paired around each bucket.
- Any purged entries invoke
batadv_dat_entry_put(), which queues the object via kref_put() and kfree_rcu() (mm/slab_common.c:kvfree_call_rcu()), which uses raw_spin_unlock_irqrestore() and correctly maintains IRQ state flags.
- Neither
__batadv_dat_purge() nor batadv_dat_entry_put() disables hard IRQs. The assertion warning at kernel/softirq.c:289 is caused by lockdep's per-CPU hardirqs_enabled tracker becoming desynchronized from the CPU's hardware interrupt state prior to or concurrently with the bottom-half re-enabling in spin_unlock_bh().
Sample crash report:
------------[ cut here ]------------
__lockdep_enabled && !this_cpu_read(hardirqs_enabled)
WARNING: kernel/softirq.c:289 at __local_bh_enable_ip+0x1ea/0x2b0 kernel/softirq.c:289, CPU#1: kworker/u8:12/1182
Modules linked in:
CPU: 1 UID: 0 PID: 1182 Comm: kworker/u8:12 Not tainted syzkaller #0 PREEMPT_{RT,(full)}
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 11/06/2026
Workqueue: bat_events batadv_dat_purge
RIP: 0010:__local_bh_enable_ip+0x1ea/0x2b0 kernel/softirq.c:289
Code: 5b 41 5c 41 5d 41 5e 41 5f 5d c3 cc cc cc cc cc 90 0f 0b 90 e9 66 fe ff ff e8 02 78 ec 09 41 f7 c4 00 02 00 00 75 d8 eb d7 90 <0f> 0b 90 e9 92 fe ff ff 90 0f 0b 90 e9 6f ff ff ff e8 e0 77 ec 09
RSP: 0018:ffffc9000694fa30 EFLAGS: 00010246
RAX: 0000000000000000 RBX: 0000000000000200 RCX: 0000000000000046
RDX: 0000000000000001 RSI: 0000000000000200 RDI: ffffffff8b3e742e
RBP: 0000000000000400 R08: ffffffff90d5a8bf R09: 1ffffffff21ab517
R10: dffffc0000000000 R11: fffffbfff21ab518 R12: ffffffff8b3e7790
R13: dffffc0000000000 R14: ffff888061f74b90 R15: 0000000000000001
FS: 0000000000000000(0000) GS:ffff88812480e000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007fe1171a6000 CR3: 0000000034222000 CR4: 00000000003526f0
Call Trace:
<TASK>
local_bh_enable include/linux/bottom_half.h:33 [inline]
spin_unlock_bh include/linux/spinlock_rt.h:122 [inline]
__batadv_dat_purge+0x343/0x400 net/batman-adv/distributed-arp-table.c:252
batadv_dat_purge+0x20/0x70 net/batman-adv/distributed-arp-table.c:271
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>
* Struck through repros no longer work on HEAD.