Commit bfd0621a authored by Stefano Sanfilippo's avatar Stefano Sanfilippo Committed by Commit Bot

[Trusted Types] Support eval(TT) when TT Is enforced.

Reintroduce the modifying hook with the new signature. Scripts are executed if both TT and CSP allow them, otherwise throw an EvalError and send reports as side-effects.

Non-string, non-trusted values are passed through, and skip any TT default policy.

eval(TT) still returns the object if TT enforcement is disabled, for now. This is because unconditional codegen from strings overrides our callback.

Bug: 940927
Change-Id: I718694cc050cf071a2ccf9669543628cf568cbdc
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1768427
Commit-Queue: Stefano Sanfilippo <ssanfilippo@chromium.org>
Reviewed-by: default avatarMike West <mkwst@chromium.org>
Reviewed-by: default avatarDaniel Vogelheim <vogelheim@chromium.org>
Cr-Commit-Position: refs/heads/master@{#714283}
parent e7d02d40
...@@ -51,6 +51,7 @@ ...@@ -51,6 +51,7 @@
#include "third_party/blink/renderer/bindings/core/v8/v8_gc_controller.h" #include "third_party/blink/renderer/bindings/core/v8/v8_gc_controller.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_idle_task_runner.h" #include "third_party/blink/renderer/bindings/core/v8/v8_idle_task_runner.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_throw_dom_exception.h" #include "third_party/blink/renderer/bindings/core/v8/v8_throw_dom_exception.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_trusted_script.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_wasm_response_extensions.h" #include "third_party/blink/renderer/bindings/core/v8/v8_wasm_response_extensions.h"
#include "third_party/blink/renderer/bindings/core/v8/worker_or_worklet_script_controller.h" #include "third_party/blink/renderer/bindings/core/v8/worker_or_worklet_script_controller.h"
#include "third_party/blink/renderer/core/dom/document.h" #include "third_party/blink/renderer/core/dom/document.h"
...@@ -382,6 +383,67 @@ static bool ContentSecurityPolicyCodeGenerationCheck( ...@@ -382,6 +383,67 @@ static bool ContentSecurityPolicyCodeGenerationCheck(
return false; return false;
} }
static std::pair<bool, v8::MaybeLocal<v8::String>>
TrustedTypesCodeGenerationCheck(v8::Local<v8::Context> context,
v8::Local<v8::Value> source) {
v8::Isolate* isolate = context->GetIsolate();
ExceptionState exception_state(isolate, ExceptionState::kExecutionContext,
"eval", "");
// If the input is not a string or TrustedScript, pass it through.
if (!source->IsString() && !V8TrustedScript::HasInstance(source, isolate)) {
return {true, v8::MaybeLocal<v8::String>()};
}
StringOrTrustedScript string_or_trusted_script;
V8StringOrTrustedScript::ToImpl(
context->GetIsolate(), source, string_or_trusted_script,
UnionTypeConversionMode::kNotNullable, exception_state);
if (exception_state.HadException()) {
exception_state.ClearException();
// The input was a string or TrustedScript but the conversion failed.
// Block, just in case.
return {false, v8::MaybeLocal<v8::String>()};
}
String stringified_source = GetStringFromTrustedScript(
string_or_trusted_script, ToExecutionContext(context), exception_state);
if (exception_state.HadException()) {
exception_state.ClearException();
return {false, v8::MaybeLocal<v8::String>()};
}
return {true, V8String(context->GetIsolate(), stringified_source)};
}
static v8::ModifyCodeGenerationFromStringsResult
CodeGenerationCheckCallbackInMainThread(v8::Local<v8::Context> context,
v8::Local<v8::Value> source) {
// With Trusted Types, we always run the TT check first because of reporting,
// and because a default policy might want to stringify or modify the original
// source. When TT enforcement is disabled, codegen is always allowed, and we
// just use the check to stringify any trusted type source.
bool codegen_allowed_by_tt = false;
v8::MaybeLocal<v8::String> stringified_source;
std::tie(codegen_allowed_by_tt, stringified_source) =
TrustedTypesCodeGenerationCheck(context, source);
if (!codegen_allowed_by_tt) {
return {false, v8::MaybeLocal<v8::String>()};
}
if (stringified_source.IsEmpty()) {
return {true, v8::MaybeLocal<v8::String>()};
}
if (!ContentSecurityPolicyCodeGenerationCheck(
context, stringified_source.ToLocalChecked())) {
return {false, v8::MaybeLocal<v8::String>()};
}
return {true, std::move(stringified_source)};
}
static bool WasmCodeGenerationCheckCallbackInMainThread( static bool WasmCodeGenerationCheckCallbackInMainThread(
v8::Local<v8::Context> context, v8::Local<v8::Context> context,
v8::Local<v8::String> source) { v8::Local<v8::String> source) {
...@@ -639,8 +701,8 @@ void V8Initializer::InitializeMainThread(const intptr_t* reference_table) { ...@@ -639,8 +701,8 @@ void V8Initializer::InitializeMainThread(const intptr_t* reference_table) {
v8::Isolate::kMessageLog); v8::Isolate::kMessageLog);
isolate->SetFailedAccessCheckCallbackFunction( isolate->SetFailedAccessCheckCallbackFunction(
FailedAccessCheckCallbackInMainThread); FailedAccessCheckCallbackInMainThread);
isolate->SetAllowCodeGenerationFromStringsCallback( isolate->SetModifyCodeGenerationFromStringsCallback(
ContentSecurityPolicyCodeGenerationCheck); CodeGenerationCheckCallbackInMainThread);
isolate->SetAllowWasmCodeGenerationCallback( isolate->SetAllowWasmCodeGenerationCallback(
WasmCodeGenerationCheckCallbackInMainThread); WasmCodeGenerationCheckCallbackInMainThread);
if (RuntimeEnabledFeatures::V8IdleTasksEnabled()) { if (RuntimeEnabledFeatures::V8IdleTasksEnabled()) {
......
...@@ -5854,10 +5854,9 @@ crbug.com/1016772 http/tests/devtools/sources/debugger-ui/source-url-comment.js ...@@ -5854,10 +5854,9 @@ crbug.com/1016772 http/tests/devtools/sources/debugger-ui/source-url-comment.js
crbug.com/1019613 http/tests/devtools/sources/debugger/debug-inlined-scripts.js [ Pass Failure ] crbug.com/1019613 http/tests/devtools/sources/debugger/debug-inlined-scripts.js [ Pass Failure ]
# eval+Trusted Types failures while the feature is implemented. # eval+Trusted Types failures while the feature is implemented.
crbug.com/940927 external/wpt/trusted-types/trusted-types-eval-reporting-report-only.tentative.https.html [ Timeout ] external/wpt/trusted-types/eval-csp-no-tt.tentative.html [ Failure ]
crbug.com/940927 external/wpt/trusted-types/trusted-types-eval-reporting.tentative.https.html [ Failure ] external/wpt/trusted-types/eval-no-csp-no-tt.tentative.html [ Failure ]
crbug.com/940927 external/wpt/trusted-types/trusted-types-eval-reporting-no-unsafe-eval.tentative.https.html [ Timeout ] external/wpt/trusted-types/eval-no-csp-no-tt-default-policy.tentative.html [ Failure ]
crbug.com/940927 external/wpt/trusted-types/eval-with-permissive-csp.tentative.html [ Failure ]
# Sheriff 2019-10-31 # Sheriff 2019-10-31
crbug.com/1020036 [ Debug ] external/wpt/lifecycle/freeze.html [ Failure Pass ] crbug.com/1020036 [ Debug ] external/wpt/lifecycle/freeze.html [ Failure Pass ]
......
...@@ -21,7 +21,9 @@ ...@@ -21,7 +21,9 @@
test(t => { test(t => {
let a = 0; let a = 0;
eval(p.createScript('a="Hello transformed string"')); assert_throws(new EvalError(), _ => {
eval(p.createScript('a="Hello transformed string"'));
});
assert_equals(a, 0); assert_equals(a, 0);
}, "eval with TrustedScript throws (script-src blocks)."); }, "eval with TrustedScript throws (script-src blocks).");
</script> </script>
......
<!DOCTYPE html>
<html>
<head>
<script nonce="abc" src="/resources/testharness.js"></script>
<script nonce="abc" src="/resources/testharnessreport.js"></script>
<script nonce="abc" src="support/helper.sub.js"></script>
<meta http-equiv="Content-Security-Policy" content="script-src 'unsafe-eval' 'nonce-abc'">
</head>
<body>
<script nonce="abc">
const p = trustedTypes.createPolicy("p", {createScript: s => s});
test(t => {
assert_equals(eval(p.createScript("1+1")), 2);
}, "eval of TrustedScript works.");
test(t => {
assert_equals(eval('1+1'), 2);
}, "eval of string works.");
test(t => {
assert_equals(eval(42), 42);
assert_object_equals(eval({}), {});
assert_equals(eval(null), null);
assert_equals(eval(undefined), undefined);
}, "eval of !TrustedScript and !string works.");
</script>
<!DOCTYPE html>
<html>
<head>
<script nonce="abc" src="/resources/testharness.js"></script>
<script nonce="abc" src="/resources/testharnessreport.js"></script>
<script nonce="abc" src="support/helper.sub.js"></script>
<meta http-equiv="Content-Security-Policy" content="trusted-types *">
</head>
<body>
<script>
trustedTypes.createPolicy("default", {createScript: s => s + 4});
const p = trustedTypes.createPolicy("p", {createScript: s => s});
test(t => {
assert_equals(eval(p.createScript('1+1')), 2);
}, "eval of TrustedScript works.");
test(t => {
assert_equals(eval('1+1'), 15);
}, "eval of string works.");
test(t => {
assert_equals(eval(42), 42);
assert_object_equals(eval({}), {});
assert_equals(eval(null), null);
assert_equals(eval(undefined), undefined);
}, "eval of !TrustedScript and !string works.");
</script>
<!DOCTYPE html>
<html>
<head>
<script nonce="abc" src="/resources/testharness.js"></script>
<script nonce="abc" src="/resources/testharnessreport.js"></script>
<script nonce="abc" src="support/helper.sub.js"></script>
<meta http-equiv="Content-Security-Policy" content="trusted-types *">
</head>
<body>
<script>
const p = trustedTypes.createPolicy("p", {createScript: s => s});
test(t => {
assert_equals(eval(p.createScript('1+1')), 2);
}, "eval of TrustedScript works.");
test(t => {
assert_throws(new EvalError(), _ => eval('1+1'));
}, "eval of string fails.");
test(t => {
assert_equals(eval(42), 42);
assert_object_equals(eval({}), {});
assert_equals(eval(null), null);
assert_equals(eval(undefined), undefined);
}, "eval of !TrustedScript and !string works.");
</script>
<!DOCTYPE html>
<html>
<head>
<script nonce="abc" src="/resources/testharness.js"></script>
<script nonce="abc" src="/resources/testharnessreport.js"></script>
<script nonce="abc" src="support/helper.sub.js"></script>
<!-- No CSP header. -->
</head>
<body>
<script>
trustedTypes.createPolicy("default", {createScript: s => s + 4});
const p = trustedTypes.createPolicy("p", {createScript: s => s});
test(t => {
assert_equals(eval(p.createScript('1+1')), 2);
}, "eval of TrustedScript works.");
test(t => {
assert_equals(eval('1+1'), 2);
}, "eval of string works and does not call a default policy.");
test(t => {
assert_equals(eval(42), 42);
assert_object_equals(eval({}), {});
assert_equals(eval(null), null);
assert_equals(eval(undefined), undefined);
}, "eval of !TrustedScript and !string works.");
</script>
<!DOCTYPE html>
<html>
<head>
<script nonce="abc" src="/resources/testharness.js"></script>
<script nonce="abc" src="/resources/testharnessreport.js"></script>
<script nonce="abc" src="support/helper.sub.js"></script>
<!-- No CSP header. -->
</head>
<body>
<script nonce="abc">
const p = trustedTypes.createPolicy("p", {createScript: s => s});
test(t => {
assert_equals(eval(p.createScript('1+1')), 2);
}, "eval of TrustedScript works.");
test(t => {
assert_equals(eval('1+1'), 2);
}, "eval of string works.");
test(t => {
assert_equals(eval(42), 42);
assert_object_equals(eval({}), {});
assert_equals(eval(null), null);
assert_equals(eval(undefined), undefined);
}, "eval of !TrustedScript and !string works.");
</script>
...@@ -88,8 +88,7 @@ ...@@ -88,8 +88,7 @@
let p = Promise.resolve() let p = Promise.resolve()
.then(promise_violation("script-src")) .then(promise_violation("script-src"))
.then(promise_flush()); .then(promise_flush());
expect_throws(_ => eval('script_run_beacon="should not run"')); expect_throws(_ => eval(scriptyPolicy.createScript('script_run_beacon="i ran"')));
eval(scriptyPolicy.createScript('script_run_beacon="i ran"'));
flush(); flush();
assert_not_equals(script_run_beacon, 'i ran'); // Code did not run. assert_not_equals(script_run_beacon, 'i ran'); // Code did not run.
return p; return p;
......
...@@ -179,6 +179,18 @@ ...@@ -179,6 +179,18 @@
return p; return p;
}, "Trusted Type violation report: sample for script innerText assignment"); }, "Trusted Type violation report: sample for script innerText assignment");
promise_test(t => {
let p = Promise.resolve()
.then(promise_violation("trusted-types one"))
.then(expect_blocked_uri("trusted-types-sink"))
.then(expect_sample("eval"))
.then(expect_sample("2+2"))
.then(promise_flush());
expect_throws(_ => eval("2+2"));
flush();
return p;
}, "Trusted Type violation report: sample for eval");
promise_test(t => { promise_test(t => {
// We expect the sample string to always contain the name, and at least the // We expect the sample string to always contain the name, and at least the
// start of the value, but it should not be excessively long. // start of the value, but it should not be excessively long.
......
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