Bytecode Alliance のアナウンス: デフォルトでセキュア、合成可能な WebAssembly の未来を作る

本日、Bytecode Alliance が発足されたことをお伝えします。Bytecode Alliance は WebAssembly のブラウザ外における利用を推し進めるための産業パートナーシップであり、規格の実装や新しい規格の提案を行います。設立メンバーは Mozilla, Fastly, Intel, Red Hat で、さらなるメンバーを迎え入れるのを楽しみにしています。

私たちが思い描く WebAssembly エコシステムのビジョンは、デフォルトでセキュア (secure by default) なエコシステムが現在のソフトウェア基盤が持つ欠点を修正するというものです。WebAssembly コミュニティは速いペースで進歩しているので、このビジョンは実現可能だと私たちは信じています。

WebAssembly のソリューションは現実世界の問題に対して動作し始めており、プロダクションに向かっています。しかし Alliance (同盟) として私たちにはもっと大きな目標があります。

Why

産業全体として、私たちは毎日のようにユーザーをリスクに晒し続けています。私たちが作成するのは高度にモジュール化されたアプリケーションであり、コードベースの 80% は npm, PyPI, crates.io といったパッケージレジストリから取得したものです。

こういった広く使われるエコシステムを使うのは悪いことではありません。むしろ良いことです。

問題は、現在のソフトウェアアーキテクチャがそれを安全に行えるようになっておらず、悪いヤツらがそれを悪用する点にあります ...そしてその頻度は急上昇しています。

悪いヤツらが利用しているのが、現在のアプリケーションがユーザーに開発者を信用させている事実です。ユーザーがアプリケーションを開始するのは、「信用するよ」と言ってそのコードに家の鍵を渡すようなものです。

しかしそうすると依存するコードも全て招き入れることになり、その全員が家の鍵を手に入れます。依存コードはあなたの知らない人によって書かれており、信頼する理由はありません。

ユーザー「君は信頼できそうだ。ほら、我が家の鍵を渡そう。扱いには気を付けて!」 (数ナノ秒後...) アプリケーション「おい、みんな! 鍵だ! みんな鍵が使えるぞ!」 悪意あるコード「哀れで孤独なビットコイン... 素敵な家を用意してあげよう」 悪意あるコード「この素敵なファイルシステムを見ろよ」
ユーザー「君は信頼できそうだ。ほら、我が家の鍵を渡そう。扱いには気を付けて!」 (数ナノ秒後...) アプリケーション「おい、みんな! 鍵だ! みんな鍵が使えるぞ!」 悪意あるコード「哀れで孤独なビットコイン... 素敵な家を用意してあげよう」 悪意あるコード「この素敵なファイルシステムを見ろよ」

私たちコミュニティには選択肢があります。WebAssembly エコシステムがデフォルトでセキュア (secure by default) な設計を採用すれば、少なくとも解決策を提供できます。もしそうしなければ、WebAssembly は問題を悪化させるでしょう。

WebAssembly のエコシステムが成長するにしたがって、この問題を解決する必要が生じました。これは一人で解くには大きすぎる問題であり、ここで Bytecode Alliance が登場します。

WebAssembly「ユーザーのために立ち上がるぞ!」 vs. WebAssembly「忙しいんだよね。ユーザーは自分で身を守れるでしょ?」 赤字「私たちの選ぶ未来」
WebAssembly「ユーザーのために立ち上がるぞ!」 vs. WebAssembly「忙しいんだよね。ユーザーは自分で身を守れるでしょ?」 赤字「私たちの選ぶ未来」

What

Bytecode Alliance は産業パートナーシップを設立するために集まった企業と個人のグループです。

私たちは協力して、信頼できないコードを利用するための強固でセキュアな基盤の構築に取り組みます。コードの実行場所は問題にしません クラウド、ネイティブなデスクトップ、あるいは小さな IoT デバイスだって考えます。

この基盤があれば、現在と同じオープンソースを使った開発を今と変わらない効率で行いつつも、ユーザーをリスクに晒さずに済みます。

この再利用可能な共通基盤は独立して利用できますが、他のライブラリやアプリケーションに組み込んで使うこともできます。

現在私たちは次のプロジェクトに取り組んでいます:

ランタイム

ランタイムのコンポーネント

言語ツール

このプロジェクトのリストが Bytecode Alliance の拡大と共に大きくなることを期待しています。

私たちメンバーは WASI の標準化および Rust to WebAssembly working group も率いています。

Who

