Stage 0 Draft / June 5, 2019
HostEnsureCanCompileStrings Passthru
See also the
explainer.
Background
The
Trusted Types proposal seeks
to double-check risky operations like code loading by requiring that code portions
have a runtime type that indicates that they have been explicitly trusted. The Function builtin, whether invoked via [[Construct]] or [[Apply]],
interprets its last argument as a JS FunctionBody. Step 16 of
Runtime Semantics: CreateDynamicFunction says
- Set bodyText to ? ToString(bodyText).
which loses information about whether the body is a
TrustedScript
preventing Trusted Types guards from allowing
TrustedScripts from loading as code while
preventing strings that might be attacker controlled from loading as code. Currently, the information available to the check is per-
realm:
- Perform ? HostEnsureCanCompileStrings(callerRealm, calleeRealm).
This proposal aims to provide additional context to HostEnsureCanCompileStrings,
and reorder the steps in CreateDynamicFunction so that HostEnsureCanCompilerStrings
has runtime-type-information.
The production to use to parse bodyText. The value of bodyText before coercion to a string.
Additionally, the
default policy's
createScript
callback return a value that is used in place of their input.
For example, if the default policy's createScript
callback returns "output"
given
"input"
then eval("input")
would load and run a ScriptBody parsed from the source text
output
.
<meta http-equiv="Content-Security-Policy" content="trusted-types default" />
<script>
TrustedTypes.createPolicy(
'default',
{
createScript(code) {
if (code === 'input') { return 'output'; }
throw new Error('blocked script execution');
},
});
globalThis.input = 1;
globalThis.output = 2;
eval('input') === globalThis.output;
</script>
This proposal adjusts callers of the callback to expect a result and to use it in place
of the inputs.
Changes to HostEnsureCanCompileStrings
1HostEnsureCanCompileStrings HostBeforeCompileValue ( callerRealm, calleeRealm, goal, args)
HostEnsureCanCompileStrings HostBeforeCompileValue is an implementation-defined abstract operation that allows host environments to block certain ECMAScript functions which allow developers to compile strings into ECMAScript code.
An implementation
of HostEnsureCanCompileStrings HostBeforeCompileValue
may complete normally or abruptly. Any abrupt completions will be
propagated to its callers. The default implementation
of HostEnsureCanCompileStrings HostBeforeCompileValue
is to unconditionally return an empty a normal
completion with a value of args.
args must be a list of values.
Any normal completion value from HostBeforeCompileValue must
be a list of values.
Any normal completion value from HostBeforeCompileValue
should be used by callers in place of the input args.
Note 1For eval
args will have a single element, the
source to evaluate. For MakeDynamicFunction it may have any number
of elements that are joined on comma to form source text for
FormalParameters followed by a body.
Note 2goal is the production which will be used to parse
the last element of args. For example, if called via %Function%
it might be the grammar symbol FunctionBody[~Yield, ~Await] and
from %eval% it might be the grammar symbol
Script.
Implementations that stringify any element i of args where
i ≠ 0 must stringify args[i-1] before stringifying args[i].
Implementations that stringify any element of args should return
the stringified result in place of that element where that element informs
part of the output.
Note 3This avoids visible side-effects due to multiple
stringification of user-defined objects as in:
new Function(
{
toString() {
console.log('parameter stringified');
return 'x, y';
}
},
{
toString() {
console.log('body stringified');
return 'x + y';
}
});
Implementations must return a single element list when args has
a single element and goal is ScriptBody.
Note 4This avoids complicating PerformEval.
Changes to CreateDynamicFunction
CreateDynamicFunction waits until after it figures out what kind of function it is creating
and uses the result of the adjusted host callout.
2Runtime Semantics: CreateDynamicFunction ( constructor, newTarget, kind, args )
The abstract operation CreateDynamicFunction is called with arguments constructor, newTarget, kind, and args. constructor is the constructor function that is performing this action, newTarget is the constructor that new
was initially applied to, kind is either "normal"
, "generator"
, "async"
, or "async generator"
, and args is a List containing the actual argument values that were passed to constructor. The following steps are taken:
- Assert: The execution context stack has at least two elements.
- Let callerContext be the second to top element of the execution context stack.
- Let callerRealm be callerContext's Realm.
- Let calleeRealm be the current Realm Record.
Perform ? HostEnsureCanCompileStrings(callerRealm, calleeRealm).- If newTarget is undefined, set newTarget to constructor.
- If kind is
"normal"
, then- Let goal be the grammar symbol FunctionBody[~Yield, ~Await].
- Let parameterGoal be the grammar symbol FormalParameters[~Yield, ~Await].
- Let fallbackProto be
"%FunctionPrototype%"
.
- Else if kind is
"generator"
, then- Let goal be the grammar symbol GeneratorBody.
- Let parameterGoal be the grammar symbol FormalParameters[+Yield, ~Await].
- Let fallbackProto be
"%Generator%"
.
- Else if kind is
"async"
, then- Let goal be the grammar symbol AsyncFunctionBody.
- Let parameterGoal be the grammar symbol FormalParameters[~Yield, +Await].
- Let fallbackProto be
"%AsyncFunctionPrototype%"
.
- Else,
- Assert: kind is
"async generator"
. - Let goal be the grammar symbol AsyncGeneratorBody.
- Let parameterGoal be the grammar symbol FormalParameters[+Yield, +Await].
- Let fallbackProto be
"%AsyncGenerator%"
.
- Set args to ! HostBeforeCompileValue(callerRealm, calleeRealm, goal, args).
- Let argCount be the number of elements in args.
- Let P be the empty String.
- If argCount = 0, let bodyText be the empty String.
- Else if argCount = 1, let bodyText be args[0].
- Else argCount > 1,
- Let firstArg be args[0].
- Set P to ? ToString(firstArg).
- Let k be 1.
- Repeat, while k < argCount - 1
- Let nextArg be args[k].
- Let nextArgString be ? ToString(nextArg).
- Set P to the string-concatenation of the previous value of P,
","
(a comma), and nextArgString. - Increase k by 1.
- Let bodyText be args[k].
- Set bodyText to ? ToString(bodyText).
- Let parameters be the result of parsing P, interpreted as UTF-16 encoded Unicode text as described in 6.1.4, using parameterGoal as the goal symbol. Throw a SyntaxError exception if the parse fails.
- Let body be the result of parsing bodyText, interpreted as UTF-16 encoded Unicode text as described in 6.1.4, using goal as the goal symbol. Throw a SyntaxError exception if the parse fails.
- Let strict be ContainsUseStrict of body.
- If any static semantics errors are detected for parameters or body, throw a SyntaxError or a ReferenceError exception, depending on the type of the error. If strict is true, the Early Error rules for
UniqueFormalParameters:FormalParameters
are applied. Parsing and early error detection may be interweaved in an implementation-dependent manner.
- If strict is true and IsSimpleParameterList of parameters is false, throw a SyntaxError exception.
- If any element of the BoundNames of parameters also occurs in the LexicallyDeclaredNames of body, throw a SyntaxError exception.
- If body Contains SuperCall is true, throw a SyntaxError exception.
- If parameters Contains SuperCall is true, throw a SyntaxError exception.
- If body Contains SuperProperty is true, throw a SyntaxError exception.
- If parameters Contains SuperProperty is true, throw a SyntaxError exception.
- If kind is
"generator"
or "async generator"
, then- If parameters Contains YieldExpression is true, throw a SyntaxError exception.
- If kind is
"async"
or "async generator"
, then- If parameters Contains AwaitExpression is true, throw a SyntaxError exception.
- If strict is true, then
- If BoundNames of parameters contains any duplicate elements, throw a SyntaxError exception.
- Let proto be ? GetPrototypeFromConstructor(newTarget, fallbackProto).
- Let F be FunctionAllocate(proto, strict, kind).
- Let realmF be F.[[Realm]].
- Let scope be realmF.[[GlobalEnv]].
- Perform FunctionInitialize(F, Normal, parameters, body, scope).
- If kind is
"generator"
, then- Let prototype be ObjectCreate(%GeneratorPrototype%).
- Perform DefinePropertyOrThrow(F,
"prototype"
, PropertyDescriptor { [[Value]]: prototype, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }).
- Else if kind is
"async generator"
, then- Let prototype be ObjectCreate(%AsyncGeneratorPrototype%).
- Perform DefinePropertyOrThrow(F,
"prototype"
, PropertyDescriptor { [[Value]]: prototype, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }).
- Else if kind is
"normal"
, perform MakeConstructor(F). - NOTE: Async functions are not constructable and do not have a [[Construct]] internal method or a
"prototype"
property. - Perform SetFunctionName(F,
"anonymous"
). - Let prefix be the prefix associated with kind in .
- Let sourceText be the string-concatenation of prefix,
" anonymous("
, P, 0x000A (LINE FEED), ") {"
, 0x000A (LINE FEED), bodyText, 0x000A (LINE FEED), and "}"
. - Set F.[[SourceText]] to sourceText.
- Return F.
Changes to eval
%eval% uses the adjusted host callout and finds the source text in its result.
3eval ( x )
The eval
function is the %eval% intrinsic object. When the eval
function is called with one argument x, the following steps are taken:
- Assert: The execution context stack has at least two elements.
- Let callerContext be the second to top element of the execution context stack.
- Let callerRealm be callerContext's Realm.
- Let calleeRealm be the current Realm Record.
- Let args be a list containing only x.
Perform ? HostEnsureCanCompileStrings(callerRealm, calleeRealm).
Set args to ! HostBeforeCompileValue(callerRealm, calleeRealm, Script, args).- Set x to ? Get(args, 0).
- Return ? PerformEval(x, calleeRealm, false, false).
Changes to direct eval
Direct eval uses the adjusted host callout and finds the source text in its result.
4Runtime Semantics: Evaluation
CallExpression:CoverCallExpressionAndAsyncArrowHead
- Let expr be CoveredCallExpression of CoverCallExpressionAndAsyncArrowHead.
- Let memberExpr be the MemberExpression of expr.
- Let arguments be the Arguments of expr.
- Let ref be the result of evaluating memberExpr.
- Let func be ? GetValue(ref).
- If Type(ref) is Reference and IsPropertyReference(ref) is false and GetReferencedName(ref) is
"eval"
, then- If SameValue(func, %eval%) is true, then
- Let argList be ? ArgumentListEvaluation of arguments.
- If argList has no elements, return undefined.
- Let evalText be the first element of argList.
- If the source code matching this CallExpression is strict mode code, let strictCaller be true. Otherwise let strictCaller be false.
- Let evalRealm be the current Realm Record.
- Let args be a list containing only evalText.
Perform ? HostEnsureCanCompileStrings(evalRealm, evalRealm).
Set args to ! HostBeforeCompileValue(evalRealm, evalRealm, Script, args).- Set evalText to ? Get(args, 0).
- Return ? PerformEval(evalText, evalRealm, strictCaller, true).
- Let thisCall be this CallExpression.
- Let tailCall be IsInTailPosition(thisCall).
- Return ? EvaluateCall(func, ref, arguments, tailCall).
A CallExpression evaluation that executes step 6.a.ixvii is a direct eval.
ACopyright & Software License
Copyright Notice
© 2019 Mike Samuel
Software License
All Software contained in this document ("Software") is protected by copyright and is being made available under the "BSD License", included below. This Software may be subject to third party rights (rights from parties other than Ecma International), including patent rights, and no licenses under such third party rights are granted under this license even if the third party concerned is a member of Ecma International. SEE THE ECMA CODE OF CONDUCT IN PATENT MATTERS AVAILABLE AT https://ecma-international.org/memento/codeofconduct.htm FOR INFORMATION REGARDING THE LICENSING OF PATENT CLAIMS THAT ARE REQUIRED TO IMPLEMENT ECMA INTERNATIONAL STANDARDS.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- Neither the name of the authors nor Ecma International may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE ECMA INTERNATIONAL "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ECMA INTERNATIONAL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.