btrfs: don't use btrfs_chunk::sub_stripes from disk
authorQu Wenruo <wqu@suse.com>
Fri, 21 Oct 2022 00:43:45 +0000 (08:43 +0800)
committerDavid Sterba <dsterba@suse.com>
Tue, 25 Oct 2022 08:17:33 +0000 (10:17 +0200)
[BUG]
There are two reports (the earliest one from LKP, a more recent one from
kernel bugzilla) that we can have some chunks with 0 as sub_stripes.

This will cause divide-by-zero errors at btrfs_rmap_block, which is
introduced by a recent kernel patch ac0677348f3c ("btrfs: merge
calculations for simple striped profiles in btrfs_rmap_block"):

if (map->type & (BTRFS_BLOCK_GROUP_RAID0 |
 BTRFS_BLOCK_GROUP_RAID10)) {
stripe_nr = stripe_nr * map->num_stripes + i;
stripe_nr = div_u64(stripe_nr, map->sub_stripes); <<<
}

[CAUSE]
From the more recent report, it has been proven that we have some chunks
with 0 as sub_stripes, mostly caused by older mkfs.

It turns out that the mkfs.btrfs fix is only introduced in 6718ab4d33aa
("btrfs-progs: Initialize sub_stripes to 1 in btrfs_alloc_data_chunk")
which is included in v5.4 btrfs-progs release.

So there would be quite some old filesystems with such 0 sub_stripes.

[FIX]
Just don't trust the sub_stripes values from disk.

We have a trusted btrfs_raid_array[] to fetch the correct sub_stripes
numbers for each profile and that are fixed.

By this, we can keep the compatibility with older filesystems while
still avoid divide-by-zero bugs.

Reported-by: kernel test robot <oliver.sang@intel.com>
Reported-by: Viktor Kuzmin <kvaster@gmail.com>
Bugzilla: https://bugzilla.kernel.org/show_bug.cgi?id=216559
Fixes: ac0677348f3c ("btrfs: merge calculations for simple striped profiles in btrfs_rmap_block")
CC: stable@vger.kernel.org # 6.0
Reviewed-by: Su Yue <glass@fydeos.io>
Reviewed-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
fs/btrfs/volumes.c

index 94ba46d..a8d4bc6 100644 (file)
@@ -7142,6 +7142,7 @@ static int read_one_chunk(struct btrfs_key *key, struct extent_buffer *leaf,
        u64 devid;
        u64 type;
        u8 uuid[BTRFS_UUID_SIZE];
+       int index;
        int num_stripes;
        int ret;
        int i;
@@ -7149,6 +7150,7 @@ static int read_one_chunk(struct btrfs_key *key, struct extent_buffer *leaf,
        logical = key->offset;
        length = btrfs_chunk_length(leaf, chunk);
        type = btrfs_chunk_type(leaf, chunk);
+       index = btrfs_bg_flags_to_raid_index(type);
        num_stripes = btrfs_chunk_num_stripes(leaf, chunk);
 
 #if BITS_PER_LONG == 32
@@ -7202,7 +7204,15 @@ static int read_one_chunk(struct btrfs_key *key, struct extent_buffer *leaf,
        map->io_align = btrfs_chunk_io_align(leaf, chunk);
        map->stripe_len = btrfs_chunk_stripe_len(leaf, chunk);
        map->type = type;
-       map->sub_stripes = btrfs_chunk_sub_stripes(leaf, chunk);
+       /*
+        * We can't use the sub_stripes value, as for profiles other than
+        * RAID10, they may have 0 as sub_stripes for filesystems created by
+        * older mkfs (<v5.4).
+        * In that case, it can cause divide-by-zero errors later.
+        * Since currently sub_stripes is fixed for each profile, let's
+        * use the trusted value instead.
+        */
+       map->sub_stripes = btrfs_raid_array[index].sub_stripes;
        map->verified_stripes = 0;
        em->orig_block_len = btrfs_calc_stripe_length(em);
        for (i = 0; i < num_stripes; i++) {