From 3c2d9247277491f47c83de0fee9511b6e8d9bff3 Mon Sep 17 00:00:00 2001 From: Kris Kowal Date: Wed, 13 Dec 2023 17:50:54 -0800 Subject: [PATCH 1/6] feat(daemon): Undeniable host INFO tool --- packages/daemon/src/daemon.js | 129 +++++++++++++++++++++++++++++++ packages/daemon/src/host.js | 3 + packages/daemon/src/pet-store.js | 2 +- 3 files changed, 133 insertions(+), 1 deletion(-) diff --git a/packages/daemon/src/daemon.js b/packages/daemon/src/daemon.js index c6442959a8..d4c0a90bb4 100644 --- a/packages/daemon/src/daemon.js +++ b/packages/daemon/src/daemon.js @@ -37,6 +37,17 @@ const delay = async (ms, cancelled) => { }); }; +const makeInfo = (type, number, record) => + Far(`Inspector (${type} ${number})`, { + lookup: async petName => { + if (!Object.hasOwn(record, petName)) { + return undefined; + } + return record[petName]; + }, + list: () => Object.keys(record), + }); + /** * @param {import('./types.js').DaemonicPowers} powers * @param {Promise} webletPortP @@ -371,14 +382,21 @@ const makeEndoBootstrap = ( assertPetName, ); return { external, internal: undefined }; + } else if (formulaIdentifier === 'pet-inspector') { + // Behold, unavoidable forward-reference: + // eslint-disable-next-line no-use-before-define + const external = makeIdentifiedInspector('pet-store'); + return { external, internal: undefined }; } else if (formulaIdentifier === 'host') { const storeFormulaIdentifier = 'pet-store'; + const infoFormulaIdentifier = 'pet-inspector'; const workerFormulaIdentifier = `worker-id512:${zero512}`; // Behold, recursion: // eslint-disable-next-line no-use-before-define return makeIdentifiedHost( formulaIdentifier, storeFormulaIdentifier, + infoFormulaIdentifier, workerFormulaIdentifier, terminator, ); @@ -407,6 +425,13 @@ const makeEndoBootstrap = ( return { external, internal: undefined }; } else if (formulaType === 'worker-id512') { return makeIdentifiedWorkerController(formulaNumber, terminator); + } else if (formulaType === 'pet-inspector-id512') { + // Behold, unavoidable forward-reference: + // eslint-disable-next-line no-use-before-define + const external = makeIdentifiedInspector( + `pet-store-id512:${formulaNumber}`, + ); + return { external, internal: undefined }; } else if (formulaType === 'pet-store-id512') { const external = petStorePowers.makeIdentifiedPetStore( formulaNumber, @@ -415,12 +440,14 @@ const makeEndoBootstrap = ( return { external, internal: undefined }; } else if (formulaType === 'host-id512') { const storeFormulaIdentifier = `pet-store-id512:${formulaNumber}`; + const infoFormulaIdentifier = `pet-inspector-id512:${formulaNumber}`; const workerFormulaIdentifier = `worker-id512:${formulaNumber}`; // Behold, recursion: // eslint-disable-next-line no-use-before-define return makeIdentifiedHost( formulaIdentifier, storeFormulaIdentifier, + infoFormulaIdentifier, workerFormulaIdentifier, terminator, ); @@ -583,6 +610,108 @@ const makeEndoBootstrap = ( makeMailbox, }); + const makeIdentifiedInspector = async petStoreFormulaIdentifier => { + const petStore = await provideValueForFormulaIdentifier( + petStoreFormulaIdentifier, + ); + + /** @param {string} petName */ + const lookup = async petName => { + const formulaIdentifier = petStore.lookup(petName); + if (formulaIdentifier === undefined) { + throw new Error(`Unknown pet name ${petName}`); + } + const delimiterIndex = formulaIdentifier.indexOf(':'); + // eslint-disable-next-line @endo/restrict-comparison-operands + if (delimiterIndex < 0) { + return undefined; + } + const prefix = formulaIdentifier.slice(0, delimiterIndex); + const formulaNumber = formulaIdentifier.slice(delimiterIndex + 1); + if ( + ![ + 'eval-id512', + 'import-unsafe-id512', + 'import-bundle-id512', + 'guest-id512', + 'web-bundle', + ].includes(prefix) + ) { + return makeInfo(prefix, formulaNumber, harden({})); + } + const formula = await persistencePowers.readFormula( + prefix, + formulaNumber, + ); + if (formula.type === 'eval') { + return makeInfo( + formula.type, + formulaNumber, + harden({ + SOURCE: formula.source, + WORKER: provideValueForFormulaIdentifier(formula.worker), + ENDOWMENTS: Object.fromEntries( + formula.names.map((name, index) => { + return [ + name, + provideValueForFormulaIdentifier(formula.values[index]), + ]; + }), + ), + }), + ); + } else if (formula.type === 'import-unsafe') { + return makeInfo( + formula.type, + formulaNumber, + harden({ + SPECIFIER: formula.type, + WORKER: provideValueForFormulaIdentifier(formula.worker), + POWERS: provideValueForFormulaIdentifier(formula.powers), + }), + ); + } else if (formula.type === 'import-bundle') { + return makeInfo( + formula.type, + formulaNumber, + harden({ + WORKER: provideValueForFormulaIdentifier(formula.worker), + BUNDLE: provideValueForFormulaIdentifier(formula.bundle), + POWERS: provideValueForFormulaIdentifier(formula.powers), + }), + ); + } else if (formula.type === 'guest') { + return makeInfo( + formula.type, + formulaNumber, + harden({ + HOST: provideValueForFormulaIdentifier(formula.host), + }), + ); + } else if (formula.type === 'web-bundle') { + return makeInfo( + formula.type, + formulaNumber, + harden({ + BUNDLE: provideValueForFormulaIdentifier(formula.bundle), + POWERS: provideValueForFormulaIdentifier(formula.powers), + }), + ); + } + // @ts-expect-error this should never occur + return makeInfo(formula.type, formulaNumber, harden({})); + }; + + const list = () => petStore.list(); + + const info = Far('Endo info facet', { + lookup, + list, + }); + + return info; + }; + const endoBootstrap = Far('Endo private facet', { // TODO for user named diff --git a/packages/daemon/src/host.js b/packages/daemon/src/host.js index 2fe576495b..ba212e53d8 100644 --- a/packages/daemon/src/host.js +++ b/packages/daemon/src/host.js @@ -18,12 +18,14 @@ export const makeHostMaker = ({ /** * @param {string} hostFormulaIdentifier * @param {string} storeFormulaIdentifier + * @param {string} infoFormulaIdentifier * @param {string} mainWorkerFormulaIdentifier * @param {import('./types.js').Terminator} terminator */ const makeIdentifiedHost = async ( hostFormulaIdentifier, storeFormulaIdentifier, + infoFormulaIdentifier, mainWorkerFormulaIdentifier, terminator, ) => { @@ -58,6 +60,7 @@ export const makeHostMaker = ({ selfFormulaIdentifier: hostFormulaIdentifier, specialNames: { SELF: hostFormulaIdentifier, + INFO: infoFormulaIdentifier, NONE: 'least-authority', ENDO: 'endo', }, diff --git a/packages/daemon/src/pet-store.js b/packages/daemon/src/pet-store.js index c936722ced..c4dd30d400 100644 --- a/packages/daemon/src/pet-store.js +++ b/packages/daemon/src/pet-store.js @@ -9,7 +9,7 @@ const { quote: q } = assert; const validIdPattern = /^[0-9a-f]{128}$/; const validFormulaPattern = - /^(?:host|pet-store|(?:readable-blob-sha512|worker-id512|pet-store-id512|eval-id512|make-unconfined-id512|make-bundle-id512|host-id512|guest-id512):[0-9a-f]{128}|web-bundle:[0-9a-f]{32})$/; + /^(?:host|pet-store|pet-inspector|(?:readable-blob-sha512|worker-id512|pet-store-id512|eval-id512|make-unconfined-id512|make-bundle-id512|host-id512|guest-id512):[0-9a-f]{128}|web-bundle:[0-9a-f]{32})$/; /** * @param {import('./types.js').FilePowers} filePowers From c23dc27d2679d7632552d2bf2116782ee8c34a14 Mon Sep 17 00:00:00 2001 From: Erik Marks Date: Mon, 22 Jan 2024 22:29:56 -0800 Subject: [PATCH 2/6] refactor(daemon): Use parseFormulaIdentifier in makeIdentifiedInspector --- packages/daemon/src/daemon.js | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/packages/daemon/src/daemon.js b/packages/daemon/src/daemon.js index d4c0a90bb4..152d36b70e 100644 --- a/packages/daemon/src/daemon.js +++ b/packages/daemon/src/daemon.js @@ -621,13 +621,8 @@ const makeEndoBootstrap = ( if (formulaIdentifier === undefined) { throw new Error(`Unknown pet name ${petName}`); } - const delimiterIndex = formulaIdentifier.indexOf(':'); - // eslint-disable-next-line @endo/restrict-comparison-operands - if (delimiterIndex < 0) { - return undefined; - } - const prefix = formulaIdentifier.slice(0, delimiterIndex); - const formulaNumber = formulaIdentifier.slice(delimiterIndex + 1); + const { type: formulaType, number: formulaNumber } = + parseFormulaIdentifier(formulaIdentifier); if ( ![ 'eval-id512', @@ -635,12 +630,12 @@ const makeEndoBootstrap = ( 'import-bundle-id512', 'guest-id512', 'web-bundle', - ].includes(prefix) + ].includes(formulaType) ) { - return makeInfo(prefix, formulaNumber, harden({})); + return makeInfo(formulaType, formulaNumber, harden({})); } const formula = await persistencePowers.readFormula( - prefix, + formulaType, formulaNumber, ); if (formula.type === 'eval') { From d0eaab150d2accb3be7ef1ad21a6e48c49dd5ee0 Mon Sep 17 00:00:00 2001 From: Erik Marks Date: Wed, 24 Jan 2024 20:36:30 -0800 Subject: [PATCH 3/6] fix(daemon): Support pet-inspector in parseFormulaIdentifier --- packages/daemon/src/formula-identifier.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/daemon/src/formula-identifier.js b/packages/daemon/src/formula-identifier.js index 8e301a0a50..b37e4a59a2 100644 --- a/packages/daemon/src/formula-identifier.js +++ b/packages/daemon/src/formula-identifier.js @@ -1,10 +1,11 @@ const { quote: q } = assert; const numberlessFormulasIdentifiers = new Set([ - 'pet-store', - 'host', 'endo', + 'host', 'least-authority', + 'pet-inspector', + 'pet-store', 'web-page-js', ]); From e7898f05c84e3717312dc48f86716a0c51bd303d Mon Sep 17 00:00:00 2001 From: Erik Marks Date: Mon, 29 Jan 2024 21:55:02 -0800 Subject: [PATCH 4/6] feat(daemon): Add inspector types - Updates INFO-related internals to refer to "inspector" instead of "info". - Adds types for different "known" inspectors (e.g. for `eval` formulas). - Lower-cases known inspector special names (e.g. `bundle`). --- packages/daemon/src/daemon.js | 90 +++++++++++++++++++++------------- packages/daemon/src/host.js | 6 +-- packages/daemon/src/types.d.ts | 15 ++++++ 3 files changed, 74 insertions(+), 37 deletions(-) diff --git a/packages/daemon/src/daemon.js b/packages/daemon/src/daemon.js index 152d36b70e..cb63a4ba62 100644 --- a/packages/daemon/src/daemon.js +++ b/packages/daemon/src/daemon.js @@ -37,7 +37,15 @@ const delay = async (ms, cancelled) => { }); }; -const makeInfo = (type, number, record) => +/** + * Creates an inspector object for a formula. + * + * @param {string} type - The formula type. + * @param {string} number - The formula number. + * @param {Record} record - A mapping from special names to formula values. + * @returns {import('./types.js').EndoInspector} The inspector for the given formula. + */ +const makeInspector = (type, number, record) => Far(`Inspector (${type} ${number})`, { lookup: async petName => { if (!Object.hasOwn(record, petName)) { @@ -385,18 +393,18 @@ const makeEndoBootstrap = ( } else if (formulaIdentifier === 'pet-inspector') { // Behold, unavoidable forward-reference: // eslint-disable-next-line no-use-before-define - const external = makeIdentifiedInspector('pet-store'); + const external = makePetStoreInspector('pet-store'); return { external, internal: undefined }; } else if (formulaIdentifier === 'host') { const storeFormulaIdentifier = 'pet-store'; - const infoFormulaIdentifier = 'pet-inspector'; + const inspectorFormulaIdentifier = 'pet-inspector'; const workerFormulaIdentifier = `worker-id512:${zero512}`; // Behold, recursion: // eslint-disable-next-line no-use-before-define return makeIdentifiedHost( formulaIdentifier, storeFormulaIdentifier, - infoFormulaIdentifier, + inspectorFormulaIdentifier, workerFormulaIdentifier, terminator, ); @@ -428,7 +436,7 @@ const makeEndoBootstrap = ( } else if (formulaType === 'pet-inspector-id512') { // Behold, unavoidable forward-reference: // eslint-disable-next-line no-use-before-define - const external = makeIdentifiedInspector( + const external = makePetStoreInspector( `pet-store-id512:${formulaNumber}`, ); return { external, internal: undefined }; @@ -440,14 +448,14 @@ const makeEndoBootstrap = ( return { external, internal: undefined }; } else if (formulaType === 'host-id512') { const storeFormulaIdentifier = `pet-store-id512:${formulaNumber}`; - const infoFormulaIdentifier = `pet-inspector-id512:${formulaNumber}`; + const inspectorFormulaIdentifier = `pet-inspector-id512:${formulaNumber}`; const workerFormulaIdentifier = `worker-id512:${formulaNumber}`; // Behold, recursion: // eslint-disable-next-line no-use-before-define return makeIdentifiedHost( formulaIdentifier, storeFormulaIdentifier, - infoFormulaIdentifier, + inspectorFormulaIdentifier, workerFormulaIdentifier, terminator, ); @@ -610,12 +618,25 @@ const makeEndoBootstrap = ( makeMailbox, }); - const makeIdentifiedInspector = async petStoreFormulaIdentifier => { + /** + * Creates an inspector for the current party's pet store, used to create + * inspectors for values therein. Notably, can provide references to otherwise + * un-nameable values such as the `MAIN` worker. See `KnownEndoInspectors` for + * more details. + * + * @param {string} petStoreFormulaIdentifier + * @returns {Promise} + */ + const makePetStoreInspector = async petStoreFormulaIdentifier => { const petStore = await provideValueForFormulaIdentifier( petStoreFormulaIdentifier, ); - /** @param {string} petName */ + /** + * @param {string} petName - The pet name to inspect. + * @returns {Promise} An + * inspector for the value of the given pet name. + */ const lookup = async petName => { const formulaIdentifier = petStore.lookup(petName); if (formulaIdentifier === undefined) { @@ -626,26 +647,24 @@ const makeEndoBootstrap = ( if ( ![ 'eval-id512', - 'import-unsafe-id512', - 'import-bundle-id512', + 'make-unconfined-id512', + 'make-bundle-id512', 'guest-id512', 'web-bundle', ].includes(formulaType) ) { - return makeInfo(formulaType, formulaNumber, harden({})); + return makeInspector(formulaType, formulaNumber, harden({})); } const formula = await persistencePowers.readFormula( formulaType, formulaNumber, ); if (formula.type === 'eval') { - return makeInfo( + return makeInspector( formula.type, formulaNumber, harden({ - SOURCE: formula.source, - WORKER: provideValueForFormulaIdentifier(formula.worker), - ENDOWMENTS: Object.fromEntries( + endowments: Object.fromEntries( formula.names.map((name, index) => { return [ name, @@ -653,53 +672,56 @@ const makeEndoBootstrap = ( ]; }), ), + source: formula.source, + worker: provideValueForFormulaIdentifier(formula.worker), }), ); - } else if (formula.type === 'import-unsafe') { - return makeInfo( + } else if (formula.type === 'guest') { + return makeInspector( formula.type, formulaNumber, harden({ - SPECIFIER: formula.type, - WORKER: provideValueForFormulaIdentifier(formula.worker), - POWERS: provideValueForFormulaIdentifier(formula.powers), + host: provideValueForFormulaIdentifier(formula.host), }), ); - } else if (formula.type === 'import-bundle') { - return makeInfo( + } else if (formula.type === 'make-bundle') { + return makeInspector( formula.type, formulaNumber, harden({ - WORKER: provideValueForFormulaIdentifier(formula.worker), - BUNDLE: provideValueForFormulaIdentifier(formula.bundle), - POWERS: provideValueForFormulaIdentifier(formula.powers), + bundle: provideValueForFormulaIdentifier(formula.bundle), + powers: provideValueForFormulaIdentifier(formula.powers), + worker: provideValueForFormulaIdentifier(formula.worker), }), ); - } else if (formula.type === 'guest') { - return makeInfo( + } else if (formula.type === 'make-unconfined') { + return makeInspector( formula.type, formulaNumber, harden({ - HOST: provideValueForFormulaIdentifier(formula.host), + powers: provideValueForFormulaIdentifier(formula.powers), + specifier: formula.type, + worker: provideValueForFormulaIdentifier(formula.worker), }), ); } else if (formula.type === 'web-bundle') { - return makeInfo( + return makeInspector( formula.type, formulaNumber, harden({ - BUNDLE: provideValueForFormulaIdentifier(formula.bundle), - POWERS: provideValueForFormulaIdentifier(formula.powers), + bundle: provideValueForFormulaIdentifier(formula.bundle), + powers: provideValueForFormulaIdentifier(formula.powers), }), ); } // @ts-expect-error this should never occur - return makeInfo(formula.type, formulaNumber, harden({})); + return makeInspector(formula.type, formulaNumber, harden({})); }; + /** @returns {string[]} The list of all names in the pet store. */ const list = () => petStore.list(); - const info = Far('Endo info facet', { + const info = Far('Endo inspector facet', { lookup, list, }); diff --git a/packages/daemon/src/host.js b/packages/daemon/src/host.js index ba212e53d8..da046475d8 100644 --- a/packages/daemon/src/host.js +++ b/packages/daemon/src/host.js @@ -18,14 +18,14 @@ export const makeHostMaker = ({ /** * @param {string} hostFormulaIdentifier * @param {string} storeFormulaIdentifier - * @param {string} infoFormulaIdentifier + * @param {string} inspectorFormulaIdentifier * @param {string} mainWorkerFormulaIdentifier * @param {import('./types.js').Terminator} terminator */ const makeIdentifiedHost = async ( hostFormulaIdentifier, storeFormulaIdentifier, - infoFormulaIdentifier, + inspectorFormulaIdentifier, mainWorkerFormulaIdentifier, terminator, ) => { @@ -60,7 +60,7 @@ export const makeHostMaker = ({ selfFormulaIdentifier: hostFormulaIdentifier, specialNames: { SELF: hostFormulaIdentifier, - INFO: infoFormulaIdentifier, + INFO: inspectorFormulaIdentifier, NONE: 'least-authority', ENDO: 'endo', }, diff --git a/packages/daemon/src/types.d.ts b/packages/daemon/src/types.d.ts index 0d37136b7e..5e0d3095ab 100644 --- a/packages/daemon/src/types.d.ts +++ b/packages/daemon/src/types.d.ts @@ -247,6 +247,21 @@ export interface EndoHost { ): Promise; } +export type EndoInspector = { + lookup: (petName: Record) => Promise; + list: () => Record[]; +}; + +export type KnownEndoInspectors = { + 'eval-id512': EndoInspector<'endowments' | 'source' | 'worker'>; + 'make-unconfined-id512': EndoInspector<'host'>; + 'make-bundle-id512': EndoInspector<'bundle' | 'powers' | 'worker'>; + 'guest-id512': EndoInspector<'bundle' | 'powers'>; + 'web-bundle': EndoInspector<'powers' | 'specifier' | 'worker'>; + // This is an "empty" inspector, in that there is nothing to `lookup()` or `list()`. + [formulaType: string]: EndoInspector; +}; + export type EndoWebBundle = { url: string; bundle: ERef; From d43e1bda55cd216d0199fde8a18bf750275458d1 Mon Sep 17 00:00:00 2001 From: Erik Marks Date: Mon, 29 Jan 2024 22:29:08 -0800 Subject: [PATCH 5/6] test(daemon): Add inspector naming and reuse test --- packages/daemon/test/test-endo.js | 41 +++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/packages/daemon/test/test-endo.js b/packages/daemon/test/test-endo.js index 6e27c37c86..8c54969ece 100644 --- a/packages/daemon/test/test-endo.js +++ b/packages/daemon/test/test-endo.js @@ -694,3 +694,44 @@ test('make a host', async t => { await stop(locator); }); + +test('name and reuse inspector', async t => { + const { promise: cancelled, reject: cancel } = makePromiseKit(); + t.teardown(() => cancel(Error('teardown'))); + const locator = makeLocator('tmp', 'inspector-reuse'); + + await stop(locator).catch(() => {}); + await reset(locator); + await start(locator); + + const { getBootstrap } = await makeEndoClient( + 'client', + locator.sockPath, + cancelled, + ); + const bootstrap = getBootstrap(); + const host = E(bootstrap).host(); + await E(host).provideWorker('worker'); + + const counterPath = path.join(dirname, 'test', 'counter.js'); + await E(host).makeUnconfined('worker', counterPath, 'NONE', 'counter'); + + const inspector = await E(host).evaluate( + 'worker', + 'E(INFO).lookup("counter")', + ['INFO'], + ['INFO'], + 'inspector', + ); + t.regex(String(inspector), /Alleged: Inspector.+make-unconfined/u); + + const worker = await E(host).evaluate( + 'worker', + 'E(inspector).lookup("worker")', + ['inspector'], + ['inspector'], + ); + t.regex(String(worker), /Alleged: EndoWorker/u); + + await stop(locator); +}); From 4b45a7aa473870c80e6e386390129d36054368e8 Mon Sep 17 00:00:00 2001 From: Erik Marks Date: Tue, 30 Jan 2024 09:48:27 -0800 Subject: [PATCH 6/6] test(daemon): Add test for eval-mediated worker names --- packages/daemon/test/test-endo.js | 49 +++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/packages/daemon/test/test-endo.js b/packages/daemon/test/test-endo.js index 8c54969ece..53423186fe 100644 --- a/packages/daemon/test/test-endo.js +++ b/packages/daemon/test/test-endo.js @@ -735,3 +735,52 @@ test('name and reuse inspector', async t => { await stop(locator); }); + +// TODO: This test verifies existing behavior when pet-naming workers. +// This behavior is undesirable. See: https://github.com/endojs/endo/issues/2021 +test('eval-mediated worker name', async t => { + const { promise: cancelled, reject: cancel } = makePromiseKit(); + t.teardown(() => cancel(Error('teardown'))); + const locator = makeLocator('tmp', 'eval-worker-name'); + + await stop(locator).catch(() => {}); + await reset(locator); + await start(locator); + + const { getBootstrap } = await makeEndoClient( + 'client', + locator.sockPath, + cancelled, + ); + const bootstrap = getBootstrap(); + const host = E(bootstrap).host(); + await E(host).provideWorker('worker'); + + const counterPath = path.join(dirname, 'test', 'counter.js'); + await E(host).makeUnconfined('worker', counterPath, 'NONE', 'counter'); + + // We create a petname for the worker of `counter`. + // Note that while `worker === counter-worker`, it doesn't matter here. + const counterWorker = await E(host).evaluate( + 'worker', + 'E(E(INFO).lookup("counter")).lookup("worker")', + ['INFO'], + ['INFO'], + 'counter-worker', + ); + t.regex(String(counterWorker), /Alleged: EndoWorker/u); + + try { + await E(host).evaluate( + 'counter-worker', // Our worker pet name + 'E(counter).incr()', + ['counter'], + ['counter'], + ); + t.fail('should have thrown'); + } catch (error) { + // This is the error that we don't want + t.regex(error.message, /typeof target is "undefined"/u); + await stop(locator); + } +});