Commit 16f0defc authored by Karan Bhatia's avatar Karan Bhatia Committed by Commit Bot

IsolatedWorldCSP: Support CSP handling of unsafe-eval.

This CL adds support for handling of unsafe-eval in isolated worlds.

Since the IsolatedWorldCSP feature is disabled by default, no behavior changes
are expected.

BUG=896041

Change-Id: I41b5ddc905b2193e29e4fbe56b4c28c350b298b4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1670352Reviewed-by: default avatarHiroshige Hayashizaki <hiroshige@chromium.org>
Reviewed-by: default avatarKentaro Hara <haraken@chromium.org>
Commit-Queue: Karan Bhatia <karandeepb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#676605}
parent 6e5e0dcb
...@@ -7,8 +7,10 @@ ...@@ -7,8 +7,10 @@
#include <utility> #include <utility>
#include "base/logging.h" #include "base/logging.h"
#include "third_party/blink/renderer/bindings/core/v8/script_controller.h"
#include "third_party/blink/renderer/core/dom/document.h" #include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/frame/csp/content_security_policy.h" #include "third_party/blink/renderer/core/frame/csp/content_security_policy.h"
#include "third_party/blink/renderer/core/probe/core_probes.h"
#include "third_party/blink/renderer/platform/bindings/dom_wrapper_world.h" #include "third_party/blink/renderer/platform/bindings/dom_wrapper_world.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h" #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/instrumentation/use_counter.h" #include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
...@@ -30,9 +32,11 @@ class IsolatedWorldCSPDelegate final ...@@ -30,9 +32,11 @@ class IsolatedWorldCSPDelegate final
public: public:
IsolatedWorldCSPDelegate(Document& document, IsolatedWorldCSPDelegate(Document& document,
scoped_refptr<SecurityOrigin> security_origin, scoped_refptr<SecurityOrigin> security_origin,
int world_id,
bool apply_policy) bool apply_policy)
: document_(&document), : document_(&document),
security_origin_(std::move(security_origin)), security_origin_(std::move(security_origin)),
world_id_(world_id),
apply_policy_(apply_policy) { apply_policy_(apply_policy) {
DCHECK(security_origin_); DCHECK(security_origin_);
} }
...@@ -95,14 +99,17 @@ class IsolatedWorldCSPDelegate final ...@@ -95,14 +99,17 @@ class IsolatedWorldCSPDelegate final
} }
void DisableEval(const String& error_message) override { void DisableEval(const String& error_message) override {
// TODO(crbug.com/896041): Implement this. if (!document_->GetFrame())
NOTIMPLEMENTED(); return;
document_->GetFrame()->GetScriptController().DisableEvalForIsolatedWorld(
world_id_, error_message);
} }
void ReportBlockedScriptExecutionToInspector( void ReportBlockedScriptExecutionToInspector(
const String& directive_text) override { const String& directive_text) override {
// TODO(crbug.com/896041): Figure out if this needs to be implemented. // This allows users to set breakpoints in the Devtools for the case when
NOTIMPLEMENTED(); // script execution is blocked by CSP.
probe::ScriptExecutionBlockedByCSP(document_, directive_text);
} }
void DidAddContentSecurityPolicies( void DidAddContentSecurityPolicies(
...@@ -111,6 +118,7 @@ class IsolatedWorldCSPDelegate final ...@@ -111,6 +118,7 @@ class IsolatedWorldCSPDelegate final
private: private:
const Member<Document> document_; const Member<Document> document_;
const scoped_refptr<SecurityOrigin> security_origin_; const scoped_refptr<SecurityOrigin> security_origin_;
const int world_id_;
// Whether the 'IsolatedWorldCSP' feature is enabled, and we are applying the // Whether the 'IsolatedWorldCSP' feature is enabled, and we are applying the
// CSP provided by the isolated world. // CSP provided by the isolated world.
...@@ -172,7 +180,7 @@ ContentSecurityPolicy* IsolatedWorldCSP::CreateIsolatedWorldCSP( ...@@ -172,7 +180,7 @@ ContentSecurityPolicy* IsolatedWorldCSP::CreateIsolatedWorldCSP(
IsolatedWorldCSPDelegate* delegate = IsolatedWorldCSPDelegate* delegate =
MakeGarbageCollected<IsolatedWorldCSPDelegate>( MakeGarbageCollected<IsolatedWorldCSPDelegate>(
document, std::move(self_origin), apply_policy); document, std::move(self_origin), world_id, apply_policy);
csp->BindToDelegate(*delegate); csp->BindToDelegate(*delegate);
if (apply_policy) { if (apply_policy) {
......
...@@ -31,6 +31,7 @@ ...@@ -31,6 +31,7 @@
#include "third_party/blink/renderer/bindings/core/v8/local_window_proxy.h" #include "third_party/blink/renderer/bindings/core/v8/local_window_proxy.h"
#include "third_party/blink/renderer/bindings/core/v8/initialize_v8_extras_binding.h" #include "third_party/blink/renderer/bindings/core/v8/initialize_v8_extras_binding.h"
#include "third_party/blink/renderer/bindings/core/v8/isolated_world_csp.h"
#include "third_party/blink/renderer/bindings/core/v8/script_controller.h" #include "third_party/blink/renderer/bindings/core/v8/script_controller.h"
#include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h" #include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h" #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
...@@ -167,19 +168,30 @@ void LocalWindowProxy::Initialize() { ...@@ -167,19 +168,30 @@ void LocalWindowProxy::Initialize() {
SetupWindowPrototypeChain(); SetupWindowPrototypeChain();
const SecurityOrigin* origin = nullptr; // Setup handling for eval checks for the context. Isolated worlds which don't
if (world_->IsMainWorld()) { // specify their own CSPs are exempt from eval checks currently.
// ActivityLogger for main world is updated within updateDocumentInternal(). // TODO(crbug.com/982388): For other CSP checks, we use the main world CSP
UpdateDocumentInternal(); // when an isolated world doesn't specify its own CSP. We should do the same
origin = GetFrame()->GetDocument()->GetSecurityOrigin(); // here.
// FIXME: Can this be removed when CSP moves to browser? const bool evaluate_csp_for_eval =
world_->IsMainWorld() ||
(world_->IsIsolatedWorld() &&
IsolatedWorldCSP::Get().HasContentSecurityPolicy(world_->GetWorldId()));
if (evaluate_csp_for_eval) {
ContentSecurityPolicy* csp = ContentSecurityPolicy* csp =
GetFrame()->GetDocument()->GetContentSecurityPolicy(); GetFrame()->GetDocument()->GetContentSecurityPolicyForWorld();
context->AllowCodeGenerationFromStrings(csp->AllowEval( context->AllowCodeGenerationFromStrings(csp->AllowEval(
nullptr, SecurityViolationReportingPolicy::kSuppressReporting, nullptr, SecurityViolationReportingPolicy::kSuppressReporting,
ContentSecurityPolicy::kWillNotThrowException, g_empty_string)); ContentSecurityPolicy::kWillNotThrowException, g_empty_string));
context->SetErrorMessageForCodeGenerationFromStrings( context->SetErrorMessageForCodeGenerationFromStrings(
V8String(GetIsolate(), csp->EvalDisabledErrorMessage())); V8String(GetIsolate(), csp->EvalDisabledErrorMessage()));
}
const SecurityOrigin* origin = nullptr;
if (world_->IsMainWorld()) {
// ActivityLogger for main world is updated within updateDocumentInternal().
UpdateDocumentInternal();
origin = GetFrame()->GetDocument()->GetSecurityOrigin();
} else { } else {
UpdateActivityLogger(); UpdateActivityLogger();
origin = world_->IsolatedWorldSecurityOrigin(); origin = world_->IsolatedWorldSecurityOrigin();
......
...@@ -60,6 +60,7 @@ ...@@ -60,6 +60,7 @@
#include "third_party/blink/renderer/core/loader/frame_loader.h" #include "third_party/blink/renderer/core/loader/frame_loader.h"
#include "third_party/blink/renderer/core/loader/progress_tracker.h" #include "third_party/blink/renderer/core/loader/progress_tracker.h"
#include "third_party/blink/renderer/core/probe/core_probes.h" #include "third_party/blink/renderer/core/probe/core_probes.h"
#include "third_party/blink/renderer/platform/bindings/dom_wrapper_world.h"
#include "third_party/blink/renderer/platform/histogram.h" #include "third_party/blink/renderer/platform/histogram.h"
#include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h" #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
#include "third_party/blink/renderer/platform/instrumentation/use_counter.h" #include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
...@@ -153,23 +154,41 @@ TextPosition ScriptController::EventHandlerPosition() const { ...@@ -153,23 +154,41 @@ TextPosition ScriptController::EventHandlerPosition() const {
} }
void ScriptController::EnableEval() { void ScriptController::EnableEval() {
v8::HandleScope handle_scope(GetIsolate()); SetEvalForWorld(DOMWrapperWorld::MainWorld(), true /* allow_eval */,
v8::Local<v8::Context> v8_context = g_empty_string /* error_message */);
window_proxy_manager_->MainWorldProxyMaybeUninitialized()
->ContextIfInitialized();
if (v8_context.IsEmpty())
return;
v8_context->AllowCodeGenerationFromStrings(true);
} }
void ScriptController::DisableEval(const String& error_message) { void ScriptController::DisableEval(const String& error_message) {
SetEvalForWorld(DOMWrapperWorld::MainWorld(), false /* allow_eval */,
error_message);
}
void ScriptController::DisableEvalForIsolatedWorld(
int world_id,
const String& error_message) {
DCHECK(DOMWrapperWorld::IsIsolatedWorldId(world_id));
scoped_refptr<DOMWrapperWorld> world =
DOMWrapperWorld::EnsureIsolatedWorld(GetIsolate(), world_id);
SetEvalForWorld(*world, false /* allow_eval */, error_message);
}
void ScriptController::SetEvalForWorld(DOMWrapperWorld& world,
bool allow_eval,
const String& error_message) {
v8::HandleScope handle_scope(GetIsolate()); v8::HandleScope handle_scope(GetIsolate());
v8::Local<v8::Context> v8_context = LocalWindowProxy* proxy =
window_proxy_manager_->MainWorldProxyMaybeUninitialized() world.IsMainWorld()
->ContextIfInitialized(); ? window_proxy_manager_->MainWorldProxyMaybeUninitialized()
: WindowProxy(world);
v8::Local<v8::Context> v8_context = proxy->ContextIfInitialized();
if (v8_context.IsEmpty()) if (v8_context.IsEmpty())
return; return;
v8_context->AllowCodeGenerationFromStrings(false);
v8_context->AllowCodeGenerationFromStrings(allow_eval);
if (allow_eval)
return;
v8_context->SetErrorMessageForCodeGenerationFromStrings( v8_context->SetErrorMessageForCodeGenerationFromStrings(
V8String(GetIsolate(), error_message)); V8String(GetIsolate(), error_message));
} }
......
...@@ -120,8 +120,13 @@ class CORE_EXPORT ScriptController final ...@@ -120,8 +120,13 @@ class CORE_EXPORT ScriptController final
scoped_refptr<DOMWrapperWorld> CreateNewInspectorIsolatedWorld( scoped_refptr<DOMWrapperWorld> CreateNewInspectorIsolatedWorld(
const String& world_name); const String& world_name);
// Disables eval for the main world.
void DisableEval(const String& error_message); void DisableEval(const String& error_message);
// Disables eval for the given isolated |world_id|. This initializes the
// window proxy for the isolated world, if it's not yet initialized.
void DisableEvalForIsolatedWorld(int world_id, const String& error_message);
TextPosition EventHandlerPosition() const; TextPosition EventHandlerPosition() const;
void ClearWindowProxy(); void ClearWindowProxy();
...@@ -143,6 +148,12 @@ class CORE_EXPORT ScriptController final ...@@ -143,6 +148,12 @@ class CORE_EXPORT ScriptController final
} }
void EnableEval(); void EnableEval();
// Sets whether eval is enabled for the context corresponding to the given
// |world|. |error_message| is used only when |allow_eval| is false.
void SetEvalForWorld(DOMWrapperWorld& world,
bool allow_eval,
const String& error_message);
v8::Local<v8::Value> EvaluateScriptInMainWorld(const ScriptSourceCode&, v8::Local<v8::Value> EvaluateScriptInMainWorld(const ScriptSourceCode&,
const KURL& base_url, const KURL& base_url,
SanitizeScriptErrors, SanitizeScriptErrors,
......
...@@ -33,6 +33,7 @@ ...@@ -33,6 +33,7 @@
#include "services/metrics/public/cpp/ukm_recorder.h" #include "services/metrics/public/cpp/ukm_recorder.h"
#include "third_party/blink/public/platform/platform.h" #include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/renderer/bindings/core/v8/binding_security.h" #include "third_party/blink/renderer/bindings/core/v8/binding_security.h"
#include "third_party/blink/renderer/bindings/core/v8/isolated_world_csp.h"
#include "third_party/blink/renderer/bindings/core/v8/referrer_script_info.h" #include "third_party/blink/renderer/bindings/core/v8/referrer_script_info.h"
#include "third_party/blink/renderer/bindings/core/v8/rejected_promises.h" #include "third_party/blink/renderer/bindings/core/v8/rejected_promises.h"
#include "third_party/blink/renderer/bindings/core/v8/sanitize_script_errors.h" #include "third_party/blink/renderer/bindings/core/v8/sanitize_script_errors.h"
...@@ -357,8 +358,16 @@ static bool ContentSecurityPolicyCodeGenerationCheck( ...@@ -357,8 +358,16 @@ static bool ContentSecurityPolicyCodeGenerationCheck(
if (ExecutionContext* execution_context = ToExecutionContext(context)) { if (ExecutionContext* execution_context = ToExecutionContext(context)) {
DCHECK(execution_context->IsDocument() || DCHECK(execution_context->IsDocument() ||
execution_context->IsMainThreadWorkletGlobalScope()); execution_context->IsMainThreadWorkletGlobalScope());
v8::Context::Scope scope(context);
// Note this callback is only triggered for contexts which have eval
// disabled. Hence we don't need to handle the case of isolated world
// contexts with no CSP specified. (They should be exempt from the page CSP.
// See crbug.com/982388.)
if (ContentSecurityPolicy* policy = if (ContentSecurityPolicy* policy =
execution_context->GetContentSecurityPolicy()) { execution_context->GetContentSecurityPolicyForWorld()) {
v8::String::Value source_str(context->GetIsolate(), source); v8::String::Value source_str(context->GetIsolate(), source);
UChar snippet[ContentSecurityPolicy::kMaxSampleLength + 1]; UChar snippet[ContentSecurityPolicy::kMaxSampleLength + 1];
size_t len = std::min((sizeof(snippet) / sizeof(UChar)) - 1, size_t len = std::min((sizeof(snippet) / sizeof(UChar)) - 1,
......
CONSOLE MESSAGE: line 38: Testing main world. Eval should be blocked by main world CSP.
CONSOLE MESSAGE: line 7: EvalError: Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "script-src 'self'".
CONSOLE MESSAGE: line 13: PASS: eval blocked as expected.
CONSOLE MESSAGE: line 44: Testing isolated world with no csp. Eval should be allowed.
CONSOLE MESSAGE: PASS: eval allowed as expected.
CONSOLE MESSAGE: line 55: Testing isolated world with strict csp.
CONSOLE MESSAGE: line 58: internals.runtimeFlags.isolatedWorldCSPEnabled is false
CONSOLE MESSAGE: PASS: eval allowed as expected.
CONSOLE MESSAGE: line 68: Testing isolated world with permissive csp.
CONSOLE MESSAGE: PASS: eval allowed as expected.
This tests the handling of unsafe-eval CSP checks and its interaction with the isolated world CSP.
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Security-Policy" content="script-src 'self'">
<script src="resources/isolated-world-eval-csp.js"></script>
</head>
<body id="body">
<p>
This tests the handling of unsafe-eval CSP checks and its interaction
with the isolated world CSP.
</p>
</body>
</html>
function testEval(expectBlocked) {
let evalBlocked;
try {
const x = eval('200');
evalBlocked = (x != 200);
} catch(e) {
console.log(e);
evalBlocked = true;
}
finally {
if (expectBlocked === evalBlocked) {
if (expectBlocked)
console.log('PASS: eval blocked as expected.');
else
console.log('PASS: eval allowed as expected.');
} else {
if (expectBlocked)
console.log('FAIL: eval allowed unexpectedly.');
else
console.log('FAIL: eval blocked unexpectedly.');
}
window.postMessage('next', '*');
}
}
let isolatedWorldId = 1;
const isolatedWorldSecurityOrigin = 'chrome-extensions://123';
function testEvalInIsolatedWorld(expectBlocked) {
const expectBlockedStr = expectBlocked ? 'true' : 'false';
testRunner.evaluateScriptInIsolatedWorld(
isolatedWorldId,
String(testEval.toString()) + `\ntestEval(${expectBlockedStr});`);
}
const tests = [
function() {
console.log(
'Testing main world. Eval should be blocked by main world CSP.');
testEval(true);
},
function() {
// TODO(karandeepb): Ideally we should use the main world CSP in this case.
console.log(
'Testing isolated world with no csp. Eval should be allowed.');
testRunner.setIsolatedWorldInfo(isolatedWorldId, null, null);
testEvalInIsolatedWorld(false);
// We use a different isolated world ID for each test since the eval-based
// CSP checks are set-up when a v8::context is initialized. This happens for
// an isolated world when a script is executed in it for the first time.
isolatedWorldId++;
},
function() {
console.log('Testing isolated world with strict csp.');
testRunner.setIsolatedWorldInfo(
isolatedWorldId, isolatedWorldSecurityOrigin, 'script-src \'none\'');
console.log(
'internals.runtimeFlags.isolatedWorldCSPEnabled is ' +
internals.runtimeFlags.isolatedWorldCSPEnabled);
const expectBlocked = internals.runtimeFlags.isolatedWorldCSPEnabled;
testEvalInIsolatedWorld(expectBlocked);
testRunner.setIsolatedWorldInfo(isolatedWorldId, null, null);
isolatedWorldId++;
},
function() {
console.log('Testing isolated world with permissive csp.');
testRunner.setIsolatedWorldInfo(
isolatedWorldId, isolatedWorldSecurityOrigin,
'script-src \'unsafe-eval\'');
testEvalInIsolatedWorld(false);
testRunner.setIsolatedWorldInfo(isolatedWorldId, null, null);
isolatedWorldId++;
},
];
// This test is meaningless without testRunner.
if (window.testRunner) {
testRunner.dumpAsText();
testRunner.waitUntilDone();
let currentTest = 0;
window.addEventListener('message', function(e) {
if (e.data == 'next') {
// Move to the next test.
currentTest++;
if (currentTest == tests.length) {
testRunner.notifyDone();
return;
}
// Move to the next sub-test.
tests[currentTest]();
}
}, false);
tests[0]();
}
CONSOLE MESSAGE: line 38: Testing main world. Eval should be blocked by main world CSP.
CONSOLE MESSAGE: line 7: EvalError: Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "script-src 'self'".
CONSOLE MESSAGE: line 13: PASS: eval blocked as expected.
CONSOLE MESSAGE: line 44: Testing isolated world with no csp. Eval should be allowed.
CONSOLE MESSAGE: PASS: eval allowed as expected.
CONSOLE MESSAGE: line 55: Testing isolated world with strict csp.
CONSOLE MESSAGE: line 58: internals.runtimeFlags.isolatedWorldCSPEnabled is true
CONSOLE MESSAGE: EvalError: Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "script-src 'none'".
CONSOLE MESSAGE: PASS: eval blocked as expected.
CONSOLE MESSAGE: line 68: Testing isolated world with permissive csp.
CONSOLE MESSAGE: PASS: eval allowed as expected.
This tests the handling of unsafe-eval CSP checks and its interaction with the isolated world CSP.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment