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(ses): option to fake harden unsafely #1528

Merged
merged 1 commit into from
Mar 30, 2023
Merged
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
9 changes: 6 additions & 3 deletions packages/captp/test/test-export-hook.js
Original file line number Diff line number Diff line change
@@ -66,8 +66,11 @@ test('exportHook', async t => {

// Trigger the hook to throw.
harden(exports);
await t.throwsAsync(() => E(bs).echo(Promise.resolve('never exported')), {
message: /.*object is not extensible/,
});
// @ts-ignore `isFake` purposely omitted from type
if (!harden.isFake) {
await t.throwsAsync(() => E(bs).echo(Promise.resolve('never exported')), {
message: /.*object is not extensible/,
});
}
t.deepEqual(exports, []);
});
12 changes: 9 additions & 3 deletions packages/check-bundle/test/test-check-bundle.js
Original file line number Diff line number Diff line change
@@ -100,9 +100,15 @@ test('bundle and check corrupt endo zip base64 package', async t => {

test('bundle and hash unfrozen object', async t => {
const bundle = {};
await t.throwsAsync(checkBundle(bundle, computeSha512, 'fixture/main.js'), {
message: `checkBundle cannot vouch for the ongoing integrity of an unfrozen object, got {}`,
});
await null;
// @ts-ignore `isFake` purposely omitted from type
if (harden.isFake) {
t.pass();
} else {
await t.throwsAsync(checkBundle(bundle, computeSha512, 'fixture/main.js'), {
message: `checkBundle cannot vouch for the ongoing integrity of an unfrozen object, got {}`,
});
}
});

test('bundle and hash bogus package', async t => {
23 changes: 13 additions & 10 deletions packages/far/test/test-marshal-far-function.js
Original file line number Diff line number Diff line change
@@ -22,16 +22,19 @@ test('Acceptable far functions', t => {
});

test('Unacceptable far functions', t => {
t.throws(
() =>
Far(
'alreadyFrozen',
freeze(a => a + 1),
),
{
message: /is already frozen/,
},
);
// @ts-ignore `isFake` purposely omitted from type
if (!harden.isFake) {
t.throws(
() =>
Far(
'alreadyFrozen',
freeze(a => a + 1),
),
{
message: /is already frozen/,
},
);
}
// eslint-disable-next-line prefer-arrow-callback -- under test
t.throws(() => Far('keywordFunc', function keyword() {}), {
message: /unexpected properties besides \.name and \.length/,
8 changes: 6 additions & 2 deletions packages/init/test/test-async_hooks.js
Original file line number Diff line number Diff line change
@@ -50,7 +50,9 @@ test('async_hooks Promise patch', async t => {

// Create a promise with symbols attached
const p3 = Promise.resolve();
t.is(Reflect.ownKeys(p3).length > 0, hasAsyncSymbols);
if (!harden.isFake) {
t.is(Reflect.ownKeys(p3).length > 0, hasAsyncSymbols);
}

return Promise.resolve().then(() => {
resolve(8);
@@ -62,7 +64,9 @@ test('async_hooks Promise patch', async t => {
// node versions will fail and generate a new one because of an own check
p1.then(() => {});

t.is(Reflect.ownKeys(ret).length > 0, hasAsyncSymbols);
if (!harden.isFake) {
t.is(Reflect.ownKeys(ret).length > 0, hasAsyncSymbols);
}

// testHooks.disable();

39 changes: 26 additions & 13 deletions packages/marshal/test/test-marshal-capdata.js
Original file line number Diff line number Diff line change
@@ -187,9 +187,13 @@ test('serialize unserialize round trip pairs', t => {
test('serialize static data', t => {
const m = makeTestMarshal();
const ser = val => m.serialize(val);
t.throws(() => ser([1, 2]), {
message: /Cannot pass non-frozen objects like/,
});

// @ts-ignore `isFake` purposely omitted from type
if (!harden.isFake) {
t.throws(() => ser([1, 2]), {
message: /Cannot pass non-frozen objects like/,
});
}
// -0 serialized as 0
t.deepEqual(ser(0), { body: '0', slots: [] });
t.deepEqual(ser(-0), { body: '0', slots: [] });
@@ -242,14 +246,20 @@ test('serialize errors', t => {
errExtra.foo = [];
freeze(errExtra);
t.assert(isFrozen(errExtra));
// @ts-ignore Check dynamic consequences of type violation
t.falsy(isFrozen(errExtra.foo));
// @ts-ignore `isFake` purposely omitted from type
if (!harden.isFake) {
// @ts-ignore Check dynamic consequences of type violation
t.falsy(isFrozen(errExtra.foo));
}
t.deepEqual(ser(errExtra), {
body: '{"@qclass":"error","errorId":"error:anon-marshal#10003","message":"has extra properties","name":"Error"}',
slots: [],
});
// @ts-ignore Check dynamic consequences of type violation
t.falsy(isFrozen(errExtra.foo));
// @ts-ignore `isFake` purposely omitted from type
if (!harden.isFake) {
// @ts-ignore Check dynamic consequences of type violation
t.falsy(isFrozen(errExtra.foo));
}

// Bad prototype and bad "message" property
const nonErrorProto1 = { __proto__: Error.prototype, name: 'included' };
@@ -322,12 +332,15 @@ test('records', t => {

// empty objects

// rejected because it is not hardened
t.throws(
() => ser({}),
{ message: /Cannot pass non-frozen objects/ },
'non-frozen data cannot be serialized',
);
// @ts-ignore `isFake` purposely omitted from type
if (!harden.isFake) {
// rejected because it is not hardened
t.throws(
() => ser({}),
{ message: /Cannot pass non-frozen objects/ },
'non-frozen data cannot be serialized',
);
}

// harden({})
t.deepEqual(ser(build()), emptyData);
23 changes: 13 additions & 10 deletions packages/marshal/test/test-marshal-far-function.js
Original file line number Diff line number Diff line change
@@ -21,16 +21,19 @@ test('Acceptable far functions', t => {
});

test('Unacceptable far functions', t => {
t.throws(
() =>
Far(
'alreadyFrozen',
freeze(a => a + 1),
),
{
message: /is already frozen/,
},
);
// @ts-ignore `isFake` purposely omitted from type
if (!harden.isFake) {
t.throws(
() =>
Far(
'alreadyFrozen',
freeze(a => a + 1),
),
{
message: /is already frozen/,
},
);
}
// eslint-disable-next-line prefer-arrow-callback -- under test
t.throws(() => Far('keywordFunc', function keyword() {}), {
message: /unexpected properties besides \.name and \.length/,
41 changes: 27 additions & 14 deletions packages/marshal/test/test-marshal-smallcaps.js
Original file line number Diff line number Diff line change
@@ -33,9 +33,13 @@ test('smallcaps serialize unserialize round trip half pairs', t => {
test('smallcaps serialize static data', t => {
const { serialize } = makeTestMarshal();
const ser = val => serialize(val);
t.throws(() => ser([1, 2]), {
message: /Cannot pass non-frozen objects like/,
});

// @ts-ignore `isFake` purposely omitted from type
if (!harden.isFake) {
t.throws(() => ser([1, 2]), {
message: /Cannot pass non-frozen objects like/,
});
}
// -0 serialized as 0
t.deepEqual(ser(0), { body: '#0', slots: [] });
t.deepEqual(ser(-0), { body: '#0', slots: [] });
@@ -102,14 +106,20 @@ test('smallcaps serialize errors', t => {
errExtra.foo = [];
freeze(errExtra);
t.assert(isFrozen(errExtra));
// @ts-ignore Check dynamic consequences of type violation
t.falsy(isFrozen(errExtra.foo));
// @ts-ignore `isFake` purposely omitted from type
if (!harden.isFake) {
// @ts-ignore Check dynamic consequences of type violation
t.falsy(isFrozen(errExtra.foo));
}
t.deepEqual(ser(errExtra), {
body: '#{"#error":"has extra properties","name":"Error"}',
slots: [],
});
// @ts-ignore Check dynamic consequences of type violation
t.falsy(isFrozen(errExtra.foo));
// @ts-ignore `isFake` purposely omitted from type
if (!harden.isFake) {
// @ts-ignore Check dynamic consequences of type violation
t.falsy(isFrozen(errExtra.foo));
}

// Bad prototype and bad "message" property
const nonErrorProto1 = { __proto__: Error.prototype, name: 'included' };
@@ -176,12 +186,15 @@ test('smallcaps records', t => {

// empty objects

// rejected because it is not hardened
t.throws(
() => ser({}),
{ message: /Cannot pass non-frozen objects/ },
'non-frozen data cannot be serialized',
);
// @ts-ignore `isFake` purposely omitted from type
if (!harden.isFake) {
// rejected because it is not hardened
t.throws(
() => ser({}),
{ message: /Cannot pass non-frozen objects/ },
'non-frozen data cannot be serialized',
);
}

// harden({})
t.deepEqual(ser(build()), emptyData);
@@ -336,7 +349,7 @@ test('smallcaps encoding examples', t => {
harden(nonPassableErr);
t.throws(() => passStyleOf(nonPassableErr), {
message:
'Passed Error has extra unpassed properties {"extraProperty":{"value":"something bad","writable":false,"enumerable":true,"configurable":false}}',
/Passed Error has extra unpassed properties {"extraProperty":{"value":"something bad","writable":.*,"enumerable":true,"configurable":.*}}/,
});
assertSer(
nonPassableErr,
27 changes: 15 additions & 12 deletions packages/marshal/test/test-marshal-stringify.js
Original file line number Diff line number Diff line change
@@ -27,18 +27,21 @@ test('marshal parse', t => {
});

test('marshal stringify errors', t => {
t.throws(() => stringify([]), {
message: /Cannot pass non-frozen objects like .*. Use harden()/,
});
t.throws(() => stringify({}), {
message: /Cannot pass non-frozen objects like .*. Use harden()/,
});
t.throws(() => stringify(harden(new Uint8Array(1))), {
message: 'Cannot pass mutable typed arrays like "[Uint8Array]".',
});
t.throws(() => stringify(harden(new Int16Array(1))), {
message: 'Cannot pass mutable typed arrays like "[Int16Array]".',
});
// @ts-ignore `isFake` purposely omitted from type
if (!harden.isFake) {
t.throws(() => stringify([]), {
message: /Cannot pass non-frozen objects like .*. Use harden()/,
});
t.throws(() => stringify({}), {
message: /Cannot pass non-frozen objects like .*. Use harden()/,
});
t.throws(() => stringify(harden(new Uint8Array(1))), {
message: 'Cannot pass mutable typed arrays like "[Uint8Array]".',
});
t.throws(() => stringify(harden(new Int16Array(1))), {
message: 'Cannot pass mutable typed arrays like "[Int16Array]".',
});
}

t.throws(() => stringify(harden(Promise.resolve(8))), {
message: /Marshal's stringify rejects presences and promises .*/,
11 changes: 9 additions & 2 deletions packages/pass-style/src/make-far.js
Original file line number Diff line number Diff line change
@@ -94,8 +94,15 @@ export const Remotable = (
Fail`Remotable ${remotable} is already marked as a ${q(
remotable[PASS_STYLE],
)}`;
// Ensure that the remotable isn't already frozen.
!isFrozen(remotable) || Fail`Remotable ${remotable} is already frozen`;
// `isFrozen` always returns true with a fake `harden`, but we want that case
// to succeed anyway. Faking `harden` is only correctness preserving
// if the code in question contains no bugs that the real `harden` would
// have caught.
// @ts-ignore `isFake` purposely not in the type
harden.isFake ||
// Ensure that the remotable isn't already frozen.
!isFrozen(remotable) ||
Fail`Remotable ${remotable} is already frozen`;
const remotableProto = makeRemotableProto(remotable, iface);

// Take a static copy of the enumerable own properties as data properties.
44 changes: 29 additions & 15 deletions packages/pass-style/test/test-passStyleOf.js
Original file line number Diff line number Diff line change
@@ -39,9 +39,13 @@ test('some passStyleOf rejections', t => {
message:
/Only registered symbols or well-known symbols are passable: "\[Symbol\(unique\)\]"/,
});
t.throws(() => passStyleOf({}), {
message: /Cannot pass non-frozen objects like {}. Use harden\(\)/,
});
if (harden.isFake) {
t.is(passStyleOf({}), 'copyRecord');
} else {
t.throws(() => passStyleOf({}), {
message: /Cannot pass non-frozen objects like {}. Use harden\(\)/,
});
}

const copyRecordBadAccessor = Object.defineProperty({}, 'foo', {
get: () => undefined,
@@ -180,10 +184,14 @@ test('passStyleOf testing remotables', t => {
const farObj2 = Object.freeze({
__proto__: tagRecord2,
});
t.throws(() => passStyleOf(farObj2), {
message:
/A tagRecord must be frozen: "\[Alleged: tagRecord not hardened\]"/,
});
if (harden.isFake) {
t.is(passStyleOf(farObj2), 'remotable');
} else {
t.throws(() => passStyleOf(farObj2), {
message:
/A tagRecord must be frozen: "\[Alleged: tagRecord not hardened\]"/,
});
}

const tagRecord3 = Object.freeze(
makeTagishRecord('Alleged: both manually frozen'),
@@ -357,14 +365,20 @@ test('remotables - safety from the gibson042 attack', t => {
const input1 = makeInput();
const input2 = makeInput();

// Further original attack text in comments. The attacks depends on
// `passStyleOf` succeeding on `input1`. Since `passStyleOf` now throws,
// that seems to stop the attack:
// console.log('# passStyleOf(input1)');
// console.log(passStyleOf(input1)); // => "remotable"
t.throws(() => passStyleOf(input1), {
message: 'A tagRecord must be frozen: "[undefined: undefined]"',
});
if (harden.isFake) {
t.throws(() => passStyleOf(input1), {
message: /^Expected "remotable", not "error"/,
});
} else {
// Further original attack text in comments. The attacks depends on
// `passStyleOf` succeeding on `input1`. Since `passStyleOf` now throws,
// that seems to stop the attack:
// console.log('# passStyleOf(input1)');
// console.log(passStyleOf(input1)); // => "remotable"
t.throws(() => passStyleOf(input1), {
message: 'A tagRecord must be frozen: "[undefined: undefined]"',
});
}

// same because of passStyleMemo WeakMap
// console.log(`# passStyleOf(input1) again (cached "Purely for performance")`);
Loading