KCSAN: data-race in __filemap_add_folio / nr_blockdev_pages (4)

Status: moderation: reported on 2026/11/05 01:31
Subsystems: fs mm
Labels: race:benign prio:low
[Documentation on labels]
Reported-by: syzbot+5d380c3d6727a9581fd4@syzkaller.appspotmail.com
First crash: 12h03m, last: 12h03m
✨ AI-generated root cause summary:

A KCSAN data race occurs between __filemap_add_folio() modifying mapping->nrpages and nr_blockdev_pages() reading inode->i_mapping->nrpages. In struct address_space, nrpages is protected by the i_pages XArray spinlock (xa_lock), which __filemap_add_folio() holds during modification. nr_blockdev_pages() iterates over blockdev_superblock->s_inodes holding only s_inode_list_lock and performs an unannotated, lockless plain read of inode->i_mapping->nrpages without holding xa_lock or using data_race() / READ_ONCE(). Because nr_blockdev_pages() only computes an approximate bufferram statistic for si_meminfo(), this unannotated lockless read triggers KCSAN.

▶ ▼ ✨ Full AI root cause analysis
Based on kernel commit 05441d697f7f:

Root Cause Analysis

1. Involved Components and Data Structures

  • Source Files:
    • block/bdev.c: Contains nr_blockdev_pages(), which calculates the total page count across all block devices.
    • mm/filemap.c: Contains __filemap_add_folio(), which adds newly allocated folios to an address space's page cache.
    • include/linux/fs.h: Defines struct address_space.
  • Structures and Fields:
    • struct address_space::nrpages: Tracks the total number of page entries present in the address space page cache. As documented in include/linux/fs.h, this field is protected by the i_pages lock (xa_lock).
    • struct super_block::s_inode_list_lock: Protects the list of inodes associated with blockdev_superblock.

2. Synchronization Mechanism and Invariant Violation

According to the synchronization rules of the page cache (struct address_space), modifications to mapping->nrpages must be performed while holding the i_pages.xa_lock:

/* mm/filemap.c: __filemap_add_folio() */
xas_lock_irq(&xas);
...
xas_store(&xas, folio);
if (xas_error(&xas))
    goto unlock;

mapping->nrpages += nr;  /* line 919 */

unlock:
xas_unlock_irq(&xas);

In nr_blockdev_pages(), the kernel calculates the total number of block device buffer pages (val->bufferram) for si_meminfo() by iterating through all block device inodes on blockdev_superblock->s_inodes:

/* block/bdev.c: nr_blockdev_pages() */
long nr_blockdev_pages(void)
{
    struct inode *inode;
    long ret = 0;

    spin_lock(&blockdev_superblock->s_inode_list_lock);
    list_for_each_entry(inode, &blockdev_superblock->s_inodes, i_sb_list)
        ret += inode->i_mapping->nrpages;  /* line 568 */
    spin_unlock(&blockdev_superblock->s_inode_list_lock);

    return ret;
}

While nr_blockdev_pages() holds blockdev_superblock->s_inode_list_lock to protect the list traversal of block device inodes, it intentionally does not acquire each individual block device's inode->i_mapping->i_pages.xa_lock when reading nrpages, avoiding global spinlock contention with block device I/O and readahead paths.

However, the read ret += inode->i_mapping->nrpages; is an unannotated plain read. When a concurrent thread (such as task 8211 executing __filemap_add_folio()) concurrently executes mapping->nrpages += nr;, KCSAN detects a data race between the concurrent read-write and the unannotated read on mapping->nrpages.

3. Resolution

To inform concurrency tools and the compiler that lockless, approximate reads of mapping->nrpages in statistical accounting functions like nr_blockdev_pages() are intentional, the read should be annotated using data_race(...) or READ_ONCE(...) (e.g., ret += data_race(inode->i_mapping->nrpages);).

