Skip to content

Commit

Permalink
[Trusted Types] Get list of event handlers from WebIDL
Browse files Browse the repository at this point in the history
This change retrieves the list of attributes declared as event handlers
from WebIDL and uses that to check for TrustedScript, instead of using
the string prefix "on".

Bug: 993268, 1084587
Change-Id: Ic15bc0994bcd19d9d7385cbef4af0f01af820ae1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3616765
Reviewed-by: Mason Freed <masonf@chromium.org>
Reviewed-by: Yuki Shiino <yukishiino@chromium.org>
Reviewed-by: Yifan Luo <lyf@chromium.org>
Commit-Queue: Daniel Vogelheim <vogelheim@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1003034}
NOKEYCHECK=True
GitOrigin-RevId: 20acdd57bc0c2900456a9717629834686cdb4890
  • Loading branch information
otherdaniel authored and copybara-github committed May 13, 2022
1 parent ed2dd68 commit a69b64a
Show file tree
Hide file tree
Showing 8 changed files with 223 additions and 22 deletions.
23 changes: 23 additions & 0 deletions blink/renderer/core/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ component("core") {
}

deps = [
":generate_eventhandler_names",
":generated_settings_macros",
"//build:chromeos_buildflags",
"//components/paint_preview/common",
Expand Down Expand Up @@ -1214,6 +1215,28 @@ target(core_generated_target_type, "core_generated") {
}
}

# Generate a list of event handler attributes, for use by Trusted Types.
action("generate_eventhandler_names") {
script = "//third_party/blink/renderer/build/scripts/run_with_pythonpath.py"
real_script = "trustedtypes/generate_eventhandler_names.py"
inputs = [
web_idl_database_filepath,
real_script,
]
outputs = [ "$target_gen_dir/trustedtypes/event_handler_names.h" ]
deps = [ "//third_party/blink/renderer/bindings:web_idl_database" ]
args = [
"-I",
rebase_path("//third_party/blink/renderer/bindings/scripts",
root_build_dir),
rebase_path(real_script, root_build_dir),
"--webidl",
rebase_path(inputs[0], root_build_dir),
"--out",
rebase_path(outputs[0], root_build_dir),
]
}

