Skip to content

Commit 49d9dfb

Browse files
committed
feat(ses,pass-style): use no-trapping integrity level for safety
1 parent f7d527c commit 49d9dfb

21 files changed

+153
-54
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/captp/src/trap.js

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
// Lifted mostly from `@endo/eventual-send/src/E.js`.
22

3+
const { freeze } = Object;
4+
35
/**
46
* Default implementation of Trap for near objects.
57
*
@@ -63,7 +65,10 @@ const TrapProxyHandler = (x, trapImpl) => {
6365
export const makeTrap = trapImpl => {
6466
const Trap = x => {
6567
const handler = TrapProxyHandler(x, trapImpl);
66-
return harden(new Proxy(() => {}, handler));
68+
return new Proxy(
69+
freeze(() => {}),
70+
handler,
71+
);
6772
};
6873

6974
const makeTrapGetterProxy = x => {
@@ -77,7 +82,7 @@ export const makeTrap = trapImpl => {
7782
return trapImpl.get(x, prop);
7883
},
7984
});
80-
return new Proxy(Object.create(null), handler);
85+
return new Proxy(freeze(Object.create(null)), handler);
8186
};
8287
Trap.get = makeTrapGetterProxy;
8388

packages/eventual-send/src/E.js

+20-7
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { trackTurns } from './track-turns.js';
22
import { makeMessageBreakpointTester } from './message-breakpoints.js';
33

44
const { details: X, quote: q, Fail, error: makeError } = assert;
5-
const { assign, create } = Object;
5+
const { assign, create, freeze } = Object;
66

77
/**
88
* @import { HandledPromiseConstructor } from './types.js';
@@ -171,6 +171,13 @@ const makeEGetProxyHandler = (x, HandledPromise) =>
171171
* @param {HandledPromiseConstructor} HandledPromise
172172
*/
173173
const makeE = HandledPromise => {
174+
// Note the use of `freeze` rather than `harden` below. This is because
175+
// `harden` now implies no-trapping, and we depend on proxies with these
176+
// almost-empty targets to remain trapping for traps `get`, `apply`, and `set`
177+
// which can still be interesting even when the target is frozen.
178+
// `get` and `has`, if not naming an own property, are still general traps,
179+
// which we rely on. `apply`, surprisingly perhaps, is free to ignore the
180+
// target's call behavior and just do its own thing instead.
174181
return harden(
175182
assign(
176183
/**
@@ -182,8 +189,12 @@ const makeE = HandledPromise => {
182189
* @param {T} x target for method/function call
183190
* @returns {ECallableOrMethods<RemoteFunctions<T>>} method/function call proxy
184191
*/
185-
// @ts-expect-error XXX typedef
186-
x => harden(new Proxy(() => {}, makeEProxyHandler(x, HandledPromise))),
192+
x =>
193+
// @ts-expect-error XXX typedef
194+
new Proxy(
195+
freeze(() => {}),
196+
makeEProxyHandler(x, HandledPromise),
197+
),
187198
{
188199
/**
189200
* E.get(x) returns a proxy on which you can get arbitrary properties.
@@ -198,8 +209,9 @@ const makeE = HandledPromise => {
198209
*/
199210
get: x =>
200211
// @ts-expect-error XXX typedef
201-
harden(
202-
new Proxy(create(null), makeEGetProxyHandler(x, HandledPromise)),
212+
new Proxy(
213+
freeze(create(null)),
214+
makeEGetProxyHandler(x, HandledPromise),
203215
),
204216

205217
/**
@@ -224,8 +236,9 @@ const makeE = HandledPromise => {
224236
*/
225237
sendOnly: x =>
226238
// @ts-expect-error XXX typedef
227-
harden(
228-
new Proxy(() => {}, makeESendOnlyProxyHandler(x, HandledPromise)),
239+
new Proxy(
240+
freeze(() => {}),
241+
makeESendOnlyProxyHandler(x, HandledPromise),
229242
),
230243

231244
/**

packages/eventual-send/src/handled-promise.js

+2
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,8 @@ export const makeHandledPromise = () => {
307307
const { proxy: proxyOpts } = options;
308308
let presence;
309309
if (proxyOpts) {
310+
// TODO for these cases, it will be unreasonably hard for all uses
311+
// to avoid hardening the returned proxy.
310312
const {
311313
handler: proxyHandler,
312314
target: proxyTarget,

packages/exo/src/exo-makers.js

+10-3
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,14 @@ import { defendPrototype, defendPrototypeKit } from './exo-tools.js';
88
* @import {Amplify, ExoClassKitMethods, ExoClassMethods, FarClassOptions, Guarded, GuardedKit, ExoClassInterfaceGuardKit, IsInstance, KitContext, ExoClassInterfaceGuard, Methods, FacetName} from './types.js';
99
*/
1010

11-
const { create, seal, freeze, defineProperty, values } = Object;
11+
const {
12+
create,
13+
seal,
14+
defineProperty,
15+
values,
16+
// @ts-expect-error TS doesn't know this is on ObjectConstructor
17+
suppressTrapping,
18+
} = Object;
1219

1320
// Turn on to give each exo instance its own toStringTag value.
1421
const LABEL_INSTANCES = environmentOptionsListHas('DEBUG', 'label-instances');
@@ -92,7 +99,7 @@ export const defineExoClass = (
9299

93100
// Be careful not to freeze the state record
94101
/** @type {import('./types.js').ClassContext<ReturnType<I>,M>} */
95-
const context = freeze({ state, self });
102+
const context = suppressTrapping({ state, self });
96103
contextMap.set(self, context);
97104
if (finish) {
98105
finish(context);
@@ -173,7 +180,7 @@ export const defineExoClassKit = (
173180
});
174181
context.facets = facets;
175182
// Be careful not to freeze the state record
176-
freeze(context);
183+
suppressTrapping(context);
177184
if (finish) {
178185
finish(context);
179186
}

packages/far/test/marshal-far-function.test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ test('Data can contain far functions', t => {
5858
const arrow = Far('arrow', a => a + 1);
5959
t.is(passStyleOf(harden({ x: 8, foo: arrow })), 'copyRecord');
6060
const mightBeMethod = a => a + 1;
61-
t.throws(() => passStyleOf(freeze({ x: 8, foo: mightBeMethod })), {
61+
t.throws(() => passStyleOf(harden({ x: 8, foo: mightBeMethod })), {
6262
message: /Remotables with non-methods like "x" /,
6363
});
6464
});

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 know this is 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/marshal/src/marshal-stringify.js

+16-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { makeMarshal } from './marshal.js';
55

66
/** @import {Passable} from '@endo/pass-style' */
77

8+
const { freeze } = Object;
9+
810
/** @type {import('./types.js').ConvertValToSlot<any>} */
911
const doNotConvertValToSlot = val =>
1012
Fail`Marshal's stringify rejects presences and promises ${val}`;
@@ -23,7 +25,13 @@ const badArrayHandler = harden({
2325
},
2426
});
2527

26-
const badArray = harden(new Proxy(harden([]), badArrayHandler));
28+
// Note the use of `freeze` rather than `harden` below. This is because
29+
// `harden` now implies no-trapping, and we depend on proxies with these
30+
// almost-empty targets to remain trapping for the `get` trap
31+
// which can still be interesting even when the target is frozen.
32+
// `get`, if not naming an own property, are still general traps,
33+
// which we rely on.
34+
const badArray = new Proxy(freeze([]), badArrayHandler);
2735

2836
const { serialize, unserialize } = makeMarshal(
2937
doNotConvertValToSlot,
@@ -48,7 +56,13 @@ harden(stringify);
4856
*/
4957
const parse = str =>
5058
unserialize(
51-
harden({
59+
// Note the use of `freeze` rather than `harden` below. This is because
60+
// `harden` now implies no-trapping, and we depend on proxies with these
61+
// almost-empty targets to remain trapping for the `get` trap
62+
// which can still be interesting even when the target is frozen.
63+
// `get`, if not naming an own property, are still general traps,
64+
// which we rely on.
65+
freeze({
5266
body: str,
5367
slots: badArray,
5468
}),

packages/marshal/test/marshal-far-function.test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ test('Data can contain far functions', t => {
6060
const arrow = Far('arrow', a => a + 1);
6161
t.is(passStyleOf(harden({ x: 8, foo: arrow })), 'copyRecord');
6262
const mightBeMethod = a => a + 1;
63-
t.throws(() => passStyleOf(freeze({ x: 8, foo: mightBeMethod })), {
63+
t.throws(() => passStyleOf(harden({ x: 8, foo: mightBeMethod })), {
6464
message: /Remotables with non-methods like "x" /,
6565
});
6666
});

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

+3-2
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ const {
1111
getOwnPropertyDescriptor,
1212
getPrototypeOf,
1313
hasOwnProperty: objectHasOwnProperty,
14-
isFrozen,
1514
prototype: objectPrototype,
15+
// @ts-expect-error TS does not yet have `isNoTrapping` on ObjectConstructor
16+
isNoTrapping,
1617
} = Object;
1718
const { apply } = Reflect;
1819
const { toStringTag: toStringTagSymbol } = Symbol;
@@ -165,7 +166,7 @@ const makeCheckTagRecord = checkProto => {
165166
(isObject(tagRecord) ||
166167
(!!check &&
167168
CX(check)`A non-object cannot be a tagRecord: ${tagRecord}`)) &&
168-
(isFrozen(tagRecord) ||
169+
(isNoTrapping(tagRecord) ||
169170
(!!check && CX(check)`A tagRecord must be frozen: ${tagRecord}`)) &&
170171
(!isArray(tagRecord) ||
171172
(!!check && CX(check)`An array cannot be a tagRecord: ${tagRecord}`)) &&

packages/pass-style/src/passStyleOf.js

+8-3
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,12 @@ 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+
// @ts-expect-error TS does not yet have `isNoTrapping` on ObjectConstructor
38+
isNoTrapping,
39+
} = Object;
3540

3641
/**
3742
* @param {PassStyleHelper[]} passStyleHelpers
@@ -143,7 +148,7 @@ const makePassStyleOf = passStyleHelpers => {
143148
if (inner === null) {
144149
return 'null';
145150
}
146-
if (!isFrozen(inner)) {
151+
if (!isNoTrapping(inner)) {
147152
assert.fail(
148153
// TypedArrays get special treatment in harden()
149154
// and a corresponding special error message here.
@@ -177,7 +182,7 @@ const makePassStyleOf = passStyleHelpers => {
177182
return 'remotable';
178183
}
179184
case 'function': {
180-
isFrozen(inner) ||
185+
isNoTrapping(inner) ||
181186
Fail`Cannot pass non-frozen objects like ${inner}. Use harden()`;
182187
typeof inner.then !== 'function' ||
183188
Fail`Cannot pass non-promise thenables`;

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 `isNoTrapping` on ObjectConstructor
30+
isNoTrapping,
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 (!isNoTrapping(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 `isNoTrapping` on ObjectConstructor
13+
isNoTrapping,
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+
isNoTrapping(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+
(isNoTrapping(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

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

16-
const { getPrototypeOf, defineProperty } = Object;
16+
const { getPrototypeOf, defineProperty, suppressTrapping } = Object;
1717
const { ownKeys } = Reflect;
1818

1919
test('passStyleOf basic success cases', t => {
@@ -200,7 +200,7 @@ test('passStyleOf testing remotables', t => {
200200

201201
const tagRecord2 = makeTagishRecord('Alleged: tagRecord not hardened');
202202
/** @type {any} UNTIL https://github.com/microsoft/TypeScript/issues/38385 */
203-
const farObj2 = Object.freeze({
203+
const farObj2 = suppressTrapping({
204204
__proto__: tagRecord2,
205205
});
206206
if (harden.isFake) {
@@ -212,11 +212,11 @@ test('passStyleOf testing remotables', t => {
212212
});
213213
}
214214

215-
const tagRecord3 = Object.freeze(
215+
const tagRecord3 = suppressTrapping(
216216
makeTagishRecord('Alleged: both manually frozen'),
217217
);
218218
/** @type {any} UNTIL https://github.com/microsoft/TypeScript/issues/38385 */
219-
const farObj3 = Object.freeze({
219+
const farObj3 = suppressTrapping({
220220
__proto__: tagRecord3,
221221
});
222222
t.is(passStyleOf(farObj3), 'remotable');
@@ -387,7 +387,7 @@ test('remotables - safety from the gibson042 attack', t => {
387387
},
388388
);
389389

390-
const makeInput = () => Object.freeze({ __proto__: mercurialProto });
390+
const makeInput = () => suppressTrapping({ __proto__: mercurialProto });
391391
const input1 = makeInput();
392392
const input2 = makeInput();
393393

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/no-trapping-shim": "^0.1.0"
8990
},
9091
"devDependencies": {
9192
"@endo/compartment-mapper": "workspace:^",

0 commit comments

Comments
 (0)