forked from mirrors/gecko-dev
Bug 1377587, part 1 - Always act like __exposedProps__ is missing. r=krizsa
This patch gently removes support for __exposedProps__ by changing ExposedPropertiesOnly::check() to always return false, while still failing silently in deny for some kinds of access. The tests that I changed all involve testing the behavior with __exposedProps__. I adjusted them to expect it to fail, or to adjust the error message they get when they fail. That seemed better than deleting them entirely. Note that test_bug1065185.html had a bug, so that it never executed the first case. I fixed that, and then fixed up the test to work when __exposedProps__ is not supported. This also removes various bits of the test framework that use __exposedProps__, but don't actually need to. MozReview-Commit-ID: 8fvkAmITmXY --HG-- extra : rebase_source : ef7e2c55adc12511f17f3865ebb46c343875f0b3
This commit is contained in:
parent
fee8c46ef7
commit
2b68b38709
27 changed files with 74 additions and 335 deletions
|
|
@ -62,16 +62,6 @@ function PlainTextConsole(print, innerID) {
|
|||
}
|
||||
});
|
||||
|
||||
// We defined the `__exposedProps__` in our console chrome object.
|
||||
//
|
||||
// Meanwhile we're investigating with the platform team if `__exposedProps__`
|
||||
// are needed, or are just a left-over.
|
||||
|
||||
console.__exposedProps__ = Object.keys(ConsoleAPI.prototype).reduce(function(exposed, prop) {
|
||||
exposed[prop] = "r";
|
||||
return exposed;
|
||||
}, {});
|
||||
|
||||
Object.freeze(console);
|
||||
return console;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -53,11 +53,6 @@ function HookedPlainTextConsole(hook, print, innerID) {
|
|||
this.exception = hook.bind(null, "exception", innerID);
|
||||
this.time = hook.bind(null, "time", innerID);
|
||||
this.timeEnd = hook.bind(null, "timeEnd", innerID);
|
||||
|
||||
this.__exposedProps__ = {
|
||||
log: "rw", info: "rw", warn: "rw", error: "rw", debug: "rw",
|
||||
exception: "rw", time: "rw", timeEnd: "rw"
|
||||
};
|
||||
}
|
||||
|
||||
// Creates a custom loader instance whose console module is hooked in order
|
||||
|
|
|
|||
|
|
@ -444,7 +444,7 @@ exports["test Highlight toString Behavior"] = createProxyTest("", function (help
|
|||
let strToString = helper.rawWindow.Object.prototype.toString.call("");
|
||||
assert.ok(/\[object String.*\]/.test(strToString), "strings are strings");
|
||||
|
||||
let o = {__exposedProps__:{}};
|
||||
let o = {};
|
||||
let objToString = helper.rawWindow.Object.prototype.toString.call(o);
|
||||
assert.ok(/\[object Object.*\]/.test(objToString), "objects are objects");
|
||||
|
||||
|
|
@ -622,10 +622,6 @@ exports["test Functions"] = createProxyTest("", function (helper) {
|
|||
helper.rawWindow.isEqual = function isEqual(a, b) {
|
||||
return a == b;
|
||||
};
|
||||
// bug 784116: workaround in order to allow proxy code to cache proxies on
|
||||
// these functions:
|
||||
helper.rawWindow.callFunction.__exposedProps__ = {__proxy: 'rw'};
|
||||
helper.rawWindow.isEqual.__exposedProps__ = {__proxy: 'rw'};
|
||||
|
||||
helper.createWorker(
|
||||
'new ' + function ContentScriptScope() {
|
||||
|
|
|
|||
|
|
@ -444,7 +444,7 @@ exports["test Highlight toString Behavior"] = createProxyTest("", function (help
|
|||
let strToString = helper.rawWindow.Object.prototype.toString.call("");
|
||||
assert.ok(/\[object String.*\]/.test(strToString), "strings are strings");
|
||||
|
||||
let o = {__exposedProps__:{}};
|
||||
let o = {};
|
||||
let objToString = helper.rawWindow.Object.prototype.toString.call(o);
|
||||
assert.ok(/\[object Object.*\]/.test(objToString), "objects are objects");
|
||||
|
||||
|
|
@ -622,10 +622,6 @@ exports["test Functions"] = createProxyTest("", function (helper) {
|
|||
helper.rawWindow.isEqual = function isEqual(a, b) {
|
||||
return a == b;
|
||||
};
|
||||
// bug 784116: workaround in order to allow proxy code to cache proxies on
|
||||
// these functions:
|
||||
helper.rawWindow.callFunction.__exposedProps__ = {__proxy: 'rw'};
|
||||
helper.rawWindow.isEqual.__exposedProps__ = {__proxy: 'rw'};
|
||||
|
||||
helper.createWorker(
|
||||
'new ' + function ContentScriptScope() {
|
||||
|
|
|
|||
|
|
@ -106,10 +106,6 @@ function testPrincipal(principal, wantXrays = true) {
|
|||
var trapDidRun = false;
|
||||
var proxy = new Proxy({}, new Proxy({}, {get: (_, trap) => {
|
||||
return function(_, arg) {
|
||||
if (trap === "has" && arg === "__exposedProps__") {
|
||||
// Tolerate this case until bug 1392026 is fixed.
|
||||
return false;
|
||||
}
|
||||
trapDidRun = true;
|
||||
throw new Error("proxy trap '" + trap + "' was called.");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ DEPRECATED_OPERATION(NodeValue)
|
|||
DEPRECATED_OPERATION(TextContent)
|
||||
DEPRECATED_OPERATION(EnablePrivilege)
|
||||
DEPRECATED_OPERATION(DOMExceptionCode)
|
||||
DEPRECATED_OPERATION(NoExposedProps)
|
||||
DEPRECATED_OPERATION(MutationEvent)
|
||||
DEPRECATED_OPERATION(Components)
|
||||
DEPRECATED_OPERATION(PrefixedVisibilityAPI)
|
||||
|
|
|
|||
|
|
@ -105,7 +105,13 @@ function parent_test(finish)
|
|||
|
||||
addMessageListener("cpows:from_parent", (msg) => {
|
||||
let obj = msg.objects.obj;
|
||||
ok(obj.a == 1, "correct value from parent");
|
||||
if (is_remote) {
|
||||
ok(obj.a == undefined, "__exposedProps__ should not work");
|
||||
} else {
|
||||
// The same process test is not run as content, so the field can
|
||||
// be accessed even though __exposedProps__ has been removed.
|
||||
ok(obj.a == 1, "correct value from parent");
|
||||
}
|
||||
|
||||
// Test that a CPOW reference to a function in the chrome process
|
||||
// is callable from unprivileged content. Greasemonkey uses this
|
||||
|
|
@ -260,11 +266,11 @@ function lifetime_test(finish)
|
|||
var obj = {"will_die": {"f": 1}};
|
||||
let [result] = sendRpcMessage("cpows:lifetime_test_1", {}, {obj: obj});
|
||||
ok(result == 10, "got sync result");
|
||||
ok(obj.wont_die.f == 2, "got reverse CPOW");
|
||||
ok(obj.wont_die.f == undefined, "got reverse CPOW");
|
||||
obj.will_die = null;
|
||||
Components.utils.schedulePreciseGC(function() {
|
||||
addMessageListener("cpows:lifetime_test_3", (msg) => {
|
||||
ok(obj.wont_die.f == 2, "reverse CPOW still works");
|
||||
ok(obj.wont_die.f == undefined, "reverse CPOW still works");
|
||||
finish();
|
||||
});
|
||||
sendRpcMessage("cpows:lifetime_test_2");
|
||||
|
|
|
|||
|
|
@ -154,8 +154,6 @@ MediaEMENoCapabilitiesDeprecatedWarning=Calling navigator.requestMediaKeySystemA
|
|||
MediaEMENoCodecsDeprecatedWarning=Calling navigator.requestMediaKeySystemAccess() (at %S) passing a candidate MediaKeySystemConfiguration containing audioCapabilities or videoCapabilities without a contentType with a “codecs” string is deprecated and will soon become unsupported.
|
||||
# LOCALIZATION NOTE: Do not translate "DOMException", "code" and "name"
|
||||
DOMExceptionCodeWarning=Use of DOMException’s code attribute is deprecated. Use name instead.
|
||||
# LOCALIZATION NOTE: Do not translate "__exposedProps__"
|
||||
NoExposedPropsWarning=Exposing chrome JS objects to content without __exposedProps__ is insecure and deprecated. See https://developer.mozilla.org/en/XPConnect_wrappers for more information.
|
||||
# LOCALIZATION NOTE: Do not translate "Mutation Event" and "MutationObserver"
|
||||
MutationEventWarning=Use of Mutation Events is deprecated. Use MutationObserver instead.
|
||||
# LOCALIZATION NOTE: Do not translate "Components"
|
||||
|
|
|
|||
|
|
@ -92,7 +92,6 @@ const char* const XPCJSRuntime::mStrings[] = {
|
|||
"createInstance", // IDX_CREATE_INSTANCE
|
||||
"item", // IDX_ITEM
|
||||
"__proto__", // IDX_PROTO
|
||||
"__exposedProps__", // IDX_EXPOSEDPROPS
|
||||
"eval", // IDX_EVAL
|
||||
"controllers", // IDX_CONTROLLERS
|
||||
"Controllers", // IDX_CONTROLLERS_CLASS
|
||||
|
|
|
|||
|
|
@ -458,7 +458,6 @@ public:
|
|||
IDX_CREATE_INSTANCE ,
|
||||
IDX_ITEM ,
|
||||
IDX_PROTO ,
|
||||
IDX_EXPOSEDPROPS ,
|
||||
IDX_EVAL ,
|
||||
IDX_CONTROLLERS ,
|
||||
IDX_CONTROLLERS_CLASS ,
|
||||
|
|
|
|||
|
|
@ -25,11 +25,11 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1065185
|
|||
|
||||
var gLoadCount = 0;
|
||||
function loaded() {
|
||||
switch(++gLoadCount) {
|
||||
switch(gLoadCount++) {
|
||||
case 0:
|
||||
doMonitor([]);
|
||||
doMonitor([/access to property "a"/i]);
|
||||
window[0].wrappedJSObject.probe = { a: 2, __exposedProps__: { 'a': 'r' } };
|
||||
is(window[0].eval('probe.a'), 2, "Accessed exposed prop");
|
||||
is(window[0].eval('probe.a'), undefined, "Accessed exposed prop");
|
||||
endMonitor();
|
||||
break;
|
||||
case 1:
|
||||
|
|
|
|||
|
|
@ -49,13 +49,6 @@ sandbox.getCOW = getCOW;
|
|||
const TEST_API = ['is', 'isnot', 'ok', 'todo_is', 'todo_isnot', 'todo'];
|
||||
TEST_API.forEach(function(name) { sandbox[name] = window[name]; });
|
||||
|
||||
sandbox.alienObject = {
|
||||
__exposedProps__: {funProp: 'r'},
|
||||
funProp: function foo(x) {
|
||||
return x + 1;
|
||||
}
|
||||
};
|
||||
|
||||
sandbox.chromeGet = function (obj, prop) { return obj[prop]; };
|
||||
|
||||
function COWTests() {
|
||||
|
|
@ -74,17 +67,6 @@ function COWTests() {
|
|||
// functions like assertIsWritable(myObj, 'someproperty') might
|
||||
// be useful.
|
||||
|
||||
function isProp(obj, propName, value, desc) {
|
||||
try {
|
||||
is(obj[propName], value, "getting " + propName + " on " + desc);
|
||||
ok(propName in obj,
|
||||
propName + " on " + desc + " should exist");
|
||||
ok(Object.hasOwnProperty.call(obj, propName),
|
||||
propName + " on " + desc + " should exist");
|
||||
} catch (e) {
|
||||
ok(false, "getting " + propName + " on " + desc + " threw " + e);
|
||||
}
|
||||
}
|
||||
function isPropHidden(obj, propName, desc) {
|
||||
try {
|
||||
is(obj[propName], undefined,
|
||||
|
|
@ -103,7 +85,7 @@ function COWTests() {
|
|||
var empty = {};
|
||||
var nonempty = {foo: 42, bar: 33};
|
||||
is(getCOW(empty).foo, undefined,
|
||||
"shouldn't throw when accessing exposed properties that doesn't exist");
|
||||
"shouldn't throw when accessing exposed properties that don't exist");
|
||||
|
||||
PROPS_TO_TEST.forEach(function(name) {
|
||||
isPropHidden(getCOW(nonempty), name, "object without exposedProps");
|
||||
|
|
@ -135,18 +117,12 @@ function COWTests() {
|
|||
var strict = { __exposedProps__: { foo: "r" }, foo: "foo property" };
|
||||
var strictCOWr = getCOW(strict);
|
||||
PROPS_TO_TEST.forEach(function(name) {
|
||||
if (name == "foo") {
|
||||
isProp(strictCOWr, name, "foo property",
|
||||
"object with exposed 'foo'");
|
||||
}
|
||||
else {
|
||||
isPropHidden(strictCOW, name, "object with exposed 'foo'");
|
||||
}
|
||||
isPropHidden(strictCOW, name, "object with exposed 'foo'");
|
||||
});
|
||||
is(getNames(strictCOWr).length, 1,
|
||||
"object with exposedProps only enumerate exposed props");
|
||||
is(getNames(strictCOWr)[0], "foo",
|
||||
"object with exposedProps only enumerate exposed props");
|
||||
is(getNames(strictCOWr).length, 0,
|
||||
"exposed props does not enumerate anything");
|
||||
is(getNames(strictCOWr)[0], undefined,
|
||||
"exposed props does not enumerate anything");
|
||||
|
||||
// Test writable property
|
||||
var writable = getCOW({ __exposedProps__: {foo: 'w'}});
|
||||
|
|
@ -154,25 +130,18 @@ function COWTests() {
|
|||
ok(!("foo" in writable),
|
||||
"non-existing write-only property shouldn't exist");
|
||||
writable.foo = 5;
|
||||
is(chromeGet(writable, "foo"), 5, "writing to a write-only exposed prop works");
|
||||
todo("foo" in writable,
|
||||
"existing write-only property should exist");
|
||||
ok(false, "writing to a write-only exposed prop should throw");
|
||||
} catch (e) {
|
||||
ok(false, "writing to a write-only exposed prop shouldn't throw " + e);
|
||||
}
|
||||
try {
|
||||
writable.foo;
|
||||
todo(false, "reading from a write-only exposed prop should throw");
|
||||
} catch (e) {
|
||||
todo(/Permission denied/.test(e),
|
||||
"reading from a write-only exposed prop should throw");
|
||||
ok(/Permission denied/.test(e),
|
||||
"writing to a write-only exposed prop should throw the right error");
|
||||
}
|
||||
is(writable.foo, undefined,
|
||||
"reading from a write-only exposed prop should return undefined");
|
||||
try {
|
||||
delete writable.foo;
|
||||
is(chromeGet(writable, "foo"), undefined,
|
||||
"deleting a write-only exposed prop works");
|
||||
ok(false, "deleting a write-only exposed prop should throw");
|
||||
} catch (e) {
|
||||
ok(false, "deleting a write-only exposed prop shouldn't throw " + e);
|
||||
ok(true, "deleting a write-only exposed prop should throw " + e);
|
||||
}
|
||||
|
||||
// Test readable property
|
||||
|
|
@ -180,8 +149,8 @@ function COWTests() {
|
|||
foo: 5,
|
||||
bar: 6 };
|
||||
try {
|
||||
isProp(getCOW(readable), "foo", 5,
|
||||
"reading from a readable exposed prop works");
|
||||
isPropHidden(getCOW(readable), "foo", undefined,
|
||||
"reading from a readable exposed prop shouldn't work");
|
||||
} catch (e) {
|
||||
ok(false, "reading from a readable exposed prop shouldn't throw " + e);
|
||||
}
|
||||
|
|
@ -202,8 +171,7 @@ function COWTests() {
|
|||
|
||||
try {
|
||||
var props = getNames(getCOW(readable));
|
||||
is(props.length, 1, "COW w/ one exposed prop should enumerate once");
|
||||
is(props[0], 'foo', "COW w/ one exposed prop should enumerate it");
|
||||
is(props.length, 0, "COW w/ one exposed prop should not enumerate");
|
||||
} catch (e) {
|
||||
ok(false, "COW w/ a readable prop should not raise exc " +
|
||||
"on enumeration: " + e);
|
||||
|
|
@ -215,21 +183,17 @@ function COWTests() {
|
|||
ok(!("foo" in readwrite),
|
||||
"non-existing readwrite property shouldn't exist");
|
||||
readwrite.foo = 5;
|
||||
is(readwrite.foo, 5, "writing to a readwrite exposed prop looks like it worked");
|
||||
is(chromeGet(readwrite, "foo"), 5, "writing to a readwrite exposed prop works");
|
||||
ok("foo" in readwrite,
|
||||
"existing readwrite property should exist");
|
||||
ok(false, "writing to a readwrite exposed prop should throw");
|
||||
} catch (e) {
|
||||
ok(false, "writing to a readwrite exposed prop shouldn't throw " + e);
|
||||
ok(/Permission denied/.test(e),
|
||||
"writing to a readwrite exposed prop should throw the right error");
|
||||
}
|
||||
try {
|
||||
delete readwrite.foo;
|
||||
is(readwrite.foo, undefined, "deleting readwrite prop looks like it worked");
|
||||
ok(!("foo" in readwrite), "deleting readwrite prop looks like it really worked");
|
||||
is(chromeGet(readwrite, "foo"), undefined,
|
||||
"deleting a readwrite exposed prop works");
|
||||
ok(false, "deleting a readwrite prop should throw");
|
||||
} catch (e) {
|
||||
ok(false, "deleting a readwrite exposed prop shouldn't throw " + e);
|
||||
ok(/Permission denied/.test(e),
|
||||
"deleting a readwrite exposed prop should throw the right error");
|
||||
}
|
||||
|
||||
// Readables and functions
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=804630
|
|||
<!-- test code goes here -->
|
||||
<script type="application/javascript">
|
||||
<![CDATA[
|
||||
/** Test to make sure that COWed objects can expose properties from their prototypes. **/
|
||||
/** Test to make sure that COWed objects can't expose properties from their prototypes. **/
|
||||
const Cu = Components.utils;
|
||||
|
||||
// Set up the sandbox.
|
||||
|
|
@ -25,7 +25,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=804630
|
|||
sb.ok = ok;
|
||||
sb.is = is;
|
||||
|
||||
// Make a chrome object that exposes objects off its prototype.
|
||||
// Make a chrome object that tries to expose objects off its prototype.
|
||||
sb.proto = { read: 42, readWrite: 32, __exposedProps__: {} };
|
||||
sb.obj = { __exposedProps__: { read: 'r', readWrite: 'rw' } };
|
||||
sb.obj.__proto__ = sb.proto;
|
||||
|
|
@ -36,10 +36,11 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=804630
|
|||
'try { proto.readWrite = 12; wrote = true; } catch(e) {} ' +
|
||||
' ok(!wrote, "Should not write proto property");', sb);
|
||||
|
||||
// Make sure we can access the exposed properties via the derived object.
|
||||
Cu.evalInSandbox('is(obj.read, 42, "obj.read accessible");', sb);
|
||||
Cu.evalInSandbox('is(obj.readWrite, 32, "obj.readWrite is readable");', sb);
|
||||
Cu.evalInSandbox('obj.readWrite = 8; is(obj.readWrite, 8, "obj.readWrite is writable");', sb);
|
||||
// Make sure we can't access the exposed properties via the derived object.
|
||||
Cu.evalInSandbox('is(obj.read, undefined, "obj.read inaccessible");', sb);
|
||||
Cu.evalInSandbox('is(obj.readWrite, undefined, "obj.readWrite is not readable");', sb);
|
||||
Cu.evalInSandbox('try { obj.readWrite = 8; ok(false, "obj.readWrite is not writable"); } catch (e) {};',
|
||||
sb);
|
||||
|
||||
]]>
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ function run_test() {
|
|||
function checkThrows(str, rgxp) {
|
||||
try {
|
||||
sb.eval(str);
|
||||
do_check_true(false);
|
||||
do_check_true(false, "eval should have thrown");
|
||||
} catch (e) {
|
||||
do_check_true(rgxp.test(e));
|
||||
do_check_true(rgxp.test(e), "error message should match");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -29,12 +29,12 @@ function run_test() {
|
|||
chromeCallableValueProp: 'r' }
|
||||
};
|
||||
|
||||
do_check_eq(sb.eval('exposed.simpleValueProp'), 42);
|
||||
do_check_eq(sb.eval('exposed.objectValueProp.val'), 42);
|
||||
checkThrows('exposed.getterProp;', /privileged accessor/i);
|
||||
checkThrows('exposed.setterProp = 42;', /privileged accessor/i);
|
||||
checkThrows('exposed.getterSetterProp;', /privileged accessor/i);
|
||||
checkThrows('exposed.getterSetterProp = 42;', /privileged accessor/i);
|
||||
do_check_eq(sb.eval('exposed.contentCallableValueProp()'), 42);
|
||||
checkThrows('exposed.chromeCallableValueProp();', /privileged or cross-origin callable/i);
|
||||
do_check_eq(sb.eval('exposed.simpleValueProp'), undefined);
|
||||
do_check_eq(sb.eval('exposed.objectValueProp'), undefined);
|
||||
do_check_eq(sb.eval('exposed.getterProp;'), undefined);
|
||||
do_check_eq(sb.eval('exposed.getterSetterProp;'), undefined);
|
||||
checkThrows('exposed.setterProp = 42;', /Permission denied/i);
|
||||
checkThrows('exposed.getterSetterProp = 42;', /Permission denied/i);
|
||||
do_check_eq(sb.eval('exposed.contentCallableValueProp'), undefined);
|
||||
checkThrows('exposed.chromeCallableValueProp();', /is not a function/i);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,10 +14,5 @@ function run_test()
|
|||
var sb = Cu.Sandbox("http://www.example.com");
|
||||
sb.obj = { foo: 42, __exposedProps__: { hasOwnProperty: 'r' } };
|
||||
do_check_eq(Cu.evalInSandbox('typeof obj.foo', sb), 'undefined', "COW works as expected");
|
||||
try {
|
||||
Cu.evalInSandbox('obj.hasOwnProperty', sb);
|
||||
do_check_true(false);
|
||||
} catch (e) {
|
||||
do_check_true(/privileged or cross-origin callable/i.test(e));
|
||||
}
|
||||
do_check_eq(Cu.evalInSandbox('obj.hasOwnProperty', sb), undefined);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,5 +21,5 @@ function run_test() {
|
|||
checkThrows('obj.foo = 3;', sb, /denied/);
|
||||
Cu.evalInSandbox("var p = {__exposedProps__: {foo: 'rw'}};", sb);
|
||||
sb.obj.__proto__ = sb.p;
|
||||
checkThrows('obj.foo = 4;', sb, /__exposedProps__/);
|
||||
checkThrows('obj.foo = 4;', sb, /denied/);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ function setupChromeSandbox() {
|
|||
function checkDefineThrows(sb, obj, prop, desc) {
|
||||
var result = Cu.evalInSandbox('(function() { try { Object.defineProperty(' + obj + ', "' + prop + '", ' + desc.toSource() + '); return "nothrow"; } catch (e) { return e.toString(); }})();', sb);
|
||||
do_check_neq(result, 'nothrow');
|
||||
do_check_true(!!/denied/.exec(result));
|
||||
do_check_true(!!/denied|prohibited/.exec(result));
|
||||
do_check_true(result.indexOf(prop) != -1); // Make sure the prop name is in the error message.
|
||||
}
|
||||
|
||||
|
|
@ -19,7 +19,7 @@ function run_test() {
|
|||
contentSB.chromeObj = chromeSB.chromeObj;
|
||||
contentSB.chromeArr = chromeSB.chromeArr;
|
||||
|
||||
do_check_eq(Cu.evalInSandbox('chromeObj.a', contentSB), 2);
|
||||
do_check_eq(Cu.evalInSandbox('chromeObj.a', contentSB), undefined);
|
||||
try {
|
||||
Cu.evalInSandbox('chromeArr[1]', contentSB);
|
||||
do_check_true(false);
|
||||
|
|
|
|||
|
|
@ -7,5 +7,5 @@ function run_test() {
|
|||
contentSB.foo = chromeSB.foo;
|
||||
do_check_eq(Cu.evalInSandbox('foo.a', contentSB), undefined, "Default deny with no __exposedProps__");
|
||||
Cu.evalInSandbox('this.foo.__exposedProps__ = {a: "r"}', chromeSB);
|
||||
do_check_eq(Cu.evalInSandbox('foo.a', contentSB), 2, "works with __exposedProps__");
|
||||
do_check_eq(Cu.evalInSandbox('foo.a', contentSB), undefined, "Still not allowed with __exposedProps__");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ function checkThrows(fn) {
|
|||
fn();
|
||||
ok(false, "Should have thrown");
|
||||
} catch (e) {
|
||||
do_check_true(/denied|insecure/.test(e));
|
||||
do_check_true(/denied|insecure|prohibited/.test(e));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -252,20 +252,6 @@ AccessCheck::checkPassToPrivilegedCode(JSContext* cx, HandleObject wrapper, Hand
|
|||
return true;
|
||||
}
|
||||
|
||||
// COWs are fine to pass to chrome if and only if they have __exposedProps__,
|
||||
// since presumably content should never have a reason to pass an opaque
|
||||
// object back to chrome.
|
||||
if (AccessCheck::isChrome(js::UncheckedUnwrap(wrapper)) && WrapperFactory::IsCOW(obj)) {
|
||||
RootedObject target(cx, js::UncheckedUnwrap(obj));
|
||||
JSAutoCompartment ac(cx, target);
|
||||
RootedId id(cx, GetJSIDByIndex(cx, XPCJSContext::IDX_EXPOSEDPROPS));
|
||||
bool found = false;
|
||||
if (!JS_HasPropertyById(cx, target, id, &found))
|
||||
return false;
|
||||
if (found)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Same-origin wrappers are fine.
|
||||
if (AccessCheck::wrapperSubsumes(obj))
|
||||
return true;
|
||||
|
|
@ -323,171 +309,6 @@ AccessCheck::reportCrossOriginDenial(JSContext* cx, JS::HandleId id,
|
|||
MOZ_ALWAYS_TRUE(rv.MaybeSetPendingException(cx));
|
||||
}
|
||||
|
||||
enum Access { READ = (1<<0), WRITE = (1<<1), NO_ACCESS = 0 };
|
||||
|
||||
static void
|
||||
EnterAndThrowASCII(JSContext* cx, JSObject* wrapper, const char* msg)
|
||||
{
|
||||
JSAutoCompartment ac(cx, wrapper);
|
||||
JS_ReportErrorASCII(cx, "%s", msg);
|
||||
}
|
||||
|
||||
bool
|
||||
ExposedPropertiesOnly::check(JSContext* cx, HandleObject wrapper, HandleId id, Wrapper::Action act)
|
||||
{
|
||||
RootedObject wrappedObject(cx, Wrapper::wrappedObject(wrapper));
|
||||
|
||||
if (act == Wrapper::CALL)
|
||||
return false;
|
||||
|
||||
// For the case of getting a property descriptor, we allow if either GET or SET
|
||||
// is allowed, and rely on FilteringWrapper to filter out any disallowed accessors.
|
||||
if (act == Wrapper::GET_PROPERTY_DESCRIPTOR) {
|
||||
return check(cx, wrapper, id, Wrapper::GET) ||
|
||||
check(cx, wrapper, id, Wrapper::SET);
|
||||
}
|
||||
|
||||
RootedId exposedPropsId(cx, GetJSIDByIndex(cx, XPCJSContext::IDX_EXPOSEDPROPS));
|
||||
|
||||
// We need to enter the wrappee's compartment to look at __exposedProps__,
|
||||
// but we want to be in the wrapper's compartment if we call Deny().
|
||||
//
|
||||
// Unfortunately, |cx| can be in either compartment when we call ::check. :-(
|
||||
JSAutoCompartment ac(cx, wrappedObject);
|
||||
|
||||
bool found = false;
|
||||
if (!JS_HasPropertyById(cx, wrappedObject, exposedPropsId, &found))
|
||||
return false;
|
||||
|
||||
// If no __exposedProps__ existed, deny access.
|
||||
if (!found) {
|
||||
// Previously we automatically granted access to indexed properties and
|
||||
// .length for Array COWs. We're not doing that anymore, so make sure to
|
||||
// let people know what's going on.
|
||||
bool isArray;
|
||||
if (!JS_IsArrayObject(cx, wrappedObject, &isArray))
|
||||
return false;
|
||||
if (!isArray)
|
||||
isArray = JS_IsTypedArrayObject(wrappedObject);
|
||||
bool isIndexedAccessOnArray = isArray && JSID_IS_INT(id) && JSID_TO_INT(id) >= 0;
|
||||
bool isLengthAccessOnArray = isArray && JSID_IS_STRING(id) &&
|
||||
JS_FlatStringEqualsAscii(JSID_TO_FLAT_STRING(id), "length");
|
||||
if (isIndexedAccessOnArray || isLengthAccessOnArray) {
|
||||
JSAutoCompartment ac2(cx, wrapper);
|
||||
ReportWrapperDenial(cx, id, WrapperDenialForCOW,
|
||||
"Access to elements and length of privileged Array not permitted");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (id == JSID_VOID)
|
||||
return true;
|
||||
|
||||
Rooted<PropertyDescriptor> desc(cx);
|
||||
if (!JS_GetPropertyDescriptorById(cx, wrappedObject, exposedPropsId, &desc))
|
||||
return false;
|
||||
|
||||
if (!desc.object())
|
||||
return false;
|
||||
|
||||
if (desc.hasGetterOrSetter()) {
|
||||
EnterAndThrowASCII(cx, wrapper, "__exposedProps__ must be a value property");
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedValue exposedProps(cx, desc.value());
|
||||
if (exposedProps.isNullOrUndefined())
|
||||
return false;
|
||||
|
||||
if (!exposedProps.isObject()) {
|
||||
EnterAndThrowASCII(cx, wrapper, "__exposedProps__ must be undefined, null, or an Object");
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedObject hallpass(cx, &exposedProps.toObject());
|
||||
|
||||
if (!AccessCheck::subsumes(js::UncheckedUnwrap(hallpass), wrappedObject)) {
|
||||
EnterAndThrowASCII(cx, wrapper, "Invalid __exposedProps__");
|
||||
return false;
|
||||
}
|
||||
|
||||
Access access = NO_ACCESS;
|
||||
|
||||
if (!JS_GetPropertyDescriptorById(cx, hallpass, id, &desc)) {
|
||||
return false; // Error
|
||||
}
|
||||
if (!desc.object() || !desc.enumerable())
|
||||
return false;
|
||||
|
||||
if (!desc.value().isString()) {
|
||||
EnterAndThrowASCII(cx, wrapper, "property must be a string");
|
||||
return false;
|
||||
}
|
||||
|
||||
JSFlatString* flat = JS_FlattenString(cx, desc.value().toString());
|
||||
if (!flat)
|
||||
return false;
|
||||
|
||||
size_t length = JS_GetStringLength(JS_FORGET_STRING_FLATNESS(flat));
|
||||
|
||||
for (size_t i = 0; i < length; ++i) {
|
||||
char16_t ch = JS_GetFlatStringCharAt(flat, i);
|
||||
switch (ch) {
|
||||
case 'r':
|
||||
if (access & READ) {
|
||||
EnterAndThrowASCII(cx, wrapper, "duplicate 'readable' property flag");
|
||||
return false;
|
||||
}
|
||||
access = Access(access | READ);
|
||||
break;
|
||||
|
||||
case 'w':
|
||||
if (access & WRITE) {
|
||||
EnterAndThrowASCII(cx, wrapper, "duplicate 'writable' property flag");
|
||||
return false;
|
||||
}
|
||||
access = Access(access | WRITE);
|
||||
break;
|
||||
|
||||
default:
|
||||
EnterAndThrowASCII(cx, wrapper, "properties can only be readable or read and writable");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (access == NO_ACCESS) {
|
||||
EnterAndThrowASCII(cx, wrapper, "specified properties must have a permission bit set");
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((act == Wrapper::SET && !(access & WRITE)) ||
|
||||
(act != Wrapper::SET && !(access & READ))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Inspect the property on the underlying object to check for red flags.
|
||||
if (!JS_GetPropertyDescriptorById(cx, wrappedObject, id, &desc))
|
||||
return false;
|
||||
|
||||
// Reject accessor properties.
|
||||
if (desc.hasGetterOrSetter()) {
|
||||
EnterAndThrowASCII(cx, wrapper, "Exposing privileged accessor properties is prohibited");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reject privileged or cross-origin callables.
|
||||
if (desc.value().isObject()) {
|
||||
RootedObject maybeCallable(cx, js::UncheckedUnwrap(&desc.value().toObject()));
|
||||
if (JS::IsCallable(maybeCallable) && !AccessCheck::subsumes(wrapper, maybeCallable)) {
|
||||
EnterAndThrowASCII(cx, wrapper, "Exposing privileged or cross-origin callable is prohibited");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ExposedPropertiesOnly::deny(JSContext* cx, js::Wrapper::Action act, HandleId id,
|
||||
bool mayThrow)
|
||||
|
|
|
|||
|
|
@ -104,10 +104,15 @@ struct CrossOriginAccessiblePropertiesOnly : public Policy {
|
|||
}
|
||||
};
|
||||
|
||||
// This policy only permits access to properties if they appear in the
|
||||
// objects exposed properties list.
|
||||
// This class used to support permitting access to properties if they
|
||||
// appeared in an access list on the object, but now it acts like an
|
||||
// Opaque wrapper, with the exception that it fails silently for GET,
|
||||
// ENUMERATE, and GET_PROPERTY_DESCRIPTOR. This is done for backwards
|
||||
// compatibility. See bug 1397513.
|
||||
struct ExposedPropertiesOnly : public Policy {
|
||||
static bool check(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, js::Wrapper::Action act);
|
||||
static bool check(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, js::Wrapper::Action act) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool deny(JSContext* cx, js::Wrapper::Action act, JS::HandleId id,
|
||||
bool mayThrow);
|
||||
|
|
|
|||
|
|
@ -16,9 +16,9 @@ namespace xpc {
|
|||
struct ExposedPropertiesOnly;
|
||||
|
||||
// When a vanilla chrome JS object is exposed to content, we use a wrapper that
|
||||
// supports __exposedProps__ for legacy reasons. For extra security, we override
|
||||
// the traps that allow content to pass an object to chrome, and perform extra
|
||||
// security checks on them.
|
||||
// fails silently on GET, ENUMERATE, and GET_PROPERTY_DESCRIPTOR for legacy
|
||||
// reasons. For extra security, we override the traps that allow content to pass
|
||||
// an object to chrome, and perform extra security checks on them.
|
||||
#define ChromeObjectWrapperBase \
|
||||
FilteringWrapper<js::CrossCompartmentSecurityWrapper, ExposedPropertiesOnly>
|
||||
|
||||
|
|
|
|||
|
|
@ -514,8 +514,8 @@ WrapperFactory::Rewrap(JSContext* cx, HandleObject existing, HandleObject obj)
|
|||
wrapper = &FilteringWrapper<CrossCompartmentSecurityWrapper, OpaqueWithCall>::singleton;
|
||||
}
|
||||
|
||||
// For Vanilla JSObjects exposed from chrome to content, we use a wrapper
|
||||
// that supports __exposedProps__. We'd like to get rid of these eventually,
|
||||
// For vanilla JSObjects exposed from chrome to content, we use a wrapper
|
||||
// that fails silently in a few cases. We'd like to get rid of this eventually,
|
||||
// but in their current form they don't cause much trouble.
|
||||
else if (IdentifyStandardInstance(obj) == JSProto_Object) {
|
||||
wrapper = &ChromeObjectWrapper::singleton;
|
||||
|
|
|
|||
|
|
@ -291,7 +291,7 @@ ReportWrapperDenial(JSContext* cx, HandleId id, WrapperDenialType type, const ch
|
|||
MOZ_ASSERT(type == WrapperDenialForCOW);
|
||||
errorMessage.emplace("Security wrapper denied access to property %s on privileged "
|
||||
"Javascript object. Support for exposing privileged objects "
|
||||
"to untrusted content via __exposedProps__ is being gradually "
|
||||
"to untrusted content via __exposedProps__ has been "
|
||||
"removed - use WebIDL bindings or Components.utils.cloneInto "
|
||||
"instead. Note that only the first denied property access from a "
|
||||
"given global object will be reported.",
|
||||
|
|
|
|||
|
|
@ -104,15 +104,6 @@ ChromePowers.prototype.executeAfterFlushingMessageQueue = function(aCallback) {
|
|||
aCallback();
|
||||
};
|
||||
|
||||
// Expose everything but internal APIs (starting with underscores) to
|
||||
// web content. We cannot use Object.keys to view SpecialPowers.prototype since
|
||||
// we are using the functions from SpecialPowersAPI.prototype
|
||||
ChromePowers.prototype.__exposedProps__ = {};
|
||||
for (var i in ChromePowers.prototype) {
|
||||
if (i.charAt(0) != "_")
|
||||
ChromePowers.prototype.__exposedProps__[i] = "r";
|
||||
}
|
||||
|
||||
if ((window.parent !== null) &&
|
||||
(window.parent !== undefined) &&
|
||||
(window.parent.wrappedJSObject.SpecialPowers) &&
|
||||
|
|
|
|||
|
|
@ -83,15 +83,3 @@ MockPermissionPromptInstance.prototype = {
|
|||
request.allow();
|
||||
}
|
||||
};
|
||||
|
||||
// Expose everything to content. We call reset() here so that all of the relevant
|
||||
// lazy expandos get added.
|
||||
MockPermissionPrompt.reset();
|
||||
function exposeAll(obj) {
|
||||
var props = {};
|
||||
for (var prop in obj)
|
||||
props[prop] = "rw";
|
||||
obj.__exposedProps__ = props;
|
||||
}
|
||||
exposeAll(MockPermissionPrompt);
|
||||
exposeAll(MockPermissionPromptInstance.prototype);
|
||||
|
|
|
|||
|
|
@ -735,7 +735,7 @@ var SandboxParent = {
|
|||
if (rest.length) {
|
||||
// Do a shallow copy of the options object into the child
|
||||
// process. This way we don't have to access it through a Chrome
|
||||
// object wrapper, which would require __exposedProps__.
|
||||
// object wrapper, which would not let us access any properties.
|
||||
//
|
||||
// The only object property here is sandboxPrototype. We assume
|
||||
// it's a child process object (since that's what Greasemonkey
|
||||
|
|
|
|||
Loading…
Reference in a new issue