inspector: Add flag to Runtime.evaluate() for unsafe eval

evaluate() bypassed CSP for unsafe-eval by default. This is a useful
option for debugging clients, but is not always what we want.

e.g. in the devtools console we want to match the page's CSP settings
to make debugging CSP issues on the page easier.

Add a toggle that keeps the current behavior by default.

Bug: chromium:1084558
Change-Id: Ia01142d5be00f8ef5f65e5eeba17549efc6f9120
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2250245
Commit-Queue: Peter Marshall <petermarshall@chromium.org>
Reviewed-by: Simon Zünd <szuend@chromium.org>
Reviewed-by: Yang Guo <yangguo@chromium.org>
Cr-Commit-Position: refs/heads/master@{#68432}
This commit is contained in:
Peter Marshall 2020-06-19 11:33:29 +02:00 committed by Commit Bot
parent c6642b5112
commit f510c66b96
5 changed files with 127 additions and 3 deletions

View File

@ -1370,6 +1370,11 @@ domain Runtime
# Note that `let` variables can only be re-declared if they originate from
# `replMode` themselves.
experimental optional boolean replMode
# The Content Security Policy (CSP) for the target might block 'unsafe-eval'
# which includes eval(), Function(), setTimeout() and setInterval()
# when called with non-callable arguments. This flag bypasses CSP for this
# evaluation and allows unsafe-eval. Defaults to true.
experimental optional boolean allowUnsafeEvalBlockedByCSP
returns
# Evaluation result.
RemoteObject result

View File

@ -237,6 +237,7 @@ void V8RuntimeAgentImpl::evaluate(
Maybe<bool> generatePreview, Maybe<bool> userGesture,
Maybe<bool> maybeAwaitPromise, Maybe<bool> throwOnSideEffect,
Maybe<double> timeout, Maybe<bool> disableBreaks, Maybe<bool> maybeReplMode,
Maybe<bool> allowUnsafeEvalBlockedByCSP,
std::unique_ptr<EvaluateCallback> callback) {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"),
"EvaluateScript");
@ -262,8 +263,10 @@ void V8RuntimeAgentImpl::evaluate(
const bool replMode = maybeReplMode.fromMaybe(false);
// Temporarily enable allow evals for inspector.
scope.allowCodeGenerationFromStrings();
if (allowUnsafeEvalBlockedByCSP.fromMaybe(true)) {
// Temporarily enable allow evals for inspector.
scope.allowCodeGenerationFromStrings();
}
v8::MaybeLocal<v8::Value> maybeResultValue;
{
V8InspectorImpl::EvaluateScope evaluateScope(scope);

View File

@ -68,7 +68,7 @@ class V8RuntimeAgentImpl : public protocol::Runtime::Backend {
Maybe<bool> generatePreview, Maybe<bool> userGesture,
Maybe<bool> awaitPromise, Maybe<bool> throwOnSideEffect,
Maybe<double> timeout, Maybe<bool> disableBreaks,
Maybe<bool> replMode,
Maybe<bool> replMode, Maybe<bool> allowUnsafeEvalBlockedByCSP,
std::unique_ptr<EvaluateCallback>) override;
void awaitPromise(const String16& promiseObjectId, Maybe<bool> returnByValue,
Maybe<bool> generatePreview,

View File

@ -31,6 +31,104 @@ Running test: testEvaluatePaused
}
}
Running test: testEvaluateUnsafeEval
{
id : <messageId>
result : {
result : {
description : 2
type : number
value : 2
}
}
}
{
id : <messageId>
result : {
result : {
description : 2
type : number
value : 2
}
}
}
Running test: testEvaluateUnsafeEvalDisableBypass
{
id : <messageId>
result : {
exceptionDetails : {
columnNumber : 0
exception : {
className : EvalError
description : EvalError: Code generation from strings disallowed for this context at <anonymous>:1:1
objectId : <objectId>
subtype : error
type : object
}
exceptionId : <exceptionId>
lineNumber : 0
scriptId : <scriptId>
stackTrace : {
callFrames : [
[0] : {
columnNumber : 0
functionName :
lineNumber : 0
scriptId : <scriptId>
url :
}
]
}
text : Uncaught
}
result : {
className : EvalError
description : EvalError: Code generation from strings disallowed for this context at <anonymous>:1:1
objectId : <objectId>
subtype : error
type : object
}
}
}
{
id : <messageId>
result : {
exceptionDetails : {
columnNumber : 0
exception : {
className : EvalError
description : EvalError: Code generation from strings disallowed for this context at new Function (<anonymous>) at <anonymous>:1:1
objectId : <objectId>
subtype : error
type : object
}
exceptionId : <exceptionId>
lineNumber : 0
scriptId : <scriptId>
stackTrace : {
callFrames : [
[0] : {
columnNumber : 0
functionName :
lineNumber : 0
scriptId : <scriptId>
url :
}
]
}
text : Uncaught
}
result : {
className : EvalError
description : EvalError: Code generation from strings disallowed for this context at new Function (<anonymous>) at <anonymous>:1:1
objectId : <objectId>
subtype : error
type : object
}
}
}
Running test: testCallFunctionOn
{
id : <messageId>

View File

@ -36,6 +36,24 @@ InspectorTest.runAsyncTestSuite([
await Protocol.Debugger.resume();
},
async function testEvaluateUnsafeEval() {
contextGroup.addScript(`inspector.setAllowCodeGenerationFromStrings(false);`);
await Protocol.Debugger.onceScriptParsed();
InspectorTest.logMessage(
await Protocol.Runtime.evaluate({expression: 'eval("1+1")'}));
InspectorTest.logMessage(
await Protocol.Runtime.evaluate({expression: 'new Function("return 1+1")()'}));
},
async function testEvaluateUnsafeEvalDisableBypass() {
contextGroup.addScript(`inspector.setAllowCodeGenerationFromStrings(false);`);
await Protocol.Debugger.onceScriptParsed();
InspectorTest.logMessage(
await Protocol.Runtime.evaluate({expression: 'eval("1+1")', allowUnsafeEvalBlockedByCSP: false}));
InspectorTest.logMessage(
await Protocol.Runtime.evaluate({expression: 'new Function("return 1+1")()', allowUnsafeEvalBlockedByCSP: false}));
},
async function testCallFunctionOn() {
await contextGroup.addScript(`inspector.setAllowCodeGenerationFromStrings(false);`);
const globalObject = await Protocol.Runtime.evaluate({expression: 'this'});