Bytecode Alliance の設立メンバーは Mozilla, Fastly, Intel, Red Hat です。

簡単なガバナンス構造を作り始めており、少しずつ時間をかけて正式なものにする予定です。詳細は FAQ を参照してください。

前述の通り、この問題は単独で取り組むには大きすぎます。そのため Bytecode Alliance は新しいメンバーを歓迎しています。もし加わりたいのであれば、hello@bytecodealliance.org にメールしてください。

私たちが Bytecode Alliance を重要だと思う理由をあげます:

WebAssembly はウェブを変革しています。WebAssembly がブラウザ外への拡張を続ければ、ソフトウェアエコシステムにおいてさらに大きな役割を担えるようになると信じています。今は新たな技術が誕生するまたとない機会であり、壊れた部分を修正し、デフォルトでセキュアなネイティブ開発の新基盤をポータブルでスケーラブルに構築するチャンスが我々にはあります。それを正しく行うには、産業をまたいだ慎重な協議が必要です。Mozilla は Bytecode Alliance のパートナーと協力して、小さな組み込みデバイスから大きなコンピューティングクラウドまでの全てに対するセキュアな新基盤の構築を行います。

— Luke Wagner, Distinguished Engineer at Mozilla and co-creator of WebAssembly

Bytecode Alliance をコミュニティに紹介できることを Fastly は非常に嬉しく思います。Lucet と Cranelift は数年にわたって協力しながら開発されており、今回その関係を正式化し、お互いの成長を加速できると期待しています。これはコンピューティングの歴史における重要な瞬間であり、クライアント、オリジン、エッジをまたいだソフトウェアの構築方法を再定義するチャンスです。Bytecode Alliance を通して私たちはコミュニティと協力し、これからのインターネットの基盤を構築します。

— Tyler McMullen, CTO at Fastly

Intel は Bytecode Alliance に設立メンバーとして参加し、ブラウザを超えた幅広いアプリケーションとサーバーに WebAssembly のパフォーマンスとセキュリティを届ける手助けを行います。Bytecode Alliance が提供する技術を使えば、開発者は最先端の計算プラットフォームが持つ全ての機能を利用して様々な言語からソフトウェアを拡張できます。

— Mark Skarpness; VP, Intel Architecture, Graphics, and Software; Director, Data-Centric System Stacks

オペレーティングシステム、ブラウザ、オープンハイブリッドクラウドにおけるコンピューティングの基盤の提供においてオープンソースの技術が持つ役割に、Red Hat は大きな期待を寄せています。wasmtime は WebAssembly をブラウザの外に連れ出しサーバー空間で利用するための注目すべき進歩であり、私たちはこれを使ってアプリケーションの信頼モデルを変更する実験を行っています。wasmtime をコミュニティベースの成熟したプロジェクトに成長させる手助けができることを嬉しく思います。

— Chris Wright, senior vice president and Chief Technology Officer at Red Hat

つまり、これはビッグニュースなのです! 🎉

私たちが作っているものについてもっと知りたいのであれば、どうぞ読み進めてください。

問題

ソフトウェアを構築する方法は過去 20 年で大きく変化しました。2003 年には、企業は開発者にコードを再利用させようと苦労していました。

それが今では、コードベースの平均 80% は JavaScript の npm, Python の PyPI, Rust の crates.io といったレジストリからダウンロードされたものです。C++ でさえ合成可能なモジュールのエコシステムの作成に動き出しています。

平均的なコードベースの組成: 20% はあなたのコード、 80% は他人のコード
平均的なコードベースの組成: 20% はあなたのコード、 80% は他人のコード

この新たなアプリケーション開発手法により、産業全体として私たちはずっと効率的になりました。しかし一方で、大きなセキュリティホールが表れました。先ほど話した通り、悪いヤツらはこの穴を使ってユーザーを攻撃しています。

ユーザーは家の鍵の管理を開発者に任せるのですが、開発者がそれをアメか何かのように誰にでも渡してしまうのです... これは私たちが責任感に欠けているためではありません。そうしてしまう理由は、パッケージに大きな価値があるにもかかわらず、パッケージと共にやってくるセキュリティリスクを和らげる簡単な方法がないためです。

さらに、話に加わるのは自身の依存モジュールだけではありません。依存モジュールが依存するモジュール 間接依存モジュール もあります。

