diff mbox series

btrfs: handle missing chunk mapping more gracefully

Message ID 7ff90508841683ca3aaeb5c84e27d7d823218146.1670389796.git.wqu@suse.com (mailing list archive)
State New, archived
Headers show
Series btrfs: handle missing chunk mapping more gracefully | expand

Commit Message

Qu Wenruo Dec. 7, 2022, 5:09 a.m. UTC
[BUG]
During my scrub rework, I did a stupid thing like this:

        bio->bi_iter.bi_sector = stripe->logical;
        btrfs_submit_bio(fs_info, bio, stripe->mirror_num);

Above bi_sector assignment is using logical address directly, which
lacks ">> SECTOR_SHIFT".

This results a read on a range which has no chunk mapping.

This results the following crash:

 BTRFS critical (device dm-1): unable to find logical 11274289152 length 65536
 assertion failed: !IS_ERR(em), in fs/btrfs/volumes.c:6387
 ------------[ cut here ]------------

Sure this is all my fault, but this shows a possible problem in real
world, that some bitflip in file extents/tree block can point to
unmapped ranges, and trigger above ASSERT().

[PROBLEMS]
In above call chain, there are 2 locations not properly handling the
errors:

- __btrfs_map_block()
  If btrfs_get_chunk_map() returned error, we just trigger an ASSERT().

- btrfs_submit_bio()
  If the returned mapped length is smaller than expected, we just BUG().

[FIX]
This patch will fix the problems by:

- Add extra WARN_ON() if btrfs_get_chunk_map() failed
  I know syzbot will complain, but it's better noisy for fstests.

- Replace the ASSERT()
  By returning the error.

- Handle the error when mapped length is smaller than expected length

Signed-off-by: Qu Wenruo <wqu@suse.com>
---
 fs/btrfs/bio.c     | 6 +++++-
 fs/btrfs/volumes.c | 5 ++++-
 2 files changed, 9 insertions(+), 2 deletions(-)

Comments

Boris Burkov Jan. 27, 2023, 8:10 p.m. UTC | #1
On Wed, Dec 07, 2022 at 01:09:59PM +0800, Qu Wenruo wrote:
> [BUG]
> During my scrub rework, I did a stupid thing like this:
> 
>         bio->bi_iter.bi_sector = stripe->logical;
>         btrfs_submit_bio(fs_info, bio, stripe->mirror_num);
> 
> Above bi_sector assignment is using logical address directly, which
> lacks ">> SECTOR_SHIFT".
> 
> This results a read on a range which has no chunk mapping.
> 
> This results the following crash:
> 
>  BTRFS critical (device dm-1): unable to find logical 11274289152 length 65536
>  assertion failed: !IS_ERR(em), in fs/btrfs/volumes.c:6387
>  ------------[ cut here ]------------
> 
> Sure this is all my fault, but this shows a possible problem in real
> world, that some bitflip in file extents/tree block can point to
> unmapped ranges, and trigger above ASSERT().
> 
> [PROBLEMS]
> In above call chain, there are 2 locations not properly handling the
> errors:
> 
> - __btrfs_map_block()
>   If btrfs_get_chunk_map() returned error, we just trigger an ASSERT().
> 
> - btrfs_submit_bio()
>   If the returned mapped length is smaller than expected, we just BUG().
> 
> [FIX]
> This patch will fix the problems by:
> 
> - Add extra WARN_ON() if btrfs_get_chunk_map() failed
>   I know syzbot will complain, but it's better noisy for fstests.
> 
> - Replace the ASSERT()
>   By returning the error.
> 
> - Handle the error when mapped length is smaller than expected length
> 
> Signed-off-by: Qu Wenruo <wqu@suse.com>

Looks good to me, you can add
Reviewed-by: Boris Burkov <boris@bur.io>

