Subject: bug#63831: [PATCH] Elaboration on the bugfix for failed inflation of gz archives

From: Amritpal Singh <[email-masked]>
Date: Sat, 3 Jun 2023 08:38:18 +0530

> Now after logging some code in $EMACS_REPO/src/decompress.c, it was
> learned that in the pigz specific case, `inflate()` was returning
> Z_BUF_ERROR(-5) which is an indicator for zstream's either `avail_in`
> or `avail_out` fields are 0.
>
> Observing the code in `$EMACS_REPO/src/decompress.c`
> L154:
>     } while (!stream.avail_out);
> only checks stream.avail_out and not stream.avail_in which also might
> have been set to 0. A special case here can be constructed where
> `avail_in` is 0, and the code keeps looping even though our input
> buffer is empty and thus causing a Z_BUF_ERROR. Placing a simple check
> for it fixes the bug in pigz's gz archives case and does not cause any
> issue with gzip archives.

I'd like to elaborate a bit further on this part mentioned in the bug
report-cum-patch email. The code in question where the bug occurs is
as follows:

> do {
>    stream.avail_in = fread (in, 1, MD5_BLOCKSIZE, source);
>    if (ferror (source)) {
>      inflateEnd (&stream);
>      return -1;
>    }
>    if (stream.avail_in == 0)
>      break;
>    stream.next_in = in;
>
>    do {
>      stream.avail_out = MD5_BLOCKSIZE;
>      stream.next_out = out;
>      res = inflate (&stream, Z_NO_FLUSH);
>
>      if (res != Z_OK && res != Z_STREAM_END)
>    return -1;
>
>      accumulate_and_process_md5 (out, MD5_BLOCKSIZE - stream.avail_out, &ctx);
L154:
>    } while (!stream.avail_out);
L155:
>
>  } while (res != Z_STREAM_END);

>From here, it is a bit more clear what the originally mentioned `L154`
was referring to. The archives compressed using pigz set as the systems'
gzip program either via symlink as `gzip` in $PATH or using $GZIP_PROG,
fail to hash in this specific instance which is quite strange,
nonetheless this patch (provided in the original bug report) fixes the
issue of calling inflate() repeatedly (in the inner do-while() loop)
when we do not have any more input to process.

This happens so, as mentioned before, because `infalte()` can set
`stream.avail_in` to 0 which is the available number of bytes to
process. And since we're only checking if stream.avail_out hasn't been
set to 0 in the inner loop, the hashing fails even though our archive is
correctly made and decompresses successfully externally.

ps. The patch is in the original bug report.