feat(core): add Trusted Types workaround for Function constructor (#39209)

Chrome currently does not support passing TrustedScript to the Function
constructor, and instead fails with a Trusted Types violation when
called. As the Function constructor is used in a handful of places
within Angular, such as in the JIT compiler and named_array_type, the
workaround proposed on the following page is implemented:
https://github.com/w3c/webappsec-trusted-types/wiki/Trusted-Types-for-function-constructor

To be precise, it constructs a string representing an anonymous function
in a way that is equivalent to what the Function constructor does,
promotes it to a TrustedScript and then calls eval.

To facilitate backwards compatibility, new Function is used directly in
environments that do not support Trusted Types.

PR Close #39209
This commit is contained in:
Bjarki 2020-10-09 11:57:32 +00:00 committed by atscott
parent 929e0df377
commit 5913e5c4e8
1 changed files with 46 additions and 0 deletions

View File

@ -85,3 +85,49 @@ export function trustedScriptFromString(script: string): TrustedScript|string {
export function trustedScriptURLFromString(url: string): TrustedScriptURL|string { export function trustedScriptURLFromString(url: string): TrustedScriptURL|string {
return getPolicy()?.createScriptURL(url) || url; return getPolicy()?.createScriptURL(url) || url;
} }
/**
* Unsafely call the Function constructor with the given string arguments. It
* is only available in development mode, and should be stripped out of
* production code.
* @security This is a security-sensitive function; any use of this function
* must go through security review. In particular, it must be assured that it
* is only called from development code, as use in production code can lead to
* XSS vulnerabilities.
*/
export function newTrustedFunctionForDev(...args: string[]): Function {
if (typeof ngDevMode === 'undefined') {
throw new Error('newTrustedFunctionForDev should never be called in production');
}
if (!global.trustedTypes) {
// In environments that don't support Trusted Types, fall back to the most
// straightforward implementation:
return new Function(...args);
}
// Chrome currently does not support passing TrustedScript to the Function
// constructor. The following implements the workaround proposed on the page
// below, where the Chromium bug is also referenced:
// https://github.com/w3c/webappsec-trusted-types/wiki/Trusted-Types-for-function-constructor
const fnArgs = args.slice(0, -1).join(',');
const fnBody = args.pop()!.toString();
const body = `(function anonymous(${fnArgs}
) { ${fnBody}
})`;
// Using eval directly confuses the compiler and prevents this module from
// being stripped out of JS binaries even if not used. The global['eval']
// indirection fixes that.
const fn = global['eval'](trustedScriptFromString(body) as string) as Function;
// To completely mimic the behavior of calling "new Function", two more
// things need to happen:
// 1. Stringifying the resulting function should return its source code
fn.toString = () => body;
// 2. When calling the resulting function, `this` should refer to `global`
return fn.bind(global);
// When Trusted Types support in Function constructors is widely available,
// the implementation of this function can be simplified to:
// return new Function(...args.map(a => trustedScriptFromString(a)));
}