ext4: fix memory leak in ext4_fill_super
Buffer head references must be released before calling kill_bdev(); otherwise the buffer head (and its page referenced by b_data) will not be freed by kill_bdev, and subsequently that bh will be leaked. If blocksizes differ, sb_set_blocksize() will kill current buffers and page cache by using kill_bdev(). And then super block will be reread again but using correct blocksize this time. sb_set_blocksize() didn't fully free superblock page and buffer head, and being busy, they were not freed and instead leaked. This can easily be reproduced by calling an infinite loop of: systemctl start <ext4_on_lvm>.mount, and systemctl stop <ext4_on_lvm>.mount ... since systemd creates a cgroup for each slice which it mounts, and the bh leak get amplified by a dying memory cgroup that also never gets freed, and memory consumption is much more easily noticed. Fixes:ce40733ce9
("ext4: Check for return value from sb_set_blocksize") Fixes:ac27a0ec11
("ext4: initial copy of files from ext3") Link: https://lore.kernel.org/r/20210521075533.95732-1-amakhalov@vmware.com Signed-off-by: Alexey Makhalov <amakhalov@vmware.com> Signed-off-by: Theodore Ts'o <tytso@mit.edu> Cc: stable@kernel.org
This commit is contained in:
Родитель
a7ba36bc94
Коммит
afd09b617d
|
@ -4462,14 +4462,20 @@ static int ext4_fill_super(struct super_block *sb, void *data, int silent)
|
|||
}
|
||||
|
||||
if (sb->s_blocksize != blocksize) {
|
||||
/*
|
||||
* bh must be released before kill_bdev(), otherwise
|
||||
* it won't be freed and its page also. kill_bdev()
|
||||
* is called by sb_set_blocksize().
|
||||
*/
|
||||
brelse(bh);
|
||||
/* Validate the filesystem blocksize */
|
||||
if (!sb_set_blocksize(sb, blocksize)) {
|
||||
ext4_msg(sb, KERN_ERR, "bad block size %d",
|
||||
blocksize);
|
||||
bh = NULL;
|
||||
goto failed_mount;
|
||||
}
|
||||
|
||||
brelse(bh);
|
||||
logical_sb_block = sb_block * EXT4_MIN_BLOCK_SIZE;
|
||||
offset = do_div(logical_sb_block, blocksize);
|
||||
bh = ext4_sb_bread_unmovable(sb, logical_sb_block);
|
||||
|
@ -5202,8 +5208,9 @@ failed_mount:
|
|||
kfree(get_qf_name(sb, sbi, i));
|
||||
#endif
|
||||
fscrypt_free_dummy_policy(&sbi->s_dummy_enc_policy);
|
||||
ext4_blkdev_remove(sbi);
|
||||
/* ext4_blkdev_remove() calls kill_bdev(), release bh before it. */
|
||||
brelse(bh);
|
||||
ext4_blkdev_remove(sbi);
|
||||
out_fail:
|
||||
sb->s_fs_info = NULL;
|
||||
kfree(sbi->s_blockgroup_lock);
|
||||
|
|
Загрузка…
Ссылка в новой задаче