WASM vs seccomp:コードグレーダーのサンドボックス起動ベンチマーク
先週sandbox_execを出荷した——AWS Lambdaで学生コードを分離するためにseccomp-bpfを使用する224行のCプログラム。その時の正直な答えは:「WASMの方がクリーンだが、Pythonエコシステムがまだそこにない。」
今週は「Pythonエコシステムがまだそこにない」がミリ秒で何を意味するか正確に測定した。答えは予想以上にニュアンスがある。
セットアップ
- ランタイム:Wasmtime v42.0.1
- プラットフォーム:macOS arm64
- 方法論:シナリオごとに50回、5ウォームアップ回、平均
- 比較:Python 3.xをラップするsandbox_exec
フェーズ1:起動オーバーヘッド
最初の質問はシンプル:些細なコードでサンドボックスが起動するのにどれくらいかかるか?
| 環境 | 平均 | P95 |
|---|---|---|
| WASM(JIT) | 9.79ms | 10.86ms |
| WASM(AOTプリコンパイル) | 9.25ms | 10.14ms |
| Python(サンドボックスなし) | 14.71ms | 15.29ms |
WASMはPython自体より速く起動する。これがここでの直感に反する結果だ——人々は「VM = 遅い」と仮定するが、Wasmtimeの起動はCPythonのインタプリタ初期化よりタイトだ。
これらの約10msの内訳:
0-2ms: fork() + exec(wasmtime)
2-7ms: Wasmtimeランタイム初期化
├── コマンドライン解析
├── 設定読み込み
└── WASI環境セットアップ
7-9ms: WASMモジュール処理
├── ファイル読み取り
├── 検証(型チェック)
└── JITコンパイル
9-10ms: 実行 + クリーンアップ時間のほとんどはモジュール解析やJITではなく、Wasmtime自体の初期化にある。
フェーズ2:モジュールサイズは重要か?
| モジュールサイズ | 時間 | デルタ |
|---|---|---|
| 約100B | 9.58ms | ベースライン |
| 約4KB | 9.68ms | +0.1ms |
| 約40KB | 10.97ms | +1.4ms |
モジュールサイズが400倍増加してもコストは1.4ms。初期化コストが他のすべてを支配する。
フェーズ3:計算パフォーマンス
これがWASMのJIT優位性が見えるところ。
| ワークロード | WASM | Python | 高速化 |
|---|---|---|---|
| fib(10) | 10.06ms | 15.12ms | 1.5x |
| fib(20) | 9.63ms | 16.80ms | 1.7x |
| fib(25) | 10.77ms | 25.94ms | 2.4x |
| fib(30) | 15.91ms | 128.97ms | 8.1x |
fib(30)で、WASM合計時間は約16ms(10ms起動 + 6ms計算)。Pythonは129ms。WASMが全体的に速くなるクロスオーバーポイントはfib(20-25)あたり——計算が起動に対して無視できなくなるあたり。
アルゴリズム提出を評価する宿題グレーダーでは、このギャップが重要だ。
フェーズ4:I/Oオーバーヘッド
| 操作 | WASM | Python |
|---|---|---|
| 1× fd_write | 10.16ms | 15.15ms |
| 100× fd_write | 9.97ms | 15.23ms |
100回の書き込み操作は1回と同じ時間。起動コストが完全に支配し、ランタイム内でのWASI I/Oオーバーヘッドは無視できる。
フェーズ5:メモリ割り当て
| メモリ | 時間 |
|---|---|
| 64KB | 9.62ms |
| 1MB | 9.86ms |
| 4MB | 10.04ms |
| 16MB | 9.74ms |
WASMは遅延割り当てを使用。16MBのメモリを宣言しても起動時にはほぼ何もコストがかからない。
フェーズ6:セキュリティ機能にはコストがない
| 設定 | 時間 | 備考 |
|---|---|---|
| 制限なし | 9.58ms | ベースライン |
| +fuel(命令カウンタ) | 7.94ms | わずかに高速 |
| +メモリ制限 | 7.76ms | わずかに高速 |
| +ディレクトリプリオープン | 10.50ms | +0.9ms |
| すべての制限 | 7.91ms |
fuelとメモリ制限を追加する方がそれらを持たないより速い——おそらく最適化された実行パスをトリガーするため。測定可能なコストはディレクトリプリオープンのみ(ファイルシステムケイパビリティセットアップに+0.9ms)。
ここでのセキュリティは負のオーバーヘッド。 珍しい。
セキュリティモデルのギャップ
パフォーマンスは別として、セキュリティ比較は鮮明だ:
| 次元 | sandbox_exec | WASM |
|---|---|---|
| 分離レベル | プロセス | VM |
| メモリ分離 | 共有アドレス空間 | リニアメモリ(ハード境界) |
| システムコール制御 | seccomp許可リスト | システムコールがまったくない |
| ファイルシステム | 外部クリーンアップが必要 | ケイパビリティゲート |
| ネットワーク | seccompブロック | デフォルトで不在 |
WASMはシステムコールをフィルタしない——システムコールがない。WASI下で実行されるWASMモジュールはsocket()、ptrace()、io_uring_setup()を呼び出せない。それらの呼び出しを行うメカニズムがないから。サンドボックス内からは存在しない。
これはseccompの許可リストより根本的に強い保証だ。seccompでは「これらの62のシステムコールをブロック」と言っている。WASMでは「システムコールがない」と言っている。攻撃対象領域の違いはカテゴリカルだ。
なぜまだWASMを使っていないか
セキュリティモデルはより良い。CPUバウンドコードの計算パフォーマンスはより良い。起動オーバーヘッドは同等。
問題はPython:
| Python WASMランタイム | サイズ | C拡張 | 評価 |
|---|---|---|---|
| MicroPython | 370KB | ❌ | 限定されたstdlib |
| RustPython | 約5MB | 部分的 | 不完全 |
| Pyodide | 約15MB | ✅ | ブラウザ専用、500ms+起動 |
宿題グレーダーにはnumpy、scipy、任意のC拡張が必要。Pyodideはこれらをサポートするがブラウザ JavaScript エンジンが必要——Wasmtime下では動作しない。MicroPythonとRustPythonは完全な科学Python スタックをサポートしない。
これはパフォーマンス問題ではない。エコシステム問題だ。WASM Pythonツールチェーンは急速に進化しているが、「任意の学生numpyコードを実行する」にはまだ達していない。
ロードマップ
今: sandbox_exec(seccomp + rlimit)
└── フルPython + C拡張
└── 約1.5msサンドボックス + 約15ms Python起動
└── 62のブロックされたシステムコール
1-2年後:非Python言語にWASM
└── JS、Rust、Go学生 → 直接WASM
└── より良いセキュリティ、同等のパフォーマンス
2-3年後:エコシステムが成熟したらWASM Python
└── Component Model + WASI Preview 2
└── ハイブリッド:Python → sandbox_exec、その他 → WASMハイブリッドアーキテクチャが最終状態になる可能性が高い:Python(C拡張サポートが交渉不可)にはseccomp、その他すべて(エコシステムがすでに成熟)にはWASM。
数値まとめ
同様のユースケースでWASMを評価しているなら:
- 起動: 約10ms(Python起動自体と同等かより速い)
- JIT計算: CPUバウンドコードでCPythonより2-8倍高速
- セキュリティオーバーヘッド: ゼロ(セキュリティ機能は無料または負のコスト)
- Python互換性: numpy/scipy/C拡張が必要ならまだ実行不可能
- その他すべて: すでに実行可能
10msの起動コストはブロッカーではない。Pythonエコシステムがブロッカーだ。
