Commit 172cfa0f authored by Baokun Li's avatar Baokun Li Committed by sanglipeng1
Browse files

ext4: avoid bb_free and bb_fragments inconsistency in mb_free_blocks()

stable inclusion
from stable-v5.10.212
commit 2871728127264603b6a289c1e4c1d7fa80fe5c4b
category: bugfix
bugzilla: https://gitee.com/openeuler/kernel/issues/IAGOP2

Reference: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=2871728127264603b6a289c1e4c1d7fa80fe5c4b



--------------------------------

commit 2331fd4a49864e1571b4f50aa3aa1536ed6220d0 upstream.

After updating bb_free in mb_free_blocks, it is possible to return without
updating bb_fragments because the block being freed is found to have
already been freed, which leads to inconsistency between bb_free and
bb_fragments.

Since the group may be unlocked in ext4_grp_locked_error(), this can lead
to problems such as dividing by zero when calculating the average fragment
length. Hence move the update of bb_free to after the block double-free
check guarantees that the corresponding statistics are updated only after
the core block bitmap is modified.

Fixes: eabe0444 ("ext4: speed-up releasing blocks on commit")
CC:  <stable@vger.kernel.org> # 3.10
Suggested-by: default avatarJan Kara <jack@suse.cz>
Signed-off-by: default avatarBaokun Li <libaokun1@huawei.com>
Reviewed-by: default avatarJan Kara <jack@suse.cz>
Link: https://lore.kernel.org/r/20240104142040.2835097-5-libaokun1@huawei.com


Signed-off-by: default avatarTheodore Ts'o <tytso@mit.edu>
Signed-off-by: default avatarBaokun Li <libaokun1@huawei.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: default avatarsanglipeng1 <sanglipeng1@jd.com>
parent b0a09b79
Loading
Loading
Loading
Loading
+21 −18
Original line number Diff line number Diff line
@@ -1497,11 +1497,6 @@ static void mb_free_blocks(struct inode *inode, struct ext4_buddy *e4b,
	mb_check_buddy(e4b);
	mb_free_blocks_double(inode, e4b, first, count);

	this_cpu_inc(discard_pa_seq);
	e4b->bd_info->bb_free += count;
	if (first < e4b->bd_info->bb_first_free)
		e4b->bd_info->bb_first_free = first;

	/* access memory sequentially: check left neighbour,
	 * clear range and then check right neighbour
	 */
@@ -1515,23 +1510,31 @@ static void mb_free_blocks(struct inode *inode, struct ext4_buddy *e4b,
		struct ext4_sb_info *sbi = EXT4_SB(sb);
		ext4_fsblk_t blocknr;

		/*
		 * Fastcommit replay can free already freed blocks which
		 * corrupts allocation info. Regenerate it.
		 */
		if (sbi->s_mount_state & EXT4_FC_REPLAY) {
			mb_regenerate_buddy(e4b);
			goto check;
		}

		blocknr = ext4_group_first_block_no(sb, e4b->bd_group);
		blocknr += EXT4_C2B(sbi, block);
		if (!(sbi->s_mount_state & EXT4_FC_REPLAY)) {
		ext4_grp_locked_error(sb, e4b->bd_group,
					      inode ? inode->i_ino : 0,
					      blocknr,
				      inode ? inode->i_ino : 0, blocknr,
				      "freeing already freed block (bit %u); block bitmap corrupt.",
				      block);
			ext4_mark_group_bitmap_corrupted(
				sb, e4b->bd_group,
		ext4_mark_group_bitmap_corrupted(sb, e4b->bd_group,
				EXT4_GROUP_INFO_BBITMAP_CORRUPT);
		} else {
			mb_regenerate_buddy(e4b);
		}
		goto done;
		return;
	}

	this_cpu_inc(discard_pa_seq);
	e4b->bd_info->bb_free += count;
	if (first < e4b->bd_info->bb_first_free)
		e4b->bd_info->bb_first_free = first;

	/* let's maintain fragments counter */
	if (left_is_free && right_is_free)
		e4b->bd_info->bb_fragments--;
@@ -1556,8 +1559,8 @@ static void mb_free_blocks(struct inode *inode, struct ext4_buddy *e4b,
	if (first <= last)
		mb_buddy_mark_free(e4b, first >> 1, last >> 1);

done:
	mb_set_largest_free_order(sb, e4b->bd_info);
check:
	mb_check_buddy(e4b);
}