Skip to content

Commit e61e950

Browse files
committed
feat(ses,pass-style): use non-trapping integrity level for safety
1 parent 9b20c1b commit e61e950

12 files changed

+101
-81
lines changed

packages/captp/src/captp.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ const reverseSlot = slot => {
4747
};
4848

4949
/**
50-
* @typedef {object} CapTPImportExportTables
50+
* @typedef {object} CapTPImportExportTables
5151
* @property {(value: any) => CapTPSlot} makeSlotForValue
5252
* @property {(slot: CapTPSlot, iface: string | undefined) => any} makeValueForSlot
5353
* @property {(slot: CapTPSlot) => boolean} hasImport
@@ -58,12 +58,12 @@ const reverseSlot = slot => {
5858
* @property {(slot: CapTPSlot, value: any) => void} markAsExported
5959
* @property {(slot: CapTPSlot) => void} deleteExport
6060
* @property {() => void} didDisconnect
61-
61+
*
6262
* @typedef {object} MakeCapTPImportExportTablesOptions
6363
* @property {boolean} gcImports
6464
* @property {(slot: CapTPSlot) => void} releaseSlot
6565
* @property {(slot: CapTPSlot) => RemoteKit} makeRemoteKit
66-
66+
*
6767
* @param {MakeCapTPImportExportTablesOptions} options
6868
* @returns {CapTPImportExportTables}
6969
*/

packages/marshal/src/encodeToCapData.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ const {
3030
is,
3131
entries,
3232
fromEntries,
33-
freeze,
33+
// @ts-expect-error TS doesn't see this on ObjectConstructor
34+
suppressTrapping,
3435
} = Object;
3536

3637
/**
@@ -176,10 +177,10 @@ export const makeEncodeToCapData = (encodeOptions = {}) => {
176177
// We harden the entire capData encoding before we return it.
177178
// `encodeToCapData` requires that its input be Passable, and
178179
// therefore hardened.
179-
// The `freeze` here is needed anyway, because the `rest` is
180+
// The `suppressTrapping` here is needed anyway, because the `rest` is
180181
// freshly constructed by the `...` above, and we're using it
181182
// as imput in another call to `encodeToCapData`.
182-
result.rest = encodeToCapDataRecur(freeze(rest));
183+
result.rest = encodeToCapDataRecur(suppressTrapping(rest));
183184
}
184185
return result;
185186
}

packages/pass-style/src/passStyle-helpers.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ const {
1111
getOwnPropertyDescriptor,
1212
getPrototypeOf,
1313
hasOwnProperty: objectHasOwnProperty,
14-
isFrozen,
1514
prototype: objectPrototype,
15+
isFrozen,
16+
// @ts-expect-error TS does not yet have `isNonTrapping` on ObjectConstructor
17+
isNonTrapping,
1618
} = Object;
1719
const { apply } = Reflect;
1820
const { toStringTag: toStringTagSymbol } = Symbol;
@@ -167,6 +169,9 @@ const makeCheckTagRecord = checkProto => {
167169
CX(check)`A non-object cannot be a tagRecord: ${tagRecord}`)) &&
168170
(isFrozen(tagRecord) ||
169171
(!!check && CX(check)`A tagRecord must be frozen: ${tagRecord}`)) &&
172+
(isNonTrapping(tagRecord) ||
173+
(!!check &&
174+
CX(check)`A tagRecord must be non-trapping: ${tagRecord}`)) &&
170175
(!isArray(tagRecord) ||
171176
(!!check && CX(check)`An array cannot be a tagRecord: ${tagRecord}`)) &&
172177
checkPassStyle(

packages/pass-style/src/passStyleOf.js

+24-11
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,13 @@ import { assertPassableString } from './string.js';
3131
/** @typedef {Exclude<PassStyle, PrimitiveStyle | "promise">} HelperPassStyle */
3232