> ---
>  fs/btrfs/bio.c     | 6 +++++-
>  fs/btrfs/volumes.c | 5 ++++-
>  2 files changed, 9 insertions(+), 2 deletions(-)
> 
> diff --git a/fs/btrfs/bio.c b/fs/btrfs/bio.c
> index b8fb7ef6b520..8f7b56a0290f 100644
> --- a/fs/btrfs/bio.c
> +++ b/fs/btrfs/bio.c
> @@ -246,7 +246,11 @@ void btrfs_submit_bio(struct btrfs_fs_info *fs_info, struct bio *bio, int mirror
>  		btrfs_crit(fs_info,
>  			   "mapping failed logical %llu bio len %llu len %llu",
>  			   logical, length, map_length);

nit: for these WARN_ON(1)s, how about changing them from
if (cond) {
        btrfs_crit(<msg>);
        WARN_ON(1);
        <return error>;
}

to

if (WARN_ON(<cond>)) {
        btrfs_crit(<msg>);
	<return err>
}

> -		BUG();
> +		WARN_ON(1);
> +		ret = -EINVAL;
> +		btrfs_bio_counter_dec(fs_info);
> +		btrfs_bio_end_io(btrfs_bio(bio), errno_to_blk_status(ret));
> +		return;
>  	}
>  
>  	if (!bioc) {
> diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c
> index aa25fa335d3e..f69475fb1bc1 100644
> --- a/fs/btrfs/volumes.c
> +++ b/fs/btrfs/volumes.c
> @@ -3012,6 +3012,7 @@ struct extent_map *btrfs_get_chunk_map(struct btrfs_fs_info *fs_info,
>  	if (!em) {
>  		btrfs_crit(fs_info, "unable to find logical %llu length %llu",
>  			   logical, length);
> +		WARN_ON(1);
>  		return ERR_PTR(-EINVAL);
>  	}
>  
> @@ -3020,6 +3021,7 @@ struct extent_map *btrfs_get_chunk_map(struct btrfs_fs_info *fs_info,
>  			   "found a bad mapping, wanted %llu-%llu, found %llu-%llu",
>  			   logical, length, em->start, em->start + em->len);
>  		free_extent_map(em);
> +		WARN_ON(1);
>  		return ERR_PTR(-EINVAL);
>  	}
>  
> @@ -6384,7 +6386,8 @@ int __btrfs_map_block(struct btrfs_fs_info *fs_info, enum btrfs_map_op op,
>  	ASSERT(op != BTRFS_MAP_DISCARD);
>  
>  	em = btrfs_get_chunk_map(fs_info, logical, *length);
> -	ASSERT(!IS_ERR(em));
> +	if (IS_ERR(em))
> +		return PTR_ERR(em);
>  
>  	ret = btrfs_get_io_geometry(fs_info, em, op, logical, &geom);
>  	if (ret < 0)
> -- 
> 2.38.1
Qu Wenruo Jan. 27, 2023, 11:59 p.m. UTC | #2
On 2023/1/28 04:10, Boris Burkov wrote:
> On Wed, Dec 07, 2022 at 01:09:59PM +0800, Qu Wenruo wrote:
>> [BUG]
>> During my scrub rework, I did a stupid thing like this:
>>
>>          bio->bi_iter.bi_sector = stripe->logical;
>>          btrfs_submit_bio(fs_info, bio, stripe->mirror_num);
>>
>> Above bi_sector assignment is using logical address directly, which
>> lacks ">> SECTOR_SHIFT".
>>
>> This results a read on a range which has no chunk mapping.
>>
>> This results the following crash:
>>
>>   BTRFS critical (device dm-1): unable to find logical 11274289152 length 65536
>>   assertion failed: !IS_ERR(em), in fs/btrfs/volumes.c:6387
>>   ------------[ cut here ]------------
>>
>> Sure this is all my fault, but this shows a possible problem in real
>> world, that some bitflip in file extents/tree block can point to
>> unmapped ranges, and trigger above ASSERT().
>>
>> [PROBLEMS]
>> In above call chain, there are 2 locations not properly handling the
>> errors:
>>
>> - __btrfs_map_block()
>>    If btrfs_get_chunk_map() returned error, we just trigger an ASSERT().
>>
>> - btrfs_submit_bio()
>>    If the returned mapped length is smaller than expected, we just BUG().
>>
>> [FIX]
>> This patch will fix the problems by:
>>
>> - Add extra WARN_ON() if btrfs_get_chunk_map() failed
>>    I know syzbot will complain, but it's better noisy for fstests.
>>
>> - Replace the ASSERT()
>>    By returning the error.
>>
>> - Handle the error when mapped length is smaller than expected length
>>
>> Signed-off-by: Qu Wenruo <wqu@suse.com>
> 
> Looks good to me, you can add
> Reviewed-by: Boris Burkov <boris@bur.io>
> 
>> ---
>>   fs/btrfs/bio.c     | 6 +++++-
>>   fs/btrfs/volumes.c | 5 ++++-
>>   2 files changed, 9 insertions(+), 2 deletions(-)
>>
>> diff --git a/fs/btrfs/bio.c b/fs/btrfs/bio.c
>> index b8fb7ef6b520..8f7b56a0290f 100644
>> --- a/fs/btrfs/bio.c
>> +++ b/fs/btrfs/bio.c
>> @@ -246,7 +246,11 @@ void btrfs_submit_bio(struct btrfs_fs_info *fs_info, struct bio *bio, int mirror
>>   		btrfs_crit(fs_info,
>>   			   "mapping failed logical %llu bio len %llu len %llu",
>>   			   logical, length, map_length);
> 
> nit: for these WARN_ON(1)s, how about changing them from
> if (cond) {
>          btrfs_crit(<msg>);
>          WARN_ON(1);
>          <return error>;
> }
> 
> to
> 
> if (WARN_ON(<cond>)) {
>          btrfs_crit(<msg>);
> 	<return err>
> }

In fact, the behavior is discouraged by David IIRC.

The problem is, one has to rely on the fact WARN_ON() has a return 
value, which is not straightforward by a first glance.

Thanks,
Qu
> 
>> -		BUG();
>> +		WARN_ON(1);
>> +		ret = -EINVAL;
>> +		btrfs_bio_counter_dec(fs_info);
>> +		btrfs_bio_end_io(btrfs_bio(bio), errno_to_blk_status(ret));
>> +		return;
>>   	}
>>   
>>   	if (!bioc) {
>> diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c
>> index aa25fa335d3e..f69475fb1bc1 100644
>> --- a/fs/btrfs/volumes.c
>> +++ b/fs/btrfs/volumes.c
>> @@ -3012,6 +3012,7 @@ struct extent_map *btrfs_get_chunk_map(struct btrfs_fs_info *fs_info,
>>   	if (!em) {
>>   		btrfs_crit(fs_info, "unable to find logical %llu length %llu",
>>   			   logical, length);
>> +		WARN_ON(1);
>>   		return ERR_PTR(-EINVAL);
>>   	}
>>   
>> @@ -3020,6 +3021,7 @@ struct extent_map *btrfs_get_chunk_map(struct btrfs_fs_info *fs_info,
>>   			   "found a bad mapping, wanted %llu-%llu, found %llu-%llu",
>>   			   logical, length, em->start, em->start + em->len);
>>   		free_extent_map(em);
>> +		WARN_ON(1);
>>   		return ERR_PTR(-EINVAL);
>>   	}
>>   
>> @@ -6384,7 +6386,8 @@ int __btrfs_map_block(struct btrfs_fs_info *fs_info, enum btrfs_map_op op,
>>   	ASSERT(op != BTRFS_MAP_DISCARD);
>>   
>>   	em = btrfs_get_chunk_map(fs_info, logical, *length);
>> -	ASSERT(!IS_ERR(em));
>> +	if (IS_ERR(em))
>> +		return PTR_ERR(em);
>>   
>>   	ret = btrfs_get_io_geometry(fs_info, em, op, logical, &geom);
>>   	if (ret < 0)
>> -- 
>> 2.38.1
Anand Jain Jan. 31, 2023, 11:38 a.m. UTC | #3
LGTM.

Reviewed-by: Anand Jain <anand.jain@oracle.com>


Nit:

> diff --git a/fs/btrfs/bio.c b/fs/btrfs/bio.c
> index b8fb7ef6b520..8f7b56a0290f 100644
> --- a/fs/btrfs/bio.c
> +++ b/fs/btrfs/bio.c
> @@ -246,7 +246,11 @@ void btrfs_submit_bio(struct btrfs_fs_info *fs_info, struct bio *bio, int mirror
>   		btrfs_crit(fs_info,
>   			   "mapping failed logical %llu bio len %llu len %llu",
>   			   logical, length, map_length);
> -		BUG();
> +		WARN_ON(1);
> +		ret = -EINVAL;
> +		btrfs_bio_counter_dec(fs_info);
> +		btrfs_bio_end_io(btrfs_bio(bio), errno_to_blk_status(ret));
> +		return;
>   	}

After this patch, the duplicate code lines can be consolidated to:


diff --git a/fs/btrfs/bio.c b/fs/btrfs/bio.c
index 64268278bf8c..d13825a4ea7c 100644
--- a/fs/btrfs/bio.c
+++ b/fs/btrfs/bio.c
@@ -236,11 +236,8 @@ void btrfs_submit_bio(struct btrfs_fs_info 
*fs_info, struct bio *bio, int mirror
         btrfs_bio_counter_inc_blocked(fs_info);
         ret = __btrfs_map_block(fs_info, btrfs_op(bio), logical, 
&map_length,
                                 &bioc, &smap, &mirror_num, 1);
-       if (ret) {
-               btrfs_bio_counter_dec(fs_info);
-               btrfs_bio_end_io(btrfs_bio(bio), errno_to_blk_status(ret));
-               return;
-       }
+       if (ret)
+               goto err_out;

         if (map_length < length) {
                 btrfs_crit(fs_info,
@@ -248,9 +245,7 @@ void btrfs_submit_bio(struct btrfs_fs_info *fs_info, 
struct bio *bio, int mirror
                            logical, length, map_length);
                 WARN_ON(1);
                 ret = -EINVAL;
-               btrfs_bio_counter_dec(fs_info);
-               btrfs_bio_end_io(btrfs_bio(bio), errno_to_blk_status(ret));
-               return;
+               goto err_out;
         }

         if (!bioc) {
@@ -278,6 +273,13 @@ void btrfs_submit_bio(struct btrfs_fs_info 
*fs_info, struct bio *bio, int mirror
                 for (dev_nr = 0; dev_nr < total_devs; dev_nr++)
                         btrfs_submit_mirrored_bio(bioc, dev_nr);
         }
+
+       return;
+
+err_out:
+       btrfs_bio_counter_dec(fs_info);
+       btrfs_bio_end_io(btrfs_bio(bio), errno_to_blk_status(ret));
+       return;
  }

  /*
David Sterba March 1, 2023, 8:16 p.m. UTC | #4
On Wed, Dec 07, 2022 at 01:09:59PM +0800, Qu Wenruo wrote:
> [BUG]
> During my scrub rework, I did a stupid thing like this:
> 
>         bio->bi_iter.bi_sector = stripe->logical;
>         btrfs_submit_bio(fs_info, bio, stripe->mirror_num);
> 
> Above bi_sector assignment is using logical address directly, which
> lacks ">> SECTOR_SHIFT".
> 
> This results a read on a range which has no chunk mapping.
> 
> This results the following crash:
> 
>  BTRFS critical (device dm-1): unable to find logical 11274289152 length 65536
>  assertion failed: !IS_ERR(em), in fs/btrfs/volumes.c:6387
>  ------------[ cut here ]------------
> 
> Sure this is all my fault, but this shows a possible problem in real
> world, that some bitflip in file extents/tree block can point to
> unmapped ranges, and trigger above ASSERT().
> 
> [PROBLEMS]
> In above call chain, there are 2 locations not properly handling the
> errors:
> 
> - __btrfs_map_block()
>   If btrfs_get_chunk_map() returned error, we just trigger an ASSERT().
> 
> - btrfs_submit_bio()
>   If the returned mapped length is smaller than expected, we just BUG().
> 
> [FIX]
> This patch will fix the problems by:
> 
> - Add extra WARN_ON() if btrfs_get_chunk_map() failed
>   I know syzbot will complain, but it's better noisy for fstests.
> 
> - Replace the ASSERT()
>   By returning the error.
> 
> - Handle the error when mapped length is smaller than expected length
> 
> Signed-off-by: Qu Wenruo <wqu@suse.com>

This does not apply since the recent bio and checksum rework, please
resend, thanks.
diff mbox series

Patch

diff --git a/fs/btrfs/bio.c b/fs/btrfs/bio.c
index b8fb7ef6b520..8f7b56a0290f 100644
--- a/fs/btrfs/bio.c
+++ b/fs/btrfs/bio.c
@@ -246,7 +246,11 @@  void btrfs_submit_bio(struct btrfs_fs_info *fs_info, struct bio *bio, int mirror
 		btrfs_crit(fs_info,
 			   "mapping failed logical %llu bio len %llu len %llu",
 			   logical, length, map_length);
-		BUG();
+		WARN_ON(1);
+		ret = -EINVAL;
+		btrfs_bio_counter_dec(fs_info);
+		btrfs_bio_end_io(btrfs_bio(bio), errno_to_blk_status(ret));
+		return;
 	}
 
 	if (!bioc) {
diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c
index aa25fa335d3e..f69475fb1bc1 100644
--- a/fs/btrfs/volumes.c
+++ b/fs/btrfs/volumes.c
@@ -3012,6 +3012,7 @@  struct extent_map *btrfs_get_chunk_map(struct btrfs_fs_info *fs_info,
 	if (!em) {
 		btrfs_crit(fs_info, "unable to find logical %llu length %llu",
 			   logical, length);
+		WARN_ON(1);
 		return ERR_PTR(-EINVAL);
 	}
 
@@ -3020,6 +3021,7 @@  struct extent_map *btrfs_get_chunk_map(struct btrfs_fs_info *fs_info,
 			   "found a bad mapping, wanted %llu-%llu, found %llu-%llu",
 			   logical, length, em->start, em->start + em->len);
 		free_extent_map(em);
+		WARN_ON(1);
 		return ERR_PTR(-EINVAL);
 	}
 
@@ -6384,7 +6386,8 @@  int __btrfs_map_block(struct btrfs_fs_info *fs_info, enum btrfs_map_op op,
 	ASSERT(op != BTRFS_MAP_DISCARD);
 
 	em = btrfs_get_chunk_map(fs_info, logical, *length);
-	ASSERT(!IS_ERR(em));
+	if (IS_ERR(em))
+		return PTR_ERR(em);
 
 	ret = btrfs_get_io_geometry(fs_info, em, op, logical, &geom);
 	if (ret < 0)