diff options
| author | Zhang Yi <yi.zhang@huawei.com> | 2026-10-09 18:30:19 +0800 |
|---|---|---|
| committer | Theodore Ts'o <tytso@mit.edu> | 2026-10-15 10:53:13 -0400 |
| commit | 3c612a572094291a752fe983af2868cde634ba02 (patch) | |
| tree | 00463c9f214e713793f65b9ddadebf535570622b /include | |
| parent | 68738d77ce86cd4028e23ac2fbc45c56683bea7a (diff) | |
ext4: drain writeback before removing extents on the iomap path
Because the iomap infrastructure does not always create an ifs to
manage sub-folio state when folio size is larger than blocksize,
invalidating a partial dirty folio during punch hole may fail to
clear the dirty state of the affected range. As a result, writeback
of that folio may observe a hole. At writeback submit time,
ext4_map_blocks() already handles this case and will not allocate
blocks. However, when punch hole races with writeback, the
following scenario can cause I/O completion to encounter a hole.
punch hole writeback
---------- ---------
ext4_punch_hole()
ext4_truncate_page_cache_block_range()
iomap_invalidate_folio() [partial folio]
iomap_clear_range_dirty()
-- no ifs, sub-block dirty bits NOT cleared
ext4_iomap_writepages()
iomap_writepages()
ext4_iomap_writeback_submit()
ext4_iomap_map_writeback_range()
ext4_map_blocks(IO_SUBMIT)
-> extent exists, not a hole
submit_io() -> bio in flight
down_write(&i_data_sem)
ext4_es_remove_extent()
ext4_ext_remove_space()
-> extent removed, hole inserted
up_write(&i_data_sem)
[ transaction commits, freed blocks released to buddy ]
[ allocator reuses freed blocks for another inode ]
[bio completes]
ext4_iomap_finish_ioend()
ext4_convert_unwritten_extents()
ext4_map_blocks(IO_CONVERT_EXT)
-> returns 0 (hole found)
[bio writes to reallocated blocks ]
-> silent data corruption
If the bio completes after the freed blocks have been reallocated to
another inode, it silently overwrites them and causes data corruption.
Therefore, on the iomap path, drain writeback in ext4_punch_hole() after
partial zeroing and before starting the freeing transaction, so any bio
in flight on a partial folio completes against blocks still owned by
this inode before extent removal. Once extent removal begins, the ES
hole insert and the existing IO_SUBMIT short-circuit prevent further bio
submission.
This is a workaround. The proper fix is for the iomap infrastructure to
always maintain an ifs when folio size is larger than blocksize, so that
partial invalidate reliably clears sub-block dirty bits and no draining
is needed.
Signed-off-by: Zhang Yi <yi.zhang@huawei.com>
Reviewed-by: Ojaswin Mujoo <ojaswin@linux.ibm.com>
Tested-by: syzbot@syzkaller.appspotmail.com
Link: https://patch.msgid.link/20261009103033.2920530-19-yi.zhang@huaweicloud.com
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
Diffstat (limited to 'include')
0 files changed, 0 insertions, 0 deletions
