20199125 2019-2020-2 《網絡攻防實踐》綜合實踐

論文信息

論文題目:Enhancing Memory Error Detection for Large-Scale Applications and Fuzz Testing
做者:Wookhyun Han (KAIST)Byunggill Joe(KAIST)Byoungyoung Lee(Purdue University)Chengyu Song(University of California,Riverside)Insik Shin(KAIST)
所屬會議:Network and Distributed Systems Security (NDSS) Symposium 2018html

摘要

內存錯誤是致使內存不安全語言(包括C和C ++)流行的最多見漏洞之一。一旦被利用,它很容易致使系統崩潰(即,拒絕服務攻擊)或使對手徹底破壞受害系統。本文提出了一種實用的內存錯誤檢測器MEDS。 MEDS經過近似兩個理想的特性(稱爲無限間隙和無限堆)顯着加強了其檢測能力。 MEDS的近似無限間隙在對象之間創建了較大的不可訪問的存儲區域(即4 MB),而且近似無限堆使MEDS能夠充分利用虛擬地址空間(即45位存儲空間)。 MEDS實現這些特性的關鍵思想是一種新穎的用戶空間內存分配機制MEDSALLOC。 MEDSALLOC利用頁面別名機制,該機制容許MEDS最大化虛擬內存空間利用率,但最小化物理內存使用量。爲了突出MEDS的檢測功能和實際影響,咱們進行了評估,而後與Google最新的檢測工具AddressSanitizer進行了比較。 MEDS對Chrome和Firefox中的四個真實漏洞的檢測率提升了三倍。更重要的是,當用於模糊測試時,在相同的測試時間內,MEDS可以比AddressSanitizer識別出68.3%的內存錯誤,突出了其在軟件測試領域的實際狀況。在性能開銷方面,與包括Chrome,Firefox,Apache,Nginx和OpenSSL在內的實際應用程序的本機執行和AddressSanitizer相比,MEDS分別下降了108%和86%。web

1、導言

因爲內存不安全語言如C和C++的普及,內存錯誤是最多見的軟件錯誤之一,尤爲是在瀏覽器和OS內核等大規模軟件中。從安全角度來看,內存錯誤也是最嚴重的錯誤之一,它們很容易致使系統崩潰(即拒絕服務攻擊),甚至容許對手徹底控制易受攻擊的系統(即任意代碼執行和權限提高)。在過去的幾十年裏,人們提出了許多解決方案來防止與內存錯誤相關的攻擊。這些防護技術能夠分爲兩個大的方向:利用緩解技術和內存錯誤檢測器。算法

利用漏洞緩解技術的重點是防止攻擊者利用內存錯誤執行惡意活動。因爲這些技術每每具備較低的運行時性能開銷(<10%),所以最普遍部署的機制屬於這一類,例如數據執行預防(DEP)、地址空間佈局隨機化(ASLR)和控制流完整性(CFI)。可是,它們的侷限性也很明顯:能夠經過新的利用技術輕鬆地繞過它們-從代碼注入到面向返回的程序到高級ROP攻擊到純數據攻擊,攻擊者始終可以尋找新的創造性方法來利用內存錯誤。docker

另外一方面,存儲器錯誤檢測器旨在檢測根本緣由。因爲這些檢測技術能夠在第一時間阻止攻擊的發生,所以它們可以防止全部與內存錯誤相關的攻擊。不幸的是,實現這一目標並不是沒有代價。首先,這些技術每每具備相對較高的性能開銷,從基於硬件的方法的30%到基於純軟件的方法的100%不等。第二,其中一些在支持C和
C ++的所有語言功能方面存在困難。shell

儘管存在缺點,咱們相信內存錯誤檢測器是從根本上防止內存錯誤相關攻擊的更有但願的方向。更具體地說,爲了戰勝現有的攻擊,咱們已經積累了大量的漏洞緩解技術。例如,最新的Windows系統(Windows 10)部署瞭如下漏洞緩解技術:DEP、ASLR、stack guard、control flow guard、return flow guard等。可是,因爲攻擊者如今轉向純數據攻擊和信息泄漏攻擊,必須添加新的緩解技術。問題是,即便每一個單獨的緩解技術的性能開銷可能很低,但累積的開銷仍然可能很高,特別是對於擊敗純數據攻擊(例如,數據流完整性)和信息泄漏(例如,動態信息流跟蹤)。與內存錯誤檢測器相比,它們仍然不能提供強大的安全保證。編程

因爲上述緣由,咱們提出了MEDS,該系統可加強基於Redzone的內存錯誤檢測的可檢測性。特別是,現有的內存錯誤檢測器能夠分爲兩個方向:基於Redzone和基於指針。基於Redzone的檢測器在內存對象之間插入未定義的內存,並禁止訪問未定義的區域。基於指針的檢測器跟蹤每一個指針的功能,並在訪問對象時檢查該功能。一般,基於Redzone的檢測器與C / C ++功能具備更好的兼容性,但其檢測內存錯誤的能力不如基於指針的檢測器。瀏覽器

MEDS背後的關鍵思想是,能夠利用完整的64位虛擬地址空間來近似分配的內存區域之間的「無限」間隙(以便檢測空間錯誤)和「無限」堆(以免避免重用已釋放的內存,而且檢測時間錯誤)。更重要的是,MEDS在不增長物理內存使用量的狀況下實現了這一目標。 MEDS經過新的內存分配器MEDSALLOC實現了這一想法。緩存

MEDSALLOC使用用戶空間頁面別名機制(即,物理和虛擬內存頁面之間的別名)來管理內存池,從而在最大程度地減小虛擬內存使用的同時最大化虛擬地址的利用率。與基於最新的基於Redzone的內存錯誤檢測器AddressSanitizer 相比,「無限」的差距使MEDS可以檢測更多的空間內存錯誤,這些錯誤表現出更大的出界偏移。 MEDS還能夠更有效地檢測時間內存錯誤,由於它充分利用了可用的虛擬地址空間進行分配,所以虛擬地址不太可能被重用。安全

咱們已經實現了基於LLVM工具鏈的MEDS,並在各類實際大型應用程序(包括Chrome、Firefox、Apache、Nginx和OpenSSL)上評估了MEDS的原型。首先,咱們在一組單元測試中評估了MEDS,MEDS可以正確地檢測全部測試的內存錯誤。而後,咱們使用Chrome和Firefox中的四個實際內存損壞漏洞測試MEDS,MEDS的檢測率是由Google開發的最早進的內存錯誤檢測工具AddressSanitizer(ASAN)的三倍。MEDS平均帶來了中等的運行開銷,MEDS的速度下降了108%,與ASAN至關;它使用的內存增長了212%。bash

利用MEDS的檢測能力,它能夠用於檢測生產服務器或模糊基礎設施中的潛在內存錯誤,一樣,ASAN已經被普遍地部署和使用。爲了清楚地說明這一點,咱們使用AFL進行了模糊測試,目標是12個實際應用程序。綜上所述,MEDS在幫助大多數目標應用程序的模糊化內存錯誤檢測能力方面明顯優於ASAN—平均提升68.3%,範圍從1%到256%,具體取決於應用程序(如表四所示)。考慮到AFL和ASAN在實際模糊測試中的巨大普及,這些結果也代表了MEDS的強大實際影響。與AFL結合使用,MEDS能夠加強模糊測試的檢測能力,明顯優於目前最早進的記憶錯誤檢測工具ASAN。咱們注意到ASAN是GCC(從v4.8開始)和LL VM/Clang(從v3.1開始)兩條主線的一部分,許多主要供應商和開源社區在調試和模糊測試方面都很是依賴ASAN。

總之,本文作出瞭如下貢獻:

設計:咱們設計MEDS,一種新的加強了檢測能力的存儲器錯誤檢測器。MEDS的核心是MEDSALLOC,這是一種新的內存分配程序,它(1)充分利用64位虛擬地址空間,在對象之間提供「無限」的間隔,避免重用釋放的虛擬地址;以及(2)利用一種新的內存混疊方案來最小化物理內存開銷。

實現和評估:咱們實現了一個基於LLVM工具鏈的MEDS原型,併成功地將其應用於一組大型的現實應用程序,包括Chrome、Firefox、Apache、Nginx和OpenSSL。咱們評估了MEDS的幾個方面,包括(1)它的兼容性,(2)它對人工和真實攻擊的檢測能力,以及(3)它的運行性能和內存開銷。

實際影響:根據咱們在模糊測試(使用AFL)中的評估,MEDS在檢測記憶錯誤方面明顯優於ASAN。咱們計劃使用開源MEDS,以便軟件供應商和開源社區可以從使用MEDS中受益。如咱們的評估所示,MEDS已經足夠成熟,能夠發佈並用於實際應用。

2、背景和挑戰

A、 內存錯誤

