Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

with (scopeProxy) { foo() } leaks scopeProxy if foo mentions this. #31

Closed
2 tasks
erights opened this issue Oct 27, 2019 · 11 comments
Closed
2 tasks

with (scopeProxy) { foo() } leaks scopeProxy if foo mentions this. #31

erights opened this issue Oct 27, 2019 · 11 comments
Assignees
Labels
confinement Pertaining to confinement of guest programs.

Comments

@erights
Copy link
Contributor

erights commented Oct 27, 2019

Typing valueOf() into the evaluator shim playground (demo/index.html) and hitting the Evaluate button causes the error

please report internal shim error: unexpected scope handler trap called: ownKeys

Unlike other surprising scopeProxy traps, this does not look like an engine bug. We see identical symptoms on Brave, FF, and Safari.

[edit by @kriskowal 2020-08-12:]

Actions needed (to be independently tracked, and this issue closed)

  • Create a specification proposal that introduces a hiddenWith symbol that with would respect.
  • Document this known-limitation of SES-shim, barring eventual engine support for a hiddenWith symbol.
  • Confirm that absent hiddenWith, revealing the ScopeProxy does not break containment.
@erights
Copy link
Contributor Author

erights commented Oct 27, 2019

Due to https://github.com/google/caja/wiki/SES#this-binding-of-global-function-calls
The weird trap isn't happening in evaluating the user code. Rather, the valueOf() causes the scopeProxy to leak as the completion value. The playground code then treats it as a more normal object, provoking the surprising trap.

The scopeProxy was never supposed to leak. The problem is only caused by with lookup of a function that mentions this, followed by a function-call-syntax invocation of that function.

At an unmodified normal JS prompt,

const obj = { foo() {'use strict'; return this;} };
with (obj) { foo() === obj; }  // true
with (obj) { (1,foo)() === undefined; }  // true

This leakage of obj on line 2 is according to the spec, and is an unrepairable deficiency emulating global scope with with. Had we noticed it in time, we likely could have fixed it in ES5 strict-mode. But even by https://github.com/google/caja/wiki/SES#this-binding-of-global-function-calls (2015) it was already too late. Attn @allenwb @jfparadis @caridy @warner @XmiliaH

What this means under the evaluator-shim and the realms-shim, and therefore under SES:

const e = new Evaluator();

function foo() { return this; }
e.evaluate('foo()', { foo });  // evaluates to the scopeProxy.

Amusingly, once #18 is fixed by Agoric/evaluator-shim#3 the following works

const e = new Evaluator();

function foo() { return this; }
e.evaluate('foo()', Object.freeze({ foo }));  // undefined

because foo becomes an optimized const variable.

If we cannot practically prevent the scopeProxy from leaking, we need to ensure that no vulnerability follows from leaking the scopeProxy. We could probably make the scopeProxy no more powerful than the safeGlobal plus the values of the endowments. That should be both possible and adequate.

@erights erights changed the title valueOf() causes surprising scopeProxy trap with (scopeProxy) { foo() } leaks scopeProxy if foo mentions this. Oct 27, 2019
@allenwb
Copy link

allenwb commented Oct 27, 2019

(grr...it's too easy to accidentally loose/close a github tab with an unsaved text entry field...trying again)

This should be easy (at least at the ECMA-262 spec. level) to fix. Object Environment Records already have a specification mechanism that controls whether or not they make their backing object available for inclusion in resolved References. This is controlled by the setting their withValue to true. With statements normally do that for all with objects. What we need to do is not set withValue to true when the with object is a ScopeProxy.

There are various ways this might be done in the spec, but I suggest doing all the work in with statement evaluation. Specifically we need to put a "not a ScopeProxy" guard on step 5 which currently unconditionally sets withValue.

What does the guard look like? We really don't want to build knowledge of a specific ScopeProxy definition into this level of ECMA-262. Instead, I suggest we define a new well-known symbol named @@hiddenWith. The guard would be: HasOwnProperty(obj, @@hiddenWith) is false. The definition of ScopeProxy (its proxy handler) would ensure that they always report that they have a @@hiddenWith own property. That's it!

Actually updating implementations to do this is probably not quite so simple, I assume that they don't have anything that exactly corresponds to an Object environment record with a withValue flag. But I suspect that adapting an implementation to conform to this updated specification would be a modest effort—and well worth it.

@allenwb
Copy link

allenwb commented Oct 27, 2019

I left out of my redo: this ECMA-262 specification change appears be backwards compatible with all existing ES code.

@dckc
Copy link
Contributor

dckc commented Jun 7, 2021

This is a use of this, not a mention of this, no?

...
function foo() { return this; }
...

@erights
Copy link
Contributor Author

erights commented Jun 7, 2021

Well, it is also a mention of this. But good point. I genuinely do not know what the least confusing short title would be. "Uses this" can be misunderstood in other ways (such as "uses the value that this evaluates to"). I'll just let these notes in this issue thread clarify, and leave the title as is. Thanks.

@dckc
Copy link
Contributor

dckc commented Jun 7, 2021

Well, it is also a mention of this.

Ah. I see. Thanks.

@mhofman
Copy link
Contributor

mhofman commented Sep 22, 2022

We've noticed that some Web APIs such as fetch are sensitive to the this / context. If it's null-ish or a real window object, it works fine, but fails with a TypeError if any other object: Failed to execute 'fetch' on 'Window': Illegal invocation (message on v8/Chrome) or 'fetch' called on an object that does not implement interface Window (message in SpiderMonkey/Gecko).

This fidelity bug of the shim causes code that would otherwise work to break if these functions are endowed to a compartment's global.

@erights
Copy link
Contributor Author

erights commented Dec 24, 2022

How has multi-backflip affected this?

@mhofman
Copy link
Contributor

mhofman commented Dec 24, 2022

It still leaks, but only the globalThis regular object.

Main problem left is for module lexical created by the shim, as detailed in #912.

Should we rename the title of this issue? It might be good to keep tracking an engine level change allowing to disable the current with context behavior.

@erights
Copy link
Contributor Author

erights commented Dec 24, 2022

Should we rename the title of this issue?

Please do!

@kriskowal
Copy link
Member

Narrowed scope and posted for follow-up: #1954

SMotaal added a commit that referenced this issue Sep 1, 2024
## Description

This PR changes the `HIDDEN_PREFIX` of `ModuleSource` from the
non-conforming `$h\u200D_` zero-width joiner (`ZWJ`) notation to the
conforming `$h\u034F_` combining grapheme joiner (`CGJ`) notation.

A future PR may address further changes to a `$\u034F`-prefixed and
`\u034F$`-suffixed format as was suggested by @michaelfig in
discussions.

### Motivation

This change is motivated after encountering a parsing error when using
`rollup` which was traced back to the `$h\u200D_`-prefixed identifier in
an `endoScript` bundle. More importantly, this is also motivated by the
subsequent discovery that `rollup`'s implementation was actually
conforming to the ECMAScript Specification when it was throwing this
error.

To elaborate, while runtimes today will accept the special identifier
notation that is currently being introduced by the `ModuleSource`
rewrites, the current `$h\u200D_` zero-width joiner (`ZWJ`) notation
does not conform to the specifications defined in the [ECMAScript
Lexical Grammar](https://tc39.es/ecma262/#sec-names-and-keywords). In
essence, what the specifications entail is that the character sequence
for [Identifier Names](https://tc39.es/ecma262/#sec-identifier-names)
once unescaped would be expected to match the
`/^[$_\p{ID_Start}][$_\p{ID_Continue}]*$/u` pattern, aside from the
additional `#` character prefix required in the case of private fields.

As such, one can test this in the console by evaluating the following:

```js
Object.fromEntries([String.raw`$h\u200D_`, String.raw`$h\u034F_`].map(id => [id, /^[$_\p{ID_Start}][$_\p{ID_Continue}]*$/u.test(JSON.parse(`"${id}"`))]))
```

The above would yield the following object in a runtime where the
unicode escape sequences are retained:

```js
{$h\u200D_: false, $h\u034F_: true}
```

Digging closer in the Unicode Standard, it seems that the zero-width
joiner (`ZWJ`) may indeed be used in a conforming notation per [Emoji
Profile in Annex #31 of the Unicode
Standard](http://www.unicode.org/reports/tr31/#Emoji_Profile), however
this is not applicable for this purpose as it would require the use of
emojis.

At this point, my suggestion to instead use the combining grapheme
joiner (`CGJ`) is best articulated with this excerpt that I am borrowing
from its canonical Wikipedia entry:

> However, in contrast to the zero-width joiner and similar characters,
the `CGJ` does not affect whether the two letters are rendered
separately or as a ligature or cursively joined—the default behavior for
this is determined by the font.[^1]
>
>
> [^1]: https://en.wikipedia.org/wiki/Combining_grapheme_joiner

The Wikipedia article offers additional nuances about the differences,
while the [Proposal for addition of COMBINING GRAPHEME
JOINER](https://www.unicode.org/L2/L2000/00274-N2236-grapheme-joiner.htm)
offers the necessary context about its intent.

It is fair to note that there are many uses of the zero-width joiner
(`ZWJ`) already in the wild, and in fact there are currently `test262`
tests for its occurrence. That said, unless those uses are conforming to
the ECMAScript Specification and the Unicode Standard, they will limit
code portability and adoption by users who may end up confused by
failures similar to the one encountered with `rollup`.

Ultimately, with the reasonable recommendations to exercise caution when
it comes to bundling `ses` and related sources that are best bundled
with `bundleSource` instead, those sources may still need to be parsed
with tools like `rollup` for different purposes that would be aligned
with the expectations that they are being handled safely.

### Approach

#### Substituting the invisible joiner character

A search across the monorepo for `(?:\u200d|\\u200d)_` yields only 3
files of interest:

- `packages/module-source/TESTS.md`
- `packages/module-source/src/hidden.js`
- `packages/module-source/test/module-source.test.js`

While making changes to the 3 files of interest, a distinction is made
between matching `\$h\\u200d_` and `\$h\u200d_` where the replacements
are respectively `$h\\u034f_` and `$h\u034f_`, along with their `$c`
equivalents.

The search across the monorepo for `(?:\u200d|\\u200d)_` yields another
978 files that are not of interest found in:

-
`packages/test262-runner/test262/test/language/expressions/class/elements`
-
`packages/test262-runner/test262/test/language/statements/class/elements`

All those files remain unchanged.

#### Ensuring generic wording is used

For testing and other purposes where descriptive phrases are used to
refer to the use of `ZWJ`, `CGJ` or other characters for this same
intent, the phrase *"invisible joiner character"* is suggested.

### Security Considerations

**Does not apply to my knowledge**

### Scaling Considerations

**Does not apply to my knowledge**

### Documentation Considerations

**Does not apply to my knowledge**

### Testing Considerations

**See**:
#2436 (comment)

### Compatibility Considerations

While the changes do not affect compatibility when the generated code is
evaluated at runtime, there can potentially be compatibility concerns
with tools that have been specifically designed to work with the current
notation.

### Upgrade Considerations

**Does not apply to my knowledge**
mergify bot pushed a commit to Agoric/agoric-sdk that referenced this issue Dec 11, 2024
)

closes: #XXXX
refs: endojs/endo#31 (comment)

## Description

Testing on Brave, the browser's global `fetch` function works when called with its `this` bound to either `undefined` or the browser global object, but does not work if its `this` is bound to something else. The code this PR fixes was misbehaving because it was calling `powers.fetch(...)`, i.e., happening to call it as a method with its `this` bound to the irrelevant `powers` object.

This refactor just expresses the intention of this code, which is just to call the `fetch` function without passing it some object as its `this` binding. This seems to fix the problem.

Not at all addressed by this PR: The fact that this problem happened from a programming patterns that seems correct and would have passed review indicates a deeper hazard. This PR does nothing to mitigate this deeper hazard. It only fixes this case where we fell into the hazard.

### Security Considerations

none

### Scaling Considerations
none

### Documentation Considerations
none, except as needed to address the more general hazard.

### Testing Considerations
Only tested `fetch` behavior on Brave. Based on that, proceeding under untested assumption that this refactor will work on all supported browsers.

### Upgrade Considerations
none
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
confinement Pertaining to confinement of guest programs.
Projects
None yet
Development

No branches or pull requests

5 participants