一個硬體安全工程師眼中的Meltdown
本文作者是安天微電子與嵌入式安全實驗室的Tbsoft博士。
1、現代計算機體系結構(Architecture,也譯作架構或者系統結構)和CPU微體系結構(Microarchitecture,也譯作微架構或者微結構)
現代計算機的體系結構基本都是基於馮·諾依曼體系結構,也就是「存儲程序方式」,即程序指令代碼(指令)和數據都放在內存中,運行時CPU從內存中逐條取指令並執行,指令還可以在必要時訪問內存數據。
哈佛結構是馮·諾依曼結構的一種變形,特點是將程序(指令)存儲器和數據存儲器分開,即有兩塊獨立的內存,一塊存放指令,一塊存放數據,但沒有脫離「存儲程序方式」這種基本的方式,因此仍然屬於馮·諾依曼結構的變形或者改進。
對於程序員,即使是彙編語言甚至機器語言程序員,最多也只能涉及到內存指令和數據這一級,計算機暴露給程序員的,只能是馮·諾依曼結構或者哈佛結構,稱為計算機體系結構(Architecture)。
但CPU本身是由運算器、控制器、寄存器等部件組成的硬體電路,內存指令被取入CPU後,在CPU內部還要經過複雜的指令解碼等過程,才能驅動CPU硬體電路相應部件,按照指令具體需求進行相應動作,最終完成指令執行,這一過程是由CPU內部的取指令、指令解碼、部件驅動等硬體電路完成的,不同CPU差異很大,即使指令集相同的CPU也可以有不同的電路實現方法,這種CPU內部硬體電路的結構,稱為CPU的微體系結構(Microarchitecture)。
CPU微結構是設計CPU的核心,對於程序員而言,CPU微結構並不暴露給程序員,最底層的程序員的極限也只能是通過CPU指令來訪問CPU,換而言之,程序員使用CPU的最小操作粒度是指令。但CPU指令在CPU內部可能還會被解析成為更小的執行單元,最終驅動CPU的單個硬體電路部件,也就是說,在CPU微結構中有更小的操作粒度,例如微操作(MicroOP,μOP)或者微指令,它們都是由CPU微結構決定的。
或者說,CPU對於程序員而言也是一個黑盒子,程序員只要按照CPU指令集,用相應的指令編寫程序(如果是高級語言,則由編譯器或者解釋器將高級語言語句最終轉換為CPU指令),並將指令程序和相應數據載入到內存(現代通常由操作系統完成)即可執行程序,程序員只訪問到計算機體系結構這一級,至於指令在CPU內部是怎麼進一步解析並最終執行的,那是CPU微結構決定的,對程序員不開放,程序員不關心,通常也不應該關心,實際也無法關心。
2、如何提高CPU的運行速度
CPU執行一條指令,一般來說,最少也要經過從內存中取指令,將指令解碼解析成微操作(μOP),微操作最終驅動硬體電路部件三個步驟(簡稱取指令、解碼和執行),如果執行一條指令,要等到這三個步驟都完成後,才能執行下一條指令,則一條指令執行時間過長(用CPU硬體術語說就是消耗多個時鐘周期),CPU運行速度就無法得到有效提高。
如果在第一條指令解碼時,同時取第二條指令;然後等到第一條指令執行時,第二條指令同時解碼,同時還取第三條指令……這樣周而復始,取指令、解碼和執行就可以重疊進行,就好比生產流水線上的工人,第一個工人對第一個產品加工好第一道工序後,將產品傳遞給第二個工人加工第二道工序,這是第一個工人可以對第二個產品加工第一道工序了……這樣可以避免工人不必要的空閑等待,生產效率就會大大提高,這一提高CPU運行速度的方法,也就稱為「流水線」,現代CPU均使用流水線技術。
流水線有效減少了單條指令的平均執行時間,但指令仍然是一條條順序執行的,實際上,很多指令是彼此不相關的,例如訪問兩組完全不同寄存器的兩條指令,它們完全可以並行執行,執行順序的先後並不影響最終執行結果,對於這樣的不相關指令,完全可以設計多個執行部件電路,直接扔到不同執行部件中去並行執行,稱為亂序(Out-of-order)執行,可以進一步提高指令執行速度。亂序執行在現代CPU中廣泛應用,甚至對於有相關性的指令,也可以採用寄存器換名等手段將它們變成不相關指令,從而進行亂序執行。
但流水線和亂序執行都會碰到一個問題,從原理上講它們僅適用於純順序執行的指令,一旦遇到分支,即條件跳轉指令,因為不執行到條件跳轉指令本身,是沒法知道程序轉向何處執行的,也就是條件跳轉指令的下一條指令在未執行前不確定,因此無法預先取得條件跳轉指令的後續指令,這時流水線和亂序執行都會失效,因為它們的前提是預先取得後續指令。
為了盡量解決這個問題,現代CPU廣泛使用「分支預測」手段,也就是預測條件跳轉指令會跳向哪個分支,然後對這個分支進行預取後續指令。分支預測的常用策略是:如果某一段時間內某一條件跳轉都走向某一固定分支,則可以預測這條條件跳轉指令下一次很大可能也走向這一分支。
典型例子:程序中的循環結構一般要循環多次才結束,那麼在循環結束之前,判斷循環條件的條件跳轉指令顯然都是走向繼續循環分支的。
分支預測配合流水線和亂序執行,能夠大大提高CPU的運行速度,因此現代CPU微結構基本都使用這種設計。
3、分支預測帶來的問題——指令執行的「回滾」
分支預測並不能保證100%的成功預測,一旦預測失敗,也就是最終執行到條件跳轉指令時,發現跳轉目標不是先前預測的分支方向,那麼按照分支預測預取的後續指令實際上失效,這些指令已經完成的工作必須「取消」掉,否則就會造成錯誤的指令執行。
用一個程序員容易理解的比喻:分支預測的後續指令執行,好比一個「事務」,如果分支預測是正確的,那麼「事務」可以「提交」,這些後續指令就真正起作用;如果最終執行到條件跳轉指令時發現分支預測是錯誤的,則「事務」必須「回滾」,即使後續指令已經執行了,甚至是亂序執行了,已經完成的工作也都必須全部「撤銷」,後續指令要看起來沒有起任何作用,重新到正確的分支取新的指令執行。
理論上說,不管指令執行「提交」還是「回滾」,都只與CPU微結構相關,其過程程序員應該看不到,程序員只能看到宏觀指令按照程序流程執行,CPU內部對程序員仍然應該是黑盒子。
4、克服CPU運行速度與內存訪問速度的差異——高速緩存(Cache)
目前CPU主頻已經達到3GHz以上,普遍採用多核並行,儘管主內存(DDR SDRAM)的主頻已經達到2GHz—3GHz甚至更高,也無法完全滿足多核CPU運行速度的需求,因為指令執行還是必須從內存中取指令,如果內存訪問速度不夠,CPU運行速度會受到內存訪問速度的限制。
為了克服這個問題,目前採用在CPU與主內存之間插入多級高速緩存(Cache)的方法,Cache是一種訪問速度極高的存儲器,甚至可以集成在CPU內部,成為CPU微結構的一部分。Cache與主內存之間以塊為單位交換數據,塊長一般為數十位元組。
當CPU需要訪問內存,例如從內存中取指令時,第一次需要先將相應內存塊一次性讀入到空閑的Cache塊,CPU再直接訪問Cache塊,此時內存訪問速度會慢一些,因為存在主內存與Cache之間傳輸成塊數據的時間;CPU第二次訪問相同塊內存時,即可直接訪問Cache塊,而無須訪問主內存,內存訪問速度會快得多。
主內存—Cache系統構成現代CPU的內存儲器系統,其原理與操作系統中的硬碟—內存系統構成虛擬內存的原理極其相似。
5、指令執行的「回滾」在主內存—Cache系統留下的「痕迹」
如上所述,如果分支預測失敗,則分支預測預取的後續指令,哪怕已經亂序執行了多條指令,也必須「回滾」,指令在CPU微結構中已經完成的工作必須全部「撤銷」。
「撤銷」指令是容易的,指令最終完成的工作,無外乎是對寄存器或者內存的修改,可以暫且將修改「緩存」起來,如果「撤銷」,最終不真正修改寄存器或者內存即可。
但對於內存讀寫指令(在CPU設計中通常稱為Load/Store指令或者LD/ST指令),以讀內存指令為例,如果最終「提交」,就必須讀取實際的內存地址,如果相應內存塊還沒有被讀入到Cache塊,讀取速度就會受到影響,因此在讀內存指令最終「提交」或者「回滾」之前,CPU微結構一般會事先將Cache塊準備好,也就是如果相應內存塊還沒有被讀入到Cache塊則預先讀入。
也就是說,即使分支預測失敗,已經亂序執行的多條預取指令中只要有讀內存指令,就算最後被「回滾」,對廣義的CPU微結構還是有影響的——相應內存塊已經讀入到了Cache塊。
而內存塊是否已經讀入到Cache塊,訪問速度是有一定差異的,這相當於「回滾」的讀內存指令在CPU微結構中留下的「痕迹」,這個「痕迹」是可以被作為「側信道」利用的。
6、Meltdown攻擊原理的通俗簡明解釋
操作系統的內核數據是受到CPU微結構保護的,用戶模式的應用程序無法訪問,如果訪問是要引發CPU錯誤異常的。
構造一個分支,先檢測讀取內存的地址是否合法,合法就讀取相應地址內存位元組,然後根據內存位元組的值,讓內存位元組的值與映射到不同Cache塊的內存塊對應起來,再故意讀取一下映射到不同Cache塊的內存塊;如果訪問內存的地址非法,例如操作系統內核數據地址,直接不讀取。
顯然,這樣的分支,無論讀取合法地址還是非法地址都是不會出錯的。
用大循環執行多次這個分支,前若干次,讀取內存地址都是合法的,「訓練」CPU的分支預測,讓CPU微結構認為下次也應該走向讀取內存這一分支。
然後,突然執行一次非法的操作系統內核數據地址讀取。
按道理說,讀取內存地址非法,應該走向不讀取這一分支,可是CPU的分支預測已經被「訓練」成了「條件反射」,CPU稀里糊塗地預取了讀取非法地址的指令,並亂序並行執行,只是暫時沒有「提交」,因為沒有「提交」,即使讀取非法地址內存,也不會引發CPU異常;而且此時根據非法地址內存位元組的值,故意讀取映射到不同Cache塊內存塊的指令,相應的Cache塊也被CPU微結構準備好了(因為是並行亂序執行嘛),也就是相應內存塊已經讀入到了Cache塊。
當然,最後CPU發現這次分支預測錯了,沒關係,預取的指令亂序並行執行「回滾」,讀取非法地址的指令根本沒執行,沒有引發CPU異常,皆大歡喜。
可是,「痕迹」卻悄悄留下了,與非法地址內存位元組值有意對應起來的內存塊已經讀入到了Cache塊。
用程序遍歷一下所有可能對應的內存塊,看誰訪問速度最快,誰就很可能在Cache中,而非法地址對應的操作系統內核數據位元組值是有意與內存塊對應的,原本在用戶模式下不能訪問的操作系統內核數據位元組值就被推算出來。
這就是所謂的「側信道泄漏」。
7、這一攻擊實質的一句話解釋
CPU微結構內部信息通過側信道向宏觀計算機體系結構的泄漏。
8、一句話教訓
設計CPU追求速度快是理所當然的,但速度和安全性之間要有平衡點,微結構無論怎樣追求高速優化,屁股要擦乾淨,不要向宏觀體系結構泄漏內部信息。
1965年intel創世人之一、時任仙童半導體公司電子工程師的戈登摩爾提出了摩爾定律,對人類的計算之路的快速進步做出了預言。過去二十年,人類在互聯網的帶動下,信息化發展一路狂奔。這種對速度的追求一定程度上,透支的是安全的掉隊。也許這正是一個重新定義平衡點的時刻。
推薦閱讀:
※「幽靈」和「熔斷」,究竟是什麼?(事件匯總)
※【S2-046】Struts2遠程命令執行漏洞(CVE-2017-5638)
※Apache OpenOffice更新修復四個中危漏洞
※由Typecho 深入理解PHP反序列化漏洞