內存錯誤通常有兩種類型:空間錯誤和時間錯誤。空間內存錯誤是指訪問已分配內存邊界以外的內存。此類錯誤多是由許多類型的軟件錯誤引發的,包括缺乏邊界檢查,邊界檢查不正確,內存分配不足,類型混淆等。臨時內存錯誤可進一步分爲兩個子類別:讀取未初始化的內存和訪問已釋放的內存。讀取未初始化的內存可能會出現問題,由於其值要麼不可預測,要麼能夠被攻擊者控制。訪問已釋放的內存是有問題的,由於能夠從新分配已釋放的內存以存儲另外一個可能由攻擊者控制的內存對象。

Hicks[15]將內存錯誤的定義形式化爲兩種類型:

1.對未定義內存的訪問:若是內存區域未分配(超出界限)、未初始化或已釋放,則該內存區域未定義。雖然這個定義很簡單,但並不現實。爲了支持這個定義,任何兩個分配區域之間的間隔必須是無限的(即無限的間隔),而且釋放的內存區域永遠不能被重用(即無限堆)。

2.與指針的功能衝突:第二個定義將指針與訪問base和end之間的內存的功能相關聯。功能只能經過合法操做(如分配)來建立,所以得到的地址是不可僞造的;而且在釋放相應的內存區域時被撤銷(即沒有功能)。內存錯誤能夠定義爲訪問指針功能以外的內存。

圖1:在ASAN中使用Redzone和shadow內存進行基於Redzone的檢測。一開始有三個被分配的對象(最左邊),而後obj1被釋放(中間)。若是隔離區因爲重複分配而耗盡,則能夠重用釋放的空間(最右邊)。

根據上述定義,現有的檢測內存錯誤的方法一般能夠分爲兩個方向:(1)基於redzone的檢測,它在對象之間插入未定義的內存並檢測對未定義區域的訪問;(2)基於指針的檢測,它跟蹤每一個指針的功能,並在訪問對象時檢查功能。兩個方向各有利弊:通常來講,基於RealZeon的內存錯誤檢測器與C/C++語言特徵和線程模型有較好的兼容性,所以能夠應用於瀏覽器等大規模軟件。基於指針的檢測器一般存在兼容性問題。例如,SoftBound與某些特定的CPU基準測試不兼容,並且據報道,GCC對Intel MPX(內存保護擴展)的支持也存在兼容性問題。另外一方面,基於指針的解決方案一般具備更好的檢測能力,由於實現對象之間的無限間隙和從不重用釋放的內存是不現實的。由於咱們旨在構建一種實用的工具,以利用各類語言功能來支持大規模C / C ++程序,因此咱們選擇遵循基於redzone的方向,而且本節將重點介紹基於redzone的檢測。咱們將在§VIII中詳細描述基於指針的檢測。

B、 基於Redzone的內存錯誤檢測

基於redzone的檢測器在有效內存對象之間插入未定義的內存區域(也稱爲redzone)。而後,這些檢測器設置機制來捕獲訪問redzone的嘗試(例如,沒有虛擬頁面權限),以便可以檢測到對該區域的訪問。通常來講,基於redzone的方法有兩個關鍵設計因素,即(1)如何設置redzone以提升檢測率和(2)如何實際檢測對redzone的訪問嘗試。例如,爲了檢測時間錯誤,DieHard[5]及其繼承者DieHard用magic值填充新分配的內存和釋放的內存,但願之後使用magic值會致使可捕獲的錯誤。它們還會在分配的內存區域周圍添加Redzone以檢測空間錯誤。越界讀取的捕獲方式與檢測時間錯誤的捕獲方式相同。經過檢查釋放內存時是否修改了redzones的magic值來捕獲越界寫入。分頁堆用兩個沒有訪問權限的額外內存頁(每一個方向一個)包圍分配的區域,這樣超出限制的訪問將觸發頁面錯誤。Valgrind[25]使用有效值位和有效地址位來捕獲讀取未定義內存和越界訪問。

AddressSanitizer(ASAN)是迄今爲止最成熟的基於redzone的內存錯誤檢測器,Clang和GCC都支持它。它展現了檢測精度和性能之間的良好平衡,可以處理大型複雜軟件,如Google Chrome和Firefox。ASAN高效的關鍵在於它如何使用shadow內存[shadow memory]表示Redzone(如圖1所示)。shadow memory是一個位向量,顯示有效/無效的內存地址。shadow memory中的一個位表示目標應用程序虛擬內存空間中的一個字節,shadow內存中的位0表示有效,1表示無效。ASAN強制全部內存讀寫操做必須首先引用shadow內存,以檢查目標地址的有效性(即shadow內存中的相應位應爲0)。爲了檢測出越界訪問,ASAN用Redzone包圍全部內存對象(包括堆棧和全局對象)。此外,爲了檢測釋放後的使用狀況,ASAN在釋放對象時將整個釋放區域標記爲redzone。而後ASAN保持隔離區的固定大小(默認爲256mb),以免重用釋放的內存(即ASAN不會真正釋放釋放的內存區域,而是將這些區域保持在隔離區中,直到隔離區變滿)。例如,當對象obj3被釋放時,經過將相應的shadow內存位更新爲無效,相應的區域被標記爲redzone(如圖1-1所示)。這個釋放的區域將保留在隔離區內,以免重複使用。

雖然這種方法相似於Valgrind的有效地址位,但ASAN的特殊shadow內存地址方案使檢查速度更快。具體地說,ASAN使用直接映射方案來定位影子存儲器,由於它只需對虛擬地址執行位移操做來得到相應的shadow存儲器位置。這實際上須要爲shadow內存保留必定的虛擬地址空間,但因爲定位相應的shadow內存只須要簡單的位移位指令,所以效率很高。ASAN在實踐中已經顯示出與現有代碼很是好的兼容性。它在支持像瀏覽器這樣的大型軟件方面沒有問題,它是谷歌基於雲的模糊化平臺的默認內存錯誤檢測器。

現有Redzone探測器的侷限性。如前所述,基於redzone的內存錯誤檢測器的可檢測性取決於(1)對象之間的redzone有多大;(2)釋放的對象做爲redzone保留多長時間。具體地說,若是越界訪問落入另外一個分配的內存區域,或者在空閒訪問落入從新分配的內存區域以後使用,則沒法檢測到錯誤。不幸的是,現有的基於redzone的內存錯誤檢測器都不能正確地實現或近似無限間隙和無限堆的需求,所以它們的可檢測性受到限制。例如,默認狀況下,ASAN在16字節和2048字節的範圍內設置redzone,跳過這個redzone能夠很容易地繞過它。此外,考慮到堆的無限大,其隔離區的默認大小隻有256MB;所以,若是程序保持(或攻擊者誘使程序保持)分配內存對象以強制重用,則也能夠繞過對時間錯誤的檢測。

爲了闡明這些侷限性,圖1展現了內存佈局及其經過影子內存執行的redzone。開頭有三個分配的對象,obj1,obj2和obj3(最左側)。在此設置下,假設程序執行了指針算術運算,即p = p + idx,其中p是最初指向obj1基址(即圈1)的指針,而idx是整數變量。而後進一步假設程序使用p取消引用。經過檢查影子內存位,ASAN能夠分別正確地肯定當idx爲零(即圈1)時解除引用是有效的,而當idx是obj1的大小(即圈2)時取消引用是無效的。可是,若是idx大於obj1的大小,則能夠根據影子存儲位容許取消引用,儘管不該這樣作(即圈3)。此外,儘管將釋放的obj1區域保留在隔離區中(即黑圈1以後),可是若是因爲重複分配而耗盡了隔離區,則能夠從新使用該區域(即,在黑圈2以後可用於objX)。所以,若是在釋放obj1以後使用p進行另外一個內存取消引用,則可能致使使用後釋放(即圈4)。

爲了瞭解這個限制的真實含義,咱們還測試了四個真實的漏洞,發現ASAN很容易被繞過(見表一)。咱們注意到,在ASAN中擴大這些參數從根本上來講是一個挑戰,由於它將顯著增長內存使用。

3、問題範圍和目標

問題範圍:本工程重點討論了用戶空間C/C++程序內存錯誤檢測問題。咱們假設操做系統內核、全部固件和全部硬件都是可信計算基礎(TCB)。咱們不考慮針對咱們的TCB或從咱們的TCB內部發起攻擊。咱們也不考慮攻擊除內存錯誤或其餘語言中的內存錯誤之外的漏洞(例如,程序集和動態生成的代碼)。咱們不限制目標程序可使用哪一種語言功能,也不限制內存錯誤可能發生的位置該漏洞可能存在於主可執行文件或任何連接庫中。咱們也不限制攻擊者如何利用該漏洞。

目標:正如第二節所討論的,不一樣的記憶檢測器在檢測記憶錯誤方面有不一樣的能力,其中一些檢測器只能檢測空間錯誤,一些檢測器能夠檢測空閒但未初始化的記憶後的使用,一些檢測器能夠檢測全部類型的錯誤。它們的檢測率也各不相同,有的只能提供機率檢測,有的能夠提供肯定性檢測,但能夠繞過,有的能夠檢測出它們能檢測到的全部錯誤的發生,從而能夠提供強大的內存安全保證。

