| 0/0 |
2026/05/30 11:38 |
flow |
assessment-security |
57m
Results: map[DenialOfService:true Explanation:**Analysis of the Bug Report**
**Root Cause:**
The bug report indicates an RCU stall and a softlockup occurring on CPU 0. The
stack trace shows that the CPU is stuck processing softirqs (`do_softirq`,
`net_rx_action`, `process_backlog`, etc.) which were triggered by
`spin_unlock_bh` inside `__batadv_dat_purge` (in
`net/batman-adv/distributed-arp-table.c`).
`__batadv_dat_purge` iterates over the Distributed ARP Table (DAT) hash table,
which is initialized with 1024 buckets. For each bucket, it acquires and
releases a spinlock using `spin_lock_bh()` and `spin_unlock_bh()`.
The `spin_unlock_bh()` function enables softirqs and immediately processes any
pending ones. If there is a continuous stream of network packets (a packet
storm), the softirq processing (`net_rx_action`) will consume its maximum time
slice (usually 2ms, but can be significantly longer with debugging tools like
KASAN enabled) on *every single iteration* of the 1024-bucket loop.
This causes the `__batadv_dat_purge` function to monopolize the CPU for an
extended period (e.g., 1024 * 2ms = ~2 seconds minimum, or much longer with
KASAN), starving other tasks such as the RCU grace-period kthread, ultimately
leading to the reported RCU stall and softlockup.
The syzbot log explicitly shows `bridge0: received packet on bridge_slave_0 with
own address as source address`, which indicates that a layer 2 bridging loop was
created, causing the packet storm that triggered this behavior.
**Security Impact:**
* **Exploitable:** No. This is a CPU monopolization issue leading to a
softlockup. It does not cause memory corruption, out-of-bounds accesses, or
use-after-free conditions, so it cannot be exploited for privilege escalation or
arbitrary code execution.
* **Denial Of Service:** Yes. The bug causes the kernel to stall and trigger
softlockups/RCU stalls, effectively freezing the affected CPU and leading to a
system crash or unresponsiveness.
* **Accessible From Unprivileged Processes:** No. Creating a `batadv`
(batman-adv) interface requires `CAP_NET_ADMIN` network capabilities, which a
completely unprivileged user does not have.
* **Accessible From User Namespaces:** Yes. A local user can create a new user
namespace, acquire `CAP_NET_ADMIN` within that namespace, set up a `batadv`
interface, and create a bridging loop using `veth` pairs to trigger the packet
storm and the subsequent softlockup.
* **Ethernet Network Trigger:** Yes. If a system is already configured to use
batman-adv on a local network, an attacker on the same layer 2 network can
trigger this DoS by intentionally causing a bridging loop or by flooding the
interface with a massive packet storm, forcing the periodic `batadv_dat_purge`
worker to stall the CPU.
* **VM Guest / Host / Peripheral / Filesystem Trigger:** No. This is strictly a
network stack and softirq processing issue. Exploitable:false FilesystemTrigger:false NetworkTrigger:true PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
|
| 1/1 |
2026/05/30 11:38 |
action |
syz-repro-to-c-repro |
0m
Results:
|
| 2/1 |
2026/05/30 11:38 |
action |
kernel-checkouter |
1m
Results: map[KernelSrc:/app/workdir/cache/src/853ce7bb1c2beec9acd77139ab72cccc46e1879b]
|
| 3/1 |
2026/05/30 11:40 |
action |
kernel-builder |
29m
Results: map[KernelObj:/app/workdir/cache/build/093a3aed15d3dfd34f4b0d4781823e00ea31d823]
|
| 4/1 |
2026/05/30 12:09 |
action |
codesearch-prepare |
19m
Results: map[Index:codesearch-index]
|
| 5/1 |
2026/05/30 12:28 |
agent |
expert |
7m
Model:
Results: map[DenialOfService:true Exploitable:false FilesystemTrigger:false NetworkTrigger:true PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
Instruction: You are an experienced Linux kernel security engineer. Your task is to analyze given kernel bug report
and determine its security impact based on the following dimensions.
Use the provided tools to examine the source code, check for capability checks (e.g., capable(), ns_capable()),
and understand the nature of the bug. Analyze the given kernel build and configuration.
You can check the kernel config by grepping ".config" file; you can check kernel cmdline by grepping
".config" file for "CONFIG_CMDLINE=". Assume sysctl parameters have default values.
But analyze for the corresponding production build w/o debugging tools enabled (like KASAN, KMSAN, UBSAN).
Try different strategies when analyzing the bug:
- think of ways in which the vulnerable code is unreachable
- or the other way around: try to come up with different ideas of how an unprivileged user can reach the bug
If still unsure err on the side of the bug being non-exploitable/not-accessible.
In the final reply, provide a reasoning for your assessment.
Analysis dimensions:
* Exploitable:
Determine if the bug can result in memory corruption or elevated privileges.
Memory safety issues are almost always exploitable (KASAN or UBSAN reports for use-after-free, out-of-bounds;
refcounting issues, corrupted lists, etc). When kernel is crashing on a completely wild pointer access
(e.g. user-space address, or non-canonical address, but not on NULL or address corresponding to KASAN shadow
for NULL address), including both data accesses and control transfers, that also usually implies possibility
of exploitation. Such reports usually say "unable to handle kernel paging request".
Uses of uninitialized values detected by KMSAN may be exploitable b/c attacker frequently can affect uninit
values with spraying techniques. However, for these exploitability depends on how exactly the uninit value
is used in the code, and what it affects.
Think of what happens after the bug is triggered. Some bugs cause kernel panic and halt execution,
they are harder to exploit. For example, BUG reports halts the kernel. However, WARNING reports don't halt
execution in production builds. Debug bug detection tools (like KASAN, KMSAN, KCSAN, UBSAN) are also not enabled
in production builds, so attacker can freely exploit these bugs w/o being detected by these tools.
If you see an integer overflow, think how the overflowed value used later (if it's used as allocation size,
or an array index). If you see an out-of-bounds read, think if it's followed by an out-of-bounds write as well.
Some KCSAN data-races may be exploitable by skilled attackers as well. Think what data structures got corrupted
as the result of data races and how. However, note that kernel has lots of "benign" data races that don't lead
to any runtime misbehavior at all.
* Denial Of Service:
Determine if the bug can result in denial-of-service. Most bugs can, since they cause system crash,
hangs, deadlocks, or resource leaks. This is mostly applicable to WARNING bugs that won't cause system crash
in production. For these think what will be consequences of the violation of the kernel assumptions flagged
by the WARNING. In some cases the unexpected condition is also properly handled by the normal control flow
(e.g. with "if (WARN_ON(...))"), these won't cause denial-of-service. If the condition is not handled,
then it may or may not cause denial-of-service.
* Accessible From Unprivileged Processes:
Determine if the bug can be reached from a typical (non-root) user process that does NOT have any special capabilities
(like CAP_SYS_ADMIN, CAP_NET_ADMIN, CAP_NET_RAW, CAP_PERFMON) or access to device nodes restricted to root.
Assume that unprivileged_bpf_disabled=1, that is eBPF loading is not accessible. However, cBPF (classical BPF)
is still accessible to non-root processes.
Assume that user namespaces are not accessible, that is, the process cannot get the mentioned capabilities even
within a new user namespace (checked by ns_capable() function in the kernel sources).
* Accessible From User Namespaces:
Determine if the bug can be reached within a user-namespace where the process has all capabilities
(including CAP_SYS_ADMIN, CAP_NET_ADMIN, CAP_NET_RAW, CAP_PERFMON). Such capabilities are checked with ns_capable()
function in the kernel sources.
* VM Guest Trigger:
Determine if the bug can be triggered from the context of a typical KVM guest (e.g., set up by a QEMU VMM).
Consider accesses to standard Linux host paravirtualized features (virtio-blk, virtio-net, etc.),
and handling of VM exits in the KVM code.
* VM Host Trigger in The Confidential Computing Context:
Determine if the bug can be triggered in a confidential computing guest kernel from the context of a KVM host.
Consider access to standard Linux guest paravirtualized features (virtio-blk, virtio-net, etc.).
* Ethernet Network Trigger:
Determine if the bug can be triggered by processing ingress network Ethernet traffic, either directly (network stack)
or via drivers exposed to network data.
* Other Remote Trigger:
Determine if the bug can be triggered by processing remote traffic other than Ethernet (Wifi, Bluetooth, NFC, etc).
* Peripheral Trigger:
Determine if the bug can be triggered via an untrusted peripheral device that can be physically plugged
into a system, such as a USB device or a niche hardware driver handling external hardware inputs.
This is particularly important for mobile and desktop environments where users can plug in unknown devices.
* Malicious Filesystem Trigger:
Determine if the bug can be triggered by the kernel mounting and parsing a malicious filesystem image.
This is highly critical for Desktop and Mobile environments where external media or downloaded images
might be auto-mounted.
Don't make assumptions about the kernel source code (it may be different from what you assume it is).
Extensively use the provided code access tools (codesearch-*, git-*, grepper, etc)
to examine the actual source code, and confirm any assumptions.
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt:
The kernel bug report is:
bridge0: received packet on bridge_slave_0 with own address as source address (addr:aa:aa:aa:aa:aa:0c, vlan:0)
bridge0: received packet on veth0_to_bridge with own address as source address (addr:aa:aa:aa:aa:aa:0c, vlan:0)
rcu: INFO: rcu_preempt detected stalls on CPUs/tasks:
rcu: Tasks blocked on level-0 rcu_node (CPUs 0-1): P9050/1:b..l P5842/1:b..l
rcu: (detected by 0, t=10502 jiffies, g=76773, q=665 ncpus=1)
task:syz-executor state:R running task stack:23704 pid:5842 tgid:5842 ppid:5835 task_flags:0x400140 flags:0x00080000
Call Trace:
<TASK>
context_switch kernel/sched/core.c:5387 [inline]
__schedule+0x10e9/0x6820 kernel/sched/core.c:7188
preempt_schedule_common+0x42/0xc0 kernel/sched/core.c:7369
preempt_schedule_thunk+0x16/0x30 arch/x86/entry/thunk.S:12
__raw_spin_unlock include/linux/spinlock_api_smp.h:169 [inline]
_raw_spin_unlock+0x3e/0x50 kernel/locking/spinlock.c:190
spin_unlock include/linux/spinlock.h:390 [inline]
copy_pte_range mm/memory.c:1331 [inline]
copy_pmd_range mm/memory.c:1392 [inline]
copy_pud_range mm/memory.c:1429 [inline]
copy_p4d_range mm/memory.c:1453 [inline]
copy_page_range+0x1c02/0x5b00 mm/memory.c:1539
dup_mmap+0xd25/0x2180 mm/mmap.c:1840
dup_mm kernel/fork.c:1534 [inline]
copy_mm kernel/fork.c:1586 [inline]
copy_process+0x7b37/0x7fa0 kernel/fork.c:2262
kernel_clone+0x12e/0x9c0 kernel/fork.c:2723
__do_sys_clone+0xd9/0x120 kernel/fork.c:2864
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x10b/0xf80 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7ff3d47c5292
RSP: 002b:00007ffc7ebab010 EFLAGS: 00000246 ORIG_RAX: 0000000000000038
RAX: ffffffffffffffda RBX: 00007ffc7ebab010 RCX: 00007ff3d47c5292
RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000001200011
RBP: 00007ffc7ebab19c R08: 0000000000000000 R09: 0000000000000001
R10: 00005555668a37d0 R11: 0000000000000246 R12: 0000000000000001
R13: 00000000000927c0 R14: 000000000004c3fd R15: 00007ffc7ebab1f0
</TASK>
task:syz.2.952 state:R running task stack:24424 pid:9050 tgid:9044 ppid:5840 task_flags:0x440040 flags:0x00080000
Call Trace:
<TASK>
context_switch kernel/sched/core.c:5387 [inline]
__schedule+0x10e9/0x6820 kernel/sched/core.c:7188
preempt_schedule_irq+0x50/0x90 kernel/sched/core.c:7512
irqentry_exit_to_kernel_mode_preempt include/linux/irq-entry-common.h:476 [inline]
irqentry_exit_to_kernel_mode include/linux/irq-entry-common.h:547 [inline]
irqentry_exit+0x1fe/0x790 kernel/entry/common.c:164
asm_sysvec_apic_timer_interrupt+0x1a/0x20 arch/x86/include/asm/idtentry.h:697
RIP: 0010:raw_atomic_fetch_add_unless include/linux/atomic/atomic-arch-fallback.h:2429 [inline]
RIP: 0010:raw_atomic_add_unless include/linux/atomic/atomic-arch-fallback.h:2456 [inline]
RIP: 0010:atomic_add_unless include/linux/atomic/atomic-instrumented.h:1518 [inline]
RIP: 0010:page_ref_add_unless_zero include/linux/page_ref.h:233 [inline]
RIP: 0010:get_page_unless_zero include/linux/mm.h:1783 [inline]
RIP: 0010:brd_lookup_page drivers/block/brd.c:65 [inline]
RIP: 0010:brd_rw_bvec drivers/block/brd.c:149 [inline]
RIP: 0010:brd_submit_bio+0x435/0x1cd0 drivers/block/brd.c:214
Code: fc 4c 89 f8 48 c1 e8 03 42 0f b6 14 28 4c 89 f8 83 e0 07 83 c0 03 38 d0 7c 08 84 d2 0f 85 61 15 00 00 48 89 5c 24 10 8b 6b 34 <e8> 06 07 ca fb 31 ff 89 ee 41 89 ee e8 aa 01 ca fb 85 ed 0f 84 b4
RSP: 0018:ffffc90003d8e7e8 EFLAGS: 00000246
RAX: 0000000000000007 RBX: ffffea00014d2dc0 RCX: ffffffff863ebfec
RDX: 0000000000000000 RSI: 0000000000000004 RDI: ffffea00014d2df4
RBP: 0000000000000001 R08: 0000000000000001 R09: fffff9400029a5be
R10: ffffea00014d2df7 R11: 0000000000000000 R12: 0000000000000085
R13: dffffc0000000000 R14: ffffea0000d65f40 R15: ffffea00014d2df4
__submit_bio+0x419/0x6c0 block/blk-core.c:644
__submit_bio_noacct block/blk-core.c:690 [inline]
submit_bio_noacct_nocheck+0x730/0xbf0 block/blk-core.c:755
submit_bio_noacct+0xd18/0x2000 block/blk-core.c:884
mpage_bio_submit_read fs/mpage.c:74 [inline]
mpage_readahead+0x534/0x740 fs/mpage.c:379
read_pages+0x1b1/0xdf0 mm/readahead.c:163
page_cache_ra_order+0x76b/0xf10 mm/readahead.c:521
do_sync_mmap_readahead mm/filemap.c:3406 [inline]
filemap_fault+0x1900/0x2e90 mm/filemap.c:3555
__do_fault+0x10b/0x440 mm/memory.c:5458
do_read_fault mm/memory.c:5893 [inline]
do_fault+0xa99/0x1750 mm/memory.c:6027
do_pte_missing mm/memory.c:4550 [inline]
handle_pte_fault mm/memory.c:6411 [inline]
__handle_mm_fault+0x187d/0x2a00 mm/memory.c:6549
handle_mm_fault+0x36d/0xa20 mm/memory.c:6718
faultin_page mm/gup.c:1126 [inline]
__get_user_pages+0x1178/0x32a0 mm/gup.c:1428
__get_user_pages_locked mm/gup.c:1692 [inline]
__gup_longterm_locked+0x87d/0x16f0 mm/gup.c:2476
gup_fast_fallback+0x16dc/0x2790 mm/gup.c:3209
pin_user_pages_fast+0xa7/0xf0 mm/gup.c:3315
iov_iter_extract_user_pages lib/iov_iter.c:1763 [inline]
iov_iter_extract_pages+0xa0d/0x1ef0 lib/iov_iter.c:1826
iov_iter_extract_bvecs+0x10b/0xa60 lib/iov_iter.c:1917
bio_iov_iter_get_pages+0x26a/0x970 block/bio.c:1265
blkdev_iov_iter_get_pages block/fops.c:49 [inline]
__blkdev_direct_IO block/fops.c:219 [inline]
blkdev_direct_IO+0x1302/0x1fb0 block/fops.c:436
blkdev_direct_write block/fops.c:722 [inline]
blkdev_write_iter+0x703/0xd70 block/fops.c:790
new_sync_write fs/read_write.c:595 [inline]
vfs_write+0x6ac/0x1070 fs/read_write.c:688
ksys_write+0x12a/0x250 fs/read_write.c:740
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x10b/0xf80 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7fad4179c819
RSP: 002b:00007fad426c6028 EFLAGS: 00000246 ORIG_RAX: 0000000000000001
RAX: ffffffffffffffda RBX: 00007fad41a16180 RCX: 00007fad4179c819
RDX: 00000000fffffdef RSI: 0000000000000000 RDI: 0000000000000003
RBP: 00007fad41832c91 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007fad41a16218 R14: 00007fad41a16180 R15: 00007ffda8159428
</TASK>
rcu: rcu_preempt kthread starved for 580 jiffies! g76773 f0x0 RCU_GP_WAIT_FQS(5) ->state=0x0 ->cpu=0
rcu: Unless rcu_preempt kthread gets sufficient CPU time, OOM is now expected behavior.
rcu: RCU grace-period kthread stack dump:
task:rcu_preempt state:R running task stack:27752 pid:16 tgid:16 ppid:2 task_flags:0x208040 flags:0x00080000
Call Trace:
<TASK>
context_switch kernel/sched/core.c:5387 [inline]
__schedule+0x10e9/0x6820 kernel/sched/core.c:7188
__schedule_loop kernel/sched/core.c:7267 [inline]
schedule+0xdd/0x390 kernel/sched/core.c:7282
schedule_timeout+0x127/0x280 kernel/time/sleep_timeout.c:99
rcu_gp_fqs_loop+0x1a9/0x900 kernel/rcu/tree.c:2095
rcu_gp_kthread+0x179/0x230 kernel/rcu/tree.c:2297
kthread+0x370/0x450 kernel/kthread.c:436
ret_from_fork+0x72b/0xd50 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
</TASK>
rcu: Stack dump where RCU GP kthread last ran:
CPU: 0 UID: 0 PID: 3425 Comm: kworker/R-bat_e Tainted: G L syzkaller #0 PREEMPT(full)
Tainted: [L]=SOFTLOCKUP
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 04/18/2026
Workqueue: bat_events batadv_dat_purge
RIP: 0010:lock_acquire+0x5e/0x370 kernel/locking/lockdep.c:5872
Code: 05 3b 19 2f 12 83 f8 07 0f 87 d9 02 00 00 48 0f a3 05 46 88 f7 0e 0f 82 a4 02 00 00 8b 35 ae bb f7 0e 85 f6 0f 85 bf 00 00 00 <48> 8b 44 24 30 65 48 2b 05 dd 18 2f 12 0f 85 ed 02 00 00 48 83 c4
RSP: 0018:ffffc900000075a0 EFLAGS: 00000206
RAX: 0000000000000046 RBX: 0000000000000000 RCX: 0000000000000003
RDX: 0000000000000000 RSI: ffffffff8df30545 RDI: ffffffff8c1c0c00
RBP: ffffffff8e7e5260 R08: 0000000006fa0785 R09: 0000000000000007
R10: 0000000000000200 R11: 0000000000000000 R12: 0000000000000002
R13: 0000000000000000 R14: 0000000000000000 R15: 0000000000000000
FS: 0000000000000000(0000) GS:ffff8881242e2000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 000055e495b46dc8 CR3: 000000007cbe6000 CR4: 00000000003526f0
Call Trace:
<IRQ>
rcu_lock_acquire include/linux/rcupdate.h:300 [inline]
rcu_read_lock include/linux/rcupdate.h:838 [inline]
class_rcu_constructor include/linux/rcupdate.h:1181 [inline]
unwind_next_frame+0xd1/0x2090 arch/x86/kernel/unwind_orc.c:495
arch_stack_walk+0x94/0xf0 arch/x86/kernel/stacktrace.c:25
stack_trace_save+0x8e/0xc0 kernel/stacktrace.c:122
kasan_save_stack+0x30/0x50 mm/kasan/common.c:57
kasan_save_track+0x14/0x30 mm/kasan/common.c:78
kasan_save_free_info+0x3b/0x70 mm/kasan/generic.c:584
poison_slab_object mm/kasan/common.c:253 [inline]
__kasan_slab_free+0x5f/0x80 mm/kasan/common.c:285
kasan_slab_free include/linux/kasan.h:235 [inline]
slab_free_hook mm/slub.c:2689 [inline]
slab_free mm/slub.c:6246 [inline]
kmem_cache_free+0x127/0x6c0 mm/slub.c:6373
kfree_skbmem+0x19a/0x210 net/core/skbuff.c:1137
__kfree_skb net/core/skbuff.c:1204 [inline]
sk_skb_reason_drop+0x10f/0x1b0 net/core/skbuff.c:1241
kfree_skb_reason include/linux/skbuff.h:1322 [inline]
kfree_skb include/linux/skbuff.h:1331 [inline]
ip6_mc_input+0x832/0xf50 net/ipv6/ip6_input.c:633
dst_input include/net/dst.h:480 [inline]
ip6_rcv_finish net/ipv6/ip6_input.c:119 [inline]
NF_HOOK include/linux/netfilter.h:318 [inline]
NF_HOOK include/linux/netfilter.h:312 [inline]
ipv6_rcv+0x34c/0x3d0 net/ipv6/ip6_input.c:351
__netif_receive_skb_one_core+0x12d/0x1e0 net/core/dev.c:6209
__netif_receive_skb+0x1f/0x120 net/core/dev.c:6322
process_backlog+0x37a/0x1580 net/core/dev.c:6673
__napi_poll.constprop.0+0xaf/0x450 net/core/dev.c:7737
napi_poll net/core/dev.c:7800 [inline]
net_rx_action+0xa40/0xf20 net/core/dev.c:7957
handle_softirqs+0x1ea/0xa00 kernel/softirq.c:622
do_softirq kernel/softirq.c:523 [inline]
do_softirq+0xac/0xe0 kernel/softirq.c:510
</IRQ>
<TASK>
__local_bh_enable_ip+0xf8/0x120 kernel/softirq.c:450
spin_unlock_bh include/linux/spinlock.h:396 [inline]
__batadv_dat_purge.part.0+0x294/0x3c0 net/batman-adv/distributed-arp-table.c:185
__batadv_dat_purge net/batman-adv/distributed-arp-table.c:166 [inline]
batadv_dat_purge+0x4b/0xa0 net/batman-adv/distributed-arp-table.c:204
process_one_work+0xa0e/0x1980 kernel/workqueue.c:3302
process_scheduled_works kernel/workqueue.c:3385 [inline]
rescuer_thread+0x905/0x14a0 kernel/workqueue.c:3609
kthread+0x370/0x450 kernel/kthread.c:436
ret_from_fork+0x72b/0xd50 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
</TASK>
net_ratelimit: 3710 callbacks suppressed
bridge0: received packet on bridge_slave_0 with own address as source address (addr:aa:aa:aa:aa:aa:0c, vlan:0)
bridge0: received packet on veth0_to_bridge with own address as source address (addr:aa:aa:aa:aa:aa:0c, vlan:0)
bridge0: received packet on bridge_slave_0 with own address as source address (addr:aa:aa:aa:aa:aa:1b, vlan:0)
bridge0: received packet on veth0_to_bridge with own address as source address (addr:46:d1:c2:cc:fe:13, vlan:0)
bridge0: received packet on bridge_slave_0 with own address as source address (addr:aa:aa:aa:aa:aa:0c, vlan:0)
bridge0: received packet on veth0_to_bridge with own address as source address (addr:aa:aa:aa:aa:aa:0c, vlan:0)
bridge0: received packet on bridge_slave_0 with own address as source address (addr:aa:aa:aa:aa:aa:1b, vlan:0)
bridge0: received packet on veth0_to_bridge with own address as source address (addr:46:d1:c2:cc:fe:13, vlan:0)
bridge0: received packet on bridge_slave_0 with own address as source address (addr:aa:aa:aa:aa:aa:0c, vlan:0)
bridge0: received packet on veth0_to_bridge with own address as source address (addr:aa:aa:aa:aa:aa:0c, vlan:0)
net_ratelimit: 5154 callbacks suppressed
bridge0: received packet on bridge_slave_0 with own address as source address (addr:aa:aa:aa:aa:aa:0c, vlan:0)
bridge0: received packet on veth0_to_bridge with own address as source address (addr:aa:aa:aa:aa:aa:0c, vlan:0)
bridge0: received packet on bridge_slave_0 with own address as source address (addr:aa:aa:aa:aa:aa:1b, vlan:0)
bridge0: received packet on veth0_to_bridge with own address as source address (addr:46:d1:c2:cc:fe:13, vlan:0)
bridge0: received packet on bridge_slave_0 with own address as source address (addr:aa:aa:aa:aa:aa:0c, vlan:0)
bridge0: received packet on veth0_to_bridge with own address as source address (addr:aa:aa:aa:aa:aa:0c, vlan:0)
bridge0: received packet on bridge_slave_0 with own address as source address (addr:aa:aa:aa:aa:aa:1b, vlan:0)
bridge0: received packet on veth0_to_bridge with own address as source address (addr:46:d1:c2:cc:fe:13, vlan:0)
bridge0: received packet on bridge_slave_0 with own address as source address (addr:aa:aa:aa:aa:aa:0c, vlan:0)
bridge0: received packet on veth0_to_bridge with own address as source address (addr:aa:aa:aa:aa:aa:0c, vlan:0)
Reply: **Analysis of the Bug Report**
**Root Cause:**
The bug report indicates an RCU stall and a softlockup occurring on CPU 0. The stack trace shows that the CPU is stuck processing softirqs (`do_softirq`, `net_rx_action`, `process_backlog`, etc.) which were triggered by `spin_unlock_bh` inside `__batadv_dat_purge` (in `net/batman-adv/distributed-arp-table.c`).
`__batadv_dat_purge` iterates over the Distributed ARP Table (DAT) hash table, which is initialized with 1024 buckets. For each bucket, it acquires and releases a spinlock using `spin_lock_bh()` and `spin_unlock_bh()`.
The `spin_unlock_bh()` function enables softirqs and immediately processes any pending ones. If there is a continuous stream of network packets (a packet storm), the softirq processing (`net_rx_action`) will consume its maximum time slice (usually 2ms, but can be significantly longer with debugging tools like KASAN enabled) on *every single iteration* of the 1024-bucket loop.
This causes the `__batadv_dat_purge` function to monopolize the CPU for an extended period (e.g., 1024 * 2ms = ~2 seconds minimum, or much longer with KASAN), starving other tasks such as the RCU grace-period kthread, ultimately leading to the reported RCU stall and softlockup.
The syzbot log explicitly shows `bridge0: received packet on bridge_slave_0 with own address as source address`, which indicates that a layer 2 bridging loop was created, causing the packet storm that triggered this behavior.
**Security Impact:**
* **Exploitable:** No. This is a CPU monopolization issue leading to a softlockup. It does not cause memory corruption, out-of-bounds accesses, or use-after-free conditions, so it cannot be exploited for privilege escalation or arbitrary code execution.
* **Denial Of Service:** Yes. The bug causes the kernel to stall and trigger softlockups/RCU stalls, effectively freezing the affected CPU and leading to a system crash or unresponsiveness.
* **Accessible From Unprivileged Processes:** No. Creating a `batadv` (batman-adv) interface requires `CAP_NET_ADMIN` network capabilities, which a completely unprivileged user does not have.
* **Accessible From User Namespaces:** Yes. A local user can create a new user namespace, acquire `CAP_NET_ADMIN` within that namespace, set up a `batadv` interface, and create a bridging loop using `veth` pairs to trigger the packet storm and the subsequent softlockup.
* **Ethernet Network Trigger:** Yes. If a system is already configured to use batman-adv on a local network, an attacker on the same layer 2 network can trigger this DoS by intentionally causing a bridging loop or by flooding the interface with a massive packet storm, forcing the periodic `batadv_dat_purge` worker to stall the CPU.
* **VM Guest / Host / Peripheral / Filesystem Trigger:** No. This is strictly a network stack and softirq processing issue.
|
| 6/2 |
2026/05/30 12:28 |
llm |
expert |
0m
Model:
Tokens:
input: 12389
output: 42
thoughts: 1441
Thoughts: **Analyzing an RCU Stall in Batman-adv**
Okay, so this bug report is pointing to an RCU stall. The kernel is complaining about `rcu_preempt` detecting stalls on CPUs and tasks, specifically mentioning CPU 0 being stuck in a softlockup. The RCU kthread has been starved for a significant number of jiffies. We need to figure out what's blocking RCU.
The stack trace for CPU 0 looks interesting. It shows an RCU lock acquisition (`rcu_read_lock`), and then leads into a chain involving `kfree_skb`, IP6 processing, and ultimately, `process_backlog` and `net_rx_action`. Then, there's a softirq context, where it goes into `__local_bh_enable_ip`, `spin_unlock_bh`, and then `__batadv_dat_purge`.
Hold on... the softlockup is *inside* `__batadv_dat_purge`! That's a red flag. Batman-adv is a mesh networking protocol, and `dat` likely relates to the Distributed ARP Table. The backtrace indicates that freeing a SKB is involved in `__batadv_dat_purge`, this suggests there may be a resource contention within the RCU critical section that is causing it to stall and become a softlockup. I need to examine `net/batman-adv/distributed-arp-table.c` to see what's happening around line 185, where the backtrace points the finger. There is potentially a race condition or a deadlock in the dat purge routine that is affecting RCU. We need to identify any potential lock contention or long-running operations within that critical section, or potentially an issue with SKB processing within the dat purge routine.
|
| 7/2 |
2026/05/30 12:29 |
tool |
read-file |
0m
Args: map[File:net/batman-adv/distributed-arp-table.c FirstLine:160 LineCount:50]
Results: map[Contents: 160: spinlock_t *list_lock; /* protects write access to the hash lists */
161: struct batadv_dat_entry *dat_entry;
162: struct hlist_node *node_tmp;
163: struct hlist_head *head;
164: u32 i;
165:
166: if (!bat_priv->dat.hash)
167: return;
168:
169: for (i = 0; i < bat_priv->dat.hash->size; i++) {
170: head = &bat_priv->dat.hash->table[i];
171: list_lock = &bat_priv->dat.hash->list_locks[i];
172:
173: spin_lock_bh(list_lock);
174: hlist_for_each_entry_safe(dat_entry, node_tmp, head,
175: hash_entry) {
176: /* if a helper function has been passed as parameter,
177: * ask it if the entry has to be purged or not
178: */
179: if (to_purge && !to_purge(dat_entry))
180: continue;
181:
182: hlist_del_rcu(&dat_entry->hash_entry);
183: batadv_dat_entry_put(dat_entry);
184: }
185: spin_unlock_bh(list_lock);
186: }
187: }
188:
189: /**
190: * batadv_dat_purge() - periodic task that deletes old entries from the local
191: * DAT hash table
192: * @work: kernel work struct
193: */
194: static void batadv_dat_purge(struct work_struct *work)
195: {
196: struct delayed_work *delayed_work;
197: struct batadv_priv_dat *priv_dat;
198: struct batadv_priv *bat_priv;
199:
200: delayed_work = to_delayed_work(work);
201: priv_dat = container_of(delayed_work, struct batadv_priv_dat, work);
202: bat_priv = container_of(priv_dat, struct batadv_priv, dat);
203:
204: __batadv_dat_purge(bat_priv, batadv_dat_to_purge);
205: batadv_dat_start_timer(bat_priv);
206: }
207:
208: /**
209: * batadv_compare_dat() - comparing function used in the local DAT hash table
]
|
| 8/2 |
2026/05/30 12:29 |
llm |
expert |
0m
Model:
Tokens:
input: 15048
output: 35
thoughts: 853
Thoughts: **Analyzing the Softlockup in `__batadv_dat_purge`**
Okay, so I'm looking at this softlockup, and it's happening inside `__batadv_dat_purge`. The code is iterating through a hash table, protected by spinlocks. I see the standard pattern: spinlock, iterate, delete entries that need purging (or are flagged by a helper function), and spinunlock. My immediate thought is, the potential for a stall comes from `spin_unlock_bh` which enables softirqs. If this hash table is large and the purge process has a lot to do, or if there's a problem in the softirq processing itself, that unlock could open the floodgates.
Now, wait a minute, the stall is actually being detected by RCU. The log message says "rcu_preempt kthread starved...". That means the RCU grace period thread is being starved, and the CPU is in `__local_bh_enable_ip`, the code that enables softirqs. So, the softirq processing is taking an *excessively* long time, or is looping. Let's dig deeper: What are those softirqs doing?
Following the call stack, it goes through `net_rx_action`, `napi_poll`, `process_backlog`, `__netif_receive_skb`, `ipv6_rcv`, `ip6_mc_input`, `kfree_skb`, and finally `kmem_cache_free`. Okay, we're talking about a massive flood of packets being processed within the softirq context. So `spin_unlock_bh` calls the softirq handler, which sees a huge backlog. Now, normally, `net_rx_action` has time and budget limits, so it shouldn't spin forever, right?
But, ah ha! A network loop could bypass the normal limits, even in `net_rx_action`. If the packet processing gets stuck in a loop, it might process packets indefinitely, as the softirq context loops back on itself in that case! Looking at the log, that's what's happening.
The log clearly shows a loop: "received packet on bridge... with own address as source address". Repeatedly. That screams bridging loop, generating a packet storm. This storm is constantly triggering softirqs, and then when `spin_unlock_bh` is called, it processes *all* those packets. If the loop is bad enough, the softirq processing becomes a black hole - and the `net_rx_action` limits are likely being overridden by some bad interaction on the bridges. Wait. If the time limit is reached inside `net_rx_action` it will schedule a softirq which may loop in `__local_bh_enable_ip`
I should probably check the code for `__local_bh_enable_ip` to be sure.
|
| 9/2 |
2026/05/30 12:29 |
tool |
read-file |
0m
Args: map[File:kernel/softirq.c FirstLine:430 LineCount:50]
Results: map[Contents: 430: lockdep_assert_irqs_enabled();
431: #ifdef CONFIG_TRACE_IRQFLAGS
432: local_irq_disable();
433: #endif
434: /*
435: * Are softirqs going to be turned on now:
436: */
437: if (softirq_count() == SOFTIRQ_DISABLE_OFFSET)
438: lockdep_softirqs_on(ip);
439: /*
440: * Keep preemption disabled until we are done with
441: * softirq processing:
442: */
443: __preempt_count_sub(cnt - 1);
444:
445: if (unlikely(!in_interrupt() && local_softirq_pending())) {
446: /*
447: * Run softirq if any pending. And do it in its own stack
448: * as we may be calling this deep in a task call stack already.
449: */
450: do_softirq();
451: }
452:
453: preempt_count_dec();
454: #ifdef CONFIG_TRACE_IRQFLAGS
455: local_irq_enable();
456: #endif
457: preempt_check_resched();
458: }
459: EXPORT_SYMBOL(__local_bh_enable_ip);
460:
461: static inline void softirq_handle_begin(void)
462: {
463: __local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);
464: }
465:
466: static inline void softirq_handle_end(void)
467: {
468: __local_bh_enable(SOFTIRQ_OFFSET);
469: WARN_ON_ONCE(in_interrupt());
470: }
471:
472: static inline void ksoftirqd_run_begin(void)
473: {
474: local_irq_disable();
475: }
476:
477: static inline void ksoftirqd_run_end(void)
478: {
479: local_irq_enable();
]
|
| 10/2 |
2026/05/30 12:29 |
llm |
expert |
0m
Model:
Tokens:
input: 17248
output: 35
thoughts: 23
Thoughts: Okay, here's my interpretation of that thought process, from an expert's perspective:
**Diving into SoftIRQ Execution**
Right, `do_softirq()` has been invoked. My immediate thought is, "Let's dig in and examine the execution path of `do_softirq()`." I need to understand what triggered this, what's queued up, and how the system is responding to these software interrupts. I'm thinking about the implications for latency, the critical sections it might be entering, and the potential for contention. Time to see what's on the stack!
|
| 11/2 |
2026/05/30 12:29 |
tool |
read-file |
0m
Args: map[File:kernel/softirq.c FirstLine:500 LineCount:50]
Results: map[Contents: 500: * be potentially deep already. So call softirq in its own stack
501: * to prevent from any overrun.
502: */
503: do_softirq_own_stack();
504: #endif
505: } else {
506: wakeup_softirqd();
507: }
508: }
509:
510: asmlinkage __visible void do_softirq(void)
511: {
512: __u32 pending;
513: unsigned long flags;
514:
515: if (in_interrupt())
516: return;
517:
518: local_irq_save(flags);
519:
520: pending = local_softirq_pending();
521:
522: if (pending)
523: do_softirq_own_stack();
524:
525: local_irq_restore(flags);
526: }
527:
528: #endif /* !CONFIG_PREEMPT_RT */
529:
530: /*
531: * We restart softirq processing for at most MAX_SOFTIRQ_RESTART times,
532: * but break the loop if need_resched() is set or after 2 ms.
533: * The MAX_SOFTIRQ_TIME provides a nice upper bound in most cases, but in
534: * certain cases, such as stop_machine(), jiffies may cease to
535: * increment and so we need the MAX_SOFTIRQ_RESTART limit as
536: * well to make sure we eventually return from this method.
537: *
538: * These limits have been established via experimentation.
539: * The two things to balance is latency against fairness -
540: * we want to handle softirqs as soon as possible, but they
541: * should not be able to lock up the box.
542: */
543: #define MAX_SOFTIRQ_TIME msecs_to_jiffies(2)
544: #define MAX_SOFTIRQ_RESTART 10
545:
546: #ifdef CONFIG_TRACE_IRQFLAGS
547: /*
548: * When we run softirqs from irq_exit() and thus on the hardirq stack we need
549: * to keep the lockdep irq context tracking as tight as possible in order to
]
|
| 12/2 |
2026/05/30 12:29 |
llm |
expert |
0m
Model:
Tokens:
input: 18161
output: 36
thoughts: 35
Thoughts: Okay, here's my interpretation of that thought process, from an expert's perspective:
**Diving into the SoftIRQ Implementation**
Alright, hang on a second. I'm seeing a call to `do_softirq_own_stack`, and that's usually a pretty low-level function. It's reminding me that `do_softirq_own_stack` calls `__do_softirq`. That's the core. Let's dig deeper in understanding how that works. I've got to revisit `__do_softirq`. Time to refresh my memory on the intimate details of that function.
|
| 13/2 |
2026/05/30 12:29 |
tool |
read-file |
0m
Args: map[File:kernel/softirq.c FirstLine:550 LineCount:100]
Results: map[Contents: 550: * not miss-qualify lock contexts and miss possible deadlocks.
551: */
552:
553: static inline bool lockdep_softirq_start(void)
554: {
555: bool in_hardirq = false;
556:
557: if (lockdep_hardirq_context()) {
558: in_hardirq = true;
559: lockdep_hardirq_exit();
560: }
561:
562: lockdep_softirq_enter();
563:
564: return in_hardirq;
565: }
566:
567: static inline void lockdep_softirq_end(bool in_hardirq)
568: {
569: lockdep_softirq_exit();
570:
571: if (in_hardirq)
572: lockdep_hardirq_enter();
573: }
574: #else
575: static inline bool lockdep_softirq_start(void) { return false; }
576: static inline void lockdep_softirq_end(bool in_hardirq) { }
577: #endif
578:
579: static void handle_softirqs(bool ksirqd)
580: {
581: unsigned long end = jiffies + MAX_SOFTIRQ_TIME;
582: unsigned long old_flags = current->flags;
583: int max_restart = MAX_SOFTIRQ_RESTART;
584: struct softirq_action *h;
585: bool in_hardirq;
586: __u32 pending;
587: int softirq_bit;
588:
589: /*
590: * Mask out PF_MEMALLOC as the current task context is borrowed for the
591: * softirq. A softirq handled, such as network RX, might set PF_MEMALLOC
592: * again if the socket is related to swapping.
593: */
594: current->flags &= ~PF_MEMALLOC;
595:
596: pending = local_softirq_pending();
597:
598: softirq_handle_begin();
599: in_hardirq = lockdep_softirq_start();
600: account_softirq_enter(current);
601:
602: restart:
603: /* Reset the pending bitmask before enabling irqs */
604: set_softirq_pending(0);
605:
606: local_irq_enable();
607:
608: h = softirq_vec;
609:
610: while ((softirq_bit = ffs(pending))) {
611: unsigned int vec_nr;
612: int prev_count;
613:
614: h += softirq_bit - 1;
615:
616: vec_nr = h - softirq_vec;
617: prev_count = preempt_count();
618:
619: kstat_incr_softirqs_this_cpu(vec_nr);
620:
621: trace_softirq_entry(vec_nr);
622: h->action();
623: trace_softirq_exit(vec_nr);
624: if (unlikely(prev_count != preempt_count())) {
625: pr_err("huh, entered softirq %u %s %p with preempt_count %08x, exited with %08x?\n",
626: vec_nr, softirq_to_name[vec_nr], h->action,
627: prev_count, preempt_count());
628: preempt_count_set(prev_count);
629: }
630: h++;
631: pending >>= softirq_bit;
632: }
633:
634: if (!IS_ENABLED(CONFIG_PREEMPT_RT) && ksirqd)
635: rcu_softirq_qs();
636:
637: local_irq_disable();
638:
639: pending = local_softirq_pending();
640: if (pending) {
641: if (time_before(jiffies, end) && !need_resched() &&
642: --max_restart)
643: goto restart;
644:
645: wakeup_softirqd();
646: }
647:
648: account_softirq_exit(current);
649: lockdep_softirq_end(in_hardirq);
]
|
| 14/2 |
2026/05/30 12:29 |
llm |
expert |
0m
Model:
Tokens:
input: 19794
output: 35
thoughts: 642
Thoughts: **Analyzing a Potential Softirq Stall**
Okay, so I'm looking at this stall, and initially, the `handle_softirqs` function caught my eye. It has a loop, a maximum of 10 times or until 2ms is reached, which seems reasonable for a brief burst of work. But then the jiffies stall duration jumped out – 580 jiffies is a significant amount of time, and it got me thinking.
The stack trace is what really clued me in. It's showing an RCU stall, but it's occurring during the call to `lock_acquire`, which is highly suspect. The trace goes: `__local_bh_enable_ip` -> `do_softirq` -> `handle_softirqs` -> `net_rx_action` -> `napi_poll` -> `process_backlog` -> `__netif_receive_skb` -> `ipv6_rcv` -> `ip6_mc_input` -> `kfree_skb` -> `kmem_cache_free` -> `slab_free` -> `kasan_slab_free` -> `kasan_save_free_info` -> `stack_trace_save` -> `arch_stack_walk` -> `unwind_next_frame` -> `class_rcu_constructor` -> `rcu_read_lock` -> `rcu_lock_acquire` -> `lock_acquire`. Clearly, softirqs are involved, and it looks like a lock contention issue.
My first thought was, could `batadv_dat_purge` be holding a spinlock that the softirq is trying to acquire? Let me examine the code:
```c
spin_lock_bh(list_lock);
hlist_for_each_entry_safe(dat_entry, node_tmp, head,
hash_entry) {
/* if a helper function has been passed as parameter,
* ask it if the entry has to be purged or not
*/
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);
```
Okay, it *does* use a spinlock, `list_lock`, and it's protected by `spin_lock_bh` and released by `spin_unlock_bh`. `spin_unlock_bh` is interesting here because it enables softirqs, which could mean a pending softirq would run immediately after the unlock.
My initial thought was that a softirq was trying to acquire `list_lock` *before* the purge finished. However, let's think about the timing. `spin_unlock_bh` calls `__local_bh_enable_ip`, which, if softirqs are pending, calls `do_softirq`. So, *inside* `do_softirq`, the network packets are processed. But, hang on. The lock is released *before* softirqs are processed.
Wait, `spin_unlock_bh` releases the lock *before* calling `__local_bh_enable_ip`. So softirqs can't be contending with the original `batadv_dat_purge` while it holds the lock. I need to check `spin_unlock_bh` to be sure.
|
| 15/2 |
2026/05/30 12:29 |
tool |
read-file |
0m
Args: map[File:include/linux/spinlock.h FirstLine:380 LineCount:50]
Results: map[Contents: 380:
381: #define spin_lock_irqsave_nested(lock, flags, subclass) \
382: do { \
383: raw_spin_lock_irqsave_nested(spinlock_check(lock), flags, subclass); \
384: __release(spinlock_check(lock)); __acquire(lock); \
385: } while (0)
386:
387: static __always_inline void spin_unlock(spinlock_t *lock)
388: __releases(lock) __no_context_analysis
389: {
390: raw_spin_unlock(&lock->rlock);
391: }
392:
393: static __always_inline void spin_unlock_bh(spinlock_t *lock)
394: __releases(lock) __no_context_analysis
395: {
396: raw_spin_unlock_bh(&lock->rlock);
397: }
398:
399: static __always_inline void spin_unlock_irq(spinlock_t *lock)
400: __releases(lock) __no_context_analysis
401: {
402: raw_spin_unlock_irq(&lock->rlock);
403: }
404:
405: static __always_inline void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags)
406: __releases(lock) __no_context_analysis
407: {
408: raw_spin_unlock_irqrestore(&lock->rlock, flags);
409: }
410:
411: static __always_inline int spin_trylock_bh(spinlock_t *lock)
412: __cond_acquires(true, lock) __no_context_analysis
413: {
414: return raw_spin_trylock_bh(&lock->rlock);
415: }
416:
417: static __always_inline int spin_trylock_irq(spinlock_t *lock)
418: __cond_acquires(true, lock) __no_context_analysis
419: {
420: return raw_spin_trylock_irq(&lock->rlock);
421: }
422:
423: static __always_inline bool _spin_trylock_irqsave(spinlock_t *lock, unsigned long *flags)
424: __cond_acquires(true, lock) __no_context_analysis
425: {
426: return raw_spin_trylock_irqsave(spinlock_check(lock), *flags);
427: }
428: #define spin_trylock_irqsave(lock, flags) _spin_trylock_irqsave(lock, &(flags))
429:
]
|
| 16/2 |
2026/05/30 12:29 |
llm |
expert |
0m
Model:
Tokens:
input: 22061
output: 35
thoughts: 13
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Diving into `raw_spin_unlock_bh`**
Okay, so I'm looking at `raw_spin_unlock_bh`. Immediately, I'm thinking about the implications. It's a low-level spinlock unlock function, and the "bh" suffix suggests it has something to do with bottom halves or, more specifically, disabling bottom half processing. I need to refresh my memory and pinpoint what the potential areas for analysis are, considering that:
* I'll definitely need to scrutinize how it interacts with interrupt contexts and preemption. How carefully does it preserve atomicity while potentially enabling bottom halves?
* I want to remember whether this is architecture-specific, and what architecture-specific tricks or optimizations might be going on, like memory barriers or instruction ordering.
* I'll need to check the exact sequence of operations. Is there a clear ordering constraint between the spinlock release and disabling bottom half processing, and what are the effects if that ordering goes wrong?
* Given its purpose, is there any chance of deadlock or starvation. What would the conditions be?
* Ultimately, the goal is to understand how well the code handles potential race conditions, concurrent access, and other tricky multi-threaded scenarios related to kernel synchronization.
|
| 17/2 |
2026/05/30 12:29 |
tool |
read-file |
0m
Args: map[File:include/linux/spinlock.h FirstLine:250 LineCount:50]
Results: map[Contents: 250: do { \
251: typecheck(unsigned long, flags); \
252: flags = _raw_spin_lock_irqsave_nested(lock, subclass); \
253: } while (0)
254: #else
255: #define raw_spin_lock_irqsave_nested(lock, flags, subclass) \
256: do { \
257: typecheck(unsigned long, flags); \
258: flags = _raw_spin_lock_irqsave(lock); \
259: } while (0)
260: #endif
261:
262: #else
263:
264: #define raw_spin_lock_irqsave(lock, flags) \
265: do { \
266: typecheck(unsigned long, flags); \
267: _raw_spin_lock_irqsave(lock, flags); \
268: } while (0)
269:
270: #define raw_spin_lock_irqsave_nested(lock, flags, subclass) \
271: raw_spin_lock_irqsave(lock, flags)
272:
273: #endif
274:
275: #define raw_spin_lock_irq(lock) _raw_spin_lock_irq(lock)
276: #define raw_spin_lock_bh(lock) _raw_spin_lock_bh(lock)
277: #define raw_spin_unlock(lock) _raw_spin_unlock(lock)
278: #define raw_spin_unlock_irq(lock) _raw_spin_unlock_irq(lock)
279:
280: #define raw_spin_unlock_irqrestore(lock, flags) \
281: do { \
282: typecheck(unsigned long, flags); \
283: _raw_spin_unlock_irqrestore(lock, flags); \
284: } while (0)
285: #define raw_spin_unlock_bh(lock) _raw_spin_unlock_bh(lock)
286:
287: #define raw_spin_trylock_bh(lock) _raw_spin_trylock_bh(lock)
288:
289: #define raw_spin_trylock_irq(lock) _raw_spin_trylock_irq(lock)
290:
291: #define raw_spin_trylock_irqsave(lock, flags) _raw_spin_trylock_irqsave(lock, &(flags))
292:
293: #ifndef CONFIG_PREEMPT_RT
294: /* Include rwlock functions for !RT */
295: #include <linux/rwlock.h>
296: #endif
297:
298: /*
299: * Pull the _spin_*()/_read_*()/_write_*() functions/declarations:
]
|
| 18/2 |
2026/05/30 12:29 |
llm |
expert |
0m
Model:
Tokens:
input: 23264
output: 35
thoughts: 14
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience and written in the first person:
**Diving into `_raw_spin_unlock_bh`**
Okay, let's take a look at `_raw_spin_unlock_bh`. This likely means we're dealing with a raw spinlock unlock, but with some interaction with bottom halves. The `_raw_` prefix strongly suggests it's a primitive, low-level function, so I'll expect to see something fundamental, probably dealing directly with the lock's state within a memory location. The `_bh` part immediately flags the potential for interactions with interrupt context or bottom half processing, which adds a layer of complexity.
I'll be looking for how it handles disabling or modifying interrupts, specifically if it masks them and then re-enables them after the unlock operation. The *order* of these operations will be very important. If it's a true bottom-half interaction, there will likely be some form of bottom-half scheduling mechanism at play. I'll need to confirm if it's atomic and if it uses memory barriers to ensure the write to the lock's memory location is visible to other CPUs. I'll also be mindful of potential race conditions.
I'm also curious about the compiler barriers, so I can see what kind of optimizations this code disables. I want to check for potential deadlocks or interrupt nesting issues. Since this likely has interrupt context implications, I'll be focused on how it ensures atomicity and prevents preemption during the critical section. I anticipate a very direct and efficient implementation. Let's see if my initial assumptions are correct.
|
| 19/2 |
2026/05/30 12:29 |
tool |
read-file |
0m
Args: map[File:kernel/locking/spinlock.c FirstLine:190 LineCount:50]
Results: map[Contents: 190: __raw_spin_unlock(lock);
191: }
192: EXPORT_SYMBOL(_raw_spin_unlock);
193: #endif
194:
195: #ifndef CONFIG_INLINE_SPIN_UNLOCK_IRQRESTORE
196: noinline void __lockfunc _raw_spin_unlock_irqrestore(raw_spinlock_t *lock, unsigned long flags)
197: {
198: __raw_spin_unlock_irqrestore(lock, flags);
199: }
200: EXPORT_SYMBOL(_raw_spin_unlock_irqrestore);
201: #endif
202:
203: #ifndef CONFIG_INLINE_SPIN_UNLOCK_IRQ
204: noinline void __lockfunc _raw_spin_unlock_irq(raw_spinlock_t *lock)
205: {
206: __raw_spin_unlock_irq(lock);
207: }
208: EXPORT_SYMBOL(_raw_spin_unlock_irq);
209: #endif
210:
211: #ifndef CONFIG_INLINE_SPIN_UNLOCK_BH
212: noinline void __lockfunc _raw_spin_unlock_bh(raw_spinlock_t *lock)
213: {
214: __raw_spin_unlock_bh(lock);
215: }
216: EXPORT_SYMBOL(_raw_spin_unlock_bh);
217: #endif
218:
219: #ifndef CONFIG_PREEMPT_RT
220:
221: #ifndef CONFIG_INLINE_READ_TRYLOCK
222: noinline int __lockfunc _raw_read_trylock(rwlock_t *lock)
223: {
224: return __raw_read_trylock(lock);
225: }
226: EXPORT_SYMBOL(_raw_read_trylock);
227: #endif
228:
229: #ifndef CONFIG_INLINE_READ_LOCK
230: noinline void __lockfunc _raw_read_lock(rwlock_t *lock)
231: {
232: __raw_read_lock(lock);
233: }
234: EXPORT_SYMBOL(_raw_read_lock);
235: #endif
236:
237: #ifndef CONFIG_INLINE_READ_LOCK_IRQSAVE
238: noinline unsigned long __lockfunc _raw_read_lock_irqsave(rwlock_t *lock)
239: {
]
|
| 20/2 |
2026/05/30 12:29 |
llm |
expert |
0m
Model:
Tokens:
input: 24415
output: 40
thoughts: 14
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Diving into `__raw_spin_unlock_bh`**
Okay, time to dissect `__raw_spin_unlock_bh`. Let's assume I'm digging into a kernel locking issue, performance bottleneck, or perhaps just brushing up on how the spinlock/BH interaction *actually* works in the current context. My immediate instinct is to look at the implementation itself, because I'm not going to find anything helpful in any documentation. I know the name implies that it's the raw (uninstrumented) unlock primitive designed for use in a bottom-half context, so I'm expecting something stripped down and optimized. I'm mentally parsing the likely candidates within the kernel code.
I'm thinking about the potential interactions with interrupt disabling and enabling, preemption, and the memory ordering guarantees. Is it just a simple `raw_spin_unlock` with added `local_bh_enable()`? Or does it also involve some form of memory barrier, either explicit or implicit due to instruction ordering? The `BH` postfix suggests it could be related to enabling bottom-half processing. I need to understand whether this is a full barrier unlock operation, or whether other locking primitives are involved. My gut tells me this will be at the very core of the problem. Time to start reading the code.
|
| 21/2 |
2026/05/30 12:29 |
tool |
read-file |
0m
Args: map[File:include/linux/spinlock_api_smp.h FirstLine:150 LineCount:50]
Results: map[Contents: 150: spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
151: LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
152: }
153:
154: static inline void __raw_spin_lock(raw_spinlock_t *lock)
155: __acquires(lock) __no_context_analysis
156: {
157: preempt_disable();
158: spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
159: LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
160: }
161:
162: #endif /* !CONFIG_GENERIC_LOCKBREAK || CONFIG_DEBUG_LOCK_ALLOC */
163:
164: static inline void __raw_spin_unlock(raw_spinlock_t *lock)
165: __releases(lock)
166: {
167: spin_release(&lock->dep_map, _RET_IP_);
168: do_raw_spin_unlock(lock);
169: preempt_enable();
170: }
171:
172: static inline void __raw_spin_unlock_irqrestore(raw_spinlock_t *lock,
173: unsigned long flags)
174: __releases(lock)
175: {
176: spin_release(&lock->dep_map, _RET_IP_);
177: do_raw_spin_unlock(lock);
178: local_irq_restore(flags);
179: preempt_enable();
180: }
181:
182: static inline void __raw_spin_unlock_irq(raw_spinlock_t *lock)
183: __releases(lock)
184: {
185: spin_release(&lock->dep_map, _RET_IP_);
186: do_raw_spin_unlock(lock);
187: local_irq_enable();
188: preempt_enable();
189: }
190:
191: static inline void __raw_spin_unlock_bh(raw_spinlock_t *lock)
192: __releases(lock)
193: {
194: spin_release(&lock->dep_map, _RET_IP_);
195: do_raw_spin_unlock(lock);
196: __local_bh_enable_ip(_RET_IP_, SOFTIRQ_LOCK_OFFSET);
197: }
198:
199: static inline int __raw_spin_trylock_bh(raw_spinlock_t *lock)
]
|
| 22/2 |
2026/05/30 12:29 |
llm |
expert |
0m
Model:
Tokens:
input: 25558
output: 24
thoughts: 980
Thoughts: **Analyzing a Potential Softlockup**
Okay, so `__raw_spin_unlock_bh` first calls `do_raw_spin_unlock(lock)` and *then* `__local_bh_enable_ip`. This is key. The lock is released *before* the softirqs are processed. So, the `lock_acquire` in the softirq stack trace is *not* trying to acquire `list_lock`.
Let's revisit that stack trace. It looks like it's acquiring `rcu_read_lock` inside `unwind_next_frame`. Ah, right, `unwind_next_frame` is part of the stack unwinder KASAN uses when saving the stack trace during a free operation. This is likely taking up a lot of time. Therefore, the CPU is likely just busy with softirq processing, and KASAN is slowing things down with stack unwinding.
Now, why is this manifesting as a problem? Even a packet storm shouldn't cause a *softlockup*, because `handle_softirqs` should yield. It's supposed to yield after 2ms or 10 restarts, via `wakeup_softirqd()`. But, wait! `__local_bh_enable_ip` calls `do_softirq()`. If `do_softirq()` yields, it wakes up `ksoftirqd`. Then `__local_bh_enable_ip` returns. And then... the loop in `__batadv_dat_purge` continues to the *next* iteration!
```c
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);
// ...
spin_unlock_bh(list_lock);
}
```
Okay, so if `bat_priv->dat.hash->size` is large, like, say, 1024, it's going to loop through that *1024 times*. Every time it hits `spin_unlock_bh`, it *might* process softirqs for up to 2ms. This means it will take 1024 * 2ms = 2048ms, which is roughly 2 seconds! If the hash size is even larger, say 65536, it will take 130 seconds! The real question is: What is the size of `bat_priv->dat.hash`?
|
| 23/2 |
2026/05/30 12:29 |
tool |
codesearch-find-references |
0m
Args: map[Name:batadv_dat_init]
Results: map[References:[map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:batadv_mesh_init SourceFile:net/batman-adv/main.c SourceLine:212]] TruncatedOutput:false]
|
| 24/2 |
2026/05/30 12:29 |
llm |
expert |
0m
Model:
Tokens:
input: 27214
output: 40
thoughts: 12
Thoughts: Okay, let's dive into this. Here's my take on those thoughts, as if they're my own:
**Analyzing `batadv_dat_init`**
Alright, `batadv_dat_init`. I immediately recognize the function name; it screams "initialization" within the BATMAN Advanced framework. My expert intuition suggests this is the core setup for the data structures related to the data transfer part of the mesh network. I need to understand what specific data structures it's touching and how it's preparing them. Is it allocating memory? Initializing values to defaults? Setting up any internal references or callbacks? What is the *context* this initialization occurs in? Is this during module loading, interface creation, or something else entirely? A thorough analysis requires me to see how this function plays with others.
|
| 25/2 |
2026/05/30 12:30 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/batman-adv/main.c Name:batadv_dat_init]
Results: map[SourceCode: 802: /**
803: * batadv_dat_init() - initialise the DAT internals
804: * @bat_priv: the bat priv with all the mesh interface information
805: *
806: * Return: 0 in case of success, a negative error code otherwise
807: */
808: int batadv_dat_init(struct batadv_priv *bat_priv)
809: {
810: if (bat_priv->dat.hash)
811: return 0;
812:
813: bat_priv->dat.hash = batadv_hash_new(1024);
814:
815: if (!bat_priv->dat.hash)
816: return -ENOMEM;
817:
818: INIT_DELAYED_WORK(&bat_priv->dat.work, batadv_dat_purge);
819: batadv_dat_start_timer(bat_priv);
820:
821: batadv_tvlv_handler_register(bat_priv, batadv_dat_tvlv_ogm_handler_v1,
822: NULL, NULL, BATADV_TVLV_DAT, 1,
823: BATADV_TVLV_HANDLER_OGM_CIFNOTFND);
824: batadv_dat_tvlv_container_update(bat_priv);
825: return 0;
826: }
SourceFile:net/batman-adv/distributed-arp-table.c]
|
| 26/2 |
2026/05/30 12:30 |
llm |
expert |
0m
Model:
Tokens:
input: 27884
output: 23
thoughts: 351
Thoughts: **RCU Stall Investigation**
Okay, so the hash size is 1024. If we assume a processing time of 2ms per entry (1024 * 2ms = 2 seconds), this doesn't seem long enough to trigger an RCU stall of 580 jiffies. Wait, but 580 jiffies, what's that in seconds? Right, it depends on HZ. If HZ=100, then it's 5.8 seconds, if HZ=250, then 2.3 seconds, or 0.58 seconds if HZ=1000. But, hang on, the RCU stall timeout is *usually* 21 seconds. This "rcu_preempt kthread starved for 580 jiffies!" message is a warning related to RCU, specifically that the grace period kthread is getting starved. I've got it, it's `rcu_gp_kthread` that's starved, which is consistent.
Now, again, 580 jiffies depends on HZ. If HZ=100, 5.8 seconds. If HZ=1000, 0.58 seconds. The warning is repeated, confirming the starvation of `rcu_preempt kthread` for 580 jiffies. I need to be precise here.
Let's check the RCU stall timeout. We're expecting `rcu_cpu_stall_timeout` to be around 21 seconds, typically. But, this "rcu_preempt kthread starved" message is *different*, not the usual stall warning. I'll need to pinpoint where this message is printed in the code.
|
| 27/2 |
2026/05/30 12:30 |
tool |
grepper |
0m
Error:
Args: map[Expression:rcu_preempt kthread starved for]
Results:
|
| 28/2 |
2026/05/30 12:30 |
llm |
expert |
0m
Model:
Tokens:
input: 28658
output: 18
thoughts: 11
Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience, and written in the first person:
**Investigating a Potential Kernel Thread Starvation Issue**
Alright, my initial thought is to dive right in with a targeted search. I'm thinking "kthread starved for" is a good starting point. This phrasing immediately flags a specific area of concern: the potential for a kernel thread (kthread) to be starved of resources or blocked indefinitely. Given my understanding of kernel internals, this could manifest in a variety of ways – from inefficient scheduling leading to CPU hogging by other processes, to I/O bottlenecks preventing a kthread from completing its work, or even something more insidious like a deadlock situation. The "starved for" part is important because it's not just a general performance issue; it suggests a specific type of resource contention or blocking that needs to be diagnosed. I expect that a search for this string is likely to yield insightful results from others who have likely previously wrestled with the same issue, as well as providing me with relevant documentation or kernel source references.
|
| 29/2 |
2026/05/30 12:30 |
tool |
grepper |
0m
Args: map[Expression:kthread starved for]
Results: map[Output:Documentation/RCU/stallwarn.rst=18=warnings:
--
Documentation/RCU/stallwarn.rst-43- ran and how often it should be expected to run. It can also
Documentation/RCU/stallwarn.rst:44: result in the ``rcu_.*kthread starved for`` console-log message,
Documentation/RCU/stallwarn.rst-45- which will include additional debugging information.
--
Documentation/RCU/stallwarn.rst=319=the following additional line is printed::
Documentation/RCU/stallwarn.rst-320-
Documentation/RCU/stallwarn.rst:321: rcu_sched kthread starved for 23807 jiffies! g7075 f0x0 RCU_GP_WAIT_FQS(3) ->state=0x1 ->cpu=5
Documentation/RCU/stallwarn.rst-322- Unless rcu_sched kthread gets sufficient CPU time, OOM is now expected behavior.
--
kernel/rcu/tree_stall.h=569=static void rcu_check_gp_kthread_starvation(void)
--
kernel/rcu/tree_stall.h-576- cpu = gpk ? task_cpu(gpk) : -1;
kernel/rcu/tree_stall.h:577: pr_err("%s kthread starved for %ld jiffies! g%ld f%#x %s(%d) ->state=%#x ->cpu=%d\n",
kernel/rcu/tree_stall.h-578- rcu_state.name, j,
--
tools/testing/selftests/rcutorture/bin/console-badness.sh-12-
tools/testing/selftests/rcutorture/bin/console-badness.sh:13:grep -E 'Badness|WARNING:|Warn|BUG|===========|BUG: KCSAN:|Call Trace:|Call trace:|Oops:|detected stalls on CPUs/tasks:|self-detected stall...
tools/testing/selftests/rcutorture/bin/console-badness.sh-14-grep -v 'ODEBUG: ' |
--
tools/testing/selftests/rcutorture/bin/parse-console.sh=121=then
--
tools/testing/selftests/rcutorture/bin/parse-console.sh-164- fi
tools/testing/selftests/rcutorture/bin/parse-console.sh:165: n_starves=`grep -c 'rcu_.*kthread starved for' $file`
tools/testing/selftests/rcutorture/bin/parse-console.sh-166- if test "$n_starves" -ne 0
]
|
| 30/2 |
2026/05/30 12:30 |
llm |
expert |
0m
Model:
Tokens:
input: 29596
output: 38
thoughts: 31
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience:
**Analyzing Potential RCU GP Starvation in Kernel**
Right, time to dig into this potential RCU GP starvation issue. My gut tells me to start deep, so I'm heading straight for the source code. Let's get right into `rcu_check_gp_kthread_starvation` within `kernel/rcu/tree_stall.h`. I need to understand the logic in that function intimately. What signals a stall? What are the various checks performed? What are the conditions that trigger it? Are there any potential subtle race conditions within that check itself that might be masked from a quick glance? I want to see exactly how the kernel perceives a stalled grace period and what it *does* about it. I need to refresh my memory on the underlying RCU implementation to make sure I'm fully comprehending the interactions. This feels like the right place to get a concrete grasp on the problem. I expect to be in here for a while.
|
| 31/2 |
2026/05/30 12:30 |
tool |
read-file |
0m
Args: map[File:kernel/rcu/tree_stall.h FirstLine:560 LineCount:50]
Results: map[Contents: 560: rdp->softirq_snap, kstat_softirqs_cpu(RCU_SOFTIRQ, cpu),
561: data_race(rcu_state.n_force_qs) - rcu_state.n_force_qs_gpstart,
562: rcuc_starved ? buf : "",
563: falsepositive ? " (false positive?)" : "");
564:
565: print_cpu_stat_info(cpu);
566: }
567:
568: /* Complain about starvation of grace-period kthread. */
569: static void rcu_check_gp_kthread_starvation(void)
570: {
571: int cpu;
572: struct task_struct *gpk = rcu_state.gp_kthread;
573: unsigned long j;
574:
575: if (rcu_is_gp_kthread_starving(&j)) {
576: cpu = gpk ? task_cpu(gpk) : -1;
577: pr_err("%s kthread starved for %ld jiffies! g%ld f%#x %s(%d) ->state=%#x ->cpu=%d\n",
578: rcu_state.name, j,
579: (long)rcu_seq_current(&rcu_state.gp_seq),
580: data_race(READ_ONCE(rcu_state.gp_flags)),
581: gp_state_getname(rcu_state.gp_state),
582: data_race(READ_ONCE(rcu_state.gp_state)),
583: gpk ? data_race(READ_ONCE(gpk->__state)) : ~0, cpu);
584: if (gpk) {
585: struct rcu_data *rdp = per_cpu_ptr(&rcu_data, cpu);
586:
587: pr_err("\tUnless %s kthread gets sufficient CPU time, OOM is now expected behavior.\n", rcu_state.name);
588: pr_err("RCU grace-period kthread stack dump:\n");
589: sched_show_task(gpk);
590: if (cpu_is_offline(cpu)) {
591: pr_err("RCU GP kthread last ran on offline CPU %d.\n", cpu);
592: } else if (!(data_race(READ_ONCE(rdp->mynode->qsmask)) & rdp->grpmask)) {
593: pr_err("Stack dump where RCU GP kthread last ran:\n");
594: dump_cpu_task(cpu);
595: }
596: wake_up_process(gpk);
597: }
598: }
599: }
600:
601: /* Complain about missing wakeups from expired fqs wait timer */
602: static void rcu_check_gp_kthread_expired_fqs_timer(void)
603: {
604: struct task_struct *gpk = rcu_state.gp_kthread;
605: short gp_state;
606: unsigned long jiffies_fqs;
607: int cpu;
608:
609: /*
]
|
| 32/2 |
2026/05/30 12:30 |
llm |
expert |
0m
Model:
Tokens:
input: 30833
output: 38
thoughts: 54
Thoughts: Here's my summary, as the thought process unfolds:
**Digging into RCU Grace Period Starvation**
Okay, so I see that `rcu_check_gp_kthread_starvation` gets called when `rcu_is_gp_kthread_starving` returns true. That makes sense; we check for starvation, and if we *are* starving, we act. Now, the logical next step is to understand what triggers that `true` return value. Therefore, I need to understand the logic within `rcu_is_gp_kthread_starving`. Let's dive in.
|
| 33/2 |
2026/05/30 12:30 |
tool |
read-file |
0m
Args: map[File:kernel/rcu/tree_stall.h FirstLine:540 LineCount:20]
Results: map[Contents: 540: ticks_value = rdp->ticks_this_gp;
541: }
542: delta = rcu_seq_ctr(rdp->mynode->gp_seq - rdp->rcu_iw_gp_seq);
543: falsepositive = rcu_is_gp_kthread_starving(NULL) &&
544: rcu_watching_snap_in_eqs(ct_rcu_watching_cpu(cpu));
545: rcuc_starved = rcu_is_rcuc_kthread_starving(rdp, &j);
546: if (rcuc_starved)
547: // Print signed value, as negative values indicate a probable bug.
548: snprintf(buf, sizeof(buf), " rcuc=%ld jiffies(starved)", j);
549: pr_err("\t%d-%c%c%c%c: (%lu %s) idle=%04x/%ld/%#lx softirq=%u/%u fqs=%ld%s%s\n",
550: cpu,
551: "O."[!!cpu_online(cpu)],
552: "o."[!!(rdp->grpmask & rdp->mynode->qsmaskinit)],
553: "N."[!!(rdp->grpmask & rdp->mynode->qsmaskinitnext)],
554: !IS_ENABLED(CONFIG_IRQ_WORK) ? '?' :
555: rdp->rcu_iw_pending ? (int)min(delta, 9UL) + '0' :
556: "!."[!delta],
557: ticks_value, ticks_title,
558: ct_rcu_watching_cpu(cpu) & 0xffff,
559: ct_nesting_cpu(cpu), ct_nmi_nesting_cpu(cpu),
]
|
| 34/2 |
2026/05/30 12:30 |
llm |
expert |
0m
Model:
Tokens:
input: 31571
output: 26
thoughts: 18
Thoughts: Okay, here's my attempt at summarizing that thought process, tailored for an expert audience:
**Hunting Down `rcu_is_gp_kthread_starving`**
Alright, time to get down to brass tacks. I need to track down the function `rcu_is_gp_kthread_starving`. My initial mental map starts with the understanding that RCU (Read-Copy-Update) is a critical synchronization mechanism. Given the function name, I'm already inferring that it's likely related to monitoring or detecting situations where the grace period (GP) kthread, the essential background thread for RCU reclamation, might be delayed or stalled. Starvation, in this context, suggests a potential performance bottleneck, or even a system reliability risk if the GP isn't able to complete and free resources in a timely manner.
My first thought is where I'd expect to find this type of function: within the RCU subsystem code itself. I'll mentally sift through my knowledge of the kernel's RCU implementation – the relevant files and data structures – looking for hints. I'll likely start with the core RCU source files, looking for declarations, definitions, or references that might give me a lead.
I'd quickly consider where the kthread monitoring logic is most likely to reside. Does this function involve checking global flags, or does it operate on per-CPU data that tracks grace period progress? Is it involved in determining timeout conditions, or simply exposed as a check for higher-level debugging or performance monitoring tools?
Ultimately, my goal is to understand not just *where* this function exists, but also its role within the RCU lifecycle. Is it used internally to drive corrective actions, or is it an externally-visible API for introspection? Understanding that context will be key to understanding the function's implications for the system. Let's dig in.
|
| 35/2 |
2026/05/30 12:30 |
tool |
grepper |
0m
Args: map[Expression:rcu_is_gp_kthread_starving]
Results: map[Output:kernel/rcu/tree_stall.h=440=static const char *gp_state_getname(short gs)
--
kernel/rcu/tree_stall.h-447-/* Is the RCU grace-period kthread being starved of CPU time? */
kernel/rcu/tree_stall.h:448:static bool rcu_is_gp_kthread_starving(unsigned long *jp)
kernel/rcu/tree_stall.h-449-{
--
kernel/rcu/tree_stall.h=518=static void print_cpu_stall_info(int cpu)
--
kernel/rcu/tree_stall.h-542- delta = rcu_seq_ctr(rdp->mynode->gp_seq - rdp->rcu_iw_gp_seq);
kernel/rcu/tree_stall.h:543: falsepositive = rcu_is_gp_kthread_starving(NULL) &&
kernel/rcu/tree_stall.h-544- rcu_watching_snap_in_eqs(ct_rcu_watching_cpu(cpu));
--
kernel/rcu/tree_stall.h=569=static void rcu_check_gp_kthread_starvation(void)
--
kernel/rcu/tree_stall.h-574-
kernel/rcu/tree_stall.h:575: if (rcu_is_gp_kthread_starving(&j)) {
kernel/rcu/tree_stall.h-576- cpu = gpk ? task_cpu(gpk) : -1;
]
|
| 36/2 |
2026/05/30 12:30 |
llm |
expert |
0m
Model:
Tokens:
input: 32417
output: 38
thoughts: 18
Thoughts: Okay, here's my interpretation of what's going through my mind as I approach this function, aimed at another expert:
**Analyzing `rcu_is_gp_kthread_starving`**
Alright, let's dive into `rcu_is_gp_kthread_starving`. My initial thought is, "Okay, this sounds like it's a diagnostic or perhaps a mechanism for RCU grace period monitoring. 'Starving' implies we're concerned about the global preemption kthread, probably the one managing the grace periods, being unable to complete its work. A sluggish grace period completion could indicate a problem, so I'm already anticipating we might be looking at some critical path bottlenecks or lock contention."
I'm instantly considering the context. Is this function called frequently? What parts of RCU's machinery are calling this function? Is this part of a debugging framework, performance monitoring, or is it part of the core RCU scheduling logic? I need to understand its relationship to the other RCU primitives.
I'm mentally running through the various failure scenarios. What would cause the global preemption kthread to be starved? Are there long-lived RCU readers that are blocking progress? Are there contention issues on some global state? Are there potentially subtle interaction patterns with other kernel subsystems?
My experience tells me this function likely returns some boolean indicator. It’s either going to tell me "Yes, we're starving", "No, everything is fine", or maybe something in-between. I wonder if there is an indicator of starvation level. If the function is reporting starvation, the severity of the problem becomes immediately interesting.
Finally, I'm already formulating questions for myself. I need to understand the exact criteria this function uses to determine "starvation." What thresholds, counters, or timers are being examined? What's the time granularity of the measurements? How is "starvation" defined in this context? I'll probably start by looking for those details within the function itself or in its surrounding code. Let's dig in.
|
| 37/2 |
2026/05/30 12:30 |
tool |
read-file |
0m
Args: map[File:kernel/rcu/tree_stall.h FirstLine:440 LineCount:30]
Results: map[Contents: 440: static const char *gp_state_getname(short gs)
441: {
442: if (gs < 0 || gs >= ARRAY_SIZE(gp_state_names))
443: return "???";
444: return gp_state_names[gs];
445: }
446:
447: /* Is the RCU grace-period kthread being starved of CPU time? */
448: static bool rcu_is_gp_kthread_starving(unsigned long *jp)
449: {
450: unsigned long j = jiffies - READ_ONCE(rcu_state.gp_activity);
451:
452: if (jp)
453: *jp = j;
454: return j > 2 * HZ;
455: }
456:
457: static bool rcu_is_rcuc_kthread_starving(struct rcu_data *rdp, unsigned long *jp)
458: {
459: int cpu;
460: struct task_struct *rcuc;
461: unsigned long j;
462:
463: rcuc = rdp->rcu_cpu_kthread_task;
464: if (!rcuc)
465: return false;
466:
467: cpu = task_cpu(rcuc);
468: if (cpu_is_offline(cpu) || idle_cpu(cpu))
469: return false;
]
|
| 38/2 |
2026/05/30 12:30 |
llm |
expert |
0m
Model:
Tokens:
input: 33355
output: 35
thoughts: 452
Thoughts: **Analyzing the Blocking Behavior**
Okay, so `j > 2 * HZ`... that means if this thing is starved for more than two seconds, it's going to return true. Hold on, let's look at the jiffies. 580 jiffies, right? That's about 5.8 seconds if `HZ` is 100, but only 2.32 seconds if `HZ` is 250. So the stall is definitely in the 2-5 second range.
Now, if `batadv_dat_purge` loops 1024 times and, each time it potentially handles softirqs for up to 2ms, the total time adds up to 2.048 seconds. That's a strong connection! It seems that the loop in `batadv_dat_purge` does take 2 seconds because it calls `spin_unlock_bh` 1024 times, and each time, it processes softirqs for about 2ms.
But why the 2ms softirq processing? Ah, of course! A packet storm, or a bridging loop, is the likely culprit. This means `local_softirq_pending()` is always true, and `handle_softirqs` is running for its maximum time slice, which is `MAX_SOFTIRQ_TIME`, or 2ms.
So, the whole sequence is: `spin_unlock_bh` calls `__local_bh_enable_ip`, which triggers `do_softirq` and then `handle_softirqs`, and that takes 2ms. Then, `batadv_dat_purge` loops to the next bucket, acquires the lock, does effectively nothing (or very little), and then releases the lock, which *again* takes 2ms. This is where the time is going.
1024 buckets times 2ms per iteration equals 2048ms, which equals 2.048 seconds. Perfect match. During all this time, the CPU is constantly stuck in softirq or `batadv_dat_purge`, preventing other tasks from running.
But wait a minute... `__local_bh_enable_ip` doesn't actually call `schedule()`. It just processes softirqs and returns. Does it even check `need_resched()`? I need to go back and check `__local_bh_enable_ip` again.
|
| 39/2 |
2026/05/30 12:30 |
tool |
read-file |
0m
Args: map[File:kernel/softirq.c FirstLine:445 LineCount:20]
Results: map[Contents: 445: if (unlikely(!in_interrupt() && local_softirq_pending())) {
446: /*
447: * Run softirq if any pending. And do it in its own stack
448: * as we may be calling this deep in a task call stack already.
449: */
450: do_softirq();
451: }
452:
453: preempt_count_dec();
454: #ifdef CONFIG_TRACE_IRQFLAGS
455: local_irq_enable();
456: #endif
457: preempt_check_resched();
458: }
459: EXPORT_SYMBOL(__local_bh_enable_ip);
460:
461: static inline void softirq_handle_begin(void)
462: {
463: __local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);
464: }
]
|
| 40/2 |
2026/05/30 12:30 |
llm |
expert |
0m
Model:
Tokens:
input: 34661
output: 36
thoughts: 1165
Thoughts: **Analyzing the RCU Stall in `batadv_dat_purge`**
Okay, so `preempt_check_resched()` is being called, which *should* mean a task is eligible for preemption if `need_resched()` is true. I'm seeing this with `batadv_dat_purge` in a workqueue, so I expect the system to preempt the task, which should give other tasks a chance to run. But, it's not preempting, so `rcu_gp_kthread` can't make progress. This indicates a stall.
I know `batadv_dat_purge` runs in a workqueue, which *should* be preemptible. `preempt_count_dec()` drops the preempt count to 0, so preemption shouldn't be blocked. Looking at the stack trace confirms that we're within the softirq processing, specifically `__batadv_dat_purge`. The problem must be elsewhere.
The backtrace shows this is on CPU 0 and that the RCU stall is happening, and that CPU 0 is within `__batadv_dat_purge`. The RCU grace period thread is waiting for CPU 0 to enter a quiescent state, and I see CPU 0 spinning inside `batadv_dat_purge`. A quiescent state is reached when the CPU schedules, enters userspace, or explicitly signals it. Since `batadv_dat_purge` is a kernel thread within a workqueue and appears to be constantly looping, it's *not* scheduling, and therefore, not entering a quiescent state. The core issue, then, is that `batadv_dat_purge` is not yielding.
So, `preempt_check_resched()` is called, and *should* call `schedule()` if `need_resched()` is set, and it is. However, `need_resched()` is set when the scheduler tick triggers. That tick runs in hardirq context, and interrupts the workqueue thread, sets the `need_resched()` flag. Then, after the hardirq handler is done, `preempt_check_resched()` *should* call `schedule()`, which would lead to a quiescent state. Why isn't it preempting?
I confirm `CONFIG_PREEMPT` is enabled via the logs. The log shows `PREEMPT(full)`. So, preemption is *definitely* enabled. If that is the case, `preempt_check_resched()` should be preempting the task.
The only thing I can think of is what if there are no other runnable tasks on CPU 0? If `schedule()` is called but there's nothing to run, it'll just return immediately, and `batadv_dat_purge` will continue running, *without* a context switch, which would mean it still doesn't reach a quiescent state! This seems likely, given the setup. I need to verify that `schedule()` still signals a quiescent state, even if there's nothing to run. I'll check `__schedule()`.
|
| 41/2 |
2026/05/30 12:30 |
tool |
read-file |
0m
Args: map[File:kernel/sched/core.c FirstLine:7150 LineCount:50]
Results: map[Contents:7150: rq->nr_switches++;
7151: /*
7152: * RCU users of rcu_dereference(rq->curr) may not see
7153: * changes to task_struct made by pick_next_task().
7154: */
7155: RCU_INIT_POINTER(rq->curr, next);
7156:
7157: /*
7158: * The membarrier system call requires each architecture
7159: * to have a full memory barrier after updating
7160: * rq->curr, before returning to user-space.
7161: *
7162: * Here are the schemes providing that barrier on the
7163: * various architectures:
7164: * - mm ? switch_mm() : mmdrop() for x86, s390, sparc, PowerPC,
7165: * RISC-V. switch_mm() relies on membarrier_arch_switch_mm()
7166: * on PowerPC and on RISC-V.
7167: * - finish_lock_switch() for weakly-ordered
7168: * architectures where spin_unlock is a full barrier,
7169: * - switch_to() for arm64 (weakly-ordered, spin_unlock
7170: * is a RELEASE barrier),
7171: *
7172: * The barrier matches a full barrier in the proximity of
7173: * the membarrier system call entry.
7174: *
7175: * On RISC-V, this barrier pairing is also needed for the
7176: * SYNC_CORE command when switching between processes, cf.
7177: * the inline comments in membarrier_arch_switch_mm().
7178: */
7179: ++*switch_count;
7180:
7181: psi_account_irqtime(rq, prev, next);
7182: psi_sched_switch(prev, next, !task_on_rq_queued(prev) ||
7183: prev->se.sched_delayed);
7184:
7185: trace_sched_switch(preempt, prev, next, prev_state);
7186:
7187: /* Also unlocks the rq: */
7188: rq = context_switch(rq, prev, next, &rf);
7189: } else {
7190: rq_unpin_lock(rq, &rf);
7191: __balance_callbacks(rq, NULL);
7192: hrtick_schedule_exit(rq);
7193: raw_spin_rq_unlock_irq(rq);
7194: }
7195: trace_sched_exit_tp(is_switch);
7196: }
7197:
7198: void __noreturn do_task_dead(void)
7199: {
]
|
| 42/2 |
2026/05/30 12:30 |
llm |
expert |
0m
Model:
Tokens:
input: 37323
output: 22
thoughts: 25
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**RCU Context Switch Context**
Okay, so I'm digging into this `rcu_note_context_switch` thing. The fact that it's called within `__schedule` is immediately interesting, and frankly, expected. Now the question is *where* precisely. I need to trace its use within the scheduler to get a clear picture of how it's hooked in. I need to understand the control flow. Is this being called during a specific scheduler path or within a general path? Understanding the calling context is key. Let's see how deep I need to go.
|
| 43/2 |
2026/05/30 12:31 |
tool |
grepper |
0m
Args: map[Expression:rcu_note_context_switch]
Results: map[Output:Documentation/RCU/Design/Data-Structures/Data-Structures.rst=1021=badly RCU wants this quiescent state. This flag is checked by RCU's
Documentation/RCU/Design/Data-Structures/Data-Structures.rst:1022:context-switch path (``rcu_note_context_switch``) and the cond_resched
Documentation/RCU/Design/Data-Structures/Data-Structures.rst-1023-code.
--
Documentation/RCU/Design/Expedited-Grace-Periods/Expedited-Grace-Periods.rst=78=which is called from ``rcu_preempt_note_context_switch()``, which
Documentation/RCU/Design/Expedited-Grace-Periods/Expedited-Grace-Periods.rst:79:in turn is called from ``rcu_note_context_switch()``, which in
Documentation/RCU/Design/Expedited-Grace-Periods/Expedited-Grace-Periods.rst-80-turn is called from the scheduler.
--
Documentation/RCU/Design/Memory-Ordering/Tree-RCU-Memory-Ordering.rst=449=If the CPU does a context switch, a quiescent state will be noted by
Documentation/RCU/Design/Memory-Ordering/Tree-RCU-Memory-Ordering.rst:450:``rcu_note_context_switch()`` on the left. On the other hand, if the CPU
Documentation/RCU/Design/Memory-Ordering/Tree-RCU-Memory-Ordering.rst-451-takes a scheduler-clock interrupt while executing in usermode, a
--
Documentation/RCU/Design/Memory-Ordering/TreeRCU-gp.svg-3873- x="3745.7725"
Documentation/RCU/Design/Memory-Ordering/TreeRCU-gp.svg:3874: xml:space="preserve">rcu_note_context_switch()</text>
Documentation/RCU/Design/Memory-Ordering/TreeRCU-gp.svg-3875- </g>
--
Documentation/RCU/Design/Memory-Ordering/TreeRCU-qs.svg-755- x="3745.7725"
Documentation/RCU/Design/Memory-Ordering/TreeRCU-qs.svg:756: xml:space="preserve">rcu_note_context_switch()</text>
Documentation/RCU/Design/Memory-Ordering/TreeRCU-qs.svg-757- </g>
--
Documentation/admin-guide/kernel-parameters.txt=98=Kernel parameters
--
Documentation/admin-guide/kernel-parameters.txt-5721- soliciting quiescent-state help from
Documentation/admin-guide/kernel-parameters.txt:5722: rcu_note_context_switch() and cond_resched().
Documentation/admin-guide/kernel-parameters.txt-5723- If not specified, the kernel will calculate
--
Documentation/trace/ftrace.rst=1929=Here is a trace with function-trace set::
--
Documentation/trace/ftrace.rst-1951- kworker/-59 3...1 0us : __schedule <-schedule
Documentation/trace/ftrace.rst:1952: kworker/-59 3d..1 0us : rcu_preempt_qs <-rcu_note_context_switch
Documentation/trace/ftrace.rst-1953- kworker/-59 3d..1 1us : add_preempt_count <-_raw_spin_lock_irq
--
Documentation/trace/ftrace.rst=2160=Doing the same with chrt -r 5 and function-trace set.
--
Documentation/trace/ftrace.rst-2255- <idle>-0 3.N.. 26us : add_preempt_count <-__schedule
Documentation/trace/ftrace.rst:2256: <idle>-0 3.N.1 26us : rcu_note_context_switch <-__schedule
Documentation/trace/ftrace.rst:2257: <idle>-0 3.N.1 26us : rcu_sched_qs <-rcu_note_context_switch
Documentation/trace/ftrace.rst:2258: <idle>-0 3dN.1 27us : rcu_preempt_qs <-rcu_note_context_switch
Documentation/trace/ftrace.rst-2259- <idle>-0 3.N.1 27us : _raw_spin_lock_irq <-__schedule
--
include/linux/rcutiny.h=95=static inline void rcu_softirq_qs(void)
--
include/linux/rcutiny.h-99-
include/linux/rcutiny.h:100:#define rcu_note_context_switch(preempt) \
include/linux/rcutiny.h-101- do { \
--
include/linux/rcutree.h=20=void rcu_softirq_qs(void);
include/linux/rcutree.h:21:void rcu_note_context_switch(bool preempt);
include/linux/rcutree.h-22-int rcu_needs_cpu(void);
--
include/linux/rcutree.h=24=void rcu_request_urgent_qs_task(struct task_struct *t);
--
include/linux/rcutree.h-27- * Note a virtualization-based context switch. This is simply a
include/linux/rcutree.h:28: * wrapper around rcu_note_context_switch(), which allows TINY_RCU
include/linux/rcutree.h-29- * to save a few bytes. The caller must have disabled interrupts.
--
include/linux/rcutree.h=31=static inline void rcu_virt_note_context_switch(void)
include/linux/rcutree.h-32-{
include/linux/rcutree.h:33: rcu_note_context_switch(false);
include/linux/rcutree.h-34-}
--
kernel/rcu/tree.c=442=module_param(rcu_resched_ns, long, 0644);
--
kernel/rcu/tree.c-445- * How long the grace period must be before we start recruiting
kernel/rcu/tree.c:446: * quiescent-state help from rcu_note_context_switch().
kernel/rcu/tree.c-447- */
--
kernel/rcu/tree_plugin.h=298=static void rcu_qs(void)
--
kernel/rcu/tree_plugin.h-323- */
kernel/rcu/tree_plugin.h:324:void rcu_note_context_switch(bool preempt)
kernel/rcu/tree_plugin.h-325-{
--
kernel/rcu/tree_plugin.h-373-}
kernel/rcu/tree_plugin.h:374:EXPORT_SYMBOL_GPL(rcu_note_context_switch);
kernel/rcu/tree_plugin.h-375-
--
kernel/rcu/tree_plugin.h=990=EXPORT_SYMBOL_GPL(rcu_all_qs);
--
kernel/rcu/tree_plugin.h-994- */
kernel/rcu/tree_plugin.h:995:void rcu_note_context_switch(bool preempt)
kernel/rcu/tree_plugin.h-996-{
--
kernel/rcu/tree_plugin.h-1008-}
kernel/rcu/tree_plugin.h:1009:EXPORT_SYMBOL_GPL(rcu_note_context_switch);
kernel/rcu/tree_plugin.h-1010-
--
kernel/sched/core.c=7016=static void __sched notrace __schedule(int sched_mode)
--
kernel/sched/core.c-7042- local_irq_disable();
kernel/sched/core.c:7043: rcu_note_context_switch(preempt);
kernel/sched/core.c-7044- migrate_disable_switch(rq, prev);
--
tools/perf/Documentation/perf-intel-pt.txt=1592=Notes:
--
tools/perf/Documentation/perf-intel-pt.txt-1648- CPU 3/KVM 13376/13384 [002] 7919.408804057: branches: ffffffffc0f66161 vmx_vcpu_run+0x231 ([kernel.kallsyms]) => ffffffffc0f8eb20 vmx_vcpu...
tools/perf/Documentation/perf-intel-pt.txt:1649: CPU 3/KVM 13376/13384 [002] 7919.408804057: branches: ffffffffc0f8eb44 vmx_vcpu_enter_exit+0x24 ([kernel.kallsyms]) => ffffffffb2353e10 rc...
tools/perf/Documentation/perf-intel-pt.txt:1650: CPU 3/KVM 13376/13384 [002] 7919.408804057: branches: ffffffffb2353e1c rcu_note_context_switch+0xc ([kernel.kallsyms]) => ffffffffb2353db0...
tools/perf/Documentation/perf-intel-pt.txt-1651- CPU 3/KVM 13376/13384 [002] 7919.408804066: branches: ffffffffc0f8ebe0 vmx_vcpu_enter_exit+0xc0 ([kernel.kallsyms]) => ffffffffc0f8edc0 __...
]
|
| 44/2 |
2026/05/30 12:31 |
llm |
expert |
0m
Model:
Tokens:
input: 40015
output: 36
thoughts: 394
Thoughts: **My Current Train of Thought: RCU Quiescence and Scheduler Interactions**
Okay, so I'm digging into this RCU stall issue, and it looks like `rcu_note_context_switch` is called within `__schedule`. That makes sense; a context switch is inherently a quiescent state, right? But here's a wrinkle: what if `__schedule` *doesn't* actually switch tasks? Like, there are no other runnable tasks. Does `rcu_note_context_switch` *still* register that as a quiescent state?
Ah, right! I remember now. `rcu_note_context_switch` gets called *before* `pick_next_task`, so yeah, it definitely notes a quiescent state even if we end up running the same task.
But wait a minute, if a quiescent state *is* noted, why am I still seeing an RCU stall? This doesn't compute. Unless... the problem isn't that a context switch *isn't* detected as a quiescent state, but that `__schedule` *isn't being called at all*.
Why wouldn't `__schedule` get called? That's when I remember `preempt_check_resched()` only calling `__schedule` if `need_resched()` is set. Okay, is `need_resched()` set? And when would that occur?
Right, the scheduler tick sets `need_resched()` if a task has run for its time slice. But this stall is happening during a `batadv_dat_purge` call, which is a workqueue task. I wonder if the scheduler tick is somehow missing on this workqueue task?
If `CONFIG_HZ=100`, the scheduler tick happens every 10ms. So, during a 2-second `batadv_dat_purge` call, the tick would run 200 times. That seems like plenty of opportunities for `need_resched()` to get set, so that can't be it...
...Unless? Wait, if `need_resched()` is set, `__local_bh_enable_ip` calls `preempt_check_resched()`, which eventually calls `preempt_schedule()`. Let me go back and double check `preempt_schedule()` to see if I'm missing something there...
|
| 45/2 |
2026/05/30 12:31 |
tool |
read-file |
0m
Args: map[File:kernel/sched/core.c FirstLine:7350 LineCount:50]
Results: map[Contents:7350:
7351: static void __sched notrace preempt_schedule_common(void)
7352: {
7353: do {
7354: /*
7355: * Because the function tracer can trace preempt_count_sub()
7356: * and it also uses preempt_enable/disable_notrace(), if
7357: * NEED_RESCHED is set, the preempt_enable_notrace() called
7358: * by the function tracer will call this function again and
7359: * cause infinite recursion.
7360: *
7361: * Preemption must be disabled here before the function
7362: * tracer can trace. Break up preempt_disable() into two
7363: * calls. One to disable preemption without fear of being
7364: * traced. The other to still record the preemption latency,
7365: * which can also be traced by the function tracer.
7366: */
7367: preempt_disable_notrace();
7368: preempt_latency_start(1);
7369: __schedule(SM_PREEMPT);
7370: preempt_latency_stop(1);
7371: preempt_enable_no_resched_notrace();
7372:
7373: /*
7374: * Check again in case we missed a preemption opportunity
7375: * between schedule and now.
7376: */
7377: } while (need_resched());
7378: }
7379:
7380: #ifdef CONFIG_PREEMPTION
7381: /*
7382: * This is the entry point to schedule() from in-kernel preemption
7383: * off of preempt_enable.
7384: */
7385: asmlinkage __visible void __sched notrace preempt_schedule(void)
7386: {
7387: /*
7388: * If there is a non-zero preempt_count or interrupts are disabled,
7389: * we do not want to preempt the current task. Just return..
7390: */
7391: if (likely(!preemptible()))
7392: return;
7393: preempt_schedule_common();
7394: }
7395: NOKPROBE_SYMBOL(preempt_schedule);
7396: EXPORT_SYMBOL(preempt_schedule);
7397:
7398: #ifdef CONFIG_PREEMPT_DYNAMIC
7399: # ifdef CONFIG_HAVE_PREEMPT_DYNAMIC_CALL
]
|
| 46/2 |
2026/05/30 12:31 |
llm |
expert |
0m
Model:
Tokens:
input: 41670
output: 36
thoughts: 58
Thoughts: Okay, let me think this through.
**Analyzing Preemption Logic**
Alright, so `preemptible()` – that's the key function here. It essentially decides if we *can* schedule a preemption, right? It looks like it checks two things: first, that `preempt_count()` is zero, which makes sense - we don't want to preempt if we're nested in a critical section. And then, it checks if interrupts are *not* disabled using `!irqs_disabled()`. Makes perfect sense; if interrupts are off, then preemption is definitely off, too.
Now, here's the clever bit: If `preemptible()` comes back false, then `preempt_schedule()` is designed to just bail out and return. No actual scheduling work gets done. It just exits early. So, before anything else, we really need to figure out if we *are* preemptible. This seems like the crucial first step. So, let's start by checking `preemptible()`.
|
| 47/2 |
2026/05/30 12:31 |
tool |
read-file |
0m
Args: map[File:include/linux/preempt.h FirstLine:240 LineCount:50]
Results: map[Contents: 240: } while (0)
241:
242: #define preempt_check_resched() \
243: do { \
244: if (should_resched(0)) \
245: __preempt_schedule(); \
246: } while (0)
247:
248: #else /* !CONFIG_PREEMPTION */
249: #define preempt_enable() \
250: do { \
251: barrier(); \
252: preempt_count_dec(); \
253: } while (0)
254:
255: #define preempt_enable_notrace() \
256: do { \
257: barrier(); \
258: __preempt_count_dec(); \
259: } while (0)
260:
261: #define preempt_check_resched() do { } while (0)
262: #endif /* CONFIG_PREEMPTION */
263:
264: #define preempt_disable_notrace() \
265: do { \
266: __preempt_count_inc(); \
267: barrier(); \
268: } while (0)
269:
270: #define preempt_enable_no_resched_notrace() \
271: do { \
272: barrier(); \
273: __preempt_count_dec(); \
274: } while (0)
275:
276: #else /* !CONFIG_PREEMPT_COUNT */
277:
278: /*
279: * Even if we don't have any preemption, we need preempt disable/enable
280: * to be barriers, so that we don't have things like get_user/put_user
281: * that can cause faults and scheduling migrate into our preempt-protected
282: * region.
283: */
284: #define preempt_disable() barrier()
285: #define sched_preempt_enable_no_resched() barrier()
286: #define preempt_enable_no_resched() barrier()
287: #define preempt_enable() barrier()
288: #define preempt_check_resched() do { } while (0)
289:
]
|
| 48/2 |
2026/05/30 12:31 |
llm |
expert |
0m
Model:
Tokens:
input: 42676
output: 22
thoughts: 705
Thoughts: **RCU Stall Analysis**
Okay, let's break this down. My initial thought was that a context switch, indicated by the `preemptible()` check being true within `__local_bh_enable_ip`, should have triggered `rcu_note_context_switch`, leading to a quiescent state being noted. But if we're noting quiescence, why are we seeing an RCU stall?
Then I remembered that `rcu_note_context_switch` only observes the *current* CPU's state. The log specifically points to the `rcu_preempt` kthread starving: "rcu_preempt kthread starved for 580 jiffies! g76773 f0x0 RCU_GP_WAIT_FQS(5) ->state=0x0 ->cpu=0". The key here is that `->cpu=0`, the kthread *is* runnable on CPU 0 and has state `0x0` (TASK_RUNNING), but it's not being scheduled.
So the obvious question is, why isn't the scheduler picking it up? If `__schedule` is called, it *should* pick the highest priority task, which is a key assumption here. `rcu_preempt` is probably a normal `SCHED_NORMAL` task, unless someone's fiddled with `rcutree.kthread_prio`. If it is SCHED_NORMAL, it's competing for CPU time. But what is it competing with?
That's when it hit me. `batadv_dat_purge` is running in a workqueue, `kworker/R-bat_e`. The "R" in `kworker/R-bat_e` usually means "rescuer" thread. And the stack trace confirms it: `rescuer_thread`. Now, rescuer threads are created by kthread_create, and so may be real time! Let's check the priority of that thread. This could be our bottleneck.
|
| 49/2 |
2026/05/30 12:31 |
tool |
codesearch-find-references |
0m
Args:
Results: map[References:[map[ReferenceKind:takes-address-of ReferencingEntityKind:function ReferencingEntityName:init_rescuer SourceFile:kernel/workqueue.c SourceLine:5704]] TruncatedOutput:false]
|
| 50/2 |
2026/05/30 12:31 |
llm |
expert |
0m
Model:
Tokens:
input: 43908
output: 34
thoughts: 10
Thoughts: Okay, here's my interpretation of that thought process, assuming I'm an expert in this field and the context is relevant to my work:
**Deconstructing `init_rescuer`**
Alright, my internal review just kicked in. When I see something like "`init_rescuer`" immediately, my mind starts to break it down. I don't need a verbose explanation. It's concise. My initial thought is to think about the nature of a function or procedure with the name "`init_rescuer`". I assume this is a function, or method, probably in some kind of rescue or exception handling system. The "init" tells me it's likely an initializer; it's probably setting up something to manage potential problems or errors. The "rescuer" part itself is fascinating. It implies this isn't just a generic error handler, but something specific, maybe focused on recovering from issues, restoring state, or finding alternative solutions. What kind of rescuer is it? Is it thread-aware? Does it deal with hardware problems? Is it related to system security and attack mitigation? Is it generic? Does it have dependencies? Is it responsible for memory management? This command is the first of many things to think about when working on code.
|
| 51/2 |
2026/05/30 12:31 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:kernel/workqueue.c Name:init_rescuer]
Results: map[SourceCode:5679: /*
5680: * Workqueues which may be used during memory reclaim should have a rescuer
5681: * to guarantee forward progress.
5682: */
5683: static int init_rescuer(struct workqueue_struct *wq)
5684: {
5685: struct worker *rescuer;
5686: char id_buf[WORKER_ID_LEN];
5687: int ret;
5688:
5689: lockdep_assert_held(&wq_pool_mutex);
5690:
5691: if (!(wq->flags & WQ_MEM_RECLAIM))
5692: return 0;
5693:
5694: rescuer = alloc_worker(NUMA_NO_NODE);
5695: if (!rescuer) {
5696: pr_err("workqueue: Failed to allocate a rescuer for wq \"%s\"\n",
5697: wq->name);
5698: return -ENOMEM;
5699: }
5700:
5701: rescuer->rescue_wq = wq;
5702: format_worker_id(id_buf, sizeof(id_buf), rescuer, NULL);
5703:
5704: rescuer->task = kthread_create(rescuer_thread, rescuer, "%s", id_buf);
5705: if (IS_ERR(rescuer->task)) {
5706: ret = PTR_ERR(rescuer->task);
5707: pr_err("workqueue: Failed to create a rescuer kthread for wq \"%s\": %pe",
5708: wq->name, ERR_PTR(ret));
5709: kfree(rescuer);
5710: return ret;
5711: }
5712:
5713: wq->rescuer = rescuer;
5714:
5715: /* initial cpumask is consistent with the detached rescuer and unbind_worker() */
5716: if (cpumask_intersects(wq_unbound_cpumask, cpu_active_mask))
5717: kthread_bind_mask(rescuer->task, wq_unbound_cpumask);
5718: else
5719: kthread_bind_mask(rescuer->task, cpu_possible_mask);
5720:
5721: wake_up_process(rescuer->task);
5722:
5723: return 0;
5724: }
SourceFile:kernel/workqueue.c]
|
| 52/2 |
2026/05/30 12:31 |
llm |
expert |
0m
Model:
Tokens:
input: 44943
output: 36
thoughts: 1032
Thoughts: **Analyzing the RCU Preempt Starvation**
Okay, so I'm looking at this `rcu_preempt` starvation issue. Initially, I thought it was a real-time priority issue, but that's wrong. It's just a normal kthread. So why is `rcu_preempt` being starved while `batadv_dat_purge` is running? The first thing that jumps out is the fact that `batadv_dat_purge` runs for two seconds without yielding, but even if this is the case, `preempt_check_resched()` should call `schedule()` to allow other threads to run.
But, hang on, does `preempt_check_resched()` actually *call* `schedule()`? I need to check how `need_resched()` is being set. If `need_resched()` is set, then `preempt_check_resched()` calls `preempt_schedule()`, which in turn calls `__schedule()`. If `__schedule()` is called, it should pick the highest priority task, and since both are `SCHED_NORMAL`, they should share the CPU. So, if `__schedule()` is run, `rcu_preempt` should be run. But it's not.
So, maybe `preempt_check_resched()` *isn't* calling `schedule()`? Why wouldn't it? Ah, because `preempt_count()` might not be zero. Let me see... `__local_bh_enable_ip` is relevant here: it decrements the preempt count and then calls `preempt_check_resched()`. If `batadv_dat_purge` is called from `rescuer_thread`, what's the preempt count at that point? The `rescuer_thread` doesn't disable preemption before calling `process_one_work`. `spin_unlock_bh` calls `__local_bh_enable_ip`, and before the corresponding `spin_lock_bh`, the preempt count is zero, then softirqs are disabled, which adds `SOFTIRQ_LOCK_OFFSET` to the preempt count. `spin_unlock_bh` calls `__local_bh_enable_ip` and subtracts `SOFTIRQ_LOCK_OFFSET`. So the preempt count *should* be zero. Unless another lock is held... but `batadv_dat_purge` doesn't hold any other locks. So `preempt_count()` *is* zero, and therefore `preempt_check_resched()` should call `schedule()`.
So, if it *is* calling `schedule()`, why is `rcu_preempt` starved? Wait... let me look at the RCU stall message again. It says `rcu_preempt kthread starved for 580 jiffies! g76773 f0x0 RCU_GP_WAIT_FQS(5) ->state=0x0 ->cpu=0`. `->state=0x0` means TASK_RUNNING. `->cpu=0` means it's on CPU 0. So, if `rcu_preempt` is TASK_RUNNING, it's on the runqueue. So, if `batadv_dat_purge` *is* calling `schedule()`, then `rcu_preempt` *should* be picked. Why isn't it?
Maybe `batadv_dat_purge` isn't calling `schedule()`? Why would `need_resched()` be false? If `need_resched()` is false, the scheduler tick hasn't set it. So, why wouldn't the scheduler tick set it? Is the scheduler tick *not running*? That could be because interrupts are disabled, right? No, `spin_unlock_bh` enables softirqs, so interrupts are enabled. Wait, is the CPU in a softlockup? The log says: `Tainted: [L]=SOFTLOCKUP`.
Aha! A softlockup was detected! A softlockup means the CPU hasn't scheduled for 20 seconds! If the CPU hasn't scheduled for 20 seconds, then `need_resched()` *must* have been set, but `schedule()` was *not* called! Why wasn't `schedule()` called? It can only mean that `preempt_count()` is *not* zero! But I just went through the lock logic and the count should be 0. Let me check the `rescuer_thread`. Something is causing the preemption count to be non-zero during the purge.
|
| 53/2 |
2026/05/30 12:31 |
tool |
read-file |
0m
Args: map[File:kernel/workqueue.c FirstLine:3550 LineCount:100]
Results: map[Contents:3550: * developing into deadlock if some works currently on the same queue
3551: * need to be processed to satisfy the GFP_KERNEL allocation. This is
3552: * the problem rescuer solves.
3553: *
3554: * When such condition is possible, the pool summons rescuers of all
3555: * workqueues which have works queued on the pool and let them process
3556: * those works so that forward progress can be guaranteed.
3557: *
3558: * This should happen rarely.
3559: *
3560: * Return: 0
3561: */
3562: static int rescuer_thread(void *__rescuer)
3563: {
3564: struct worker *rescuer = __rescuer;
3565: struct workqueue_struct *wq = rescuer->rescue_wq;
3566: bool should_stop;
3567:
3568: set_user_nice(current, RESCUER_NICE_LEVEL);
3569:
3570: /*
3571: * Mark rescuer as worker too. As WORKER_PREP is never cleared, it
3572: * doesn't participate in concurrency management.
3573: */
3574: set_pf_worker(true);
3575: repeat:
3576: set_current_state(TASK_IDLE);
3577:
3578: /*
3579: * By the time the rescuer is requested to stop, the workqueue
3580: * shouldn't have any work pending, but @wq->maydays may still have
3581: * pwq(s) queued. This can happen by non-rescuer workers consuming
3582: * all the work items before the rescuer got to them. Go through
3583: * @wq->maydays processing before acting on should_stop so that the
3584: * list is always empty on exit.
3585: */
3586: should_stop = kthread_should_stop();
3587:
3588: /* see whether any pwq is asking for help */
3589: raw_spin_lock_irq(&wq_mayday_lock);
3590:
3591: while (!list_empty(&wq->maydays)) {
3592: struct pool_workqueue *pwq = list_first_entry(&wq->maydays,
3593: struct pool_workqueue, mayday_node);
3594: struct worker_pool *pool = pwq->pool;
3595: unsigned int count = 0;
3596:
3597: __set_current_state(TASK_RUNNING);
3598: list_del_init(&pwq->mayday_node);
3599:
3600: raw_spin_unlock_irq(&wq_mayday_lock);
3601:
3602: worker_attach_to_pool(rescuer, pool);
3603:
3604: raw_spin_lock_irq(&pool->lock);
3605:
3606: WARN_ON_ONCE(!list_empty(&rescuer->scheduled));
3607:
3608: while (assign_rescuer_work(pwq, rescuer)) {
3609: process_scheduled_works(rescuer);
3610:
3611: /*
3612: * If the per-turn work item limit is reached and other
3613: * PWQs are in mayday, requeue mayday for this PWQ and
3614: * let the rescuer handle the other PWQs first.
3615: */
3616: if (++count > RESCUER_BATCH && !list_empty(&pwq->wq->maydays) &&
3617: pwq->nr_active && need_to_create_worker(pool)) {
3618: raw_spin_lock(&wq_mayday_lock);
3619: send_mayday(pwq);
3620: raw_spin_unlock(&wq_mayday_lock);
3621: break;
3622: }
3623: }
3624:
3625: /* The cursor can not be left behind without the rescuer watching it. */
3626: if (!list_empty(&pwq->mayday_cursor.entry) && list_empty(&pwq->mayday_node))
3627: list_del_init(&pwq->mayday_cursor.entry);
3628:
3629: /*
3630: * Leave this pool. Notify regular workers; otherwise, we end up
3631: * with 0 concurrency and stalling the execution.
3632: */
3633: kick_pool(pool);
3634:
3635: raw_spin_unlock_irq(&pool->lock);
3636:
3637: worker_detach_from_pool(rescuer);
3638:
3639: /*
3640: * Put the reference grabbed by send_mayday(). @pool might
3641: * go away any time after it.
3642: */
3643: put_pwq_unlocked(pwq);
3644:
3645: raw_spin_lock_irq(&wq_mayday_lock);
3646: }
3647:
3648: raw_spin_unlock_irq(&wq_mayday_lock);
3649:
]
|
| 54/2 |
2026/05/30 12:31 |
llm |
expert |
0m
Model:
Tokens:
input: 48530
output: 35
thoughts: 119
Thoughts: Okay, let's break this down.
**Potential Concurrency Issue Spotted**
Hang on a second... the code snippet is calling `process_scheduled_works` *while* holding `pool->lock`. That feels... off. My immediate concern is whether `process_scheduled_works` might try to acquire the same lock, or a related one, leading to a deadlock. This is a classic concurrency problem, and I need to be sure this code is handling it correctly. The `WARN_ON_ONCE` suggests they are aware of *something*, but I'm not familiar enough with this particular codebase to know precisely what. Now, I need to investigate what that function actually *does*. I need to know what `process_scheduled_works` is doing to fully understand this. Let's dig into that function.
|
| 55/2 |
2026/05/30 12:31 |
tool |
read-file |
0m
Args: map[File:kernel/workqueue.c FirstLine:3370 LineCount:50]
Results: map[Contents:3370: * CONTEXT:
3371: * raw_spin_lock_irq(pool->lock) which may be released and regrabbed
3372: * multiple times.
3373: */
3374: static void process_scheduled_works(struct worker *worker)
3375: {
3376: struct work_struct *work;
3377: bool first = true;
3378:
3379: while ((work = list_first_entry_or_null(&worker->scheduled,
3380: struct work_struct, entry))) {
3381: if (first) {
3382: worker->pool->last_progress_ts = jiffies;
3383: first = false;
3384: }
3385: process_one_work(worker, work);
3386: }
3387: }
3388:
3389: static void set_pf_worker(bool val)
3390: {
3391: mutex_lock(&wq_pool_attach_mutex);
3392: if (val)
3393: current->flags |= PF_WQ_WORKER;
3394: else
3395: current->flags &= ~PF_WQ_WORKER;
3396: mutex_unlock(&wq_pool_attach_mutex);
3397: }
3398:
3399: /**
3400: * worker_thread - the worker thread function
3401: * @__worker: self
3402: *
3403: * The worker thread function. All workers belong to a worker_pool -
3404: * either a per-cpu one or dynamic unbound one. These workers process all
3405: * work items regardless of their specific target workqueue. The only
3406: * exception is work items which belong to workqueues with a rescuer which
3407: * will be explained in rescuer_thread().
3408: *
3409: * Return: 0
3410: */
3411: static int worker_thread(void *__worker)
3412: {
3413: struct worker *worker = __worker;
3414: struct worker_pool *pool = worker->pool;
3415:
3416: /* tell the scheduler that this is a workqueue worker */
3417: set_pf_worker(true);
3418: woke_up:
3419: raw_spin_lock_irq(&pool->lock);
]
|
| 56/2 |
2026/05/30 12:31 |
llm |
expert |
0m
Model:
Tokens:
input: 49570
output: 36
thoughts: 31
Thoughts: Okay, here's my take on those thoughts, framed as an expert's internal monologue:
**Investigating a Concurrency Concern**
Alright, hang on a second. I'm noticing something potentially problematic here. The code calls `process_one_work`, but I see that it's doing so while `pool->lock` is *already* held. That's a red flag – a potential deadlock situation or at least some unnecessary contention. I need to dig into `process_one_work` immediately to understand what's happening within that function while a lock is held. This could be a critical area to examine for any performance bottlenecks or race conditions. This is going to require a closer look at the data being accessed in `process_one_work`, and the locks that are used or accessed.
|
| 57/2 |
2026/05/30 12:31 |
tool |
read-file |
0m
Args: map[File:kernel/workqueue.c FirstLine:3200 LineCount:100]
Results: map[Contents:3200: static void process_one_work(struct worker *worker, struct work_struct *work)
3201: __releases(&pool->lock)
3202: __acquires(&pool->lock)
3203: {
3204: struct pool_workqueue *pwq = get_work_pwq(work);
3205: struct worker_pool *pool = worker->pool;
3206: unsigned long work_data;
3207: int lockdep_start_depth, rcu_start_depth;
3208: bool bh_draining = pool->flags & POOL_BH_DRAINING;
3209: #ifdef CONFIG_LOCKDEP
3210: /*
3211: * It is permissible to free the struct work_struct from
3212: * inside the function that is called from it, this we need to
3213: * take into account for lockdep too. To avoid bogus "held
3214: * lock freed" warnings as well as problems when looking into
3215: * work->lockdep_map, make a copy and use that here.
3216: */
3217: struct lockdep_map lockdep_map;
3218:
3219: lockdep_copy_map(&lockdep_map, &work->lockdep_map);
3220: #endif
3221: /* ensure we're on the correct CPU */
3222: WARN_ON_ONCE(!(pool->flags & POOL_DISASSOCIATED) &&
3223: raw_smp_processor_id() != pool->cpu);
3224:
3225: /* claim and dequeue */
3226: debug_work_deactivate(work);
3227: hash_add(pool->busy_hash, &worker->hentry, (unsigned long)work);
3228: worker->current_work = work;
3229: worker->current_func = work->func;
3230: worker->current_pwq = pwq;
3231: if (worker->task)
3232: worker->current_at = worker->task->se.sum_exec_runtime;
3233: worker->current_start = jiffies;
3234: work_data = *work_data_bits(work);
3235: worker->current_color = get_work_color(work_data);
3236:
3237: /*
3238: * Record wq name for cmdline and debug reporting, may get
3239: * overridden through set_worker_desc().
3240: */
3241: strscpy(worker->desc, pwq->wq->name, WORKER_DESC_LEN);
3242:
3243: list_del_init(&work->entry);
3244:
3245: /*
3246: * CPU intensive works don't participate in concurrency management.
3247: * They're the scheduler's responsibility. This takes @worker out
3248: * of concurrency management and the next code block will chain
3249: * execution of the pending work items.
3250: */
3251: if (unlikely(pwq->wq->flags & WQ_CPU_INTENSIVE))
3252: worker_set_flags(worker, WORKER_CPU_INTENSIVE);
3253:
3254: /*
3255: * Kick @pool if necessary. It's always noop for per-cpu worker pools
3256: * since nr_running would always be >= 1 at this point. This is used to
3257: * chain execution of the pending work items for WORKER_NOT_RUNNING
3258: * workers such as the UNBOUND and CPU_INTENSIVE ones.
3259: */
3260: kick_pool(pool);
3261:
3262: /*
3263: * Record the last pool and clear PENDING which should be the last
3264: * update to @work. Also, do this inside @pool->lock so that
3265: * PENDING and queued state changes happen together while IRQ is
3266: * disabled.
3267: */
3268: set_work_pool_and_clear_pending(work, pool->id, pool_offq_flags(pool));
3269:
3270: pwq->stats[PWQ_STAT_STARTED]++;
3271: raw_spin_unlock_irq(&pool->lock);
3272:
3273: rcu_start_depth = rcu_preempt_depth();
3274: lockdep_start_depth = lockdep_depth(current);
3275: /* see drain_dead_softirq_workfn() */
3276: if (!bh_draining)
3277: lock_map_acquire(pwq->wq->lockdep_map);
3278: lock_map_acquire(&lockdep_map);
3279: /*
3280: * Strictly speaking we should mark the invariant state without holding
3281: * any locks, that is, before these two lock_map_acquire()'s.
3282: *
3283: * However, that would result in:
3284: *
3285: * A(W1)
3286: * WFC(C)
3287: * A(W1)
3288: * C(C)
3289: *
3290: * Which would create W1->C->W1 dependencies, even though there is no
3291: * actual deadlock possible. There are two solutions, using a
3292: * read-recursive acquire on the work(queue) 'locks', but this will then
3293: * hit the lockdep limitation on recursive locks, or simply discard
3294: * these locks.
3295: *
3296: * AFAICT there is no possible deadlock scenario between the
3297: * flush_work() and complete() primitives (except for single-threaded
3298: * workqueues), so hiding them isn't a problem.
3299: */
]
|
| 58/2 |
2026/05/30 12:31 |
llm |
expert |
0m
Model:
Tokens:
input: 51477
output: 40
thoughts: 2203
Thoughts: **RCU Starvation in batadv_dat_purge: A Deep Dive**
Okay, so `process_one_work` releases the `pool->lock`, meaning `batadv_dat_purge` runs without the lock and with interrupts enabled. The preempt count is zero, so preemption *should* be possible. Yet, there's RCU starvation. That's a problem. If `batadv_dat_purge` is running, `need_resched()` is set, and `preempt_check_resched()` calls `schedule()`, then `rcu_preempt` *should* run. Why isn't it? Is `rcu_preempt` even runnable? The logs say it's TASK_RUNNING. Okay, that's good. But, could it be assigned to a *different* CPU, and the log just shows the current CPU? The log shows CPU 0, so it's assigned there, and `batadv_dat_purge` is running on CPU 0. So why isn't `schedule()` picking `rcu_preempt`?
Aha! `batadv_dat_purge` might not be calling `schedule()` directly. Let's trace it back. The crucial clue is `preempt_check_resched()` is called *at the end* of `__local_bh_enable_ip`. But, that's only if `CONFIG_TRACE_IRQFLAGS` is defined. Does `__local_bh_enable_ip` run with interrupts disabled? Let's trace through `spin_unlock_bh` which calls `__raw_spin_unlock_bh`, which calls `__local_bh_enable_ip`. `do_raw_spin_unlock` does *not* disable interrupts. So `__local_bh_enable_ip` is called with interrupts enabled!
Here's the critical bit: `__local_bh_enable_ip` has this: `if (unlikely(!in_interrupt() && local_softirq_pending())) { do_softirq(); }`. If `do_softirq()` runs, then softirqs are processed. And inside `do_softirq()`, it calls `handle_softirqs()`. Importantly, `handle_softirqs()` has a loop and it runs up to 10 times or for 2ms *with interrupts enabled*.
So, it's possible for there to be a packet storm or some activity in an interrupt that causes a 2ms delay while processing softirqs. If `handle_softirqs` processes for 2ms and returns, then `__local_bh_enable_ip` *then* calls `preempt_check_resched()`. If `preempt_check_resched` calls `schedule()`, the scheduler might switch to `rcu_preempt`.
Why isn't that happening? Let's check the stack trace of `kworker/R-bat_e`. Wait! The stack trace shows `<IRQ>`! Meaning it is running in an interrupt context. But `do_softirq` is called from `__local_bh_enable_ip`. Does `__local_bh_enable_ip` count as `<IRQ>` context? Yes, because `do_softirq` processes the interrupts/softirqs and it is tagged as an interrupt by the stack unwinder. So, if `handle_softirqs()` is busy, and there's a packet storm, it can loop for 2ms and then return. So then `batadv_dat_purge` loops. But is the 2ms long enough? If it loops 1024 times, it's 2 seconds. That's NOT enough for the 5.8s RCU stall. Aha! What if the `batadv_dat_purge` loop is *more* than 1024 times. Or what if `bat_priv->dat.hash->size` is larger? So does `batadv_hash_new(1024)` create a hash of size 1024? This needs further investigation.
|
| 59/2 |
2026/05/30 12:32 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/batman-adv/hash.c Name:batadv_hash_new]
Results: map[SourceCode: 38: /**
39: * batadv_hash_new() - Allocates and clears the hashtable
40: * @size: number of hash buckets to allocate
41: *
42: * Return: newly allocated hashtable, NULL on errors
43: */
44: struct batadv_hashtable *batadv_hash_new(u32 size)
45: {
46: struct batadv_hashtable *hash;
47:
48: hash = kmalloc_obj(*hash, GFP_ATOMIC);
49: if (!hash)
50: return NULL;
51:
52: hash->table = kmalloc_objs(*hash->table, size, GFP_ATOMIC);
53: if (!hash->table)
54: goto free_hash;
55:
56: hash->list_locks = kmalloc_objs(*hash->list_locks, size, GFP_ATOMIC);
57: if (!hash->list_locks)
58: goto free_table;
59:
60: hash->size = size;
61: batadv_hash_init(hash);
62: return hash;
63:
64: free_table:
65: kfree(hash->table);
66: free_hash:
67: kfree(hash);
68: return NULL;
69: }
SourceFile:net/batman-adv/hash.c]
|
| 60/2 |
2026/05/30 12:32 |
llm |
expert |
0m
Model:
Tokens:
input: 55040
output: 18
thoughts: 628
Thoughts: **Analyzing a Potential Softlockup**
Okay, so the size is definitively 1024. That gives us 1024 iterations * 2ms = 2.048 seconds, in an ideal scenario. But the critical question is, what if `handle_softirqs` actually takes longer than 2ms? The code breaks its loop if `time_before(jiffies, end)` is false. `end` is determined by `jiffies + MAX_SOFTIRQ_TIME`, where `MAX_SOFTIRQ_TIME` is set to `msecs_to_jiffies(2)`.
If `jiffies` doesn't advance, we'll loop up to `MAX_SOFTIRQ_RESTART` (which is 10) times. But that means if `jiffies` *does* advance, the loop will stop after those initial 2ms.
Then I remembered, KASAN is enabled! KASAN dramatically slows things down. So, a single packet might take a significant amount of time to process. But `handle_softirqs` explicitly checks `jiffies`. So, it *should* still break after 2ms (or one jiffy, if HZ=1000). But that still means the total processing time will still be 1024 iterations * 2ms = 2 seconds.
Could `batadv_dat_purge` be called multiple times? I know it's a delayed work, and it reschedules itself through `__batadv_dat_purge` and `batadv_dat_start_timer`. But wait, what if there are multiple `batadv_priv` instances? Multiple batman-adv interfaces would mean multiple `batadv_dat_purge` works, which are processed sequentially, and the rescuer thread will call `schedule()` in-between. Wait, if the rescuer thread calls `schedule()`, it yields!
So, *why* are we seeing a softlockup? The log says `Tainted: [L]=SOFTLOCKUP`, and the RCU stall says, "rcu_preempt kthread starved for 580 jiffies!" At 100 Hz, that's 5.8 seconds. At 250 Hz, it's 2.32 seconds. And if HZ is 250, then 2.048 seconds is very close to 2.32 seconds. Maybe the kernel is running with HZ=250? We should check the kernel configuration.
|
| 61/2 |
2026/05/30 12:32 |
tool |
grepper |
0m
Args: map[Expression:CONFIG_HZ=]
Results: map[Output:Documentation/core-api/timekeeping.rst=100=Some additional variants exist for more specialized cases:
--
Documentation/core-api/timekeeping.rst-122- The time returned here corresponds to the last timer tick, which
Documentation/core-api/timekeeping.rst:123: may be as much as 10ms in the past (for CONFIG_HZ=100), same as
Documentation/core-api/timekeeping.rst-124- reading the 'jiffies' variable. These are only useful when called
--
arch/arc/configs/tb10x_defconfig=27=CONFIG_ARC_CACHE_LINE_SHIFT=5
arch/arc/configs/tb10x_defconfig:28:CONFIG_HZ=250
arch/arc/configs/tb10x_defconfig-29-CONFIG_BUILTIN_DTB_NAME="abilis_tb100_dvk"
--
arch/mips/configs/bmips_stb_defconfig=3=CONFIG_NO_HZ=y
arch/mips/configs/bmips_stb_defconfig:4:CONFIG_HZ=1000
arch/mips/configs/bmips_stb_defconfig-5-CONFIG_BLK_DEV_INITRD=y
--
drivers/media/dvb-frontends/dib0090.c=2542=static int dib0090_set_params(struct dvb_frontend *fe)
--
drivers/media/dvb-frontends/dib0090.c-2556- * the actual sleep time depends on CONFIG_HZ. The worse case
drivers/media/dvb-frontends/dib0090.c:2557: * is when CONFIG_HZ=100. In such case, the minimum granularity
drivers/media/dvb-frontends/dib0090.c-2558- * is 10ms. On some real field tests, the tuner sometimes don't
--
drivers/media/dvb-frontends/dib8000.c=3604=static int dib8000_set_frontend(struct dvb_frontend *fe)
--
drivers/media/dvb-frontends/dib8000.c-3672- * the actual sleep time depends on CONFIG_HZ. The worse case
drivers/media/dvb-frontends/dib8000.c:3673: * is when CONFIG_HZ=100. In such case, the minimum granularity
drivers/media/dvb-frontends/dib8000.c-3674- * is 10ms. On some real field tests, the tuner sometimes don't
--
tools/power/cpupower/bench/cpufreq-bench_script.sh=15=UP_THRESHOLD="60 80 95"
--
tools/power/cpupower/bench/cpufreq-bench_script.sh-18-# E.g. before git commit cef9615a853ebc4972084f7 one could only set
tools/power/cpupower/bench/cpufreq-bench_script.sh:19:# min sampling rate of 80000 if CONFIG_HZ=250
tools/power/cpupower/bench/cpufreq-bench_script.sh-20-SAMPLING_RATE="20000 80000"
--
tools/testing/selftests/net/packetdrill/config:1:CONFIG_HZ=1000
tools/testing/selftests/net/packetdrill/config-2-CONFIG_HZ_1000=y
]
|
| 62/2 |
2026/05/30 12:32 |
llm |
expert |
0m
Model:
Tokens:
input: 57142
output: 36
thoughts: 791
Thoughts: **Analyzing a Potential Softlockup in the Kernel**
Okay, so I'm looking at this kernel configuration, and it seems like syzbot usually sets `CONFIG_HZ=100`. Now, if that's the case, 580 jiffies translates to 5.8 seconds. My initial thought was that if `batadv_dat_purge` takes 2 seconds, it shouldn't be enough to trigger a 5.8-second stall. But what if there are multiple instances of `batadv_dat_purge` queued? Or, what if `spin_unlock_bh` takes longer than 2ms?
Then, I started thinking about `handle_softirqs` – that thing loops up to 10 times. With KASAN enabled, processing a single packet could take 1ms. If the loop runs 10 times, that's 10ms. If this happens 1024 times, that's 10.24 seconds! But, wait a minute, `handle_softirqs` checks `time_before(jiffies, end)`. If `jiffies` hasn't advanced, it'll loop up to 10 times.
Hold on... with KASAN, interrupts are disabled during `local_irq_disable()`, but `handle_softirqs` runs with *enabled* interrupts. So `jiffies` *should* advance! If `jiffies` advances, it should break after 2ms. However, `jiffies` is updated by the timer interrupt. If the timer interrupt is delayed, `jiffies` might not advance! But interrupts are enabled, so that shouldn't happen...
Let's assume that `handle_softirqs` processes ONE packet and that ONE packet takes 5ms. Then, it will break after one loop, but it still took 5ms. If we extrapolate, 1024 buckets * 5ms = 5.12 seconds! What if there are multiple batman-adv interfaces? With two interfaces, this could be 10 seconds!
Okay, I'm pretty sure this is a Denial of Service (specifically, a softlockup/RCU stall) caused by a packet storm combined with the `spin_unlock_bh` calls within a loop. The core of the issue is that `__batadv_dat_purge` iterates 1024 times, and within each iteration, it calls `spin_unlock_bh`, which processes softirqs. If there's a packet storm, it will process softirqs for up to 2ms (or potentially more, depending on packet processing time) *on each iteration*. This means the loop could potentially take 1024 * 2ms = 2 seconds, or even longer. During this time, the task *doesn't* yield the CPU, because `preempt_count()` is not zero during `spin_unlock_bh`, and even *after* `spin_unlock_bh`, it immediately calls `spin_lock_bh` for the next bucket.
Wait a second, after `spin_unlock_bh`, `preempt_count()` *is* zero! Does it call `schedule()`? `spin_unlock_bh` calls `__local_bh_enable_ip`, which then calls `preempt_check_resched()`. If `need_resched()` is set, `preempt_check_resched()` *will* call `schedule()`! If that's the case, then the task *does* yield! So if it yields, why is there a softlockup? Let me re-examine `preempt_check_resched()`...
|
| 63/2 |
2026/05/30 12:32 |
tool |
read-file |
0m
Args: map[File:include/linux/preempt.h FirstLine:230 LineCount:50]
Results: map[Contents: 230: barrier(); \
231: if (unlikely(preempt_count_dec_and_test())) \
232: __preempt_schedule(); \
233: } while (0)
234:
235: #define preempt_enable_notrace() \
236: do { \
237: barrier(); \
238: if (unlikely(__preempt_count_dec_and_test())) \
239: __preempt_schedule_notrace(); \
240: } while (0)
241:
242: #define preempt_check_resched() \
243: do { \
244: if (should_resched(0)) \
245: __preempt_schedule(); \
246: } while (0)
247:
248: #else /* !CONFIG_PREEMPTION */
249: #define preempt_enable() \
250: do { \
251: barrier(); \
252: preempt_count_dec(); \
253: } while (0)
254:
255: #define preempt_enable_notrace() \
256: do { \
257: barrier(); \
258: __preempt_count_dec(); \
259: } while (0)
260:
261: #define preempt_check_resched() do { } while (0)
262: #endif /* CONFIG_PREEMPTION */
263:
264: #define preempt_disable_notrace() \
265: do { \
266: __preempt_count_inc(); \
267: barrier(); \
268: } while (0)
269:
270: #define preempt_enable_no_resched_notrace() \
271: do { \
272: barrier(); \
273: __preempt_count_dec(); \
274: } while (0)
275:
276: #else /* !CONFIG_PREEMPT_COUNT */
277:
278: /*
279: * Even if we don't have any preemption, we need preempt disable/enable
]
|
| 64/2 |
2026/05/30 12:32 |
llm |
expert |
0m
Model:
Tokens:
input: 59441
output: 35
thoughts: 43
Thoughts: Okay, let's see...
**Deconstructing the Scheduler's Logic**
Alright, so `preempt_check_resched()` is the function in question. I recall that it actually calls `__preempt_schedule()` under a specific condition. Hmm, let me think... that condition is if `should_resched(0)` evaluates to true. Makes sense, that's where the actual context switch presumably happens. So, the crucial question is: what makes `should_resched()` return true? It looks like I need to delve into how `should_resched()` functions. Let me get a look at that implementation.
|
| 65/2 |
2026/05/30 12:32 |
tool |
read-file |
0m
Args: map[File:include/linux/preempt.h FirstLine:70 LineCount:50]
Results: map[Contents: 70: *
71: * preempt_count() == 2*PREEMPT_DISABLE_OFFSET
72: *
73: * Note: PREEMPT_DISABLE_OFFSET is 0 for !PREEMPT_COUNT kernels.
74: * Note: See finish_task_switch().
75: */
76: #define FORK_PREEMPT_COUNT (2*PREEMPT_DISABLE_OFFSET + PREEMPT_ENABLED)
77:
78: /* preempt_count() and related functions, depends on PREEMPT_NEED_RESCHED */
79: #include <asm/preempt.h>
80:
81: /**
82: * interrupt_context_level - return interrupt context level
83: *
84: * Returns the current interrupt context level.
85: * 0 - normal context
86: * 1 - softirq context
87: * 2 - hardirq context
88: * 3 - NMI context
89: */
90: static __always_inline unsigned char interrupt_context_level(void)
91: {
92: unsigned long pc = preempt_count();
93: unsigned char level = 0;
94:
95: level += !!(pc & (NMI_MASK));
96: level += !!(pc & (NMI_MASK | HARDIRQ_MASK));
97: level += !!(pc & (NMI_MASK | HARDIRQ_MASK | SOFTIRQ_OFFSET));
98:
99: return level;
100: }
101:
102: /*
103: * These macro definitions avoid redundant invocations of preempt_count()
104: * because such invocations would result in redundant loads given that
105: * preempt_count() is commonly implemented with READ_ONCE().
106: */
107:
108: #define nmi_count() (preempt_count() & NMI_MASK)
109: #define hardirq_count() (preempt_count() & HARDIRQ_MASK)
110: #ifdef CONFIG_PREEMPT_RT
111: # define softirq_count() (current->softirq_disable_cnt & SOFTIRQ_MASK)
112: # define irq_count() ((preempt_count() & (NMI_MASK | HARDIRQ_MASK)) | softirq_count())
113: #else
114: # define softirq_count() (preempt_count() & SOFTIRQ_MASK)
115: # define irq_count() (preempt_count() & (NMI_MASK | HARDIRQ_MASK | SOFTIRQ_MASK))
116: #endif
117:
118: /*
119: * Macros to retrieve the current execution context:
]
|
| 66/2 |
2026/05/30 12:32 |
llm |
expert |
0m
Model:
Tokens:
input: 60448
output: 19
thoughts: 10
Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience, and written in the first person:
**Finding `should_resched`**
Alright, let's get down to brass tacks. The immediate goal is to locate the variable or function, `should_resched`. Depending on the context I am in, this could be a few things. It could be a simple boolean variable used in a scheduling loop, or a method call that encapsulates a more complex rescheduling logic. I need to figure out where it's being used and, more importantly, *why* we're interested in it. Is this part of debugging, optimization, or a refactoring effort? Knowing the surrounding code will determine my path.
My first thought is, is this a global or local variable? Or is it scoped to a class? Knowing the scope will help me narrow my search significantly. I might start with a quick text search across the relevant codebase. A smarter approach, depending on my tooling, would be to use cross-referencing capabilities to find every instance where `should_resched` is used (called/accessed/assigned). I should be particularly careful with this approach because it will only find direct instances, and would likely not follow any aliasing or abstraction.
I will need to consider the broader system design and what rescheduling implies. How is the system scheduling the work? What are the criteria to check, and are they being evaluated in the `should_resched`? Are there external dependencies? And am I looking at one particular task or something more general, like scheduling of the system itself? I need to understand the intent.
Ultimately, I need to know where this variable is *defined*, where it's *modified*, and where it's *used* to determine if something needs rescheduling. This is where I'll get to the real meat of the matter.
|
| 67/2 |
2026/05/30 12:32 |
tool |
grepper |
0m
Error:
Args: map[Expression:define should_resched]
Results:
|
| 68/2 |
2026/05/30 12:32 |
llm |
expert |
0m
Model:
Tokens:
input: 60884
output: 18
thoughts: 11
Thoughts: Okay, here's my attempt at summarizing the thought process, as requested, geared toward an expert audience, and written in the first person:
**Investigating the Rescheduling Logic**
Alright, I'm diving in. The first thing that comes to mind is to get right to the core of the rescheduling process. I need to understand the logic behind when and why tasks get rescheduled. So, let's start with a focused search for `should_resched`. That variable name, or something similar, is the most likely entry point to where this crucial decision-making process is managed. I'm expecting to find a function, method, or conditional block that evaluates conditions and ultimately determines whether a task is flagged for rescheduling. This search will ideally lead me to the heart of the system's ability to adapt to changes, failures, or other dynamic events. From there, I can begin to dissect the specific factors that influence this critical `should_resched` flag and how the system is designed to respond to these triggers.
|
| 69/2 |
2026/05/30 12:32 |
tool |
grepper |
0m
Args: map[Expression:should_resched]
Results: map[Output:arch/arm64/include/asm/preempt.h=58=static inline bool __preempt_count_dec_and_test(void)
--
arch/arm64/include/asm/preempt.h-75-
arch/arm64/include/asm/preempt.h:76:static inline bool should_resched(int preempt_offset)
arch/arm64/include/asm/preempt.h-77-{
--
arch/s390/include/asm/preempt.h=114=static __always_inline bool __preempt_count_dec_and_test(void)
--
arch/s390/include/asm/preempt.h-136- */
arch/s390/include/asm/preempt.h:137:static __always_inline bool should_resched(int preempt_offset)
arch/s390/include/asm/preempt.h-138-{
--
arch/x86/include/asm/preempt.h=93=static __always_inline bool __preempt_count_dec_and_test(void)
--
arch/x86/include/asm/preempt.h-101- */
arch/x86/include/asm/preempt.h:102:static __always_inline bool should_resched(int preempt_offset)
arch/x86/include/asm/preempt.h-103-{
--
include/asm-generic/preempt.h=62=static __always_inline bool __preempt_count_dec_and_test(void)
--
include/asm-generic/preempt.h-74- */
include/asm-generic/preempt.h:75:static __always_inline bool should_resched(int preempt_offset)
include/asm-generic/preempt.h-76-{
--
include/linux/preempt.h=194=extern void preempt_count_sub(int val);
include/linux/preempt.h-195-#define preempt_count_dec_and_test() \
include/linux/preempt.h:196: ({ preempt_count_sub(1); should_resched(0); })
include/linux/preempt.h-197-#else
--
include/linux/preempt.h=243=do { \
include/linux/preempt.h:244: if (should_resched(0)) \
include/linux/preempt.h-245- __preempt_schedule(); \
--
kernel/sched/core.c=7693=int __sched __cond_resched(void)
kernel/sched/core.c-7694-{
kernel/sched/core.c:7695: if (should_resched(0) && !irqs_disabled()) {
kernel/sched/core.c-7696- preempt_schedule_common();
--
kernel/sched/core.c=7760=int __cond_resched_lock(spinlock_t *lock)
kernel/sched/core.c-7761-{
kernel/sched/core.c:7762: int resched = should_resched(PREEMPT_LOCK_OFFSET);
kernel/sched/core.c-7763- int ret = 0;
--
kernel/sched/core.c=7778=int __cond_resched_rwlock_read(rwlock_t *lock)
kernel/sched/core.c-7779-{
kernel/sched/core.c:7780: int resched = should_resched(PREEMPT_LOCK_OFFSET);
kernel/sched/core.c-7781- int ret = 0;
--
kernel/sched/core.c=7796=int __cond_resched_rwlock_write(rwlock_t *lock)
kernel/sched/core.c-7797-{
kernel/sched/core.c:7798: int resched = should_resched(PREEMPT_LOCK_OFFSET);
kernel/sched/core.c-7799- int ret = 0;
]
|
| 70/2 |
2026/05/30 12:32 |
llm |
expert |
0m
Model:
Tokens:
input: 62151
output: 41
thoughts: 27
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Diving into Preemption Logic**
Okay, so I need to understand the preemption mechanics at the architecture level, specifically for x86. The relevant logic seems to be around determining if a reschedule is required. My gut feeling is that `should_resched` in `arch/x86/include/asm/preempt.h` is where the key decision point resides. Let's start there. I'm mentally parsing the likely scenarios: interrupt handling, task switching, and the interplay with spinlocks and other synchronization primitives. I'm expecting to see flags, possibly atomic operations, and perhaps architecture-specific optimizations related to the hardware's interrupt controller or task state segment management. Let's see if my initial hunch is correct. I anticipate finding a definition that ties into the global interrupt enable/disable state and the current task's preemption count. I'll need to consider how this interacts with the various context-switching paths, especially those involving `schedule()`. I'm also preparing myself to look for potential race conditions or locking issues within the preemption checks themselves.
|
| 71/2 |
2026/05/30 12:32 |
tool |
read-file |
0m
Args: map[File:arch/x86/include/asm/preempt.h FirstLine:90 LineCount:50]
Results: map[Contents: 90: * a decrement which hits zero means we have no preempt_count and should
91: * reschedule.
92: */
93: static __always_inline bool __preempt_count_dec_and_test(void)
94: {
95: return GEN_UNARY_RMWcc("decl", __my_cpu_var(__preempt_count), e,
96: __percpu_arg([var]));
97: }
98:
99: /*
100: * Returns true when we need to resched and can (barring IRQ state).
101: */
102: static __always_inline bool should_resched(int preempt_offset)
103: {
104: return unlikely(raw_cpu_read_4(__preempt_count) == preempt_offset);
105: }
106:
107: #ifdef CONFIG_PREEMPTION
108:
109: extern asmlinkage void preempt_schedule(void);
110: extern asmlinkage void preempt_schedule_thunk(void);
111:
112: #define preempt_schedule_dynamic_enabled preempt_schedule_thunk
113: #define preempt_schedule_dynamic_disabled NULL
114:
115: extern asmlinkage void preempt_schedule_notrace(void);
116: extern asmlinkage void preempt_schedule_notrace_thunk(void);
117:
118: #define preempt_schedule_notrace_dynamic_enabled preempt_schedule_notrace_thunk
119: #define preempt_schedule_notrace_dynamic_disabled NULL
120:
121: #ifdef CONFIG_PREEMPT_DYNAMIC
122:
123: DECLARE_STATIC_CALL(preempt_schedule, preempt_schedule_dynamic_enabled);
124:
125: #define __preempt_schedule() \
126: do { \
127: __STATIC_CALL_MOD_ADDRESSABLE(preempt_schedule); \
128: asm volatile ("call " STATIC_CALL_TRAMP_STR(preempt_schedule) : ASM_CALL_CONSTRAINT); \
129: } while (0)
130:
131: DECLARE_STATIC_CALL(preempt_schedule_notrace, preempt_schedule_notrace_dynamic_enabled);
132:
133: #define __preempt_schedule_notrace() \
134: do { \
135: __STATIC_CALL_MOD_ADDRESSABLE(preempt_schedule_notrace); \
136: asm volatile ("call " STATIC_CALL_TRAMP_STR(preempt_schedule_notrace) : ASM_CALL_CONSTRAINT); \
137: } while (0)
138:
139: #else /* PREEMPT_DYNAMIC */
]
|
| 72/2 |
2026/05/30 12:32 |
llm |
expert |
0m
Model:
Tokens:
input: 63276
output: 41
thoughts: 66
Thoughts: **Analyzing the Preemption Logic**
Okay, so `should_resched` is designed to determine if a reschedule is actually needed. It looks like the key is this comparison: `__preempt_count == preempt_offset`. Hmm, interesting. I need to be careful here, because I remember that `__preempt_count` isn't just a simple count. It actually holds both the current preempt count and the `TIF_NEED_RESCHED` flag. I have to remind myself to take that into consideration. I need to understand this better to figure out how the kernel decides when to initiate a reschedule. I should check how `__preempt_count` is defined, precisely. I want to see how these bits and pieces are arranged and interpreted.
|
| 73/2 |
2026/05/30 12:33 |
tool |
read-file |
0m
Args: map[File:arch/x86/include/asm/preempt.h FirstLine:10 LineCount:50]
Results: map[Contents: 10: DECLARE_PER_CPU_CACHE_HOT(int, __preempt_count);
11:
12: /* We use the MSB mostly because its available */
13: #define PREEMPT_NEED_RESCHED 0x80000000
14:
15: /*
16: * We use the PREEMPT_NEED_RESCHED bit as an inverted NEED_RESCHED such
17: * that a decrement hitting 0 means we can and should reschedule.
18: */
19: #define PREEMPT_ENABLED (0 + PREEMPT_NEED_RESCHED)
20:
21: /*
22: * We mask the PREEMPT_NEED_RESCHED bit so as not to confuse all current users
23: * that think a non-zero value indicates we cannot preempt.
24: */
25: static __always_inline int preempt_count(void)
26: {
27: return raw_cpu_read_4(__preempt_count) & ~PREEMPT_NEED_RESCHED;
28: }
29:
30: static __always_inline void preempt_count_set(int pc)
31: {
32: int old, new;
33:
34: old = raw_cpu_read_4(__preempt_count);
35: do {
36: new = (old & PREEMPT_NEED_RESCHED) |
37: (pc & ~PREEMPT_NEED_RESCHED);
38: } while (!raw_cpu_try_cmpxchg_4(__preempt_count, &old, new));
39: }
40:
41: /*
42: * must be macros to avoid header recursion hell
43: */
44: #define init_task_preempt_count(p) do { } while (0)
45:
46: #define init_idle_preempt_count(p, cpu) do { \
47: per_cpu(__preempt_count, (cpu)) = PREEMPT_DISABLED; \
48: } while (0)
49:
50: /*
51: * We fold the NEED_RESCHED bit into the preempt count such that
52: * preempt_enable() can decrement and test for needing to reschedule with a
53: * single instruction.
54: *
55: * We invert the actual bit, so that when the decrement hits 0 we know we both
56: * need to resched (the bit is cleared) and can resched (no preempt count).
57: */
58:
59: static __always_inline void set_preempt_need_resched(void)
]
|
| 74/2 |
2026/05/30 12:33 |
llm |
expert |
0m
Model:
Tokens:
input: 64300
output: 26
thoughts: 1262
Thoughts: **Debugging a Preemptible Task Starvation**
Okay, so I'm looking at this `PREEMPT_NEED_RESCHED` bit, and it's inverted – that's a key observation. When `NEED_RESCHED` is set, the `PREEMPT_NEED_RESCHED` bit is *cleared*. Therefore, `__preempt_count` will be 0 when both `preempt_count` is 0 AND `NEED_RESCHED` is set. `should_resched(0)` checks `__preempt_count == 0`, and I see that it returns true if `NEED_RESCHED` is set, and `preempt_count` is 0. This in turn calls `preempt_check_resched()`, which then calls `__preempt_schedule()`. That eventually leads to `preempt_schedule()` calling `__schedule()`. And `__schedule()` *should* be switching to `rcu_preempt`! So why is `rcu_preempt` starving?
I'm checking the `preempt_schedule()` function. There's a `preemptible()` check: `if (likely(!preemptible())) return;`. `preemptible()` checks if `preempt_count() == 0 && !irqs_disabled()`. Aha! If interrupts are disabled, it returns. Could this be the issue? I need to check where `preempt_check_resched()` is called from, specifically, in `__local_bh_enable_ip`.
Okay, I'm looking at `__local_bh_enable_ip` again. It *starts* with `lockdep_assert_irqs_enabled()`. So, IRQs *must* be enabled when `__local_bh_enable_ip` is called. The `do_softirq()` call won't disable IRQs permanently. And after the softirq, it calls `preempt_check_resched()`. So, definitely, IRQs are *enabled* when `preempt_check_resched()` gets called. Thus, if `preempt_count()` is 0, `preemptible()` should be true. Then, `preempt_schedule()` *should* call `__schedule()`, and it *should* switch to `rcu_preempt`. So why isn't it? This is quite odd.
Maybe I'm missing something, maybe the `need_resched()` flag isn't getting set? Why wouldn't `need_resched()` be set? Could the scheduler tick be missing? But if the CPU is in a softlockup, that means interrupts are enabled and the timer *is* firing. The timer interrupt calls `scheduler_tick()`, which, for a `SCHED_NORMAL` task, eventually calls `task_tick_fair()`. It checks if the task has run long enough, and then it calls `resched_curr()`. And `resched_curr()` *does* set `TIF_NEED_RESCHED` and clears `PREEMPT_NEED_RESCHED` in `__preempt_count`. So, `need_resched()` *should* be set! Therefore, `preempt_check_resched()` *should* call `schedule()`, and it *should* switch to `rcu_preempt`.
Wait a minute... is `rcu_preempt` even on CPU 0? "rcu_preempt kthread starved for 580 jiffies! g76773 f0x0 RCU_GP_WAIT_FQS(5) ->state=0x0 ->cpu=0." Yes, it *is* on CPU 0. `->state=0x0` means TASK_RUNNING, so it is on the runqueue. If it's on the runqueue, and `schedule()` is called, why isn't it picked? Could another task be higher priority? What if the `kworker/R-bat_e` is higher priority? The `kworker/R-bat_e` is a *rescuer* thread. Let me check the rescuer thread priority... `set_user_nice(current, RESCUER_NICE_LEVEL);`. Okay, what *is* `RESCUER_NICE_LEVEL`?
|
| 75/2 |
2026/05/30 12:33 |
tool |
codesearch-find-references |
0m
Error:
Args: map[Name:RESCUER_NICE_LEVEL]
Results: map[References:<nil> TruncatedOutput:false]
|
| 76/2 |
2026/05/30 12:33 |
llm |
expert |
0m
Model:
Tokens:
input: 66565
output: 22
thoughts: 15
Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience, and written in the first person:
**Initial Investigation into a Potential System Variable**
Alright, my first instinct is to dive directly into the code. The term `RESCUER_NICE_LEVEL` has immediately caught my attention. It suggests a system variable, likely related to resource management or process prioritization within a system. Given my understanding of operating systems and application design, I need to understand what this specifically controls and why. I'm going to start with a targeted search. I'm assuming this will be defined somewhere in the code base, likely a configuration file, a system header, or perhaps even hardcoded within a core module. The goal is to identify how `RESCUER_NICE_LEVEL` is used, its valid values (if any), and what impact different levels have on system behavior. This will likely involve looking into scheduling mechanisms or possibly even network related actions if the "rescuing" implied by the name extends to other systems. My initial search terms will be very focused, but I'll be prepared to broaden the search if the direct query yields no results.
|
| 77/2 |
2026/05/30 12:33 |
tool |
grepper |
0m
Args: map[Expression:RESCUER_NICE_LEVEL]
Results: map[Output:kernel/workqueue.c=106=enum wq_internal_consts {
--
kernel/workqueue.c-126- */
kernel/workqueue.c:127: RESCUER_NICE_LEVEL = MIN_NICE,
kernel/workqueue.c-128- HIGHPRI_NICE_LEVEL = MIN_NICE,
--
kernel/workqueue.c=3562=static int rescuer_thread(void *__rescuer)
--
kernel/workqueue.c-3567-
kernel/workqueue.c:3568: set_user_nice(current, RESCUER_NICE_LEVEL);
kernel/workqueue.c-3569-
]
|
| 78/2 |
2026/05/30 12:33 |
llm |
expert |
0m
Model:
Tokens:
input: 67038
output: 23
thoughts: 531
Thoughts: **Analyzing the Scheduler's Impact on a Thread Starvation Scenario**
Okay, let's break this down. My initial thought is: *Aha! The `RESCUER_NICE_LEVEL` is set to `MIN_NICE`! And `MIN_NICE` is -20.* This means the rescuer thread has a very high priority—the *highest* priority, in fact, because -20 is the most favorable nice value for a SCHED_NORMAL task. Now, compare this to the `rcu_preempt` kthread, which defaults to a nice value of 0.
So, the CFS (Completely Fair Scheduler) should always favor the rescuer thread over `rcu_preempt` if both are runnable. CFS works with vruntime. A task with a nice value of -20 gets an *enormous* time slice, and its vruntime advances much slower than a task with nice 0. Specifically, looking at the weights, nice -20 has a weight of 88761, while nice 0 has a weight of 1024. That means the rescuer thread gets roughly 88 times more CPU time.
If the rescuer thread is continuously runnable – say, looping within `batadv_dat_purge` – it'll consume almost all the CPU time! But wait, even with a huge time slice, shouldn't `rcu_preempt` still get *some* CPU time eventually? If it gets to run, the starvation should be cleared.
Okay, here's a wrinkle: `batadv_dat_purge` loops 1024 times. Each loop takes 2ms. So, the total loop execution time is 2 seconds. After those 2 seconds, if there is no more work, `batadv_dat_purge` *should* finish, the rescuer thread goes to sleep. At that point, `rcu_preempt` *should* get a chance to run!
But why am I seeing a 5.8-second stall? The 2-second loop isn't the whole story. Either there are other work items, or maybe the bridging loop is repeatedly queuing `batadv_dat_purge`?
Hmm, let's see. It appears `batadv_dat_purge` is a delayed work item. Specifically:
```c
__batadv_dat_purge(bat_priv, batadv_dat_to_purge);
batadv_dat_start_timer(bat_priv);
```
`batadv_dat_start_timer` is queuing it after `BATADV_DAT_PURGE_TIMEOUT`. I need to figure out what `BATADV_DAT_PURGE_TIMEOUT` is to see if this explains the stall.
|
| 79/2 |
2026/05/30 12:33 |
tool |
grepper |
0m
Error:
Args: map[Expression:BATADV_DAT_PURGE_TIMEOUT]
Results:
|
| 80/2 |
2026/05/30 12:33 |
llm |
expert |
0m
Model:
Tokens:
input: 68206
output: 46
thoughts: 15
Thoughts: Here's my thought process, summarized as you requested:
**Investigating a Timer Function Within BATMAN Advanced**
Okay, let's dive in. My immediate instinct is to pinpoint a specific timer function within the BATMAN Advanced code. I'm going to start by searching for `batadv_dat_start_timer`. That function name, based on my knowledge of BATMAN and generally of kernel programming practices, *should* be relatively specific. It's likely related to starting or initializing a timer associated with data structures within the BATADV subsystem. This search is a good starting point to understand the code flow surrounding it. From there, I can get a better understanding of how the timers are used.
|
| 81/2 |
2026/05/30 12:33 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/batman-adv/distributed-arp-table.c Name:batadv_dat_start_timer]
Results: map[SourceCode: 97: /**
98: * batadv_dat_start_timer() - initialise the DAT periodic worker
99: * @bat_priv: the bat priv with all the mesh interface information
100: */
101: static void batadv_dat_start_timer(struct batadv_priv *bat_priv)
102: {
103: queue_delayed_work(batadv_event_workqueue, &bat_priv->dat.work,
104: msecs_to_jiffies(10000));
105: }
SourceFile:net/batman-adv/distributed-arp-table.c]
|
| 82/2 |
2026/05/30 12:33 |
llm |
expert |
0m
Model:
Tokens:
input: 68606
output: 24
thoughts: 122
Thoughts: **Investigating a CPU Stall**
Okay, this is weird. The task I was expecting to run has been queued with a 10-second delay, so it shouldn't be running immediately again. But the CPU is still pegged for almost six seconds! That's not normal. Looking closer, the soft lockup is occurring within `__batadv_dat_purge`. Right, `batadv`… could there be an issue with the data structures? Specifically, could `bat_priv->dat.hash->size` somehow be *larger* than I expect? I know `batadv_hash_new(1024)` is used during the `batadv_dat_init` call, setting the hash table size to 1024, which is the expected size. But I need to eliminate the possibility that it's being reset somewhere else. Is there any other place in the code where `bat_priv->dat.hash` is re-initialized, or where its size could be inadvertently modified? I need to check every initialization of this hash.
|
| 83/2 |
2026/05/30 12:33 |
tool |
codesearch-find-references |
0m
Args: map[Name:batadv_hash_new]
Results: map[References:[map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:batadv_bla_init SourceFile:net/batman-adv/bridge_loop_avoidance.c SourceLine:1563] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:batadv_bla_init SourceFile:net/batman-adv/bridge_loop_avoidance.c SourceLine:1567] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:batadv_dat_init SourceFile:net/batman-adv/distributed-arp-table.c SourceLine:813] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:batadv_originator_init SourceFile:net/batman-adv/originator.c SourceLine:223] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:batadv_tt_global_init SourceFile:net/batman-adv/translation-table.c SourceLine:1366] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:batadv_tt_local_init SourceFile:net/batman-adv/translation-table.c SourceLine:539]] TruncatedOutput:false]
|
| 84/2 |
2026/05/30 12:33 |
llm |
expert |
0m
Model:
Tokens:
input: 69342
output: 25
thoughts: 1087
Thoughts: **Analyzing the Batadv Data Purge and Softlockups**
Okay, let's break this down. My initial thought was that `batadv_dat_init` sets a hash table size of 1024. That means `batadv_dat_purge` loops through 1024 entries. If each loop iteration took 2ms, we'd be looking at about 2 seconds. But hold on, what if `handle_softirqs` is the bottleneck? It can loop up to 10 times (`MAX_SOFTIRQ_RESTART`). A packet storm would ensure it loops the maximum number of times.
If each loop in `handle_softirqs` takes 1ms, that’s 10ms total. Multiplying that out by the 1024 iterations in the purge gives us 10.24 seconds, but why is it taking so long? Then I remember KASAN! KASAN adds significant overhead. The stack trace confirms this with the extra time spent in stack unwinding, memory poisoning, and other checks. That means processing a single packet takes longer!
If we assume a single packet takes 0.1ms (with KASAN), and `net_rx_action` processes up to 300 packets at a time (`netdev_budget`), that's 30ms per execution of the softirq handler. Now, `handle_softirqs` checks `time_before(jiffies, end)`. If more than 2ms have elapsed, it bails out. Since `net_rx_action` can take 30ms, the condition will be met, and it won't restart! It'll just call `wakeup_softirqd()` and return. This means the softirq handler takes no more than the duration of the one execution of `net_rx_action`, plus a little bit if it restarted.
Therefore, `handle_softirqs` will take at MOST 30ms. Then, `batadv_dat_purge` loops through 1024 entries, leading to 1024 * 30ms = 30.7 seconds. This perfectly explains the observed softlockups and RCU stalls! The root cause is `__batadv_dat_purge` calling `spin_unlock_bh` 1024 times. During a packet storm (like a bridging loop), `net_rx_action` processes a full budget of, say, 300 packets on EACH iteration, resulting in processing 307,200 packets!
Even without KASAN, if those 300 packets take 1ms, it’s still a full second. With KASAN, the 300 packets could take 10ms, or more. This locks the CPU in `__batadv_dat_purge` for an extended period, starving other tasks like `rcu_preempt` and triggering the RCU stalls and softlockups.
Is it exploitable? It’s a Denial of Service (softlockup/RCU stall). It requires creating a batman-adv interface and inducing a bridging loop. Creating a batman-adv interface usually requires `CAP_NET_ADMIN`. Can we do this in a user namespace? Let's investigate that...
|
| 85/2 |
2026/05/30 12:33 |
tool |
codesearch-find-references |
0m
Args: map[Name:batadv_netlink_family]
Results: map[References:<nil> TruncatedOutput:false]
|
| 86/2 |
2026/05/30 12:33 |
llm |
expert |
0m
Model:
Tokens:
input: 71184
output: 21
thoughts: 14
Thoughts: Okay, here's my summarized thought process, presented as if I were thinking it:
**Digging into the `batadv_netlink_family` Code**
Alright, time to dive into this. The objective is to understand what's happening with `batadv_netlink_family`. My initial instinct is to go straight for a search. I'm going to start with a broad search, probably using a tool like grep or ripgrep, to pinpoint all instances of `batadv_netlink_family` within the codebase. I need to see where it's *defined*, where it's *used*, and how it interacts with other parts of the batadv code, or potentially even interacts with the broader netlink subsystem. I'm looking for function calls, assignments, and struct member references. Essentially, I'm trying to map its role in the system. I'm also going to note the files where the search results are, that helps me organize my exploration. Then I can organize the information in a hierarchy, noting the significant files. I'll need to look at any include files as I trace through the code. I'm really looking to understand the underlying data structures, control flow, and what network communication or management aspects this family is responsible for. It could be for configuration, statistics, or even routing updates within the BATMAN-adv mesh network. This is going to be interesting!
|
| 87/2 |
2026/05/30 12:34 |
tool |
grepper |
0m
Args: map[Expression:batadv_netlink_family]
Results: map[Output:net/batman-adv/bat_algo.c=160=static int batadv_algo_dump_entry(struct sk_buff *msg, u32 portid, u32 seq,
--
net/batman-adv/bat_algo.c-164-
net/batman-adv/bat_algo.c:165: hdr = genlmsg_put(msg, portid, seq, &batadv_netlink_family,
net/batman-adv/bat_algo.c-166- NLM_F_MULTI, BATADV_CMD_GET_ROUTING_ALGOS);
--
net/batman-adv/bat_iv_ogm.c=1812=batadv_iv_ogm_orig_dump_subentry(struct sk_buff *msg, u32 portid, u32 seq,
--
net/batman-adv/bat_iv_ogm.c-1831-
net/batman-adv/bat_iv_ogm.c:1832: hdr = genlmsg_put(msg, portid, seq, &batadv_netlink_family,
net/batman-adv/bat_iv_ogm.c-1833- NLM_F_MULTI, BATADV_CMD_GET_ORIGINATORS);
--
net/batman-adv/bat_iv_ogm.c=2054=batadv_iv_ogm_neigh_dump_neigh(struct sk_buff *msg, u32 portid, u32 seq,
--
net/batman-adv/bat_iv_ogm.c-2061-
net/batman-adv/bat_iv_ogm.c:2062: hdr = genlmsg_put(msg, portid, seq, &batadv_netlink_family,
net/batman-adv/bat_iv_ogm.c-2063- NLM_F_MULTI, BATADV_CMD_GET_NEIGHBORS);
--
net/batman-adv/bat_iv_ogm.c=2396=static int batadv_iv_gw_dump_entry(struct sk_buff *msg, u32 portid,
--
net/batman-adv/bat_iv_ogm.c-2417- hdr = genlmsg_put(msg, portid, cb->nlh->nlmsg_seq,
net/batman-adv/bat_iv_ogm.c:2418: &batadv_netlink_family, NLM_F_MULTI,
net/batman-adv/bat_iv_ogm.c-2419- BATADV_CMD_GET_GATEWAYS);
--
net/batman-adv/bat_v.c=128=batadv_v_neigh_dump_neigh(struct sk_buff *msg, u32 portid, u32 seq,
--
net/batman-adv/bat_v.c-138-
net/batman-adv/bat_v.c:139: hdr = genlmsg_put(msg, portid, seq, &batadv_netlink_family, NLM_F_MULTI,
net/batman-adv/bat_v.c-140- BATADV_CMD_GET_NEIGHBORS);
--
net/batman-adv/bat_v.c=264=batadv_v_orig_dump_subentry(struct sk_buff *msg, u32 portid, u32 seq,
--
net/batman-adv/bat_v.c-289-
net/batman-adv/bat_v.c:290: hdr = genlmsg_put(msg, portid, seq, &batadv_netlink_family, NLM_F_MULTI,
net/batman-adv/bat_v.c-291- BATADV_CMD_GET_ORIGINATORS);
--
net/batman-adv/bat_v.c=653=static int batadv_v_gw_dump_entry(struct sk_buff *msg, u32 portid,
--
net/batman-adv/bat_v.c-674- hdr = genlmsg_put(msg, portid, cb->nlh->nlmsg_seq,
net/batman-adv/bat_v.c:675: &batadv_netlink_family, NLM_F_MULTI,
net/batman-adv/bat_v.c-676- BATADV_CMD_GET_GATEWAYS);
--
net/batman-adv/bridge_loop_avoidance.c=2127=batadv_bla_claim_dump_entry(struct sk_buff *msg, u32 portid,
--
net/batman-adv/bridge_loop_avoidance.c-2139- hdr = genlmsg_put(msg, portid, cb->nlh->nlmsg_seq,
net/batman-adv/bridge_loop_avoidance.c:2140: &batadv_netlink_family, NLM_F_MULTI,
net/batman-adv/bridge_loop_avoidance.c-2141- BATADV_CMD_GET_BLA_CLAIM);
--
net/batman-adv/bridge_loop_avoidance.c=2290=batadv_bla_backbone_dump_entry(struct sk_buff *msg, u32 portid,
--
net/batman-adv/bridge_loop_avoidance.c-2302- hdr = genlmsg_put(msg, portid, cb->nlh->nlmsg_seq,
net/batman-adv/bridge_loop_avoidance.c:2303: &batadv_netlink_family, NLM_F_MULTI,
net/batman-adv/bridge_loop_avoidance.c-2304- BATADV_CMD_GET_BLA_BACKBONE);
--
net/batman-adv/distributed-arp-table.c=853=batadv_dat_cache_dump_entry(struct sk_buff *msg, u32 portid,
--
net/batman-adv/distributed-arp-table.c-860- hdr = genlmsg_put(msg, portid, cb->nlh->nlmsg_seq,
net/batman-adv/distributed-arp-table.c:861: &batadv_netlink_family, NLM_F_MULTI,
net/batman-adv/distributed-arp-table.c-862- BATADV_CMD_GET_DAT_CACHE);
--
net/batman-adv/multicast.c=1976=batadv_mcast_flags_dump_entry(struct sk_buff *msg, u32 portid,
--
net/batman-adv/multicast.c-1982- hdr = genlmsg_put(msg, portid, cb->nlh->nlmsg_seq,
net/batman-adv/multicast.c:1983: &batadv_netlink_family, NLM_F_MULTI,
net/batman-adv/multicast.c-1984- BATADV_CMD_GET_MCAST_FLAGS);
--
net/batman-adv/netlink.c-50-
net/batman-adv/netlink.c:51:struct genl_family batadv_netlink_family;
net/batman-adv/netlink.c-52-
--
net/batman-adv/netlink.c=222=static int batadv_netlink_mesh_fill(struct sk_buff *msg,
--
net/batman-adv/netlink.c-231-
net/batman-adv/netlink.c:232: hdr = genlmsg_put(msg, portid, seq, &batadv_netlink_family, flags, cmd);
net/batman-adv/netlink.c-233- if (!hdr)
--
net/batman-adv/netlink.c=368=static int batadv_netlink_notify_mesh(struct batadv_priv *bat_priv)
--
net/batman-adv/netlink.c-383-
net/batman-adv/netlink.c:384: genlmsg_multicast_netns(&batadv_netlink_family,
net/batman-adv/netlink.c-385- dev_net(bat_priv->mesh_iface), msg, 0,
--
net/batman-adv/netlink.c=627=int batadv_netlink_tpmeter_notify(struct batadv_priv *bat_priv, const u8 *dst,
--
net/batman-adv/netlink.c-638-
net/batman-adv/netlink.c:639: hdr = genlmsg_put(msg, 0, 0, &batadv_netlink_family, 0,
net/batman-adv/netlink.c-640- BATADV_CMD_TP_METER);
--
net/batman-adv/netlink.c-663-
net/batman-adv/netlink.c:664: genlmsg_multicast_netns(&batadv_netlink_family,
net/batman-adv/netlink.c-665- dev_net(bat_priv->mesh_iface), msg, 0,
--
net/batman-adv/netlink.c=687=batadv_netlink_tp_meter_start(struct sk_buff *skb, struct genl_info *info)
--
net/batman-adv/netlink.c-713- msg_head = genlmsg_put(msg, info->snd_portid, info->snd_seq,
net/batman-adv/netlink.c:714: &batadv_netlink_family, 0,
net/batman-adv/netlink.c-715- BATADV_CMD_TP_METER);
--
net/batman-adv/netlink.c=773=static int batadv_netlink_hardif_fill(struct sk_buff *msg,
--
net/batman-adv/netlink.c-782-
net/batman-adv/netlink.c:783: hdr = genlmsg_put(msg, portid, seq, &batadv_netlink_family, flags, cmd);
net/batman-adv/netlink.c-784- if (!hdr)
--
net/batman-adv/netlink.c=840=static int batadv_netlink_notify_hardif(struct batadv_priv *bat_priv,
--
net/batman-adv/netlink.c-856-
net/batman-adv/netlink.c:857: genlmsg_multicast_netns(&batadv_netlink_family,
net/batman-adv/netlink.c-858- dev_net(bat_priv->mesh_iface), msg, 0,
--
net/batman-adv/netlink.c=999=static int batadv_netlink_vlan_fill(struct sk_buff *msg,
--
net/batman-adv/netlink.c-1006-
net/batman-adv/netlink.c:1007: hdr = genlmsg_put(msg, portid, seq, &batadv_netlink_family, flags, cmd);
net/batman-adv/netlink.c-1008- if (!hdr)
--
net/batman-adv/netlink.c=1041=static int batadv_netlink_notify_vlan(struct batadv_priv *bat_priv,
--
net/batman-adv/netlink.c-1057-
net/batman-adv/netlink.c:1058: genlmsg_multicast_netns(&batadv_netlink_family,
net/batman-adv/netlink.c-1059- dev_net(bat_priv->mesh_iface), msg, 0,
--
net/batman-adv/netlink.c=1400=static const struct genl_small_ops batadv_netlink_ops[] = {
--
net/batman-adv/netlink.c-1523-
net/batman-adv/netlink.c:1524:struct genl_family batadv_netlink_family __ro_after_init = {
net/batman-adv/netlink.c-1525- .hdrsize = 0,
--
net/batman-adv/netlink.c=1544=void __init batadv_netlink_register(void)
--
net/batman-adv/netlink.c-1547-
net/batman-adv/netlink.c:1548: ret = genl_register_family(&batadv_netlink_family);
net/batman-adv/netlink.c-1549- if (ret)
--
net/batman-adv/netlink.c=1556=void batadv_netlink_unregister(void)
net/batman-adv/netlink.c-1557-{
net/batman-adv/netlink.c:1558: genl_unregister_family(&batadv_netlink_family);
net/batman-adv/netlink.c-1559-}
--
net/batman-adv/netlink.h=22=int batadv_netlink_tpmeter_notify(struct batadv_priv *bat_priv, const u8 *dst,
--
net/batman-adv/netlink.h-25-
net/batman-adv/netlink.h:26:extern struct genl_family batadv_netlink_family;
net/batman-adv/netlink.h-27-
--
net/batman-adv/translation-table.c=1039=batadv_tt_local_dump_entry(struct sk_buff *msg, u32 portid,
--
net/batman-adv/translation-table.c-1061- hdr = genlmsg_put(msg, portid, cb->nlh->nlmsg_seq,
net/batman-adv/translation-table.c:1062: &batadv_netlink_family, NLM_F_MULTI,
net/batman-adv/translation-table.c-1063- BATADV_CMD_GET_TRANSTABLE_LOCAL);
--
net/batman-adv/translation-table.c=1763=batadv_tt_global_dump_subentry(struct sk_buff *msg, u32 portid, u32 seq,
--
net/batman-adv/translation-table.c-1782-
net/batman-adv/translation-table.c:1783: hdr = genlmsg_put(msg, portid, seq, &batadv_netlink_family,
net/batman-adv/translation-table.c-1784- NLM_F_MULTI,
]
|
| 88/2 |
2026/05/30 12:34 |
llm |
expert |
0m
Model:
Tokens:
input: 75298
output: 40
thoughts: 28
Thoughts: Okay, let's dive into this. Here's my perspective on understanding that `batadv_netlink_family` definition:
**Analyzing the `batadv_netlink_family` Definition**
Right, so I need to get a handle on how Batman-adv handles its Netlink interactions. The first place I'll go is `net/batman-adv/netlink.c`. I'm going to look specifically for the definition of `batadv_netlink_family`. I suspect this is the central structure or identifier for Batman-adv's Netlink family, likely the key to understanding how it registers and interacts with the kernel's Netlink infrastructure. I'm expecting this to be a crucial starting point for tracing Netlink message handling related to Batman-adv's specific functionality. It'll define the message types, handlers, and overall structure for communication with userspace. I'll be looking to see if it's a `struct genl_family` or something else entirely. Let's see what's defined and go from there.
|
| 89/2 |
2026/05/30 12:34 |
tool |
read-file |
0m
Args: map[File:net/batman-adv/netlink.c FirstLine:1520 LineCount:20]
Results: map[Contents:1520: BATADV_FLAG_NEED_VLAN,
1521: },
1522: };
1523:
1524: struct genl_family batadv_netlink_family __ro_after_init = {
1525: .hdrsize = 0,
1526: .name = BATADV_NL_NAME,
1527: .version = 1,
1528: .maxattr = BATADV_ATTR_MAX,
1529: .policy = batadv_netlink_policy,
1530: .netnsok = true,
1531: .pre_doit = batadv_pre_doit,
1532: .post_doit = batadv_post_doit,
1533: .module = THIS_MODULE,
1534: .small_ops = batadv_netlink_ops,
1535: .n_small_ops = ARRAY_SIZE(batadv_netlink_ops),
1536: .resv_start_op = BATADV_CMD_SET_VLAN + 1,
1537: .mcgrps = batadv_netlink_mcgrps,
1538: .n_mcgrps = ARRAY_SIZE(batadv_netlink_mcgrps),
1539: };
]
|
| 90/2 |
2026/05/30 12:34 |
llm |
expert |
0m
Model:
Tokens:
input: 75956
output: 24
thoughts: 58
Thoughts: Okay, here's my take on those thoughts, framed as my own mental processing:
**Batman-adv and User Namespaces - A Quick Check**
Alright, so `.netnsok = true`... that immediately tells me that this particular thing *can* play nice within a user namespace. That's good news; it simplifies a lot of deployment scenarios. But hold on a sec... to actually create that batman-adv interface, I recall needing to interact with `rtnl_link_ops`. I've got a mental checklist and `rtnl_link_ops` is definitely on it. So, the key question becomes, does batman-adv actually register those link operations? That's the crucial piece of the puzzle. Gotta dig into the code and confirm that's happening.
|
| 91/2 |
2026/05/30 12:34 |
tool |
codesearch-find-references |
0m
Args: map[Name:batadv_link_ops]
Results: map[References:<nil> TruncatedOutput:false]
|
| 92/2 |
2026/05/30 12:34 |
llm |
expert |
0m
Model:
Tokens:
input: 76221
output: 33
thoughts: 23
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience:
**Diving into the Batman-Adv Network Implementation**
Right, let's get down to brass tacks. I need to understand how Batman-Adv is handling its link operations. Given my background, `rtnl_link_ops` is the obvious starting point. It's the standard netlink interface for managing network links within the kernel. My initial thought is to zero in on how Batman-Adv interacts with this framework. I expect to find the Batman-Adv specific configurations and handling of link state changes, address allocation, and so on, implemented within the `net/batman-adv/` directory. That's the logical place for the Batman-Adv specific code that hooks into the generic network stack components. I'll be looking for where they register their `rtnl_link_ops` structure and what its associated functions actually *do* within the Batman-Adv context. I'm anticipating custom implementations for things like setting neighbor MAC addresses, handling MTU changes, and perhaps even some more esoteric Batman-Adv specific link configurations that aren't part of standard Ethernet link operations. Let's see what we find.
|
| 93/2 |
2026/05/30 12:34 |
tool |
grepper |
0m
Args: map[Expression:rtnl_link_ops PathPrefix:net/batman-adv/]
Results: map[Output:net/batman-adv/hard-interface.c=67=batadv_hardif_get_by_netdev(const struct net_device *net_dev)
--
net/batman-adv/hard-interface.c-89- *
net/batman-adv/hard-interface.c:90: * Return: result of rtnl_link_ops->get_link_net or @fallback_net
net/batman-adv/hard-interface.c-91- */
net/batman-adv/hard-interface.c=92=static struct net *batadv_getlink_net(const struct net_device *netdev,
--
net/batman-adv/hard-interface.c-94-{
net/batman-adv/hard-interface.c:95: if (!netdev->rtnl_link_ops)
net/batman-adv/hard-interface.c-96- return fallback_net;
net/batman-adv/hard-interface.c-97-
net/batman-adv/hard-interface.c:98: if (!netdev->rtnl_link_ops->get_link_net)
net/batman-adv/hard-interface.c-99- return fallback_net;
net/batman-adv/hard-interface.c-100-
net/batman-adv/hard-interface.c:101: return netdev->rtnl_link_ops->get_link_net(netdev);
net/batman-adv/hard-interface.c-102-}
--
net/batman-adv/mesh-interface.c=1120=static const struct nla_policy batadv_ifla_policy[IFLA_BATADV_MAX + 1] = {
--
net/batman-adv/mesh-interface.c-1123-
net/batman-adv/mesh-interface.c:1124:struct rtnl_link_ops batadv_link_ops __read_mostly = {
net/batman-adv/mesh-interface.c-1125- .kind = "batadv",
--
net/batman-adv/mesh-interface.h=21=bool batadv_meshif_is_valid(const struct net_device *net_dev);
net/batman-adv/mesh-interface.h:22:extern struct rtnl_link_ops batadv_link_ops;
net/batman-adv/mesh-interface.h-23-int batadv_meshif_create_vlan(struct batadv_priv *bat_priv, unsigned short vid);
]
|
| 94/2 |
2026/05/30 12:34 |
llm |
expert |
0m
Model:
Tokens:
input: 77171
output: 41
thoughts: 12
Thoughts: Okay, let's dive into this. Here's my thought process on the matter:
**Analyzing `batadv_link_ops`**
Alright, time to get to it. My first inclination is to zero in on `batadv_link_ops`. This has got to be a core structure, likely holding function pointers or data related to the BatAdv link layer operations. Given my deep understanding of mesh networking and specifically the BATMAN Advanced protocol, I need to understand how the system manages the creation, modification, and deletion of links within the mesh. I anticipate this structure will be critical for aspects of neighbor discovery, route calculation, and data forwarding. I want to see how these operations interact. Let's dig in and see exactly how it's implemented.
|
| 95/2 |
2026/05/30 12:34 |
tool |
read-file |
0m
Args: map[File:net/batman-adv/mesh-interface.c FirstLine:1120 LineCount:20]
Results: map[Contents:1120: static const struct nla_policy batadv_ifla_policy[IFLA_BATADV_MAX + 1] = {
1121: [IFLA_BATADV_ALGO_NAME] = { .type = NLA_NUL_STRING },
1122: };
1123:
1124: struct rtnl_link_ops batadv_link_ops __read_mostly = {
1125: .kind = "batadv",
1126: .priv_size = sizeof(struct batadv_priv),
1127: .setup = batadv_meshif_init_early,
1128: .maxtype = IFLA_BATADV_MAX,
1129: .policy = batadv_ifla_policy,
1130: .validate = batadv_meshif_validate,
1131: .newlink = batadv_meshif_newlink,
1132: .dellink = batadv_meshif_destroy_netlink,
1133: };
]
|
| 96/2 |
2026/05/30 12:34 |
llm |
expert |
0m
Model:
Tokens:
input: 77675
output: 41
thoughts: 806
Thoughts: **Analyzing the `batadv` Softlockup Vulnerability**
Okay, so I'm digging into this `batadv` issue. The first thing that jumps out is that `rtnl_link_ops` is involved. I know that registration is global across namespaces. The core question here is, can a user namespace trigger this? I see that while there isn't a specific flag for user namespaces, the `rtnl_link_register` function registers globally. If someone has `CAP_NET_ADMIN` within a user namespace, creating a `batadv` interface with `ip link add type batadv` is possible.
Now, if a user can create a `batadv` interface, they can add a `veth` interface to it, and potentially induce a bridging loop, essentially a packet storm. This packet storm can then trigger the `batadv_dat_purge` function, and that's the culprit leading to the softlockup or RCU stall, resulting in a denial-of-service. So it looks like it is triggerable from a user namespace.
The prompt, however, states that user namespaces are *not* accessible. If user namespaces are disabled, an unprivileged user cannot trigger this because they can't create a `batadv` interface since that requires `CAP_NET_ADMIN`. So, user namespaces are the attack vector here.
This isn't a VM guest trigger, it's a network stack bug. It's also not a VM host trigger in a confidential computing context. This is an Ethernet Network Trigger! If there's an existing `batman-adv` interface, an attacker could trigger a packet storm, or misconfigure a network causing a bridging loop to exploit the vulnerability.
Can an attacker remotely cause a bridging loop? Yes, if the network is misconfigured. But even without a loop, flooding packets to the `batman-adv` interface could do it. `net_rx_action` would process the flood. And `batadv_dat_purge` would then loop, causing the softlockup.
Hold on, I'm thinking about `net_rx_action`'s limitations and CPU yielding behavior. It processes limited amounts of packets, it normally yields the CPU to other tasks, but this `batadv_dat_purge` function calls `spin_unlock_bh` in a loop of 1024 iterations *without* yielding the CPU! Even if there were other tasks ready to run, they wouldn't, because a lock is acquired and held! Even if it is a rescuer thread with nice -20, it will preempt other tasks.
Wait, the rescuer thread only runs during memory pressure. So, is `batadv_dat_purge` always in the rescuer thread? No, it's normally a delayed work. Normally, a `kworker` thread is used. Why was it a rescuer thread in the syzbot report? Oh, right, syzbot causes memory allocation failures or memory pressure, which triggers the rescuer thread!
Right, it runs in a kworker thread, so it's priority is nice 0 (or nice -20 if it's a high-priority workqueue). Let me check `batadv_event_workqueue` and see where this leads...
|
| 97/2 |
2026/05/30 12:34 |
tool |
codesearch-definition-source |
0m
Error:
Args: map[ContextFile:net/batman-adv/main.c Name:batadv_event_workqueue]
Results: map[SourceCode: SourceFile:]
|
| 98/2 |
2026/05/30 12:34 |
llm |
expert |
0m
Model:
Tokens:
input: 79241
output: 21
thoughts: 14
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience:
**Investigating `batadv_event_workqueue`**
Alright, let's dive in. My immediate thought is to start a targeted search for `batadv_event_workqueue`. I'm assuming we're dealing with the Batman-adv mesh networking implementation here, and that workqueue name is very specific. This suggests we're likely looking at a component within Batman-adv that deals with asynchronous event handling or processing – most probably concerning events within the Batadv framework. This might be related to link state changes, packet processing, control message handling, or even garbage collection. It’s also possible the workqueue is for the userland interfaces or netlink events. Knowing the context of the search will be critical. Understanding *why* we're interested in that specific workqueue is essential to guide this search. Is this for performance analysis, debugging, understanding a specific control flow, or something else entirely? A quick check of relevant kernel source code, specifically around the `net/batman-adv/` directory, should be the first port of call, and I'll want to quickly look for cross-references to understand the dependencies and triggering mechanisms. I will be looking to get some useful information and not just the location of the workqueue.
|
| 99/2 |
2026/05/30 12:34 |
tool |
grepper |
0m
Args: map[Expression:batadv_event_workqueue]
Results: map[Output:net/batman-adv/bat_v_elp.c=61=static void batadv_v_elp_start_timer(struct batadv_hard_iface *hard_iface)
--
net/batman-adv/bat_v_elp.c-67-
net/batman-adv/bat_v_elp.c:68: queue_delayed_work(batadv_event_workqueue, &hard_iface->bat_v.elp_wq,
net/batman-adv/bat_v_elp.c-69- msecs_to_jiffies(msecs));
--
net/batman-adv/bat_v_ogm.c=85=static void batadv_v_ogm_start_queue_timer(struct batadv_hard_iface *hard_iface)
--
net/batman-adv/bat_v_ogm.c-90- msecs += get_random_u32_below(msecs / 5) - (msecs / 10);
net/batman-adv/bat_v_ogm.c:91: queue_delayed_work(batadv_event_workqueue, &hard_iface->bat_v.aggr_wq,
net/batman-adv/bat_v_ogm.c-92- msecs_to_jiffies(msecs / 1000));
--
net/batman-adv/bat_v_ogm.c=99=static void batadv_v_ogm_start_timer(struct batadv_priv *bat_priv)
--
net/batman-adv/bat_v_ogm.c-109- msecs += get_random_u32_below(2 * BATADV_JITTER);
net/batman-adv/bat_v_ogm.c:110: queue_delayed_work(batadv_event_workqueue, &bat_priv->bat_v.ogm_wq,
net/batman-adv/bat_v_ogm.c-111- msecs_to_jiffies(msecs));
--
net/batman-adv/bridge_loop_avoidance.c=1425=static void batadv_bla_periodic_work(struct work_struct *work)
--
net/batman-adv/bridge_loop_avoidance.c-1508-
net/batman-adv/bridge_loop_avoidance.c:1509: queue_delayed_work(batadv_event_workqueue, &bat_priv->bla.work,
net/batman-adv/bridge_loop_avoidance.c-1510- msecs_to_jiffies(BATADV_BLA_PERIOD_LENGTH));
--
net/batman-adv/bridge_loop_avoidance.c=1527=int batadv_bla_init(struct batadv_priv *bat_priv)
--
net/batman-adv/bridge_loop_avoidance.c-1581-
net/batman-adv/bridge_loop_avoidance.c:1582: queue_delayed_work(batadv_event_workqueue, &bat_priv->bla.work,
net/batman-adv/bridge_loop_avoidance.c-1583- msecs_to_jiffies(BATADV_BLA_PERIOD_LENGTH));
--
net/batman-adv/bridge_loop_avoidance.c=1836=batadv_bla_loopdetect_check(struct batadv_priv *bat_priv, struct sk_buff *skb,
--
net/batman-adv/bridge_loop_avoidance.c-1865-
net/batman-adv/bridge_loop_avoidance.c:1866: ret = queue_work(batadv_event_workqueue, &backbone_gw->report_work);
net/batman-adv/bridge_loop_avoidance.c-1867-
--
net/batman-adv/distributed-arp-table.c=101=static void batadv_dat_start_timer(struct batadv_priv *bat_priv)
net/batman-adv/distributed-arp-table.c-102-{
net/batman-adv/distributed-arp-table.c:103: queue_delayed_work(batadv_event_workqueue, &bat_priv->dat.work,
net/batman-adv/distributed-arp-table.c-104- msecs_to_jiffies(10000));
--
net/batman-adv/main.c=66=static int (*batadv_rx_handler[256])(struct sk_buff *skb,
--
net/batman-adv/main.c-68-
net/batman-adv/main.c:69:struct workqueue_struct *batadv_event_workqueue;
net/batman-adv/main.c-70-
--
net/batman-adv/main.c=89=static int __init batadv_init(void)
--
net/batman-adv/main.c-105-
net/batman-adv/main.c:106: batadv_event_workqueue = create_singlethread_workqueue("bat_events");
net/batman-adv/main.c:107: if (!batadv_event_workqueue)
net/batman-adv/main.c-108- goto err_create_wq;
--
net/batman-adv/main.c=125=static void __exit batadv_exit(void)
--
net/batman-adv/main.c-130-
net/batman-adv/main.c:131: destroy_workqueue(batadv_event_workqueue);
net/batman-adv/main.c:132: batadv_event_workqueue = NULL;
net/batman-adv/main.c-133-
--
net/batman-adv/main.h=234=extern unsigned int batadv_hardif_generation;
net/batman-adv/main.h-235-
net/batman-adv/main.h:236:extern struct workqueue_struct *batadv_event_workqueue;
net/batman-adv/main.h-237-
--
net/batman-adv/multicast.c=68=static void batadv_mcast_start_timer(struct batadv_priv *bat_priv)
net/batman-adv/multicast.c-69-{
net/batman-adv/multicast.c:70: queue_delayed_work(batadv_event_workqueue, &bat_priv->mcast.work,
net/batman-adv/multicast.c-71- msecs_to_jiffies(BATADV_MCAST_WORK_PERIOD));
--
net/batman-adv/originator.c=218=int batadv_originator_init(struct batadv_priv *bat_priv)
--
net/batman-adv/originator.c-231- INIT_DELAYED_WORK(&bat_priv->orig_work, batadv_purge_orig);
net/batman-adv/originator.c:232: queue_delayed_work(batadv_event_workqueue,
net/batman-adv/originator.c-233- &bat_priv->orig_work,
--
net/batman-adv/originator.c=1300=static void batadv_purge_orig(struct work_struct *work)
--
net/batman-adv/originator.c-1307- batadv_purge_orig_ref(bat_priv);
net/batman-adv/originator.c:1308: queue_delayed_work(batadv_event_workqueue,
net/batman-adv/originator.c-1309- &bat_priv->orig_work,
--
net/batman-adv/send.c=656=static void batadv_forw_packet_queue(struct batadv_forw_packet *forw_packet,
--
net/batman-adv/send.c-676-
net/batman-adv/send.c:677: queue_delayed_work(batadv_event_workqueue,
net/batman-adv/send.c-678- &forw_packet->delayed_work,
--
net/batman-adv/tp_meter.c=805=static int batadv_tp_send(void *arg)
--
net/batman-adv/tp_meter.c-845- /* queue the worker in charge of terminating the test */
net/batman-adv/tp_meter.c:846: queue_delayed_work(batadv_event_workqueue, &tp_vars->finish_work,
net/batman-adv/tp_meter.c-847- msecs_to_jiffies(tp_vars->test_length));
--
net/batman-adv/translation-table.c=3504=static void batadv_tt_purge(struct work_struct *work)
--
net/batman-adv/translation-table.c-3518-
net/batman-adv/translation-table.c:3519: queue_delayed_work(batadv_event_workqueue, &bat_priv->tt.work,
net/batman-adv/translation-table.c-3520- msecs_to_jiffies(BATADV_TT_WORK_PERIOD));
--
net/batman-adv/translation-table.c=4105=int batadv_tt_init(struct batadv_priv *bat_priv)
--
net/batman-adv/translation-table.c-4130- INIT_DELAYED_WORK(&bat_priv->tt.work, batadv_tt_purge);
net/batman-adv/translation-table.c:4131: queue_delayed_work(batadv_event_workqueue, &bat_priv->tt.work,
net/batman-adv/translation-table.c-4132- msecs_to_jiffies(BATADV_TT_WORK_PERIOD));
]
|
| 100/2 |
2026/05/30 12:34 |
llm |
expert |
0m
Model:
Tokens:
input: 82106
output: 36
thoughts: 84
Thoughts: **Batadv Event Workqueue Analysis**
Okay, so the `batadv_event_workqueue`... right, it's created using `create_singlethread_workqueue("bat_events")`. Got it. Now, the crucial part: I see it **wasn't** created with `WQ_MEM_RECLAIM`. Aha! That's significant. If it's *not* created with `WQ_MEM_RECLAIM`, then it *doesn't* have a rescuer thread, which is a key characteristic for handling memory pressure. Let me just confirm this behavior of `create_singlethread_workqueue` is consistent with my understanding. Time to double-check the documentation.
|
| 101/2 |
2026/05/30 12:34 |
tool |
read-file |
0m
Args: map[File:include/linux/workqueue.h FirstLine:420 LineCount:20]
Results: map[Contents: 420: WQ_DFL_ACTIVE = WQ_MAX_ACTIVE / 2,
421:
422: /*
423: * Per-node default cap on min_active. Unless explicitly set, min_active
424: * is set to min(max_active, WQ_DFL_MIN_ACTIVE). For more details, see
425: * workqueue_struct->min_active definition.
426: */
427: WQ_DFL_MIN_ACTIVE = 8,
428: };
429:
430: /*
431: * System-wide workqueues which are always present.
432: *
433: * system_percpu_wq is the one used by schedule[_delayed]_work[_on]().
434: * Multi-CPU multi-threaded. There are users which expect relatively
435: * short queue flush time. Don't queue works which can run for too
436: * long.
437: *
438: * system_highpri_wq is similar to system_percpu_wq but for work items which
439: * require WQ_HIGHPRI.
]
|
| 102/2 |
2026/05/30 12:34 |
llm |
expert |
0m
Model:
Tokens:
input: 82718
output: 22
thoughts: 14
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience:
**Investigating Single-Threaded Work Queues**
Alright, let's dive in. My immediate thought is to zero in on the function, specifically `create_singlethread_workqueue`. I'm going to start with a targeted search for that term. This suggests I'm looking for a specific implementation or a particular piece of code within a broader system. Perhaps I'm trying to understand its purpose, its usage, or maybe I'm troubleshooting something related to a single-threaded execution model and need to identify the key component involved. Is it a kernel-level construct? User-space library? The context around it is going to be crucial here; I'll need to see how it's used to really grasp its implications. My intuition is telling me it's likely a critical piece in managing task scheduling and synchronization when we want to enforce serial execution. Let's see what we can find.
|
| 103/2 |
2026/05/30 12:34 |
tool |
grepper |
0m
Args: map[Expression:define create_singlethread_workqueue]
Results: map[Output:include/linux/workqueue.h=557=alloc_workqueue_lockdep_map(const char *fmt, unsigned int flags, int max_active,
--
include/linux/workqueue.h-603- WQ_MEM_RECLAIM, 1, (name))
include/linux/workqueue.h:604:#define create_singlethread_workqueue(name) \
include/linux/workqueue.h-605- alloc_ordered_workqueue("%s", __WQ_LEGACY | WQ_MEM_RECLAIM, name)
]
|
| 104/2 |
2026/05/30 12:34 |
llm |
expert |
1m
Model:
Tokens:
input: 83109
output: 41
thoughts: 1113
Thoughts: **Analysis of the batman-adv Denial-of-Service Vulnerability**
Okay, so looking at this, it seems like the `create_singlethread_workqueue` function *is* created with `WQ_MEM_RECLAIM`! That's the key. This means that we do have a rescuer thread, and it's running with a nice value of -20. This is significant because when there's memory pressure, `batadv_dat_purge` executes in that rescuer thread. Its high priority means it'll preempt nearly everything else and hog the CPU. That's what's causing the RCU stall, the 5.8-second delay that's mentioned.
Even without memory pressure, running in a regular kworker might still lead to softlockups if it takes too long, especially with things like KASAN enabled, where it might take 10-30 seconds. This is definitely a Denial of Service.
Let's go through the trigger scenarios:
* **Exploitable:** No, it’s not a memory corruption vulnerability; it's a softlockup/RCU stall.
* **Denial of Service:** Yes, definitely.
* **Unprivileged Process:** No, because creating a `batadv` interface requires `CAP_NET_ADMIN`.
* **User Namespaces:** Yes, you *can* create the `batadv` interface in a user namespace if you have `CAP_NET_ADMIN`.
* **VM Guest Trigger:** No, it's a network stack issue, not related to VM exits or paravirtualization.
* **VM Host Trigger (Confidential Computing):** No.
* **Ethernet Network Trigger:** Yes, this is the key. You can trigger it by flooding packets to a batman-adv interface or by causing a bridging loop.
* **Other Remote Trigger:** No. This is specific to the Ethernet layer (batman-adv).
* **Peripheral Trigger:** No.
* **Malicious Filesystem Trigger:** No.
Okay, let's look at that Ethernet Network Trigger a bit more closely. Batman-adv operates on Layer 2 (Ethernet). So if a batman-adv interface is set up on a network, an attacker on that *local* network can trigger the softlockup. It's an *Ethernet* network trigger.
So, the next question: is it remotely triggerable? `batadv_dat_purge` itself is a periodic timer that runs every 10 seconds. It purges old entries in the DAT hash table. The softlockup happens because during that iteration, it calls `spin_unlock_bh`, which processes softirqs. If there's a packet storm - triggered remotely by a flood of packets or a bridging loop - `spin_unlock_bh` will get overwhelmed. And since it loops through 1024 entries, it processes softirqs 1024 times. This monopolizes the CPU and causes the softlockup. So, while the attacker doesn't directly trigger `batadv_dat_purge`, they trigger the packet storm, which makes `batadv_dat_purge` take a *very* long time and softlockup the system. So, yes, it's remotely triggerable if the batman-adv interface is active.
Now, any other way to trigger it? A local user within a user namespace can set up a batman-adv interface, create a bridging loop using veth pairs, and trigger the softlockup. That’s exactly what the syzbot report showed.
Looking at the syzbot log, with the bridge messages, it shows a bridging loop was created locally. So, it's definitely accessible from user namespaces.
Now, let's summarize:
* Exploitable: False
* DenialOfService: True
* Unprivileged: False
* UserNamespace: True
* VMGuestTrigger: False
* VMHostTrigger: False
* NetworkTrigger: True
* RemoteTrigger: Yes
* PeripheralTrigger: False
* FilesystemTrigger: False
Wait... is `batadv_dat_purge` the *only* thing that can do this? No, that's not right. There are other periodic tasks in batman-adv that do similar things. `batadv_tt_purge`, `batadv_bla_periodic_work`, and `batadv_purge_orig`. They are periodic timers, too. While the bug report specifically points to `__batadv_dat_purge`, the underlying mechanism is the same: `spin_unlock_bh` inside a loop over a hash table. This is a well-known anti-pattern in the kernel, because it allows softirq processing to
|
| 105/2 |
2026/05/30 12:36 |
tool |
read-file |
0m
Args: map[File:net/batman-adv/translation-table.c FirstLine:3450 LineCount:50]
Results: map[Contents:3450: ret = true;
3451: }
3452:
3453: unlock:
3454: spin_unlock_bh(&bat_priv->tt.roam_list_lock);
3455: return ret;
3456: }
3457:
3458: /**
3459: * batadv_send_roam_adv() - send a roaming advertisement message
3460: * @bat_priv: the bat priv with all the mesh interface information
3461: * @client: mac address of the roaming client
3462: * @vid: VLAN identifier
3463: * @orig_node: message destination
3464: *
3465: * Send a ROAMING_ADV message to the node which was previously serving this
3466: * client. This is done to inform the node that from now on all traffic destined
3467: * for this particular roamed client has to be forwarded to the sender of the
3468: * roaming message.
3469: */
3470: static void batadv_send_roam_adv(struct batadv_priv *bat_priv, u8 *client,
3471: unsigned short vid,
3472: struct batadv_orig_node *orig_node)
3473: {
3474: struct batadv_hard_iface *primary_if;
3475: struct batadv_tvlv_roam_adv tvlv_roam;
3476:
3477: primary_if = batadv_primary_if_get_selected(bat_priv);
3478: if (!primary_if)
3479: goto out;
3480:
3481: /* before going on we have to check whether the client has
3482: * already roamed to us too many times
3483: */
3484: if (!batadv_tt_check_roam_count(bat_priv, client))
3485: goto out;
3486:
3487: batadv_dbg(BATADV_DBG_TT, bat_priv,
3488: "Sending ROAMING_ADV to %pM (client %pM, vid: %d)\n",
3489: orig_node->orig, client, batadv_print_vid(vid));
3490:
3491: batadv_inc_counter(bat_priv, BATADV_CNT_TT_ROAM_ADV_TX);
3492:
3493: memcpy(tvlv_roam.client, client, sizeof(tvlv_roam.client));
3494: tvlv_roam.vid = htons(vid);
3495:
3496: batadv_tvlv_unicast_send(bat_priv, primary_if->net_dev->dev_addr,
3497: orig_node->orig, BATADV_TVLV_ROAM, 1,
3498: &tvlv_roam, sizeof(tvlv_roam));
3499:
]
|
| 106/2 |
2026/05/30 12:36 |
llm |
expert |
0m
Model:
Tokens:
input: 86119
output: 66
thoughts: 36
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience:
**Finalizing the Batadv_dat_purge Analysis**
Alright, the in-depth analysis of `batadv_dat_purge` has proven to be quite robust. After all the scrutiny, the underlying reasoning is sound and I'm confident in the conclusions. Now, it's time to formalize these findings and deliver the critical conclusions. I'll initiate the `set-results` function to solidify the rationale.
|
| 107/2 |
2026/05/30 12:36 |
tool |
set-results |
0m
Args: map[DenialOfService:true Exploitable:false FilesystemTrigger:false NetworkTrigger:true PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
Results: map[DenialOfService:true Exploitable:false FilesystemTrigger:false NetworkTrigger:true PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
|
| 108/2 |
2026/05/30 12:36 |
llm |
expert |
0m
Model:
Tokens:
input: 86392
output: 704
thoughts: 419
Thoughts: **Final Response: Batman-adv Softlockup Vulnerability**
Okay, the analysis is complete, and the solution is clear. Time to draft the final response. My findings point to a denial-of-service vulnerability within the `batman-adv` module, specifically stemming from the `__batadv_dat_purge` function within `net/batman-adv/distributed-arp-table.c`.
The root cause is a softlockup/RCU stall. Essentially, `__batadv_dat_purge` meticulously walks the DAT hash table, which has 1024 buckets. The problem arises because, during this iteration, it acquires and releases a spinlock for each bucket using `spin_lock_bh` and `spin_unlock_bh`. Here's where it gets interesting: `spin_unlock_bh` enables softirqs, and critically, processes pending softirqs. If there's a heavy network load, like a continuous stream of packets—perhaps a packet storm or a bridging loop, as seen in the syzbot log— `spin_unlock_bh` can get bogged down processing packets during each of the 1024 iterations.
This is a problem because it can take a significant amount of time, perhaps up to 2ms per iteration, and possibly even longer with KASAN enabled. Multiply that by 1024 iterations, and the CPU ends up monopolized for a prolonged period, easily exceeding 2 seconds, or longer when KASAN is enabled. This starvation of other tasks, particularly the RCU grace period kthread, is what ultimately leads to the RCU stall and the reported softlockup.
It's crucial to understand this is a denial-of-service vulnerability. While it doesn't lead to memory corruption or any form of privilege escalation, it's certainly disruptive. Locally, a user within a user namespace with `CAP_NET_ADMIN` privileges can trigger this by creating a `batadv` interface and then setting up a bridging loop—think veth pairs. This is not something an unprivileged user can exploit. Remote triggering is also possible if a `batman-adv` interface is already present. A flood of packets or inducing a layer 2 bridging loop on the local Ethernet network can do the trick. Now, to concisely and accurately communicate these findings.
|
| 109/1 |
2026/05/30 12:36 |
action |
format-explanation |
0m
Results: map[Explanation:**Analysis of the Bug Report**
**Root Cause:**
The bug report indicates an RCU stall and a softlockup occurring on CPU 0. The
stack trace shows that the CPU is stuck processing softirqs (`do_softirq`,
`net_rx_action`, `process_backlog`, etc.) which were triggered by
`spin_unlock_bh` inside `__batadv_dat_purge` (in
`net/batman-adv/distributed-arp-table.c`).
`__batadv_dat_purge` iterates over the Distributed ARP Table (DAT) hash table,
which is initialized with 1024 buckets. For each bucket, it acquires and
releases a spinlock using `spin_lock_bh()` and `spin_unlock_bh()`.
The `spin_unlock_bh()` function enables softirqs and immediately processes any
pending ones. If there is a continuous stream of network packets (a packet
storm), the softirq processing (`net_rx_action`) will consume its maximum time
slice (usually 2ms, but can be significantly longer with debugging tools like
KASAN enabled) on *every single iteration* of the 1024-bucket loop.
This causes the `__batadv_dat_purge` function to monopolize the CPU for an
extended period (e.g., 1024 * 2ms = ~2 seconds minimum, or much longer with
KASAN), starving other tasks such as the RCU grace-period kthread, ultimately
leading to the reported RCU stall and softlockup.
The syzbot log explicitly shows `bridge0: received packet on bridge_slave_0 with
own address as source address`, which indicates that a layer 2 bridging loop was
created, causing the packet storm that triggered this behavior.
**Security Impact:**
* **Exploitable:** No. This is a CPU monopolization issue leading to a
softlockup. It does not cause memory corruption, out-of-bounds accesses, or
use-after-free conditions, so it cannot be exploited for privilege escalation or
arbitrary code execution.
* **Denial Of Service:** Yes. The bug causes the kernel to stall and trigger
softlockups/RCU stalls, effectively freezing the affected CPU and leading to a
system crash or unresponsiveness.
* **Accessible From Unprivileged Processes:** No. Creating a `batadv`
(batman-adv) interface requires `CAP_NET_ADMIN` network capabilities, which a
completely unprivileged user does not have.
* **Accessible From User Namespaces:** Yes. A local user can create a new user
namespace, acquire `CAP_NET_ADMIN` within that namespace, set up a `batadv`
interface, and create a bridging loop using `veth` pairs to trigger the packet
storm and the subsequent softlockup.
* **Ethernet Network Trigger:** Yes. If a system is already configured to use
batman-adv on a local network, an attacker on the same layer 2 network can
trigger this DoS by intentionally causing a bridging loop or by flooding the
interface with a massive packet storm, forcing the periodic `batadv_dat_purge`
worker to stall the CPU.
* **VM Guest / Host / Peripheral / Filesystem Trigger:** No. This is strictly a
network stack and softirq processing issue.]
|