Skip to content

Commit 7d618d0

Browse files
committed
feat(ocap-kernel): Setup a general endowment pattern
1 parent 2d1c409 commit 7d618d0

7 files changed

Lines changed: 186 additions & 18 deletions

File tree

packages/ocap-kernel/src/VatSupervisor.ts

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
1-
import {
2-
makeLiveSlots as localMakeLiveSlots,
3-
makeMarshaller,
4-
} from '@agoric/swingset-liveslots';
1+
import { makeLiveSlots as localMakeLiveSlots } from '@agoric/swingset-liveslots';
52
import type {
63
VatDeliveryObject,
74
VatSyscallResult,
85
} from '@agoric/swingset-liveslots';
9-
import { Fail } from '@endo/errors';
106
import { importBundle } from '@endo/import-bundle';
117
import { makeMarshal } from '@endo/marshal';
128
import type { CapData } from '@endo/marshal';
@@ -20,6 +16,7 @@ import { serializeError } from '@metamask/rpc-errors';
2016
import type { DuplexStream } from '@metamask/streams';
2117
import { isJsonRpcRequest, isJsonRpcResponse } from '@metamask/utils';
2218

19+
import { makeEndowments } from './endowments/index.ts';
2320
import { vatSyscallMethodSpecs, vatHandlers } from './rpc/index.ts';
2421
import { makeGCAndFinalize } from './services/gc-finalize.ts';
2522
import { makeDummyMeterControl } from './services/meter-control.ts';
@@ -31,7 +28,6 @@ import type {
3128
VatDeliveryResult,
3229
VatId,
3330
VatSyscallObject,
34-
VRef,
3531
} from './types.ts';
3632
import { isVatConfig, coerceVatSyscallObject } from './types.ts';
3733

@@ -260,21 +256,10 @@ export class VatSupervisor {
260256
meterControl: makeDummyMeterControl(),
261257
});
262258

263-
const { m } = makeMarshaller(syscall, gcTools, this.id);
264-
const toRef = (object: unknown): VRef =>
265-
m.toCapData(object).slots[0] ?? Fail`cannot revoke object ${object}`;
266-
const makeRevoker = (object: unknown): (() => void) => {
267-
const ref = toRef(object);
268-
return harden(() => {
269-
syscall.revoke([ref]);
270-
});
271-
};
272-
harden(makeRevoker);
273-
274259
const workerEndowments = {
275260
console: this.#logger.subLogger({ tags: ['console'] }),
276261
assert: globalThis.assert,
277-
makeRevoker,
262+
...makeEndowments(syscall, gcTools, this.id),
278263
};
279264

280265
const { bundleSpec, parameters } = vatConfig;
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { makeMarshaller } from '@agoric/swingset-liveslots';
2+
import { Fail } from '@endo/errors';
3+
4+
// Used in the docs for a safe use of `toRef`.
5+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
6+
import type { factory as makeRevoker } from './factories/make-revoker.ts';
7+
import type { EndowmentContext, ToRef, Marshaller } from './types.ts';
8+
import type { GCTools, Syscall } from '../services/types.ts';
9+
import type { VatId } from '../types.ts';
10+
11+
/**
12+
* Make a function that converts an object to a vref.
13+
*
14+
* **ATTN**: Do not expose the return value of this function to user code.
15+
*
16+
* This is a hack that disrespects liveslots's encapsulation of the marshaller.
17+
* If the user code gets a handle on `toRef` and a capability to send messages
18+
* to the kernel, it can break vat containment by impersonating its supervisor.
19+
*
20+
* It is fine to expose a hardened function which passes an object to `toRef`,
21+
* as long as the vref cannot escape the scope of that function.
22+
*
23+
* @see {@link makeRevoker} for an example of safe use of `toRef`.
24+
*
25+
* @param marshaller - The liveslots marshaller.
26+
* @returns A function that converts an object to a vref.
27+
*/
28+
function makeToRef(marshaller: Marshaller): ToRef {
29+
const toRef: ToRef = (object) =>
30+
marshaller.toCapData(object).slots[0] ??
31+
Fail`cannot make ocap url for object ${object}`;
32+
return harden(toRef);
33+
}
34+
35+
/**
36+
* Make a context for an endowment.
37+
*
38+
* @param syscall - The syscall object.
39+
* @param gcTools - The gc tools.
40+
* @param vatId - The vat id.
41+
* @returns A context for an endowment.
42+
*/
43+
export function makeEndowmentContext(
44+
syscall: Syscall,
45+
gcTools: GCTools,
46+
vatId: VatId,
47+
): EndowmentContext {
48+
const toRef = makeToRef(makeMarshaller(syscall, gcTools, vatId).m);
49+
return { syscall, gcTools, vatId, toRef };
50+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import * as makeRevoker from './make-revoker.ts';
2+
import * as scry from './scry.ts';
3+
import type { EndowmentDefinition } from '../types.ts';
4+
5+
const endowmentDefinitions = {
6+
makeRevoker,
7+
scry,
8+
} as const satisfies Record<string, EndowmentDefinition>;
9+
10+
export type EndowmentName = keyof typeof endowmentDefinitions;
11+
12+
export type Endowments = {
13+
[K in EndowmentName]: ReturnType<(typeof endowmentDefinitions)[K]['factory']>;
14+
};
15+
16+
export default endowmentDefinitions;
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import type { VRef } from '../../types.ts';
2+
import type { EndowmentContext } from '../types.ts';
3+
4+
/**
5+
* A function that revokes a distributed object, making it impossible to call
6+
* any methods on it.
7+
*/
8+
export type Revoker = () => void;
9+
10+
/**
11+
* A function that makes a revoker for a given object.
12+
*/
13+
export type MakeRevoker = (object: unknown) => Revoker;
14+
15+
/**
16+
* Make a function that makes a revoker for a given object. Intended to be used
17+
* as a user code endowment.
18+
*
19+
* @param context - The context in which the endowment is created.
20+
* @returns A function that makes a revoker for a given object.
21+
*/
22+
export function factory(context: EndowmentContext): MakeRevoker {
23+
const { syscall, toRef } = context;
24+
const revoke = (vref: VRef): void => {
25+
syscall.revoke([vref]);
26+
};
27+
/**
28+
* Make a revoker for a given object. A vat can only revoke its own objects,
29+
* so revokable delegation is not possible. After the revoker is called, all
30+
* eventual method calls from importers of the distributed object will fail.
31+
*
32+
* @param object - The object to revoke.
33+
* @returns A function that revokes the object.
34+
*/
35+
function makeRevoker(object: unknown): Revoker {
36+
const ref = toRef(object);
37+
return harden(() => revoke(ref));
38+
}
39+
return harden(makeRevoker);
40+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import type { VRef } from '../../types.ts';
2+
import type { EndowmentContext } from '../types.ts';
3+
4+
/**
5+
* A function that scries a vref.
6+
*/
7+
export type Scry = (vref: VRef) => unknown;
8+
9+
/**
10+
* Make a function that scries a vref. It logs to the console, which in
11+
* production is a no-op. In development, it can be used to inspect the
12+
* contents of a vref.
13+
*
14+
* @param context - The context in which the endowment is created.
15+
* @returns A function that scries a vref.
16+
*/
17+
export function factory(context: EndowmentContext): Scry {
18+
const { toRef } = context;
19+
const scry: Scry = (object: unknown) => console.log(`scry ${toRef(object)}`);
20+
return harden(scry);
21+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import type { GCTools, Syscall } from '../services/types.ts';
2+
import type { VatId } from '../types.ts';
3+
import { makeEndowmentContext } from './context.ts';
4+
import type { EndowmentName, Endowments } from './factories/index.ts';
5+
import endowmentDefinitions from './factories/index.ts';
6+
7+
const allEndowmentNames = Object.keys(endowmentDefinitions) as EndowmentName[];
8+
9+
/**
10+
* Make a set of endowments for a vat.
11+
*
12+
* @param syscall - The syscall object.
13+
* @param gcTools - The gc tools.
14+
* @param vatId - The vat id.
15+
* @param names - The names of the endowments to make. If not provided, all
16+
* endowments are made. XXX The default should be to make *no* endowments.
17+
* @returns A set of endowments for a vat.
18+
*/
19+
export function makeEndowments(
20+
syscall: Syscall,
21+
gcTools: GCTools,
22+
vatId: VatId,
23+
names: EndowmentName[] = allEndowmentNames,
24+
): Endowments {
25+
const context = makeEndowmentContext(syscall, gcTools, vatId);
26+
return Object.fromEntries(
27+
Object.entries(endowmentDefinitions)
28+
.filter(([name]) => names.includes(name as EndowmentName))
29+
.map(([name, definition]) => [name, definition.factory(context)]),
30+
) as Endowments;
31+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import type { makeMarshaller } from '@agoric/swingset-liveslots';
2+
3+
import type { GCTools, Syscall } from '../services/types.ts';
4+
import type { VRef, VatId } from '../types.ts';
5+
6+
export type Marshaller = ReturnType<typeof makeMarshaller>['m'];
7+
8+
/**
9+
* A function that converts an object to a vref.
10+
*/
11+
export type ToRef = (object: unknown) => VRef;
12+
13+
/**
14+
* The context in which an endowment is created.
15+
*/
16+
export type EndowmentContext = {
17+
syscall: Syscall;
18+
gcTools: GCTools;
19+
vatId: VatId;
20+
toRef: ToRef;
21+
};
22+
23+
export type EndowmentDefinition = {
24+
factory: (context: EndowmentContext) => unknown;
25+
};

0 commit comments

Comments
 (0)