# Fuzzer for blink::TextResourceDecoder.
fuzzer_test("text_resource_decoder_fuzzer") {
sources = [
Expand Down
17 changes: 11 additions & 6 deletions blink/renderer/core/dom/element.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2292,13 +2292,18 @@ SpecificTrustedType Element::ExpectedTrustedTypeForAttribute(
if (iter != attribute_types->end())
return iter->value;

if (q_name.LocalName().StartsWith("on")) {
// TODO(jakubvrana): This requires TrustedScript in all attributes
// starting with "on", including e.g. "one". We use this pattern elsewhere
// (e.g. in IsEventHandlerAttribute) but it's not ideal. Consider using
// the event attribute of the resulting AttributeTriggers.
// Since event handlers can be defined on nearly all elements, we will
// consider them independently of the specific element they're attached to.
//
// Note: Element::IsEventHandlerAttribute is different and over-approximates
// event-handler-ness, since it is expected to work only for builtin
// attributes (like "onclick"), while Trusted Types needs to deal with
// whatever users pass into setAttribute (for example "one"). Also, it
// requires the actual Attribute rather than the QName, which means
// Element::IsEventHandlerAttribute can only be called after an attribute has
// been constructed.
if (IsTrustedTypesEventHandlerAttribute(q_name))
return SpecificTrustedType::kScript;
}

return SpecificTrustedType::kNone;
}
Expand Down
70 changes: 70 additions & 0 deletions blink/renderer/core/trustedtypes/generate_eventhandler_names.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#!/usr/bin/env python
# Copyright 2022 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.

import optparse
import sys

import web_idl


# Read the WebIDL database and write a list of all event handler attributes.
#
# Reads the WebIDL database (--webidl) and writes a C++ .h file with a macro
# containing all event handler names (to --out). All attributes declared as
# EventHandler or On(BeforeUnload|Error)EventHandler types are considered
# event handlers.
#
# The macro is called EVENT_HANDLER_LIST and follows the "X macro" model of
# macro lists [1], as its used elsewhere [2] in the Chromium code base.
#
# [1] https://en.wikipedia.org/wiki/X_Macro
# [2] https://source.chromium.org/search?q=%5E%23define%5C%20%5BA-Z_%5D*LIST%5C(%20file:v8
def main(argv):
parser = optparse.OptionParser()
parser.add_option("--out")
parser.add_option("--webidl")
options, args = parser.parse_args(argv[1:])

for option in ("out", "webidl"):
if not getattr(options, option):
parser.error(f"--{option} is required.")
if args:
parser.error("No positional arguments supported.")

event_handlers = set()
event_handler_types = ("EventHandler", "OnBeforeUnloadEventHandler",
"OnErrorEventHandler")

web_idl_database = web_idl.Database.read_from_file(options.webidl)
for interface in web_idl_database.interfaces:
for attribute in interface.attributes:
idl_type = attribute.idl_type
if (idl_type.is_typedef
and idl_type.identifier in event_handler_types):
event_handlers.add(attribute.identifier)

license_and_header = """\
// Copyright 2022 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.
"""

with open(options.out, "w") as out:
print(license_and_header, file=out)
print("// Generated from WebIDL database. Don't edit, just generate.",
file=out)
print("//", file=out)
print(f"// Generator: {argv[0]}", file=out)
print("", file=out)
print("#define EVENT_HANDLER_LIST(EH) \\", file=out)
for event in event_handlers:
print(f" EH({event}) \\", file=out)
print("\n", file=out)

return 0


if __name__ == "__main__":
sys.exit(main(sys.argv))
58 changes: 42 additions & 16 deletions blink/renderer/core/trustedtypes/trusted_type_policy_factory.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "third_party/blink/renderer/core/inspector/exception_metadata.h"
#include "third_party/blink/renderer/core/inspector/identifiers_factory.h"
#include "third_party/blink/renderer/core/probe/core_probes.h"
#include "third_party/blink/renderer/core/trustedtypes/event_handler_names.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_type_policy.h"
Expand Down Expand Up @@ -193,7 +194,10 @@ const struct {
true},
{"*", "innerHTML", nullptr, SpecificTrustedType::kHTML, false, true},
{"*", "outerHTML", nullptr, SpecificTrustedType::kHTML, false, true},
{"*", "on*", nullptr, SpecificTrustedType::kScript, true, false},
#define FOREACH_EVENT_HANDLER(name) \
{"*", #name, nullptr, SpecificTrustedType::kScript, true, false},
EVENT_HANDLER_LIST(FOREACH_EVENT_HANDLER)
#undef FOREACH_EVENT_HANDLER
};

// Does a type table entry match a property?
Expand All @@ -204,22 +208,20 @@ bool EqualsProperty(decltype(*kTypeTable)& left,
const String& ns) {
DCHECK_EQ(tag.LowerASCII(), tag);
return (left.element == tag || !strcmp(left.element, "*")) &&
(left.property == attr ||
(!strcmp(left.property, "on*") && attr.StartsWith("on"))) &&
left.element_namespace == ns && !left.is_not_property;
left.property == attr && left.element_namespace == ns &&
!left.is_not_property;
}

// Does a type table entry match an attribute?
// (Attributes get queried by calling acecssor methods on the DOM. These are
// case-insensitivem, because DOM.)
// case-insensitive, because DOM.)
bool EqualsAttribute(decltype(*kTypeTable)& left,
const String& tag,
const String& attr,
const String& ns) {
DCHECK_EQ(tag.LowerASCII(), tag);
return (left.element == tag || !strcmp(left.element, "*")) &&
(String(left.property).LowerASCII() == attr.LowerASCII() ||
(!strcmp(left.property, "on*") && attr.StartsWith("on"))) &&
CodeUnitCompareIgnoringASCIICase(attr, left.property) == 0 &&
left.element_namespace == ns && !left.is_not_attribute;
}

Expand All @@ -241,35 +243,43 @@ typedef bool (*PropertyEqualsFn)(decltype(*kTypeTable)&,
const String&,
const String&);

String FindTypeInTypeTable(const String& tagName,
const String& propertyName,
const String& elementNS,
PropertyEqualsFn equals) {
SpecificTrustedType FindTypeInTypeTable(const String& tagName,
const String& propertyName,
const String& elementNS,
PropertyEqualsFn equals) {
SpecificTrustedType type = SpecificTrustedType::kNone;
for (auto* it = std::cbegin(kTypeTable); it != std::cend(kTypeTable); it++) {
if ((*equals)(*it, tagName, propertyName, elementNS)) {
type = it->type;
break;
}
}
return getTrustedTypeName(type);
return type;
}

String FindTypeNameInTypeTable(const String& tagName,
const String& propertyName,
const String& elementNS,
PropertyEqualsFn equals) {
return getTrustedTypeName(
FindTypeInTypeTable(tagName, propertyName, elementNS, equals));
}

String TrustedTypePolicyFactory::getPropertyType(
const String& tagName,
const String& propertyName,
const String& elementNS) const {
return FindTypeInTypeTable(tagName.LowerASCII(), propertyName, elementNS,
&EqualsProperty);
return FindTypeNameInTypeTable(tagName.LowerASCII(), propertyName, elementNS,
&EqualsProperty);
}

String TrustedTypePolicyFactory::getAttributeType(
const String& tagName,
const String& attributeName,
const String& tagNS,
const String& attributeNS) const {
return FindTypeInTypeTable(tagName.LowerASCII(), attributeName, tagNS,
&EqualsAttribute);
return FindTypeNameInTypeTable(tagName.LowerASCII(), attributeName, tagNS,
&EqualsAttribute);
}

String TrustedTypePolicyFactory::getPropertyType(
Expand Down Expand Up @@ -372,4 +382,20 @@ void TrustedTypePolicyFactory::Trace(Visitor* visitor) const {
visitor->Trace(policy_map_);
}

inline bool FindEventHandlerAttributeInTable(
const AtomicString& attributeName) {
return SpecificTrustedType::kScript ==
FindTypeInTypeTable("*", attributeName, String(), &EqualsAttribute);
}

bool TrustedTypePolicyFactory::IsEventHandlerAttributeName(
const AtomicString& attributeName) {
// Check that the "on" prefix indeed filters out only non-event handlers.
DCHECK(!FindEventHandlerAttributeInTable(attributeName) ||
attributeName.StartsWithIgnoringASCIICase("on"));

return attributeName.StartsWithIgnoringASCIICase("on") &&
FindEventHandlerAttributeInTable(attributeName);
}

} // namespace blink
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,13 @@ class CORE_EXPORT TrustedTypePolicyFactory final
ExecutionContext* GetExecutionContext() const override;
void Trace(Visitor*) const override;

// Check whether a given attribute is considered an event handler.
//
// This function is largely unrelated to the TrustedTypePolicyFactory, but
// it reuses the data from getTypeMapping, which is why we have defined it
// here.
static bool IsEventHandlerAttributeName(const AtomicString& attributeName);

private:
const WrapperTypeInfo* GetWrapperTypeInfoFromScriptValue(ScriptState*,
const ScriptValue&);
Expand Down
6 changes: 6 additions & 0 deletions blink/renderer/core/trustedtypes/trusted_types_util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -599,4 +599,10 @@ String TrustedTypesCheckForExecCommand(
return TrustedTypesCheckForHTML(html, execution_context, exception_state);
}

bool IsTrustedTypesEventHandlerAttribute(const QualifiedName& q_name) {
return q_name.NamespaceURI().IsNull() &&
TrustedTypePolicyFactory::IsEventHandlerAttributeName(
q_name.LocalName());
}

} // namespace blink
11 changes: 11 additions & 0 deletions blink/renderer/core/trustedtypes/trusted_types_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ namespace blink {

class ExceptionState;
class ExecutionContext;
class QualifiedName;
class V8UnionStringOrTrustedScript;
class V8UnionStringTreatNullAsEmptyStringOrTrustedScript;

Expand Down Expand Up @@ -87,6 +88,16 @@ TrustedTypesCheckForExecCommand(const String&,
// into account.
CORE_EXPORT bool RequireTrustedTypesCheck(const ExecutionContext*);

// Determine whether an attribute is considered an event handler by Trusted
// Types.
//
// Note: This is different from Element::IsEventHandlerAttribute, because
// Element only needs this distinction for built-in attributes, but not for
// user-defined property names. But Trusted Types needs this for any built-in or
// user-defined attribute/property, and thus must check against a list of known
// event handlers.
bool IsTrustedTypesEventHandlerAttribute(const QualifiedName&);

} // namespace blink

#endif // THIRD_PARTY_BLINK_RENDERER_CORE_TRUSTEDTYPES_TRUSTED_TYPES_UTIL_H_
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<!DOCTYPE html>
<head>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<meta http-equiv="Content-Security-Policy" content="require-trusted-types-for 'script'">
</head>
<body>
<script>
const element = document.createElement("div");

[
"onclick",
"onchange",
"onfocus",
"oNclick",
"OnClIcK"
].forEach(name => {
test(t => {
assert_throws_js(TypeError,
_ => element.setAttribute(name, "2+2"));
}, `Event handler ${name} should be blocked.`);
});

[
"one",
"oNe",
"onIcon",
"offIcon",
"blubb"
].forEach(name => {
test(t => {
element.setAttribute(name, "2+2");
}, `Non-event handler ${name} should not be blocked.`);
});

// We'd like to be sure we're not missing anything. Let's "query" an HTML
// element about which attributes it knows.
const div = document.createElement("div");
for(name in div.__proto__) {
const should_be_event_handler = name.startsWith("on");
if (should_be_event_handler) {
test(t => {
assert_throws_js(TypeError,
_ => element.setAttribute(name, "2+2"));
}, `Event handler div.${name} should be blocked.`);
} else {
test(t => {
element.setAttribute(name, "2+2");
}, `Non-event handler div.${name} should not be blocked.`);
}
}
</script>
</body>

0 comments on commit a69b64a

Please sign in to comment.