]> git.dujemihanovic.xyz Git - u-boot.git/commitdiff
fs: btrfs: limit the mapped length to the original length
authorQu Wenruo <wqu@suse.com>
Mon, 13 Feb 2023 00:37:59 +0000 (08:37 +0800)
committerTom Rini <trini@konsulko.com>
Thu, 23 Feb 2023 18:29:19 +0000 (13:29 -0500)
[BUG]
There is a bug report that btrfs driver caused hang during file read:

  This breaks btrfs on the HiFive Unmatched.

  => pci enum
  PCIE-0: Link up (Gen1-x8, Bus0)
  => nvme scan
  => load nvme 0:2 0x8c000000 /boot/dtb/sifive/hifive-unmatched-a00.dtb
  [hangs]

[CAUSE]
The reporter provided some debug output:

  read_extent_data: cur=615817216, orig_len=16384, cur_len=16384
  read_extent_data: btrfs_map_block: cur_len=479944704; ret=0
  read_extent_data: ret=0
  read_extent_data: cur=615833600, orig_len=4096, cur_len=4096
  read_extent_data: btrfs_map_block: cur_len=479928320; ret=0

Note the second and the last line, the @cur_len is 450+MiB, which is
almost a chunk size.

And inside __btrfs_map_block(), we limits the returned value to stripe
length, but that's depending on the chunk type:

if (map->type & (BTRFS_BLOCK_GROUP_RAID0 | BTRFS_BLOCK_GROUP_RAID1 |
 BTRFS_BLOCK_GROUP_RAID1C3 | BTRFS_BLOCK_GROUP_RAID1C4 |
 BTRFS_BLOCK_GROUP_RAID5 | BTRFS_BLOCK_GROUP_RAID6 |
 BTRFS_BLOCK_GROUP_RAID10 |
 BTRFS_BLOCK_GROUP_DUP)) {
/* we limit the length of each bio to what fits in a stripe */
*length = min_t(u64, ce->size - offset,
      map->stripe_len - stripe_offset);
} else {
*length = ce->size - offset;
}

This means, if the chunk is SINGLE profile, then we don't limit the
returned length at all, and even for other profiles, we can still return
a length much larger than the requested one.

[FIX]
Properly clamp the returned length, preventing it from returning a much
larger range than expected.

Reported-by: Andreas Schwab <schwab@linux-m68k.org>
Signed-off-by: Qu Wenruo <wqu@suse.com>
fs/btrfs/volumes.c

index 4aaaeab663f5654b9ceca589cbb9dcf927aebae0..7d4095d9ca88d9933dd61a216bd91e875956f0d1 100644 (file)
@@ -956,6 +956,7 @@ int __btrfs_map_block(struct btrfs_fs_info *fs_info, int rw,
        struct btrfs_mapping_tree *map_tree = &fs_info->mapping_tree;
        struct cache_extent *ce;
        struct map_lookup *map;
+       u64 orig_len = *length;
        u64 offset;
        u64 stripe_offset;
        u64 *raid_map = NULL;
@@ -1047,6 +1048,7 @@ again:
        } else {
                *length = ce->size - offset;
        }
+       *length = min_t(u64, *length, orig_len);
 
        if (!multi_ret)
                goto out;