| 0/0 |
2026/08/14 01:07 |
flow |
assessment-security |
40m
Results: map[DenialOfService:false Explanation:An analysis of the bug report and the corresponding kernel source code reveals
the following:
**Bug Mechanism:**
The bug is a data race detected by the Kernel Concurrency Sanitizer (KCSAN) in
the FAT filesystem driver.
- **Thread 1 (CPU 1)** is executing `fat32_ent_put`, which performs a 4-byte
write to update a specific FAT entry within a cached buffer head block. This
happens during `fat_chain_add` when a file is being appended or allocated new
clusters (e.g., via `fallocate`).
- **Thread 2 (CPU 0)** is concurrently executing `fat_mirror_bhs`, which uses
`memcpy` to copy the entire buffer head block (e.g., 2048 bytes) to a backup FAT
block. This happens after another file operation has modified the FAT and is now
syncing the changes to the backup FAT.
Because `fat_chain_add` modifies the file's last FAT entry without holding the
global FAT lock (it relies on the file's `inode_lock`), it can write to the
buffer head at the exact same time another thread is copying that same buffer
head to the backup FAT.
**Security Impact:**
This data race is **benign** and **not exploitable**.
When Thread 2's `memcpy` reads the buffer head, it might read a torn or stale
4-byte write from Thread 1. However, Thread 1's operation (`fat_ent_write`) is
structured such that immediately after calling `fat32_ent_put`, it will also
call `fat_mirror_bhs` to copy the updated buffer head to the backup FAT.
Consequently, even if Thread 2 writes an inconsistent state to the backup FAT,
Thread 1 will immediately overwrite it with the correct, fully updated block.
There are no memory safety violations (like out-of-bounds accesses or
use-after-frees), no control-flow hijacks, and no kernel panics or deadlocks
resulting from this race. The primary FAT remains perfectly consistent, and the
backup FAT achieves eventual consistency.
**Trigger Vectors:**
The bug can be triggered by any unprivileged user who has write access to a
mounted FAT filesystem (e.g., by calling `fallocate` or `write` concurrently
from multiple threads). This makes it reachable via physically plugged-in
peripheral devices (like USB flash drives formatted with FAT32) or by mounting a
malicious/crafted FAT filesystem image. Exploitable:false FilesystemTrigger:true NetworkTrigger:false PeripheralTrigger:true RemoteTrigger:false Unprivileged:true UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
|
| 1/1 |
2026/08/14 01:07 |
action |
syz-repro-to-c-repro |
0m
Results:
|
| 2/1 |
2026/08/14 01:07 |
action |
kernel-checkouter |
0m
Results: map[KernelSrc:/app/workdir/cache/src/7c7ab42d7c9c9b5cf6fa573a15333bf947be47d7]
|
| 3/1 |
2026/08/14 01:07 |
action |
kernel-builder |
15m
Results: map[KernelObj:/app/workdir/cache/build/d90172680b240b43a6b3b32a062237d97496fd3c]
|
| 4/1 |
2026/08/14 01:22 |
action |
codesearch-prepare |
19m
Results: map[Index:codesearch-index]
|
| 5/1 |
2026/08/14 01:42 |
agent |
expert |
5m
Model:
Results: map[DenialOfService:false Exploitable:false FilesystemTrigger:true NetworkTrigger:false PeripheralTrigger:true RemoteTrigger:false Unprivileged:true UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
Instruction: You are an experienced Linux kernel security engineer. Your task is to analyze given kernel bug report
and determine its security impact based on the following dimensions.
Use the provided tools to examine the source code, check for capability checks (e.g., capable(), ns_capable()),
and understand the nature of the bug. Analyze the given kernel build and configuration.
You can check the kernel config by grepping ".config" file; you can check kernel cmdline by grepping
".config" file for "CONFIG_CMDLINE=". Assume sysctl parameters have default values.
But analyze for the corresponding production build w/o debugging tools enabled (like KASAN, KMSAN, UBSAN).
Try different strategies when analyzing the bug:
- think of ways in which the vulnerable code is unreachable
- or the other way around: try to come up with different ideas of how an unprivileged user can reach the bug
If still unsure err on the side of the bug being non-exploitable/not-accessible.
In the final reply, provide a reasoning for your assessment.
Analysis dimensions:
* Exploitable:
Determine if the bug can result in memory corruption, 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 fat32_ent_put / fat_mirror_bhs
read-write to 0xffff88811db1234c of 4 bytes by task 4861 on cpu 1:
fat32_ent_put+0x4e/0x90 fs/fat/fatent.c:194
fat_ent_write+0x6c/0xe0 fs/fat/fatent.c:422
fat_chain_add+0x174/0x4a0 fs/fat/misc.c:136
fat_add_cluster fs/fat/inode.c:113 [inline]
__fat_get_block fs/fat/inode.c:155 [inline]
fat_get_block+0x468/0x5e0 fs/fat/inode.c:190
__block_write_begin_int+0x400/0xf90 fs/buffer.c:2123
block_write_begin fs/buffer.c:2234 [inline]
cont_write_begin+0x5bf/0x920 fs/buffer.c:2596
fat_write_begin+0x52/0xe0 fs/fat/inode.c:230
cont_expand_zero fs/buffer.c:2524 [inline]
cont_write_begin+0x18d/0x920 fs/buffer.c:2586
fat_write_begin+0x52/0xe0 fs/fat/inode.c:230
generic_cont_expand_simple+0xb0/0x150 fs/buffer.c:2487
fat_cont_expand+0x3e/0x170 fs/fat/file.c:227
fat_fallocate+0x177/0x1c0 fs/fat/file.c:305
vfs_fallocate+0x3ac/0x400 fs/open.c:338
ksys_fallocate fs/open.c:362 [inline]
__do_sys_fallocate fs/open.c:367 [inline]
__se_sys_fallocate fs/open.c:365 [inline]
__x64_sys_fallocate+0x7a/0xd0 fs/open.c:365
x64_sys_call+0x298e/0x3020 arch/x86/include/generated/asm/syscalls_64.h:286
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 0xffff88811db12000 of 2048 bytes by task 4866 on cpu 0:
fat_mirror_bhs+0x1df/0x330 fs/fat/fatent.c:399
fat_ent_write+0xd0/0xe0 fs/fat/fatent.c:428
fat_chain_add+0x174/0x4a0 fs/fat/misc.c:136
fat_add_cluster fs/fat/inode.c:113 [inline]
__fat_get_block fs/fat/inode.c:155 [inline]
fat_get_block+0x468/0x5e0 fs/fat/inode.c:190
__block_write_begin_int+0x400/0xf90 fs/buffer.c:2123
block_write_begin fs/buffer.c:2234 [inline]
cont_write_begin+0x5bf/0x920 fs/buffer.c:2596
fat_write_begin+0x52/0xe0 fs/fat/inode.c:230
generic_perform_write+0x186/0x490 mm/filemap.c:4364
__generic_file_write_iter+0x9e/0x120 mm/filemap.c:4481
generic_file_write_iter+0x77/0x1f0 mm/filemap.c:4507
__kernel_write_iter+0x30f/0x590 fs/read_write.c:621
dump_emit_page fs/coredump.c:1298 [inline]
dump_user_range+0xa73/0xd00 fs/coredump.c:1372
elf_core_dump+0x21c0/0x2340 fs/binfmt_elf.c:2109
coredump_write+0xaf9/0xdd0 fs/coredump.c:1052
do_coredump fs/coredump.c:1131 [inline]
vfs_coredump+0x2770/0x3290 fs/coredump.c:1200
get_signal+0xd33/0xf10 kernel/signal.c:3031
arch_do_signal_or_restart+0x96/0x480 arch/x86/kernel/signal.c:337
__exit_to_user_mode_loop kernel/entry/common.c:66 [inline]
exit_to_user_mode_loop kernel/entry/common.c:101 [inline]
__exit_to_user_mode_prepare include/linux/irq-entry-common.h:207 [inline]
irqentry_exit_to_user_mode_prepare include/linux/irq-entry-common.h:244 [inline]
irqentry_exit_to_user_mode include/linux/irq-entry-common.h:315 [inline]
irqentry_exit+0x14d/0x610 kernel/entry/common.c:165
asm_exc_page_fault+0x26/0x30 arch/x86/include/asm/idtentry.h:595
Reported by Kernel Concurrency Sanitizer on:
CPU: 0 UID: 0 PID: 4866 Comm: syz.3.376 Not tainted syzkaller #0 PREEMPT(lazy)
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 07/24/2026
==================================================================
syz.3.376 (4866) used greatest stack depth: 9320 bytes left
Reply: An analysis of the bug report and the corresponding kernel source code reveals the following:
**Bug Mechanism:**
The bug is a data race detected by the Kernel Concurrency Sanitizer (KCSAN) in the FAT filesystem driver.
- **Thread 1 (CPU 1)** is executing `fat32_ent_put`, which performs a 4-byte write to update a specific FAT entry within a cached buffer head block. This happens during `fat_chain_add` when a file is being appended or allocated new clusters (e.g., via `fallocate`).
- **Thread 2 (CPU 0)** is concurrently executing `fat_mirror_bhs`, which uses `memcpy` to copy the entire buffer head block (e.g., 2048 bytes) to a backup FAT block. This happens after another file operation has modified the FAT and is now syncing the changes to the backup FAT.
Because `fat_chain_add` modifies the file's last FAT entry without holding the global FAT lock (it relies on the file's `inode_lock`), it can write to the buffer head at the exact same time another thread is copying that same buffer head to the backup FAT.
**Security Impact:**
This data race is **benign** and **not exploitable**.
When Thread 2's `memcpy` reads the buffer head, it might read a torn or stale 4-byte write from Thread 1. However, Thread 1's operation (`fat_ent_write`) is structured such that immediately after calling `fat32_ent_put`, it will also call `fat_mirror_bhs` to copy the updated buffer head to the backup FAT.
Consequently, even if Thread 2 writes an inconsistent state to the backup FAT, Thread 1 will immediately overwrite it with the correct, fully updated block.
There are no memory safety violations (like out-of-bounds accesses or use-after-frees), no control-flow hijacks, and no kernel panics or deadlocks resulting from this race. The primary FAT remains perfectly consistent, and the backup FAT achieves eventual consistency.
**Trigger Vectors:**
The bug can be triggered by any unprivileged user who has write access to a mounted FAT filesystem (e.g., by calling `fallocate` or `write` concurrently from multiple threads). This makes it reachable via physically plugged-in peripheral devices (like USB flash drives formatted with FAT32) or by mounting a malicious/crafted FAT filesystem image.
|
| 6/2 |
2026/08/14 01:42 |
llm |
expert |
0m
Model:
Tokens:
input: 7490
output: 72
thoughts: 61
Thoughts: Okay, let's break this down.
**Investigating a FAT32 KCSAN Data-Race**
Alright, so the KCSAN is flagging a data-race. Specifically, it's pinpointing a problem in the `fat32_ent_put` function and the `fat_mirror_bhs` function. My initial thought is to zero in on these functions within the FAT filesystem code. I'll need to examine `fs/fat/fatent.c` because that's where I expect to find the relevant implementation. I'll focus my attention around line 194 for `fat32_ent_put`, and around line 399 for `fat_mirror_bhs`, as those seem like the likely starting points for the issue. This suggests some sort of concurrent access to shared data within that context. I'll have to carefully trace the data flow and locking mechanisms around those areas to understand the race condition.
|
| 7/2 |
2026/08/14 01:42 |
tool |
read-file |
0m
Args: map[File:fs/fat/fatent.c FirstLine:180 LineCount:50]
Results: map[Contents: 180: static void fat16_ent_put(struct fat_entry *fatent, int new)
181: {
182: if (new == FAT_ENT_EOF)
183: new = EOF_FAT16;
184:
185: *fatent->u.ent16_p = cpu_to_le16(new);
186: mmb_mark_buffer_dirty(fatent->bhs[0],
187: &MSDOS_I(fatent->fat_inode)->i_metadata_bhs);
188: }
189:
190: static void fat32_ent_put(struct fat_entry *fatent, int new)
191: {
192: WARN_ON(new & 0xf0000000);
193: new |= le32_to_cpu(*fatent->u.ent32_p) & ~0x0fffffff;
194: *fatent->u.ent32_p = cpu_to_le32(new);
195: mmb_mark_buffer_dirty(fatent->bhs[0],
196: &MSDOS_I(fatent->fat_inode)->i_metadata_bhs);
197: }
198:
199: static int fat12_ent_next(struct fat_entry *fatent)
200: {
201: u8 **ent12_p = fatent->u.ent12_p;
202: struct buffer_head **bhs = fatent->bhs;
203: u8 *nextp = ent12_p[1] + 1 + (fatent->entry & 1);
204:
205: fatent->entry++;
206: if (fatent->nr_bhs == 1) {
207: WARN_ON(ent12_p[0] > (u8 *)(bhs[0]->b_data +
208: (bhs[0]->b_size - 2)));
209: WARN_ON(ent12_p[1] > (u8 *)(bhs[0]->b_data +
210: (bhs[0]->b_size - 1)));
211: if (nextp < (u8 *)(bhs[0]->b_data + (bhs[0]->b_size - 1))) {
212: ent12_p[0] = nextp - 1;
213: ent12_p[1] = nextp;
214: return 1;
215: }
216: } else {
217: WARN_ON(ent12_p[0] != (u8 *)(bhs[0]->b_data +
218: (bhs[0]->b_size - 1)));
219: WARN_ON(ent12_p[1] != (u8 *)bhs[1]->b_data);
220: ent12_p[0] = nextp - 1;
221: ent12_p[1] = nextp;
222: brelse(bhs[0]);
223: bhs[0] = bhs[1];
224: fatent->nr_bhs = 1;
225: return 1;
226: }
227: ent12_p[0] = NULL;
228: ent12_p[1] = NULL;
229: return 0;
]
|
| 8/2 |
2026/08/14 01:42 |
tool |
read-file |
0m
Args: map[File:fs/fat/fatent.c FirstLine:380 LineCount:50]
Results: map[Contents: 380: static int fat_mirror_bhs(struct super_block *sb, struct buffer_head **bhs,
381: int nr_bhs)
382: {
383: struct msdos_sb_info *sbi = MSDOS_SB(sb);
384: struct buffer_head *c_bh;
385: int err, n, copy;
386:
387: err = 0;
388: for (copy = 1; copy < sbi->fats; copy++) {
389: sector_t backup_fat = sbi->fat_length * copy;
390:
391: for (n = 0; n < nr_bhs; n++) {
392: c_bh = sb_getblk(sb, backup_fat + bhs[n]->b_blocknr);
393: if (!c_bh) {
394: err = -ENOMEM;
395: goto error;
396: }
397: /* Avoid race with userspace read via bdev */
398: lock_buffer(c_bh);
399: memcpy(c_bh->b_data, bhs[n]->b_data, sb->s_blocksize);
400: set_buffer_uptodate(c_bh);
401: unlock_buffer(c_bh);
402: mmb_mark_buffer_dirty(c_bh,
403: &MSDOS_I(sbi->fat_inode)->i_metadata_bhs);
404: if (sb->s_flags & SB_SYNCHRONOUS)
405: err = sync_dirty_buffer(c_bh);
406: brelse(c_bh);
407: if (err)
408: goto error;
409: }
410: }
411: error:
412: return err;
413: }
414:
415: int fat_ent_write(struct inode *inode, struct fat_entry *fatent,
416: int new, int wait)
417: {
418: struct super_block *sb = inode->i_sb;
419: const struct fatent_operations *ops = MSDOS_SB(sb)->fatent_ops;
420: int err;
421:
422: ops->ent_put(fatent, new);
423: if (wait) {
424: err = fat_sync_bhs(fatent->bhs, fatent->nr_bhs);
425: if (err)
426: return err;
427: }
428: return fat_mirror_bhs(sb, fatent->bhs, fatent->nr_bhs);
429: }
]
|
| 9/2 |
2026/08/14 01:42 |
llm |
expert |
0m
Model:
Tokens:
input: 9569
output: 33
thoughts: 4743
Thoughts: **Data Race in FAT32: A Deep Dive**
Okay, so the KCSAN report flagged a data race. My initial thought is it centers around `fat32_ent_put` and `fat_mirror_bhs`, a known pair of functions in FAT32 file system implementations. `fat32_ent_put` updates FAT entries in the primary FAT, writing to `fatent->u.ent32_p` within a buffer head (`fatent->bhs[0]->b_data`). Concurrently, `fat_mirror_bhs` mirrors these updates to the backup FAT, reading from `bhs[n]->b_data` (which is `fatent->bhs[n]->b_data`) to write to `c_bh->b_data`. The vulnerability is in the potential race between the primary FAT update and the backup copy.
The code flow, as I see it, is `fat_ent_write` calling `ops->ent_put` (which is our `fat32_ent_put`) and subsequently `fat_mirror_bhs` in the same thread. This immediately suggests it's *supposed* to be sequential, but the KCSAN report shows two different tasks (Task 4861 and Task 4866), allocated on different CPUs, calling these functions. This concurrent cluster allocation is where it gets tricky, the fact that two tasks are allocating clusters concurrently, is key to the problem. The first writes to `fatent->u.ent32_p` and the second copies `bhs[n]->b_data`. A race appears because the different tasks could be modifying different FAT entries that happen to reside in the same FAT block (`bhs[0]`). One task is modifying the entry, another is mirroring.
The core concern revolves around the potential for `fat_mirror_bhs` to read a partially written FAT entry (a "torn write") from Task 4861, leading to a corrupted backup. Now, thankfully, `fat32_ent_put` atomically writes a 32-bit value using `cpu_to_le32`, and the entries are 4-byte aligned on x86, which is crucial. Even though there is a read-modify-write operation, on x86, a 32-bit aligned store is atomic. However, `fat32_ent_put` reads *and* writes. If `fat_mirror_bhs` has a concurrent read, it could read the new or the old value. However, the first task has to mirror its updates, too, as it finishes the call to `fat_ent_write` which also invokes `fat_mirror_bhs`. This means there will always be a backup copy in the end. It's essentially self-correcting.
Let's work through this. One task runs `fat_mirror_bhs`. It acquires the block (`bhs[n]`). It copies the contents of the buffer to `c_bh`. To prevent race conditions during copy, `lock_buffer(c_bh)` is called on the destination. This serializes `memcpy` operations. Thus, `memcpy(c_bh->b_data, bhs[n]->b_data, sb->s_blocksize)` will *eventually* copy a consistent version of the block. If Task B's `memcpy` occurs before the other's entry write, it will copy the old value, but the other task's `memcpy` *after* will copy the new one. Thus, we have atomicity.
The critical insight lies in the `lock_buffer` mechanism. Since both tasks lock `c_bh` before the `memcpy` operations, there is no corruption during the copying. The second task’s write to `c_bh` effectively overwrites the first one's and ensures consistency.
The more detailed flow is to realize the order of the events; let's say Task B locks the buffer, writes, unlocks; Task A locks the buffer, writes. In this case, there are no race conditions because the second write is to happen. But if they lock the buffer at the same time, one of them will happen first, so the first write might be inconsistent; however, the second will be complete. If task B locks, it must read new B in the second `memcpy`. If task B holds the old value, there's a problem, but it's not the case. Both operations work and guarantee correctness.
So, there isn’t any scenario where there’s a torn or old value of A. The final copy of `c_bh` will *always* reflect the state of the primary FAT after all writes. The primary mirror is always updated after the entry write.
This
|
| 10/2 |
2026/08/14 01:43 |
tool |
git-log |
0m
Args: map[MessageRegexps:[fat32_ent_put fat_mirror_bhs]]
Results:
|
| 11/2 |
2026/08/14 01:43 |
llm |
expert |
0m
Model:
Tokens:
input: 10640
output: 35
thoughts: 989
Thoughts: **Analyzing the KCSAN Report: A Deep Dive into the FAT32 Data Race**
Okay, so the KCSAN report flagged a data race. Let's break this down. The issue is between `fat32_ent_put` and `fat_mirror_bhs`. `fat32_ent_put` modifies a FAT entry like this:
```c
new |= le32_to_cpu(*fatent->u.ent32_p) & ~0x0fffffff;
*fatent->u.ent32_p = cpu_to_le32(new);
```
While `fat_mirror_bhs` essentially copies data from one buffer to another:
```c
memcpy(c_bh->b_data, bhs[n]->b_data, sb->s_blocksize);
```
The race is specifically between writing to `*fatent->u.ent32_p` (which is within `bhs[0]->b_data`) and reading from potentially any `bhs[n]->b_data`. `fat_ent_write` is the culprit as it calls `ops->ent_put(fatent, new);` which, in turn, modifies `bhs[0]->b_data` and subsequently calls `fat_mirror_bhs(sb, fatent->bhs, fatent->nr_bhs);`, which reads from `bhs[n]->b_data` and copies to `c_bh->b_data`. So, one CPU is writing, another is reading from the same buffer.
Now, where does this come from? `fat_ent_write` is called by `fat_chain_add`. The scenario is clear: if two threads are calling `fat_chain_add` concurrently, they might be touching different FAT entries, but *within* the same buffer block (`bhs[0]`). Think about it, thread 1 modifies entry A, thread 2 modifies entry B, and both are within `bhs[0]`.
Thread 1 then calls `fat_mirror_bhs`, copying the entire `bhs[0]->b_data`. Thread 2 will then call `fat32_ent_put`, changing entry B within `bhs[0]`. Since `fat_mirror_bhs` uses `memcpy`, the data race can occur. The `memcpy` could read a partially written value, a "torn" write.
Okay, let's look at `fat32_ent_put` more closely. It's a read-modify-write on a 32-bit value. If `memcpy` overlaps this write, even if the write itself is atomic (which it is, on x86, because of the alignment), the read could be disrupted. The `memcpy` reads byte by byte or word by word. It could read a partially written value if the compiler generates non-atomic writes, which is possible. The consequence is that the backup FAT block gets a bad value for entry B.
But... here's the kicker: Thread 2 *also* calls `fat_mirror_bhs` right after `fat32_ent_put`. Thread 2's `fat_mirror_bhs` copies *the entire* `bhs[0]->b_data` again, overwriting Thread 1's previous write. So, the question is not about the immediate "torn" write, but the *order* of `fat_mirror_bhs` calls. As long as Thread 2's `fat_mirror_bhs` occurs *after* `fat32_ent_put`, the correct value for entry B *should* be in the backup... unless Thread 1's `fat_mirror_bhs` somehow executes *after* Thread 2's. Now, it's time to check the locking mechanism in `fat_chain_add`.
|
| 12/2 |
2026/08/14 01:43 |
tool |
read-file |
0m
Args: map[File:fs/fat/misc.c FirstLine:100 LineCount:50]
Results: map[Contents: 100: return 0;
101: }
102:
103: /*
104: * fat_chain_add() adds a new cluster to the chain of clusters represented
105: * by inode.
106: */
107: int fat_chain_add(struct inode *inode, int new_dclus, int nr_cluster)
108: {
109: struct super_block *sb = inode->i_sb;
110: struct msdos_sb_info *sbi = MSDOS_SB(sb);
111: int ret, new_fclus, last;
112:
113: /*
114: * We must locate the last cluster of the file to add this new
115: * one (new_dclus) to the end of the link list (the FAT).
116: */
117: last = new_fclus = 0;
118: if (MSDOS_I(inode)->i_start) {
119: int fclus, dclus;
120:
121: ret = fat_get_cluster(inode, FAT_ENT_EOF, &fclus, &dclus);
122: if (ret < 0)
123: return ret;
124: new_fclus = fclus + 1;
125: last = dclus;
126: }
127:
128: /* add new one to the last of the cluster chain */
129: if (last) {
130: struct fat_entry fatent;
131:
132: fatent_init(&fatent);
133: ret = fat_ent_read(inode, &fatent, last);
134: if (ret >= 0) {
135: int wait = inode_needs_sync(inode);
136: ret = fat_ent_write(inode, &fatent, new_dclus, wait);
137: fatent_brelse(&fatent);
138: }
139: if (ret < 0)
140: return ret;
141: /*
142: * FIXME:Although we can add this cache, fat_cache_add() is
143: * assuming to be called after linear search with fat_cache_id.
144: */
145: // fat_cache_add(inode, new_fclus, new_dclus);
146: } else {
147: MSDOS_I(inode)->i_start = new_dclus;
148: MSDOS_I(inode)->i_logstart = new_dclus;
149: /*
]
|
| 13/2 |
2026/08/14 01:43 |
llm |
expert |
0m
Model:
Tokens:
input: 12304
output: 72
thoughts: 694
Thoughts: **Analyzing a Data Race in the FAT32 Filesystem**
Okay, so I've got a potential data race brewing in the FAT32 code, specifically within `fat32_ent_put` and `fat_mirror_bhs`. My initial thought is that `fat32_ent_put` is writing to `*fatent->u.ent32_p` – a 4-byte value – while `fat_mirror_bhs` is reading from `bhs[n]->b_data` via a `memcpy`. This already screams potential conflict, right?
The issue crystallizes when I realize that `fat32_ent_put` directly modifies the buffer `bhs[0]->b_data`, and `fat_mirror_bhs` reads from `bhs[n]->b_data`, also directly. The important connection is `fat_ent_write`. This function calls `ops->ent_put(fatent, new);`, which turns out to be `fat32_ent_put`, effectively modifying `bhs[0]`. Then, it calls `fat_mirror_bhs(sb, fatent->bhs, fatent->nr_bhs);`, copying `bhs[n]->b_data` to `c_bh->b_data`.
The race is between `fat32_ent_put` running on CPU 1 and `fat_mirror_bhs` running on CPU 0. CPU 1 is busy modifying the FAT entry (writing), while CPU 0 is copying the entire block (reading). The critical question: how can they be executing concurrently on the *same* buffer?
Ah, right, `fat32_ent_put` is called from `fat_ent_write` on CPU 1, and `fat_mirror_bhs` is called from `fat_ent_write` on CPU 0. The key lies in the fact that CPU 0 is doing `fat_mirror_bhs` for a `fatent` that shares the same `bhs[n]` as CPU 1's `fatent`. Bingo! The `bhs[n]` is the buffer head for the FAT table block itself. Multiple FAT entries can reside within a single block.
So, CPU 0 is essentially copying the entire FAT block (`bhs[n]->b_data`) to create the mirror, while CPU 1 is modifying a single FAT entry within that same block (`bhs[0]->b_data`). It's a textbook data race: one thread reading the whole block while another thread writes a portion of it. Classic.
The next question: Is this actually a bug? What's protecting the FAT table block from concurrent access? I don't see any explicit locking in `fat_ent_write` around the calls to `ops->ent_put`. This is concerning, and warrants a closer inspection of `fat_ent_read` and `fat_ent_write` to see if there's any implicit synchronization that I'm missing.
|
| 14/2 |
2026/08/14 01:43 |
tool |
read-file |
0m
Args: map[File:fs/fat/fatent.c FirstLine:415 LineCount:20]
Results: map[Contents: 415: int fat_ent_write(struct inode *inode, struct fat_entry *fatent,
416: int new, int wait)
417: {
418: struct super_block *sb = inode->i_sb;
419: const struct fatent_operations *ops = MSDOS_SB(sb)->fatent_ops;
420: int err;
421:
422: ops->ent_put(fatent, new);
423: if (wait) {
424: err = fat_sync_bhs(fatent->bhs, fatent->nr_bhs);
425: if (err)
426: return err;
427: }
428: return fat_mirror_bhs(sb, fatent->bhs, fatent->nr_bhs);
429: }
430:
431: static inline int fat_ent_next(struct msdos_sb_info *sbi,
432: struct fat_entry *fatent)
433: {
434: if (sbi->fatent_ops->ent_next(fatent)) {
]
|
| 15/2 |
2026/08/14 01:43 |
tool |
read-file |
0m
Args: map[File:fs/fat/fatent.c FirstLine:300 LineCount:50]
Results: map[Contents: 300: sbi->fatent_shift = 2;
301: sbi->fatent_ops = &fat32_ops;
302: } else if (is_fat16(sbi)) {
303: sbi->fatent_shift = 1;
304: sbi->fatent_ops = &fat16_ops;
305: } else if (is_fat12(sbi)) {
306: sbi->fatent_shift = -1;
307: sbi->fatent_ops = &fat12_ops;
308: } else {
309: fat_fs_error(sb, "invalid FAT variant, %u bits", sbi->fat_bits);
310: }
311: }
312:
313: static void mark_fsinfo_dirty(struct super_block *sb)
314: {
315: struct msdos_sb_info *sbi = MSDOS_SB(sb);
316:
317: if (sb_rdonly(sb) || !is_fat32(sbi))
318: return;
319:
320: __mark_inode_dirty(sbi->fsinfo_inode, I_DIRTY_SYNC);
321: }
322:
323: static inline int fat_ent_update_ptr(struct super_block *sb,
324: struct fat_entry *fatent,
325: int offset, sector_t blocknr)
326: {
327: struct msdos_sb_info *sbi = MSDOS_SB(sb);
328: const struct fatent_operations *ops = sbi->fatent_ops;
329: struct buffer_head **bhs = fatent->bhs;
330:
331: /* Is this fatent's blocks including this entry? */
332: if (!fatent->nr_bhs || bhs[0]->b_blocknr != blocknr)
333: return 0;
334: if (is_fat12(sbi)) {
335: if ((offset + 1) < sb->s_blocksize) {
336: /* This entry is on bhs[0]. */
337: if (fatent->nr_bhs == 2) {
338: brelse(bhs[1]);
339: fatent->nr_bhs = 1;
340: }
341: } else {
342: /* This entry needs the next block. */
343: if (fatent->nr_bhs != 2)
344: return 0;
345: if (bhs[1]->b_blocknr != (blocknr + 1))
346: return 0;
347: }
348: }
349: ops->ent_set_ptr(fatent, offset);
]
|
| 16/2 |
2026/08/14 01:43 |
llm |
expert |
0m
Model:
Tokens:
input: 14215
output: 37
thoughts: 4666
Thoughts: **Data Race Analysis in FAT32 Entry Updates and Mirroring**
Okay, so I've got this report of a data race. My immediate thought is, let's break this down. The core issue seems to be a conflict between how we update FAT entries using `fat32_ent_put` and how we mirror those updates to backup FAT tables with `fat_mirror_bhs`.
`fat32_ent_put`, which is part of the `ent_put` function, is writing a 4-byte value to a specific memory location. This write happens directly to the `*fatent->u.ent32_p` pointer, which is associated with a `buffer_head`. Simultaneously, `fat_mirror_bhs` is reading 2048 bytes from a related `buffer_head` to replicate its content. My concern is, are we potentially reading stale or incomplete data?
The `fat_ent_write` function is key. It calls the entry write operation, and then mirrors it. The problem is, if two threads are updating different entries that happen to reside in the same `buffer_head`, we're potentially in trouble. One thread might be halfway through writing to the buffer head via `fat32_ent_put`, and the other could be attempting to copy the *entire* buffer head via `fat_mirror_bhs`. The `memcpy` could grab a "torn" value.
Now, let's walk through this and consider various interleaving possibilities, and if this results in an error.
The function `fat_mirror_bhs` uses `lock_buffer` before `memcpy`, and then `unlock_buffer`. So while one thread is copying (reading) the buffer head, other threads cannot modify the buffer head.
If `fat_mirror_bhs` is called in two different threads, they cannot copy to the same `c_bh` at the same time. The first thread locks the buffer. Then the first thread copies. Then the first thread unlocks the buffer. Then the second thread locks the buffer, copies, and unlocks. This protects against corrupted writes.
However, if `SB_SYNCHRONOUS` is set, `sync_dirty_buffer(c_bh)` is called within the mirror function. This immediately writes the backup copy to disk. In a synchronous write scenario, we can see a possibility for error because the torn write will be synced to disk.
But wait. The `fat_sync_bhs` operation (if `wait` is true in `fat_ent_write`) *also* calls `sync_dirty_buffer` on the primary buffer. This also writes to disk. So there's potential for the torn write to hit the primary disk as well.
Tracing it back, a scenario could be:
1. Thread A starts updating `bh` with entry A, then starts `fat_mirror_bhs`.
2. Thread B updates entry B in the same `bh` concurrently.
3. Thread A syncs the dirty buffer to disk. The content to disk is a partial write of entry A *and* a partial write of B because they are both updating the same `bh`.
This means a crash between threads A5 and B5 could cause a torn write to disk for the mirror FAT. Although, if the threads continue running, a clean state will be written, potentially overwriting a torn write. However, if the crash happens, the torn write will remain.
|
| 17/2 |
2026/08/14 01:44 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/fat/fatent.c Name:write_dirty_buffer]
Results: map[SourceCode:2771: void write_dirty_buffer(struct buffer_head *bh, blk_opf_t op_flags)
2772: {
2773: lock_buffer(bh);
2774: if (!test_clear_buffer_dirty(bh)) {
2775: unlock_buffer(bh);
2776: return;
2777: }
2778: bh_submit(bh, REQ_OP_WRITE | op_flags, bh_end_write);
2779: }
SourceFile:fs/buffer.c]
|
| 18/2 |
2026/08/14 01:44 |
llm |
expert |
0m
Model:
Tokens:
input: 15163
output: 35
thoughts: 752
Thoughts: **Analyzing a Data Race in the FAT32 Filesystem**
Okay, so I've got a nasty data race on my hands. It appears the problem lies between `fat32_ent_put` and `fat_mirror_bhs`. Basically, `fat32_ent_put` is writing to a location pointed to by `*fatent->u.ent32_p`, which is within `fatent->bhs[0]->b_data`. Meanwhile, `fat_mirror_bhs` is reading the same data through a `memcpy` from `bhs[n]->b_data`. The crux of it is that `fat_mirror_bhs` only locks `c_bh` and not `bhs[n]`. That's a textbook race condition, right there.
Let me trace back through the code, starting with `fat_ent_write`. I can see the write sequence: `ops->ent_put(fatent, new);` followed by a sync, and then the mirroring. The key here seems to be `ops->ent_put`, which translates to the problematic `fat32_ent_put` function. So it looks like the write to the FAT is happening, and then the mirroring is happening, and it's missing the lock.
Okay, let's nail down the specifics of the race. It's CPU 1 doing `fat32_ent_put` and CPU 0 doing `fat_mirror_bhs`. This is interesting: `fat32_ent_put` is reached through a chain: `fat_chain_add` -> `fat_add_cluster` -> `__fat_get_block` -> `fat_get_block` -> `__block_write_begin_int` and then a bunch more, all the way to `fat_fallocate`. And `fat_mirror_bhs` is reached through a different chain, which starts with `fat_chain_add` too but comes from `coredump_write`. It sounds like two different processes or threads might be calling `fat_ent_write` on, possibly, different files?
The implication here is that these tasks are allocating clusters, and thus, modifying the FAT. And I'm pretty sure that any modification to the FAT should be protected by the FAT lock, `sbi->fat_lock`. This is a critical point. I *need* to verify that `fat_chain_add`, or `fat_add_cluster`, or `fat_ent_write` is properly holding this lock. Let's start with `fat_add_cluster`.
|
| 19/2 |
2026/08/14 01:44 |
tool |
read-file |
0m
Args: map[File:fs/fat/inode.c FirstLine:100 LineCount:50]
Results: map[Contents: 100: .fat_length = 2,
101: },
102: };
103:
104: int fat_add_cluster(struct inode *inode)
105: {
106: int err, cluster;
107:
108: err = fat_alloc_clusters(inode, &cluster, 1);
109: if (err)
110: return err;
111: /* FIXME: this cluster should be added after data of this
112: * cluster is writed */
113: err = fat_chain_add(inode, cluster, 1);
114: if (err)
115: fat_free_clusters(inode, cluster);
116: return err;
117: }
118:
119: static inline int __fat_get_block(struct inode *inode, sector_t iblock,
120: unsigned long *max_blocks,
121: struct buffer_head *bh_result, int create)
122: {
123: struct super_block *sb = inode->i_sb;
124: struct msdos_sb_info *sbi = MSDOS_SB(sb);
125: unsigned long mapped_blocks;
126: sector_t phys, last_block;
127: int err, offset;
128:
129: err = fat_bmap(inode, iblock, &phys, &mapped_blocks, create, false);
130: if (err)
131: return err;
132: if (phys) {
133: map_bh(bh_result, sb, phys);
134: *max_blocks = min(mapped_blocks, *max_blocks);
135: return 0;
136: }
137: if (!create)
138: return 0;
139:
140: if (iblock != MSDOS_I(inode)->mmu_private >> sb->s_blocksize_bits) {
141: fat_fs_error(sb, "corrupted file size (i_pos %lld, %lld)",
142: MSDOS_I(inode)->i_pos, MSDOS_I(inode)->mmu_private);
143: return -EIO;
144: }
145:
146: last_block = inode->i_blocks >> (sb->s_blocksize_bits - 9);
147: offset = (unsigned long)iblock & (sbi->sec_per_clus - 1);
148: /*
149: * allocate a cluster according to the following.
]
|
| 20/2 |
2026/08/14 01:44 |
llm |
expert |
0m
Model:
Tokens:
input: 16529
output: 38
thoughts: 0
|
| 21/2 |
2026/08/14 01:44 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/fat/fatent.c Name:fat_mirror_bhs]
Results: map[SourceCode: 379: /* FIXME: We can write the blocks as more big chunk. */
380: static int fat_mirror_bhs(struct super_block *sb, struct buffer_head **bhs,
381: int nr_bhs)
382: {
383: struct msdos_sb_info *sbi = MSDOS_SB(sb);
384: struct buffer_head *c_bh;
385: int err, n, copy;
386:
387: err = 0;
388: for (copy = 1; copy < sbi->fats; copy++) {
389: sector_t backup_fat = sbi->fat_length * copy;
390:
391: for (n = 0; n < nr_bhs; n++) {
392: c_bh = sb_getblk(sb, backup_fat + bhs[n]->b_blocknr);
393: if (!c_bh) {
394: err = -ENOMEM;
395: goto error;
396: }
397: /* Avoid race with userspace read via bdev */
398: lock_buffer(c_bh);
399: memcpy(c_bh->b_data, bhs[n]->b_data, sb->s_blocksize);
400: set_buffer_uptodate(c_bh);
401: unlock_buffer(c_bh);
402: mmb_mark_buffer_dirty(c_bh,
403: &MSDOS_I(sbi->fat_inode)->i_metadata_bhs);
404: if (sb->s_flags & SB_SYNCHRONOUS)
405: err = sync_dirty_buffer(c_bh);
406: brelse(c_bh);
407: if (err)
408: goto error;
409: }
410: }
411: error:
412: return err;
413: }
SourceFile:fs/fat/fatent.c]
|
| 22/2 |
2026/08/14 01:44 |
llm |
expert |
0m
Model:
Tokens:
input: 17171
output: 39
thoughts: 0
|
| 23/2 |
2026/08/14 01:44 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/fat/fatent.c Name:fat32_ent_put]
Results: map[SourceCode: 190: static void fat32_ent_put(struct fat_entry *fatent, int new)
191: {
192: WARN_ON(new & 0xf0000000);
193: new |= le32_to_cpu(*fatent->u.ent32_p) & ~0x0fffffff;
194: *fatent->u.ent32_p = cpu_to_le32(new);
195: mmb_mark_buffer_dirty(fatent->bhs[0],
196: &MSDOS_I(fatent->fat_inode)->i_metadata_bhs);
197: }
SourceFile:fs/fat/fatent.c]
|
| 24/2 |
2026/08/14 01:44 |
llm |
expert |
0m
Model:
Tokens:
input: 17426
output: 37
thoughts: 0
|
| 25/2 |
2026/08/14 01:44 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/fat/fatent.c Name:fat_ent_write]
Results: map[SourceCode: 415: int fat_ent_write(struct inode *inode, struct fat_entry *fatent,
416: int new, int wait)
417: {
418: struct super_block *sb = inode->i_sb;
419: const struct fatent_operations *ops = MSDOS_SB(sb)->fatent_ops;
420: int err;
421:
422: ops->ent_put(fatent, new);
423: if (wait) {
424: err = fat_sync_bhs(fatent->bhs, fatent->nr_bhs);
425: if (err)
426: return err;
427: }
428: return fat_mirror_bhs(sb, fatent->bhs, fatent->nr_bhs);
429: }
SourceFile:fs/fat/fatent.c]
|
| 26/2 |
2026/08/14 01:44 |
llm |
expert |
0m
Model:
Tokens:
input: 17736
output: 37
thoughts: 958
Thoughts: **Analyzing a Data Race in the FAT Filesystem**
Okay, so I've got a bug report in front of me, and it smells like a classic data race in the FAT filesystem code. The details point to concurrent access to the FAT table, which isn't exactly a new concern, but it looks like there's a specific window here.
My initial thought is that Thread 1, running on CPU 1, is writing to `fatent->u.ent32_p` within the `fat32_ent_put` function. Specifically, it's modifying the FAT entry with a new value and marking the buffer dirty. At the same time, Thread 2 on CPU 0 is reading from `bhs[n]->b_data` within `fat_mirror_bhs`. The critical point here is that `fatent->u.ent32_p` is actually a pointer *into* `bhs[0]->b_data`. So, we have one thread actively mutating the buffer while another is trying to copy that very data to a mirrored buffer. That's a textbook data race scenario.
But wait, looking closer, both threads are actually calling `fat_ent_write`! Thread 1 is calling `fat32_ent_put` from inside `fat_ent_write`. And Thread 2 is calling `fat_mirror_bhs` from inside `fat_ent_write`. They both end up being within `fat_chain_add`. That's interesting, and narrows down the issue further. The calls appear to be nested and interleaved.
Now, `fat_ent_write` calls `ops->ent_put` which is the code above and then calls `fat_mirror_bhs`. If two threads call `fat_ent_write` on the *same* `fatent->bhs` simultaneously, we have a race condition since it writes to the buffer and then mirrors it. But, I don't see any explicit locking on the buffer head within `fat_ent_write`. That's a red flag. So, it's up to other functions to ensure synchronization.
Let's trace this back a bit more through the call stack. The code snippet shows `fat_chain_add` calls `fat_ent_write`. `fat_chain_add` reads and then writes a FAT entry. Okay, now I need to check `fat_ent_read`, does *that* take a lock? I need to know if the read operation that happens before the write synchronizes access to the FAT entry data.
|
| 27/2 |
2026/08/14 01:45 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/fat/fatent.c Name:fat_ent_read]
Results: map[SourceCode: 353: int fat_ent_read(struct inode *inode, struct fat_entry *fatent, int entry)
354: {
355: struct super_block *sb = inode->i_sb;
356: struct msdos_sb_info *sbi = MSDOS_SB(inode->i_sb);
357: const struct fatent_operations *ops = sbi->fatent_ops;
358: int err, offset;
359: sector_t blocknr;
360:
361: if (!fat_valid_entry(sbi, entry)) {
362: fatent_brelse(fatent);
363: fat_fs_error_ratelimit(sb, "invalid access to FAT (entry 0x%08x)", entry);
364: return -EIO;
365: }
366:
367: fatent_set_entry(fatent, entry);
368: ops->ent_blocknr(sb, entry, &offset, &blocknr);
369:
370: if (!fat_ent_update_ptr(sb, fatent, offset, blocknr)) {
371: fatent_brelse(fatent);
372: err = ops->ent_bread(sb, fatent, offset, blocknr);
373: if (err)
374: return err;
375: }
376: return ops->ent_get(fatent);
377: }
SourceFile:fs/fat/fatent.c]
|
| 28/2 |
2026/08/14 01:45 |
llm |
expert |
0m
Model:
Tokens:
input: 18773
output: 36
thoughts: 497
Thoughts: **Data Race Deep Dive**
Okay, so I've got a handle on this bug. It's a classic data race, specifically in `fat32_ent_put` and `fat_mirror_bhs`. Thread 1 is busy executing `fat32_ent_put`, and it's writing to `*fatent->u.ent32_p`, which, if I'm reading this right, points directly into `bhs[0]->b_data`. Thread 2, on the other hand, is in `fat_mirror_bhs` and is reading from `bhs[n]->b_data` using `memcpy`. Bingo. Potential concurrency nightmare.
Now, let's trace this back a bit. `fat_ent_write` is the key function. It's calling `ops->ent_put(fatent, new)`, which is the problematic `fat32_ent_put` in this case and which modifies the buffer, and then, right after that, it calls `fat_mirror_bhs` which copies the buffer's contents to the backup FAT. Seems straightforward enough, but this race involves two separate threads.
So, one thread is modifying the FAT (Thread 1 - via `fat_ent_write`, called from `fat_chain_add`, and so on, all the way back to `fat_fallocate`). And another thread is simultaneously reading the FAT (Thread 2, also via `fat_ent_write`, which, in this case, traces back to `generic_perform_write` which ultimately causes a coredump!). That's a textbook data race. It's clear that it isn't the same thread.
The big question now is, what's supposed to be protecting this? The obvious candidate is `sbi->fat_lock`, which should be guarding all modifications to the FAT. Let's dig deeper in `fat_chain_add` and `fat_add_cluster` to check whether `fat_lock` is being used correctly, or if there's a missed lock somewhere. This is where the root cause probably lives. We'll find out whether we're missing an explicit lock, or whether a lock is being held for the wrong duration.
|
| 29/2 |
2026/08/14 01:45 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/fat/inode.c Name:fat_add_cluster]
Results: map[SourceCode: 104: int fat_add_cluster(struct inode *inode)
105: {
106: int err, cluster;
107:
108: err = fat_alloc_clusters(inode, &cluster, 1);
109: if (err)
110: return err;
111: /* FIXME: this cluster should be added after data of this
112: * cluster is writed */
113: err = fat_chain_add(inode, cluster, 1);
114: if (err)
115: fat_free_clusters(inode, cluster);
116: return err;
117: }
SourceFile:fs/fat/inode.c]
|
| 30/2 |
2026/08/14 01:45 |
llm |
expert |
0m
Model:
Tokens:
input: 19512
output: 36
thoughts: 0
|
| 31/2 |
2026/08/14 01:45 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/fat/misc.c Name:fat_chain_add]
Results: map[SourceCode: 103: /*
104: * fat_chain_add() adds a new cluster to the chain of clusters represented
105: * by inode.
106: */
107: int fat_chain_add(struct inode *inode, int new_dclus, int nr_cluster)
108: {
109: struct super_block *sb = inode->i_sb;
110: struct msdos_sb_info *sbi = MSDOS_SB(sb);
111: int ret, new_fclus, last;
112:
113: /*
114: * We must locate the last cluster of the file to add this new
115: * one (new_dclus) to the end of the link list (the FAT).
116: */
117: last = new_fclus = 0;
118: if (MSDOS_I(inode)->i_start) {
119: int fclus, dclus;
120:
121: ret = fat_get_cluster(inode, FAT_ENT_EOF, &fclus, &dclus);
122: if (ret < 0)
123: return ret;
124: new_fclus = fclus + 1;
125: last = dclus;
126: }
127:
128: /* add new one to the last of the cluster chain */
129: if (last) {
130: struct fat_entry fatent;
131:
132: fatent_init(&fatent);
133: ret = fat_ent_read(inode, &fatent, last);
134: if (ret >= 0) {
135: int wait = inode_needs_sync(inode);
136: ret = fat_ent_write(inode, &fatent, new_dclus, wait);
137: fatent_brelse(&fatent);
138: }
139: if (ret < 0)
140: return ret;
141: /*
142: * FIXME:Although we can add this cache, fat_cache_add() is
143: * assuming to be called after linear search with fat_cache_id.
144: */
145: // fat_cache_add(inode, new_fclus, new_dclus);
146: } else {
147: MSDOS_I(inode)->i_start = new_dclus;
148: MSDOS_I(inode)->i_logstart = new_dclus;
149: /*
150: * Since generic_write_sync() synchronizes regular files later,
151: * we sync here only directories.
152: */
153: if (S_ISDIR(inode->i_mode) && IS_DIRSYNC(inode)) {
154: ret = fat_sync_inode(inode);
155: if (ret)
156: return ret;
157: } else
158: mark_inode_dirty(inode);
159: }
160: if (new_fclus != (inode->i_blocks >> (sbi->cluster_bits - 9))) {
161: fat_fs_error_ratelimit(
162: sb, "clusters badly computed (%d != %llu)", new_fclus,
163: (llu)(inode->i_blocks >> (sbi->cluster_bits - 9)));
164: fat_cache_inval_inode(inode);
165: }
166: inode->i_blocks += nr_cluster << (sbi->cluster_bits - 9);
167:
168: return 0;
169: }
SourceFile:fs/fat/misc.c]
|
| 32/2 |
2026/08/14 01:45 |
llm |
expert |
0m
Model:
Tokens:
input: 20638
output: 36
thoughts: 0
|
| 33/2 |
2026/08/14 01:45 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/fat/inode.c Name:fat_get_block]
Results: map[SourceCode: 183: static int fat_get_block(struct inode *inode, sector_t iblock,
184: struct buffer_head *bh_result, int create)
185: {
186: struct super_block *sb = inode->i_sb;
187: unsigned long max_blocks = bh_result->b_size >> inode->i_blkbits;
188: int err;
189:
190: err = __fat_get_block(inode, iblock, &max_blocks, bh_result, create);
191: if (err)
192: return err;
193: bh_result->b_size = max_blocks << sb->s_blocksize_bits;
194: return 0;
195: }
SourceFile:fs/fat/inode.c]
|
| 34/2 |
2026/08/14 01:45 |
llm |
expert |
0m
Model:
Tokens:
input: 20922
output: 37
thoughts: 0
|
| 35/2 |
2026/08/14 01:45 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/fat/inode.c Name:__fat_get_block]
Results: map[SourceCode: 119: static inline int __fat_get_block(struct inode *inode, sector_t iblock,
120: unsigned long *max_blocks,
121: struct buffer_head *bh_result, int create)
122: {
123: struct super_block *sb = inode->i_sb;
124: struct msdos_sb_info *sbi = MSDOS_SB(sb);
125: unsigned long mapped_blocks;
126: sector_t phys, last_block;
127: int err, offset;
128:
129: err = fat_bmap(inode, iblock, &phys, &mapped_blocks, create, false);
130: if (err)
131: return err;
132: if (phys) {
133: map_bh(bh_result, sb, phys);
134: *max_blocks = min(mapped_blocks, *max_blocks);
135: return 0;
136: }
137: if (!create)
138: return 0;
139:
140: if (iblock != MSDOS_I(inode)->mmu_private >> sb->s_blocksize_bits) {
141: fat_fs_error(sb, "corrupted file size (i_pos %lld, %lld)",
142: MSDOS_I(inode)->i_pos, MSDOS_I(inode)->mmu_private);
143: return -EIO;
144: }
145:
146: last_block = inode->i_blocks >> (sb->s_blocksize_bits - 9);
147: offset = (unsigned long)iblock & (sbi->sec_per_clus - 1);
148: /*
149: * allocate a cluster according to the following.
150: * 1) no more available blocks
151: * 2) not part of fallocate region
152: */
153: if (!offset && !(iblock < last_block)) {
154: /* TODO: multiple cluster allocation would be desirable. */
155: err = fat_add_cluster(inode);
156: if (err)
157: return err;
158: }
159: /* available blocks on this cluster */
160: mapped_blocks = sbi->sec_per_clus - offset;
161:
162: *max_blocks = min(mapped_blocks, *max_blocks);
163: MSDOS_I(inode)->mmu_private += *max_blocks << sb->s_blocksize_bits;
164:
165: err = fat_bmap(inode, iblock, &phys, &mapped_blocks, create, false);
166: if (err)
167: return err;
168: if (!phys) {
169: fat_fs_error(sb,
170: "invalid FAT chain (i_pos %lld, last_block %llu)",
171: MSDOS_I(inode)->i_pos,
172: (unsigned long long)last_block);
173: return -EIO;
174: }
175:
176: BUG_ON(*max_blocks != mapped_blocks);
177: set_buffer_new(bh_result);
178: map_bh(bh_result, sb, phys);
179:
180: return 0;
181: }
SourceFile:fs/fat/inode.c]
|
| 36/2 |
2026/08/14 01:45 |
llm |
expert |
0m
Model:
Tokens:
input: 21988
output: 37
thoughts: 0
|
| 37/2 |
2026/08/14 01:45 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/fat/fatent.c Name:fat_ent_read]
Results: map[SourceCode: 353: int fat_ent_read(struct inode *inode, struct fat_entry *fatent, int entry)
354: {
355: struct super_block *sb = inode->i_sb;
356: struct msdos_sb_info *sbi = MSDOS_SB(inode->i_sb);
357: const struct fatent_operations *ops = sbi->fatent_ops;
358: int err, offset;
359: sector_t blocknr;
360:
361: if (!fat_valid_entry(sbi, entry)) {
362: fatent_brelse(fatent);
363: fat_fs_error_ratelimit(sb, "invalid access to FAT (entry 0x%08x)", entry);
364: return -EIO;
365: }
366:
367: fatent_set_entry(fatent, entry);
368: ops->ent_blocknr(sb, entry, &offset, &blocknr);
369:
370: if (!fat_ent_update_ptr(sb, fatent, offset, blocknr)) {
371: fatent_brelse(fatent);
372: err = ops->ent_bread(sb, fatent, offset, blocknr);
373: if (err)
374: return err;
375: }
376: return ops->ent_get(fatent);
377: }
SourceFile:fs/fat/fatent.c]
|
| 38/2 |
2026/08/14 01:45 |
llm |
expert |
0m
Model:
Tokens:
input: 22476
output: 39
thoughts: 0
|
| 39/2 |
2026/08/14 01:45 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/fat/fatent.c Name:fat32_ent_get]
Results: map[SourceCode: 147: static int fat32_ent_get(struct fat_entry *fatent)
148: {
149: int next = le32_to_cpu(*fatent->u.ent32_p) & 0x0fffffff;
150: WARN_ON((unsigned long)fatent->u.ent32_p & (4 - 1));
151: if (next >= BAD_FAT32)
152: next = FAT_ENT_EOF;
153: return next;
154: }
SourceFile:fs/fat/fatent.c]
|
| 40/2 |
2026/08/14 01:45 |
llm |
expert |
0m
Model:
Tokens:
input: 22697
output: 39
thoughts: 0
|
| 41/2 |
2026/08/14 01:45 |
tool |
codesearch-definition-source |
0m
Error:
Args: map[ContextFile:fs/fat/fatent.c Name:fat32_ent_bread]
Results: map[SourceCode: SourceFile:]
|
| 42/2 |
2026/08/14 01:45 |
llm |
expert |
0m
Model:
Tokens:
input: 22757
output: 37
thoughts: 0
|
| 43/2 |
2026/08/14 01:45 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/fat/fatent.c Name:fat_ent_bread]
Results: map[SourceCode: 102: static int fat_ent_bread(struct super_block *sb, struct fat_entry *fatent,
103: int offset, sector_t blocknr)
104: {
105: const struct fatent_operations *ops = MSDOS_SB(sb)->fatent_ops;
106:
107: WARN_ON(blocknr < MSDOS_SB(sb)->fat_start);
108: fatent->fat_inode = MSDOS_SB(sb)->fat_inode;
109: fatent->bhs[0] = sb_bread(sb, blocknr);
110: if (!fatent->bhs[0]) {
111: fat_msg_ratelimit(sb, KERN_ERR, "FAT read failed (blocknr %llu)",
112: (llu)blocknr);
113: return -EIO;
114: }
115: fatent->nr_bhs = 1;
116: ops->ent_set_ptr(fatent, offset);
117: return 0;
118: }
SourceFile:fs/fat/fatent.c]
|
| 44/2 |
2026/08/14 01:45 |
llm |
expert |
0m
Model:
Tokens:
input: 23132
output: 41
thoughts: 0
|
| 45/2 |
2026/08/14 01:45 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/fat/fatent.c Name:fat32_ent_set_ptr]
Results: map[SourceCode: 63: static void fat32_ent_set_ptr(struct fat_entry *fatent, int offset)
64: {
65: WARN_ON(offset & (4 - 1));
66: fatent->u.ent32_p = (__le32 *)(fatent->bhs[0]->b_data + offset);
67: }
SourceFile:fs/fat/fatent.c]
|
| 46/2 |
2026/08/14 01:45 |
llm |
expert |
0m
Model:
Tokens:
input: 23301
output: 39
thoughts: 0
|
| 47/2 |
2026/08/14 01:45 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/fat/fatent.c Name:fat_ent_update_ptr]
Results: map[SourceCode: 323: static inline int fat_ent_update_ptr(struct super_block *sb,
324: struct fat_entry *fatent,
325: int offset, sector_t blocknr)
326: {
327: struct msdos_sb_info *sbi = MSDOS_SB(sb);
328: const struct fatent_operations *ops = sbi->fatent_ops;
329: struct buffer_head **bhs = fatent->bhs;
330:
331: /* Is this fatent's blocks including this entry? */
332: if (!fatent->nr_bhs || bhs[0]->b_blocknr != blocknr)
333: return 0;
334: if (is_fat12(sbi)) {
335: if ((offset + 1) < sb->s_blocksize) {
336: /* This entry is on bhs[0]. */
337: if (fatent->nr_bhs == 2) {
338: brelse(bhs[1]);
339: fatent->nr_bhs = 1;
340: }
341: } else {
342: /* This entry needs the next block. */
343: if (fatent->nr_bhs != 2)
344: return 0;
345: if (bhs[1]->b_blocknr != (blocknr + 1))
346: return 0;
347: }
348: }
349: ops->ent_set_ptr(fatent, offset);
350: return 1;
351: }
SourceFile:fs/fat/fatent.c]
|
| 48/2 |
2026/08/14 01:45 |
llm |
expert |
0m
Model:
Tokens:
input: 23848
output: 38
thoughts: 0
|
| 49/2 |
2026/08/14 01:45 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/fat/fatent.c Name:fat_ent_blocknr]
Results: map[SourceCode: 33: static void fat_ent_blocknr(struct super_block *sb, int entry,
34: int *offset, sector_t *blocknr)
35: {
36: struct msdos_sb_info *sbi = MSDOS_SB(sb);
37: int bytes = (entry << sbi->fatent_shift);
38: WARN_ON(!fat_valid_entry(sbi, entry));
39: *offset = bytes & (sb->s_blocksize - 1);
40: *blocknr = sbi->fat_start + (bytes >> sb->s_blocksize_bits);
41: }
SourceFile:fs/fat/fatent.c]
|
| 50/2 |
2026/08/14 01:45 |
llm |
expert |
0m
Model:
Tokens:
input: 24091
output: 37
thoughts: 0
|
| 51/2 |
2026/08/14 01:45 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/fat/fatent.c Name:fat_valid_entry]
Results: map[SourceCode: 386: static inline bool fat_valid_entry(struct msdos_sb_info *sbi, int entry)
387: {
388: return FAT_START_ENT <= entry && entry < sbi->max_cluster;
389: }
SourceFile:fs/fat/fat.h]
|
| 52/2 |
2026/08/14 01:45 |
llm |
expert |
0m
Model:
Tokens:
input: 24230
output: 38
thoughts: 0
|
| 53/2 |
2026/08/14 01:45 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/fat/fatent.c Name:fat_sync_bhs]
Results: map[SourceCode: 349: int fat_sync_bhs(struct buffer_head **bhs, int nr_bhs)
350: {
351: int i, err = 0;
352:
353: for (i = 0; i < nr_bhs; i++)
354: write_dirty_buffer(bhs[i], 0);
355:
356: for (i = 0; i < nr_bhs; i++) {
357: wait_on_buffer(bhs[i]);
358: if (!err && !buffer_uptodate(bhs[i]))
359: err = -EIO;
360: }
361: return err;
362: }
SourceFile:fs/fat/misc.c]
|
| 54/2 |
2026/08/14 01:45 |
llm |
expert |
0m
Model:
Tokens:
input: 24512
output: 37
thoughts: 0
|
| 55/2 |
2026/08/14 01:45 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/fat/fatent.c Name:fat_ent_write]
Results: map[SourceCode: 415: int fat_ent_write(struct inode *inode, struct fat_entry *fatent,
416: int new, int wait)
417: {
418: struct super_block *sb = inode->i_sb;
419: const struct fatent_operations *ops = MSDOS_SB(sb)->fatent_ops;
420: int err;
421:
422: ops->ent_put(fatent, new);
423: if (wait) {
424: err = fat_sync_bhs(fatent->bhs, fatent->nr_bhs);
425: if (err)
426: return err;
427: }
428: return fat_mirror_bhs(sb, fatent->bhs, fatent->nr_bhs);
429: }
SourceFile:fs/fat/fatent.c]
|
| 56/2 |
2026/08/14 01:45 |
llm |
expert |
0m
Model:
Tokens:
input: 24822
output: 38
thoughts: 0
|
| 57/2 |
2026/08/14 01:45 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/fat/fatent.c Name:fat_mirror_bhs]
Results: map[SourceCode: 379: /* FIXME: We can write the blocks as more big chunk. */
380: static int fat_mirror_bhs(struct super_block *sb, struct buffer_head **bhs,
381: int nr_bhs)
382: {
383: struct msdos_sb_info *sbi = MSDOS_SB(sb);
384: struct buffer_head *c_bh;
385: int err, n, copy;
386:
387: err = 0;
388: for (copy = 1; copy < sbi->fats; copy++) {
389: sector_t backup_fat = sbi->fat_length * copy;
390:
391: for (n = 0; n < nr_bhs; n++) {
392: c_bh = sb_getblk(sb, backup_fat + bhs[n]->b_blocknr);
393: if (!c_bh) {
394: err = -ENOMEM;
395: goto error;
396: }
397: /* Avoid race with userspace read via bdev */
398: lock_buffer(c_bh);
399: memcpy(c_bh->b_data, bhs[n]->b_data, sb->s_blocksize);
400: set_buffer_uptodate(c_bh);
401: unlock_buffer(c_bh);
402: mmb_mark_buffer_dirty(c_bh,
403: &MSDOS_I(sbi->fat_inode)->i_metadata_bhs);
404: if (sb->s_flags & SB_SYNCHRONOUS)
405: err = sync_dirty_buffer(c_bh);
406: brelse(c_bh);
407: if (err)
408: goto error;
409: }
410: }
411: error:
412: return err;
413: }
SourceFile:fs/fat/fatent.c]
|
| 58/2 |
2026/08/14 01:45 |
llm |
expert |
0m
Model:
Tokens:
input: 25464
output: 39
thoughts: 0
|
| 59/2 |
2026/08/14 01:45 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/fat/fatent.c Name:fat32_ent_put]
Results: map[SourceCode: 190: static void fat32_ent_put(struct fat_entry *fatent, int new)
191: {
192: WARN_ON(new & 0xf0000000);
193: new |= le32_to_cpu(*fatent->u.ent32_p) & ~0x0fffffff;
194: *fatent->u.ent32_p = cpu_to_le32(new);
195: mmb_mark_buffer_dirty(fatent->bhs[0],
196: &MSDOS_I(fatent->fat_inode)->i_metadata_bhs);
197: }
SourceFile:fs/fat/fatent.c]
|
| 60/2 |
2026/08/14 01:45 |
llm |
expert |
0m
Model:
Tokens:
input: 25719
output: 37
thoughts: 2141
Thoughts: **Data Race in FAT Filesystem: A Deep Dive**
Okay, so I'm looking at this KCSAN report, and it's pointing to a data race. Specifically, it's between `fat32_ent_put` and `fat_mirror_bhs` in the FAT filesystem code. My initial thought is that `fat32_ent_put` is doing a read-modify-write on a FAT entry, and `fat_mirror_bhs` is concurrently reading the *entire* block containing that entry. That sounds like a problem.
`fat32_ent_put` modifies the 32-bit FAT entry value through a read-modify-write operation, and its `*fatent->u.ent32_p` is pointing to `fatent->bhs[0]->b_data + offset`, which is within a block buffer. On the other hand, `fat_mirror_bhs` is reading and mirroring the entire buffer to another block. The function `fat_ent_write` calls `ops->ent_put` which then calls `fat32_ent_put`. Then it calls `fat_mirror_bhs`.
My concern is that if two concurrent `fat_ent_write` calls are operating on the *same* FAT block (but different entries within that block), we've got a problem. One could be writing to it in `fat32_ent_put`, modifying `bhs[0]->b_data`, while the other thread is in `fat_mirror_bhs` reading that same `bhs[0]->b_data`. `fat_ent_write` doesn't lock the `bhs[0]` buffer, which is very likely the root of the problem.
Let's trace this back. `fat_ent_write` is called from `fat_chain_add`, which in turn comes from `fat_add_cluster`, and then from `__fat_get_block`. `__fat_get_block` is used during write operations. So we can trigger this race with concurrent writes or, say, `fallocate` operations. The `fat_ent_write` function doesn't seem to contain any locking. The `fat_ent_read` doesn't lock the buffer either, it gets the buffer via `sb_bread` which waits for the buffer but doesn't lock it. This confirms my suspicion: concurrent writes to different entries in the *same* FAT block are possible without protection.
The core issue is that while `fat32_ent_put` does a read-modify-write on a 4-byte entry in the FAT, the `memcpy` in `fat_mirror_bhs` is reading the entire block. The question is: is this tearing an issue? Technically, `memcpy` reads the whole block, which might grab a partially-written 4 byte entry. However, if the primary FAT block is updated correctly and the mirror is used as a backup, then the issue will happen when the primary FAT is updated with the value written by the other thread. Then, the block may be written to the mirror with an incorrect 4 byte entry, but the primary block will be correct. In case of a crash or unclean shutdown, the torn value *might* make it to the mirror, but it would affect just the mirror, and the primary block would have the right values.
KCSAN report indicates a 4-byte read-write race and a 2048-byte read from the `memcpy`, which indicates the tearing may be an issue.
So, is this a real problem? It's not exploitable for privilege escalation or remote code execution. It's not a denial-of-service issue. The concurrent write *might* lead to a torn write of a 32-bit value in the mirror FAT, which is a backup. However, it *is* accessible from unprivileged processes – any user with write access to a mounted FAT filesystem can trigger it by performing concurrent writes. It doesn't require a *malicious* filesystem image, but can be triggered by normal FAT filesystem operations, like concurrent allocation. So the trigger is any normal user writing to a FAT volume. It could be a USB drive or a VM guest.
Double-checking: There are no crashes caused by the data race. `memcpy` reads are safe (just byte reads), and the write in `fat32_ent_put` is also safe.
In summary:
* **Exploitable:** No.
* **Denial of Service:** No.
* **Accessible from Unprivileged Processes:** Yes.
* **User Namespaces:** Yes.
* **VM Guest Trigger:** Yes.
* **Peripheral Trigger:** Yes (e.g
|
| 61/2 |
2026/08/14 01:46 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/fat/fatent.c Name:fat_alloc_clusters]
Results: map[SourceCode: 471: int fat_alloc_clusters(struct inode *inode, int *cluster, int nr_cluster)
472: {
473: struct super_block *sb = inode->i_sb;
474: struct msdos_sb_info *sbi = MSDOS_SB(sb);
475: const struct fatent_operations *ops = sbi->fatent_ops;
476: struct fat_entry fatent, prev_ent;
477: struct buffer_head *bhs[MAX_BUF_PER_PAGE];
478: int i, count, err, nr_bhs, idx_clus;
479:
480: BUG_ON(nr_cluster > (MAX_BUF_PER_PAGE / 2)); /* fixed limit */
481:
482: lock_fat(sbi);
483: if (sbi->free_clusters != -1 && sbi->free_clus_valid &&
484: sbi->free_clusters < nr_cluster) {
485: unlock_fat(sbi);
486: return -ENOSPC;
487: }
488:
489: err = nr_bhs = idx_clus = 0;
490: count = FAT_START_ENT;
491: fatent_init(&prev_ent);
492: fatent_init(&fatent);
493: fatent_set_entry(&fatent, sbi->prev_free + 1);
494: while (count < sbi->max_cluster) {
495: if (fatent.entry >= sbi->max_cluster)
496: fatent.entry = FAT_START_ENT;
497: fatent_set_entry(&fatent, fatent.entry);
498: err = fat_ent_read_block(sb, &fatent);
499: if (err)
500: goto out;
501:
502: /* Find the free entries in a block */
503: do {
504: if (ops->ent_get(&fatent) == FAT_ENT_FREE) {
505: int entry = fatent.entry;
506:
507: /* make the cluster chain */
508: ops->ent_put(&fatent, FAT_ENT_EOF);
509: if (prev_ent.nr_bhs)
510: ops->ent_put(&prev_ent, entry);
511:
512: fat_collect_bhs(bhs, &nr_bhs, &fatent);
513:
514: sbi->prev_free = entry;
515: if (sbi->free_clusters != -1)
516: sbi->free_clusters--;
517:
518: cluster[idx_clus] = entry;
519: idx_clus++;
520: if (idx_clus == nr_cluster)
521: goto out;
522:
523: /*
524: * fat_collect_bhs() gets ref-count of bhs,
525: * so we can still use the prev_ent.
526: */
527: prev_ent = fatent;
528: }
529: count++;
530: if (count == sbi->max_cluster)
531: break;
532: } while (fat_ent_next(sbi, &fatent));
533: }
534:
535: /* Couldn't allocate the free entries */
536: sbi->free_clusters = 0;
537: sbi->free_clus_valid = 1;
538: err = -ENOSPC;
539:
540: out:
541: unlock_fat(sbi);
542: mark_fsinfo_dirty(sb);
543: fatent_brelse(&fatent);
544: if (!err) {
545: if (inode_needs_sync(inode))
546: err = fat_sync_bhs(bhs, nr_bhs);
547: if (!err)
548: err = fat_mirror_bhs(sb, bhs, nr_bhs);
549: }
550: for (i = 0; i < nr_bhs; i++)
551: brelse(bhs[i]);
552:
553: if (err && idx_clus)
554: fat_free_clusters(inode, cluster[0]);
555:
556: return err;
557: }
SourceFile:fs/fat/fatent.c]
|
| 62/2 |
2026/08/14 01:46 |
llm |
expert |
0m
Model:
Tokens:
input: 28145
output: 37
thoughts: 0
|
| 63/2 |
2026/08/14 01:46 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/fat/fatent.c Name:fat_free_clusters]
Results: map[SourceCode: 559: int fat_free_clusters(struct inode *inode, int cluster)
560: {
561: struct super_block *sb = inode->i_sb;
562: struct msdos_sb_info *sbi = MSDOS_SB(sb);
563: const struct fatent_operations *ops = sbi->fatent_ops;
564: struct fat_entry fatent;
565: struct buffer_head *bhs[MAX_BUF_PER_PAGE];
566: int i, err, nr_bhs;
567: int first_cl = cluster, dirty_fsinfo = 0;
568:
569: nr_bhs = 0;
570: fatent_init(&fatent);
571: lock_fat(sbi);
572: do {
573: cluster = fat_ent_read(inode, &fatent, cluster);
574: if (cluster < 0) {
575: err = cluster;
576: goto error;
577: } else if (cluster == FAT_ENT_FREE) {
578: fat_fs_error(sb, "%s: deleting FAT entry beyond EOF",
579: __func__);
580: err = -EIO;
581: goto error;
582: }
583:
584: if (sbi->options.discard) {
585: /*
586: * Issue discard for the sectors we no longer
587: * care about, batching contiguous clusters
588: * into one request
589: */
590: if (cluster != fatent.entry + 1) {
591: int nr_clus = fatent.entry - first_cl + 1;
592:
593: sb_issue_discard(sb,
594: fat_clus_to_blknr(sbi, first_cl),
595: nr_clus * sbi->sec_per_clus,
596: GFP_NOFS, 0);
597:
598: first_cl = cluster;
599: }
600: }
601:
602: ops->ent_put(&fatent, FAT_ENT_FREE);
603: if (sbi->free_clusters != -1) {
604: sbi->free_clusters++;
605: dirty_fsinfo = 1;
606: }
607:
608: if (nr_bhs + fatent.nr_bhs > MAX_BUF_PER_PAGE) {
609: if (sb->s_flags & SB_SYNCHRONOUS) {
610: err = fat_sync_bhs(bhs, nr_bhs);
611: if (err)
612: goto error;
613: }
614: err = fat_mirror_bhs(sb, bhs, nr_bhs);
615: if (err)
616: goto error;
617: for (i = 0; i < nr_bhs; i++)
618: brelse(bhs[i]);
619: nr_bhs = 0;
620: }
621: fat_collect_bhs(bhs, &nr_bhs, &fatent);
622: } while (cluster != FAT_ENT_EOF);
623:
624: if (sb->s_flags & SB_SYNCHRONOUS) {
625: err = fat_sync_bhs(bhs, nr_bhs);
626: if (err)
627: goto error;
628: }
629: err = fat_mirror_bhs(sb, bhs, nr_bhs);
630: error:
631: fatent_brelse(&fatent);
632: for (i = 0; i < nr_bhs; i++)
633: brelse(bhs[i]);
634: unlock_fat(sbi);
635: if (dirty_fsinfo)
636: mark_fsinfo_dirty(sb);
637:
638: return err;
639: }
SourceFile:fs/fat/fatent.c]
|
| 64/2 |
2026/08/14 01:46 |
llm |
expert |
0m
Model:
Tokens:
input: 29434
output: 38
thoughts: 0
|
| 65/2 |
2026/08/14 01:46 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/fat/fatent.c Name:fat_collect_bhs]
Results: map[SourceCode: 453: static void fat_collect_bhs(struct buffer_head **bhs, int *nr_bhs,
454: struct fat_entry *fatent)
455: {
456: int n, i;
457:
458: for (n = 0; n < fatent->nr_bhs; n++) {
459: for (i = 0; i < *nr_bhs; i++) {
460: if (fatent->bhs[n] == bhs[i])
461: break;
462: }
463: if (i == *nr_bhs) {
464: get_bh(fatent->bhs[n]);
465: bhs[i] = fatent->bhs[n];
466: (*nr_bhs)++;
467: }
468: }
469: }
SourceFile:fs/fat/fatent.c]
|
| 66/2 |
2026/08/14 01:46 |
llm |
expert |
0m
Model:
Tokens:
input: 29764
output: 39
thoughts: 0
|
| 67/2 |
2026/08/14 01:46 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/fat/fatent.c Name:fat_ent_read_block]
Results: map[SourceCode: 441: static inline int fat_ent_read_block(struct super_block *sb,
442: struct fat_entry *fatent)
443: {
444: const struct fatent_operations *ops = MSDOS_SB(sb)->fatent_ops;
445: sector_t blocknr;
446: int offset;
447:
448: fatent_brelse(fatent);
449: ops->ent_blocknr(sb, fatent->entry, &offset, &blocknr);
450: return ops->ent_bread(sb, fatent, offset, blocknr);
451: }
SourceFile:fs/fat/fatent.c]
|
| 68/2 |
2026/08/14 01:46 |
llm |
expert |
0m
Model:
Tokens:
input: 30018
output: 37
thoughts: 0
|
| 69/2 |
2026/08/14 01:46 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/fat/fatent.c Name:fat_ent_next]
Results: map[SourceCode: 431: static inline int fat_ent_next(struct msdos_sb_info *sbi,
432: struct fat_entry *fatent)
433: {
434: if (sbi->fatent_ops->ent_next(fatent)) {
435: if (fatent->entry < sbi->max_cluster)
436: return 1;
437: }
438: return 0;
439: }
SourceFile:fs/fat/fatent.c]
|
| 70/2 |
2026/08/14 01:46 |
llm |
expert |
0m
Model:
Tokens:
input: 30222
output: 39
thoughts: 0
|
| 71/2 |
2026/08/14 01:46 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/fat/fatent.c Name:fat32_ent_next]
Results: map[SourceCode: 244: static int fat32_ent_next(struct fat_entry *fatent)
245: {
246: const struct buffer_head *bh = fatent->bhs[0];
247: fatent->entry++;
248: if (fatent->u.ent32_p < (__le32 *)(bh->b_data + (bh->b_size - 4))) {
249: fatent->u.ent32_p++;
250: return 1;
251: }
252: fatent->u.ent32_p = NULL;
253: return 0;
254: }
SourceFile:fs/fat/fatent.c]
|
| 72/2 |
2026/08/14 01:46 |
llm |
expert |
0m
Model:
Tokens:
input: 30478
output: 37
thoughts: 0
|
| 73/2 |
2026/08/14 01:46 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/fat/fatent.c Name:fat_ent_write]
Results: map[SourceCode: 415: int fat_ent_write(struct inode *inode, struct fat_entry *fatent,
416: int new, int wait)
417: {
418: struct super_block *sb = inode->i_sb;
419: const struct fatent_operations *ops = MSDOS_SB(sb)->fatent_ops;
420: int err;
421:
422: ops->ent_put(fatent, new);
423: if (wait) {
424: err = fat_sync_bhs(fatent->bhs, fatent->nr_bhs);
425: if (err)
426: return err;
427: }
428: return fat_mirror_bhs(sb, fatent->bhs, fatent->nr_bhs);
429: }
SourceFile:fs/fat/fatent.c]
|
| 74/2 |
2026/08/14 01:46 |
llm |
expert |
0m
Model:
Tokens:
input: 30788
output: 36
thoughts: 0
|
| 75/2 |
2026/08/14 01:46 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/fat/misc.c Name:fat_chain_add]
Results: map[SourceCode: 103: /*
104: * fat_chain_add() adds a new cluster to the chain of clusters represented
105: * by inode.
106: */
107: int fat_chain_add(struct inode *inode, int new_dclus, int nr_cluster)
108: {
109: struct super_block *sb = inode->i_sb;
110: struct msdos_sb_info *sbi = MSDOS_SB(sb);
111: int ret, new_fclus, last;
112:
113: /*
114: * We must locate the last cluster of the file to add this new
115: * one (new_dclus) to the end of the link list (the FAT).
116: */
117: last = new_fclus = 0;
118: if (MSDOS_I(inode)->i_start) {
119: int fclus, dclus;
120:
121: ret = fat_get_cluster(inode, FAT_ENT_EOF, &fclus, &dclus);
122: if (ret < 0)
123: return ret;
124: new_fclus = fclus + 1;
125: last = dclus;
126: }
127:
128: /* add new one to the last of the cluster chain */
129: if (last) {
130: struct fat_entry fatent;
131:
132: fatent_init(&fatent);
133: ret = fat_ent_read(inode, &fatent, last);
134: if (ret >= 0) {
135: int wait = inode_needs_sync(inode);
136: ret = fat_ent_write(inode, &fatent, new_dclus, wait);
137: fatent_brelse(&fatent);
138: }
139: if (ret < 0)
140: return ret;
141: /*
142: * FIXME:Although we can add this cache, fat_cache_add() is
143: * assuming to be called after linear search with fat_cache_id.
144: */
145: // fat_cache_add(inode, new_fclus, new_dclus);
146: } else {
147: MSDOS_I(inode)->i_start = new_dclus;
148: MSDOS_I(inode)->i_logstart = new_dclus;
149: /*
150: * Since generic_write_sync() synchronizes regular files later,
151: * we sync here only directories.
152: */
153: if (S_ISDIR(inode->i_mode) && IS_DIRSYNC(inode)) {
154: ret = fat_sync_inode(inode);
155: if (ret)
156: return ret;
157: } else
158: mark_inode_dirty(inode);
159: }
160: if (new_fclus != (inode->i_blocks >> (sbi->cluster_bits - 9))) {
161: fat_fs_error_ratelimit(
162: sb, "clusters badly computed (%d != %llu)", new_fclus,
163: (llu)(inode->i_blocks >> (sbi->cluster_bits - 9)));
164: fat_cache_inval_inode(inode);
165: }
166: inode->i_blocks += nr_cluster << (sbi->cluster_bits - 9);
167:
168: return 0;
169: }
SourceFile:fs/fat/misc.c]
|
| 76/2 |
2026/08/14 01:46 |
llm |
expert |
0m
Model:
Tokens:
input: 31914
output: 36
thoughts: 0
|
| 77/2 |
2026/08/14 01:46 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/fat/inode.c Name:fat_add_cluster]
Results: map[SourceCode: 104: int fat_add_cluster(struct inode *inode)
105: {
106: int err, cluster;
107:
108: err = fat_alloc_clusters(inode, &cluster, 1);
109: if (err)
110: return err;
111: /* FIXME: this cluster should be added after data of this
112: * cluster is writed */
113: err = fat_chain_add(inode, cluster, 1);
114: if (err)
115: fat_free_clusters(inode, cluster);
116: return err;
117: }
SourceFile:fs/fat/inode.c]
|
| 78/2 |
2026/08/14 01:46 |
llm |
expert |
0m
Model:
Tokens:
input: 32170
output: 37
thoughts: 0
|
| 79/2 |
2026/08/14 01:46 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/fat/inode.c Name:__fat_get_block]
Results: map[SourceCode: 119: static inline int __fat_get_block(struct inode *inode, sector_t iblock,
120: unsigned long *max_blocks,
121: struct buffer_head *bh_result, int create)
122: {
123: struct super_block *sb = inode->i_sb;
124: struct msdos_sb_info *sbi = MSDOS_SB(sb);
125: unsigned long mapped_blocks;
126: sector_t phys, last_block;
127: int err, offset;
128:
129: err = fat_bmap(inode, iblock, &phys, &mapped_blocks, create, false);
130: if (err)
131: return err;
132: if (phys) {
133: map_bh(bh_result, sb, phys);
134: *max_blocks = min(mapped_blocks, *max_blocks);
135: return 0;
136: }
137: if (!create)
138: return 0;
139:
140: if (iblock != MSDOS_I(inode)->mmu_private >> sb->s_blocksize_bits) {
141: fat_fs_error(sb, "corrupted file size (i_pos %lld, %lld)",
142: MSDOS_I(inode)->i_pos, MSDOS_I(inode)->mmu_private);
143: return -EIO;
144: }
145:
146: last_block = inode->i_blocks >> (sb->s_blocksize_bits - 9);
147: offset = (unsigned long)iblock & (sbi->sec_per_clus - 1);
148: /*
149: * allocate a cluster according to the following.
150: * 1) no more available blocks
151: * 2) not part of fallocate region
152: */
153: if (!offset && !(iblock < last_block)) {
154: /* TODO: multiple cluster allocation would be desirable. */
155: err = fat_add_cluster(inode);
156: if (err)
157: return err;
158: }
159: /* available blocks on this cluster */
160: mapped_blocks = sbi->sec_per_clus - offset;
161:
162: *max_blocks = min(mapped_blocks, *max_blocks);
163: MSDOS_I(inode)->mmu_private += *max_blocks << sb->s_blocksize_bits;
164:
165: err = fat_bmap(inode, iblock, &phys, &mapped_blocks, create, false);
166: if (err)
167: return err;
168: if (!phys) {
169: fat_fs_error(sb,
170: "invalid FAT chain (i_pos %lld, last_block %llu)",
171: MSDOS_I(inode)->i_pos,
172: (unsigned long long)last_block);
173: return -EIO;
174: }
175:
176: BUG_ON(*max_blocks != mapped_blocks);
177: set_buffer_new(bh_result);
178: map_bh(bh_result, sb, phys);
179:
180: return 0;
181: }
SourceFile:fs/fat/inode.c]
|
| 80/2 |
2026/08/14 01:46 |
llm |
expert |
0m
Model:
Tokens:
input: 33236
output: 36
thoughts: 0
|
| 81/2 |
2026/08/14 01:46 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/fat/inode.c Name:fat_get_block]
Results: map[SourceCode: 183: static int fat_get_block(struct inode *inode, sector_t iblock,
184: struct buffer_head *bh_result, int create)
185: {
186: struct super_block *sb = inode->i_sb;
187: unsigned long max_blocks = bh_result->b_size >> inode->i_blkbits;
188: int err;
189:
190: err = __fat_get_block(inode, iblock, &max_blocks, bh_result, create);
191: if (err)
192: return err;
193: bh_result->b_size = max_blocks << sb->s_blocksize_bits;
194: return 0;
195: }
SourceFile:fs/fat/inode.c]
|
| 82/2 |
2026/08/14 01:46 |
llm |
expert |
0m
Model:
Tokens:
input: 33520
output: 36
thoughts: 0
|
| 83/2 |
2026/08/14 01:47 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/fat/inode.c Name:fat_write_begin]
Results: map[SourceCode: 223: static int fat_write_begin(const struct kiocb *iocb,
224: struct address_space *mapping,
225: loff_t pos, unsigned len,
226: struct folio **foliop, void **fsdata)
227: {
228: int err;
229:
230: err = cont_write_begin(iocb, mapping, pos, len,
231: foliop, fsdata, fat_get_block,
232: &MSDOS_I(mapping->host)->mmu_private);
233: if (err < 0)
234: fat_write_failed(mapping, pos + len);
235: return err;
236: }
SourceFile:fs/fat/inode.c]
|
| 84/2 |
2026/08/14 01:47 |
llm |
expert |
0m
Model:
Tokens:
input: 33801
output: 36
thoughts: 0
|
| 85/2 |
2026/08/14 01:47 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/fat/file.c Name:fat_cont_expand]
Results: map[SourceCode: 221: static int fat_cont_expand(struct inode *inode, loff_t size)
222: {
223: struct address_space *mapping = inode->i_mapping;
224: loff_t start = inode->i_size, count = size - inode->i_size;
225: int err;
226:
227: err = generic_cont_expand_simple(inode, size);
228: if (err)
229: goto out;
230:
231: fat_truncate_time(inode, NULL, FAT_UPDATE_CMTIME);
232: mark_inode_dirty(inode);
233: if (IS_SYNC(inode)) {
234: int err2;
235:
236: /*
237: * Opencode syncing since we don't have a file open to use
238: * standard fsync path.
239: */
240: err = filemap_fdatawrite_range(mapping, start,
241: start + count - 1);
242: err2 = mmb_sync(&MSDOS_I(inode)->i_metadata_bhs);
243: if (!err)
244: err = err2;
245: err2 = write_inode_now(inode, 1);
246: if (!err)
247: err = err2;
248: if (!err) {
249: err = filemap_fdatawait_range(mapping, start,
250: start + count - 1);
251: }
252: }
253: out:
254: return err;
255: }
SourceFile:fs/fat/file.c]
|
| 86/2 |
2026/08/14 01:47 |
llm |
expert |
0m
Model:
Tokens:
input: 34363
output: 35
thoughts: 0
|
| 87/2 |
2026/08/14 01:47 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/fat/file.c Name:fat_fallocate]
Results: map[SourceCode: 257: /*
258: * Preallocate space for a file. This implements fat's fallocate file
259: * operation, which gets called from sys_fallocate system call. User
260: * space requests len bytes at offset. If FALLOC_FL_KEEP_SIZE is set
261: * we just allocate clusters without zeroing them out. Otherwise we
262: * allocate and zero out clusters via an expanding truncate.
263: */
264: static long fat_fallocate(struct file *file, int mode,
265: loff_t offset, loff_t len)
266: {
267: int nr_cluster; /* Number of clusters to be allocated */
268: loff_t mm_bytes; /* Number of bytes to be allocated for file */
269: loff_t ondisksize; /* block aligned on-disk size in bytes*/
270: struct inode *inode = file->f_mapping->host;
271: struct super_block *sb = inode->i_sb;
272: struct msdos_sb_info *sbi = MSDOS_SB(sb);
273: int err = 0;
274:
275: /* No support for hole punch or other fallocate flags. */
276: if (mode & ~FALLOC_FL_KEEP_SIZE)
277: return -EOPNOTSUPP;
278:
279: /* No support for dir */
280: if (!S_ISREG(inode->i_mode))
281: return -EOPNOTSUPP;
282:
283: inode_lock(inode);
284: if (mode & FALLOC_FL_KEEP_SIZE) {
285: ondisksize = inode->i_blocks << 9;
286: if ((offset + len) <= ondisksize)
287: goto error;
288:
289: /* First compute the number of clusters to be allocated */
290: mm_bytes = offset + len - ondisksize;
291: nr_cluster = (mm_bytes + (sbi->cluster_size - 1)) >>
292: sbi->cluster_bits;
293:
294: /* Start the allocation.We are not zeroing out the clusters */
295: while (nr_cluster-- > 0) {
296: err = fat_add_cluster(inode);
297: if (err)
298: goto error;
299: }
300: } else {
301: if ((offset + len) <= i_size_read(inode))
302: goto error;
303:
304: /* This is just an expanding truncate */
305: err = fat_cont_expand(inode, (offset + len));
306: }
307:
308: error:
309: inode_unlock(inode);
310: return err;
311: }
SourceFile:fs/fat/file.c]
|
| 88/2 |
2026/08/14 01:47 |
llm |
expert |
0m
Model:
Tokens:
input: 35275
output: 35
thoughts: 0
|
| 89/2 |
2026/08/14 01:47 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/filemap.c Name:generic_perform_write]
Results: map[SourceCode:4336: ssize_t generic_perform_write(struct kiocb *iocb, struct iov_iter *i)
4337: {
4338: struct file *file = iocb->ki_filp;
4339: loff_t pos = iocb->ki_pos;
4340: struct address_space *mapping = file->f_mapping;
4341: const struct address_space_operations *a_ops = mapping->a_ops;
4342: size_t chunk = mapping_max_folio_size(mapping);
4343: long status = 0;
4344: ssize_t written = 0;
4345:
4346: do {
4347: struct folio *folio;
4348: size_t offset; /* Offset into folio */
4349: size_t bytes; /* Bytes to write to folio */
4350: size_t copied; /* Bytes copied from user */
4351: void *fsdata = NULL;
4352:
4353: bytes = iov_iter_count(i);
4354: retry:
4355: offset = pos & (chunk - 1);
4356: bytes = min(chunk - offset, bytes);
4357: balance_dirty_pages_ratelimited(mapping);
4358:
4359: if (fatal_signal_pending(current)) {
4360: status = -EINTR;
4361: break;
4362: }
4363:
4364: status = a_ops->write_begin(iocb, mapping, pos, bytes,
4365: &folio, &fsdata);
4366: if (unlikely(status < 0))
4367: break;
4368:
4369: offset = offset_in_folio(folio, pos);
4370: if (bytes > folio_size(folio) - offset)
4371: bytes = folio_size(folio) - offset;
4372:
4373: if (mapping_writably_mapped(mapping))
4374: flush_dcache_folio(folio);
4375:
4376: /*
4377: * Faults here on mmap()s can recurse into arbitrary
4378: * filesystem code. Lots of locks are held that can
4379: * deadlock. Use an atomic copy to avoid deadlocking
4380: * in page fault handling.
4381: */
4382: copied = copy_folio_from_iter_atomic(folio, offset, bytes, i);
4383: flush_dcache_folio(folio);
4384:
4385: status = a_ops->write_end(iocb, mapping, pos, bytes, copied,
4386: folio, fsdata);
4387: if (unlikely(status != copied)) {
4388: iov_iter_revert(i, copied - max(status, 0L));
4389: if (unlikely(status < 0))
4390: break;
4391: }
4392: cond_resched();
4393:
4394: if (unlikely(status == 0)) {
4395: /*
4396: * A short copy made ->write_end() reject the
4397: * thing entirely. Might be memory poisoning
4398: * halfway through, might be a race with munmap,
4399: * might be severe memory pressure.
4400: */
4401: if (chunk > PAGE_SIZE)
4402: chunk /= 2;
4403: if (copied) {
4404: bytes = copied;
4405: goto retry;
4406: }
4407:
4408: /*
4409: * 'folio' is now unlocked and faults on it can be
4410: * handled. Ensure forward progress by trying to
4411: * fault it in now.
4412: */
4413: if (fault_in_iov_iter_readable(i, bytes) == bytes) {
4414: status = -EFAULT;
4415: break;
4416: }
4417: } else {
4418: pos += status;
4419: written += status;
4420: }
4421: } while (iov_iter_count(i));
4422:
4423: if (!written)
4424: return status;
4425: iocb->ki_pos += written;
4426: return written;
4427: }
SourceFile:mm/filemap.c]
|
| 90/2 |
2026/08/14 01:47 |
llm |
expert |
0m
Model:
Tokens:
input: 36634
output: 38
thoughts: 0
|
| 91/2 |
2026/08/14 01:47 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/fat/fatent.c Name:fat_mirror_bhs]
Results: map[SourceCode: 379: /* FIXME: We can write the blocks as more big chunk. */
380: static int fat_mirror_bhs(struct super_block *sb, struct buffer_head **bhs,
381: int nr_bhs)
382: {
383: struct msdos_sb_info *sbi = MSDOS_SB(sb);
384: struct buffer_head *c_bh;
385: int err, n, copy;
386:
387: err = 0;
388: for (copy = 1; copy < sbi->fats; copy++) {
389: sector_t backup_fat = sbi->fat_length * copy;
390:
391: for (n = 0; n < nr_bhs; n++) {
392: c_bh = sb_getblk(sb, backup_fat + bhs[n]->b_blocknr);
393: if (!c_bh) {
394: err = -ENOMEM;
395: goto error;
396: }
397: /* Avoid race with userspace read via bdev */
398: lock_buffer(c_bh);
399: memcpy(c_bh->b_data, bhs[n]->b_data, sb->s_blocksize);
400: set_buffer_uptodate(c_bh);
401: unlock_buffer(c_bh);
402: mmb_mark_buffer_dirty(c_bh,
403: &MSDOS_I(sbi->fat_inode)->i_metadata_bhs);
404: if (sb->s_flags & SB_SYNCHRONOUS)
405: err = sync_dirty_buffer(c_bh);
406: brelse(c_bh);
407: if (err)
408: goto error;
409: }
410: }
411: error:
412: return err;
413: }
SourceFile:fs/fat/fatent.c]
|
| 92/2 |
2026/08/14 01:47 |
llm |
expert |
0m
Model:
Tokens:
input: 37276
output: 39
thoughts: 0
|
| 93/2 |
2026/08/14 01:47 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/fat/fatent.c Name:fat32_ent_put]
Results: map[SourceCode: 190: static void fat32_ent_put(struct fat_entry *fatent, int new)
191: {
192: WARN_ON(new & 0xf0000000);
193: new |= le32_to_cpu(*fatent->u.ent32_p) & ~0x0fffffff;
194: *fatent->u.ent32_p = cpu_to_le32(new);
195: mmb_mark_buffer_dirty(fatent->bhs[0],
196: &MSDOS_I(fatent->fat_inode)->i_metadata_bhs);
197: }
SourceFile:fs/fat/fatent.c]
|
| 94/2 |
2026/08/14 01:47 |
llm |
expert |
0m
Model:
Tokens:
input: 37531
output: 66
thoughts: 0
|
| 95/2 |
2026/08/14 01:47 |
tool |
set-results |
0m
Args: map[DenialOfService:false Exploitable:false FilesystemTrigger:true NetworkTrigger:false PeripheralTrigger:true RemoteTrigger:false Unprivileged:true UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
Results: map[DenialOfService:false Exploitable:false FilesystemTrigger:true NetworkTrigger:false PeripheralTrigger:true RemoteTrigger:false Unprivileged:true UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
|
| 96/2 |
2026/08/14 01:47 |
llm |
expert |
0m
Model:
Tokens:
input: 37661
output: 531
thoughts: 4265
Thoughts: **Analysis of KCSAN Data Race in FAT Filesystem**
Okay, here's the breakdown. I've got a KCSAN data race on my hands in the FAT filesystem, specifically in `fat32_ent_put` and `fat_mirror_bhs`. Seems like one CPU (Task 1) is writing to `fatent->u.ent32_p` within `fat32_ent_put`, which is just a 32-bit modification of a FAT entry inside a buffer head. At the same time, another CPU (Task 2) is reading from the same buffer head's data via `memcpy` in `fat_mirror_bhs`. `fat_mirror_bhs` is copying the *entire* block to a backup FAT area. The issue boils down to this: one thread updates the FAT entry, another copies the whole block *including* that updated entry to a backup.
Looking deeper, `fat_mirror_bhs` is called from `fat_ent_write`, and it makes sense: the intent is to keep the backup copy in sync. `fat_ent_write` is responsible for updating the FAT entry and mirroring the changes. The data race arises when multiple threads call `fat_ent_write` on different entries within the *same* buffer block. That's the crucial point.
Initially, I was a bit concerned. What if the copy reads a partially written entry? Or, what if the write is "missed" by the copy? But then I realized, the thread doing the write *also* calls `fat_mirror_bhs`. So, eventually, the backup *will* be consistent. Even if the backup temporarily has a "torn" value, the next call from the updating thread will fix it.
I played through a few scenarios in my head. One thread modifying an entry (A), and another thread copying the block (potentially containing A and other entries). The order of operations is key, but the subsequent call to `fat_mirror_bhs` by the modifying thread always ensures the correct backup. Even if the memcpy in `fat_mirror_bhs` gets a torn write, it gets overwritten.
Now, could there be a read-modify-write race *on the same* FAT entry? Potentially, if two threads *concurrently* tried to modify the *same* cluster's FAT entry. However, that's supposed to be protected by the `lock_fat(sbi)` or `inode_lock(inode)` mechanisms. I reviewed `fat_alloc_clusters` and confirmed that it uses these locks. So, I figured the concurrent modification on the *same* entry should be prevented.
However, the analysis revealed the core: threads update *different* FAT entries within the same buffer head (different inodes). Here `fat_chain_add` comes into play. It's called after the FAT lock is released in the `fat_alloc_clusters` flow, and it's calling the `ops->ent_put` and `fat_mirror_bhs`. So, while the inodes are locked (for modification on the last cluster for the allocated file), the thread can access the same block concurrently from different threads for different inodes.
This is a data race. The writes in `fat32_ent_put` and memcpy in `fat_mirror_bhs` race.
The writes are 32-bit, so they are atomic.
So, here's my verdict: this *is* a data race, but it's a benign one. No memory corruption, no out-of-bounds access, no control flow hijacking. The worst-case scenario is a temporarily inconsistent backup FAT. It's not exploitable because the backup gets eventually consistent.
Can a regular user trigger this? Yes. Any user with write access to a FAT filesystem can trigger the race by creating or modifying files, especially if they are running multiple programs or threads that perform these actions. FAT filesystems are commonly used for USB drives.
|
| 97/1 |
2026/08/14 01:48 |
action |
format-explanation |
0m
Results: map[Explanation:An analysis of the bug report and the corresponding kernel source code reveals
the following:
**Bug Mechanism:**
The bug is a data race detected by the Kernel Concurrency Sanitizer (KCSAN) in
the FAT filesystem driver.
- **Thread 1 (CPU 1)** is executing `fat32_ent_put`, which performs a 4-byte
write to update a specific FAT entry within a cached buffer head block. This
happens during `fat_chain_add` when a file is being appended or allocated new
clusters (e.g., via `fallocate`).
- **Thread 2 (CPU 0)** is concurrently executing `fat_mirror_bhs`, which uses
`memcpy` to copy the entire buffer head block (e.g., 2048 bytes) to a backup FAT
block. This happens after another file operation has modified the FAT and is now
syncing the changes to the backup FAT.
Because `fat_chain_add` modifies the file's last FAT entry without holding the
global FAT lock (it relies on the file's `inode_lock`), it can write to the
buffer head at the exact same time another thread is copying that same buffer
head to the backup FAT.
**Security Impact:**
This data race is **benign** and **not exploitable**.
When Thread 2's `memcpy` reads the buffer head, it might read a torn or stale
4-byte write from Thread 1. However, Thread 1's operation (`fat_ent_write`) is
structured such that immediately after calling `fat32_ent_put`, it will also
call `fat_mirror_bhs` to copy the updated buffer head to the backup FAT.
Consequently, even if Thread 2 writes an inconsistent state to the backup FAT,
Thread 1 will immediately overwrite it with the correct, fully updated block.
There are no memory safety violations (like out-of-bounds accesses or
use-after-frees), no control-flow hijacks, and no kernel panics or deadlocks
resulting from this race. The primary FAT remains perfectly consistent, and the
backup FAT achieves eventual consistency.
**Trigger Vectors:**
The bug can be triggered by any unprivileged user who has write access to a
mounted FAT filesystem (e.g., by calling `fallocate` or `write` concurrently
from multiple threads). This makes it reachable via physically plugged-in
peripheral devices (like USB flash drives formatted with FAT32) or by mounting a
malicious/crafted FAT filesystem image.]
|