The chunked decompression loop in gzwrite() treats any inflate()
return value other than Z_OK and Z_STREAM_END as a fatal error. When
the current input chunk happens to be exhausted at exactly the same
time as the write buffer fills up, the inner loop calls inflate()
again with avail_in == 0. No forward progress is possible in that
state, so inflate() returns Z_BUF_ERROR and gzwrite() bails out:
Error: inflate() returned -5
Per the zlib documentation, Z_BUF_ERROR is not fatal and only means
that no progress was possible; the call should be repeated once more
input is available. The reference implementation in zlib
examples/zpipe.c continues in this exact situation.
The failure is data dependent: it needs a stream position where the
consumed input and produced output line up with both the chunk and
the write buffer boundary at once, and the inflate side must have no
buffered output. That is most likely with incompressible input, where
deflate emits stored blocks and inflate holds no lookahead bits. This
is how dm_test_cmd_zip_gzwrite occasionally fails in sandbox64 CI on
random data with gzwrite_chunk = SZ_1M + 1, stopping at a multiple of
the 1 MiB write buffer:
12582912/16777216
Error: inflate() returned -5
Detect this case and let the outer loop refill the input chunk
instead of failing.
On sandbox64, the random data dm_test_cmd_zip_gzwrite test failed
17 out of 2000 runs (about 1 percent) without this fix, every time
with the same signature as the CI flake, and passed 2000 out of 2000
runs with it.
Fixes: 58e523fedf48 ("gunzip: Implement chunked decompression")
Signed-off-by: Aristo Chen <[email protected]>
Reviewed-by: Simon Glass <[email protected]>
---
v2: no code changes, collected Reviewed-by
lib/gunzip.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/lib/gunzip.c b/lib/gunzip.c
index 20cc14f9688..b30cbfc34ef 100644
--- a/lib/gunzip.c
+++ b/lib/gunzip.c
@@ -246,6 +246,16 @@ int gzwrite(unsigned char *src, size_t len, struct
blk_desc *dev,
s.next_out = writebuf;
}
r = inflate(&s, Z_SYNC_FLUSH);
+ if (r == Z_BUF_ERROR && !s.avail_in && payload_size) {
+ /*
+ * The input chunk was exhausted at exactly
+ * the same time as the write buffer filled
+ * up, so no progress was possible. This is
+ * not fatal, let the outer loop refill the
+ * input chunk.
+ */
+ break;
+ }
if ((r != Z_OK) &&
(r != Z_STREAM_END)) {
printf("Error: inflate() returned %d\n", r);
--
2.43.0