在這項工做中,咱們的目的是提升對大規模C/C++程序內存錯誤的檢測能力。這個聲明有兩個目標。首先,咱們的目標是處理大型程序,如流行的服務器應用程序和瀏覽器。咱們之因此選擇它們做爲目標,是由於它們的重要性和安全解決方案必須切實可行才能產生實際影響的信念。其次,咱們但願爲大型程序提供比現有解決方案更好的可檢測性。然而,提供較低的運行時性能開銷並非咱們的主要目標,咱們將盡力下降性能開銷,可是當可檢測性和性能之間存在權衡時,咱們將選擇可檢測性。

評估指標:鑑於目前的現狀,爲了實現咱們的目標,咱們能夠嘗試解決基於指針的解決方案的兼容性問題,或者嘗試提升基於redzone的解決方案的可檢測性。這項工做探索了第二個方向,咱們的評估指標是:(1)MEDS必須可以運行全部的程序,最早進的重分區解決方案,ASAN能夠處理;(2)MEDS必須可以檢測出比ASAN更多的內存錯誤;(3)運行時性能和內存開銷必須與ASAN至關。

4、 設計

本節介紹MEDS的設計。A部分說明了MEDS的設計概況。而後,B部分引入了MEDSALLOC,一種具備頁面別名的新內存分配器。而後,C部分描述了MEDS如何管理和執行不可訪問的內存區域redzone。D部分描述瞭如何經過MEDSALLOC分配全部內存對象(包括堆、堆棧和全局對象),以便MEDS全面地爲全部類型的內存對象提供redzone。最後,E部分給出了用於MEDS的用戶級寫時拷貝方案。

A、概述

MEDS採用基於redzone的方法來檢測內存錯誤,由於它提供了兩個不一樣方向之間的最佳兼容性(§II)。顧名思義,基於redzone的方法經過在內存對象之間插入redzone(未定義的內存區域)並將釋放的內存對象標記爲redzone來檢測內存錯誤。所以,基於redzone的內存錯誤檢測器的可檢測性取決於它能多接近兩個理想屬性:

P1:無限間隙:檢測全部的空間錯誤,兩個內存對象之間的Redzone必須是無限的,這樣,沒有綁定的訪問將始終落入Redzone。

P2:無限堆:爲了檢測全部暫時性錯誤,必須始終重新的虛擬地址分配一個新的內存對象,這樣在執行期間釋放的區域(redzone)就不會被重用。

不幸的是,因爲當前計算體系結構所施加的硬件資源(物理和虛擬內存空間)有限,徹底知足這些屬性是不可行的。所以,最早進的基於redzone的檢測工具在安全風險和資源消耗之間作出了實用的設計權衡。例如,默認狀況下ASAN[31]只在內存對象之間插入256字節的redzone來檢測空間錯誤,而只維護256 MB的堆隔離區來檢測時間錯誤。放大這兩個參數中的任何一個都會致使不適合大型程序的大量物理內存使用。爲了清楚地說明這一點,咱們嘗試使用這些放大的設置來嘗試ASAN:對於redzone大小,ASAN包括硬編碼斷言和限制這些參數的設計決策,所以咱們沒法運行;對於隔離區,ASAN在提供較大隔離區大小的狀況下,會很快耗盡全部物理內存空間,由於失去記憶而被殺。所以,若是空間內存錯誤發生在超出redzone大小的範圍內,則沒法檢測到這種內存訪問衝突。相似地,當隔離區已滿時,釋放的內存將被回收,從而致使沒法檢測到的時間錯誤。咱們在第六節中的評估清楚地代表了這一侷限性,由於Chrome和Firefox中的四個現實世界的漏洞經過稍微修改一個輸入很容易被繞過。

經過充分利用64位虛擬地址空間,MEDS改進了對這兩個理想屬性的近似。具體來講,64位虛擬地址空間爲咱們提供了一個很好的機會:(1)增長對象之間的redzone大小;(2)減小虛擬地址重用。然而,挑戰在於如何將物理內存的使用量降到最低。MEDS經過頁面別名和redzones的新組合克服了這一挑戰。頁別名表示虛擬內存頁和物理內存頁之間的有意別名(即將一組不一樣的虛擬頁映射到同一物理頁),這是一種經常使用的技術,用於減小物理頁的使用,例如in copy-on-write(CoW)和same-page merge[4]。可是,使用頁面別名的redzone強制一般會顯著增長碎片。這是由於內存對象分配的粒度不一樣於頁面訪問權限的粒度。也就是說,同一虛擬頁中的全部對象必須共享相同的訪問權限。這使得在單個虛擬頁同時包含有效對象和redzone時執行訪問檢查變得複雜。如PageHeap[20]所建議的,克服這一問題的一種方法是將全部redzone虛擬頁(即僅包含redzone而沒有任何有效對象)映射到單個物理頁,同時將分配粒度增長到頁級別(即在單個虛擬頁中最多分配一個對象),代價是生成內部分裂。所以,MEDS的目標是在不浪費物理內存的狀況下,過分提供虛擬內存空間,以同時知足P1和P2。爲此,咱們爲MEDS設計了新的Redzone方案。因爲MEDS密集地利用了巨大的虛擬地址空間,簡單地採用ASAN的基於shadow內存的redzones須要不切實際的物理內存空間來存儲shadow內存自己。所以,MEDS協調頁面訪問權限設置以及基於shadow memory的redzone,以有效地管理和強制全部無效內存空間的redzone。

B、MEDSALLOC:具備頁別名的內存分配器

爲了實現上述思想,咱們設計了一個新的用戶空間分配器MEDSALLOC,它維護虛擬和物理頁面之間的特殊映射以及redzone設置。使用MEDSALLOC,咱們能夠爲每一個內存對象提供虛擬視圖,就像它們不與其餘對象共享頁面同樣。所以,當對象被緊密地壓縮在物理內存空間中時,那些對象被稀疏地放置在虛擬內存空間中(圖2)。這使得MEDS可以在低內存開銷的狀況下知足P1的要求目標程序如今能夠在對象之間看到大的redzone,可是因爲redzone實際上沒有專門用於redzone的物理內存支持,所以這些只會佔用少許內存。值得注意的是,MEDSALLOC只使用shadow內存在子頁面級別(紅色框)標記redzone,頁面級別的間隙(在圖2中表示爲點)仍然由頁面表權限標記。這進一步減小了shadow內存的內存佔用。

圖2:具備基於shadow內存的redzone的別名內存頁

圖3: 帶有redzone的MEDS頁面別名方案的示例
爲了知足P2,MEDSALLOC維護虛擬內存空間的分配池,並始終嘗試將新分配的對象映射到新的虛擬地址,以便充分利用整個虛擬地址空間以免地址重用。請注意,MEDSALLOC不須要與ASLR兼容,由於MEDS能夠提供比ASLR更強的安全性保證。儘管MEDS是內存錯誤檢測器,但在利用了內存錯誤以後,ASLR在統計上會有所幫助。所以,MEDSALLOC能夠簡單的順序方式利用虛擬地址。

本節的其他部分首先提供有關頁面別名機制的詳細信息,由於它們是MEDSALLOC的關鍵啓用功能。而後咱們將介紹更多關於MEDSALLOC的設計細節,它採用使用全局和本地分配器的兩層方案(如圖4所示)。

頁別名:頁別名涉及虛擬內存頁和物理內存頁之間的有意別名,以便將多個虛擬頁映射到同一物理頁。使用頁面別名,能夠有多個內存視圖(經過多個虛擬頁面)指向同一內存內容(由同一物理頁面支持)。實際上,頁面別名一般用於減小物理頁面的使用,例如寫時拷貝(CoW)和同一頁面合併。

圖4:MEDSALLOC的工做流程。

因爲此頁面別名機制必須依賴於虛擬內存管理,所以其實現因底層架構和內核而異。對於運行Linux的x86-64體系結構(以及x86),能夠經過調用mmap()和mremap()系統調用來實現頁面別名。響應mmap()請求,Linux內核建立一個新的虛擬內存頁,該頁映射到一個新的物理內存頁。這裏,若是指定了MAP_SHARED標誌,內核容許之後共享映射(即,新的物理內存頁能夠由同一進程中的多個虛擬內存頁或其子進程映射)。而後,咱們使用mremap在同一進程的虛擬地址空間中建立其餘別名虛擬頁。假設mmap創建了虛擬頁面V1和物理頁面P1之間的映射。當調用mremap()時,內核將新的虛擬頁V2映射到相同的物理頁P1,而不移除V1和P1之間的舊映射,其中(1)old_address和new_address分別指向V1和V2,(2)old_size等於零,以及(3)MAP_FIXED標誌被設置。請注意,此行爲在手冊頁中沒有記錄,但在[35]中有描述。

