CVE-2026-72354: ntfs: avoid stale runlist element dereference in MFT writeback
In the Linux kernel, the following vulnerability has been resolved:
ntfs: avoid stale runlist element dereference in MFT writeback
ntfswritemftblock() maps each $MFT record through the $MFT data runlist. For sub-folio clusters it looks up a struct runlistelement under ni->runlist.lock, drops the lock, and later uses rl->length and rl->vcn when choosing foliosz.
That pointer is only borrowed from ni->runlist.rl. Concurrent $MFT allocation extension can merge a replacement runlist under the same lock, and ntfsrlrealloc() can free the old backing array. If that happens between the lookup and the later foliosz decision, writeback can dereference freed runlist storage.
The buggy scenario involves two paths, with each column showing the order within that path:
MFT writeback path: $MFT allocation extension: 1. Look up rl under 1. Extend the $MFT data allocation. ni->runlist.lock. 2. Publish a replacement runlist. 2. Drop ni->runlist.lock. 3. Free the old runlist array. 3. Read rl->length and rl->vcn to choose foliosz.
Compute the remaining run length while ni->runlist.lock is still held, and use that scalar after unlock. This preserves the existing folio sizing decision without carrying a borrowed runlistelement across the lock boundary.
Validation reproduced this kernel report: BUG: KASAN: slab-use-after-free in ntfsmftwritepages+0x1c8d/0x1fb0
Call Trace: <TASK> dumpstacklvl+0x66/0xa0 printreport+0xce/0x630 ? ntfsmftwritepages+0x1c8d/0x1fb0 ? srsoaliasreturnthunk+0x5/0xfbef5 ? virtaddrvalid+0x20d/0x410 ? ntfsmftwritepages+0x1c8d/0x1fb0 kasanreport+0xe0/0x110 ? ntfsmftwritepages+0x1c8d/0x1fb0 ntfsmftwritepages+0x1c8d/0x1fb0 ? pfxntfsmftwritepages+0x10/0x10 ? pfxmutexunlockslowpath+0x10/0x10 ? srsoaliasreturnthunk+0x5/0xfbef5 ? iput+0x92/0xa80 dowritepages+0x219/0x530 ? pfxdowritepages+0x10/0x10 writebacksingleinode+0x117/0xf50 ? dorawspinlock+0x130/0x270 ? pfxdorawspinlock+0x10/0x10 ? pfxwritebacksingleinode+0x10/0x10 ? srsoaliasreturnthunk+0x5/0xfbef5 writebacksbinodes+0x65b/0x1810 ? srsoaliasreturnthunk+0x5/0xfbef5 ? lockacquire+0x2b8/0x2f0 ? pfxwritebacksbinodes+0x10/0x10 ? lockrelease+0x1e0/0x280 ? rawspinunlock+0x23/0x40 ? moveexpiredinodes+0x2b8/0x850 writebackinodeswb+0xf4/0x270 ? pfxwritebackinodeswb+0x10/0x10 ? srsoaliasreturnthunk+0x5/0xfbef5 ? queueio+0x2e4/0x410 wbwriteback+0x666/0x880 ? srsoaliasreturnthunk+0x5/0xfbef5 ? pfxwbwriteback+0x10/0x10 ? srsoaliasreturnthunk+0x5/0xfbef5 ? srsoaliasreturnthunk+0x5/0xfbef5 ? getnrdirtyinodes+0x1c/0x170 wbworkfn+0x75e/0xbb0 ? srsoaliasreturnthunk+0x5/0xfbef5 ? rawspinunlockirqrestore+0x27/0x60 ? pfxwbworkfn+0x10/0x10 ? pfxdebugobjectdeactivate+0x10/0x10 ? lockacquire+0x2b8/0x2f0 ? srsoaliasreturnthunk+0x5/0xfbef5 ? lockrelease+0x1e0/0x280 processonework+0x8d0/0x1870 ? pfxprocessonework+0x10/0x10 ? srsoaliasreturnthunk+0x5/0xfbef5 workerthread+0x575/0xf80 ? pfxworkerthread+0x10/0x10 kthread+0x2e7/0x3c0 ? pfxkthread+0x10/0x10 retfromfork+0x576/0x810 ? pfxretfromfork+0x10/0x10 ? srsoaliasreturnthunk+0x5/0xfbef5 ? switchto+0x57e/0xe10 ? switchtoasm+0x33/0x70 ? pfxkthread+0x10/0x10 retfromforkasm+0x1a/0x30 </TASK>
Allocated by task 970: kasansavestack+0x33/0x60 kasansavetrack+0x14/0x30 kasankmalloc+0xaa/0xb0 kvmallocnodenoprof+0x353/0x920 ntfsrlrealloc+0x3c/0x80 ntfsrunlistsmerge+0x1212/0x3010 ntfsmftdataextendallocationnolock+0x3e0/0x1f40 ntfsmftrecordalloc+0x1ab4/0x4f10 ntfscreate+0x680/0x2e50 ntfscreate+0x1e6/0x3a0 pathopenat+0x2b55/0x3c10 dofileopen+0x1f4/0x460 dosysopenat2+0xde/0x170 x64sysopenat+0x122/0x1e0 dosyscall64+0x115/0x6a0 entrySYSCALL64afterhwframe+0x77/0x7f
Freed by task 1294: kasansave ---truncated---