ext4: add inode table check in __ext4_get_inode_loc to aovid possible infinite loop
authorBaokun Li <libaokun1@huawei.com>
Wed, 17 Aug 2022 13:27:01 +0000 (21:27 +0800)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 12 Jan 2023 10:59:00 +0000 (11:59 +0100)
commit eee22187b53611e173161e38f61de1c7ecbeb876 upstream.

In do_writepages, if the value returned by ext4_writepages is "-ENOMEM"
and "wbc->sync_mode == WB_SYNC_ALL", retry until the condition is not met.

In __ext4_get_inode_loc, if the bh returned by sb_getblk is NULL,
the function returns -ENOMEM.

In __getblk_slow, if the return value of grow_buffers is less than 0,
the function returns NULL.

When the three processes are connected in series like the following stack,
an infinite loop may occur:

do_writepages <--- keep retrying
 ext4_writepages
  mpage_map_and_submit_extent
   mpage_map_one_extent
    ext4_map_blocks
     ext4_ext_map_blocks
      ext4_ext_handle_unwritten_extents
       ext4_ext_convert_to_initialized
        ext4_split_extent
         ext4_split_extent_at
          __ext4_ext_dirty
           __ext4_mark_inode_dirty
            ext4_reserve_inode_write
             ext4_get_inode_loc
              __ext4_get_inode_loc <--- return -ENOMEM
               sb_getblk
                __getblk_gfp
                 __getblk_slow <--- return NULL
                  grow_buffers
                   grow_dev_page <--- return -ENXIO
                    ret = (block < end_block) ? 1 : -ENXIO;

In this issue, bg_inode_table_hi is overwritten as an incorrect value.
As a result, `block < end_block` cannot be met in grow_dev_page.
Therefore, __ext4_get_inode_loc always returns '-ENOMEM' and do_writepages
keeps retrying. As a result, the writeback process is in the D state due
to an infinite loop.

Add a check on inode table block in the __ext4_get_inode_loc function by
referring to ext4_read_inode_bitmap to avoid this infinite loop.

Cc: stable@kernel.org
Signed-off-by: Baokun Li <libaokun1@huawei.com>
Reviewed-by: Ritesh Harjani (IBM) <ritesh.list@gmail.com>
Link: https://lore.kernel.org/r/20220817132701.3015912-3-libaokun1@huawei.com
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
fs/ext4/inode.c

index 26e8859..a381dc0 100644 (file)
@@ -4299,9 +4299,17 @@ static int __ext4_get_inode_loc(struct super_block *sb, unsigned long ino,
        inodes_per_block = EXT4_SB(sb)->s_inodes_per_block;
        inode_offset = ((ino - 1) %
                        EXT4_INODES_PER_GROUP(sb));
-       block = ext4_inode_table(sb, gdp) + (inode_offset / inodes_per_block);
        iloc->offset = (inode_offset % inodes_per_block) * EXT4_INODE_SIZE(sb);
 
+       block = ext4_inode_table(sb, gdp);
+       if ((block <= le32_to_cpu(EXT4_SB(sb)->s_es->s_first_data_block)) ||
+           (block >= ext4_blocks_count(EXT4_SB(sb)->s_es))) {
+               ext4_error(sb, "Invalid inode table block %llu in "
+                          "block_group %u", block, iloc->block_group);
+               return -EFSCORRUPTED;
+       }
+       block += (inode_offset / inodes_per_block);
+
        bh = sb_getblk(sb, block);
        if (unlikely(!bh))
                return -ENOMEM;