アプリケーション「城への鍵を渡そう。コピーして自分の依存モジュールにも渡すんだ」 赤字 (上から)「あなたのコード」 「依存モジュール (あなたのコードもしくは他人のコード)」
アプリケーション「城への鍵を渡そう。コピーして自分の依存モジュールにも渡すんだ」 赤字 (上から)「あなたのコード」 「依存モジュール (あなたのコードもしくは他人のコード)」

開発者は何に対するアクセスを得るのでしょうか?

  1. マシン上のリソース ファイルやメモリなど

  2. API とシステムコール マシン上のリソースを使った処理のためのツール

「システムリソース」 「ホストによって提供される API とシステムコール」
「システムリソース」 「ホストによって提供される API とシステムコール」

これは依存モジュールがユーザーに重大な被害を与えられることを意味します。悪意あるコードが意図的に行う場合もあれば、脆弱性のあるコードが全く無意識のうちに行う場合もあるでしょう。

こういった攻撃の動作について見ていきます。

悪意あるコード

悪意あるコードは攻撃者自身によって書かれます。

攻撃者はたいていソーシャルエンジニアリングを使ってパッケージをアプリケーションに組み込みます。つまり便利な機能を持ったパッケージを作成し、その後に悪意あるコードを忍び込ませるのです。そのコードがアプリケーションに入り込みユーザーがそれを起動すると、ユーザーに対する攻撃を行います。

悪意あるモジュール「あのファイルシステムを見てやろう...」
悪意あるモジュール「あのファイルシステムを見てやろう...」

あるハッカーが 150 万ドル相当の仮想通貨をユーザーから盗んだ (そして 1300 万ドルを保存するウォレットを手中に収めたかけた) ときに使ったのがこの手法です。犯行は今年の三月に始まりました

悪意あるコードが攻撃を成功させるために必要だったアクセスを考えます。

攻撃にはシードが必要です。シードの入手するために、悪意あるコードはシードを保存したメモリへのアクセスを取得しました。

それから悪意あるコードはシードをサーバーに送信しました。このためにはソケットへのアクセス、そしてソケットを開くシステムコールへの API またはシステムコールが必要です。

「シード変数を保持するメモリ」「ネットワークソケット」「<code>open</code>」
「シード変数を保持するメモリ」「ネットワークソケット」「open

悪意あるコードによる攻撃は急増しており、私たちが使う信頼システムの脆弱さに多くの攻撃者が気付き始めています。例えば npm に公開された悪意あるモジュールの数は 2017 年から 2019 年にかけて倍増しました。npm のセキュリティ部門の VP は攻撃が深刻なものになっていると指摘します:

2019 年には利益目当ての攻撃が増加しています。初期の攻撃は嫌がらせが主で、ファイルを削除したり極秘情報を盗んだりするものでした。現実世界における「押し入って取れるだけ取る」スタイルの単純な攻撃です。しかし現在ではエンドユーザーは攻撃目標であり、[攻撃者は]辛抱強く、誰にも気が付かないように、入念に練られた攻撃計画を持つようになっています。

脆弱なコード

脆弱性は別の問題です。モジュールの管理者が悪事を働くわけではありません。

モジュールの管理者は自身のコードにバグを入れるだけです。しかし攻撃者がそのバグを利用すれば、すべきでないことをコードに行わせることができます。

攻撃者「よーよー、ちょっと手を貸してくんない?」 脆弱なモジュール「あなたが誰かは知りませんが、了解です! 何をすべきか教えてください!」
攻撃者「よーよー、ちょっと手を貸してくんない?」 脆弱なモジュール「あなたが誰かは知りませんが、了解です! 何をすべきか教えてください!」

脆弱性の例として、ZipSlip を取り上げます。

ZipSlip と呼ばれる脆弱性を持つモジュールは様々なエコシステムで見つかりました: JavaScript, Java, .NET, Go,Ruby, C++, Python はその一部です。HP, Amazon, Apache, Pivtal を含む数千のプロジェクトが影響を受けました。

モジュールがこの脆弱性を持っている場合、攻撃者はファイルシステムの任意の場所にあるファイルの置き換えが可能です。例えば node_modules ディレクトリにある .js ファイルを置き換えれば、その .js ファイルが必要とされたときに攻撃者のコードが実行されるようにできます。

攻撃者「アップロードした zip アーカイブを解凍してほしいんだけど」 脆弱なモジュール「了解です。すぐにファイルシステム上にファイルを配置します!」
攻撃者「アップロードした zip アーカイブを解凍してほしいんだけど」 脆弱なモジュール「了解です。すぐにファイルシステム上にファイルを配置します!」

これはどのように動作するのでしょうか?

