WebAssembly System Interface (WASI)#

安定性: 1 - Experimental

node:wasi モジュールは現在、一部のWASIランタイムが提供するような包括的なファイルシステムのセキュリティプロパティを提供していません。安全なファイルシステムのサンドボックス化の完全なサポートは、将来実装されるかもしれないし、されないかもしれません。それまでの間、信頼できないコードを実行するためにこのモジュールに依存しないでください。

ソースコード: lib/wasi.js

WASI APIは、WebAssembly System Interface 仕様の実装を提供します。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は、アプリケーションに独自のカスタム envpreopensstdinstdoutstderr、および exit 機能が提供される、ケーパビリティベースのモデルを提供します。

現在のNode.jsの脅威モデルは、一部のWASIランタイムに存在するような安全なサンドボックス化を提供していません。

ケーパビリティ機能はサポートされていますが、Node.jsにおいてはセキュリティモデルを形成しません。例えば、ファイルシステムのサンドボックス化は様々なテクニックで回避できます。このプロジェクトでは、これらのセキュリティ保証を将来追加できるかどうかを検討しています。

クラス: WASI#

WASI クラスは、WASIシステムコールAPIと、WASIベースのアプリケーションを扱うための便利な追加メソッドを提供します。各 WASI インスタンスは、それぞれ独立した環境を表します。

new WASI([options])#

  • options <Object>
    • args <Array> WebAssemblyアプリケーションがコマンドライン引数として受け取る文字列の配列です。最初の引数はWASIコマンド自体の仮想パスです。デフォルト: []
    • env <Object> WebAssemblyアプリケーションが自身の環境として認識する、process.env に似たオブジェクトです。デフォルト: {}
    • 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_start() エクスポートを含まない場合、または instance_initialize() エクスポートを含む場合は、例外がスローされます。

start() は、instancememory という名前の WebAssembly.Memory をエクスポートすることを要求します。instancememory エクスポートを持たない場合、例外がスローされます。

start() が複数回呼び出された場合、例外がスローされます。

wasi.initialize(instance)#

instance_initialize() エクスポートが存在する場合、それを呼び出してWASIリアクターとして初期化しようとします。instance_start() エクスポートを含む場合、例外がスローされます。

initialize() は、instancememory という名前の WebAssembly.Memory をエクスポートすることを要求します。instancememory エクスポートを持たない場合、例外がスローされます。

initialize() が複数回呼び出された場合、例外がスローされます。

wasi.finalizeBindings(instance[, options])#

initialize()start() を呼び出さずに、instance へのWASIホストバインディングを設定します。このメソッドは、スレッド間でメモリを共有するために、WASIモジュールが子スレッドでインスタンス化される場合に便利です。

finalizeBindings() は、instancememory という名前の WebAssembly.Memory をエクスポートするか、ユーザーが options.memoryWebAssembly.Memory オブジェクトを指定することを要求します。memory が無効な場合、例外がスローされます。

start()initialize() は内部で finalizeBindings() を呼び出します。finalizeBindings() が複数回呼び出された場合、例外がスローされます。

wasi.wasiImport#

wasiImport は、WASIシステムコールAPIを実装するオブジェクトです。このオブジェクトは、WebAssembly.Instance のインスタンス化の際に、wasi_snapshot_preview1 インポートとして渡されるべきです。