diff --git a/README.md b/README.md index f08dbf3..d565db5 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,7 @@ - [Motivation](#motivation) - [Problem 1: %eval% does not accept objects in lieu of strings for code](#problem-1-eval-does-not-accept-objects-in-lieu-of-strings-for-code) - [Problem 2: Host callout does not receive type information](#problem-2-host-callout-does-not-receive-type-information) -- [Problem 3: Host callout does not receive the code to check](#problem-3-host-callout-does-not-receive-the-code-to-check) -- [Problem 4: Host callout cannot adjust values](#problem-4-host-callout-cannot-adjust-values) +- [Problem 3: Host callout cannot adjust values](#problem-3-host-callout-cannot-adjust-values) - [Tests](#tests) ## Status @@ -149,9 +148,9 @@ but without changing the semantics of pre-existing programs. ## Problem 2: Host callout does not receive type information Currently the information available to decide whether to allow compilation of -a string is a pair of realms. +a string is a realm, a list of strings from parameters, a string from body, and a boolean. -> HostEnsureCanCompileStrings( _callerRealm_, _calleeRealm_ ) +> HostEnsureCanCompileStrings( __calleeRealm_, _parameterStrings_, _bodyString_, _direct_ ) > > HostEnsureCanCompileStrings is an implementation-defined abstract > operation that allows host environments to block certain ECMAScript @@ -178,43 +177,16 @@ the Function constructor arguments passed the `IsCodeLike` check. - Requires changes to the host callout (see also below): -## Problem 3: Host callout does not receive the code to check +## Problem 3: Host callout does not receive the full code to check -`HostEnsureCanCompileStrings` only passes the realm. In the web platform, -that callout is hooked to the [Content Security Policy algorithms](https://w3c.github.io/webappsec-csp/#should-block-inline"), which take action based on _code_ that is to be executed - e.g. an eval() argument, or a dynamically-created function body, to be able to include that code in [violation reports](https://w3c.github.io/webappsec-csp/#security-violation-reports) ([CSP3's issue 8](https://www.w3.org/TR/CSP3/#issues-index)) - -As such, some implementations (v8 and SpiderMonkey) actually also pass the code string -to the host, and in the case of new Function() perform the callout -later in `CreateDynamicFunction`, after the function body is assembled. +`HostEnsureCanCompileStrings` is called with parameters for the source code, but they're not in a unified string. ### Solution -This proposal updates the host callout to contain the code string to be executed, +This proposal updates the host callout to contain the full code string to be executed, and moves the callout in `CreateDynamicFunction` after the function body is assembled. -### Spec / implementation mismatch - -Moving the callout in CreateDynamicFunction changes the behavior in -implementations that specify a non-default host callout. - -Currently the stringifier should not execute if the host disables the string -compilation: - -```javascript -new Function({ - toString: () => { - throw "Should not happen, as the callout would reject this earlier"; - }, -}); -``` - -Current implementations differ in behavior. For example, v8 and Spidermonkey -are not ES spec-compliant. JSC follows the spec - [In-browser proof of concept](https://gadgets.kotowicz.net/poc/createdynamicfunction.php). - -This proposal would cause the stringifier to execute _before_ the callout, -making it possible for the browser hosts to actually follow the CSP spec. - ## Problem 4: Host callout cannot adjust values Trusted Types defines a [default policy][] to make it easier to diff --git a/spec.emu b/spec.emu index 1a43936..f4912f3 100644 --- a/spec.emu +++ b/spec.emu @@ -23,83 +23,121 @@ contributors: Krzysztof Kotowicz, Mike Samuel - -

HostEnsureCanCompileStrings HostValidateDynamicCode ( _callerRealm_, _calleeRealm_, _codeString_, _wasCodeLike_, _compilationSink_)

-

HostEnsureCanCompileStrings HostValidateDynamicCode is a host-defined abstract operation that takes arguments _callerRealm_ (a Realm), _calleeRealm_ (a Realm), _codeString_ (a string), _wasCodeLike_ (a boolean), and _compilationSink_ (*"Function"* or *"eval"*) and allows host environments to blockguard certain ECMAScript functions which allow developers to compile strings into ECMAScript code.

-

An implementation of HostEnsureCanCompileStrings HostValidateDynamicCode may complete normally or abruptly. Any normal completion must return a String. Any abrupt completions will be propagated to its callers. The default implementation of HostEnsureCanCompileStringsHostValidateDynamicCode is to unconditionally return a empty normal completionNormalCompletion(_codeString_).

+ +

+ HostEnsureCanCompileStrings HostValidateDynamicCode ( + _calleeRealm_: a Realm Record, + _parameterStrings_: a List of Strings, + _bodyString_: a String, + _codeString_: a string, + _direct_: a Boolean, + _wasCodeLike_: a boolean, + _compilationSink_: *"Function"* or *"eval"* + ): either a normal completion containing ~unused~a string or a throw completion +

+
+
description
+
It allows host environments to guardblock certain ECMAScript functions which allow developers to interpret and evaluate strings as ECMAScript code.
+
+

+ _parameterStrings_ represents the strings that, when using one of the function constructors, will be concatenated together to build the parameters list. _bodyString_ represents the function body or the string passed to an `eval` call. + _codeString_ represents the full code string, + _direct_ signifies whether the evaluation is a direct eval. _wasCodeLike_ is a boolean that indicates whether the code was constructed from code-like objects. _compilationSink_ is a string that indicates the type of the compilation sink, either *"Function"* or *"eval"*. +

+

The default implementation of HostEnsureCanCompileStringsHostValidateDynamicCode is to return NormalCompletion(_codeString_).

- -

Runtime Semantics: PerformEval ( _x_, _callerRealm_, _strictCaller_, _direct_ )

-

The abstract operation PerformEval with arguments _x_, _callerRealm_, _strictCaller_, and _direct_ performs the following steps:

- - 1. Assert: If _direct_ is *false*, then _strictCaller_ is also *false*. - 1. Let _isCodeLike_ be ! IsCodeLike(_x_). - 1. If _isCodeLike_ is *true*, set _x_ to _x_.[[HostDefinedCodeLike]]. - 1. If Type(_x_) is not String, return _x_. - 1. Let _evalRealm_ be the current Realm Record. - 1. Perform ? HostEnsureCanCompileStrings(_callerRealm_, _calleeRealm_).
- Set _x_ to be ? HostValidateDynamicCode(_callerRealm_, _calleeRealm_, _x_, _isCodeLike_, *"eval"*). - 1. ... -
+ +

+ PerformEval ( + _x_: an ECMAScript language value, + _strictCaller_: a Boolean, + _direct_: a Boolean, + ): either a normal completion containing an ECMAScript language value or a throw completion +

+
+
+ + 1. Assert: If _direct_ is *false*, then _strictCaller_ is also *false*. + 1. Let _isCodeLike_ be ! IsCodeLike(_x_). + 1. If _isCodeLike_ is *true*, set _x_ to _x_.[[HostDefinedCodeLike]]. + 1. If _x_ is not a String, return _x_. + 1. Let _evalRealm_ be the current Realm Record. + 1. NOTE: In the case of a direct eval, _evalRealm_ is the realm of both the caller of `eval` and of the `eval` function itself. + 1. Perform ? HostEnsureCanCompileStrings(_evalRealm_, « », _x_, _direct_).
+ Set _x_ to be ? HostValidateDynamicCode(_evalRealm_, « », _x_, _x_, _isCodeLike_, *"eval"*). + 1. ... +
- - -

CreateDynamicFunction ( _constructor_, _newTarget_, _kind_, _args_ )

-

The abstract operation CreateDynamicFunction takes arguments _constructor_ (a constructor), _newTarget_ (a constructor), _kind_ (either ~normal~, ~generator~, ~async~, or ~asyncGenerator~), and _args_ (a List of ECMAScript language values). _constructor_ is the constructor function that is performing this action. _newTarget_ is the constructor that `new` was initially applied to. _args_ is the argument values that were passed to _constructor_. It performs the following steps when called:

+ +

+ CreateDynamicFunction ( + _constructor_: a constructor, + _newTarget_: a constructor, + _kind_: ~normal~, ~generator~, ~async~, or ~async-generator~, + _parameterArgs_: a List of ECMAScript language values, + _bodyArg_: an ECMAScript language value, + ): either a normal completion containing an ECMAScript function object or a throw completion +

+
+
description
+
_constructor_ is the constructor function that is performing this action. _newTarget_ is the constructor that `new` was initially applied to. _parameterArgs_ and _bodyArg_ reflect the argument values that were passed to _constructor_.
+
- 1. Assert: The execution context stack has at least two elements. - 1. Let _callerContext_ be the second to top element of the execution context stack. - 1. Let _callerRealm_ be _callerContext_'s Realm. - 1. Let _calleeRealm_ be the current Realm Record. - 1. Perform ? HostEnsureCanCompileStrings(_callerRealm_, _calleeRealm_). 1. If _newTarget_ is *undefined*, set _newTarget_ to _constructor_. 1. If _kind_ is ~normal~, then - 1. Let _goal_ be the grammar symbol |FunctionBody[~Yield, ~Await]|. - 1. Let _parameterGoal_ be the grammar symbol |FormalParameters[~Yield, ~Await]|. + 1. Let _prefix_ be *"function"*. + 1. Let _exprSym_ be the grammar symbol |FunctionExpression|. + 1. Let _bodySym_ be the grammar symbol |FunctionBody[~Yield, ~Await]|. + 1. Let _parameterSym_ be the grammar symbol |FormalParameters[~Yield, ~Await]|. 1. Let _fallbackProto_ be *"%Function.prototype%"*. 1. Else if _kind_ is ~generator~, then - 1. Let _goal_ be the grammar symbol |GeneratorBody|. - 1. Let _parameterGoal_ be the grammar symbol |FormalParameters[+Yield, ~Await]|. + 1. Let _prefix_ be *"function\*"*. + 1. Let _exprSym_ be the grammar symbol |GeneratorExpression|. + 1. Let _bodySym_ be the grammar symbol |GeneratorBody|. + 1. Let _parameterSym_ be the grammar symbol |FormalParameters[+Yield, ~Await]|. 1. Let _fallbackProto_ be *"%GeneratorFunction.prototype%"*. 1. Else if _kind_ is ~async~, then - 1. Let _goal_ be the grammar symbol |AsyncFunctionBody|. - 1. Let _parameterGoal_ be the grammar symbol |FormalParameters[~Yield, +Await]|. + 1. Let _prefix_ be *"async function"*. + 1. Let _exprSym_ be the grammar symbol |AsyncFunctionExpression|. + 1. Let _bodySym_ be the grammar symbol |AsyncFunctionBody|. + 1. Let _parameterSym_ be the grammar symbol |FormalParameters[~Yield, +Await]|. 1. Let _fallbackProto_ be *"%AsyncFunction.prototype%"*. 1. Else, - 1. Assert: _kind_ is ~asyncGenerator~. - 1. Let _goal_ be the grammar symbol |AsyncGeneratorBody|. - 1. Let _parameterGoal_ be the grammar symbol |FormalParameters[+Yield, +Await]|. + 1. Assert: _kind_ is ~async-generator~. + 1. Let _prefix_ be *"async function\*"*. + 1. Let _exprSym_ be the grammar symbol |AsyncGeneratorExpression|. + 1. Let _bodySym_ be the grammar symbol |AsyncGeneratorBody|. + 1. Let _parameterSym_ be the grammar symbol |FormalParameters[+Yield, +Await]|. 1. Let _fallbackProto_ be *"%AsyncGeneratorFunction.prototype%"*. - 1. Let _argCount_ be the number of elements in _args_. - 1. Let _P_ be the empty String. + 1. Let _argCount_ be the number of elements in _parameterArgs_. 1. Let _assembledFromCodeLike_ be *true*. - 1. If _argCount_ = 0, let _bodyArg_ be the empty String. - 1. Else if _argCount_ = 1, let _bodyArg_ be _args_[0]. - 1. Let _bodyArg_ be _args_[0]. - 1. If IsCodeLike(_bodyArg_) is *true*, set _bodyArg_ to _bodyArg_.[[HostDefinedCodeLike]]. Otherwise, set _assembledFromCodeLike_ to *false*. - 1. Else, - 1. Assert: _argCount_ > 1. - 1. Let _firstArg_ be _args_[0]. - 1. If IsCodeLike(_firstArg_) is *true*, set _firstArg_ to _firstArg_.[[HostDefinedCodeLike]]. Otherwise, set _assembledFromCodeLike_ to *false*. - 1. Set _P_ to ? ToString(_firstArg_). + 1. If IsCodeLike(_bodyArg_) is *true*, set _bodyArg_ to _bodyArg_.[[HostDefinedCodeLike]]. Otherwise, set _assembledFromCodeLike_ to *false*. + 1. Let _bodyString_ be ? ToString(_bodyArg_). + 1. Let _parameterStrings_ be a new empty List. + 1. For each element _arg_ of _parameterArgs_, do + 1. If IsCodeLike(_arg_) is *true*, set _arg_ to _arg_.[[HostDefinedCodeLike]]. Otherwise, set _assembledFromCodeLike_ to *false*. + 1. Append ? ToString(_arg_) to _parameterStrings_. + 1. Let _currentRealm_ be the current Realm Record. + 1. Perform ? HostEnsureCanCompileStrings(_currentRealm_, _parameterStrings_, _bodyString_, *false*). + 1. Let _P_ be the empty String. + 1. If _argCount_ > 0, then + 1. Set _P_ to _parameterStrings_[0]. 1. Let _k_ be 1. - 1. Repeat, while _k_ < _argCount_ - 1, - 1. Let _nextArg_ be _args_[_k_]. - 1. If IsCodeLike(_nextArg_) is *true*, set _nextArg_ to _nextArg_.[[HostDefinedCodeLike]]. Otherwise, set _assembledFromCodeLike_ to *false*. - 1. Let _nextArgString_ be ? ToString(_nextArg_). + 1. Repeat, while _k_ < _argCount_, + 1. Let _nextArgString_ be _parameterStrings_[_k_]. 1. Set _P_ to the string-concatenation of _P_, *","* (a comma), and _nextArgString_. 1. Set _k_ to _k_ + 1. - 1. Let _bodyArg_ be _args_[_k_]. - 1. If IsCodeLike(_bodyArg_) is *true*, set _bodyArg_ to _bodyArg_.[[HostDefinedCodeLike]]. Otherwise, set _assembledFromCodeLike_ to *false*. - 1. Let _bodyString_ be the string-concatenation of 0x000A (LINE FEED), ? ToString(_bodyArg_), and 0x000A (LINE FEED). - 1. Let _prefix_ be the prefix associated with _kind_ in . - 1. Let _sourceString_ be the string-concatenation of _prefix_, *" anonymous("*, _P_, 0x000A (LINE FEED), *") {"*, _bodyString_, and *"}"*. - 1. Set _sourceString_ to be ? HostValidateDynamicCode(_callerRealm_, _calleeRealm_, _sourceString_, _assembledFromCodeLike_, *"Function"*). + 1. Let _bodyParseString_ be the string-concatenation of 0x000A (LINE FEED), _bodyString_, and 0x000A (LINE FEED). + 1. Let _sourceString_ be the string-concatenation of _prefix_, *" anonymous("*, _P_, 0x000A (LINE FEED), *") {"*, _bodyParseString_, and *"}"*. + 1. Set _sourceString_ to ? HostValidateDynamicCode(_currentRealm_, _parameterStrings_, _bodyString_, _sourceString_, *false*, _assembledFromCodeLike_, *"Function"*). 1. ... + +

CreateDynamicFunction defines a *"prototype"* property on any function it creates whose _kind_ is not ~async~ to provide for the possibility that the function will be used as a constructor.

+