From 53f56145cc4fc9f14060c0a022f502c210d175a7 Mon Sep 17 00:00:00 2001
From: Lasse Collin <lasse.collin@tukaani.org>
Date: Mon, 28 Mar 2022 09:35:27 -0700
Subject: [PATCH 1/6] zgrep: avoid exploit via multi-newline file names

* zgrep.in: The issue with the old code is that with multiple
newlines, the N-command will read the second line of input,
then the s-commands will be skipped because it's not the end
of the file yet, then a new sed cycle starts and the pattern
space is printed and emptied. So only the last line or two get
escaped. This patch makes sed read all lines into the pattern
space and then do the escaping.

This vulnerability was discovered by:
cleemy desu wayo working with Trend Micro Zero Day Initiative
---
 zgrep.in | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

Index: gzip-1.6/zgrep.in
===================================================================
--- gzip-1.6.orig/zgrep.in
+++ gzip-1.6/zgrep.in
@@ -66,30 +66,33 @@ while test $# -ne 0; do
 
   case $option in
   (-[0123456789EFGHIKLPRTUVZabchilnoqrsuvwxyz]*[!0123456789]*)
-    arg2=-\'$(expr "X$option" : 'X-.[0-9]*\(.*\)' | sed "$escape")
+    arg2=-\'$(LC_ALL=C expr "X$option" : 'X-.[0-9]*\(.*\)' |
+                LC_ALL=C sed "$escape")
     eval "set -- $arg2 "'${1+"$@"}'
