UNPKG

ses

Version:

Hardened JavaScript for Fearless Cooperation

79 lines (71 loc) 3.12 kB
import { FERAL_FUNCTION, arrayJoin, arrayPop, defineProperties, getPrototypeOf, } from './commons.js'; import { assert } from './error/assert.js'; const { Fail } = assert; /* * makeFunctionConstructor() * A safe version of the native Function which relies on * the safety of safeEvaluate for confinement. */ export const makeFunctionConstructor = safeEvaluate => { // Define an unused parameter to ensure Function.length === 1 const newFunction = function Function(_body) { // Sanitize all parameters at the entry point. // eslint-disable-next-line prefer-rest-params const bodyText = `${arrayPop(arguments) || ''}`; // eslint-disable-next-line prefer-rest-params const parameters = `${arrayJoin(arguments, ',')}`; // Are parameters and bodyText valid code, or is someone // attempting an injection attack? This will throw a SyntaxError if: // - parameters doesn't parse as parameters // - bodyText doesn't parse as a function body // - either contain a call to super() or references a super property. // // It seems that XS may still be vulnerable to the attack explained at // https://github.com/tc39/ecma262/pull/2374#issuecomment-813769710 // where `new Function('/*', '*/ ) {')` would incorrectly validate. // Before we worried about this, we check the parameters and bodyText // together in one call // ```js // new FERAL_FUNCTION(parameters, bodyTest); // ``` // However, this check is vulnerable to that bug. Aside from that case, // all engines do seem to validate the parameters, taken by themselves, // correctly. And all engines do seem to validate the bodyText, taken // by itself correctly. So with the following two checks, SES builds a // correct safe `Function` constructor by composing two calls to an // original unsafe `Function` constructor that may suffer from this bug // but is otherwise correctly validating. // // eslint-disable-next-line no-new new FERAL_FUNCTION(parameters, ''); // eslint-disable-next-line no-new new FERAL_FUNCTION(bodyText); // Safe to be combined. Defeat potential trailing comments. // TODO: since we create an anonymous function, the 'this' value // isn't bound to the global object as per specs, but set as undefined. const src = `(function anonymous(${parameters}\n) {\n${bodyText}\n})`; return safeEvaluate(src); }; defineProperties(newFunction, { // Ensure that any function created in any evaluator in a realm is an // instance of Function in any evaluator of the same realm. prototype: { value: FERAL_FUNCTION.prototype, writable: false, enumerable: false, configurable: false, }, }); // Assert identity of Function.__proto__ accross all compartments getPrototypeOf(FERAL_FUNCTION) === FERAL_FUNCTION.prototype || Fail`Function prototype is the same accross compartments`; getPrototypeOf(newFunction) === FERAL_FUNCTION.prototype || Fail`Function constructor prototype is the same accross compartments`; return newFunction; };