FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

PortSmashで学ぶ高性能プロセッサの同時マルチスレッディング

CPUの脆弱性として新たに報告された "PortSmash" 、どういう脆弱性なのかを調べるために論文を探すと公開されていた。

  • Port Contention for Fun and Profit

https://eprint.iacr.org/2018/1060.pdf

"Port Contention for Fun and Profit" という論文として公開されている。 どうでもいいけれども、"Fun and Profit" (趣味と実益)っていう名前がついているのは謎である。

読み進めていくと、結構理屈としては分かりやすい攻撃手法だということが分かった。 ちなみにこのPortという言葉は、SSHなどのポートのことではない。 現代のアウトオブオーダのプロセッサには、命令の種類に応じて実行されるユニットが分けられている。 例えば算術演算命令ならばALUユニット、メモリアクセス命令だったらLSUユニットが使われる、といった具合だ。 これらのユニットは1プロセッサに同一の機能のものが複数入っており、命令発行ユニットからどのポート(どのユニット)に発行されるかは動的に決められる。

この「ポート」をうまく活用するので、"PortSmash"という名前がついている、という訳だ。

PortSmashが利用するハイパースレッディングとSMT(Simultaneous Multi Threading)とは

現代のハイパフォーマンスプロセッサには、物理コアと論理コアという考え方があり、物理的には1つのCPUでも、仮想的(論理的)には2つのコアに見せるという機能がある。

例えば、汎用レジスタなどは物理的に別々のものを用意しないと2つのプロセッサとして機能しないが、ALUやメモリアクセスユニットなどは同じものを時分割共有で使うことで、プロセッサのパイプライン重点率を上げつつ、チップサイズを削減することが可能だ。

これがハイパースレッディングの基本的な考え方だ。Intelのプロセッサではハイパースレッディングと呼び、一般的にはSMT(Simultaneous Multi Threading)と呼ぶ。

f:id:msyksphinz:20181109000634p:plain

ハイパースレッディングでは同じタイミングでCPU内に複数の命令系列が流れる。論理コアが2つの場合、プロセッサ内では2つの命令系列が流れている。 これらは明確に分離され、互いのスレッドの情報が読み取れてはならない。 これが読み取れてしまうというのがこのCPUの脆弱性であり、Meltdown, Spectreの脆弱性も類似するものである。

同時に複数のスレッドが混在されており、さらにプロセッサの内部には複数の機能ユニットが内蔵されているため、どの命令をどのユニットに発行するかというものが制御される。

例えばALUの算術演算命令は実行頻度が高いので、ALUユニットはCPU内に複数個用意されている。 命令発行ユニットは、空いているユニットに算術演算を発行し、たとえ異なるスレッドであってもALUは共有されている。

現代のIntelの高性能プロセッサ(SkyLake / Kaby Lake)では、実行ユニットとして8つのユニットが用意されている。

f:id:msyksphinz:20181108225632p:plain
図2. Intel SkyLake, Kaby Lakeのマイクロアーキテクチャ (本論文より抜粋)
  • Port 0 : 整数ALU、整数除算、ベクトルALU、ベクトル乗算、AES、ベクトル文字列処理、浮動小数点除算、分岐命令
  • Port 1 : 整数ALU、整数乗算、ベクトルALU、ベクトル乗算、ビットスキャン
  • Port 2 : アドレス生成(AGU)、ロード命令
  • Port 3 : アドレス生成(AGU)、ロード命令
  • Port 4 : ストア命令
  • Port 5 : 整数ALU、ベクトルシャッフル、ベクトルALU、アドレス計算(LEA)
  • Port 6 : 整数ALU、分岐
  • Port 7 : アドレス生成(AGU)

これらをどのように各スレッドが使っていくか、このポートの使い方の特徴を見抜いて、隣で走っているスレッドの情報を読み取ろうというのが、PortSmashの基本的な考え方だ。

ポート割り当て方のモデル化

物理コア1つの中に論理コアが2つ入っているとして、各スレッドの命令がどのようにしてポート割り当てを行っているのかというのをモデルかしてみる。 論文中には結構小難しいことが書いてある。

 i はクロックサイクル番号を示し、 j = i \mod 2を示し、Pはポートのセットを示す。

  1.  C _ {j} is allotted P _ {j} ⊆ P such that  \left|P \backslash P _ {j} \right| is minimal.
  2.  C _ {1−j} is allotted P _ {1−j} = P \backslash P _ {j}.

小難しそうだが、大したことはない。1サイクル毎にスレッドの優先度を交換し、最大優先度のスレッドが所望のポートを取ることができる。 優先度の低いスレッドは残ったポートから割り当てが行われる。

では、同一のスレッドが同じ命令ばかりを実行してスレッドを占有したらどうなるだろうか? もう一つのスレッドは、ある命令を流したくてそのポートを使いたいのだけれども、他のスレッドに邪魔をされて毎サイクルその命令を流すことができない。 したがって、スレッド的には命令の実行レイテンシに影響が出てくる。 アリスとボブの例で、これが説明されている。これを、「Secret Timing Channel」と呼ばれている。

