彭茜珍,胡 莉
(湖北科技學(xué)院 學(xué)報(bào)編輯部,湖北 咸寧 437100)
在現(xiàn)代程序設(shè)計(jì)中,經(jīng)常會(huì)涉及大量數(shù)據(jù)的處理,通用的方式就是在內(nèi)存中開辟固定大小的存儲(chǔ)區(qū)域作為緩沖區(qū),并將待處理數(shù)據(jù)放入其中,以便程序?qū)ζ溥M(jìn)行各種處理,由于編程中難免出現(xiàn)錯(cuò)誤,當(dāng)代碼向緩沖區(qū)內(nèi)寫入的數(shù)據(jù)量超過其大小時(shí),就會(huì)發(fā)生溢出現(xiàn)象,攻擊者可以利用這個(gè)漏洞,通過用精心設(shè)計(jì)的一段入侵代碼覆蓋緩沖區(qū)之外的內(nèi)存區(qū)域,這些入侵代碼就可能被CPU執(zhí)行,從而危及系統(tǒng)的安全。本文提出了一種基于Intel MPX指令集監(jiān)測技術(shù)可以預(yù)防此類危害系統(tǒng)的事情發(fā)生。
緩沖區(qū)溢出是軟件代碼中常見的漏洞[1,2],對系統(tǒng)的安全帶來隱患。Intel內(nèi)存保護(hù)擴(kuò)展(Intel MPX)可用于消除此類缺陷,它于2013年首次發(fā)布,并于2015年底作為Skylake微體系結(jié)構(gòu)的一部分推出的一項(xiàng)新功能。使用Intel MPX 并與修改后的編譯器相結(jié)合,可以檢查內(nèi)存引用,提高軟件的穩(wěn)健性,還可以使編譯時(shí)正常運(yùn)用這些引用在運(yùn)行時(shí)由于緩沖區(qū)上溢或下溢而不被惡意攻擊。Intel MPX還提供了與舊式的軟件組件相兼容的機(jī)制。
Intel MPX 被設(shè)計(jì)成允許系統(tǒng)(即邏輯處理器和操作系統(tǒng)軟件)運(yùn)行支持Intel MPX 的軟件以及傳統(tǒng)軟件(為沒有Intel MPX 的處理器編寫)。在執(zhí)行包含Intel MPX 未知代碼(傳統(tǒng)代碼)和Intel MPX 啟用代碼混合的軟件時(shí),傳統(tǒng)代碼不會(huì)受到Intel MPX 影響,它類似于在指令流中嵌入 NOP操作,不會(huì)發(fā)生任何功能變化或性能下降。
開啟了Intel MPX 的指令代碼會(huì)從針對緩沖區(qū)溢出等漏洞的內(nèi)存保護(hù)中受益。因此,軟件開發(fā)者想采用這項(xiàng)技術(shù)的內(nèi)在動(dòng)力很高。同時(shí),由于Intel MPX 保護(hù)帶來的安全優(yōu)勢可以根據(jù)開發(fā)者的業(yè)務(wù)優(yōu)先級(jí)來實(shí)施。開發(fā)者可以選擇在某些模塊中采用Intel MPX以快速實(shí)現(xiàn)Intel MPX 的部分優(yōu)勢,并且分階段在其他模塊中引入Intel MPX(例如,傳統(tǒng)調(diào)用的接口處)。Intel MPX 的這種自適應(yīng)特性旨在讓軟件開發(fā)者控制他們的規(guī)劃和模塊化。它還可使開發(fā)者首先保護(hù)更高優(yōu)先級(jí)或更容易受到攻擊的軟件,并在軟件工程的某個(gè)階段(例如,測試)中使用Intel MPX 功能,而不是在另一個(gè)階段(例如,發(fā)布)中由業(yè)務(wù)現(xiàn)實(shí)決定采用Intel MPX 功能。
像任何指令集擴(kuò)展一樣,應(yīng)用程序開發(fā)者不但使用Intel MPX檢測緩沖區(qū)溢出,還使處理器可以使用Intel MPX 做緩沖區(qū)溢出檢測之外的事情。
為了支持檢測緩沖區(qū)溢出缺陷的硬件特性,Intel MPX引入了新的邊界寄存器,以及在這些寄存器上運(yùn)行的新指令集擴(kuò)展。此外,還定義了一組新的“邊界表(bound table)”,使其可以存儲(chǔ)超出邊界寄存器范圍的邊界。這些硬件特征的具體內(nèi)容如下[3]:
1.邊界寄存器
共4個(gè),它們是BND0、BND1、BND2和BND3,128位長,保存指針變量的下限和上限(每個(gè)界限64位寬),上限在架構(gòu)上是以1的補(bǔ)碼形式表示。下限 = 0,上限 = 0(全 1 的 1 的補(bǔ)碼)將允許訪問整個(gè)地址空間。當(dāng)下限和上限均為 0(覆蓋整個(gè)地址空間)時(shí),該邊界被視為INIT。它們用于支持Intel MPX指令。
2.配置寄存器和狀態(tài)寄存器
共3個(gè),兩個(gè)配置寄存器和一個(gè)狀態(tài)寄存器,兩個(gè)配置寄存器是為用戶模式(CPL = 3)和管理模式(CPL < 3)定義的。配置寄存器用于設(shè)置邊界目錄的基地址(線性地址)和開啟Intel MPX功能。狀態(tài)寄存器用于報(bào)錯(cuò),并提供錯(cuò)誤代碼。
3.越界異常
當(dāng)程序運(yùn)行過程中出現(xiàn)指針引用不正確時(shí),CPU觸發(fā)越界異常#BR,借助于此異常可以及時(shí)處理緩沖區(qū)溢出等漏洞,保護(hù)內(nèi)存不受侵犯。
4.新指令
共7條,它們是:
1)BNDCL bnd, r/m,如果r/m中的地址低于bnd.LB中的下限,則生成#BR異常。
2)BNDCU bnd, r/m,如果r/m32中的地址高于bnd.UB中的上限(以1的補(bǔ)碼形式表示bnb.UB),則產(chǎn)生#BR異常。
3)BNDCN bnd, r/m,如果r/m中的地址高于bnd.UB中的上限(bnb.UB不是1的補(bǔ)碼形式),則產(chǎn)生#BR異常。
4)BNDMOV b, b/m,從內(nèi)存或邊界寄存器復(fù)制/加載LB和UB界限。
5)BNDMOV b/m, b,把在邊界寄存器中LB和UB界限存儲(chǔ)到內(nèi)存或其他寄存器中。
6)BNDLDX b, mib,利用sib-addressing表達(dá)式mib,使用地址轉(zhuǎn)換加載邊界。
7)BNDSTX mib, b,利用sib-addressing表達(dá)式mib,使用地址轉(zhuǎn)換存儲(chǔ)邊界。
利用這些硬件設(shè)施,Intel MPX特別適用于其與舊代碼的向后兼容性和互操作性。 一方面,MPX檢測代碼可以在傳統(tǒng)硬件上運(yùn)行,因?yàn)镸PX指令在舊架構(gòu)上被解釋為NOP,這簡化了二進(jìn)制文件的分發(fā),可以將同一個(gè)支持MPX的程序/庫分發(fā)給所有客戶端。另一方面,MPX全面支持與未修改的舊代碼進(jìn)行互操作:(1)BNDPRESERVE配置位允許傳遞由舊代碼創(chuàng)建的沒有邊界信息的指針,(2)當(dāng)舊代碼更改內(nèi)存中的指針時(shí),這個(gè)指針的bndldx注意到了變化,并為它分配了總是為真(INIT)的界限。在這兩種情況下,在舊代碼中創(chuàng)建/更改的指針被認(rèn)為是“無邊界的”:這允許互操作性。
Intel MPX關(guān)鍵指令是BNDLDX和BNDSTX[4]。在應(yīng)用程序可以包含數(shù)百個(gè)指針,并且四個(gè)BND寄存器不足以存儲(chǔ)此指針數(shù)量的邊界。這時(shí),借助于邊界目錄及其條目,該條目指向的邊界表用于存儲(chǔ)其邊界和其他指針值。邊界表是一個(gè)邊界表?xiàng)l目數(shù)組,它是64位長的4元組,存儲(chǔ)指針值,緩沖區(qū)的下限,緩沖區(qū)的上限和保留字段,如圖1所示。
圖1 MPX邊界目錄和邊界表?xiàng)l目
當(dāng)使用bndstx和bndldx時(shí),邊界存儲(chǔ)在使用兩級(jí)地址轉(zhuǎn)換方案計(jì)算的存儲(chǔ)器位置,以便存儲(chǔ)/加載指針邊界,地址轉(zhuǎn)換方案如圖2所示。
圖2 兩階段地址轉(zhuǎn)換(64位模式)
在第一階段,必須加載相應(yīng)的BD條目。為此,CPU:(1)從指針地址的位20-47中提取BD條目的偏移量并將其移3位(因?yàn)樗蠦D條目都是23位長),(2)加載BD的基址,它來自BNDCFGx(特別是用戶空間中的BNDCFGU和內(nèi)核模式中的BNDCFGS)寄存器,以及(3)對基數(shù)和偏移量求和并從結(jié)果地址加載BD條目。
在第二階段,CPU:(4)從指針地址的第3-19位提取BT條目的偏移量并將其移5位(因?yàn)樗蠦T條目都是25位長),(5)移位這個(gè)加載的條目——對應(yīng)于BT的基址除以3以便去除包含在前3位中的元數(shù)據(jù),(6)對基址和偏移量求和,(7)最終從結(jié)果地址加載BT條目。 注意,BT條目具有附加的“指針”字段 —— 如果實(shí)際指針值和該字段中的值不匹配,則MPX將邊界標(biāo)記為始終為真(INIT)。這是與傳統(tǒng)代碼進(jìn)行互操作所必需的,并且僅在某些傳統(tǒng)代碼修改指針時(shí)才會(huì)發(fā)生。
Intel MPX指令基于處理器硬件設(shè)施,可以實(shí)現(xiàn)快速檢測邊界越界并引發(fā)#BR異常,借助于異常處理,避免系統(tǒng)受到危及。特別是可以透明地為傳統(tǒng)版C/C++程序添加邊界檢查。下面給出Intel MPX指令應(yīng)用舉例及指導(dǎo),請?zhí)接懴铝蠧語言程序段:
struct object
{
char buffer[100];
int len;
}
1: object * a[20] // 指向object的指針數(shù)組
2: sum = 0
3: for (i=0; i 4: ai = a + i // 在a上做指針?biāo)阈g(shù)運(yùn)算 5: objectptr = load ai // a[i]是指向object的指針 6: lenptr = objptr + 100 // 指向object.len的指針 7: len = load lenptr 8: sum += len } // 所有object的總長度 這段程序分配一個(gè)指針數(shù)組a[20],其中每個(gè)指針指向一些object類型的緩沖區(qū)對象(第1行)。接著,它遍歷數(shù)組的前N個(gè)元素以計(jì)算對象長度值的總和(第3-8行)。在C程序中,這個(gè)循環(huán)可以這樣寫: for (i=0; i sum += a[i]->len; } 注意如何訪問數(shù)組元素a[i]請看第4行的指針ai,以及如何訪問其子字段請看第6行的lenptr。 當(dāng)應(yīng)用Intel MPX保護(hù)后,這段程序?qū)⑥D(zhuǎn)換為以下代碼: 1: object * a[20] 2: a_b = bndmk a, a+159 // 生成邊界[a, a+159] 3: sum = 0 4: for (i=0; i 5: ai = a + i 6: bndcl a_b, ai // 監(jiān)測a[i]的下限 7: bndcu a_b, ai+7 // 監(jiān)測a[i]的上限 8: objectptr = load ai 9: objectptr_b = bndldx ai // a[i]作為指針的邊界 10: lenptr = objptr + 100 11: bndcl objectptr_b, lenptr // 監(jiān)測object.len的下限 12: bndcu objectptr_b, lenptr+3 // 監(jiān)測object.len的上下限 13: len = load lenptr 14: sum += len } 首先,在第2行創(chuàng)建數(shù)組a[20]的邊界(數(shù)組包含20個(gè)指針,每個(gè)指針寬8個(gè)字節(jié),因此上限界限為159)。然后在循環(huán)中,在第8行的數(shù)組元素訪問之前,插入兩個(gè)MPX邊界檢查以檢測a[i]是否溢出(第6-7行)。請注意,由于受保護(hù)的加載從內(nèi)存中讀取一個(gè)8字節(jié)的指針,因此檢查ai + 7與上限(第7行)非常重要。 既然指向?qū)ο蟮闹羔樖窃趏bjectptr中加載的,程序想要加載object.len子字段。按照設(shè)計(jì),MPX必須通過檢查objectptr指針的邊界來保護(hù)第二個(gè)加載。在MPX中,存儲(chǔ)在存儲(chǔ)器中的每個(gè)指針都有其相關(guān)的邊界,也存儲(chǔ)在由bndstx和bndldx MPX指令訪問的特殊存儲(chǔ)區(qū)中(前述)。因此,當(dāng)從存儲(chǔ)器地址ai檢索objectptr指針時(shí),使用來自相同地址的bndldx檢索其對應(yīng)的邊界(第9行)。最后,在第11-12行加載長度值之前,插入兩個(gè)邊界檢查。 由于這些Intel MPX指令可以引發(fā)#BR異常。因此,在操作系統(tǒng)級(jí)別,增加了一個(gè)新的#BR處理程序,它具有兩個(gè)主要功能:(1)按需分配存儲(chǔ)區(qū)域;(2)每當(dāng)檢測到邊界違規(guī)時(shí)向主程序發(fā)送越界異常#BR。 一方面,借助于編譯器、運(yùn)行時(shí)庫和操作系統(tǒng)的支持,Intel MPX 通過檢查指針引用為軟件帶來了更高的安全性,杜絕緩沖區(qū)溢出在運(yùn)行時(shí)被惡意利用的缺陷。另一方面,Intel MPX 的突出特點(diǎn)是其與傳統(tǒng)代碼的向后兼容性和互操作性。這就為軟件開發(fā)者帶來了一項(xiàng)新的設(shè)計(jì)技術(shù)。五、結(jié)語