About Spectre

引言


現今操作系統(OS)設計一般分為應用層 (Ring 3) 與 內核層(Ring 0) , 應用層屬於普通應用程式級別, 而內核層屬於OS的代碼, Intel CPU Spectre漏洞產生後, 有大量的非技術性文章, 但Google Project Zero的文章講得比較艱深, 因此筆者在這做一個比較簡單的解釋與定義

理論背景


Out-of-Order Execute(OoOE) - 非順序執行 即表示正常匯編語言(Assembly Language) 不按正常的順序執行, 這是因為處理器中的各個運算單元實際上是可以異步工作的, 不需要像8086等老CPU, 同步執行指令。常見的如Cache Load/Store Unit, ALU 等等

Indirect Branch Prediction (分支預測) - CPU在執行過程中, 遇上了分支的話, 會先進行分支預測, 如常見的if 則是分支指令之一, 而他對應的匯編指令一般不會等到比較后才執行, 而cpu發現比較需要更久的時間的話, 那就會把if中的內容優先執行, 而執行的內容不一定會影響到結果, 但是執行的內容使用到的緩存則不會被修改(L1/2 DCache)


詳見: The Intel Optimization Reference Manual section 2.3.2.3 ("Branch Prediction"):

Spectre漏洞的產生就是基於以上兩個處理器優化機制而出現的

見以下代碼, 處理器在執行過程中, 假如arr1-length不在cache中, CPU則不會等到條件判斷完成才執行if{..}中的內容, 如果條件不成立, 才會退回對寄存器的影響, 但是arr1->data則會一直在L1 DCache中

struct array {
unsigned long length;
unsigned char data[];
};
struct array *arr1 = ...;
unsigned long untrusted_offset_from_caller = ...;


if (untrusted_offset_from_caller < arr1->length) {
  unsigned char value 
arr1->data[untrusted_offset_from_caller];   ...
}



把代碼擴展到如下, 假如其中arr1->length , arr2->data均不在cache中, 則會觸發CPU優先把IF中的內容執行, 看以下代碼可以發現arr2->data[index2] 其中index2依賴value, 而value亦來自arr1->data[untrusted_offset_from_caller] , 而這個會被加載到緩存裡,
這段代碼執行以後, if是沒有成功進入, 但代碼卻是被CPU被執行了, 而且結果也在cache了


由於載入cache後, 訪問內存的速度會變快, 只要我們給出一個臨界值, 便可以判斷出一個內存當前狀態是否存在於cache, 假如arr2->data[0x200] 在cache 那麼他時間一定比arr->data[0x300] 少很多 (一倍以上)。


當我發們哪個更少的話, 就能夠反向說明 arr1->data[untrusted_offset_from_caller] 中的值是1還是0

把例子單位細化到bit 我們可以知道一個字節每1個bit中的值是多少, 只要遍歷8次就能知道一個字節, 遍歷32次就能知道一個ULONG。


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];
 }
}


假設我們需要知道arr1->data[untrusted_offset_from_caller]中的值, 但沒辨法進入該if,整個過程我們可以通過時間間隔逆向反推而得知arr1->data中裡的值, 最核心原因也是因為cache的時間差比較大, 通過估量arr2->data[index2]的內存訪問時間間隔得知哪一個才是真的,從而得知arr1->data[untrusted_offset_from_caller]是1或0。


由於是預取內存, 沒有特權級的限制


漏洞構造總結:
(0) 建立分支, 使分支預測執行
(1) 在分支裡把需要知道的地址讀取一次 (內核地址也可以, 例子中是arr1->data[..])
(2) 在分支裡把該值 & 1 , 然后當成數組的索引訪問arr2->data一次
(3) 測試arr2->data[index2]的執行時間
(4) 較小的那個index2 反向推算后發現value 為1或0
(5) 得知目標內存中第一個bit的值, 反覆執行能知道整個內核中的所有值






留言

這個網誌中的熱門文章

How does Nested-Virtualization works?

Dig into IRQL