add 'shims' option to Realm.makeRootRealm(), with inheritance
Each RootRealm first applies all the shims that their nearest enclosing
RootRealm applied, then it adds any new shims provided as options.shims (an
array of strings)
MODIFY
shim/src/commons.js
shim/src/commons.js
@@ -41,5 +41,6 @@ export const objectHasOwnProperty = uncurryThis(Object.prototype.hasOwnProperty)
arrayPush = uncurryThis(Array.prototype.push),
arrayPop = uncurryThis(Array.prototype.pop),
arrayJoin = uncurryThis(Array.prototype.join),
+ arrayConcat = uncurryThis(Array.prototype.concat),
regexpMatch = uncurryThis(RegExp.prototype.match),
stringIncludes = uncurryThis(String.prototype.includes);

MODIFY
shim/src/realm.js
shim/src/realm.js
import { createRealmFacade } from './realmFacade';
import { createNewUnsafeRec, createCurrentUnsafeRec } from './unsafeRec';
import { createSafeEvaluator, createFunctionEvaluator } from './evaluators';
-import { create, defineProperty, defineProperties, freeze } from './commons';
+import { create, defineProperty, defineProperties, freeze, arrayConcat } from './commons';
// Create a registry to mimic a private static members on the realm classes.
// We define it in the same module and do not export it.
@@ -104,15 +104,22 @@ function createRealmRec(unsafeRec) {
return realmRec;
}
-function initRootRealm(selfClass, self) {
+function initRootRealm(selfClass, self, options) {
+ options = Object(options); // Todo: sanitize
// note: 'self' is the instance of the Realm, and 'selfClass' is the
// Realm constructor (facade) we build in buildChildRealm().
// In 'undefined' mode, intrinics are not provided, we create a root
// realm using the fresh set of new intrinics from a new context.
+ // todo: investigate attacks via Array.species
+ const newShims = options.shims || [];
+ const { allShims: oldShims } = getUnsafeRecForRealm(selfClass);
+ // todo: this accepts newShims='string', but it should reject that
+ const allShims = arrayConcat(oldShims, newShims);
+
// The unsafe record is returned with its constructors repaired.
- const unsafeRec = createNewUnsafeRec();
+ const unsafeRec = createNewUnsafeRec(allShims);
// Define Realm onto new sharedGlobalDescs, so it can be copied onto the
// safeGlobal like the rest of the .
@@ -122,7 +129,12 @@ function initRootRealm(selfClass, self) {
const realmRec = createRealmRec(unsafeRec);
registerRealmRecForRealmInstance(self, realmRec);
- // todo: is this where we run shims? but only in RootRealms, not compartments
+ // Now run all shims in the new RootRealm. We don't do this for
+ // compartments
+ for (const s of allShims) {
+ // eslint-disable-next-line no-use-before-define
+ realmEvaluate(self, s);
+ }
}
function initCompartment(selfClass, self) {

MODIFY
shim/src/unsafeRec.js
shim/src/unsafeRec.js
@@ -49,14 +49,15 @@ const getNewUnsafeGlobal = isNode ? createNewUnsafeGlobalForNode : createNewUnsa
// tied to a set of intrinsics, aka the "undeniables". If it were possible to
// mix-and-match them from different contexts, that would enable some
// attacks.
-function createUnsafeRec(unsafeGlobal) {
+function createUnsafeRec(unsafeGlobal, allShims) {
const sharedGlobalDescs = getSharedGlobalDescs(unsafeGlobal);
return freeze({
unsafeGlobal,
sharedGlobalDescs,
unsafeEval: unsafeGlobal.eval,
- unsafeFunction: unsafeGlobal.Function
+ unsafeFunction: unsafeGlobal.Function,
+ allShims
});
}
@@ -71,9 +72,9 @@ function sanitizeUnsafeRec(unsafeRec) {
// Create a new unsafeRec from a brand new context, with new intrinsics and a
// new global object
-export function createNewUnsafeRec() {
+export function createNewUnsafeRec(allShims) {
const unsafeGlobal = getNewUnsafeGlobal();
- const unsafeRec = createUnsafeRec(unsafeGlobal);
+ const unsafeRec = createUnsafeRec(unsafeGlobal, allShims);
sanitizeUnsafeRec(unsafeRec);
return unsafeRec;
}
@@ -82,7 +83,7 @@ export function createNewUnsafeRec() {
// being parsed and executed, aka the "Primal Realm"
export function createCurrentUnsafeRec() {
const unsafeGlobal = (0, eval)(unsafeGlobalSrc);
- const unsafeRec = createUnsafeRec(unsafeGlobal);
+ const unsafeRec = createUnsafeRec(unsafeGlobal, []); // todo: fixAccessors here
sanitizeUnsafeRec(unsafeRec);
return unsafeRec;
}

ADD
shim/test/realm/test-shims.js
+import test from 'tape';
+import Realm from '../../src/realm';
+
+test('shims: no options', t => {
+ Realm.makeRootRealm();
+ t.end();
+});
+
+test('shims: empty options', t => {
+ Realm.makeRootRealm({});
+ t.end();
+});
+
+test('shims: no shims', t => {
+ Realm.makeRootRealm({ shims: [] });
+ t.end();
+});
+
+function addKilroy(global) {
+ global.kilroy = 'was here';
+}
+const shim1 = `${addKilroy} addKilroy(this)`;
+const shim2 = `this.kilroy += ' but he left';`;
+
+test('shims: one shim', t => {
+ const r = Realm.makeRootRealm({ shims: [shim1] });
+ t.equal(r.global.kilroy, 'was here');
+ t.end();
+});
+
+test('shims: two shims', t => {
+ const r = Realm.makeRootRealm({ shims: [shim1, shim2] });
+ t.equal(r.global.kilroy, 'was here but he left');
+ t.end();
+});
+
+test('shims: inherited shims', t => {
+ const r1 = Realm.makeRootRealm({ shims: [shim1] });
+ const r2 = r1.evaluate(`Realm.makeRootRealm({shims: [${JSON.stringify(shim2)}]})`);
+ t.equal(r1.global.kilroy, 'was here');
+ t.equal(r2.global.kilroy, 'was here but he left');
+
+ const r3root = r2.evaluate(`Realm.makeRootRealm()`);
+ t.equal(r3root.global.kilroy, 'was here but he left');
+
+ // compartments have their own global, so they don't take shims
+ const r3compartment = r2.evaluate(`Realm.makeCompartment()`);
+ t.equal(r3compartment.global.kilroy, undefined);
+
+ // but a new RootRealm *under* that compartment *should* get the shims of
+ // the nearest enclosing RootRealm
+ const r4 = r3compartment.evaluate(`Realm.makeRootRealm()`);
+ t.equal(r4.global.kilroy, 'was here but he left');
+
+ t.end();
+});

add 'shims' option to Realm.makeRootRealm(), with inheritance

Each RootRealm first applies all the shims that their nearest enclosing
RootRealm applied, then it adds any new shims provided as options.shims (an
array of strings)