blob: c66bec1ae9b26f36f892e8c1a3d555bf0837a8d2 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/trustedtypes/trusted_types_util.h"
#include "third_party/blink/public/mojom/devtools/console_message.mojom-blink-forward.h"
#include "third_party/blink/public/mojom/reporting/reporting.mojom-blink.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/renderer/bindings/core/v8/script_value.h"
#include "third_party/blink/renderer/bindings/core/v8/string_or_trusted_html_or_trusted_script_or_trusted_script_url.h"
#include "third_party/blink/renderer/bindings/core/v8/string_or_trusted_script.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/frame/csp/content_security_policy.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/core/probe/core_probes.h"
#include "third_party/blink/renderer/core/script/script_element_base.h"
#include "third_party/blink/renderer/core/trustedtypes/trusted_html.h"
#include "third_party/blink/renderer/core/trustedtypes/trusted_script.h"
#include "third_party/blink/renderer/core/trustedtypes/trusted_script_url.h"
#include "third_party/blink/renderer/core/trustedtypes/trusted_type_policy.h"
#include "third_party/blink/renderer/core/trustedtypes/trusted_type_policy_factory.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
namespace blink {
namespace {
enum TrustedTypeViolationKind {
kTrustedHTMLAssignment,
kTrustedScriptAssignment,
kTrustedScriptURLAssignment,
kTrustedHTMLAssignmentAndDefaultPolicyFailed,
kTrustedHTMLAssignmentAndNoDefaultPolicyExisted,
kTrustedScriptAssignmentAndDefaultPolicyFailed,
kTrustedScriptAssignmentAndNoDefaultPolicyExisted,
kTrustedScriptURLAssignmentAndDefaultPolicyFailed,
kTrustedScriptURLAssignmentAndNoDefaultPolicyExisted,
kNavigateToJavascriptURL,
kNavigateToJavascriptURLAndDefaultPolicyFailed,
kScriptExecution,
kScriptExecutionAndDefaultPolicyFailed,
};
// String to determine whether an incoming eval-ish call is comig from
// an actual eval or a Function constructor. The value is derived from
// from how JS builds up a string in the Function constructor, which in
// turn is defined in the TC39 spec.
const char* kAnonymousPrefix = "(function anonymous";
const char kFunctionConstructorFailureConsoleMessage[] =
"The JavaScript Function constructor does not accept TrustedString "
"arguments. See https://github.com/w3c/webappsec-trusted-types/wiki/"
"Trusted-Types-for-function-constructor for more information.";
const char* GetMessage(TrustedTypeViolationKind kind) {
switch (kind) {
case kTrustedHTMLAssignment:
return "This document requires 'TrustedHTML' assignment.";
case kTrustedScriptAssignment:
return "This document requires 'TrustedScript' assignment.";
case kTrustedScriptURLAssignment:
return "This document requires 'TrustedScriptURL' assignment.";
case kTrustedHTMLAssignmentAndDefaultPolicyFailed:
return "This document requires 'TrustedHTML' assignment and the "
"'default' policy failed to execute.";
case kTrustedHTMLAssignmentAndNoDefaultPolicyExisted:
return "This document requires 'TrustedHTML' assignment and no "
"'default' policy for 'TrustedHTML' has been defined.";
case kTrustedScriptAssignmentAndDefaultPolicyFailed:
return "This document requires 'TrustedScript' assignment and the "
"'default' policy failed to execute.";
case kTrustedScriptAssignmentAndNoDefaultPolicyExisted:
return "This document requires 'TrustedScript' assignment and no "
"'default' policy for 'TrustedScript' has been defined.";
case kTrustedScriptURLAssignmentAndDefaultPolicyFailed:
return "This document requires 'TrustedScriptURL' assignment and the "
"'default' policy failed to execute.";
case kTrustedScriptURLAssignmentAndNoDefaultPolicyExisted:
return "This document requires 'TrustedScriptURL' assignment and no "
"'default' policy for 'TrustedScriptURL' has been defined.";
case kNavigateToJavascriptURL:
return "This document requires 'TrustedScript' assignment. "
"Navigating to a javascript:-URL is equivalent to a "
"'TrustedScript' assignment.";
case kNavigateToJavascriptURLAndDefaultPolicyFailed:
return "This document requires 'TrustedScript' assignment. "
"Navigating to a javascript:-URL is equivalent to a "
"'TrustedScript' assignment and the 'default' policy failed to"
"execute.";
case kScriptExecution:
return "This document requires 'TrustedScript' assignment. "
"This script element was modified without use of TrustedScript "
"assignment.";
case kScriptExecutionAndDefaultPolicyFailed:
return "This document requires 'TrustedScript' assignment. "
"This script element was modified without use of TrustedScript "
"assignment and the 'default' policy failed to execute.";
}
NOTREACHED();
return "";
}
String GetSamplePrefix(const ExceptionState& exception_state,
const String& value) {
const char* interface_name = exception_state.InterfaceName();
const char* property_name = exception_state.PropertyName();
// We have two sample formats, one for eval and one for assignment.
// If we don't have the required values being passed in, just leave the
// sample empty.
StringBuilder sample_prefix;
if (!interface_name) {
// No interface name? Then we have no prefix to use.
} else if (strcmp("eval", interface_name) == 0) {
// eval? Try to distinguish between eval and Function constructor.
sample_prefix.Append(value.StartsWith(kAnonymousPrefix) ? "Function"
: "eval");
} else if ((strcmp("Worker", interface_name) == 0 ||
strcmp("SharedWorker", interface_name) == 0) &&
!property_name) {
// Worker/SharedWorker constructor has nullptr as property_name.
sample_prefix.Append(interface_name);
sample_prefix.Append(" constructor");
} else if (interface_name && property_name) {
sample_prefix.Append(interface_name);
sample_prefix.Append(" ");
sample_prefix.Append(property_name);
}
return sample_prefix.ToString();
}
const char* GetElementName(const ScriptElementBase::Type type) {
switch (type) {
case ScriptElementBase::Type::kHTMLScriptElement:
return "HTMLScriptElement";
case ScriptElementBase::Type::kSVGScriptElement:
return "SVGScriptElement";
}
NOTREACHED();
return "";
}
HeapVector<ScriptValue> GetDefaultCallbackArgs(
v8::Isolate* isolate,
const char* type,
const ExceptionState& exception_state,
const String& value = g_empty_string) {
ScriptState* script_state = ScriptState::Current(isolate);
HeapVector<ScriptValue> args;
args.push_back(ScriptValue::From(script_state, type));
args.push_back(
ScriptValue::From(script_state, GetSamplePrefix(exception_state, value)));
return args;
}
// Handle failure of a Trusted Type assignment.
//
// If trusted type assignment fails, we need to
// - report the violation via CSP
// - increment the appropriate counter,
// - raise a JavaScript exception (if enforced).
//
// Returns whether the failure should be enforced.
bool TrustedTypeFail(TrustedTypeViolationKind kind,
const ExecutionContext* execution_context,
ExceptionState& exception_state,
const String& value) {
if (!execution_context)
return true;
// Test case docs (Document::CreateForTest()) might not have a window
// and hence no TrustedTypesPolicyFactory.
if (execution_context->GetTrustedTypes())
execution_context->GetTrustedTypes()->CountTrustedTypeAssignmentError();
String prefix = GetSamplePrefix(exception_state, value);
bool allow =
execution_context->GetContentSecurityPolicy()
->AllowTrustedTypeAssignmentFailure(
GetMessage(kind),
prefix == "Function" ? value.Substring(strlen(kAnonymousPrefix))
: value,
prefix);
// TODO(1087743): Add a console message for Trusted Type-related Function
// constructor failures, to warn the developer of the outstanding issues
// with TT and Function constructors. This should be removed once the
// underlying issue has been fixed.
if (prefix == "Function" && !allow &&
!RuntimeEnabledFeatures::TrustedTypesUseCodeLikeEnabled()) {
DCHECK(kind == kTrustedScriptAssignment ||
kind == kTrustedScriptAssignmentAndDefaultPolicyFailed ||
kind == kTrustedScriptAssignmentAndNoDefaultPolicyExisted);
execution_context->GetContentSecurityPolicy()->LogToConsole(
MakeGarbageCollected<ConsoleMessage>(
mojom::blink::ConsoleMessageSource::kRecommendation,
mojom::blink::ConsoleMessageLevel::kInfo,
kFunctionConstructorFailureConsoleMessage));
}
probe::OnContentSecurityPolicyViolation(
const_cast<ExecutionContext*>(execution_context),
ContentSecurityPolicy::ContentSecurityPolicyViolationType::
kTrustedTypesSinkViolation);
if (!allow) {
exception_state.ThrowTypeError(GetMessage(kind));
}
return !allow;
}
TrustedTypePolicy* GetDefaultPolicy(const ExecutionContext* execution_context) {
DCHECK(execution_context);
return execution_context->GetTrustedTypes()
? execution_context->GetTrustedTypes()->defaultPolicy()
: nullptr;
}
// Functionally identical to TrustedTypesCheckForScript(const String&, ..), but
// to be called outside of regular script execution. This is required for both
// GetStringForScriptExecution & TrustedTypesCheckForJavascriptURLinNavigation,
// and has a number of additional parameters to enable proper error reporting
// for each case.
String GetStringFromScriptHelper(
String script,
ExecutionContext* context,
// Parameters to customize error messages:
const char* element_name_for_exception,
const char* attribute_name_for_exception,
TrustedTypeViolationKind violation_kind,
TrustedTypeViolationKind violation_kind_when_default_policy_failed) {
if (!context)
return script;
if (!RequireTrustedTypesCheck(context))
return script;
// Set up JS context & friends.
//
// All other functions in here are expected to be called during JS execution,
// where naturally everything is properly set up for more JS execution.
// This one is called during navigation, and thus needs to do a bit more
// work. We need two JavaScript-ish things:
// - TrustedTypeFail expects an ExceptionState, which it will use to throw
// an exception. In our case, we will always clear the exception (as there
// is no user script to pass it to), and we only use this as a signalling
// mechanism.
// - If the default policy applies, we need to execute the JS callback.
// Unlike the various ScriptController::Execute* and ..::Eval* methods,
// we are not executing a source String, but an already compiled callback
// function.
v8::HandleScope handle_scope(context->GetIsolate());
ScriptState::Scope script_state_scope(
ToScriptState(context, DOMWrapperWorld::MainWorld()));
ExceptionState exception_state(
context->GetIsolate(), ExceptionState::kUnknownContext,
element_name_for_exception, attribute_name_for_exception);
TrustedTypePolicy* default_policy = GetDefaultPolicy(context);
if (!default_policy) {
if (TrustedTypeFail(violation_kind, context, exception_state, script)) {
exception_state.ClearException();
return String();
}
return script;
}
TrustedScript* result = default_policy->CreateScript(
context->GetIsolate(), script,
GetDefaultCallbackArgs(context->GetIsolate(), "TrustedScript",
exception_state, script),
exception_state);
if (exception_state.HadException()) {
exception_state.ClearException();
return String();
}
if (result->toString().IsNull()) {
if (TrustedTypeFail(violation_kind_when_default_policy_failed, context,
exception_state, script)) {
exception_state.ClearException();
return String();
}
return script;
}
return result->toString();
}
} // namespace
bool RequireTrustedTypesCheck(const ExecutionContext* execution_context) {
return execution_context && execution_context->RequireTrustedTypes() &&
!ContentSecurityPolicy::ShouldBypassMainWorld(execution_context);
}
String TrustedTypesCheckForHTML(String html,
const ExecutionContext* execution_context,
ExceptionState& exception_state) {
bool require_trusted_type = RequireTrustedTypesCheck(execution_context);
if (!require_trusted_type) {
return html;
}
TrustedTypePolicy* default_policy = GetDefaultPolicy(execution_context);
if (!default_policy) {
if (TrustedTypeFail(kTrustedHTMLAssignment, execution_context,
exception_state, html)) {
return g_empty_string;
}
return html;
}
if (!default_policy->HasCreateHTML()) {
if (TrustedTypeFail(kTrustedHTMLAssignmentAndNoDefaultPolicyExisted,
execution_context, exception_state, html)) {
return g_empty_string;
} else {
return html;
}
}
// TODO(ajwong): This can be optimized to avoid a AddRef in the
// StringCache::CreateStringAndInsertIntoCache() also, but it's a hard mess.
// Punt for now.
TrustedHTML* result = default_policy->CreateHTML(
execution_context->GetIsolate(), html,
GetDefaultCallbackArgs(execution_context->GetIsolate(), "TrustedHTML",
exception_state),
exception_state);
if (exception_state.HadException()) {
return g_empty_string;
}
if (result->toString().IsNull()) {
if (TrustedTypeFail(kTrustedHTMLAssignmentAndDefaultPolicyFailed,
execution_context, exception_state, html)) {
return g_empty_string;
} else {
return html;
}
}
return result->toString();
}
String TrustedTypesCheckForScript(String script,
const ExecutionContext* execution_context,
ExceptionState& exception_state) {
bool require_trusted_type = RequireTrustedTypesCheck(execution_context);
if (!require_trusted_type) {
return script;
}
TrustedTypePolicy* default_policy = GetDefaultPolicy(execution_context);
if (!default_policy) {
if (TrustedTypeFail(kTrustedScriptAssignment, execution_context,
exception_state, script)) {
return g_empty_string;
}
return script;
}
if (!default_policy->HasCreateScript()) {
if (TrustedTypeFail(kTrustedScriptAssignmentAndNoDefaultPolicyExisted,
execution_context, exception_state, script)) {
return g_empty_string;
} else {
return script;
}
}
// TODO(ajwong): This can be optimized to avoid a AddRef in the
// StringCache::CreateStringAndInsertIntoCache() also, but it's a hard mess.
// Punt for now.
TrustedScript* result = default_policy->CreateScript(
execution_context->GetIsolate(), script,
GetDefaultCallbackArgs(execution_context->GetIsolate(), "TrustedScript",
exception_state, script),
exception_state);
DCHECK_EQ(!result, exception_state.HadException());
if (exception_state.HadException()) {
return g_empty_string;
}
if (result->toString().IsNull()) {
if (TrustedTypeFail(kTrustedScriptAssignmentAndDefaultPolicyFailed,
execution_context, exception_state, script)) {
return g_empty_string;
} else {
return script;
}
}
return result->toString();
}
String TrustedTypesCheckForScriptURL(String script_url,
const ExecutionContext* execution_context,
ExceptionState& exception_state) {
bool require_trusted_type =
RequireTrustedTypesCheck(execution_context) &&
RuntimeEnabledFeatures::TrustedDOMTypesEnabled(execution_context);
if (!require_trusted_type) {
return script_url;
}
TrustedTypePolicy* default_policy = GetDefaultPolicy(execution_context);
if (!default_policy) {
if (TrustedTypeFail(kTrustedScriptURLAssignment, execution_context,
exception_state, script_url)) {
return g_empty_string;
}
return script_url;
}
if (!default_policy->HasCreateScriptURL()) {
if (TrustedTypeFail(kTrustedScriptURLAssignmentAndNoDefaultPolicyExisted,
execution_context, exception_state, script_url)) {
return g_empty_string;
} else {
return script_url;
}
}
// TODO(ajwong): This can be optimized to avoid a AddRef in the
// StringCache::CreateStringAndInsertIntoCache() also, but it's a hard mess.
// Punt for now.
TrustedScriptURL* result = default_policy->CreateScriptURL(
execution_context->GetIsolate(), script_url,
GetDefaultCallbackArgs(execution_context->GetIsolate(),
"TrustedScriptURL", exception_state),
exception_state);
if (exception_state.HadException()) {
return g_empty_string;
}
if (result->toString().IsNull()) {
if (TrustedTypeFail(kTrustedScriptURLAssignmentAndDefaultPolicyFailed,
execution_context, exception_state, script_url)) {
return g_empty_string;
} else {
return script_url;
}
}
return result->toString();
}
String TrustedTypesCheckFor(
SpecificTrustedType type,
const StringOrTrustedHTMLOrTrustedScriptOrTrustedScriptURL& trusted,
const ExecutionContext* execution_context,
ExceptionState& exception_state) {
// Whatever happens below, we will need the string value:
String value;
if (trusted.IsTrustedHTML()) {
value = trusted.GetAsTrustedHTML()->toString();
} else if (trusted.IsTrustedScript()) {
value = trusted.GetAsTrustedScript()->toString();
} else if (trusted.IsTrustedScriptURL()) {
value = trusted.GetAsTrustedScriptURL()->toString();
} else if (trusted.IsString()) {
value = trusted.GetAsString();
} // else: trusted.IsNull(). But we don't have anything to do in that case.
// The check passes if we have the proper trusted type:
if (type == SpecificTrustedType::kNone ||
(trusted.IsTrustedHTML() && type == SpecificTrustedType::kHTML) ||
(trusted.IsTrustedScript() && type == SpecificTrustedType::kScript) ||
(trusted.IsTrustedScriptURL() &&
type == SpecificTrustedType::kScriptURL)) {
return value;
}
// In all other cases: run the full check against the string value.
return TrustedTypesCheckFor(type, std::move(value), execution_context,
exception_state);
}
String TrustedTypesCheckForScript(StringOrTrustedScript trusted,
const ExecutionContext* execution_context,
ExceptionState& exception_state) {
// To remain compatible with legacy behaviour, HTMLElement uses extended IDL
// attributes to allow for nullable union of (DOMString or TrustedScript).
// Thus, this method is required to handle the case where
// string_or_trusted_script.IsNull(), unlike the various similar methods in
// this file.
if (trusted.IsTrustedScript()) {
return trusted.GetAsTrustedScript()->toString();
}
if (trusted.IsNull()) {
trusted = StringOrTrustedScript::FromString(g_empty_string);
}
return TrustedTypesCheckForScript(trusted.GetAsString(), execution_context,
exception_state);
}
String TrustedTypesCheckFor(SpecificTrustedType type,
String trusted,
const ExecutionContext* execution_context,
ExceptionState& exception_state) {
switch (type) {
case SpecificTrustedType::kHTML:
return TrustedTypesCheckForHTML(std::move(trusted), execution_context,
exception_state);
case SpecificTrustedType::kScript:
return TrustedTypesCheckForScript(std::move(trusted), execution_context,
exception_state);
case SpecificTrustedType::kScriptURL:
return TrustedTypesCheckForScriptURL(std::move(trusted),
execution_context, exception_state);
case SpecificTrustedType::kNone:
return trusted;
}
NOTREACHED();
return g_empty_string;
}
String CORE_EXPORT
GetStringForScriptExecution(String script,
const ScriptElementBase::Type type,
ExecutionContext* context) {
return GetStringFromScriptHelper(
std::move(script), context, GetElementName(type), "text",
kScriptExecution, kScriptExecutionAndDefaultPolicyFailed);
}
String TrustedTypesCheckForJavascriptURLinNavigation(
String javascript_url,
ExecutionContext* context) {
return GetStringFromScriptHelper(
std::move(javascript_url), context, "Location", "href",
kNavigateToJavascriptURL, kNavigateToJavascriptURLAndDefaultPolicyFailed);
}
} // namespace blink