[Proposal] Native code/module access via WASI/VFS #237
Replies: 8 comments 41 replies
-
Question: "Wasm execution blocks until the operation completes [3]." How does the wasm execution know when to block? In the sense that the wasm program might write the file, e.g. |
Beta Was this translation helpful? Give feedback.
-
"and receives back a capability to another object containing the result": Is that bit wrong/misleading? Because the JSON example suggests that the capability for the result is created by the caller and passed into the service. |
Beta Was this translation helpful? Give feedback.
-
I'd simplify to include source data in the request, this removes any requirement for the component program to have to access the wasi visible VFS. The response includes the result. Any sort of streaming (seek style operations) gets handled by the WASM accessing the interface through multiple invocations. (which is pattern 1). Pattern 2 will be a useful optimization, but maybe get the MVP first. Pattern 1 makes the interface on the native module essentially equivalent to what one might expect from a web application, all data it needs to process the request is in the request body, and it sends the result. |
Beta Was this translation helpful? Give feedback.
-
Open questions:
|
Beta Was this translation helpful? Give feedback.
-
Just to expand on my mount table derivation -- right now the wasi fs has a couple of structs managing the filesystem.
So really at the file level, we are talking about inodes, my suggestion is we extend inode to have file system operation function table per inode. For normal files (the ramfs) it just points to the current functions. For synthetic files (such as aes) it points to the library implementations. There can be a mount function which registers a library's synthetic path in the path_table and creates the inode with the right function hooks. How to reference the library in the mount could be done one of two ways:
|
Beta Was this translation helpful? Give feedback.
-
Some proposals:
|
Beta Was this translation helpful? Give feedback.
This comment has been minimized.
This comment has been minimized.
-
This looks well thought-out, butthere is a lot of string parsing in this proposal: The module name, JSON, etc. String parsing is extremely error-prone, and has a history of producing severe security vulnerabilities. Could we consider other options? |
Beta Was this translation helpful? Give feedback.
-
Status: work in progress.
Rationale:
We want a way of exposing certain native functionality (such as side-channel safe
cryptography, or efficient NN inference) to Wasm programs on Veracruz.
The normal Wasm way of doing this is with imports, directly giving the Wasm
program functions it can call. Depending on the implementation of the Wasm runtime,
this may imply copying of data in and out of the Wasm linear memory. It also implies
giving the native functionality access to the Wasm Linear Memory (to read or write
any data not passed by value), which is a security risk for arbitrary native code.
While Wasm Interface Types [1] would be helpful here, for Veracruz we additionally
want to restrict (at least for now) the functionality that is made available with
native modules, and for now don't want programs to be able to bring their own arbitrary
native modules. This is to limit the policy complexity, to maximise the benefit we
get from sandboxing in the first place, and to make our future lives easier as we
start looking at bounding program behaviour.
Therefore, for now, we propose to add only necessary native functionality as trusted,
Veracruz-provided modules, and to access them primarily using the WASI system interface
and VFS. The first such module will be for cryptography, where the functionality can
not adequately be implemented in Wasm due to the inability to write side-channel safe
code portably in Wasm (and also, where native instructions such as Armv8-CE or AES-NI
provide a very significant performance uplift).
Patterns:
Direct VFS access: native services are accessed using a "device" file on the VFS.
This has the advantages of simplicity and familiarity, and disadvantages of
requiring multiple copies of data in memory (e.g. to decrypt a file in the VFS, the
program needs to read the ciphertext into linear memory, write it to the device file,
and read back the results into linear memory). This pattern also avoids confused deputy
threats (see below), since the native module can not directly accces the VFS.
Indirect VFS access: native services are accessed using a device file as above, but
these native services can also directly access the VFS. Instead of copying via the
linear memory, the program hands the native functionality a handle/capability to an
object on the VFS (such as an encrypted blob) and another capability to
a VFS object which will contain the result. We must use capabilities here to avoid
confused deputy attacks, wherein a program can e.g. confuse the decryption module into
giving it access to a VFS file it does not have policy-granted permission to access.
As with POSIX and other programming of this sort, both approaches suffer from the challenge
of in-band vs out-of-band communications and signalling, e.g. for the decryption service
example, where the program supplies the decryption key. Traditionally this is solved
with
ioctl
s andsignal
s; we want to avoid that complexity if possible in our VFS,however the alternatives (e.g. sockets) don't seem better. For the first prototype, we
propose to handle comms (e.g. for keying the decryptor module) in band using an RPC style
mechanism (e.g. commands and arguments serialized to protobuf and written to the device file)
and to ignore out of band signalling.
Note that it's possible to combine these models, e.g. input indirectly, result directly, etc.
Example:
Here is a very rough sketch of how a file decryption service might look (from the Wasm program's perspective):
/crypto/file-decryption/aes
as<cap-aes>
as
<cap-result>
. No one else will have a capability to access this.<cap-aes>
:<cap-aes>
. Conceptually, this triggers the operation itself,perhaps blocking until it completes [3].
<cap-result>
and goes on with it's day.Internally, the AES file decryption module is implemented as part of the Veracruz
binary, like the rest of the VFS – a dynamically extensible design (i.e. runtime loadable
native modules) is possible within this interface, but out of scope for this proposal.
(Note: some operations might benefit from in-place/destructive processing;
in such cases, the API could permit the same cap be passed as both source
and dest, although this is messy and complicates the capability system).
[1] https://github.com/WebAssembly/interface-types/blob/main/proposals/interface-types/Explainer.md
[2] JSON in strings avoids requiring the application to have a dependency on a library for
object serialization (such as protobuf or Cap'n Proto) for the common case where the
application only has to write to (not read from) these synthetic files. Such strings can
easily be constructed with standard-library functionality in most languages. If the
application wants to, it can of course include a JSON library for constructing and parsing
these strings.
[3] There are obvious problems and obvious solutions to this, we can deal with them later. A
simple solution would be to have this operation return immediately, and have subsequent
reads from
<cap-result>
block until the operation has produced sufficient result for theread to return. If the application closes
<cap-source>
before reading from<cap-result>
(and no other entities hold a capability to
<cap-source>
) then the system couldtransparently perform the operation destructively. This could be extended to chaining
multiple native operations together (e.g. file decrypt and video decode) without having
to roundtrip in and out of Wasm linear memory.
Beta Was this translation helpful? Give feedback.
All reactions