repairDataProperties: cleanups and whitelist additions
MODIFY
src/bundle/createSES.js
src/bundle/createSES.js
@@ -25,7 +25,7 @@ import whitelist from './whitelist';
import makeConsole from './make-console';
import makeMakeRequire from './make-require';
import dataPropertiesToRepair from './dataPropertiesToRepair';
-import makeRepairDataProperties from './makeRepairDataProperties';
+import repairDataProperties from './repairDataProperties';
const FORWARDED_REALMS_OPTIONS = ['transforms'];
@@ -120,10 +120,6 @@ You probably want a Compartment instead, like:
const r = Realm.makeRootRealm({ ...realmsOptions, shims });
- // Build repairDataProperties() to be called before harden.
- const makeRepairDataPropertiesSrc = `(${makeRepairDataProperties})`;
- const repairDataProperties = r.evaluate(makeRepairDataPropertiesSrc)();
-
// Build a harden() with an empty fringe. It will be populated later when
// we call harden(allIntrinsics).
const makeHardenerSrc = `(${makeHardener})`;
@@ -150,7 +146,7 @@ You probably want a Compartment instead, like:
optDataPropertiesToRepair !== undefined
? optDataPropertiesToRepair
: dataPropertiesToRepair;
- repairDataProperties(allIntrinsics, repairPlan);
+ r.evaluate(`(${repairDataProperties})`)(allIntrinsics, repairPlan);
// Finally freeze all the primordials, and the global object. This must
// be the last thing we do that modifies the Realm's globals.