3333
const { ownKeys } = Reflect;
34-
const { isFrozen, getOwnPropertyDescriptors, values } = Object;
34+
const {
35+
getOwnPropertyDescriptors,
36+
values,
37+
isFrozen,
38+
// @ts-expect-error TS does not yet have `isNonTrapping` on ObjectConstructor
39+
isNonTrapping,
40+
} = Object;
3541

3642
/**
3743
* @param {PassStyleHelper[]} passStyleHelpers
@@ -143,14 +149,17 @@ const makePassStyleOf = passStyleHelpers => {
143149
if (inner === null) {
144150
return 'null';
145151
}
146-
if (!isFrozen(inner)) {
147-
assert.fail(
148-
// TypedArrays get special treatment in harden()
149-
// and a corresponding special error message here.
150-
isTypedArray(inner)
151-
? X`Cannot pass mutable typed arrays like ${inner}.`
152-
: X`Cannot pass non-frozen objects like ${inner}. Use harden()`,
153-
);
152+
if (!isNonTrapping(inner)) {
153+
if (!isFrozen(inner)) {
154+
throw assert.fail(
155+
// TypedArrays get special treatment in harden()
156+
// and a corresponding special error message here.
157+
isTypedArray(inner)
158+
? X`Cannot pass mutable typed arrays like ${inner}.`
159+
: X`Cannot pass non-frozen objects like ${inner}. Use harden()`,
160+
);
161+
}
162+
throw Fail`Cannot pass non-trapping objects like ${inner}`;
154163
}
155164
if (isPromise(inner)) {
156165
assertSafePromise(inner);
@@ -177,8 +186,12 @@ const makePassStyleOf = passStyleHelpers => {
177186
return 'remotable';
178187
}
179188
case 'function': {
180-
isFrozen(inner) ||
181-
Fail`Cannot pass non-frozen objects like ${inner}. Use harden()`;
189+
if (!isNonTrapping(inner)) {
190+
if (!isFrozen(inner)) {
191+
throw Fail`Cannot pass non-frozen objects like ${inner}. Use harden()`;
192+
}
193+
throw Fail`Cannot pass trapping objects like ${inner}. Use harden()`;
194+
}
182195
typeof inner.then !== 'function' ||
183196
Fail`Cannot pass non-promise thenables`;
184197
remotableHelper.assertValid(inner, passStyleOfRecur);

packages/pass-style/src/remotable.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,10 @@ const { ownKeys } = Reflect;
2424
const { isArray } = Array;
2525
const {
2626
getPrototypeOf,
27-
isFrozen,
2827
prototype: objectPrototype,
2928
getOwnPropertyDescriptors,
29+
// @ts-expect-error TS does not yet have `isNonTrapping` on ObjectConstructor
30+
isNonTrapping,
3031
} = Object;
3132

3233
/**
@@ -154,7 +155,7 @@ const checkRemotable = (val, check) => {
154155
if (confirmedRemotables.has(val)) {
155156
return true;
156157
}
157-
if (!isFrozen(val)) {
158+
if (!isNonTrapping(val)) {
158159
return (
159160
!!check && CX(check)`cannot serialize non-frozen objects like ${val}`
160161
);

packages/pass-style/src/safe-promise.js

+8-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@ import { assertChecker, hasOwnPropertyOf, CX } from './passStyle-helpers.js';
66

77
/** @import {Checker} from './types.js' */
88

9-
const { isFrozen, getPrototypeOf, getOwnPropertyDescriptor } = Object;
9+
const {
10+
getPrototypeOf,
11+
getOwnPropertyDescriptor,
12+
// @ts-expect-error TS does not yet have `isNonTrapping` on ObjectConstructor
13+
isNonTrapping,
14+
} = Object;
1015
const { ownKeys } = Reflect;
1116
const { toStringTag } = Symbol;
1217