圖3顯示了這個別名過程的一個示例。若是用戶進程調用設置了MAP_SHARED標誌的mmap(),內核將爲該進程建立新的虛擬頁並返回此類虛擬頁的基址圈1。以後,當用戶調用mremap(),其中舊的_address參數指向mmap()返回的地址時,內核會在新的_address○2處建立一個別名頁。這個別名能夠根據用戶進程的請求重複屢次,內核會返回另外一個新的別名虛擬頁面圈3。

全局分配器:爲了提升分配器的性能(經過減小鎖的使用),現代堆分配器有一個全局分配器(即每一個進程分配器),它爲運行的進程管理可用的虛擬地址空間,分區,而後將虛擬地址空間分配給本地的每一個線程分配器(如圖所示4-1頁)。這裏,MEDSALLOC與傳統堆分配器設計的關鍵區別在於,MEDSALLOC從不從內核請求實際的物理內存;相反,它只將虛擬地址分發給本地分配器,並從內核接管管理可用虛擬地址的職責。這種設計選擇使MEDS知足P2。當MEDS尋找一個未映射的虛擬空間時,全局分配器不試圖重用最近釋放的虛擬空間,而是老是從最後一個分配地址開始,並遵循單調的方向,這樣它就能夠徹底循環整個虛擬地址空間,並儘量晚地延遲地址重用。一樣,由於做爲內存錯誤檢測器,MEDS提供了比ASLR更強的安全保證,因此MEDSALLOC不須要隨機化分配的虛擬地址。

本地分配器:本地分配器(即每一個線程分配器)維護從全局分配器分配的虛擬內存頁,並將虛擬頁映射或別名爲適當的物理內存頁。也就是說,每一個本地分配器實際上從內核提交物理內存頁分配。此外,爲了使有效的物理內存用於小對象分配(即小於頁大小),每一個物理內存頁都由多個空閒列表管理,這些空閒列表將一個頁分紅多個內存槽。咱們爲這個自由列表使用了一個大小類分配方案,相似於tcmalloc[28]-類由分配大小決定,每一個類都有本身的自由列表。區別在於,在tcmalloc中,free list用於管理映射的虛擬頁(即虛擬物理頁對);但在MEDSALLOC中,free list僅管理物理頁。本地分配器(1)使用這些空閒列表查找具備適當和空內存槽的物理頁以進行分配,(2)從保留池中提取虛擬內存頁,以及(3)將虛擬頁與物理頁別名。

例如,在本地分配器初始化期間(即,在加載目標應用程序以後,在執行任何目標程序代碼以前),它在全局分配器的幫助下保留一個虛擬地址塊(如256 MB),並建立一個本地虛擬頁池(如圖4-1所示)。接下來,當從線程接收到分配請求時,本地分配器從本地虛擬頁池中選擇一個可用的虛擬內存頁(如圖42所示)。接下來,爲了找到一個可用的物理頁面,它掃描一個對應於分配大小的空閒列表,並選擇一個可用的物理頁面,並將其映射到上面的可用虛擬頁面(如圖4-3所示,它分配了objk)。若是空閒列表是第一次使用,所以沒有與之關聯的物理頁,則本地分配器使用帶有(MAP|SHARED|MAP|FIXED)標誌的mmap()syscall將新的物理頁映射到虛擬頁。另外一方面,若是空閒列表有一個相關的物理內存,它只需使用頁面別名(即mremap)重用該物理頁面。在對象分配以後,從本地虛擬頁池分配額外的虛擬頁,以設置預先配置的大小(例如,1 MB)的redzone,確保P1。

解除分配:解除分配對象時,MEDSALLOC將關聯的物理內存頁返回到空閒列表。若是物理頁與任何活動對象都沒有關聯(即,當使用物理頁的全部對象都被釋放時),則從空閒列表中刪除物理頁。在此以後,MEDSALLOC只需取消映射對象區域,物理頁將自動返回內核,由於沒有與物理頁關聯的虛擬頁。

圖5:MEDS的Redzone管理(每字節粒度)僞代碼算法。

圖6:內存(de)分配的Redzone管理

優化:MEDS在分配大於頁面大小的對象時使用優化方案(即4kb,若是使用的是大頁面,則爲2mb)。特別是,因爲幾乎沒有執行頁別名的優點,所以物理頁將被這些對象徹底佔用,所以沒有空間用於別名,所以咱們直接從物理頁分配這些對象,而無需經過自由列表進行搜索。

C、Redzone管理和執行

咱們須要額外的機制(咱們稱之爲redzone管理和強制)來檢測空間內存錯誤。MEDSALLOC自己不提供訪問控制。例如,在圖4中,使用指向objk的地址,還能夠訪問其餘對象,包括obj1和obj2。爲了捕獲這樣一個違規訪問,能夠簡單地採用ASAN中執行的基於shadow內存的redzone強制。然而,因爲MEDS使用比ASAN大得多的Redzone,所以用於維護Redzone的shadow內存使用將致使高內存消耗。因爲這個問題,簡單的Redzone方案不適用於MEDS治療。所以,MEDS採用了兩種不一樣的redzone管理方案:頁級redzone和子頁級redzone,其中只有子頁級redzone實際上用shadow memory表示。在下面,咱們首先描述MEDS如何管理這兩個不一樣的redzone,而後解釋MEDS如何執行redzone(即檢測對redzone的內存訪問)。

管理Redzone:爲了管理Redzone,MEDS基本上在運行時攔截目標應用程序調用的全部分配和釋放函數,並更新shadow內存。這裏的一個特殊挑戰是內存消耗,若是這些Redzone都是使用shadow內存表示的話。也就是說,MEDS經過設計在內存對象之間產生很是大的Redzone。若是MEDS爲整個redzone提交專用的shadow內存,那麼shadow內存自己將佔用大量物理內存。

爲了解決這一難題,咱們首先將redzone分爲兩種不一樣的類型,一種是頁面級redzone(即虛擬頁面之間的間隙),另外一種是子頁面級redzone(即頁面內的間隙)。而後,咱們利用這樣一個觀察結果,即一個shadow內存頁正好控制32 KB內存(即8個虛擬頁),這意味着咱們的頁級redzones(4 MB)消耗128頁粒度的影子內存。因爲頁級redzones中的每一個字節都是不可訪問的,所以相應的shadow內存頁將填充1。可是,咱們不須要爲頁面級的Redzone分配單獨的shadow頁,咱們能夠簡單地將這些shadow頁保留爲未映射的,所以對這些shadow頁的檢查將始終觸發一個頁面錯誤,該錯誤將由咱們的信號處理程序捕獲。

基於上述觀察,MEDS只在shadow內存中保留子頁面級的redzone,而頁面級的redzone不強制任何物理內存使用。圖5顯示了有關MEDS如何管理影子內存的僞算法,其中虛擬內存更改的快照如圖6所示。在對象分配時(圖5-(a)),因爲分配的虛擬頁被用做頁級的redzone(即其對應的shadow內存未映射),將第一個mmap新的物理頁從內核映射到其shadow內存頁,並將整個頁初始化爲無效(第8行)。而後,MEDS將相應的shadow內存(即要分配的對象地址範圍,從addr到addr+size)設置爲有效(第12行)。

在對象解除分配時(圖5-(b)),MEDS首先標記使用mprotect沒法解除分配的虛擬頁(第7行)。在MEDS中,頁面級redzones上的此權限設置始終是可行的,由於全部虛擬頁面始終與虛擬內存空間中的單個對象獨佔關聯。此外,MEDS沒有顯式地將相應的shadow內存空間標記爲無效,而是將shadow內存空間取消映射,將其標記爲Redzone(第10行)。一樣,因爲關聯的shadow內存不可訪問,訪問此已釋放內存區域的任未嘗試都將經過頁面錯誤檢測到。

圖7:在加載和存儲指令時使用內存訪問檢測的Redzone強制(每字節粒度)。

執行Redzone。MEDS經過實施redzones來確保全部的內存訪問都是有效的。全部內存訪問都獲得適當保護的安全保證是,檢測到任何訪問嘗試接觸到Redzone,由於(1)MEDS顯式檢查shadow內存(對於子頁級的Redzone)或(2)MEDS隱式捕獲頁錯誤事件(對於頁級的Redzone)。更具體地說,MEDS檢測全部的內存訪問指令,包括加載和寫入,這樣只有在經過shadow內存檢查有效性以後才容許訪問。

圖7說明了MEDS儀器如何加載和存儲指令。對於加載指令(圖7-(a)),MEDS首先檢查shadow內存中要訪問的給定地址(第5行)。若是給定的地址指向頁級redzones,MEDS將在加載相應的卷影內存位時捕獲頁錯誤,由於這樣的卷影內存空間是不可訪問的。若是shadow內存位已正確加載,但指示無效(即子頁級Redzone),則MEDS不容許執行原始加載指令(第6行)。對於這兩種違規嘗試,不管是經過捕獲頁面錯誤事件仍是檢測無效的卷影內存位,MEDS都會報告有關違規的詳細信息,以便開發人員或用戶可以輕鬆瞭解訪問違規的緣由。若是該位指示有效,MEDS容許執行原始的加載操做(第7行),這樣程序執行語義對於良性加載操做保持不變。存儲指令的處理方式與加載。