MODIFY
src/bundle/dataPropertiesToRepair.js
src/bundle/dataPropertiesToRepair.js
* value associated with that property is not traversed. For
* example, {@code "Function.prototype.name"} leads to true,
* meaning that the {@code "name"} property of {@code
- * "Function.prototype"} should be repaired. If the property is
+ * "Function.prototype"} should be repaired (which is needed
+ * when inheriting from @code{Function} and setting the subclass's
+ * {@code "prototype.name"} property). If the property is
* already an accessor property, it is not repaired (because
* accessors are not subject to the override mistake).
* <li>"*", all properties on this object are repaired.
+ * <li>falsey, in which case this property is skipped.
* </ul>
*
* <p>We factor out {@code true} into the variable {@code t} just to
const t = true;
export default {
- global: {
+ namedIntrinsics: {
Object: {
prototype: '*',
},
@@ -74,6 +77,7 @@ export default {
Function: {
prototype: {
+ bind: t, // set by "underscore"
name: t,
toString: t,
},
@@ -81,7 +85,15 @@ export default {
Error: {
prototype: {
+ constructor: t, // set by "fast-json-patch"
message: t,
+ name: t, // set by "precond"
+ },
+ },
+
+ Promise: {
+ prototype: {
+ constructor: t, // set by "core-js"
},
},
},
@@ -93,6 +105,7 @@ export default {
GeneratorFunction: {
prototype: {
+ constructor: t, // set by "regenerator-runtime"
name: t,
toString: t,
},

RENAME
src/bundle/makeRepairDataProperties.js
src/bundle/repairDataProperties.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
-export default function makeRepairDataProperties() {
+export default function repairDataProperties(intrinsics, repairPlan) {
// Object.defineProperty is allowed to fail silently,
// use Object.defineProperties instead.
@@ -11,14 +11,14 @@ export default function makeRepairDataProperties() {
defineProperties,
getOwnPropertyDescriptor,
getOwnPropertyDescriptors,
- hasOwnProperty,
+ prototype: { hasOwnProperty },
} = Object;
const { ownKeys } = Reflect;
/**
- * For a special set of properties (defined below), it ensures that the
- * effect of freezing does not suppress the ability to override these
- * properties on derived objects by simple assignment.
+ * For a special set of properties (defined in the repairPlan), it ensures
+ * that the effect of freezing does not suppress the ability to override
+ * these properties on derived objects by simple assignment.
*
* Because of lack of sufficient foresight at the time, ES5 unfortunately
* specified that a simple assignment to a non-existent property must fail if
@@ -28,11 +28,6 @@ export default function makeRepairDataProperties() {
* tamper proof has the unfortunate side effect of breaking previously correct
* code that is considered to have followed JS best practices, if this
* previous code used assignment to override.
- *
- * To work around this mistake, deepFreeze(), prior to freezing, replaces
- * selected configurable own data properties with accessor properties which
- * simulate what we should have specified -- that assignments to derived
- * objects succeed if otherwise possible.
*/
function enableDerivedOverride(obj, prop, desc) {
if ('value' in desc && desc.configurable) {
@@ -107,6 +102,9 @@ export default function makeRepairDataProperties() {
if (!obj) {
return;
}
+ if (!plan) {
+ return;
+ }
ownKeys(plan).forEach(prop => {
const subPlan = plan[prop];
const subObj = obj[prop];
@@ -120,17 +118,19 @@ export default function makeRepairDataProperties() {
break;
default:
+ if (Object(subPlan) !== subPlan) {
+ throw TypeError(`Repair plan subPlan ${subPlan} is invalid`);
+ }
walkRepairPlan(subObj, subPlan);
}
});
}
- function repairDataProperties(intrinsics, repairPlan) {
- if (!repairPlan) {
- return;
- }
- walkRepairPlan(intrinsics, repairPlan);
- }
+ // Rename global -> namedIntrinsics to match the whitelist.js
+ // convention.
+ const { anonIntrinsics, global: namedIntrinsics } = intrinsics;
+ const renamedIntrinsics = { anonIntrinsics, namedIntrinsics };
- return repairDataProperties;
+ // Do the repair.
+ walkRepairPlan(renamedIntrinsics, repairPlan);
}

MODIFY
test/test-repairDataProperties.js
test/test-repairDataProperties.js
+/* eslint-disable no-inner-declarations */
import test from 'tape';
import SES from '../src/index';
@@ -54,7 +55,7 @@ test('override options.dataPropertiesToRepair', t => {
const sesSingle = SES.makeSESRootRealm({
dataPropertiesToRepair: {
- global: { Array: { prototype: { slice: true } } },
+ namedIntrinsics: { Array: { prototype: { slice: true } } },
},
});
t.deepEqual(
@@ -65,7 +66,7 @@ test('override options.dataPropertiesToRepair', t => {
const sesProto = SES.makeSESRootRealm({
dataPropertiesToRepair: {
- global: { Array: { prototype: '*' } },
+ namedIntrinsics: { Array: { prototype: '*' } },
},
});
t.deepEqual(
@@ -96,3 +97,84 @@ test('Can assign "slice" of Array-inherited class prototype', t => {
}
t.end();
});
+
+test('invalid repair plan', t => {
+ try {
+ t.throws(
+ () =>
+ SES.makeSESRootRealm({
+ dataPropertiesToRepair: { namedIntrinsics: { Error: 'all' } },
+ }),
+ /TypeError/,
+ 'rejected "all" repair plan',
+ );
+ } catch (e) {
+ t.isNot(e, e, 'unexpected exception');
+ } finally {
+ t.end();
+ }
+});
+
+test('packages in-the-wild', t => {
+ try {
+ const s = SES.makeSESRootRealm();
+
+ function testContent1() {
+ function* X() {
+ // empty
+ }
+ X.constructor = function XConstructor() {};
+ }
+
+ t.doesNotThrow(
+ () => s.evaluate(`(${testContent1})`)(),
+ 'regenerator-runtime generator function constructor',
+ );
+
+ function testContent2() {
+ function IllegalArgumentError(message) {
+ Error.call(this, message);
+ this.message = message;
+ }
+
+ Object.setPrototypeOf(IllegalArgumentError.prototype, Error.prototype);
+ IllegalArgumentError.prototype.name = 'IllegalArgumentError';
+ }
+
+ t.doesNotThrow(
+ () => s.evaluate(`(${testContent2})`)(),
+ 'precond error subclass name',
+ );
+
+ function testContent3() {
+ const err = Error();
+ err.constructor = function ErrConstructor() {};
+ }
+ t.doesNotThrow(
+ () => s.evaluate(`(${testContent3})`)(),
+ 'fast-json-patch error instance constructor',
+ );
+
+ function testContent4() {
+ function fn() {}
+ fn.bind = function empty() {};
+ }
+ t.doesNotThrow(
+ () => s.evaluate(`(${testContent4})`)(),
+ `underscore function instance bind`,
+ );
+
+ function testContent5() {
+ const p = new Promise(() => {});
+ p.constructor = function PConstructor() {};
+ }
+ t.doesNotThrow(
+ () => s.evaluate(`(${testContent5})`)(),
+ `core-js promise instance constructor`,
+ );
+ } catch (e) {
+ t.isNot(e, e, 'unexpected exception');
+ } finally {
+ t.end();
+ }
+});

repairDataProperties: cleanups and whitelist additions