攻撃者は zip アーカイブ内に相対パスを表す ../ を含んだ名前を付けたファイルを作成します。脆弱なコードはこのファイルを解凍するときにファイル名を正規化しません。その結果 write システムコールが相対パスに対して呼ばれ、攻撃者の意図したファイルシステム上の場所にファイルが配置されることになります。

攻撃者にこの動作を許したのは、脆弱なコードのどのアクセスだったのでしょうか?

まず、重要なファイルを含んだディレクトリへのアクセスが脆弱なコードに必要です。

それから write システムコールへのアクセスも必要です。

脆弱性もまた増加中であり、過去二年で 88% 増加しました。2019 State of Open Source Security Report で Synk が行った報告によると:

2018 年、npm の脆弱性は 47% 増加した。Maven Central と PHP Packagist における脆弱性の発覚はそれぞれ 27% と 56% 増加した。

こういったリスクがあるなら、脆弱性をパッチする作業の優先度が高いはずだと思うかもしれません。しかし Snyk の発見によると、発覚した脆弱性に対する修正が知られているパッケージは 59% のみで、その理由はメンテナに時間がないため、あるいは脆弱性を修正するだけのセキュリティのノウハウがメンテナにないためです。

これにより脆弱性が広まりました。脆弱なモジュールに依存するモジュールがあるからです。例えば npm モジュールに関する研究では、最大で 40% のパッケージが少なくとも一つのパブリックな脆弱性を持つコードに依存していることが明らかになりました。

現在のユーザーをどう守るか

現在のソフトウェアエコシステムが持つこういった脅威からユーザーを守るには、何ができるでしょうか?

勝ち筋はないように思えます。全てを諦めて何も起こらないのを願いたくなります。しかし産業として私たち全員がそうすることはできません。ユーザーにとって運任せな部分が大きすぎます。

上述した解決策はどれも悪意あるコードや脆弱なコードを検出しようとしています。しかし問題を別の視点から観察したら?

問題となったものの中に、モジュールが持つアクセスがありました。このアクセスを取り去ったら?

この種の問題は産業として過去に経験しています... ただし違う視点で。

あるコンピューター上で同時に実行される二つのプログラムがあったとき、片方がもう一方を邪魔しないことはどのように保証されるのでしょうか? プログラムが礼儀正しく振る舞うことを信頼しなければならないのでしょうか?

違います。保護機構がオペレーティングシステムに組み込まれています。OS がプログラムをお互いに保護するのに使うツールは、プロセスです。

プログラムを開始すると、OS は新しいプロセスを起動します。各プロセスはメモリの一部分を自分のものとして受け取り、他のプロセスのメモリにはアクセスできません。

他のプロセスのデータが必要な場合には明示的なリクエストが必要であり、これを受けてデータがプロセス境界を越えて送信されます。これにより全てのプログラムが自身のメモリにあるデータを制御することになります。

(上から) 利点「メモリが独立する」 欠点「データの共有には、シリアライズ...プロセス境界を越えたデータの送信...デシリアライズが必要になる」 左のプロセス「おーい!データを送ったぞ!」 右のプロセス「OK!」
(上から) 利点「メモリが独立する」 欠点「データの共有には、シリアライズ...プロセス境界を越えたデータの送信...デシリアライズが必要になる」 左のプロセス「おーい!データを送ったぞ!」 右のプロセス「OK!」

このメモリの独立性により、二つのプログラムの同時実行がはるかに安全になります。しかしこのセキュリティは完璧ではありません。悪意あるプログラムは他の種類の資源、例えばファイルシステム上のファイルをめちゃくちゃにできます。

VM やコンテナがこの問題を修正するために開発されました。VM やコンテナで実行されるものは他の VM やコンテナが持つファイルシステムにアクセスできません。さらにサンドボックスを使えば、API やシステムコールを奪うこともできます。

ではパッケージを独立した単位にいれたら? それぞれにサンドボックス化された小さなプロセスを用意したら?

そうすれば私たちの問題は解決するでしょうが、新たな問題が生まれます。

こういったテクニックはどれも比較的重い処理です。数百のパッケージそれぞれをサンドボックス化されたプロセスで包めば、すぐにメモリが足りなくなるでしょう。さらに異なるパッケージをまたいだ関数呼び出しはずっと複雑で低速になります。

ここで新しい技術が新しい選択肢を可能にします。

WebAssembly エコシステムを新たに構築する私たちは、プロセスやコンテナによる独立性をその欠点なしに取り入れるようパーツの組み合わせ方を設計できます。

