Skip to content

Commit 5bc9589

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

21 files changed

+148
-49
lines changed

packages/captp/src/captp.js

Lines changed: 3 additions & 3 deletions
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

Lines changed: 7 additions & 2 deletions
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

Lines changed: 20 additions & 7 deletions
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

Lines changed: 2 additions & 0 deletions
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

Lines changed: 10 additions & 3 deletions
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 4 additions & 3 deletions
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

Lines changed: 16 additions & 2 deletions
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 3 additions & 2 deletions
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}`)) &&

0 commit comments

Comments
 (0)