CVE-2017-5754 (a.k.a Meltdown) 是一個在 2018 年初被揭露的 side channel 攻擊手段,由 Google Project Zero 的成員 Jann Horn 獨立發現,幾乎影響了所有主流的作業系統,因為其概念上是利用 CPU 的優化手段中的 speculative execution 特性,來達成在 user mode 讀取存放在 kernel memory 中的資料。
CVE-2017-5715(a.k.a Spectre) 一樣是由 Google Project Zero 的成員 Jann 獨立發現,利用手法上和 Meltdown 類似但 approach 不同,Meltdown 是利用在 segment fault 發生時依然會 resolve data dependency 而在 cache 中洩漏資訊,Spectre 則是攻擊 CPU 中的 branch predictor,讓攻擊者期望的 branch (如非法存取記憶體) 總是會被 branch predictor 錯誤的選擇,而後 revert,進而在 cache 中洩漏資訊。
Meltdown 攻擊的基本概念其實不難,主要是因為 CPU 的 speculative execution 在亂序執行時,就算因為 segfault 中斷執行並將非預期的結果從 pipeline 中清空,但並不會將 cache 中的結果清空,也就會造成存取的時間差進而洩漏資訊。
考慮以下程式碼:
作為一個簡單的示意,若是 user 在 第 4 行時透過指標存取 kernel address 中的資料,則會因為 page table 中對該地址的存取是不被允許的而觸發 segfault 並將 flush pipeline。
但是問題出在因為 CPU 內部的優化所以部分程式碼可能會是亂序執行的,也就是說在觸發 segfault 前,第 5-6 行程式碼可能已經被執行了,而他們的值將會被遺留在 cacheline 中,結果就是當下次存取 arr[index]
時,那個位置所需的讀取時間就會相較其他位置少.. TIMING ATTACK!
擷取部分 PoC 內容:
首先宣告一個用來辨別存取時間差的 array,VARIANTS_READ
是一次讀取可能產生出來的數值範圍(1 byte == 256 種),TARGET_SIZE
則是加大每個測量點之間的距離,在 Meltdown 論文中有提到其原因是為了避免 CPU cache 嘗試 prefetch 將附近的地址放入 cache 中,而造成接下來的時間差計算失效。
readbyte 是用來非法讀取 kernel address addr
並透過測量時間差計算洩漏出來的值,在每次攻擊開始前皆會透過 clflush_target()
清除 cache 上所有 target_array
的快取,並透過一個特殊的 CPU 指令 _mm_mfence()
等待 clflush_target()
中所有清除 cache 的 memory 操作結束後 (還記得前面提到的亂序執行吧) 才往下執行 speculate(addr)
在進到 speculate
前先瞭解一些先前宣告的 helper function。
set_signal
會將 sigsegv
這個函式註冊為 SIGSEGV SIGNAL 的 handler,而 sigsegv
則是用於將 control flow 導回至發生 segfault 的下一行指令,讓程式可以繼續執行。(stopspeculate 位於 speculate()
中)
以下將各行組合語言對應到先前的 C pseudocode 中,
其中比較特別的是第 5, 13 行的處理是為了減少讀取到 0 的情況,在論文中也有對應的說明
不過不太理解最開始重複 300 次的 add $0x141, %%rax
,嘗試拿掉的話在部分機器上沒有差異,但在 Windows VMware 虛擬機中會顯著降低預測值,相當歡迎各位留言解惑!
事件過後 Linux 和 Windows 都迅速發出更新檔,以 KPTI 方式只保留部分 kernel memory 映射到 user mode 下的 virtual memory,讓 user mode 沒有辦法直接存取到完整的 kernel memory