V8:Spectreで1年

2018年1月3日に、Google Project Zeroなどは、投機的実行プロセッサに影響する新しいクラスの脆弱性の最初の3つを明らかにしました 。 それらはSpectre (1および2)およびMeltdownと呼ばれていました。 投機的な CPU 実行メカニズムを使用して、攻撃者は明示的および暗黙的なソフトウェアセキュリティチェックの両方を一時的にバイパスし、プログラムがメモリ内のアクセスできないデータを読み取れないようにします。 投機的実行は、アーキテクチャレベルでは見えないマイクロアーキテクチャの一部として設計されましたが、慎重に設計されたプログラムは、投機的ブロック内のアクセスできない情報を読み取り、プログラムフラグメントの実行時間などのサイドチャネルを通じてそれを明らかにすることができました。



JavaScriptを使用してSpectre攻撃が可能であることが示されたとき、V8チームは問題の解決に参加しました。 緊急対応チームを編成し、他のGoogleチーム、他のブラウザパートナー、ハードウェアパートナーと緊密に連携しました。 彼らと一緒に、攻撃的な研究(概念を証明するための攻撃モジュールの構築)と防御的な攻撃(潜在的な攻撃の緩和)の両方を積極的に実施しました。



スペクター攻撃は2つの部分で構成されています。





理論的には、攻撃の2つのコンポーネントのいずれかをブロックすれば十分です。 それらを完全にブロックする方法がわからないため、CPUキャッシュに漏洩する情報の量を大幅に削減する緩和策と、隠された状態の復元を困難にする緩和策を開発して展開しました。



高精度タイマー



投機的実行の後に残る状態の小さな変化は、それに対応して、ほとんど不可能に近い、わずか10億分の1秒の時間差を生み出します。 攻撃者は、このような個人差を直接検出するために高精度のタイマーを必要とします。 プロセッサはそのようなタイマーを提供しますが、Webプラットフォームはそれらを設定しません。 performance.now()



Webプラットフォームで最も正確なタイマーの解像度は数マイクロ秒でしたが、当初この目的には適さないと考えられていました。 ただし、2年前、マイクロアーキテクチャ攻撃を専門とする研究グループがWebプラットフォームのタイマーに関する記事を公​​開しまし 。 彼らは、同時可変メモリとさまざまな解像度の回復方法により、ナノ秒までのさらに高い解像度のタイマーを作成できるという結論に達しました。 このようなタイマーは、L1キャッシュの個々のヒットとミスを検出するのに十分正確です。 スペクター攻撃で情報をキャプチャするために通常使用されるのは彼です。



タイマー保護



時間のわずかな違いを検出する機能を混乱させるために、ブラウザ開発者は多国間アプローチを選択しました。 すべてのブラウザーで、 performance.now()



解像度が低下し(Chromeでは5マイクロ秒から100)、解像度の復元を防ぐためにランダムジッターが導入されました。 すべてのブラウザーの開発者間で協議した後、一緒に前例のない手順を踏むことを決定しました。ナノ秒タイマーの作成を防ぐために、すべてのブラウザーでSharedArrayBuffer



APIを直ちに遡及的に無効にします。



ゲイン



攻撃的な調査の最初に、タイマーの軟化だけでは不十分であることが明らかになりました。 理由の1つは、攻撃者がコードを繰り返し実行するだけで、累積時間差が1回のヒットまたはキャッシュミスよりもはるかに大きくなるためです。 一度に多くのキャッシュラインを使用する信頼性の高い「ガジェット」を構築することができました。これは、最大キャッシュ容量までであり、最大600マイクロ秒の時間差を与えます。 その後、キャッシュ容量に制限されない任意の増幅方法を発見しました。 そのような増幅方法は、秘密データを読み取ろうとする繰り返しの試みに基づいています。



JIT保護



Spectreを使用してアクセスできないデータを読み取るために、攻撃者はCPUに、通常アクセスできないデータを読み取ってキャッシュに入れるコードを投機的に実行させます。 保護は2つの側面から検討できます。



  1. 投機的なコード実行を防ぎます。

  2. 投機的パイプラインからアクセスできないデータを読み取ることの防止。


最初のオプションで実験し、各重要な条件分岐からのインテルLFENCE



などの投機を防ぐための推奨命令を挿入し、間接分岐にretpolinsを使用しました 。 残念ながら、このような大幅な緩和により、パフォーマンスが大幅に低下します(Octaneベンチマークで2〜3倍の速度低下)。 代わりに、不適切な投機による機密データの読み取りを防ぐ緩和シーケンスを挿入することにより、2番目のアプローチを取りました。 次のコードスニペットでテクニックを説明します。



 if (condition) { return a[i]; }
      
      





簡単にするために、条件0



または1



を仮定します。 上記のコードは、 i



が範囲外のときにCPU a[i]



から投機的に読み取り、通常はアクセスできないデータにアクセスする場合に脆弱です。 重要な観察結果は、この場合、条件が0



ときに投機a[i]



を読み取ろうとすることです。 緩和策は、元のプログラムとまったく同じように動作するようにこのプログラムを書き換えますが、投機的にロードされたデータの漏洩を許可しません。



コードが誤って解釈されたブランチで実行されているかどうかを追跡するために、「ポイズン」と呼ばれるCPUレジスタを1つ予約します。 ポイズンレジスタは、生成されたコードのすべてのブランチと呼び出しでサポートされているため、誤って解釈されたブランチがあると、ポイズンレジスタは0



になり0



