引言
現(xiàn)代處理器使用分支預(yù)測(cè)和推測(cè)執(zhí)行來(lái)最大限度地提高性能。例如,如果分支的目標(biāo)取決于正在讀取的內(nèi)存值,CPU將嘗試猜測(cè)目標(biāo)并嘗試提前執(zhí)行。當(dāng)內(nèi)存值最終到達(dá)時(shí),CPU要么丟棄,要么提交推測(cè)計(jì)算。投機(jī)邏輯在執(zhí)行方式上是不可信的,可以訪問(wèn)受害者的內(nèi)存和寄存器,并可以執(zhí)行具有可觀副作用的操作。幽靈攻擊包括誘使受害者投機(jī)性地執(zhí)行在正確程序執(zhí)行期間不會(huì)發(fā)生的操作,并通過(guò)側(cè)通道將受害者的機(jī)密信息泄露給攻擊者。
注:幽靈攻擊有很多變體,比如 Spectre Variant 1/2/3/3a/4、L1TF, Foreshadow (SGX)、MSBDS, Fallout、TAA, ZombieLoad V2等1, 這里只介紹 spectre v1, 其他幾個(gè)變體暫不涉及。
1、基本概念
亂序執(zhí)行:又稱無(wú)序執(zhí)行,它允許程序指令流下游的指令與先前指令并行執(zhí)行,有時(shí)甚至在先前指令之前執(zhí)行,從而提高了處理器組件的利用率。
投機(jī)性執(zhí)行:通常,處理器不知道程序的未來(lái)指令流。例如,當(dāng)無(wú)序執(zhí)行到達(dá)條件分支指令時(shí),就會(huì)發(fā)生這種情況,該指令的方向取決于先前指令執(zhí)行情況。在這種情況下,處理器可以保留其當(dāng)前寄存器狀態(tài),預(yù)測(cè)程序?qū)⒆裱穆窂?,并推測(cè)地沿著路徑執(zhí)行指令。如果預(yù)測(cè)結(jié)果是正確的,則提交(即保存)推測(cè)執(zhí)行的結(jié)果,從而產(chǎn)生比等待期間CPU空轉(zhuǎn)的性能優(yōu)勢(shì)。否則,當(dāng)處理器確定它遵循了錯(cuò)誤的路徑時(shí),它通過(guò)恢復(fù)其寄存器狀態(tài)并沿著正確的路徑繼續(xù),放棄 "推測(cè)執(zhí)行" 的工作。
分支預(yù)測(cè):在推測(cè)執(zhí)行期間,處理器猜測(cè)分支指令的可能結(jié)果。更好的預(yù)測(cè)通過(guò)增加可以成功提交的推測(cè)性執(zhí)行操作的數(shù)量來(lái)提高性能。
內(nèi)存層次結(jié)構(gòu):為了彌合較快處理器和較慢內(nèi)存之間的速度差距,處理器使用連續(xù)較小但較快的緩存的層次結(jié)構(gòu)。緩存將內(nèi)存劃分為固定大小的塊,稱為行,典型的行大小為64或128字節(jié)。當(dāng)處理器需要內(nèi)存中的數(shù)據(jù)時(shí),它首先檢查層次結(jié)構(gòu)頂部的L1緩存是否包含副本。在緩存命中的情況下,即在緩存中找到數(shù)據(jù),從L1緩存中檢索并使用數(shù)據(jù)。否則,在緩存未命中的情況下,重復(fù)該過(guò)程,嘗試從下一個(gè)緩存級(jí)別檢索數(shù)據(jù),最后從外部?jī)?nèi)存檢索數(shù)據(jù)。一旦讀取完成,數(shù)據(jù)通常存儲(chǔ)在緩存中(以前緩存的值被驅(qū)逐以騰出空間),以防在不久的將來(lái)再次需要數(shù)據(jù)。
微體系結(jié)構(gòu)側(cè)信道攻擊:我們上面討論的所有微體系結(jié)構(gòu)組件都通過(guò)預(yù)測(cè)未來(lái)的程序行為來(lái)提高處理器性能。為此,他們維護(hù)依賴于過(guò)去程序行為的狀態(tài),并假設(shè)未來(lái)行為與過(guò)去行為相似或相關(guān)。當(dāng)多個(gè)程序同時(shí)或通過(guò)分時(shí)在同一硬件上執(zhí)行時(shí),由一個(gè)程序的行為引起的微體系結(jié)構(gòu)狀態(tài)的變化可能會(huì)影響其他程序。這反過(guò)來(lái)又可能導(dǎo)致意外信息從一個(gè)程序泄露到另一個(gè)程序。初始微體系結(jié)構(gòu)側(cè)信道攻擊利用時(shí)序可變性和通過(guò) L1 cache 的泄漏從密碼原語(yǔ)中提取密鑰。多年來(lái),通道已經(jīng)在多個(gè)微體系結(jié)構(gòu)組件上得到了演示,包括指令緩存、低級(jí)緩存、BTB 和分支歷史。目標(biāo)已經(jīng)擴(kuò)大到包括共址檢測(cè)、打破 ASLR、擊鍵監(jiān)測(cè)、網(wǎng)站指紋識(shí)別和基因組處理。最近的結(jié)果包括跨核和跨CPU攻擊、基于云的攻擊、對(duì)可信執(zhí)行環(huán)境的攻擊、來(lái)自移動(dòng)代碼的攻擊以及新的攻擊技術(shù)。
2、攻擊舉例
思考下面這個(gè)程序,可否在不輸入正確密碼情況下通過(guò)驗(yàn)證?(答案有很多,比如:24個(gè)1)
intmain(){ intret=0; chardef_password[8]="1234567"; charsave_password[8]={0}; charpassword[8]={0}; while(true){ printf("pleaseinputpassword:"); scanf("%s",password); memset(save_password,0,sizeof(save_password)); if(strcmp(password,def_password)){//比較是否和密碼一致 printf("incorrectpassword! "); }else{ printf("Congratulation!Youhavepassedtheverification! "); strcpy(save_password,password); break; } } returnret; }
這里對(duì)數(shù)組的訪問(wèn)沒(méi)有檢查下標(biāo)是否合法,但是更進(jìn)一步的思考下,加上長(zhǎng)度檢查就一定安全了嗎?
實(shí)際上,軟件即使沒(méi)有漏洞,數(shù)組訪問(wèn)時(shí)都加了下標(biāo)有效性檢查,也是不一定是安全的??紤]一個(gè)例子,其中程序的控制流依賴于位于外部物理內(nèi)存中的未緩存值。由于此內(nèi)存比CPU慢得多,因此通常需要幾百個(gè)時(shí)鐘周期才能知道該值。這時(shí)候,CPU會(huì)通過(guò)投機(jī)執(zhí)行把這段空閑時(shí)間利用起來(lái),從安全的角度來(lái)看,投機(jī)性執(zhí)行涉及以可能不正確的方式執(zhí)行程序。然而,由于CPU的設(shè)計(jì)了通過(guò)將不正確的投機(jī)執(zhí)行的結(jié)果恢復(fù)到其先前狀態(tài)來(lái)保持功能正確性,因此這些錯(cuò)誤以前被認(rèn)為是安全的。
具體的,比如下面的代碼片段(完整的在論文2最后):
if(x
假設(shè)變量 x 包含攻擊者控制的數(shù)據(jù),為了確保對(duì) array1 的內(nèi)存訪問(wèn)的有效性,上面的代碼包含了一個(gè) if 語(yǔ)句,其目的是驗(yàn)證x的值是否在合法范圍內(nèi)。接下來(lái)我們將介紹一下攻擊者如何繞過(guò)此if語(yǔ)句,從而從進(jìn)程的地址空間讀取潛在的秘密數(shù)據(jù)。
首先,在初始錯(cuò)誤訓(xùn)練階段,攻擊者使用有效輸入調(diào)用上述代碼,從而訓(xùn)練分支預(yù)測(cè)器期望 if 為真。接下來(lái),在漏洞攻擊階段,攻擊者在 array1 的邊界之外調(diào)用值 x 的代碼。CPU不會(huì)等待分支結(jié)果的確定,而是猜測(cè)邊界檢查將為真,并已經(jīng)推測(cè)性地執(zhí)行使用惡意x訪問(wèn)array2[array1[x]*4096]的指令。
請(qǐng)注意,從 array2 讀取的數(shù)據(jù)使用惡意x將數(shù)據(jù)加載到依賴于 array1[x] 的地址的緩存中,并進(jìn)行映射,以便訪問(wèn)轉(zhuǎn)到不同的緩存行,并避免硬件預(yù)取效應(yīng)。當(dāng)邊界檢查的結(jié)果最終被確定時(shí),CPU 會(huì)發(fā)現(xiàn)其錯(cuò)誤,并將所做的任何更改恢復(fù)到其標(biāo)稱微體系結(jié)構(gòu)狀態(tài)(nominal microarchitectural state)。但是,對(duì)緩存狀態(tài)所做的更改不會(huì)恢復(fù),因此攻擊者可以分析緩存內(nèi)容,并找到從受害者內(nèi)存讀取的越界中檢索到的潛在秘密字節(jié)的值。
3、攻擊的消減方法
攻擊的消減方法主要有以下幾個(gè):
防止投機(jī)性執(zhí)行: 幽靈攻擊需要投機(jī)執(zhí)行。確保只有在確定導(dǎo)致指令的控制流時(shí)才能執(zhí)行指令,將防止推測(cè)性執(zhí)行,并防止幽靈攻擊。雖然作為一種對(duì)策有效,但防止投機(jī)執(zhí)行將導(dǎo)致處理器性能的顯著下降。
防止訪問(wèn)機(jī)密數(shù)據(jù)
防止數(shù)據(jù)進(jìn)入隱蔽通道
限制從隱蔽通道提取數(shù)據(jù)
Preventing Branch Poisoning
關(guān)于使用編譯器(如畢昇編譯器)進(jìn)行 spectre v1 的消減在LLVM社區(qū)4已有針對(duì)函數(shù)級(jí)別的方案,使用方法如下:
為單個(gè)函數(shù)添加屬性.
voidf1()__attribute__((speculative_load_hardening)){}
為代碼片段添加函數(shù)屬性.
#pragmaclangattributepush(__attribute__((speculative_load_hardening)),apply_to=function) voidf2(){}; #pragmaclangattributepop
添加編譯選項(xiàng), 整體使能幽靈攻擊防護(hù)
-mllvm-antisca-spec-mitigations=true -mllvm-debug-only=aarch64-speculation-hardening#查看調(diào)試信息
下面簡(jiǎn)單介紹一下編譯器(如畢昇編譯器)的消減原理3。
原始代碼:
if(untrusted_value
生成的匯編大概是這樣:
CMP untrusted_value, limit B.HS label LDRB val, [array, untrusted_value] label: // Use val to access other memory locations
消減后:
if(untrusted_value
生成的匯編大概是這樣:
CMP untrusted_value, limit B.HS label CSEL tmp, untrusted_value, WZr, LO CSDB LDRB val, [array, tmp] label: // Use val to access other memory locations
可以看到,我們主要使用CSEL+CSDB(Consume Speculative Data Barrier) 兩個(gè)指令組合進(jìn)行消減,CSEL 指令引入一個(gè)臨時(shí)的變量,如果沒(méi)有投機(jī)執(zhí)行,這個(gè)指令看起來(lái)是多余的,因?yàn)樗€是會(huì)等于untrusted_value, 在投機(jī)執(zhí)行且推測(cè)錯(cuò)誤的情況下,臨時(shí)變量的值就變成了0,且 CSDB 確保 CSEL 的結(jié)果不是基于預(yù)測(cè)的。
附:編譯器防護(hù)前后反匯編對(duì)比
4.編譯器的實(shí)現(xiàn)
主要代碼在文件AArch64SpeculationHardening.cpp, 雖然 LLVM社區(qū)4有很多討論5,但代碼一共只有七百行左右,主要有三個(gè)步驟:
啟用自動(dòng)插入投機(jī)安全值。
//對(duì)于可能讀寫(xiě)內(nèi)存的指令(不止是load),加固其相關(guān)的寄存器 MachineInstr&MI=*MBB.begin(); if(MI.mayLoad()) BuildMI(MBB,MBBI,MI.getDebugLoc(), TII->get(Is64Bit?AArch64::SpeculationSafeValueX :AArch64::SpeculationSafeValueW)) .addDef(Reg) .addUse(Reg);
其中:如果全是load到 GPR(通用寄存器),就對(duì)寄存器加固,否則對(duì)地址加固,因?yàn)?mask load 的值預(yù)計(jì)會(huì)導(dǎo)致更少的性能開(kāi)銷,因?yàn)榕c mask load 地址相比,load 仍然可以推測(cè)性地執(zhí)行。但是,mask 只在 GPR寄存器上很容易有效地完成,因此對(duì)于load到非GPR寄存器中的負(fù)載(例如浮點(diǎn)load),mask load 的地址。
將消減代碼添加到函數(shù)入口和出口(初始化)。
for(autoEntry:EntryBlocks) insertSPToRegTaintPropagation( *Entry,Entry->SkipPHIsLabelsAndDebug(Entry->begin())); ... //CMPSP,#0===SUBSxzr,SP,#0 BuildMI(MBB,MBBI,DebugLoc(),TII->get(AArch64::SUBSXri)) .addDef(AArch64::XZR) .addUse(AArch64::SP) .addImm(0) .addImm(0);//noshift //CSETMx16,NE===CSINVx16,xzr,xzr,EQ BuildMI(MBB,MBBI,DebugLoc(),TII->get(AArch64::CSINVXr)) .addDef(MisspeculatingTaintReg) .addUse(AArch64::XZR) .addUse(AArch64::XZR) .addImm(AArch64CC::EQ);
將消減代碼添加到每個(gè)基本塊。
BuildMI(SplitEdgeBB,SplitEdgeBB.begin(),DL,TII->get(AArch64::CSELXr)) .addDef(MisspeculatingTaintReg) .addUse(MisspeculatingTaintReg) .addUse(AArch64::XZR) .addImm(CondCode); SplitEdgeBB.addLiveIn(AArch64::NZCV); ... BuildMI(MBB,MBBI,DL,TII->get(AArch64::HINT)).addImm(0x14);
其他說(shuō)明,這個(gè)方案依賴于X16/W16寄存器(CSEL 要用到),如果已經(jīng)被使用,則只能插入內(nèi)存屏障指令:
BuildMI(MBB,MBBI,DL,TII->get(AArch64::DSB)).addImm(0xf); BuildMI(MBB,MBBI,DL,TII->get(AArch64::ISB)).addImm(0xf);
5. 總結(jié)2
支持軟件安全技術(shù)的一個(gè)基本假設(shè)是,處理器將忠實(shí)地執(zhí)行程序指令,包括其安全檢查。本文介紹的幽靈攻擊,它利用了投機(jī)執(zhí)行違反這一假設(shè)的事實(shí)。實(shí)際攻擊的示例不需要任何軟件漏洞,并允許攻擊者讀取私有內(nèi)存并從其他進(jìn)程和安全上下文注冊(cè)內(nèi)容。軟件安全性從根本上取決于硬件和軟件開(kāi)發(fā)人員之間對(duì)CPU實(shí)現(xiàn)允許(和不允許)從計(jì)算中暴露哪些信息有明確的共識(shí)。因此,雖然文中描述的對(duì)策可能有助于在短期內(nèi)限制攻擊,但它們只是權(quán)宜之計(jì),因?yàn)樽詈糜姓降捏w系結(jié)構(gòu)保證,以確定任何特定代碼構(gòu)建在當(dāng)今的處理器中是否安全。因此,長(zhǎng)期解決方案將需要從根本上改變指令集體系結(jié)構(gòu)。更廣泛地說(shuō),安全性和性能之間存在權(quán)衡。本文中的漏洞以及許多未介紹的漏洞都來(lái)自于技術(shù)行業(yè)長(zhǎng)期以來(lái)對(duì)最大限度地提高性能的結(jié)果。這之后,處理器、編譯器、設(shè)備驅(qū)動(dòng)程序、操作系統(tǒng)和許多其他關(guān)鍵組件已經(jīng)進(jìn)化出了復(fù)雜優(yōu)化的復(fù)合層,從而引入了安全風(fēng)險(xiǎn)。隨著不安全成本的上升,這些設(shè)計(jì)需要選擇性修改。在許多情況下,需要改為安全性優(yōu)化的替代實(shí)現(xiàn)。
審核編輯:彭靜
-
處理器
+關(guān)注
關(guān)注
68文章
19313瀏覽量
230054 -
cpu
+關(guān)注
關(guān)注
68文章
10873瀏覽量
212020 -
代碼
+關(guān)注
關(guān)注
30文章
4791瀏覽量
68694
原文標(biāo)題:技術(shù)分享 | 幽靈攻擊與編譯器中的消減方法介紹
文章出處:【微信號(hào):openEulercommunity,微信公眾號(hào):openEuler】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論