All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] btrfs: fix a bug where a compressed write can cross stripe boundary
@ 2021-05-25  5:52 Qu Wenruo
  2021-05-25  6:32 ` Johannes Thumshirn
  2021-05-25 16:00 ` David Sterba
  0 siblings, 2 replies; 3+ messages in thread
From: Qu Wenruo @ 2021-05-25  5:52 UTC (permalink / raw)
  To: linux-btrfs; +Cc: Josef Bacik

[BUG]
When running btrfs/027 with "-o compress" mount option, it always crash
with the following call trace:

  BTRFS critical (device dm-4): mapping failed logical 298901504 bio len 12288 len 8192
  ------------[ cut here ]------------
  kernel BUG at fs/btrfs/volumes.c:6651!
  invalid opcode: 0000 [#1] PREEMPT SMP NOPTI
  CPU: 5 PID: 31089 Comm: kworker/u24:10 Tainted: G           OE     5.13.0-rc2-custom+ #26
  Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 0.0.0 02/06/2015
  Workqueue: btrfs-delalloc btrfs_work_helper [btrfs]
  RIP: 0010:btrfs_map_bio.cold+0x58/0x5a [btrfs]
  Call Trace:
   btrfs_submit_compressed_write+0x2d7/0x470 [btrfs]
   submit_compressed_extents+0x3b0/0x470 [btrfs]
   ? mark_held_locks+0x49/0x70
   btrfs_work_helper+0x131/0x3e0 [btrfs]
   process_one_work+0x28f/0x5d0
   worker_thread+0x55/0x3c0
   ? process_one_work+0x5d0/0x5d0
   kthread+0x141/0x160
   ? __kthread_bind_mask+0x60/0x60
   ret_from_fork+0x22/0x30
  ---[ end trace 63113a3a91f34e68 ]---

[CAUSE]
The critical message before the crash means we have a bio at logical
bytenr 298901504 length 12288, but only 8192 bytes can fit into one
stripe, the remaining 4096 bytes is into another stripe.

In btrfs, all bio is properly split to avoid cross stripe boundary, but
commit 764c7c9a464b ("btrfs: zoned: fix parallel compressed writes")
changed the behavior for compressed write.

The offending code looks like this:

                        submit = btrfs_bio_fits_in_stripe(page, PAGE_SIZE, bio,
                                                          0);

+               if (pg_index == 0 && use_append)
+                       len = bio_add_zone_append_page(bio, page, PAGE_SIZE, 0);
+               else
+                       len = bio_add_page(bio, page, PAGE_SIZE, 0);
+
                page->mapping = NULL;
-               if (submit || bio_add_page(bio, page, PAGE_SIZE, 0) <
-                   PAGE_SIZE) {
+               if (submit || len < PAGE_SIZE) {

Previously if we find our new page can't be fitted into current stripe,
aka "submitted == 1" case, we submit current bio without adding current
page.

But after the modification, we will add the page no matter if it crosses
stripe boundary, leading to the above crash.

[FIX]
It's no longer possible to revert to the original code style as we have
two different bio_add_*_page() calls now.

The new fix is to skip the bio_add_*_page() call if @submit is true.

Also to avoid @len to be uninitialized, always initialize it to zero.

If @submit is true, @len will not be checked.
If @submit is not true, @len will be the return value of
bio_add_*_page() call.
Either way, the behavior is still the same as the old code.

Reported-by: Josef Bacik <josef@toxicpanda.com>
Fixes: 764c7c9a464b ("btrfs: zoned: fix parallel compressed writes")
Signed-off-by: Qu Wenruo <wqu@suse.com>
---
 fs/btrfs/compression.c | 17 ++++++++++++-----
 1 file changed, 12 insertions(+), 5 deletions(-)

diff --git a/fs/btrfs/compression.c b/fs/btrfs/compression.c
index d17ac301032e..1346d698463a 100644
--- a/fs/btrfs/compression.c
+++ b/fs/btrfs/compression.c
@@ -457,7 +457,7 @@ blk_status_t btrfs_submit_compressed_write(struct btrfs_inode *inode, u64 start,
 	bytes_left = compressed_len;
 	for (pg_index = 0; pg_index < cb->nr_pages; pg_index++) {
 		int submit = 0;
-		int len;
+		int len = 0;
 
 		page = compressed_pages[pg_index];
 		page->mapping = inode->vfs_inode.i_mapping;
@@ -465,10 +465,17 @@ blk_status_t btrfs_submit_compressed_write(struct btrfs_inode *inode, u64 start,
 			submit = btrfs_bio_fits_in_stripe(page, PAGE_SIZE, bio,
 							  0);
 
-		if (pg_index == 0 && use_append)
-			len = bio_add_zone_append_page(bio, page, PAGE_SIZE, 0);
-		else
-			len = bio_add_page(bio, page, PAGE_SIZE, 0);
+		/*
+		 * Page can only be added to bio if the current bio fits in
+		 * stripe.
+		 */
+		if (!submit) {
+			if (pg_index == 0 && use_append)
+				len = bio_add_zone_append_page(bio, page,
+							       PAGE_SIZE, 0);
+			else
+				len = bio_add_page(bio, page, PAGE_SIZE, 0);
+		}
 
 		page->mapping = NULL;
 		if (submit || len < PAGE_SIZE) {
-- 
2.31.1


^ permalink raw reply related	[flat|nested] 3+ messages in thread

* Re: [PATCH] btrfs: fix a bug where a compressed write can cross stripe boundary
  2021-05-25  5:52 [PATCH] btrfs: fix a bug where a compressed write can cross stripe boundary Qu Wenruo
@ 2021-05-25  6:32 ` Johannes Thumshirn
  2021-05-25 16:00 ` David Sterba
  1 sibling, 0 replies; 3+ messages in thread
From: Johannes Thumshirn @ 2021-05-25  6:32 UTC (permalink / raw)
  To: Qu Wenruo, linux-btrfs; +Cc: Josef Bacik

On 25/05/2021 07:52, Qu Wenruo wrote:
> [BUG]
> When running btrfs/027 with "-o compress" mount option, it always crash
> with the following call trace:
> 
>   BTRFS critical (device dm-4): mapping failed logical 298901504 bio len 12288 len 8192
>   ------------[ cut here ]------------
>   kernel BUG at fs/btrfs/volumes.c:6651!
>   invalid opcode: 0000 [#1] PREEMPT SMP NOPTI
>   CPU: 5 PID: 31089 Comm: kworker/u24:10 Tainted: G           OE     5.13.0-rc2-custom+ #26
>   Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 0.0.0 02/06/2015
>   Workqueue: btrfs-delalloc btrfs_work_helper [btrfs]
>   RIP: 0010:btrfs_map_bio.cold+0x58/0x5a [btrfs]
>   Call Trace:
>    btrfs_submit_compressed_write+0x2d7/0x470 [btrfs]
>    submit_compressed_extents+0x3b0/0x470 [btrfs]
>    ? mark_held_locks+0x49/0x70
>    btrfs_work_helper+0x131/0x3e0 [btrfs]
>    process_one_work+0x28f/0x5d0
>    worker_thread+0x55/0x3c0
>    ? process_one_work+0x5d0/0x5d0
>    kthread+0x141/0x160
>    ? __kthread_bind_mask+0x60/0x60
>    ret_from_fork+0x22/0x30
>   ---[ end trace 63113a3a91f34e68 ]---
> 
> [CAUSE]
> The critical message before the crash means we have a bio at logical
> bytenr 298901504 length 12288, but only 8192 bytes can fit into one
> stripe, the remaining 4096 bytes is into another stripe.
> 
> In btrfs, all bio is properly split to avoid cross stripe boundary, but
> commit 764c7c9a464b ("btrfs: zoned: fix parallel compressed writes")
> changed the behavior for compressed write.
> 
> The offending code looks like this:
> 
>                         submit = btrfs_bio_fits_in_stripe(page, PAGE_SIZE, bio,
>                                                           0);
> 
> +               if (pg_index == 0 && use_append)
> +                       len = bio_add_zone_append_page(bio, page, PAGE_SIZE, 0);
> +               else
> +                       len = bio_add_page(bio, page, PAGE_SIZE, 0);
> +
>                 page->mapping = NULL;
> -               if (submit || bio_add_page(bio, page, PAGE_SIZE, 0) <
> -                   PAGE_SIZE) {
> +               if (submit || len < PAGE_SIZE) {
> 
> Previously if we find our new page can't be fitted into current stripe,
> aka "submitted == 1" case, we submit current bio without adding current
> page.
> 
> But after the modification, we will add the page no matter if it crosses
> stripe boundary, leading to the above crash.
> 
> [FIX]
> It's no longer possible to revert to the original code style as we have
> two different bio_add_*_page() calls now.
> 
> The new fix is to skip the bio_add_*_page() call if @submit is true.
> 
> Also to avoid @len to be uninitialized, always initialize it to zero.
> 
> If @submit is true, @len will not be checked.
> If @submit is not true, @len will be the return value of
> bio_add_*_page() call.
> Either way, the behavior is still the same as the old code.
> 
> Reported-by: Josef Bacik <josef@toxicpanda.com>
> Fixes: 764c7c9a464b ("btrfs: zoned: fix parallel compressed writes")
> Signed-off-by: Qu Wenruo <wqu@suse.com>
> ---


Looks good, thanks.
And sorry for breaking it.

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH] btrfs: fix a bug where a compressed write can cross stripe boundary
  2021-05-25  5:52 [PATCH] btrfs: fix a bug where a compressed write can cross stripe boundary Qu Wenruo
  2021-05-25  6:32 ` Johannes Thumshirn
@ 2021-05-25 16:00 ` David Sterba
  1 sibling, 0 replies; 3+ messages in thread
From: David Sterba @ 2021-05-25 16:00 UTC (permalink / raw)
  To: Qu Wenruo; +Cc: linux-btrfs, Josef Bacik

On Tue, May 25, 2021 at 01:52:43PM +0800, Qu Wenruo wrote:
> [BUG]
> When running btrfs/027 with "-o compress" mount option, it always crash
> with the following call trace:
> 
>   BTRFS critical (device dm-4): mapping failed logical 298901504 bio len 12288 len 8192
>   ------------[ cut here ]------------
>   kernel BUG at fs/btrfs/volumes.c:6651!
>   invalid opcode: 0000 [#1] PREEMPT SMP NOPTI
>   CPU: 5 PID: 31089 Comm: kworker/u24:10 Tainted: G           OE     5.13.0-rc2-custom+ #26
>   Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 0.0.0 02/06/2015
>   Workqueue: btrfs-delalloc btrfs_work_helper [btrfs]
>   RIP: 0010:btrfs_map_bio.cold+0x58/0x5a [btrfs]
>   Call Trace:
>    btrfs_submit_compressed_write+0x2d7/0x470 [btrfs]
>    submit_compressed_extents+0x3b0/0x470 [btrfs]
>    ? mark_held_locks+0x49/0x70
>    btrfs_work_helper+0x131/0x3e0 [btrfs]
>    process_one_work+0x28f/0x5d0
>    worker_thread+0x55/0x3c0
>    ? process_one_work+0x5d0/0x5d0
>    kthread+0x141/0x160
>    ? __kthread_bind_mask+0x60/0x60
>    ret_from_fork+0x22/0x30
>   ---[ end trace 63113a3a91f34e68 ]---
> 
> [CAUSE]
> The critical message before the crash means we have a bio at logical
> bytenr 298901504 length 12288, but only 8192 bytes can fit into one
> stripe, the remaining 4096 bytes is into another stripe.
> 
> In btrfs, all bio is properly split to avoid cross stripe boundary, but
> commit 764c7c9a464b ("btrfs: zoned: fix parallel compressed writes")
> changed the behavior for compressed write.
> 
> The offending code looks like this:
> 
>                         submit = btrfs_bio_fits_in_stripe(page, PAGE_SIZE, bio,
>                                                           0);
> 
> +               if (pg_index == 0 && use_append)
> +                       len = bio_add_zone_append_page(bio, page, PAGE_SIZE, 0);
> +               else
> +                       len = bio_add_page(bio, page, PAGE_SIZE, 0);
> +
>                 page->mapping = NULL;
> -               if (submit || bio_add_page(bio, page, PAGE_SIZE, 0) <
> -                   PAGE_SIZE) {
> +               if (submit || len < PAGE_SIZE) {

Please don't put diffs into changelogs, it's harder to read than two code
fragments before and after.

> Previously if we find our new page can't be fitted into current stripe,
> aka "submitted == 1" case, we submit current bio without adding current
> page.

So here would go the "-" part.

> But after the modification, we will add the page no matter if it crosses
> stripe boundary, leading to the above crash.

And here the "+" part.

> [FIX]
> It's no longer possible to revert to the original code style as we have
> two different bio_add_*_page() calls now.
> 
> The new fix is to skip the bio_add_*_page() call if @submit is true.
> 
> Also to avoid @len to be uninitialized, always initialize it to zero.
> 
> If @submit is true, @len will not be checked.
> If @submit is not true, @len will be the return value of
> bio_add_*_page() call.
> Either way, the behavior is still the same as the old code.
> 
> Reported-by: Josef Bacik <josef@toxicpanda.com>
> Fixes: 764c7c9a464b ("btrfs: zoned: fix parallel compressed writes")
> Signed-off-by: Qu Wenruo <wqu@suse.com>

Thanks for the fix, I'll have to send it to Linus this week but I want
to be sure there are no more surprises so this will stay in the tested
branches for a few days.

^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2021-05-25 16:03 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-05-25  5:52 [PATCH] btrfs: fix a bug where a compressed write can cross stripe boundary Qu Wenruo
2021-05-25  6:32 ` Johannes Thumshirn
2021-05-25 16:00 ` David Sterba

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.