syzbot
KCSAN: data-race in __filemap_add_folio / filemap_write_and_wait_range (2)
Status:
moderation: reported on 2026/11/12 12:30
Subsystems:
ext4
Labels:
prio:low
race:benign
[Documentation on labels]
Reported-by: syzbot+79a93585914b2555125c@syzkaller.appspotmail.com
First crash: 2h22m, last: 2h22m
✨ AI-generated root cause summary:
A data race occurs on mapping->nrpages between __filemap_add_folio() and mapping_needs_writeback() (called from filemap_write_and_wait_range()). In __filemap_add_folio(), mapping->nrpages += nr; updates the counter under the XArray lock (mapping->i_pages.xa_lock) using an unmarked plain store. Concurrently, filemap_write_and_wait_range() calls mapping_needs_writeback() which performs an unsynchronized, lockless plain read (return mapping->nrpages;) without READ_ONCE() or data_race(). Because neither access uses atomic accessors or KCSAN annotations, KCSAN flags the concurrent read and write as a data race.
▶
▼
✨ Full AI root cause analysis
Root Cause Analysis
1. Functions, Structs, and Fields Involved
- File:
mm/filemap.c
- Struct:
struct address_space (include/linux/fs.h)
- Field:
mapping->nrpages (unsigned long, offset 80 bytes within struct address_space), serialized on writes by the XArray spinlock (mapping->i_pages.xa_lock).
- Concurrent Functions:
- Writer:
__filemap_add_folio() (mm/filemap.c:918), reached via bdev_getblk() / ext4_sb_bread_unmovable() during ext4_fill_super() on mount().
- Reader:
mapping_needs_writeback() (mm/filemap.c:633), inlined into filemap_write_and_wait_range() (mm/filemap.c:682), reached via sync_blockdev() / bdev_release() on close().
2. Triggering Mechanism & Concurrency Breakdown
-
Write Path (Task 4147, CPU 1):
- While mounting a filesystem, task 4147 reads filesystem metadata blocks from the block device.
- In
__filemap_add_folio(), it inserts a newly allocated folio into mapping->i_pages.
- The writer acquires the XArray lock via
xas_lock_irq(&xas) and stores the folio into the XArray.
- At
mm/filemap.c:918, it increments the page counter:
mapping->nrpages += nr;
This update is an unmarked, plain C read-modify-write (+=) without WRITE_ONCE().
-
Read Path (Task 2985, CPU 0):
-
Data Race:
- Under the Linux Kernel Memory Model (LKMM), concurrent unmarked plain accesses where at least one is a write constitute a data race.
- KCSAN observed the simultaneous 8-byte write (
0x0 -> 0x1) in __filemap_add_folio() and lockless load in mapping_needs_writeback(), triggering the splat.
3. Invariant / Lifecycle Evaluation & Fix
mapping_needs_writeback() is intended purely as a fast-path optimization to skip filemap_fdatawrite_range() and __filemap_fdatawait_range() when no pages are mapped.
- Even if a concurrent caller reads
0 while a folio is being added to the mapping, the newly added folio has not yet been modified or marked dirty, meaning there is no dirty data or in-flight writeback to flush or wait upon. If it reads a non-zero value, writeback routines safely proceed to inspect the XArray tags (PAGECACHE_TAG_DIRTY / PAGECACHE_TAG_WRITEBACK) under RCU and lock protection.
- To eliminate the data race under KCSAN and comply with LKMM:
- The lockless fast-path reader should read the counter using
data_race(READ_ONCE(mapping->nrpages)) (or READ_ONCE(mapping->nrpages) / data_race(...), similar to usage in mm/shmem.c).
- Writes in
mm/filemap.c (__filemap_add_folio, page_cache_delete, etc.) should annotate concurrent modifications with WRITE_ONCE() or data_race().
Sample crash report:
* Struck through repros no longer work on HEAD.