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

feat: explicitly harden some shared prototypes #1939

Merged
merged 4 commits into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
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
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