Last Comment Bug 1299686 - (CVE-2016-9066) Integer overflow leading to a buffer overflow in nsScriptLoadHandler
: Integer overflow leading to a buffer overflow in nsScriptLoadHandler
: csectype-intoverflow, regression, sec-high
Product: Core
Classification: Components
Component: DOM (show other bugs)
: 45 Branch
: Unspecified Unspecified
-- normal (vote)
: mozilla52
Assigned To: Andrea Marchesini [:baku]
: Andrew Overholt [:overholt]
Depends on:
  Show dependency treegraph
Reported: 2016-08-31 17:12 PDT by Samuel Groß
Modified: 2017-02-09 08:03 PST (History)
14 users (show)
dveditz: sec‑bounty+
ryanvm: in‑testsuite?
See Also:
Crash Signature:
QA Whiteboard:
Iteration: ---
Points: ---
Has Regression Range: ---
Has STR: ---

Attachments (3.17 KB, application/zip)
2016-08-31 17:12 PDT, Samuel Groß
no flags Details
Another Proof-of-Concept (4.49 KB, application/zip)
2016-09-01 15:15 PDT, Samuel Groß
no flags Details
crash.patch (1.38 KB, patch)
2016-09-01 15:17 PDT, Andrea Marchesini [:baku]
bugs: review+
rkothari: approval‑mozilla‑aurora+
rkothari: approval‑mozilla‑beta+
rkothari: approval‑mozilla‑esr45+
abillings: sec‑approval+
Details | Diff | Splinter Review
PoC Exploit (9.50 KB, application/zip)
2016-09-17 13:48 PDT, Samuel Groß
no flags Details

Description User image Samuel Groß 2016-08-31 17:12:48 PDT
Created attachment 8787005 [details]

While loading external script data, the following code is executed in nsScriptLoadHandler (nsScriptLoader.cpp):

