summaryrefslogtreecommitdiffstats
path: root/js/xpconnect/tests/unit/test_xpcwn_tamperproof.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/xpconnect/tests/unit/test_xpcwn_tamperproof.js')
-rw-r--r--js/xpconnect/tests/unit/test_xpcwn_tamperproof.js171
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"
+ });
+}