Spectre 漏洞
/ SPEK-ter /
現代 CPU 不會乾等。當它走到一個分支——一個 if、一次邊界檢查、路上的岔口——它會猜往哪邊走,並依那個猜測推測性地往前跑,於是等真正答案到時,它早已深入在有用的工作中(見 branch-prediction 與 speculative-execution)。若猜錯了,它就丟掉推測的結果,讓官方程式狀態看起來像什麼都沒發生。Spectre 在 2018 年揭露,正是發現「像什麼都沒發生」並不完全為真:誤推測的路徑會在快取裡留下指紋,而攻擊者能讀出那些指紋。
把訣竅一口氣說完。攻擊者訓練分支預測器去預期某個方向(例如反覆讓一次邊界檢查通過),然後餵進一個越界的輸入。CPU 信任它的訓練,推測性地越過檢查、讀了它不該讀的記憶體,並用那個秘密值去索引一個陣列——把一條依秘密而定的快取列拉進快取。接著真正的邊界檢查算出來了,CPU 發現那次存取違法,便丟棄架構效果。但那條快取列仍是暖的。一次 Flush+Reload 快取時序量測,就從快取裡把秘密讀出來(見 cache-timing-attack)。官方結果正確;秘密透過本應被抹除的工作所留下的快取足跡洩漏了。
Spectre 之所以重要、且被誠實地教,是因為它不是某顆晶片裡可修補的臭蟲——它是推測本身的後果,一個近二十年來幾乎每顆快速處理器都有的效能特性。它讓一個程式能跨越邏輯界線、從自己的位址空間內洩漏資料(一個沙箱誘使 CPU 去讀同一行程的另一部分),這破壞了 JavaScript 沙箱與許多防禦所仰賴的隔離。緩解措施是有的——在敏感處停止推測的柵欄、即使在錯誤路徑上也夾住索引的遮罩、抗再訓練的預測器——但它們是局部的、且耗費效能,而新的 Spectre 變種不斷出現。Spectre 持久的教訓是:乾淨的 ISA 契約藏著真實攻擊能撬開的微架構狀態,而效能與安全處於真正的拉扯之中。
程式做:if (i < array_len) y = probe[ array[i] x 256 ]。攻擊者先用合法的 i 呼叫多次,訓練預測器認為檢查會通過,然後用越界的 i 呼叫。CPU 推測性地假設檢查通過,讀了秘密位元組 array[i],並用它去碰 probe[secret x 256]——快取了某條特定的列。接著邊界檢查失敗、存取被抹除,但 probe 裡被暖過的那條列,向隨後的快取時序探測洩漏了 secret。
被訓練過的分支預測器推測越過邊界檢查;被抹除的讀取仍留下一個秘密形狀的快取足跡。
Spectre 不是你能在受害者那邊單純打補丁掉的軟體臭蟲,也非單一廠商獨有——它是推測本質的一部分,這就是為什麼它只能緩解、不能根治,也為什麼新變種不斷冒出。