▶ ▼ ✨ AI Jobs (2)
ID Workflow Result Correct Bug Created Started Finished Revision
b91f9fa8 assessment-security DenialOfService: ❌ Exploitable: ❌ FilesystemTrigger: ❌ FuzzingArtifact: ❌ MemoryCorruption: ❌ NetworkNeedsSetup: ❌ NetworkTrigger: ❌ PeripheralTrigger: ❌ RemoteTrigger: ❌ Unprivileged: ✅ UserNamespace: ✅ VMGuestTrigger: ❌ VMHostTrigger: ❌ ❓ KCSAN: data-race in __filemap_add_folio / nr_blockdev_pages (4) 2026/11/05 01:01 2026/11/05 01:01 2026/11/05 01:23 73438ed0
9b6621cd assessment-kcsan Benign: ✅ ❓ KCSAN: data-race in __filemap_add_folio / nr_blockdev_pages (4) 2026/11/05 00:51 2026/11/05 00:51 2026/11/05 01:12 73438ed0
▶ ▼ Similar bugs (3)
Kernel Title Rank 🛈 Repro Cause bisect Fix bisect Count Last Reported Patched Status
upstream KCSAN: data-race in __filemap_add_folio / nr_blockdev_pages ext4 6 45 1713d 1820d 0/29 auto-closed as invalid on 2022/04/01 15:15
upstream KCSAN: data-race in __filemap_add_folio / nr_blockdev_pages (2) ext4 6 63 1168d 1674d 0/29 auto-obsoleted due to no activity on 2023/09/28 20:31
upstream KCSAN: data-race in __filemap_add_folio / nr_blockdev_pages (3) prio:low mm 6 180 81d 1061d 0/29 auto-obsoleted due to no activity on 2026/10/11 08:46

Sample crash report:
==================================================================
BUG: KCSAN: data-race in __filemap_add_folio / nr_blockdev_pages

read-write to 0xffff888100637178 of 8 bytes by task 8211 on cpu 1:
 __filemap_add_folio+0x6c4/0x930 mm/filemap.c:919
 filemap_add_folio+0x1cd/0x380 mm/filemap.c:971
 page_cache_ra_unbounded+0x1a4/0x3a0 mm/readahead.c:293
 do_page_cache_ra mm/readahead.c:348 [inline]
 page_cache_ra_order+0x149/0x210 mm/readahead.c:558
 page_cache_async_ra+0x382/0x4c0 mm/readahead.c:720
 filemap_readahead mm/filemap.c:2700 [inline]
 filemap_get_pages+0x589/0x1360 mm/filemap.c:2746
 filemap_splice_read+0x3ac/0x750 mm/filemap.c:3121
 blkdev_splice_read+0x61/0x90 block/fops.c:916
 do_splice_read fs/splice.c:980 [inline]
 splice_direct_to_actor+0x25d/0x680 fs/splice.c:1077
 do_splice_direct_actor fs/splice.c:1230 [inline]
 do_splice_direct+0x119/0x1a0 fs/splice.c:1256
 do_sendfile+0x37d/0x640 fs/read_write.c:1371
 __do_sys_sendfile64 fs/read_write.c:1432 [inline]
 __se_sys_sendfile64 fs/read_write.c:1418 [inline]
 __x64_sys_sendfile64+0x105/0x150 fs/read_write.c:1418
 x64_sys_call+0x1015/0x2550 arch/x86/include/generated/asm/syscalls_64.h:41
 do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
 do_syscall_64+0x112/0x360 arch/x86/entry/syscall_64.c:84
 entry_SYSCALL_64_after_hwframe+0x77/0x7f

read to 0xffff888100637178 of 8 bytes by task 3437 on cpu 0:
 nr_blockdev_pages+0x7e/0xd0 block/bdev.c:568
 si_meminfo+0x87/0xd0 mm/show_mem.c:81
 update_defense_level+0x44/0x5c0 net/netfilter/ipvs/ip_vs_ctl.c:102
 defense_work_handler+0x1f/0x80 net/netfilter/ipvs/ip_vs_ctl.c:235
 process_one_work kernel/workqueue.c:3495 [inline]
 process_scheduled_works+0x53f/0xa20 kernel/workqueue.c:3578
 worker_thread+0x56c/0x770 kernel/workqueue.c:3659
 kthread+0x223/0x270 kernel/kthread.c:436
 ret_from_fork+0x146/0x330 arch/x86/kernel/process.c:158
 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245

value changed: 0x0000000000001012 -> 0x000000000000101c

Reported by Kernel Concurrency Sanitizer on:
CPU: 0 UID: 0 PID: 3437 Comm: kworker/u8:7 Tainted: G        W           syzkaller #0 PREEMPT(lazy) 
Tainted: [W]=WARN
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 10/07/2026
Workqueue: events_dfl_long defense_work_handler
==================================================================

Crashes (1):
Time Kernel Commit Syzkaller Config Log Report Syz repro C repro VM info Assets (help?) Manager Title
2026/11/05 00:48 upstream 05441d697f7f 73438ed0 .config console log report info [disk image] [vmlinux] [kernel image] ci2-upstream-kcsan-gce KCSAN: data-race in __filemap_add_folio / nr_blockdev_pages
* Struck through repros no longer work on HEAD.