f2fs: atomic: fix UAF issue on f2fs_inode_info.atomic_inode

- 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: 3db1de0e58 ("f2fs: change the current atomic write way")
Fixes: f18d007693 ("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>
This commit is contained in:
Chao Yu 2026-05-21 10:15:05 +08:00 committed by Jaegeuk Kim
parent 8b4468ec02
commit e0288584ba
2 changed files with 50 additions and 11 deletions

View 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 @@ static int ra_data_block(struct inode *inode, pgoff_t index)
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 @@ static int ra_data_block(struct inode *inode, pgoff_t index)
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 @@ static int move_data_block(struct inode *inode, block_t bidx,
folio_unlock(folio);
folio_end_dropbehind(folio);
folio_put(folio);
out_iput:
if (atomic_inode)
iput(atomic_inode);
return err;
}

View 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);