快取時序攻擊(cache-timing attack)
快取是一個又小又快的儲存區,記住最近用過的資料,讓下次存取很快;命中很快,未命中得跑主記憶體則很慢,而這個速度落差是真實且可量測的(見 cache)。快取時序攻擊把這個有用的落差變成洩漏。攻擊者無法直接讀受害者的秘密,但受害者的秘密會影響它碰哪塊記憶體,這決定了什麼東西最後進到共享快取——而攻擊者靠對自己的存取計時,就能分辨哪些列是暖的,從而重建受害者做了什麼。
經典手法叫 Flush+Reload,在攻擊者與受害者共享的記憶體(例如共享函式庫)上分三步運作。先 flush:攻擊者把選定的快取列逐出,保證它是冷的。接著讓受害者跑一小段。然後 reload:攻擊者存取同一條列並計時。存取快代表受害者碰過那條列(它把列拉回了快取);存取慢代表受害者沒碰。藉由挑選對應到依秘密而定資料的列——比方說以金鑰位元為索引的密碼學查表項——攻擊者就一位一位地讀出金鑰。相關的方法 Prime+Probe 則把快取自己填滿,再觀察自己哪些列被受害者逐出。
讓這件事如此重要的是:快取時序正是招牌推測執行攻擊背後的讀出機制。Spectre 與 Meltdown 誘使處理器推測性地載入一個秘密、並用它去索引記憶體;推測被回滾了,但它碰過的那條快取列仍是暖的,於是 Flush+Reload 就從快取狀態把那個秘密讀出來(見 spectre-vulnerability 與 meltdown-vulnerability)。誠實的難處是:快取對效能不可或缺,且為了效率而共享,所以這條通道就嵌在電腦跑得多快這件事裡。緩解措施——切分快取、絕不用秘密索引記憶體的常數時間程式、在切換脈絡時清空——全都耗費速度。
對一張共享密碼學表做 Flush+Reload:攻擊者把表項 T[k] 的快取列逐出。讓受害者做一次加密。接著重新載入 T[k] 並計時。若存取快(比如 50 週期),代表受害者存取了 T[k],洩漏相關金鑰位元選了索引 k;若慢(比如 300 週期),代表沒有。在各表項間重複,就拼出金鑰——純粹靠攻擊者自己重新載入花了多久。
重新載入很快 = 受害者碰過那條列;命中/未命中的時間落差就是全部的訊號。
受害者的程式碼可以功能正確、毫無臭蟲——它只因為依秘密決定碰哪塊記憶體就洩漏了。這就是為什麼即使結果正確,「依秘密而定的記憶體存取」仍被視為危險。