Closed
Bug 1444668
Opened 6 years ago
Closed 6 years ago
Write beyond bounds caused by overlarge offset in WASM assembler
Categories
(Core :: JavaScript Engine: JIT, defect, P1)
Tracking
()
RESOLVED
FIXED
mozilla61
People
(Reporter: mozillabugs, Assigned: jandem)
Details
(Keywords: csectype-bounds, csectype-intoverflow, sec-high, Whiteboard: [adv-main60+][adv-esr52.8+])
Attachments
(3 files, 4 obsolete files)
149.29 KB,
application/x-7z-compressed
|
Details | |
5.74 KB,
patch
|
jandem
:
review+
RyanVM
:
approval-mozilla-beta+
abillings
:
sec-approval+
|
Details | Diff | Splinter Review |
6.68 KB,
patch
|
jandem
:
review+
RyanVM
:
approval-mozilla-esr52+
|
Details | Diff | Splinter Review |
The WASM assembler can generate an out-of-range value for |ModuleGenerator::callFarJumps_.jump.offset_|. If it does, it causes SetInt32() (Patching-x86-shared.h) to attempt to write beyond bounds. When this occurs, SetInt32() is called via the following chain: SetInt32() ^--- SetRel32() (Patching-x86-shared.h) ^--- patchFarJump() (Assembler-x86-shared.h) ^--- ModuleGenerator::finishCode() (WasmGenerator.cpp) Attached is a POC that causes this write. The out-of-range value for |offset_| (0xffffffff820206cb in the attached POC) appears to occur because the POC's WASM program is large enough to caused js::jit::X86Encoding::BaseAssembler::m_formatter.m_buffer to contain more than 0x7fffffff bytes. When ModuleGenerator::finishCode() attempts to link the code, it eventually calls X86Encoding::BaseAssembler::X86InstructionFormatter::immediateRel32(), which contains this code: 5093: m_buffer.putIntUnchecked(0); 5094: return JmpSrc(m_buffer.size()); However, m_buffer.size() returns a |size_t| (64 bits on 64 bit builds) while JmpSrc() takes an |int32_t|. The code here should (but does not) catch this out-of-range value. (Probably some other code should catch it earlier, but I don't understand the assembler well enough to identify that code). In any case, the bad value propagates back to the caller farJumpWithPatch() (Assembler_x86_shared.h), which calls CodeOffset() (Assembler_shared.h), which puts it, along with the relevant function index, into |callFarJumps_.jump.offset_| (seer ModuleGenerator::linkCallSites()). Later, when ModuleGenerator::finishCode() calls masm_.patchFarJump(), pathFarJump() computes code + farJump.offset() which points well before the start of |code| because offset() is negative. The result is an attempt to write beyond bounds. This crashes FF 58.0.1 and 58.0.2 x64 release. On at least the debug version of 58.0.2, however, it successfully overwrites something else on the heap. I found this bug while attempting to force an overflow elsewhere in the WASM assembler. I will report that bug separately. Use the PoC by starting FF 58.0.1/.2 x64, attaching a debugger to it, and setting a BP in ModuleGenerator::finishCode() where it processes |callFarJumps_|. Put the POC files on a webserver. Load test40.html in FF (you might need to attach the debugger again if a new content process appears), and wait for the BP to fire. When it does, examine |callFarJumps_| and notice that at least one entry contains a negative offset. Trace the code until you hit the mov dword ptr [r10-4], edx and watch it either write far beyond bounds or crash with an access violation.
Reporter | ||
Comment 1•6 years ago
|
||
Updated•6 years ago
|
Flags: sec-bounty?
Reporter | ||
Comment 2•6 years ago
|
||
> I found this bug while attempting to force an overflow elsewhere in the WASM > assembler. I will report that bug separately. https://bugzilla.mozilla.org/show_bug.cgi?id=1444673
Reporter | ||
Comment 3•6 years ago
|
||
"large enough to caused" => large enough to cause.
Updated•6 years ago
|
Group: core-security → javascript-core-security
Comment 4•6 years ago
|
||
Benjamin, can you confirm this issue and find the right person to fix it as soon as possible?
Flags: needinfo?(bbouvier)
Priority: -- → P1
Updated•6 years ago
|
Comment 5•6 years ago
|
||
Great catch, thanks! I think we should: - either report masm.oom() when the size > INT32_MAX, which I'm going to try. - or replace JmpSrc/JmpDst by a JmpOffset which has a size_t, and audit usage up to callers. The first solution would be a better stopgap solution, and the second one would be better in the long term.
Assignee: nobody → bbouvier
Updated•6 years ago
|
Status: UNCONFIRMED → ASSIGNED
Ever confirmed: true
Flags: needinfo?(bbouvier)
Assignee | ||
Comment 6•6 years ago
|
||
(In reply to Benjamin Bouvier [:bbouvier] from comment #5) > - either report masm.oom() when the size > INT32_MAX, which I'm going to try. See bug 1444673 comment 8, I think we should use a lower/safer limit.
Comment 7•6 years ago
|
||
Stopgap solution using MaxCodeBytesPerProcess (and fixing another potential overflow in same class).
Attachment #8959594 -
Flags: review?(jdemooij)
Assignee | ||
Comment 8•6 years ago
|
||
Comment on attachment 8959594 [details] [diff] [review] stopgap.patch Review of attachment 8959594 [details] [diff] [review]: ----------------------------------------------------------------- Thanks for fixing this! Some comments below. ::: js/src/jit/x86-shared/AssemblerBuffer-x86-shared.h @@ +87,5 @@ > > void ensureSpace(size_t space) > { > + if (MOZ_UNLIKELY(m_buffer.length() > SIZE_MAX - space || > + !m_buffer.reserve(m_buffer.length() + space))) This cannot overflow because we call this with |space <= 16| everywhere as far as I can see. So on 32-bit we'd have to allocate almost 4 GB (our whole address space) and that's not possible because we also have the binary and a ton of other stuff in there. So I think we should MOZ_ASSERT space <= 16 or so, with a comment saying that we only allow small values to avoid overflow risk here. Usually I don't mind adding overflow checks, but this is called for every instruction we emit so I want to be a bit more careful. @@ +124,5 @@ > } > > bool oom() const > { > + return m_oom || size() > MaxCodeBytesPerProcess; I was hoping we could check MaxCodeBytesPerProcess when we actually resize the buffer, but doing that in ensureSpace is unfortunate because we call that for every instruction. What we could do is add our own alloc policy where we check this and then use that instead of SystemAllocPolicy for our AssemblerBuffer. Are you interested in doing this? I can steal this if you want.
Attachment #8959594 -
Flags: review?(jdemooij)
Reporter | ||
Comment 9•6 years ago
|
||
> This cannot overflow because we call this with |space <= 16| everywhere as far as I can see. So on 32-bit we'd have to allocate
> almost 4 GB (our whole address space) and that's not possible because we also have the binary and a ton of other stuff in
> there.
But allocating >= 4GB is no problem on x64 builds. What am I missing?
Assignee | ||
Comment 10•6 years ago
|
||
(In reply to mozillabugs from comment #9) > But allocating >= 4GB is no problem on x64 builds. What am I missing? On 64-bit platforms, size_t is 8 bytes so it's fine to allocate 4 GB (or even 4 TB) and the same thing applies.
Reporter | ||
Comment 11•6 years ago
|
||
(In reply to Jan de Mooij [:jandem] from comment #10) > (In reply to mozillabugs from comment #9) > > But allocating >= 4GB is no problem on x64 builds. What am I missing? > > On 64-bit platforms, size_t is 8 bytes so it's fine to allocate 4 GB (or > even 4 TB) and the same thing applies. Ah, I see: void ensureSpace(size_t space) Thanks.
Comment 12•6 years ago
|
||
(In reply to Jan de Mooij [:jandem] from comment #8) > What we could do is add our own alloc policy where we check this and then > use that instead of SystemAllocPolicy for our AssemblerBuffer. Are you > interested in doing this? I can steal this if you want. Yeah, feel free to steal; I'd be happy to review it though.
Assignee | ||
Updated•6 years ago
|
Flags: needinfo?(jdemooij)
Assignee | ||
Comment 13•6 years ago
|
||
Also r? luke because this is related to bug 1444673.
Assignee: bbouvier → jdemooij
Flags: needinfo?(jdemooij)
Attachment #8960506 -
Flags: review?(luke)
Attachment #8960506 -
Flags: review?(bbouvier)
Assignee | ||
Comment 14•6 years ago
|
||
Comment on attachment 8960506 [details] [diff] [review] Patch Review of attachment 8960506 [details] [diff] [review]: ----------------------------------------------------------------- Actually bytesNeeded adds extra bytes for the relocation tables so the release asserts in this patch are not entirely correct.
Attachment #8960506 -
Flags: review?(luke)
Attachment #8960506 -
Flags: review?(bbouvier)
Assignee | ||
Comment 15•6 years ago
|
||
Attachment #8959594 -
Attachment is obsolete: true
Attachment #8960506 -
Attachment is obsolete: true
Attachment #8960603 -
Flags: review?(luke)
Attachment #8960603 -
Flags: review?(bbouvier)
![]() |
||
Comment 16•6 years ago
|
||
Comment on attachment 8960603 [details] [diff] [review] Patch Review of attachment 8960603 [details] [diff] [review]: ----------------------------------------------------------------- Awesome, thanks!
Attachment #8960603 -
Flags: review?(luke) → review+
Assignee | ||
Comment 18•6 years ago
|
||
Updated patch. We now also check bytesNeeded() in MacroAssembler::finish and OOM if that exceeds MaxCodeBytesPerProcess. I also moved the size() release assert to that function.
Attachment #8960603 -
Attachment is obsolete: true
Attachment #8960603 -
Flags: review?(bbouvier)
Attachment #8960648 -
Flags: review?(bbouvier)
Comment 19•6 years ago
|
||
Comment on attachment 8960648 [details] [diff] [review] Patch Review of attachment 8960648 [details] [diff] [review]: ----------------------------------------------------------------- LGTM, thanks. ::: js/src/jit/shared/IonAssemblerBuffer.h @@ +194,5 @@ > } > > private: > Slice* newSlice(LifoAlloc& a) { > + if (size() + sizeof(Slice) > MaxCodeBytesPerProcess) { if size() grows very quick, could it overflow? Just to be sure, could we check `if (size() > MaxCodeBytesPerProcess - sizeof(Slice))`?
Attachment #8960648 -
Flags: review?(bbouvier) → review+
Assignee | ||
Comment 20•6 years ago
|
||
(In reply to Benjamin Bouvier [:bbouvier] from comment #19) > if size() grows very quick, could it overflow? Just to be sure, could we > check `if (size() > MaxCodeBytesPerProcess - sizeof(Slice))`? The patch is making sure this won't overflow :) But sure I can change it anyway.
Assignee | ||
Comment 21•6 years ago
|
||
[Security approval request comment] > How easily could an exploit be constructed based on the patch? Not trivial but might not be too hard, as the PoC here shows. > Do comments in the patch, the check-in comment, or tests included in the patch paint a bulls-eye on the security problem? No, it's pretty obvious from the code. > Which older supported branches are affected by this flaw? All. > Do you have backports for the affected branches? If not, how different, hard to create, and risky will they be? Should be easy to backport. > How likely is this patch to cause regressions; how much testing does it need? Unlikely.
Attachment #8960648 -
Attachment is obsolete: true
Attachment #8961306 -
Flags: sec-approval?
Attachment #8961306 -
Flags: review+
Assignee | ||
Updated•6 years ago
|
status-firefox59:
--- → affected
status-firefox60:
--- → affected
status-firefox61:
--- → affected
status-firefox-esr52:
--- → affected
tracking-firefox60:
--- → ?
tracking-firefox61:
--- → ?
tracking-firefox-esr52:
--- → ?
Updated•6 years ago
|
Comment 22•6 years ago
|
||
I'm worried about exposure here. I'm like to wait until April 10 to check this in so it isn't exposed on trunk for too long. I'd like Beta and ESR52 patches prepared ahead of time and nominated to go in after this lands on trunk.
Whiteboard: [checkin on 4/10]
Updated•6 years ago
|
Attachment #8961306 -
Flags: sec-approval? → sec-approval+
Updated•6 years ago
|
Flags: sec-bounty? → sec-bounty+
Assignee | ||
Comment 23•6 years ago
|
||
I'll send this to Try, land it today, and post branch patches.
Assignee | ||
Comment 24•6 years ago
|
||
https://hg.mozilla.org/integration/mozilla-inbound/rev/d4aad468062493810bdc58e6a18f7322d59d4066
Assignee | ||
Updated•6 years ago
|
Whiteboard: [checkin on 4/10]
Assignee | ||
Comment 25•6 years ago
|
||
Comment on attachment 8961306 [details] [diff] [review] Patch This applies to beta, will post a separate patch for ESR52. Approval Request Comment [Feature/Bug causing the regression]: Older bug. [User impact if declined]: Security issues. [Is this code covered by automated tests?]: Yes. [Has the fix been verified in Nightly?]: Not yet. [Needs manual test from QE? If yes, steps to reproduce]: No. [List of other uplifts needed for the feature/fix]: None. [Is the change risky?]: Low risk. [Why is the change risky/not risky?]: Should only affect very unlikely cases that involve very large buffers. This code is exercised by tons of tests. [String changes made/needed]: None.
Attachment #8961306 -
Flags: approval-mozilla-beta?
Assignee | ||
Comment 26•6 years ago
|
||
[Approval Request Comment] User impact if declined: Security issues. Fix Landed on Version: 61, backporting to 60. Risk to taking this patch (and alternatives if risky): Very low. String or UUID changes made by this patch: None.
Attachment #8966576 -
Flags: review+
Attachment #8966576 -
Flags: approval-mozilla-esr52?
![]() |
||
Comment 27•6 years ago
|
||
https://hg.mozilla.org/mozilla-central/rev/d4aad4680624
Status: ASSIGNED → RESOLVED
Closed: 6 years ago
Resolution: --- → FIXED
Target Milestone: --- → mozilla61
Comment 28•6 years ago
|
||
Comment on attachment 8961306 [details] [diff] [review] Patch Fix a JS sec-high. Approved for 60.0b12 and ESR 52.8.0.
Attachment #8961306 -
Flags: approval-mozilla-beta? → approval-mozilla-beta+
Updated•6 years ago
|
Attachment #8966576 -
Flags: approval-mozilla-esr52? → approval-mozilla-esr52+
Comment 29•6 years ago
|
||
uplift |
https://hg.mozilla.org/releases/mozilla-beta/rev/29b2342e2a32 https://hg.mozilla.org/releases/mozilla-esr52/rev/666fc84ec72d
Updated•6 years ago
|
Group: javascript-core-security → core-security-release
Updated•6 years ago
|
Whiteboard: [adv-main60+][adv-esr52.8+]
Comment 30•6 years ago
|
||
(In reply to Jan de Mooij [:jandem] from comment #25) > [Is this code covered by automated tests?]: Yes. > [Needs manual test from QE? If yes, steps to reproduce]: No. > [List of other uplifts needed for the feature/fix]: None. > [Is the change risky?]: Low risk. Does not require manual testing, per Jan.
Flags: qe-verify-
Updated•5 years ago
|
Group: core-security-release
You need to log in
before you can comment on or make changes to this bug.
Description
•