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 等等
詳見: 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的值, 反覆執行能知道整個內核中的所有值
Comments
Post a Comment