Closed Bug 937697 Opened 11 years ago Closed 11 years ago

Dangerous crash on Heap, possibly near [@ js::jit::CharCodeAt]

Categories

(Core :: JavaScript Engine: JIT, defect)

x86_64
Linux
defect
Not set
critical

Tracking

()

VERIFIED FIXED
mozilla29
blocking-b2g koi+
Tracking Status
firefox25 --- wontfix
firefox26 --- wontfix
firefox27 + fixed
firefox28 + fixed
firefox29 + fixed
firefox-esr17 --- unaffected
firefox-esr24 27+ fixed
b2g18 --- unaffected
b2g-v1.1hd --- unaffected
b2g-v1.2 --- fixed
b2g-v1.3 --- fixed
b2g-v1.3T --- fixed
b2g-v1.4 --- fixed

People

(Reporter: decoder, Assigned: jandem)

References

Details

(4 keywords, Whiteboard: [jsbugmon:update][adv-main27+][adv-esr24.3+])

Crash Data

Attachments

(1 file)

The following testcase crashes on mozilla-central revision 581d180a37f3 (run with --fuzzing-safe):


while (true) {
  foo("0");
}
function foo(s) {
  s.charAt(0);
  Number(s.charAt(-2147483648));
}
Crash trace:

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7dfc0ac in ?? ()
#0  0x00007ffff7dfc0ac in ?? ()
#1  0xfff9000000000000 in ?? ()
#2  0x00007ffff7e150a7 in ?? ()
#3  0x0000000000000000 in ?? ()
rbx     0xf6a00630      140737331070512
rdx     0x80000000      2147483648
=> 0x7ffff7dfc0ac:      movzwl (%rbx,%rdx,2),%ebx
   0x7ffff7dfc0b0:      cmp    $0x100,%ebx


Assuming at least sec-high, possibly a bounds issue.
Whiteboard: [jsbugmon:update,bisect]
Whiteboard: [jsbugmon:update,bisect] → [jsbugmon:update]
JSBugMon: Bisection requested, failed due to error (try manually).
(after fixing some harness failures)

autoBisect shows this is probably related to the following changeset:

The first bad revision is:
changeset:   http://hg.mozilla.org/mozilla-central/rev/4473d84c6ab6
user:        Boris Zbarsky
date:        Thu Sep 12 11:08:21 2013 -0500
summary:     Bug 899296 - Restore bytecode-level constant folding in lazily compiled functions. r=jorendorff.

Boris, is bug 899296 a likely regressor?
Blocks: 899296
Flags: needinfo?(bzbarsky)
It's possible, at least, yes....  That said, I don't know enough about this stuff to be able to debug this usefully on a short timeframe.  Jason, do you have time to deal with this one?
Flags: needinfo?(bzbarsky) → needinfo?(jorendorff)
The changeset reveals a bug in Ion.

TryEliminateBoundsCheck ends up combining these two bounds checks:

(gdb) p dominating->minimum()
$49 = 0
(gdb) p dominating->maximum()
$50 = 0
(gdb) p sumA
$51 = {
  term = 0x0, 
  constant = 0
}
(gdb) p dominated->minimum()
$52 = 0
(gdb) p dominated->maximum()
$53 = 0
(gdb) p sumB
$54 = {
  term = 0x0, 
  constant = -2147483648
}

So far, this makes sense. We have two charAt calls and two bounds checks.

This code in TryEliminateBoundsCheck, at least, is wrong:

    int32_t newMinimum, newMaximum;
    if (!SafeSub(Min(minimumA, minimumB), sumA.constant, &newMinimum) ||
        !SafeSub(Max(maximumA, maximumB), sumA.constant, &newMaximum))
    {
        return false;
    }

The new min and max are computed to permit the union of both ranges rather than the intersection. But the code before this, that computes minimumA and friends, is buggy too.

I suspect this bug could be easily used to read arbitrary memory on 32-bit platforms. -->sec-critical.

