Skip to content

Commit 0c13733

Browse files
committed
feat(ses,pass-style): use non-trapping integrity trait ponyfill for safety
1 parent 4c095c0 commit 0c13733

12 files changed

+74
-32
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/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"@endo/errors": "workspace:^",
4646
"@endo/eventual-send": "workspace:^",
4747
"@endo/nat": "workspace:^",
48+
"@endo/non-trapping-shim": "^0.1.0",
4849
"@endo/pass-style": "workspace:^",
4950
"@endo/promise-kit": "workspace:^"
5051
},

packages/marshal/src/encodeToCapData.js

+5-4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
// encodes to CapData, a JSON-representable data structure, and leaves it to
77
// the caller (`marshal.js`) to stringify it.
88

9+
import { X, Fail, q } from '@endo/errors';
10+
import { extraObjectMethods } from '@endo/non-trapping-shim';
911
import {
1012
passStyleOf,
1113
isErrorLike,
@@ -17,7 +19,6 @@ import {
1719
nameForPassableSymbol,
1820
passableSymbolForName,
1921
} from '@endo/pass-style';
20-
import { X, Fail, q } from '@endo/errors';
2122

2223
/** @import {Passable, RemotableObject} from '@endo/pass-style' */
2324
/** @import {Encoding, EncodingUnion} from './types.js' */
@@ -30,8 +31,8 @@ const {
3031
is,
3132
entries,
3233
fromEntries,
33-
freeze,
3434
} = Object;
35+
const { suppressTrapping } = extraObjectMethods;
3536

3637
/**
3738
* Special property name that indicates an encoding that needs special
@@ -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/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"@endo/env-options": "workspace:^",
3838
"@endo/errors": "workspace:^",
3939
"@endo/eventual-send": "workspace:^",
40+
"@endo/non-trapping-shim": "^0.1.0",
4041
"@endo/promise-kit": "workspace:^"
4142
},
4243
"devDependencies": {

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

+6-1
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,20 @@
44
/** @import {PassStyle} from './types.js' */
55

66
import { X, q } from '@endo/errors';
7+
import { extraObjectMethods } from '@endo/non-trapping-shim';
78

89
const { isArray } = Array;
910
const { prototype: functionPrototype } = Function;
1011
const {
1112
getOwnPropertyDescriptor,
1213
getPrototypeOf,
1314
hasOwnProperty: objectHasOwnProperty,
14-
isFrozen,
1515
prototype: objectPrototype,
16+
isFrozen,
1617
} = Object;
1718
const { apply } = Reflect;
1819
const { toStringTag: toStringTagSymbol } = Symbol;
20+
const { isNonTrapping } = extraObjectMethods;
1921

2022
const typedArrayPrototype = getPrototypeOf(Uint8Array.prototype);
2123
const typedArrayToStringTagDesc = getOwnPropertyDescriptor(
@@ -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

+21-12
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44

55
import { isPromise } from '@endo/promise-kit';
66
import { X, Fail, q, annotateError, makeError } from '@endo/errors';
7-
import { isObject, isTypedArray, PASS_STYLE } from './passStyle-helpers.js';
7+
import { extraObjectMethods } from '@endo/non-trapping-shim';
88

9+
import { isObject, isTypedArray, PASS_STYLE } from './passStyle-helpers.js';
910
import { CopyArrayHelper } from './copyArray.js';
1011
import { CopyRecordHelper } from './copyRecord.js';
1112
import { TaggedHelper } from './tagged.js';
@@ -31,7 +32,8 @@ import { assertPassableString } from './string.js';
3132
/** @typedef {Exclude<PassStyle, PrimitiveStyle | "promise">} HelperPassStyle */
3233

3334
const { ownKeys } = Reflect;
34-
const { isFrozen, getOwnPropertyDescriptors, values } = Object;
35+
const { getOwnPropertyDescriptors, values, isFrozen } = Object;
36+
const { isNonTrapping } = extraObjectMethods;
3537

3638
/**
3739
* @param {PassStyleHelper[]} passStyleHelpers
@@ -143,14 +145,17 @@ const makePassStyleOf = passStyleHelpers => {
143145
if (inner === null) {
144146
return 'null';
145147
}
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-
);
148+
if (!isNonTrapping(inner)) {
149+
if (!isFrozen(inner)) {
150+
throw assert.fail(
151+
// TypedArrays get special treatment in harden()
152+
// and a corresponding special error message here.
153+
isTypedArray(inner)
154+
? X`Cannot pass mutable typed arrays like ${inner}.`
155+
: X`Cannot pass non-frozen objects like ${inner}. Use harden()`,
156+
);
157+
}
158+
throw Fail`Cannot pass non-trapping objects like ${inner}`;
154159
}
155160
if (isPromise(inner)) {
156161
assertSafePromise(inner);
@@ -177,8 +182,12 @@ const makePassStyleOf = passStyleHelpers => {
177182
return 'remotable';
178183
}
179184
case 'function': {
180-
isFrozen(inner) ||
181-
Fail`Cannot pass non-frozen objects like ${inner}. Use harden()`;
185+
if (!isNonTrapping(inner)) {
186+
if (!isFrozen(inner)) {
187+
throw Fail`Cannot pass non-frozen objects like ${inner}. Use harden()`;
188+
}
189+
throw Fail`Cannot pass trapping objects like ${inner}. Use harden()`;
190+
}
182191
typeof inner.then !== 'function' ||
183192
Fail`Cannot pass non-promise thenables`;
184193
remotableHelper.assertValid(inner, passStyleOfRecur);

packages/pass-style/src/remotable.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/// <reference types="ses"/>
22

33
import { Fail, q } from '@endo/errors';
4+
import { extraObjectMethods } from '@endo/non-trapping-shim';
45
import {
56
assertChecker,
67
canBeMethod,
@@ -24,10 +25,10 @@ const { ownKeys } = Reflect;
2425
const { isArray } = Array;
2526
const {
2627
getPrototypeOf,
27-
isFrozen,
2828
prototype: objectPrototype,
2929
getOwnPropertyDescriptors,
3030
} = Object;
31+
const { isNonTrapping } = extraObjectMethods;
3132

3233
/**
3334
* @param {InterfaceSpec} iface
@@ -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

+5-3
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22

33
import { isPromise } from '@endo/promise-kit';
44
import { q } from '@endo/errors';
5+
import { extraObjectMethods } from '@endo/non-trapping-shim';
56
import { assertChecker, hasOwnPropertyOf, CX } from './passStyle-helpers.js';
67

78
/** @import {Checker} from './types.js' */
89

9-
const { isFrozen, getPrototypeOf, getOwnPropertyDescriptor } = Object;
10+
const { getPrototypeOf, getOwnPropertyDescriptor } = Object;
1011
const { ownKeys } = Reflect;
1112
const { toStringTag } = Symbol;
13+
const { isNonTrapping } = extraObjectMethods;
1214

1315
/**
1416
* @param {Promise} pr The value to examine
@@ -88,7 +90,7 @@ const checkPromiseOwnKeys = (pr, check) => {
8890
if (
8991
typeof val === 'object' &&
9092
val !== null &&
91-
isFrozen(val) &&
93+
isNonTrapping(val) &&
9294
getPrototypeOf(val) === Object.prototype
9395
) {
9496
const subKeys = ownKeys(val);
@@ -132,7 +134,7 @@ const checkPromiseOwnKeys = (pr, check) => {
132134
*/
133135
const checkSafePromise = (pr, check) => {
134136
return (
135-
(isFrozen(pr) || CX(check)`${pr} - Must be frozen`) &&
137+
(isNonTrapping(pr) || CX(check)`${pr} - Must be frozen`) &&
136138
(isPromise(pr) || CX(check)`${pr} - Must be a promise`) &&
137139
(getPrototypeOf(pr) === Promise.prototype ||
138140
CX(check)`${pr} - Must inherit from Promise.prototype: ${q(

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

+5-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import test from '@endo/ses-ava/prepare-endo.js';
33

44
import { q } from '@endo/errors';
5+
import { extraObjectMethods } from '@endo/non-trapping-shim';
56

67
import { passStyleOf } from '../src/passStyleOf.js';
78
import { Far, ToFarFunction } from '../src/make-far.js';
@@ -13,8 +14,9 @@ const harden = /** @type {import('ses').Harden & { isFake?: boolean }} */ (
1314
global.harden
1415
);
1516

16-
const { getPrototypeOf, defineProperty, freeze } = Object;
17+
const { getPrototypeOf, defineProperty } = Object;
1718
const { ownKeys } = Reflect;
19+
const { suppressTrapping } = extraObjectMethods;
1820

1921
test('passStyleOf basic success cases', t => {
2022
// Test in same order as `passStyleOf` for easier maintenance.
@@ -208,7 +210,7 @@ test('passStyleOf testing remotables', t => {
208210
*
209211
* @type {any} UNTIL https://github.com/microsoft/TypeScript/issues/38385
210212
*/
211-
const farObj2 = freeze({ __proto__: tagRecord2 });
213+
const farObj2 = suppressTrapping({ __proto__: tagRecord2 });
212214
if (harden.isFake) {
213215
t.is(passStyleOf(farObj2), 'remotable');
214216
} else {
@@ -386,7 +388,7 @@ test('remotables - safety from the gibson042 attack', t => {
386388
* explicitly make this non-trapping, which we cannot yet express.
387389
* @see https://github.com/endojs/endo/blob/master/packages/ses/docs/preparing-for-stabilize.md
388390
*/
389-
const makeInput = () => freeze({ __proto__: mercurialProto });
391+
const makeInput = () => suppressTrapping({ __proto__: mercurialProto });
390392
const input1 = makeInput();
391393
const input2 = makeInput();
392394

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/make-hardener.js

+18-2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
// @ts-check
2323

24+
import { extraObjectMethods } from '@endo/non-trapping-shim';
2425
import {
2526
Set,
2627
String,
@@ -30,7 +31,6 @@ import {
3031
apply,
3132
arrayForEach,
3233
defineProperty,
33-
freeze,
3434
getOwnPropertyDescriptor,
3535
getOwnPropertyDescriptors,
3636
getPrototypeOf,
@@ -49,9 +49,12 @@ import {
4949
FERAL_STACK_GETTER,
5050
FERAL_STACK_SETTER,
5151
isError,
52+
isFrozen,
5253
} from './commons.js';
5354
import { assert } from './error/assert.js';
5455

56+
const { suppressTrapping } = extraObjectMethods;
57+
5558
/**
5659
* @import {Harden} from '../types.js'
5760
*/
@@ -128,6 +131,10 @@ const freezeTypedArray = array => {
128131
* @returns {Harden}
129132
*/
130133
export const makeHardener = () => {
134+
// TODO Get the native hardener to suppressTrapping at each step,
135+
// rather than freeze. Until then, we cannot use it, which is *expensive*!
136+
// TODO Comment out the following to skip the native hardener.
137+
//
131138
// Use a native hardener if possible.
132139
if (typeof globalThis.harden === 'function') {
133140
const safeHarden = globalThis.harden;
@@ -182,8 +189,17 @@ export const makeHardener = () => {
182189
// Also throws if the object is an ArrayBuffer or any TypedArray.
183190
if (isTypedArray(obj)) {
184191
freezeTypedArray(obj);
192+
if (isFrozen(obj)) {
193+
// After `freezeTypedArray`, the typed array might actually be
194+
// frozen if
195+
// - it has no indexed properties
196+
// - it is backed by an Immutable ArrayBuffer as proposed.
197+
// In either case, this makes it a candidate to be made
198+
// non-trapping.
199+
suppressTrapping(obj);
200+
}
185201
} else {
186-
freeze(obj);
202+
suppressTrapping(obj);
187203
}
188204

189205
// we rely upon certain commitments of Object.freeze and proxies here

yarn.lock

+4-1
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,7 @@ __metadata:
607607
"@endo/init": "workspace:^"
608608
"@endo/lockdown": "workspace:^"
609609
"@endo/nat": "workspace:^"
610+
"@endo/non-trapping-shim": "npm:^0.1.0"
610611
"@endo/pass-style": "workspace:^"
611612
"@endo/promise-kit": "workspace:^"
612613
"@endo/ses-ava": "workspace:^"
@@ -702,7 +703,7 @@ __metadata:
702703
languageName: unknown
703704
linkType: soft
704705

705-
"@endo/non-trapping-shim@workspace:packages/non-trapping-shim":
706+
"@endo/non-trapping-shim@npm:^0.1.0, @endo/non-trapping-shim@workspace:packages/non-trapping-shim":
706707
version: 0.0.0-use.local
707708
resolution: "@endo/non-trapping-shim@workspace:packages/non-trapping-shim"
708709
dependencies:
@@ -721,6 +722,7 @@ __metadata:
721722
"@endo/errors": "workspace:^"
722723
"@endo/eventual-send": "workspace:^"
723724
"@endo/init": "workspace:^"
725+
"@endo/non-trapping-shim": "npm:^0.1.0"
724726
"@endo/promise-kit": "workspace:^"
725727
"@endo/ses-ava": "workspace:^"
726728
"@fast-check/ava": "npm:^1.1.5"
@@ -8960,6 +8962,7 @@ __metadata:
89608962
"@endo/compartment-mapper": "workspace:^"
89618963
"@endo/env-options": "workspace:^"
89628964
"@endo/module-source": "workspace:^"
8965+
"@endo/non-trapping-shim": "npm:^0.1.0"
89638966
"@endo/test262-runner": "workspace:^"
89648967
ava: "npm:^6.1.3"
89658968
babel-eslint: "npm:^10.1.0"

0 commit comments

Comments
 (0)