endojs / endo

Endo is a distributed secure JavaScript sandbox, based on SES
Apache License 2.0
804 stars 71 forks source link

SES.confine: find better exception-unwrapping solution #194

Closed warner closed 4 years ago

warner commented 6 years ago

SES.confine (in a child realm) delegates directly to r.evaluate on the enclosing (parent) RootRealm. To avoid leaking the parent Realm's exception objects, if that r.evaluate throws an exception, we replace the exception object with one we build from the child realm. The code we use for this is identical to the code that r.evaluate uses when wrapping exceptions raised in the child: https://github.com/Agoric/SES/blob/f32edcb77101c052d3a6e5696f8ff7d6c4060dba/src/bundle/createSES.js#L61

As a result, when SES.confine sees an exception, we rewrite it twice: once from child to parent, then again from parent back to the same child. This feels a bit silly, and it'd be nice to have a better approach. Fundamentally, SES.confine is the two-argument safe eval() that we wish javascript had to begin with. If we could arrange to expose this from within the realm, rather than only on the parent's controller object, then we wouldn't need to map/rewrite/wrap exceptions at all.

This might take the form of Realm.evaluate, which would be a static method on the Realm constructor object (as opposed to r.evaluate, which is a normal method on the constructed object). It would simply be equal to the existing internal safeEvaluator() defined by the Realms shim (used to implement both eval() and the Function constructor).

const r = Realm.makeRootRealm();
const a = r.evaluate(code);
const b = r.evaluate('Realm.evaluate(code)', { code });
// a and b are equivalent

Of course having two methods named evaluate is probably too confusing. Calling it Realm.confine might work, given that it behaves the same way as the SES.confine that this module creates.

erights commented 6 years ago

I like Realm.confine

Attn @jfparadis

jfparadis commented 4 years ago

Overall:

If there is a need for an evaluator that supports endowments, we could consider Compartment.evaluate(). However, in SES, endowments are only necessary to emulate a lexical scope in order to support modules. However:

Performance:

Still, if an application needs to provide a parametrized evaluator inside a compartment, it can create a global for that use case:

const cmp = new Compartment();
const evaluate = (src, options) => cmp.evaluate(src, options);
cmp.global.evaluate = evaluate;

Closing, not required.

erights commented 4 years ago

A further note on how to cope with an apparent need for a per-module endowment. (This came up during the TC53 meeting yesterday, Feb 25 2020.)

Common JS (CJS) Modules, the module system Node uses prior to standard EcmaScript Modules (ESM), has per-module bindings of require and module. If we supported per-evaluation endowments, that would have been a natural way to support CJS modules on SES when that is desired. In its absence, however, we can support CJS modules on SES in the same way that Node itself supports CJS: Treat the CJS module body as a function body, wrap it in a function providing require and module parameters, and effectively provide per-module bindings for those names by invoking that function. Rather than wrapping the module textually, we can use the SES Function constructor to do that wrapping. This works because CJS modules are syntactically EcmaScript scripts, not EcmaScript modules, and any syntactically valid EcmaScript script is also a syntactically valid EcmaScript function body. Unlike EcmaScript scripts, and like EcmaScript function bodies, CJS modules have no completion value.

More generally, anytime we might have wanted to do

evaluate(src, { endowments });

ignoring completion value; assuming a predicate varName that tests whether a string is a valid EcmaScript variable name, we can instead do

const names = getOwnPropertyNames(endowments).filter(varName);
new Function(`{ ${names.join(',')} }`, src)(endowments);

Thus, there's no significant loss of generality in removing per-evaluation endowments from direct support in SES. We lose only the ability to capture the completion value, which is not an issue for the one known use case: emulating CJS modules.