Shimmy WASM:システムコールのないセキュリティモデル
前の2つの投稿は脅威モデルとseccompサンドボックスについてだった。この投稿はさらに進む:セキュリティプロパティがOSレベルフィルターではなくコンパイルターゲットから来るWebAssembly実行環境について。
なぜWASMセキュリティが違うのか
seccompでは62項目のブロックリストを書いた。新しい危険なシステムコールが現れたら(io_uring、お前を見ているぞ)、リストに追加する。セキュリティモデルは「悪いものをブロックする」だ。
WASMでは、セキュリティモデルは「システムコールがない」だ。.wasmバイナリにはsocket()、ptrace()、io_uring_setup()を呼び出すメカニズムがない——ブロックしたからではなく、命令セットにそれらが含まれていないから。すべてのI/OはランタイムによってコントロールされるケイパビリティベースのインターフェースであるWASIを通過する。
これから流れるプロパティ:
| プロパティ | ネイティブコード | WASM |
|---|---|---|
| 直接システムコール | 可能 | 不可能 |
| メモリ破損 | 悪用可能 | トラップ(境界チェック) |
| ROP/JOP攻撃 | 可能 | 不可能(コードポインタなし) |
| バッファオーバーフロー | 危険 | トラップ |
| Fork爆弾 | 可能 | 不可能(WASIにforkなし) |
forkをブロックする必要はない——存在しないから。
アーキテクチャ
ユーザーコード(C/C++/Rust/Go)
│
▼ clang --target=wasm32-wasi
WASMバイナリ(.wasm)
│
▼
Wasmtimeランタイム
├── WASI機能(プリオープンパス、フィルタされた環境)
├── リソース制限(--fuel、--max-memory-size)
└── エフェメラルファイルシステム(一時ディレクトリ、実行後クリーン)
│
▼
ホストシステム(プリオープンパス以外は何も見えない)WASIケイパビリティモデル
WASMはデフォルトで何も取得しない。すべてのケイパビリティは明示的に付与される必要がある。
安全 — 自由に付与:
| ケイパビリティ | デフォルト | 備考 |
|---|---|---|
timeout | 5s | ウォールクロック制限 |
memory_mb | 128 | リニアメモリ上限 |
fuel | 10億命令 | CPU制限 |
allow_clock | ✅ | 時間クエリ |
allow_random | ✅ | 暗号化RNG |
注意 — 限定的な露出:
| ケイパビリティ | デフォルト | 備考 |
|---|---|---|
allow_fs_read | ❌ | プリオープンパスのみ読み取り |
allow_args | ✅ | argvがプログラムに見える |
allow_simd | ✅ | リスク:タイミングサイドチャネル |
不可能 — WASIにこれらはない:
| ケイパビリティ | 理由 |
|---|---|
| プロセス生成 | WASI仕様にない |
| シグナル処理 | WASI仕様にない |
| 生システムコール | システムコール命令がない |
| ホストメモリアクセス | リニアメモリは分離されている |
不可能カテゴリがWASMを根本的に異なるものにする。allow_forkを付与できない——forkがインターフェースに存在しないから。
エフェメラルモード
デフォルト実行モードはホストに痕跡を残さない:
1. 一時ディレクトリ作成:/var/.../shimmy_wasm_abc123/
2. /tmpを分離: shimmy_wasm_abc123/sandbox_tmp/
3. 書き込み可能ディレクトリをコピー:/data → abc123/copy_data/(マウントではなくコピー)
4. WASMを実行: すべての書き込みは一時コピーへ
5. 出力ファイルを収集:result.output_files = {name: bytes}
6. すべてを削除: 一時ディレクトリを削除、ホスト不変パフォーマンス数値
正直なベンチマーク(50回、5ウォームアップ、macOS arm64):
| ワークロード | ネイティブ | WASM実行 | WASMフル* | ランタイムオーバーヘッド |
|---|---|---|---|---|
| Hello World | 1ms | 4-6ms | 50-100ms | 4-6x |
| 計算(100k演算) | 3ms | 5-8ms | 60-110ms | 1.7-2.7x |
| Fibonacci(35) | 50ms | 70-100ms | 120-200ms | 1.4-2x |
| メモリ(1MB割り当て) | 2ms | 4-6ms | 50-100ms | 2-3x |
*「WASMフル」はソースからのコンパイルを含む。「WASM実行」はプリコンパイル済み.wasmを使用。
50-100msのコンパイルオーバーヘッドが主なコスト。緩和策:コンパイル済みモジュールをキャッシュ(同じソース = 同じ.wasm)、AOTプリコンパイル、実行時ではなく提出時にプリコンパイル。
コンパイル後のランタイムオーバーヘッドは1.5-3x——セキュリティ優先コンテキストでは許容範囲。
他のサンドボックスアプローチとの比較
| アプローチ | 起動 | ランタイムオーバーヘッド | 脱出難易度 |
|---|---|---|---|
| WASM | 約50ms | 約2x | wasmtimeバグが必要 |
| seccomp(Sandlock) | 約1.5ms | 約1.01x | 許可されたシステムコール悪用 |
| Docker | 約500ms | 約1.05x | カーネル悪用 |
| gVisor | 約200ms | 約1.5x | ハイパーバイザー悪用 |
| Firecracker | 約125ms | 約1.1x | ハイパーバイザー悪用 |
WASMは「高速起動」と「最も脱出困難」の交差点を占める。脱出にはwasmtime自体のバグが必要——フィルタールールでも、ポリシー設定でも、ランタイムにある。それはずっと小さな攻撃対象領域だ。
スレッディング:意図的に未実装
WASMスレッドは存在する。wasm32-wasi-threadsはコンパイルターゲットだ。Wasmtimeは--wasm-threads=yをサポートする。実装していない。
理由はSharedArrayBuffer + 高精度クロック = Spectreだ。組み合わせはブラウザでのSpectre攻撃の元のベクトルだったタイミングサイドチャネルを提供する。ブラウザベンダーはこの発見後にクロック精度を下げるためにかなりの努力をした。
信頼できないコードを実行するサンドボックスでは、そのベクトルを追加することは並列性の利点に見合わない。コードベースで意図的として文書化:
# スレッディング(未実装 - 完全性のために文書化)
# wasm32-wasi-threads + wasmtime --wasm-threads=yでWASMスレッドは可能
# 未実装:Spectreリスク(SharedArrayBuffer + タイミング)、複雑さ、サンドボックス化されたスニペットには利点なしWASMかSandlockか
| ユースケース | 選択 |
|---|---|
| 最大セキュリティ | WASM |
| Lambda実行 | WASM |
| numpy/scipy付きPython | Sandlock(現時点) |
| プリコンパイルバイナリ | Sandlock |
| <2msレイテンシ要件 | Sandlock |
| クロスプラットフォーム | WASM |
| C/C++/Rust/Goスニペット | WASM |
Pythonの注意点は本物:PyodideにはブラウザJSエンジンが必要、MicroPythonは限定されたstdlib、RustPythonは不完全。そのエコシステムが成熟するまで、PythonコードはSandlockを通過する。他のすべてはWASM経由でより良いセキュリティストーリーを持つ。
次のステップ
- モジュールキャッシング — 同じソース → 再コンパイルをスキップ
- Python WASM — MicroPython/WASI-threadsエコシステムを監視;12-18ヶ月で再評価
エンドポイントはハイブリッド:WASM Pythonエコシステムが成熟するまでSandlockを通じてPython、今すぐWASMを通じて他のすべて。
