Bug 675747 (CVE-2011-3005)

ogg crash [@ nsOggReader::ReadHeaders]




6 years ago
2 years ago


(Reporter: sczimmer, Assigned: cpearce)


(5 keywords)

5 Branch
crash, regression, testcase, verified-aurora, verified-beta
Bug Flags:
sec-bounty +

Firefox Tracking Flags

(firefox5- wontfix, firefox6- wontfix, firefox7+ fixed, firefox8+ fixed, status1.9.2 unaffected, status1.9.1 unaffected)


(Whiteboard: [sg:critical?][qa!], crash signature)


(4 attachments, 1 obsolete attachment)



6 years ago
Created attachment 549931 [details]

User Agent: Mozilla/5.0 (X11; U; Linux x86_64; c) AppleWebKit/531.2+ (KHTML, like Gecko) Safari/531.2+ Epiphany/2.30.6

Steps to reproduce:

ran firefox on an .ogg file

it seems like firefox crashes on any ogg file where the beginning of stream flag is set in the second page of a stream, i attached one example

Actual results:

firefox seg faults

Expected results:

firefox should just not play the video like it normally does for corrupted videos rather than seg faulting
Component: General → Video/Audio
Product: Firefox → Core
QA Contact: general → video.audio


6 years ago
Crash Signature: [@ nsOggReader::ReadHeaders]
Ever confirmed: true
Keywords: crash, testcase
Summary: ogg crash → ogg crash [@ nsOggReader::ReadHeaders]
Whiteboard: [sg:critical?]
Created attachment 549941 [details]

Stack from a recent debug build on Linux64.


6 years ago
Severity: normal → critical
status-firefox5: --- → affected
status-firefox6: --- → affected
status-firefox7: --- → affected
status-firefox8: --- → affected


6 years ago
Assignee: nobody → chris
OS: Linux → All
Hardware: x86_64 → All

Comment 2

6 years ago
Created attachment 549965 [details] [diff] [review]
Patch v1

The problem is that we're creating a new nsOggCodecState for every bos page we encounter in the stream, and storing a mapping from stream serialno to the nsOggCodecState in the mCodecStates hash table. There are two bos pages for the Vorbis stream. When we create the second nsOggCodecState and add it to mCodecState, we overwrite the original seiral-to-nsOggCodecState mapping in mCoedcStates. mCodecStates holds the owning reference, so the memory pointed to be mVorbisState is then cleared, and when we try to use mVorbisState at nsOggReader.cpp:288 we crash.

So I propose we simply don't create a new nsOggCodecState for serialnos we've seen before in bos pages. The media plays correctly then.
Attachment #549965 - Flags: review?(chris.double)

Comment 3

6 years ago
Comment on attachment 549965 [details] [diff] [review]
Patch v1

Review of attachment 549965 [details] [diff] [review]:

Can you add a test? r- for the main thread/decode thread issue.

::: content/media/ogg/nsOggReader.cpp
@@ +195,5 @@
>      nsOggCodecState* codecState = 0;
> +    if (!ogg_page_bos(&page)) {
> +      // We've encountered the a non Beginning Of Stream page. No more
> +      // BOS pages can follow in this Ogg segment, so there will be no other

The first sentence in the comment seems grammatically incorrect. Should the 'the' be there?

@@ +198,5 @@
> +      // We've encountered the a non Beginning Of Stream page. No more
> +      // BOS pages can follow in this Ogg segment, so there will be no other
> +      // bitstreams in the Ogg (unless it's invalid).
> +      readAllBOS = PR_TRUE;
> +    } else if (!IsKnownStream(ogg_page_serialno(&page))) {

The comment for IsKnownStream says this can be called on the main thread only, but this call is inside ReadMetadata which is called on the decode thread.
Attachment #549965 - Flags: review?(chris.double) → review-

Comment 4

6 years ago
Created attachment 550507 [details] [diff] [review]
Patch v2

Add test and use the decode-thread hash table instead of IsKnownStream.
Attachment #549965 - Attachment is obsolete: true
Attachment #550507 - Flags: review?(chris.double)


6 years ago
Attachment #550507 - Flags: review?(chris.double) → review+
Chris, I think we're fairly well done with 6 by now, so I won't be tracking this for 6, but if you feel it's critical that we get this into 6 then please nominate the patch for beta approval.
status-firefox5: affected → wontfix
status-firefox6: affected → wontfix
tracking-firefox5: --- → -
tracking-firefox6: --- → -
tracking-firefox7: --- → +
tracking-firefox8: --- → +

Comment 6

6 years ago
Ok, cool. This isn't exploitable, so it's not a big deal if we don't get this onto beta at this stage. I'll request aurora approval once it's stuck on central.

Whiteboard: [sg:critical?] → [sg:critical?][inbound]

Comment 7

6 years ago
I believe this is exploitable, in most files that trigger the crash the free'd memory is not given to anything, but in the example.ogg I attached, for some reason the free'd mVorbisState buffer is then always allocated in the theora decoder at huffdec.c:399 "tree=(ogg_int16_t *)_ogg_malloc(size*sizeof(*tree));"

in particular what is stored in the important part of the buffer is "leaf=(ogg_int16_t)-(_tokens[ti][1]-depth[l]<<8|_tokens[ti][0]);" in example.ogg leaf is 0xfbfc and when I run the normal linux 32 bit binary from the mozilla download page it crashes with instruction pointer = this address:

Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0xe56ffb70 (LWP 5514)]
0xfbfcfbfc in ?? ()

