fs/gfs2/lops.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-)
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
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
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
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
© 2016 - 2026 Red Hat, Inc.