* [PATCH] erofs: complete fscache pseudo-bio once when a read is split
@ 2026-06-19 13:58 Michael Bommarito
2026-06-19 14:20 ` Gao Xiang
0 siblings, 1 reply; 4+ messages in thread
From: Michael Bommarito @ 2026-06-19 13:58 UTC (permalink / raw)
To: Gao Xiang, Chao Yu
Cc: Yue Hu, Jeffle Xu, Sandeep Dhavale, linux-erofs, linux-kernel
In fscache mode a compressed read uses one pseudo-bio whose io->end_io is
erofs_fscache_bio_endio(). When prepare_ondemand_read() splits the read at
a cached/uncached boundary, erofs_fscache_read_io_async() issues several
fscache subreads on the same bio and erofs_fscache_bio_endio() calls
bio_endio() on each. The pseudo-bio is not chained, so z_erofs_endio()
runs once per subread while z_erofs_submit_queue() counted the bio only
once, underflowing pending_bios: the reader hangs in D state, or, on async
completion, the first completion frees the decompress queue and the rest
are use-after-free.
Hold a bio_inc_remaining() reference per issued subread and drop the
submitter's initial reference with one bio_endio() once submission
finishes, so the bio completes exactly once. The request path
(erofs_fscache_req_end_io) is unaffected; it uses its own refcount and
never calls bio_endio().
Fixes: a1bafc3109d7 ("erofs: support compressed inodes over fscache")
Cc: stable@vger.kernel.org
Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
Assisted-by: Claude:claude-opus-4-7
---
Reproduced on x86-64 with KASAN via the erofs-on-demand path (a cachefiles
ondemand daemon serving a crafted compressed image that splits a pcluster
read). Found with the help of an automated review tool.
Without this patch a stock kernel either hangs the reader:
task:dd state:D
filemap_get_pages / erofs_file_read_iter
or, when completion is asynchronous, faults:
BUG: KASAN: slab-use-after-free in z_erofs_endio
Kernel panic - not syncing: Fatal exception in interrupt
With this patch the same daemon, image and reads complete cleanly: no
hang, no KASAN report, no panic. Harness and full logs available on
request.
fs/erofs/fscache.c | 27 ++++++++++++++++++---------
1 file changed, 18 insertions(+), 9 deletions(-)
diff --git a/fs/erofs/fscache.c b/fs/erofs/fscache.c
index 685c68774379b..0cce498faa32d 100644
--- a/fs/erofs/fscache.c
+++ b/fs/erofs/fscache.c
@@ -29,6 +29,14 @@ struct erofs_fscache_rq {
refcount_t ref;
};
+struct erofs_fscache_bio {
+ struct erofs_fscache_io io;
+ struct bio bio; /* w/o bdev to share bio_add_page/endio() */
+ struct bio_vec bvecs[BIO_MAX_VECS];
+};
+
+static void erofs_fscache_bio_endio(void *priv, ssize_t transferred_or_error);
+
static bool erofs_fscache_io_put(struct erofs_fscache_io *io)
{
if (!refcount_dec_and_test(&io->ref))
@@ -122,8 +130,13 @@ static int erofs_fscache_read_io_async(struct fscache_cookie *cookie,
enum netfs_io_source source;
struct netfs_cache_resources *cres = &io->cres;
struct iov_iter *iter = &io->iter;
+ struct bio *bio = NULL;
int ret;
+ /* Chain-account a split pseudo-bio so it completes only once. */
+ if (io->end_io == erofs_fscache_bio_endio)
+ bio = &container_of(io, struct erofs_fscache_bio, io)->bio;
+
ret = fscache_begin_read_operation(cres, cookie);
if (ret)
return ret;
@@ -143,6 +156,8 @@ static int erofs_fscache_read_io_async(struct fscache_cookie *cookie,
iov_iter_truncate(iter, len);
refcount_inc(&io->ref);
+ if (bio)
+ bio_inc_remaining(bio);
ret = fscache_read(cres, pstart, iter, NETFS_READ_HOLE_FAIL,
io->end_io, io);
if (ret == -EIOCBQUEUED)
@@ -160,12 +175,6 @@ static int erofs_fscache_read_io_async(struct fscache_cookie *cookie,
return 0;
}
-struct erofs_fscache_bio {
- struct erofs_fscache_io io;
- struct bio bio; /* w/o bdev to share bio_add_page/endio() */
- struct bio_vec bvecs[BIO_MAX_VECS];
-};
-
static void erofs_fscache_bio_endio(void *priv, ssize_t transferred_or_error)
{
struct erofs_fscache_bio *io = priv;
@@ -200,9 +209,9 @@ void erofs_fscache_submit_bio(struct bio *bio)
ret = erofs_fscache_read_io_async(io->io.private,
bio->bi_iter.bi_sector << 9, &io->io);
erofs_fscache_io_put(&io->io);
- if (!ret)
- return;
- bio->bi_status = errno_to_blk_status(ret);
+ if (ret)
+ bio->bi_status = errno_to_blk_status(ret);
+ /* Drop the submitter's ref; subread chain refs complete the bio. */
bio_endio(bio);
}
--
2.53.0
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH] erofs: complete fscache pseudo-bio once when a read is split
2026-06-19 13:58 [PATCH] erofs: complete fscache pseudo-bio once when a read is split Michael Bommarito
@ 2026-06-19 14:20 ` Gao Xiang
2026-06-19 16:21 ` Michael Bommarito
0 siblings, 1 reply; 4+ messages in thread
From: Gao Xiang @ 2026-06-19 14:20 UTC (permalink / raw)
To: Michael Bommarito, Gao Xiang, Chao Yu
Cc: Yue Hu, Jeffle Xu, Sandeep Dhavale, linux-erofs, linux-kernel
On 2026/6/19 21:58, Michael Bommarito wrote:
> In fscache mode a compressed read uses one pseudo-bio whose io->end_io is
> erofs_fscache_bio_endio(). When prepare_ondemand_read() splits the read at
> a cached/uncached boundary, erofs_fscache_read_io_async() issues several
> fscache subreads on the same bio and erofs_fscache_bio_endio() calls
> bio_endio() on each. The pseudo-bio is not chained, so z_erofs_endio()
> runs once per subread while z_erofs_submit_queue() counted the bio only
> once, underflowing pending_bios: the reader hangs in D state, or, on async
> completion, the first completion frees the decompress queue and the rest
> are use-after-free.
>
> Hold a bio_inc_remaining() reference per issued subread and drop the
> submitter's initial reference with one bio_endio() once submission
> finishes, so the bio completes exactly once. The request path
> (erofs_fscache_req_end_io) is unaffected; it uses its own refcount and
> never calls bio_endio().
>
> Fixes: a1bafc3109d7 ("erofs: support compressed inodes over fscache")
> Cc: stable@vger.kernel.org
> Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
> Assisted-by: Claude:claude-opus-4-7
> ---
>
> Reproduced on x86-64 with KASAN via the erofs-on-demand path (a cachefiles
> ondemand daemon serving a crafted compressed image that splits a pcluster
> read). Found with the help of an automated review tool.
>
> Without this patch a stock kernel either hangs the reader:
>
> task:dd state:D
> filemap_get_pages / erofs_file_read_iter
>
> or, when completion is asynchronous, faults:
>
> BUG: KASAN: slab-use-after-free in z_erofs_endio
> Kernel panic - not syncing: Fatal exception in interrupt
>
> With this patch the same daemon, image and reads complete cleanly: no
> hang, no KASAN report, no panic. Harness and full logs available on
> request.
fscache is already deprecated, I will remove this path
in this or the next cycle: it's not worth to improve
this, and bio_inc_remaining is suspicious since I never
tend to introduce chain pseudo-bios.
Thanks,
Gao Xiang
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH] erofs: complete fscache pseudo-bio once when a read is split
2026-06-19 14:20 ` Gao Xiang
@ 2026-06-19 16:21 ` Michael Bommarito
2026-06-19 16:32 ` Gao Xiang
0 siblings, 1 reply; 4+ messages in thread
From: Michael Bommarito @ 2026-06-19 16:21 UTC (permalink / raw)
To: Gao Xiang
Cc: Gao Xiang, Chao Yu, Yue Hu, Jeffle Xu, Sandeep Dhavale,
linux-erofs, linux-kernel
On Fri, Jun 19, 2026 at 10:20 AM Gao Xiang <hsiangkao@linux.alibaba.com> wrote:
> fscache is already deprecated, I will remove this path
> in this or the next cycle: it's not worth to improve
> this, and bio_inc_remaining is suspicious since I never
> tend to introduce chain pseudo-bios.
I didn't chase a POC for this one, but I can't rule out an LPE on the
or at least helpful shaping from this.
I know it's not relevant going forward, but the UAF / double free will
live forever unless stable@ gets something.
Thanks,
Mike
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH] erofs: complete fscache pseudo-bio once when a read is split
2026-06-19 16:21 ` Michael Bommarito
@ 2026-06-19 16:32 ` Gao Xiang
0 siblings, 0 replies; 4+ messages in thread
From: Gao Xiang @ 2026-06-19 16:32 UTC (permalink / raw)
To: Michael Bommarito
Cc: Gao Xiang, Chao Yu, Yue Hu, Jeffle Xu, Sandeep Dhavale,
linux-erofs, linux-kernel
On 2026/6/20 00:21, Michael Bommarito wrote:
> On Fri, Jun 19, 2026 at 10:20 AM Gao Xiang <hsiangkao@linux.alibaba.com> wrote:
>> fscache is already deprecated, I will remove this path
>> in this or the next cycle: it's not worth to improve
>> this, and bio_inc_remaining is suspicious since I never
>> tend to introduce chain pseudo-bios.
>
> I didn't chase a POC for this one, but I can't rule out an LPE on the
> or at least helpful shaping from this.
>
> I know it's not relevant going forward, but the UAF / double free will
> live forever unless stable@ gets something.
It's already _deprecate_ for almost two years: and
I believe there should be more serious issues all
over the kernel, why just focus on a deprecated
feature?
Or please provide an accurate reproducible way first
so that I can play with that myself, I think I can
fix it in a cleaner way. If it's really necessary,
it will be fixed to the stable kernels only later,
but this feature will be removed in the near future
upstream instead.
Thanks,
Gao Xiang
>
> Thanks,
> Mike
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-06-19 16:32 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-06-19 13:58 [PATCH] erofs: complete fscache pseudo-bio once when a read is split Michael Bommarito
2026-06-19 14:20 ` Gao Xiang
2026-06-19 16:21 ` Michael Bommarito
2026-06-19 16:32 ` Gao Xiang
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®