tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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();