diff options
Diffstat (limited to 'js/xpconnect/tests/unit/test_xpcwn_tamperproof.js')
-rw-r--r-- | js/xpconnect/tests/unit/test_xpcwn_tamperproof.js | 171 |
1 files changed, 171 insertions, 0 deletions
diff --git a/js/xpconnect/tests/unit/test_xpcwn_tamperproof.js b/js/xpconnect/tests/unit/test_xpcwn_tamperproof.js new file mode 100644 index 000000000..bf7b65927 --- /dev/null +++ b/js/xpconnect/tests/unit/test_xpcwn_tamperproof.js @@ -0,0 +1,171 @@ +// Test that it's not possible to create expando properties on XPCWNs. +// See <https://bugzilla.mozilla.org/show_bug.cgi?id=1143810#c5>. + +const Cc = Components.classes; +const Ci = Components.interfaces; + +function check_throws(f) { + try { + f(); + } catch (exc) { + return; + } + throw new TypeError("Expected exception, no exception thrown"); +} + +/* + * Test that XPCWrappedNative objects do not permit expando properties to be created. + * + * This function is called twice. The first time, realObj is an nsITimer XPCWN + * and accessObj === realObj. + * + * The second time, accessObj is a scripted proxy with realObj as its target. + * So the second time we are testing that scripted proxies don't magically + * bypass whatever mechanism enforces the expando policy on XPCWNs. + */ +function test_tamperproof(realObj, accessObj, {method, constant, attribute}) { + // Assignment can't create an expando property. + check_throws(function () { accessObj.expando = 14; }); + do_check_false("expando" in realObj); + + // Strict assignment throws. + check_throws(function () { "use strict"; accessObj.expando = 14; }); + do_check_false("expando" in realObj); + + // Assignment to an inherited method name doesn't work either. + check_throws(function () { accessObj.hasOwnProperty = () => "lies"; }); + check_throws(function () { "use strict"; accessObj.hasOwnProperty = () => "lies"; }); + do_check_false(realObj.hasOwnProperty("hasOwnProperty")); + + // Assignment to a method name doesn't work either. + let originalMethod; + if (method) { + originalMethod = accessObj[method]; + accessObj[method] = "nope"; // non-writable data property, no exception in non-strict code + check_throws(function () { "use strict"; accessObj[method] = "nope"; }); + do_check_true(realObj[method] === originalMethod); + } + + // A constant is the same thing. + let originalConstantValue; + if (constant) { + originalConstantValue = accessObj[constant]; + accessObj[constant] = "nope"; + do_check_eq(realObj[constant], originalConstantValue); + check_throws(function () { "use strict"; accessObj[constant] = "nope"; }); + do_check_eq(realObj[constant], originalConstantValue); + } + + // Assignment to a readonly accessor property with no setter doesn't work either. + let originalAttributeDesc; + if (attribute) { + originalAttributeDesc = Object.getOwnPropertyDescriptor(realObj, attribute); + do_check_true("set" in originalAttributeDesc); + do_check_true(originalAttributeDesc.set === undefined); + + accessObj[attribute] = "nope"; // accessor property with no setter: no exception in non-strict code + check_throws(function () { "use strict"; accessObj[attribute] = "nope"; }); + + let desc = Object.getOwnPropertyDescriptor(realObj, attribute); + do_check_true("set" in desc); + do_check_eq(originalAttributeDesc.get, desc.get); + do_check_eq(undefined, desc.set); + } + + // Reflect.set doesn't work either. + if (method) { + do_check_false(Reflect.set({}, method, "bad", accessObj)); + do_check_eq(realObj[method], originalMethod); + } + if (attribute) { + do_check_false(Reflect.set({}, attribute, "bad", accessObj)); + do_check_eq(originalAttributeDesc.get, Object.getOwnPropertyDescriptor(realObj, attribute).get); + } + + // Object.defineProperty can't do anything either. + let names = ["expando"]; + if (method) names.push(method); + if (constant) names.push(constant); + if (attribute) names.push(attribute); + for (let name of names) { + let originalDesc = Object.getOwnPropertyDescriptor(realObj, name); + check_throws(function () { + Object.defineProperty(accessObj, name, {configurable: true}); + }); + check_throws(function () { + Object.defineProperty(accessObj, name, {writable: true}); + }); + check_throws(function () { + Object.defineProperty(accessObj, name, {get: function () { return "lies"; }}); + }); + check_throws(function () { + Object.defineProperty(accessObj, name, {value: "bad"}); + }); + let desc = Object.getOwnPropertyDescriptor(realObj, name); + if (originalDesc === undefined) { + do_check_eq(undefined, desc); + } else { + do_check_eq(originalDesc.configurable, desc.configurable); + do_check_eq(originalDesc.enumerable, desc.enumerable); + do_check_eq(originalDesc.writable, desc.writable); + do_check_eq(originalDesc.value, desc.value); + do_check_eq(originalDesc.get, desc.get); + do_check_eq(originalDesc.set, desc.set); + } + } + + // Existing properties can't be deleted. + if (method) { + do_check_false(delete accessObj[method]); + check_throws(function () { "use strict"; delete accessObj[method]; }); + do_check_eq(realObj[method], originalMethod); + } + if (constant) { + do_check_false(delete accessObj[constant]); + check_throws(function () { "use strict"; delete accessObj[constant]; }); + do_check_eq(realObj[constant], originalConstantValue); + } + if (attribute) { + do_check_false(delete accessObj[attribute]); + check_throws(function () { "use strict"; delete accessObj[attribute]; }); + desc = Object.getOwnPropertyDescriptor(realObj, attribute); + do_check_eq(originalAttributeDesc.get, desc.get); + } +} + +function test_twice(obj, options) { + test_tamperproof(obj, obj, options); + + let handler = { + getPrototypeOf(t) { + return new Proxy(Object.getPrototypeOf(t), handler); + } + }; + test_tamperproof(obj, new Proxy(obj, handler), options); +} + +function run_test() { + let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + test_twice(timer, { + method: "init", + constant: "TYPE_ONE_SHOT", + attribute: "callback" + }); + + let principal = Cc["@mozilla.org/nullprincipal;1"].createInstance(Ci.nsIPrincipal); + test_twice(principal, {}); + + test_twice(Object.getPrototypeOf(principal), { + method: "subsumes", + constant: "APP_STATUS_INSTALLED", + attribute: "origin" + }); + + // Test a tearoff object. + Components.manager.autoRegister(do_get_file('../components/js/xpctest.manifest')); + let b = Cc["@mozilla.org/js/xpc/test/js/TestInterfaceAll;1"].createInstance(Ci.nsIXPCTestInterfaceB); + let tearoff = b.nsIXPCTestInterfaceA; + test_twice(tearoff, { + method: "QueryInterface" + }); +} |