天天看點

2018-2019-2 20189206 《密碼與安全新技術專題》 論文總結

20189206 2018-2019-2 《密碼與安全新技術專題》 論文總結

課程:《密碼與安全新技術專題》

班級: 1892

姓名: 王子榛

學号:20189206

上課教師:王志強

論文總結

  • 論文名稱:SafeInit: Comprehensive and Practical Mitigation of Uninitialized Read Vulnerabilities
  • 會議名稱:ndss2017
  • 作者:Alyssa Milburn、Herbert Bos、Cristiano Giffrida

SafeInit: 全面而實用的未初始化讀取漏洞緩解

背景知識

未初始化漏洞

未初始化值的使用仍然是C / C ++代碼中的常見錯誤。這不僅導緻未定義的和通常不期望的行為,而且還導緻資訊洩露和其他安全漏洞。

我們都知道C/C++中的局部變量,在未初始化的情況下,初值為随機值。

以C++中局部變量的初始化和未初始化為例:(int x;和int x = 0;)
編譯器在編譯的時候針對這兩種情況會産生兩種符号放在目标檔案的符号表中,對于初始化的,叫強符号,未初始化的,叫弱符号。連接配接器在連接配接目标檔案的時候,如果遇到兩個重名符号,會有以下處理規 則:
1、如果有多個重名的強符号,則報錯。
2、如果有一個強符号,多個弱符号,則以強符号為準。
3、如果沒有強符号,但有多個重名的弱符号,則任選一個弱符号。

未定義行為

簡單地說,未定義行為是指C語言标準未做規定的行為。編譯器可能不會報錯,但是這些行為編譯器會自行處理,是以不同的編譯器會出現不同的結果,什麼都有可能發生,這是一個極大的隐患,是以我們應該盡量避免這種情況的發生。

介紹

由于C/C++不會像C#或JAVA語言,確定變量的有限配置設定,要求在所有可能執行的路徑上對它們進行初始化。是以,C/C++代碼可能容易受到未初始化的攻擊讀取。同時C/C++編譯器可以在利用讀取未初始化的記憶體是“未定義行為”時引入新的漏洞。

在本文中,提出了一種全面而實用的解決方案,通過調整工具鍊(什麼是工具鍊)來確定所有棧和堆配置設定始終初始化,進而減輕通用程式中的這些錯誤。 SafeInit在編譯器級别實作。

本文實作了:

  • 提出了safeinit,一種基于編譯器的解決方案,結合強化配置設定器,確定堆和棧上的初始化來自動減輕未初始化值讀取。
  • 提出的優化可以将解決方案的開銷降到最低水準(< 5%)并可以在現代編譯器中實作
  • 基于clang和LLVM的SafeInit原型實作,并表明它可以應用于大多數真實的C / C ++應用程式而無需任何額外的手動工作。
  • CPU-intensive 、 I/O intensive (server) applications 和 Linux kernel測試驗證了現實世界的漏洞确實被緩解

背景

在幾乎所有應用程式中,記憶體不斷被重新配置設定,是以被重用。

  • 在棧中,函數激活幀包含來自先前函數調用的資料
  • 在堆上,配置設定包含來自先前釋放的配置設定資料

如果在使用之前不覆寫這些資料,就會出現未初始化資料的問題,進而将舊資料的生命周期延長到新配置設定點之外。

記憶體也可能隻是部分初始化; C中的結構和聯合類型通常是故意不完全初始化的,并且出于簡單性或性能原因,通常為數組配置設定比存儲其内容所需的(最初)更大的大小。實際上,重用記憶體不僅常見,而且出于性能原因也是可取的。 當不清楚變量是否在使用之前被初始化時,唯一實用且安全的方法是在所有情況下初始化它。

存在的四點威脅

敏感資料洩露

由于未初始化資料而導緻資訊洩露的最明顯危險是直接敏感資料的洩露,例如加密密鑰,密碼,配置資訊和保密檔案的内容。

  • 資料生命周期持續時間長于預期,可能會産生許多無意的資料副本。
  • 不是所有情況下編譯器可以提供memset優化調用,如果資料不再有效并且是以在該點之後不再使用,編譯器可以通過調用memset來優化這些調用。但是如果之後的資料還有效,禁止編譯器優化的替代函數(例如memset_s和explicit_bzero)并不是普遍可用的。
  • 未初始化資料的使用受到不可信輸入的影響,必須考慮各種潛在的攻擊媒介,這種不同的攻擊面意味着應該認真對待所有未初始化的資料漏洞。
繞過安全防禦

現代軟體防禦依賴于敏感中繼資料的保密性,同時,未初始化的值提供了指針公開的豐富資源。

例如:位址空間布局随機化(ASLR)之類的防禦一般取決于指針的保密性,并且由于這通常僅通過随機化一個基位址來完成,是以攻擊者僅需要擷取單個指針以完全抵消保護。 這樣的指針可以是代碼,堆棧或堆指針,并且這些指針通常存儲在棧和堆上,是以未初始化的值錯誤提供了阻止這種資訊隐藏所需的指針公開的豐富源。

軟體開發

未初始化資料導緻的其他漏洞允許攻擊者直接劫持控制流。常見的軟體開發的錯誤是:無法在遇到錯誤時在執行路徑上初始化變量或緩沖區。

兩個例子分别是:

  • Microsoft描述了由于2008年Microsoft Excel中未初始化的堆棧變量導緻的任意寫入漏洞
  • Microsoft的XML解析器中的一個錯誤使用存儲在局部變量中的指針進行虛函數調用,該局部變量未在所有執行路徑上初始化。
檢測工具

有些工具試圖在開發過程中檢測未初始化變量,而不是試圖減輕未初始化的值錯誤,允許它們由程式員手動校正。有些工具試圖在開發過程中檢測它們,而不是試圖減輕未初始化的值錯誤,允許它們由程式員手動校正。更重要的是,編譯器警告和檢測工具隻報告問題,而不是解決問題。 這可能會導緻錯誤和危險的錯誤。

堆棧變量

函數堆棧幀:在堆棧中為目前正在運作的函數配置設定的區域、傳入的參數。傳回位址以及函數所用的内部存儲單元都存儲在堆棧幀中。

函數堆棧幀包含局部變量的副本,或具有被忽略的局部變量,同時還包含其他局部變量和編譯器生成的臨時變量的溢出副本,以及函數參數,幀指針和傳回位址。 鑒于堆棧記憶體的不斷重用,這些幀提供了豐富的敏感資料源。

現代編譯器使用複雜的算法進行寄存器和堆棧幀配置設定,這種方式減少了記憶體使用并改善了緩存局部性,但意味着即使在函數調用之前/之後清除寄存器和堆棧幀也不足以避免所有潛在的未初始化變量。

下面的例子說明:記憶體重用了循環中的局部變量,doSomthing将在所有循環疊代中傳遞秘密值,由于未初始化的局部變量,但是如果初始化了局部變量,隻會在第一次doSomthing傳遞秘密值。

2018-2019-2 20189206 《密碼與安全新技術專題》 論文總結
2018-2019-2 20189206 《密碼與安全新技術專題》 論文總結
2018-2019-2 20189206 《密碼與安全新技術專題》 論文總結

當C / C ++程式無法遵循該語言強加的規則時,會發生未定義的行為。在我們讨論的環境中,未定義行為是指在代碼讀取未初始化的堆棧變量或者是未初始化的堆配置設定。

為了實作最大數量的優化,特别是在可能從模闆和宏擴充的代碼中,并最終被大部分丢棄為無法通路,現代編譯器轉換利用了大規模的未定義的行為。這樣的轉換可以将未定義的值(以及是以也未初始化的值)解釋為使得優化更友善的任何值,即使這使得程式邏輯不一緻。

概述

2018-2019-2 20189206 《密碼與安全新技術專題》 論文總結

SafeInit通過強制初始化堆配置設定(在配置設定之後)和所有棧變量(無論何時進入範圍)來減輕未初始化的值問題。這是通過修改編譯器直接在所有點插入初始化調用來完成的。為了提供實用和全面的安全性,此工具必須在編譯器本身内完成。 隻需在編譯過程中傳遞額外的加強标記即可啟用SafeInit。

上圖是利用額外的編譯器傳遞,進而增加了必要的初始化

種簡單的初始化方法會導緻過多的運作開銷,而我們系統的一個重要元素是專門的強化配置設定器。 在許多情況下,通過利用額外的資訊并結合我們的編譯器工具,可以避免初始化問題。

可以看到編譯器在獲得C/C++檔案後,編譯器前端将源檔案轉換為中間語言(IR),通過初始化、代碼優化結合現存編譯器的優化器,之後通過無效資料消除、強化配置設定器最後獲得二進制檔案。Safeinit在整個過程中所添加的就是 初始化全部變量、優化以及強化配置設定器,來避免或緩解未初始化值。

緩解未定義變量

初始化

SafeInit在首次使用之前初始化所有局部變量,作為新配置設定變量的作用域處理。SafeInit通過修改編譯器編譯代碼的中間表示(IR),在每個變量進入作用域後進行初始化(例如内置memset)。

強化配置設定器

SafeInit的強化配置設定器可確定在傳回應用程式之前将所有新配置設定的記憶體清零。我們通過修改現代高性能堆配置設定器tcmalloc來實作我們的強化配置設定器。同時還修改了LLVM,以便在啟用SafeInit時将來自新配置設定的記憶體的讀取視為傳回零而不是undef。 同時,我們在安全配置設定器中執行覆寫所有堆配置設定函數以確定始終使用強化的配置設定器函數(對初始化堆配置設定是在配置設定之後進行強制初始化)。編譯器知道我們的強化配置設定器正在使用中; 任何已配置設定記憶體的代碼都不再使用未定義行為,并且編譯器無法修改或删除

優化器

目的:可在提高效率和非侵入性的同時提高SafeInit的性能。優化器的主要目标是更改現有編譯器中可用的其他标準優化,以消除任何不必要的初始化。

  • 存儲下沉:存儲到本地的變量應盡可能接近它的用途。
2018-2019-2 20189206 《密碼與安全新技術專題》 論文總結

在正常執行期間不執行此代碼路徑,并且我們不需要初始化緩沖區,直到我們到達它将到達的路徑使用。

  • 檢測初始化:檢測初始化數組(或部分數組)的典型代碼
  • 字元串緩沖區:用于存儲C風格的以空字元結尾的字元串的緩沖區通常僅以“安全”方式使用,其中永遠不會使用超出空終止符的記憶體中的資料。傳遞給已知C庫字元串函數(例如strcpy和strlen)的緩沖區是“安全的”,優化器檢測到該緩沖區始終被初始化,可以删除掉該緩沖區的初始化代碼。

無效存儲消除

“無效存儲消除”(DSE)優化,它可以删除總是被另一個存儲覆寫而不被讀取的存儲。

  • 堆清除:所有堆配置設定都保證初始化為零,如果有存儲到新配置設定堆記憶體中的零值都會被删除
  • 非恒定長度存儲清除:為了删除動态堆棧配置設定和堆配置設定的不必要初始化
  • 交叉塊DSE:可以跨多個基本塊執行無效存儲消除
  • 隻寫緩沖區:通過指定該緩沖區隻用來存儲而不是删除,就可以将該緩沖區删除。

實施

LLVM中的局部變量是使用alloca指令定義的; 我們的pass通過在每條指令之後添加對LLVM memset内部的調用來執行初始化。可以保證清除整個配置設定,并在适當的時候轉換為存儲指令。

通過修改現代高性能堆配置設定器tcmalloc來實作我們的強化配置設定器。 隻需清除在配置設定器傳回指針之前,所有其他堆配置設定為零。還修改了LLVM,以便在啟用SafeInit時将來自新配置設定的記憶體的讀取視為傳回零而不是undef。 如上所述,這對于避免未定義值的不可預測後果至關重要。

優化

通過将插入的memset調用移動到alloca的所有使用的主導點來實作我們提出的用于堆棧初始化的下沉存儲優化。 在啟用優化的情況下進行編譯時,clang将發出'lifetime'标記,訓示局部變量進入範圍的點; 我們修改了clang以在所有情況下發出适當的生命周期标記,并在這些點之後插入初始化。

通過添加一個新的内部函數“initialized”來實作初始化檢測優化,該函數具有與memset相同的存儲殺死副作用,但是被代碼生成忽略。 通過擴充諸如LLVM的循環習語檢測之類的元件來生成這種新的内在函數,其中無法用memset替換代碼,我們允許其他現有的優化傳遞利用這些資訊而無需單獨修改它們。

我們通過擴充現有的LLVM代碼實作了上述其他優化,盡可能減少我們的更改。 我們對隻寫緩沖區的實作使用了D18714中的更新檔(自合并以來),它為writeonly屬性添加了基本架構。

我們還基于D13363中的(拒絕)更新檔實作了跨塊死區消除。 由于性能回歸,我們為小型商店(≤8位元組)禁用此交叉塊DSE; 我們還擴充了此代碼以支援删除memset,并縮短此類存儲。

評估

我們的基準測試運作在(4核)Intel i7-3770上,記憶體為8GB,運作(64位)Ubuntu 14.04.1。 禁用CPU頻率縮放,并啟用超線程。

基線配置:clang / LLVM的未修改版本,使用未修改的tcmalloc版本。

除了将它與SafeInit進行比較之外,我們還提供了簡單方法的結果,它簡單地應用了我們的初始化過程而沒有包含任何我們提出的優化,使用一個簡單地将所有配置設定歸零的強化配置設定器。

SPEC CPU2006

我們使用LTO和-O3在SPEC CPU2006中建構了所有C / C ++基準測試。 我們使用參考資料集提供3次運作中值的開銷圖。

2018-2019-2 20189206 《密碼與安全新技術專題》 論文總結

在沒有我們的優化器的情況下應用時,運作時開銷的(幾何)平均值為8%。應用我們的優化器可以顯着降低剩餘基準測試的開銷,與我們的基線編譯器相比,導緻CINT2006的平均開銷為3.5%。 CFP2006的結果類似,如圖14所示,平均開銷為2.2%。

2018-2019-2 20189206 《密碼與安全新技術專題》 論文總結

表I提供了每個基準測試的allocas數量(表示局部變量的數量,偶爾的參數副本或動态配置設定)的詳細資訊。 該表還提供了(剝離的)二進制大小; 在許多情況下,初始化的影響對最終的二進制大小沒有任何影響,并且在最壞的情況下它是最小的。#INITS是現有編譯器優化之後剩餘的大量初始化數量,并且我們的優化器已經分别運作。

2018-2019-2 20189206 《密碼與安全新技術專題》 論文總結

使用我們的優化器作為基線時的平均開銷為3.8%

2018-2019-2 20189206 《密碼與安全新技術專題》 論文總結

服務

我們通過使用兩個現代高性能Web伺服器nginx(1.10.1)和lighttpd(1.4.41)來評估SafeInit的開銷,以減少計算密集度較低的任務。 我們使用LTO和-O3建構了Web伺服器。 由于它們在我們的1gbps網絡接口上使用時受到I / O限制,是以我們使用環回接口對它們進行基準測試。

我們使用apachebench重複下載下傳4Kb,64Kb和1MB檔案,持續30秒。 我們啟用了流水線操作,使用了8個并發工作程式,并使用CPU功能為apachebench保留了CPU核心。 我們測量了10次運作中每秒請求中位數的開銷; 我們沒有看到大量的差異。

Linux

使用我們的工具鍊建構了最新的LLVM Linux核心樹。 我們定制了建構系統,以允許使用LTO,重新啟用内置clang函數,并修改gold連結器以解決我們在符号排序時遇到的一些LTO代碼生成問題。

由于Linux核心執行自己的記憶體管理,是以它不會與使用者空間強化配置設定器連結; 我們的自動加強僅保護局部變量。

下表提供了使用核心微基準測試工具LMbench的典型系統調用的延遲和帶寬選擇。 我們運作了每個基準測試10次,每次運作的預熱時間很短,疊代次數很多(100次),并提供中位數結果。 TCP連接配接是localhost,其他參數是預設LMbench腳本使用的參數。

2018-2019-2 20189206 《密碼與安全新技術專題》 論文總結

對于stat和open系統調用,我們會産生大量開銷; 雖然我們的優化器提供的性能得到了很大程度的緩解,但這是值得關注的,我們打算進一步研究它,以及fstat和(信号)保護故障,這是我們所看到的開銷大于5%的唯一系統調用。

為了評估應用于核心堆棧的SafeInit的實際性能,我們使用SafeInit強化了nginx和核心,并将性能與在非強化核心下運作的非強化nginx進行了比較。 使用我們上面讨論過的發送檔案配置,再次使用環回接口提供極端情況,我們分别觀察到1M,64kB和4kB情況下的開銷分别為2.9%,3%和4.5%。

安全

為了驗證SafeInit是否按預期工作,不僅考慮了各種現實漏洞,例如下表中的漏洞,還建立了一套單獨的測試用例。 我們手動檢查了為相關代碼生成的bitcode和機器代碼,并使用我們上面描述的檢測系統運作我們的測試套件。 我們還用valgrind來驗證我們的硬化; 例如,我們确認當使用SafeInit強化OpenSSL 0.8.9a時,來自valgrind的所有未初始化的值警告都會消失。

2018-2019-2 20189206 《密碼與安全新技術專題》 論文總結

總的來說,我們的SafeInit原型在LLVM中添加或修改的代碼少于2000行,包括一些調試代碼和基于第三方更新檔的大約400行代碼。 雖然我們的修改很複雜,但這是一個相對較少的代碼,每個元件都應該是可單獨審查的; 為了比較,我們(單獨的)幀清除通道單獨超過350行代碼。

我們的強化不會阻止程式在内部重用記憶體。例如,堆棧緩沖區可以在同一個函數中重用于不同的目的,或者自定義内部堆配置設定器可以重用記憶體而不清除它,例如我們在PHP中看到的。 盡管可能使用啟發式方法或通過附加某種注釋來捕獲其中一些案例,但我們認為編譯器支援這種情況并不現實也不合理。将變量清零可確定任何未初始化的指針為空。 嘗試取消引用這樣的指針将導緻錯誤; 在這種情況下,我們的緩解措施已将更嚴重的問題減少為拒絕服務漏洞。

結論

未初始化的資料漏洞繼續在現代C / C ++軟體中造成安全問題,并且確定不使用未初始化值的安全性并不像看起來那麼容易。 從簡單的資訊披露到嚴重問題(如任意記憶體寫入,靜态分析限制以及利用未定義行為的編譯器優化)等威脅相結合,使這成為一個難題。

本文提出了一種基于工具鍊的強化技術SafeInit,它通過確定在使用前初始化所有局部變量和堆棧配置設定來減輕C / C ++程式中未初始化值的使用。通過使用适當的優化,我們發現許多應用程式的運作時開銷可以降低到可以作為标準強化保護應用的水準,并且這可以在現代編譯器中實際完成。

總結

本文通過在clang/LLVM編譯器架構上,通過修改代碼,實作了safeinit原型,在編譯C/C++源代碼時,傳遞一個标記即可使用safeinit實作優化編譯,緩解未定義變量。使用了強化配置設定器的safeinit可以進一步優化代碼的同時,保證所有需要初始化的變量進行初始化,删除多餘初始化代碼進行優化,這樣既保證緩解了未定義變量漏洞的威脅,同時與其他現有方法相比,提升了性能。

選到這篇文章一開始沒有具體了解是涉及較為底層的編譯器的内容,但是看完後,覺得其實有時候越是較為底層的東西學習可以幫助我們更好地了解我們利用程式設計語言實作的一些應用,可以在以後編寫代碼時注意到一些以往可能注意不到的點,了解程式運作邏輯,是以選擇這篇文章也促使我了解了一些計算機系統和編譯原理中的内容,獲益匪淺。但是遺憾的是,文章中的編譯器并沒能複現出來,來對一些測試代碼進行編譯以更好了解其運作機制,我也會在以後繼續學習,争取讀懂了解這篇文章的内容。