優化:與ASAN相似,對於memset()和memcpy()這樣的內存內部函數,MEDS使用其參數檢查其安全性,而不是檢查這些內存內部函數中全部重複加載/存儲指令的安全性。可是,因爲其Redzone較小,在檢查緩衝區的開始、結束和中間以後,若是全部檢查都成功,ASAN仍然須要檢查緩衝區全部字節的shadow值。可是,因爲MEDS在對象之間使用的間隔要大得多,所以咱們只須要檢查開始、結束和對齊良好的字節(例如,4mb對齊,這是MEDS的當前默認redzone大小)。

D、內存對象分配

MEDS使用MEDSALLOC(§IV-B)分配全部內存對象,使得全部內存對象都被近似無限間隙包圍,其分配池遵循近似無限堆的概念。一般,根據對象在何處被分配堆、堆棧和全局對象,能夠有三種不一樣類型的內存對象。當每種對象類型經過不一樣的分配機制時,MEDS按照每一個分配類型適當地知足其分配過程,以便使用MEDSALLOC分配全部內存對象。

堆對象:堆對象經過一組有限的運行時函數(例如malloc、calloc等)進行分配。與ASAN相似,咱們在這些函數上安裝截取鉤子,這樣MEDS能夠控制分配過程。而後,當從用戶程序接收到堆分配請求時,MEDS簡單地利用MEDSALLOC返回一個別名內存對象。

堆疊對象:堆棧對象在相應函數的堆棧幀內分配。與堆對象不一樣,MEDS在處理堆棧對象時採用不一樣的方法,這取決於它們是隱式分配仍是顯式分配。對於隱式分配的堆棧對象,如返回地址和溢出寄存器,因爲對它們的訪問老是安全的,MEDS不須要用redzones來保護它們(也稱爲安全堆棧)。另外一方面,MEDS使用MEDSALLOC將顯式分配的堆棧對象(即堆棧變量)遷移到堆空間中,以便輕鬆地利用MEDSALLOC的特性用redzone保護它們。對於函數中的每一個堆棧對象,MEDS在相應函數的開頭插入運行時函數alloc_stack_obj(size,alignment)。此函數使用給定大小的MEDSALLOC執行動態堆分配,同時觀察已分配對象的對齊約束。MEDS還在函數的結尾插入另外一個運行時函數free_stack_obj(ptr),它在函數返回以前正確地釋放堆棧對象(位於MEDS下的堆空間中)。MEDS還將這個自由運行時函數調用註冊到異常處理鏈,以便在堆棧因異常而展開時釋放堆棧對象。這樣,MEDS將全部堆棧變量放在heap中,MEDSALLOC始終在heap中執行其分配。

全局對象:與堆棧和堆對象不一樣,全局對象的地址位於加載程序時。更準確地說,在ELF可執行文件的狀況下,ELF加載器映射在ELF格式的程序頭部分中指定的虛擬內存頁。

利用MEDSALLOC的一個簡單的設計決策是爲每一個全局對象建立別名內存頁,同時將加載程序映射的數據頁視爲壓縮的物理內存頁。然而,咱們發現這在不影響兼容性的狀況下是不可行的。由於在Linux內核中實現的內置ELF加載程序在映射數據內存頁時老是分配MAP_PRIVATE(而不是分配MAP_SHARED),因此這些內存頁不能被別名化。咱們能夠經過如下兩種方法解決此問題:1)使用用戶級自定義ELF加載程序而不是使用內置ELF加載程序;2)只需修改內置ELF加載程序以指定MAP_SHARED。這兩種解決方法均可能對兼容性產生負面影響。使用自定義加載程序爲最終用戶設置執行環境可能會很麻煩,或者一般不建議修改底層內核。

所以,爲了保持兼容性,MEDS實現了全局對象的用戶級從新分配方案。加載目標程序後,在執行程序的原始入口點以前,MEDS枚舉全局對象列表,並使用MEDSALLOC從新分配每一個全局對象。若是全局對象須要初始化(即具備非零字節的數據),MEDS也會相應地複製這些底層數據。從如今起,全局對象的位置已經遷移到堆空間,MEDS經過引用ELF中的從新定位表,將全部引用(指向原始全局對象)從新定位到堆空間中從新分配的引用。雖然這個全局對象的方案看起來性能昂貴,但咱們強調這個過程只須要執行一次,所以它只會給程序加載過程增長一次性的固定成本。

E、用戶級寫時拷貝(CoW)

如B部分所述,MEDS使用帶有MAP_SHARED標誌的mmap()syscall將物理頁與多個虛擬頁別名,但這種方法與Linux內核的CoW方案存在兼容性問題。更具體地說,調用fork()syscall時,子進程與其父進程共享相同的物理頁。而後,內核CoW執行延遲複製並取消對修改後的物理頁面的共享。可是,映射了MAP_SHARED的頁面不適用於CoW,由於內核解釋說,父進程和子進程之間應該共享這樣的頁面。所以,對於受MEDS保護的進程,fork()將破壞進程之間的正常隔離保證。

爲了解決這個問題,MEDS設計了一個用戶級的copy-onwrite機制。首先,MEDS截獲全部相似fork的系統調用。在fork()以前,MEDS將全部由MEDS分配的MAP_SHARED虛擬頁標記爲不可寫。fork()以後,當進程嘗試寫入任何此類頁時,預安裝的信號處理程序將經過頁面錯誤捕獲該嘗試。而後,MEDS分配一個新的物理頁,將其映射到一個臨時虛擬地址(與MAP_共享),硬拷貝舊物理頁中的內容,取消映射舊物理頁,將新物理頁從新映射到舊虛擬地址,取消映射臨時地址中的新物理頁,而後將控制權傳遞迴進程以繼續寫操做。這種機制也能夠在內核級別經過添加頁面別名的專用標誌來實現,可是咱們選擇實現用戶級別的解決方案,以免安裝內核擴展以得到更好的兼容性。

5、 實施

咱們已經基於LLVM編譯器項目(版本4.0)實現了MEDS的原型。 MEDS在總共10,812行c和c ++代碼中實現。整體而言,MEDS將目標應用程序的C或C ++源代碼做爲輸入,並生成可執行文件。儀表模塊實現爲額外的LLVM傳遞。運行時庫模塊基於LLVM中的清理程序例程。全部標準分配和解除分配功能均由MEDSALLOC委託。寫入時複製(COW)經過掛接fork()並安裝自定義信號處理程序來捕獲無效的內存訪問嘗試來實現。

論文中主要實現的技術包括MEDSALLOC內存分配機制和頁內存訪問錯誤捕捉機制。主要基於LLVM編譯器項目,項目總體是一個LLVM extra pass,經過對目標項目的C/C++代碼進行編譯插樁,並生成可執行文件,運行時動態庫主要仍是基於LLVM的sanitizer規則來作的。MEDSALLOC基本上會把全部的內存分配和回收函數hook,用來完成程序內存對象以及影子內存的分配和監控操做。代碼量10,812行。MEDS會與ASLR發送衝突,因此須要關閉ASLR。

構建支持MEDS的編譯器

$ make

Build Using Docker

uploading-image-367079.png

用docker build -t 文件名稱 . 這個命令構建鏡像文件image ,後面那個「.」表示當前目錄,以後運行


注意:meds後面的「.」,"."是該命令必須得加的參數,意思是在當前目錄下找Dockerfile文件, 勿忘

# build docker image $ docker build -t meds

# run docker image $ docker run --cap-add=SYS_PTRACE -it meds /bin/bash

測試MEDS

MEDS的測試運行原始的ASAN測試用例以及MEDS特定的測試用例。

  • 將ASAN的測試用例複製到 llvm/projects/compiler-rt/test/meds/TestCases/ASan
  • MEDS特定的測試用例 llvm/projects/compiler-rt/test/meds/TestCases/Meds
    運行測試
    $ make test

Testing Time: 30.70s Expected Passes : 183 Expected Failures : 1 Unsupported Tests : 50

使用MEDS堆分配以及ASan堆棧和全局構建應用程序

  • 給定一個測試程序test.cc

$ cat > test.cc

int main(int argc, char **argv) { int *a = new int[10]; a[argc * 10] = 1; return 0; }

  • test.cc可使用選項構建-fsanitize=meds
$ ./test

==90589==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x43fff67eb078 at pc 0x0000004f926d bp 0x7fffffffe440 sp 0x7fffffffe438
WRITE of size 4 at 0x43fff67eb078 thread T0
    #0 0x4f926c in main (/home/wookhyun/release/meds-release/a.out+0x4f926c)
    #1 0x7ffff6b5c82f in __libc_start_main /build/glibc-bfm8X4/glibc-2.23/csu/../csu/libc-start.c:291
    #2 0x419cb8 in _start (/home/wookhyun/release/meds-release/a.out+0x419cb8)

