fix(core): address Trusted Types bug in Chrome 83 (#40815)

In Chrome 83 passing a TrustedScript to eval just returns the
TrustedScript back without evaluating it, causing the
newTrustedFunctionFor{Dev,JIT} functions to fail. This is a browser bug
that has been fixed in Chrome 84, and only affects Angular applications
running with JIT (which includes unit tests).

As a temporary workaround for users still on Chrome 83, detect when this
occurs in the newTrustedFunctionFor* functions and fall back to the
straightforward, non-Trusted Types compatible implementation. The only
combination that is left affected consists of Angular applications
running with JIT, that have explicitly configured Trusted Types in
enforcement mode, with users that are still on Chrome 83.

Also correct docstring for newTrustedFunctionForJIT.

PR Close #40815
This commit is contained in:
Bjarki 2021-02-12 00:16:12 +00:00 committed by Joey Perrott
parent 9661873df1
commit 980f6a4958
2 changed files with 17 additions and 5 deletions

View File

@ -93,9 +93,7 @@ function trustedScriptFromString(script: string): TrustedScript|string {
} }
/** /**
* Unsafely call the Function constructor with the given string arguments. It * Unsafely call the Function constructor with the given string arguments.
* 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 * @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 * must go through security review. In particular, it must be assured that it
* is only called from the JIT compiler, as use in other code can lead to XSS * is only called from the JIT compiler, as use in other code can lead to XSS
@ -113,7 +111,7 @@ export function newTrustedFunctionForJIT(...args: string[]): Function {
// below, where the Chromium bug is also referenced: // below, where the Chromium bug is also referenced:
// https://github.com/w3c/webappsec-trusted-types/wiki/Trusted-Types-for-function-constructor // https://github.com/w3c/webappsec-trusted-types/wiki/Trusted-Types-for-function-constructor
const fnArgs = args.slice(0, -1).join(','); const fnArgs = args.slice(0, -1).join(',');
const fnBody = args.pop()!.toString(); const fnBody = args[args.length - 1];
const body = `(function anonymous(${fnArgs} const body = `(function anonymous(${fnArgs}
) { ${fnBody} ) { ${fnBody}
})`; })`;
@ -122,6 +120,13 @@ export function newTrustedFunctionForJIT(...args: string[]): Function {
// being stripped out of JS binaries even if not used. The global['eval'] // being stripped out of JS binaries even if not used. The global['eval']
// indirection fixes that. // indirection fixes that.
const fn = global['eval'](trustedScriptFromString(body) as string) as Function; const fn = global['eval'](trustedScriptFromString(body) as string) as Function;
if (fn.bind === undefined) {
// Workaround for a browser bug that only exists in Chrome 83, where passing
// a TrustedScript to eval just returns the TrustedScript back without
// evaluating it. In that case, fall back to the most straightforward
// implementation:
return new Function(...args);
}
// To completely mimic the behavior of calling "new Function", two more // To completely mimic the behavior of calling "new Function", two more
// things need to happen: // things need to happen:

View File

@ -111,7 +111,7 @@ export function newTrustedFunctionForDev(...args: string[]): Function {
// below, where the Chromium bug is also referenced: // below, where the Chromium bug is also referenced:
// https://github.com/w3c/webappsec-trusted-types/wiki/Trusted-Types-for-function-constructor // https://github.com/w3c/webappsec-trusted-types/wiki/Trusted-Types-for-function-constructor
const fnArgs = args.slice(0, -1).join(','); const fnArgs = args.slice(0, -1).join(',');
const fnBody = args.pop()!.toString(); const fnBody = args[args.length - 1];
const body = `(function anonymous(${fnArgs} const body = `(function anonymous(${fnArgs}
) { ${fnBody} ) { ${fnBody}
})`; })`;
@ -120,6 +120,13 @@ export function newTrustedFunctionForDev(...args: string[]): Function {
// being stripped out of JS binaries even if not used. The global['eval'] // being stripped out of JS binaries even if not used. The global['eval']
// indirection fixes that. // indirection fixes that.
const fn = global['eval'](trustedScriptFromString(body) as string) as Function; const fn = global['eval'](trustedScriptFromString(body) as string) as Function;
if (fn.bind === undefined) {
// Workaround for a browser bug that only exists in Chrome 83, where passing
// a TrustedScript to eval just returns the TrustedScript back without
// evaluating it. In that case, fall back to the most straightforward
// implementation:
return new Function(...args);
}
// To completely mimic the behavior of calling "new Function", two more // To completely mimic the behavior of calling "new Function", two more
// things need to happen: // things need to happen: