Commit 78a02050 authored by Daniel Vogelheim's avatar Daniel Vogelheim Committed by Commit Bot

[Trusted Types] Implement new <script> handling for Trusted Types.

This follows the proposal at
https://github.com/w3c/webappsec-trusted-types/pull/236
and effectively reverts crrev.com/c/1547746. This replaces the
(arguably rather invasive) changes in node.cc and element.cc with
more elaborate logic in html_script_element.cc. (I.e., it pushes
complexity from the super-classes into a specific subclass, at the
expense of making the sub-class do more work.)

Bug: 1026549
Change-Id: I929e9e669db7f9e6b8de5a3d0d0df661f109b644
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1924523
Commit-Queue: Daniel Vogelheim <vogelheim@chromium.org>
Reviewed-by: default avatarMike West <mkwst@chromium.org>
Cr-Commit-Position: refs/heads/master@{#723017}
parent 5e660b2a
...@@ -4684,7 +4684,7 @@ Node* Element::InsertAdjacent(const String& where, ...@@ -4684,7 +4684,7 @@ Node* Element::InsertAdjacent(const String& where,
ExceptionState& exception_state) { ExceptionState& exception_state) {
if (DeprecatedEqualIgnoringCase(where, "beforeBegin")) { if (DeprecatedEqualIgnoringCase(where, "beforeBegin")) {
if (ContainerNode* parent = parentNode()) { if (ContainerNode* parent = parentNode()) {
parent->insertBefore(new_child, this, exception_state); parent->InsertBefore(new_child, this, exception_state);
if (!exception_state.HadException()) if (!exception_state.HadException())
return new_child; return new_child;
} }
...@@ -4692,18 +4692,18 @@ Node* Element::InsertAdjacent(const String& where, ...@@ -4692,18 +4692,18 @@ Node* Element::InsertAdjacent(const String& where,
} }
if (DeprecatedEqualIgnoringCase(where, "afterBegin")) { if (DeprecatedEqualIgnoringCase(where, "afterBegin")) {
insertBefore(new_child, firstChild(), exception_state); InsertBefore(new_child, firstChild(), exception_state);
return exception_state.HadException() ? nullptr : new_child; return exception_state.HadException() ? nullptr : new_child;
} }
if (DeprecatedEqualIgnoringCase(where, "beforeEnd")) { if (DeprecatedEqualIgnoringCase(where, "beforeEnd")) {
appendChild(new_child, exception_state); AppendChild(new_child, exception_state);
return exception_state.HadException() ? nullptr : new_child; return exception_state.HadException() ? nullptr : new_child;
} }
if (DeprecatedEqualIgnoringCase(where, "afterEnd")) { if (DeprecatedEqualIgnoringCase(where, "afterEnd")) {
if (ContainerNode* parent = parentNode()) { if (ContainerNode* parent = parentNode()) {
parent->insertBefore(new_child, nextSibling(), exception_state); parent->InsertBefore(new_child, nextSibling(), exception_state);
if (!exception_state.HadException()) if (!exception_state.HadException())
return new_child; return new_child;
} }
......
...@@ -674,10 +674,6 @@ void Node::DidEndCustomizedScrollPhase() { ...@@ -674,10 +674,6 @@ void Node::DidEndCustomizedScrollPhase() {
Node* Node::insertBefore(Node* new_child, Node* Node::insertBefore(Node* new_child,
Node* ref_child, Node* ref_child,
ExceptionState& exception_state) { ExceptionState& exception_state) {
new_child = TrustedTypesCheckForScriptNode(new_child, exception_state);
if (!new_child)
return nullptr;
auto* this_node = DynamicTo<ContainerNode>(this); auto* this_node = DynamicTo<ContainerNode>(this);
if (this_node) if (this_node)
return this_node->InsertBefore(new_child, ref_child, exception_state); return this_node->InsertBefore(new_child, ref_child, exception_state);
...@@ -695,10 +691,6 @@ Node* Node::insertBefore(Node* new_child, Node* ref_child) { ...@@ -695,10 +691,6 @@ Node* Node::insertBefore(Node* new_child, Node* ref_child) {
Node* Node::replaceChild(Node* new_child, Node* Node::replaceChild(Node* new_child,
Node* old_child, Node* old_child,
ExceptionState& exception_state) { ExceptionState& exception_state) {
new_child = TrustedTypesCheckForScriptNode(new_child, exception_state);
if (!new_child)
return nullptr;
auto* this_node = DynamicTo<ContainerNode>(this); auto* this_node = DynamicTo<ContainerNode>(this);
if (this_node) if (this_node)
return this_node->ReplaceChild(new_child, old_child, exception_state); return this_node->ReplaceChild(new_child, old_child, exception_state);
...@@ -729,10 +721,6 @@ Node* Node::removeChild(Node* old_child) { ...@@ -729,10 +721,6 @@ Node* Node::removeChild(Node* old_child) {
} }
Node* Node::appendChild(Node* new_child, ExceptionState& exception_state) { Node* Node::appendChild(Node* new_child, ExceptionState& exception_state) {
new_child = TrustedTypesCheckForScriptNode(new_child, exception_state);
if (!new_child)
return nullptr;
auto* this_node = DynamicTo<ContainerNode>(this); auto* this_node = DynamicTo<ContainerNode>(this);
if (this_node) if (this_node)
return this_node->AppendChild(new_child, exception_state); return this_node->AppendChild(new_child, exception_state);
...@@ -3292,21 +3280,6 @@ void Node::RemovedFromFlatTree() { ...@@ -3292,21 +3280,6 @@ void Node::RemovedFromFlatTree() {
GetDocument().GetStyleEngine().RemovedFromFlatTree(*this); GetDocument().GetStyleEngine().RemovedFromFlatTree(*this);
} }
Node* Node::TrustedTypesCheckForScriptNode(
Node* child,
ExceptionState& exception_state) const {
DCHECK(child);
bool needs_check = IsA<HTMLScriptElement>(this) && child->IsTextNode() &&
GetDocument().IsTrustedTypesEnabledForDoc();
if (!needs_check)
return child;
child = TrustedTypesCheckForHTMLScriptElement(child, &GetDocument(),
exception_state);
DCHECK_EQ(!child, exception_state.HadException());
return child;
}
void Node::Trace(Visitor* visitor) { void Node::Trace(Visitor* visitor) {
visitor->Trace(parent_or_shadow_host_node_); visitor->Trace(parent_or_shadow_host_node_);
visitor->Trace(previous_); visitor->Trace(previous_);
......
...@@ -1063,9 +1063,6 @@ class CORE_EXPORT Node : public EventTarget { ...@@ -1063,9 +1063,6 @@ class CORE_EXPORT Node : public EventTarget {
const HeapHashSet<Member<MutationObserverRegistration>>* const HeapHashSet<Member<MutationObserverRegistration>>*
TransientMutationObserverRegistry(); TransientMutationObserverRegistry();
inline Node* TrustedTypesCheckForScriptNode(Node* child,
ExceptionState&) const;
uint32_t node_flags_; uint32_t node_flags_;
Member<Node> parent_or_shadow_host_node_; Member<Node> parent_or_shadow_host_node_;
Member<TreeScope> tree_scope_; Member<TreeScope> tree_scope_;
......
...@@ -134,7 +134,11 @@ void HTMLScriptElement::setInnerText( ...@@ -134,7 +134,11 @@ void HTMLScriptElement::setInnerText(
String value = GetStringFromTrustedScript(string_or_trusted_script, String value = GetStringFromTrustedScript(string_or_trusted_script,
&GetDocument(), exception_state); &GetDocument(), exception_state);
if (!exception_state.HadException()) { if (!exception_state.HadException()) {
// https://w3c.github.io/webappsec-trusted-types/dist/spec/#setting-slot-values
// On setting, the innerText [...] perform the regular steps, and then set
// content object's [[ScriptText]] internal slot value [...].
HTMLElement::setInnerText(value, exception_state); HTMLElement::setInnerText(value, exception_state);
script_text_internal_slot_ = value;
} }
} }
...@@ -144,7 +148,11 @@ void HTMLScriptElement::setTextContent( ...@@ -144,7 +148,11 @@ void HTMLScriptElement::setTextContent(
String value = GetStringFromTrustedScript(string_or_trusted_script, String value = GetStringFromTrustedScript(string_or_trusted_script,
&GetDocument(), exception_state); &GetDocument(), exception_state);
if (!exception_state.HadException()) { if (!exception_state.HadException()) {
// https://w3c.github.io/webappsec-trusted-types/dist/spec/#setting-slot-values
// On setting, [..] textContent [..] perform the regular steps, and then set
// content object's [[ScriptText]] internal slot value [...].
Node::setTextContent(value); Node::setTextContent(value);
script_text_internal_slot_ = value;
} }
} }
...@@ -153,6 +161,12 @@ void HTMLScriptElement::setAsync(bool async) { ...@@ -153,6 +161,12 @@ void HTMLScriptElement::setAsync(bool async) {
loader_->HandleAsyncAttribute(); loader_->HandleAsyncAttribute();
} }
void HTMLScriptElement::FinishParsingChildren() {
Element::FinishParsingChildren();
DCHECK(script_text_internal_slot_.IsEmpty());
script_text_internal_slot_ = TextFromChildren();
}
bool HTMLScriptElement::async() const { bool HTMLScriptElement::async() const {
return FastHasAttribute(html_names::kAsyncAttr) || loader_->IsNonBlocking(); return FastHasAttribute(html_names::kAsyncAttr) || loader_->IsNonBlocking();
} }
...@@ -205,6 +219,10 @@ String HTMLScriptElement::ChildTextContent() { ...@@ -205,6 +219,10 @@ String HTMLScriptElement::ChildTextContent() {
return TextFromChildren(); return TextFromChildren();
} }
String HTMLScriptElement::ScriptTextInternalSlot() const {
return script_text_internal_slot_;
}
bool HTMLScriptElement::AsyncAttributeValue() const { bool HTMLScriptElement::AsyncAttributeValue() const {
return FastHasAttribute(html_names::kAsyncAttr); return FastHasAttribute(html_names::kAsyncAttr);
} }
......
...@@ -62,6 +62,8 @@ class CORE_EXPORT HTMLScriptElement final : public HTMLElement, ...@@ -62,6 +62,8 @@ class CORE_EXPORT HTMLScriptElement final : public HTMLElement,
void Trace(Visitor*) override; void Trace(Visitor*) override;
void FinishParsingChildren() override;
private: private:
void ParseAttribute(const AttributeModificationParams&) override; void ParseAttribute(const AttributeModificationParams&) override;
InsertionNotificationRequest InsertedInto(ContainerNode&) override; InsertionNotificationRequest InsertedInto(ContainerNode&) override;
...@@ -73,6 +75,9 @@ class CORE_EXPORT HTMLScriptElement final : public HTMLElement, ...@@ -73,6 +75,9 @@ class CORE_EXPORT HTMLScriptElement final : public HTMLElement,
bool HasLegalLinkAttribute(const QualifiedName&) const override; bool HasLegalLinkAttribute(const QualifiedName&) const override;
const QualifiedName& SubResourceAttributeName() const override; const QualifiedName& SubResourceAttributeName() const override;
void SetTrustedTextContent(const String&);
void SetTrustedInnerText(const String&, ExceptionState&);
// ScriptElementBase overrides: // ScriptElementBase overrides:
String SourceAttributeValue() const override; String SourceAttributeValue() const override;
String CharsetAttributeValue() const override; String CharsetAttributeValue() const override;
...@@ -86,6 +91,7 @@ class CORE_EXPORT HTMLScriptElement final : public HTMLElement, ...@@ -86,6 +91,7 @@ class CORE_EXPORT HTMLScriptElement final : public HTMLElement,
String ReferrerPolicyAttributeValue() const override; String ReferrerPolicyAttributeValue() const override;
String ImportanceAttributeValue() const override; String ImportanceAttributeValue() const override;
String ChildTextContent() override; String ChildTextContent() override;
String ScriptTextInternalSlot() const override;
bool AsyncAttributeValue() const override; bool AsyncAttributeValue() const override;
bool DeferAttributeValue() const override; bool DeferAttributeValue() const override;
bool HasSourceAttribute() const override; bool HasSourceAttribute() const override;
...@@ -105,6 +111,9 @@ class CORE_EXPORT HTMLScriptElement final : public HTMLElement, ...@@ -105,6 +111,9 @@ class CORE_EXPORT HTMLScriptElement final : public HTMLElement,
Element& CloneWithoutAttributesAndChildren(Document&) const override; Element& CloneWithoutAttributesAndChildren(Document&) const override;
// https://w3c.github.io/webappsec-trusted-types/dist/spec/#script-scripttext
String script_text_internal_slot_;
Member<ScriptLoader> loader_; Member<ScriptLoader> loader_;
}; };
......
...@@ -37,8 +37,8 @@ class MockScriptElementBase : public GarbageCollected<MockScriptElementBase>, ...@@ -37,8 +37,8 @@ class MockScriptElementBase : public GarbageCollected<MockScriptElementBase>,
MOCK_CONST_METHOD0(NomoduleAttributeValue, bool()); MOCK_CONST_METHOD0(NomoduleAttributeValue, bool());
MOCK_CONST_METHOD0(SourceAttributeValue, String()); MOCK_CONST_METHOD0(SourceAttributeValue, String());
MOCK_CONST_METHOD0(TypeAttributeValue, String()); MOCK_CONST_METHOD0(TypeAttributeValue, String());
MOCK_METHOD0(ChildTextContent, String()); MOCK_METHOD0(ChildTextContent, String());
MOCK_CONST_METHOD0(ScriptTextInternalSlot, String());
MOCK_CONST_METHOD0(HasSourceAttribute, bool()); MOCK_CONST_METHOD0(HasSourceAttribute, bool());
MOCK_CONST_METHOD0(IsConnected, bool()); MOCK_CONST_METHOD0(IsConnected, bool());
MOCK_CONST_METHOD0(HasChildren, bool()); MOCK_CONST_METHOD0(HasChildren, bool());
......
...@@ -52,7 +52,11 @@ class CORE_EXPORT ScriptElementBase : public GarbageCollectedMixin { ...@@ -52,7 +52,11 @@ class CORE_EXPORT ScriptElementBase : public GarbageCollectedMixin {
virtual String ReferrerPolicyAttributeValue() const = 0; virtual String ReferrerPolicyAttributeValue() const = 0;
virtual String ImportanceAttributeValue() const = 0; virtual String ImportanceAttributeValue() const = 0;
// This implements https://dom.spec.whatwg.org/#concept-child-text-content
virtual String ChildTextContent() = 0; virtual String ChildTextContent() = 0;
// This supports
// https://w3c.github.io/webappsec-trusted-types/dist/spec/#prepare-script-url-and-text
virtual String ScriptTextInternalSlot() const = 0;
virtual bool HasSourceAttribute() const = 0; virtual bool HasSourceAttribute() const = 0;
virtual bool IsConnected() const = 0; virtual bool IsConnected() const = 0;
virtual bool HasChildren() const = 0; virtual bool HasChildren() const = 0;
......
...@@ -53,6 +53,7 @@ ...@@ -53,6 +53,7 @@
#include "third_party/blink/renderer/core/script/script_element_base.h" #include "third_party/blink/renderer/core/script/script_element_base.h"
#include "third_party/blink/renderer/core/script/script_runner.h" #include "third_party/blink/renderer/core/script/script_runner.h"
#include "third_party/blink/renderer/core/svg_names.h" #include "third_party/blink/renderer/core/svg_names.h"
#include "third_party/blink/renderer/core/trustedtypes/trusted_types_util.h"
#include "third_party/blink/renderer/platform/bindings/parkable_string.h" #include "third_party/blink/renderer/platform/bindings/parkable_string.h"
#include "third_party/blink/renderer/platform/instrumentation/histogram.h" #include "third_party/blink/renderer/platform/instrumentation/histogram.h"
#include "third_party/blink/renderer/platform/instrumentation/use_counter.h" #include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
...@@ -289,7 +290,15 @@ bool ScriptLoader::PrepareScript(const TextPosition& script_start_position, ...@@ -289,7 +290,15 @@ bool ScriptLoader::PrepareScript(const TextPosition& script_start_position,
non_blocking_ = true; non_blocking_ = true;
// <spec step="4">Let source text be the element's child text content.</spec> // <spec step="4">Let source text be the element's child text content.</spec>
const String source_text = element_->ChildTextContent(); //
// Trusted Types additionally requires:
// https://w3c.github.io/webappsec-trusted-types/dist/spec/#slot-value-verification
// - Step 4: Execute the Prepare the script URL and text algorithm upon the
// script element. If that algorithm threw an error, then return. The
// script is not executed.
// - Step 5: Let source text be the element’s [[ScriptText]] internal slot
// value.
const String source_text = GetScriptText();
// <spec step="5">If the element has no src attribute, and source text is the // <spec step="5">If the element has no src attribute, and source text is the
// empty string, then return. The script is not executed.</spec> // empty string, then return. The script is not executed.</spec>
...@@ -386,8 +395,7 @@ bool ScriptLoader::PrepareScript(const TextPosition& script_start_position, ...@@ -386,8 +395,7 @@ bool ScriptLoader::PrepareScript(const TextPosition& script_start_position,
// executed. [CSP]</spec> // executed. [CSP]</spec>
if (!element_->HasSourceAttribute() && if (!element_->HasSourceAttribute() &&
!element_->AllowInlineScriptForCSP(element_->GetNonceForElement(), !element_->AllowInlineScriptForCSP(element_->GetNonceForElement(),
position.line_, position.line_, source_text)) {
element_->ChildTextContent())) {
return false; return false;
} }
...@@ -633,8 +641,8 @@ bool ScriptLoader::PrepareScript(const TextPosition& script_start_position, ...@@ -633,8 +641,8 @@ bool ScriptLoader::PrepareScript(const TextPosition& script_start_position,
// 1. Let import map parse result be the result of create an import map // 1. Let import map parse result be the result of create an import map
// parse result, given source text, base URL and settings object. [spec // parse result, given source text, base URL and settings object. [spec
// text] // text]
PendingImportMap* pending_import_map = PendingImportMap::CreateInline( PendingImportMap* pending_import_map =
*element_, element_->ChildTextContent(), base_url); PendingImportMap::CreateInline(*element_, source_text, base_url);
// Because we currently support inline import maps only, the pending // Because we currently support inline import maps only, the pending
// import map is ready immediately and thus we call `register an import // import map is ready immediately and thus we call `register an import
...@@ -662,8 +670,8 @@ bool ScriptLoader::PrepareScript(const TextPosition& script_start_position, ...@@ -662,8 +670,8 @@ bool ScriptLoader::PrepareScript(const TextPosition& script_start_position,
} }
prepared_pending_script_ = ClassicPendingScript::CreateInline( prepared_pending_script_ = ClassicPendingScript::CreateInline(
element_, position, base_url, element_->ChildTextContent(), element_, position, base_url, source_text, script_location_type,
script_location_type, options); options);
// <spec step="25.2.A.2">Set the script's script to script.</spec> // <spec step="25.2.A.2">Set the script's script to script.</spec>
// //
...@@ -689,10 +697,10 @@ bool ScriptLoader::PrepareScript(const TextPosition& script_start_position, ...@@ -689,10 +697,10 @@ bool ScriptLoader::PrepareScript(const TextPosition& script_start_position,
// <spec label="fetch-an-inline-module-script-graph" step="1">Let script // <spec label="fetch-an-inline-module-script-graph" step="1">Let script
// be the result of creating a JavaScript module script using source // be the result of creating a JavaScript module script using source
// text, settings object, base URL, and options.</spec> // text, settings object, base URL, and options.</spec>
ModuleScript* module_script = JSModuleScript::Create( ModuleScript* module_script =
ParkableString(element_->ChildTextContent().Impl()), nullptr, JSModuleScript::Create(ParkableString(source_text.Impl()), nullptr,
ScriptSourceLocationType::kInline, modulator, source_url, base_url, ScriptSourceLocationType::kInline, modulator,
options, position); source_url, base_url, options, position);
// <spec label="fetch-an-inline-module-script-graph" step="2">If script // <spec label="fetch-an-inline-module-script-graph" step="2">If script
// is null, asynchronously complete this algorithm with null, and abort // is null, asynchronously complete this algorithm with null, and abort
...@@ -1015,4 +1023,20 @@ ScriptLoader::GetPendingScriptIfControlledByScriptRunnerForCrossDocMove() { ...@@ -1015,4 +1023,20 @@ ScriptLoader::GetPendingScriptIfControlledByScriptRunnerForCrossDocMove() {
return pending_script_; return pending_script_;
} }
String ScriptLoader::GetScriptText() const {
// Step 3 of
// https://w3c.github.io/webappsec-trusted-types/dist/spec/#abstract-opdef-prepare-the-script-url-and-text
// called from § 4.1.3.3, step 4 of
// https://w3c.github.io/webappsec-trusted-types/dist/spec/#slot-value-verification
// This will return the [[ScriptText]] internal slot value after that step,
// or a null string if the the Trusted Type algorithm threw an error.
String child_text_content = element_->ChildTextContent();
DCHECK(!child_text_content.IsNull());
String script_text_internal_slot = element_->ScriptTextInternalSlot();
if (child_text_content == script_text_internal_slot)
return child_text_content;
return GetStringForScriptExecution(child_text_content,
element_->GetDocument().ContextDocument());
}
} // namespace blink } // namespace blink
...@@ -140,6 +140,9 @@ class CORE_EXPORT ScriptLoader final : public GarbageCollected<ScriptLoader>, ...@@ -140,6 +140,9 @@ class CORE_EXPORT ScriptLoader final : public GarbageCollected<ScriptLoader>,
// PendingScriptClient // PendingScriptClient
void PendingScriptFinished(PendingScript*) override; void PendingScriptFinished(PendingScript*) override;
// Get the effective script text (after Trusted Types checking).
String GetScriptText() const;
Member<ScriptElementBase> element_; Member<ScriptElementBase> element_;
// https://html.spec.whatwg.org/C/#script-processing-model // https://html.spec.whatwg.org/C/#script-processing-model
......
...@@ -93,6 +93,8 @@ bool SVGScriptElement::IsURLAttribute(const Attribute& attribute) const { ...@@ -93,6 +93,8 @@ bool SVGScriptElement::IsURLAttribute(const Attribute& attribute) const {
void SVGScriptElement::FinishParsingChildren() { void SVGScriptElement::FinishParsingChildren() {
SVGElement::FinishParsingChildren(); SVGElement::FinishParsingChildren();
have_fired_load_ = true; have_fired_load_ = true;
DCHECK(script_text_internal_slot_.IsEmpty());
script_text_internal_slot_ = TextFromChildren();
} }
bool SVGScriptElement::HaveLoadedRequiredResources() { bool SVGScriptElement::HaveLoadedRequiredResources() {
...@@ -111,6 +113,10 @@ String SVGScriptElement::ChildTextContent() { ...@@ -111,6 +113,10 @@ String SVGScriptElement::ChildTextContent() {
return TextFromChildren(); return TextFromChildren();
} }
String SVGScriptElement::ScriptTextInternalSlot() const {
return script_text_internal_slot_;
}
bool SVGScriptElement::HasSourceAttribute() const { bool SVGScriptElement::HasSourceAttribute() const {
return href()->IsSpecified(); return href()->IsSpecified();
} }
......
...@@ -82,6 +82,7 @@ class SVGScriptElement final : public SVGElement, ...@@ -82,6 +82,7 @@ class SVGScriptElement final : public SVGElement,
String SourceAttributeValue() const override; String SourceAttributeValue() const override;
String TypeAttributeValue() const override; String TypeAttributeValue() const override;
String ChildTextContent() override; String ChildTextContent() override;
String ScriptTextInternalSlot() const override;
bool HasSourceAttribute() const override; bool HasSourceAttribute() const override;
bool IsConnected() const override; bool IsConnected() const override;
bool HasChildren() const override; bool HasChildren() const override;
...@@ -105,6 +106,8 @@ class SVGScriptElement final : public SVGElement, ...@@ -105,6 +106,8 @@ class SVGScriptElement final : public SVGElement,
bool have_fired_load_ = false; bool have_fired_load_ = false;
String script_text_internal_slot_;
Member<ScriptLoader> loader_; Member<ScriptLoader> loader_;
}; };
......
...@@ -13,8 +13,6 @@ ...@@ -13,8 +13,6 @@
#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"
#include "third_party/blink/renderer/bindings/core/v8/window_proxy_manager.h" #include "third_party/blink/renderer/bindings/core/v8/window_proxy_manager.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/dom/node.h"
#include "third_party/blink/renderer/core/dom/text.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/frame/local_frame.h" #include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/trustedtypes/trusted_html.h" #include "third_party/blink/renderer/core/trustedtypes/trusted_html.h"
...@@ -41,10 +39,10 @@ enum TrustedTypeViolationKind { ...@@ -41,10 +39,10 @@ enum TrustedTypeViolationKind {
kTrustedHTMLAssignmentAndDefaultPolicyFailed, kTrustedHTMLAssignmentAndDefaultPolicyFailed,
kTrustedScriptAssignmentAndDefaultPolicyFailed, kTrustedScriptAssignmentAndDefaultPolicyFailed,
kTrustedScriptURLAssignmentAndDefaultPolicyFailed, kTrustedScriptURLAssignmentAndDefaultPolicyFailed,
kTextNodeScriptAssignment,
kTextNodeScriptAssignmentAndDefaultPolicyFailed,
kNavigateToJavascriptURL, kNavigateToJavascriptURL,
kNavigateToJavascriptURLAndDefaultPolicyFailed, kNavigateToJavascriptURLAndDefaultPolicyFailed,
kScriptExecution,
kScriptExecutionAndDefaultPolicyFailed,
}; };
const char* GetMessage(TrustedTypeViolationKind kind) { const char* GetMessage(TrustedTypeViolationKind kind) {
...@@ -66,15 +64,6 @@ const char* GetMessage(TrustedTypeViolationKind kind) { ...@@ -66,15 +64,6 @@ const char* GetMessage(TrustedTypeViolationKind kind) {
case kTrustedScriptURLAssignmentAndDefaultPolicyFailed: case kTrustedScriptURLAssignmentAndDefaultPolicyFailed:
return "This document requires 'TrustedScriptURL' assignment and the " return "This document requires 'TrustedScriptURL' assignment and the "
"'default' policy failed to execute."; "'default' policy failed to execute.";
case kTextNodeScriptAssignment:
return "This document requires 'TrustedScript' assignment, "
"and inserting a text node into a script element is equivalent to "
"a 'TrustedScript' assignment.";
case kTextNodeScriptAssignmentAndDefaultPolicyFailed:
return "This document requires 'TrustedScript' assignment. "
"Inserting a text node into a script element is equivalent to "
"a 'TrustedScript' assignment and the default policy failed to "
"execute.";
case kNavigateToJavascriptURL: case kNavigateToJavascriptURL:
return "This document requires 'TrustedScript' assignment. " return "This document requires 'TrustedScript' assignment. "
"Navigating to a javascript:-URL is equivalent to a " "Navigating to a javascript:-URL is equivalent to a "
...@@ -82,8 +71,16 @@ const char* GetMessage(TrustedTypeViolationKind kind) { ...@@ -82,8 +71,16 @@ const char* GetMessage(TrustedTypeViolationKind kind) {
case kNavigateToJavascriptURLAndDefaultPolicyFailed: case kNavigateToJavascriptURLAndDefaultPolicyFailed:
return "This document requires 'TrustedScript' assignment. " return "This document requires 'TrustedScript' assignment. "
"Navigating to a javascript:-URL is equivalent to a " "Navigating to a javascript:-URL is equivalent to a "
"'TrustedScript' assignment and the default policy failed to" "'TrustedScript' assignment and the 'default' policy failed to"
"execute."; "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(); NOTREACHED();
return ""; return "";
...@@ -153,6 +150,75 @@ TrustedTypePolicy* GetDefaultPolicy(const ExecutionContext* execution_context) { ...@@ -153,6 +150,75 @@ TrustedTypePolicy* GetDefaultPolicy(const ExecutionContext* execution_context) {
: nullptr; : nullptr;
} }
// Functionally identical to GetStringFromTrustedScript(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(
const String& script,
Document* doc,
// 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) {
bool require_trusted_type = RequireTrustedTypesCheck(doc);
if (!require_trusted_type)
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(doc->GetIsolate());
ScriptState::Scope script_state_scope(
ScriptState::From(static_cast<LocalWindowProxyManager*>(
doc->GetFrame()->GetWindowProxyManager())
->MainWorldProxy()
->ContextIfInitialized()));
ExceptionState exception_state(
doc->GetIsolate(), ExceptionState::kUnknownContext,
element_name_for_exception, attribute_name_for_exception);
TrustedTypePolicy* default_policy = GetDefaultPolicy(doc);
if (!default_policy) {
if (TrustedTypeFail(violation_kind, doc, exception_state, script)) {
exception_state.ClearException();
return String();
}
return script;
}
TrustedScript* result =
default_policy->CreateScript(doc->GetIsolate(), script, exception_state);
if (exception_state.HadException()) {
exception_state.ClearException();
return String();
}
if (result->toString().IsNull()) {
if (TrustedTypeFail(violation_kind_when_default_policy_failed, doc,
exception_state, script)) {
exception_state.ClearException();
return String();
}
return script;
}
return result->toString();
}
} // namespace } // namespace
bool RequireTrustedTypesCheck(const ExecutionContext* execution_context) { bool RequireTrustedTypesCheck(const ExecutionContext* execution_context) {
...@@ -410,95 +476,19 @@ String GetStringFromTrustedScriptURL( ...@@ -410,95 +476,19 @@ String GetStringFromTrustedScriptURL(
return result->toString(); return result->toString();
} }
Node* TrustedTypesCheckForHTMLScriptElement(Node* child, String CORE_EXPORT GetStringForScriptExecution(const String& script,
Document* doc, Document* doc) {
ExceptionState& exception_state) { return GetStringFromScriptHelper(script, doc, "script", "text",
bool require_trusted_type = RequireTrustedTypesCheck(doc); kScriptExecution,
if (!require_trusted_type) kScriptExecutionAndDefaultPolicyFailed);
return child;
TrustedTypePolicy* default_policy = GetDefaultPolicy(doc);
if (!default_policy) {
return TrustedTypeFail(kTextNodeScriptAssignment, doc, exception_state,
child->textContent())
? nullptr
: child;
}
TrustedScript* result = default_policy->CreateScript(
doc->GetIsolate(), child->textContent(), exception_state);
if (exception_state.HadException()) {
return nullptr;
}
if (result->toString().IsNull()) {
return TrustedTypeFail(kTextNodeScriptAssignmentAndDefaultPolicyFailed, doc,
exception_state, child->textContent())
? nullptr
: child;
}
return Text::Create(*doc, result->toString());
} }
String TrustedTypesCheckForJavascriptURLinNavigation( String TrustedTypesCheckForJavascriptURLinNavigation(
const String& javascript_url, const String& javascript_url,
Document* doc) { Document* doc) {
bool require_trusted_type = RequireTrustedTypesCheck(doc); return GetStringFromScriptHelper(
if (!require_trusted_type) javascript_url, doc, "Location", "href", kNavigateToJavascriptURL,
return javascript_url; kNavigateToJavascriptURLAndDefaultPolicyFailed);
// Set up JS context & friends.
//
// All other functions in here are expected to be called during JS execution,
// where naturally everything is propertly 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(doc->GetIsolate());
ScriptState::Scope script_state_scope(
ScriptState::From(static_cast<LocalWindowProxyManager*>(
doc->GetFrame()->GetWindowProxyManager())
->MainWorldProxy()
->ContextIfInitialized()));
ExceptionState exception_state(
doc->GetIsolate(), ExceptionState::kUnknownContext, "Location", "href");
TrustedTypePolicy* default_policy = GetDefaultPolicy(doc);
if (!default_policy) {
if (TrustedTypeFail(kNavigateToJavascriptURL, doc, exception_state,
javascript_url)) {
exception_state.ClearException();
return String();
}
return javascript_url;
}
TrustedScript* result = default_policy->CreateScript(
doc->GetIsolate(), javascript_url, exception_state);
if (exception_state.HadException()) {
exception_state.ClearException();
return String();
}
if (result->toString().IsNull()) {
if (TrustedTypeFail(kNavigateToJavascriptURLAndDefaultPolicyFailed, doc,
exception_state, javascript_url)) {
exception_state.ClearException();
return String();
}
return javascript_url;
}
// TODO(vogelheim): Figure out whether we need to check whether this string
// parses as a URL, and whether we need to do this here.
return result->toString();
} }
} // namespace blink } // namespace blink
...@@ -13,7 +13,6 @@ namespace blink { ...@@ -13,7 +13,6 @@ namespace blink {
class Document; class Document;
class ExecutionContext; class ExecutionContext;
class ExceptionState; class ExceptionState;
class Node;
class StringOrTrustedHTML; class StringOrTrustedHTML;
class StringOrTrustedHTMLOrTrustedScriptOrTrustedScriptURL; class StringOrTrustedHTMLOrTrustedScriptOrTrustedScriptURL;
class StringOrTrustedScript; class StringOrTrustedScript;
...@@ -57,25 +56,19 @@ String CORE_EXPORT GetStringFromTrustedScript(StringOrTrustedScript, ...@@ -57,25 +56,19 @@ String CORE_EXPORT GetStringFromTrustedScript(StringOrTrustedScript,
const ExecutionContext*, const ExecutionContext*,
ExceptionState&); ExceptionState&);
String GetStringFromTrustedScript(const String&, String CORE_EXPORT GetStringFromTrustedScript(const String&,
const ExecutionContext*, const ExecutionContext*,
ExceptionState&); ExceptionState&);
String CORE_EXPORT GetStringFromTrustedScriptURL(StringOrTrustedScriptURL, String CORE_EXPORT GetStringFromTrustedScriptURL(StringOrTrustedScriptURL,
const ExecutionContext*, const ExecutionContext*,
ExceptionState&); ExceptionState&);
// For <script> elements, we need to treat insertion of DOM text nodes // Functionally equivalent to GetStringFromTrustedScript(const String&, ...),
// as equivalent to string assignment. This checks the child-node to be // but with setup & error handling suitable for the asynchronous execution
// inserted and runs all of the Trusted Types checks if it's a text node. // cases.
//
// Returns nullptr if the check failed, or the node to use (possibly child)
// if they succeeded.
Node* TrustedTypesCheckForHTMLScriptElement(Node* child,
Document*,
ExceptionState&);
String TrustedTypesCheckForJavascriptURLinNavigation(const String&, Document*); String TrustedTypesCheckForJavascriptURLinNavigation(const String&, Document*);
String CORE_EXPORT GetStringForScriptExecution(const String&, Document*);
// Determine whether a Trusted Types check is needed in this execution context. // Determine whether a Trusted Types check is needed in this execution context.
// //
......
...@@ -3,64 +3,235 @@ ...@@ -3,64 +3,235 @@
<head> <head>
<script src="/resources/testharness.js"></script> <script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script> <script src="/resources/testharnessreport.js"></script>
<script src="support/helper.sub.js"></script>
<meta http-equiv="Content-Security-Policy" content="trusted-types *"> <meta http-equiv="Content-Security-Policy" content="trusted-types *">
</head> </head>
<body> <body>
<div id="container"></div> <div id="container"></div>
<script id="script1">"hello world!";</script>
<script id="trigger"></script>
<script> <script>
var container = document.querySelector("#container"); var container = document.querySelector("#container");
const policy_dict = {
createScript: s => (s.includes("fail") ? null : s.replace("default", "count")),
createHTML: s => s.replace(/2/g, "3"),
};
const policy = trustedTypes.createPolicy("policy", policy_dict);
// Regression tests for 'Bypass via insertAdjacentText', reported at // Regression tests for 'Bypass via insertAdjacentText', reported at
// https://github.com/WICG/trusted-types/issues/133 // https://github.com/WICG/trusted-types/issues/133
// We are trying to assert that scripts do _not_ get executed. We
// accomplish by having the script under examination containing a
// postMessage, and to send a second guaranteed-to-execute postMessage
// so there's a point in time when we're sure the first postMessage
// must have arrived (if indeed it had been sent).
//
// We'll interpret the message data as follows:
// - includes "block": error (this message should have been blocked by TT)
// - includes "count": Count these, and later check against expect_count.
// - includes "done": Unregister the event handler and finish the test.
// - all else: Reject, as this is probably an error in the test.
function checkMessage(expect_count) {
postMessage("done", "*");
return new Promise((resolve, reject) => {
let count = 0;
globalThis.addEventListener("message", function handler(e) {
if (e.data.includes("block")) {
reject(`'block' received (${e.data})`);
} else if (e.data.includes("count")) {
count = count + 1;
} else if (e.data.includes("done")) {
globalThis.removeEventListener("message", handler);
if (expect_count && count != expect_count) {
reject(
`'done' received, but unexpected counts: expected ${expect_count} != actual ${count} (${e.data})`);
} else {
resolve(e.data);
}
} else {
reject("unexpected message received: " + e.data);
}
});
});
}
function checkSecurityPolicyViolationEvent(expect_count) {
return new Promise((resolve, reject) => {
let count = 0;
document.addEventListener("securitypolicyviolation", e => {
if (e.sample.includes("trigger")) {
if (expect_count && count != expect_count) {
reject(
`'trigger' received, but unexpected counts: expected ${expect_count} != actual ${count}`);
} else {
resolve();
}
} else {
count = count + 1;
}
});
try {
document.getElementById("trigger").text = "trigger fail";
} catch(e) { }
});
}
// Original report: // Original report:
test(t => { promise_test(t => {
// Setup: Create a <script> element with a <p> child. // Setup: Create a <script> element with a <p> child.
let s = document.createElement("script"); let s = document.createElement("script");
// Sanity check: Element child content (<p>) doesn't count as source text.
let p = document.createElement("p"); let p = document.createElement("p");
p.textContent = "hello('world');"; p.text = "hello('world');";
s.appendChild(p); s.appendChild(p);
container.appendChild(s); container.appendChild(s);
// Sanity check: The <p> content doesn't count as source text.
assert_equals(s.text, ""); assert_equals(s.text, "");
// Try to insertAdjacentText into the <script>, starting from the <p> // Try to insertAdjacentText into the <script>, starting from the <p>
try { p.insertAdjacentText("beforebegin",
p.insertAdjacentText("beforebegin", "hello('before');"); "postMessage('beforebegin should be blocked', '*');");
} catch (err) { } assert_true(s.text.includes("postMessage"));
assert_equals(s.text, ""); p.insertAdjacentText("afterend",
try { "postMessage('afterend should be blocked', '*');");
p.insertAdjacentText("afterend", "hello('after');"); assert_true(s.text.includes("after"));
} catch (err) { } return checkMessage();
assert_equals(s.text, ""); }, "Regression test: Bypass via insertAdjacentText, initial comment.");
}, "Regression test: Bypass via insertAdjacentText, initial comment");
// Variant: Create a <script> element and create & insert a text node. Then const script_elements = {
// insert it into the document container to make it live. "script": doc => doc.createElement("script"),
test(t => { "svg:script": doc => doc.createElementNS("http://www.w3.org/2000/svg", "script"),
// Setup: Create a <script> element, and insert text via a text node. };
let s = document.createElement("script"); for (let [element, create_element] of Object.entries(script_elements)) {
let text = document.createTextNode("alert('hello');"); // Like the "Original Report" test case above, but avoids use of the "text"
assert_throws(new TypeError(), // accessor that <svg:script> doesn't support.
_ => { s.appendChild(text); }, promise_test(t => {
"We should not be able to modify <script>."); let s = create_element(document);
container.appendChild(s);
}, "Regression test: Bypass via appendChild into off-document script element");
// Variant: Create a <script> element and insert it into the document. Then // Sanity check: Element child content (<p>) doesn't count as source text.
// create a text node and insert it into the live <script> element. let p = document.createElement("p");
test(t => { p.textContent = "hello('world');";
// Setup: Create a <script> element, insert it into the doc, and then create s.appendChild(p);
// and insert text via a text node. container.appendChild(s);
let s = document.createElement("script");
container.appendChild(s); // Try to insertAdjacentText into the <script>, starting from the <p>
let text = document.createTextNode("alert('hello');"); p.insertAdjacentText("beforebegin",
assert_throws(new TypeError(), "postMessage('beforebegin should be blocked', '*');");
_ => { s.appendChild(text); }, assert_true(s.textContent.includes("postMessage"));
"We should not be able to modify <script>."); p.insertAdjacentText("afterend",
}, "Regression test: Bypass via appendChild into live script element"); "postMessage('afterend should be blocked', '*');");
assert_true(s.textContent.includes("after"));
return checkMessage();
}, "Regression test: Bypass via insertAdjacentText, initial comment. " + element);
// Variant: Create a <script> element and create & insert a text node. Then
// insert it into the document container to make it live.
promise_test(t => {
// Setup: Create a <script> element, and insert text via a text node.
let s = create_element(document);
let text = document.createTextNode("postMessage('appendChild with a " +
"text node should be blocked', '*');");
s.appendChild(text);
container.appendChild(s);
return checkMessage();
}, "Regression test: Bypass via appendChild into off-document script element" + element);
// Variant: Create a <script> element and insert it into the document. Then
// create a text node and insert it into the live <script> element.
promise_test(t => {
// Setup: Create a <script> element, insert it into the doc, and then create
// and insert text via a text node.
let s = create_element(document);
container.appendChild(s);
let text = document.createTextNode("postMessage('appendChild with a live " +
"text node should be blocked', '*');");
s.appendChild(text);
return checkMessage();
}, "Regression test: Bypass via appendChild into live script element " + element);
}
promise_test(t => {
return checkSecurityPolicyViolationEvent();
}, "Prep for subsequent tests: Clear SPV event queue.");
promise_test(t => {
const inserted_script = document.getElementById("script1");
assert_throws(new TypeError(), _ => {
inserted_script.innerHTML = "2+2";
});
let new_script = document.createElement("script");
assert_throws(new TypeError(), _ => {
new_script.innerHTML = "2+2";
container.appendChild(new_script);
});
const trusted_html = policy.createHTML("2*4");
assert_equals("" + trusted_html, "3*4");
inserted_script.innerHTML = trusted_html;
assert_equals(inserted_script.textContent, "3*4");
new_script = document.createElement("script");
new_script.innerHTML = trusted_html;
container.appendChild(new_script);
assert_equals(inserted_script.textContent, "3*4");
// We expect 3 SPV events: two for the two assert_throws cases, and one
// for script element, which will be rejected at the time of execution.
return checkSecurityPolicyViolationEvent(3);
}, "Spot tests around script + innerHTML interaction.");
// Create default policy. Wrapped in a promise_test to ensure it runs only
// after the other tests.
let default_policy;
promise_test(t => {
default_policy = trustedTypes.createPolicy("default", policy_dict);
return Promise.resolve();
}, "Prep for subsequent tests: Create default policy.");
for (let [element, create_element] of Object.entries(script_elements)) {
promise_test(t => {
let s = create_element(document);
let text = document.createTextNode("postMessage('default', '*');");
s.appendChild(text);
container.appendChild(s);
return Promise.all([checkMessage(1),
checkSecurityPolicyViolationEvent(0)]);
}, "Test that default policy applies. " + element);
promise_test(t => {
let s = create_element(document);
let text = document.createTextNode("fail");
s.appendChild(text);
container.appendChild(s);
return Promise.all([checkMessage(0),
checkSecurityPolicyViolationEvent(1)]);
}, "Test a failing default policy. " + element);
}
promise_test(t => {
const inserted_script = document.getElementById("script1");
inserted_script.innerHTML = "2+2";
assert_equals(inserted_script.textContent, "3+3");
let new_script = document.createElement("script");
new_script.innerHTML = "2+2";
container.appendChild(new_script);
assert_equals(inserted_script.textContent, "3+3");
const trusted_html = default_policy.createHTML("2*4");
assert_equals("" + trusted_html, "3*4");
inserted_script.innerHTML = trusted_html;
assert_equals(inserted_script.textContent, "3*4");
new_script = document.createElement("script");
new_script.innerHTML = trusted_html;
container.appendChild(new_script);
assert_equals(inserted_script.textContent, "3*4");
return checkSecurityPolicyViolationEvent(0);
}, "Spot tests around script + innerHTML interaction with default policy.");
</script> </script>
</body> </body>
</html> </html>
......
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