Skip to content

Commit

Permalink
Explicitly harden some shared prototypes (#1939)
Browse files Browse the repository at this point in the history
  • Loading branch information
mhofman authored Jan 10, 2024
2 parents b89e457 + 148f101 commit 91b0ac9
Show file tree
Hide file tree
Showing 13 changed files with 88 additions and 38 deletions.
4 changes: 2 additions & 2 deletions packages/captp/src/atomics.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export const makeAtomicsTrapHost = transferBuffer => {

const te = new TextEncoder();

return async function* trapHost([isReject, serialized]) {
return harden(async function* trapHost([isReject, serialized]) {
// Get the complete encoded message buffer.
const json = JSON.stringify(serialized);
const encoded = te.encode(json);
Expand Down Expand Up @@ -94,7 +94,7 @@ export const makeAtomicsTrapHost = transferBuffer => {
yield;
}
}
};
});
};

/**
Expand Down
2 changes: 2 additions & 0 deletions packages/exo/test/test-exo-wobbly-point.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class ExoBaseClass {
return harden({});
}
}
harden(ExoBaseClass);

const defineExoClassFromJSClass = klass =>
defineExoClass(klass.name, klass.implements, klass.init, klass.prototype);
Expand All @@ -55,6 +56,7 @@ class ExoAbstractPoint extends ExoBaseClass {
return `<${self.getX()},${self.getY()}>`;
}
}
harden(ExoAbstractPoint);

test('cannot make abstract class concrete', t => {
t.throws(() => defineExoClassFromJSClass(ExoAbstractPoint), {
Expand Down
10 changes: 6 additions & 4 deletions packages/far/test/test-e.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,16 +83,16 @@ test('E call missing method', async t => {
});

test('E call missing inherited methods', async t => {
const x = {
__proto__: {
const x = harden({
__proto__: harden({
half(n) {
return n / 2;
},
},
}),
double(n) {
return 2 * n;
},
};
});
await t.throwsAsync(() => E(x).triple(6), {
message: 'target has no method "triple", has ["double","half"]',
});
Expand All @@ -108,6 +108,7 @@ test('E call missing class methods', async t => {
return n / 2;
}
}
harden(X1);
class X2 extends X1 {
constructor() {
super();
Expand All @@ -118,6 +119,7 @@ test('E call missing class methods', async t => {
return 2 * n;
}
}
harden(X2);
const x = new X2();
await t.throwsAsync(() => E(x).triple(6), {
message:
Expand Down
1 change: 1 addition & 0 deletions packages/lp32/reader.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ async function* makeLp32Iterator(
);
}
}
harden(makeLp32Iterator);

/**
* @param {Iterable<Uint8Array> | AsyncIterable<Uint8Array>} reader
Expand Down
9 changes: 6 additions & 3 deletions packages/marshal/test/test-marshal-capdata.js
Original file line number Diff line number Diff line change
Expand Up @@ -264,9 +264,12 @@ test('serialize errors', t => {
}

// Bad prototype and bad "message" property
const nonErrorProto1 = { __proto__: Error.prototype, name: 'included' };
const nonError1 = { __proto__: nonErrorProto1, message: [] };
t.deepEqual(ser(harden(nonError1)), {
const nonErrorProto1 = harden({
__proto__: Error.prototype,
name: 'included',
});
const nonError1 = harden({ __proto__: nonErrorProto1, message: [] });
t.deepEqual(ser(nonError1), {
body: '{"@qclass":"error","errorId":"error:anon-marshal#10004","message":"","name":"included"}',
slots: [],
});
Expand Down
9 changes: 6 additions & 3 deletions packages/marshal/test/test-marshal-smallcaps.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,12 @@ test('smallcaps serialize errors', t => {
}

// Bad prototype and bad "message" property
const nonErrorProto1 = { __proto__: Error.prototype, name: 'included' };
const nonError1 = { __proto__: nonErrorProto1, message: [] };
t.deepEqual(ser(harden(nonError1)), {
const nonErrorProto1 = harden({
__proto__: Error.prototype,
name: 'included',
});
const nonError1 = harden({ __proto__: nonErrorProto1, message: [] });
t.deepEqual(ser(nonError1), {
body: '#{"#error":"","name":"included"}',
slots: [],
});
Expand Down
4 changes: 2 additions & 2 deletions packages/marshal/test/test-marshal-testing.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ const bob6 = harden({
});
const bob7 = harden({ __proto__: bob6 });
const bob8 = harden({
__proto__: {
__proto__: harden({
[Symbol.toStringTag]: 'Alleged: bob',
foo: 'x',
},
}),
});

test('ava deepEqual related edge cases', t => {
Expand Down
1 change: 1 addition & 0 deletions packages/netstring/reader.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ async function* makeNetstringIterator(

return undefined;
}
harden(makeNetstringIterator);

/**
* @param {Iterable<Uint8Array> | AsyncIterable<Uint8Array>} input
Expand Down
4 changes: 4 additions & 0 deletions packages/pass-style/test/test-far-class-instances.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class FarSubclass1 extends FarBaseClass {
return x + x;
}
}
harden(FarSubclass1);

class FarSubclass2 extends FarSubclass1 {
#y = 0;
Expand All @@ -43,6 +44,7 @@ class FarSubclass2 extends FarSubclass1 {
return this.double(x) + this.#y;
}
}
harden(FarSubclass2);

const assertMethodNames = (t, obj, names) => {
t.deepEqual(getMethodNames(obj), names);
Expand Down Expand Up @@ -88,6 +90,7 @@ test('far class instances', t => {
return this.double(x) + yField.get(this);
}
}
harden(FarSubclass3);

const fs3 = new FarSubclass3(3);
t.is(passStyleOf(fs3), 'remotable');
Expand All @@ -105,6 +108,7 @@ test('far class instance hardened empty', t => {
class FarClass4 extends FarBaseClass {
z = 0;
}
harden(FarClass4);
t.throws(() => new FarClass4(), {
// TODO message depends on JS engine, and so is a fragile golden test
message: 'Cannot define property z, object is not extensible',
Expand Down
28 changes: 15 additions & 13 deletions packages/pass-style/test/test-passStyleOf.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ test('some passStyleOf rejections', t => {
});

const prbad1 = Promise.resolve();
Object.setPrototypeOf(prbad1, { __proto__: Promise.prototype });
Object.setPrototypeOf(prbad1, harden({ __proto__: Promise.prototype }));
harden(prbad1);
t.throws(() => passStyleOf(prbad1), {
message:
Expand Down Expand Up @@ -125,7 +125,7 @@ test('passStyleOf testing tagged records', t => {
t.is(passStyleOf(harden(makeTagRecordVariant())), 'tagged');
t.is(passStyleOf(harden(makeTagRecordVariant({ passable: true }))), 'tagged');

for (const proto of [null, {}]) {
for (const proto of [null, harden({})]) {
const tagRecordBadProto = makeTagRecordVariant(undefined, proto);
t.throws(
() => passStyleOf(harden(tagRecordBadProto)),
Expand Down Expand Up @@ -176,7 +176,7 @@ test('passStyleOf testing remotables', t => {
t.is(passStyleOf(Far('foo', {})), 'remotable');
t.is(passStyleOf(Far('foo', () => 'far function')), 'remotable');

const tagRecord1 = makeTagishRecord('Alleged: manually constructed');
const tagRecord1 = harden(makeTagishRecord('Alleged: manually constructed'));
const farObj1 = harden({
__proto__: tagRecord1,
});
Expand All @@ -203,13 +203,13 @@ test('passStyleOf testing remotables', t => {
});
t.is(passStyleOf(farObj3), 'remotable');

const tagRecord4 = makeTagishRecord('Remotable');
const tagRecord4 = harden(makeTagishRecord('Remotable'));
const farObj4 = harden({
__proto__: tagRecord4,
});
t.is(passStyleOf(farObj4), 'remotable');

const tagRecord5 = makeTagishRecord('Not alleging');
const tagRecord5 = harden(makeTagishRecord('Not alleging'));
const farObj5 = harden({
__proto__: tagRecord5,
});
Expand All @@ -218,7 +218,7 @@ test('passStyleOf testing remotables', t => {
/For now, iface "Not alleging" must be "Remotable" or begin with "Alleged: " or "DebugName: "; unimplemented/,
});

const tagRecord6 = makeTagishRecord('Alleged: manually constructed');
const tagRecord6 = harden(makeTagishRecord('Alleged: manually constructed'));
const farObjProto6 = harden({
__proto__: tagRecord6,
});
Expand Down Expand Up @@ -259,6 +259,7 @@ test('passStyleOf testing remotables', t => {
return this.add(4) + this.add(4);
}
}
harden(FarSubclass8);
const farObj8 = new FarSubclass8(3);
t.is(passStyleOf(farObj8), 'remotable');
t.is(farObj8.twice(), 14);
Expand All @@ -272,9 +273,8 @@ test('passStyleOf testing remotables', t => {
const unusualTagRecordProtoMessage =
/A tagRecord must inherit from Object.prototype/;

const tagRecordA1 = makeTagishRecord(
'Alleged: null-proto tagRecord proto',
null,
const tagRecordA1 = harden(
makeTagishRecord('Alleged: null-proto tagRecord proto', null),
);
const farObjA1 = harden({ __proto__: tagRecordA1 });
t.throws(
Expand All @@ -283,9 +283,8 @@ test('passStyleOf testing remotables', t => {
'null-proto-tagRecord proto is rejected',
);

const tagRecordA2 = makeTagishRecord(
'Alleged: null-proto tagRecord grandproto',
null,
const tagRecordA2 = harden(
makeTagishRecord('Alleged: null-proto tagRecord grandproto', null),
);
const farObjProtoA2 = harden({ __proto__: tagRecordA2 });
const farObjA2 = harden({ __proto__: farObjProtoA2 });
Expand All @@ -299,7 +298,9 @@ test('passStyleOf testing remotables', t => {
message: 'cannot serialize Remotables with accessors like "toString" in {}',
});

const fauxTagRecordB = makeTagishRecord('Alleged: manually constructed', {});
const fauxTagRecordB = harden(
makeTagishRecord('Alleged: manually constructed', harden({})),
);
const farObjProtoB = harden({
__proto__: fauxTagRecordB,
});
Expand All @@ -315,6 +316,7 @@ test('passStyleOf testing remotables', t => {
'Alleged: manually constructed',
);
Object.defineProperty(farObjProtoWithExtra, 'extra', { value: () => {} });
harden(farObjProtoWithExtra);
const badFarObjExtraProtoProp = harden({ __proto__: farObjProtoWithExtra });
t.throws(() => passStyleOf(badFarObjExtraProtoProp), {
message: 'Unexpected properties on Remotable Proto ["extra"]',
Expand Down
48 changes: 39 additions & 9 deletions packages/ses/src/lockdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
stringSplit,
noEvalEvaluate,
getOwnPropertyNames,
getPrototypeOf,
} from './commons.js';
import { makeHardener } from './make-hardener.js';
import { makeIntrinsicsCollector } from './intrinsics.js';
Expand Down Expand Up @@ -281,6 +282,14 @@ export const repairIntrinsics = (options = {}) => {

const intrinsics = finalIntrinsics();

const hostIntrinsics = { __proto__: null };

// The Node.js Buffer is a derived class of Uint8Array, and as such is often
// passed around where a Uint8Array is expected.
if (typeof globalThis.Buffer === 'function') {
hostIntrinsics.Buffer = globalThis.Buffer;
}

/**
* Wrap console unless suppressed.
* At the moment, the console is considered a host power in the start
Expand All @@ -301,6 +310,19 @@ export const repairIntrinsics = (options = {}) => {
);
globalThis.console = /** @type {Console} */ (consoleRecord.console);

// The untamed Node.js console cannot itself be hardened as it has mutable
// internal properties, but some of these properties expose internal versions
// of classes from node's "primordials" concept.
// eslint-disable-next-line no-underscore-dangle
if (typeof (/** @type {any} */ (consoleRecord.console)._times) === 'object') {
// SafeMap is a derived Map class used internally by Node
// There doesn't seem to be a cleaner way to reach it.
hostIntrinsics.SafeMap = getPrototypeOf(
// eslint-disable-next-line no-underscore-dangle
/** @type {any} */ (consoleRecord.console)._times,
);
}

// @ts-ignore assert is absent on globalThis type def.
if (errorTaming === 'unsafe' && globalThis.assert === assert) {
// If errorTaming is 'unsafe' we replace the global assert with
Expand Down Expand Up @@ -388,20 +410,28 @@ export const repairIntrinsics = (options = {}) => {

// Finally register and optionally freeze all the intrinsics. This
// must be the operation that modifies the intrinsics.
tamedHarden(intrinsics);

// Harden evaluators
tamedHarden(globalThis.Function);
tamedHarden(globalThis.eval);
// @ts-ignore Compartment does exist on globalThis
tamedHarden(globalThis.Compartment);
const toHarden = {
intrinsics,
hostIntrinsics,
globals: {
// Harden evaluators
Function: globalThis.Function,
eval: globalThis.eval,
// @ts-ignore Compartment does exist on globalThis
Compartment: globalThis.Compartment,

// Harden Symbol
Symbol: globalThis.Symbol,
},
};

// Harden Symbol and properties for initialGlobalPropertyNames in the host realm
tamedHarden(globalThis.Symbol);
for (const prop of getOwnPropertyNames(initialGlobalPropertyNames)) {
tamedHarden(globalThis[prop]);
toHarden.globals[prop] = globalThis[prop];
}

tamedHarden(toHarden);

return tamedHarden;
};

Expand Down
5 changes: 3 additions & 2 deletions packages/ses/test/test-make-hardener.js
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,9 @@ test('harden a typed array subclass', t => {
class Ooint8Array extends Uint8Array {
oo = 'ghosts';
}
h(Ooint8Array);
t.truthy(Object.isFrozen(Ooint8Array.prototype));
t.truthy(Object.isFrozen(Object.getPrototypeOf(Ooint8Array.prototype)));

const a = new Ooint8Array(1);
t.is(h(a), a);
Expand All @@ -290,8 +293,6 @@ test('harden a typed array subclass', t => {
configurable: false,
enumerable: true,
});
t.truthy(Object.isFrozen(Ooint8Array.prototype));
t.truthy(Object.isFrozen(Object.getPrototypeOf(Ooint8Array.prototype)));
t.truthy(Object.isSealed(a));
});

Expand Down
1 change: 1 addition & 0 deletions packages/stream/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ export const mapReader = (reader, transform) => {
}
return undefined;
}
harden(transformGenerator);
return harden(transformGenerator());
};
harden(mapReader);
Expand Down

0 comments on commit 91b0ac9

Please sign in to comment.