Closed Bug 443299 (CVE-2008-0017) Opened 16 years ago Closed 16 years ago

Investigate possible buffer overflow in nsDirIndexParser


(Core :: XPCOM, defect, P1)






(Reporter: bsterne, Assigned: jdschuh)



(Keywords: fixed1.8.1.18, fixed1.9.1, verified1.9.0.4, Whiteboard: [sg:critical])


(3 files, 1 obsolete file)

Attached file IBM advisory
Justin Schuh of the IBM X-Force reported the following issue via security@m.o.

The issue appears to require specially-formatted HTTP response headers, so I will send email to the reporter to see if they have a PoC CGI they can provide.

From the advisory:

The http-index-format parser does not check for an allocation failure on a format array. The resulting array is then terminated with a sentinel value of 0xFFFFFFFF. A malicious party can exploit this issue to execute arbitrary code by overwriting a sensitive memory location (such as a buffer length or boolean variable).

When handling a content type of "application/http-index-format", a specially crafted 200 header line in an HTTP index response can result in an exploitable memory corruption condition. The following code fails to check for a NULL pointer returned from a new [] statement (and no exception is thrown). 

lines 203-204

  mFormat = new int[num+1];
  mFormat[num] = -1;

The resulting pointer is then dereferenced by four times the number of tokens provided in the header line, and a 32-bit integer value of -1 (0xFFFFFFFF) is assigned to the dereferenced location.

By manipulating the currently available memory (such as through Javascript) a malicious party can reliably exploit this issue to overwrite an arbitrary memory location with the 4-byte value 0xFFFFFFFF.
Sent email to reporter requesting proof-of-concept CGI for exploit.
Whiteboard: [sg:investigate]
Whiteboard: [sg:investigate] → [sg:needinfo]
Reporter sent additional exploit notes but isn't able to send CGI code for PoC due to his company's internal policies.  From his email:

General Exploitation
The exploit runs against Firefox on Windows, and takes advantage of a 4-byte write of 0xFFFFFFFF to an arbitrary (attacker selectable) location by dereferencing a NULL pointer. The exploit occurs in multiple stages in which memory is fragmented, a buffer length is overwritten (nsHTMLTags::sMaxTagNameLength at 0xB45AD0), and a group of function
pointers are then overwritten past the buffer's intended bounds.

Stage 1
This stage fragments memory in the browser by using JavaScript to create a large number of canvas elements set to consume roughly 0xB419E8 bytes. The canvas element was chosen because it is initialized reasonably quickly to a controllable size and does not result in a crash on allocation failure.

Stage 2
This stage delivers a application/http-index-format content body containing a single 200 header line with exactly 0x2D16B4 space-delimited, single-character tokens. In the interest of efficiency, this content is delivered gzip-compressed, resulting in a 5778-byte buffer.

If the fragmentation is successful this will result in a failed allocation of nsDirIndexParser::mFormat and the value 0xFFFFFFFF being written to the nsHTMLTags::sMaxTagNameLength at address 0xB45AD0. Because the token string matches no standard tokens, the token buffer nsDirIndexParser::mFormat is never written to after being terminated.

Further examination will likely reveal other exploit methods (particularly if common plugins are considered). However, this location is adequate for demonstration purposes and has been shown to produce a 100% reliable code execution exploit.

Stage 3
This stage contains a malformed HTML tag to write beyond the bounds of the static variable buf (address 0xB45CA8) in nsHTMLTags::LookupTag(). The exploit buffer overwrites several function pointers and replaces them with a pointer to the shellcode stub. Current tests have shown that the overwritten value previously containing uxtheme.OpenThemeDat () is called immediately after the overwrite occurs.

Stage 3 always proceeds regardless of whether stage 2 succeeded or not. If code execution does not result from this stage, available memory is altered and stage 2 is repeated.
Attached file crash script
The the obvious fix is to change line 203  in nsDirIndexParser::ParseFormat() to check for an allocation failure with something like this:

  if ((mFormat = new int[num+1]) == NULL) return NS_ERROR_OUT_OF_MEMORY;

