]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
ext4: prevent stale extent cache entries caused by concurrent fiemap
authorZhang Yi <yi.zhang@huawei.com>
Wed, 23 Apr 2025 08:52:52 +0000 (16:52 +0800)
committerTheodore Ts'o <tytso@mit.edu>
Wed, 14 May 2025 14:42:12 +0000 (10:42 -0400)
The ext4_fiemap() currently invokes ext4_ext_precache() and
iomap_fiemap() to preload the extent cache and query mapping information
without holding the inode's i_rwsem. This can result in stale extent
cache entries when competing with operations such as
ext4_collapse_range() which calls ext4_ext_remove_space() or
ext4_ext_shift_extents().

The problem arises when ext4_ext_remove_space() temporarily releases
i_data_sem due to insufficient journal credits. During this interval, a
concurrent ext4_fiemap() may cache extent entries that are about to be
deleted. As a result, these cached entries become stale and inconsistent
with the actual extents.

Loading the extents cache without holding the inode's i_rwsem or the
mapping's invalidate_lock is not permitted besides during the writeback.
Fix this by holding the i_rwsem in ext4_fiemap().

Signed-off-by: Zhang Yi <yi.zhang@huawei.com>
Link: https://patch.msgid.link/20250423085257.122685-5-yi.zhang@huaweicloud.com
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
fs/ext4/extents.c

index 8a5724b2dc517f0493eff380f6758611121a7635..3adf05fbdd591fd02214872c223414b8ed8092c3 100644 (file)
@@ -4963,10 +4963,11 @@ int ext4_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo,
 {
        int error = 0;
 
+       inode_lock_shared(inode);
        if (fieinfo->fi_flags & FIEMAP_FLAG_CACHE) {
                error = ext4_ext_precache(inode);
                if (error)
-                       return error;
+                       goto unlock;
                fieinfo->fi_flags &= ~FIEMAP_FLAG_CACHE;
        }
 
@@ -4977,15 +4978,19 @@ int ext4_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo,
         */
        error = ext4_fiemap_check_ranges(inode, start, &len);
        if (error)
-               return error;
+               goto unlock;
 
        if (fieinfo->fi_flags & FIEMAP_FLAG_XATTR) {
                fieinfo->fi_flags &= ~FIEMAP_FLAG_XATTR;
-               return iomap_fiemap(inode, fieinfo, start, len,
-                                   &ext4_iomap_xattr_ops);
+               error = iomap_fiemap(inode, fieinfo, start, len,
+                                    &ext4_iomap_xattr_ops);
+       } else {
+               error = iomap_fiemap(inode, fieinfo, start, len,
+                                    &ext4_iomap_report_ops);
        }
-
-       return iomap_fiemap(inode, fieinfo, start, len, &ext4_iomap_report_ops);
+unlock:
+       inode_unlock_shared(inode);
+       return error;
 }
 
 int ext4_get_es_cache(struct inode *inode, struct fiemap_extent_info *fieinfo,