* NEWS: Describe gzexe changes noted below.
authorPaul Eggert <eggert@cs.ucla.edu>
Fri, 1 Dec 2006 20:52:52 +0000 (20:52 +0000)
committerPaul Eggert <eggert@cs.ucla.edu>
Fri, 1 Dec 2006 20:52:52 +0000 (20:52 +0000)
* Makefile.am (.in): Don't bother with SEDCMD.  This stuff isn't needed
any more (the hosts it caters to are long extinct), and was questionable
anyway since the code assumes the skip= line was line 2.
* configure.ac (AC_SYS_INTERPRETER, SEDCMD): Remove; no longer needed.

* gzexe.in: Sweep the code and fix some bugs.
My, what sharp teeth you have, gzexe!
The straw that broke this camel's back was Matthew Burgess's bug report
<http://lists.gnu.org/archive/html/bug-gzip/2006-11/msg00012.html>.

(IFS): Set it to the standard value, both here and in the
script gzexe generates.
Check for missing operand after parsing options, not before.
This fixes the case for "cpexe --".
Check for tail -n problem separately in gzexe and in the executable
it generates, in case it's a different 'tail'.
(trap): Remove $tmp only if $tmp is not the empty string.
Preserve exit status.
Don't use the nonstandard (and rarely available) "cpmod" utility.
Don't use "set -C"; it's no longer useful and it breaks things
in some cases.
(main loop): Handle file names beginning with "-".
Exit with status of failing program, not with 1.
Fix some bugs in printing diagnostics, and in quoting.
Require the skip= line to have at least one digit.
Use test -u and test -g rather than using the (less-reliable) ls.
Refuse to compress more programs, e.g., sh.
Use mktemp rather than tempfile.  Build a copy of
the compressed or uncompressed executable in the same
directory as the executable, as that's less likely to go wrong
if disk space is low.  Have the executable exit with status 127,
not 1, if the decompression process fails; this is more compatible
with meta-programs like nohup.  Have the executable
uncompress to a temp file with the same basename as the executable;
this is more likely to go right.  Fix a race condition where the
executable temporarily did not exist (in either old or new forms).
Check for race conditions better when mv fails.  Do not attempt
to use cp to do the real work, only to copy permissions to a temp
file, since we don't want to trash running executables.

ChangeLog
Makefile.am
NEWS
configure.ac
gzexe.in

index b659f5f095e2a487caceb14ed5c7d2e38c709d6d..b0fb1b4c6f70e0a4574ae95221771c4089ff19e6 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,46 @@
+2006-12-01  Paul Eggert  <eggert@cs.ucla.edu>
+
+       * NEWS: Describe gzexe changes noted below.
+       * Makefile.am (.in): Don't bother with SEDCMD.  This stuff isn't needed
+       any more (the hosts it caters to are long extinct), and was questionable
+       anyway since the code assumes the skip= line was line 2.
+       * configure.ac (AC_SYS_INTERPRETER, SEDCMD): Remove; no longer needed.
+
+       * gzexe.in: Sweep the code and fix some bugs.
+       My, what sharp teeth you have, gzexe!
+       The straw that broke this camel's back was Matthew Burgess's bug report
+       <http://lists.gnu.org/archive/html/bug-gzip/2006-11/msg00012.html>.
+
+       (IFS): Set it to the standard value, both here and in the
+       script gzexe generates.
+       Check for missing operand after parsing options, not before.
+       This fixes the case for "cpexe --".
+       Check for tail -n problem separately in gzexe and in the executable
+       it generates, in case it's a different 'tail'.
+       (trap): Remove $tmp only if $tmp is not the empty string.
+       Preserve exit status.
+       Don't use the nonstandard (and rarely available) "cpmod" utility.
+       Don't use "set -C"; it's no longer useful and it breaks things
+       in some cases.
+       (main loop): Handle file names beginning with "-".
+       Exit with status of failing program, not with 1.
+       Fix some bugs in printing diagnostics, and in quoting.
+       Require the skip= line to have at least one digit.
+       Use test -u and test -g rather than using the (less-reliable) ls.
+       Refuse to compress more programs, e.g., sh.
+       Use mktemp rather than tempfile.  Build a copy of
+       the compressed or uncompressed executable in the same
+       directory as the executable, as that's less likely to go wrong
+       if disk space is low.  Have the executable exit with status 127,
+       not 1, if the decompression process fails; this is more compatible
+       with meta-programs like nohup.  Have the executable
+       uncompress to a temp file with the same basename as the executable;
+       this is more likely to go right.  Fix a race condition where the
+       executable temporarily did not exist (in either old or new forms).
+       Check for race conditions better when mv fails.  Do not attempt
+       to use cp to do the real work, only to copy permissions to a temp
+       file, since we don't want to trash running executables.
+
 2006-11-26  Paul Eggert  <eggert@cs.ucla.edu>
 
        * inflate.c (inflate_dynamic, inflate): Don't send output to
