顛覆一個時代、讓Intel/AMD/ARM全趴下:Meltdown與Spectre攻擊深入解析
信息安全行業有一類很高端的攻擊方法,叫做邊信道攻擊(side channel,也有譯作側信道攻擊,或旁路攻擊的),聽著就感覺很高級有沒有。這種攻擊出現於上世紀 90 年代,以色列的高等學府似乎經常研製邊信道攻擊的奇技淫巧——所謂的邊信道,就是不從正面 gang,而是從側面竊取或傳遞信息的方式,比如利用設備運行時產生的電磁輻射,或者電流導致的機械波動雜訊,來分析破解出密鑰,甚至還能利用散熱風扇產生的雜訊、機箱上的 LED 燈閃爍變化,來將信息傳出。
許多邊信道攻擊條件都非常苛刻,而且攻擊雜訊比較大,情報獲取的正確率有時很堪憂,所以邊信道攻擊從來都不是主流攻擊手段。正兒八經應用邊信道攻擊的故事,除了前年也算比較知名的 Linux TCP 漏洞(CVE-2016-5696)[1]絕對也堪稱精妙絕倫,最近正火的,傳說波及 Intel、AMD、ARM 及全球絕大部分處理器的漏洞(實際上最終定了 3 個漏洞,分別是 CVE-2017-5753/5715/5754 [2]),Meltdown(熔斷)和 Spectre(幽靈)這倆攻擊的名字感覺都挺霸氣的,應該也算是邊信道攻擊的一大步了,起碼這回人家不「玄乎」了,是實實在在可以達成的攻擊。
連半死不活的 Windows 10 Mobile 都為這事兒收到更新了[2],這幾個漏洞究竟得有多大面兒啊?這次漏洞剛出來的時候,A 粉還高潮了一波,說農企終於要翻身了,結果等專門挖別家公司牆角的谷歌 Project Zero 團隊公布漏洞詳情後,說 AMD 也未能倖免,好傷心(Meltdown 主要影響 Intel 處理器,但並不意味著 AMD 不受影響;Spectre 則影響全球幾乎所有電子設備)。
作為理論分析的常客,既涉及邊信道攻擊,而且漏洞本身還是難得的硬體漏洞,據說近 1995 年以後、處理器支持亂序執行的電子設備幾乎都受影響(這麼說 A7 八核沒事?),能用來竊取信息,比如你的密碼、個人敏感數據,甚至拿下整個內核地址空間,咱們就得好好掰扯掰扯了。
Cache 的邊信道攻擊:FLUSH+RELOAD
這次有關 Meltdown 攻擊和 Spectre 攻擊,美國幾家學府和安全公司的研究人員共同發布了兩篇技術 paper,這兩篇文章中都一筆帶過了名為 FLUSH + RELOAD 的攻擊方案。但私以為,這種攻擊在 Meltdown 和 Spectre 的攻擊中還是佔據舉足輕重的地位的,即便它早在 2013 年就已經被人提出了。
FLUSH + RELOAD 當時是專門針對 Intel 處理器 L3 三級緩存的一種邊信道攻擊思路,也以 paper 的形式發表了[4],在個人計算機和搭建的虛擬環境中都能實現對敏感數據的竊取。實際上,有關利用 Cache 命中率進行密鑰分析的思路是最早 1998 年就提出的[5]。在解釋這種攻擊之前,咱先做個有關處理器的簡單科普。
注意,我要開始裝逼了。當代操作系統有個特性叫 page sharing,就是不同的進程可以進行主內存的共享。Page sharing 的價值主要在於兩個聯合運行的進程通訊更方便,還能減少內存佔用。本質上這是一種內存復用的思路,這種基於內容的 page sharing 用於在進程執行和使用共享庫時,可執行文件正文段的共享。而且不止是操作系統,如今的主流虛擬化 hypervisor 其實也用這套方案。比如雲計算技術中普遍採用的 de-duplication,就是不同虛擬主機共享一個內存模塊的技術,也是同理——像 VMware ESX、PowerVM 之類的 hypervisor 都是支持的。
系統對於共享 page 的映射,採用 copy-on-write(COW) 的方案——就是對改動的數據延後寫入到內存中,這也是 FLUSH+RELOAD 攻擊可導致信息泄露的一個基礎。
接下來就要談到處理器了。兩個進程共享內存頁,也就會共享同一個處理器上的 cache 部分,也就是處理器的高速緩存。Cache 是連接主內存和處理器前端的緩衝地帶,其讀寫速度比內存要快得多。處理器在進行數據處理時,會首先從 cache 中提取數據和指令,唯有在發現 cache 中沒有所需數據時才向主內存發出請求,要知道從主內存中提個數據,可能需要浪費多達 200 個時鐘周期,這對處理器這種高速設備而言絕對是不太能忍的,所以你看當代 CPU 的 cache 命中率有多高。
當代處理器都採用多層級 cache 方案,比如上面這張圖是 Intel 酷睿 i5-3470 處理器的 cache 架構。其 cache 分三級,分別是 L1、L2 和 L3 緩存,L1 離處理器核心最近,速度最快,容量也最小,L3 則離處理器最遠,容量相對也更大——此例中是 6MB。而且更重要的是,每個核心都分別有一個 L1 和 L2 cache,但 L3 cache 是四個處理器核心共享的。
Cache 中的存儲基本單位是 line,每條 line 包含固定位元組數。比如 i5-3470 的 cache line 尺寸為 64 位元組。另外作為共享 cache,所有 cache 數據的副本在此處都有一份(至少 Intel 處理器是這樣)。所以如果我們嘗試從 L3 擦除一些數據,則其他各級 cache 的數據都也可以被移除——這是 FLUSH+RELOAD 攻擊的基礎。
假設有這樣一個目標進程 1,為了讓它和我們構造的惡意進程 2 進行 page sharing,就將內存映射函數 mmaps 應用到目標可執行文件,令其進入惡意進程 2 的虛擬地址空間,完成映射文件的內存鏡像共享(需保證兩者在同一物理處理器上運行,可令其位於處理器的不同核心之上)。
攻擊第一步,讓惡意進程 2 監控某個特定的 cache line,並將其內容從 cache 中擦除;第二步,惡意進程 2 等待一段時間;第三步,惡意進程 2 重新載入剛才的那條 line。如果說載入這條 line 的時間比較久,就說明應該是從主內存中載入的(因為主內存很慢),最主要的是說明在等待過程中目標進程 1 並沒有嘗試載入這條 line;但如果在這一步中,惡意進程 2 載入這條 line 的時間很短,就說明目標進程 1 剛才也嘗試過載入這行 line(因為目標進程 1 載入過之後,這部分數據就已經寫到了 cache 中,而 cache 是很快的)——撞上了!
如此循環往複,如上圖所示,這樣一來就能得知哪些數據被存取過。就是所謂的 FLUSH+RELOAD L3 cache 攻擊。當然了,這種攻擊還是存在精度問題的,比如說上圖 C 的情況,惡意進程請求載入某條 line,在極短時間內目標進程也恰巧請求載入這條 line,這樣觀測到的結果就會有問題;另外還需要考慮一些處理器的性能優化方案的雜訊干擾,比如說現在的處理器的空間局部性實現的數據預取(prefetch),還有像是分支預測,這要求在設計攻擊程序(建議採用循環體之類以內存訪問相對頻繁的為目標,如上圖 E)和對結果進行分析的時候有過濾策略。
比如像上面這樣,其中第 14 行 cflush 就是從所有 cache 中擦除某行 line;另外,如 mfence、lfence 這些指令實現指令流的串列序列化,避免並行和亂序執行的出現。這篇 paper 還特別舉了攻擊的實例,以這種攻擊方法從 RSA 實施方案 GnuPG 中獲取私鑰。按照這篇 paper 所說,平均而言,這種攻擊方案通過觀察單獨的一個簽名或加密輪,平均就能恢復密鑰 96.7% 的 bits。
Meltdown:亂序執行惹的禍
上述 FLUSH+RELOAD 攻擊實際上就是這次 Meltdown 和 Spectre 攻擊最終環節的組成部分,也是這兩個攻擊被稱為邊信道攻擊的原因:想想,敏感信息的獲取,靠的是檢查讀取某條 cache line 的時間,這已經是比較典型的邊信道攻擊了,雖然好像感覺不夠神奇。不過在 Meltdown 和 Spectre 的攻擊鏈條中,FLUSH+RELOAD 並非唯一實施方案,還有各種變體,比如 Prime+Probe 也是可行的,這裡就不做展開了。但這部分構建了攻擊鏈中的 convert channel,也就是最後獲取到敏感數據的一個秘密通道。
這裡值得一提的是,原 FLUSH+RELOAD 攻擊思路對於 ARM 架構是無效的,因為 ARM 雖然也有擦除 cache line 的指令,但這些指令僅可用於高許可權模式,ARM 架構不允許用戶進程選擇性地擦除 line;對 AMD 處理器攻擊也無效,作者推測 AMD 處理器的緩存結構可能是非包含式的,即 L1 的數據不需要存在於 L2 或 L3 中,所以擦除 L3 cache 的某行 line,並不對 L1 構成影響。在 Spectre 攻擊的 paper 中[7],研究人員提到他們不僅採用 FLUSH+RELOAD 攻擊方案,另外還融合了 EVICT+RELOAD 方案——這本質上算是前者的一個變體,只不過後者對 line 的擦除方法更複雜,這可能是 Meltdown 和 Spectre 得以實現的一部分,有興趣的同學可以去深挖後一種方案[8]。
就 Meltdown 而言,這種攻擊方式主要利用的是當代處理器的亂序執行特性,可以在不需要進行系統提權的情況下,就讀取任意內核內存位置,包括敏感數據和密碼,甚至拿下整個內核地址空間。不過它對 AMD 和 ARM 的處理器也是無效的,但原因似乎並不像上面這樣。研究人員在 paper 中提到[6],Meltdown 在 ARM 和 AMD CPU 上的攻擊復現並不成功,但可能只需要對攻擊進行一定優化,深入挖掘依然可能會成功,比如對競爭條件進行一些調整,所以 Meldown 攻擊或許也並不僅限於 Intel。
新一波裝逼又要開始了,這裡我們再做一些簡單的科普。「亂序執行」本身不是個生詞了,當代的高性能處理器都有亂序執行特性:早些年 CPU 性能每年翻番都並不稀罕,通過增加核心數、時鐘頻率,加寬管線外加工藝迭代就能實現。但是在順序執行架構達到瓶頸之後,架構的優化就成為一個重要方向:順序執行架構中,指令完全按照一個不變的順序執行,就算 CPU 運算單元的執行速度很快,CPU 卻浪費大量時間在等待,許多單元處在閑置狀態,所以亂序執行成為提升效率的重要解決方案。好比你攢台 PC,如果顯卡還沒到貨,肯定不會守在門口傻等,而是把其餘部分先組裝好。
1967 年,Tomasulo 最早開發出了可用於動態規劃指令亂序指令的演算法,當時他就提出了一個叫 Unified Reservation Station 的東西。CPU 在不需要將某個值存儲到寄存器並讀取的情況下,就可以在這裡使用值。此外,Unified Reservation Station 還通過一個 CDB(共用數據匯流排)把所有執行單元連接起來。如果某個操作數尚未準備就緒,URS 單元可以監聽 CDB,獲取裡面的數據,然後就可以直接執行指令了。(請注意,這一段對於理解後面的 Meltdown 攻擊流程很重要)
在 Intel 的處理器架構中,其整條管線包含了前端、執行引擎(後端)和存儲子系統。前端會從存儲系統中(包括 cache 和主內存)讀取 x86 指令,隨後分解成為 μOP(所謂的「微指令」,有些類似於將 CISC 轉為 RISC 的過程),μOP 再發往執行引擎。亂序執行操作就是在執行引擎中發生的,如上圖所示。
其中有個 Reorder buffer(重新排序緩衝器),負責寄存器分配、寄存器重命名和 retire。在經過此處之後,μOP 就轉發到了上面提到的 Unified Reservation Station,此處對操作進行排序,排隊完了就可以發往執行單元了;執行單元就能進行 ALU 加減乘除、AES、AGU 或者載入存儲執行之類的操作了。AGU(地址生成單元)以及載入與存儲執行單元直接與存儲子系統相連,可以直接處理請求。
現在的處理器一般都已經不再直線型執行指令,比如分支預測單元可以猜測接下來該執行什麼指令——分支預測器在某個 if then 語句中的條件語句還沒有判斷之前,就已經開始預測其中的分支運行結果了。在這條線路上,那些沒有依賴關係的指令可以先執行,如果預測正確,運算結果就可以馬上使用了。如果預測錯誤,Reorder buffer 清理回滾,並重新初始化 Unified Reservation Station。
選讀:有關地址空間(可略過)
為了讓進程彼此間隔離,CPU 支持虛擬地址空間,在此虛擬地址會轉為物理地址。一個虛擬地址空間會被分成幾個 page,這些 page 通過多層級頁轉換表(multi-level page translation)分別映射到物理內存。這裡的轉換表,定義了虛擬和物理間的映射轉換,另外還定義了用於進行許可權檢測(可讀屬性、可寫屬性、可執行屬性、用戶可訪問屬性)的保護屬性。現如今的轉換表放在某個特定的 CPU 寄存器裡面。
在每次進程上下文切換的時候,操作系統都會用另一個進程的轉換表地址去更新該寄存器,所以每個進程都有個虛擬地址空間,每個進程只能參照其自有虛擬地址空間的數據。每個虛擬地址空間又切分成用戶(User)和內核(Kernel)兩部分。運行中的應用可以訪問用戶地址空間,但內核地址空間僅當 CPU 運行在特權模式下才可以訪問。這一步是由操作系統決定的,操作系統在相應的轉換表中禁用用戶可訪問屬性即可。
實際上內核地址空間不僅包含內核自己用的部分,也需要在用戶 page 執行操作,比如往裡面灌數據。因此,整個物理內存在內核中都有映射。在 Linux 和 OS X 系統中,這種映射比較直接,比如整個物理內存直接映射到預定義的虛擬地址;Windows 的情況則比較特殊,Windows 系統維護分頁池(paged pool)、非分頁池(non-paged pool)以及系統緩存(system cache)。這些「池」就是內核地址空間中的虛擬存儲區域,將物理頁映射到虛擬地址,其中非分頁池要求地址位於內存中,分頁池由於已經存儲在了磁碟上,所以可以從內存中移除。而系統緩存部分則包含所有文件備份頁的映射。
一般的內存破壞漏洞利用,就需要某個數據的地址。為了阻止內存破壞一類攻擊,就有了 ASLR 等技術。為了保護內核,KASLR(內核地址空間布局隨機化)在啟動的時候會對內核地址進行隨機化,令攻擊難度更大。KASLR 是在 2013 年引入到 Linux 內核中的技術,到去年 5 月才在 Linux 4.12 版本中默認開啟。實施 Meltdown 攻擊的話,要克服 KASLR 就需要獲取到隨機偏移量。但要做到這一點也並不難。
這裡我們看一個亂序執行的簡單例子。這段代碼第一行就產生了異常(可以是訪問無效地址,或者運算除以 0 的除法),按照控制流來說,發生異常就該跳到操作系統的異常處理程序,應用終止,後面的代碼就不會繼續執行了。但因為有亂序執行的存在,第三行指令可能已經部分被執行了,只不過沒有 retire,所以實際上並不會在架構層面產生可見的影響,即它對寄存器、內存都不會有影響。
這部分執行最終會被丟棄(恢復狀態),寄存器和內存的內容也不會執行 commit。但它對微架構層面有影響——這裡所謂的微架構也就是 cache 部分了。在亂序執行過程中,引用的內存讀取到寄存器上,也存儲到了 cache 裡面,cache 中的內容也會得以保留。這樣一來,利用本文第一部分提到的 cache 邊信道攻擊就可以獲知其中的信息了:也就是不停探測某個內存位置是否進入 cache。
完整的 Meltdown 攻擊主要分成兩個組成部分,第一部分就是讓 CPU 執行某個永遠不會在路徑中被執行的指令,如上例中的第三行代碼——我們將這種指令稱作 transient instruction。真正要講這種 transient instruction 應用到實踐攻擊中,就要求指令序列中包含密鑰之類的東西——就是攻擊者想要竊取的數據。
Meltdown 的第二部分主要就是我們這篇文章第一部分提到的那種基於時間的邊信道攻擊了,把密鑰給恢復出來,或者說對 L3 cache 進行攻擊獲取數據的方法。這樣我們基本上已經把 Meltdown 的攻擊過程給說清楚了。但在實際操作中,攻擊者要獲取敏感數據,那麼就意味著要訪問用戶不可訪問的 page,比如說內核頁——訪問這樣的位置,由於沒有許可權,所以會導致異常。攻擊者需要去想辦法處理這樣的異常,否則的話進程就要被終止了。處理方法可以是把攻擊程序分成不同部分,只在子進程中執行後面的 transient instruction 序列,而父進程通過後續的邊信道攻擊來恢復密鑰。
接下來就是第二部分的邊信道攻擊了,也就是上圖中所謂構建 covert channel 秘密通道,悄悄把已經存在於 cache 中的密鑰給竊取到了。在這部分里,我們可以把 transient instruction 序列看成是這個通道的發射端,而接收端可以是不同的進程(和第一部分的 transient instruction 可以是無關的),比如可以是前面提到的父進程。
Meltdown 攻擊的流程梳理
在上面的代碼中,攻擊者通過 probe_array 開闢了一段內存,而且是 256 個 page 大小的內存(乘以 4096 是假設一個 page 為 4KB),而且要保證這部分內存沒有 cache 過。接下來我們就徹底地理一理整個邏輯的過程究竟是什麼樣的:
第一步:讀取敏感數據
從主內存中載入數據到寄存器,引用主內存中的數據需要使用虛擬地址。在將虛擬地址轉為物理地址的過程中,CPU 還檢查了該虛擬地址的訪問許可權,用戶可訪問,還是僅內核可訪問。所有內核地址導向有效的物理地址,CPU 也就能夠訪問這些地址的內容了。當前許可權不允許訪問某地址,CPU 就會生成異常,用戶空間無法簡單地讀取到這類地址的內容。
不過亂序執行上線了,CPU 會在非法內存訪問和產生異常這個很短的時間窗口內,亂序執行後面的指令。在上面的彙編代碼中,第 4 行,RCX 寄存器裡面有個內核地址,這一行載入了該地址中的位元組值(敏感數據);然後把它放到 RAX 寄存器(這也是 x86 架構中的一個 64 位寄存器)的最低有效位上。如我們前面科普的那樣,mov 指令被處理器讀取,編碼稱 μOP,分配後發往 reorder buffer。在這個 buffer 中,RAX 和 RCX 這樣的架構級寄存器,會映射到實際上的物理寄存器。
由於亂序執行的存在,代碼第 5-7 行也已經解碼分配成了 μOP,隨後這些 μOP 被發往 unified reservation station(還記得文章前面提到的內容嗎?翻回去看看)。如果後面的執行單元此時被占,或者執行指令所需的某個操作數還沒算出來,μOP 就會在這裡等著。實際上,後面這幾行指令的 μOP 可能已經在 unified reservation station 裡面等著前面第 4 行的內核地址抵達了。這個時候,如前文所述,由於這個什麼鬼 station 通過共用數據匯流排監聽執行單元,所以這部分 μOP 就可以開始執行運算了!!!是不是感覺亂序執行頓時就高級了?
當 μOP 執行完成之後,他們按照順序 retire,執行結果會 commit 到架構狀態中。在 retire 階段,CPU 才會處理,前面指令執行過程中產生的異常和中斷。這個時候,第 4 行的 mov 指令顯然要被斃掉了(因為沒許可權訪問內核地址),異常此時被處理,整條管線清空,消除亂序執行指令計算的所有結果。
但不要忘記,cache 裡面還有貨呢,這些貨沒有清掉。
第二步:傳輸敏感數據
如果 transient instruction 序列在上面的 mov 指令 retire 之前執行,那麼第一步就成功了。這裡生成異常(即 mov 指令 retire),和 transient instruction 之間有個競爭狀態,就是 transient instruction 必須要先執行,否則異常如果先生成了,後面指令的亂序執行就不會進行——所以攻擊中需要減少 transient instruction 序列的運行時間,這才能夠提升攻擊的成功率。接下來就該是把密鑰傳出去了。
實際上,transient instruction 所做的事情其實就是把前面的敏感數據放進 cache 裡面。那麼在傳出敏感數據階段,攻擊者用 probe_array 開闢一部分內存(還記得前面的代碼嗎?),並且確保這部分內容沒有被 cache。在上面彙編代碼的第 5 行,是把從第一步中取得的敏感數據,乘以 page size,假定 page 大小是 4KB(對應於高級語言 access(probe_array[data * 4096]))。
這個乘法是為了保證對數組的訪問,相隔距離足夠大,阻止處理器的 prefetcher 去預取相鄰內存位置。這樣的話內存佔用 256 x 4096(256 個 page)。代碼的第 7 行,是將乘法運算過後的敏感數據,和 probe array 的地址相加,組成最後 convert channel 的目標地址。這個地址被讀取,用以對相應 line 進行 cache。
第三步:接受敏感數據
這部分其實就是本文的第一部分,Meltdown 攻擊描述也基本採用了 FLUSH+RELOAD 攻擊方案。上面第二步中提到的 transient instruction 序列執行後,probe array 的一個 line 被 cache。probe array 的 cache line 位置就取決於第一步中讀取的敏感數據。那麼攻擊者對 probe array 的所有 256 個 page 進行迭代,測量 page 的每個首行 cache line。包含已寫入到緩存的 cache line 的那個頁數,也就直接表示敏感信息的值了。
對 256 個 page 進行迭代,觀察讀取時間,僅有一個 cache 命中,這個命中的 page 就是 data 值啦!
這兩部分是不是表示看不懂了?沒關係,反正只要搞清楚,惡意進程依靠一些巧妙的演算法,試出了這個敏感數據是什麼——根據文章第一部分 FLUSH+RELOAD 的思路,再用編程的一些技巧就可以搞定。
通過對前面三個步驟進行重複操作,對所有不同的地址進行迭代,攻擊者是可以還原出整個內存樣貌的。研究人員利用這套方案,在 Intel 酷睿 i7-6700K 平台,外加 Ubuntu 16.10(Linux 內核 4.80) 操作系統中進行攻擊,能夠從內存中獲取及其向 web 伺服器發出請求的 HTTP header。另外,還能獲取 Firefox 56 瀏覽器部分的內存部分,並找出存儲在其內部密碼管理器中的密碼。
Firefox 56 存儲的密碼一覽無遺啊
聽說補丁會讓處理器性能下降 30%?
好了,裝逼基本結束了,請放心地往下看吧。Meltdown 的這套攻擊方案,利用的是漏洞 CVE-2017-5754。實際上,這種攻擊技術的提出並不是近期才有的,去年 7 月份就有人寫過一篇警告文《Negative Result: Reading Kernel Memory From User Mode》[9],不過此人當時未能完美復現 Meltdown 造成的問題,但他自己說似乎是「開啟了潘多拉魔盒」。
這是個實實在在的硬體級漏洞,屬於 CPU 微架構實施層面的漏洞。鑒於硬體產品的特殊性,讓 Intel 召回近 20 年的產品是不現實的。不過封堵這種程度的漏洞,最佳方案難道不是禁用亂序執行嗎?(話說移動 CPU 的 big.LITTLE 架構中,小核心貌似都不支持亂序執行誒)但這對當代 CPU 性能影響將會是災難性的。
硬體層面實際上還可以對許可權檢查和寄存器讀取,實施一個序列化過程——如果許可權檢查失敗,則不讀取該內存地址。但這麼做的代價也會非常大,每次內存讀取都要進行許可權檢查。更加現實的方案應該是從硬體層面實現對用戶空間和內核空間的隔離:設立分割比特位,內核必須位於地址空間上部,用戶空間則必須位於地址空間下半部。這樣一來,內存讀取即便通過虛擬地址都能發現讀取目標是否違反安全邊界。
硬體層面的修復對我們來說都是空談。操作系統層面,各主流操作系統這兩天都已經陸續推了針對 Meltdown 的修復補丁。主要是一種研究人員推薦叫做 KAISER 的方案,在 Linux 內核更新中這項特性叫 KPTI(內核頁表隔離)——其實現方法是,讓內核不要映射到用戶空間中。這種方案其實早兩年就有了,當時是為了杜絕攻破 KASLR 的邊信道攻擊提出的。
不過巧合的是,它也能預防 Meltdown 攻擊,因為內核空間或物理內存在用戶空間中已經沒有有效的映射了。提出相對完整的解決方案的 paper 就是去年發布的,研究人員在 paper 中將這套方案稱為用於內核地址隔離非常「高效的實踐系統」[10],號稱對性能影響僅有 0.28%。文中提到,實驗中發現禁用全局比特位(也就是標記許可權的比特位)對於性能的影響竟然是微乎其微的,而且當代 CPU 對於 TLB(可以看做是頁表的 cache)管理的優化讓性能影響並不大。有興趣的可以去研究下這套方案。
這個值的可靠性我們不知道,不過實際上微軟在 11 月份推的 Windows 10 Insider 版本(17035 以後的版本)中就應用了 KAISER 方案[11],當時也並沒有用戶反饋系統變卡。基本可以肯定的是,對 I/O 性能造成影響是一定的,因為 KAISER 的這套方案搞了個 shadow 地址空間頁結構,去掉了標記許可權的全局比特位,需要頻繁切換 CR3 寄存器,並清除非全局的 TLB 項。曝出打補丁後性能下降多達 50% 最早的一篇文章提到[12],用酷睿 i7 8700K、三星 950 PRO NVMe SSD 來跑分,主要考驗 I/O 測試,FS-Mark 項目表現出差異多達 50%。不過國外網友陸續有了新的測試結果,並且認為 KAISER 補丁的跑分標籤很大程度取決於執行的跑分任務,FSMark 似乎只是個特例。
最後值得一提的是,KAISER 仍然存在一定的局限性,鑒於 x86 架構的設計,某些高許可權內存位置必須映射到用戶空間。這仍然留下了一小撮攻擊面,這些內存位置仍然可從用戶空間讀取,只不過這些內存位置並沒有什麼敏感信息——但善加利用,比如這些位置仍然可以包含指針,打破 KASLR 也就成為可能了。
選讀:有關 Spectre(可略過)
Spectre 攻擊前文一直沒怎麼提到。Spectre 的情況在這裡只略作介紹,因為我們感覺,Spectre 的利用難度要大很多(攻擊成本更高)。其基本思路和 Meltdown 是差不多的,最終也是通過 FLUSH+RELOAD 這樣的 cache 邊信道攻擊來獲取內存裡面的東西,只不過切入方式利用的是分支預測——就是我們前面提到的,比如 If Then 語句出現,處理器在還沒有判斷 if 條件是否滿足的情況下,就會去預測後面的分支,如果預測正確就可以有效提升運算效率。對分支預測的利用,無論是分析成本,還是攻擊成本,都大了不少,只不過包括 AMD、ARM、Intel 等在內的處理器全部都中招。
Spectre 攻擊涉及到兩個漏洞,分別是 CVE-2017-5753 和 5715。如上面這個語句,綠色部分那一行是個 if 條件語句。上述代碼,如果 arr1->length, arr2->data[0x200] 和 arr2->data[0x300] 都還沒有進入 cache,處理器都還沒有判斷條件語句,但其它數據都已經 cache,而且分支條件預測為真,那麼處理器在載入 arr1->length 之前就會載入 arr1->data[untrusted_offset_from_caller] 的值,並開始載入 arr2->data 數據依賴偏移,將相應 cache line 載入 L1 cache。
但最後處理器發現,預測錯誤,此刻包含 arr2->data[index2] 的 cache line 已經位於 L1 cache,那麼此時由惡意程序去請求 arr2->data[0x200] 和 arr2->data[0x300],測量請求載入的時間,就能判斷 index2 的值是 0x200 還是 0x300 了,也就可以知道 arr1->data[untrusted_offset_from_caller]&1 是 0 還是 1。
篇幅有限,我們無法再展開做更為具體的分析和講解,不過這個邏輯本質上和 Meltdown 對於亂序執行的利用有些相似,都建基於處理器率先執行了後面的指令,而且還把數據放在了 cache 中,但明顯更為複雜。最終也都通過惡意程序去檢查讀取載入指令所需時間,來推測某值。
要利用這樣的行為,攻擊者需要在目標環境中構造這樣的代碼模式執行。有兩種思路,要麼這種代碼模式在現有代碼中就有,或者需要有個解釋器(interpreter)或者 JIT 引擎,最終可以生成這種代碼模式。第一種思路太苛刻了,第二種是谷歌採用的方案,他們選擇了 eBPF 位元組碼解釋器和 JIT 引擎。
另外,這套攻擊還涉及一些複雜的問題,比如說需要對分支預測機制首先進行訓練,令其足以產生錯誤的預測結果;然後操作目標進程,執行上述構造出來的代碼模式;最後用上咱們文章第一部分提到的 cache 邊信道攻擊。為此,谷歌的研究人員還專門逆向分析了 Intel Haswell 的分支預測器。
Meltdown 的 paper 中還提到,Spectre 攻擊需要專門為目標進程的軟體環境做定製。我個人意見是:Spectre 可能會成為未來 APT(高級持續性威脅)攻擊中的一環,畢竟它適用於幾乎所有處理器,但攻擊難度略高,指望黑客專門盯著個人用戶使勁兒搞,成本收益或許還是不對等的。
值得一提的是,針對 Spectre,目前並沒有行之有效的解決方案,包括 KAISER 對 Spectre 也是沒用的。paper 中提到的一些思路都存在一些問題。Meltdown 與 Spectre 官網在 Q&A 中表態說[13],修復 Spectre 並不容易,未來可能會困擾我們很長一段時間;未來會開發補丁讓攻擊變得更困難。
開發與安全的割裂是否將永遠持續?
Meltdown 和 Spectre 兩者幾乎影響到當代所有的電子設備,包括筆記本、筆記本、智能手機,以及對企業而言悲劇的雲伺服器。但凡操作系統(微軟、蘋果、谷歌、Linux)、晶元製造商(蘋果、三星、Intel、AMD 等)似乎都在慌忙地對這次事故做響應,尤其 Intel 公關,以為去年忙完 Zen 的滅火之後今年得交好運了,沒想到碰上這麼個事兒。但從微軟去年 11 月的動作來看,大約 Meltdown 的修復從來不是最近才開始的。
不過對個人設備的 Meltdown 和 Spectre 攻擊,初始攻擊向量應該仍然是惡意程序:這仍然是需要用戶去交互的,比如引誘用戶下載惡意程序。但在雲平台就不同了,The Verge 認為,這次漏洞波及最大的應該是雲計算,畢竟雲平台是個更大的威脅空間。一台雲伺服器上,就會有好些租戶,所以亞馬遜、谷歌、微軟、阿里也都全面卷了進來。如 paper 中對虛擬環境的測試那樣,meltdown 要針對同一台雲主機上的其他租戶竊取數據,也是完全可行的。大量中小型企業的基建現如今就在雲上,這樣一來隱患就顯得相當之大。主要的這幾家雲服務提供商也已經很快打上了補丁,谷歌發言人說,其雲伺服器現在已經不受 Meltdown 和 Spectre 影響,但沒有透露究竟是怎麼保護 Spectre 的[14]。
硬體層面的某一種優化就會帶來微架構元素的狀態改變,並危及安全軟體的實施——這其實是 20 年前的一個共識[15]。比如說出現一個漏洞,是由於硬體優化導致微架構狀態的變化,如果某種加密演算法沒有及時針對可能出現的泄露做防護,就會產生這樣的 BUG。不過 Meltdown 的出現顛覆了現狀,因為攻擊者甚至可以讀取每個比特位,而無視精度問題,這是任何演算法改進都無法做出防護的。就我們而言,唯一可以冠冕堂皇對企業做出的建議就是:把安全融入到開發中去。
信息安全行業現如今正在給企業、互聯網產品灌輸一個理念,即將安全融入到開發環節中去,而不是在開發結束後再檢測安全問題。這是相當理想化的一種開發理念,但開發和安全一直以來都有不可逾越的鴻溝,光是開發人員和安全人員兩者技能傾向性上的不同,就好像 Intel 的工程師們大部分都並非安全專家,何況這次的攻擊還是用了邊信道這麼賴皮的方法,任誰也很難想到。就不大可能讓這種「融合」真正發生。更何況,開發人員還有產品上線的壓力。
MIT 的一份研究顯示,去年一年召回的植入式醫療 IoT 設備達到 150 萬台,都是因為安全問題,其中還包括心臟起搏器——這個數字聽來似乎並沒有很龐大。如果說現如今的某些硬體遭遇安全問題,比如這次的處理器漏洞,還能在操作系統層面加以彌補,那麼醫療 IoT 設備出現安全問題就沒有這種可行性了。而且這種程度的召回已經不僅限於企業的經濟損失,更關乎到用戶本身的生命健康,還要考慮召回的難度問題。隨著工業 5.0 時代的到來,未來不光是醫療設備,植入式設備、VR/AR 設備的出現可能會越來越常見,那麼當他們出現嚴重的安全問題時,我們又該何去何從?
TAG:愛活網 |