@@ -88,7 +93,7 @@ const checkPromiseOwnKeys = (pr, check) => {
8893
if (
8994
typeof val === 'object' &&
9095
val !== null &&
91-
isFrozen(val) &&
96+
isNonTrapping(val) &&
9297
getPrototypeOf(val) === Object.prototype
9398
) {
9499
const subKeys = ownKeys(val);
@@ -132,7 +137,7 @@ const checkPromiseOwnKeys = (pr, check) => {
132137
*/
133138
const checkSafePromise = (pr, check) => {
134139
return (
135-
(isFrozen(pr) || CX(check)`${pr} - Must be frozen`) &&
140+
(isNonTrapping(pr) || CX(check)`${pr} - Must be frozen`) &&
136141
(isPromise(pr) || CX(check)`${pr} - Must be a promise`) &&
137142
(getPrototypeOf(pr) === Promise.prototype ||
138143
CX(check)`${pr} - Must inherit from Promise.prototype: ${q(

packages/pass-style/test/passStyleOf.test.js

+17-54
Original file line numberDiff line numberDiff line change
@@ -13,35 +13,7 @@ const harden = /** @type {import('ses').Harden & { isFake?: boolean }} */ (
1313
global.harden
1414
);
1515

16-
const { getPrototypeOf, defineProperty, freeze } = Object;
17-
/**
18-
* Local alias of `harden` to eventually be switched to whatever applies
19-
* the suppress-trapping integrity trait. For the shim at
20-
* https://github.com/endojs/endo/pull/2673
21-
* that is `suppressTrapping`, which is why we choose that name for the
22-
* placeholder here. But it is a separate definition so these aliased uses
23-
* do not yet depend on the final name.
24-
*
25-
* TODO Once we do have support for an explicit `suppressTrapping` operation,
26-
* we should import that instead, and if necessary rename all uses to that
27-
* operation's final name.
28-
*/
29-
const hardenToBeSuppressTrapping = harden;
30-
31-
/**
32-
* Local alias of `freeze` to eventually be switched to whatever applies
33-
* the suppress-trapping integrity trait. For the shim at
34-
* https://github.com/endojs/endo/pull/2673
35-
* that is `suppressTrapping`, which is why we choose that name for the
36-
* placeholder here. But it is a separate definition so these aliased uses
37-
* do not yet depend on the final name.
38-
*
39-
* TODO Once we do have support for an explicit `suppressTrapping` operation,
40-
* we should import that instead, and if necessary rename all uses to that
41-
* operation's final name.
42-
*/
43-
const freezeToBeSuppressTrapping = freeze;
44-
16+
const { getPrototypeOf, defineProperty, freeze, suppressTrapping } = Object;
4517
const { ownKeys } = Reflect;
4618

4719
test('passStyleOf basic success cases', t => {
@@ -224,23 +196,15 @@ test('passStyleOf testing remotables', t => {
224196
t.is(passStyleOf(Far('foo', () => 'far function')), 'remotable');
225197

226198
const tagRecord1 = harden(makeTagishRecord('Alleged: manually constructed'));
227-
const farObj1 = hardenToBeSuppressTrapping({ __proto__: tagRecord1 });
199+
const farObj1 = suppressTrapping({ __proto__: tagRecord1 });
228200
t.is(passStyleOf(farObj1), 'remotable');
229201

230202
const tagRecord2 = makeTagishRecord('Alleged: tagRecord not hardened');
231203
/**
232204
* Do not freeze `tagRecord2` in order to test that an object with
233205
* a non-frozen __proto__ is not passable.
234-
*
235-
* TODO In order to run this test before we have explicit support for a
236-
* non-trapping integrity trait, we have to `freeze` here but not `harden`.
237-
* However, once we do have that support, and `passStyleOf` checks that
238-
* its argument is also non-trapping, we still need to avoid `harden`
239-
* because that would also harden `__proto__`. So we will need to
240-
* explicitly make this non-trapping, which we cannot yet express.
241-
* @see https://github.com/endojs/endo/blob/master/packages/ses/docs/preparing-for-stabilize.md
242206
*/
243-
const farObj2 = freezeToBeSuppressTrapping({ __proto__: tagRecord2 });
207+
const farObj2 = suppressTrapping({ __proto__: tagRecord2 });
244208
if (harden.isFake) {
245209
t.is(passStyleOf(farObj2), 'remotable');
246210
} else {
@@ -251,23 +215,23 @@ test('passStyleOf testing remotables', t => {
251215
}
252216

253217
const tagRecord3 = harden(makeTagishRecord('Alleged: both manually frozen'));
254-
const farObj3 = hardenToBeSuppressTrapping({ __proto__: tagRecord3 });
218+
const farObj3 = suppressTrapping({ __proto__: tagRecord3 });
255219
t.is(passStyleOf(farObj3), 'remotable');
256220

257221
const tagRecord4 = harden(makeTagishRecord('Remotable'));
258-
const farObj4 = hardenToBeSuppressTrapping({ __proto__: tagRecord4 });
222+
const farObj4 = suppressTrapping({ __proto__: tagRecord4 });
259223
t.is(passStyleOf(farObj4), 'remotable');
260224

261225
const tagRecord5 = harden(makeTagishRecord('Not alleging'));
262-
const farObj5 = hardenToBeSuppressTrapping({ __proto__: tagRecord5 });
226+
const farObj5 = suppressTrapping({ __proto__: tagRecord5 });
263227
t.throws(() => passStyleOf(farObj5), {
264228
message:
265229
/For now, iface "Not alleging" must be "Remotable" or begin with "Alleged: " or "DebugName: "; unimplemented/,
266230
});
267231

268232
const tagRecord6 = harden(makeTagishRecord('Alleged: manually constructed'));
269-
const farObjProto6 = hardenToBeSuppressTrapping({ __proto__: tagRecord6 });
270-
const farObj6 = hardenToBeSuppressTrapping({ __proto__: farObjProto6 });
233+
const farObjProto6 = suppressTrapping({ __proto__: tagRecord6 });
234+
const farObj6 = suppressTrapping({ __proto__: farObjProto6 });
271235
t.is(passStyleOf(farObj6), 'remotable', 'tagRecord grandproto is accepted');
272236

273237
// Our current agoric-sdk plans for far classes are to create a class-like
@@ -321,7 +285,7 @@ test('passStyleOf testing remotables', t => {
321285
const tagRecordA1 = harden(
322286
makeTagishRecord('Alleged: null-proto tagRecord proto', null),
323287
);
324-
const farObjA1 = hardenToBeSuppressTrapping({ __proto__: tagRecordA1 });
288+
const farObjA1 = suppressTrapping({ __proto__: tagRecordA1 });
325289
t.throws(
326290
() => passStyleOf(farObjA1),
327291
{ message: unusualTagRecordProtoMessage },
@@ -331,8 +295,8 @@ test('passStyleOf testing remotables', t => {
331295
const tagRecordA2 = harden(
332296
makeTagishRecord('Alleged: null-proto tagRecord grandproto', null),
333297
);
334-
const farObjProtoA2 = hardenToBeSuppressTrapping({ __proto__: tagRecordA2 });
335-
const farObjA2 = hardenToBeSuppressTrapping({ __proto__: farObjProtoA2 });
298+
const farObjProtoA2 = suppressTrapping({ __proto__: tagRecordA2 });
299+
const farObjA2 = suppressTrapping({ __proto__: farObjProtoA2 });
336300
t.throws(
337301
() => passStyleOf(farObjA2),
338302
{ message: unusualTagRecordProtoMessage },
@@ -346,10 +310,10 @@ test('passStyleOf testing remotables', t => {
346310
const fauxTagRecordB = harden(
347311
makeTagishRecord('Alleged: manually constructed', harden({})),
348312
);
349-
const farObjProtoB = hardenToBeSuppressTrapping({
313+
const farObjProtoB = suppressTrapping({
350314
__proto__: fauxTagRecordB,
351315
});
352-
const farObjB = hardenToBeSuppressTrapping({ __proto__: farObjProtoB });
316+
const farObjB = suppressTrapping({ __proto__: farObjProtoB });
353317
t.throws(() => passStyleOf(farObjB), {
354318
message:
355319
'cannot serialize Remotables with non-methods like "Symbol(passStyle)" in "[Alleged: manually constructed]"',
@@ -360,7 +324,7 @@ test('passStyleOf testing remotables', t => {
360324
);
361325
Object.defineProperty(farObjProtoWithExtra, 'extra', { value: () => {} });
362326
harden(farObjProtoWithExtra);
363-
const badFarObjExtraProtoProp = hardenToBeSuppressTrapping({
327+
const badFarObjExtraProtoProp = suppressTrapping({
364328
__proto__: farObjProtoWithExtra,
365329
});
366330
t.throws(() => passStyleOf(badFarObjExtraProtoProp), {
@@ -425,8 +389,7 @@ test('remotables - safety from the gibson042 attack', t => {
425389
* explicitly make this non-trapping, which we cannot yet express.
426390
* @see https://github.com/endojs/endo/blob/master/packages/ses/docs/preparing-for-stabilize.md
427391
*/
428-
const makeInput = () =>
429-
freezeToBeSuppressTrapping({ __proto__: mercurialProto });
392+
const makeInput = () => suppressTrapping({ __proto__: mercurialProto });
430393
const input1 = makeInput();
431394
const input2 = makeInput();
432395

@@ -487,12 +450,12 @@ test('Allow toStringTag overrides', t => {
487450
t.is(`${alice}`, '[object DebugName: Allison]');
488451
t.is(`${q(alice)}`, '"[DebugName: Allison]"');
489452

490-
const carol = hardenToBeSuppressTrapping({ __proto__: alice });
453+
const carol = suppressTrapping({ __proto__: alice });
491454
t.is(passStyleOf(carol), 'remotable');
492455
t.is(`${carol}`, '[object DebugName: Allison]');
493456
t.is(`${q(carol)}`, '"[DebugName: Allison]"');
494457

495-
const bob = hardenToBeSuppressTrapping({
458+
const bob = suppressTrapping({
496459
__proto__: carol,
497460
[Symbol.toStringTag]: 'DebugName: Robert',
498461
});

packages/ses/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,8 @@
8585
"postpack": "git clean -f '*.d.ts*' '*.tsbuildinfo'"
8686
},
8787
"dependencies": {
88-
"@endo/env-options": "workspace:^"
88+
"@endo/env-options": "workspace:^",
89+
"@endo/non-trapping-shim": "^0.1.0"
8990
},
9091
"devDependencies": {
9192
"@endo/compartment-mapper": "workspace:^",

packages/ses/src/commons.js

+10
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
/* global globalThis */
1515
/* eslint-disable no-restricted-globals */
1616

17+
import '@endo/non-trapping-shim/shim.js';
18+
1719
// We cannot use globalThis as the local name since it would capture the
1820
// lexical name.
1921
const universalThis = globalThis;
@@ -75,6 +77,11 @@ export const {
7577
setPrototypeOf,
7678
values,
7779
fromEntries,
80+
// https://github.com/endojs/endo/pull/2673
81+
// @ts-expect-error TS does not yet have this on ObjectConstructor.
82+
isNonTrapping,
83+
// @ts-expect-error TS does not yet have this on ObjectConstructor.
84+
suppressTrapping,
7885
} = Object;
7986

8087
export const {
@@ -125,6 +132,9 @@ export const {
125132
ownKeys,
126133
preventExtensions: reflectPreventExtensions,
127134
set: reflectSet,
135+
// https://github.com/endojs/endo/pull/2673
136+
isNonTrapping: reflectIsNonTrapping,
137+
suppressTrapping: reflectSuppressTrapping,
128138
} = Reflect;
129139

130140
export const { isArray, prototype: arrayPrototype } = Array;

0 commit comments

Comments
 (0)