Address 0x43fff67eb078 is a wild pointer.
SUMMARY: AddressSanitizer: heap-buffer-overflow (/home/wookhyun/release/meds-release/a.out+0x4f926c) in main
Shadow bytes around the buggy address:
  0x08807ecf55b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x08807ecf55c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x08807ecf55d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x08807ecf55e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x08807ecf55f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x08807ecf5600: fa fa fa fa fa fa fa fa fa fa 00 00 00 00 00[fa]
  0x08807ecf5610: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x08807ecf5620: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x08807ecf5630: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x08807ecf5640: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x08807ecf5650: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==90589==ABORTING

關於選件

  • fsanitize=meds:使用MEDS啓用堆保護(使用ASAN保護堆棧和全局堆)

  • mllvm -meds-stack=1:使用MEDS啓用堆棧保護

  • mllvm -meds-global=1 -mcmodel=large:使用MEDS啓用全局保護

    • 這也須要--emit-relocsLDFLAGS
  • 示例:使用MEDS保護堆/堆棧並使用ASAN全局保護

$ clang -fsanitize=meds -mllvm -meds-stack=1 test.c -o test

  • 示例:使用MEDS保護堆/全局和使用ASAN保護堆棧

$ clang -fsanitize=meds -mllvm -meds-globals=1 -mcmodel=large -Wl,-emit-relocs test.c -o test

  • 示例:使用MEDS保護堆/堆棧/全局

$ clang -fsanitize=meds -mllvm -meds-stack=1 -mllvm -meds-globals=1 -mcmodel=large -Wl,--emit-relocs

6、 評價

實驗配置:MEDS配置爲4 MB的Redzone和80 TB的隔離區。ASAN被配置爲具備從16字節到2048字節到redzone2的默認參數,以及256 MB的隔離區參數。如前所述,因爲大量使用物理內存,放大ASAN的參數最終會出現內存不足的問題。

實驗裝置:咱們全部的評估都是在Intel(R)Xeon(R)CPU E5-4655 v4@2.50GHz(30MB緩存)和512GB RAM上執行的。咱們用Linux 4.4.0 64位運行Ubuntu16.04。咱們使用MEDS構建瞭如下五個應用程序進行評估:Chrome瀏覽器(58.0.2992.0)、Firefox瀏覽器(53.0a1)、Apache web服務器(2.4.25)、Nginx web服務器(1.11.8)和OpenSSL庫(1.0.1f)。

A、兼容性

MEDS的關鍵目標之一是在運行目標應用程序時保持兼容性,特別是對於大型商品程序。爲了檢查這種兼容性,咱們運行了受尊敬的供應商提供的基本功能單元測試:Chrome中的2242個測試用例,Firefox中的781個測試用例,Nginx中的1772個測試用例。MED經過了全部這些單元測試,這意味着MED真正知足複雜程序的兼容性要求。

B、針對攻擊漏洞的可檢測性

回想一下,MEDS經過近似無限間隙和堆的概念來檢測內存錯誤。在本小節中,咱們首先在一組致使內存損壞的簡單單元測試中測試med的檢測能力。而後咱們使用realworld漏洞來查看MEDS在實際用例中的檢測能力。最後,咱們展現了各類各樣的度量來證實MEDS近似對無限間隙和堆的有效性。

內存錯誤單元測試:爲了看看MEDS是否能檢測到全部不一樣類型的內存錯誤,咱們運行了LLVM ASAN中可用的一組單元測試。它有50個單元測試用例,包括堆棧溢出、堆溢出、空閒後使用等。除了這些用例以外,爲了更好地比較MEDS和ASAN,同時展現MEDS的侷限性,咱們還增長了如下四個測試:兩個堆溢出案例分別訪問ASAN或MEDS的redzone(4mb);和兩個在空閒狀況後使用的堆,分別分配小於或大於隔離區大小。在全部這些測試中,一個簡單的易受攻擊的程序使用觸發內存錯誤的特定輸入運行,若是程序正確中止並報告錯誤,則測試經過。全部的病例中,除了有一種MEDS能夠經過全部的記憶測試。正如預期的那樣,這個異常狀況是堆溢出訪問超過MEDS的redzone大小(4mb)。至於ASAN,因爲Redzone和隔離區面積小,未能發現3例病例。根據這些單元測試結果,MEDS的檢測能力能夠說是ASAN的超級集合。(ASAN取redzone的最小和最大大小。而後,根據分配大小,ASAN在這個最小和最大範圍內選擇redzone大小。)

朱麗葉測試套件(Juliet Test Suite):NIST提供朱麗葉測試套件[6],該套件是爲測試軟件保證工具的有效性而開發的。每一個測試用例都有兩個版本,一個是對有漏洞的壞函數的調用(以便度量誤報),另外一個是對修補了漏洞的好函數的調用(以便度量誤報)。咱們特別關注Juliet中與內存損壞相關的測試用例,總共11414個測試用例:3124個堆棧緩衝區溢出測試(CWE 121),3870個堆緩衝區溢出測試(CWE 122),1168個緩衝區包銷測試(CWE 124),870個緩衝區過讀測試(CWE 126),1168個緩衝區欠讀測試(CWE 127),820個雙自由度測試(CWE 415),394個測試在空閒後使用(CWE 416)。咱們使用MEDS和ASAN編譯了全部這些測試用例,並根據可檢測性測量了假陽性和假陰性。在大多數狀況下,MED和ASAN均顯示0假陽性和假陰性。然而,有時這兩個測試用例都顯示了假陰性,範圍從0到288。咱們分析了這些假陰性案例的細節,發現這些測試案例涉及隨機內存訪問(即訪問地址是經過隨機時間種子函數計算的)。換句話說,這些隨機訪問可能會跳過這兩種方案強制的redzone大小,從而致使誤報。

爲了更好地比較檢測能力並瞭解其與隨機訪問相關的實際意義,咱們用如下約束脩改了這288個實例:(1)再分配1000個對象(目前Juliet測試只爲每一個測試分配一個對象)和(2)將隨機訪問限制在堆棧/堆段的範圍內。第一個約束考慮實際的運行環境,在這種環境中,大多數實際程序在運行時分配大量內存對象。第二個約束考慮了通常編程實踐—實際上指針值主要是從現有對象的地址推導出來的。咱們將這些變化應用到288個測試用例中,並運行了一百萬次來測量檢測機率。咱們的結果顯示,MEDS檢測到98%的這些,而ASAN檢測到35%。儘管朱麗葉試驗的這種改進能夠說是有利於MEDS的,但咱們仍然相信MEDS的這種突出的檢測機率足以證實它在檢測能力上比ASAN有了顯著的提升。

檢測真實世界的內存錯誤。爲了更好地理解MEDS是否可以真正檢測到實際用例中的內存錯誤,咱們針對Chrome和Firefox等流行應用程序中的一組漏洞發起了內存崩潰攻擊。對於每一個漏洞,咱們首先將目標應用程序的源代碼回滾到易受攻擊的版本,而後使用ASAN和MEDS構建應用程序以比較檢測能力。

表一:MEDS對現實世界漏洞的檢測能力:S—空間內存錯誤;T—時間內存錯誤;W—寫入違規;R—讀取違規;✓—檢測到;▲—部分檢測(難以繞過);△—部分檢測(易繞過)

如表1所示,MEDS在Chrome和Firefox中加強了對ASAN的空間和時間內存錯誤的檢測能力。事實上,該表不只證實了MEDS近似無限間隙和堆的有效性,並且也證實了它的侷限性。在CVE-2016-1653中,因爲該漏洞提供的違規訪問範圍有限,小於4MB(即小於MEDS的Redzone大小),所以MEDS可以徹底檢測到,而ASAN則沒法。然而,對於其他三種狀況,由於它們提供了對指針的徹底控制(即,徹底的任意內存讀寫漏洞),MEDS也像ASAN同樣被繞過。咱們仍然注意到繞開MEDS比ASAN更困難,所以在MEDS中它們分別標記爲▲和ASAN中標記爲△。

近似的有效性:MEDS經過近似無限的間隙和堆來提升檢測能力,但顯然因爲內存資源有限,應該有必定的上限。所以,咱們研究這些限制在檢測能力方面的實際影響。特別是,內存訪問的偏移大小直接影響基於redzone的檢測的有效性。實際上,這個偏移量大小與分配的對象大小密切相關,由於中間指針算法只涉及在同一個對象內移動指針。所以,指針運算可能致使的差別大多小於相關的對象大小。所以,咱們測量了咱們評估的全部應用程序中每一個分配的大小,發現全部對象都小於4MB,這意味着4MB的redzone能夠提供至關好的檢測能力。此測量還顯示了ASAN的侷限性,由於11%的對象大於256字節(即ASAN的默認redzone大小),而且訪問11%對象的例程可能被濫用以繞過redzone大小。如前文§IV所述,因爲內存不足問題,在ASAN中放大此參數不適合大規模應用。值得注意的是,經過嚴格地將指針算法限制爲最大對象大小(即在這些應用程序中爲4MB),能夠進一步加強MED。這將使MEDS真正達到無限的差距。

