このページの本文へ

ロードマップでわかる!当世プロセッサー事情 第440回

性能低下が取り沙汰されるインテルCPUの脆弱性とは?

2018年01月08日 12時00分更新

文● 大原雄介(http://www.yusuke-ohara.com/) 編集●北村/ASCII.jp

  • この記事をはてなブックマークに追加
  • 本文印刷

脆弱性その1
命令の分岐予測メカニズムを悪用できる

 今回の脆弱性は基本的に投機実行が関係している。少なくともパイプライン化された実行ユニットを持つCPUの場合、「ある命令の処理が終わってから」次の命令を読み出していたら遅くてかなわない。

 したがって、基本的にある命令を処理している最中に、もう次の命令や次の次の命令をどんどん取り込んでいく。これはフェッチあるいはデコードの処理である。

 このレベルでは別に問題はないのだが、通常プログラムは繰り返し構造をとるため、どこかで分岐が入ることになる。分岐の場合、「命令の実行結果で分岐すべきアドレスが決まる」わけで、プロセッサーがパイプライン化されていなければこれは問題にならない。

 ところがパイプライン化されていると、実行結果が出るまで待っているのは時間の無駄すぎる。そこで「分岐結果をあらかじめ予測しておき、その分岐結果にあわせて次の命令の実行にかかる」という処理が実装される。

 ただこのためには分岐結果を正しく予測する必要があり、各社分岐予測メカニズムに力を入れているわけだが、結果として分岐命令があった場合、その直後の命令に関しては投機実行(たぶんこちらの命令が実行されるであろう、と予測して実行する)となるわけだ。

 ここで判断が間違っていた場合、それまで投機的に実行していた分を捨てて、新たに命令を読み直すところから始めることになる。Variant 1はこの投機実行を悪用したものだ。

 もともとのプログラムが、本来許される範囲を超えてメモリーアクセスをしようとしたものだとする。もちろんこれはCPUのチェック機構に引っかかるため途中で止まるはず(結果として実行されない)だが、投機実行のメカニズムのおかげで、「許されるメモリーの範囲を超えているか」をチェックするまでの間は投機的にメモリーアクセスを実行することになる。

 すると、「本来許されないメモリーアドレスのデータ」がデータキャッシュ上にロードされてしまうことになる。しかもこの際に「ロードされたか否か」は読み込み速度から推定できる。本来アクセスできないメモリー領域なので、ページミスが発生し、当然キャッシュアクセスの場合よりも遅くなるわけだ。

脆弱性その2
分岐先のメモリーアドレスを推定できる

 次がVariant 2である。先ほど分岐予測という話をしたが、分岐予測の際に用いられるのがBTB(Branch Target Buffer)である。これは頻繁に利用される分岐については、その際に分岐先のアドレスを格納しておくことで迅速に分岐予測を実行できるようにしようというものだ。

 問題はこのBTBは1つしかないことで、つまりOSの内部で利用する分岐先アドレスも、アプリケーションで利用する分岐先アドレスも一緒くたになって格納されている。

 BTBの構造そのものは公開されていないが、GoogleのZero ProjectではHaswellの構造を研究し、Indirect Branch(間接分岐)の場合に、同じ物理CPUに属するあるスレッドが、もう片方のスレッドに影響を及ぼせることを発見した。これを利用して、他方のスレッドに割り当てられているメモリーアドレスを推定できることが明らかにされた。

 Variant 1/2については確かに問題ではあるが、シビアなものではない。あくまでも他のプロセスに割り当てられているメモリアドレスを推定できる、というレベルのものでしかないからだ。

脆弱性その3
不正なキャッシュアクセスが可能

 これに対してVariant 3はもう少し深刻である。Variant 3はVariant 1の延長にある。Meltdownに関する論文では、以下のようなサンプルコードでこれを説明している。

  • 1 ; rcx = kernel address
  • 2 ; rbx = probe array
  • 3 retry:
  • 4 mov al, byte [rcx]
     (rcxで示されたアドレスの内容1Byteをalレジスターに格納)
  • 5 shl rax, 0xc
     (raxレジスターの内容を12bit左シフト)
  • 6 jz retry
     (shlの処理結果が0になったらretry:に飛ぶ)
  • 7 mov rbx, qword [rbx + rax]
     (rbxにrbx+raxのアドレスの内容を格納)

 今、サンプルコードは通常のユーザーメモリアドレスで動作している。行番号4の“mov al, byte [rcx]”(rcxレジスターで示されたアドレスのデータ1byteをalレジスターに読み込む)を実施した場合、特権違反(カーネル空間のアドレスにアクセスした)で例外処理に飛ぶことになる。

 ただ、その例外処理に飛ぶ前に、実際には4~7の処理がCPU内部で実行されてしまっており、この結果としてキャッシュにはrcxで示された先のアドレスから始まるデータが取り込まれてしまう、というものだ。

 一度キャッシュに取り込まれてしまえば、後はそのキャッシュの内容を改めて取り込みなおすことで、本来保護されるべきカーネルアドレスがそのまま読み取れてしまうことになる。

 実際Meltdownのチームはこの手法を利用し、パスワードを読み取るデモを公開している。Variant 1/2に比べてこのVariant 3の影響は非常に大きい。

 これが可能になるのは、CPU内部のPage Table(仮想メモリーのアドレスと物理メモリーのアドレスの対応を記したテーブル)が、OSカーネル用とユーザープロセス用で一緒になっていることが遠因である。

 先のリストに戻ると、rcxでカーネルアドレスを指定しても、そのアドレスをPage Tableで変換できなければ物理メモリーのアドレスがわからないので、キャッシュにそもそも取り込みができないことになる。

 対策としては、OSカーネル向けのPTEとユーザープロセス向けのPage Tableを分離するだけで良いことになる。

 この手法はKPTI(Kernel Page-Table Isolation)と呼ばれている。もともとLinuxではKASLR(Kernel Address Space Layout Randomization)と呼ばれる、万一カーネルにアクセスされても影響を少なく抑える技法が2014年ごろから実装されており、これをさらに改良したKAISER(Kernel Address Isolation to have Side-channels Efficiently Removed)というPTEを分離する対策を施したものが2017年に用意されていた。

 今回Meltdownへの対策として、このKAISERをベースとしたものがKPTIパッチとして提供されている。またWindowsやAndroid、macOSなどについても同じようにOSカーネル用とユーザープロセス用でPage Tableを分離すれば対策できるわけで、すでにパッチが用意されている。

 このKPTI対策をすれば安全性は確保できる(遠因としてのVariant 1/2についても同時にパッチが用意されるが、たとえばAMDはVariant 2についてはインテルのものとBTBの構造が違う上、今のところ攻撃された事例もないのでほぼリスク0だ、としている)わけだが、その代償は性能低下である。

パスワードを読み取れる「Meltdown」は、パッチを当てれば対策できるが、その代償が性能低下である

カテゴリートップへ

この連載の記事

注目ニュース

ASCII倶楽部

プレミアムPC試用レポート

ピックアップ

ASCII.jp RSS2.0 配信中

ASCII.jpメール デジタルMac/iPodマガジン