test_objectgrips-17.js (10854B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true); 7 8 registerCleanupFunction(() => { 9 Services.prefs.clearUserPref("security.allow_eval_with_system_principal"); 10 }); 11 12 async function testPrincipal(options, globalPrincipal, debuggeeHasXrays) { 13 const { debuggee } = options; 14 // Create a global object with the specified security principal. 15 // If none is specified, use the debuggee. 16 if (globalPrincipal === undefined) { 17 await test(options, { 18 global: debuggee, 19 subsumes: true, 20 isOpaque: false, 21 globalIsInvisible: false, 22 }); 23 return; 24 } 25 26 const debuggeePrincipal = Cu.getObjectPrincipal(debuggee); 27 const sameOrigin = debuggeePrincipal.origin === globalPrincipal.origin; 28 const subsumes = debuggeePrincipal.subsumes(globalPrincipal); 29 for (const globalHasXrays of [true, false]) { 30 const isOpaque = 31 subsumes && 32 globalPrincipal !== systemPrincipal && 33 ((sameOrigin && debuggeeHasXrays) || globalHasXrays); 34 for (const globalIsInvisible of [true, false]) { 35 let global = Cu.Sandbox(globalPrincipal, { 36 wantXrays: globalHasXrays, 37 invisibleToDebugger: globalIsInvisible, 38 }); 39 // Previously, the Sandbox constructor would (bizarrely) waive xrays on 40 // the return Sandbox if wantXrays was false. This has now been fixed, 41 // but we need to mimic that behavior here to make the test continue 42 // to pass. 43 if (!globalHasXrays) { 44 global = Cu.waiveXrays(global); 45 } 46 await test(options, { global, subsumes, isOpaque, globalIsInvisible }); 47 } 48 } 49 } 50 51 async function test({ threadFront, debuggee }, testOptions) { 52 const { global } = testOptions; 53 const packet = await executeOnNextTickAndWaitForPause(eval_code, threadFront); 54 // Get the grips. 55 const [proxyGrip, inheritsProxyGrip, inheritsProxy2Grip] = 56 packet.frame.arguments; 57 58 // Check the grip of the proxy object. 59 check_proxy_grip(debuggee, testOptions, proxyGrip); 60 61 // Check the target and handler slots of the proxy object. 62 const proxyClient = threadFront.pauseGrip(proxyGrip); 63 const proxySlots = await proxyClient.getProxySlots(); 64 check_proxy_slots(debuggee, testOptions, proxyGrip, proxySlots); 65 66 // Check the prototype and properties of the proxy object. 67 const proxyResponse = await proxyClient.getPrototypeAndProperties(); 68 check_properties(testOptions, proxyResponse.ownProperties, true, false); 69 check_prototype(debuggee, testOptions, proxyResponse.prototype, true, false); 70 71 // Check the prototype and properties of the object which inherits from the proxy. 72 const inheritsProxyClient = threadFront.pauseGrip(inheritsProxyGrip); 73 const inheritsProxyResponse = 74 await inheritsProxyClient.getPrototypeAndProperties(); 75 check_properties( 76 testOptions, 77 inheritsProxyResponse.ownProperties, 78 false, 79 false 80 ); 81 check_prototype( 82 debuggee, 83 testOptions, 84 inheritsProxyResponse.prototype, 85 false, 86 false 87 ); 88 89 // The prototype chain was not iterated if the object was inaccessible, so now check 90 // another object which inherits from the proxy, but was created in the debuggee. 91 const inheritsProxy2Client = threadFront.pauseGrip(inheritsProxy2Grip); 92 const inheritsProxy2Response = 93 await inheritsProxy2Client.getPrototypeAndProperties(); 94 check_properties( 95 testOptions, 96 inheritsProxy2Response.ownProperties, 97 false, 98 true 99 ); 100 check_prototype( 101 debuggee, 102 testOptions, 103 inheritsProxy2Response.prototype, 104 false, 105 true 106 ); 107 108 // Check that none of the above ran proxy traps. 109 strictEqual(global.trapDidRun, false, "No proxy trap did run."); 110 111 // Resume the debugger and finish the current test. 112 await threadFront.resume(); 113 114 function eval_code() { 115 // Create objects in `global`, and debug them in `debuggee`. They may get various 116 // kinds of security wrappers, or no wrapper at all. 117 // To detect that no proxy trap runs, the proxy handler should define all possible 118 // traps, but the list is long and may change. Therefore a second proxy is used as 119 // the handler, so that a single `get` trap suffices. 120 global.eval(` 121 var trapDidRun = false; 122 var proxy = new Proxy({}, new Proxy({}, {get: (_, trap) => { 123 trapDidRun = true; 124 throw new Error("proxy trap '" + trap + "' was called."); 125 }})); 126 var inheritsProxy = Object.create(proxy, {x:{value:1}}); 127 `); 128 const data = Cu.createObjectIn(debuggee, { defineAs: "data" }); 129 data.proxy = global.proxy; 130 data.inheritsProxy = global.inheritsProxy; 131 debuggee.eval(` 132 var inheritsProxy2 = Object.create(data.proxy, {x:{value:1}}); 133 stopMe(data.proxy, data.inheritsProxy, inheritsProxy2); 134 `); 135 } 136 } 137 138 function check_proxy_grip(debuggee, testOptions, grip) { 139 const { global, isOpaque, subsumes, globalIsInvisible } = testOptions; 140 const { preview } = grip; 141 142 if (global === debuggee) { 143 // The proxy has no security wrappers. 144 strictEqual(grip.class, "Proxy", "The grip has a Proxy class."); 145 strictEqual( 146 preview.ownPropertiesLength, 147 2, 148 "The preview has 2 properties." 149 ); 150 const props = preview.ownProperties; 151 ok(props["<target>"].value, "<target> contains the [[ProxyTarget]]."); 152 ok(props["<handler>"].value, "<handler> contains the [[ProxyHandler]]."); 153 } else if (isOpaque) { 154 // The proxy has opaque security wrappers. 155 strictEqual(grip.class, "Opaque", "The grip has an Opaque class."); 156 strictEqual(grip.ownPropertyLength, 0, "The grip has no properties."); 157 } else if (!subsumes) { 158 // The proxy belongs to compartment not subsumed by the debuggee. 159 strictEqual(grip.class, "Restricted", "The grip has a Restricted class."); 160 strictEqual( 161 grip.ownPropertyLength, 162 undefined, 163 "The grip doesn't know the number of properties." 164 ); 165 } else if (globalIsInvisible) { 166 // The proxy belongs to an invisible-to-debugger compartment. 167 strictEqual( 168 grip.class, 169 "InvisibleToDebugger: Object", 170 "The grip has an InvisibleToDebugger class." 171 ); 172 ok( 173 !("ownPropertyLength" in grip), 174 "The grip doesn't know the number of properties." 175 ); 176 } else { 177 // The proxy has non-opaque security wrappers. 178 strictEqual(grip.class, "Proxy", "The grip has a Proxy class."); 179 strictEqual( 180 preview.ownPropertiesLength, 181 0, 182 "The preview has no properties." 183 ); 184 ok(!("<target>" in preview), "The preview has no <target> property."); 185 ok(!("<handler>" in preview), "The preview has no <handler> property."); 186 } 187 } 188 189 function check_proxy_slots(debuggee, testOptions, grip, proxySlots) { 190 const { global } = testOptions; 191 192 if (grip.class !== "Proxy") { 193 strictEqual( 194 proxySlots, 195 null, 196 "Slots can only be retrived for Proxy grips." 197 ); 198 } else if (global === debuggee) { 199 const { proxyTarget, proxyHandler } = proxySlots; 200 strictEqual( 201 proxyTarget.getGrip().type, 202 "object", 203 "There is a [[ProxyTarget]] grip." 204 ); 205 strictEqual( 206 proxyHandler.getGrip().type, 207 "object", 208 "There is a [[ProxyHandler]] grip." 209 ); 210 } else { 211 const { proxyTarget, proxyHandler } = proxySlots; 212 strictEqual( 213 proxyTarget.type, 214 "undefined", 215 "There is no [[ProxyTarget]] grip." 216 ); 217 strictEqual( 218 proxyHandler.type, 219 "undefined", 220 "There is no [[ProxyHandler]] grip." 221 ); 222 } 223 } 224 225 function check_properties(testOptions, props, isProxy, createdInDebuggee) { 226 const { subsumes, globalIsInvisible } = testOptions; 227 const ownPropertiesLength = Reflect.ownKeys(props).length; 228 229 if (createdInDebuggee || (!isProxy && subsumes && !globalIsInvisible)) { 230 // The debuggee can access the properties. 231 strictEqual(ownPropertiesLength, 1, "1 own property was retrieved."); 232 strictEqual(props.x.value, 1, "The property has the right value."); 233 } else { 234 // The debuggee is not allowed to access the object. 235 strictEqual(ownPropertiesLength, 0, "No own property could be retrieved."); 236 } 237 } 238 239 function check_prototype( 240 debuggee, 241 testOptions, 242 proto, 243 isProxy, 244 createdInDebuggee 245 ) { 246 const { global, isOpaque, subsumes, globalIsInvisible } = testOptions; 247 if (isOpaque && !globalIsInvisible && !createdInDebuggee) { 248 // The object is or inherits from a proxy with opaque security wrappers. 249 // The debuggee sees `Object.prototype` when retrieving the prototype. 250 strictEqual( 251 proto.getGrip().class, 252 "Object", 253 "The prototype has a Object class." 254 ); 255 } else if (isProxy && isOpaque && globalIsInvisible) { 256 // The object is a proxy with opaque security wrappers in an invisible global. 257 // The debuggee sees an inaccessible `Object.prototype` when retrieving the prototype. 258 strictEqual( 259 proto.getGrip().class, 260 "InvisibleToDebugger: Object", 261 "The prototype has an InvisibleToDebugger class." 262 ); 263 } else if ( 264 createdInDebuggee || 265 (!isProxy && subsumes && !globalIsInvisible) 266 ) { 267 // The object inherits from a proxy and has no security wrappers or non-opaque ones. 268 // The debuggee sees the proxy when retrieving the prototype. 269 check_proxy_grip( 270 debuggee, 271 { global, isOpaque, subsumes, globalIsInvisible }, 272 proto.getGrip() 273 ); 274 } else { 275 // The debuggee is not allowed to access the object. It sees a null prototype. 276 strictEqual(proto.type, "null", "The prototype is null."); 277 } 278 } 279 280 function createNullPrincipal() { 281 return Services.scriptSecurityManager.createNullPrincipal({}); 282 } 283 284 async function run_tests_in_principal( 285 options, 286 debuggeePrincipal, 287 debuggeeHasXrays 288 ) { 289 const { debuggee } = options; 290 debuggee.eval( 291 // These arguments are tested. 292 // eslint-disable-next-line no-unused-vars 293 function stopMe(arg1, arg2) { 294 debugger; 295 }.toString() 296 ); 297 298 // Test objects created in the debuggee. 299 await testPrincipal(options, undefined, debuggeeHasXrays); 300 301 // Test objects created in a system principal new global. 302 await testPrincipal(options, systemPrincipal, debuggeeHasXrays); 303 304 // Test objects created in a cross-origin null principal new global. 305 await testPrincipal(options, createNullPrincipal(), debuggeeHasXrays); 306 307 if (debuggeePrincipal != systemPrincipal) { 308 // Test objects created in a same-origin principal new global. 309 await testPrincipal(options, debuggeePrincipal, debuggeeHasXrays); 310 } 311 } 312 313 for (const principal of [systemPrincipal, createNullPrincipal()]) { 314 for (const wantXrays of [true, false]) { 315 add_task( 316 threadFrontTest( 317 options => run_tests_in_principal(options, principal, wantXrays), 318 { principal, wantXrays } 319 ) 320 ); 321 } 322 }