diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /js/xpconnect/tests/unit/test_xpcwn_tamperproof.js | |
parent | Initial commit. (diff) | |
download | firefox-upstream/124.0.1.tar.xz firefox-upstream/124.0.1.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/xpconnect/tests/unit/test_xpcwn_tamperproof.js')
-rw-r--r-- | js/xpconnect/tests/unit/test_xpcwn_tamperproof.js | 180 |
1 files changed, 180 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 0000000000..62d57533fa --- /dev/null +++ b/js/xpconnect/tests/unit/test_xpcwn_tamperproof.js @@ -0,0 +1,180 @@ +// Test that it's not possible to create expando properties on XPCWNs. +// See <https://bugzilla.mozilla.org/show_bug.cgi?id=1143810#c5>. + +function TestInterfaceAll() {} +TestInterfaceAll.prototype = { + QueryInterface: ChromeUtils.generateQI(["nsIXPCTestInterfaceA", + "nsIXPCTestInterfaceB", + "nsIXPCTestInterfaceC"]), + + /* nsIXPCTestInterfaceA / nsIXPCTestInterfaceB */ + name: "TestInterfaceAllDefaultName", + + /* nsIXPCTestInterfaceC */ + someInteger: 42 +}; + +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; }); + Assert.equal(false, "expando" in realObj); + + // Strict assignment throws. + check_throws(function () { "use strict"; accessObj.expando = 14; }); + Assert.equal(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"; }); + Assert.ok(!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"; }); + Assert.ok(realObj[method] === originalMethod); + } + + // A constant is the same thing. + let originalConstantValue; + if (constant) { + originalConstantValue = accessObj[constant]; + accessObj[constant] = "nope"; + Assert.equal(realObj[constant], originalConstantValue); + check_throws(function () { "use strict"; accessObj[constant] = "nope"; }); + Assert.equal(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); + Assert.ok("set" in originalAttributeDesc); + Assert.ok(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); + Assert.ok("set" in desc); + Assert.equal(originalAttributeDesc.get, desc.get); + Assert.equal(undefined, desc.set); + } + + // Reflect.set doesn't work either. + if (method) { + Assert.ok(!Reflect.set({}, method, "bad", accessObj)); + Assert.equal(realObj[method], originalMethod); + } + if (attribute) { + Assert.ok(!Reflect.set({}, attribute, "bad", accessObj)); + Assert.equal(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) { + Assert.equal(undefined, desc); + } else { + Assert.equal(originalDesc.configurable, desc.configurable); + Assert.equal(originalDesc.enumerable, desc.enumerable); + Assert.equal(originalDesc.writable, desc.writable); + Assert.equal(originalDesc.value, desc.value); + Assert.equal(originalDesc.get, desc.get); + Assert.equal(originalDesc.set, desc.set); + } + } + + // Existing properties can't be deleted. + if (method) { + Assert.equal(false, delete accessObj[method]); + check_throws(function () { "use strict"; delete accessObj[method]; }); + Assert.equal(realObj[method], originalMethod); + } + if (constant) { + Assert.equal(false, delete accessObj[constant]); + check_throws(function () { "use strict"; delete accessObj[constant]; }); + Assert.equal(realObj[constant], originalConstantValue); + } + if (attribute) { + Assert.equal(false, delete accessObj[attribute]); + check_throws(function () { "use strict"; delete accessObj[attribute]; }); + desc = Object.getOwnPropertyDescriptor(realObj, attribute); + Assert.equal(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 cmdline = Cu.createCommandLine([], null, Ci.nsICommandLine.STATE_INITIAL_LAUNCH); + test_twice(cmdline, {}); + + test_twice(Object.getPrototypeOf(cmdline), { + method: "getArgument", + constant: "STATE_INITIAL_LAUNCH", + attribute: "length" + }); + + // Test a tearoff object. + let b = xpcWrap(new TestInterfaceAll(), Ci.nsIXPCTestInterfaceB); + let tearoff = b.nsIXPCTestInterfaceA; + test_twice(tearoff, { + method: "QueryInterface" + }); +} |