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 [crazy_einstein@yahoo.com] 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> > > > [crz@blacksand crz]$ gcc -o zlib zlib.c -lz > [crz@blacksand crz]$ ./zlib > [>] exploiting... > [>] xret = 0xbffff8f0 > sh-2.05b$ exit > exit > [crz@blacksand crz]$ On vulnerable system: [ kelledin@valhalla ~ ] # gcc -o zlibexp zlibexp.c -lz [ kelledin@valhalla ~ ] # ./zlibexp [>] exploiting... [>] xret = 0xbffffaf0 sh-2.05a$ exit exit [ kelledin@valhalla ~ ] # On patched system: [ kelledin@valhalla /usr/src ] # ./zlibexp [>] exploiting... [>] xret = 0xbffffb50 >Sent!.. gzprintf -> 0 gzclose -> 0 [1] [ kelledin@valhalla /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 <kelledin@users.sourceforge.net> + 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); }