明日の解決法: WebAssembly の「ナノプロセス」

WebAssembly は信頼できないコードの実行を安全にする分離機構を提供できます。つまりマイクロサービスやコンテナ、あるいは Unix が小さなプロセスをたくさん持つのと同じようなアーキテクチャです。

しかしこの分離機構はずっと軽量で、分離されたもの同士の通信が通常の関数呼び出しよりひどく低速になることはありません。

つまりこの分離機構を使えば単一の WebAssembly モジュールインスタンス、あるいはメモリを共有させたいいくつかのモジュールインスタンスの集まりをラップできます。

さらに、プログラミング言語が持つ素敵な機能を諦める必要もありません。関数シグネチャや静的型検査は通常通り使えます。

プロセス「高いオーバーヘッド、低速な通信」 ナノプロセス「少ないオーバーヘッド、高速な通信」
プロセス「高いオーバーヘッド、低速な通信」 ナノプロセス「少ないオーバーヘッド、高速な通信」

では、これはどのように動作するのでしょうか? WebAssembly の何が、これを可能にするのでしょうか?

まず、WebAssembly モジュールはデフォルトでサンドボックス化されるという事実があります。

デフォルトではモジュールは API やシステムコールへのアクセスを持ちません。モジュール外の何かとやり取りをしたい場合には、モジュールにその関数やシステムコールを明示的に提供する必要があります。そうして初めてモジュールはそれを呼び出せます。

エンジン「ほら、<code>getrandom</code> を使ってもいいぞ... だが皆の安全のため、君に与えるのはこれだけだ」
エンジン「ほら、getrandom を使ってもいいぞ... だが皆の安全のため、君に与えるのはこれだけだ」

次に、メモリモデルがあります。

x86 などに直接コンパイルされた通常のバイナリとは異なり、WebAssembly モジュールはプロセスのメモリ全てへのアクセスを持ちません。アクセスできるのは割り当てられた部分のメモリだけです。

理論上は、スクリプト言語もこの種の分離機構を提供します。スクリプト言語のコードはプロセスのメモリに直接はアクセスできず、スコープ上にある変数を通してのみメモリにアクセスできるからです。

しかし多くのスクリプト言語エコシステムにおいて、コードは共有されたグローバルオブジェクトを多用します。事実上これは共有メモリと変わりません。つまりエコシステムの規約により、メモリの分離という問題がスクリプト言語にも表れます。

WebAssembly もこの問題を抱えていた可能性もあります。WebAssembly の初期には、全てのモジュールに共有メモリを渡す規約を作るべきだという意見が出たことがありました。しかしコミュニティグループは、メモリをデフォルトでカプセル化するよりセキュアな規約を選択しました。

この機能により二つのモジュールの間でメモリが分離されます。これは悪意あるコードが親モジュールのメモリをめちゃくちゃにできないことを意味します。

「メモリの分離」
「メモリの分離」

ではモジュールが持つデータの共有はどうするのでしょうか? 関数呼び出しの値としてやり取りする必要があります。

しかしこのやり方には問題があります。デフォルトでは WebAssmbly はいくつかの数値型しか持たないので、数字を一つずつ渡すことしかできません。

モジュール「数字は渡せるけど... データはどうやって渡そうか?」
モジュール「数字は渡せるけど... データはどうやって渡そうか?」

ここで三つ目の機能が登場します。私たちが 8 月にデモを示した interface type proposal です。インターフェース型 (interface types) を使うと、モジュール同士がより複雑な値を通信できるようになります。例えば文字列、シーケンス、レコード、そしてこれらをネストした型の値です。

この方法を使えば、二つのモジュールはセキュアで高速にデータをやり取りできます。WebAssembly エンジンは呼び出し側と呼び出された側のメモリを直接コピーでき、データのシリアライズとデシリアライズは必要ありません。さらに二つのモジュールが異なる言語からコンパイルされていたとしても動作します。

モジュール「エンジンさん、インデックス 1 から始まる長さ 3 の文字列を渡してもらえませんか?」 エンジン「了解! メモリ上の他の場所にそのままコピーするだけだ」 赤字「高いレベルの値のモジュールをまたいだやり取り」
モジュール「エンジンさん、インデックス 1 から始まる長さ 3 の文字列を渡してもらえませんか?」 エンジン「了解! メモリ上の他の場所にそのままコピーするだけだ」 赤字「高いレベルの値のモジュールをまたいだやり取り」

