Commit 139bcd1 for gzip

commit 139bcd194b64587aa774d58c605ad8b17ebbf547
Author: Paul Eggert <eggert@cs.ucla.edu>
Date:   Fri Nov 13 11:29:35 2026 -0800

    gzip: fix lzw reinitialization

    Problem reported by Fabrice Derepas.
    * tests/lzw-concat-abuse: New test.
    * tests/Makefile.am (TESTS): Add it.
    * unlzw.c (clear_tab_prefixof): Remove.
    All uses replaced by plain memzero calls of appropriate size.
    (de_base): New macro.
    (de_stack): Use it.  Do not skip last word.
    (corrupt_input): New function.
    (unlzw): Use it to avoid missing some output when input is corrupted.
    Avoid undefined behavior by calling MAXCODE (31) on
    platform with 32-bit long.  Initialize all of tab_suffix
    that we can possibly access, not just the first 256 entries.
    With a CLEAR code, also reinitialize oldcode.
    Check for stack overflow, as a sort of belt-and-suspenders thing
    as I doubt whether stack overflow is possible given the previous
    changes.

diff --git a/NEWS b/NEWS
index 3b6b33a..5faa0b2 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,12 @@ GNU gzip NEWS                                    -*- outline -*-

 * Noteworthy changes in release ?.? (????-??-??) [?]

+** Bug fixes
+
+  A buffer overflow has been fixed when decompressing a .Z file
+  after decompressing some other file.
+  [bug present since the beginning]
+
 ** Platforms no longer supported

   Code has been removed that existed only for 16-bit MS-DOS platforms
diff --git a/THANKS b/THANKS
index 7fa2582..8564747 100644
--- a/THANKS
+++ b/THANKS
@@ -70,6 +70,7 @@ Jeff Deifik             jdeifik@isi.edu
 Vince DeMarco           vince@whatnxt.cuc.ab.ca
 Michael De La Rue       p91152@cplab.physics.edinburgh.ac.uk
 Jeff Delinck            delinck@pa621a.inland.com
+Fabrice Derepas		fabrice.derepas@canonical.com
 John DeRoo              deroo@grout.adv.shr.dec.com
 Jim Diamond             zsd@axe.drea.dnd.ca
 Antonio Diaz Diaz	ant_diaz@teleline.es
diff --git a/tests/Makefile.am b/tests/Makefile.am
index f6fdb7d..b0e0f9e 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -24,6 +24,7 @@ TESTS =					\
   keep					\
   list					\
   lzh-lzh-ctable-abuse			\
+  lzw-concat-abuse			\
   lzw-lzh-abuse				\
   memcpy-abuse				\
   mixed					\