2つのレイテンシ1命令が存在する。ポート0でのみ実行できるNOP0と、ポート1で実行されるNOP1である。 アリスは、以下のように1ビットの情報をBobに送信する。

  1. アリスがゼロを送信したい場合、彼女はNOP0の実行を継続する。それ以外の場合は、NOP1が代わりに実行される。
  2. 同時に、ボブは固定数のNOP0命令を実行し、実行時間 t0を測定する。
  3. その後、ボブは同じ固定数のNOP1命令を実行し、実行時間 t1を測定する。
  4.  t1> t0 の場合、ボブは1ビットを受信する。 そうでなければ、 t0> t1 およびゼロビットである。

これにより、本来は互いに独立したはずのスレッドで情報が読み取れてしまっている。これがPortSmashの基本的な考え方である。

f:id:msyksphinz:20181109001251p:plain:w1000
図. VictimとSpyで実行している命令の使用するポートが異なる場合、Spyコードのレイテンシは変わらない。
f:id:msyksphinz:20181109001454p:plain:w1000
図. VictimとSpyで実行している命令の使用するポートが同じ場合、Spyコードのレイテンシは伸びる。

PortSmashのSpyプログラム

PortSmashのSpyプログラムの例を以下に示す。P1P5P0156の3つのdefineで分けられていることに注意。

 mov  $COUNT, %rcx 
1: 
    lfence
    rdtsc
    lfence
    mov  %rax, %rsi
#ifdef P1 
.rept 48 
    crc32    %r8, %r8
    crc32    %r9, %r9 
    crc32    %r10, %r10 
.endr shl $32, %rax
#elif defined(P5) 
.rept 48 
    vpermd   %ymm0, %ymm1, %ymm0 
    vpermd   %ymm2, %ymm3, %ymm2 
    vpermd   %ymm4, %ymm5, %ymm4 
.endr
#elif defined(P0156)
.rept 64
    add  %r8, %r8
    add  %r9, %r9
    add  %r10, %r10
    add  %r11, %r11
.endr
#else
#error No ports defined
#endif
    lfence
    rdtsc
    or       %rsi, %rax
    mov  %rax, (%rdi)
    add  $8, %rdi
    dec  %rcx
    jnz  1b

P0156が有効の時、add命令が連続して発行され(しかも依存関係がない)るため、4つのポート0,1,5,6を占有してしまう。 これを64回繰り返すのだが、ポート競合が無い場合、これは128サイクルで実行されるはずである。

一方で、P1P5が有効の場合、それぞれcrc32命令と、vpermd命令が実行される。それぞれポート1とポート5を占有するのだが、それぞれレイテンシは3なので、48回実行することで最小で 3\times 48 + 2 = 146クロックサイクルで実行される。ポートの競合が発生すると、レイテンシはその2倍に増加する。

他の脆弱性攻撃との違い

PortSmashの最大の特徴は、それ以外のプロセッサの脆弱性攻撃(Meltdown, Spectre, TLBleed)などに比べて非常に粒度の小さいことだ。

基本的にキャッシュアクセスの脆弱性を突くタイプの攻撃は、キャッシュラインのサイズに依存して粒度が変動する。したがってあまり細かな粒度の情報を抽出することができないといわれている。 一方で、PortSmashは命令発行のポートを使用するため、その粒度は非常に小さい。 このため、取得できる情報も細かく制御できる。これがPortSmashの最大の特徴だ。

アプリケーション

PortSmashは、命令のカテゴリが違うだけで別のスレッドの情報を読み取れてしまうため、さまざまアプリケーションでその情報を抽出することができる。

  • ECC and P-384
  • ECDSA and P-384 in OpenSSL
  • Procurement Phase: TLS
  • Signal Processing Phase

などといろいろ書いてあるのだが、これまでの問題と大きく異なるのは、これまでIntelのライブラリなどはレイテンシを同一にすることで暗号化の計算結果を読み取ることができないような手法を用いていたが、そもそも発行される命令ポートが異なるだけで命令の情報が読み取れてしまうということは、この手法は使えなくなるということを意味する。

また、SGXでの保護を実装したとしても、これは意味をなさない。命令ポートの制御にはSGXは監視を行わないからだ。

回避する手段はあるのか?

PortSmashを回避する手段としては、2つ提案されている。

  • そもそもSMTを有効化しない(OpenBSDの考え方)。
  • ポートに依存しないコードを書く。プログラムの構成が非常に難しそうだが。。。

もう一つ、論文を読んで思ったことは、そもそも1サイクル毎に割り当てらるスレッドが規則正しく切り替わるというのが根本的な原因などだが、それをランダムにスレッドが切り替わるようにしてみてはどうだろう。

細粒度に同じ優先度でスレッドが切り替わらないと本来のマルチスレッドの性能が出せないのだが、例えば秘密データを取り扱うときだけ、スレッドの切り替わりのタイミングをランダムにしてみると、レイテンシを測定することによるデータの抽出が不可能になるかもしれない。