Commit 1d3d6dd3 authored by Daniel Vogelheim's avatar Daniel Vogelheim Committed by Commit Bot

Use "Dynamic Code Brand Checks" for Trusted Type function constructor.

This uses - behind a flag - the trial implementation of TC39 "Dynamic
Code Brand Checks" in crrev.com/c/2339618 to fix the handling of TrustedScript
with the JavaScript Function constructor.

Bug: 1096017, 1087743
Change-Id: I42f2f4295fdf3aab0b2e8c80ebf3d04373c17368
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2340905
Auto-Submit: Daniel Vogelheim <vogelheim@chromium.org>
Reviewed-by: default avatarCamille Lamy <clamy@chromium.org>
Reviewed-by: default avatarYuki Shiino <yukishiino@chromium.org>
Reviewed-by: default avatarYifan Luo <lyf@chromium.org>
Commit-Queue: Daniel Vogelheim <vogelheim@chromium.org>
Cr-Commit-Position: refs/heads/master@{#824937}
parent ae1caf0d
......@@ -1642,6 +1642,15 @@ Usage: The method must adhere to the following requirements:
Those requirements lead to the specific inability to throw JS exceptions and to log warnings to the console, as logging uses `MakeGarbageCollected<ConsoleMessage>`. If any such error reporting needs to happen, the method marked with `[NoAllocDirectCall]` should expect a last parameter `bool* has_error`, in which it might store `true` to signal V8. V8 will in turn re-execute the "default" callback, giving the possibility of the exception/error to be reported. This mechanism also implies that the "fast" callback is idempotent up to the point of reporting the error.
### [IsCodeLike] _(t)_
This implements the TC39 "Dynamic Code Brand Checks" proposal. By attaching
the [IsCodeLike] attribute to a type, its instances will be treated as
"code like" objects, as detailed in the spec.
Standard: [TC39 Dynamic Code Brand Checks](https://github.com/tc39/proposal-dynamic-code-brand-checks)
## Discouraged Blink-specific IDL Extended Attributes
These extended attributes are _discouraged_ - they are not deprecated, but they should be avoided and removed if possible.
......
......@@ -64,6 +64,7 @@ HighEntropy=|Direct
HTMLConstructor
ImmutablePrototype
ImplementedAs=*
IsCodeLike
LegacyLenientSetter
LegacyLenientThis
LegacyNoInterfaceObject
......
......@@ -379,13 +379,15 @@ static bool ContentSecurityPolicyCodeGenerationCheck(
static std::pair<bool, v8::MaybeLocal<v8::String>>
TrustedTypesCodeGenerationCheck(v8::Local<v8::Context> context,
v8::Local<v8::Value> source) {
v8::Local<v8::Value> source,
bool is_code_like) {
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)) {
if (!source->IsString() && !is_code_like &&
!V8TrustedScript::HasInstance(source, isolate)) {
return {true, v8::MaybeLocal<v8::String>()};
}
......@@ -400,6 +402,12 @@ TrustedTypesCodeGenerationCheck(v8::Local<v8::Context> context,
return {false, v8::MaybeLocal<v8::String>()};
}
if (is_code_like && string_or_trusted_script.IsString()) {
string_or_trusted_script = StringOrTrustedScript::FromTrustedScript(
MakeGarbageCollected<TrustedScript>(
string_or_trusted_script.GetAsString()));
}
String stringified_source = TrustedTypesCheckForScript(
string_or_trusted_script, ToExecutionContext(context), exception_state);
if (exception_state.HadException()) {
......@@ -412,7 +420,12 @@ TrustedTypesCodeGenerationCheck(v8::Local<v8::Context> context,
static v8::ModifyCodeGenerationFromStringsResult
CodeGenerationCheckCallbackInMainThread(v8::Local<v8::Context> context,
v8::Local<v8::Value> source) {
v8::Local<v8::Value> source,
bool is_code_like) {
// The TC39 "Dynamic Code Brand Check" feature is currently behind a flag.
if (!RuntimeEnabledFeatures::TrustedTypesUseCodeLikeEnabled())
is_code_like = false;
// 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
......@@ -420,7 +433,7 @@ CodeGenerationCheckCallbackInMainThread(v8::Local<v8::Context> context,
bool codegen_allowed_by_tt = false;
v8::MaybeLocal<v8::String> stringified_source;
std::tie(codegen_allowed_by_tt, stringified_source) =
TrustedTypesCodeGenerationCheck(context, source);
TrustedTypesCodeGenerationCheck(context, source, is_code_like);
if (!codegen_allowed_by_tt) {
return {false, v8::MaybeLocal<v8::String>()};
......
......@@ -5336,6 +5336,15 @@ ${prototype_template}->SetIntrinsicDataProperty(
V8AtomicString(${isolate}, "forEach"), v8::kArrayProto_forEach, v8::None);
"""))
if interface and "IsCodeLike" in interface.extended_attributes:
body.append(
CxxUnlikelyIfNode(
cond="RuntimeEnabledFeatures::TrustedTypesUseCodeLikeEnabled()",
body=[
TextNode("// [IsCodeLike]"),
TextNode("${instance_object_template}->SetCodeLike();"),
]))
if "Global" in class_like.extended_attributes:
body.append(
TextNode("""\
......
......@@ -7,6 +7,7 @@
typedef [StringContext=TrustedScript] DOMString ScriptString;
[
IsCodeLike,
Exposed=Window,
RuntimeEnabled=TrustedDOMTypes
] interface TrustedScript {
......
......@@ -1943,6 +1943,10 @@
name: "TrustedTypeBeforePolicyCreationEvent",
status: "experimental",
},
{
name: "TrustedTypesUseCodeLike",
status: "experimental",
},
{
name: "TrustTokens",
origin_trial_feature_name: "TrustTokens",
......
......@@ -27,9 +27,7 @@
}, "eval of !TrustedScript and !string works.");
test(t => {
// The Function constructor will string-ify its arguments. Hence, this
// will apply the default policy, despite being TrustedScript.
assert_equals(new Function(p.createScript('return 1+1'))(), 5);
assert_equals(new Function(p.createScript('return 1+1'))(), 2);
}, "Function constructor of TrustedScript works.");
test(t => {
......
......@@ -26,13 +26,11 @@
}, "eval of !TrustedScript and !string works.");
test(t => {
// The Function constructor will string-ify its arguments. Hence, this
// throw because no default policy is defined.
assert_throws_js(EvalError, _ => new Function(p.createScript('1+1')));
assert_equals(new Function(p.createScript('return 1+1'))(), 2);
}, "Function constructor of TrustedScript works.");
test(t => {
assert_throws_js(EvalError, _ => new Function('1+1')());
assert_throws_js(EvalError, _ => new Function('return 1+1')());
}, "Function constructor of string fails.");
</script>
<!DOCTYPE html>
<html>
<head>
<script nonce="abc" src="/resources/testharness.js"></script>
<script nonce="abc" src="/resources/testharnessreport.js"></script>
<meta http-equiv="Content-Security-Policy"
content="require-trusted-types-for 'script'">
</head>
<body>
<script>
let policy = trustedTypes.createPolicy("p", { createScript: s => s });
const args = ["a", "b", "c = 5", "return (a+b)*c;"];
const arg_max = 2 ** args.length -1;
// Call 'new Function(...args)', but with a subet of args being Strings,
// and a subset being TrustedScript. We use a bitmask to determine which
// argument gets to be trusted or not.
function new_function_with_maybe_trusted_args(mask) {
let maybe_trusted_args = args.map((value, arg_nr) => {
return (mask & (2**arg_nr)) ? policy.createScript(value) : value;
});
return new Function(...maybe_trusted_args);
}
// Generate all combinations of String/TrustedScript, except for the one
// where all argumentes are TrustedScript.
for (let mask = 0; mask < arg_max; mask++) {
test(t => {
assert_throws_js(EvalError,
_ => new_function_with_maybe_trusted_args(mask));
}, "Function constructor with mixed plain and trusted strings, mask #" + mask);
}
// Now do one with all trusted arguments.
test(t => {
const f = new_function_with_maybe_trusted_args(arg_max);
assert_equals(f(1,2,3), 9);
assert_equals(f(1,2), 15);
}, "Function constructor with mixed plain and trusted strings, mask #" + arg_max);
</script>
......@@ -34,11 +34,8 @@
}, "eval with TrustedScript and permissive CSP works.");
test(t => {
assert_throws_js(EvalError, _ => {
// The Function constructor will string-ify its arguments. Hence, this
// throw because no default policy is defined.
let s = new Function(p.createScript('return "Hello transformed string"'));
});
let s = new Function(p.createScript('return "Hello transformed string"'))();
assert_equals(s, "Hello a cat string");
}, "new Function with TrustedScript and permissive CSP works.");
trustedTypes.createPolicy("default", { createScript: createScriptJS }, true);
......
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