Bug 735544 - Allow exception stacks to cross compartment boundaries. r=luke

This commit is contained in:
Bobby Holley 2012-03-15 15:19:52 -07:00
Родитель 80710b8eea
Коммит ca16564715
6 изменённых файлов: 138 добавлений и 15 удалений

Просмотреть файл

@ -23,7 +23,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=246699
**/
function hasStack(e)
{
return e instanceof Error && /inciteCaps/.test(e.stack);
return e.constructor.name === "Error" && /inciteCaps/.test(e.stack);
}
function inciteCaps(f)

Просмотреть файл

@ -290,11 +290,42 @@ struct SuppressErrorsGuard
}
};
struct AppendArg {
struct AppendWrappedArg {
JSContext *cx;
Vector<Value> &values;
AppendArg(Vector<Value> &values) : values(values) {}
AppendWrappedArg(JSContext *cx, Vector<Value> &values)
: cx(cx),
values(values)
{}
bool operator()(unsigned, Value *vp) {
return values.append(*vp);
Value v = *vp;
/*
* Try to wrap.
*
* If wrap() fails, there's a good chance that it's because we're
* already in the process of throwing a native stack limit exception.
*
* This causes wrap() to throw, but it can't actually create an exception
* because we're already making one here, and cx->generatingError is true.
* So it returns false without an exception set on the stack. If we propagate
* that, it constitutes an uncatchable exception.
*
* So we just ignore exceptions. If wrap actually does set a pending
* exception, or if the caller sloppily left an exception on cx (which the
* e4x parser does), it doesn't matter - it will be overwritten shortly.
*
* NB: In the sloppy e4x case, one might thing we should clear the
* exception before calling wrap(). But wrap() has to be ok with pending
* exceptions, since it wraps exception objects during cross-compartment
* unwinding.
*/
if (!cx->compartment->wrap(cx, &v))
v = JSVAL_VOID;
/* Append the value. */
return values.append(v);
}
};
@ -315,16 +346,14 @@ InitExnPrivate(JSContext *cx, JSObject *exnObject, JSString *message,
{
SuppressErrorsGuard seg(cx);
for (FrameRegsIter i(cx); !i.done(); ++i) {
/*
* An exception object stores stack values from 'fp' which may be
* in a different compartment from 'exnObject'. Engine compartment
* invariants require such values to be wrapped. A simpler solution
* is to just cut off the backtrace at compartment boundaries.
* Also, avoid exposing values from different security principals.
*/
StackFrame *fp = i.fp();
if (fp->compartment() != cx->compartment)
break;
/*
* Ask the crystal CAPS ball whether we can see values across
* compartment boundaries.
*
* NB: 'fp' may point to cross-compartment values that require wrapping.
*/
if (checkAccess && fp->isNonEvalFunctionFrame()) {
Value v = NullValue();
jsid callerid = ATOM_TO_JSID(cx->runtime->atomState.callerAtom);
@ -338,7 +367,7 @@ InitExnPrivate(JSContext *cx, JSObject *exnObject, JSString *message,
if (fp->isNonEvalFunctionFrame()) {
frame.funName = fp->fun()->atom ? fp->fun()->atom : cx->runtime->emptyString;
frame.argc = fp->numActualArgs();
if (!fp->forEachCanonicalActualArg(AppendArg(values)))
if (!fp->forEachCanonicalActualArg(AppendWrappedArg(cx, values)))
return false;
} else {
frame.funName = NULL;
@ -591,7 +620,7 @@ ValueToShortSource(JSContext *cx, const Value &v)
* memory, for too many classes (see Mozilla bug 166743).
*/
char buf[100];
JS_snprintf(buf, sizeof buf, "[object %s]", obj->getClass()->name);
JS_snprintf(buf, sizeof buf, "[object %s]", js::UnwrapObject(obj, false)->getClass()->name);
str = JS_NewStringCopyZ(cx, buf);
}

Просмотреть файл

@ -76,6 +76,7 @@ _CHROME_FILES = \
test_weakmaps.xul \
test_bug706301.xul \
test_watchpoints.xul \
test_exnstack.xul \
$(NULL)
# Disabled until this test gets updated to test the new proxy based

Просмотреть файл

@ -0,0 +1,69 @@
<?xml version="1.0"?>
<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=735544
-->
<window title="Mozilla Bug 735544"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
<!-- test results are displayed in the html:body -->
<body xmlns="http://www.w3.org/1999/xhtml">
<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=735544"
target="_blank">Mozilla Bug 735544</a>
<iframe id='ifr0' onload="frameDone(0);" src="http://mochi.test:8888/tests/js/xpconnect/tests/mochitest/file_exnstack.html" />
<iframe id='ifr1' onload="frameDone(1);" src="http://mochi.test:8888/tests/js/xpconnect/tests/mochitest/file_exnstack.html" />
</body>
<!-- test code goes here -->
<script type="application/javascript">
<![CDATA[
/** Test for Bug 735544 - Allow exception stacks to cross compartment boundaries **/
SimpleTest.waitForExplicitFinish();
var gFramesDone = [false, false];
function frameDone(idx) {
gFramesDone[idx] = true;
if (gFramesDone[0] && gFramesDone[1])
startTest();
}
function throwAsChrome() {
// Grab the iframe content windows.
var cwin0 = document.getElementById('ifr0').contentWindow;
var cwin1 = document.getElementById('ifr1').contentWindow;
// Have cwin0 call a function on cwin1 that throws.
cwin0.wrappedJSObject.doThrow(cwin1);
}
function startTest() {
try {
throwAsChrome();
ok(false, "should throw");
} catch (e) {
stackFrames = e.stack.split("\n");
ok(/throwAsInner/.exec(stackFrames[0]),
"The bottom frame should be thrown by the inner");
ok(/throwAsOuter/.exec(stackFrames[2]),
"The 3rd-from-bottom frame should be thrown by the other");
ok(/Window/.exec(stackFrames[2]), "Should have a |Window| argument");
ok(!/throwAsChrome/.exec(e.stack),
"The entire stack should not cross into chrome.");
}
SimpleTest.finish();
}
]]>
</script>
</window>

Просмотреть файл

@ -96,6 +96,7 @@ _TEST_FILES = bug500931_helper.html \
test_bug691059.html \
file_nodelists.html \
file_bug706301.html \
file_exnstack.html \
$(NULL)
_CHROME_FILES = \

Просмотреть файл

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<head>
<script type="application/javascript">
window.doThrow = function(other) {
if (other)
throwAsOuter(other);
else
throwAsInner();
}
function throwAsInner() {
throw Error('look at me go!');
}
function throwAsOuter(other) {
other.doThrow(null);
}
</script>
</head>
<body>
</body>
</html>