-    option=$(expr "X$option" : 'X\(-.[0-9]*\)');;
+    option=$(LC_ALL=C expr "X$option" : 'X\(-.[0-9]*\)');;
   (--binary-*=* | --[lm]a*=* | --reg*=*)
     ;;
   (-[ABCDXdefm] | binary-* | --file | --[lm]a* | --reg*)
     case ${1?"$option option requires an argument"} in
     (*\'*)
-      optarg=" '"$(printf '%s\n' "$1" | sed "$escape");;
+      optarg=" '"$(printf '%s\n' "$1" | LC_ALL=C sed "$escape");;
     (*)
       optarg=" '$1'";;
     esac
     shift;;
   (-f?*\'*)
-    optarg=" '"$(expr "X$option" : 'X-f\(.*\)' | sed "$escape")
+    optarg=" '"$(LC_ALL=C expr "X$option" : 'X-f\(.*\)' |
+                   LC_ALL=C sed "$escape")
     option=-f;;
   (-f?*)
-    optarg=" '"$(expr "X$option" : 'X-f\(.*\)')\'
+    optarg=" '"$(LC_ALL=C expr "X$option" : 'X-f\(.*\)')\'
     option=-f;;
   (--file=*\'*)
-    optarg=" '"$(expr "X$option" : 'X--file=\(.*\)' | sed "$escape")
+    optarg=" '"$(LC_ALL=C expr "X$option" : 'X--file=\(.*\)' |
+                   LC_ALL=C sed "$escape")
     option=--file;;
   (--file=*)
-    optarg=" '"$(expr "X$option" : 'X--file=\(.*\)')\'
+    optarg=" '"$(LC_ALL=C expr "X$option" : 'X--file=\(.*\)')\'
     option=--file;;
   (--)
     break;;
@@ -98,7 +101,8 @@ while test $# -ne 0; do
   (*)
     case $option in
     (*\'*)
-      operands="$operands '"$(printf '%s\n' "$option" | sed "$escape");;
+      operands="$operands '"$(printf '%s\n' "$option" | LC_ALL=C sed "$escape")
+      ;;
     (*)
       operands="$operands '$option'";;
     esac
@@ -145,7 +149,7 @@ while test $# -ne 0; do
 
   case $option in
   (*\'?*)
-    option=\'$(printf '%s\n' "$option" | sed "$escape");;
+    option=\'$(printf '%s\n' "$option" | LC_ALL=C sed "$escape");;
   (*)
     option="'$option'";;
   esac
@@ -158,7 +162,7 @@ eval "set -- $operands "'${1+"$@"}'
 if test $have_pat -eq 0; then
   case ${1?"missing pattern; try \`$0 --help' for help"} in
   (*\'*)
-    grep="$grep -- '"$(printf '%s\n' "$1" | sed "$escape");;
+    grep="$grep -- '"$(printf '%s\n' "$1" | LC_ALL=C sed "$escape");;
   (*)
     grep="$grep -- '$1'";;
   esac
@@ -209,19 +213,17 @@ do
       case $i in
       (*'
 '* | *'&'* | *'\'* | *'|'*)
-        i=$(printf '%s\n' "$i" |
-            sed '
-              $!N
-              $s/[&\|]/\\&/g
-              $s/\n/\\n/g
-            ');;
+          icolon=$(printf '%s\n' "$i:" |
+                     LC_ALL=C sed -e 's/[&\|]/\\&/g' -e '$!s/$/\\/');;
+      (*) icolon="$i:";;
       esac
-      sed_script="s|^|$i:|"
+      sed_script="s|^|$icolon|"
 
       # Fail if grep or sed fails.
       r=$(
         exec 4>&1
-        (eval "$grep" 4>&-; echo $? >&4) 3>&- | sed "$sed_script" >&3 4>&-
+        (eval "$grep" 4>&-; echo $? >&4) 3>&- |
+           LC_ALL=C sed "$sed_script" >&3 4>&-
       ) && exit $r
       r=$?
       test 1 -lt $r && exit $r || exit 2
Index: gzip-1.6/tests/Makefile.am
===================================================================
--- gzip-1.6.orig/tests/Makefile.am
+++ gzip-1.6/tests/Makefile.am
@@ -28,6 +28,7 @@ TESTS =					\
   unpack-valid				\
   zdiff					\
   zgrep-f				\
+  zgrep-abuse				\
   zgrep-context				\
   zgrep-signal				\
   znew-k
Index: gzip-1.6/tests/zgrep-abuse
===================================================================
--- /dev/null
+++ gzip-1.6/tests/zgrep-abuse
@@ -0,0 +1,43 @@
+#!/bin/sh
+# Show how zgrep applied to a crafted file name may overwrite
+# a selected file with chosen content.  Fixed in gzip-1.12.
+
+# Copyright (C) 2022 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/>.
+# limit so don't run it by default.
+
+. "${srcdir=.}/init.sh"; path_prepend_ ..
+
+f=z
+echo test > $f || framework_failure_
+gzip $f || framework_failure_
+cp $f.gz 'zfile_crafted|
+p
+1s|.*|hacked1|
+1w hacked
+etouch .\x2fhacked2
+d
+#
+#' || framework_failure_
+
+fail=0
+
+zgrep test z*
+
+# Before, each of these would be created.
+test -f hacked && fail=1
+test -f hacked2 && fail=1
+
+Exit $fail
Index: gzip-1.6/gzexe.in
===================================================================
--- gzip-1.6.orig/gzexe.in
+++ gzip-1.6/gzexe.in
@@ -96,10 +96,11 @@ for i do
     continue
   fi
   if test $decomp -eq 0; then
-    if sed -e 1d -e 2q "$file" | grep "^skip=[0-9][0-9]*$" >/dev/null; then
+    case `LC_ALL=C sed -n -e 1d -e '/^skip=[0-9][0-9]*$/p' -e 2q "$file"` in
+    skip=[0-9] | skip=[0-9][0-9] | skip=[0-9][0-9][0-9])
       echo >&2 "$0: $i is already gzexe'd"
-      continue
-    fi
+      continue;;
+    esac
   fi
   if test -u "$file"; then
     echo >&2 "$0: $i has setuid permission, unchanged"
@@ -198,7 +199,7 @@ EOF
   else
     # decompression
     skip=44
-    skip_line=`sed -e 1d -e 2q "$file"`
+    skip_line=`LC_ALL=C sed -e 1d -e 2q "$file"`
     case $skip_line in
     skip=[0-9] | skip=[0-9][0-9] | skip=[0-9][0-9][0-9])
       eval "$skip_line";;
Index: gzip-1.6/sample/zfile
===================================================================
--- gzip-1.6.orig/sample/zfile
+++ gzip-1.6/sample/zfile
@@ -1,5 +1,8 @@
 #!/bin/sh
 
+LC_ALL=C
+export LC_ALL
+
 if test $# = 0; then
   echo 'zfile: file(1) for programs which may be compressed with gzexe'
   echo usage: `basename $0`  files...
Index: gzip-1.6/zdiff.in
===================================================================
--- gzip-1.6.orig/zdiff.in
+++ gzip-1.6/zdiff.in
@@ -59,7 +59,7 @@ while :; do
   --h*) printf '%s\n' "$usage" || exit 2; exit;;
   --v*) echo "$version" || exit 2; exit;;
   --) shift; break;;
-  -*\'*) cmp="$cmp '"`printf '%sX\n' "$1" | sed "$escape"`;;
+  -*\'*) cmp="$cmp '"`printf '%sX\n' "$1" | LC_ALL=C sed "$escape"`;;
   -?*) cmp="$cmp '$1'";;
   *) break;;
   esac
@@ -78,7 +78,7 @@ exec 3>&1
 if test $# -eq 1; then
   case $1 in
   *[-.]gz* | *[-.][zZ] | *.t[ga]z)
-    FILE=`expr "X$1" : 'X\(.*\)[-.][zZtga]*$'`
+    FILE=`LC_ALL=C expr "X$1" : 'X\(.*\)[-.][zZtga]*$'`
     gzip_status=$(
       exec 4>&1
       (gzip -cd -- "$1" 4>&-; echo $? >&4) 3>&- | eval "$cmp" - '"$FILE"' >&3
Index: gzip-1.6/znew.in
===================================================================
--- gzip-1.6.orig/znew.in
+++ gzip-1.6/znew.in
@@ -23,6 +23,9 @@ case $1 in
 esac
 PATH=$bindir:$PATH; export PATH
 
+LC_ALL=C
+export LC_ALL
+
 version="znew (gzip) @VERSION@
 Copyright (C) 2010-2013 Free Software Foundation, Inc.
 This is free software.  You may redistribute copies of it under the terms of