。 次に、すべてのメモリアクセスを測定して、すべてのダウンロードの結果をポイズンレジスタの現在の値で無条件にマスクします。 これは、プロセッサが分岐を予測(または誤って解釈)するのを防ぎませんが、誤って解釈された分岐により、ロードされた値の情報(潜在的に制限外)を破壊します。 ツールコードを以下に示します( a



は数字の配列です)。



 let poison = 1; // … if (condition) { poison *= condition; return a[i] * poison; }
      
      





追加のコードは、プログラムの通常の(アーキテクチャで定義された)動作には影響しません。 投機的実行でCPUを操作する場合にのみ、マイクロアーキテクチャの状態に影響します。 ソースコードレベルでプログラムをインスツルメントする場合、最新のコンパイラの高度な最適化により、そのようなインスツルメンテーションを削除できます。 V8では、コンパイルの非常に遅い段階でコンパイラーが挿入することにより、軽減策を削除しないようにします。



また、このポイズニング手法を使用して、インタープリターのバイトコードループおよびJavaScript関数の呼び出しシーケンスでの間接分岐からのリークを防ぎます。 インタープリターでは、バイトコードハンドラー(つまり、1つのバイトコードを解釈するマシンコードシーケンス)が現在のバイトコードと一致しない場合、ポイズンを0



設定します。 JavaScript呼び出しの場合、ターゲット関数をパラメーターとして(レジスターに)渡し、着信ターゲット関数が現在の関数と一致しない場合、各関数の先頭でポイズンを0



設定します。 この軟化により、Octaneベンチマークで20%未満の減速が見られます。



主なセキュリティチェックはメモリアクセスが境界内にあることを確認することであるため、WebAssemblyの緩和はより簡単です。 32ビットプラットフォームの場合、通常の境界チェックに加えて、すべてのメモリを次の2の累乗で満たし、ユーザーメモリインデックスの上位ビットを無条件にマスクします。 実装では境界チェックに仮想メモリ保護が使用されるため、64ビットプラットフォームではこのような緩和策は必要ありません。 潜在的に脆弱な間接分岐を使用する代わりに、switch / caseステートメントをバイナリ検索コードにコンパイルすることを実験しましたが、一部のワークロードには高すぎます。 間接呼び出しは、retpolinsによって保護されています。



ソフトウェア保護-信頼できない



幸いなことにまたは残念なことに、攻撃的な研究は防御的なものよりもはるかに速く進行し、Specter攻撃中に起こりうるすべてのリークをプログラムで軽減することは不可能であることがすぐにわかりました。 これにはいくつかの理由があります。 第一に、スペクターと戦うためのエンジニアリングの努力は、脅威のレベルに対して不釣り合いです。 V8では、一般的なバグ(Specterよりも高速で簡単)による境界外の直接読み取り、境界外への書き込み(Specterでは不可能です)、潜在的なリモートなど、はるかに悪い他の多くのセキュリティリスクに直面していますコードの実行(Specterでは不可能であり、はるかに悪い)。 第二に、私たちが開発および実装した緩和策はますます複雑になり、非常に複雑になりました。これは技術的な負債であり、実際に攻撃対象とパフォーマンスのオーバーヘッドを増加させる可能性があります。 第三に、マイクロアーキテクチャリークの緩和をテストおよび維持することは、攻撃用のガジェット自体を設計するよりもさらに困難です。緩和が設計どおりに機能し続けることを確認するのは難しいからです。 少なくとも一度、重要な緩和策は、その後のコンパイラの最適化によって効果的に取り消されました。 第4に、一部のSpectreオプション、特にオプション4を効果的に緩和することは、AppleパートナーのJITコンパイラーでの問題に対処するための大胆な努力の後でも、ソフトウェアでは単純に不可能であることがわかりました。



サイト分離



私たちの研究は結論に至りました:原則として、信頼できないコードはSpecterおよびサイドチャネルを使用してプロセスのアドレス空間全体を読み取ることができます。 ソフトウェアの緩和により、多くの潜在的なガジェットの有効性が低下しますが、効果的または包括的ではありません。 唯一の効果的な方法は、機密データをプロセスのアドレス空間外に移動することです。 幸いなことに、Chromeは長年にわたって、一般的な脆弱性による攻撃対象領域を減らすために、サイトを異なるプロセスに分離しようと試みてきました。 これらの投資は報われ、2018年5月までに準備段階に入り、最大数のプラットフォームでサイト分離を展開しました。 そのため、Chromeセキュリティモデルは、レンダリングプロセス中に言語のプライバシーを暗示することはなくなりました。



スペクターは長い道のりを歩んでおり、産業界と学界における開発者のコ​​ラボレーションのメリットを強調しています。 これまでのところ、白い帽子は黒い帽子より先です。 好奇心の強い実験者や専門家がコンセプトを証明するガジェットを開発することを除いて、実際の攻撃についてはまだ知りません。 これらの脆弱性の新しい亜種が出現し続けており、これはしばらく続くでしょう。 これらの脅威を引き続き監視し、真剣に受け止めています。



多くのプログラマと同様に、安全な言語は抽象化の適切な境界線を提供し、適切に型付けされたプログラムが任意のメモリを読み取れないようにすることも考えました。 これが間違いであることが判明したのは悲しいことです。この保証は今日の機器には対応していません。 もちろん、安全な言語にはエンジニアリング上の利点があり、将来はそれらにあると信じていますが、今日の機器では少し漏れています。



興味のある読者は、トピックをより深く掘り下げ、 科学記事でより詳細な情報を入手できます。



All Articles