nsScriptLoadHandler::TryDecodeRawData(const uint8_t* aData,
                                      uint32_t aDataLength,
                                      bool aEndOfStream)
  int32_t srcLen = aDataLength;
  const char* src = reinterpret_cast<const char *>(aData);
  int32_t dstLen;
  nsresult rv =
    mDecoder->GetMaxLength(src, srcLen, &dstLen);


  uint32_t haveRead = mBuffer.length();
  uint32_t capacity = haveRead + dstLen;        // [[ 1 ]]
  if (!mBuffer.reserve(capacity)) {

  rv = mDecoder->Convert(src,
                      mBuffer.begin() + haveRead,


  haveRead += dstLen;
  MOZ_ASSERT(haveRead <= capacity, "mDecoder produced more data than expected");

  return NS_OK;

The calculation of the new capacity at [[ 1 ]] can overflow if enough data is sent by the server (>4GB). In that case, the following call to mBuffer.reserve() won't change the size of mBuffer. Afterwards, mDecoder->Convert() will write data past the end of the 8GB buffer (mBuffer stores char16_t) [1]. (requires python 3.5) in the attached archive will trigger this condition and cause an access violation while writing data into an unmapped region following the buffer (tested on Firefox 48.0.1 and Aurora):

    Process 97720 stopped
    * thread #1: tid = 0x19afd5, 0x0000000101c66c68 XUL`nsOneByteDecoderSupport::Convert(char const*, int*, char16_t*, int*) [inlined] nsUnicodeDecodeHelper::ConvertByFastTable(aSrcLength=<unavailable>, aDestLength=<unavailable>) + 9 at nsUnicodeDecodeHelper.cpp:206, queue = '', stop reason = EXC_BAD_ACCESS (code=1, address=0x577000000)
    frame #0: 0x0000000101c66c68 XUL`nsOneByteDecoderSupport::Convert(char const*, int*, char16_t*, int*) [inlined] nsUnicodeDecodeHelper::ConvertByFastTable(aSrcLength=<unavailable>, aDestLength=<unavailable>) + 9 at nsUnicodeDecodeHelper.cpp:206
       203    }
       205    for (; src<srcEnd;) {
    -> 206      *dest = aFastTable[*src];
       207      if (*dest == 0xfffd && aErrorSignal) {
       208        res = NS_ERROR_ILLEGAL_INPUT;
       209        break;
    (lldb) bt
    frame #0: 0x0000000101c66c68 XUL`nsOneByteDecoderSupport::Convert(char const*, int*, char16_t*, int*) [inlined] nsUnicodeDecodeHelper::ConvertByFastTable(aSrcLength=<unavailable>, aDestLength=<unavailable>) + 9 at nsUnicodeDecodeHelper.cpp:206
    frame #1: 0x0000000101c66c5f XUL`nsOneByteDecoderSupport::Convert(this=0x0000000112a0e800, aSrc="", aSrcLength=0x00007fff5fbfcc6c, aDest=u"", aDestLength=0x00007fff5fbfcc68) + 207 at nsUCSupport.cpp:273
    frame #2: 0x0000000102a5c3f2 XUL`nsScriptLoadHandler::TryDecodeRawData(this=0x00000001290eb740, aData="", aDataLength=<unavailable>, aEndOfStream=<unavailable>) + 146 at nsScriptLoader.cpp:2752
    frame #3: 0x0000000102a5be73 XUL`nsScriptLoadHandler::OnIncrementalData(this=0x00000001290eb740, aLoader=<unavailable>, aContext=<unavailable>, aDataLength=101, aData="", aConsumedLength=0x00007fff5fbfccf4) + 83 at nsScriptLoader.cpp:2720

Some notes regarding exploitability:

    - It is possible to use compression to reduce the network load. The attached PoC code does that and thus only needs to send about 18MB of data.

    - It should be possible to place some objects behind the buffer, as mmap will usually allocate adjacent memory regions. This could be done as follows:
        - The server sends the first 0xffffffff bytes of the payload, causing an allocation of an 8GB memory region
        - JavaScript code executes and allocates lots of target objects. At some point new memory is requested via mmap, which should be placed behind the 8GB buffer
        - The server sends the remaining data, overflowing into the previously allocated objects

    - Due to page deduplication and/or compression beeing performed by the kernel, it should be possible to trigger the bug while having less than 8GB of available RAM. At least on my Mac this seems to be the case as memory usage stays relatively low even during the allocations.

    - A 64-bit build of Firefox is required so the bug can be triggered.

    - The data that is being written is not 100% controlled as it must be valid UTF-16

[1] There is a special case when the capacity wraps around to zero. In that case the process will (likely) not crash as the overflow only happens inside the 8GB chunk.
Comment 1 User image :Gijs (away until Feb 27) 2016-09-01 03:38:59 PDT
I haven't tried to repro, but from code inspection this seems like a valid issue. Moving to a better component. mrbkap / baku, can either of you take a look? (CC'ing people because of our security groups...)
Comment 2 User image Samuel Groß 2016-09-01 15:15:01 PDT
Created attachment 8787399 [details]
Another Proof-of-Concept

I've created a second PoC that allocates a Uint8Array behind mBuffer, which is then being overflown into. I've tested this on OS X 10.11. It's still a bit hackish since I haven't fully figured out what jemalloc and the OS X kernel are doing during the realloc calls.

If there are no mitigations in place which add guard pages in front of important memory regions (or something similar) it should be possible (at least on OS X) to place something more "interesting" behind mBuffer, which is then overwritten.

Things look a bit different on Linux though. For one, the mmap region on Linux grows towards lower addresses, while it grows towards higher addresses on OS X. I haven't checked Windows. It should still be possible to allocate something in the memory region following the buffer, but the new PoC will likely not work on Linux out of the box.
Comment 3 User image Andrea Marchesini [:baku] 2016-09-01 15:17:46 PDT
Created attachment 8787402 [details] [diff] [review]
Comment 4 User image Andrea Marchesini [:baku] 2016-09-01 16:23:19 PDT
Comment on attachment 8787402 [details] [diff] [review]

[Security approval request comment]
How easily could an exploit be constructed based on the patch?

See comment 1.

Do comments in the patch, the check-in comment, or tests included in the patch paint a bulls-eye on the security problem?


Which older supported branches are affected by this flaw?

All. 2015. Bug 1218029

Do you have backports for the affected branches? If not, how different, hard to create, and risky will they be?

Easy to write.

How likely is this patch to cause regressions; how much testing does it need?

Comment 5 User image Al Billings [:abillings] 2016-09-02 14:54:04 PDT
Too late for 49. Sec-approval to land on September 20 (two weeks into next cycle).
Comment 6 User image Andrea Marchesini [:baku] 2016-09-02 17:02:53 PDT
checkin-needed, but see comment 5!
Comment 7 User image Samuel Groß 2016-09-17 13:48:19 PDT
Created attachment 8792243 [details]
PoC Exploit

I've created another PoC. This one spawns (most of the time) on the latest Firefox (48.0.1) for OS X (10.11.6).

The large comment in code.js gives a decent overview over how the exploit works. The very short version is that it forces an allocation of an Arena (filled with ArrayBuffers) behind the buffer that is overflown. It then frees (roughly) one ArrayBuffer in every Arena and triggers GC. Finally it overflows into the |firstFreeSpan| field of the Arena and makes it look like the free slot is inside the inline data of one of the ArrayBuffer objects. When more ArrayBuffer objects are then allocated, one of them will be placed inside the data of another ArrayBuffer, making it accessible from JavaScript. From there, crafting an arbitrary read/write primitive becomes fairly easy.

The PoC currently is version dependent (due to the offsets in code.js) and only works on OS X since it assumes that the mmap region grows upwards.

For a generic mitigation, either randomizing mmap regions or inserting guard pages before/after mmap chunks should work, although I think this type of bug is best mitigated by the kernel...
Comment 8 User image Ryan VanderMeulen [:RyanVM] 2016-09-22 14:04:42 PDT
Comment 9 User image Carsten Book [:Tomcat] 2016-09-23 03:32:20 PDT
Comment 10 User image Ritu Kothari (:ritu) 2016-09-28 16:51:29 PDT
Hi Baku, should we uplift this fix to Beta50, Aurora51 and ESR45.5?
Comment 11 User image Andrea Marchesini [:baku] 2016-09-28 23:09:39 PDT
Comment on attachment 8787402 [details] [diff] [review]

[Approval Request Comment]
If this is not a sec:{high,crit} bug, please state case for ESR consideration:
User impact if declined: 
Fix Landed on Version: 51... ?
Risk to taking this patch (and alternatives if risky): zero. patch is very easy.
String or UUID changes made by this patch: none

See for more info.

Approval Request Comment
[Feature/regressing bug #]: Script Loader 
[User impact if declined]: a crash could happen if the capacity variable overflows.
[Describe test coverage new/current, TreeHerder]: green, but we don't have a test for this: it's not trivial to write it.
[Risks and why]: zero.
[String/UUID change made/needed]: none.
Comment 12 User image Ritu Kothari (:ritu) 2016-09-29 09:27:09 PDT
Comment on attachment 8787402 [details] [diff] [review]

Sec-high, Aurora51+, Beta50+, ESR45+
Comment 15 User image Ciprian Georgiu, QA [:ciprian_georgiu] 2016-11-09 00:51:57 PST
I didn't managed to reproduce this issue on Mac OS X 10.11.6. I've used both and PoC Exploit and tried to run index.html by making a http server with python 3.5. This was done on firefox 48.0.1 in 64 bit as Samuel said in comment 0 in order to trigger the bug. If I understand right this needs to crash (this is what I see: or maybe I am doing something wrong here.

Andrea, can you please provide me some additional info about how to reproduce this bug? What I'm missing?
Comment 16 User image Samuel Groß 2016-11-09 02:25:41 PST
You'll have to use the script (a custom webserver) from the zip archive since some server side logic is required for the code to work. Are you doing that?
Comment 17 User image Ciprian Georgiu, QA [:ciprian_georgiu] 2016-11-09 08:53:23 PST
Thank you Samuel, now I'm able to repoduce the crash on 49.0.2 with your info.

This issue is verified fixed on 45.5.0 (20161031153904), 50.0-build2 (20161104212021), 51.0a2 (20161108004019) and 52.0a1 (20161109030210) running Mac OS X 10.11.6. 

I will mark here accordengelly.

Note You need to log in before you can comment on or make changes to this bug.