f2fs: enhance sanity_check_raw_super() to avoid potential overflows
authorJaegeuk Kim <jaegeuk@kernel.org>
Sat, 28 Apr 2018 02:03:22 +0000 (19:03 -0700)
committerJaegeuk Kim <jaegeuk@kernel.org>
Thu, 31 May 2018 18:31:49 +0000 (11:31 -0700)
In order to avoid the below overflow issue, we should have checked the
boundaries in superblock before reaching out to allocation. As Linus suggested,
the right place should be sanity_check_raw_super().

Dr Silvio Cesare of InfoSect reported:

There are integer overflows with using the cp_payload superblock field in the
f2fs filesystem potentially leading to memory corruption.

include/linux/f2fs_fs.h

struct f2fs_super_block {
...
        __le32 cp_payload;

fs/f2fs/f2fs.h

typedef u32 block_t;    /*
                         * should not change u32, since it is the on-disk block
                         * address format, __le32.
                         */
...

static inline block_t __cp_payload(struct f2fs_sb_info *sbi)
{
        return le32_to_cpu(F2FS_RAW_SUPER(sbi)->cp_payload);
}

fs/f2fs/checkpoint.c

        block_t start_blk, orphan_blocks, i, j;
...
        start_blk = __start_cp_addr(sbi) + 1 + __cp_payload(sbi);
        orphan_blocks = __start_sum_addr(sbi) - 1 - __cp_payload(sbi);

+++ integer overflows

...
        unsigned int cp_blks = 1 + __cp_payload(sbi);
...
        sbi->ckpt = kzalloc(cp_blks * blk_size, GFP_KERNEL);

+++ integer overflow leading to incorrect heap allocation.

        int cp_payload_blks = __cp_payload(sbi);
...
        ckpt->cp_pack_start_sum = cpu_to_le32(1 + cp_payload_blks +
                        orphan_blocks);

+++ sign bug and integer overflow

...
        for (i = 1; i < 1 + cp_payload_blks; i++)

+++ integer overflow

...

      sbi->max_orphans = (sbi->blocks_per_seg - F2FS_CP_PACKS -
                        NR_CURSEG_TYPE - __cp_payload(sbi)) *
                                F2FS_ORPHANS_PER_BLOCK;

+++ integer overflow

Reported-by: Greg KH <greg@kroah.com>
Reported-by: Silvio Cesare <silvio.cesare@gmail.com>
Suggested-by: Linus Torvalds <torvalds@linux-foundation.org>
Reviewed-by: Chao Yu <yuchao0@huawei.com>
Signed-off-by: Jaegeuk Kim <jaegeuk@kernel.org>
fs/f2fs/super.c

index baab473d728e6d64ec4b1effbe5a2bc351b548c8..7b7ad3eb8d8a9a9a95418b2a2c9916710fe99f61 100644 (file)
@@ -2139,6 +2139,8 @@ static inline bool sanity_check_area_boundary(struct f2fs_sb_info *sbi,
 static int sanity_check_raw_super(struct f2fs_sb_info *sbi,
                                struct buffer_head *bh)
 {
+       block_t segment_count, segs_per_sec, secs_per_zone;
+       block_t total_sections, blocks_per_seg;
        struct f2fs_super_block *raw_super = (struct f2fs_super_block *)
                                        (bh->b_data + F2FS_SUPER_OFFSET);
        struct super_block *sb = sbi->sb;
@@ -2195,6 +2197,72 @@ static int sanity_check_raw_super(struct f2fs_sb_info *sbi,
                return 1;
        }
 
+       segment_count = le32_to_cpu(raw_super->segment_count);
+       segs_per_sec = le32_to_cpu(raw_super->segs_per_sec);
+       secs_per_zone = le32_to_cpu(raw_super->secs_per_zone);
+       total_sections = le32_to_cpu(raw_super->section_count);
+
+       /* blocks_per_seg should be 512, given the above check */
+       blocks_per_seg = 1 << le32_to_cpu(raw_super->log_blocks_per_seg);
+
+       if (segment_count > F2FS_MAX_SEGMENT ||
+                               segment_count < F2FS_MIN_SEGMENTS) {
+               f2fs_msg(sb, KERN_INFO,
+                       "Invalid segment count (%u)",
+                       segment_count);
+               return 1;
+       }
+
+       if (total_sections > segment_count ||
+                       total_sections < F2FS_MIN_SEGMENTS ||
+                       segs_per_sec > segment_count || !segs_per_sec) {
+               f2fs_msg(sb, KERN_INFO,
+                       "Invalid segment/section count (%u, %u x %u)",
+                       segment_count, total_sections, segs_per_sec);
+               return 1;
+       }
+
+       if ((segment_count / segs_per_sec) < total_sections) {
+               f2fs_msg(sb, KERN_INFO,
+                       "Small segment_count (%u < %u * %u)",
+                       segment_count, segs_per_sec, total_sections);
+               return 1;
+       }
+
+       if (segment_count > (le32_to_cpu(raw_super->block_count) >> 9)) {
+               f2fs_msg(sb, KERN_INFO,
+                       "Wrong segment_count / block_count (%u > %u)",
+                       segment_count, le32_to_cpu(raw_super->block_count));
+               return 1;
+       }
+
+       if (secs_per_zone > total_sections) {
+               f2fs_msg(sb, KERN_INFO,
+                       "Wrong secs_per_zone (%u > %u)",
+                       secs_per_zone, total_sections);
+               return 1;
+       }
+       if (le32_to_cpu(raw_super->extension_count) > F2FS_MAX_EXTENSION ||
+                       raw_super->hot_ext_count > F2FS_MAX_EXTENSION ||
+                       (le32_to_cpu(raw_super->extension_count) +
+                       raw_super->hot_ext_count) > F2FS_MAX_EXTENSION) {
+               f2fs_msg(sb, KERN_INFO,
+                       "Corrupted extension count (%u + %u > %u)",
+                       le32_to_cpu(raw_super->extension_count),
+                       raw_super->hot_ext_count,
+                       F2FS_MAX_EXTENSION);
+               return 1;
+       }
+
+       if (le32_to_cpu(raw_super->cp_payload) >
+                               (blocks_per_seg - F2FS_CP_PACKS)) {
+               f2fs_msg(sb, KERN_INFO,
+                       "Insane cp_payload (%u > %u)",
+                       le32_to_cpu(raw_super->cp_payload),
+                       blocks_per_seg - F2FS_CP_PACKS);
+               return 1;
+       }
+
        /* check reserved ino info */
        if (le32_to_cpu(raw_super->node_ino) != 1 ||
                le32_to_cpu(raw_super->meta_ino) != 2 ||
@@ -2207,13 +2275,6 @@ static int sanity_check_raw_super(struct f2fs_sb_info *sbi,
                return 1;
        }
 
-       if (le32_to_cpu(raw_super->segment_count) > F2FS_MAX_SEGMENT) {
-               f2fs_msg(sb, KERN_INFO,
-                       "Invalid segment count (%u)",
-                       le32_to_cpu(raw_super->segment_count));
-               return 1;
-       }
-
        /* check CP/SIT/NAT/SSA/MAIN_AREA area boundary */
        if (sanity_check_area_boundary(sbi, bh))
                return 1;