| 0/0 |
2026/05/13 05:10 |
flow |
assessment-security |
57m
Results: map[DenialOfService:false Explanation:The reported issue is a KCSAN data-race on the `pwq->stats[PWQ_STAT_CPU_TIME]`
counter in `wq_worker_tick()`.
### Analysis
The function `wq_worker_tick()` is called from the scheduler tick
(`sched_tick()`) in the context of the timer interrupt on the CPU where a
workqueue worker is currently executing. For unbound workqueues, multiple
workers belonging to the same pool workqueue (`pwq`) can run concurrently on
different CPUs. If two such workers receive a timer interrupt simultaneously,
they will both attempt to execute `pwq->stats[PWQ_STAT_CPU_TIME] += TICK_USEC;`
concurrently without any locks or atomic operations, leading to the reported
data race.
However, `pwq->stats` is an array of `u64` used exclusively for tracking
workqueue statistics. A review of the kernel source code reveals that these
statistics are never read or used by the kernel itself to make any runtime
decisions. They are only intended to be read by external debugging and
monitoring tools (specifically, the `tools/workqueue/wq_monitor.py` script which
uses `drgn` to read kernel memory directly).
Because this counter does not influence any kernel logic, control flow, or
memory management, the data race is completely benign. The worst-case scenario
is a slightly inaccurate statistic (e.g., a missed increment or a torn
read/write on 32-bit architectures), which has absolutely no security
implications.
### Triggers
Workqueues are a core kernel mechanism utilized by almost all subsystems,
including networking, filesystems, KVM, and device drivers. Consequently, the
`wq_worker_tick()` code path is constantly exercised during normal system
operation and can be indirectly triggered by any user-space application
(including unprivileged processes and those in user namespaces) or external
inputs (network packets, USB devices, VM guests, etc.) that cause work items to
be queued and executed.
### Conclusion
While the code path is universally reachable, the data race is entirely benign.
It cannot be exploited for memory corruption, privilege escalation, or denial of
service. Exploitable:false FilesystemTrigger:true NetworkTrigger:true PeripheralTrigger:true RemoteTrigger:true Unprivileged:true UserNamespace:true VMGuestTrigger:true VMHostTrigger:true]
|
| 1/1 |
2026/05/13 05:10 |
action |
syz-repro-to-c-repro |
0m
Results:
|
| 2/1 |
2026/05/13 05:10 |
action |
kernel-checkouter |
1m
Results: map[KernelSrc:/app/workdir/cache/src/049da299ef819359e093c7f86424c4dc2136bed6]
|
| 3/1 |
2026/05/13 05:11 |
action |
kernel-builder |
21m
Results: map[KernelObj:/app/workdir/cache/build/3cd120db9ea57f142c2177a44f5cd8459e275ca0]
|
| 4/1 |
2026/05/13 05:33 |
action |
codesearch-prepare |
13m
Results: map[Index:codesearch-index]
|
| 5/1 |
2026/05/13 05:47 |
agent |
expert |
20m
Model:
Results: map[DenialOfService:false Exploitable:false FilesystemTrigger:true NetworkTrigger:true PeripheralTrigger:true RemoteTrigger:true Unprivileged:true UserNamespace:true VMGuestTrigger:true VMHostTrigger:true]
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 greeping
".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).
Don't make assumptions; verify them with source code access. 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 completly 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 tranfers, that's 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 exploitabability 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 Confidetial 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.
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:
==================================================================
BUG: KCSAN: data-race in wq_worker_tick / wq_worker_tick
read-write to 0xffff8881000c5cd8 of 8 bytes by interrupt on cpu 0:
wq_worker_tick+0x64/0x240 kernel/workqueue.c:1508
sched_tick+0xbc/0x1f0 kernel/sched/core.c:5678
update_process_times+0x15e/0x190 kernel/time/timer.c:2480
tick_sched_handle kernel/time/tick-sched.c:298 [inline]
tick_nohz_handler+0x275/0x3d0 kernel/time/tick-sched.c:319
__run_hrtimer kernel/time/hrtimer.c:1930 [inline]
__hrtimer_run_queues+0x276/0x4f0 kernel/time/hrtimer.c:1994
hrtimer_interrupt+0x261/0x850 kernel/time/hrtimer.c:2113
local_apic_timer_interrupt arch/x86/kernel/apic/apic.c:1050 [inline]
__sysvec_apic_timer_interrupt+0x5f/0x1c0 arch/x86/kernel/apic/apic.c:1067
instr_sysvec_apic_timer_interrupt arch/x86/kernel/apic/apic.c:1061 [inline]
sysvec_apic_timer_interrupt+0x32/0x80 arch/x86/kernel/apic/apic.c:1061
asm_sysvec_apic_timer_interrupt+0x1a/0x20 arch/x86/include/asm/idtentry.h:697
__memcg_slab_free_hook+0x5/0x2c0 mm/memcontrol.c:3519
memcg_slab_free_hook mm/slub.c:2488 [inline]
slab_free mm/slub.c:6247 [inline]
kmem_cache_free+0x252/0x380 mm/slub.c:6377
kfree_skbmem net/core/skbuff.c:-1 [inline]
__kfree_skb+0x109/0x150 net/core/skbuff.c:1204
sk_skb_reason_drop+0xbd/0x230 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+0x3b6/0x470 net/ipv6/ip6_input.c:638
dst_input include/net/dst.h:480 [inline]
ip6_rcv_finish+0x31f/0x330 net/ipv6/ip6_input.c:119
NF_HOOK include/linux/netfilter.h:318 [inline]
ipv6_rcv+0x72/0x170 net/ipv6/ip6_input.c:351
__netif_receive_skb_one_core net/core/dev.c:6202 [inline]
__netif_receive_skb net/core/dev.c:6315 [inline]
process_backlog+0x2a5/0x670 net/core/dev.c:6666
__napi_poll+0x61/0x300 net/core/dev.c:7730
napi_poll net/core/dev.c:7793 [inline]
net_rx_action+0x452/0x930 net/core/dev.c:7950
handle_softirqs+0xb9/0x280 kernel/softirq.c:622
do_softirq+0x45/0x60 kernel/softirq.c:523
__local_bh_enable_ip+0x70/0x80 kernel/softirq.c:450
__raw_spin_unlock_bh include/linux/spinlock_api_smp.h:196 [inline]
_raw_spin_unlock_bh+0x18/0x20 kernel/locking/spinlock.c:214
spin_unlock_bh include/linux/spinlock.h:396 [inline]
nsim_dev_trap_report drivers/net/netdevsim/dev.c:891 [inline]
nsim_dev_trap_report_work+0x53b/0x640 drivers/net/netdevsim/dev.c:922
process_one_work kernel/workqueue.c:3302 [inline]
process_scheduled_works+0x4f0/0x9c0 kernel/workqueue.c:3385
worker_thread+0x58a/0x780 kernel/workqueue.c:3466
kthread+0x22a/0x280 kernel/kthread.c:436
ret_from_fork+0x146/0x330 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
read-write to 0xffff8881000c5cd8 of 8 bytes by interrupt on cpu 1:
wq_worker_tick+0x64/0x240 kernel/workqueue.c:1508
sched_tick+0xbc/0x1f0 kernel/sched/core.c:5678
update_process_times+0x15e/0x190 kernel/time/timer.c:2480
tick_sched_handle kernel/time/tick-sched.c:298 [inline]
tick_nohz_handler+0x275/0x3d0 kernel/time/tick-sched.c:319
__run_hrtimer kernel/time/hrtimer.c:1930 [inline]
__hrtimer_run_queues+0x276/0x4f0 kernel/time/hrtimer.c:1994
hrtimer_interrupt+0x261/0x850 kernel/time/hrtimer.c:2113
local_apic_timer_interrupt arch/x86/kernel/apic/apic.c:1050 [inline]
__sysvec_apic_timer_interrupt+0x5f/0x1c0 arch/x86/kernel/apic/apic.c:1067
instr_sysvec_apic_timer_interrupt arch/x86/kernel/apic/apic.c:1061 [inline]
sysvec_apic_timer_interrupt+0x32/0x80 arch/x86/kernel/apic/apic.c:1061
asm_sysvec_apic_timer_interrupt+0x1a/0x20 arch/x86/include/asm/idtentry.h:697
check_access kernel/kcsan/core.c:787 [inline]
__tsan_read8+0x121/0x190 kernel/kcsan/core.c:1025
ipv6_masked_addr_cmp include/net/ipv6.h:520 [inline]
ip6_packet_match net/ipv6/netfilter/ip6_tables.c:57 [inline]
ip6t_do_table+0x2ca/0xc10 net/ipv6/netfilter/ip6_tables.c:306
ip6table_mangle_hook+0x163/0x340 net/ipv6/netfilter/ip6table_mangle.c:73
nf_hook_entry_hookfn include/linux/netfilter.h:158 [inline]
nf_hook_slow+0x78/0x180 net/netfilter/core.c:619
nf_hook include/linux/netfilter.h:273 [inline]
NF_HOOK include/linux/netfilter.h:316 [inline]
br_nf_forward_ip+0x5c8/0x620 net/bridge/br_netfilter_hooks.c:717
br_nf_forward+0x5a2/0xeb0 net/bridge/br_netfilter_hooks.c:774
nf_hook_entry_hookfn include/linux/netfilter.h:158 [inline]
nf_hook_slow+0x78/0x180 net/netfilter/core.c:619
nf_hook include/linux/netfilter.h:273 [inline]
NF_HOOK include/linux/netfilter.h:316 [inline]
__br_forward+0x282/0x360 net/bridge/br_forward.c:115
deliver_clone net/bridge/br_forward.c:131 [inline]
maybe_deliver net/bridge/br_forward.c:191 [inline]
br_flood+0x451/0x6d0 net/bridge/br_forward.c:238
br_handle_frame_finish+0xe04/0x1080 net/bridge/br_input.c:229
br_nf_hook_thresh+0x233/0x270 net/bridge/br_netfilter_hooks.c:-1
br_nf_pre_routing_finish_ipv6+0x51d/0x540 net/bridge/br_netfilter_ipv6.c:-1
NF_HOOK include/linux/netfilter.h:318 [inline]
br_nf_pre_routing_ipv6+0x1fa/0x2e0 net/bridge/br_netfilter_ipv6.c:183
br_nf_pre_routing+0x56a/0xbe0 net/bridge/br_netfilter_hooks.c:509
nf_hook_entry_hookfn include/linux/netfilter.h:158 [inline]
nf_hook_bridge_pre net/bridge/br_input.c:291 [inline]
br_handle_frame+0x85f/0xa60 net/bridge/br_input.c:442
__netif_receive_skb_core+0x5b1/0x1950 net/core/dev.c:6089
__netif_receive_skb_one_core net/core/dev.c:6200 [inline]
__netif_receive_skb net/core/dev.c:6315 [inline]
process_backlog+0x25b/0x670 net/core/dev.c:6666
__napi_poll+0x61/0x300 net/core/dev.c:7730
napi_poll net/core/dev.c:7793 [inline]
net_rx_action+0x452/0x930 net/core/dev.c:7950
handle_softirqs+0xb9/0x280 kernel/softirq.c:622
do_softirq+0x45/0x60 kernel/softirq.c:523
__local_bh_enable_ip+0x70/0x80 kernel/softirq.c:450
__raw_spin_unlock_bh include/linux/spinlock_api_smp.h:196 [inline]
_raw_spin_unlock_bh+0x18/0x20 kernel/locking/spinlock.c:214
spin_unlock_bh include/linux/spinlock.h:396 [inline]
nsim_dev_trap_report drivers/net/netdevsim/dev.c:891 [inline]
nsim_dev_trap_report_work+0x53b/0x640 drivers/net/netdevsim/dev.c:922
process_one_work kernel/workqueue.c:3302 [inline]
process_scheduled_works+0x4f0/0x9c0 kernel/workqueue.c:3385
worker_thread+0x58a/0x780 kernel/workqueue.c:3466
kthread+0x22a/0x280 kernel/kthread.c:436
ret_from_fork+0x146/0x330 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
value changed: 0x000000000021dfe0 -> 0x00000000002206f0
Reported by Kernel Concurrency Sanitizer on:
CPU: 1 UID: 0 PID: 4825 Comm: kworker/u8:29 Not tainted syzkaller #0 PREEMPT(full)
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 04/18/2026
Workqueue: events_unbound nsim_dev_trap_report_work
==================================================================
net_ratelimit: 40813 callbacks suppressed
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 bridge_slave_0 with own address as source address (addr:aa:aa:aa:aa:aa:1b, 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 bridge_slave_0 with own address as source address (addr:aa:aa:aa:aa:aa:1b, vlan:0)
bridge0: received packet on bridge_slave_0 with own address as source address (addr:aa:aa:aa:aa:aa:1b, vlan:0)
net_ratelimit: 51872 callbacks suppressed
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 bridge_slave_0 with own address as source address (addr:aa:aa:aa:aa:aa:1b, 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 bridge_slave_0 with own address as source address (addr:aa:aa:aa:aa:aa:1b, vlan:0)
bridge0: received packet on bridge_slave_0 with own address as source address (addr:aa:aa:aa:aa:aa:1b, vlan:0)
Reply: The reported issue is a KCSAN data-race on the `pwq->stats[PWQ_STAT_CPU_TIME]` counter in `wq_worker_tick()`.
### Analysis
The function `wq_worker_tick()` is called from the scheduler tick (`sched_tick()`) in the context of the timer interrupt on the CPU where a workqueue worker is currently executing. For unbound workqueues, multiple workers belonging to the same pool workqueue (`pwq`) can run concurrently on different CPUs. If two such workers receive a timer interrupt simultaneously, they will both attempt to execute `pwq->stats[PWQ_STAT_CPU_TIME] += TICK_USEC;` concurrently without any locks or atomic operations, leading to the reported data race.
However, `pwq->stats` is an array of `u64` used exclusively for tracking workqueue statistics. A review of the kernel source code reveals that these statistics are never read or used by the kernel itself to make any runtime decisions. They are only intended to be read by external debugging and monitoring tools (specifically, the `tools/workqueue/wq_monitor.py` script which uses `drgn` to read kernel memory directly).
Because this counter does not influence any kernel logic, control flow, or memory management, the data race is completely benign. The worst-case scenario is a slightly inaccurate statistic (e.g., a missed increment or a torn read/write on 32-bit architectures), which has absolutely no security implications.
### Triggers
Workqueues are a core kernel mechanism utilized by almost all subsystems, including networking, filesystems, KVM, and device drivers. Consequently, the `wq_worker_tick()` code path is constantly exercised during normal system operation and can be indirectly triggered by any user-space application (including unprivileged processes and those in user namespaces) or external inputs (network packets, USB devices, VM guests, etc.) that cause work items to be queued and executed.
### Conclusion
While the code path is universally reachable, the data race is entirely benign. It cannot be exploited for memory corruption, privilege escalation, or denial of service.
|
| 6/2 |
2026/05/13 05:47 |
llm |
expert |
0m
Model:
Tokens:
input: 8185
output: 19
thoughts: 1749
Thoughts: **Data Race Deep Dive**
Okay, so I've got a KCSAN data-race report centered around `wq_worker_tick`. The key thing here is that both traces are reporting read-write access to the same 8-byte memory location, `0xffff8881000c5cd8`. Both are flagged as happening within an interrupt context on CPUs 0 and 1. The key function involved is `wq_worker_tick`, and I can see it's calling `kernel/workqueue.c:1508`.
Initially, I got a little side-tracked by the second trace, because it showed `__tsan_read8` within `ipv6_masked_addr_cmp`. I need to remember that this *doesn't* mean there's an issue with the IPV6 functions. The critical insight here is that the KCSAN report *explicitly* states that the offending access on CPU 1, *also* originated from `wq_worker_tick`.
Looking at the stack trace for CPU 1, I see the typical timer interrupt path: `sched_tick`, `update_process_times`, down to `tick_nohz_handler` and `hrtimer_interrupt`, and then the `sysvec_apic_timer_interrupt`, but this time interrupted `__tsan_read8`. It initially led me to believe the second stack was interfering, but the traces are independent. The interrupt context is critical here - I suspect the race is directly between two instances of `wq_worker_tick`.
The report clearly states that both races are in `wq_worker_tick` and at the same address, `kernel/workqueue.c:1508` is the place to check.
|
| 7/2 |
2026/05/13 05:47 |
tool |
grepper |
0m
Args: map[Expression:wq_worker_tick]
Results: map[Output:kernel/sched/core.c=5636=void sched_tick(void)
--
kernel/sched/core.c-5677- if (donor->flags & PF_WQ_WORKER)
kernel/sched/core.c:5678: wq_worker_tick(donor);
kernel/sched/core.c-5679-
--
kernel/workqueue.c=1453=void wq_worker_sleeping(struct task_struct *task)
--
kernel/workqueue.c-1492-/**
kernel/workqueue.c:1493: * wq_worker_tick - a scheduler tick occurred while a kworker is running
kernel/workqueue.c-1494- * @task: task currently running
--
kernel/workqueue.c-1498- */
kernel/workqueue.c:1499:void wq_worker_tick(struct task_struct *task)
kernel/workqueue.c-1500-{
--
kernel/workqueue.c=3202=__acquires(&pool->lock)
--
kernel/workqueue.c-3342- * In addition to %WQ_CPU_INTENSIVE, @worker may also have been marked
kernel/workqueue.c:3343: * CPU intensive by wq_worker_tick() if @work hogged CPU longer than
kernel/workqueue.c-3344- * wq_cpu_intensive_thresh_us. Clear it.
--
kernel/workqueue_internal.h=80=void wq_worker_sleeping(struct task_struct *task);
kernel/workqueue_internal.h:81:void wq_worker_tick(struct task_struct *task);
kernel/workqueue_internal.h-82-work_func_t wq_worker_last_func(struct task_struct *task);
]
|
| 8/2 |
2026/05/13 05:47 |
llm |
expert |
1m
Model:
Tokens:
input: 10796
output: 40
thoughts: 20
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Diving into the Workqueue Implementation**
Okay, so I need to get to the bottom of this. Let's start digging into the kernel, specifically the `kernel/workqueue.c` file. I'm going to jump straight to around line 1508. That area is likely where some interesting logic is, likely dealing with work item scheduling or processing. I'm hoping to get a clearer picture of how the workqueue is behaving in this specific scenario and identify the source of the issue. This should give me a good entry point to understand the overall workqueue execution flow.
|
| 9/2 |
2026/05/13 05:48 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:kernel/workqueue.c IncludeLines:true Name:wq_worker_tick]
Results: map[SourceCode:1492: /**
1493: * wq_worker_tick - a scheduler tick occurred while a kworker is running
1494: * @task: task currently running
1495: *
1496: * Called from sched_tick(). We're in the IRQ context and the current
1497: * worker's fields which follow the 'K' locking rule can be accessed safely.
1498: */
1499: void wq_worker_tick(struct task_struct *task)
1500: {
1501: struct worker *worker = kthread_data(task);
1502: struct pool_workqueue *pwq = worker->current_pwq;
1503: struct worker_pool *pool = worker->pool;
1504:
1505: if (!pwq)
1506: return;
1507:
1508: pwq->stats[PWQ_STAT_CPU_TIME] += TICK_USEC;
1509:
1510: if (!wq_cpu_intensive_thresh_us)
1511: return;
1512:
1513: /*
1514: * If the current worker is concurrency managed and hogged the CPU for
1515: * longer than wq_cpu_intensive_thresh_us, it's automatically marked
1516: * CPU_INTENSIVE to avoid stalling other concurrency-managed work items.
1517: *
1518: * Set @worker->sleeping means that @worker is in the process of
1519: * switching out voluntarily and won't be contributing to
1520: * @pool->nr_running until it wakes up. As wq_worker_sleeping() also
1521: * decrements ->nr_running, setting CPU_INTENSIVE here can lead to
1522: * double decrements. The task is releasing the CPU anyway. Let's skip.
1523: * We probably want to make this prettier in the future.
1524: */
1525: if ((worker->flags & WORKER_NOT_RUNNING) || READ_ONCE(worker->sleeping) ||
1526: worker->task->se.sum_exec_runtime - worker->current_at <
1527: wq_cpu_intensive_thresh_us * NSEC_PER_USEC)
1528: return;
1529:
1530: raw_spin_lock(&pool->lock);
1531:
1532: worker_set_flags(worker, WORKER_CPU_INTENSIVE);
1533: wq_cpu_intensive_report(worker->current_func);
1534: pwq->stats[PWQ_STAT_CPU_INTENSIVE]++;
1535:
1536: if (kick_pool(pool))
1537: pwq->stats[PWQ_STAT_CM_WAKEUP]++;
1538:
1539: raw_spin_unlock(&pool->lock);
1540: }
SourceFile:kernel/workqueue.c]
|
| 10/2 |
2026/05/13 05:48 |
llm |
expert |
0m
Model:
Tokens:
input: 11831
output: 39
thoughts: 64
Thoughts: Okay, here's my interpretation of those thoughts, tailored for an expert audience:
**Analyzing CPU Time Accounting in a Workqueue**
Right, line 1508... that's where the CPU time accounting for the pool workqueue is incremented: `pwq->stats[PWQ_STAT_CPU_TIME] += TICK_USEC;`. Good. Always a good idea to know *where* the key performance metrics are being updated. Now, wait a second... `pwq->stats`... is that an array of `u64`? Okay, yes, it's pretty clear from this code that we're dealing with 64-bit unsigned integers here. So itβs using a standard array to store the statistics. Makes sense. Okay, let's just confirm my assumptions about the `struct pool_workqueue` structure, just to be completely sure. I want to fully understand how these stats are organized within the workqueue object itself. Let's make sure I'm not missing anything critical about how this works. Need to double-check those offsets and data types.
|
| 11/2 |
2026/05/13 05:49 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:kernel/workqueue.c IncludeLines:true Name:pool_workqueue]
Results: map[SourceCode: 263: /*
264: * The per-pool workqueue. While queued, bits below WORK_PWQ_SHIFT
265: * of work_struct->data are used for flags and the remaining high bits
266: * point to the pwq; thus, pwqs need to be aligned at two's power of the
267: * number of flag bits.
268: */
269: struct pool_workqueue {
270: struct worker_pool *pool; /* I: the associated pool */
271: struct workqueue_struct *wq; /* I: the owning workqueue */
272: int work_color; /* L: current color */
273: int flush_color; /* L: flushing color */
274: int refcnt; /* L: reference count */
275: int nr_in_flight[WORK_NR_COLORS];
276: /* L: nr of in_flight works */
277: bool plugged; /* L: execution suspended */
278:
279: /*
280: * nr_active management and WORK_STRUCT_INACTIVE:
281: *
282: * When pwq->nr_active >= max_active, new work item is queued to
283: * pwq->inactive_works instead of pool->worklist and marked with
284: * WORK_STRUCT_INACTIVE.
285: *
286: * All work items marked with WORK_STRUCT_INACTIVE do not participate in
287: * nr_active and all work items in pwq->inactive_works are marked with
288: * WORK_STRUCT_INACTIVE. But not all WORK_STRUCT_INACTIVE work items are
289: * in pwq->inactive_works. Some of them are ready to run in
290: * pool->worklist or worker->scheduled. Those work itmes are only struct
291: * wq_barrier which is used for flush_work() and should not participate
292: * in nr_active. For non-barrier work item, it is marked with
293: * WORK_STRUCT_INACTIVE iff it is in pwq->inactive_works.
294: */
295: int nr_active; /* L: nr of active works */
296: struct list_head inactive_works; /* L: inactive works */
297: struct list_head pending_node; /* LN: node on wq_node_nr_active->pending_pwqs */
298: struct list_head pwqs_node; /* WR: node on wq->pwqs */
299: struct list_head mayday_node; /* MD: node on wq->maydays */
300: struct work_struct mayday_cursor; /* L: cursor on pool->worklist */
301:
302: u64 stats[PWQ_NR_STATS];
303:
304: /*
305: * Release of unbound pwq is punted to a kthread_worker. See put_pwq()
306: * and pwq_release_workfn() for details. pool_workqueue itself is also
307: * RCU protected so that the first pwq can be determined without
308: * grabbing wq->mutex.
309: */
310: struct kthread_work release_work;
311: struct rcu_head rcu;
312: } __aligned(1 << WORK_STRUCT_PWQ_SHIFT);
SourceFile:kernel/workqueue.c]
|
| 12/2 |
2026/05/13 05:49 |
llm |
expert |
1m
Model:
Tokens:
input: 13164
output: 49
thoughts: 471
Thoughts: **Analyzing a Data Race in the Workqueue CPU Time Statistic**
Okay, so I'm looking at this KCSAN report, and it's pointing out a read-modify-write operation on `pwq->stats[PWQ_STAT_CPU_TIME]`, specifically the line `pwq->stats[PWQ_STAT_CPU_TIME] += TICK_USEC;`. The KCSAN output clearly shows that this memory location is being accessed concurrently by interrupts on two different CPUs β CPU 0 and CPU 1 β both executing `wq_worker_tick`. Right, `pwq` here refers to a per-pool workqueue, and it's an unbound workqueue, which means it's shared across multiple CPUs within the same NUMA node or according to the workqueue's affinity.
So, the scenario is clear: two worker threads, part of the same unbound `pwq`, running on separate CPUs, get timer interrupts simultaneously and both update the same counter concurrently. It's a textbook data race on a shared variable, in this case, a statistic counter.
The good news is, it's just a statistic (`pwq->stats`) we're talking about, not a critical control variable. The worst-case scenario here is some inaccuracy in the CPU time counter, and we might lose a few ticks.
The immediate questions, though, are: Can this be exploited? Is it a potential denial-of-service vector? And can unprivileged processes trigger this? Well, it's a kernel background task, a workqueue, so essentially anything that schedules unbound work could potentially trigger this. But even so, it's just a benign data race on a stats counter. Still, it's important to verify that `pwq->stats` isn't used for anything critical, and that this counter isn't influencing any other system behaviour.
|
| 13/2 |
2026/05/13 05:50 |
tool |
codesearch-find-references |
0m
Args: map[ContextFile:kernel/workqueue.c IncludeSnippetLines:0 Name:pool_workqueue::stats SourceTreePrefix:]
Results: map[References:[map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:assign_rescuer_work SourceFile:kernel/workqueue.c SourceLine:3531] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:kick_pool SourceFile:kernel/workqueue.c SourceLine:1309] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:process_one_work SourceFile:kernel/workqueue.c SourceLine:3270] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:process_one_work SourceFile:kernel/workqueue.c SourceLine:3339] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:send_mayday SourceFile:kernel/workqueue.c SourceLine:3043] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:wq_worker_sleeping SourceFile:kernel/workqueue.c SourceLine:1487] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:wq_worker_tick SourceFile:kernel/workqueue.c SourceLine:1508] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:wq_worker_tick SourceFile:kernel/workqueue.c SourceLine:1534] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:wq_worker_tick SourceFile:kernel/workqueue.c SourceLine:1537]] TruncatedOutput:false]
|
| 14/2 |
2026/05/13 05:50 |
llm |
expert |
0m
Model:
Tokens:
input: 14540
output: 18
thoughts: 22
Thoughts: Here's my summary, as if I were thinking these thoughts:
**Analyzing Workqueue Statistics**
Alright, let's dive into the guts of the `workqueue` implementation. Specifically, I need to understand the usage of `stats` within the `kernel/workqueue.c` file. I'm already pretty familiar with workqueues in general, and I've got a good handle on the data structures, but I want to get a solid grasp of how these statistics are tracked and utilized. This is key to understanding performance implications and potential bottlenecks. Time to open up the code and get to it.
|
| 15/2 |
2026/05/13 05:51 |
tool |
grepper |
0m
Args: map[Expression:pwq->stats]
Results: map[Output:kernel/workqueue.c=1453=void wq_worker_sleeping(struct task_struct *task)
--
kernel/workqueue.c-1486- if (kick_pool(pool))
kernel/workqueue.c:1487: worker->current_pwq->stats[PWQ_STAT_CM_WAKEUP]++;
kernel/workqueue.c-1488-
--
kernel/workqueue.c=1499=void wq_worker_tick(struct task_struct *task)
--
kernel/workqueue.c-1507-
kernel/workqueue.c:1508: pwq->stats[PWQ_STAT_CPU_TIME] += TICK_USEC;
kernel/workqueue.c-1509-
--
kernel/workqueue.c-1533- wq_cpu_intensive_report(worker->current_func);
kernel/workqueue.c:1534: pwq->stats[PWQ_STAT_CPU_INTENSIVE]++;
kernel/workqueue.c-1535-
kernel/workqueue.c-1536- if (kick_pool(pool))
kernel/workqueue.c:1537: pwq->stats[PWQ_STAT_CM_WAKEUP]++;
kernel/workqueue.c-1538-
--
kernel/workqueue.c=3024=static void send_mayday(struct pool_workqueue *pwq)
--
kernel/workqueue.c-3042- wake_up_process(wq->rescuer->task);
kernel/workqueue.c:3043: pwq->stats[PWQ_STAT_MAYDAY]++;
kernel/workqueue.c-3044- }
--
kernel/workqueue.c=3202=__acquires(&pool->lock)
--
kernel/workqueue.c-3269-
kernel/workqueue.c:3270: pwq->stats[PWQ_STAT_STARTED]++;
kernel/workqueue.c-3271- raw_spin_unlock_irq(&pool->lock);
--
kernel/workqueue.c-3338-
kernel/workqueue.c:3339: pwq->stats[PWQ_STAT_COMPLETED]++;
kernel/workqueue.c-3340-
--
kernel/workqueue.c=3485=static bool assign_rescuer_work(struct pool_workqueue *pwq, struct worker *rescuer)
--
kernel/workqueue.c-3530- if (get_work_pwq(work) == pwq && assign_work(work, rescuer, &n)) {
kernel/workqueue.c:3531: pwq->stats[PWQ_STAT_RESCUED]++;
kernel/workqueue.c-3532- /* put the cursor for next search */
]
|
| 16/2 |
2026/05/13 05:51 |
llm |
expert |
0m
Model:
Tokens:
input: 15430
output: 19
thoughts: 25
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience:
**Investigating the Role of `pwq->stats`**
Alright, so I'm looking at this `pwq->stats` structure. My gut feeling is that it's probably only used internally for collecting performance metrics β you know, the usual stats stuff. But, before I make any assumptions, I need to confirm this. The real question is: is it *actually* private, or is it exposed in any way? I need to check the export tables, the headers, any potentially relevant API documentation, and ideally, I'd grep through the entire codebase to be absolutely sure. A careless export can mess with everything - and debugging it is a nightmare. Gotta make sure it stays encapsulated if it *should* be. Gotta verify the assumptions, and rule out the chance that somebody might have built something on its use, I can't be making any assumptions.
|
| 17/2 |
2026/05/13 05:51 |
tool |
grepper |
0m
Args: map[Expression:PWQ_STAT_]
Results: map[Output:kernel/workqueue.c=250=enum pool_workqueue_stats {
kernel/workqueue.c:251: PWQ_STAT_STARTED, /* work items started execution */
kernel/workqueue.c:252: PWQ_STAT_COMPLETED, /* work items completed execution */
kernel/workqueue.c:253: PWQ_STAT_CPU_TIME, /* total CPU time consumed */
kernel/workqueue.c:254: PWQ_STAT_CPU_INTENSIVE, /* wq_cpu_intensive_thresh_us violations */
kernel/workqueue.c:255: PWQ_STAT_CM_WAKEUP, /* concurrency-management worker wakeups */
kernel/workqueue.c:256: PWQ_STAT_REPATRIATED, /* unbound workers brought back into scope */
kernel/workqueue.c:257: PWQ_STAT_MAYDAY, /* maydays to rescuer */
kernel/workqueue.c:258: PWQ_STAT_RESCUED, /* linked work items executed by rescuer */
kernel/workqueue.c-259-
--
kernel/workqueue.c=1267=static bool kick_pool(struct worker_pool *pool)
--
kernel/workqueue.c-1308- p->wake_cpu = wake_cpu;
kernel/workqueue.c:1309: get_work_pwq(work)->stats[PWQ_STAT_REPATRIATED]++;
kernel/workqueue.c-1310- }
--
kernel/workqueue.c=1453=void wq_worker_sleeping(struct task_struct *task)
--
kernel/workqueue.c-1486- if (kick_pool(pool))
kernel/workqueue.c:1487: worker->current_pwq->stats[PWQ_STAT_CM_WAKEUP]++;
kernel/workqueue.c-1488-
--
kernel/workqueue.c=1499=void wq_worker_tick(struct task_struct *task)
--
kernel/workqueue.c-1507-
kernel/workqueue.c:1508: pwq->stats[PWQ_STAT_CPU_TIME] += TICK_USEC;
kernel/workqueue.c-1509-
--
kernel/workqueue.c-1533- wq_cpu_intensive_report(worker->current_func);
kernel/workqueue.c:1534: pwq->stats[PWQ_STAT_CPU_INTENSIVE]++;
kernel/workqueue.c-1535-
kernel/workqueue.c-1536- if (kick_pool(pool))
kernel/workqueue.c:1537: pwq->stats[PWQ_STAT_CM_WAKEUP]++;
kernel/workqueue.c-1538-
--
kernel/workqueue.c=3024=static void send_mayday(struct pool_workqueue *pwq)
--
kernel/workqueue.c-3042- wake_up_process(wq->rescuer->task);
kernel/workqueue.c:3043: pwq->stats[PWQ_STAT_MAYDAY]++;
kernel/workqueue.c-3044- }
--
kernel/workqueue.c=3202=__acquires(&pool->lock)
--
kernel/workqueue.c-3269-
kernel/workqueue.c:3270: pwq->stats[PWQ_STAT_STARTED]++;
kernel/workqueue.c-3271- raw_spin_unlock_irq(&pool->lock);
--
kernel/workqueue.c-3338-
kernel/workqueue.c:3339: pwq->stats[PWQ_STAT_COMPLETED]++;
kernel/workqueue.c-3340-
--
kernel/workqueue.c=3485=static bool assign_rescuer_work(struct pool_workqueue *pwq, struct worker *rescuer)
--
kernel/workqueue.c-3530- if (get_work_pwq(work) == pwq && assign_work(work, rescuer, &n)) {
kernel/workqueue.c:3531: pwq->stats[PWQ_STAT_RESCUED]++;
kernel/workqueue.c-3532- /* put the cursor for next search */
--
tools/workqueue/wq_monitor.py=57=WQ_MEM_RECLAIM = prog['WQ_MEM_RECLAIM']
tools/workqueue/wq_monitor.py-58-
tools/workqueue/wq_monitor.py:59:PWQ_STAT_STARTED = prog['PWQ_STAT_STARTED'] # work items started execution
tools/workqueue/wq_monitor.py:60:PWQ_STAT_COMPLETED = prog['PWQ_STAT_COMPLETED'] # work items completed execution
tools/workqueue/wq_monitor.py:61:PWQ_STAT_CPU_TIME = prog['PWQ_STAT_CPU_TIME'] # total CPU time consumed
tools/workqueue/wq_monitor.py:62:PWQ_STAT_CPU_INTENSIVE = prog['PWQ_STAT_CPU_INTENSIVE'] # wq_cpu_intensive_thresh_us violations
tools/workqueue/wq_monitor.py:63:PWQ_STAT_CM_WAKEUP = prog['PWQ_STAT_CM_WAKEUP'] # concurrency-management worker wakeups
tools/workqueue/wq_monitor.py:64:PWQ_STAT_REPATRIATED = prog['PWQ_STAT_REPATRIATED'] # unbound workers brought back into scope
tools/workqueue/wq_monitor.py:65:PWQ_STAT_MAYDAY = prog['PWQ_STAT_MAYDAY'] # maydays to rescuer
tools/workqueue/wq_monitor.py:66:PWQ_STAT_RESCUED = prog['PWQ_STAT_RESCUED'] # linked work items executed by rescuer
tools/workqueue/wq_monitor.py-67-PWQ_NR_STATS = prog['PWQ_NR_STATS']
--
tools/workqueue/wq_monitor.py=69=class WqStats:
--
tools/workqueue/wq_monitor.py-83- 'mem_reclaim' : self.mem_reclaim,
tools/workqueue/wq_monitor.py:84: 'started' : self.stats[PWQ_STAT_STARTED],
tools/workqueue/wq_monitor.py:85: 'completed' : self.stats[PWQ_STAT_COMPLETED],
tools/workqueue/wq_monitor.py:86: 'cpu_time' : self.stats[PWQ_STAT_CPU_TIME],
tools/workqueue/wq_monitor.py:87: 'cpu_intensive' : self.stats[PWQ_STAT_CPU_INTENSIVE],
tools/workqueue/wq_monitor.py:88: 'cm_wakeup' : self.stats[PWQ_STAT_CM_WAKEUP],
tools/workqueue/wq_monitor.py:89: 'repatriated' : self.stats[PWQ_STAT_REPATRIATED],
tools/workqueue/wq_monitor.py:90: 'mayday' : self.stats[PWQ_STAT_MAYDAY],
tools/workqueue/wq_monitor.py:91: 'rescued' : self.stats[PWQ_STAT_RESCUED], }
tools/workqueue/wq_monitor.py-92-
--
tools/workqueue/wq_monitor.py-103- if self.unbound:
tools/workqueue/wq_monitor.py:104: cmw_rpr = str(self.stats[PWQ_STAT_REPATRIATED]);
tools/workqueue/wq_monitor.py-105- else:
tools/workqueue/wq_monitor.py:106: cpu_intensive = str(self.stats[PWQ_STAT_CPU_INTENSIVE])
tools/workqueue/wq_monitor.py:107: cmw_rpr = str(self.stats[PWQ_STAT_CM_WAKEUP])
tools/workqueue/wq_monitor.py-108-
tools/workqueue/wq_monitor.py-109- if self.mem_reclaim:
tools/workqueue/wq_monitor.py:110: mayday = str(self.stats[PWQ_STAT_MAYDAY])
tools/workqueue/wq_monitor.py:111: rescued = str(self.stats[PWQ_STAT_RESCUED])
tools/workqueue/wq_monitor.py-112-
tools/workqueue/wq_monitor.py-113- out = f'{self.name[-24:]:24} ' \
tools/workqueue/wq_monitor.py:114: f'{self.stats[PWQ_STAT_STARTED]:8} ' \
tools/workqueue/wq_monitor.py:115: f'{max(self.stats[PWQ_STAT_STARTED] - self.stats[PWQ_STAT_COMPLETED], 0):5} ' \
tools/workqueue/wq_monitor.py:116: f'{self.stats[PWQ_STAT_CPU_TIME] / 1000000:8.1f} ' \
tools/workqueue/wq_monitor.py-117- f'{cpu_intensive:>7} ' \
]
|
| 18/2 |
2026/05/13 05:51 |
llm |
expert |
1m
Model:
Tokens:
input: 18044
output: 19
thoughts: 40
Thoughts: Okay, here's my interpretation of those thoughts, summarized from my perspective as someone familiar with system monitoring and debugging:
**Investigating the Data Source**
Right, so these stats I'm dealing with, they seem to be exclusively consumed by `wq_monitor.py`, that's a clue. And possibly by some sysfs interface, I should investigate this path. The immediate question is, how else is this data exposed besides the direct script consumption? My next move is clear - let's dive into sysfs and see what's what. I need to understand if the data's being exposed there. This will inform how I approach further analysis or modifications. Finding the sysfs interface would provide additional insight into the dataβs lifecycle and potential points of interaction, as well as confirm data availability.
|
| 19/2 |
2026/05/13 05:53 |
tool |
grepper |
0m
Args: map[Expression:PWQ_STAT_]
Results: map[Output:kernel/workqueue.c=250=enum pool_workqueue_stats {
kernel/workqueue.c:251: PWQ_STAT_STARTED, /* work items started execution */
kernel/workqueue.c:252: PWQ_STAT_COMPLETED, /* work items completed execution */
kernel/workqueue.c:253: PWQ_STAT_CPU_TIME, /* total CPU time consumed */
kernel/workqueue.c:254: PWQ_STAT_CPU_INTENSIVE, /* wq_cpu_intensive_thresh_us violations */
kernel/workqueue.c:255: PWQ_STAT_CM_WAKEUP, /* concurrency-management worker wakeups */
kernel/workqueue.c:256: PWQ_STAT_REPATRIATED, /* unbound workers brought back into scope */
kernel/workqueue.c:257: PWQ_STAT_MAYDAY, /* maydays to rescuer */
kernel/workqueue.c:258: PWQ_STAT_RESCUED, /* linked work items executed by rescuer */
kernel/workqueue.c-259-
--
kernel/workqueue.c=1267=static bool kick_pool(struct worker_pool *pool)
--
kernel/workqueue.c-1308- p->wake_cpu = wake_cpu;
kernel/workqueue.c:1309: get_work_pwq(work)->stats[PWQ_STAT_REPATRIATED]++;
kernel/workqueue.c-1310- }
--
kernel/workqueue.c=1453=void wq_worker_sleeping(struct task_struct *task)
--
kernel/workqueue.c-1486- if (kick_pool(pool))
kernel/workqueue.c:1487: worker->current_pwq->stats[PWQ_STAT_CM_WAKEUP]++;
kernel/workqueue.c-1488-
--
kernel/workqueue.c=1499=void wq_worker_tick(struct task_struct *task)
--
kernel/workqueue.c-1507-
kernel/workqueue.c:1508: pwq->stats[PWQ_STAT_CPU_TIME] += TICK_USEC;
kernel/workqueue.c-1509-
--
kernel/workqueue.c-1533- wq_cpu_intensive_report(worker->current_func);
kernel/workqueue.c:1534: pwq->stats[PWQ_STAT_CPU_INTENSIVE]++;
kernel/workqueue.c-1535-
kernel/workqueue.c-1536- if (kick_pool(pool))
kernel/workqueue.c:1537: pwq->stats[PWQ_STAT_CM_WAKEUP]++;
kernel/workqueue.c-1538-
--
kernel/workqueue.c=3024=static void send_mayday(struct pool_workqueue *pwq)
--
kernel/workqueue.c-3042- wake_up_process(wq->rescuer->task);
kernel/workqueue.c:3043: pwq->stats[PWQ_STAT_MAYDAY]++;
kernel/workqueue.c-3044- }
--
kernel/workqueue.c=3202=__acquires(&pool->lock)
--
kernel/workqueue.c-3269-
kernel/workqueue.c:3270: pwq->stats[PWQ_STAT_STARTED]++;
kernel/workqueue.c-3271- raw_spin_unlock_irq(&pool->lock);
--
kernel/workqueue.c-3338-
kernel/workqueue.c:3339: pwq->stats[PWQ_STAT_COMPLETED]++;
kernel/workqueue.c-3340-
--
kernel/workqueue.c=3485=static bool assign_rescuer_work(struct pool_workqueue *pwq, struct worker *rescuer)
--
kernel/workqueue.c-3530- if (get_work_pwq(work) == pwq && assign_work(work, rescuer, &n)) {
kernel/workqueue.c:3531: pwq->stats[PWQ_STAT_RESCUED]++;
kernel/workqueue.c-3532- /* put the cursor for next search */
--
tools/workqueue/wq_monitor.py=57=WQ_MEM_RECLAIM = prog['WQ_MEM_RECLAIM']
tools/workqueue/wq_monitor.py-58-
tools/workqueue/wq_monitor.py:59:PWQ_STAT_STARTED = prog['PWQ_STAT_STARTED'] # work items started execution
tools/workqueue/wq_monitor.py:60:PWQ_STAT_COMPLETED = prog['PWQ_STAT_COMPLETED'] # work items completed execution
tools/workqueue/wq_monitor.py:61:PWQ_STAT_CPU_TIME = prog['PWQ_STAT_CPU_TIME'] # total CPU time consumed
tools/workqueue/wq_monitor.py:62:PWQ_STAT_CPU_INTENSIVE = prog['PWQ_STAT_CPU_INTENSIVE'] # wq_cpu_intensive_thresh_us violations
tools/workqueue/wq_monitor.py:63:PWQ_STAT_CM_WAKEUP = prog['PWQ_STAT_CM_WAKEUP'] # concurrency-management worker wakeups
tools/workqueue/wq_monitor.py:64:PWQ_STAT_REPATRIATED = prog['PWQ_STAT_REPATRIATED'] # unbound workers brought back into scope
tools/workqueue/wq_monitor.py:65:PWQ_STAT_MAYDAY = prog['PWQ_STAT_MAYDAY'] # maydays to rescuer
tools/workqueue/wq_monitor.py:66:PWQ_STAT_RESCUED = prog['PWQ_STAT_RESCUED'] # linked work items executed by rescuer
tools/workqueue/wq_monitor.py-67-PWQ_NR_STATS = prog['PWQ_NR_STATS']
--
tools/workqueue/wq_monitor.py=69=class WqStats:
--
tools/workqueue/wq_monitor.py-83- 'mem_reclaim' : self.mem_reclaim,
tools/workqueue/wq_monitor.py:84: 'started' : self.stats[PWQ_STAT_STARTED],
tools/workqueue/wq_monitor.py:85: 'completed' : self.stats[PWQ_STAT_COMPLETED],
tools/workqueue/wq_monitor.py:86: 'cpu_time' : self.stats[PWQ_STAT_CPU_TIME],
tools/workqueue/wq_monitor.py:87: 'cpu_intensive' : self.stats[PWQ_STAT_CPU_INTENSIVE],
tools/workqueue/wq_monitor.py:88: 'cm_wakeup' : self.stats[PWQ_STAT_CM_WAKEUP],
tools/workqueue/wq_monitor.py:89: 'repatriated' : self.stats[PWQ_STAT_REPATRIATED],
tools/workqueue/wq_monitor.py:90: 'mayday' : self.stats[PWQ_STAT_MAYDAY],
tools/workqueue/wq_monitor.py:91: 'rescued' : self.stats[PWQ_STAT_RESCUED], }
tools/workqueue/wq_monitor.py-92-
--
tools/workqueue/wq_monitor.py-103- if self.unbound:
tools/workqueue/wq_monitor.py:104: cmw_rpr = str(self.stats[PWQ_STAT_REPATRIATED]);
tools/workqueue/wq_monitor.py-105- else:
tools/workqueue/wq_monitor.py:106: cpu_intensive = str(self.stats[PWQ_STAT_CPU_INTENSIVE])
tools/workqueue/wq_monitor.py:107: cmw_rpr = str(self.stats[PWQ_STAT_CM_WAKEUP])
tools/workqueue/wq_monitor.py-108-
tools/workqueue/wq_monitor.py-109- if self.mem_reclaim:
tools/workqueue/wq_monitor.py:110: mayday = str(self.stats[PWQ_STAT_MAYDAY])
tools/workqueue/wq_monitor.py:111: rescued = str(self.stats[PWQ_STAT_RESCUED])
tools/workqueue/wq_monitor.py-112-
tools/workqueue/wq_monitor.py-113- out = f'{self.name[-24:]:24} ' \
tools/workqueue/wq_monitor.py:114: f'{self.stats[PWQ_STAT_STARTED]:8} ' \
tools/workqueue/wq_monitor.py:115: f'{max(self.stats[PWQ_STAT_STARTED] - self.stats[PWQ_STAT_COMPLETED], 0):5} ' \
tools/workqueue/wq_monitor.py:116: f'{self.stats[PWQ_STAT_CPU_TIME] / 1000000:8.1f} ' \
tools/workqueue/wq_monitor.py-117- f'{cpu_intensive:>7} ' \
]
|
| 20/2 |
2026/05/13 05:53 |
llm |
expert |
1m
Model:
Tokens:
input: 20642
output: 40
thoughts: 849
Thoughts: **Analyzing the Benign Data Race in `wq_worker_tick`**
Okay, so I'm looking at this data race reported in `wq_monitor.py`, which uses drgn to peek directly into kernel memory. This confirms it's a completely benign race condition, a simple statistics counter. The worst-case scenario is a missed increment or a slightly corrupted value. On 64-bit architectures, which the report specifies as x86_64, it's just a missed increment, not even tearing. Even if there's tearing on a 32 bit arch, it's just a statistic read by a debugging tool.
Therefore:
* **Exploitable: False** - No security implications here.
* **DenialOfService: False** - No DoS potential.
* **Unprivileged: False** - It's in the background kernel task. However, a regular user can trigger workqueues. The question asks if a non-root user can reach it, which is true because any process can queue and execute work and that can trigger `wq_worker_tick`, but it's not a security issue, so it's accessible but not exploitable.
* **UserNamespace: Yes** - Accessible due to workqueues usage by non-root users.
* **VMGuestTrigger: Yes** - Same reasoning applies for VMs.
* **VMHostTrigger: Yes** - Same.
* **NetworkTrigger: Yes** - Workqueues are used in networking.
* **RemoteTrigger: Yes** - Workqueues can be triggered remotely.
* **PeripheralTrigger: Yes** - Workqueues are triggered by peripherals.
* **FilesystemTrigger: Yes** - workqueues are heavily utilized in Filesystems.
Now, am I missing anything else? Let's go over the KCSAN report. The report shows a read-write to `0xffff8881000c5cd8` of 8 bytes by an interrupt on multiple CPUs. The critical line is `wq_worker_tick+0x64/0x240`. Yes, the data race is on `pwq->stats[PWQ_STAT_CPU_TIME] += TICK_USEC;`. Since `pwq` is shared among CPUs in unbound workqueues, and `wq_worker_tick` runs from a timer interrupt, two workers from the same pool on different CPUs can race on the counter. We can say it's a known, benign data race.
Now, I'm checking `wq_worker_tick` again. The code is:
```c
void wq_worker_tick(struct task_struct *task)
{
struct worker *worker = kthread_data(task);
struct pool_workqueue *pwq = worker->current_pwq;
struct worker_pool *pool = worker->pool;
if (!pwq)
return;
pwq->stats[PWQ_STAT_CPU_TIME] += TICK_USEC;
...
```
Is there any lock here? No, it's called from `sched_tick()`. Is `pwq->stats` updated elsewhere? Yes, in `process_one_work`:
```c
pwq->stats[PWQ_STAT_STARTED]++;
...
pwq->stats[PWQ_STAT_COMPLETED]++;
```
Wait, `process_one_work` updates `STARTED` and `COMPLETED` without locks? Let's check `process_one_work`.
|
| 21/2 |
2026/05/13 05:55 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:kernel/workqueue.c IncludeLines:true Name:process_one_work]
Results: map[SourceCode:3186: /**
3187: * process_one_work - process single work
3188: * @worker: self
3189: * @work: work to process
3190: *
3191: * Process @work. This function contains all the logics necessary to
3192: * process a single work including synchronization against and
3193: * interaction with other workers on the same cpu, queueing and
3194: * flushing. As long as context requirement is met, any worker can
3195: * call this function to process a work.
3196: *
3197: * CONTEXT:
3198: * raw_spin_lock_irq(pool->lock) which is released and regrabbed.
3199: */
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: */
3300: lockdep_invariant_state(true);
3301: trace_workqueue_execute_start(work);
3302: worker->current_func(work);
3303: /*
3304: * While we must be careful to not use "work" after this, the trace
3305: * point will only record its address.
3306: */
3307: trace_workqueue_execute_end(work, worker->current_func);
3308:
3309: lock_map_release(&lockdep_map);
3310: if (!bh_draining)
3311: lock_map_release(pwq->wq->lockdep_map);
3312:
3313: if (unlikely((worker->task && in_atomic()) ||
3314: lockdep_depth(current) != lockdep_start_depth ||
3315: rcu_preempt_depth() != rcu_start_depth)) {
3316: pr_err("BUG: workqueue leaked atomic, lock or RCU: %s[%d]\n"
3317: " preempt=0x%08x lock=%d->%d RCU=%d->%d workfn=%ps\n",
3318: current->comm, task_pid_nr(current), preempt_count(),
3319: lockdep_start_depth, lockdep_depth(current),
3320: rcu_start_depth, rcu_preempt_depth(),
3321: worker->current_func);
3322: debug_show_held_locks(current);
3323: dump_stack();
3324: }
3325:
3326: /*
3327: * The following prevents a kworker from hogging CPU on !PREEMPTION
3328: * kernels, where a requeueing work item waiting for something to
3329: * happen could deadlock with stop_machine as such work item could
3330: * indefinitely requeue itself while all other CPUs are trapped in
3331: * stop_machine. At the same time, report a quiescent RCU state so
3332: * the same condition doesn't freeze RCU.
3333: */
3334: if (worker->task)
3335: cond_resched();
3336:
3337: raw_spin_lock_irq(&pool->lock);
3338:
3339: pwq->stats[PWQ_STAT_COMPLETED]++;
3340:
3341: /*
3342: * In addition to %WQ_CPU_INTENSIVE, @worker may also have been marked
3343: * CPU intensive by wq_worker_tick() if @work hogged CPU longer than
3344: * wq_cpu_intensive_thresh_us. Clear it.
3345: */
3346: worker_clr_flags(worker, WORKER_CPU_INTENSIVE);
3347:
3348: /* tag the worker for identification in schedule() */
3349: worker->last_func = worker->current_func;
3350:
3351: /* we're done with it, release */
3352: hash_del(&worker->hentry);
3353: worker->current_work = NULL;
3354: worker->current_func = NULL;
3355: worker->current_pwq = NULL;
3356: worker->current_color = INT_MAX;
3357:
3358: /* must be the last step, see the function comment */
3359: pwq_dec_nr_in_flight(pwq, work_data);
3360: }
SourceFile:kernel/workqueue.c]
|
| 22/2 |
2026/05/13 05:55 |
llm |
expert |
1m
Model:
Tokens:
input: 25215
output: 22
thoughts: 581
Thoughts: **Analyzing a Potential Data Race in Workqueue Statistics**
Okay, so I'm looking at this workqueue code, and something's jumped out at me. Specifically, I'm concerned about the updates to `pwq->stats`. `process_one_work` is carefully handling `pwq->stats[PWQ_STAT_STARTED]` and `pwq->stats[PWQ_STAT_COMPLETED]` with the `pool->lock` held. That makes sense, keeps things consistent. But then, I see that `wq_worker_tick` is updating `pwq->stats[PWQ_STAT_CPU_TIME]` *without* that same lock. My immediate thought is, "Wait a minute... that looks like a data race."
The code shows that `pwq->stats[PWQ_STAT_CPU_TIME]` is incremented directly with `TICK_USEC`. Furthermore, the following CPU-intensive check happens after it, and involves reading some time data, with the `pool->lock` only acquired if the worker is deemed CPU intensive. Given that `pwq` is shared amongst workers in the same pool, and that these workers could very well be running on different CPUs, this means that multiple workers can concurrently update `PWQ_STAT_CPU_TIME` if they're interrupted at the same time. The fact that the update isn't atomic is the tell-tale sign that KCSAN would flag this.
So, yeah, it's a classic data race. But let's take a deep breath; itβs likely benign. This is just a statistic. It doesn't appear to be causing any memory corruption, it doesn't open the door for a denial of service, and it's certainly not a privilege escalation vulnerability. The workqueue mechanism is accessible from unprivileged processes because unprivileged processes can trigger workqueues, and these stats are potentially read by those processes. Therefore, they are accessible from various contexts like user namespaces, VM guests, hosts, network, peripherals, and filesystems, but this is a data race, not a security vulnerability.
Now, I'm not going to stop there. I want to be *absolutely* sure. Is `pwq->stats` used in any *logic*? I remember seeing it's only read by `wq_monitor.py`, which is fine. But I'm going to double-check. I'll need to confirm that `PWQ_STAT_CPU_TIME` is not used anywhere else where it would cause problems. Itβs important to make sure it doesn't sneak its way into some critical calculation or decision.
|
| 23/2 |
2026/05/13 05:57 |
tool |
grepper |
0m
Args: map[Expression:PWQ_STAT_CPU_TIME]
Results: map[Output:kernel/workqueue.c=250=enum pool_workqueue_stats {
--
kernel/workqueue.c-252- PWQ_STAT_COMPLETED, /* work items completed execution */
kernel/workqueue.c:253: PWQ_STAT_CPU_TIME, /* total CPU time consumed */
kernel/workqueue.c-254- PWQ_STAT_CPU_INTENSIVE, /* wq_cpu_intensive_thresh_us violations */
--
kernel/workqueue.c=1499=void wq_worker_tick(struct task_struct *task)
--
kernel/workqueue.c-1507-
kernel/workqueue.c:1508: pwq->stats[PWQ_STAT_CPU_TIME] += TICK_USEC;
kernel/workqueue.c-1509-
--
tools/workqueue/wq_monitor.py=60=PWQ_STAT_COMPLETED = prog['PWQ_STAT_COMPLETED'] # work items completed execution
tools/workqueue/wq_monitor.py:61:PWQ_STAT_CPU_TIME = prog['PWQ_STAT_CPU_TIME'] # total CPU time consumed
tools/workqueue/wq_monitor.py-62-PWQ_STAT_CPU_INTENSIVE = prog['PWQ_STAT_CPU_INTENSIVE'] # wq_cpu_intensive_thresh_us violations
--
tools/workqueue/wq_monitor.py=69=class WqStats:
--
tools/workqueue/wq_monitor.py-85- 'completed' : self.stats[PWQ_STAT_COMPLETED],
tools/workqueue/wq_monitor.py:86: 'cpu_time' : self.stats[PWQ_STAT_CPU_TIME],
tools/workqueue/wq_monitor.py-87- 'cpu_intensive' : self.stats[PWQ_STAT_CPU_INTENSIVE],
--
tools/workqueue/wq_monitor.py-115- f'{max(self.stats[PWQ_STAT_STARTED] - self.stats[PWQ_STAT_COMPLETED], 0):5} ' \
tools/workqueue/wq_monitor.py:116: f'{self.stats[PWQ_STAT_CPU_TIME] / 1000000:8.1f} ' \
tools/workqueue/wq_monitor.py-117- f'{cpu_intensive:>7} ' \
]
|
| 24/2 |
2026/05/13 05:57 |
llm |
expert |
5m
Model:
Tokens:
input: 26990
output: 66
thoughts: 700
Thoughts: **My Assessment of the KCSAN Data Race**
Okay, so I've been looking at this KCSAN data race report, and I think I have a good handle on it. The code in question is only used in `wq_monitor.py` β purely for statistical counting. That tells me right away the data race is benign.
Let's break down the analysis:
* **Exploitable: False** β The prompt asks about memory corruption or privilege escalation, and this race affects a `u64` statistic counter, `pwq->stats[PWQ_STAT_CPU_TIME]`. Even on a 32-bit system where a 64-bit read/write could be torn, the affected data is not used for any kernel decision making. Itβs only read by `wq_monitor.py` through drgn which reads kernel memory directly. So, no way to affect kernel behavior.
* **DenialOfService: False** β The prompt asks if this is a DoS, but since the only impact is on the statistics reported by `wq_monitor.py`, this is not a DoS.
* **Unprivileged: True** β The prompt specifies that I need to determine if the bug can be reached from a typical (non-root) user process. Since `wq_worker_tick` is reachable by unprivileged processes, this is `True`.
Now for the triggers. Even though this isnβt a *bug* in the sense of causing corruption or DoS, the prompt asks about *reachability*. I need to think about how this code path is accessed:
* **UserNamespace: True** β Workqueues are used in user namespaces, so this trigger is definitely `True`.
* **VMGuestTrigger: True** β KVM uses workqueues, so the VM guest can trigger this.
* **VMHostTrigger: True** β Virtio drivers use workqueues, so this is `True`.
* **NetworkTrigger: True** β Workqueues are heavily involved in network processing. Looking at the stack trace, I see that the code path leads through the network stack (`nsim_dev_trap_report_work`), so the network can trigger this race.
* **RemoteTrigger: True** β Since network access can trigger it, and network is remote by default, remote is also `True`.
* **PeripheralTrigger: True** β USB drivers and other peripherals rely on workqueues, so this code is reachable from peripherals.
* **FilesystemTrigger: True** β Asynchronous I/O and delayed work in the filesystems uses workqueues, so this is also reachable.
Okay, let's double-check the exploitation possibility again. Even with the torn 64-bit read/write, this data race only affects a statistic counter. The kernel doesnβt make any decisions based on this data. It's just for `wq_monitor.py`, which directly reads kernel memory. Absolutely no way to leverage this for exploitation.
I'm confident in these assessments. I'll call `set-results` with the following:
- Exploitable: False
- DenialOfService: False
- Unprivileged: True
- UserNamespace: True
- VMGuestTrigger: True
- VMHostTrigger: True
- NetworkTrigger: True
- RemoteTrigger: True
- PeripheralTrigger: True
- FilesystemTrigger: True
|
| 25/2 |
2026/05/13 06:02 |
tool |
set-results |
0m
Args: map[DenialOfService:false Exploitable:false FilesystemTrigger:true NetworkTrigger:true PeripheralTrigger:true RemoteTrigger:true Unprivileged:true UserNamespace:true VMGuestTrigger:true VMHostTrigger:true]
Results: map[DenialOfService:false Exploitable:false FilesystemTrigger:true NetworkTrigger:true PeripheralTrigger:true RemoteTrigger:true Unprivileged:true UserNamespace:true VMGuestTrigger:true VMHostTrigger:true]
|
| 26/2 |
2026/05/13 06:02 |
llm |
expert |
5m
Model:
Tokens:
input: 28568
output: 455
thoughts: 337
Thoughts: **Final Thoughts on the Analysis**
Okay, the analysis is done. Time to synthesize my final response.
This KCSAN report flagged a data race. Specifically, it's the update of `pwq->stats[PWQ_STAT_CPU_TIME]` within `wq_worker_tick()`, and the race happens because that function is called from the timer interrupt, `sched_tick()`, on the CPU where a workqueue worker is running. The key here is that with unbound workqueues, workers from the same pool workqueue can run concurrently across different CPUs. If the timer interrupts hit simultaneously on different CPUs, we get concurrent updates to that shared `pwq->stats` counter, with no synchronization to keep things straight.
Now, considering the implications... This `pwq->stats` array is purely for statistics, and as we all know, it's only read by debugging tools like that `wq_monitor.py` script via `drgn`. The impact is benign. It doesn't touch any of the core kernel logic, control flow, or memory safety, and that's critical. At worst, we're looking at a slightly inaccurate statistic β maybe some missed increments or torn reads on a 32-bit architecture. But even that is fairly inconsequential. No security implications at all.
I'm aware that workqueues are pervasive. They're a fundamental part of the kernel, utilized by virtually every subsystem out there β networking, filesystems, peripherals, KVM... the works. This code path is easily reachable from almost any context β privileged or unprivileged, userspace, even remote or peripheral inputs. Still, because the race is benign, it can't be exploited for any kind of privilege escalation, memory corruption, or even a denial-of-service attack. A minor, statistical glitch, at worst.
|
| 27/1 |
2026/05/13 06:07 |
action |
format-explanation |
0m
Results: map[Explanation:The reported issue is a KCSAN data-race on the `pwq->stats[PWQ_STAT_CPU_TIME]`
counter in `wq_worker_tick()`.
### Analysis
The function `wq_worker_tick()` is called from the scheduler tick
(`sched_tick()`) in the context of the timer interrupt on the CPU where a
workqueue worker is currently executing. For unbound workqueues, multiple
workers belonging to the same pool workqueue (`pwq`) can run concurrently on
different CPUs. If two such workers receive a timer interrupt simultaneously,
they will both attempt to execute `pwq->stats[PWQ_STAT_CPU_TIME] += TICK_USEC;`
concurrently without any locks or atomic operations, leading to the reported
data race.
However, `pwq->stats` is an array of `u64` used exclusively for tracking
workqueue statistics. A review of the kernel source code reveals that these
statistics are never read or used by the kernel itself to make any runtime
decisions. They are only intended to be read by external debugging and
monitoring tools (specifically, the `tools/workqueue/wq_monitor.py` script which
uses `drgn` to read kernel memory directly).
Because this counter does not influence any kernel logic, control flow, or
memory management, the data race is completely benign. The worst-case scenario
is a slightly inaccurate statistic (e.g., a missed increment or a torn
read/write on 32-bit architectures), which has absolutely no security
implications.
### Triggers
Workqueues are a core kernel mechanism utilized by almost all subsystems,
including networking, filesystems, KVM, and device drivers. Consequently, the
`wq_worker_tick()` code path is constantly exercised during normal system
operation and can be indirectly triggered by any user-space application
(including unprivileged processes and those in user namespaces) or external
inputs (network packets, USB devices, VM guests, etc.) that cause work items to
be queued and executed.
### Conclusion
While the code path is universally reachable, the data race is entirely benign.
It cannot be exploited for memory corruption, privilege escalation, or denial of
service.]
|