WebAssembly 系统接口 (WASI)#

稳定性:1 - 实验性

node:wasi 模块目前不提供某些 WASI 运行时提供的全面文件系统安全属性。对安全文件系统沙箱的完全支持可能在将来实现,也可能不实现。在此期间,不要依赖它来运行不受信任的代码。

源代码: lib/wasi.js

WASI API 提供了 WebAssembly 系统接口 规范的实现。WASI 通过一组类似 POSIX 的函数,使 WebAssembly 应用程序能够访问底层操作系统。

import { readFile } from 'node:fs/promises';
import { WASI } from 'wasi';
import { argv, env } from 'node:process';

const wasi = new WASI({
  version: 'preview1',
  args: argv,
  env,
  preopens: {
    '/local': '/some/real/path/that/wasm/can/access',
  },
});

const wasm = await WebAssembly.compile(
  await readFile(new URL('./demo.wasm', import.meta.url)),
);
const instance = await WebAssembly.instantiate(wasm, wasi.getImportObject());

wasi.start(instance);'use strict';
const { readFile } = require('node:fs/promises');
const { WASI } = require('wasi');
const { argv, env } = require('node:process');
const { join } = require('node:path');

const wasi = new WASI({
  version: 'preview1',
  args: argv,
  env,
  preopens: {
    '/local': '/some/real/path/that/wasm/can/access',
  },
});

(async () => {
  const wasm = await WebAssembly.compile(
    await readFile(join(__dirname, 'demo.wasm')),
  );
  const instance = await WebAssembly.instantiate(wasm, wasi.getImportObject());

  wasi.start(instance);
})();

要运行上面的示例,请创建一个名为 demo.wat 的新 WebAssembly 文本格式文件。

(module
    ;; Import the required fd_write WASI function which will write the given io vectors to stdout
    ;; The function signature for fd_write is:
    ;; (File Descriptor, *iovs, iovs_len, nwritten) -> Returns number of bytes written
    (import "wasi_snapshot_preview1" "fd_write" (func $fd_write (param i32 i32 i32 i32) (result i32)))

    (memory 1)
    (export "memory" (memory 0))

    ;; Write 'hello world\n' to memory at an offset of 8 bytes
    ;; Note the trailing newline which is required for the text to appear
    (data (i32.const 8) "hello world\n")

    (func $main (export "_start")
        ;; Creating a new io vector within linear memory
        (i32.store (i32.const 0) (i32.const 8))  ;; iov.iov_base - This is a pointer to the start of the 'hello world\n' string
        (i32.store (i32.const 4) (i32.const 12))  ;; iov.iov_len - The length of the 'hello world\n' string

        (call $fd_write
            (i32.const 1) ;; file_descriptor - 1 for stdout
            (i32.const 0) ;; *iovs - The pointer to the iov array, which is stored at memory location 0
            (i32.const 1) ;; iovs_len - We're printing 1 string stored in an iov - so one.
            (i32.const 20) ;; nwritten - A place in memory to store the number of bytes written
        )
        drop ;; Discard the number of bytes written from the top of the stack
    )
) 

使用 wabt.wat 编译为 .wasm

wat2wasm demo.wat 

安全#

WASI 通过基于能力的模型提供应用程序自己的自定义 envpreopensstdinstdoutstderrexit 能力。

当前的 Node.js 威胁模型没有提供像某些 WASI 运行时那样存在的安全沙箱。

虽然支持能力功能,但它们在 Node.js 中不构成安全模型。例如,可以使用各种技术绕过文件系统沙箱。该项目正在探索是否可以在未来添加这些安全保证。

类:WASI#

WASI 类提供 WASI 系统调用 API 和用于处理基于 WASI 的应用程序的其他便利方法。每个 WASI 实例代表一个不同的环境。

new WASI([options])#

  • options <Object>
    • args <Array> WebAssembly 应用程序将看到的一组字符串,作为命令行参数。第一个参数是 WASI 命令本身的虚拟路径。默认值:[]
    • env <Object>process.env 类似的对象,WebAssembly 应用程序将看到它作为其环境。默认值:{}
    • preopens <Object> 此对象表示 WebAssembly 应用程序的本地目录结构。preopens 的字符串键被视为文件系统中的目录。preopens 中的对应值是主机上这些目录的真实路径。
    • returnOnExit <boolean> 默认情况下,当 WASI 应用程序调用 __wasi_proc_exit() 时,wasi.start() 将使用指定的退出代码返回,而不是终止进程。将此选项设置为 false 将导致 Node.js 进程使用指定的退出代码退出。默认值: true
    • stdin <integer> 用作 WebAssembly 应用程序中标准输入的文件描述符。默认值: 0
    • stdout <integer> 用作 WebAssembly 应用程序中标准输出的文件描述符。默认值: 1
    • stderr <integer> 用作 WebAssembly 应用程序中标准错误的文件描述符。默认值: 2
    • version <string> 请求的 WASI 版本。目前唯一支持的版本是 unstablepreview1。此选项是必需的。

wasi.getImportObject()#

返回一个导入对象,如果除了 WASI 提供的以外不需要其他 WASM 导入,则可以将其传递给 WebAssembly.instantiate()

如果在构造函数中传递了版本 unstable,它将返回

{ wasi_unstable: wasi.wasiImport } 

如果在构造函数中传递了版本 preview1 或未指定版本,它将返回

{ wasi_snapshot_preview1: wasi.wasiImport } 

wasi.start(instance)#

尝试通过调用其 _start() 导出项将 instance 作为 WASI 命令执行。如果 instance 不包含 _start() 导出项,或者如果 instance 包含 _initialize() 导出项,则会抛出异常。

start() 要求 instance 导出一个名为 memoryWebAssembly.Memory。如果 instance 没有 memory 导出项,则会抛出异常。

如果 start() 被调用多次,则会抛出异常。

wasi.initialize(instance)#

尝试通过调用其 _initialize() 导出项(如果存在)将 instance 初始化为 WASI 反应器。如果 instance 包含 _start() 导出项,则会抛出异常。

initialize() 要求 instance 导出一个名为 memoryWebAssembly.Memory。如果 instance 没有 memory 导出项,则会抛出异常。

如果 initialize() 被调用多次,则会抛出异常。

wasi.wasiImport#

wasiImport 是一个实现 WASI 系统调用 API 的对象。此对象应在实例化 WebAssembly.Instance 时作为 wasi_snapshot_preview1 导入项传递。