このようにして、悪意あるコードはアプリケーションに含まれる他のモジュールのメモリに対して悪さを働けなくなります。

しかし、用心すべきはモジュールによるメモリの扱いだけではありません。アプリケーションがデータを使って何かするときには、API やシステムコールを呼び出すからです。こういった API やシステムコールはファイルシステムと同様に共有リソースへのアクセスを持つかもしれません。前にお話しした通り、多くのオペレーティングシステムが持つファイルシステムのアクセス管理は、私たちが必要としているセキュリティに全く達していません。

つまり異なるリソースに対する異なる権限を異なるモジュールに与えるためには、権限の概念が埋め込まれた API やシステムコールが必要です。

ここで登場するのが四番目の機能 WebAssembly system interface (WASI) です。

モジュールを互いに分離し、それぞれにファイルシステムなどのリソースの特定部分に対する細かい権限、および異なるシステムコールに対するきめ細かい権限を与える手段を WASI は提供します。

「細かい権限モデルを持った API」
「細かい権限モデルを持った API」

WASI があれば、私たちが鍵を管理できます。

何が足りないでしょうか?

今のままでは、この鍵を依存ツリーに渡せません。親のモジュールが依存モジュールに鍵を渡す方法が必要です。これがあれば必要とする鍵だけを子に渡し、それ以外は一切渡さないようにできます。それから依存モジュールも自身の子について同じことを行い、ツリーに含まれる全てのモジュールが鍵を手にします。

私たちが次に取り組んでいるのがこれです。専門用語を使うと、私たちはモジュールごとの仮想化 (per-module virtualization) をさらに細かくした形で使うことを計画しています。これは研究者によって示されているアイデアであり、私たちはこれを WebAssembly に導入する作業を進めています。

「君にはシステムコール <code>open</code> とディレクトリのアップロード用の鍵を... そして君にはシステムコール <code>getrandom</code> 用の鍵を」
「君にはシステムコール open とディレクトリのアップロード用の鍵を... そして君にはシステムコール getrandom 用の鍵を」

まとめると、こういった機能によりプロセスと似たような分離機構がずっと低いオーバーヘッドで手に入ります。この利用パターンを、私たちは WebAssembly ナノプロセス (nanoprocess) と呼んでいます。

ナノプロセスはただの WebAssembly に過ぎませんが、特定のパターンに従っています。このパターンをツールと規約に組み込めば、WebAssembly におけるサードパーティコードの再利用を安全にできます。これは今までのどんなエコシステムも達成できていない安全性です。

注記: 現時点では全てのナノプロセスがちょうど一つの wasm モジュールからなりますが、将来的には複数の wasm モジュールを含むナノプロセスのツールチェインサポートにも注力する予定です。これによりネイティブスタイルの動的リンクがナノプロセスのメモリ分離機構を保ったまま可能になります。

こういった基盤が手に入れば、開発者は依存ツリーに含まれるパッケージを分離し、それぞれに専用のナノプロセスを持たせられるようになります。

ナノプロセスによるユーザーの保護

ナノプロセスはどのようにユーザーを保護するのでしょうか?

二つのケースの両方において、私たちをセキュアに保っているのは最小権限の原則 (principle of least privilege)です。この原則がどのように役に立つのかを見ていきましょう。

悪意あるコード

攻撃を成功させるには、モジュールからは通常アクセスする必要のないリソースや API に対するアクセスがたいていは必要になります。私たちのアプローチでは、モジュールがそういったものにアクセスするときにはそのことを宣言しなくてはならないので、すべきでないことに対する要求を簡単に検出できます。

例えば先述のウォレットを盗むモジュールは、シードを送信するサーバーへのソケットとネットワーク接続を開く機能を必要とします。

しかしこのモジュールの機能は受け取ったテキストを表示するシステムに渡すだけです。そんなモジュールがリモートサーバーへの接続を開くなんてあり得ません。

もし electron-native-notify がそのような権限を最初に要求したなら、それは深刻な事態を示す赤い旗です。EasyDEX-GUI のメンテナはそのようなモジュールを依存ツリーに入れないでしょう。

