メルトダウンの仕組み

3日目は、誰もがMeltdownとSpectreの言葉を耳にしました。プロセッサーの新鮮で小さな脆弱性です。 残念ながら、これらの脆弱性がどのように機能するかについては何も見つけることができませんでした(まず、Meldownに焦点を当て、簡単です)、元の出版物と記事を調べなければなりませんでした。 2017年夏 ハブには元の出版物の翻訳が既にあるという事実にもかかわらず、私は何とか読んで理解したことを共有したいと思います。







UPD :攻撃の説明に擬似コードを追加







プロセッサはどのように機能しますか?



過去数十年、最初のPentiumが登場した1992年以降、Intelはプロセッサのスーパースカラーアーキテクチャを開発しました。 肝心なのは、後方互換性を維持しながら、プロセッサをより高速にしたかったということです。 その結果、最新のプロセッサは非常に複雑な設計です。 想像してみてください:コンパイラーは一生懸命動作し、単一のスレッドで実行されるように命令をパックします。また、プロセッサー自体がコードを個別の命令に取り込み、可能な場合はそれらを並べ替えながら並列実行を開始します。 また、プロセッサで命令を実行するためのハードウェアブロックが多数あるという事実により、通常、各命令にはそれらの1つのみが含まれます。 火に燃料を追加し、プロセッサのクロック周波数がRAMの速度よりもはるかに速く成長したという事実により、キャッシュ1、2、および3レベルが出現しました。 RAMに移動するには100プロセッササイクル以上かかり、レベル1に移動するにはキャッシュは既にユニットであり、加算などの単純な算術演算を実行するには、数サイクルかかります。







画像

その結果、1つの命令がメモリからのデータの受信を待っている間、浮動小数点などの作業ブロックを解放するために、プロセッサは投機的に次のことを実行します。 したがって、最新のプロセッサは約100の命令を並列処理できます(正確にはスカイレイクでは97)。 このような各命令は、独自のレジスタのコピーで動作し(これはリザベーションステーションで発生します)、実行時には互いに影響しません。 命令が完了した後、プロセッサはリタイアメントブロックに実行結果を並べようとします。まるでこのスーパースカラリティの魔法がすべてないかのように(コンパイラはそれについて何も知らず、コマンドのシーケンシャル実行があると考えます-これを覚えていますか?) 。 前の命令が実際に変更したレジスタ値を使用したなどの理由で、何らかの理由でプロセッサが命令が誤って実行されたと判断した場合、現在の命令は単純に破棄されます。 メモリ内の値が変更された場合、または遷移予測子が間違っている場合にも、同じことが起こります。







ところで、ここでハイパートレッドの仕組みが明らかになるはずです。2番目のレジスタ割り当てテーブルと2番目のリタイアメントレジスタファイルブロックを追加します。







Linuxメモリ



64ビットモードの操作では、各アプリケーションには、読み取りと書き込みに使用可能なメモリが割り当てられており、実際にはユーザー空間のメモリです。 ただし、実際には、プロセスのアドレス空間にもカーネルメモリが存在します(syskolsのパフォーマンスを向上させるために行われたと思われます)が、ユーザーコードからのアクセスから保護されています。 このメモリにアクセスしようとすると、エラーが発生します;これは、プロセッサとその保護リングのレベルで機能します。







サイドチャネル攻撃



データを読み取れない場合、攻撃オブジェクトの副作用を利用しようとすることができます。 古典的な例:電力消費量を高精度で測定することにより、プロセッサが実行する操作を区別できます。これは、KeeLoqカーアラームチップがハッキングされた方法です。 メルトダウンの場合、このようなサイドチャネルはデータの読み取り時間です。 データバイトがキャッシュに含まれている場合、RAMから読み取られてキャッシュに読み込まれる場合よりもはるかに高速に読み取られます。







この知識をまとめる



実際、攻撃の本質は非常にシンプルで非常に美しいものです。







  1. プロセッサキャッシュをリセットします。

    char userspace_array[256*4096];
    for (i = 0; i < 256*4096; i++) {
    _mm_clflush(&userspace_array[i]);
    }
          
          



  2. , , .

    const char* kernel_space_ptr = 0xBAADF00D;
    char tmp = *kernel_space_ptr;
          
          



  3. , , , 2.

    char not_used = userspace_array[tmp * 4096];
          
          



  4. . , , , , — , .

    for (i = 0; i < 256; i++) {
    if (is_in_cache(userspace_array[i*4096])) {
        // Got it! *kernel_space_ptr == i
    }
    }
          
          





, , .









; rcx = kernel address
; rbx = probe array
retry:
mov al, byte [rcx]
shl rax, 0xc
jz retry
mov rbx, qword [rbx + rax]
      
      





, .

mov al, byte [rcx]



— , . , , .

shl rax, 0xc



— 4096 ,

mov rbx, qword [rbx + rax]



— "" ,

retry



jz retry



- , , , . , , — rax , . , , , .







Linux



— , Kernel page-table isolation. , 1.5 .








All Articles