Skip to content

WebAssembly 系统接口 (WASI)

[稳定版: 1 - 实验性]

稳定版: 1 稳定性: 1 - 实验性

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

源代码: lib/wasi.js

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

js
import { readFile } from 'node:fs/promises'
import { WASI } from 'node: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)
js
'use strict'
const { readFile } = require('node:fs/promises')
const { WASI } = require('node: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 文本格式文件:

text
(module
    ;; 导入所需的 fd_write WASI 函数,该函数将给定的 io 向量写入 stdout
    ;; fd_write 函数签名为:
    ;; (文件描述符, *iovs, iovs_len, nwritten) -> 返回写入的字节数
    (import "wasi_snapshot_preview1" "fd_write" (func $fd_write (param i32 i32 i32 i32) (result i32)))

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

    ;; 将 'hello world\n' 写入内存偏移 8 字节处
    ;; 请注意尾随换行符,这是文本显示所必需的
    (data (i32.const 8) "hello world\n")

    (func $main (export "_start")
        ;; 在线性内存中创建一个新的 io 向量
        (i32.store (i32.const 0) (i32.const 8))  ;; iov.iov_base - 这是指向 'hello world\n' 字符串开头的指针
        (i32.store (i32.const 4) (i32.const 12))  ;; iov.iov_len - 'hello world\n' 字符串的长度

        (call $fd_write
            (i32.const 1) ;; file_descriptor - stdout 为 1
            (i32.const 0) ;; *iovs - 指向 iov 数组的指针,存储在内存位置 0
            (i32.const 1) ;; iovs_len - 我们正在打印一个存储在 iov 中的字符串 - 所以是 1。
            (i32.const 20) ;; nwritten - 内存中存储写入字节数的位置
        )
        drop ;; 从堆栈顶部丢弃写入的字节数
    )
)

使用 wabt.wat 编译为 .wasm

bash
wat2wasm demo.wat

安全性

[历史]

版本变更
v21.2.0, v20.11.0阐明 WASI 安全属性。
v21.2.0, v20.11.0新增于:v21.2.0, v20.11.0

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

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

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

类:WASI

新增于:v13.3.0, v12.16.0

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

new WASI([options])

[历史]

版本变更
v20.1.0returnOnExit 的默认值更改为 true
v20.0.0version 选项现在是必需的,并且没有默认值。
v19.8.0options 添加了 version 字段。
v13.3.0, v12.16.0新增于:v13.3.0, v12.16.0
  • options <对象>
    • args <数组> WebAssembly 应用程序将看到的命令行参数的字符串数组。第一个参数是 WASI 命令本身的虚拟路径。默认值: []
    • env <对象> WebAssembly 应用程序将看到的类似于 process.env 的环境对象。默认值: {}
    • preopens <对象> 此对象表示 WebAssembly 应用程序的本地目录结构。preopens 的字符串键被视为文件系统中的目录。preopens 中对应的值是主机机器上这些目录的真实路径。
    • returnOnExit <布尔值> 默认情况下,当 WASI 应用程序调用 __wasi_proc_exit() 时,wasi.start() 将返回指定的退出代码,而不是终止进程。将此选项设置为 false 将导致 Node.js 进程以指定的退出代码退出。默认值: true
    • stdin <整数> WebAssembly 应用程序中用作标准输入的文件描述符。默认值: 0
    • stdout <整数> WebAssembly 应用程序中用作标准输出的文件描述符。默认值: 1
    • stderr <整数> WebAssembly 应用程序中用作标准错误的文件描述符。默认值: 2
    • version <字符串> 请求的 WASI 版本。当前唯一支持的版本是 unstablepreview1。此选项是必需的。

wasi.getImportObject()

新增于:v19.8.0

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

如果将版本 unstable 传递给构造函数,它将返回:

json
{ wasi_unstable: wasi.wasiImport }

如果将版本 preview1 传递给构造函数,或者未指定版本,它将返回:

json
{ wasi_snapshot_preview1: wasi.wasiImport }

wasi.start(instance)

新增于:v13.3.0, v12.16.0

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

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

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

wasi.initialize(instance)

新增于:v14.6.0, v12.19.0

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

initialize() 需要 instance 导出名为 memoryWebAssembly.Memory。如果 instance 没有 memory 导出,则抛出异常。

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

wasi.wasiImport

新增于:v13.3.0, v12.16.0

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