summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZhang Yi <yi.zhang@huawei.com>2026-10-09 18:30:05 +0800
committerTheodore Ts'o <tytso@mit.edu>2026-10-15 10:53:12 -0400
commitecabe6bd6175dda07b68bf47ec29f4e422edd9c2 (patch)
treeaba879307b0a47a6c0a7df8f113cc997690233dd
parent5b7a0efa8590929e44ae6b3777502314bd6a07a0 (diff)
downloadlinux-next-ecabe6bd6175dda07b68bf47ec29f4e422edd9c2.tar.gz
linux-next-ecabe6bd6175dda07b68bf47ec29f4e422edd9c2.zip
ext4: recheck extent status tree before block allocation
After acquiring i_data_sem in write mode, recheck that the mapping found via the extent status tree or disk query has not changed. A racing truncate may have trimmed the extent between the earlier lookup and the write lock acquisition, since writeback does not hold i_rwsem or the folio locks covering the full extent. This could cause ext4_map_create_blocks() to allocate blocks beyond the truncated range, potentially leading to quota leaks in the upcomming iomap buffered writeback path, because the iomap writeback infrastructure caches extents beyond the folio range. Therefore, if we find a valid extent and the sequence number has changed, retry the entire lookup to obtain the correct trimmed mapping. Note that a retry only happens when i_es_seq has actually changed, which requires another thread to hold i_data_sem in write mode, so each retry implies real lock contention, and the writer yields on the rwsem slowpath rather than spinning. Moreover, the read-write lock also tries its best to guarantee fairness, so even though no retry limit is added here, livelock should not occur in theory. Also, the retry here mirrors the existing write_ops->iomap_valid() + IOMAP_F_STALE mechanism in iomap, which also rechecks the sequence number and retries without any upper bound, and has not shown livelock problems in practice. In addition, if ext4_map_query_blocks() fails, return the error immediately. Otherwise the following sequence number comparison would use a map->m_seq that may not have been updated, and silently continuing on an error is not advisable. Suggested-by: Jan Kara <jack@suse.cz> Link: https://lore.kernel.org/linux-ext4/b0781809-4759-4e12-be17-71555b764f48@gmail.com/ 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-5-yi.zhang@huaweicloud.com Signed-off-by: Theodore Ts'o <tytso@mit.edu>
-rw-r--r--fs/ext4/inode.c18
1 files changed, 18 insertions, 0 deletions
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index 309c656247e0..86d1e027e543 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -734,6 +734,7 @@ int ext4_map_blocks(handle_t *handle, struct inode *inode,
else
ext4_check_map_extents_env(inode);
+create_retry:
/* Lookup extent status tree firstly */
if (ext4_es_lookup_extent(inode, map->m_lblk, NULL, &es, &map->m_seq)) {
if (ext4_es_is_written(&es) || ext4_es_is_unwritten(&es)) {
@@ -784,6 +785,8 @@ int ext4_map_blocks(handle_t *handle, struct inode *inode,
down_read(&EXT4_I(inode)->i_data_sem);
retval = ext4_map_query_blocks(handle, inode, map, flags);
up_read((&EXT4_I(inode)->i_data_sem));
+ if (retval < 0)
+ return retval;
found:
if (retval > 0 && map->m_flags & EXT4_MAP_MAPPED) {
@@ -820,6 +823,21 @@ found:
* with create == 1 flag.
*/
down_write(&EXT4_I(inode)->i_data_sem);
+
+ /*
+ * Check the validity of the mapping found via the extent status
+ * tree or the disk query. A racing truncate may have changed the
+ * extent, since writeback may not hold i_rwsem or the folio locks
+ * covering the full extent(e.g., the iomap writeback path may
+ * allocate an extent that extends beyond the length of the locked
+ * folio in advance).
+ */
+ if (map->m_seq != READ_ONCE(EXT4_I(inode)->i_es_seq)) {
+ up_write(&EXT4_I(inode)->i_data_sem);
+ map->m_flags = 0;
+ map->m_len = orig_mlen;
+ goto create_retry;
+ }
retval = ext4_map_create_blocks(handle, inode, map, flags);
up_write((&EXT4_I(inode)->i_data_sem));