Weitere ähnliche Inhalte Ähnlich wie SpectreBustersあるいはLinuxにおけるSpectre対策 (19) Mehr von Masami Hiramatsu (6) SpectreBustersあるいはLinuxにおけるSpectre対策3. 落ち着こう
現状、Spectre (Variant 1/2) は動作が解析され、緩和策が進んでいる。
- 根本対策はないが、緩和策はある
- いくつかはコンパイラの修正を伴うので、ユーザバイナリを含めて必要なものは総入
れ替えになる覚悟が必要
- 修正は安定版カーネルに順次取り込まれているので4.4, 4.9, 4.14はなんとかなりそ
う。(とは言え、大きな修正で今も不具合報告が上がっている)
- ディストロカーネルも出来るだけ最新版を採用しよう。
- これはlivepatchでは修正できない。再起動は必須。
- 出来る限り最新版のGCCとLinuxに入れ替えよう。(まだ開発は続いている)
- 一部問題についてはライブラリやアプリケーションでの対応が必要
3
8. Variant 1: Bounds Check Bypass (おさらい)
データ配列のインデックスの境界条件、チェックしてますか?
してますよね?
でも投機的実行は境界条件の分岐命令を越えて先に進みます。
8
if (x < array1_size) {
idx = array1[x] * 256;
y = array2[idx];
}
9. Bound Check Bypass + Cache Side Channel Attack
untrasted_offset_from_callerに変な値が入っても、 ifブロックは投機的に実行されうる
9
struct array {
unsigned long length;
unsigned char data[];
};
struct array *arr1 = ...; /* small array */
struct array *arr2 = ...; /* array of size 0x400 */
/* >0x400 (OUT OF BOUNDS!) */
unsigned long untrusted_offset_from_caller = ...;
if (untrusted_offset_from_caller < arr1->length) {
unsigned char value = arr1->data[untrusted_offset_from_caller];
unsigned long index2 = ((value&1)*0x100)+0x200;
if (index2 < arr2->length) {
unsigned char value2 = arr2->data[index2];
}
} (出典: Project Zero blog)
10. Bound Check Bypass + Cache Side Channel Attack
untrasted_offset_from_callerに変な値が入っても、 ifブロックは投機的に実行されうる
10
struct array {
unsigned long length;
unsigned char data[];
};
struct array *arr1 = ...; /* small array */
struct array *arr2 = ...; /* array of size 0x400 */
/* >0x400 (OUT OF BOUNDS!) */
unsigned long untrusted_offset_from_caller = ...;
if (untrusted_offset_from_caller < arr1->length) {
unsigned char value = arr1->data[untrusted_offset_from_caller];
unsigned long index2 = ((value&1)*0x100)+0x200;
if (index2 < arr2->length) {
unsigned char value2 = arr2->data[index2];
}
}
まずこのif文をパスするように安全な値で
繰り返し 呼び出しを行う
(BHBの学習:後述)
攻撃時にはarr1をキャッシュから外す
(時間稼ぎ)
攻撃前に読みたいデータがキャッシュ
に乗るような操作を行う(別の場所で)
11. Bound Check Bypass + Cache Side Channel Attack
untrasted_offset_from_callerに変な値が入っても、 ifブロックは投機的に実行されうる
11
struct array {
unsigned long length;
unsigned char data[];
};
struct array *arr1 = ...; /* small array */
struct array *arr2 = ...; /* array of size 0x400 */
/* >0x400 (OUT OF BOUNDS!) */
unsigned long untrusted_offset_from_caller = ...;
if (untrusted_offset_from_caller < arr1->length) {
unsigned char value = arr1->data[untrusted_offset_from_caller];
unsigned long index2 = ((value&1)*0x100)+0x200;
if (index2 < arr2->length) {
unsigned char value2 = arr2->data[index2];
}
}
lengthがキャッシュにない場合
メモリアクセスに時間がかかる
→投機的によくあるケースの場合
の処理をしてみよう
おっ、このデータはキャッ
シュにあるぞ!
先に進もう
これもよくOKになるぞ。先に進もう
arr2をプリフェッチしよう
えっ、最初のifがダメだった?じゃあ実行結果を破棄しよう
CPU
CPU
CPU
12. Bound Check Bypass + Cache Side Channel Attack
untrasted_offset_from_callerに変な値が入っても、 ifブロックは投機的に実行されうる
12
struct array {
unsigned long length;
unsigned char data[];
};
struct array *arr1 = ...; /* small array */
struct array *arr2 = ...; /* array of size 0x400 */
/* >0x400 (OUT OF BOUNDS!) */
unsigned long untrusted_offset_from_caller = ...;
if (untrusted_offset_from_caller < arr1->length) {
unsigned char value = arr1->data[untrusted_offset_from_caller];
unsigned long index2 = ((value&1)*0x100)+0x200;
if (index2 < arr2->length) {
unsigned char value2 = arr2->data[index2];
}
}
しかしvalueに対応するarr2の「場所」のエ
ントリはキャッシュにロード済み
Arr2のエントリに片っ端からアクセスし
て速度を確かめるぞ!
17. Array_index_nospec適用後
untrasted_offset_from_callerに変な値が入っても、 ifブロック内部でサニタイズされる
17
struct array *arr1 = ...; /* small array */
struct array *arr2 = ...; /* array of size 0x400 */
/* >0x400 (OUT OF BOUNDS!) */
unsigned long untrusted_offset_from_caller = ...;
if (untrusted_offset_from_caller < arr1->length) {
untrusted_offset_from_caller =
array_index_nospec(untrusted_offset_from_caller, arr1->length);
unsigned char value = arr1->data[untrusted_offset_from_caller];
unsigned long index2 = ((value&1)*0x100)+0x200;
if (index2 < arr2->length) {
unsigned char value2 = arr2->data[index2];
}
}
lengthがキャッシュにない場合
メモリアクセスに時間がかかる
→投機的によくあるケースの場合
の処理をしてみよう
内部の演算の依存があるから
lengthのロードを待とう
上記演算の結果0になるvalue = arr1->data[0]
CPU
CPU
22. Variant 2: Branch Target Injection
投機的実行とBTB(分岐先バッファ)と間接分岐命令を利用した攻撃
間接分岐命令とは?
- 分岐先がレジスタやメモリの値によって指定される命令
- x86ではcallとjmpの両方に間接分岐命令がある。
間接分岐先は事前の命令が実行された後に決まる(fops->read()など)
- 分岐先を決めるために待ち合わせが必要(メモリロードが絡むと遅い)
→待機時間が無駄になるので先に進めたい
→投機的実行
- 分岐先がわからないのに実行できるの?→BTB
22
23. BTB: Branch Target Buffer
分岐先を分岐元アドレスで保存するバッファ
- 分岐元アドレスを下位31ビットだけのキーで保存し、分岐先についても分岐元の上
位32ビットで補完する。
- 例えば 0xBEEF.1234 が分岐先(ターゲット)とされていて、 0xDEAD.CAFE.1234から分岐する場
合、分岐先は0xDEAD.BEEF.1234となる
- 31ビットは長すぎるので、30ビットから14ビットまでの16ビットは、8ビットずつペアに
してXORされる
- 例えば 0x100.0000 と 0x180.8000 は同じ値として認識される(両方の 8がXORで0になる)
- 異なる分岐元なのに同じエントリになるエイリアスが出来る
- 例 0xBEEF.CAFE.1234 と 0xDEAD.FACE.3234 は同じキーになる
23
24. BTB (Project Zeroの観測に基づく)
1エントリ6つの情報で出来ている(64ビット)
24
Source Target
a b c d e f
1 1 01010101 10101.01010101 1 01010101.01010101.01010101.01010101
a: (e)が1だった場合に、ソースの上位 32ビットの値をインクリメントして適用するかどうか
b: 分岐元アドレスの31ビット目
c: 分岐元アドレスの30ビット目から14ビット目までを8ビットずつXORした値
d: 分岐元アドレスの13ビット目から1ビット目までの値
e: 32ビット境界を超えて分岐するかどうか(上位 32ビットが変わるかどうか )
f: 分岐先アドレスの下位 32ビット
※bitの並びは素直にProject Zeroの説明から起こした。多分 e a b c d f の順で並んでる気がする
25. BHB: Branch History Buffer
過去の分岐予測結果の当たり外れを記録するバッファ
- ややこしいビット演算によって過去29回分の「当たった」分岐パターンを58ビットに
エンコード
- 現在の分岐パターンを BHBに見つけると、分岐先をBTBに探しに行く
- IRET以外の分岐パターンを記録する
- 詳しくはProject ZeroのBlogを参照(実験では26回分は記録しているとされた)
- 分岐パターンは命令アドレスの下位20ビットを使って更新される
25
26. Branch Target Injection
BTBのエイリアスエントリを悪用する
- 1. ユーザ空間でBTBとBHBを「学習」させる
- 対象となる間接分岐にたどり着く分岐パターンを再現( BHBの学習のため29回分は再現する)
- 何度もパターンを辿って学習させる ( IRET でループ →BHBに同じパターンが残る )
- 2. カーネル空間や別のスレッドで、そのBTBを使って「推論」する
(カーネル空間でBTBのエイリアスエントリを参照)
- 3. 狙ったコードが投機的に実行される
実行結果は破棄されるが、キャッシュアクセスは行われる→サイドチャネル攻撃
26
40. Spectreに対するハードウェア緩和策
Intel/AMDから、新しいマイクロコード命令であるIBRS/IBPB/STIBPが提供された
- IBRS (Indirect Branch Restricted Speculation)
間接分岐命令の投機的実行を行う際、IBRSが1にセットされる前のBTBの結果を使わないよう
にする。(Kernel→User遷移時に0にしておく)
- IBPB (Indirect Branch Prediction Barrier)
簡単に言うと、Indirect Branch用BTBのフラッシュ。プロセスやVMの切り替え時に発行し、
Indirect Branch Predicationの状態を初期化する
- STIBP(Single Thread Indirect Branch Prediction)
一部のプロセッサではHT間でBTBが共有されているが、これをスレッドごとにする
→(1/30日現在) LinusがIntelの出したコードが煩雑で重く確実性が不明で嫌いということでリジェクト
されている(5000サイクルほど無駄になるらしい)
→IBPBは採用されている(VMの切替時に) 40
47. 参考文献 1
Project Zero :Reading privileged memory with a side-channel
https://googleprojectzero.blogspot.jp/2018/01/reading-privileged-memory-with-side.html
Meltdown and Spectre
https://meltdownattack.com/
LinuxコアメンバーによるMeltdownとSpectre 対応状況の説明
https://qiita.com/hogemax/items/008f19fd14eebca474d7
prevent bounds-check bypass via speculative execution
https://lwn.net/Articles/744257/
Gcc retpoline branch:
http://git.infradead.org/users/dwmw2/gcc-retpoline.git/shortlog/refs/heads/retpoline
LLVM retpoline review:
https://reviews.llvm.org/D41723
47
48. 参考文献 2
Spectre Attacks: Exploiting Speculative Execution
https://spectreattack.com/spectre.pdf
KASLR is Dead: Long Live KASLR
https://gruss.cc/files/kaiser.pdf
Prefetch Side-Channel Attacks
https://gruss.cc/files/prefetch.pdf
Intel Analysis of Speculative Execution Side Channels
https://newsroom.intel.com/wp-content/uploads/sites/11/2018/01/Intel-Analysis-of-Speculative-Execution-Side-Channels.pdf
Vulnerability of Speculative Processors to Cache Timing Side-Channel Mechanism
https://developer.arm.com/support/security-update
CPU security bugs caused by speculative execution
https://github.com/marcan/speculation-bugs/blob/master/README.md
48
49. 参考文献 3
ARM-TF Security Advisory TFV6
https://github.com/ARM-software/arm-trusted-firmware/wiki/ARM-Trusted-Firmware-Security-Advisory-TF
V-6
arm64 kernel for-next
https://git.kernel.org/pub/scm/linux/kernel/git/arm64/linux.git/log/?h=for-next/core
Downloads and help for Meltdown and Spectre
https://www.linaro.org/downloads/security/
Linaro Security Blog
https://www.linaro.org/blog/meltdown-spectre/
arm/arm64 Spectre/Meltdown backporting
https://lkml.org/lkml/2018/2/9/310
49