CVE-2026-90131: ntfs: serialize resident iomap reads with mrec_lock
In the Linux kernel, the following vulnerability has been resolved:
ntfs: serialize resident iomap reads with mreclock
ntfsreadiomapbeginresident() walks the MFT record through ntfsattrlookup() -> ntfsattrfind() without taking ni->mreclock, while ntfsattrrecordresize(), ntfsmakeroomforattr() and ntfsresidentattrrecordadd() memmove() the same baseni->mrec buffer under that lock. mapmftrecord() only takes a reference and does not serialize, so the reader can observe torn attribute length and offset fields while a writer is relocating the records.
KCSAN reports the race between the mmap read fault path and both link() and unlink():
BUG: KCSAN: data-race in ntfsattrfind / ntfsattrrecordresize
write to 0xffff888100af1018 of 4 bytes by task 96 on cpu 1: ntfsattrrecordresize+0xd2/0x130 ntfsattrrecordrm+0xad/0x530 ntfsdelete+0x224/0x640 ntfsunlink+0x14d/0x280 vfsunlink+0x157/0x520
read to 0xffff888100af1018 of 4 bytes by task 95 on cpu 0: ntfsattrfind+0x104/0x5b0 ntfsattrlookup+0x39c/0x10c0 ntfsreadiomapbeginresident+0xc6/0x230 ntfsreadiomapbegin+0x5d/0xa0 iomapiter+0x2e2/0x6e0 iomapreadfolio+0x147/0x2a0 ntfsreadfolio+0x108/0x170 filemapreadfolio+0x35/0x100 filemapfault+0x993/0x1000
value changed: 0x00000250 -> 0x000001f0
The address is mrec + 0x18, i.e. mftrecord.bytesinuse, and the change is the 96 bytes of one $FILENAME attribute being removed.
Keep baseni->mreclock from the resident read iomap lookup through iomapend(). This protects both the attribute walk and the subsequent copy from iomap->inlinedata, which points into the MFT record. The non-resident path is left alone: ntfslookup() already holds the directory inode's mreclock when it reads an index folio through readmappingfolio(), and taking the lock in the shared wrapper deadlocks there with recursive locking on mreclock. The comment above the readmappingfolio() call in fs/ntfs/dir.c notes the same hazard.
The seek path uses the same lookup helper but does not dereference iomap->inlinedata. Release the lock before returning from that path, whereas the regular read path records baseni in iomap->private and releases the lock from its iomapend() callback.
Tested with a reproducer that faults in a 16-byte resident file while another thread runs link()/unlink() on it. Before: 40 KCSAN reports in about one second. After: no reports in 180 seconds over 206,090 read iterations and 423,540 link/unlink cycles. A PROVELOCKING build shows no lockdep splat with the same reproducer running for 60 seconds.
Affected Software
Event History
Frequently Asked Questions
Which systems are exposed to this race?
Systems using the Linux kernel NTFS implementation are exposed when a resident attribute is read through the iomap path while the same MFT record is being modified. The reported race involves mmap read faults concurrent with link() or unlink() operations.
What concurrent activity is required to trigger the issue?
A reader must walk attributes in an MFT record while another operation relocates records in the same mrec buffer. Attribute resizing, making room for an attribute, and adding a resident attribute can perform the conflicting memmove() operations.
Can normal MFT-record mapping prevent the race?
No. map_mft_record() takes a reference to the record but does not serialize access to the mrec buffer, so it does not prevent readers from observing partially updated attribute length or offset fields.
How might the race be observed during testing?
KCSAN can report a data race between ntfs_attr_find() on the mmap read-fault path and writes from ntfs_attr_record_resize() reached through unlink-related operations.