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];