syzbot
WARNING in ext4_do_writepages
Status:
upstream: reported C repro on 2026/10/30 16:29
Subsystems:
ext4
Labels:
prio:low
[Documentation on labels]
Reported-by: syzbot+97fbc97fdd4e4fa2ed3a@syzkaller.appspotmail.com
First crash: 4d07h, last: 17h59m
✨ AI-generated root cause summary:
In ext4 writeback (mpage_map_one_extent), a WARN_ON_ONCE(!err) is hit because ext4_map_blocks() returns 0 (indicating a hole) for an extent that was queued for writeback by mpage_add_bh_to_extent(). This occurs when a buffer head remains marked mapped, dirty, and delayed/unwritten (for example, during file truncation down via ftruncate/ext4_block_zero_eof or direct I/O fallback paths), while the extent status tree or on-disk extent mapping for the logical block is or becomes a hole. When ext4_map_blocks() is called with EXT4_GET_BLOCKS_IO_SUBMIT, it avoids allocating blocks for holes and returns 0, triggering the warning and returning -EINVAL. In production kernels, the warning does not halt execution and error recovery safely invalidates the page without causing memory corruption, information leaks, or denial of service.
▶
▼
✨ Full AI root cause analysis
Root Cause Analysis
1. Functions, Structs, and Files Involved
fs/ext4/inode.c:
mpage_map_one_extent(): Maps delayed/unwritten extents during writeback. Hits WARN_ON_ONCE(!err) at line 2512 when ext4_map_blocks() returns 0.
mpage_add_bh_to_extent(): Inspects page buffer heads and queues logical blocks into mpd->map when buffer_dirty(bh) && buffer_mapped(bh) && (buffer_delay(bh) || buffer_unwritten(bh)).
ext4_map_blocks(): Maps/allocates blocks. When invoked with EXT4_GET_BLOCKS_IO_SUBMIT, it explicitly avoids creating blocks if the extent status tree or disk query encounters a hole (retval == 0 and !map->m_flags), returning 0.
ext4_block_zero_eof() / ext4_block_do_zero_range() / ext4_load_tail_bh(): Executed during truncation (ext4_truncate()) to zero the tail of the block straddling i_size.
2. Trigger and Invariant Violation
During delayed allocation writeback (ext4_do_writepages() via wb_workfn):
mpage_process_page_bufs() iterates over page buffers within file limits (lblk < blocks) and invokes mpage_add_bh_to_extent().
- An invariant assumed by the writeback code is that any buffer head added to
mpd->map (due to possessing BH_Dirty, BH_Mapped, and either BH_Delay or BH_Unwritten) corresponds to a valid delayed or unwritten extent in the extent tree or disk inode. Consequently, ext4_map_blocks() called with EXT4_GET_BLOCKS_CREATE | EXT4_GET_BLOCKS_IO_SUBMIT is expected to return > 0 (the number of blocks mapped/allocated) or < 0 (an error like -ENOSPC).
- In the reproducer sequence (
open(O_DIRECT) -> sendfile -> open -> ftruncate(7)), a state discrepancy occurs between the page cache buffer head state and the extent status tree / on-disk block state. Specifically, the buffer head for block 0 is marked dirty, mapped, and delayed/unwritten (or re-dirtied during EOF zeroing by ext4_block_zero_eof() -> ext4_block_do_zero_range() -> mark_buffer_dirty(bh)), while the extent status tree or on-disk lookup reports that the block is a hole (!map->m_flags and retval == 0).
- In
ext4_map_blocks():
} else if (retval == 0) {
/*
* Do not allocate blocks for holes in the context of
* data submission path.
*/
if (!map->m_flags && (flags & EXT4_GET_BLOCKS_IO_SUBMIT))
goto out;
}
Because flags includes EXT4_GET_BLOCKS_IO_SUBMIT, ext4_map_blocks() returns 0.
mpage_map_one_extent() treats 0 as an unexpected condition and triggers:
if (WARN_ON_ONCE(!err))
return -EINVAL;
3. Runtime Consequences in Production
- Memory Corruption (
false): No invalid pointers, use-after-free, double-free, or out-of-bounds writes occur.
- Information Leak (
none): No kernel data or pointers are exposed to userspace or the network.
- Denial of Service (
false): In production kernels without CONFIG_PANIC_ON_WARN, WARN_ON_ONCE(!err) logs a warning and returns -EINVAL. In mpage_map_and_submit_extent(), the error is caught, the failure is logged (Delayed block allocation failed for inode...), and dirty pages are invalidated via invalidate_dirty_pages. Normal error recovery completes without a hang, deadlock, resource leak, or system crash.
- Virtualization / Network Candidates (
false): The defect is purely in the ext4 filesystem writeback / page cache subsystem.
Sample crash report:
* Struck through repros no longer work on HEAD.