Skip to content

Commit

Permalink
feat(ses): Tame ModuleSource (#2469)
Browse files Browse the repository at this point in the history
Refs: #2463, #2252

## Description

In #2463 we introduced ModuleSource to the SES permits, but this
interacted catastrophically with the native XS ModuleSource. We reverted
this change #2468 to unbreak Agoric SDK integration. This change
reintroduces the ModuleSource permits, such that they are compatible
with both XS and the `@endo/module-source/shim.js`, which anticipates
the introduction of an AbstractModuleSource base class. Because SES can
more gracefully tolerate the absence of an permitted [[Proto]] than the
presence of a non-permitted [[Proto]], this adjusts the shim to ensure
that the AbstractModuleSource shape exists as a side-effect of
repairs/taming, before permits are applied.

### Security Considerations

Increase in memory safety exposure in native code implementation of ModuleSource where applicable.

### Scaling Considerations

None.

### Documentation Considerations

This change reintroduces a note in NEWS.md for the next release.

### Testing Considerations

The prior regression went unnoticed because we did not yet have CI for
XS #2465. With this change, `yarn test:xs` in SES validates the shim on
XS. We also test `@endo/module-source/shim.js` in
`ses/test/module-source.test.js` on Node.js.

### Compatibility Considerations

This change is designed to tolerate either path forward for the
language, whether or not it accepts an AbstractModuleSource base class.

### Upgrade Considerations

None.
  • Loading branch information
kriskowal authored Sep 26, 2024
2 parents 392e9b2 + 4702335 commit c1b8b03
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 2 deletions.
2 changes: 2 additions & 0 deletions packages/ses/NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ User-visible changes in `ses`:
- Node 18, Node 20, and all browsers have `structuredClone`
- Node <= 16 have neither, but are also no longer supported by Endo.
- Now exports separate layer for console shim: `ses/console-shim.js`.
- Adds permits for `ModuleSource`, either the native implementation or from
`@endo/module-source/shim.js`.

# v1.8.0 (2024-08-27)

Expand Down
10 changes: 8 additions & 2 deletions packages/ses/src/error/console.js
Original file line number Diff line number Diff line change
Expand Up @@ -368,8 +368,10 @@ export const makeCausalConsole = (baseConsole, loggedErrorHandler) => {
const levelMethod = (...logArgs) => {
const subErrors = [];
const argTags = extractErrorArgs(logArgs, subErrors);
// eslint-disable-next-line @endo/no-polymorphic-call
baseConsole[level](...argTags);
if (baseConsole[level]) {
// eslint-disable-next-line @endo/no-polymorphic-call
baseConsole[level](...argTags);
}
// @ts-expect-error ConsoleProp vs LogSeverity mismatch
logSubErrors(level, subErrors);
};
Expand Down Expand Up @@ -476,12 +478,16 @@ export const defineCausalConsoleFromLogger = loggedErrorHandler => {
// the host console will separate them with additional spaces.
arrayPush(indents, ' ');
});
} else {
baseConsole[name] = () => {};
}
}
if (baseConsole.groupEnd) {
baseConsole.groupEnd = makeNamed('groupEnd', (...args) => {
arrayPop(indents);
});
} else {
baseConsole.groupEnd = () => {};
}
harden(baseConsole);
const causalConsole = makeCausalConsole(
Expand Down
2 changes: 2 additions & 0 deletions packages/ses/src/lockdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import { makeSafeEvaluator } from './make-safe-evaluator.js';
import { initialGlobalPropertyNames } from './permits.js';
import { tameFunctionToString } from './tame-function-tostring.js';
import { tameDomains } from './tame-domains.js';
import { tameModuleSource } from './tame-module-source.js';

import { tameConsole } from './error/tame-console.js';
import tameErrorConstructor from './error/tame-error-constructor.js';
Expand Down Expand Up @@ -286,6 +287,7 @@ export const repairIntrinsics = (options = {}) => {
addIntrinsics(tameRegExpConstructor(regExpTaming));
addIntrinsics(tameSymbolConstructor());
addIntrinsics(shimArrayBufferTransfer());
addIntrinsics(tameModuleSource());

addIntrinsics(getAnonymousIntrinsics());

Expand Down
30 changes: 30 additions & 0 deletions packages/ses/src/permits.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,12 @@ export const universalPropertyNames = {

// ESNext

// https://github.com/tc39/proposal-source-phase-imports?tab=readme-ov-file#js-module-source
ModuleSource: 'ModuleSource',

lockdown: 'lockdown',
harden: 'harden',

HandledPromise: 'HandledPromise', // TODO: Until Promise.delegate (see below).
};

Expand Down Expand Up @@ -1505,6 +1509,32 @@ export const permitted = {
resolve: fn,
},

// https://github.com/tc39/proposal-source-phase-imports?tab=readme-ov-file#js-module-source

ModuleSource: {
'[[Proto]]': '%AbstractModuleSource%',
prototype: '%ModuleSourcePrototype%',
},

'%ModuleSourcePrototype%': {
'[[Proto]]': '%AbstractModuleSourcePrototype%',
constructor: 'ModuleSource',
'@@toStringTag': 'string',
// https://github.com/tc39/proposal-compartments
bindings: getter,
needsImport: getter,
needsImportMeta: getter,
},

'%AbstractModuleSource%': {
'[[Proto]]': '%FunctionPrototype%',
prototype: '%AbstractModuleSourcePrototype%',
},

'%AbstractModuleSourcePrototype%': {
constructor: '%AbstractModuleSource%',
},

Promise: {
// Properties of the Promise Constructor
'[[Proto]]': '%FunctionPrototype%',
Expand Down
51 changes: 51 additions & 0 deletions packages/ses/src/tame-module-source.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import {
functionPrototype,
getPrototypeOf,
globalThis,
objectPrototype,
setPrototypeOf,
} from './commons.js';

export const tameModuleSource = () => {
const newIntrinsics = {};

const ModuleSource = globalThis.ModuleSource;
if (ModuleSource !== undefined) {
newIntrinsics.ModuleSource = ModuleSource;

// We introduce ModuleSource.[[Proto]] === AbstractModuleSource
// and ModuleSource.prototype.[[Proto]] === AbstractModuleSource.prototype
// if that layer is absent because the permitting system can more
// gracefully tolerate the absence of an expected prototype than the
// presence of an unexpected prototype,.
function AbstractModuleSource() {
// no-op safe to super()
}

const ModuleSourceProto = getPrototypeOf(ModuleSource);
if (ModuleSourceProto === functionPrototype) {
setPrototypeOf(ModuleSource, AbstractModuleSource);
newIntrinsics['%AbstractModuleSource%'] = AbstractModuleSource;
newIntrinsics['%AbstractModuleSourcePrototype%'] =
AbstractModuleSource.prototype;
} else {
newIntrinsics['%AbstractModuleSource%'] = ModuleSourceProto;
newIntrinsics['%AbstractModuleSourcePrototype%'] =
ModuleSourceProto.prototype;
}

const ModuleSourcePrototype = ModuleSource.prototype;
if (ModuleSourcePrototype !== undefined) {
newIntrinsics['%ModuleSourcePrototype%'] = ModuleSourcePrototype;

// ModuleSource.prototype.__proto__ should be the
// AbstractModuleSource.prototype.
const ModuleSourcePrototypeProto = getPrototypeOf(ModuleSourcePrototype);
if (ModuleSourcePrototypeProto === objectPrototype) {
setPrototypeOf(ModuleSource.prototype, AbstractModuleSource.prototype);
}
}
}

return newIntrinsics;
};
17 changes: 17 additions & 0 deletions packages/ses/test/module-source.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@ import '@endo/module-source/shim.js';

lockdown();

test('module source property/prototype graph and hardening', t => {
const AbstractModuleSource = Object.getPrototypeOf(ModuleSource);
t.is(
Object.getPrototypeOf(ModuleSource.prototype),
AbstractModuleSource.prototype,
);

t.truthy(Object.isFrozen(ModuleSource));
t.truthy(Object.isFrozen(AbstractModuleSource));
t.truthy(Object.isFrozen(ModuleSource.prototype));
t.truthy(Object.isFrozen(AbstractModuleSource.prototype));
});

test('module source constructor', t => {
const msr = new ModuleSource(`
import foo from 'import-default-export-from-me.js';
Expand Down Expand Up @@ -44,3 +57,7 @@ test('module source constructor', t => {
'ModuleSource imports should be frozen',
);
});

test('ModuleSource is a shared intrinsic', t => {
t.truthy(ModuleSource === new Compartment().globalThis.ModuleSource);
});

0 comments on commit c1b8b03

Please sign in to comment.