index 48c774a767343f71be7e15b7bb63c1b5ea3f56d7..2bb19548f2af4e1103b26b2cc066c75988a8b109 100644 (file)
@@ -51,7 +51,6 @@ gzip.doc: gzip.1
 SUFFIXES = .in
 .in:
        sed \
-               -e '$(SEDCMD)' \
                -e 's|/bin/sh|$(SHELL)|g' \
                -e 's|BINDIR|$(bindir)|g' \
                -e 's|[@]VERSION@|$(VERSION)|g' \
diff --git a/NEWS b/NEWS
index a19a05be21b2e3a962bd99a32ec9f0e1fb0a61d6..0e2c01f294562d60c88a9d9734d70d471247afc1 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,14 @@
+Major changes in Gzip 1.3.7 (not yet released)
+
+* Fix some gzexe problems:
+  - Improve resistance to denial-of-service attacks.
+  - Fix some quoting and escaping bugs.
+  - Do not assume /tmp is sticky (though it should be!).
+  - Do not assume the working directory can be written.
+  - Rely on PATH in the generated executable, as the man page says.
+  - Don't assume IFS is sane.
+  - Exit with signal's status, if signaled.
+
 Major changes in Gzip 1.3.6 (2006-11-20)
 
 * Fix some race conditions in setting file time stamps, permissions, and owner.
index 7ae50151b4b7c531cee63f4d06421a9b50b7b99a..2bc25a45987abfeeb9d6ad9bf75fabfa7a9babaf 100644 (file)
@@ -84,15 +84,9 @@ AC_HEADER_DIRENT
 AC_TYPE_SIGNAL
 AC_TYPE_SIZE_T
 AC_TYPE_OFF_T
-AC_SYS_INTERPRETER
-case $interpval in
-yes) SEDCMD='1d';;
-*) SEDCMD='';;
-esac
 
 AC_PREFIX_PROGRAM(gzip)
 AC_SUBST(ASCPP)dnl
-AC_SUBST(SEDCMD)dnl
 
 AC_CONFIG_FILES([Makefile doc/Makefile lib/Makefile])
 AC_OUTPUT
index 69e8c3fc87f69ac7dd08c6d3cb1d81e0013b4090..139ec615c2e2b74dfcd2df2593d4adc2685697e0 100644 (file)
--- a/gzexe.in
+++ b/gzexe.in
@@ -1,4 +1,3 @@
-:
 #!/bin/sh
 # gzexe: compressor for Unix executables.
 # Use this only for binaries that you do not use frequently.
@@ -14,7 +13,7 @@
 # On Ultrix, /bin/sh is too buggy, change the first line to: #!/bin/sh5
 
 
-# Copyright (C) 1998, 2002, 2004 Free Software Foundation
+# Copyright (C) 1998, 2002, 2004, 2006 Free Software Foundation
 # Copyright (C) 1993 Jean-loup Gailly
 
 # This program is free software; you can redistribute it and/or modify
 # with this program; if not, write to the Free Software Foundation, Inc.,
 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 
+tab='  '
+nl='
+'
+IFS=" $tab$nl"
+
 version='gzexe (gzip) @VERSION@
 Copyright (C) 2006 Free Software Foundation, Inc.
 This is free software.  You may redistribute copies of it under the terms of
@@ -50,10 +54,6 @@ Report bugs to <bug-gzip@gnu.org>.'
 
 
 PATH="BINDIR:$PATH"
-if test $# = 0; then
-  echo "$usage"
-  exit 1
-fi
 
 decomp=0
 res=0
@@ -67,134 +67,154 @@ while :; do
   esac
 done
 