I believe _tokens comes from the video file so we should have some control over the value. I don't think its even necessary to have complete control over the value, just make it something a bit less than 0xfbfcfbfc and you can do a standard javascript heap spray

Comment 8

6 years ago
(In reply to comment #7)
> I believe this is exploitable, in most files that trigger the crash the
> free'd memory is not given to anything, but in the example.ogg I attached,
> for some reason the free'd mVorbisState buffer is then always allocated in
> the theora decoder at huffdec.c:399 "tree=(ogg_int16_t
> *)_ogg_malloc(size*sizeof(*tree));"

Just so I understand this, are you saying that exploit happens if we don't crash in ReaderHeaders()? The memory pointed to by mVorbisState gets reallocated in the Theora decoder setup, and then we overwrite the Theora decode info when we init mVorbisState and then the exploit happens when we use the Theora decode info later to to decode some Theora data pages subsequently?

That sounds... plausible to me...

CCing Tim Terriberry, libtheora maintainer, in case he has any insights or can convince me otherwise...
(In reply to comment #8)
> CCing Tim Terriberry, libtheora maintainer, in case he has any insights or
> can convince me otherwise...

The attacker control of that value is somewhat limited: the smallest non-zero value you can get is 0xf8e1f8e1. Bytes 0 and 2 will always be either between 0xe1 and 0xff, or 0x00 (_tokens[ti][0] will be between 0 and 31, inclusive), and bytes 1 and 3 will always be between 0xf8 and 0xff, or 0x00 (nbits will always be between 1 and 7, inclusive, and _tokens[ti][1]-depth[l] is <= nbits). The only way to get _tokens[ti][1]==depth[l] (i.e., to get 0x00 in one of the odd bytes) is with a singleton tree, in which case you will write exactly 32 bits into the buffer, both odd bytes will be 0x00, and both even bytes will also have to be 0x00 (or carry propagation would make the odd bytes 0xff). So the only possible value less than 0xf8e1f8e1 is 0x00000000.

But I don't know enough to say that that would make this unexploitable.

Comment 10

6 years ago
To clarify,
1. mVorbisState is free'd
2. theora mallocs some memory and recieves the same location mVorbisState was
3. theora modifies the memory, which you have some control over as tim describes above. If you can only make the value > 0xf8e1f8e1 it should still be fine, just a bit less convenient as you will need to place shellcode in the stack instead of the heap
4. mVorbisState->Init() is called. I do not know c++ internals, but apparently that equals the assembly instruction call [rax+0x30] where rax comes from the mVorbisState buffer and is the previously mentioned value from theora we have some control over

This is in 64bit linux firefox 5, the "recent linux64 debug build" above looks like it crashes in a slightly different place

So 64 bit linux crashes due to a read violation on the instruction call [0xfbfcfbfcfbfcfbfc + 0x30], but on 32 bit linux (I am not sure why) it actually crashes with the instruction pointer at address $eip = 0xfbfcfbfc

Of course the only real way to show it's exploitable is with an actual exploit. I'm busy this weekend but will try to write one next week.

Comment 11

6 years ago
Fixed on central.
Last Resolved: 6 years ago
Resolution: --- → FIXED
Whiteboard: [sg:critical?][inbound] → [sg:critical?]
Target Milestone: --- → mozilla8

Comment 12

6 years ago
Comment on attachment 550507 [details] [diff] [review]
Patch v2

Requesting aurora approval. Still not sure if this is exploitable, so haven't requested beta approval.
Attachment #550507 - Flags: approval-mozilla-aurora?
Comment on attachment 550507 [details] [diff] [review]
Patch v2

Land this on aurora this week please, before the migration
Attachment #550507 - Flags: approval-mozilla-aurora? → approval-mozilla-aurora+

Comment 15

6 years ago
status-firefox7: affected → fixed
status-firefox8: affected → fixed
Attachment #549931 - Attachment mime type: application/octet-stream → video/ogg
fwiw a slightly different (similar) crash with Fx6 Beta:
Crash Signature: [@ nsOggReader::ReadHeaders] → [@ nsOggReader::ReadHeaders] [@ nsDeque::PopFront ]
This is a regression from something, Firefox 3.6.x is not affected by the testcase and the code being patched doesn't exist AFAICT.
status1.9.1: --- → unaffected
status1.9.2: --- → unaffected
Keywords: regression

Comment 18

6 years ago
I finally wrote an exploit for this bug. Download the attached exploit.html and exploit.ogg in the same folder and visit exploit.html in firefox. I have only coded the exploit for 32 bit linux, and while it might work with random distro's firefox binary I have only tested it with the official mozilla 32 bit linux download firefox-5.0.1. Currently the exploit just prints out "hi!" but of course you can replace the code for printing "hi!" with something more malicious. It works about 90% of the time for me with address space layout randomization off and a bit under 50% of the time with address space layout randomization on though I'm sure that could be improved by someone better at exploit writing than me.

Comment 19

6 years ago
Created attachment 552888 [details]
exploit for 32 bit linux firefox-5.0.1

Comment 20

6 years ago
Created attachment 552889 [details]
ogg file for exploit.html

Comment 21

6 years ago
Comment on attachment 550507 [details] [diff] [review]
Patch v2

Someone more qualified than I needs to make a call on whether this exploit warrants respinning the beta/release-candidate builds, so nominating for beta approval.

* Exploit has only been demonstrated on 32bit Linux, and is less than 100% reproducibility, though higher may be possible.
* Exploit relies on the system malloc returning the memory allocated to a Vorbis decoder (which was incorrectly freed, but to which we still hold a pointer) when allocating a Theora decoder. The Vorbis decoder is initialized in that memory (using setup data read from an ogg file), which overwrites the Theora decoder's memory. When the Theora decoder decodes data, because its setup data is corrupted, the exploit happens.
* Not sure if this can happen on non-Linux platforms, it relies on system malloc behaviour (not jemalloc behaviour).
* Patch is landed in aurora, so if we don't take this on beta, the fix will reach users in 6 more weeks.
Attachment #550507 - Flags: approval-mozilla-beta?

Comment 22

6 years ago
Just to clarify a few things,
While what I said above about using the theora stuff might be possible but this exploit works a much simpler way.
The exploitable bug:
1. A buffer used by ogg decoder is freed.
2. Stuff happens.
3. the instruction call [$eax + 0x18] is executed where $eax is the first word from the freed buffer

Normally the freed memory is garbage so it simply crashes. But, in the exploit.html as the video is started I have looping in the background javascript that rapidly allocates strings of the same size as the buffer that is freed. Almost of all of the time, one of these strings receives the same location in memory as the buffer. We can put whatever we want in the string, so we can control the value of $eax at the instruction call [$eax+0x18], which is straightforward to exploit. I have tested this on windows xp 32 bit and it crashes with attacker control of $eax so it is certainly exploitable on windows as well, I just have a little experience writing exploits for linux and no experience with windows so I wrote it for linux.
Comment on attachment 550507 [details] [diff] [review]
Patch v2

We did not respin Firefox 6 for this and the fix is now already in the current "Beta" (Firefox 7) releases.
Attachment #550507 - Flags: approval-mozilla-beta?
qa+ for verification in Firefox 7 and 8
Whiteboard: [sg:critical?] → [sg:critical?][qa+]
Mozilla/5.0 (Windows NT 6.1; WOW64; rv:7.0) Gecko/20100101 Firefox/7.0 ID:20110922153450
Mozilla/5.0 (Windows NT 6.1; WOW64; rv:8.0a2) Gecko/20110926 Firefox/8.0a2 ID:20110926042011
Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:7.0) Gecko/20100101 Firefox/7.0 ID:20110922153450
Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:8.0a2) Gecko/20110926 Firefox/8.0a2 ID:20110926042011
Mozilla/5.0 (X11; Linux x86_64; rv:7.0) Gecko/20100101 Firefox/7.0 ID:20110922153450
Mozilla/5.0 (X11; Linux x86_64; rv:8.0a2) Gecko/20110926 Firefox/8.0a2 ID:20110926042011

Verified fixed using the attached test video.
Keywords: verified-aurora, verified-beta
Whiteboard: [sg:critical?][qa+] → [sg:critical?][qa!]
Alias: CVE-2011-3005
Group: core-security
Flags: sec-bounty+
You need to log in before you can comment on or make changes to this bug.