* [PATCH] erofs-utils: lib: avoid infinite loop on zero-sized reads
@ 2026-11-03 8:41 邓博
0 siblings, 0 replies; only message in thread
From: 邓博 @ 2026-11-03 8:41 UTC (permalink / raw)
To: linux-erofs
[-- Attachment #1.1: Type: text/plain, Size: 2941 bytes --]
Hi all,
I would like to report and propose a fix for an infinite loop I hit in
mkfs.erofs while compressing a directory with fragments enabled.
For reference, I also recorded the report here:
https://github.com/erofs/erofs-utils/issues/59
The affected command was equivalent to:
mkfs.erofs -z lz4 \
-Efragments,dedupe,ztailpacking \
-C1048576 -b4096 output.ef input/
mkfs.erofs remained at 100% CPU and made no forward progress. strace showed
the process repeatedly issuing:
read(5, "", 0) = 0
The source file was 8,192,008 bytes, while Z_EROFS_COMPR_QUEUE_SZ is
8,192,000 bytes. Fragment deduplication found a 31-byte common suffix with an
earlier file in the input tree. After the deduplicated fragment was added back
to ctx->remaining, only 23 more bytes fit in the queue. This left
ctx->remaining non-zero while ctx->tail had reached the end of the queue.
The next iteration therefore computed:
rx = min(ctx->remaining,
Z_EROFS_COMPR_QUEUE_SZ - ctx->tail);
with rx equal to zero. A zero-length read returns zero, which matches rx, so
the existing error check did not fire. Since neither remaining nor tail changed,
the loop continued forever.
The attached patch checks for this no-progress condition before attempting the
read. It first tries to drop already-consumed data with z_erofs_need_refill().
If that cannot make room, it reports the affected source path and returns -EIO
instead of spinning indefinitely.
I tested the change in two ways:
* Backported the same guard to 1.8.6, where the original input previously
timed out. With the guard, compression completed in about 0.36 seconds.
* Applied the patch to the current code and compressed the same input
successfully with equivalent timing.
The resulting images passed fsck.erofs in both cases. I also confirmed that the
normal compression path still produces a valid image.
I understand that the exact queue state is sensitive to fragment handling and
pcluster sizing, so I would be very grateful for your review. If a different
place is more appropriate for this guard, or if the error handling should be
changed, I am happy to revise the patch.
Thank you for your time and for maintaining erofs-utils.
Best regards,
dengbo
Link: https://github.com/erofs/erofs-utils/issues/59
Signed-off-by: dengbo11 <dengbo@deepin.org>
深度社区
邓博
[-- Attachment #1.2: Type: text/html, Size: 8374 bytes --]
[-- Attachment #2: 0001-fix-avoid-infinite-loop-on-zero-sized-reads.patch --]
[-- Type: application/octet-stream, Size: 2195 bytes --]
From d8f431fee9ed30e3b291ff38a52ef376e7f79b49 Mon Sep 17 00:00:00 2001
From: dengbo11 <dengbo@deepin.org>
Date: Mon, 2 Nov 2026 15:41:42 +0800
Subject: [PATCH] fix: avoid infinite loop on zero-sized reads
1. Add a guard in z_erofs_compress_segment that checks whether
erofs_io_read/erofs_io_pread returned a zero-byte result (rx == 0)
2. When rx is zero, first attempt to drain already-consumed queue data
via z_erofs_need_refill and continue, so the producer can make progress
3. If the queue still needs refilling and no data can be dropped, log an
error including the inode source path and return -EIO
4. This prevents an unbounded spin where a zero-sized read would
otherwise loop forever without advancing ctx->tail
Influence:
1. Exercise compression on inputs where the underlying read can return
zero bytes (truncated source, special files, empty regions) and verify
no hang or infinite CPU spin occurs
2. Confirm that when the queue is full and no data can be dropped, the
operation fails with -EIO and an error message referencing the source
path is emitted
3. Verify the refill path correctly drops already-consumed data and
continues processing without losing correctness
4. Run normal compression workloads to ensure the new guard does not
alter successful compression output
5. Test boundary conditions around the compression queue size to confirm
the full-queue detection logic behaves as expected
---
lib/compress.c | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/lib/compress.c b/lib/compress.c
index 9262afa..07aba08 100644
--- a/lib/compress.c
+++ b/lib/compress.c
@@ -1297,6 +1297,18 @@ int z_erofs_compress_segment(struct z_erofs_compress_sctx *ctx,
Z_EROFS_COMPR_QUEUE_SZ - ctx->tail);
int ret;
+ if (!rx) {
+ /*
+ * A zero-sized read cannot make progress. Drop already
+ * consumed data first, otherwise fail instead of spinning.
+ */
+ if (z_erofs_need_refill(ctx))
+ continue;
+ erofs_err("compression queue is full for %s",
+ inode->i_srcpath);
+ return -EIO;
+ }
+
ret = (offset == -1 ?
erofs_io_read(vf, ctx->queue + ctx->tail, rx) :
erofs_io_pread(vf, ctx->queue + ctx->tail, rx,
--
2.50.1
^ permalink raw reply related [flat|nested] only message in thread
only message in thread, other threads:[~2026-11-03 8:49 UTC | newest]
Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-03 8:41 [PATCH] erofs-utils: lib: avoid infinite loop on zero-sized reads 邓博
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).