As for triggering the bug, that's a little more annoying and the impact is OS and runtime dependent. You also have to force an allocation failure, which is why the proof of concept exploit is a bit complicated. So, I'm attaching a python script (not the exploit script) that should crash Firefox on Windows, but the crash is very slow and will consume over a gig of memory in the process.

An offending content body will look something like what I've shown below, but with a much longer string of "x x x" tokens (enough that the new call will fail). The address at the tokens * 4 is then overwritten with 0xFFFFFFFF. Also, the script gzips the content to save some time (and even then it's intolerably slow).

Response Content Body:

HTTP/1.0 200 OK
Content-Type: application/http-index-format

200: x x x x x x x x x x x x x x x
I just realized that this bug is mislabeled. It affects all hardware/OS/versions. In some combinations it's a code execution bug (Firefox 2.0 and below on Windows) and on some it's a crash bug (like Firefox 3.0 on Windows). Whether it's code execution or crash depends primarily on if the new call throws an unhandled exception (which Firefox 3.0 does). After that, exploitation is a matter of finding a reasonable address to overwrite.
This prevents the vulnerability both by checking for an excessive number of column headers and identifying a failed allocation. 

Checking the column headers isn't necessary to fix the bug, but there's no good reason to allow an arbitrary number of columns given the spec at
Attachment #335756 - Flags: review?(bsterne)
Attachment #335756 - Flags: review?(bsterne) → review?(dveditz)
Flags: wanted1.8.1.x+
Flags: blocking1.9.1?
Flags: blocking1.9.0.4?
Flags: blocking1.8.1.18?
Assigning to Justin since he's supplied the patch, but we'll find someone to check it in for you
Assignee: nobody → jdschuh
Flags: blocking1.9.0.4?
Flags: blocking1.9.0.4+
Flags: blocking1.8.1.18?
Flags: blocking1.8.1.18+
Whiteboard: [sg:needinfo] → [sg:critical?]
OS: Mac OS X → All
Hardware: PC → All
Whiteboard: [sg:critical?] → [sg:critical?] needs r=dveditz
Comment on attachment 335756 [details] [diff] [review]
Path to limit columns and catch NULL deref


Looks good to me, but we need a network peer to also OK this.

Looks like the original code should have had "if(!*pos) break;" before incrementing num, but it doesn't really hurt to allocate one too many if the format ends with extra spaces. Irrelevant to the security problem.
Attachment #335756 - Flags: superreview+
Attachment #335756 - Flags: review?(dveditz)
Attachment #335756 - Flags: review?(cbiesinger)
Whiteboard: [sg:critical?] needs r=dveditz → [sg:critical?] needs r=cbiesinger
Whiteboard: [sg:critical?] needs r=cbiesinger → [sg:critical] needs r=cbiesinger
Attachment #335756 - Flags: review?(cbiesinger) → review-
Comment on attachment 335756 [details] [diff] [review]
Path to limit columns and catch NULL deref

+    // There are a maximum of six allowed header fields (doubled + terminator, just in case)

please limit your lines to 80 characters

+    if (num > (2*(sizeof(gFieldTable) / sizeof(gFieldTable[0]))))

spaces around the * operator, please. and use NS_ARRAY_LENGTH to get the size of the array


no tabs please
Attachment #344000 - Flags: review?(cbiesinger)
Attachment #335756 - Attachment is obsolete: true
Christian: did you have substantive complaints about the patch? I've addressed your comments, please re-review so we can get this in for the code-freeze.
Attachment #344000 - Flags: review?(cbiesinger) → review+
Whiteboard: [sg:critical] needs r=cbiesinger → [sg:critical]
Attachment #344000 - Flags: approval1.9.0.4?
Attachment #344000 - Flags: approval1.8.1.18?
Whiteboard: [sg:critical] → [sg:critical] needs trunk landing
Comment on attachment 344000 [details] [diff] [review]
updated to comments

