repair the accessor-related functions with a new automatic shim
MODIFY
shim/src/accessors.js
shim/src/accessors.js
// https://github.com/google/caja/blob/master/src/com/google/caja/ses/startSES.js
// https://github.com/google/caja/blob/master/src/com/google/caja/ses/repairES5.js
-import {
- getPrototypeOf,
- defineProperty,
- defineProperties,
- getOwnPropertyDescriptor
-} from './commons';
-
-// TOCTTOU and .asString() games could enable attacker to skip some
-// intermediate ancestors, so we stringify/propify this once, first.
-export function asPropertyName(prop) {
- if (typeof prop === 'symbol') {
- return prop;
- }
- return `${prop}`;
-}
-
/**
* Replace the legacy accessors of Object to comply with strict mode
* and ES2016 semantics, we do this by redefining them while in 'use strict'
@@ -35,15 +19,28 @@ this, and we need to include repairAccessors. E.g. Chrome in 2016.
todo: It would be better to detect and only repair the functions that have
the bug.
+ */
-todo: all of these leak access to the primal realm's Function.prototype, via
-e.g. Object.prototype.__lookupGetter__.__proto__
+// todo: this file should be moved out to a separate repo and npm module
- */
-export function repairAccessors(unsafeRec) {
- const { unsafeGlobal: g } = unsafeRec;
+// We use this function in two ways. We use it directly to fix the primal
+// realm's Object.prototype, and we convert it into a string to be executed
+// inside each new RootRealm to fix their Object.prototypes too. So don't
+// import anything from the outside.
- defineProperties(g.Object.prototype, {
+export function repairAccessors() {
+ const { getPrototypeOf, defineProperties, defineProperty, getOwnPropertyDescriptor } = Object;
+
+ // TOCTTOU and .asString() games could enable attacker to skip some
+ // intermediate ancestors, so we stringify/propify this once, first.
+ function asPropertyName(prop) {
+ if (typeof prop === 'symbol') {
+ return prop;
+ }
+ return `${prop}`;
+ }
+
+ defineProperties(Object.prototype, {
__defineGetter__: {
value(prop, func) {
return defineProperty(this, prop, {
@@ -86,3 +83,5 @@ export function repairAccessors(unsafeRec) {
}
});
}
+
+export const repairAccessorsShim = `${repairAccessors} repairAccessors();`;

MODIFY
shim/src/unsafeRec.js
shim/src/unsafeRec.js
+// this module must never be importable outside the Realm shim itself
import { getSharedGlobalDescs } from './stdlib';
-import { repairAccessors } from './accessors';
+import { repairAccessors, repairAccessorsShim } from './accessors';
import { repairFunctions } from './functions';
import { freeze } from './commons';
@@ -41,7 +42,11 @@ function createNewUnsafeGlobalForBrowser() {
return unsafeGlobal;
}
-const getNewUnsafeGlobal = isNode ? createNewUnsafeGlobalForNode : createNewUnsafeGlobalForBrowser;
+// we only export this so test-repair.js can get an unrepaired
+// Object.prototype, to sense if this platform has the buggy behavior
+export const getNewUnsafeGlobal = isNode
+ ? createNewUnsafeGlobalForNode
+ : createNewUnsafeGlobalForBrowser;
// The unsafeRec is shim-specific. It acts as the mechanism to obtain a fresh
// set of intrinsics together with their associated eval and Function
@@ -66,8 +71,8 @@ function sanitizeUnsafeRec(unsafeRec) {
// Ensures that neither the legacy accessors nor the function constructors
// can be used to escape the confinement of the evaluators to execute in the
// context.
- repairAccessors(unsafeRec);
repairFunctions(unsafeRec);
+ // we repair the accessors by injecting a shim string, so it gets the right types
}
// Create a new unsafeRec from a brand new context, with new intrinsics and a
@@ -83,7 +88,8 @@ export function createNewUnsafeRec(allShims) {
// being parsed and executed, aka the "Primal Realm"
export function createCurrentUnsafeRec() {
const unsafeGlobal = (0, eval)(unsafeGlobalSrc);
- const unsafeRec = createUnsafeRec(unsafeGlobal, []); // todo: fixAccessors here
- sanitizeUnsafeRec(unsafeRec);
+ const unsafeRec = createUnsafeRec(unsafeGlobal, [repairAccessorsShim]);
+ // sanitizeUnsafeRec(unsafeRec); // todo: markm not sure we want to repair functions
+ repairAccessors();
return unsafeRec;
}

MODIFY
shim/test/realm/test-repair.js
shim/test/realm/test-repair.js
import test from 'tape';
-// import Realm from '../../src/realm';
+import Realm from '../../src/realm';
+import { getNewUnsafeGlobal } from '../../src/unsafeRec';
// import { walkObjects } from '../../src/scan';
export const protectedObjects = new WeakMap();
@@ -9,19 +10,52 @@ protectedObjects.set(Function.prototype, 'Function.prototype');
protectedObjects.set(eval, 'eval');
protectedObjects.set((0, eval)('this'), 'global object');
-// todo: these tests are disabled until we fix the underlying leaks in
-// accessors.js
-
test('eval.toString', t => {
- /*
const r = Realm.makeRootRealm();
const p = r.evaluate('Object.prototype.__lookupGetter__.__proto__');
- t.equal(p, r.global.Function.prototype); // should be
+ t.equal(p, r.global.Function.prototype);
t.notEqual(p, Function.prototype);
- */
t.end();
});
+function testForBug(o) {
+ // on a fixed platform, this should throw
+ // eslint-disable-next-line no-restricted-properties, no-underscore-dangle
+ (0, o.prototype.__defineGetter__)('x', () => {});
+}
+
+test('fix the bug in which accessor methods leak the global', t => {
+ const unfixedGlobal = getNewUnsafeGlobal();
+ try {
+ testForBug(unfixedGlobal.Object);
+ // eslint-disable-next-line no-console
+ console.log('this platform has the bug');
+ } catch (e) {
+ if (e instanceof unfixedGlobal.TypeError) {
+ // eslint-disable-next-line no-console
+ console.log('this platform does not have the bug');
+ } else {
+ throw e; // some other problem
+ }
+ }
+
+ // first test that the primal realm was fixed: either as a side-effect of
+ // creating the Realm shim, or because it wasn't buggy in the first place
+ t.throws(() => testForBug(Object), TypeError);
+
+ // now test that the bug is fixed inside a new RootRealm too
+ const r = Realm.makeRootRealm();
+ t.throws(() => testForBug(r.global.Object), r.global.TypeError);
+
+ // and the fix we applied should not leak the unsafe Function
+ const p = r.evaluate('Object.prototype.__lookupGetter__.__proto__');
+ t.equal(p, r.global.Function.prototype);
+ t.notEqual(p, Function.prototype);
+ t.end();
+});
+
+// todo: rewrite and re-enable
+
test('scan', t => {
/*
const r = Realm.makeRootRealm();

repair the accessor-related functions with a new automatic shim