btrfs: Do super block verification before writing it to disk
authorQu Wenruo <wqu@suse.com>
Fri, 11 May 2018 05:35:27 +0000 (13:35 +0800)
committerDavid Sterba <dsterba@suse.com>
Mon, 28 May 2018 16:07:36 +0000 (18:07 +0200)
There are already 2 reports about strangely corrupted super blocks,
where csum still matches but extra garbage gets slipped into super block.

The corruption would looks like:
------
superblock: bytenr=65536, device=/dev/sdc1
---------------------------------------------------------
csum_type               41700 (INVALID)
csum                    0x3b252d3a [match]
bytenr                  65536
flags                   0x1
                        ( WRITTEN )
magic                   _BHRfS_M [match]
...
incompat_flags          0x5b22400000000169
                        ( MIXED_BACKREF |
                          COMPRESS_LZO |
                          BIG_METADATA |
                          EXTENDED_IREF |
                          SKINNY_METADATA |
                          unknown flag: 0x5b22400000000000 )
...
------
Or
------
superblock: bytenr=65536, device=/dev/mapper/x
---------------------------------------------------------
csum_type              35355 (INVALID)
csum_size              32
csum                   0xf0dbeddd [match]
bytenr                 65536
flags                  0x1
                       ( WRITTEN )
magic                  _BHRfS_M [match]
...
incompat_flags         0x176d200000000169
                       ( MIXED_BACKREF |
                         COMPRESS_LZO |
                         BIG_METADATA |
                         EXTENDED_IREF |
                         SKINNY_METADATA |
                         unknown flag: 0x176d200000000000 )
------

Obviously, csum_type and incompat_flags get some garbage, but its csum
still matches, which means kernel calculates the csum based on corrupted
super block memory.
And after manually fixing these values, the filesystem is completely
healthy without any problem exposed by btrfs check.

Although the cause is still unknown, at least detect it and prevent further
corruption.

Both reports have same symptoms, there's an overwrite on offset 192 of
the superblock, by 4 bytes. The superblock structure is not allocated or
freed and stays in the memory for the whole filesystem lifetime, so it's
not a use-after-free kind of error on someone else's leaked page.

As a vague point for the problable cause is mentioning of other system
freezing related to graphic card drivers.

Reported-by: Ken Swenson <flat@imo.uto.moe>
Reported-by: Ben Parsons <9parsonsb@gmail.com>
Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
[ add brief analysis of the reports ]
Signed-off-by: David Sterba <dsterba@suse.com>
fs/btrfs/disk-io.c

index eff8673700362bb4a3d679184612b382fbae0328..a163850915721261bb143595c0c9c2a4a40ce319 100644 (file)
@@ -2610,6 +2610,41 @@ static int btrfs_validate_mount_super(struct btrfs_fs_info *fs_info)
        return validate_super(fs_info, fs_info->super_copy, 0);
 }
 
+/*
+ * Validation of super block at write time.
+ * Some checks like bytenr check will be skipped as their values will be
+ * overwritten soon.
+ * Extra checks like csum type and incompat flags will be done here.
+ */
+static int btrfs_validate_write_super(struct btrfs_fs_info *fs_info,
+                                     struct btrfs_super_block *sb)
+{
+       int ret;
+
+       ret = validate_super(fs_info, sb, -1);
+       if (ret < 0)
+               goto out;
+       if (btrfs_super_csum_type(sb) != BTRFS_CSUM_TYPE_CRC32) {
+               ret = -EUCLEAN;
+               btrfs_err(fs_info, "invalid csum type, has %u want %u",
+                         btrfs_super_csum_type(sb), BTRFS_CSUM_TYPE_CRC32);
+               goto out;
+       }
+       if (btrfs_super_incompat_flags(sb) & ~BTRFS_FEATURE_INCOMPAT_SUPP) {
+               ret = -EUCLEAN;
+               btrfs_err(fs_info,
+               "invalid incompat flags, has 0x%llx valid mask 0x%llx",
+                         btrfs_super_incompat_flags(sb),
+                         (unsigned long long)BTRFS_FEATURE_INCOMPAT_SUPP);
+               goto out;
+       }
+out:
+       if (ret < 0)
+               btrfs_err(fs_info,
+               "super block corruption detected before writing it to disk");
+       return ret;
+}
+
 int open_ctree(struct super_block *sb,
               struct btrfs_fs_devices *fs_devices,
               char *options)
@@ -3770,6 +3805,14 @@ int write_all_supers(struct btrfs_fs_info *fs_info, int max_mirrors)
                flags = btrfs_super_flags(sb);
                btrfs_set_super_flags(sb, flags | BTRFS_HEADER_FLAG_WRITTEN);
 
+               ret = btrfs_validate_write_super(fs_info, sb);
+               if (ret < 0) {
+                       mutex_unlock(&fs_info->fs_devices->device_list_mutex);
+                       btrfs_handle_fs_error(fs_info, -EUCLEAN,
+                               "unexpected superblock corruption detected");
+                       return -EUCLEAN;
+               }
+
                ret = write_dev_supers(dev, sb, max_mirrors);
                if (ret)
                        total_errors++;