Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid polluting host Function prototypes #305

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 32 additions & 10 deletions src/realm.js
Original file line number Diff line number Diff line change
@@ -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,
41 changes: 36 additions & 5 deletions src/repair/functions.js
Original file line number Diff line number Diff line change
@@ -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) {
9 changes: 6 additions & 3 deletions src/unsafeRec.js
Original file line number Diff line number Diff line change
@@ -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);
}