All this code was contributed by sunfish.
Flags: needinfo?(jorendorff) → needinfo?(nicolas.b.pierron)
Keywords: sec-highsec-critical
Flags: needinfo?(nicolas.b.pierron)
(In reply to Jason Orendorff [:jorendorff] from comment #5)
> The changeset reveals a bug in Ion.
> 
> TryEliminateBoundsCheck ends up combining these two bounds checks:
> 
> (gdb) p dominating->minimum()
> $49 = 0
> (gdb) p dominating->maximum()
> $50 = 0
> (gdb) p sumA
> $51 = {
>   term = 0x0, 
>   constant = 0
> }
> (gdb) p dominated->minimum()
> $52 = 0
> (gdb) p dominated->maximum()
> $53 = 0
> (gdb) p sumB
> $54 = {
>   term = 0x0, 
>   constant = -2147483648
> }
> 
> So far, this makes sense. We have two charAt calls and two bounds checks.
> 
> This code in TryEliminateBoundsCheck, at least, is wrong:
> 
>     int32_t newMinimum, newMaximum;
>     if (!SafeSub(Min(minimumA, minimumB), sumA.constant, &newMinimum) ||
>         !SafeSub(Max(maximumA, maximumB), sumA.constant, &newMaximum))
>     {
>         return false;
>     }
> 
> The new min and max are computed to permit the union of both ranges rather
> than the intersection. But the code before this, that computes minimumA and
> friends, is buggy too.

Ok, I think the problem is caused by the fact that we have 2 constants, and we think the index is the same because both terms are equal.  One easy way to fix this issue would be to check that the sumA.term is not NULL.
I don't understand why computing the union of the two ranges, rather than the intersection, is OK.
(In reply to Jason Orendorff [:jorendorff] from comment #7)
> I don't understand why computing the union of the two ranges, rather than
> the intersection, is OK.

In this case it is not ok, because one of the assumption is not verified (same index).

Otherwise it is ok, because we are looking for the union of all bound checks and fail on the first out-of bound.

Let's take an example to illustrate why we want an union and not an intersection:

    x[i] = x[i + 1] - x[i - 1];

  will produce 3 bound checks:
    bail if i + 0  < 0 || i + 0  > x.length
    bail if i + 1  < 0 || i + 1  > x.length
    bail if i + -1 < 0 || i + -1 > x.length

  Note that "i + -1 >= 0" implies "i + 0 >= 0" and "i + 1 >= 0".
  Which means that if we bail with "i + -1 < 0" first, then we can ensure that we will not bail because of neither "i + 0 < 0" nor "i + 1 < 0"
  And the same thing for the other condition.

  So we can summarize the 3 previous bound checks by:

    bail if i + -1 < 0 || i + 1 > x.length

which is the min of the minimums and the max of the maximums.

Note: we should probably at this example on top of the merging logic to make this clear.

In the current test case we have no "i", and the linear sum is only composed of a constant index.  This merging logic attempt to merge:

  (A) bail if 0 + 0 < 0 || 0 + 0 > x.length
  (B) bail if -21… + 0 < 0 || -21… + 0 > x.length

into to:

  (A) bail if 0 + -21… < 0 || 0 + 0 > x.length

which by the way sounds correct.
So I guess the problem might be in the way we encode the bound check.
(In reply to Nicolas B. Pierron [:nbp] from comment #8)
> Note: we should probably at this example on top of the merging logic to make
> this clear.

s/at/add/
(In reply to Jason Orendorff [:jorendorff] from comment #5)
> This code in TryEliminateBoundsCheck, at least, is wrong:
> 
>     int32_t newMinimum, newMaximum;
>     if (!SafeSub(Min(minimumA, minimumB), sumA.constant, &newMinimum) ||
>         !SafeSub(Max(maximumA, maximumB), sumA.constant, &newMaximum))
>     {
>         return false;
>     }
> 
> The new min and max are computed to permit the union of both ranges rather
> than the intersection. But the code before this, that computes minimumA and
> friends, is buggy too.

This code, including the code that computes minimumA and friends, was introduced in 4e74ada3a64f for bug 719541.
(In reply to Dan Gohman [:sunfish] from comment #10)
> This code, including the code that computes minimumA and friends, was
> introduced in 4e74ada3a64f for bug 719541.

You're right, of course. Sorry, Dan.

Thanks for the explanation, nbp. I was reading the semantics of MBoundsCheck incorrectly.

But now I'm not sure there's anything wrong in this function after all.  We start with two bounds checks

            index        min       index        max
    bail if 0           + 0 < 0 || 0           + 0 >= length
    bail if -2147483648 + 0 < 0 || -2147483648 + 0 >= length

These are merged into a single bounds check

           idx  min                index        max
    bail if 0 + -2147483648 < 0 || 0           + 0 >= length

This bounds check can't succeed, but that's the correct outcome.

Now I tend to suspect CodeGenerator::visitBoundsCheckRange(LBoundsCheckRange *lir).
Here's the code we're emitting, comments added:

[Codegen] instruction BoundsCheckRange
[Codegen] xorl       %edx, %edx
// If the minimum and maximum differ then do an underflow check first.
[Codegen] addl       $0x80000000, %edx
[Codegen] Encoding 2 of resume point 0x103b03800's operands starting from 0
[Codegen] jo         ((205))
// Bail if index + min < 0 (signed comparison)  <--- THE CODE HERE LOOKS WRONG
[Codegen] subl       $0x80000000, %edx
[Codegen] testl      %edx, %edx
[Codegen] jl         ((219))
// Bail if index + max >= length (signed comparison).
// max is 0 so there's no addl instruction + overflow check here.
[Codegen] cmpl       %edx, %eax
[Codegen] jbe        ((227))

My x86-64 is terrifically bad, so I should stop here.
Flags: needinfo?(jdemooij)
Attached patch PatchSplinter Review
BoundsCheckRange bug. If the SafeSub overflows, we will restore |temp| before doing the < 0 check.

I think this goes back all the way to bug 719541, so all Firefox versions with Ion are affected. Not sure why we're only seeing this now, but I think it's an edge case: SafeSub(max, min) has to overflow and the checks that follow have to succeed.
Assignee: general → jdemooij
Status: NEW → ASSIGNED
Attachment #8339812 - Flags: review?(bhackett1024)
Flags: needinfo?(jdemooij)
(In reply to Jason Orendorff [:jorendorff] from comment #11)
> // Bail if index + min < 0 (signed comparison)  <--- THE CODE HERE LOOKS
> WRONG
> [Codegen] subl       $0x80000000, %edx
> [Codegen] testl      %edx, %edx
> [Codegen] jl         ((219))

FWIW, you were right, with my patch we emit the subl after the testl/jl instead of before it.
Attachment #8339812 - Flags: review?(bhackett1024) → review+
Comment on attachment 8339812 [details] [diff] [review]
Patch

[Security approval request comment]
> How easily could an exploit be constructed based on the patch?
Not trivial but it's possible with some work.

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

> Which older supported branches are affected by this flaw?
Firefox 18+.

> If not all supported branches, which bug introduced the flaw?
Bug 719541.

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

> How likely is this patch to cause regressions; how much testing does it need?
Unlikely to cause regressions.
Attachment #8339812 - Flags: sec-approval?
Just saw this because of holiday. Since we ship in a week, this cannot really go in right now. I'm going to suggest landing it two weeks after we ship, on 12/23 (if you'll be around). Will you be able to land it on affected branches then?
Whiteboard: [jsbugmon:update] → [jsbugmon:update][checkin on 12/23 or later]
Comment on attachment 8339812 [details] [diff] [review]
Patch

sec-approval+ for December 23 or later checkin (for next cycle). When it goes in, you should nominate it for affected branches as well and we should get it in there.
Attachment #8339812 - Flags: sec-approval? → sec-approval+
(In reply to Al Billings [:abillings] from comment #15)
> I'm going to suggest landing it two weeks after we
> ship, on 12/23 (if you'll be around). Will you be able to land it on
> affected branches then?

Sure, I should have time to push this then. Setting needinfo so that I don't forget about this.
Flags: needinfo?(jdemooij)
(In reply to Al Billings [:abillings] from comment #16)
> sec-approval+ for December 23 or later checkin (for next cycle). When it
> goes in, you should nominate it for affected branches as well and we should
> get it in there.

https://hg.mozilla.org/integration/mozilla-inbound/rev/c3f75843aed0
Whiteboard: [jsbugmon:update][checkin on 12/23 or later] → [jsbugmon:update]
Comment on attachment 8339812 [details] [diff] [review]
Patch

[Approval Request Comment]
Bug caused by (feature/regressing bug #): Bug 719541.
User impact if declined: Security bugs.
Testing completed (on m-c, etc.): On m-i.
Risk to taking this patch (and alternatives if risky): Very low.
String or IDL/UUID changes made by this patch: None.

[Approval Request Comment]
User impact if declined: Security bugs.
Fix Landed on Version: Just landed on m-i, will likely be backported to 27.
Risk to taking this patch (and alternatives if risky): Very low.
String or UUID changes made by this patch: None.
Attachment #8339812 - Flags: approval-mozilla-esr24?
Attachment #8339812 - Flags: approval-mozilla-beta?
Attachment #8339812 - Flags: approval-mozilla-aurora?
https://hg.mozilla.org/mozilla-central/rev/c3f75843aed0
Status: ASSIGNED → RESOLVED
Closed: 11 years ago
Resolution: --- → FIXED
Target Milestone: --- → mozilla29
Attachment #8339812 - Flags: approval-mozilla-beta?
Attachment #8339812 - Flags: approval-mozilla-beta+
Attachment #8339812 - Flags: approval-mozilla-aurora?
Attachment #8339812 - Flags: approval-mozilla-aurora+
Status: RESOLVED → VERIFIED
JSBugMon: This bug has been automatically verified fixed.
blocking-b2g: --- → koi?
koi+ for sec-critical bug
blocking-b2g: koi? → koi+
Attachment #8339812 - Flags: approval-mozilla-esr24? → approval-mozilla-esr24+
Whiteboard: [jsbugmon:update] → [jsbugmon:update][adv-main27+][adv-esr24.3+]
Group: core-security
is this one sec-critical or sec-high?  its marked as both.
Keywords: sec-high
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: