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

implement device "hooks" to invoke kernel endowments with dref-kref translation #4726

Closed
warner opened this issue Mar 3, 2022 · 1 comment · Fixed by #4865
Closed

implement device "hooks" to invoke kernel endowments with dref-kref translation #4726

warner opened this issue Mar 3, 2022 · 1 comment · Fixed by #4865
Assignees
Labels
enhancement New feature or request SwingSet package: SwingSet
Milestone

Comments

@warner
Copy link
Member

warner commented Mar 3, 2022

What is the Problem Being Solved?

To facilitate #4381, I need a way for device-vat-admin to pass capdata into the kernel endowment through a path that will translate the device-side slots (drefs) into kernel-side ones (krefs). The current endowment mechanism just gives the device a kernel function to call (like getBundle or pushCreateVatIDEvent), but the kernel function does not know which device is calling it, and does not have access to the deviceTranslator.js objects to perform the translation.

This looks a lot like a syscall. Devices can use syscall.sendOnly, which translates dref-to-kref and then pushes a kref-based message onto the run-queue. So I'm thinking I should add a syscall that can invoke pre-arranged "kernel hooks".

Description of the Design

The kernel would configure these for each device by providing a record of named functions. Each function will be called with kref-based capdata. On the device side, a new syscall.callKernelHook is used: it takes a hook name and an argument. deviceManager.js arranges for the syscall to translate the argument into kref space, invoke the given kernel function, then translate any return value back into dref space:

// device code:
import { buildSerializationTools } from '.../deviceTools.js';
const { serialize, unserialize, deviceNodeForSlot, presenceForSlot } = buildSerializationTools(syscall, deviceName);

const argCapdata = serialize([deviceNodeForSlot('d+1'), presenceForSlot('o-2')]);
const resultCapdata = syscall.callKernelHook('add', argCapdata);
const result = unserialize(resultCapData);

This is the moral equivalent of a vat doing something like result = D(hooks).add([devNode, presence]).

Within the kernel.js start function, the hook would be configured with:

deviceHooks[deviceName] = {
  add(argCapData) { ... ; return resultCapData; },
};

where the kernel-side hook receives and returns (kref-space) capdata. Translation occurs in both directions.

All arguments passed through callKernelHook() must be serializable: devices have access to external "magic" endowment functions, but they should not send these through callKernelHook. The serializer provided by buildSerializationTools will not know what to do with such endowments (it is much more limited than the one used by liveslots).

Security Considerations

Devices are pretty powerful. They're run in their own compartment, but they share an engine with the kernel, so an infinite loop will prevent the kernel from making progress. They are subject to ocap protections (the syscall they receive is hardened and defensive, etc), but if the host application chooses to provide a device with powerful endowments, the device becomes as powerful as those endowments.

These hooks provide a way to limit that power somewhat, at least they provide a data-only interface to things outside the device. It might be interesting to pursue this model for externally-provided endowments (like the Mailbox device getting access to the host-provided storage buffer), and also for input endowments (q.v. #720). I'd set aside the name syscall.callExternalHook or callEndowedHook or something for that path.

Test Plan

Testing this might be tricky. These hooks only make sense for the kernel to configure itself: kref space isn't meaningful outside the kernel (although test functions like controller.queueToKref suggest otherwise). For regular devices, a unit test can configure the device and its endowments in the same way normal host applications would: config.devices and the deviceEndowments that is passed as the second argument to makeSwingsetController(). But if these are only meaningful when provided by the kernel, it would be awkward to introduce a user-visible way to inject them.

The built-in kernel endowments would be limited to vat-admin (indeed that's the primary, and thus far only use case for this feature). And I wouldn't want test functionality to be hanging off a device or endowment that's used in production.

One possibility is to put an addTestDeviceHook property on runtimeOptions. If present, it would be a function that would be used by a hook made available to device-vat-admin.

Another is to add kernel.addDeviceHook(deviceName, hook). Calling this would require access to the kernel object, not just the controller, which matches the pattern used in a handful of unit tests (test-kernel.js among them), and would be hidden from normal userspace.

@warner warner added enhancement New feature or request SwingSet package: SwingSet labels Mar 3, 2022
@warner warner self-assigned this Mar 3, 2022
@warner
Copy link
Member Author

warner commented Mar 18, 2022

I've built this and will file a PR shortly. But in a discussion with @FUDCo about how I'm actually using this to achieve #4381, it occurred to me that an alternative approach would be to implement "kernel-provided device nodes", in which the kernel would set dNN.owner = null to indicate that the kernel owns a device node. So when vat-vat-admin does syscall.callNow(deviceVatAdmin, 'createVat', args), it would get routed to the kernel instead of device-vat-admin (and device-vat-admin would go away).

This would have a lot of benefits over the PR I'm working on:

  • with no device-vat-admin, userspace is no longer obligated to help the kernel wire up its own internal components
  • the bootstrap vat no longer has a way to forge newVatCallback or vat-termination messages
  • the lack of GC in devices no longer causes vatParameter objects to get retained forever

This would not require changes to the code in vat-vat-admin (it will keep making D() calls), but device-vat-admin goes away, userspace bootstrap() functions must change to replace their vatAdminService = await E(vats.vatAdmin).createVatAdminService(devices.vatAdmin) with vatAdminService = vats.vatAdmin.

It would require initializeSwingSet to allocate the new kernel-owned device node, and include it in the vatParameters arguments passed to vat-vat-admin, effectively moving the createVatAdminService call into buildRootObject.

warner added a commit that referenced this issue Mar 18, 2022
Raw devices can use this feature to call kernel functions through a "hook"
that translates device-side slots ("drefs") into kernel-side slots ("krefs").
This will make it possible for devices to submit capdata to kernel endowments
like `pushCreateVatIDEvent`, so vatParameters can carry slots.

closes #4726
@mergify mergify bot closed this as completed in #4865 Mar 19, 2022
mergify bot pushed a commit that referenced this issue Mar 19, 2022
Raw devices can use this feature to call kernel functions through a "hook"
that translates device-side slots ("drefs") into kernel-side slots ("krefs").
This will make it possible for devices to submit capdata to kernel endowments
like `pushCreateVatIDEvent`, so vatParameters can carry slots.

closes #4726
@Tartuffo Tartuffo added this to the Mainnet 1 milestone Mar 23, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request SwingSet package: SwingSet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants