Friday, August 29, 2008

Ode to the bug that almost was

This post is a tribute to the hundreds of bugs that never quite were serious, and the emotional roller coaster ride on which they take researchers.

Some brief background. The skill in finding serious bugs these days isn't in being a demon code auditor or a furious fuzzer; there are thousands of these. The skill lies instead in finding a piece of software, or a piece of functionality, that has the curious mix of being important yet not having seen much scrutiny.

Onto today's almost-bug: an interesting integer condition in the ZIP parser of Sun's JDK. The ZIP parser is a critical piece of code because not only is it used to parse JARs, but also server-side Java applications will often parse untrusted ZIPs. (Such direct server-side attacks, along the lines of my JDK ICC parser vulnerabilities last year, are nasty, and starting to recently become in-vogue for Python, Ruby and Perl too). The affected API is java.util.zip.ZipFile. Best I know, java.util.zip.ZipInputStream is not backed by the same native code, and thereby unaffected.

The interesting code, in zip_util.c follows:


/* Following are unsigned 32-bit */
jlong endpos, cenpos, cenlen;
...
/* Get position and length of central directory */
cenlen = ENDSIZ(endbuf);
if (cenlen > endpos)
ZIP_FORMAT_ERROR("invalid END header (bad central directory size)");
cenpos = endpos - cenlen;


jlong is a signed 64-bit type. The ENDSIZ macro, because of the way it is formulated, returned a signed int. Therefore, the assignment to cenlen triggers sign extension. This means that cenlen can end up being negative, rather than the stated intent of being treated as an unsigned 32-bit quantity. The negative value will of course bypass the security check and lead to subsequent undesirable state. (Note that the best fix is not to enhance the check, but to add a cast to unsigned int to the underlying macro as it is used in multiple places).

So why does this appear to be just a bug and not a security vulnerability? Well, on systems without mmap(), a huge allocation will either cleanly fail, or a read() attempt past EOF will cleanly fail. On systems with mmap(), things are more interesting. A 32-bit build will attempt a 2Gb large mapping on a potentially much smaller file. This could lead to interesting SIGBUS conditions as a server DoS. By quite some luck, the Sun JVM process seems to spray mappings liberally through the address space, leaving no room for a contiguous 2Gb mapping.

The same sign-extension bug exists in other parts of the ZIP handling, and leads to some interesting negative values getting to some interesting places. But lower-level sanity checks save the day in the cases that I could find.

A zip file capable of triggering the interesting log line "mmap failed for CEN and END part of zip file" is available at http://scary.beasts.org/misc/mmap_fail.zip.

Ah well, maybe next time. Come to thing of it, my pipeline does include real JDK vulns. Watch this space.

2 comments:

Alex said...

I heard about not bad application-Fix zip file, recovers information from damaged zip files and minimizes data loss during the zip recovery process, allows the program to minimize data loss during zip recovery, therefore the user sees as much as possible about the files being recovered, repair self-extracting (SFX) files.

Raju said...

Thanks for the nice post.