-tmp=gz$$
-trap "rm -f $tmp; exit 1" 1 2 3 5 10 13 15
-
-set -C
-echo hi > $tmp || exit
-if test -z "`(${CPMOD-cpmod} $tmp $tmp) 2>&1`"; then
-  cpmod=${CPMOD-cpmod}
-fi
-
-tail=""
-IFS="${IFS=    }"; saveifs="$IFS"; IFS="${IFS}:"
-for dir in $PATH; do
-  test -z "$dir" && dir=.
-  if test -f $dir/tail; then
-    tail="$dir/tail"
-    break
-  fi
-done
-IFS="$saveifs"
-if test -z "$tail"; then
-  echo cannot find tail
+if test $# -eq 0; then
+  echo >&2 "$0: missing operand
+Try \`$0 --help' for more information."
   exit 1
 fi
-case `echo foo | $tail -n +1 2>/dev/null` in
-foo) tail="$tail -n";;
-esac
+
+tmp=
+trap '
+  res=$?
+  test -n "$tmp" && rm -f "$tmp"
+  (exit $res); exit $res
+' 0 1 2 3 5 10 13 15
 
 for i do
-  if test ! -f "$i" ; then
-    echo ${x}: $i not a file
-    res=1
+  case $i in
+  -*) file=./$i;;
+  *)  file=$i;;
+  esac
+  if test ! -f "$file" || test ! -r "$file"; then
+    res=$?
+    echo >&2 "$0: $i is not a readable regular file"
     continue
   fi
   if test $decomp -eq 0; then
-    if sed -e 1d -e 2q "$i" | grep "^skip=[0-9]*$" >/dev/null; then
-      echo "${x}: $i is already gzexe'd"
+    if sed -e 1d -e 2q "$file" | grep "^skip=[0-9][0-9]*$" >/dev/null; then
+      echo >&2 "$0: $i is already gzexe'd"
       continue
     fi
   fi
-  if ls -l "$i" | grep '^...[sS]' > /dev/null; then
-    echo "${x}: $i has setuid permission, unchanged"
+  if test -u "$file"; then
+    echo >&2 "$0: $i has setuid permission, unchanged"
     continue
   fi
-  if ls -l "$i" | grep '^......[sS]' > /dev/null; then
-    echo "${x}: $i has setgid permission, unchanged"
+  if test -g "$file"; then
+    echo >&2 "$0: $i has setgid permission, unchanged"
     continue
   fi
-  case "$i" in
-  */gzip | */tail | */sed | */chmod | */ln | */sleep | */rm)
-       echo "${x}: $i would depend on itself"; continue ;;
+  case /$file in
+  */basename | */cat | */chmod | */cp | \
+  */dirname | */echo | */expr | */gzip | \
+  */ln | */mkdir | */mktemp | */mv | */rm | */rmdir | \
+  */sed | */sh | */sleep | */test | */tail)
+    echo >&2 "$0: $i would depend on itself"; continue;;
   esac
-  if test -z "$cpmod"; then
-    cp -p "$i" $tmp 2>/dev/null || cp "$i" $tmp
-    if test -w $tmp 2>/dev/null; then
-      writable=1
-    else
-      writable=0
-      chmod u+w $tmp 2>/dev/null
-    fi
-    : >| $tmp
+
+  dir=`dirname "$file"` || dir=$TMPDIR
+  test -d "$dir" && test -w "$dir" && test -x "$dir" || dir=/tmp
+  test -n "$tmp" && rm -f "$tmp"
+  tmp=`TMPDIR=$dir mktemp -t gzexeXXXXXX` || tmp=$dir/gzexe$$
+  cp -p "$file" "$tmp" 2>/dev/null || cp "$file" "$tmp" || {
+    res=$?
+    echo >&2 "$0: cannot copy $file"
+    continue
+  }
+  if test -w "$tmp"; then
+    writable=1
+  else
+    writable=0
+    chmod u+w "$tmp" || {
+      res=$?
+      echo >&2 "$0: cannot chmod $tmp"
+      continue
+    }
   fi
   if test $decomp -eq 0; then
-    (sed 1q $0 &&
-     sed "s|^if tail|if $tail|" <<'EOF' &&
-skip=26
-set -C
+    (cat <<'EOF' &&
+#!/bin/sh
+skip=43
+
+tab='  '
+nl='
+'
+IFS=" $tab$nl"
+
 umask=`umask`
 umask 77
-if (tempfile --version) >/dev/null 2>&1
-then gztmp=`tempfile -p gztmp` || exit
-else gztmp=/tmp/gztmp$$
-fi
-if tail +$skip "$0" | "BINDIR"/gzip -cd > "$gztmp"; then
+
+gztmpdir=
+trap '
+  res=$?
+  test -n "$gztmpdir" && rm -fr "$gztmpdir"
+  (exit $res); exit $res
+' 0 1 2 3 5 10 13 15
+
+gztmpdir=`(mktemp -dt) 2>/dev/null` ||
+  { gztmpdir=/tmp/gztmp$$; mkdir $gztmpdir; } ||
+  { (exit 127); exit 127; }
+
+gztmp=$gztmpdir/$0
+case $0 in
+-* | */*'
+') mkdir -p "$gztmp" && rmdir "$gztmp";;
+*/*) gztmp=$gztmpdir/`basename "$0"`;;
+esac || { (exit 127); exit 127; }
+
+case `echo X | tail -n +1 2>/dev/null` in
+X) tail_n=-n;;
+*) tail_n=;;
+esac
+if tail $tail_n +$skip <"$0" | gzip -cd > "$gztmp"; then
   umask $umask
-  /bin/chmod 700 "$gztmp"
-  prog=`echo "$gztmp" | /bin/sed 's|[^/]*$||'; echo $0 | /bin/sed 's|.*/||'`
-  if /bin/ln "$gztmp" "$prog" 2>/dev/null; then
-    trap '/bin/rm -f "$gztmp" "$prog"; exit $res' 0
-    (/bin/sleep 5; /bin/rm -f "$gztmp" "$prog") 2>/dev/null &
-    "$prog" ${1+"$@"}; res=$?
-  else
-    trap '/bin/rm -f "$gztmp"; exit $res' 0
-    (/bin/sleep 5; /bin/rm -f "$gztmp") 2>/dev/null &
-    "$gztmp" ${1+"$@"}; res=$?
-  fi
+  chmod 700 "$gztmp"
+  (sleep 5; rm -fr "$gztmpdir") 2>/dev/null &
+  "$gztmp" ${1+"$@"}; res=$?
 else
-  echo Cannot decompress $0; exit 1
+  echo >&2 "Cannot decompress $0"
+  (exit 127); res=127
 fi; exit $res
 EOF
-     gzip -cv9 "$i") > $tmp || {
-      /bin/rm -f $tmp
-      echo ${x}: compression not possible for $i, file unchanged.
-      res=1
+    gzip -cv9 "$file") > "$tmp" || {
+      res=$?
+      echo >&2 "$0: compression not possible for $i, file unchanged."
       continue
     }
 
   else
     # decompression
-    skip=26
-    if sed -e 1d -e 2q "$i" | grep "^skip=[0-9][0-9]*$" >/dev/null; then
-      eval `sed -e 1d -e 2q "$i"`
-    fi
-    if $tail +$skip "$i" | gzip -cd > $tmp; then
-      :
-    else
-      echo ${x}: $i probably not in gzexe format, file unchanged.
-      res=1
+    skip=43
+    skip_line=`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";;
+    esac
+    case `echo X | tail -n +1 2>/dev/null` in
+    X) tail_n=-n;;
+    *) tail_n=;;
+    esac
+    tail $tail_n +$skip "$file" | gzip -cd > "$tmp" || {
+      res=$?
+      echo >&2 "$0: $i probably not in gzexe format, file unchanged."
       continue
-    fi
+    }
   fi
-  rm -f "$i~"
-  mv "$i" "$i~" || {
-    echo ${x}: cannot backup $i as $i~
-    rm -f $tmp
-    res=1
+  test $writable -eq 1 || chmod u-w "$tmp" || {
+    res=$?
+    echo >&2 "$0: $tmp: cannot chmod"
     continue
   }
-  mv $tmp "$i" || cp -p $tmp "$i" 2>/dev/null || cp $tmp "$i" || {
-    echo ${x}: cannot create $i
-    rm -f $tmp
-    res=1
+  ln -f "$file" "$file~" || {
+    res=$?
+    echo >&2 "$0: cannot backup $i as $i~"
     continue
   }
-  rm -f $tmp
-  if test -n "$cpmod"; then
-    $cpmod "$i~" "$i" 2>/dev/null
-  elif test $writable -eq 0; then
-    chmod u-w $i 2>/dev/null
-  fi
+  mv -f "$tmp" "$file" || {
+    res=$?
+    echo >&2 "$0: cannot rename $tmp to $i"
+    continue
+  }
+  tmp=
 done
-exit $res
+(exit $res); exit $res