MEDS還經過循環64位虛擬內存空間來近似無限堆。更準確地說,單靠MEDS沒法充分利用這樣的64位空間,但它目前使用80 TB的虛擬內存空間—考慮到x86中47位的用戶陸地虛擬地址空間(總計128 TB),它爲影子內存預留了16 TB,另外16 TB用於MED的內部內存分配,還有16 TB爲Linux保留堆棧。所以,因爲MEDS在分配超過80tb的對象後開始重用虛擬內存空間,所以咱們嘗試從最終用戶的角度預測觸發此操做所需的時間。具體來講,咱們運行了Chrome和Firefox的MEDS應用版本,每5分鐘使用同一個標籤訪問網站;運行Apache和Nginx,每秒處理25000個請求(併發級別爲50)。根據咱們的運行結果(表二),Chrome、Firefox和Apache分別在49分鐘、160分鐘和141分鐘後開始重用地址空間。咱們認爲這是一個至關長的時間,不會干擾最終用戶的體驗,尤爲是考慮到大多數用戶會頻繁關閉並建立新的選項卡時。Nginx很快就耗盡了虛擬地址空間,但它只花了4分鐘就被回收了。咱們懷疑這是由於Nginx設計用於重複重分配內存。雖然這不是一個理想的狀況,但在這種狀況下,Nginx進程能夠在達到這個虛擬地址回收時間以前頻繁地從新生成。

表二:MEDS上虛擬地址回收的頻率:H—堆對象別名;HS—堆和堆棧對象別名;HSG對全部對象(包括堆、堆棧和全局對象)進行別名化。注意,對於Chrome和Firefox,ASAN上的第一個虛擬地址重用是在初始化時快速完成的。

表三:具備微基準的MEDS和ASAN的檢測性能。

C.模糊測試中的可檢測性

爲了演示MEDS在執行模糊測試時檢測內存錯誤的有效性,咱們使用微基準測試和實際應用程序運行了模糊測試。咱們使用American Fuzzy Lop(AFL)做爲模糊測試框架[38],它是實踐中最受歡迎的模糊測試工具之一。首先使用AFL對目標程序進行檢測,以啓用其基於反饋的模糊功能,而後對它們與MEDS和ASAN進行檢測,以比較檢測能力。

模糊化微基準程序:在此評估中,咱們開發並測試了兩個簡單但現實的易受攻擊的程序,分別表現出緩衝區溢出或釋放後使用漏洞。編寫這些測試程序旨在突出MEDS的有效性,特別是在非線性內存違例狀況(即,超出Redzone的大小)和具備大量內存分配的時間違例狀況(即,超出隔離區的大小)方面)。所以,這些可能並不表明全部內存錯誤狀況的常規檢測能力。可是,因爲這些易受攻擊的代碼是從真實世界的漏洞中獲取並簡化的,所以咱們認爲此測試在內存錯誤檢測方面仍具備實際意義,咱們將在實際應用中進一步展現這些錯誤。

關於緩衝區溢出漏洞的第一種狀況是由分配大小上的整數溢出引發的。它佔用畫布的寬度和高度並分配畫布。以後,程序將偏移量,大小和數據寫入畫布。計算畫布大小時會出現整數溢出。第二種狀況有一個售後使用漏洞。最初,它具備一組指針,每一個指針都指向一個堆對象。而後,該程序將使用一個整數值k,該值將釋放k個對象。釋放對象以後,它比釋放的對象分配更多的新對象,並嘗試訪問指向堆的指針之一。

咱們已經對微基準測試執行了10次,表三顯示了遇到第一次崩潰的平均時間,以及每小時的平均崩潰次數。在咱們的微基準測試中,MEDS遇到的第一次崩潰比ASAN早12倍。在使用ASAN運行MEDS首次崩潰時,ASAN一般沒法檢測到該漏洞。此外,在模糊測試期間,MEDS的平均崩潰次數是ASAN的3倍。結果代表,MEDS能夠比ASAN更快地找到目標漏洞。換句話說,MEDS在檢測性能方面頗有效。

模糊的真實程序:爲了清楚地展現MEDS在加強模糊測試功能方面的實際狀況,咱們還使用實際程序運行AFL。表IV顯示了在每一個程序模糊測試六個小時時的結果。咱們從GitHub和Debian存儲庫中收集了一組目標應用程序,其受歡迎程度與受歡迎程度對(GitHub中的forks和star的數量)和安裝排名(Debian存儲庫中的26,762個應用程序)相關,分別。這些應用程序都是最新版本,所以從該測試中發現的錯誤都是新錯誤,咱們已經在與相應的開發社區聯繫以報告這些問題。應用程序的複雜性用代碼行(LoC)表示。執行總數表示絨毛測試六個小時內執行的實例數。因爲能夠經過許多不一樣的輸入觸發相同的內存錯誤,所以AFL僅使崩潰顯示惟一的執行路徑,這稱爲惟一崩潰。

在獨特崩潰總數方面:整體MEDS在加強咱們運行的全部目標應用程序的模糊檢測的內存錯誤檢測功能方面均優於ASAN,平均提升了68.3%,範圍從1%到256%。實際上,這些結果特別有趣,由於MEDS在執行速度上並不比ASAN更好(儘管有時MEDS比ASAN更快),由於它很大程度上取決於應用程序的運行時特性(即內存分配行爲)。該執行速度能夠從執行總數中得出。例如,在PH7的狀況下,MEDS的速度要比ASAN慢一些(即慢7%)。與MEDS一塊兒運行時,七個應用程序的速度較慢,可是,在模糊測試期間會發生更多獨特的崩潰。使用MEDS運行時,五個應用程序(即lci,picoc,swftools,exifprobe和jhead)更快。其中,就每次執行的惟一崩潰而言,MEDS在兩個應用程序(即exifprobe和jhead)中速度較慢。咱們懷疑這是由於MEDS到達的時間比ASAN早,AFL在這兩個應用程序中探索更多的執行路徑時已經飽和。飽和後,MEDS花費了其他的模糊測試周期,比ASAN花費了更多的週期,由於MEDS具備更快的執行速度,而沒有發現新的獨特崩潰。換句話說,MEDS發現大多數獨特的崩潰都比ASAN快,可是在剩餘的模糊時間上卻沒有發現更多的崩潰,由於AFL變得飽和了。對於其他三個應用程序(即lci,picoc和swftools),使用MEDS運行時,每次執行時它們具備更高的惟一崩潰。

表四:對實際應用進行模糊處理以比較ASAN和MEDS的內存錯誤檢測功能。 α表示GitHub中的(叉子數量,星數),β表示Debian人氣競賽的安裝排名。使用AFL模糊器將每一個應用程序模糊化6個小時[38]。

即便對於在MEDS中執行速度較慢的七個應用程序(即PH7,ImageMagick,wren,espruino,tinyvm,猛禽和metacam),MEDS仍然可以找到比ASAN更多的獨特崩潰。這意味着,提供加強的檢測功能的優點勝於下降執行速度的劣勢,從而使MEDS整體上提升了模糊測試的性能(就發現更多獨特的崩潰而言)。

咱們相信,這清楚地證實了MEDS在ASAN之上提升了內存錯誤檢測能力。考慮到AFL和ASAN在執行真實世界的模糊測試中的普遍普及,這些結果也代表MEDS的強大實際影響力-與AFL一塊兒使用時,MEDS的原型能夠幫助模糊測試過程,其性能遠優於最新的內存錯誤檢測工具ASAN。

D.性能開銷

MEDS的安全服務顯然帶有成本,這主要影響兩個性能因素:運行速度和物理內存使用率。

運行時速度:形成MEDS運行時速度開銷的主要因素是:(1)它執行額外的指令以檢查全部內存加載和存儲指令; (2)因爲MEDS利用了更多的虛擬地址空間,所以會有更多的TLB未命中; (3)每一個對象分配都須要調用mremap syscall來進行頁面別名。

