ses
Version:
Hardened JavaScript for Fearless Cooperation
113 lines (102 loc) • 4.16 kB
JavaScript
// Portions adapted from V8 - Copyright 2016 the V8 project authors.
// https://github.com/v8/v8/blob/master/src/builtins/builtins-function.cc
import { apply, arrayFlatMap, freeze, identity } from './commons.js';
import { strictScopeTerminator } from './strict-scope-terminator.js';
import { createSloppyGlobalsScopeTerminator } from './sloppy-globals-scope-terminator.js';
import { makeEvalScopeKit } from './eval-scope.js';
import { applyTransforms, mandatoryTransforms } from './transforms.js';
import { makeEvaluate } from './make-evaluate.js';
import { assert } from './error/assert.js';
const { Fail } = assert;
/**
* makeSafeEvaluator()
* Build the low-level operation used by all evaluators:
* eval(), Function(), Compartment.prototype.evaluate().
*
* @param {object} options
* @param {object} options.globalObject
* @param {object} [options.moduleLexicals]
* @param {Array<import('./lockdown.js').Transform>} [options.globalTransforms]
* @param {boolean} [options.sloppyGlobalsMode]
*/
export const makeSafeEvaluator = ({
globalObject,
moduleLexicals = {},
globalTransforms = [],
sloppyGlobalsMode = false,
}) => {
const scopeTerminator = sloppyGlobalsMode
? createSloppyGlobalsScopeTerminator(globalObject)
: strictScopeTerminator;
const evalScopeKit = makeEvalScopeKit();
const { evalScope } = evalScopeKit;
const evaluateContext = freeze({
evalScope,
moduleLexicals,
globalObject,
scopeTerminator,
});
// Defer creating the actual evaluator to first use.
// Creating a compartment should be possible in no-eval environments
// It also allows more global constants to be captured by the optimizer
let evaluate;
const provideEvaluate = () => {
if (!evaluate) {
evaluate = makeEvaluate(evaluateContext);
}
};
/**
* @param {string} source
* @param {object} [options]
* @param {Array<import('./lockdown.js').Transform>} [options.localTransforms]
*/
const safeEvaluate = (source, options) => {
const { localTransforms = [] } = options || {};
provideEvaluate();
// Execute the mandatory transforms last to ensure that any rewritten code
// meets those mandatory requirements.
source = applyTransforms(
source,
arrayFlatMap(
[localTransforms, globalTransforms, [mandatoryTransforms]],
identity,
),
);
let err;
try {
// Allow next reference to eval produce the unsafe FERAL_EVAL.
// eslint-disable-next-line @endo/no-polymorphic-call
evalScopeKit.allowNextEvalToBeUnsafe();
// Ensure that "this" resolves to the safe global.
return apply(evaluate, globalObject, [source]);
} catch (e) {
// stash the child-code error in hopes of debugging the internal failure
err = e;
throw e;
} finally {
const unsafeEvalWasStillExposed = 'eval' in evalScope;
delete evalScope.eval;
if (unsafeEvalWasStillExposed) {
// Barring a defect in the SES shim, the evalScope should allow the
// powerful, unsafe `eval` to be used by `evaluate` exactly once, as the
// very first name that it attempts to access from the lexical scope.
// A defect in the SES shim could throw an exception after we set
// `evalScope.eval` and before `evaluate` calls `eval` internally.
// If we get here, SES is very broken.
// This condition is one where this vat is now hopelessly confused, and
// the vat as a whole should be aborted.
// No further code should run.
// All immediately reachable state should be abandoned.
// However, that is not yet possible, so we at least prevent further
// variable resolution via the scopeHandler, and throw an error with
// diagnostic info including the thrown error if any from evaluating the
// source code.
evalScopeKit.revoked = { err };
// TODO A GOOD PLACE TO PANIC(), i.e., kill the vat incarnation.
// See https://github.com/Agoric/SES-shim/issues/490
Fail`handler did not reset allowNextEvalToBeUnsafe ${err}`;
}
}
};
return { safeEvaluate };
};