CVE-2026-92501 in Linux
Summary
by MITRE • 09/17/2026
In the Linux kernel, the following vulnerability has been resolved:
ext4: drain in-flight DIO before buffered write fallback
generic/746 started failing intermittently on ext3 (no-extent inodes). The test triggers 'Page cache invalidation failure on direct I/O' warnings and subsequent fsync returns -EIO. Adding a 50ms delay between ext4_buffered_write_iter() and filemap_write_and_wait_range() in ext4_dio_write_iter() makes the race almost always reproducible.
On no-extent inodes, DIO writes to holes cannot use unwritten extents, so ext4_iomap_alloc() leaves m_flags=0 and ext4_map_blocks() returns 0. The iomap layer then returns -ENOTBLK, causing fallback to buffered I/O.
The fallback path in ext4_dio_write_iter() calls ext4_buffered_write_iter() which dirties pages, then does flush and invalidate. However, there's an unprotected window between ext4_buffered_write_iter() returning (with inode lock released) and the subsequent flush+invalidate.
Concurrent async DIO completions from other threads can run kiocb_invalidate_post_direct_write() during this window. If pages have been re-dirtied, post-invalidation finds dirty pages and triggers the warning, setting -EIO in the error sequence.
Consider a file with two 4k extents: [hole][written]. Thread A does
DIO to the written extent, while thread B does DIO spanning both:
kworker A (4k DIO, allocated block) kworker B (8k DIO, fallback) ----------------------------------- ---------------------------- inode_lock_shared() inode_lock_shared() iomap_dio_rw(): iomap_dio_rw(): kiocb_invalidate_pages -> clean iomap_begin -> -ENOTBLK submit_bio (async) dio->size = 0 inode_unlock_shared() inode_unlock_shared()
[bio pending in block layer] /* fallback: lock released */
ext4_buffered_write_iter() inode_lock(exclusive) generic_perform_write() -> dirty pages [0, 8k]
inode_unlock(exclusive)
/* pages dirty, no lock */ [bio completes] filemap_write_and_wait_range()
iomap_dio_complete() -> flush dirty pages kiocb_invalidate_post_direct_write() invalidate_mapping_pages() invalidate_inode_pages2_range() -> finds dirty page! -> dio_warn_stale_pagecache() -> errseq_set(-EIO)
This issue can be triggered through normal I/O paths, not just intentionally overlapping DIO writes from userspace. For example, generic/746 uses a loop device where multiple kworkers issue concurrent I/O to the backing file. Additionally, when block_size < folio_size, non-overlapping DIO writes that share a large folio can also trigger the race.
Add inode_dio_wait() in ext4_buffered_write_iter() before ext4_write_checks() to drain all in-flight DIO. This ensures that all DIO clears existing pages before submitting IO (via kiocb_invalidate_pages()), all BIO waits for all DIO to complete (via inode_dio_wait()), and ext4_write_checks() observes the inode size after all completed DIO so that ext4_block_zero_eof() does not race with in-flight DIO, thus eliminating the race.
If you want to get best quality of vulnerability data, you may have to visit VulDB.
Analysis
by VulDB Data Team • 09/17/2026
The Linux kernel's ext4 filesystem implementation contained a critical concurrency flaw within its direct I/O fallback mechanism, specifically affecting scenarios where direct I/O operations could not be completed and had to fall back to buffered writes. This vulnerability primarily impacted inodes that did not use extents, such as those on ext3 or specific ext4 configurations, where the allocation of blocks for holes failed due to the inability to create unwritten extents. When this failure occurred, the iomap layer returned an error code indicating no block was available, prompting the kernel to switch from direct I/O to buffered write operations. The core technical flaw lay in the sequence of operations during this fallback path: after initiating a buffered write that dirtied page cache entries, the system released the inode lock before performing necessary flush and invalidation steps. This created an unprotected race window where concurrent asynchronous direct I/O completions from other threads could execute post-write invalidation routines while pages were still marked as dirty by the buffered writer.
The operational impact of this vulnerability manifested as intermittent test failures in standard benchmarks like generic/746, characterized by Page cache invalidation failure warnings and subsequent fsync operations returning an Input/output error code. The race condition was triggered when a thread performing direct I/O to a written extent completed its operation asynchronously while another thread concurrently performed a larger direct I/O that spanned both hole and written regions. Because the second thread fell back to buffered writes, it dirtied pages in the shared folio range without holding the inode lock during the critical transition period. Meanwhile, the completion of the first thread's direct I/O triggered invalidation routines that expected clean page states but instead encountered dirty pages left by the buffered write fallback. This mismatch caused the kernel to flag stale page cache entries and propagate an error up the stack, potentially leading to data corruption or application-level failures depending on how strictly the calling applications handled these errors.
This issue was not limited to intentionally overlapping direct I/O writes from userspace but could also arise in normal operational contexts involving loop devices where multiple worker threads issued concurrent input/output operations to a backing file. Additionally, when the block size was smaller than the folio size, even non-overlapping direct I/O requests sharing a large folio structure could trigger this race condition due to the granularity of page cache management versus filesystem locking mechanisms. The vulnerability highlighted a fundamental synchronization gap in how ext4 managed the transition between different I/O modes under high concurrency, particularly regarding the ordering of lock acquisition and release relative to memory state changes.
The resolution involved modifying the ext4_dio_write_iter function to insert an inode_dio_wait call before executing write checks during the buffered fallback path. This change ensures that all in-flight direct I/O operations are drained and completed before proceeding with buffered write logic, thereby eliminating the race window. By waiting for pending direct I/O completions, the kernel guarantees that existing pages are cleared prior to submitting new input/output requests via invalidation routines. Furthermore, this synchronization point allows ext4_write_checks to observe the accurate inode size after all direct operations have finished, preventing races between end-of-file zeroing and in-flight direct writes. This fix aligns with industry best practices for handling concurrent memory access patterns by enforcing strict ordering of state transitions through proper locking and waiting mechanisms, effectively mitigating the risk associated with CWE-362 Concurrent Execution using Shared Resource with Improper Synchronization Race Conditions. The mitigation strategy also reflects ATT&CK technique considerations related to resource manipulation during execution phases where timing dependencies can be exploited for denial of service or data integrity violations.