| 0/0 |
2026/07/27 00:20 |
flow |
assessment-security |
1h00m
Results: map[DenialOfService:false Explanation:The reported bug is a KCSAN data-race in the core mutex locking code,
specifically between `__mutex_lock_common` and `__mutex_remove_waiter`.
**Analysis:**
The race occurs when a waiting task reads `lock->first_waiter` without holding
the `wait_lock` to check if it is currently the first waiter in the queue
(`first = lock->first_waiter == &waiter;`). Concurrently, another task might be
updating `lock->first_waiter` while holding the `wait_lock` (for example, when a
waiter is removed due to a signal or timeout).
This is a classic example of a benign data race in the kernel.
1. The read of the pointer is a single load instruction, which is atomic on
64-bit architectures (and generally on 32-bit as well, given natural alignment).
It will read either the old pointer value or the new one, but not a torn
pointer.
2. The read value is only compared against `&waiter`, which is the address of a
local variable on the task's own stack.
3. If the task reads an old value and incorrectly evaluates `first` to `false`
(missing the fact that it just became the first waiter), it will simply skip the
optimistic spinning optimization and go to sleep. This is safe because the lock
owner will still correctly wake it up when the lock is released.
4. It is impossible for the task to incorrectly evaluate `first` to `true`,
because no other task's waiter can have the address of this task's local stack
variable.
**Security Impact:**
Because this data race does not lead to memory corruption, invalid pointer
dereferences, deadlocks, or any violation of the mutex semantics, it is **not
exploitable** and does **not cause a Denial of Service (DoS)**. KCSAN flags it
simply because it involves a plain C read and write without
`READ_ONCE()`/`WRITE_ONCE()` annotations.
**Reachability:**
Mutexes are a fundamental synchronization primitive used everywhere in the Linux
kernel. The specific stack trace shows it being triggered via `tty_write`, which
is accessible to unprivileged users. Similarly, because mutexes are used in
network stacks, filesystems, and device drivers, this code path is reachable via
network traffic, malicious filesystems, peripheral devices, and from within VM
guests/hosts. Exploitable:false FilesystemTrigger:true NetworkTrigger:true PeripheralTrigger:true RemoteTrigger:true Unprivileged:true UserNamespace:true VMGuestTrigger:true VMHostTrigger:true]
|
| 1/1 |
2026/07/27 00:20 |
action |
syz-repro-to-c-repro |
0m
Results:
|
| 2/1 |
2026/07/27 00:20 |
action |
kernel-checkouter |
0m
Results: map[KernelSrc:/app/workdir/cache/src/ea1ec46165a34622b3d5769ce1a7084d96856f42]
|
| 3/1 |
2026/07/27 00:20 |
action |
kernel-builder |
13m
Results: map[KernelObj:/app/workdir/cache/build/25109052d6703f3f74ee67dd78dfe5a133f45305]
|
| 4/1 |
2026/07/27 00:34 |
action |
codesearch-prepare |
13m
Results: map[Index:codesearch-index]
|
| 5/1 |
2026/07/27 00:47 |
agent |
expert |
32m
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 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, elevated privileges, or an information leak.
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.
Information leaks are exploitable on their own and should be classified as such. A bug that copies kernel
memory contents to userspace (e.g. an out-of-bounds read whose result is returned to the caller, or
uninitialized stack/heap bytes written to a user buffer) is exploitable: it can reveal kernel pointer
values and defeat KASLR, expose sensitive data such as cryptographic keys or other processes' memory, and
serves as a necessary building block in most modern kernel privilege-escalation exploit chains. Do not classify
an information leak as non-exploitable solely because it does not directly cause a memory write or control-flow
hijack; the leak itself is the exploit primitive.
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:
==================================================================
BUG: KCSAN: data-race in __mutex_lock / __mutex_lock
write to 0xffff888124366070 of 8 bytes by task 10862 on cpu 1:
__mutex_remove_waiter kernel/locking/mutex.c:249 [inline]
__mutex_lock_common kernel/locking/mutex.c:786 [inline]
__mutex_lock+0x9d2/0xf40 kernel/locking/mutex.c:821
__mutex_lock_interruptible_slowpath+0xa/0x10 kernel/locking/mutex.c:1179
mutex_lock_interruptible+0x8c/0xa0 kernel/locking/mutex.c:1112
tty_write_lock drivers/tty/tty_io.c:942 [inline]
iterate_tty_write drivers/tty/tty_io.c:958 [inline]
file_tty_write+0x2b5/0x690 drivers/tty/tty_io.c:1081
tty_write+0x25/0x30 drivers/tty/tty_io.c:1102
new_sync_write fs/read_write.c:595 [inline]
vfs_write+0x57d/0x990 fs/read_write.c:687
ksys_write+0xdc/0x1a0 fs/read_write.c:739
__do_sys_write fs/read_write.c:750 [inline]
__se_sys_write fs/read_write.c:747 [inline]
__x64_sys_write+0x40/0x50 fs/read_write.c:747
x64_sys_call+0x27e1/0x3020 arch/x86/include/generated/asm/syscalls_64.h:2
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x136/0x3c0 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
read to 0xffff888124366070 of 8 bytes by task 10859 on cpu 0:
__mutex_lock_common kernel/locking/mutex.c:728 [inline]
__mutex_lock+0x52d/0xf40 kernel/locking/mutex.c:821
__mutex_lock_interruptible_slowpath+0xa/0x10 kernel/locking/mutex.c:1179
mutex_lock_interruptible+0x8c/0xa0 kernel/locking/mutex.c:1112
tty_write_lock drivers/tty/tty_io.c:942 [inline]
iterate_tty_write drivers/tty/tty_io.c:958 [inline]
file_tty_write+0x2b5/0x690 drivers/tty/tty_io.c:1081
tty_write+0x25/0x30 drivers/tty/tty_io.c:1102
new_sync_write fs/read_write.c:595 [inline]
vfs_write+0x57d/0x990 fs/read_write.c:687
ksys_write+0xdc/0x1a0 fs/read_write.c:739
__do_sys_write fs/read_write.c:750 [inline]
__se_sys_write fs/read_write.c:747 [inline]
__x64_sys_write+0x40/0x50 fs/read_write.c:747
x64_sys_call+0x27e1/0x3020 arch/x86/include/generated/asm/syscalls_64.h:2
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x136/0x3c0 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
value changed: 0xffffc90010913ca8 -> 0xffffc9001097fca8
Reported by Kernel Concurrency Sanitizer on:
CPU: 0 UID: 0 PID: 10859 Comm: syz.0.2761 Tainted: G W syzkaller #0 PREEMPT(lazy)
Tainted: [W]=WARN
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 07/16/2026
==================================================================
Reply: The reported bug is a KCSAN data-race in the core mutex locking code, specifically between `__mutex_lock_common` and `__mutex_remove_waiter`.
**Analysis:**
The race occurs when a waiting task reads `lock->first_waiter` without holding the `wait_lock` to check if it is currently the first waiter in the queue (`first = lock->first_waiter == &waiter;`). Concurrently, another task might be updating `lock->first_waiter` while holding the `wait_lock` (for example, when a waiter is removed due to a signal or timeout).
This is a classic example of a benign data race in the kernel.
1. The read of the pointer is a single load instruction, which is atomic on 64-bit architectures (and generally on 32-bit as well, given natural alignment). It will read either the old pointer value or the new one, but not a torn pointer.
2. The read value is only compared against `&waiter`, which is the address of a local variable on the task's own stack.
3. If the task reads an old value and incorrectly evaluates `first` to `false` (missing the fact that it just became the first waiter), it will simply skip the optimistic spinning optimization and go to sleep. This is safe because the lock owner will still correctly wake it up when the lock is released.
4. It is impossible for the task to incorrectly evaluate `first` to `true`, because no other task's waiter can have the address of this task's local stack variable.
**Security Impact:**
Because this data race does not lead to memory corruption, invalid pointer dereferences, deadlocks, or any violation of the mutex semantics, it is **not exploitable** and does **not cause a Denial of Service (DoS)**. KCSAN flags it simply because it involves a plain C read and write without `READ_ONCE()`/`WRITE_ONCE()` annotations.
**Reachability:**
Mutexes are a fundamental synchronization primitive used everywhere in the Linux kernel. The specific stack trace shows it being triggered via `tty_write`, which is accessible to unprivileged users. Similarly, because mutexes are used in network stacks, filesystems, and device drivers, this code path is reachable via network traffic, malicious filesystems, peripheral devices, and from within VM guests/hosts.
|
| 6/2 |
2026/07/27 00:47 |
llm |
expert |
0m
Model:
Tokens:
input: 6960
output: 70
thoughts: 146
Thoughts: **Analyzing a KCSAN Data-Race Report in Core Mutex Locking**
Okay, so I've got a KCSAN data-race report popping up, specifically within `__mutex_lock` and, interestingly, within `__mutex_lock` itself. The report flags a race between a write in `__mutex_remove_waiter` and a read in `__mutex_lock_common`. This immediately grabs my attention. Anytime you see a race in the core mutex locking code, that's a red flag. Let's dig into `kernel/locking/mutex.c` and see what we're dealing with.
My initial thought is, is this actually exploitable? KCSAN can sometimes be overly enthusiastic, especially with lockless optimistic spinning checks and similar patterns that are, in practice, harmless or have already been addressed. But given the nature of the functions involved, I can't dismiss it outright. I'm going to focus on the code around line 728 and 249 in `kernel/locking/mutex.c`. This is where the reported functions are typically situated. Let's see if we can understand the data flow and the conditions that might be leading to this race. We need to assess the potential impact of a corrupted read.
|
| 7/2 |
2026/07/27 00:47 |
tool |
read-file |
0m
Args: map[File:kernel/locking/mutex.c FirstLine:230 LineCount:50]
Results: map[Contents: 230: if (first) {
231: list_add_tail(&waiter->list, &first->list);
232: return;
233: }
234:
235: INIT_LIST_HEAD(&waiter->list);
236: lock->first_waiter = waiter;
237: __mutex_set_flag(lock, MUTEX_FLAG_WAITERS);
238: }
239:
240: static void
241: __mutex_remove_waiter(struct mutex *lock, struct mutex_waiter *waiter)
242: __must_hold(&lock->wait_lock)
243: {
244: if (list_empty(&waiter->list)) {
245: __mutex_clear_flag(lock, MUTEX_FLAGS);
246: lock->first_waiter = NULL;
247: } else {
248: if (lock->first_waiter == waiter)
249: lock->first_waiter = list_next_entry(waiter, list);
250: list_del(&waiter->list);
251: }
252:
253: debug_mutex_remove_waiter(lock, waiter, current);
254: hung_task_clear_blocker();
255: }
256:
257: /*
258: * Give up ownership to a specific task, when @task = NULL, this is equivalent
259: * to a regular unlock. Sets PICKUP on a handoff, clears HANDOFF, preserves
260: * WAITERS. Provides RELEASE semantics like a regular unlock, the
261: * __mutex_trylock() provides a matching ACQUIRE semantics for the handoff.
262: */
263: static void __mutex_handoff(struct mutex *lock, struct task_struct *task)
264: {
265: unsigned long owner = atomic_long_read(&lock->owner);
266:
267: for (;;) {
268: unsigned long new;
269:
270: MUTEX_WARN_ON(__owner_task(owner) != current);
271: MUTEX_WARN_ON(owner & MUTEX_FLAG_PICKUP);
272:
273: new = (owner & MUTEX_FLAG_WAITERS);
274: new |= (unsigned long)task;
275: if (task)
276: new |= MUTEX_FLAG_PICKUP;
277:
278: if (atomic_long_try_cmpxchg_release(&lock->owner, &owner, new))
279: break;
]
|
| 8/2 |
2026/07/27 00:47 |
tool |
read-file |
0m
Args: map[File:kernel/locking/mutex.c FirstLine:710 LineCount:50]
Results: map[Contents: 710: * wait_lock. This ensures the lock cancellation is ordered
711: * against mutex_unlock() and wake-ups do not go missing.
712: */
713: if (signal_pending_state(state, current)) {
714: ret = -EINTR;
715: goto err;
716: }
717:
718: if (ww_ctx) {
719: ret = __ww_mutex_check_kill(lock, &waiter, ww_ctx);
720: if (ret)
721: goto err;
722: }
723:
724: raw_spin_unlock_irqrestore_wake(&lock->wait_lock, flags, &wake_q);
725:
726: schedule_preempt_disabled();
727:
728: first = lock->first_waiter == &waiter;
729:
730: raw_spin_lock_irqsave(&lock->wait_lock, flags);
731: raw_spin_lock(¤t->blocked_lock);
732: /*
733: * As we likely have been woken up by task
734: * that has cleared our blocked_on state, re-set
735: * it to the lock we are trying to acquire.
736: */
737: __set_task_blocked_on(current, lock);
738: set_current_state(state);
739: /*
740: * Here we order against unlock; we must either see it change
741: * state back to RUNNING and fall through the next schedule(),
742: * or we must see its unlock and acquire.
743: */
744: if (__mutex_trylock_or_handoff(lock, first))
745: break;
746:
747: if (first) {
748: bool opt_acquired;
749:
750: /*
751: * mutex_optimistic_spin() can call schedule(), so
752: * we need to release these locks before calling it,
753: * and clear blocked on so we don't become unselectable
754: * to run.
755: */
756: __clear_task_blocked_on(current, lock);
757: raw_spin_unlock(¤t->blocked_lock);
758: raw_spin_unlock_irqrestore(&lock->wait_lock, flags);
759:
]
|
| 9/2 |
2026/07/27 00:47 |
llm |
expert |
0m
Model:
Tokens:
input: 8803
output: 35
thoughts: 571
Thoughts: **Analyzing a Potential Data Race in `__mutex_lock_common`**
Okay, so I'm looking at this KCSAN warning and it's pointing me towards a potential data race in `__mutex_lock_common`. Specifically, it highlights the interaction with `lock->first_waiter`. I see the write to `lock->first_waiter` happens in `__mutex_remove_waiter`, and the problematic read is in `__mutex_lock_common` without proper locking.
Let me break this down: `__mutex_remove_waiter` modifies `lock->first_waiter` and the other function reads it. The critical section where the read occurs in `__mutex_lock_common` is after a `raw_spin_unlock_irqrestore_wake`, a call to `schedule_preempt_disabled()`, and then the problematic read. The lock, `lock->wait_lock`, isn't held during that critical read of `lock->first_waiter`, which is the immediate source of the KCSAN warning. The fix would be to use a `READ_ONCE` (or `WRITE_ONCE` on the write side) to address the KCSAN complaint.
Now, is this a *real* bug? On 64-bit architectures, shouldn't pointer reads and writes be atomic? KCSAN is flagging this because, technically, it's just a plain read and plain write without explicit atomicity directives. Okay, I see. It's a missing `READ_ONCE`, or `WRITE_ONCE`.
But is this exploitable? I need to understand the implications of `first` being evaluated incorrectly, right? If `first` is true or false, what happens? If `first` is true, the code *might* try optimistic spinning. If `first` is false, it won't. Wait, am I reading the code correctly? The code evaluates `first` only once. It's just a boolean.
Okay, let's think about the compiler. The compiler *could* theoretically do something weird, but in reality, this is just a single load operation – a pointer comparison. So, what's the worst-case scenario?
Hmm... No, I think this is a benign data race. It's benign because the access is only being flagged by KCSAN and isn't exploitable. The system is designed to handle this, the race results in a simple check, with no severe repercussions. The key issue is it's a plain memory access without any specific ordering guarantees, so it will simply result in a KCSAN report. I'll need to go look up the relevant details within `kernel/locking/mutex.c`.
|
| 10/2 |
2026/07/27 00:48 |
tool |
read-file |
0m
Args: map[File:kernel/locking/mutex.c FirstLine:720 LineCount:50]
Results: map[Contents: 720: if (ret)
721: goto err;
722: }
723:
724: raw_spin_unlock_irqrestore_wake(&lock->wait_lock, flags, &wake_q);
725:
726: schedule_preempt_disabled();
727:
728: first = lock->first_waiter == &waiter;
729:
730: raw_spin_lock_irqsave(&lock->wait_lock, flags);
731: raw_spin_lock(¤t->blocked_lock);
732: /*
733: * As we likely have been woken up by task
734: * that has cleared our blocked_on state, re-set
735: * it to the lock we are trying to acquire.
736: */
737: __set_task_blocked_on(current, lock);
738: set_current_state(state);
739: /*
740: * Here we order against unlock; we must either see it change
741: * state back to RUNNING and fall through the next schedule(),
742: * or we must see its unlock and acquire.
743: */
744: if (__mutex_trylock_or_handoff(lock, first))
745: break;
746:
747: if (first) {
748: bool opt_acquired;
749:
750: /*
751: * mutex_optimistic_spin() can call schedule(), so
752: * we need to release these locks before calling it,
753: * and clear blocked on so we don't become unselectable
754: * to run.
755: */
756: __clear_task_blocked_on(current, lock);
757: raw_spin_unlock(¤t->blocked_lock);
758: raw_spin_unlock_irqrestore(&lock->wait_lock, flags);
759:
760: trace_contention_begin(lock, LCB_F_MUTEX | LCB_F_SPIN);
761: opt_acquired = mutex_optimistic_spin(lock, ww_ctx, &waiter);
762:
763: raw_spin_lock_irqsave(&lock->wait_lock, flags);
764: raw_spin_lock(¤t->blocked_lock);
765: __set_task_blocked_on(current, lock);
766: set_current_state(state);
767:
768: if (opt_acquired)
769: break;
]
|
| 11/2 |
2026/07/27 00:48 |
llm |
expert |
0m
Model:
Tokens:
input: 10182
output: 41
thoughts: 595
Thoughts: **Analyzing a Potential Data Race in the Mutex Locking Mechanism**
Okay, so I've spotted something interesting in the mutex code, specifically within `__mutex_lock_common` and `__mutex_remove_waiter`. The core of the problem lies in a potential data race. I'm seeing a write to `lock->first_waiter` in `__mutex_remove_waiter` (line 249), and a read from the *same* memory location, `lock->first_waiter`, in `__mutex_lock_common` (line 728). The kicker? The read in `__mutex_lock_common` *isn't* protected by `lock->wait_lock`, while the write in `__mutex_remove_waiter` *is*.
Let's break this down. In `__mutex_lock_common`, the code releases a spinlock, schedules, and *then* checks `lock->first_waiter` (lines 724-730). Meanwhile, `__mutex_remove_waiter` *does* hold that lock when it modifies `lock->first_waiter`. That's the recipe for a race condition.
The question is, is this race harmful? If the `first` variable is incorrectly evaluated—meaning `true` when it should be `false`, or vice-versa—what's the impact? I remember that `first` seems to be an optimization or a *hint* to the code, right? Let's refresh my memory on how `first` is used (lines 744-747).
Okay, right. `first` is used in a conditional to determine whether we engage in optimistic spinning through `__mutex_trylock_or_handoff`. So if `first` is `true`, there's a chance we'll attempt some "optimistic" behavior. If it's `false`, we skip that. I see now that `__mutex_trylock_or_handoff` actually takes a `handoff` parameter, which is essentially the `first` variable.
Now, I need to understand `__mutex_trylock_or_handoff` more deeply. It appears the potential for an incorrect evaluation of `first` *could* lead to wasted CPU cycles in optimistic spinning if `first` is incorrectly true, or missing opportunities for optimization if incorrectly false.
|
| 12/2 |
2026/07/27 00:48 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:kernel/locking/mutex.c Name:__mutex_trylock_or_handoff]
Results: map[SourceCode: 121: /*
122: * Trylock or set HANDOFF
123: */
124: static inline bool __mutex_trylock_or_handoff(struct mutex *lock, bool handoff)
125: {
126: return !__mutex_trylock_common(lock, handoff);
127: }
SourceFile:kernel/locking/mutex.c]
|
| 13/2 |
2026/07/27 00:48 |
llm |
expert |
0m
Model:
Tokens:
input: 10868
output: 38
thoughts: 0
|
| 14/2 |
2026/07/27 00:48 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:kernel/locking/mutex.c Name:__mutex_trylock_common]
Results: map[SourceCode: 82: /*
83: * Returns: __mutex_owner(lock) on failure or NULL on success.
84: */
85: static inline struct task_struct *__mutex_trylock_common(struct mutex *lock, bool handoff)
86: {
87: unsigned long owner, curr = (unsigned long)current;
88:
89: owner = atomic_long_read(&lock->owner);
90: for (;;) { /* must loop, can race against a flag */
91: unsigned long flags = __owner_flags(owner);
92: unsigned long task = owner & ~MUTEX_FLAGS;
93:
94: if (task) {
95: if (flags & MUTEX_FLAG_PICKUP) {
96: if (task != curr)
97: break;
98: flags &= ~MUTEX_FLAG_PICKUP;
99: } else if (handoff) {
100: if (flags & MUTEX_FLAG_HANDOFF)
101: break;
102: flags |= MUTEX_FLAG_HANDOFF;
103: } else {
104: break;
105: }
106: } else {
107: MUTEX_WARN_ON(flags & (MUTEX_FLAG_HANDOFF | MUTEX_FLAG_PICKUP));
108: task = curr;
109: }
110:
111: if (atomic_long_try_cmpxchg_acquire(&lock->owner, &owner, task | flags)) {
112: if (task == curr)
113: return NULL;
114: break;
115: }
116: }
117:
118: return __owner_task(owner);
119: }
SourceFile:kernel/locking/mutex.c]
|
| 15/2 |
2026/07/27 00:48 |
llm |
expert |
0m
Model:
Tokens:
input: 11447
output: 35
thoughts: 0
|
| 16/2 |
2026/07/27 00:48 |
tool |
read-file |
0m
Args: map[File:kernel/locking/mutex.c FirstLine:760 LineCount:50]
Results: map[Contents: 760: trace_contention_begin(lock, LCB_F_MUTEX | LCB_F_SPIN);
761: opt_acquired = mutex_optimistic_spin(lock, ww_ctx, &waiter);
762:
763: raw_spin_lock_irqsave(&lock->wait_lock, flags);
764: raw_spin_lock(¤t->blocked_lock);
765: __set_task_blocked_on(current, lock);
766: set_current_state(state);
767:
768: if (opt_acquired)
769: break;
770: trace_contention_begin(lock, LCB_F_MUTEX);
771: }
772: }
773: __clear_task_blocked_on(current, lock);
774: __set_current_state(TASK_RUNNING);
775: raw_spin_unlock(¤t->blocked_lock);
776:
777: if (ww_ctx) {
778: /*
779: * Wound-Wait; we stole the lock (!first_waiter), check the
780: * waiters as anyone might want to wound us.
781: */
782: if (!ww_ctx->is_wait_die && lock->first_waiter != &waiter)
783: __ww_mutex_check_waiters(lock, ww_ctx, &wake_q);
784: }
785:
786: __mutex_remove_waiter(lock, &waiter);
787:
788: debug_mutex_free_waiter(&waiter);
789:
790: skip_wait:
791: /* got the lock - cleanup and rejoice! */
792: lock_acquired(&lock->dep_map, ip);
793: trace_contention_end(lock, 0);
794:
795: if (ww_ctx)
796: ww_mutex_lock_acquired(ww, ww_ctx);
797:
798: raw_spin_unlock_irqrestore_wake(&lock->wait_lock, flags, &wake_q);
799: preempt_enable();
800: return 0;
801:
802: err:
803: clear_task_blocked_on(current, lock);
804: __set_current_state(TASK_RUNNING);
805: __mutex_remove_waiter(lock, &waiter);
806: err_early_kill:
807: WARN_ON(get_task_blocked_on(current));
808: trace_contention_end(lock, ret);
809: raw_spin_unlock_irqrestore_wake(&lock->wait_lock, flags, &wake_q);
]
|
| 17/2 |
2026/07/27 00:48 |
llm |
expert |
0m
Model:
Tokens:
input: 12301
output: 34
thoughts: 1699
Thoughts: **Analyzing a KCSAN Data Race in Mutex Locking**
Okay, so KCSAN has flagged a data race within the `__mutex_lock` function, specifically around the `lock->first_waiter` pointer. My initial thought is that we're seeing a non-atomic read occurring concurrently with a write operation, which is... concerning. The write side, `__mutex_remove_waiter`, modifies `lock->first_waiter` while holding `lock->wait_lock`. The read side, in `__mutex_lock_common`, reads this same pointer *without* holding that lock. That's immediately suspect.
Diving into the code, I see the problematic line: `first = lock->first_waiter == &waiter;`. The question is, how harmful *is* this race? Since `waiter` is a local variable on the stack, the comparison is essentially checking if `lock->first_waiter` points to the current task's waiter structure. A concurrent write could cause the read to return an inconsistent value. The next line, `__mutex_trylock_or_handoff`, uses the value of `first` as a `handoff` parameter, which if it's incorrect, may lead to missing a 'handoff' situation.
I'm thinking about the possible scenarios now: if the read is "early", we might miss an actual handoff and just spin. If we read an old value of `lock->first_waiter`, we could miss the handoff, and re-loop in the function.
However, since `lock->first_waiter` is a pointer, I need to consider atomicity. On a 64-bit architecture, a read/write of a pointer is likely atomic at the hardware level. The read will either get the "old" pointer or the "new" pointer. So it's unlikely to result in a corrupted address, just a possible incorrect boolean value for `first`. Therefore, the data race would not lead to memory corruption, but it might lead to a missed handoff or a minor performance impact.
I recalled that there's a patch for this. It's likely fixing this with a `READ_ONCE` around the pointer read or some other equivalent mechanism. I should search the git log for a commit that addresses this specific issue and see what the resolution was.
|
| 18/2 |
2026/07/27 00:48 |
tool |
git-log |
10m
Error:
Args: map[CodeRegexp:first = lock->first_waiter == &waiter Count:10]
Results:
|
| 19/2 |
2026/07/27 00:58 |
llm |
expert |
0m
Model:
Tokens:
input: 12876
output: 47
thoughts: 0
|
| 20/2 |
2026/07/27 00:58 |
tool |
git-log |
0m
Args: map[CodeRegexp:first = lock->first_waiter == &waiter Count:10 PathPrefix:kernel/locking/mutex.c]
Results: map[Output:25500ba7e77c locking/mutex: Remove the list_head from struct mutex
]
|
| 21/2 |
2026/07/27 00:58 |
llm |
expert |
0m
Model:
Tokens:
input: 12960
output: 26
thoughts: 0
|
| 22/2 |
2026/07/27 00:58 |
tool |
git-show |
0m
Args:
Results: map[Output:commit 25500ba7e77ce9d3d9b5a1929d41a2ee2e23f6fe
Author: Matthew Wilcox (Oracle) <willy@infradead.org>
Date: Thu Mar 5 19:55:43 2026 +0000
locking/mutex: Remove the list_head from struct mutex
Instead of embedding a list_head in struct mutex, store a pointer to
the first waiter. The list of waiters remains a doubly linked list so
we can efficiently add to the tail of the list, remove from the front
(or middle) of the list.
Some of the list manipulation becomes more complicated, but it's a
reasonable tradeoff on the slow paths to shrink data structures which
embed a mutex like struct file.
Some of the debug checks have to be deleted because there's no equivalent
to checking them in the new scheme (eg an empty waiter->list now means
that it is the only waiter, not that the waiter is no longer on the list).
Signed-off-by: Matthew Wilcox (Oracle) <willy@infradead.org>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Link: https://patch.msgid.link/20260305195545.3707590-4-willy@infradead.org
diff --git a/include/linux/mutex.h b/include/linux/mutex.h
index 2f648ee204e7..c471b129f703 100644
--- a/include/linux/mutex.h
+++ b/include/linux/mutex.h
@@ -79,7 +79,7 @@ do { \
#define __MUTEX_INITIALIZER(lockname) \
{ .owner = ATOMIC_LONG_INIT(0) \
, .wait_lock = __RAW_SPIN_LOCK_UNLOCKED(lockname.wait_lock) \
- , .wait_list = LIST_HEAD_INIT(lockname.wait_list) \
+ , .first_waiter = NULL \
__DEBUG_MUTEX_INITIALIZER(lockname) \
__DEP_MAP_MUTEX_INITIALIZER(lockname) }
diff --git a/include/linux/mutex_types.h b/include/linux/mutex_types.h
index 80975935ec48..a8f119f81177 100644
--- a/include/linux/mutex_types.h
+++ b/include/linux/mutex_types.h
@@ -44,7 +44,7 @@ context_lock_struct(mutex) {
#ifdef CONFIG_MUTEX_SPIN_ON_OWNER
struct optimistic_spin_queue osq; /* Spinner MCS lock */
#endif
- struct list_head wait_list;
+ struct mutex_waiter *first_waiter;
#ifdef CONFIG_DEBUG_MUTEXES
void *magic;
#endif
diff --git a/kernel/locking/mutex-debug.c b/kernel/locking/mutex-debug.c
index 2c6b02d4699b..94930d506bcf 100644
--- a/kernel/locking/mutex-debug.c
+++ b/kernel/locking/mutex-debug.c
@@ -37,9 +37,8 @@ void debug_mutex_lock_common(struct mutex *lock, struct mutex_waiter *waiter)
void debug_mutex_wake_waiter(struct mutex *lock, struct mutex_waiter *waiter)
{
lockdep_assert_held(&lock->wait_lock);
- DEBUG_LOCKS_WARN_ON(list_empty(&lock->wait_list));
+ DEBUG_LOCKS_WARN_ON(!lock->first_waiter);
DEBUG_LOCKS_WARN_ON(waiter->magic != waiter);
- DEBUG_LOCKS_WARN_ON(list_empty(&waiter->list));
}
void debug_mutex_free_waiter(struct mutex_waiter *waiter)
@@ -62,7 +61,6 @@ void debug_mutex_remove_waiter(struct mutex *lock, struct mutex_waiter *waiter,
{
struct mutex *blocked_on = __get_task_blocked_on(task);
- DEBUG_LOCKS_WARN_ON(list_empty(&waiter->list));
DEBUG_LOCKS_WARN_ON(waiter->task != task);
DEBUG_LOCKS_WARN_ON(blocked_on && blocked_on != lock);
@@ -74,7 +72,6 @@ void debug_mutex_unlock(struct mutex *lock)
{
if (likely(debug_locks)) {
DEBUG_LOCKS_WARN_ON(lock->magic != lock);
- DEBUG_LOCKS_WARN_ON(!lock->wait_list.prev && !lock->wait_list.next);
}
}
diff --git a/kernel/locking/mutex.c b/kernel/locking/mutex.c
index c867f6c15530..95f1822122a1 100644
--- a/kernel/locking/mutex.c
+++ b/kernel/locking/mutex.c
@@ -47,7 +47,7 @@ static void __mutex_init_generic(struct mutex *lock)
{
atomic_long_set(&lock->owner, 0);
raw_spin_lock_init(&lock->wait_lock);
- INIT_LIST_HEAD(&lock->wait_list);
+ lock->first_waiter = NULL;
#ifdef CONFIG_MUTEX_SPIN_ON_OWNER
osq_lock_init(&lock->osq);
#endif
@@ -194,33 +194,42 @@ static inline void __mutex_clear_flag(struct mutex *lock, unsigned long flag)
atomic_long_andnot(flag, &lock->owner);
}
-static inline bool __mutex_waiter_is_first(struct mutex *lock, struct mutex_waiter *waiter)
-{
- return list_first_entry(&lock->wait_list, struct mutex_waiter, list) == waiter;
-}
-
/*
* Add @waiter to a given location in the lock wait_list and set the
* FLAG_WAITERS flag if it's the first waiter.
*/
static void
__mutex_add_waiter(struct mutex *lock, struct mutex_waiter *waiter,
- struct list_head *list)
+ struct mutex_waiter *first)
{
hung_task_set_blocker(lock, BLOCKER_TYPE_MUTEX);
debug_mutex_add_waiter(lock, waiter, current);
- list_add_tail(&waiter->list, list);
- if (__mutex_waiter_is_first(lock, waiter))
+ if (!first)
+ first = lock->first_waiter;
+
+ if (first) {
+ list_add_tail(&waiter->list, &first->list);
+ } else {
+ INIT_LIST_HEAD(&waiter->list);
+ lock->first_waiter = waiter;
__mutex_set_flag(lock, MUTEX_FLAG_WAITERS);
+ }
}
static void
__mutex_remove_waiter(struct mutex *lock, struct mutex_waiter *waiter)
{
- list_del(&waiter->list);
- if (likely(list_empty(&lock->wait_list)))
+ if (list_empty(&waiter->list)) {
__mutex_clear_flag(lock, MUTEX_FLAGS);
+ lock->first_waiter = NULL;
+ } else {
+ if (lock->first_waiter == waiter) {
+ lock->first_waiter = list_first_entry(&waiter->list,
+ struct mutex_waiter, list);
+ }
+ list_del(&waiter->list);
+ }
debug_mutex_remove_waiter(lock, waiter, current);
hung_task_clear_blocker();
@@ -340,7 +349,7 @@ bool ww_mutex_spin_on_owner(struct mutex *lock, struct ww_acquire_ctx *ww_ctx,
* Similarly, stop spinning if we are no longer the
* first waiter.
*/
- if (waiter && !__mutex_waiter_is_first(lock, waiter))
+ if (waiter && lock->first_waiter != waiter)
return false;
return true;
@@ -645,7 +654,7 @@ __mutex_lock_common(struct mutex *lock, unsigned int state, unsigned int subclas
if (!use_ww_ctx) {
/* add waiting tasks to the end of the waitqueue (FIFO): */
- __mutex_add_waiter(lock, &waiter, &lock->wait_list);
+ __mutex_add_waiter(lock, &waiter, NULL);
} else {
/*
* Add in stamp order, waking up waiters that must kill
@@ -691,7 +700,7 @@ __mutex_lock_common(struct mutex *lock, unsigned int state, unsigned int subclas
schedule_preempt_disabled();
- first = __mutex_waiter_is_first(lock, &waiter);
+ first = lock->first_waiter == &waiter;
/*
* As we likely have been woken up by task
@@ -734,8 +743,7 @@ __mutex_lock_common(struct mutex *lock, unsigned int state, unsigned int subclas
* Wound-Wait; we stole the lock (!first_waiter), check the
* waiters as anyone might want to wound us.
*/
- if (!ww_ctx->is_wait_die &&
- !__mutex_waiter_is_first(lock, &waiter))
+ if (!ww_ctx->is_wait_die && lock->first_waiter != &waiter)
__ww_mutex_check_waiters(lock, ww_ctx, &wake_q);
}
@@ -931,6 +939,7 @@ EXPORT_SYMBOL_GPL(ww_mutex_lock_interruptible);
static noinline void __sched __mutex_unlock_slowpath(struct mutex *lock, unsigned long ip)
{
struct task_struct *next = NULL;
+ struct mutex_waiter *waiter;
DEFINE_WAKE_Q(wake_q);
unsigned long owner;
unsigned long flags;
@@ -962,12 +971,8 @@ static noinline void __sched __mutex_unlock_slowpath(struct mutex *lock, unsigne
raw_spin_lock_irqsave(&lock->wait_lock, flags);
debug_mutex_unlock(lock);
- if (!list_empty(&lock->wait_list)) {
- /* get the first entry from the wait-list: */
- struct mutex_waiter *waiter =
- list_first_entry(&lock->wait_list,
- struct mutex_waiter, list);
-
+ waiter = lock->first_waiter;
+ if (waiter) {
next = waiter->task;
debug_mutex_wake_waiter(lock, waiter);
diff --git a/kernel/locking/ww_mutex.h b/kernel/locking/ww_mutex.h
index 31a785afee6c..a0847e91ae04 100644
--- a/kernel/locking/ww_mutex.h
+++ b/kernel/locking/ww_mutex.h
@@ -8,20 +8,14 @@
static inline struct mutex_waiter *
__ww_waiter_first(struct mutex *lock)
{
- struct mutex_waiter *w;
-
- w = list_first_entry(&lock->wait_list, struct mutex_waiter, list);
- if (list_entry_is_head(w, &lock->wait_list, list))
- return NULL;
-
- return w;
+ return lock->first_waiter;
}
static inline struct mutex_waiter *
__ww_waiter_next(struct mutex *lock, struct mutex_waiter *w)
{
w = list_next_entry(w, list);
- if (list_entry_is_head(w, &lock->wait_list, list))
+ if (lock->first_waiter == w)
return NULL;
return w;
@@ -31,7 +25,7 @@ static inline struct mutex_waiter *
__ww_waiter_prev(struct mutex *lock, struct mutex_waiter *w)
{
w = list_prev_entry(w, list);
- if (list_entry_is_head(w, &lock->wait_list, list))
+ if (lock->first_waiter == w)
return NULL;
return w;
@@ -40,22 +34,17 @@ __ww_waiter_prev(struct mutex *lock, struct mutex_waiter *w)
static inline struct mutex_waiter *
__ww_waiter_last(struct mutex *lock)
{
- struct mutex_waiter *w;
-
- w = list_last_entry(&lock->wait_list, struct mutex_waiter, list);
- if (list_entry_is_head(w, &lock->wait_list, list))
- return NULL;
+ struct mutex_waiter *w = lock->first_waiter;
+ if (w)
+ w = list_prev_entry(w, list);
return w;
}
static inline void
__ww_waiter_add(struct mutex *lock, struct mutex_waiter *waiter, struct mutex_waiter *pos)
{
- struct list_head *p = &lock->wait_list;
- if (pos)
- p = &pos->list;
- __mutex_add_waiter(lock, waiter, p);
+ __mutex_add_waiter(lock, waiter, pos);
}
static inline struct task_struct *
]
|
| 23/2 |
2026/07/27 00:58 |
llm |
expert |
0m
Model:
Tokens:
input: 16615
output: 41
thoughts: 0
|
| 24/2 |
2026/07/27 00:58 |
tool |
git-log |
10m
Error:
Args: map[CodeRegexp:lock->first_waiter = list_next_entry\(waiter, list\); Count:10]
Results:
|
| 25/2 |
2026/07/27 01:08 |
llm |
expert |
0m
Model:
Tokens:
input: 16691
output: 54
thoughts: 0
|
| 26/2 |
2026/07/27 01:08 |
tool |
git-log |
0m
Args: map[CodeRegexp:lock->first_waiter = list_next_entry\(waiter, list\); Count:10 PathPrefix:kernel/locking/mutex.c]
Results: map[Output:0adc92b910b3 locking/mutex: Fix ww_mutex wait_list operations
]
|
| 27/2 |
2026/07/27 01:08 |
llm |
expert |
0m
Model:
Tokens:
input: 16781
output: 25
thoughts: 0
|
| 28/2 |
2026/07/27 01:09 |
tool |
git-show |
0m
Args:
Results: map[Output:commit 0adc92b910b3d6bf4913d79869365d553154a070
Author: Peter Zijlstra <peterz@infradead.org>
Date: Wed Apr 22 10:38:41 2026 +0200
locking/mutex: Fix ww_mutex wait_list operations
Chaitanya, John and Mikhail reported commit 25500ba7e77c ("locking/mutex:
Remove the list_head from struct mutex") wrecked ww_mutex.
Specifically there were 2 issues:
- __ww_waiter_prev() had the termination condition wrong; it would terminate
when the previous entry was the first, which results in a truncated
iteration: W3, W2, (no W1).
- __mutex_add_waiter(@pos != NULL), as used by __ww_waiter_add() /
__ww_mutex_add_waiter(); this inserts @waiter before @pos (which is what
list_add_tail() does). But this should then also update lock->first_waiter.
Much thanks to Prateek for spotting the __mutex_add_waiter() issue!
Fixes: 25500ba7e77c ("locking/mutex: Remove the list_head from struct mutex")
Reported-by: "Borah, Chaitanya Kumar" <chaitanya.kumar.borah@intel.com>
Closes: https://lore.kernel.org/r/af005996-05e9-4336-8450-d14ca652ba5d%40intel.com
Reported-by: John Stultz <jstultz@google.com>
Closes: https://lore.kernel.org/r/CANDhNCq%3Doizzud3hH3oqGzTrcjB8OwGeineJ3mwZuGdDWG8fRQ%40mail.gmail.com
Reported-by: Mikhail Gavrilov <mikhail.v.gavrilov@gmail.com>
Closes: https://lore.kernel.org/r/CABXGCsO5fKq2nD9nO8yO1z50ZzgCPWqueNXHANjntaswoOh2Dg@mail.gmail.com
Debugged-by: K Prateek Nayak <kprateek.nayak@amd.com>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Tested-by: K Prateek Nayak <kprateek.nayak@amd.com>
Tested-by: Mikhail Gavrilov <mikhail.v.gavrilov@gmail.com>
Link: https://patch.msgid.link/20260422092335.GH3102924%40noisy.programming.kicks-ass.net
diff --git a/kernel/locking/mutex.c b/kernel/locking/mutex.c
index 186b463fe326..09534628dc01 100644
--- a/kernel/locking/mutex.c
+++ b/kernel/locking/mutex.c
@@ -198,27 +198,43 @@ static inline void __mutex_clear_flag(struct mutex *lock, unsigned long flag)
}
/*
- * Add @waiter to a given location in the lock wait_list and set the
- * FLAG_WAITERS flag if it's the first waiter.
+ * Add @waiter to the @lock wait_list and set the FLAG_WAITERS flag if it's
+ * the first waiter.
+ *
+ * When @pos, @waiter is added before the waiter indicated by @pos. Otherwise
+ * @waiter will be added to the tail of the list.
*/
static void
__mutex_add_waiter(struct mutex *lock, struct mutex_waiter *waiter,
- struct mutex_waiter *first)
+ struct mutex_waiter *pos)
__must_hold(&lock->wait_lock)
{
+ struct mutex_waiter *first = lock->first_waiter;
+
hung_task_set_blocker(lock, BLOCKER_TYPE_MUTEX);
debug_mutex_add_waiter(lock, waiter, current);
- if (!first)
- first = lock->first_waiter;
+ if (pos) {
+ /*
+ * Insert @waiter before @pos.
+ */
+ list_add_tail(&waiter->list, &pos->list);
+ /*
+ * If @pos == @first, then @waiter will be the new first.
+ */
+ if (pos == first)
+ lock->first_waiter = waiter;
+ return;
+ }
if (first) {
list_add_tail(&waiter->list, &first->list);
- } else {
- INIT_LIST_HEAD(&waiter->list);
- lock->first_waiter = waiter;
- __mutex_set_flag(lock, MUTEX_FLAG_WAITERS);
+ return;
}
+
+ INIT_LIST_HEAD(&waiter->list);
+ lock->first_waiter = waiter;
+ __mutex_set_flag(lock, MUTEX_FLAG_WAITERS);
}
static void
@@ -229,10 +245,8 @@ __mutex_remove_waiter(struct mutex *lock, struct mutex_waiter *waiter)
__mutex_clear_flag(lock, MUTEX_FLAGS);
lock->first_waiter = NULL;
} else {
- if (lock->first_waiter == waiter) {
- lock->first_waiter = list_first_entry(&waiter->list,
- struct mutex_waiter, list);
- }
+ if (lock->first_waiter == waiter)
+ lock->first_waiter = list_next_entry(waiter, list);
list_del(&waiter->list);
}
diff --git a/kernel/locking/ww_mutex.h b/kernel/locking/ww_mutex.h
index 016f0db892a5..6c12452097e1 100644
--- a/kernel/locking/ww_mutex.h
+++ b/kernel/locking/ww_mutex.h
@@ -6,6 +6,19 @@
#define MUTEX_WAITER mutex_waiter
#define WAIT_LOCK wait_lock
+/*
+ * +--------+
+ * | first |
+ * +--------+
+ * |
+ * v
+ * +----+ +----+ +----+
+ * | W3 | <-> | W1 | <-> | W2 |
+ * +----+ +----+ +----+
+ * ^ ^
+ * +---------------------+
+ */
+
static inline struct mutex_waiter *
__ww_waiter_first(struct mutex *lock)
__must_hold(&lock->wait_lock)
@@ -13,26 +26,43 @@ __ww_waiter_first(struct mutex *lock)
return lock->first_waiter;
}
+/*
+ * for (cur = __ww_waiter_first(); cur; cur = __ww_waiter_next())
+ *
+ * Should iterate like: W1, W2, W3
+ */
static inline struct mutex_waiter *
__ww_waiter_next(struct mutex *lock, struct mutex_waiter *w)
__must_hold(&lock->wait_lock)
{
w = list_next_entry(w, list);
+ /*
+ * Terminate if the next entry is the first again, that has already
+ * been observed.
+ */
if (lock->first_waiter == w)
return NULL;
return w;
}
+/*
+ * for (cur = __ww_waiter_last(); cur; cur = __ww_waiter_prev())
+ *
+ * Should iterate like: W3, W2, W1
+ */
static inline struct mutex_waiter *
__ww_waiter_prev(struct mutex *lock, struct mutex_waiter *w)
__must_hold(&lock->wait_lock)
{
- w = list_prev_entry(w, list);
+ /*
+ * Terminate at the first entry, the previous entry of first is the
+ * last and that has already been observed.
+ */
if (lock->first_waiter == w)
return NULL;
- return w;
+ return list_prev_entry(w, list);
}
static inline struct mutex_waiter *
]
|
| 29/2 |
2026/07/27 01:09 |
llm |
expert |
0m
Model:
Tokens:
input: 19045
output: 54
thoughts: 0
|
| 30/2 |
2026/07/27 01:09 |
tool |
git-log |
0m
Args: map[CodeRegexp:lock->first_waiter = list_next_entry\(waiter, list\); Count:10 PathPrefix:kernel/locking/mutex.c]
Results: map[Output:0adc92b910b3 locking/mutex: Fix ww_mutex wait_list operations
]
|
| 31/2 |
2026/07/27 01:09 |
llm |
expert |
0m
Model:
Tokens:
input: 19135
output: 42
thoughts: 0
|
| 32/2 |
2026/07/27 01:09 |
tool |
git-log |
0m
Args: map[CodeRegexp:__mutex_remove_waiter Count:10 PathPrefix:kernel/locking/mutex.c]
Results: map[Output:3a010c493271 locking/mutex: clear MUTEX_FLAGS if wait_list is empty due to signal
]
|
| 33/2 |
2026/07/27 01:09 |
llm |
expert |
0m
Model:
Tokens:
input: 19221
output: 27
thoughts: 0
|
| 34/2 |
2026/07/27 01:09 |
tool |
git-show |
0m
Args:
Results: map[Output:commit 3a010c493271f04578b133de977e0e5dd2848cea
Author: Zqiang <qiang.zhang@windriver.com>
Date: Mon May 17 11:40:05 2021 +0800
locking/mutex: clear MUTEX_FLAGS if wait_list is empty due to signal
When a interruptible mutex locker is interrupted by a signal
without acquiring this lock and removed from the wait queue.
if the mutex isn't contended enough to have a waiter
put into the wait queue again, the setting of the WAITER
bit will force mutex locker to go into the slowpath to
acquire the lock every time, so if the wait queue is empty,
the WAITER bit need to be clear.
Fixes: 040a0a371005 ("mutex: Add support for wound/wait style locks")
Suggested-by: Peter Zijlstra <peterz@infradead.org>
Signed-off-by: Zqiang <qiang.zhang@windriver.com>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Link: https://lkml.kernel.org/r/20210517034005.30828-1-qiang.zhang@windriver.com
diff --git a/kernel/locking/mutex-debug.c b/kernel/locking/mutex-debug.c
index a7276aaf2abc..db9301591e3f 100644
--- a/kernel/locking/mutex-debug.c
+++ b/kernel/locking/mutex-debug.c
@@ -57,7 +57,7 @@ void debug_mutex_add_waiter(struct mutex *lock, struct mutex_waiter *waiter,
task->blocked_on = waiter;
}
-void mutex_remove_waiter(struct mutex *lock, struct mutex_waiter *waiter,
+void debug_mutex_remove_waiter(struct mutex *lock, struct mutex_waiter *waiter,
struct task_struct *task)
{
DEBUG_LOCKS_WARN_ON(list_empty(&waiter->list));
@@ -65,7 +65,7 @@ void mutex_remove_waiter(struct mutex *lock, struct mutex_waiter *waiter,
DEBUG_LOCKS_WARN_ON(task->blocked_on != waiter);
task->blocked_on = NULL;
- list_del_init(&waiter->list);
+ INIT_LIST_HEAD(&waiter->list);
waiter->task = NULL;
}
diff --git a/kernel/locking/mutex-debug.h b/kernel/locking/mutex-debug.h
index 1edd3f45a4ec..53e631e1d76d 100644
--- a/kernel/locking/mutex-debug.h
+++ b/kernel/locking/mutex-debug.h
@@ -22,7 +22,7 @@ extern void debug_mutex_free_waiter(struct mutex_waiter *waiter);
extern void debug_mutex_add_waiter(struct mutex *lock,
struct mutex_waiter *waiter,
struct task_struct *task);
-extern void mutex_remove_waiter(struct mutex *lock, struct mutex_waiter *waiter,
+extern void debug_mutex_remove_waiter(struct mutex *lock, struct mutex_waiter *waiter,
struct task_struct *task);
extern void debug_mutex_unlock(struct mutex *lock);
extern void debug_mutex_init(struct mutex *lock, const char *name,
diff --git a/kernel/locking/mutex.c b/kernel/locking/mutex.c
index cb6b112ce155..013e1b08a1bf 100644
--- a/kernel/locking/mutex.c
+++ b/kernel/locking/mutex.c
@@ -194,7 +194,7 @@ static inline bool __mutex_waiter_is_first(struct mutex *lock, struct mutex_wait
* Add @waiter to a given location in the lock wait_list and set the
* FLAG_WAITERS flag if it's the first waiter.
*/
-static void __sched
+static void
__mutex_add_waiter(struct mutex *lock, struct mutex_waiter *waiter,
struct list_head *list)
{
@@ -205,6 +205,16 @@ __mutex_add_waiter(struct mutex *lock, struct mutex_waiter *waiter,
__mutex_set_flag(lock, MUTEX_FLAG_WAITERS);
}
+static void
+__mutex_remove_waiter(struct mutex *lock, struct mutex_waiter *waiter)
+{
+ list_del(&waiter->list);
+ if (likely(list_empty(&lock->wait_list)))
+ __mutex_clear_flag(lock, MUTEX_FLAGS);
+
+ debug_mutex_remove_waiter(lock, waiter, current);
+}
+
/*
* Give up ownership to a specific task, when @task = NULL, this is equivalent
* to a regular unlock. Sets PICKUP on a handoff, clears HANDOFF, preserves
@@ -1061,9 +1071,7 @@ __mutex_lock_common(struct mutex *lock, long state, unsigned int subclass,
__ww_mutex_check_waiters(lock, ww_ctx);
}
- mutex_remove_waiter(lock, &waiter, current);
- if (likely(list_empty(&lock->wait_list)))
- __mutex_clear_flag(lock, MUTEX_FLAGS);
+ __mutex_remove_waiter(lock, &waiter);
debug_mutex_free_waiter(&waiter);
@@ -1080,7 +1088,7 @@ __mutex_lock_common(struct mutex *lock, long state, unsigned int subclass,
err:
__set_current_state(TASK_RUNNING);
- mutex_remove_waiter(lock, &waiter, current);
+ __mutex_remove_waiter(lock, &waiter);
err_early_kill:
spin_unlock(&lock->wait_lock);
debug_mutex_free_waiter(&waiter);
diff --git a/kernel/locking/mutex.h b/kernel/locking/mutex.h
index 1c2287d3fa71..f0c710b1d192 100644
--- a/kernel/locking/mutex.h
+++ b/kernel/locking/mutex.h
@@ -10,12 +10,10 @@
* !CONFIG_DEBUG_MUTEXES case. Most of them are NOPs:
*/
-#define mutex_remove_waiter(lock, waiter, task) \
- __list_del((waiter)->list.prev, (waiter)->list.next)
-
#define debug_mutex_wake_waiter(lock, waiter) do { } while (0)
#define debug_mutex_free_waiter(waiter) do { } while (0)
#define debug_mutex_add_waiter(lock, waiter, ti) do { } while (0)
+#define debug_mutex_remove_waiter(lock, waiter, ti) do { } while (0)
#define debug_mutex_unlock(lock) do { } while (0)
#define debug_mutex_init(lock, name, key) do { } while (0)
]
|
| 35/2 |
2026/07/27 01:09 |
llm |
expert |
0m
Model:
Tokens:
input: 21171
output: 35
thoughts: 0
|
| 36/2 |
2026/07/27 01:09 |
tool |
read-file |
0m
Args: map[File:kernel/locking/mutex.c FirstLine:240 LineCount:20]
Results: map[Contents: 240: static void
241: __mutex_remove_waiter(struct mutex *lock, struct mutex_waiter *waiter)
242: __must_hold(&lock->wait_lock)
243: {
244: if (list_empty(&waiter->list)) {
245: __mutex_clear_flag(lock, MUTEX_FLAGS);
246: lock->first_waiter = NULL;
247: } else {
248: if (lock->first_waiter == waiter)
249: lock->first_waiter = list_next_entry(waiter, list);
250: list_del(&waiter->list);
251: }
252:
253: debug_mutex_remove_waiter(lock, waiter, current);
254: hung_task_clear_blocker();
255: }
256:
257: /*
258: * Give up ownership to a specific task, when @task = NULL, this is equivalent
259: * to a regular unlock. Sets PICKUP on a handoff, clears HANDOFF, preserves
]
|
| 37/2 |
2026/07/27 01:09 |
llm |
expert |
0m
Model:
Tokens:
input: 21535
output: 54
thoughts: 0
|
| 38/2 |
2026/07/27 01:09 |
tool |
git-log |
0m
Args: map[CodeRegexp:lock->first_waiter = list_next_entry\(waiter, list\); Count:10 PathPrefix:kernel/locking/mutex.c]
Results: map[Output:0adc92b910b3 locking/mutex: Fix ww_mutex wait_list operations
]
|
| 39/2 |
2026/07/27 01:09 |
llm |
expert |
0m
Model:
Tokens:
input: 21625
output: 41
thoughts: 0
|
| 40/2 |
2026/07/27 01:09 |
tool |
git-log |
10m
Error:
Args: map[CodeRegexp:lock->first_waiter = list_next_entry\(waiter, list\); Count:10]
Results:
|
| 41/2 |
2026/07/27 01:19 |
llm |
expert |
0m
Model:
Tokens:
input: 21701
output: 54
thoughts: 0
|
| 42/2 |
2026/07/27 01:19 |
tool |
git-log |
0m
Error:
Args: map[CodeRegexp:lock->first_waiter = list_next_entry\(waiter, list\); Count:10 PathPrefix:kernel/locking/mutex.c]
|
| 43/2 |
2026/07/27 01:19 |
llm |
expert |
0m
Model:
Tokens:
input: 21889
output: 62
thoughts: 0
|
| 44/2 |
2026/07/27 01:19 |
tool |
git-log |
0m
Args: map[CodeRegexp:lock->first_waiter = list_next_entry\(waiter, list\); Count:10 PathPrefix:kernel/locking/mutex.c Since:1 year]
Results: map[Output:0adc92b910b3 locking/mutex: Fix ww_mutex wait_list operations
]
|
| 45/2 |
2026/07/27 01:19 |
llm |
expert |
0m
Model:
Tokens:
input: 24998
output: 54
thoughts: 2366
Thoughts: **Benign Data Race in Mutex Locking: A Deep Dive**
Okay, so I'm looking at this KCSAN report. It flags a data race between reading and writing `lock->first_waiter` in the mutex locking code, specifically around the `__mutex_lock` function. The race is between the assignment `lock->first_waiter = list_next_entry(waiter, list);` and the check `first = lock->first_waiter == &waiter;`. My initial thought is, "Ah, another benign race". But, let's not be too hasty.
Let me think this through methodically. The concern is if a torn read of the pointer could lead to incorrect behavior. The system is x86_64 and pointer reads are atomic, so a torn read is not possible. However, there are two possibilities for reading the pointer: the old pointer value or the new pointer value. Let's trace it, starting with an incorrect evaluation of `first`.
If `first` is evaluated as true when it shouldn't be, this is the worst-case scenario. However, the logic for `first` is only true when `lock->first_waiter` points to our local stack variable `&waiter`. This is only true when *we* are the first waiter. Therefore, it is impossible for `first` to be incorrectly set to `true`.
Now, if `first` is falsely evaluated to `false`, we potentially skip some optimizations. Specifically, if `first` is falsely `false`, we won't set `MUTEX_FLAG_HANDOFF`. We also skip optimistic spinning. Then we go back to sleep. However, this is okay because we will be woken up if the mutex is unlocked. The next time we acquire the mutex, we will correctly determine the state.
The core of the problem here is a race condition. One task might be removing itself from the wait queue, and another task is trying to determine if it is the first waiter. If the task removing itself updates `lock->first_waiter` to point to the next waiter, then the task trying to determine if it is the first waiter could read the old pointer, evaluate `first` as false, and then go back to sleep.
So, it's not exploitable. It doesn't cause memory corruption or use-after-free, nor can it lead to any denial-of-service. The worst-case scenario is a possible one extra trip through the scheduling loop (e.g. going to sleep again). We are still eventually woken up, and the lock is eventually acquired. It's a race, but a benign one in the heart of the kernel's mutex locking code.
Now, because it's in a code path reachable via mutex contention, this can be triggered through any process, even unprivileged ones (e.g., `tty_write`), user namespaces, or any other trigger. In this case, I'd have to say "Yes" to all triggers. Since it's a code path with a potential for concurrent access, every trigger is a path that could theoretically result in the code running. Although, in practice, it is a benign race condition.
I need to confirm if there is an existing fix for this.
|
| 46/2 |
2026/07/27 01:19 |
tool |
git-log |
0m
Args: map[CodeRegexp:first = lock->first_waiter == &waiter Count:10 PathPrefix:kernel/locking/mutex.c Since:5 years]
Results: map[Output:25500ba7e77c locking/mutex: Remove the list_head from struct mutex
]
|
| 47/2 |
2026/07/27 01:19 |
llm |
expert |
0m
Model:
Tokens:
input: 28151
output: 60
thoughts: 42
Thoughts: Okay, let me think this through.
**Recalling Previous Investigations**
Hmm, I could have sworn I looked into this specific issue before. Wait a minute... yes, I did! That patch, `25500ba7e77c`, that was the one that brought in this problematic code back in 2020. I remember the commit hash now. That means I probably already have a paper trail. I should make sure I review all my prior notes, and possibly relevant issues.
The immediate question then is, has there been a subsequent patch that addresses the underlying data race? I need to focus my search on fixes, not just the original introduction. I need to get back to investigating.
|
| 48/2 |
2026/07/27 01:19 |
tool |
git-log |
0m
Args: map[CodeRegexp:first = READ_ONCE\(lock->first_waiter\) == &waiter Count:10 PathPrefix:kernel/locking/mutex.c Since:5 years]
Results:
|
| 49/2 |
2026/07/27 01:19 |
llm |
expert |
0m
Model:
Tokens:
input: 28416
output: 59
thoughts: 0
|
| 50/2 |
2026/07/27 01:20 |
tool |
git-log |
0m
Args: map[CodeRegexp:first = data_race\(lock->first_waiter == &waiter\) Count:10 PathPrefix:kernel/locking/mutex.c Since:5 years]
Results:
|
| 51/2 |
2026/07/27 01:20 |
llm |
expert |
0m
Model:
Tokens:
input: 28488
output: 53
thoughts: 0
|
| 52/2 |
2026/07/27 01:20 |
tool |
git-log |
0m
Args: map[CodeRegexp:first = __mutex_waiter_is_first Count:10 PathPrefix:kernel/locking/mutex.c Since:5 years]
Results: map[Output:25500ba7e77c locking/mutex: Remove the list_head from struct mutex
]
|
| 53/2 |
2026/07/27 01:20 |
llm |
expert |
0m
Model:
Tokens:
input: 28578
output: 45
thoughts: 51
Thoughts: Here's my take on those thoughts:
**Analyzing a Kernel Mutex Change**
Okay, so `25500ba7e77c` got rid of `__mutex_waiter_is_first`. Interesting. That means they've changed how the mutex locking logic handles the "first waiter" situation, likely to optimize or simplify something. My immediate next step is to dig into `kernel/locking/mutex.c` and see how this specific variable's removal impacts the surrounding code. I need to find all instances where `first =` was used around the time of the commit, to understand what that `first` variable was doing in context. This search will help me understand the implications of the change to `__mutex_waiter_is_first` for the overall mutex implementation and any potential performance implications. I'm going to look for recent commits, say in the last few months to focus my investigation.
|
| 54/2 |
2026/07/27 01:20 |
tool |
git-log |
0m
Args: map[CodeRegexp:first = Count:10 PathPrefix:kernel/locking/mutex.c Since:3 years]
Results: map[Output:0adc92b910b3 locking/mutex: Fix ww_mutex wait_list operations
25500ba7e77c locking/mutex: Remove the list_head from struct mutex
]
|
| 55/2 |
2026/07/27 01:20 |
llm |
expert |
0m
Model:
Tokens:
input: 28931
output: 31
thoughts: 37
Thoughts: Okay, here's my interpretation of those thoughts, crafted with the expectation that I'm speaking to a fellow expert:
**Investigating a Potential Data Race Fix**
Hmm, hold on a second. That hash, `0adc92b910b3`, that's from 2020, isn't it? Right, I remember that project now. I need to make sure we fixed a potential data race that the system was showing.
Let me quickly dig into the commit history. I need to check whether there was a specific commit addressing this particular data race reported or a related issue. I'll need to use git to pull the commit history and then assess whether there was a fix or a workaround. I'm going to scan the commit messages for keywords and then look at the code diffs for potential fixes, or at least changes in memory access that could mitigate the problem. The most likely commit would have keywords like "data race," "concurrency," "mutex," or perhaps specific synchronization primitives we use in that codebase. Let's see what we can find. I hope there's a fix and I don't have to spend a lot of time re-examining the code!
|
| 56/2 |
2026/07/27 01:20 |
tool |
git-log |
0m
Args: map[MessageRegexps:[data-race mutex] Since:3 years]
Results: map[Output:81f44409fb4f RDMA/irdma: Fix data race in irdma_free_pble
44feafbaa66e usb: using mutex lock and supporting O_NONBLOCK flag in iowarrior_read()
2e7ef287f07c ipv6: mcast: fix data-race in ipv6_mc_down / mld_ifc_work
]
|
| 57/2 |
2026/07/27 01:20 |
llm |
expert |
0m
Model:
Tokens:
input: 29363
output: 66
thoughts: 537
Thoughts: **Analyzing the Data Race in `__mutex_lock_common`**
Okay, so there's no actual fix for this data race in `__mutex_lock_common`. It's a known, and more importantly, a *benign* data race. Since it's benign, it's not exploitable, and it doesn't lead to any Denial of Service (DoS) issues. The triggers all seem to be valid because mutexes are used throughout the system.
Let's quickly run through the questions again to be absolutely sure:
* Exploitable: **False**
* DenialOfService: **False**
* Unprivileged: **True**
* UserNamespace: **True**
* VMGuestTrigger: **True**
* VMHostTrigger: **True**
* NetworkTrigger: **True**
* RemoteTrigger: **True**
* PeripheralTrigger: **True**
* FilesystemTrigger: **True**
Now, I need to remind myself what the prompt said regarding memory safety. It mentioned that memory safety issues are usually exploitable, and that some KCSAN data-races could be exploitable by skilled attackers, but then it also highlighted that the kernel contains a lot of "benign" data races that don't cause any runtime misbehavior. That description fits this situation perfectly.
I'll do a quick check to see if there's any other way this could be harmful though. I'm focusing on the code snippet:
```c
first = lock->first_waiter == &waiter;
```
I need to make sure that a torn pointer for `lock->first_waiter` wouldn't cause any issues. On 64-bit x86, 8-byte aligned reads are atomic. So, is `lock->first_waiter` guaranteed to be 8-byte aligned?
Looking at the `mutex` structure:
```c
struct mutex {
atomic_long_t owner;
raw_spinlock_t wait_lock;
#ifdef CONFIG_MUTEX_SPIN_ON_OWNER
struct optimistic_spin_queue osq; /* Spinner MCS lock */
#endif
struct mutex_waiter *first_waiter;
...
```
`atomic_long_t` is 8 bytes, `raw_spinlock_t` is 4 or 8 bytes depending on the configuration and `osq` is 4 bytes. Therefore, `first_waiter` will be 8-byte aligned. Even if it *weren't* aligned, on x86-64, unaligned reads within a cache line are atomic anyway. So, no torn pointer.
Even if we *hypothetically* had a torn pointer, the comparison would just fail to match `&waiter`. Therefore, it's entirely benign.
I'm now confident in setting the results.
|
| 58/2 |
2026/07/27 01:20 |
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]
|
| 59/2 |
2026/07/27 01:20 |
llm |
expert |
0m
Model:
Tokens:
input: 30668
output: 512
thoughts: 383
Thoughts: **My Analysis of the KCSAN Data-Race Report**
Okay, the results are in, and now it's time to craft the final response. This KCSAN report highlights a data-race within the core mutex locking code, specifically around `__mutex_lock_common` and `__mutex_remove_waiter`. The core of the issue boils down to a task reading `lock->first_waiter` without holding the `wait_lock` while simultaneously another task is updating it under the protection of the `wait_lock`.
What's really happening? A read operation for the `first_waiter` pointer, which checks if it's the first waiter (`first = lock->first_waiter == &waiter;`). The race condition comes into play because another task can update `lock->first_waiter` while holding the `wait_lock` (for example, while removing a waiter).
However, I'm not overly concerned. We're dealing with a benign data race. The read itself is atomic on 64-bit architectures, just a single load instruction. This means it'll read either the old or the new pointer. Since the read compares the pointer against the address of a local stack variable (`&waiter`), any concurrent update can only cause a false negative – the task might incorrectly conclude it *isn't* the first waiter, missing its chance at early spin. It can't evaluate as a true positive and have incorrect behavior.
In other words, the worst-case scenario is that the task skips the optimization (optimistic spinning) and goes to sleep. But, importantly, it will be woken up by the lock owner when the lock is released. There's no way this causes memory corruption, out-of-bounds access, or a deadlock, because `&waiter` is a unique local stack address for each task, preventing any incorrect evaluation of `first`.
I'm confident in saying this data race isn't exploitable. It doesn't present a security vulnerability or a denial-of-service risk. And with mutexes being a fundamental primitive used throughout the kernel, it’s not surprising that this code path can be reached from almost anywhere, from unprivileged system calls like `tty_write`, to networking, filesystem activity, and device interaction. I'll summarize it as such.
|
| 60/1 |
2026/07/27 01:20 |
action |
format-explanation |
0m
Results: map[Explanation:The reported bug is a KCSAN data-race in the core mutex locking code,
specifically between `__mutex_lock_common` and `__mutex_remove_waiter`.
**Analysis:**
The race occurs when a waiting task reads `lock->first_waiter` without holding
the `wait_lock` to check if it is currently the first waiter in the queue
(`first = lock->first_waiter == &waiter;`). Concurrently, another task might be
updating `lock->first_waiter` while holding the `wait_lock` (for example, when a
waiter is removed due to a signal or timeout).
This is a classic example of a benign data race in the kernel.
1. The read of the pointer is a single load instruction, which is atomic on
64-bit architectures (and generally on 32-bit as well, given natural alignment).
It will read either the old pointer value or the new one, but not a torn
pointer.
2. The read value is only compared against `&waiter`, which is the address of a
local variable on the task's own stack.
3. If the task reads an old value and incorrectly evaluates `first` to `false`
(missing the fact that it just became the first waiter), it will simply skip the
optimistic spinning optimization and go to sleep. This is safe because the lock
owner will still correctly wake it up when the lock is released.
4. It is impossible for the task to incorrectly evaluate `first` to `true`,
because no other task's waiter can have the address of this task's local stack
variable.
**Security Impact:**
Because this data race does not lead to memory corruption, invalid pointer
dereferences, deadlocks, or any violation of the mutex semantics, it is **not
exploitable** and does **not cause a Denial of Service (DoS)**. KCSAN flags it
simply because it involves a plain C read and write without
`READ_ONCE()`/`WRITE_ONCE()` annotations.
**Reachability:**
Mutexes are a fundamental synchronization primitive used everywhere in the Linux
kernel. The specific stack trace shows it being triggered via `tty_write`, which
is accessible to unprivileged users. Similarly, because mutexes are used in
network stacks, filesystems, and device drivers, this code path is reachable via
network traffic, malicious filesystems, peripheral devices, and from within VM
guests/hosts.]
|