Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

f2fs: fix node_cnt race between extent node destroy and writeback

f2fs_destroy_extent_node() does not set FI_NO_EXTENT before clearing
extent nodes. When called from f2fs_drop_inode() with I_SYNC set,
concurrent kworker writeback can insert new extent nodes into the same
extent tree, racing with the destroy and triggering f2fs_bug_on() in
__destroy_extent_node(). The scenario is as follows:

drop inode writeback
- iput
- f2fs_drop_inode // I_SYNC set
- f2fs_destroy_extent_node
- __destroy_extent_node
- while (node_cnt) {
write_lock(&et->lock)
__free_extent_tree
write_unlock(&et->lock)
- __writeback_single_inode
- f2fs_outplace_write_data
- f2fs_update_read_extent_cache
- __update_extent_tree_range
// FI_NO_EXTENT not set,
// insert new extent node
} // node_cnt == 0, exit while
- f2fs_bug_on(node_cnt) // node_cnt > 0

Additionally, __update_extent_tree_range() only checks FI_NO_EXTENT for
EX_READ type, leaving EX_BLOCK_AGE updates completely unprotected.

This patch set FI_NO_EXTENT under et->lock in __destroy_extent_node(),
consistent with other callers (__update_extent_tree_range and
__drop_extent_tree) and check FI_NO_EXTENT for both EX_READ and
EX_BLOCK_AGE tree.

Fixes: 3fc5d5a182f6 ("f2fs: fix to shrink read extent node in batches")
Cc: stable@vger.kernel.org
Signed-off-by: Yongpeng Yang <yangyongpeng@xiaomi.com>
Reviewed-by: Chao Yu <chao@kernel.org>
Signed-off-by: Jaegeuk Kim <jaegeuk@kernel.org>

authored by

Yongpeng Yang and committed by
Jaegeuk Kim
ed78aeeb 2a3db1e0

+10 -7
+10 -7
fs/f2fs/extent_cache.c
··· 119 119 if (!__init_may_extent_tree(inode, type)) 120 120 return false; 121 121 122 + if (is_inode_flag_set(inode, FI_NO_EXTENT)) 123 + return false; 124 + 122 125 if (type == EX_READ) { 123 - if (is_inode_flag_set(inode, FI_NO_EXTENT)) 124 - return false; 125 126 if (is_inode_flag_set(inode, FI_COMPRESSED_FILE) && 126 127 !f2fs_sb_has_readonly(F2FS_I_SB(inode))) 127 128 return false; ··· 645 644 646 645 while (atomic_read(&et->node_cnt)) { 647 646 write_lock(&et->lock); 647 + if (!is_inode_flag_set(inode, FI_NO_EXTENT)) 648 + set_inode_flag(inode, FI_NO_EXTENT); 648 649 node_cnt += __free_extent_tree(sbi, et, nr_shrink); 649 650 write_unlock(&et->lock); 650 651 } ··· 691 688 692 689 write_lock(&et->lock); 693 690 694 - if (type == EX_READ) { 695 - if (is_inode_flag_set(inode, FI_NO_EXTENT)) { 696 - write_unlock(&et->lock); 697 - return; 698 - } 691 + if (is_inode_flag_set(inode, FI_NO_EXTENT)) { 692 + write_unlock(&et->lock); 693 + return; 694 + } 699 695 696 + if (type == EX_READ) { 700 697 prev = et->largest; 701 698 dei.len = 0; 702 699