爲了更好地理解這些方面,咱們爲應用程序運行了基準測試-表V顯示了Chrome,Firefox,Apache和Nginx的運行結果,表VI顯示了OpenSSL的運行結果。對於Chrome和Firefox,咱們使用了Octane基準測試[14];對於Apache和Nginx,咱們使用Apache基準測試[13],該基準每秒可處理25,000個請求。對於OpenSSL,咱們使用OpenSSL的speed命令經過SHA1 [12]加密內存塊。對於每次運行,咱們應用了三種不一樣的MEDS設置,以更好地瞭解對象覆蓋範圍對性能的影響。換句話說,帶有H的MEDS列表示MEDS保護堆對象(即,全部堆對象均已使用MEDSALLOC分配)。相似地,HS表示堆和堆棧對象,HSG表示全部對象類型,包括堆,堆棧和全局對象。
平均而言,與基線相比,MEDS將MEDS-H的執行速度減慢約27%,將MEDS-HS的執行速度減慢94%,將MEDS-HSG的執行速度減慢108%。首先,隨着MEDS增長對象覆蓋範圍(從堆對象類型到全部對象類型),因爲MEDS會丟失更多TLB並調用更多系統調用,所以執行速度逐漸下降。 Nginx的MEDS-H和MEDSHS之間的性能變化尤爲明顯(即24%至250%)。這是由於Nginx在運行時分配了大量的堆棧對象,這又會爲MEDS帶來大量的分配(調用函數時)和釋放(當函數返回時)。可是,堆棧對象分配不是基線的性能瓶頸,由於它只須要移動堆棧指針便可爲對象保留和釋放堆棧內存空間。

與ASAN相比,MEDS下降了執行速度約11%,73%和86%。因爲MEDS與ASAN相比,在指令化指令方面沒有帶來可觀的開銷(即都檢查影子內存位),所以咱們檢查了其餘性能因素-TLB未命中(表VII)和被調用的系統調用次數(表VIII)。整體而言,MEDS確實致使了更多的TLB丟失(即,平均比ASAN多499%),而且調用了更多的系統調用(即,平均比ASAN多32倍)。可是,就可檢測性而言,咱們認爲這是MEDS加強安全服務的合理費用。

表五:MEDS的運行時性能(Octane得分,ApacheBench每秒請求數;越高越好)的開銷,以及ASAN和比較的基線。整體而言,與基準相比,MEDS的平均執行速度下降了108%,而對ASAN的執行速度則下降了86%。

表六:MEDS的OpenSSL性能(每秒處理的KB數)以及基線和ASAN。較大的塊大小可減小ASAN和MEDS的開銷。尤爲是,塊大小對於MEDS的性能相當重要。

表七:運行基準測試時的TLB利用率

表八:運行基準測試時調用的系統調用數

表九:MEDS的物理內存使用,以及用於比較的基線和ASAN。整體而言,MEDS平均使用的物理內存比基準多218%,而且比ASAN使用多68%。

物理內存:MEDS會佔用更多的物理內存,由於它保留了影子內存以及頁面別名映射信息所需的額外元數據。如表IX中所示,MEDS-H,HS和HSG分別平均比基線多了133%,200%和212%的物理內存使用。特別是,儘管MEDS在OpenSSL中施加了432%,在Apache中施加了301%,但其他四個應用程序平均施加了109%。這是由於OpenSSL中的全部內存分配都是較小的分配(即8到32字節),而MEDS會爲每一個對象元數據附加8字節以保留別名信息。此外,OpenSSL不會在評估期間取消分配內存對象。所以,對應的影子存儲器頁面被映射到物理存儲器頁面。表VII中的TLB未命中也捕獲了此運行時特徵-OpenSSL激烈地擴展了虛擬地址空間,所以TLB未命中率最高。相反,ASAN平均比基準強加了95%,由於ASAN實際上爲Redzone和隔離區分配了物理內存空間。咱們相信這證實了頁面別名機制的有效性,由於MEDS在利用巨大的虛擬地址空間時不會強加不切實際的物理內存使用。

7、討論

潛在的用例:在本文中,咱們認爲MEDS的用例對於加強內存錯誤檢測功能具備廣泛意義,所以咱們嘗試中和MEDS的用例。一種特定的用例是部署MEDS,以減輕大規模應用程序的內存損壞攻擊。因爲MEDS確實知足兼容性要求(由於它能夠運行包括Chrome,Firefox,Nginx和Apache的大型程序)而且與其餘檢測工具相比加強了檢測能力,所以特別適合於檢測自己,它很是適合這些狀況。可是,MEDS引入的性能開銷多是個問題,所以可能不適用於對性能有嚴格要求的應用程序。

MEDS的其餘用例將增長模糊測試:正如咱們在§VI-C中所示,MEDS明顯優於最新的內存錯誤檢測工具ASAN。認識到模糊測試的重要性,當今絕大多數供應商在其具備大量計算資源的常規軟件開發週期中採用模糊測試。例如,谷歌報告說,他們將專用的數百個虛擬機羣集用於模糊測試,該虛擬機同時運行約6,000個Chrome實例。因爲MEDS在相同的計算時間下比ASAN可以發現更多的內存錯誤,所以咱們認爲MEDS不只能夠節省計算資源,並且能夠在開發週期的早期通知內存錯誤。

內核級別的性能改進支持:本文着重於保持MEDS的兼容性,尤爲是在不引入基礎操做系統Linux的新功能的狀況下。如前所述,若是未來能夠利用一些內核更改,則能夠進一步提升MEDS的性能。例如,當實現用戶級的寫時複製(COW)(§IV-E)時,能夠修改內核以維護頁面別名的特殊標誌。這將須要在mremap()系統調用中添加一些其餘標誌。經過這樣作,MEDS不須要實現相對昂貴的用戶級COW機制,從而減小了MEDS的運行時開銷。做爲另外一個示例,MEDS必須在加載時間§IV-D時從新分配全部全局對象列表。這是由於內核始終在用於全局對象的內存頁面中分配MAP_PRIVATE,從而禁止將其用於頁面別名機制。若是咱們能夠在Linux內核中提供另外一個ELF加載程序,它爲那些內存頁面指定MAP_SHARED,則能夠避免此冗餘分配階段。

8、其餘相關工做

基於指針的內存錯誤檢測:基於指針的檢測技術會跟蹤指針功能,並根據這些功能檢查內存訪問的有效性。根據指針功能的存儲位置,基於指針的檢測器能夠進一步分類爲基於胖指針和基於不相交元數據。 CCured是基於胖指針的方法的表明做品,其中不安全(WILD)指針經過與指針自己一塊兒存儲的功能進行了擴展。基於軟件胖指針的方法的一個缺點是它們破壞了內存佈局與不受保護的代碼的兼容性,這須要消除特殊的硬件支持(例如CHERI )。 SoftBound是基於不相交元數據的方法的表明做品,其中功能存儲在專用表中。儘管這種方法不會破壞對象的內存佈局,可是訪問元數據一般會更昂貴。英特爾MPX 是最新的英特爾處理器中引入的一項新的基於硬件的安全功能,它實質上是SoftBound的硬件實現。有幾項工做和運用了基於指針的方法,開銷較低。 SGXBound 使用32位指針值存儲對象的上限,而地址上限則存儲對象的下限。可是,因爲它們假定應用程序在內存有限的Intel SGX上運行,所以它僅使用32位虛擬地址。

基於指針的方法的一個關鍵限制是它與C / C ++語言功能的有限兼容性。爲了正確運行,這些方法必須在指針之間正確傳播功能,這對於某些語言功能而言並不容易。結果,它們都遭受了向後兼容性問題,尤爲是對於C ++程序。例如,CCured僅支持C的有限功能,而SoftBound的原型沒法編譯SPEC CPU基準測試套件中的全部C基準測試,所以要支持Chrome和Firefox等大型複雜軟件,還有很長的路要走。甚至像英特爾MPX之類的商品功能,在運行Chrome瀏覽器時也會產生誤報。因爲此問題與覆蓋全部不一樣的C / C ++語法用例以及對Intel MPX應用優化的困難緊密相關,所以尚不清楚是否能夠在不久的未來解決。

內存錯誤利用和緩解:內存錯誤(最終)將使攻擊者可以執行任意內存讀寫。而後,攻擊者能夠利用這些功能來發起不一樣的攻擊。例如,一個簡單的堆棧緩衝區溢出錯誤可能使攻擊者能夠覆蓋(1)使用惡意shellcode的堆棧內容和(2)返回地址,從而在函數返回時致使任意代碼執行。基於這些功能的濫用方式,Szekeres等。文獻將現有攻擊分爲四類:代碼破壞攻擊,控制流劫持攻擊,僅數據攻擊和信息泄漏。而後針對每種特定的利用策略,開發一套相應的緩解機制。例如,開發了代碼完整性測量(例如,代碼簽名)和數據執行保護(DEP)以克服代碼損壞攻擊。提出了許多防止控制流劫持攻擊的技術,包括堆棧cookie ,影子堆棧,控制流完整性(CFI),vtable指針完整性和代碼。指針完整性。緩解技術的問題在於,任意讀寫功能過於強大,一般使攻擊者可以找到一種發起攻擊的新方法。

9、結論

本文提出了MEDS,以加強內存錯誤的可檢測性。 MEDS經過利用64位虛擬地址空間來近似無限間隙和無限堆來實現此目的。一種新穎的分配器MEDSALLOC使用頁面別名方案來近似上述屬性,同時將物理內存開銷降至最低。咱們對使用大型現實程序的MEDS的評估代表,MEDS具備良好的兼容性和可檢測性,而且運行時開銷適中。

10、參考資料

相關文章
相關標籤/搜索