KAISER(KASLR is Dead: Long Live KASLR)のまとめ

始めに

巷で話題の脆弱性のSpectreとMeltdownです。 Meltdownの対策として導入されたKernel Page テーブル Isolationについて簡単にまとめます。 この対策技術は、KAISERと呼ばれ、「The KASLR is Dead: Long Live KASLR」というタイトルでESSoS 2017で発表されました。 論文中での実装およびパッチは、Linuxが対象です。 論文およびパッチは、GitHubから参照できます。

Meltdown

Meltdownは、ユーザー空間のアプリケーションからカーネル空間のメモリアドレスを読み取ることができる脆弱性です。 この攻撃は、本来は権限のないメモリへ読み取り命令をCPUの投機的に実行(プリフェッチ)させ、その結果、キャッシュに残った値を推測するというサイドチャネル攻撃です。 今までも、ユーザー空間からカーネル空間へのサイドチャネル攻撃はいくつか提案されていました。 たとえば、Double Page Fault Attackやintel TSX-based Attackです。 しかし、これらは、ユーザー空間からカーネル空間のメモリアドレスの配置を推測することにとどまっており、メモリの読み取りまでは実現していませんでした。 ここで言う、メモリアドレスの配置を推測というは、kASLRのバイパスにあたります。 Meltdownは、実際に、カーネル空間のメモリを読み出すことができるという点で、今回騒がれたのだと思います。

脆弱性や攻撃の詳細については、以下の記事を読むと理解が進むと思います。

今回のMeltdownは、intel固有の脆弱性でありますが、根本的な原因は、プロセスのアドレス空間にカーネルとユーザー空間の両方がマップされていることです。

仮想アドレス空間

intelおよびLinuxでは、プロセスごとに仮想メモリを持ち、メモリアクセスの際の仮想アドレスから物理アドレスの変換は、ページテーブルを介して行われます。 x86-64では、4段階のページテーブルを用いてアドレス変換が行われ、最上位のページテーブルは、PML4と呼ばれます。 このPML4の物理アドレスをCR3レジスタにセットすることで、各プロセスのアドレス空間の切り替えを実現しています。

また、システムコールや割り込みによるユーザーモードとカーネルモードの切り替え時には、アドレス空間の切り替え(CR3のアップデート)は行われません。 これは、プロセスのページテーブルにカーネル空間もマップされており、そのページテーブル(アドレス空間)を用いて処理を継続できるからです。 カーネルモード時にも、プロセスのアドレス空間を用いることは、パフォーマンスの観点で必要です。 プロセスのアドレス空間を切り替える、すなわち、CR3をアップデートすることは、TLBをFlashすることを意味します。 TLBのFlashはパフォーマンスに直結するコストが高い処理です。

前述したサイドチャネル攻撃は、プロセスがもつアドレス空間にカーネル空間もマップされていることが原因です。 KAISERは、ユーザーとカーネルの切り替え時に、アドレス空間(ページテーブル)も切り替える技術です。

ARM (Cortex-A) もintelと似たような機構を持っていますが、少し異なります。 ARM CPUはアドレス変換テーブルの物理アドレスを保持するレジスタが2つあります(TTBR0 and TTBR1)。 1つは、ユーザーアドレス空間をマップするために、もう1つは、カーネルアドレス空間をマップするために使われ、ハードウェア的に、アドレス空間が分離されています。

KAISERの設計

先行研究でも、アドレス空間の分離が提案されている(Stronger Kernel Isolation)。
通常時と、Stronger Kernel Isolation、KAISERの概念図は次のようになる(論文より引用)。

overview

通常時では、ユーザーモードとカーネルモード時に、カーネル空間およびユーザー空間の両方がマップされた1つのアドレス空間を用いる。 Stronger Kernel Isolationは、ユーザーモード時に、カーネル空間がマップされていないアドレス空間を、逆に、カーネルモード時には、ユーザー空間がマップされていないアドレス空間を用いる。 KAISERでは、ユーザーモードでは、カーネル空間がマップされていないアドレス空間を、カーネルモード時にはユーザー空間とカーネル空間の両方がマップされているアドレス空間を用いる。 Stronger Kernel Isolationの方がKAISERより強力な分離であるといえるが、論文中では、Stronger Kernel Isolationは、カーネルへの修正が大きく、実用的でないと述べられている。
ユーザー空間とカーネル空間の分離には大きく3つの課題がある。

  1. 高度に並列化された今日のシステムでは、コンテキスト切り替え時にスレッドがページテーブル構造を変更すると、同じプロセスのすべてのスレッドに影響を与える。

  2. 現在のx86プロセッサは、コンテキストスイッチ中にユーザー空間とカーネルス空間の両方に有効な複数の領域を必要とする。 さらに、コンテキストスイッチ中に、core-local storageのようなセグメントメモリアクセスが必要である。 カーネル空間のマップされていない部分を再マッピングすることなく、セグメントを見つけて復元できなければならない。 先行研究では、カーネルの大部分を書き換える必要があった。

  3. アドレス空間の切り替えは、暗黙的なfull TLB flushを、アドレス空間の修正は、部分的なのTLB flushを招く。 近年のOSは、暗黙のFull TLB flushを削減するために、高度に最適化されており、パフォーマンスへの影響が大きい。

実装

  1. への対処
    ユーザー空間のみがマッピングされているShadow アドレス空間と、カーネルとSMAPおよびSMEPで保護されているユーザー空間がマッピングされている2つのアドレス空間を用意する。 ユーザーモードとカーネルモードの切り替え時には、対応するPML4の値であるCR3レジスタを更新する。 アドレス空間の切り替えは、カーネルとユーザー空間の両方にマッピングされているカーネルの一部のみにアクセスして行われる(一部両方のアドレス空間に共通してカーネルページをマッピングする必要がある)。
    通常のPML4は、8kBにアライメントされた物理メモリブロックとして割り当て、+ 4kBの位置にShadow PML4を割り当てる。 これにより、メモリ検索を必要とせずに、アドレス空間の切り替えに、スクラッチレジスタを1つ用意すればよい。
    Shadow アドレス空間の概念図(論文より引用)。
    shadow_memory

  2. への対処
    先行研究では、割り込みディスパッチャコードのごくわずかな部分だけが両方のアドレス空間にマッピングされる必要があることが示唆されたが、実際にはより多くの領域をマップする必要がある。 x86とLinuxはコンテキストスイッチの割り込みを使用して構築されているため、割り込み記述子テーブル(IDT)と割り込みエントリと終了の.textセクションをマップする必要がある。 また、マルチスレッドアプリケーションを異なるコアで実行できるようにするには、CPU単位のメモリ領域を識別し、それらをShadow アドレス空間にマップする必要がある。

  3. への対処
    Linuxカーネルは、暗黙的なfull TLB flushを最小限にするようにしている(e.g.カーネルモードからユーザーモード間でCR3を更新しない)。 KAISERは、2つのアドレス空間が異なるCR3レジスタ値を有するので、各コンテキストスイッチ毎にCR3更新する必要がある。 PTEのグローバルビットを設定したページは、暗黙のTLBFlashから除外されることを利用し。暗黙のTLBFlashの影響が軽減される。

評価

論文中でのパフォーマンスオーバーヘッドは最大0.68%.
メモリのオーバーヘッドはシステムで1M程度。

その他

コードを読んで加筆します..。