譯注:最近爆出來(lái)的 Intel CPU 的底層漏洞可謂是影響巨大,過(guò)去20年的電腦都可能會(huì)受影響北戏。前幾天 Raspberry Pi 的官方 Twitter(@Raspberry_Pi) 轉(zhuǎn)推了這篇文章截歉,通過(guò)簡(jiǎn)單的 Python 程序分析了各種硬件術(shù)語(yǔ)和漏洞攻擊模式,內(nèi)容簡(jiǎn)單易懂,看后神清氣爽歧蒋。今天抽空將其翻譯,分享給大家州既,如有錯(cuò)誤請(qǐng)多多包涵谜洽。——2018年1月8日
原文地址:https://www.raspberrypi.org/blog/why-raspberry-pi-isnt-vulnerable-to-spectre-or-meltdown
在過(guò)去的幾天里吴叶,有許多關(guān)于一對(duì)叫 Spectre 和 Meltdown 的安全漏洞的討論阐虚。這影響到所有近代的英特爾處理器,許多AMD處理器(在 Spectre 漏洞下)和 ARM 核心蚌卤。 Spectre 允許攻擊者繞過(guò)軟件檢查实束,去讀取當(dāng)前地址空間中任意位置的數(shù)據(jù); Meltdown 允許攻擊者去讀取操作系統(tǒng)內(nèi)核地址空間中(通常對(duì)用戶程序不可訪問(wèn))任意位置的數(shù)據(jù)逊彭。
這兩個(gè)漏洞利用許多現(xiàn)代處理器常見(jiàn)的性能特征(緩存和預(yù)測(cè)執(zhí)行)咸灿,通過(guò)所謂的側(cè)信道攻擊(side-channel attack)來(lái)泄漏數(shù)據(jù)。幸運(yùn)的是侮叮,樹莓派不會(huì)受到這些漏洞的影響避矢,因?yàn)槲覀兪褂锰貏e的(particular)ARM 內(nèi)核。
為了幫助我們理解為什么囊榜,這里有一點(diǎn)關(guān)于現(xiàn)代處理器設(shè)計(jì)中的一些概念谷异。我們將使用像下面那樣的簡(jiǎn)單的 Python 程序去說(shuō)明這些概念:
t = a+b
u = c+d
v = e+f
w = v+g
x = h+i
y = j+k
雖然計(jì)算機(jī)中的處理器不直接執(zhí)行 Python ,但這里的語(yǔ)句很簡(jiǎn)單锦聊,它們大致相當(dāng)于一個(gè)機(jī)器指令歹嘹。我們將詳細(xì)介紹一些細(xì)節(jié)(尤其是流水線(pipelining)和寄存器重命名(register renaming)),這對(duì)于處理器設(shè)計(jì)者來(lái)說(shuō)非常重要孔庭,但并不是理解 Spectre 和 Meltdown 所必須的尺上。
為了綜合描述處理器設(shè)計(jì)和現(xiàn)代計(jì)算機(jī)體系結(jié)構(gòu)的其他方面材蛛,你不能做得比 Hennessy and Patterson’s classic Computer 體系結(jié)構(gòu)更好:一種定量方法。(原文:you can’t do better than Hennessy and Patterson’s classic Computer Architecture: A Quantitative Approach.)
什么是標(biāo)量處理器
最簡(jiǎn)單的現(xiàn)代處理器每周期執(zhí)行一條指令怎抛,我們稱之為標(biāo)量處理器(scalar processor)卑吭。上面的示例將在標(biāo)量處理器上以六個(gè)周期執(zhí)行。
標(biāo)量處理器的例子包括 Intel 486 和在 Raspberry Pi 1 與 Raspberry Pi Zero 上使用的 ARM1176 核心马绝。
什么是超標(biāo)量處理器
使標(biāo)量處理器(實(shí)際上是任何處理器)運(yùn)行得更快的明顯方法是增加它的時(shí)鐘速度(clock speed)豆赏。但是,我們很快達(dá)到了處理器內(nèi)部邏輯門運(yùn)行速度的極限富稻。因此掷邦,處理器設(shè)計(jì)者開始尋找?guī)追N同時(shí)執(zhí)行多個(gè)指令的方法。
順序(in-order)超標(biāo)量處理器(superscalar processor)檢查傳入的指令流椭赋,并嘗試在一個(gè)流水線(pipelines -> pipes)中同時(shí)執(zhí)行多個(gè)指令流抚岗,但要遵守指令之間的依賴關(guān)系。依賴關(guān)系很重要:你可能認(rèn)為雙路(two-way)超標(biāo)量處理器可以結(jié)對(duì)(dual-issue)六個(gè)指令哪怔,像下面的例子一樣:
t, u = a+b, c+d
v, w = e+f, v+g
x, y = h+i, j+k
但這沒(méi)有意義:在計(jì)算 w 之前宣蔚,我們必須計(jì)算 v ,所以第三和第四指令不能同時(shí)執(zhí)行认境。我們的雙路超標(biāo)量處理器實(shí)際上不可能找到任何與第三指令相匹配的指令胚委,所以我們的示例將以四個(gè)周期執(zhí)行:
t, u = a+b, c+d
v = e+f # second pipe does nothing here
w, x = v+g, h+i
y = j+k
超標(biāo)量處理器的例子包括 Intel Pentium ,在 Raspberry Pi 2 與 Raspberry Pi 3 上使用的 ARM Cortex-A7 與 Cortex-A53 核心叉信。 Raspberry Pi 3 的時(shí)鐘速度只比 Raspberry Pi 2 快了 33% 亩冬,但性能近乎翻倍:額外性能的部分原因是 Cortex-A53 的指令結(jié)對(duì)能力比 Cortex-A7 具有更廣泛的指令范圍。
什么是亂序處理器
回到我們的例子茉盏,我們可以看到,雖然我們?cè)?v 和 w 之間有一個(gè)依賴項(xiàng)枢冤,但是在程序的后面有其他的獨(dú)立指令鸠姨,我們或許可以在第二個(gè)周期中用來(lái)填充流水線。亂序(out-of-order)超標(biāo)量處理器具有打亂即將到來(lái)的指令的能力(遵循依賴關(guān)系)淹真,以便提高流水線的效率讶迁。
在我們的示例中,亂序處理器可能有效的交換 w 和 x 的定義:
t = a+b
u = c+d
v = e+f
x = h+i
w = v+g
y = j+k
將其以三個(gè)周期執(zhí)行:
t, u = a+b, c+d
v, x = e+f, h+i
w, y = v+g, j+k
亂序處理器的例子包括 Intel Pentium 2 (絕大多數(shù)的 Intel 和 AMD x86 處理器核蘸,除了一些 Intel Atom 和 Intel Quark 設(shè)備)巍糯,最新的 ARM 核心,像 Cortex-A9, -A15, -A17, and -A57 客扎。
什么是分支預(yù)測(cè)器
上面的例子是一段順序代碼祟峦。當(dāng)然,真正的程序不是這樣的:它們還包含向前分支(forward branches徙鱼,用于實(shí)現(xiàn)條件操作宅楞,如if語(yǔ)句)和向后分支(backward branches针姿,用于實(shí)現(xiàn)循環(huán))。分支可能是無(wú)條件的(總是執(zhí)行)厌衙,或條件的(是否執(zhí)行取決于計(jì)算值)距淫。
在獲取指令時(shí),處理器可能遇到依賴于尚未計(jì)算值的條件分支婶希。為了避免停頓榕暇,處理器必須猜測(cè)下一個(gè)要取的指令:在內(nèi)存中的一個(gè)指令(對(duì)應(yīng)不執(zhí)行分支),或分支目標(biāo)中的一個(gè)(對(duì)應(yīng)執(zhí)行分支)喻杈。分支預(yù)測(cè)器(branch predictor)可幫助處理器對(duì)是否執(zhí)行分支進(jìn)行智能猜測(cè)彤枢。它通過(guò)收集有關(guān)過(guò)去特定分支的執(zhí)行頻率的統(tǒng)計(jì)數(shù)據(jù)來(lái)做到這一點(diǎn)。
現(xiàn)代分支預(yù)測(cè)是非常復(fù)雜的奕塑,可以產(chǎn)生非常準(zhǔn)確的預(yù)測(cè)堂污。Raspberry Pi 3 的額外性能的部分原因是由于分支預(yù)測(cè)在 Cortex-A7 和 Cortex-A53 之間的改進(jìn)。然而龄砰,通過(guò)執(zhí)行精心編制的一系列分支盟猖,攻擊者可以錯(cuò)誤地訓(xùn)練分支預(yù)測(cè)器,從而做出糟糕的預(yù)測(cè)换棚。
什么是推測(cè)
重排(reordering)順序指令是使更多指令級(jí)并行的強(qiáng)有力方法式镐,但是隨著處理器變得更強(qiáng)大(能夠?qū)⑷蛩膫€(gè)指令結(jié)對(duì)),要使所有這些流水線忙起來(lái)變得困難固蚤。因此娘汞,現(xiàn)代處理器推測(cè)(speculation)的能力也變得更強(qiáng)。推測(cè)執(zhí)行允許我們發(fā)出可能不需要的指令(因?yàn)榇a可能會(huì)存在分支)夕玩,這會(huì)使流水線保持繁忙(使用或丟棄)你弦,如果結(jié)果表明該指令未被執(zhí)行,我們就可以將其丟棄燎孟。
推測(cè)執(zhí)行不必要的指令(底層需要支持推測(cè)和重排)消耗額外的時(shí)間禽作,但在許多情況下,這被認(rèn)為是獲得額外單線程性能的一個(gè)合算的折衷揩页。分支預(yù)測(cè)器被用來(lái)選擇程序最可能的路徑旷偿,最大限度地提高推測(cè)的回報(bào)。
為了演示推測(cè)的好處爆侣,讓我們看看另一個(gè)例子:
t = a+b
u = t+c
v = u+d
if v:
w = e+f
x = w+g
y = x+h
現(xiàn)在我們有了從 t 到 u 到 v 萍程,從 w 到 x 到 y 的依賴關(guān)系,所以沒(méi)有推測(cè)的雙路亂序處理器永遠(yuǎn)不能填滿它的第二個(gè)流水線兔仰。處理器花費(fèi)三個(gè)周期計(jì)算 t 茫负、 u 和 v ,之后將知道 if 語(yǔ)句的主體部分是否執(zhí)行乎赴,在執(zhí)行 if 語(yǔ)句主體的情況下朽褪,再花費(fèi)三個(gè)周期計(jì)算 w 置吓、 x 和 y 。假設(shè) if 語(yǔ)句(由一個(gè)分支指令實(shí)現(xiàn))需要一個(gè)周期缔赠,我們的示例將花費(fèi)四個(gè)周期(如果 v 為 0)或七個(gè)周期(如果 v 為非 0)衍锚。
如果分支預(yù)測(cè)器表明該 if 語(yǔ)句體可能執(zhí)行,經(jīng)推測(cè)有效地打亂后的程序是這樣的:
t = a+b
u = t+c
v = u+d
w_ = e+f
x_ = w_+g
y_ = x_+h
if v:
w, x, y = w_, x_, y_
因此我們現(xiàn)在有了額外的指令并行來(lái)保持我們的流水線繁忙:
t, w_ = a+b, e+f
u, x_ = t+c, w_+g
v, y_ = u+d, x_+h
if v:
w, x, y = w_, x_, y_
循環(huán)計(jì)數(shù)在推測(cè)亂序處理器中定義不太好(原文:Cycle counting becomes less well defined in speculative out-of-order processors)嗤堰,但 w 戴质、 x 和 y 的分支和條件更新是大約不占用時(shí)間的,所以我們的示例大約在三個(gè)周期中執(zhí)行踢匣。
什么是緩存
在過(guò)去的好日子里告匠,處理器的速度與內(nèi)存訪問(wèn)速度匹配得很好。我的 BBC Micro 有 2MHz 离唬,執(zhí)行一條指令大約 2μs 后专,存儲(chǔ)周期(memory cycle time)為 0.25μs 。在接下來(lái)的35年里输莺,處理器已經(jīng)變得很快戚哎,但內(nèi)存還僅僅是那樣。在 Raspberry Pi 3 中的一個(gè) Cortex-A53 核心嫂用,執(zhí)行一條指令大約 0.5ns 型凳,但可能需要多達(dá) 100ns 去訪問(wèn)主存。
乍一看嘱函,這聽(tīng)起來(lái)像一個(gè)災(zāi)難:我們每次訪問(wèn)內(nèi)存甘畅,要等待 100ns 后才得到結(jié)果返回。下面這個(gè)例子需要花費(fèi) 200ns :
a = mem[0]
b = mem[1]
然而往弓,在實(shí)際中疏唾,程序傾向于以相對(duì)可預(yù)測(cè)的方式去訪問(wèn)內(nèi)存,同時(shí)顯示時(shí)間局部性(temporal locality 函似,如果我訪問(wèn)一個(gè)位置槐脏,我很可能很快就會(huì)再次訪問(wèn)它)和空間局部性(spatial locality ,如果我訪問(wèn)一個(gè)位置缴淋,我很可能很快就會(huì)訪問(wèn)它附近的位置)准给。緩存利用了這些特性泄朴,以減少訪問(wèn)內(nèi)存的平均成本重抖。
緩存是一個(gè)容量小的芯片存儲(chǔ)器,靠近處理器祖灰,存儲(chǔ)最近使用的地址(及其附近)的內(nèi)容的副本钟沛,以便它們?cè)诤罄m(xù)訪問(wèn)中很快可用。有了緩存局扶,上面的例子會(huì)執(zhí)行一個(gè)多 100ns :
a = mem[0] # 100ns delay, copies mem[0:15] into cache
b = mem[1] # mem[1] is in the cache
從 Spectre 和 Meltdown 的角度來(lái)看恨统,重要的一點(diǎn)是叁扫,如果能夠計(jì)算內(nèi)存訪問(wèn)的時(shí)間,就可以判定所訪問(wèn)的地址是否在緩存畜埋。
什么是側(cè)信道
維基百科zh-cn:
側(cè)信道攻擊(英語(yǔ):Side-channel attack)是一種攻擊方式莫绣,它基于從密碼系統(tǒng)的物理實(shí)現(xiàn)中獲取的信息而非暴力破解法或是算法中的理論性弱點(diǎn)(較之密碼分析)。例如:時(shí)間信息悠鞍、功率消耗对室、電磁泄露或甚是聲音可以提供額外的信息來(lái)源,這可被利用于進(jìn)一步對(duì)系統(tǒng)的破解咖祭。
Spectre 和 Meltdown 是側(cè)信道攻擊, 它推斷出內(nèi)存位置的內(nèi)容, 而內(nèi)存位置通常不應(yīng)使用定時(shí)來(lái)觀察當(dāng)前緩存中是否存在另一個(gè)可訪問(wèn)的位置掩宜。
綜上所述
現(xiàn)在讓我們來(lái)看看推測(cè)和緩存是如何結(jié)合在一起去允許一個(gè)像 Meltdown 的對(duì)處理器的攻擊∶春玻考慮下面的例子牺汤,這是一個(gè)用戶程序,從一個(gè)非法(內(nèi)核)地址讀取浩嫌,導(dǎo)致一個(gè)錯(cuò)誤(崩潰):
t = a+b
u = t+c
v = u+d
if v:
w = kern_mem[address] # if we get here, fault
x = w&0x100
y = user_mem[x]
現(xiàn)在檐迟,如果我們能訓(xùn)練分支預(yù)測(cè)器相信 v 可能是非 0 的,我們雙路亂序超標(biāo)量處理器將會(huì)這樣打亂程序:
t, w_ = a+b, kern_mem[address]
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
即使處理器總是從內(nèi)核地址地讀取, 它也必須延遲所產(chǎn)生的錯(cuò)誤, 直到它知道 v 是非零的固该。從表面上看锅减,這感覺(jué)很安全,因?yàn)椋?/p>
- v 為零伐坏,因此非法讀取的結(jié)果不會(huì)被提交到 w
- v 為非零怔匣,但在將讀取提交到 w 之前發(fā)生故障
但是,假設(shè)我們?cè)趫?zhí)行代碼之前清空緩存桦沉,并排列 a每瞒、b、c 和 d, 以便 v 實(shí)際上是零〈柯叮現(xiàn)在剿骨,在第三周期中推測(cè)讀取
v, y_ = u+d, user_mem[x_]
將訪問(wèn)用戶地址 0x000 或地址 0x100 ,具體取決于非法讀取的結(jié)果的第八位埠褪,將該地址及其附近加載到緩存中浓利。由于 v 為零,因此將丟棄推測(cè)性指令的結(jié)果钞速,并繼續(xù)執(zhí)行贷掖。如果我們對(duì)其中一個(gè)地址進(jìn)行后續(xù)訪問(wèn), 我們就可以確定哪個(gè)地址在緩存中。恭喜你渴语,你剛剛從內(nèi)核的地址空間讀取了一位!
真正的 Meltdown 利用比這更為復(fù)雜(特別是為了避免錯(cuò)誤地訓(xùn)練分支預(yù)測(cè)器苹威,作者更愿意無(wú)條件地執(zhí)行非法讀取并處理結(jié)果異常),但原理是一樣的驾凶。 Spectre 使用類似的方法來(lái)顛覆軟件數(shù)組邊界檢查牙甫。
結(jié)論
現(xiàn)代處理器不遺余力地保持抽象掷酗,即它們是直接訪問(wèn)存儲(chǔ)器的順序的標(biāo)量機(jī)器。而實(shí)際上使用許多技術(shù)窟哺,包括緩存泻轰、指令重排和推測(cè),可以提供比簡(jiǎn)單處理器更高的性能且轨。 Meltdown 和 Spectre 是我們?cè)诔橄蟮谋尘跋聦?duì)安全進(jìn)行推理的例子糕殉,然后在抽象和現(xiàn)實(shí)之間遇到細(xì)微的差異。
在 Raspberry Pi 中殖告,ARM1176阿蝶、Cortex-A7 和 Cortex-A53 核心的缺少推測(cè)功能使我們對(duì)這種類型的攻擊免疫。