WebAssembly 系統介面 (WASI)#

穩定性:1 - 實驗性

node:wasi 模組目前並未提供某些 WASI 執行環境所具備的全面性檔案系統安全性屬性。未來可能會實作,也可能不會實作對安全檔案系統沙盒的完整支援。在此同時,請勿依賴它來執行不可信的程式碼。

WASI API 提供了 WebAssembly 系統介面規範的實作。WASI 透過一系列類似 POSIX 的函數,讓 WebAssembly 應用程式能夠存取底層作業系統。

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);
'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 文字格式檔案。

(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 提供了一種基於能力 (capabilities-based) 的模型,應用程式可透過該模型獲得其自訂的 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)#

嘗試透過呼叫 instance_start() 匯出函數,以 WASI 指令方式開始執行該 instance。如果 instance 不包含 _start() 匯出,或者 instance 包含 _initialize() 匯出,則會拋出異常。

start() 要求 instance 必須匯出一個名為 memoryWebAssembly.Memory。如果 instance 沒有 memory 匯出,則會拋出異常。

如果多次呼叫 start(),將會拋出異常。

wasi.initialize(instance)#

嘗試透過呼叫 instance_initialize() 匯出函數(如果存在)來將其初始化為 WASI 反應器 (reactor)。如果 instance 包含 _start() 匯出,則會拋出異常。

initialize() 要求 instance 必須匯出一個名為 memoryWebAssembly.Memory。如果 instance 沒有 memory 匯出,則會拋出異常。

如果多次呼叫 initialize(),將會拋出異常。

wasi.finalizeBindings(instance[, options])#

在不呼叫 initialize()start() 的情況下,設定 WASI 主機繫結至 instance。當 WASI 模組在子執行緒中實例化以跨執行緒共用記憶體時,此方法非常有用。

finalizeBindings() 要求 instance 必須匯出一個名為 memoryWebAssembly.Memory,或者由使用者在 options.memory 中指定 WebAssembly.Memory 物件。如果 memory 無效,將會拋出異常。

start()initialize() 會在內部呼叫 finalizeBindings()。如果多次呼叫 finalizeBindings(),將會拋出異常。

wasi.wasiImport#

wasiImport 是一個實作了 WASI 系統呼叫 API 的物件。在 WebAssembly.Instance 實例化期間,應將此物件作為 wasi_snapshot_preview1 匯入傳遞。