test_xpcwn_tamperproof.js (6425B)
1 // Test that it's not possible to create expando properties on XPCWNs. 2 // See <https://bugzilla.mozilla.org/show_bug.cgi?id=1143810#c5>. 3 4 function TestInterfaceAll() {} 5 TestInterfaceAll.prototype = { 6 QueryInterface: ChromeUtils.generateQI(["nsIXPCTestInterfaceA", 7 "nsIXPCTestInterfaceB", 8 "nsIXPCTestInterfaceC"]), 9 10 /* nsIXPCTestInterfaceA / nsIXPCTestInterfaceB */ 11 name: "TestInterfaceAllDefaultName", 12 13 /* nsIXPCTestInterfaceC */ 14 someInteger: 42 15 }; 16 17 function check_throws(f) { 18 try { 19 f(); 20 } catch (exc) { 21 return; 22 } 23 throw new TypeError("Expected exception, no exception thrown"); 24 } 25 26 /* 27 * Test that XPCWrappedNative objects do not permit expando properties to be created. 28 * 29 * This function is called twice. The first time, realObj is an nsITimer XPCWN 30 * and accessObj === realObj. 31 * 32 * The second time, accessObj is a scripted proxy with realObj as its target. 33 * So the second time we are testing that scripted proxies don't magically 34 * bypass whatever mechanism enforces the expando policy on XPCWNs. 35 */ 36 function test_tamperproof(realObj, accessObj, {method, constant, attribute}) { 37 // Assignment can't create an expando property. 38 check_throws(function () { accessObj.expando = 14; }); 39 Assert.equal(false, "expando" in realObj); 40 41 // Strict assignment throws. 42 check_throws(function () { "use strict"; accessObj.expando = 14; }); 43 Assert.equal(false, "expando" in realObj); 44 45 // Assignment to an inherited method name doesn't work either. 46 check_throws(function () { accessObj.hasOwnProperty = () => "lies"; }); 47 check_throws(function () { "use strict"; accessObj.hasOwnProperty = () => "lies"; }); 48 Assert.ok(!realObj.hasOwnProperty("hasOwnProperty")); 49 50 // Assignment to a method name doesn't work either. 51 let originalMethod; 52 if (method) { 53 originalMethod = accessObj[method]; 54 accessObj[method] = "nope"; // non-writable data property, no exception in non-strict code 55 check_throws(function () { "use strict"; accessObj[method] = "nope"; }); 56 Assert.ok(realObj[method] === originalMethod); 57 } 58 59 // A constant is the same thing. 60 let originalConstantValue; 61 if (constant) { 62 originalConstantValue = accessObj[constant]; 63 accessObj[constant] = "nope"; 64 Assert.equal(realObj[constant], originalConstantValue); 65 check_throws(function () { "use strict"; accessObj[constant] = "nope"; }); 66 Assert.equal(realObj[constant], originalConstantValue); 67 } 68 69 // Assignment to a readonly accessor property with no setter doesn't work either. 70 let originalAttributeDesc; 71 if (attribute) { 72 originalAttributeDesc = Object.getOwnPropertyDescriptor(realObj, attribute); 73 Assert.ok("set" in originalAttributeDesc); 74 Assert.ok(originalAttributeDesc.set === undefined); 75 76 accessObj[attribute] = "nope"; // accessor property with no setter: no exception in non-strict code 77 check_throws(function () { "use strict"; accessObj[attribute] = "nope"; }); 78 79 let desc = Object.getOwnPropertyDescriptor(realObj, attribute); 80 Assert.ok("set" in desc); 81 Assert.equal(originalAttributeDesc.get, desc.get); 82 Assert.equal(undefined, desc.set); 83 } 84 85 // Reflect.set doesn't work either. 86 if (method) { 87 Assert.ok(!Reflect.set({}, method, "bad", accessObj)); 88 Assert.equal(realObj[method], originalMethod); 89 } 90 if (attribute) { 91 Assert.ok(!Reflect.set({}, attribute, "bad", accessObj)); 92 Assert.equal(originalAttributeDesc.get, Object.getOwnPropertyDescriptor(realObj, attribute).get); 93 } 94 95 // Object.defineProperty can't do anything either. 96 let names = ["expando"]; 97 if (method) names.push(method); 98 if (constant) names.push(constant); 99 if (attribute) names.push(attribute); 100 for (let name of names) { 101 let originalDesc = Object.getOwnPropertyDescriptor(realObj, name); 102 check_throws(function () { 103 Object.defineProperty(accessObj, name, {configurable: true}); 104 }); 105 check_throws(function () { 106 Object.defineProperty(accessObj, name, {writable: true}); 107 }); 108 check_throws(function () { 109 Object.defineProperty(accessObj, name, {get: function () { return "lies"; }}); 110 }); 111 check_throws(function () { 112 Object.defineProperty(accessObj, name, {value: "bad"}); 113 }); 114 let desc = Object.getOwnPropertyDescriptor(realObj, name); 115 if (originalDesc === undefined) { 116 Assert.equal(undefined, desc); 117 } else { 118 Assert.equal(originalDesc.configurable, desc.configurable); 119 Assert.equal(originalDesc.enumerable, desc.enumerable); 120 Assert.equal(originalDesc.writable, desc.writable); 121 Assert.equal(originalDesc.value, desc.value); 122 Assert.equal(originalDesc.get, desc.get); 123 Assert.equal(originalDesc.set, desc.set); 124 } 125 } 126 127 // Existing properties can't be deleted. 128 if (method) { 129 Assert.equal(false, delete accessObj[method]); 130 check_throws(function () { "use strict"; delete accessObj[method]; }); 131 Assert.equal(realObj[method], originalMethod); 132 } 133 if (constant) { 134 Assert.equal(false, delete accessObj[constant]); 135 check_throws(function () { "use strict"; delete accessObj[constant]; }); 136 Assert.equal(realObj[constant], originalConstantValue); 137 } 138 if (attribute) { 139 Assert.equal(false, delete accessObj[attribute]); 140 check_throws(function () { "use strict"; delete accessObj[attribute]; }); 141 desc = Object.getOwnPropertyDescriptor(realObj, attribute); 142 Assert.equal(originalAttributeDesc.get, desc.get); 143 } 144 } 145 146 function test_twice(obj, options) { 147 test_tamperproof(obj, obj, options); 148 149 let handler = { 150 getPrototypeOf(t) { 151 return new Proxy(Object.getPrototypeOf(t), handler); 152 } 153 }; 154 test_tamperproof(obj, new Proxy(obj, handler), options); 155 } 156 157 function run_test() { 158 let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); 159 test_twice(timer, { 160 method: "init", 161 constant: "TYPE_ONE_SHOT", 162 attribute: "callback" 163 }); 164 165 let cmdline = Cu.createCommandLine([], null, Ci.nsICommandLine.STATE_INITIAL_LAUNCH); 166 test_twice(cmdline, {}); 167 168 test_twice(Object.getPrototypeOf(cmdline), { 169 method: "getArgument", 170 constant: "STATE_INITIAL_LAUNCH", 171 attribute: "length" 172 }); 173 174 // Test a tearoff object. 175 let b = xpcWrap(new TestInterfaceAll(), Ci.nsIXPCTestInterfaceB); 176 let tearoff = b.nsIXPCTestInterfaceA; 177 test_twice(tearoff, { 178 method: "QueryInterface" 179 }); 180 }