-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Trusted Types] Fix bypass with multiple-argument DOM accessors
Some DOM methods accept multiple DOM nodes or text strings as arguments. When applied to a <script> node, these (presently) allow string insertion into <script> elements without Trusted Types checking. Bug: 948614 Change-Id: I18dd0f3c0652bda2c6256cdba3359e476ada8b09 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1688030 Reviewed-by: Mason Freed <masonfreed@chromium.org> Reviewed-by: Mike West <mkwst@chromium.org> Commit-Queue: Daniel Vogelheim <vogelheim@chromium.org> Cr-Commit-Position: refs/heads/master@{#678633}
- Loading branch information
1 parent
f04df0d
commit c71415b
Showing
2 changed files
with
155 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<script src="/resources/testharness.js"></script> | ||
<script src="/resources/testharnessreport.js"></script> | ||
<script src="support/helper.sub.js"></script> | ||
</head> | ||
<body> | ||
<div id="container"></div> | ||
<script> | ||
const container = document.querySelector("#container"); | ||
const policy = window.TrustedTypes.createPolicy("policy", { | ||
createScript: t => t, | ||
}); | ||
function stringify(arg) { | ||
return "textContent" in arg.__proto__ ? arg.textContent : arg.toString() | ||
} | ||
|
||
// This test case mirrors the block-Node-multiple-arguments case except | ||
// that, because Trusted Types is not enabled, no exceptions should be | ||
// thrown anyhwere. | ||
const targets = ["div", "script"]; | ||
const all_args = [ | ||
[ policy.createScript("'createScript';") ], | ||
[ policy.createScript("'cresteScript #1';"), policy.createScript("'#2;'") ], | ||
[ "'plain text';" ], | ||
[ "'plain text #1';", "'plain text #2';" ], | ||
[ document.createTextNode("'node';") ], | ||
[ document.createTextNode("'node #1';"), | ||
document.createTextNode("'node #2';") ], | ||
[ "'mixed';", document.createTextNode("'node';") ], | ||
[ "'mixed';", policy.createScript("'script';") ], | ||
[ document.createTextNode("'node';"), | ||
policy.createScript("'script';") ], | ||
]; | ||
|
||
for (target of targets) { | ||
for (args of all_args) { | ||
|
||
for (setter of [container.replaceWith, container.after, container.before]) { | ||
test(t => { | ||
var outer = document.createElement(target); | ||
container.appendChild(outer); | ||
var inner = document.createElement("p"); | ||
outer.appendChild(inner); | ||
setter.apply(inner, args); | ||
assert_equals(outer.textContent, args.map(stringify).join("")); | ||
|
||
}, `${setter.name}(${args.toString()}) on <${target}> should pass`); | ||
} | ||
|
||
for (setter of [container.append, container.prepend]) { | ||
test(t => { | ||
let outer = document.createElement(target); | ||
container.appendChild(outer); | ||
setter.apply(outer, args); | ||
assert_equals(outer.textContent, args.map(stringify).join("")); | ||
}, `${setter.name}(${args.toString()}) on <${target}> should pass`); | ||
} | ||
|
||
} | ||
} | ||
</script> | ||
</body> | ||
</html> |
90 changes: 90 additions & 0 deletions
90
trusted-types/block-Node-multiple-arguments.tentative.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<script src="/resources/testharness.js"></script> | ||
<script src="/resources/testharnessreport.js"></script> | ||
<script src="support/helper.sub.js"></script> | ||
<meta http-equiv="Content-Security-Policy" content="trusted-types *"> | ||
</head> | ||
<body> | ||
<div id="container"></div> | ||
<script> | ||
const container = document.querySelector("#container"); | ||
const policy = window.TrustedTypes.createPolicy("policy", { | ||
createScript: t => t, | ||
}); | ||
function stringify(arg) { | ||
return "textContent" in arg.__proto__ ? arg.textContent : arg.toString() | ||
} | ||
|
||
// Test all combinations of: | ||
// - DOM methods: append, prepend, replaceWith, after, before | ||
// - one or two parameters, string, Text node, Trusted Script | ||
// - into regular container or <script> element. | ||
// | ||
// Test arguments are all string literals with a semicolon, because - | ||
// depending on target - it might be injected into a <script> and shouldn't | ||
// cause syntax errors there. | ||
const targets = ["div", "script"]; | ||
const pass_args = [ | ||
[ policy.createScript("'createScript';") ], | ||
[ policy.createScript("'cresteScript #1';"), policy.createScript("'#2;'") ], | ||
]; | ||
const fail_args = [ | ||
[ "'plain text';" ], | ||
[ "'plain text #1';", "'plain text #2';" ], | ||
[ document.createTextNode("'node';") ], | ||
[ document.createTextNode("'node #1';"), | ||
document.createTextNode("'node #2';") ], | ||
[ "'mixed';", document.createTextNode("'node';") ], | ||
[ "'mixed';", policy.createScript("'script';") ], | ||
[ document.createTextNode("'node';"), | ||
policy.createScript("'script';") ], | ||
]; | ||
const all_args = [].concat(pass_args).concat(fail_args); | ||
|
||
for (target of targets) { | ||
for (args of all_args) { | ||
var should_fail = target == "script" && fail_args.indexOf(args) != -1; | ||
var fail_string = should_fail ? "fail" : "pass"; | ||
|
||
for (setter of [container.replaceWith, container.after, container.before]) { | ||
test(t => { | ||
var outer = document.createElement(target); | ||
container.appendChild(outer); | ||
var inner = document.createElement("p"); | ||
outer.appendChild(inner); | ||
var test_fn = _ => { setter.apply(inner, args); }; | ||
var expected; | ||
if (should_fail) { | ||
assert_throws(new TypeError(), test_fn, "This should throw."); | ||
expected = ""; | ||
} else { | ||
test_fn(); | ||
expected = args.map(stringify).join(""); | ||
} | ||
assert_equals(outer.textContent, expected); | ||
}, `${setter.name}(${args.toString()}) on <${target}> should ${fail_string}`); | ||
} | ||
|
||
for (setter of [container.append, container.prepend]) { | ||
test(t => { | ||
let outer = document.createElement(target); | ||
container.appendChild(outer); | ||
var test_fn = _ => { setter.apply(outer, args); }; | ||
var expected; | ||
if (should_fail) { | ||
assert_throws(new TypeError(), test_fn, "This should throw."); | ||
expected = ""; | ||
} else { | ||
test_fn(); | ||
expected = args.map(stringify).join(""); | ||
} | ||
assert_equals(outer.textContent, expected); | ||
}, `${setter.name}(${args.toString()}) on <${target}> should ${fail_string}`); | ||
} | ||
} | ||
} | ||
</script> | ||
</body> | ||
</html> |