diff mbox series

fs: btrfs: limit the mapped length to the original length

Message ID 99e2c01bb0366af9aec7522f7109c74b09c5509d.1676248644.git.wqu@suse.com
State Accepted
Commit 511a1303c9cf9663c7d4312e3a0693319f41095b
Delegated to: Tom Rini
Headers show
Series fs: btrfs: limit the mapped length to the original length | expand

Commit Message

Qu Wenruo Feb. 13, 2023, 12:37 a.m. UTC
[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 | 2 ++
 1 file changed, 2 insertions(+)

Comments

Andreas Schwab Feb. 13, 2023, 9:01 p.m. UTC | #1
On Feb 13 2023, Qu Wenruo wrote:

> diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c
> index 4aaaeab663f5..7d4095d9ca88 100644
> --- a/fs/btrfs/volumes.c
> +++ b/fs/btrfs/volumes.c
> @@ -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;

I can confirm that this fixes the issue.
Tom Rini Feb. 24, 2023, 2:43 p.m. UTC | #2
On Mon, Feb 13, 2023 at 08:37:59AM +0800, Qu Wenruo wrote:

> [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>

Applied to u-boot/master, thanks!
diff mbox series

Patch

diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c
index 4aaaeab663f5..7d4095d9ca88 100644
--- a/fs/btrfs/volumes.c
+++ b/fs/btrfs/volumes.c
@@ -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;