Skip to content

Commit

Permalink
Bug 1934373 - Add more tests for EnsureCSPDoesNotBlockStringCompilati…
Browse files Browse the repository at this point in the history
…on. r=smaug

Improve test coverage for [1], considering string checks for arguments
that implement TrustedScript [2] and the rejection condition on whether
"Get Trusted Type compliant string" modified the input [3].

[1] https://w3c.github.io/webappsec-csp/#can-compile-strings
[2] web-platform-tests/wpt#49371
[3] web-platform-tests/wpt#49367

Differential Revision: https://phabricator.services.mozilla.com/D230369
  • Loading branch information
fred-wang committed Nov 30, 2024
1 parent 94c4474 commit 02ba234
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[eval-function-constructor-untrusted-arguments-and-applying-default-policy.html]
[plain string at index 0 (default policy modifying the function text).]
expected: FAIL

[plain string at index 1 (default policy modifying the function text).]
expected: FAIL

[plain string at index 2 (default policy modifying the function text).]
expected: FAIL

[plain string at index 3 (default policy modifying the function text).]
expected: FAIL

[TrustedScript with forged toString() at index 0 (default policy modifying the function text).]
expected: FAIL

[TrustedScript with forged toString() at index 1 (default policy modifying the function text).]
expected: FAIL

[TrustedScript with forged toString() at index 2 (default policy modifying the function text).]
expected: FAIL

[TrustedScript with forged toString() at index 3 (default policy modifying the function text).]
expected: FAIL
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,15 @@

[Function constructor with mixed plain and trusted strings, mask #14]
expected: FAIL

[Function constructor with trusted strings, and a forged toString() for the one at index 0]
expected: FAIL

[Function constructor with trusted strings, and a forged toString() for the one at index 1]
expected: FAIL

[Function constructor with trusted strings, and a forged toString() for the one at index 2]
expected: FAIL

[Function constructor with trusted strings, and a forged toString() for the one at index 3]
expected: FAIL
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<!DOCTYPE html>
<html>
<head>
<link rel="help" href="https://w3c.github.io/webappsec-csp/#can-compile-strings">
<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>
let policy = trustedTypes.createPolicy("p", { createScript: s => s });

// Define a default policy that rename variables Xn to Yn.
function renameVariableXtoY(s) { return s.replace(/X([1-9]+)/g, "Y$1"); }
trustedTypes.createPolicy("default", {
createScript: s => renameVariableXtoY(s)
});

// As in eval-function-constructor.html we consider two kind of untrusted
// arguments: plain string or TrustedScript with a forged toString().
const untrusted_argument_factory = {
"plain string": arg_value => arg_value,
"TrustedScript with forged toString()": arg_value => Object.assign(
policy.createScript(arg_value), { toString: () => ` ${arg_value} ` })
};

for (const [untrusted_argument_name, untrusted_argument_builder] of
Object.entries(untrusted_argument_factory)) {
const args = ["X1", "X2", "X3 = 5", "return (X1+X2)*X3;"];
// Wrap the function arguments into TrustedTypes, except for the one at the
// specified index. That argument will cause isTrusted=false in
// EnsureCSPDoesNotBlockStringCompilation and so "Get Trusted Type
// compliant string" will be executed on the function text, which use Xn
// variables. Consequently, the default policy will modify the function text
// which will cause an EvalError to be thrown.
args.forEach((_, index) => {
test(t => {
let mixed_args = args.map((arg_value, arg_index) =>
arg_index == index ? untrusted_argument_builder(arg_value)
: policy.createScript(arg_value));
assert_throws_js(EvalError, _ => new Function(...mixed_args));
}, `${untrusted_argument_name} at index ${index} (default policy modifying the function text).`);
});

// Do the same but apply the variable renaming before building the function,
// so that the default policy won't modify the function text anymore. In
// that case, the function is built without error.
const argsWithY = args.map(renameVariableXtoY);
argsWithY.forEach((_, index) => {
test(t => {
let mixed_args = argsWithY.map((arg_value, arg_index) =>
arg_index == index ? untrusted_argument_builder(arg_value)
: policy.createScript(arg_value));
const f = new Function(...mixed_args);
assert_equals(f(1,2,3), 9);
assert_equals(f(1,2), 15);
}, `${untrusted_argument_name} at index ${index} (default policy leaving the function text unchanged).`);
});
}
</script>
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<script nonce="abc" src="/resources/testharness.js"></script>
<script nonce="abc" src="/resources/testharnessreport.js"></script>
<link rel="help" href="https://w3c.github.io/webappsec-csp/#can-compile-strings">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<!-- This CSP rule as well as the null default policy will make the
"Get Trusted Type compliant string" algorithm throw for String input. -->
<meta http-equiv="Content-Security-Policy"
content="require-trusted-types-for 'script'">
</head>
Expand All @@ -12,7 +15,7 @@
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,
// Call 'new Function(...args)', but with a subset 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) {
Expand All @@ -23,19 +26,38 @@
}

// Generate all combinations of String/TrustedScript, except for the one
// where all argumentes are TrustedScript.
// where all arguments are TrustedScript. The first String argument will set
// isTrusted=false in EnsureCSPDoesNotBlockStringCompilation and so
// "Get Trusted Type compliant string" will be executed on the function text.
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.
// Now do one with all trusted arguments. In that case, isTrusted=true in
// EnsureCSPDoesNotBlockStringCompilation and the function is built without
// error.
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);

// Similarly wrap all args in TrustedScript objects, but forges the toString()
// method at one index so that it adds extra leading/trailing space compared
// to the trusted data. This string mismatch also lead to isTrusted=false in
// EnsureCSPDoesNotBlockStringCompilation.
args.forEach((_, index) => {
test(t => {
let mixed_args = args.map((arg_value, arg_index) => {
let obj = policy.createScript(arg_value);
return arg_index == index ?
Object.assign(obj, { toString: () => ` ${arg_value} ` }) : obj;
});
assert_throws_js(EvalError, _ => new Function(...mixed_args));
}, `Function constructor with trusted strings, and a forged toString() for the one at index ${index}`);
});
</script>

0 comments on commit 02ba234

Please sign in to comment.