[PATCH] erofs: complete fscache pseudo-bio once when a read is split

Michael Bommarito <[email protected]>
Newsgroups org.ozlabs.lists.linux-erofs,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
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: [email protected]
Signed-off-by: Michael Bommarito <[email protected]>
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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.