[PATCH] gfs2: keep per-bio end_io when splitting journal head reads

Wxm-233 posted 1 patch 1 month, 3 weeks ago
fs/gfs2/lops.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
[PATCH] gfs2: keep per-bio end_io when splitting journal head reads
Posted by Wxm-233 1 month, 3 weeks ago
gfs2_find_jhead() can split a folio across two bios when part of the
folio is already queued in the current bio and the remaining blocks
need a new bio.

That split path currently calls bio_chain(new, prev).  But journal read
bios need to retain gfs2_end_log_read() and bi_private so that each bio
completes its own folios with folio_end_read().  Replacing the new
bio's completion handler with the block layer chaining callback breaks
that expectation, and fuzzing workloads can hit a BUG in bio_chain()
on this path.

Keep the per-bio completion state for the new bio and only submit the
previous bio.

Signed-off-by: Wxm-233 <2200013188@stu.pku.edu.cn>
---
 fs/gfs2/lops.c | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/fs/gfs2/lops.c b/fs/gfs2/lops.c
index 6586963..3d0fad3 100644
--- a/fs/gfs2/lops.c
+++ b/fs/gfs2/lops.c
@@ -481,12 +481,16 @@ static void gfs2_jhead_process_page(struct gfs2_jdesc *jd, unsigned long index,
 static struct bio *gfs2_chain_bio(struct bio *prev, unsigned int nr_iovecs,
 				  sector_t sector, blk_opf_t opf)
 {
+	bio_end_io_t *end_io = prev->bi_end_io;
+	void *private = prev->bi_private;
 	struct bio *new;
 
 	new = bio_alloc(prev->bi_bdev, nr_iovecs, opf, GFP_NOIO);
 	bio_clone_blkg_association(new, prev);
 	new->bi_iter.bi_sector = sector;
-	bio_chain(new, prev);
+	/* Each journal read bio must complete its own folios. */
+	new->bi_end_io = end_io;
+	new->bi_private = private;
 	submit_bio(prev);
 	return new;
 }
-- 
2.45.2.windows.1
Re: [PATCH] gfs2: keep per-bio end_io when splitting journal head reads
Posted by Andreas Gruenbacher 1 month, 3 weeks ago
Hello,

On Fri, Apr 24, 2026 at 6:16 AM Wxm-233 <2200013188@stu.pku.edu.cn> wrote:
> gfs2_find_jhead() can split a folio across two bios when part of the
> folio is already queued in the current bio and the remaining blocks
> need a new bio.
>
> That split path currently calls bio_chain(new, prev).  But journal read
> bios need to retain gfs2_end_log_read() and bi_private so that each bio
> completes its own folios with folio_end_read().  Replacing the new
> bio's completion handler with the block layer chaining callback breaks
> that expectation,

please see commit 469d71512d13 ('Revert "gfs2: Fix use of bio_chain"')
for a hint on how this works.

> and fuzzing workloads can hit a BUG in bio_chain() on this path.

What specific test case / reproducer is causing problems?

> Keep the per-bio completion state for the new bio and only submit the
> previous bio.
>
> Signed-off-by: Wxm-233 <2200013188@stu.pku.edu.cn>
> ---
>  fs/gfs2/lops.c | 6 +++++-
>  1 file changed, 5 insertions(+), 1 deletion(-)
>
> diff --git a/fs/gfs2/lops.c b/fs/gfs2/lops.c
> index 6586963..3d0fad3 100644
> --- a/fs/gfs2/lops.c
> +++ b/fs/gfs2/lops.c
> @@ -481,12 +481,16 @@ static void gfs2_jhead_process_page(struct gfs2_jdesc *jd, unsigned long index,
>  static struct bio *gfs2_chain_bio(struct bio *prev, unsigned int nr_iovecs,
>                                   sector_t sector, blk_opf_t opf)
>  {
> +       bio_end_io_t *end_io = prev->bi_end_io;
> +       void *private = prev->bi_private;
>         struct bio *new;
>
>         new = bio_alloc(prev->bi_bdev, nr_iovecs, opf, GFP_NOIO);
>         bio_clone_blkg_association(new, prev);
>         new->bi_iter.bi_sector = sector;
> -       bio_chain(new, prev);
> +       /* Each journal read bio must complete its own folios. */
> +       new->bi_end_io = end_io;
> +       new->bi_private = private;

We surely are not going to hand-roll bio_chain().

>         submit_bio(prev);
>         return new;
>  }
> --
> 2.45.2.windows.1
>

Thanks,
Andreas
Re: [PATCH] gfs2: keep per-bio end_io when splitting journal head reads
Posted by Wxm-233 1 month, 3 weeks ago
Hi Andreas,

Thanks for pointing me to commit 469d71512d13 ("Revert "gfs2: Fix use
of bio_chain"").

I had missed that revert. After looking at it again, I agree that my
patch direction was wrong, so I will drop that patch.

What I can say about the trigger so far is:

- we reproduced this on 6.18.5
- the report came from a syzkaller-style fuzzing run
- the crash happens during GFS2 journal recovery, not on a normal data
  path
- the stack is:

    gfs2_recover_func()
      -> gfs2_find_jhead()
      -> gfs2_chain_bio()
      -> bio_chain()

From the code path, it appears to happen during journal head lookup
when the read enters the split-bio case in gfs2_find_jhead().

However, I do not yet have a minimized standalone reproducer that I am
confident is correct. The "last executing programs" section in the log
does not isolate the responsible operation reliably enough, so at the
moment I can only describe it as being triggered by mounting a crafted
GFS2 image generated during fuzzing, which then enters journal
recovery and hits this path.

I will go back and try to extract a concrete reproducer / test case
before following up further.

Thanks,
Wxm-233
Re: [PATCH] gfs2: keep per-bio end_io when splitting journal head reads
Posted by Andreas Gruenbacher 1 month, 3 weeks ago
On Fri, Apr 24, 2026 at 10:52 PM Wxm-233 <2200013188@stu.pku.edu.cn> wrote:
> Hi Andreas,
>
> Thanks for pointing me to commit 469d71512d13 ("Revert "gfs2: Fix use
> of bio_chain"").
>
> I had missed that revert. After looking at it again, I agree that my
> patch direction was wrong, so I will drop that patch.
>
> What I can say about the trigger so far is:
>
> - we reproduced this on 6.18.5

Okay, commit 469d71512d13 ("Revert "gfs2: Fix use of bio_chain"") was
added to v6.18.7, so no surprise there.

> - the report came from a syzkaller-style fuzzing run
> - the crash happens during GFS2 journal recovery, not on a normal data
>   path
> - the stack is:
>
>     gfs2_recover_func()
>       -> gfs2_find_jhead()
>       -> gfs2_chain_bio()
>       -> bio_chain()
>
> From the code path, it appears to happen during journal head lookup
> when the read enters the split-bio case in gfs2_find_jhead().

Thanks,
Andreas