On Sunday 23 February 2003 12:38 pm, Crazy Einstein wrote:
> /*
> \ 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]
> */
Ok, one simple proof of concept is enough. A second potentially
dangerous one (even for fun)...time to address this. ;)
Attached below is a patch RK and I whipped up yesterday, after I
caught wind of this problem sometime in the afternoon. It adds
extra code to properly gather the vsnprintf() return code if
available, and some ./configure checks to automatically set
macro definitions when it detects the requisite features. zlib
will still build if the host doesn't have the requisite
functions for full security, but ./configure will tell you about
how far you're bending over. The patch went through two
revisions to get to this level of completeness; it works as it
should on Linux==2.4.18/glibc>=2.2.5 but has not been tested on
other platforms.
RK and I both considered just completely dropping the vulnerable
codepaths; environments where zlib would have to fall back to
these codepaths honestly just don't deserve breathing rights.
But...I figure a fix isn't truly robust unless the fixed product
will still build on all the systems where it would build before.
At least now zlib builds secure-where-possible, instead of
broken-by-default.
During zlib ./configure, you should now see the following lines:
Checking whether to use vsnprintf() or snprintf()... using \
vsnprintf()
Checking for vsnprintf() in stdio.h... Yes.
Checking for return value of vsnprintf()... Yes.
> #include <zlib.h>
> #include <errno.h>
> #include <stdio.h>
<snip harmless but potentially wicked Proof-of-Concept code>
>
>
> [EMAIL PROTECTED] crz]$ gcc -o zlib zlib.c -lz
> [EMAIL PROTECTED] crz]$ ./zlib
> [>] exploiting...
> [>] xret = 0xbffff8f0
> sh-2.05b$ exit
> exit
> [EMAIL PROTECTED] crz]$
On vulnerable system:
[ [EMAIL PROTECTED] ~ ] # gcc -o zlibexp zlibexp.c -lz
[ [EMAIL PROTECTED] ~ ] # ./zlibexp
[>] exploiting...
[>] xret = 0xbffffaf0
sh-2.05a$ exit
exit
[ [EMAIL PROTECTED] ~ ] #
On patched system:
[ [EMAIL PROTECTED] /usr/src ] # ./zlibexp
[>] exploiting...
[>] xret = 0xbffffb50
>Sent!..
gzprintf -> 0
gzclose -> 0 [1]
[ [EMAIL PROTECTED] /usr/src ] #
The vulnerability consists of a buffer overflow and a
string-format vulnerability (in case something feeds '("Hello%c
there\n", '\0')' to gzprintf). Both should be fixed by the
patch below. How exploitable is this? Well, not very. The
gzprintf() function is seldom used, even on a fully loaded
system, so a would-be 0wner would likely have to code his own
app and trick the 0wnee into running it. I've got reliable
anecdotal evidence that ImageMagick calls gzprintf(), though I
haven't checked for myself.
--
Kelledin
"If a server crashes in a server farm and no one pings it, does
it still cost four figures to fix?"
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);
}