間接的な依存モジュールになろうとしている悪意あるモジュール (例えば electron-native-notify)「ヘイ! 俺も入れてくれよ! ソケットと <code>open</code> システムコールのアクセスだけくれればいいんだ!」 直接の依存モジュール (例えば EasyDEX-GUI)「待って、なんで? そんなアクセス私も持ってないよ! アクセスを要求しないといけないけど、そんなことしないよ」 一番上がターゲットのアプリ (例えば Agama wallet)
間接的な依存モジュールになろうとしている悪意あるモジュール (例えば electron-native-notify)「ヘイ! 俺も入れてくれよ! ソケットと open システムコールのアクセスだけくれればいいんだ!」 直接の依存モジュール (例えば EasyDEX-GUI)「待って、なんで? そんなアクセス私も持ってないよ! アクセスを要求しないといけないけど、そんなことしないよ」 一番上がターゲットのアプリ (例えば Agama wallet)

仮に悪意あるメンテナが EasyDEX-GUI に組み込まれた後にアクセスの要求を追加したら、それは破壊的な変更となります。モジュールのシグネチャが変更され、モジュールが期待するインポートやパラメータを呼び出し側のコードが提供しないに場合は WebAssembly がエラーを出します。

新しいリソースやシステムコールを気付かれずに忍び込ませる方法はありません。

必要なアクセスを取得しようとしている悪意ある間接依存モジュール「アップデートの時間だ! ソケットと <code>open</code> システムコールのアクセスをくれ!」 親モジュール「それはおかしい。変更を見てみよう...」
必要なアクセスを取得しようとしている悪意ある間接依存モジュール「アップデートの時間だ! ソケットと open システムコールのアクセスをくれ!」 親モジュール「それはおかしい。変更を見てみよう...」

つまりサーバーとの通信に必要な基本的なアクセスをメンテナが入手する可能性は低くなります。しかしもしこのメンテナが何らかの方法でアプリの開発者を欺いてアクセスを得たとしても、シードを取得するのは非常に困難です。

なぜなら、シードが他のモジュールのメモリにあるからです。他のモジュールのメモリにある値に対するアクセスを得る方法は二つありますが、どちらも悪意からコードから行えることではありません:

  1. シードを持つモジュールがシード変数をエクスポートし、その値をアプリ内の全てのモジュールから見えるようにする

  2. シードを持つモジュールが悪意あるモジュールの関数へのパラメータとしてシードを直接渡す

シード値ほどに重要な変数に対しては、どちらもまずあり得ません。

シード変数を覗き見ようとしている悪意ある間接依存モジュール「くそ! シードを見るにはどうすれば...」 右にあるのがシード変数を持ったモジュール
シード変数を覗き見ようとしている悪意ある間接依存モジュール「くそ! シードを見るにはどうすれば...」 右にあるのがシード変数を持ったモジュール

残念ながら、攻撃者が他のモジュールのメモリにアクセスする手の込んだ手法が存在します Spectre のようなサイドチャネル攻撃です。OS プロセスはタイムプロテクション (time protection) を取り入れようとしており、これを使えばそういった攻撃に対する保護を得られます。しかし CPU と OS はプロセスよりも粒度の細かいタイムプロテクションの機能を提供しません。

コードを保護するためにプロセスを使うなど聞いたことがないかもしれません。多くの人が気にする必要がないのはなぜでしょうか? それは、ホスティングプロバイダがときには複雑な技術を使って処理してくれるからです。

分離したプロセスを使ってコードを設計しているなら、まだそうしておく必要があるかもしれません。ナノプロセスへの移行には慎重な解析が必要です。

しかしタイムプロテクションが必要でない、あるいは現在プロセスをそもそも使っていないという状況も多くあります。こういったケースはナノプロセスを使うのに適しています。

加えて、CPU が進化してタイムプロテクションの機能が簡単に使えるようになれば、WebAssembly のナノプロセスはその機能を素早く利用できる立場となります。そうなれば OS プロセスを使う必要はなくなるでしょう。

脆弱なコード

この保護はどのようにして脆弱性の悪用を防ぐのでしょうか?

悪意あるモジュールのときの同様、脆弱なモジュールが攻撃に必要なアクセスを全ての持つことは常識的に考えてあり得ません。

ZipSlip の例を考えると、ファイルへの書き込みが可能な危険なシステムコール write へのアクセスが脆弱なモジュールを普通に使う場合でさえ必要です。

しかし普通に使う限り node_modules ディレクトリへのアクセスは必要ではありません。アクセスを持つのは親がそのモジュールに渡したディレクトリだけであり、それは zip ファイルが解凍されるディレクトリです。

そのため脆弱なモジュールが攻撃者の意図に沿った動作をすることはありません (親が脆弱なモジュールに重要なディレクトリを故意に渡さない限り)。

