UNPKG

ses

Version:

Hardened JavaScript for Fearless Cooperation

113 lines (102 loc) 4.16 kB
// 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 }; };