Approved for and, a=dveditz for release-drivers
Attachment #344000 - Flags: approval1.9.0.4?
Attachment #344000 - Flags: approval1.9.0.4+
Attachment #344000 - Flags: approval1.8.1.18?
Attachment #344000 - Flags: approval1.8.1.18+
Fix checked into trunk
Closed: 16 years ago
Resolution: --- → FIXED
Whiteboard: [sg:critical] needs trunk landing → [sg:critical]
Fix checked into the 1.8 and 1.9.0 branches
So, reading above, we don't have a repro case because we didn't get a PoC cgi. QA would like to know if there is a way to verify this fix works.
Never mind, I see that the python file acts as a server for the creating the bug conditions.  Script says:

localhost - - [28/Oct/2008 17:18:42] "GET / HTTP/1.1" 200 -
388877 compressed bytes served

when running and a connection is made to it via

Running with 3.0.3, Firefox peaks memory usage at around 490 MB and pegs my CPU at 100% while doing it. After about 15 minutes, it recovers and Firefox is usable again. No crash is evident...

Running with last night's nightly on the same virtual machine (Ubuntu 8.04), I'm seeing the exact same behavior with no difference at all. Comment 4 states that with Firefox 3, I should get a crash when I try this in 3.0.3. 

I'm going to try this on Windows as well but I need to set up an environment to do so.
Interesting. Testing on Windows XP, the same test crashed Firefox 3.0.3. With the nightly with the fix (Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv: Gecko/2008102905 GranParadiso/3.0.4pre), the memory usage rose but it eventually recovered with no crash. Marking verified for
Testing with Firefox on Windows XP, I get up to about 420 MB of RAM usage (and 99% cpu) before it drops back to 28 MB of RAM and no cpu usage, bringing up a page. I cannot get to crash with this.
(In reply to comment #17)
> Testing with Firefox on Windows XP, I get up to about 420 MB of RAM
> usage (and 99% cpu) before it drops back to 28 MB of RAM and no cpu usage,
> bringing up a page. I cannot get to crash with this.

Try editing the python script and changing the value 800000000 on the commented line to something bigger like 1000000000 or 1200000000. Or, if the test system is low on memory, try changing it to something smaller like 500000000. The proof-of-concept exploit used JavaScript to dynamically set up the memory space since that varies between installations.
So, I've tried this a bunch of different times with on XP, even dropping the available RAM in the virtual machine down to 128 MB to constrain things. While I can get 3.0.3 to crash, nothing ever happens with other than it becoming very busy for a while

Has anyone actually seen this crash or earlier?
Comment on attachment 344000 [details] [diff] [review]
updated to comments

a=asac for 1.8.0 branch
Attachment #344000 - Flags: approval1.8.0.15+
Flags: blocking1.8.0.15+
I'm unable to verify this fix in because of the inability to replicate the crash on a 1.8.1.x build.
Summary: Investigate CVE 2008-0017: possible buffer overflow in nsDirIndexParser → Investigate possible buffer overflow in nsDirIndexParser
Group: core-security
the patch is not nice:

206 mFormat = new int[num+1];
207 // Prevent NULL Deref - Bug 443299 
208 if (mFormat == nsnull)

operator |new| throws exception on OOM, it doesn't return 0 as demonstrated by this proggie:

int *mFormat;
mFormat=new int[-2000];
if (mFormat == 0)
	cout << "==" << endl;
return 0;

terminate called after throwing an instance of 'std::bad_alloc'
  what():  std::bad_alloc
georgi: this isn't the case with gecko 1.8 on windows, the toolchain we use is msvc6 where new returns null instead of throwing.

handling oom by null checking is the way all of our code expects to work. until we've changed things, we should continue to ensure we have null checks.
This landed before 1.9.1 branched
Flags: blocking1.9.1? → blocking1.9.1+
Priority: -- → P1
Depends on: CVE-2011-0070
You need to log in before you can comment on or make changes to this bug.