Frame-eval-csp-bypass.js (5546B)
1 // Test that debugger evaluation can bypass CSP restrictions when bypassCSP option is set. 2 3 var g = newGlobal({newCompartment: true}); 4 var dbg = new Debugger(g); 5 6 // Create a function to trigger a debugger statement before enabling CSP. 7 g.eval("function triggerDebugger() { debugger; }"); 8 9 // Create functions that will use eval/new Function and which will be called 10 // from Frame.eval. 11 g.eval("function triggerEval() { return eval('2 + 2'); }"); 12 g.eval("function triggerNewFunction() { return new Function('return 2 + 2')(); }"); 13 14 const EXPECTED_VALUE = 4; 15 function assertSuccess(expression, options = {}) { 16 let evaluated = false; 17 dbg.onDebuggerStatement = function (frame) { 18 assertEq(frame.eval(expression, options).return, EXPECTED_VALUE); 19 evaluated = true; 20 }; 21 g.triggerDebugger(); 22 assertEq(evaluated, true); 23 } 24 25 function assertThrows(expression, options = {}) { 26 let evaluated = false; 27 dbg.onDebuggerStatement = function (frame) { 28 assertEq(frame.eval(expression, options).throw !== undefined, true); 29 evaluated = true; 30 }; 31 g.triggerDebugger(); 32 assertEq(evaluated, true); 33 } 34 35 // Smoke test without enabling CSP 36 37 // evaluation without eval/new Function always succeeds 38 assertSuccess("2 + 2"); 39 assertSuccess("2 + 2", { bypassCSP: false }); 40 assertSuccess("2 + 2", { bypassCSP: true }); 41 42 // Default value for bypassCSP 43 assertSuccess("eval('2 + 2')"); 44 assertSuccess("new Function('return 2 + 2')()"); 45 assertSuccess("triggerEval()"); 46 assertSuccess("triggerNewFunction()"); 47 48 // bypassCSP=false 49 assertSuccess("eval('2 + 2')", { bypassCSP: false }); 50 assertSuccess("new Function('return 2 + 2')()", { bypassCSP: false }); 51 assertSuccess("triggerEval()", { bypassCSP: false }); 52 assertSuccess("triggerNewFunction()", { bypassCSP: false }); 53 54 // bypassCSP=true 55 assertSuccess("eval('2 + 2')", { bypassCSP: true }); 56 assertSuccess("new Function('return 2 + 2')()", { bypassCSP: true }); 57 assertSuccess("triggerEval()", { bypassCSP: true }); 58 assertSuccess("triggerNewFunction()", { bypassCSP: true }); 59 60 // Enable CSP 61 setCSPEnabled(true); 62 63 // evaluation without eval/new Function always succeeds 64 assertSuccess("2 + 2"); 65 assertSuccess("2 + 2", { bypassCSP: false }); 66 assertSuccess("2 + 2", { bypassCSP: true }); 67 68 // Default value for bypassCSP, should all fail 69 assertThrows("eval('2 + 2')"); 70 assertThrows("new Function('return 2 + 2')()"); 71 assertThrows("triggerEval()"); 72 assertThrows("triggerNewFunction()"); 73 74 // bypassCSP=false, should all fail 75 assertThrows("eval('2 + 2')", { bypassCSP: false }); 76 assertThrows("new Function('return 2 + 2')()", { bypassCSP: false }); 77 assertThrows("triggerEval()", { bypassCSP: false }); 78 assertThrows("triggerNewFunction()", { bypassCSP: false }); 79 80 // bypassCSP=true, should all succeed 81 assertSuccess("eval('2 + 2')", { bypassCSP: true }); 82 assertSuccess("new Function('return 2 + 2')()", { bypassCSP: true }); 83 assertSuccess("triggerEval()", { bypassCSP: true }); 84 assertSuccess("triggerNewFunction()", { bypassCSP: true }); 85 86 // Define a few functions using eval with and without bypassCSP. 87 // They should both behave the same when triggered from a later Frame.eval, and 88 // only the bypassCSP flag for the later Frame.eval should matter. 89 dbg.onDebuggerStatement = function (frame) { 90 frame.eval("globalThis.fnWithBypass = () => eval('2 + 2')", { bypassCSP: true }); 91 frame.eval("globalThis.fnWithoutBypass = () => eval('2 + 2')", { bypassCSP: false }); 92 }; 93 g.triggerDebugger(); 94 95 // Call the function defined with bypassCSP=true, should only work when 96 // bypassCSP is true for the current Frame.eval. 97 assertThrows("globalThis.fnWithBypass()"); 98 assertThrows("globalThis.fnWithBypass()", { bypassCSP: false }); 99 assertSuccess("globalThis.fnWithBypass()", { bypassCSP: true }); 100 101 // Call the function defined with bypassCSP=false, should only work when 102 // bypassCSP is true for the current Frame.eval. 103 assertThrows("globalThis.fnWithoutBypass()"); 104 assertThrows("globalThis.fnWithoutBypass()", { bypassCSP: false }); 105 assertSuccess("globalThis.fnWithoutBypass()", { bypassCSP: true }); 106 107 // Test for async frames. 108 const asyncEvalExpression = `(async function() { 109 try { 110 globalThis.evalBeforeAwaitSuccess = false; 111 globalThis.evalAfterAwaitSuccess = false; 112 eval("1 + 1"); 113 globalThis.evalBeforeAwaitSuccess = true; 114 await 1; 115 eval("2 + 2"); 116 globalThis.evalAfterAwaitSuccess = true; 117 } catch {} 118 })()`; 119 120 // Test async code with bypassCSP=true, only the eval before the await should be 121 // successful. 122 dbg.onDebuggerStatement = function (frame) { 123 frame.onPop = completion => { 124 frame.eval(asyncEvalExpression, { bypassCSP: true }); 125 dbg.removeDebuggee(g); // avoid the DebuggeeWouldRun exception 126 drainJobQueue(); 127 dbg.addDebuggee(g); 128 } 129 }; 130 g.triggerDebugger(); 131 132 dbg.onDebuggerStatement = function (frame) { 133 assertEq(frame.eval("globalThis.evalBeforeAwaitSuccess", options).return, true); 134 assertEq(frame.eval("globalThis.evalAfterAwaitSuccess", options).return, false); 135 }; 136 g.triggerDebugger(); 137 138 // Test async code with bypassCSP=false, all eval should fail. 139 dbg.onDebuggerStatement = function (frame) { 140 frame.onPop = completion => { 141 frame.eval(asyncEvalExpression, { bypassCSP: false }); 142 dbg.removeDebuggee(g); // avoid the DebuggeeWouldRun exception 143 drainJobQueue(); 144 dbg.addDebuggee(g); 145 } 146 }; 147 g.triggerDebugger(); 148 drainJobQueue(); 149 150 dbg.onDebuggerStatement = function (frame) { 151 assertEq(frame.eval("globalThis.evalBeforeAwaitSuccess", options).return, false); 152 assertEq(frame.eval("globalThis.evalAfterAwaitSuccess", options).return, false); 153 }; 154 g.triggerDebugger();