Spectre 與 Meltdown(暫態執行攻擊)
/ SPEK-ter, MELT-down /
現代 CPU 不會乾等。為了維持快速,它們「猜」接下來會發生什麼——某個分支會走哪邊、某次載入會回傳什麼——並推測性地搶先去做那份工作,準備在猜錯時把它丟棄。Spectre 與 Meltdown(2018 年公開)正是利用一個驚人疏忽的攻擊:當 CPU 丟棄猜錯的工作時,它復原了可見的「暫存器」結果,卻「沒有」復原留在快取裡的副作用。那些殘留的快取痕跡,洩漏了推測工作所碰過的秘密。
把形狀走一遍。CPU 推測性地(亂序地、在確定之前)執行指令,只在確認它們走在正確路徑上後才「提交」;被誤推測的指令是「暫態的」——它們執行、影響微架構狀態,然後被壓除,彷彿在架構上從未執行。Meltdown 濫用這點,從使用者行程讀取核心記憶體:它發出一次對被禁核心位址的載入,後接一次依賴於它、對探測陣列的存取;權限錯誤只在提交時才送達,所以在推測視窗期間,那個秘密位元組被用來索引探測陣列,把一條列拉進快取。接著一個 Flush+Reload 快取旁路讀出哪條列被快取,復原了那個位元組——即使架構上的載入「失敗」了。Spectre 更微妙也更廣:它誘騙「分支預測器」推測性地讓受害者的程式碼走上它不該走的路徑(例如越過一個最終會失敗的邊界檢查),使受害者自己推測性地讀出越界的秘密資料並留在快取裡,攻擊者再透過同樣的旁路把它讀出。
它之所以重要,是因為這些攻擊打破了一道人人以為堅固的邊界——你就是不可能讀取自己沒有權限讀的記憶體——而它們住在晶片的效能最佳化裡,不在任何軟體漏洞裡,所以一個「正確」的程式仍會洩漏。誠實的說法:Meltdown 大致可藉分離核心與使用者分頁表(KPTI)修補,但 Spectre 是一個深而持久的問題,因為推測無處不在;緩解措施(像 lfence 的推測屏障、retpoline、微碼更新、停用某些共享)付出真實的效能代價,而新的暫態執行變種不斷出現。請對照微架構章節談推測執行與分支預測:這些攻擊正是那些功能被變成了洩漏。
// Spectre v1 的形狀:訓練分支預測器,再誤推測越過檢查 if (x < array1_len) { // 邊界檢查;攻擊者讓 x 越界 y = array2[ array1[x] * 256 ]; // 推測性地讀 array1[x](一個秘密) } // 並快取一條以那個秘密為索引的列 // 錯誤/檢查解析後壓除 y,但被快取的列存活下來 -> Flush+Reload
推測越過一個將失敗的檢查讀出秘密;壓除藏起了結果,但快取痕跡留了下來。
這些不是受害者程式裡的軟體漏洞——程式在邏輯上是正確的;洩漏在 CPU 的推測加上快取,這正是為何緩解措施付出效能代價,而 Spectre 不像 Meltdown,沒有乾淨的通用修法。