diff --git a/tests/lzw-concat-abuse b/tests/lzw-concat-abuse
new file mode 100755
index 0000000..60ed6a0
--- /dev/null
+++ b/tests/lzw-concat-abuse
@@ -0,0 +1,128 @@
+#!/bin/sh
+# Test whether stale compression data can mess up later LZW compression.
+
+# Copyright 2026 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/init.sh"; path_prepend_ ..
+
+fail=0
+
+printf \
+'\037\235\fA\204\f\0312d\310\220!C\206\f\031'\
+'2d\310\220!C\206\f\0312d\310\220!C\206'\
+'\f\0312d\310\220!C\206\f\0312d\310\220!'\
+'C\206\f\0312d\310\220!C\206\f\0312d\310'\
+'\220!C\206\f\0312d\310\220!C\206\f\0312'\
+'d\310\220!C\206\f\0312d\310\220!C\206\f'\
+'\0312d\310\220!C\206\f\0312d\310\220!C'\
+'\206\f\0312d\310\220!C\206\f\0312d\310\220'\
+'!C\206\f\0312d\310\220!C\206\f\0312d'\
+'\310\220!C\206\f\0312d\310\220!C\206\f\031'\
+'2d\310\220!C\206\f\0312d\310\220!C\206'\
+'\f\0312d\310\220!C\206\f\0312d\310\220!'\
+'C\206\f\0312d\310\220!C\206\f\0312d\310'\
+'\220!C\206\f\0312d\310\220!C\206\f\0312'\
+'d\310\220!C\206\f\0312d\310\220!C\206\f'\
+'\0312d\310\220!C\206\f\0312d\310\220!C'\
+'\206\f\0312d\310\220!C\206\f\0312d\310\220'\
+'!C\206\f\0312d\310\220!C\206\f\0312d'\
+'\310\220!\0\001\0\0\0\0\0\0\0\314\0\0\0'\
+'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0'\
+'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0'\
+'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0'\
+'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0'\
+'\0\0\0\0\0\0\0\0\0\0\0\0'\
+  >c1.Z || framework_failure_
+
+printf \
+'\037\235\211a\302\204\t\023&L\2300a\302\204\t'\
+'\023&L\2300a\302\204\t\023&L\2300a\302'\
+'\204\t\023&L\2300a\302\204\t\023&L\2300'\
+'a\302\204\t\023&L\2300a\302\204\t\023&L'\
+'\2300a\302\204\t\023&L\2300a\302\204\t\023'\
+'&L\2300a\302\204\t\023&L\2300a\302\204'\
+'\t\023&L\2300a\302\204\t\023&L\2300a'\
+'\302\204\t\023&L\2300a\302\204\t\023&L\230'\
+'0a\302\204\t\023&L\2300a\302\204\t\023&'\
+'L\2300a\302\204\t\023&L\2300a\302\204\t'\
+'\023&L\2300a\302\204\t\023&L\2300a\302'\
+'\204\t\023&L\2300a\302\204\t\023&L\2300'\
+'a\302\204\t\023&L\2300a\302\204\t\023&L'\
+'\2300a\302\204\t\023&L\2300a\302\204\t\023'\
+'&L\2300a\302\204\t\023&L\2300a\302\204'\
+'\t\023&L\2300a\302\204\t\023&L\2300a'\
+'\302\204\t\023&L\2300a\302\204\t\023&L\230'\
+'0a\302\204\t\023&L\2300a\302\204\t\023&'\
+'L\2300\0\002\004\0\0\0\0\0\0\0\335\0\0'\
+'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0'\
+'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0'\
+'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0'\
+'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0'\
+'\0\0\0\0\0\0\0\0\0\0\0\0'\
+  >c2.Z || framework_failure_
+
+printf '\037\235\bA\0\002\004\0\0\0\0\0\0\0\0\0' >c3.Z || framework_failure_
+
+printf \
+'ABCCCCCCCCCCCCCC'\
+'CCCCCCCCCCCCCCCC'\
+'CCCCCCCCCCCCCCCC'\
+'CCCCCCCCCCCCCCCC'\
+'CCCCCCCCCCCCCCCC'\
+'CCCCCCCCCCCCCCCC'\
+'CCCCCCCCCCCCCCCC'\
+'CCCCCCCCCCCCCCCC'\
+'CCCCCCCCCCCCCCCC'\
+'CCCCCCCCCCCCCCCC'\
+'CCCCCCCCCCCCCCCC'\
+'CCCCCCCCCCCCCCCC'\
+'CCCCCCCCCCCCCCCC'\
+'CCCCCCCCCCCCCCCC'\
+'CCCCCCCCCCCCCCCC'\
+'CCCCCCCCCCCCCCCC'\
+'AB\314\0\0\0\0\0\0\0\0\0\0\0\0\0'\
+'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0'\
+'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0'\
+'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0'\
+'\0\0aaaaaaaaaaaaaa'\
+'aaaaaaaaaaaaaaaa'\
+'aaaaaaaaaaaaaaaa'\
+'aaaaaaaaaaaaaaaa'\
+'aaaaaaaaaaaaaaaa'\
+'aaaaaaaaaaaaaaaa'\
+'aaaaaaaaaaaaaaaa'\
+'aaaaaaaaaaaaaaaa'\
+'aaaaaaaaaaaaaaaa'\
+'aaaaaaaaaaaaaaaa'\
+'aaaaaaaaaaaaaaaa'\
+'aaaaaaaaaaaaaaaa'\
+'aaaaaaaaaaaaaaaa'\
+'aaaaaaaaaaaaaaaa'\
+'aaaaaaaaaaaaaaaa'\
+'aaaaaaaaaaaaaaaa'\
+'aaaa\335\0\0\0\0\0\0\0\0\0\0\0'\
+'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0'\
+'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0'\
+'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0'\
+'\0\0\0\0\0\0\0\0\0\0AAA\0\0A'\
+'\0\0\0\0\0\0\0\0'\
+  >exp || framework_failure_
+
+gzip -dc c1.Z c2.Z c3.Z >out 2>err || fail=1
+compare exp out || fail=1
+compare /dev/null err || fail=1
+
+Exit $fail
diff --git a/unlzw.c b/unlzw.c
index 479ad4f..42eed82 100644
--- a/unlzw.c
+++ b/unlzw.c
@@ -70,12 +70,22 @@ union bytes
 #endif

 /* extern ush tab_prefix[1 << BITS]; prefix code */
