]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
f2fs: atomic: fix UAF issue on f2fs_inode_info.atomic_inode
authorChao Yu <chao@kernel.org>
Thu, 21 May 2026 02:15:05 +0000 (10:15 +0800)
committerJaegeuk Kim <jaegeuk@kernel.org>
Mon, 22 Jun 2026 19:52:35 +0000 (19:52 +0000)
- ioctl(F2FS_IOC_GARBAGE_COLLECT_RANGE) - shrink
 - f2fs_gc
  - gc_data_segment
   - ra_data_block(cow_inode)
    - mapping = F2FS_I(inode)->atomic_inode->i_mapping
    : f2fs_is_cow_file(cow_inode) is true
 - f2fs_evict_inode(atomic_inode)
  - clear_inode_flag(fi->cow_inode, FI_COW_FILE)
  - F2FS_I(fi->cow_inode)->atomic_inode = NULL
  ...
  - truncate_inode_pages_final(atomic_inode)
    - f2fs_grab_cache_folio(mapping)
    : create folio in atomic_inode->mapping
  - clear_inode(atomic_inode)
   - BUG_ON(atomic_inode->i_data.nrpages)

We need to add a reference on fi->atomic_inode before using its mapping
field during garbage collection, otherwise, it will cause UAF issue.

Cc: stable@kernel.org
Cc: Daeho Jeong <daehojeong@google.com>
Cc: Sunmin Jeong <s_min.jeong@samsung.com>
Fixes: 3db1de0e582c ("f2fs: change the current atomic write way")
Fixes: f18d00769336 ("f2fs: use meta inode for GC of COW file")
Signed-off-by: Chao Yu <chao@kernel.org>
Signed-off-by: Jaegeuk Kim <jaegeuk@kernel.org>
fs/f2fs/gc.c
fs/f2fs/inode.c

index 99bc598898251e227cfd8a8db391b2950159699d..69e0a867219d13ee3b0320336da8e746219c89a1 100644 (file)
@@ -1220,8 +1220,8 @@ static bool is_alive(struct f2fs_sb_info *sbi, struct f2fs_summary *sum,
 static int ra_data_block(struct inode *inode, pgoff_t index)
 {
        struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
-       struct address_space *mapping = f2fs_is_cow_file(inode) ?
-                               F2FS_I(inode)->atomic_inode->i_mapping : inode->i_mapping;
+       struct address_space *mapping = inode->i_mapping;
+       struct inode *atomic_inode = NULL;
        struct dnode_of_data dn;
        struct folio *folio, *efolio;
        struct f2fs_io_info fio = {
@@ -1236,9 +1236,22 @@ static int ra_data_block(struct inode *inode, pgoff_t index)
        };
        int err = 0;
 
+       f2fs_down_read(&F2FS_I(inode)->i_sem);
+       if (f2fs_is_cow_file(inode)) {
+               atomic_inode = igrab(F2FS_I(inode)->atomic_inode);
+               if (!atomic_inode) {
+                       f2fs_up_read(&F2FS_I(inode)->i_sem);
+                       return -EBUSY;
+               }
+               mapping = atomic_inode->i_mapping;
+       }
+       f2fs_up_read(&F2FS_I(inode)->i_sem);
+
        folio = f2fs_grab_cache_folio(mapping, index, true);
-       if (IS_ERR(folio))
-               return PTR_ERR(folio);
+       if (IS_ERR(folio)) {
+               err = PTR_ERR(folio);
+               goto out_iput;
+       }
 
        if (f2fs_lookup_read_extent_cache_block(inode, index,
                                                &dn.data_blkaddr)) {
@@ -1299,11 +1312,16 @@ got_it:
        f2fs_update_iostat(sbi, inode, FS_DATA_READ_IO, F2FS_BLKSIZE);
        f2fs_update_iostat(sbi, NULL, FS_GDATA_READ_IO, F2FS_BLKSIZE);
 
+       if (atomic_inode)
+               iput(atomic_inode);
        return 0;
 put_encrypted_page:
        f2fs_put_page(fio.encrypted_page, true);
 put_folio:
        f2fs_folio_put(folio, true);
+out_iput:
+       if (atomic_inode)
+               iput(atomic_inode);
        return err;
 }
 
@@ -1314,8 +1332,8 @@ put_folio:
 static int move_data_block(struct inode *inode, block_t bidx,
                                int gc_type, unsigned int segno, int off)
 {
-       struct address_space *mapping = f2fs_is_cow_file(inode) ?
-                               F2FS_I(inode)->atomic_inode->i_mapping : inode->i_mapping;
+       struct address_space *mapping = inode->i_mapping;
+       struct inode *atomic_inode = NULL;
        struct f2fs_io_info fio = {
                .sbi = F2FS_I_SB(inode),
                .ino = inode->i_ino,
@@ -1337,10 +1355,23 @@ static int move_data_block(struct inode *inode, block_t bidx,
                                (fio.sbi->gc_mode != GC_URGENT_HIGH) ?
                                CURSEG_ALL_DATA_ATGC : CURSEG_COLD_DATA;
 
+       f2fs_down_read(&F2FS_I(inode)->i_sem);
+       if (f2fs_is_cow_file(inode)) {
+               atomic_inode = igrab(F2FS_I(inode)->atomic_inode);
+               if (!atomic_inode) {
+                       f2fs_up_read(&F2FS_I(inode)->i_sem);
+                       return -EBUSY;
+               }
+               mapping = atomic_inode->i_mapping;
+       }
+       f2fs_up_read(&F2FS_I(inode)->i_sem);
+
        /* do not read out */
        folio = f2fs_grab_cache_folio(mapping, bidx, false);
-       if (IS_ERR(folio))
-               return PTR_ERR(folio);
+       if (IS_ERR(folio)) {
+               err = PTR_ERR(folio);
+               goto out_iput;
+       }
 
        if (!check_valid_map(F2FS_I_SB(inode), segno, off)) {
                err = -ENOENT;
@@ -1473,6 +1504,9 @@ out:
        folio_unlock(folio);
        folio_end_dropbehind(folio);
        folio_put(folio);
+out_iput:
+       if (atomic_inode)
+               iput(atomic_inode);
        return err;
 }
 
index 1694726122e60c44dfee2f2e8307fc7a825b47d8..939d75663a4078204cbdaeed2fd193b916f8e472 100644 (file)
@@ -863,10 +863,15 @@ void f2fs_evict_inode(struct inode *inode)
        f2fs_abort_atomic_write(inode, true);
 
        if (fi->cow_inode && f2fs_is_cow_file(fi->cow_inode)) {
-               clear_inode_flag(fi->cow_inode, FI_COW_FILE);
-               F2FS_I(fi->cow_inode)->atomic_inode = NULL;
-               iput(fi->cow_inode);
+               struct inode *cow_inode = fi->cow_inode;
+
+               f2fs_down_write(&F2FS_I(cow_inode)->i_sem);
+               clear_inode_flag(cow_inode, FI_COW_FILE);
+               F2FS_I(cow_inode)->atomic_inode = NULL;
                fi->cow_inode = NULL;
+               f2fs_up_write(&F2FS_I(cow_inode)->i_sem);
+
+               iput(cow_inode);
        }
 
        trace_f2fs_evict_inode(inode);