前段時間,對Meltdown和spectre安全漏洞的討論很是激烈,該漏洞影響了全部的現代intel處理器,一開始ARM還聲稱這些漏洞不會影響ARM系列的處理器,但後面的事實證實AMD處理器和ARM內核都沒有免遭其害。python
spectre漏洞使得攻擊者能夠繞過軟件檢查,讀取當前地址空間中的任意位置數據,meltdown漏洞使得攻擊者能夠讀取操做系統核地址空間的任意位置數據(用戶一般不可訪問該數據)。這兩種漏洞皆經過邊信道攻擊利用不少現代處理器都有的性能特徵(緩存和推測執行)來泄露數據。但樹莓派稱不受這些漏洞的影響。算法
meltdown影響了intel處理器,打破了用戶應用程序和操做系統之間最基本的隔離(地址空間隔離),這種攻擊容許程序訪問其餘程序和操做系統的內存,這會致使數據泄露。(一般而言,目前操做系統都採用了虛擬內存管理方式,這樣可讓內存需求超過實際物理內存限制的進程或線程可以運行,加上頁面置換便可。虛擬內存管理也能夠實現對不一樣內存區域的保護)緩存
而spectre除了可以影響intel還能影響AMD和ARM架構的大量處理器,也就是說除了PC,手機等終端也會受到影響,幾乎全部現代計算機處理器均沒法倖免。安全
可是相似樹莓派等廉價計算設備可能並不會受到影響。架構
接下來介紹一些如今處理器設計的概念,使用簡單的python來解釋這些概念:oop
t=a+b性能
u=c+d操作系統
v=e+f線程
w=v+g設計
x=h+i
y=j+k
標量處理器
最簡單的現代處理器每次循環執行一個指令,稱之爲標量處理器,也就是說上面的語句在標量處理器中須要執行6次循環。
樹莓派1和zero中使用的intel486和arm11767都是標量處理器
超標量處理器
很明顯,加速標量處理器的方式就是提供其時鐘頻率,可是這樣很快就會達處處理器內部邏輯門運行的極限,所以處理器設計者開始尋找一次性處理多件事情的方式。
有序超標量處理器嘗試在一個pipeline中一次性執行多個指令,這取決於指令之間的依賴關係,依賴關係很重要,你或許認爲雙向超標量處理器能夠將6個指令配置執行:
t,u = a+b, c+d
v,w = e+f, v+g
x,y = h+i, j+k
但這沒有做用,咱們必須先計算v再計算w,也就是指令三和四沒法同時執行,所以會執行四個循環:
t,u =a+b , c+d
v=e+f #第二個pipe沒有
w,x=v+g, h+i
y=j+k
超標量處理器包括intel pentium以及樹莓派2,3使用的ARM cortex-A7和cortex-A53,樹莓派3的時鐘頻率只比2高33%,但性能大約是後者的2倍,部分緣由在於A53超出A7的對大量指令的配對執行能力
無序處理器(與原子操做概念相關)
即便v和w存在依賴關係,咱們仍能找到其餘獨立指令來填補第二次循環中的空pipe。無序超標量處理器可以打亂指令執行的順序(固然一樣受限於指令之間的依賴關係)以保持每一個pipe處於忙碌狀態。
無序處理器能夠交換w和x的順序:
t=a+b
u=c+d
v=e+f
x=h+i
w=v+g
y=j+k
這樣就容許執行三次循環:
t,u = a+b, c+d
v,x = e+f, h+i
w,y = v+g, j+k
無序處理器包括了intel pentium2(以及大部分後序intel和AM x86處理器)還有近期的ARM處理器,如cortex-A9,A15,A17等。
分支預測器
上述的示例是直線式代碼塊,真正運行的程序不是這樣的:他們還包括正向分支(if語句),反向分支(用於實現loop)。
在獲取指令時,處理器可能遇到依賴於計算值的條件分支(而該值目前還沒有計算出),爲了不停頓,處理器必須猜想出下一個要得到的指令。分支預測器經過收集某一個分支以前被採用頻率的相關統計數據,幫助處理器猜想該分支是否被採用。
如今分支預測器很是複雜,能夠生成很是準確的預測,樹莓派3的額外性能是因爲cortex-A7和A53之間分支預測的改進。
推測
重排序順序指令是一種恢復指令級別並行化的強大方法,可是因爲處理器變得更寬(可以一次執行3-4個指令),保證全部pipeline處於忙碌就更難了,所以如今處理器提升了推測能力,推測執行能夠處理並不須要的指令:這樣能夠保證pipeline處理忙碌狀態,若是最後該指令沒有被執行,咱們只須要放棄結果就能夠了。
推測執行沒必要要的指令須要耗費大量能源,可是在不少狀況下爲了得到單線程性能的提高,該方法是值得的。
爲了展現推測的好處,看另外一個示例:
t=a+b
u=t+c
v=u+d
if v:
w=e+f
x=w+g
y=x+h
如今咱們有從t到u到v,從w到x到y的依賴關係,那麼沒有推測的雙向無序處理器沒法填充第二個pipeline,它會用三次循環來計算t,u和v,以後處理器知道if語句是否被執行,而後再用三次循環來計算w,x和y。假設if使用了一次循環,那麼該示例能夠執行4次(v爲0)或7次循環(v不是0),若是分支預測器代表if語句的主體極可能被執行,那麼推測能夠有效打亂程序順序,以下:
t=a+b
u=t+c
v=u=d
w_=e+f
x_=w_+f
y_=x_+h
if v:
w,x,y=w_,x_,y_
循環計數在推測無序處理器中變得不太明確,但w,x和y的分支和條件更新幾乎是空閒的,所以上述示例幾乎執行三個循環。
什麼是緩存?
在過去處理器速度與內存訪問速度成正比,可是如今,處理器已經變得很是快,但內存幾乎沒有變化,樹莓派3只須要0.5ns執行一次指令,但可能須要100ns才能訪問主存。
在實踐中,程序傾向於以相對可預測的方式訪問內存,同時展現時間局部性(若是我訪問一個定位,我可能很快會再次訪問)和空間局部性(若是訪問一個定位,極可能會很快訪問附近的位置),緩存利用這些屬性來下降訪問內存的平均成本。
緩存是一個小的片上內存,接近於處理器,存儲最近使用的位置還有近鄰內容的副本,以便隨後的訪問中能夠快速獲取(最快的存儲器固然是CPU內部的寄存器,與CPU同一種材質製成,訪問速度與CPU同樣。可是大小極其有限)。
什麼是邊信道攻擊?
邊信道攻擊是基於從密碼系統的物理實現得到的信息的任何攻擊,而不是算法中的蠻力或者理論弱點。例如定時信息,功耗等均可以提供額外的信息。
meltdown和spectre是經過定時觀察緩存中是會否有另外一個可訪問的位置,以推測內存位置的內容,這些內容一般不該該被訪問。
把這些概念放在一塊兒
如今讓咱們看看如何結合推測和緩存以容許相似meltdown的攻擊。
考慮下面這個示例,該程序時一個讀取非法(內核)地址的用戶程序,會致使錯誤:
t=a+b
u=t+c
v=u+d
if v:
w=kern_mem[addr] //若是到了這,因爲非法訪問內核地址,會致使程序錯誤
x=w&0x100
y=user_mem[x]
如今,假設咱們能夠訓練分支預測器,使其相信v極可能是非0的,那麼咱們的無序雙向超標量處理器就會混洗程序:
t,w_=a+b, kern_mem[addr]
u,x_=t+c, w&0x100
v,y_=u+d, user_mem[x]
if v:
#fault
w,x,y=w_,x_,y_ #we never get here
即便處理器老是推測性讀取內核地址,它必須推測產生的錯誤,直到知道v是非0,也就是說當v爲非0,意味着if中的語句要被執行,也就是要用戶能夠獲得內核地址中的內容了,這個時候會程序報錯。也就是說,從表面上看,這是安全的:
1.若v是0,,非法讀取的結果不會提交給w
2.若v是非0,但在讀取結果被提交給w以前發生了錯誤
然而,假設在執行代碼前刷新了緩存,並排列a,b,c,d使得v實際爲0,第三個循環中的推測性讀取爲:
v,y_=u+d, user_mem[x_]
將其依賴非法讀取結果的第八位獲取用戶地址0x000或0x100,並把地址及其近鄰加載進緩存,因爲v是0,推測性指令的結果將被摒棄,執行將繼續。若是咱們隨後訪問其中一個地址,就能夠決定哪一個地址在緩存之中,那麼恭喜:你剛剛從內核地址空間讀取了一個位。而且你知道了該位的具體地址。
真正的meltdown實際上更爲複雜(由於咱們實際上優先執行了非法讀取,並處理產生的異常),但原理是相同的。
結論:
現代處理器不遺餘力保持抽象,從而成爲直接訪問內存的有序標量機器,而事實上,使用包括緩存,指令重排序以及推測在內的大量技術來提供比簡單處理器更高的性能成爲了現實。meltdown和spectre就是當咱們在發展過程當中,咱們的理想和現實的細微差異致使的漏洞。
樹莓派使用的ARM1176, cortex-A7和cortex-A53內核中推測功能的缺失使得樹莓派免疫此類攻擊。