-#define clear_tab_prefixof() memzero (tab_prefix, 256);
-#define de_stack        ((char_type *)(&d_buf[DIST_BUFSIZE-1]))
+#define de_base   ((char_type *) d_buf)
+#define de_stack  (de_base + sizeof d_buf)

 /* block compress mode -C compatible with 2.0 */
 static int block_mode = BLOCK_MODE;

+static void
+corrupt_input (int out, int outpos)
+{
+  if (0 < outpos)
+    write_buf (out, outbuf, outpos);
+  gzip_error (to_stdout
+              ? "corrupt input."
+              : "corrupt input. Use zcat to recover some data.");
+}
+
 /* ============================================================================
  * Decompress in to out.  This routine adapts to the codes in the
  * file building the "string" table on-the-fly; requiring no table to
@@ -113,7 +123,6 @@ unlzw (int in, int out)
               (unsigned int) maxbits & LZW_RESERVED));
     }
     maxbits &= BIT_MASK;
-    maxmaxcode = MAXCODE(maxbits);

     if (maxbits > BITS) {
         fprintf(stderr,
@@ -122,6 +131,7 @@ unlzw (int in, int out)
         exit_code = ERROR;
         return ERROR;
     }
+    maxmaxcode = MAXCODE (maxbits);
     rsize = insize;
     maxcode = MAXCODE(n_bits = INIT_BITS)-1;
     bitmask = (1<<n_bits)-1;
@@ -132,11 +142,12 @@ unlzw (int in, int out)

     free_ent = ((block_mode) ? FIRST : 256);

-    clear_tab_prefixof(); /* Initialize the first 256 entries in the table. */
-
+    memzero (tab_prefix, sizeof tab_prefix);
     for (code = 255 ; code >= 0 ; --code) {
         tab_suffix[code] = (char_type)code;
     }
+    memzero (tab_suffix + 256, ((1 << BITS) - 256) * sizeof *tab_suffix);
+
     do {
         int i;
         int  e;
@@ -181,17 +192,18 @@ unlzw (int in, int out)

             if (oldcode == -1) {
                 if (256 <= code)
-                  gzip_error ("corrupt input.");
+                  corrupt_input (out, outpos);
                 outbuf[outpos++] = (char_type)(finchar = (int)(oldcode=code));
                 continue;
             }
             if (code == CLEAR && block_mode) {
-                clear_tab_prefixof();
+                memzero (tab_prefix, sizeof tab_prefix);
                 free_ent = FIRST - 1;
                 posbits = ((posbits-1) +
                            ((n_bits<<3)-(posbits-1+(n_bits<<3))%(n_bits<<3)));
                 maxcode = MAXCODE(n_bits = INIT_BITS)-1;
                 bitmask = (1<<n_bits)-1;
+                oldcode = -1;
                 goto resetbuf;
             }
             incode = code;
@@ -211,11 +223,7 @@ unlzw (int in, int out)
                             "posbits:%ld inbuf:%02X %02X %02X %02X %02X\n",
                             posbits, p[-1],p[0],p[1],p[2],p[3]);
 #endif
-                    if (outpos > 0)
-                      write_buf (out, outbuf, outpos);
-                    gzip_error (to_stdout
-                                ? "corrupt input."
-                                : "corrupt input. Use zcat to recover some data.");
+                    corrupt_input (out, outpos);
                 }
                 *--stackp = (char_type)finchar;
                 code = oldcode;
@@ -224,6 +232,8 @@ unlzw (int in, int out)
             while ((cmp_code_int)code >= (cmp_code_int)256) {
                 /* Generate output characters in reverse order */
                 *--stackp = tab_suffix[code];
+                if (stackp == de_base)
+                  corrupt_input (out, outpos);
                 code = tab_prefix[code];
             }
             finchar = tab_suffix[code];