diff --git a/src/realm.js b/src/realm.js index df0800f..a6992fd 100644 --- a/src/realm.js +++ b/src/realm.js @@ -130,6 +130,15 @@ function initRootRealm(parentUnsafeRec, self, options) { registerRealmRecForRealmInstance(self, realmRec); } +// This is an internal object that tells `repairFunction` whether +// `Function.prototype.constructor` is being accessed from code outside +// the compartment, and thus should get access to the real Function constructor, +// or if code should get the tamed version created by `repairFunctions` instead. +const allowUnsafeFunctionConstructorAccessRecord = Object.seal({ + __proto__: null, + value: true +}); + /** * A compartment shares the intrinsics of its root realm. Here, only a * realmRec is necessary to hold the global object, eval() and Function(). @@ -138,10 +147,15 @@ function initCompartment(unsafeRec, self, options = {}) { // note: 'self' is the instance of the Realm. const { transforms, sloppyGlobals } = options; - const realmRec = createRealmRec(unsafeRec, transforms, sloppyGlobals); - - // The realmRec acts as a private field on the realm instance. - registerRealmRecForRealmInstance(self, realmRec); + try { + allowUnsafeFunctionConstructorAccessRecord.value = false; + const realmRec = createRealmRec(unsafeRec, transforms, sloppyGlobals); + + // The realmRec acts as a private field on the realm instance. + registerRealmRecForRealmInstance(self, realmRec); + } finally { + allowUnsafeFunctionConstructorAccessRecord.value = true; + } } function getRealmGlobal(self) { @@ -150,11 +164,17 @@ function getRealmGlobal(self) { } function realmEvaluate(self, x, endowments = {}, options = {}) { - // todo: don't pass in primal-realm objects like {}, for safety. OTOH its - // properties are copied onto the new global 'target'. - // todo: figure out a way to membrane away the contents to safety. - const { safeEvalWhichTakesEndowments } = getRealmRecForRealmInstance(self); - return safeEvalWhichTakesEndowments(x, endowments, options); + try { + allowUnsafeFunctionConstructorAccessRecord.value = false; + + // todo: don't pass in primal-realm objects like {}, for safety. OTOH its + // properties are copied onto the new global 'target'. + // todo: figure out a way to membrane away the contents to safety. + const { safeEvalWhichTakesEndowments } = getRealmRecForRealmInstance(self); + return safeEvalWhichTakesEndowments(x, endowments, options); + } finally { + allowUnsafeFunctionConstructorAccessRecord.value = true; + } } const BaseRealm = { @@ -166,7 +186,9 @@ const BaseRealm = { // Create the current unsafeRec from the current "primal" environment (the realm // where the Realm shim is loaded and executed). -const currentUnsafeRec = createCurrentUnsafeRec(); +const currentUnsafeRec = createCurrentUnsafeRec( + allowUnsafeFunctionConstructorAccessRecord +); /** * The "primal" realm class is defined in the current "primal" environment, diff --git a/src/repair/functions.js b/src/repair/functions.js index 4a75982..a8e1600 100644 --- a/src/repair/functions.js +++ b/src/repair/functions.js @@ -18,7 +18,7 @@ */ // todo: this file should be moved out to a separate repo and npm module. -export function repairFunctions() { +export function repairFunctions(allowUnsafeFunctionConstructorAccessRecord) { const { defineProperties, getPrototypeOf, setPrototypeOf } = Object; /** @@ -45,13 +45,17 @@ export function repairFunctions() { throw e; } const FunctionPrototype = getPrototypeOf(FunctionInstance); + const unsafeFunctionConstructor = FunctionPrototype.constructor; // Prevents the evaluation of source when calling constructor on the // prototype of functions. const TamedFunction = function() { throw new TypeError('Not available'); }; - defineProperties(TamedFunction, { name: { value: name } }); + defineProperties(TamedFunction, { + length: { value: 1 }, + name: { value: name } + }); // (new Error()).constructors does not inherit from Function, because Error // was defined before ES6 classes. So we don't need to repair it too. @@ -65,15 +69,42 @@ export function repairFunctions() { // we're fine: the constructor inherits from Object.prototype // This line replaces the original constructor in the prototype chain - // with the tamed one. No copy of the original is peserved. + // with the tamed one. No copy of the original is preserved. defineProperties(FunctionPrototype, { - constructor: { value: TamedFunction } + constructor: allowUnsafeFunctionConstructorAccessRecord + ? { + configurable: true, + // Ensure that Function.prototype.constructor === Function + get() { + if (allowUnsafeFunctionConstructorAccessRecord.value === true) { + return unsafeFunctionConstructor; + } + return TamedFunction; + }, + // Pretend to be a data property. + set(value) { + // 1. Perform ? CreateDataPropertyOrThrow(*this* value, *"constructor"*, _value_). + defineProperties(this, { + constructor: { + configurable: true, + enumerable: true, + writable: true, + value + } + }); + // 2. Return *undefined*. + } + } + : { value: TamedFunction } }); // This line sets the tamed constructor's prototype data property to // the original one. defineProperties(TamedFunction, { - prototype: { value: FunctionPrototype } + prototype: { + writable: false, + value: FunctionPrototype + } }); if (TamedFunction !== Function.prototype.constructor) { diff --git a/src/unsafeRec.js b/src/unsafeRec.js index 89d377f..8e0930d 100644 --- a/src/unsafeRec.js +++ b/src/unsafeRec.js @@ -3,7 +3,7 @@ import { buildCallAndWrapErrorString } from './callAndWrapError'; import { getSharedGlobalDescs } from './stdlib'; import { repairAccessors } from './repair/accessors'; import { repairFunctions } from './repair/functions'; -import { safeStringifyFunction } from './utilities'; +import { assert, safeStringifyFunction } from './utilities'; import { freeze } from './commons'; // A "context" is a fresh unsafe Realm as given to us by existing platforms. @@ -123,10 +123,13 @@ export function createNewUnsafeRec(allShims, configurableGlobals = false) { // Create a new unsafeRec from the current context, where the Realm shim is // being parsed and executed, aka the "Primal Realm" -export function createCurrentUnsafeRec() { +export function createCurrentUnsafeRec( + allowUnsafeFunctionConstructorAccessRecord +) { + assert(typeof allowUnsafeFunctionConstructorAccessRecord === 'object'); const unsafeEval = eval; const unsafeGlobal = unsafeEval(unsafeGlobalSrc); repairAccessors(); - repairFunctions(); + repairFunctions(allowUnsafeFunctionConstructorAccessRecord); return createUnsafeRec(unsafeGlobal); }