ZipSlip 脆弱性を持った依存モジュール「申し訳ないけど、そのフォルダは持ってないんだ。親がくれたのは <code>uploads</code> ディレクトリだけだから」
ZipSlip 脆弱性を持った依存モジュール「申し訳ないけど、そのフォルダは持ってないんだ。親がくれたのは uploads ディレクトリだけだから」

なお括弧に書いた通り、アプリケーション開発者であればルートディレクトリのような重要なディレクトリをモジュールに渡せます。しかしこの状態でアプリケーションが動作するには、このミスがトップレベルのパッケージで起こる必要があります。依存モジュールでミスが起きた場合は動作しません。こういったアクセスを全て明示的にすれば、この種のありがちなミスをレビューで発見できます。

ナノプロセスが持つその他の利点

ナノプロセスを使えば高度にモジュール化されたアプリケーションの作成をこれからも続けられます... 開発者の効率は犠牲にならず、ユーザーの安全も保障されます。

ナノプロセスは他に何の役に立つのでしょうか?

どこでも使える分離機構

ナノプロセス 極小のコンテナのようなもの は、通常のプロセス、コンテナ、VM が対応できないようなあらゆる場所で利用できます。

パッケージとパッケージのグループのそれぞれを極小のミクロなプロセスにラップできているのもこれが理由です。ナノプロセスは非常に小さいので、アプリケーションのサイズを増加させません。しかし、ナノプロセスが利用できるのは依存ツリーだけではありません。

ナノプロセスの利用例をいくつかあげます。どれも Bytecode Alliance のパートナーが取り組んでいるものです。

数万人の顧客からのリクエストを同じマシンで処理する

Fastly はインターネットのトラフィックの 13% を処理しています。数百万のリクエストに応えており、顧客をセキュアに保ちながらも可能な限り少ないオーバーヘッドが求められます。

彼らは WebAssembly のナノプロセスを使った革新的なアーキテクチャを開発し、同じプロセスに属し同時実行される数万ものプログラムのセキュアなホストに成功しました。彼らのアプローチはリクエスト同士を完璧に隔離し、完全な VM 隔離を保証します。さらにコールドスタートを利用した桁違いの高速化も達成しました。

ネイティブアプリケーションの個別ライブラリで発生するソフトウェア障害を隔離する

一部のアプリケーションではコードのほとんどがインハウスで書かれ、いくつかのライブラリだけが外部の信頼できない開発者によって書かれます。

そのような場合には、ナノプロセスを使って単一のライブラリに対するプロセス内軽量サンドボックスを作成できます。これは依存ツリーの全てをナノプロセスでラップするのとは異なります。

このタイプのサンドボックスは Firefox で作成が始まっています。これはフォントレンダリングエンジンや画像とオーディオのデコーダーといったサードパーティライブラリで発生するバグからユーザーを守ります。

ソフトウェアの合成可能性を高める

数十年の間に、ソフトウェア開発のベストプラクティスはレゴのような小さな部品を組み合わせるコンポーネント化されたアーキテクチャに向かって進んできました。

最初、ソフトウェアは同じ言語で書かれ同じプロセスで実行される上述のようなライブラリとモジュールを使っていました。最近になってサービスあるいはマイクロサービスという考え方が生まれ、分離による高いセキュリティが可能になりました。

セキュリティの他にも、サービスの分離によりソフトウェアがより合成可能 (composable) になります。つまり異なる言語、あるいは同じ言語の異なるバージョンで書かれたサービスを組み合わせることができ、ずっと多くのレゴブロックが使えるようになります。

しかしこういったサービスも、ライブラリが役に立つ全ての場所で活躍できるわけではありません。大きすぎるからです。サービスはサーバーが実行するコンテナの中で実行されるプロセスで実行されることが多く、そのような場合にはアプリをサービスに分けるときに目の粗いアプローチを取らなければなりません。

wasm を使えばマイクロサービスをナノプロセスに置き換え、さらに同じセキュリティと言語非依存性を得られます。つまりマイクロサービスの合成可能性がその重荷なしに手に入ります。マイクロサービススタイルのアーキテクチャとその言語の相互運用を利用し、さらにサービスのコンポーネントの定義には目の細かいアプローチを取ることが可能です。

Join us

以上が私たちの考えるユーザーにとって安全な未来のビジョンです。WebAssembly はこういったセキュリティを提供するチャンスなだけではなく、提供する責任が私たちにはあると考えています。

そして、あなたが参加することを願っています!

Lin Clark について

Lin は Mozilla の Advanced Development で働いており、Rust と WebAssembly にフォーカスしています。

広告