| 0/0 |
2026/01/14 17:05 |
flow |
assessment-kcsan |
2m
Model:
Results: map[Benign:true Confident:true Explanation:The data race occurs on the `ra_pages` field of `struct file_ra_state` within the `shrink_readahead_size_eio` function in `mm/filemap.c`.
### Analysis:
1. **Context**: The function `shrink_readahead_size_eio` is called when a page read error (`-EIO`) occurs during a `filemap_read_folio` operation. Its purpose is to reduce the readahead size (`ra_pages`) to avoid repeatedly attempting large, failing I/O requests, which can be particularly problematic on certain media like CD/DVDs.
2. **The Race**: The code performs `ra->ra_pages /= 4;`, which is a read-modify-write operation. KCSAN detected that two tasks (one doing a `write` call that faulted on a mmapped buffer, and another doing `mlock` or `mmap(MAP_POPULATE)`) encountered I/O errors on the same file concurrently and attempted to update `ra_pages` at the same time.
3. **Impact**: `ra_pages` is a heuristic hint for the readahead algorithm. If two threads race on this update, the final value of `ra_pages` might be slightly different than if the operations were serialized (e.g., it might be divided by 4 once instead of twice). However, any value resulting from this race is a valid readahead size. The variable is typically an `unsigned int`, and on the reported architecture (x86), a 4-byte aligned write is atomic, so no "torn" values that aren't a result of the division would be seen by other readers.
4. **Benign Nature**: Readahead state in the Linux kernel is generally handled without strict locking for performance reasons, as it only affects performance (the efficiency of future reads) and not the correctness or stability of the system. A slightly "incorrect" readahead size due to a race does not lead to memory corruption, crashes, or invalid logic.
### Conclusion:
This data race is **benign**. It is a race on a heuristic variable where the exact value is not critical for system stability or correctness. Many similar readahead-related races exist in the kernel and are tolerated. While it could be silenced using `WRITE_ONCE()` and `READ_ONCE()`, it does not pose a functional risk.]
|
| 1/1 |
2026/01/14 17:05 |
action |
kernel-checkouter |
0m
Model:
Results: map[KernelSrc:/usr/local/google/home/dvyukov/syzkaller/agent/workdir/cache/src/4f2ef7a08ce0fae452c2096cb574d40164904aba]
|
| 2/1 |
2026/01/14 17:05 |
action |
kernel-builder |
0m
Model:
Results: map[KernelObj:/usr/local/google/home/dvyukov/syzkaller/agent/workdir/cache/build/a577cb68f61346d251b5dc6cbe3dac8c8dcfa47a]
|
| 3/1 |
2026/01/14 17:05 |
action |
codesearch-prepare |
0m
Model:
Results: map[Index:codesearch-index]
|
| 4/1 |
2026/01/14 17:05 |
agent |
expert |
2m
Model:
Results: map[Benign:true Confident:true]
Instruction:
You are an experienced Linux kernel developer tasked with determining if the given kernel
data race is benign or not. The data race report is from KCSAN tool.
It contains 2 stack traces of the memory accesses that constitute a data race.
A "benign" data races are on a simple int/bool variable or similar field,
and the accesses are not supposed to be protected by any mutual exclusion primitives.
Common examples of such "benign" data races are accesses to various flags fields,
statistics counters, and similar. A "benign" data race does not lead to memory corruption/crash
with a conservative compiler that compiles memory accesses to primitive types
effectively as atomic.
A non-benign (or "harmful" data race) can lead to corruption/crash even with
a conservative compiler that compiles memory accesses to primitive types
effectively as atomic. A common example of a "harmful" data races is race on
a complex container (list/hashmap/etc), where accesses are supposed to be protected
by a mutual exclusion primitive.
In the final reply explain why you think the given data race is benign or is harmful.
Use the provided tools to confirm any assumptions, variables/fields being accessed, etc.
In particular, don't make assumptions about the kernel source code,
use codesearch tools to read the actual source code.
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 data race report is:
Buffer I/O error on dev loop3, logical block 2072, async page read
==================================================================
BUG: KCSAN: data-race in filemap_read_folio / filemap_read_folio
read-write to 0xffff888117725760 of 4 bytes by task 6134 on cpu 0:
shrink_readahead_size_eio mm/filemap.c:2386 [inline]
filemap_read_folio+0xea/0x110 mm/filemap.c:2456
filemap_fault+0x58f/0xb60 mm/filemap.c:3581
__do_fault+0xbc/0x200 mm/memory.c:5281
do_read_fault mm/memory.c:5716 [inline]
do_fault mm/memory.c:5850 [inline]
do_pte_missing mm/memory.c:4362 [inline]
handle_pte_fault mm/memory.c:6195 [inline]
__handle_mm_fault mm/memory.c:6336 [inline]
handle_mm_fault+0xf78/0x2be0 mm/memory.c:6505
faultin_page mm/gup.c:1126 [inline]
__get_user_pages+0x102a/0x1ed0 mm/gup.c:1428
populate_vma_page_range mm/gup.c:1860 [inline]
__mm_populate+0x243/0x3a0 mm/gup.c:1963
mm_populate include/linux/mm.h:3478 [inline]
vm_mmap_pgoff+0x232/0x2e0 mm/util.c:586
ksys_mmap_pgoff+0x268/0x310 mm/mmap.c:604
x64_sys_call+0x14a3/0x3000 arch/x86/include/generated/asm/syscalls_64.h:10
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0xd2/0x200 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
read-write to 0xffff888117725760 of 4 bytes by task 6139 on cpu 1:
shrink_readahead_size_eio mm/filemap.c:2386 [inline]
filemap_read_folio+0xea/0x110 mm/filemap.c:2456
filemap_fault+0x58f/0xb60 mm/filemap.c:3581
__do_fault+0xbc/0x200 mm/memory.c:5281
do_read_fault mm/memory.c:5716 [inline]
do_fault mm/memory.c:5850 [inline]
do_pte_missing mm/memory.c:4362 [inline]
handle_pte_fault mm/memory.c:6195 [inline]
__handle_mm_fault mm/memory.c:6336 [inline]
handle_mm_fault+0xf78/0x2be0 mm/memory.c:6505
do_user_addr_fault+0x3fe/0x1080 arch/x86/mm/fault.c:1387
handle_page_fault arch/x86/mm/fault.c:1476 [inline]
exc_page_fault+0x62/0xa0 arch/x86/mm/fault.c:1532
asm_exc_page_fault+0x26/0x30 arch/x86/include/asm/idtentry.h:618
fault_in_readable+0xad/0x170 mm/gup.c:-1
fault_in_iov_iter_readable+0x129/0x210 lib/iov_iter.c:94
generic_perform_write+0x3cf/0x490 mm/filemap.c:4304
__generic_file_write_iter+0x9e/0x120 mm/filemap.c:4372
generic_file_write_iter+0x8d/0x2f0 mm/filemap.c:4398
new_sync_write fs/read_write.c:593 [inline]
vfs_write+0x52a/0x960 fs/read_write.c:686
ksys_write+0xda/0x1a0 fs/read_write.c:738
__do_sys_write fs/read_write.c:749 [inline]
__se_sys_write fs/read_write.c:746 [inline]
__x64_sys_write+0x40/0x50 fs/read_write.c:746
x64_sys_call+0x2802/0x3000 arch/x86/include/generated/asm/syscalls_64.h:2
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0xd2/0x200 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
value changed: 0x00000020 -> 0x00000008
Reported by Kernel Concurrency Sanitizer on:
CPU: 1 UID: 0 PID: 6139 Comm: syz.3.869 Not tainted syzkaller #0 PREEMPT(voluntary)
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 10/02/2025
==================================================================
Reply: The data race occurs on the `ra_pages` field of `struct file_ra_state` within the `shrink_readahead_size_eio` function in `mm/filemap.c`.
### Analysis:
1. **Context**: The function `shrink_readahead_size_eio` is called when a page read error (`-EIO`) occurs during a `filemap_read_folio` operation. Its purpose is to reduce the readahead size (`ra_pages`) to avoid repeatedly attempting large, failing I/O requests, which can be particularly problematic on certain media like CD/DVDs.
2. **The Race**: The code performs `ra->ra_pages /= 4;`, which is a read-modify-write operation. KCSAN detected that two tasks (one doing a `write` call that faulted on a mmapped buffer, and another doing `mlock` or `mmap(MAP_POPULATE)`) encountered I/O errors on the same file concurrently and attempted to update `ra_pages` at the same time.
3. **Impact**: `ra_pages` is a heuristic hint for the readahead algorithm. If two threads race on this update, the final value of `ra_pages` might be slightly different than if the operations were serialized (e.g., it might be divided by 4 once instead of twice). However, any value resulting from this race is a valid readahead size. The variable is typically an `unsigned int`, and on the reported architecture (x86), a 4-byte aligned write is atomic, so no "torn" values that aren't a result of the division would be seen by other readers.
4. **Benign Nature**: Readahead state in the Linux kernel is generally handled without strict locking for performance reasons, as it only affects performance (the efficiency of future reads) and not the correctness or stability of the system. A slightly "incorrect" readahead size due to a race does not lead to memory corruption, crashes, or invalid logic.
### Conclusion:
This data race is **benign**. It is a race on a heuristic variable where the exact value is not critical for system stability or correctness. Many similar readahead-related races exist in the kernel and are tolerated. While it could be silenced using `WRITE_ONCE()` and `READ_ONCE()`, it does not pose a functional risk.
|
| 5/2 |
2026/01/14 17:05 |
llm |
expert |
0m
Model:
Thoughts: **Investigating a KCSAN Report: A Deep Dive into a Data Race**
Okay, so KCSAN is flagging a data race, and it looks like it's within the filemap code, specifically in `mm/filemap.c` at `shrink_readahead_size_eio`. Interesting. The trace points to `filemap_read_folio` calling `shrink_readahead_size_eio`, and the race involves a read-write operation on the same memory location β `0xffff888117725760` to be precise. The value changes from 32 to 8. That's a significant reduction, and a definite clue.
My next step is to get into the details of `shrink_readahead_size_eio`. Time to dive into the code and see what's happening there, understand how `filemap_read_folio` interacts with it, and what's going on with the readahead size. I need to figure out the synchronization mechanisms at play (or, more accurately, the *lack* of them), and why this data race is happening. This could be a concurrency bug where one thread is modifying the readahead size while another thread is trying to read it. I need to understand the implications of this change and how it affects file I/O performance or even correctness. Time to dig in!
|
| 6/2 |
2026/01/14 17:06 |
tool |
codesearch-definition-source |
0m
Model:
Args: map[IncludeLines:true Name:shrink_readahead_size_eio SourceFile:mm/filemap.c]
Results: map[Missing:false SourceCode:2368: /*
2369: * CD/DVDs are error prone. When a medium error occurs, the driver may fail
2370: * a _large_ part of the i/o request. Imagine the worst scenario:
2371: *
2372: * ---R__________________________________________B__________
2373: * ^ reading here ^ bad block(assume 4k)
2374: *
2375: * read(R) => miss => readahead(R...B) => media error => frustrating retries
2376: * => failing the whole request => read(R) => read(R+1) =>
2377: * readahead(R+1...B+1) => bang => read(R+2) => read(R+3) =>
2378: * readahead(R+3...B+2) => bang => read(R+3) => read(R+4) =>
2379: * readahead(R+4...B+3) => bang => read(R+4) => read(R+5) => ......
2380: *
2381: * It is going insane. Fix it by quickly scaling down the readahead size.
2382: */
2383: static void shrink_readahead_size_eio(struct file_ra_state *ra)
2384: {
2385: ra->ra_pages /= 4;
2386: }
SourceFile:mm/filemap.c]
|
| 7/2 |
2026/01/14 17:06 |
llm |
expert |
0m
Model:
Thoughts: **Analyzing a Potential Race Condition in the Read-Ahead Logic**
Okay, so the race condition is clearly flagged on `ra->ra_pages`. The code snippet shows `ra->ra_pages /= 4;`, which, of course, is equivalent to `ra->ra_pages = ra->ra_pages / 4;`. The report indicates concurrent read-write operations to the same memory location (0xffff888117725760), by tasks 6134 and 6139, both hitting the `shrink_readahead_size_eio` function at `mm/filemap.c:2386`.
Given that it's a read-write operation on the same variable from multiple tasks, it's virtually guaranteed that this isn't an atomic operation. Each task is reading the value of `ra->ra_pages`, dividing it by four, and then writing the new, potentially stale, result back. If both tasks read the *same* value of `ra->ra_pages` before either writes, we'll see data corruption. We're going to want to lock this structure to make the operation atomic.
Therefore, the critical vulnerability is in the division operation, as it's not atomic. The next step is to examine the definition of `struct file_ra_state` to understand its fields and how `ra_pages` fits into the broader read-ahead mechanism. We need to check the context of this member - what it represents, how itβs being used, and where it's being accessed, to gauge the extent of the impact of this particular race condition.
|
| 8/2 |
2026/01/14 17:06 |
tool |
codesearch-definition-source |
0m
Model:
Args: map[IncludeLines:true Name:file_ra_state SourceFile:include/linux/fs.h]
Results: map[Missing:true SourceCode: SourceFile:]
|
| 9/2 |
2026/01/14 17:06 |
llm |
expert |
0m
Model:
Thoughts: Okay, let me think this through. Based on my existing knowledge, that functionality *could* reside within the `include/linux/fs.h` file. It's a prime location for file system related definitions and structures. Therefore, my next logical step is to perform a targeted search within that specific header file. Let's dig in and see what we can find.
|
| 10/2 |
2026/01/14 17:06 |
tool |
codesearch-file-index |
0m
Model:
Args: map[SourceFile:include/linux/fs.h]
Results: map[Entities:[map[Kind:function Name:HAS_UNMAPPED_ID] map[Kind:function Name:__blockdev_direct_IO] map[Kind:function Name:__check_sticky] map[Kind:function Name:__compat_vma_mmap_prepare] map[Kind:function Name:__destroy_inode] map[Kind:function Name:__f_setown] map[Kind:function Name:__free_putname] map[Kind:function Name:__generic_file_fsync] map[Kind:function Name:__generic_file_write_iter] map[Kind:function Name:__generic_remap_file_range_prep] map[Kind:function Name:__getname_maybe_null] map[Kind:function Name:__iget] map[Kind:function Name:__inode_add_bytes] map[Kind:function Name:__inode_get_bytes] map[Kind:function Name:__inode_sub_bytes] map[Kind:function Name:__insert_inode_hash] map[Kind:function Name:__kernel_read] map[Kind:function Name:__kernel_write] map[Kind:function Name:__mark_inode_dirty] map[Kind:function Name:__register_chrdev] map[Kind:function Name:__remove_inode_hash] map[Kind:function Name:__sb_end_write] map[Kind:function Name:__sb_start_write] map[Kind:function Name:__sb_start_write_trylock] map[Kind:function Name:__sb_write_started] map[Kind:function Name:__simple_attr_check_format] map[Kind:function Name:__unregister_chrdev] map[Kind:function Name:address_space_init_once] map[Kind:function Name:alloc_anon_inode] map[Kind:function Name:alloc_chrdev_region] map[Kind:function Name:alloc_inode] map[Kind:function Name:allow_write_access] map[Kind:function Name:always_delete_dentry] map[Kind:function Name:anon_inode_make_secure_inode] map[Kind:function Name:atime_needs_update] map[Kind:function Name:backing_file_user_path] map[Kind:function Name:blockdev_direct_IO] map[Kind:function Name:bmap] map[Kind:function Name:can_mmap_file] map[Kind:function Name:check_sticky] map[Kind:function Name:chrdev_show] map[Kind:function Name:clear_inode] map[Kind:function Name:clear_nlink] map[Kind:function Name:compat_ptr_ioctl] map[Kind:function Name:compat_vma_mmap_prepare] map[Kind:function Name:copy_splice_read] map[Kind:function Name:current_time] map[Kind:function Name:current_umask] map[Kind:function Name:d_alloc_name] map[Kind:function Name:d_mark_dontcache] map[Kind:function Name:dcache_dir_close] map[Kind:function Name:dcache_dir_lseek] map[Kind:function Name:dcache_dir_open] map[Kind:function Name:dcache_readdir] map[Kind:function Name:deactivate_locked_super] map[Kind:function Name:deactivate_super] map[Kind:function Name:default_llseek] map[Kind:function Name:dentry_create] map[Kind:function Name:dentry_open] map[Kind:function Name:dentry_open_nonotify] map[Kind:function Name:deny_write_access] map[Kind:function Name:dir_emit] map[Kind:function Name:dir_emit_dot] map[Kind:function Name:dir_emit_dotdot] map[Kind:function Name:dir_emit_dots] map[Kind:function Name:dir_relax] map[Kind:function Name:dir_relax_shared] map[Kind:function Name:direct_write_fallback] map[Kind:function Name:discard_new_inode] map[Kind:function Name:do_pipe_flags] map[Kind:function Name:do_sys_open] map[Kind:function Name:do_truncate] map[Kind:function Name:drop_nlink] map[Kind:function Name:drop_super] map[Kind:function Name:drop_super_exclusive] map[Kind:function Name:dump_mapping] map[Kind:function Name:emergency_remount] map[Kind:function Name:emergency_sync] map[Kind:function Name:emergency_thaw_all] map[Kind:function Name:evict_inodes] map[Kind:function Name:exe_file_allow_write_access] map[Kind:function Name:exe_file_deny_write_access] map[Kind:function Name:execute_ok] map[Kind:function Name:extensible_ioctl_valid] map[Kind:function Name:f_delown] map[Kind:function Name:f_getown] map[Kind:function Name:f_setown] map[Kind:function Name:fasync_alloc] map[Kind:function Name:fasync_free] map[Kind:function Name:fasync_helper] map[Kind:function Name:fasync_insert_entry] map[Kind:function Name:fasync_remove_entry] map[Kind:function Name:fd_statfs] map[Kind:function Name:file_accessed] map[Kind:function Name:file_check_and_advance_wb_err] map[Kind:function Name:file_clone_open] map[Kind:function Name:file_dentry] map[Kind:function Name:file_end_write] map[Kind:function Name:file_f_owner] map[Kind:function Name:file_f_owner_allocate] map[Kind:function Name:file_fdatawait_range] map[Kind:function Name:file_inode] map[Kind:function Name:file_is_dax] map[Kind:function Name:file_mnt_idmap] map[Kind:function Name:file_modified] map[Kind:function Name:file_open_name] map[Kind:function Name:file_open_root] map[Kind:function Name:file_open_root_mnt] map[Kind:function Name:file_path] map[Kind:function Name:file_permission] map[Kind:function Name:file_ra_state_init] map[Kind:function Name:file_remove_privs] map[Kind:function Name:file_set_fsnotify_mode] map[Kind:function Name:file_start_write] map[Kind:function Name:file_start_write_trylock] map[Kind:function Name:file_update_time] map[Kind:function Name:file_user_inode] map[Kind:function Name:file_user_path] map[Kind:function Name:file_write_and_wait] map[Kind:function Name:file_write_and_wait_range] map[Kind:function Name:file_write_not_started] map[Kind:function Name:file_write_started] map[Kind:function Name:filemap_fdatawrite_range_kick] map[Kind:function Name:filemap_invalidate_lock] map[Kind:function Name:filemap_invalidate_lock_shared] map[Kind:function Name:filemap_invalidate_lock_two] map[Kind:function Name:filemap_invalidate_trylock_shared] map[Kind:function Name:filemap_invalidate_unlock] map[Kind:function Name:filemap_invalidate_unlock_shared] map[Kind:function Name:filemap_invalidate_unlock_two] map[Kind:function Name:filemap_read] map[Kind:function Name:filemap_splice_read] map[Kind:function Name:files_init] map[Kind:function Name:files_maxfiles_init] map[Kind:function Name:filesystems_freeze] map[Kind:function Name:filesystems_thaw] map[Kind:function Name:fill_mg_cmtime] map[Kind:function Name:filp_close] map[Kind:function Name:filp_open] map[Kind:function Name:find_inode_by_ino_rcu] map[Kind:function Name:find_inode_nowait] map[Kind:function Name:find_inode_rcu] map[Kind:function Name:finish_no_open] map[Kind:function Name:finish_open] map[Kind:function Name:finish_open_simple] map[Kind:function Name:fixed_size_llseek] map[Kind:function Name:free_anon_bdev] map[Kind:function Name:free_inode_nonrcu] map[Kind:function Name:freeze_super] map[Kind:function Name:fsuidgid_has_mapping] map[Kind:function Name:generic_atomic_write_valid] map[Kind:function Name:generic_check_addressable] map[Kind:function Name:generic_ci_validate_strict_name] map[Kind:function Name:generic_fadvise] map[Kind:function Name:generic_file_direct_write] map[Kind:function Name:generic_file_fsync] map[Kind:function Name:generic_file_llseek] map[Kind:function Name:generic_file_llseek_size] map[Kind:function Name:generic_file_mmap] map[Kind:function Name:generic_file_mmap_prepare] map[Kind:function Name:generic_file_open] map[Kind:function Name:generic_file_read_iter] map[Kind:function Name:generic_file_readonly_mmap] map[Kind:function Name:generic_file_readonly_mmap_prepare] map[Kind:function Name:generic_file_rw_checks] map[Kind:function Name:generic_file_write_iter] map[Kind:function Name:generic_fill_statx_atomic_writes] map[Kind:function Name:generic_fill_statx_attr] map[Kind:function Name:generic_fillattr] map[Kind:function Name:generic_llseek_cookie] map[Kind:function Name:generic_perform_write] map[Kind:function Name:generic_permission] map[Kind:function Name:generic_read_dir] map[Kind:function Name:generic_remap_file_range_prep] map[Kind:function Name:generic_set_sb_d_ops] map[Kind:function Name:generic_shutdown_super] map[Kind:function Name:generic_update_time] map[Kind:function Name:generic_write_check_limits] map[Kind:function Name:generic_write_checks] map[Kind:function Name:generic_write_checks_count] map[Kind:function Name:generic_write_sync] map[Kind:function Name:get_anon_bdev] map[Kind:function Name:get_file] map[Kind:function Name:get_file_active] map[Kind:function Name:get_file_rcu] map[Kind:function Name:get_filesystem] map[Kind:function Name:get_fs_type] map[Kind:function Name:get_max_files] map[Kind:function Name:get_next_ino] map[Kind:function Name:get_write_access] map[Kind:function Name:getname] map[Kind:function Name:getname_flags] map[Kind:function Name:getname_kernel] map[Kind:function Name:getname_maybe_null] map[Kind:function Name:getname_uflags] map[Kind:function Name:i_blocksize] map[Kind:function Name:i_gid_into_vfsgid] map[Kind:function Name:i_gid_needs_update] map[Kind:function Name:i_gid_read] map[Kind:function Name:i_gid_update] map[Kind:function Name:i_gid_write] map[Kind:function Name:i_mmap_assert_locked] map[Kind:function Name:i_mmap_assert_write_locked] map[Kind:function Name:i_mmap_lock_read] map[Kind:function Name:i_mmap_lock_write] map[Kind:function Name:i_mmap_trylock_read] map[Kind:function Name:i_mmap_trylock_write] map[Kind:function Name:i_mmap_unlock_read] map[Kind:function Name:i_mmap_unlock_write] map[Kind:function Name:i_readcount_dec] map[Kind:function Name:i_readcount_inc] map[Kind:function Name:i_size_read] map[Kind:function Name:i_size_write] map[Kind:function Name:i_uid_into_vfsuid] map[Kind:function Name:i_uid_needs_update] map[Kind:function Name:i_uid_read] map[Kind:function Name:i_uid_update] map[Kind:function Name:i_uid_write] map[Kind:function Name:i_user_ns] map[Kind:function Name:icount_read] map[Kind:function Name:iget5_locked] map[Kind:function Name:iget5_locked_rcu] map[Kind:function Name:iget_failed] map[Kind:function Name:iget_locked] map[Kind:function Name:igrab] map[Kind:function Name:ihold] map[Kind:function Name:ilookup] map[Kind:function Name:ilookup5] map[Kind:function Name:ilookup5_nowait] map[Kind:function Name:imajor] map[Kind:function Name:iminor] map[Kind:function Name:in_group_or_capable] map[Kind:function Name:inc_nlink] map[Kind:function Name:init_special_inode] map[Kind:function Name:init_sync_kiocb] map[Kind:function Name:inode_add_bytes] map[Kind:function Name:inode_add_lru] map[Kind:function Name:inode_bit_waitqueue] map[Kind:function Name:inode_dec_link_count] map[Kind:function Name:inode_dio_begin] map[Kind:function Name:inode_dio_end] map[Kind:function Name:inode_dio_finished] map[Kind:function Name:inode_dio_wait] map[Kind:function Name:inode_dio_wait_interruptible] map[Kind:function Name:inode_fake_hash] map[Kind:function Name:inode_fsgid_set] map[Kind:function Name:inode_fsuid_set] map[Kind:function Name:inode_generic_drop] map[Kind:function Name:inode_get_atime] map[Kind:function Name:inode_get_atime_nsec] map[Kind:function Name:inode_get_atime_sec] map[Kind:function Name:inode_get_bytes] map[Kind:function Name:inode_get_ctime] map[Kind:function Name:inode_get_ctime_nsec] map[Kind:function Name:inode_get_ctime_sec] map[Kind:function Name:inode_get_mtime] map[Kind:function Name:inode_get_mtime_nsec] map[Kind:function Name:inode_get_mtime_sec] map[Kind:function Name:inode_has_no_xattr] map[Kind:function Name:inode_inc_link_count] map[Kind:function Name:inode_init] map[Kind:function Name:inode_init_always] map[Kind:function Name:inode_init_always_gfp] map[Kind:function Name:inode_init_early] map[Kind:function Name:inode_init_once] map[Kind:function Name:inode_init_owner] map[Kind:function Name:inode_insert5] map[Kind:function Name:inode_is_dirtytime_only] map[Kind:function Name:inode_is_locked] map[Kind:function Name:inode_is_open_for_write] map[Kind:function Name:inode_just_drop] map[Kind:function Name:inode_lock] map[Kind:function Name:inode_lock_killable] map[Kind:function Name:inode_lock_nested] map[Kind:function Name:inode_lock_shared] map[Kind:function Name:inode_lock_shared_killable] map[Kind:function Name:inode_lock_shared_nested] map[Kind:function Name:inode_needs_sync] map[Kind:function Name:inode_newsize_ok] map[Kind:function Name:inode_nohighmem] map[Kind:function Name:inode_owner_or_capable] map[Kind:function Name:inode_permission] map[Kind:function Name:inode_sb_list_add] map[Kind:function Name:inode_set_atime] map[Kind:function Name:inode_set_atime_to_ts] map[Kind:function Name:inode_set_bytes] map[Kind:function Name:inode_set_cached_link] map[Kind:function Name:inode_set_ctime] map[Kind:function Name:inode_set_ctime_current] map[Kind:function Name:inode_set_ctime_deleg] map[Kind:function Name:inode_set_ctime_to_ts] map[Kind:function Name:inode_set_flags] map[Kind:function Name:inode_set_mtime] map[Kind:function Name:inode_set_mtime_to_ts] map[Kind:function Name:inode_sub_bytes] map[Kind:function Name:inode_trylock] map[Kind:function Name:inode_trylock_shared] map[Kind:function Name:inode_unhashed] map[Kind:function Name:inode_unlock] map[Kind:function Name:inode_unlock_shared] map[Kind:function Name:inode_update_time] map[Kind:function Name:inode_update_timestamps] map[Kind:function Name:inode_wake_up_bit] map[Kind:function Name:inode_wrong_type] map[Kind:function Name:insert_inode_hash] map[Kind:function Name:insert_inode_locked] map[Kind:function Name:insert_inode_locked4] map[Kind:function Name:iocb_flags] map[Kind:function Name:iocb_is_dsync] map[Kind:function Name:iput] map[Kind:function Name:iput_not_last] map[Kind:function Name:is_bad_inode] map[Kind:function Name:is_dot_dotdot] map[Kind:function Name:is_empty_dir_inode] map[Kind:function Name:is_idmapped_mnt] map[Kind:function Name:is_mgtime] map[Kind:function Name:is_root_inode] map[Kind:function Name:is_subdir] map[Kind:function Name:is_sxid] map[Kind:function Name:is_sync_kiocb] map[Kind:function Name:is_uncached_acl] map[Kind:function Name:is_zero_ino] map[Kind:function Name:iter_file_splice_write] map[Kind:function Name:iterate_dir] map[Kind:function Name:iterate_supers] map[Kind:function Name:iterate_supers_type] map[Kind:function Name:iunique] map[Kind:function Name:kernel_file_open] map[Kind:function Name:kernel_read] map[Kind:function Name:kernel_tmpfile_open] map[Kind:function Name:kernel_write] map[Kind:function Name:kfree_link] map[Kind:function Name:kill_anon_super] map[Kind:function Name:kill_block_super] map[Kind:function Name:kill_fasync] map[Kind:function Name:kill_litter_super] map[Kind:function Name:kiocb_clone] map[Kind:function Name:kiocb_end_write] map[Kind:function Name:kiocb_modified] map[Kind:function Name:kiocb_set_rw_flags] map[Kind:function Name:kiocb_start_write] map[Kind:function Name:list_bdev_fs_names] map[Kind:function Name:lock_two_nondirectories] map[Kind:function Name:lockdep_annotate_inode_mutex_key] map[Kind:function Name:locked_recursive_removal] map[Kind:function Name:make_bad_inode] map[Kind:function Name:make_empty_dir_inode] map[Kind:function Name:mapping_allow_writable] map[Kind:function Name:mapping_deny_writable] map[Kind:function Name:mapping_map_writable] map[Kind:function Name:mapping_mapped] map[Kind:function Name:mapping_tagged] map[Kind:function Name:mapping_unmap_writable] map[Kind:function Name:mapping_writably_mapped] map[Kind:function Name:mark_inode_dirty] map[Kind:function Name:mark_inode_dirty_sync] map[Kind:function Name:may_open_dev] map[Kind:function Name:may_setattr] map[Kind:function Name:mode_strip_sgid] map[Kind:function Name:mount_subtree] map[Kind:function Name:name_contains_dotdot] map[Kind:function Name:new_inode] map[Kind:function Name:new_inode_pseudo] map[Kind:function Name:no_seek_end_llseek] map[Kind:function Name:no_seek_end_llseek_size] map[Kind:function Name:nonseekable_open] map[Kind:function Name:noop_direct_IO] map[Kind:function Name:noop_fsync] map[Kind:function Name:noop_llseek] map[Kind:function Name:notify_change] map[Kind:function Name:open_exec] map[Kind:function Name:page_get_link] map[Kind:function Name:page_get_link_raw] map[Kind:function Name:page_put_link] map[Kind:function Name:page_readlink] map[Kind:function Name:page_symlink] map[Kind:function Name:path_is_under] map[Kind:function Name:path_noexec] map[Kind:function Name:path_permission] map[Kind:function Name:put_filesystem] map[Kind:function Name:put_write_access] map[Kind:function Name:putname] map[Kind:function Name:ra_has_index] map[Kind:function Name:readlink_copy] map[Kind:function Name:refname] map[Kind:function Name:register_chrdev] map[Kind:function Name:register_chrdev_region] map[Kind:function Name:register_filesystem] map[Kind:function Name:remap_verify_area] map[Kind:function Name:remove_inode_hash] map[Kind:function Name:retire_super] map[Kind:function Name:rw_verify_area] map[Kind:function Name:sb_encoding] map[Kind:function Name:sb_end_intwrite] map[Kind:function Name:sb_end_pagefault] map[Kind:function Name:sb_end_write] map[Kind:function Name:sb_has_encoding] map[Kind:function Name:sb_is_blkdev_sb] map[Kind:function Name:sb_min_blocksize] map[Kind:function Name:sb_rdonly] map[Kind:function Name:sb_same_encoding] map[Kind:function Name:sb_set_blocksize] map[Kind:function Name:sb_start_intwrite] map[Kind:function Name:sb_start_intwrite_trylock] map[Kind:function Name:sb_start_pagefault] map[Kind:function Name:sb_start_write] map[Kind:function Name:sb_start_write_trylock] map[Kind:function Name:sb_write_not_started] map[Kind:function Name:sb_write_started] map[Kind:function Name:send_sigio] map[Kind:function Name:send_sigurg] map[Kind:function Name:set_anon_super] map[Kind:function Name:set_anon_super_fc] map[Kind:function Name:set_nlink] map[Kind:function Name:setattr_copy] map[Kind:function Name:setattr_prepare] map[Kind:function Name:setattr_should_drop_sgid] map[Kind:function Name:setattr_should_drop_suidgid] map[Kind:function Name:sget] map[Kind:function Name:sget_dev] map[Kind:function Name:sget_fc] map[Kind:function Name:simple_attr_open] map[Kind:function Name:simple_attr_read] map[Kind:function Name:simple_attr_release] map[Kind:function Name:simple_attr_write] map[Kind:function Name:simple_attr_write_signed] map[Kind:function Name:simple_empty] map[Kind:function Name:simple_fill_super] map[Kind:function Name:simple_get_link] map[Kind:function Name:simple_getattr] map[Kind:function Name:simple_inode_init_ts] map[Kind:function Name:simple_link] map[Kind:function Name:simple_lookup] map[Kind:function Name:simple_nosetlease] map[Kind:function Name:simple_offset_add] map[Kind:function Name:simple_offset_destroy] map[Kind:function Name:simple_offset_init] map[Kind:function Name:simple_offset_remove] map[Kind:function Name:simple_offset_rename] map[Kind:function Name:simple_offset_rename_exchange] map[Kind:function Name:simple_open] map[Kind:function Name:simple_pin_fs] map[Kind:function Name:simple_read_from_buffer] map[Kind:function Name:simple_recursive_removal] map[Kind:function Name:simple_release_fs] map[Kind:function Name:simple_rename] map[Kind:function Name:simple_rename_exchange] map[Kind:function Name:simple_rename_timestamp] map[Kind:function Name:simple_rmdir] map[Kind:function Name:simple_setattr] map[Kind:function Name:simple_start_creating] map[Kind:function Name:simple_statfs] map[Kind:function Name:simple_transaction_get] map[Kind:function Name:simple_transaction_read] map[Kind:function Name:simple_transaction_release] map[Kind:function Name:simple_transaction_set] map[Kind:function Name:simple_unlink] map[Kind:function Name:simple_write_begin] map[Kind:function Name:simple_write_to_buffer] map[Kind:function Name:stream_open] map[Kind:function Name:super_set_sysfs_name_bdev] map[Kind:function Name:super_set_sysfs_name_generic] map[Kind:function Name:super_set_sysfs_name_id] map[Kind:function Name:super_set_sysfs_name_uuid] map[Kind:function Name:super_set_uuid] map[Kind:function Name:super_setup_bdi] map[Kind:function Name:super_setup_bdi_name] map[Kind:function Name:sync_file_range] map[Kind:function Name:sync_filesystem] map[Kind:function Name:sync_inode_metadata] map[Kind:function Name:thaw_super] map[Kind:function Name:timestamp_truncate] map[Kind:function Name:touch_atime] map[Kind:function Name:uncached_acl_sentinel] map[Kind:function Name:unlock_new_inode] map[Kind:function Name:unlock_two_nondirectories] map[Kind:function Name:unregister_chrdev] map[Kind:function Name:unregister_chrdev_region] map[Kind:function Name:unregister_filesystem] map[Kind:function Name:user_statfs] map[Kind:function Name:vfs_caches_init] map[Kind:function Name:vfs_caches_init_early] map[Kind:function Name:vfs_clone_file_range] map[Kind:function Name:vfs_copy_file_range] map[Kind:function Name:vfs_create] map[Kind:function Name:vfs_dedupe_file_range] map[Kind:function Name:vfs_dedupe_file_range_one] map[Kind:function Name:vfs_empty_path] map[Kind:function Name:vfs_fadvise] map[Kind:function Name:vfs_fallocate] map[Kind:function Name:vfs_fchmod] map[Kind:function Name:vfs_fchown] map[Kind:function Name:vfs_fstat] map[Kind:function Name:vfs_fstatat] map[Kind:function Name:vfs_fsync] map[Kind:function Name:vfs_fsync_range] map[Kind:function Name:vfs_get_link] map[Kind:function Name:vfs_getattr] map[Kind:function Name:vfs_getattr_nosec] map[Kind:function Name:vfs_iocb_iter_read] map[Kind:function Name:vfs_iocb_iter_write] map[Kind:function Name:vfs_iter_read] map[Kind:function Name:vfs_iter_write] map[Kind:function Name:vfs_link] map[Kind:function Name:vfs_llseek] map[Kind:function Name:vfs_lstat] map[Kind:function Name:vfs_mkdir] map[Kind:function Name:vfs_mknod] map[Kind:function Name:vfs_mkobj] map[Kind:function Name:vfs_mmap] map[Kind:function Name:vfs_mmap_prepare] map[Kind:function Name:vfs_read] map[Kind:function Name:vfs_readlink] map[Kind:function Name:vfs_rename] map[Kind:function Name:vfs_rmdir] map[Kind:function Name:vfs_setpos] map[Kind:function Name:vfs_stat] map[Kind:function Name:vfs_statfs] map[Kind:function Name:vfs_symlink] map[Kind:function Name:vfs_truncate] map[Kind:function Name:vfs_unlink] map[Kind:function Name:vfs_utimes] map[Kind:function Name:vfs_whiteout] map[Kind:function Name:vfs_write] map[Kind:function Name:vma_is_dax] map[Kind:function Name:vma_is_fsdax] map[Kind:function Name:wrap_directory_iterator]] Missing:false]
|
| 11/2 |
2026/01/14 17:06 |
llm |
expert |
0m
Model:
Thoughts: Here's a summary of my thought process:
**Hunting Down That Elusive Structure**
Okay, so I was looking for `struct file_ra_state` in the usual places, namely the entity list in `include/linux/fs.h`. Weirdly, it wasn't listed, which is unusual. My initial thought is that it *must* be defined there. It's almost certainly part of the core filesystem infrastructure, so it'd make sense for it to be present. The more likely reason it's not showing up in the `codesearch-file-index` is that, being a `struct`, the index might treat it differently. It is very probable I'm just missing something obvious.
So, I'll switch gears and try a more targeted approach. I'll fire up `codesearch-definition-source`, since that's designed to track down precise definitions, regardless of how the index handles structural elements. Hopefully, that will get me the full picture on this structure.
|
| 12/2 |
2026/01/14 17:06 |
tool |
codesearch-definition-source |
0m
Model:
Args: map[IncludeLines:true Name:file_ra_state SourceFile:include/linux/fs.h]
Results: map[Missing:true SourceCode: SourceFile:]
|
| 13/2 |
2026/01/14 17:06 |
llm |
expert |
0m
Model:
Thoughts: Here's a summary of my thought process:
**Diving into the Kernel Data Structures**
Okay, that wasn't what I was expecting. I need to figure out where this thing is defined. My first instinct is to check `include/linux/pagemap.h` because that's a common location for core page-related structures. But, realistically, it's probably hanging out in `include/linux/fs.h`. Let me quickly try `codesearch-definition-comment`; that should hopefully give me the source file. Hm, wait a second, I might have just mistyped the struct name. Let me verify the exact spelling by trying to locate `struct file_ra_state`.
|
| 14/2 |
2026/01/14 17:06 |
tool |
codesearch-definition-source |
0m
Model:
Args: map[IncludeLines:true Name:struct file_ra_state SourceFile:include/linux/fs.h]
Results: map[Missing:true SourceCode: SourceFile:]
|
| 15/2 |
2026/01/14 17:06 |
llm |
expert |
0m
Model:
Thoughts: Here's my thought process, as if I were the one thinking it:
**Locating the Definition of `file_ra_state`**
Alright, I need to understand what `file_ra_state` is and how it's being used. The first logical step is to pinpoint where this variable, or perhaps more likely, a structure or class member, is actually defined. I need to know its type, scope, and potentially any initializations or constraints. So, I should start by searching the codebase, likely using grep or my IDE's "find definition" feature, for the exact string `file_ra_state`. This search will be the foundation for figuring out what I'm looking at.
|
| 16/2 |
2026/01/14 17:06 |
tool |
codesearch-definition-source |
0m
Model:
Args: map[IncludeLines:true Name:file_ra_state SourceFile:mm/filemap.c]
Results: map[Missing:true SourceCode: SourceFile:]
|
| 17/2 |
2026/01/14 17:06 |
llm |
expert |
0m
Model:
Thoughts: Here's a summary of my thought process:
**Diving into the Kernel's File System Infrastructure**
Alright, I'm trying to figure this out, and ideally, I'd just reach for `grep` or a similar tool. But I'm limited to these specific commands... let me rethink my approach. Okay, I'll revisit `include/linux/fs.h`. Maybe I skimmed over something important in the initial list. Hold on, does `codesearch-file-index` actually show me structs, too? It mentions "function, struct, variable" as entity kinds. That's a good sign.
Okay, let's say I *can* read `include/linux/fs.h` directly now... I'll need to scrutinize it more carefully. But wait, a potentially faster route: I'll use `codesearch-definition-source` on `filemap_read_folio`. That function is probably a key part of the file reading process. If I trace how `filemap_read_folio` uses `file_ra_state`, I'll probably get the crucial information directly.
|
| 18/2 |
2026/01/14 17:06 |
tool |
codesearch-definition-source |
0m
Model:
Args: map[IncludeLines:true Name:filemap_read_folio SourceFile:mm/filemap.c]
Results: map[Missing:false SourceCode:2433: static int filemap_read_folio(struct file *file, filler_t filler,
2434: struct folio *folio)
2435: {
2436: bool workingset = folio_test_workingset(folio);
2437: unsigned long pflags;
2438: int error;
2439:
2440: /* Start the actual read. The read will unlock the page. */
2441: if (unlikely(workingset))
2442: psi_memstall_enter(&pflags);
2443: error = filler(file, folio);
2444: if (unlikely(workingset))
2445: psi_memstall_leave(&pflags);
2446: if (error)
2447: return error;
2448:
2449: error = folio_wait_locked_killable(folio);
2450: if (error)
2451: return error;
2452: if (folio_test_uptodate(folio))
2453: return 0;
2454: if (file)
2455: shrink_readahead_size_eio(&file->f_ra);
2456: return -EIO;
2457: }
SourceFile:mm/filemap.c]
|
| 19/2 |
2026/01/14 17:06 |
llm |
expert |
0m
Model:
Thoughts: **Investigating a Potential Data Race in File Readahead Size Reduction**
Okay, so I'm looking at this `filemap_read_folio` function and I see it calling `shrink_readahead_size_eio`. This is interesting because I know `file` is a `struct file *`, and `f_ra` is a `struct file_ra_state` within it. So, basically, we're updating a field within the file's readahead state, specifically `ra->ra_pages /= 4;`. This seems like a straightforward optimization related to dealing with I/O errors, but I immediately start thinking about concurrency.
Hmm, the potential for multiple tasks accessing the same `struct file` concurrently is definitely something to consider. Let me check the stack traces to see what's happening. Task 6134 is calling `vm_mmap_pgoff`, and task 6139 is calling `vfs_write`. These are clearly different operations. The question is, are they working on the *same* file descriptor? If they are, then they share the same `struct file`.
Right, `shrink_readahead_size_eio` is called on an `-EIO` error, which makes sense. The comment in the code says it's a heuristic to dial down readahead when things go wrong, as in "it's going insane", which I find amusing. It's meant to quickly scale down the readahead size in response to errors.
So, the problem is obvious now: multiple threads could be hitting this `-EIO` on the same file concurrently, and both of them would be trying to shrink `ra->ra_pages`. The issue is, `ra->ra_pages /= 4;` isn't an atomic operation. Let me walk through an example. If `ra_pages` is 32, one thread reads 32, another reads 32. Both then write 8 (32/4). The result is 8, where we would expect the result to have converged from 32 -> 8 -> 2. There's a race condition. The KCSAN report confirms this, showing the value changing from 32 to 8.
The real question is, is this data race *harmful*? `ra_pages` is really just a hint for readahead. The fact that the readahead size shrinks faster or slower because of the race isn't ideal, but it doesn't seem like it's a source of memory corruption or crashes. It might lead to slightly more or less aggressive readahead, but that's a performance tweak, not a critical bug. This is consistent with how I typically see this kind of optimization. These `file_ra_state` updates are usually not protected by a lock because they're heuristics, after all.
I'm curious about the specific documentation around the `file_ra_state` struct. Let me see if I can find the definition in `include/linux/fs.h`. I'll look around there to check if there are any comments or known issues related to locking in this area. It'll be good to confirm that my understanding is correct and that the lack of locking is intentional, as a tradeoff for performance.
|
| 20/2 |
2026/01/14 17:06 |
tool |
codesearch-definition-comment |
0m
Model:
Args: map[Name:file_ra_state SourceFile:include/linux/fs.h]
Results: map[Comment: Kind: Missing:true]
|
| 21/2 |
2026/01/14 17:06 |
llm |
expert |
0m
Model:
Thoughts: Okay, here's my thought process as I approach this.
**Tracing the Origin of `file_ra_state`**
Right, so I need to figure out where the `file_ra_state` struct is being defined, or at least how it's being initialized. Given that I'm dealing with kernel code, the best approach is to use my tools. Let's start with `codesearch-definition-source`. I'll focus on a function that *uses* or returns `file_ra_state`, that should point me in the right direction. Ideally, I'm looking for a function that's a direct consumer of this struct.
Alternatively, since I remember seeing `file_ra_state_init` listed in `include/linux/fs.h`, it's probably a good starting point for a definition search, that would almost certainly give me what I need. Might be the quicker path. Either approach should work. I'm going to start with the `_init` function and see what it turns up. That's usually the cleanest starting point for tracking down an object's origins.
|
| 22/2 |
2026/01/14 17:06 |
tool |
codesearch-definition-source |
0m
Model:
Args: map[IncludeLines:true Name:file_ra_state_init SourceFile:include/linux/fs.h]
Results: map[Missing:false SourceCode:3469: extern void
3470: file_ra_state_init(struct file_ra_state *ra, struct address_space *mapping);
SourceFile:include/linux/fs.h]
|
| 23/2 |
2026/01/14 17:06 |
llm |
expert |
0m
Model:
Thoughts: **Delving into the `file_ra_state` Mystery**
Okay, so I'm still just staring at a declaration here, that's all. Let me think... what's the best way to track down the definition of this `file_ra_state` structure? My first instinct was to jump into `mm/readahead.c`, but hold on. Let's step back. Ideally, I'd like to just pull up the struct definition from `include/linux/fs.h`, maybe specifying a line range if possible... though I don't think I can do that. Ah, right. I remember, `codesearch-definition-source` *should* work if I search without prefixing `struct`.
Alright, let's try searching for the name "file_ra_state". (Oh right, I did that already, it didn't find it).
Okay, Plan B. Maybe it's in `include/linux/pagemap.h`? Actually, that won't work, because the `codesearch-file-index` of `include/linux/fs.h` didn't list `file_ra_state` as a struct. Hmm, maybe it's in another header file. Let's try `include/linux/mm.h` or perhaps `include/linux/mm_types.h`?
Okay, here's a thought: instead of searching for the struct definition directly, why not just look for the field `ra_pages`? I can use `codesearch-definition-source` on the `file` struct. That might be much more efficient.
|
| 24/2 |
2026/01/14 17:06 |
tool |
codesearch-definition-source |
0m
Model:
Args: map[IncludeLines:true Name:file SourceFile:include/linux/fs.h]
Results: map[Missing:true SourceCode: SourceFile:]
|
| 25/2 |
2026/01/14 17:06 |
llm |
expert |
0m
Model:
Thoughts: **Investigating a Data Race in the Linux Kernel's Readahead Mechanism**
Okay, so I'm trying to understand a reported data race concerning `file_ra_state`. It looks like I can't easily jump to the struct definition directly, so I'll try searching for `struct file_ra_state {` or by searching for the definition of `file_ra_state_init`.
It seems the issue is with `ra->ra_pages /= 4;`. This is a classic read-modify-write scenario without any locking, screaming "potential data race!" But is it a *benign* data race, as is often the case in the kernel? Readahead hints are frequently racy, which is considered acceptable.
Let's look at the code. This `shrink_readahead_size_eio` function in `mm/filemap.c` is where the divide-by-four happens. Two CPUs doing this concurrently could indeed lead to a loss of precision in the division. Say `ra_pages` starts at 100, both CPUs read 100, both write 25, resulting in 25 instead of 6 if the operation was performed serially. But, none of these values are "corrupt". They are all valid values for `ra_pages`, which is an `unsigned int` or `unsigned long`.
I can see in the KCSAN output that `ra_pages` is updated: `0x00000020 -> 0x00000008` (32 -> 8), so at least one write worked correctly. This is very likely a heuristic, so the risk is very low.
I'll check other fields in `file_ra_state` for potential problems from racing. It's usually per-file, which means if multiple threads share the same file descriptor (same `fd`), they'll race on `f_ra`. This is normal for shared file descriptors, though.
Back to the code in `filemap_read_folio`, this `shrink_readahead_size_eio` is only called on error. The value will just keep going down to 0 if there are many errors.
If it's 4 bytes, aligned writes of a 4-byte value are typically atomic on x86, which would avoid the issue of tearing writes. Even if it weren't atomic, these values are still from division by 4.
The race is: Load `ra->ra_pages`, Divide by 4, and Store `ra->ra_pages`. KCSAN detected a race between these sequences.
I'll check for where else `ra_pages` is updated. `mm/readahead.c` is a good place to start.
I'm leaning towards calling this benign because it's part of the readahead heuristic. Let me be sure though: If `ra_pages` is "wrong," all it does is limit how much readahead we do. A large value might cause excess readahead, but this function *shrinks* the value. A small value just means less readahead, hurting performance but not correctness.
Let's look for `file_ra_state` in `include/linux/fs.h` manually, because I'm having trouble searching for it.
Okay, I'll search for the struct definition using `codesearch-definition-source` on `file_ra_state_init` this time, and I'll focus on where it is DEFINED, not declared.
The previous output says that the declaration of `file_ra_state_init` is in `include/linux/fs.h`, as `extern`. Let's find its definition, likely in `mm/readahead.c` or `mm/filemap.c`. Let's check `mm/readahead.c`.
|
| 26/2 |
2026/01/14 17:07 |
tool |
codesearch-file-index |
0m
Model:
Args: map[SourceFile:mm/readahead.c]
Results: map[Entities:[map[Kind:function Name:__do_sys_readahead] map[Kind:function Name:__do_sys_readahead] map[Kind:function Name:__ia32_sys_readahead] map[Kind:function Name:__se_sys_readahead] map[Kind:function Name:__x64_sys_readahead] map[Kind:function Name:do_page_cache_ra] map[Kind:function Name:file_ra_state_init] map[Kind:function Name:file_ra_state_init] map[Kind:function Name:force_page_cache_ra] map[Kind:function Name:get_init_ra_size] map[Kind:function Name:get_next_ra_size] map[Kind:function Name:ksys_readahead] map[Kind:function Name:page_cache_async_ra] map[Kind:function Name:page_cache_async_ra] map[Kind:function Name:page_cache_ra_order] map[Kind:function Name:page_cache_ra_unbounded] map[Kind:function Name:page_cache_ra_unbounded] map[Kind:function Name:page_cache_sync_ra] map[Kind:function Name:page_cache_sync_ra] map[Kind:function Name:ra_alloc_folio] map[Kind:function Name:ractl_alloc_folio] map[Kind:function Name:ractl_max_pages] map[Kind:function Name:read_pages] map[Kind:function Name:readahead_expand] map[Kind:function Name:readahead_expand]] Missing:false]
|
| 27/2 |
2026/01/14 17:07 |
llm |
expert |
0m
Model:
Thoughts: Okay, here's my interpretation of that thought process, as a summary from my perspective, keeping in mind my expertise:
**Diving into the Readahead Initialization Code**
Right, `file_ra_state_init`β¦ That rings a bell, I know where it lives: `mm/readahead.c`. Let's get right to it. I'm going to pull up that function in my mental debugger and start dissecting how the kernel sets up the readahead data structures for a file. My focus will be on understanding the initial conditions and how the readahead process is kicked off. I want to quickly grasp the rationale behind the initial values, any flags being set, and the overall design strategy. This initialization function is the bedrock; it's the beginning of everything. I need to understand it cold to troubleshoot any future readahead related issues.
|
| 28/2 |
2026/01/14 17:07 |
tool |
codesearch-definition-source |
0m
Model:
Args: map[IncludeLines:true Name:file_ra_state_init SourceFile:mm/readahead.c]
Results: map[Missing:false SourceCode: 136: /*
137: * Initialise a struct file's readahead state. Assumes that the caller has
138: * memset *ra to zero.
139: */
140: void
141: file_ra_state_init(struct file_ra_state *ra, struct address_space *mapping)
142: {
143: ra->ra_pages = inode_to_bdi(mapping->host)->ra_pages;
144: ra->prev_pos = -1;
145: }
SourceFile:mm/readahead.c]
|
| 29/2 |
2026/01/14 17:07 |
llm |
expert |
0m
Model:
Thoughts: **Investigating Readahead State and Concurrency**
Okay, so I'm digging into how readahead is managed, specifically focusing on `ra->ra_pages` which is initialized from `bdi->ra_pages`. My initial goal was to get a handle on the `struct file_ra_state` definition. Since `include/linux/fs.h` seemed the likely location, I tried to revisit that, but got a bit thrown by potentially misinterpreting a previous search. I considered a broader project-wide search but realised that wasn't an option with the current tools.
I initially thought I could just look directly in `include/linux/fs.h`, but I had a thought that maybe the struct was defined under a different name or was missed. Then I remembered I could try `codesearch-definition-source` on `file_ra_state`, which I did, and it wasn't there. Then I thought about looking at the `file` struct and the `f_ra` field within. I tried just `file` and that was missing. Oh right, for structs it's just the name - so I tried `struct file`, but that wasn't it either.
Next thought, maybe it was moved to `fs_types.h` in a recent kernel update, so I checked there and also `types.h`, but no luck. But you know what? Actually, I don't *need* the full struct definition right now. The comment in `mm/filemap.c` already explains that it's a heuristic for quickly scaling down the readahead size, which is what I'm interested in here.
Now, a more pressing question: What about concurrency? If multiple threads are reading from the same file, are they going to step on each other's toes when updating `f_ra`? This usually happens during read/write calls, so it's a valid concern. Most of the readahead functions don't use locks on `f_ra`. Things like `page_cache_sync_ra` and `page_cache_async_ra` take `f_ra` as input.
I'm going to take a look at `page_cache_sync_ra` in `mm/readahead.c` and see exactly how it works with `f_ra`.
|
| 30/2 |
2026/01/14 17:07 |
tool |
codesearch-definition-source |
0m
Model:
Args: map[IncludeLines:true Name:page_cache_sync_ra SourceFile:mm/readahead.c]
Results: map[Missing:false SourceCode: 553: void page_cache_sync_ra(struct readahead_control *ractl,
554: unsigned long req_count)
555: {
556: pgoff_t index = readahead_index(ractl);
557: bool do_forced_ra = ractl->file && (ractl->file->f_mode & FMODE_RANDOM);
558: struct file_ra_state *ra = ractl->ra;
559: unsigned long max_pages, contig_count;
560: pgoff_t prev_index, miss;
561:
562: trace_page_cache_sync_ra(ractl->mapping->host, index, ra, req_count);
563: /*
564: * Even if readahead is disabled, issue this request as readahead
565: * as we'll need it to satisfy the requested range. The forced
566: * readahead will do the right thing and limit the read to just the
567: * requested range, which we'll set to 1 page for this case.
568: */
569: if (!ra->ra_pages || blk_cgroup_congested()) {
570: if (!ractl->file)
571: return;
572: req_count = 1;
573: do_forced_ra = true;
574: }
575:
576: /* be dumb */
577: if (do_forced_ra) {
578: force_page_cache_ra(ractl, req_count);
579: return;
580: }
581:
582: max_pages = ractl_max_pages(ractl, req_count);
583: prev_index = (unsigned long long)ra->prev_pos >> PAGE_SHIFT;
584: /*
585: * A start of file, oversized read, or sequential cache miss:
586: * trivial case: (index - prev_index) == 1
587: * unaligned reads: (index - prev_index) == 0
588: */
589: if (!index || req_count > max_pages || index - prev_index <= 1UL) {
590: ra->start = index;
591: ra->size = get_init_ra_size(req_count, max_pages);
592: ra->async_size = ra->size > req_count ? ra->size - req_count :
593: ra->size >> 1;
594: goto readit;
595: }
596:
597: /*
598: * Query the page cache and look for the traces(cached history pages)
599: * that a sequential stream would leave behind.
600: */
601: rcu_read_lock();
602: miss = page_cache_prev_miss(ractl->mapping, index - 1, max_pages);
603: rcu_read_unlock();
604: contig_count = index - miss - 1;
605: /*
606: * Standalone, small random read. Read as is, and do not pollute the
607: * readahead state.
608: */
609: if (contig_count <= req_count) {
610: do_page_cache_ra(ractl, req_count, 0);
611: return;
612: }
613: /*
614: * File cached from the beginning:
615: * it is a strong indication of long-run stream (or whole-file-read)
616: */
617: if (miss == ULONG_MAX)
618: contig_count *= 2;
619: ra->start = index;
620: ra->size = min(contig_count + req_count, max_pages);
621: ra->async_size = 1;
622: readit:
623: ra->order = 0;
624: ractl->_index = ra->start;
625: page_cache_ra_order(ractl, ra);
626: }
SourceFile:mm/readahead.c]
|
| 31/2 |
2026/01/14 17:07 |
llm |
expert |
0m
Model:
Thoughts: **Readahead Heuristic Race Analysis**
Okay, so I'm looking at this KCSAN report and the core of the issue seems to be in `shrink_readahead_size_eio`. This function, as I understand it, is part of the kernel's mechanism to adaptively manage readahead behavior when I/O errors occur. Specifically, it reduces `ra_pages`, which is the maximum number of pages to prefetch, and this is done without any locking, on a `struct file_ra_state`. So, the updates to `file_ra_state` are indeed racy when multiple threads are using the same `file` object. This is a common pattern in the kernel, and it's considered acceptable in many cases, often for performance reasons.
The race is specifically on the `ra_pages` variable. The goal is to shrink the readahead size after an EIO, to avoid hammering the disk with the same failing requests. If multiple threads hit an EIO concurrently, `ra_pages` might get shrunk "incorrectly" β perhaps not as aggressively as it should be, or maybe to a value that's slightly different than what would result from serial execution.
Let's think through an example. Suppose `ra_pages` starts at 32, and two threads hit EIO simultaneously. Ideally, weβd expect the value to move from 32 to 8 to 2. However, if they both read 32, the result might be 8 and 8. Then if another EIO happens, it's 8 -> 2. So the end result is that it eventually reaches zero, which disables readahead. The question is, can there be an underflow here? Since `ra_pages` is unsigned and the operation is integer division, it's safe. A division by 4 on 1 (and 2 or 3) results in zero.
My conclusion at this point is that this is a benign data race on a readahead heuristic.
I'll check to confirm that `ra_pages` is indeed `unsigned int`. The KCSAN report says "4 bytes", so it confirms it's a 32-bit integer.
Now, could this racy update to `ra_pages` lead to some other bug? I'll look at where `ra_pages` is used. It's used in `ractl_max_pages`. This function determines how many pages to read, but it has a check where `max_pages` is capped at the smaller of `ra_pages` and a constant `MAX_READAHEAD`. So even if `ra_pages` gets a "wrong" value due to the race, the result is still constrained by `MAX_READAHEAD`.
Consider `ra->ra_pages /= 4;`. The compiler might emit assembly that reads `ra_pages` into a register, does a shift right two bits (dividing by 4), and then writes the result back. So, a race here means one CPU could read the initial value, another CPU also reads the initial value, they both calculate the shrunk value (which is the same), and then write it back. Serial execution gives different values. Both results are valid. This means the result could be either 2 or 8 in the simple example, and that's not going to lead to corruption.
There might be something related in the kernel's history about `READ_ONCE`/`WRITE_ONCE` being used with readahead fields. In the kernel, if something is intended to be atomic, it should use either an atomic type or some locking primitive. The `file_ra_state` is specifically designed to be lightweight.
I will attempt to check the history of `shrink_readahead_size_eio`. I can't.
Looking at the stack trace, Task 6134 is `filemap_fault` -> `filemap_read_folio` -> `shrink_readahead_size_eio`. Task 6139 is `generic_perform_write` -> `fault_in_iov_iter_readable` -> `exc_page_fault` -> `filemap_fault` -> `filemap_read_folio` -> `shrink_readahead_size_eio`. Both are faulting on the same file. Task 6139 is doing a `write` call. `generic_perform_write` calls `fault_in_iov_iter_readable`. This means it's trying to read from a user buffer that is being written to the file. If that user buffer is memory-mapped to the SAME file, it *can* trigger a page fault on that same file. So it's very likely that they are indeed accessing the same `
|
| 32/2 |
2026/01/14 17:07 |
tool |
set-results |
0m
Model:
Args: map[Benign:true Confident:true]
Results: map[Benign:true Confident:true]
|
| 33/2 |
2026/01/14 17:07 |
llm |
expert |
0m
Model:
Thoughts: **Analyzing the Read-Ahead Size Reduction**
Okay, let me walk through this again. My initial concern was, "Am I missing something potentially harmful with this `ra_pages` change?". Could a "wrong" value there trigger a buffer overflow? No, it's just a value for readahead. What about an infinite loop, or a null pointer dereference? No, nothing points to that. The only field being modified is `ra_pages`.
The observed change, `0x20 -> 0x08`, aligns perfectly with what I'd expect from a specific call to `shrink_readahead_size_eio`. The fact that KCSAN flagged this indicates a concurrent modification, another caller doing the same thing simultaneously. Both were trying to divide `ra_pages` by four (`ra_pages /= 4`). If a race condition exists, then `ra_pages` might wind up being `initial / 4` instead of `initial / 16`. And that's... actually, that's fine. It's perfectly acceptable for a readahead heuristic. No real problem there.
Alright, I'm confident. This change is good to go.
|