ses
Version:
Hardened JavaScript for Fearless Cooperation
79 lines (71 loc) • 3.12 kB
JavaScript
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;
};