Firefox: W^X JIT-code が有効になりました

jandemooij.nlで 2015 年 12 月 29 に公開された、W^X JIT-code enabled in Firefox の抄訳です。

6 月にさかのぼりますが、SpiderMonkey の JIT コードに W^X 保護を有効にするオプションを追加しました。過去数週間にわたって、パフォーマンスに関する問題の修正を行い、昨日すべてのプラットフォームの Nightly でこの機能を標準で有効にしました。これによって、JIT コードを持つメモリ上のページは、実行可能であるか、書き込み可能であるかのどちらか一方になります。両方のフラグが立つことはありません。

この変更をいれた理由

ほとんどすべての JIT コンパイラ、そして昨日までの Firefox は、コード用のメモリを RWX (read-write-execute) フラグをつけてアロケートします。JIT はその特質として、インラインキャッシュのように動いているコードを修正することがあります。あらかじめメモリを書き込み可能としておくことで、パフォーマンス低下を引き起こさずにコード修正を行えます。しかし RWX メモリは以下にあげるような問題の遠因となります:

  • セキュリティ:RWX ページは、ある種のバグを利用した攻撃の難易度を下げてしまいます。そのため、モダンなオペレーティングシステムは実行コードが格納されているメモリ領域を、実行可能 / 書き込み不可能に設定します。一方データ用の領域は通常、実行不可能に設定されます。このあたりの詳細については W^X もしくは DEP を参照してください。RWX JIT コードは、このルールの例外となっており、そのため興味深い攻撃対象となっています。
  • メモリ破壊:他の場所のメモリ破壊に起因する、JIT コード内でのクラッシュのメモリダンプを解析したことがあります。メモリ破壊を引き起こすバグはすべて深刻ですが、その原因がなんであれ、メモリ破壊が発生したら直ちにクラッシュするほうが良いとされているバグでもあります。

W^X 保護の動作

W^X が有効になっている場合、JIT コードを格納するすべてのページは標準で書き込み不可能となります。格納されている JIT コードを修正する場合には、RAII クラスである AutoWritableJitCode を利用して、書き込み可能(RW)なページを作成します。Windows では VirtualProtect を、それ以外のプラットフォームでは mprotect を利用して実現しています。修正後、AutoWritableJitCode オブジェクトのデストラクタが、ページを RW から RX に戻します。

ちなみに、W^X に代わる手段としては、デュアルマッピングがあります。この方式では、ページはまず RW にマップされ、その後 RX にマップされます。2010 年に、何人かが TraceMonkey のために、この手法を実装し、パッチを作成しました。しかしパッチが取り込まれることはありませんでした。この方式は mprotect のオーバヘッドを回避できますが、安全性を保つために RW マッピングは異なるプロセスで実行される必要があります。複雑で、IPC によるオーバーヘッドもあるため、取り込まれませんでした。

性能

先週、W^X を動作させる際に行われていた暗黙的な割り込みチェックに関する問題を修正し、不要な mprotect の呼び出しを削除し、W^X の性能を低下させていたコードの最適化をおこないました。

この結果ベンチマークや、テストを行ったサイトで W^X による性能低下は極めて小さくなりました。Lraken や Octane ではオーバーヘッドは 1% 以下でした。昔の SunSpider でのオーバーヘッドはより大きくなりました。これはほとんどのテストが数ミリ秒で終わるためコンパイル時間による影響が大きいためです。それでも Windows と Linux では 3% 以下、OS X では 4% 以下となっています。OS X の方が遅いのは、mprotect が他のプラットフォームと比較して遅いためです。

W^X 保護が SpiderMonkey にうまく組み込めたのは、いくつかの理由があると考えています:

  • SpiderMonkey はベースラインコンパイルを行う前に、バイトコードを解釈するインタプリタモードで動作します。Web の場合、ほとんどの関数の呼び出し回数は 10 回未満です。これらの関数は JIT コンパイルされることがないため、メモリ保護のためのオーバヘッドも存在しません。
  • ベースライン JIT はほとんどの演算に IC スタブを利用します。これは間接的に呼ばれるため、スタブをアタッチする際にコード領域を書き込み可能にする必要はありません。またスタブはコードを共有するため、コンパイルを行うのは最初に特定のスタブをアタッチする時のみです。Ion IC スタブを利用するためにはメモリが書き込み可能でなければなりませんが、ベースラインコンパイル時に Ion はほとんど IC を利用しません。
  • asm.js に関しては(そしてすぐに WebAssembly も!)、モジュール全体が AOT コンパイルされます。コンパイル後、コード領域の全てを RW から RX に変更するために 1 度だけ mprotect が呼ばれます。さらに低速の実行パスを通る時にのみコード変更が行われるため、基本的に asm.js / WebAssembly に関しては性能上のオーバヘッドはありません。

結論

Firefox Nightly で動作する JIT 用のコードで、W^X 保護を行うようになりました。バグや深刻な性能低下がないと思われるため、Firefox 46 に組み込まれる予定です。

最後に、私たちに先だって勇敢にも W^X 保護を導入したOpenBSD と HardenedBSD のチームに感謝します!

3 件のコメント

  1. Pingback from 「Firefox 46」がリリース – セキュリティと安定性が改善 on :

    […] Firefox 46は追加された新機能が2つだけという割と小規模なアップデートとなっていて、具体的な新機能としてはW^X JIT-codeの有効化によるJavaScriptのJust In Time (JIT)コンパイラのセキュリティ面での改良と、GNU/Linux環境におけるGTK3の統合があげられています。 […]

  2. Pingback from Firefox 46アップデートでは、パフォーマンスとセキュリティの改善及びAndroid Honeycombをサポート on :

    […] JavaScript の Just In Time (JIT) コンパイラがセキュリティの面で改良されました […]

  3. Pingback from MozillaZine.jp » Blog Archive » Firefox 46 がリリースされた on :

    […] JavaScript の Just In Time (JIT) コンパイラの セキュリティの面の改良 […]