------------------------------------------------------------ Security Audit of Mozilla's .bmp image parsing (August 2004) by Gael Delalleau ------------------------------------------------------------ Overview -------- This report presents the results of a security audit of a part of the Mozilla 1.7.2 C++ source code tree. This security audit was done during my free time as an attempt to promote and participate to the Mozilla Security Bug Bounty Program (http://www.mozilla.org/security/bug-bounty.html). Several integer overflows were found in the image parsing code of Mozilla. Some of them leads to exploitable security bugs, ranging from Denial of Service (crash of the Mozilla client) to arbitrary code execution on the user's computer. Source code files audited ------------------------- mozilla/modules/libpr0n/decoders/bmp/nsBMPDecoder.cpp mozilla/modules/libpr0n/decoders/bmp/nsBMPDecoder.h + parts of various related files Details ------- There are mutiple integer overflows in the code used to parse and display .bmp images. Some of them leads to exploitable security bugs. These integer overflow can be found: - in the .bmp decoder of the libpr0n module - in the OS-specific code used to display images. For instance, the following file is used on Windows systems: mozilla/gfx/src/windows/nsImageWin.cpp I will now highlight all the integer overflow issues found during the audit, and try to show how these various issues finally allow for arbitrary code execution. (1) *** Denial Of Service while parsing a malformed .bmp image *** Vulnerable systems: Linux, Windows XP Other systems: untested * in nsBMPDecoder.cpp line 287: mRow = new PRUint8[(mBIH.width*mBIH.bpp)/8 + 4]; mBIH.width*mBIH.bpp can wrap around and thus the allocated buffer can be made very small. On Linux (Mozilla 1.7.2 developpement version) a negative value in the new[] operator will crash the client (uncatched exception?) Then mFrame->Init() is called, followed by NS_ENSURE_SUCESS, which ensures some sanity checks on the height, width and bpp of the image. However, these checks are not strict enough (they do not prevent mBIH.width*mBIH.bpp from wrapping around). ~ Fortunately on systems using gtk to display images (like Linux) an additional check is done in mImage->Init() which prevents Mozilla to display the image if the width or height is greater than 32767, thus preventing arbitrary code execution. ~ On Windows systems, there is no limit to the height and width of the image. Denial of service and arbitrary code execution are possible as shown below. ~ Other systems: untested. * in nsImageWin.cpp line 160: mRowBytes = CalcBytesSpan(mBHead->biWidth); with : 1498 nsImageWin :: CalcBytesSpan(PRUint32 aWidth) 1499 { 1500 PRInt32 spanBytes; 1503 spanBytes = (aWidth * mBHead->biBitCount) >> 5; (...) 1510 spanBytes <<= 2; 1513 return(spanBytes); 1514 } aWidth * mBHead->biBitCount is actually similar to mBIH.width*mBIH.bpp. It can wrap around, so mRowBytes can be very small. This value finds its way in nsBMPDecoder.cpp in the mBpr variable, which is used in line 374 to allocate a buffer (which can be very small due to the integer wrap around we just explained): mDecoded = (PRUint8*)malloc(mBpr); Thus at lines 420 to 428 we can end in a big loop (lpos==width iterations) which writes bytes to memory pointed by p, which is actually our small mDecoded buffer: 421 while (lpos > 0) { 422 SetPixel(d, p[2], p[1], p[0]); 423 p += 2; 424 --lpos; 425 if (mBIH.bpp == 32) 426 p++; // Padding byte 427 ++p; 428 } The SetPixel function writes the 3 bytes to d and increments d by 3. Thus, memory is being copied from the p buffer allocated in the heap, to the mDecoded buffer allocated later on the same heap. Both buffers can be made very small, so the loop will soon copy a part of the heap to another part of the heap. Consequences: ~ the client will crash when this big loop reaches the end of the allocated memory for the heap (write attempt to an unmapped memory area). ~ it might be possible to trigger arbitrary code execution if another thread uses the corrupted heap before the crash happens, or if we can allocate enough memory on the same heap to make it big enough to avoid the crash. ~ Consequences on systems other than Windows (32 bits): not evaluated. ---------- Side Note: there are other suspicious pieces of code in nsImageWin.cpp, without checks for integer overflows. For instance at line 161: mSizeImage = mRowBytes * mBHead->biHeight; (...) mImageBits = new unsigned char[mSizeImage]; ---------- (2) *** Arbitrary code execution while parsing a malformed .bmp image *** Vulnerable: Windows XP Not vulnerable (?): Linux Other systems: untested With the same bugs, there is a better way to corrupt the heap after the mDecoded pointer with arbitrary values, and without crashing the client. Indeed, we can use a RLE-encoded bitmap image (depth 8 bits, encoded with RLE8, width big enough to make mBpr wrap around, small height to pass the overflow check on width*height. See the sample image file: http://www.zencomsec.com/advisories/mozilla-1.7.2-BMP.bmp Line 464, the buffer is allocated (but too small!): mDecoded = (PRUint8*)calloc(mBpr, 1); Then we can legitimately use the RLE decoder to write arbitrary data in the mDecoded buffer, with the length limit being mBIH.width which is a big value (much bigger than the size of the buffer). Heap corruption with arbitrary data on the Windows OS is a common situation, known to allow arbitrary code execution. At least two exploitation techniques may be used here to get control of EIP and hijack the execution flow of the program: (a) overwriting C++ pointers to the methods of objects stored on the heap. The debugger shows that we can land in: CALL [EAX+10] where EAX=0x42424242 (our arbitrary data) (b) using standard heap overflow techniques. The debugger shows that we can land in ntdll.dll in: MOV [ECX], EAX where EAX and ECX are arbitrary values supplied by the attacker in the malicious image file. --------- Side Note: in the RLE decoder of nsBMPDecoder.cpp at line 568, there is: mDecoding += byte * GFXBYTESPERPIXEL; This can be abused to put the mDecoding pointer out of the bounds of the mDecoded buffer. Even though this does not seems to be exploitable with the current surrounding code, it deserves to be fixed. --------- Exploit code ------------ I won't provide actual exploit code because: - it is outside the scope of a source code audit - I gave enough information to show such an exploit can be written. The issue is just the same kind as many other heap overflow vulnerabilities, including previous Mozilla vulnerabilities like the SOAPParameter overflow (236618). - in France, the legal status of publishing exploit code is unknown at the moment. - Cheers - Gael Delalleau The original version of this report can be found at: http://www.zencomsec.com/advisories/mozilla-1.7.2-BMP.txt