LWN: 改進GCC的 -fanalyzer 選項!
關注了就能看到更多這么棒的文章哦~
Improvements to GCC's -fanalyzer option
By Jonathan Corbet
September 23, 2021
LPC
DeepL assisted translation
https://lwn.net/Articles/869880/
GNU Tools Cauldron(GNU 工具鏈開發(fā)者的年度聚會)已經(jīng)是連續(xù)第二年在 Linux Plumbers 在線大會上作為一個專門 track 來進行了。在 2021 年的會議上,David Malcolm 首先介紹了 GCC -fanalyzer 選項相關的工作,該選項提供了一些靜態(tài)分析功能。在 -fanalyzer 這一個領域已經(jīng)有了很多進展,而且在即將發(fā)布的 GCC 12 版本中還會有更多的功能,包括增加的一系列檢查可能已經(jīng)發(fā)現(xiàn)內核中的若干漏洞了。

GCC 在被調用時如果帶有 -fanalyzer 選項的話,它會運行一個 module 來創(chuàng)建一個 "爆炸圖,exploded graph",可以將程序的控制流和數(shù)據(jù)流的狀態(tài)信息結合在一起顯示出來。其包括對內存內容的抽象描述、對變量值的已知限制、以及代碼是不是可能正在 signal handler 中運行等等信息。然后,analyzer 就可以使用這個圖來嘗試探索代碼中所有感興趣的路徑,看看可能會發(fā)生什么。
GCC 10 版本中,針對潛在錯誤方面已經(jīng)新增了 15 個 warning 信息,如兩次釋放內存、釋放內存后繼續(xù)使用、signal handler 程序中不安全的函數(shù)調用、可能在將敏感數(shù)據(jù)寫入日志文件等等。兩次釋放的檢測正是讓人們有動力開發(fā)這些檢查功能的主要因素。GCC 11 中又增加了五個 warning,包括可以檢查出從一個 allocator 中獲得的內存是否被意外地釋放到另一個 allocator 中了,以及對位移運算中一些未定義行為的檢查。GCC 11 中還支持使用插件來擴展 analyzer。在 test suite 里面就可以找到一個插件樣例,用于檢查出 Python 全局解釋器鎖(GIL)的錯誤使用。
最近,Malcolm 在思考針對 GCC 12 應該做些什么。他原本想增加對 C++ 的支持,但后來發(fā)現(xiàn)他自己更愿意改進 C 語言相關的功能。在得出這個結論之前,他已經(jīng)在 GCC 11 中實現(xiàn)了 new 和 delete 的支持,并把異常處理(exception handling)作為了他自己的下一個目標,但結果發(fā)現(xiàn)這個領域 "相當復雜"。同時,谷歌夏季代碼挑戰(zhàn)項目的學生 Ankur Saini 也增加了對虛擬函數(shù)的支持。因此,C++ 這方面已經(jīng)取得了一些進展,但 Malcolm 的工作目前將繼續(xù)集中在 C 語言上。
有幾個 C 語言的問題引起了他的注意,其中之一是緩沖區(qū)溢出的檢測。他實現(xiàn)了一個原型方案,可以根據(jù) symbol 捕捉記錄動態(tài)分配的 size,并在后續(xù)讀寫時可能超出這個 size 的時候給出 diagnostics 診斷信息。但是很難確定應該在什么時候來報出 warning,現(xiàn)有代碼中產(chǎn)生了太多的誤報(他的說法是 "a wall of noise"),因此無法真正實用起來。
不過,他想到有一種方法可能可以找到一類特定的問題:就是針對那些可能被攻擊者影響某次 access 是否有效的位置。這就引出了 taint detection(污染檢測)以及如何確定程序中的信任邊界(trust boundaries)在哪里的問題。要想對隨便一段 C 代碼來確認這兩點,這會是一個非常困難的問題。不過,針對特定程序的話,是可以進行標注(annotate)并獲得有用的診斷信息。他的注意力轉向了內核,因為內核具有一個定義非常明確的信任邊界,以及一個用來跨越該邊界進行數(shù)據(jù)搬移的 API。通過對 copy_from_user() 以及 system-call handler 函數(shù)的標注,就可能可以發(fā)現(xiàn)那些沒有對用戶所提供的數(shù)據(jù)進行檢查(sanitize)的代碼。
GCC 有一個屬性(access),用來描述數(shù)據(jù)是如何在某個的變量或函數(shù)中移動的。Malcolm 為該屬性增加了兩個新的可用值(untrusted_read 和 untrusted_write),用來標記讀取來自于(或寫入目標是)一個不受信任的位置的數(shù)據(jù)。因此,比如通過 copy_from_user() 讀入內核的數(shù)據(jù)就會被標記為 untrusted_read。他還為這些函數(shù)添加了一個新的 tainted 屬性,用來表示該函數(shù)的所有參數(shù)都應被視為不可信任的。通過修改內核頭文件中的一個宏,他就能夠將內核中所有的 system-call handler 函數(shù)都打上這個 tainted 屬性。也可以對其他一些函數(shù)進行類似標記,比如說內核生成的文件系統(tǒng)的 callback 函數(shù)。
有了這些標注之后,analyzer 就可以檢測到兩類問題:信息泄露(information leaks)和使用了污染數(shù)據(jù)(tainted data)。信息泄露都是發(fā)生在將未進行初始化的數(shù)據(jù)直接寫回到用戶空間的時候。這種情況相對比較容易檢測,或者至少他是這樣認為的。作為這類問題的一個例子,Malcolm 提出了 CVE-2017-18549,這是一個將 stack 中的隨機數(shù)據(jù)寫回用戶空間的驅動程序 bug。在這種情況下,被寫入的這個未進行初始化的數(shù)據(jù)是在一個原本被正常進行了初始化的結構中填入的 padding 數(shù)據(jù),analyzer 就可以發(fā)現(xiàn)這個問題。要讓這個問題得到 fix,需要對未初始化數(shù)據(jù)的跟蹤記錄代碼進行重構,這不是一個容易的任務。
還有一個類似的問題,是從用戶空間讀取數(shù)據(jù)進行修改,然后把結果 copy 回用戶空間,也許是 copy 到另一個位置。如果 read 操作失敗了的話,內核可能會對未初始化的數(shù)據(jù)進行一些處理然后直接寫入用戶空間。要 fix 這個問題,需要對處理 copy_from_user()失敗的情況。一旦完成了這個工作之后,analyzer 也就可以處理 realloc() 了,盡管它有三種可能結果。
當用戶提供的數(shù)據(jù)被拿來當作數(shù)組的索引時,就會出現(xiàn)數(shù)據(jù)被污損(tainted)的情況。這種情況更難檢測,但似乎更重要一些,因為這種類型的漏洞往往可以被利用來攻破內核。例如 CVE-2011-0521 這種情況下,內核代碼會讀取一個有符號的 "size" 值,根據(jù)所允許的最大值來進行檢查,但是沒有檢查是不是負數(shù)就直接使用它了。改進后的 analyzer 就能夠捕捉到這種情況。
他仍在研究如何實現(xiàn)這個功能的原型,期待可以展示給大家看。作為這個工作的一部分,他開發(fā)了一個世界上的最糟糕的內核 module,其中包含了他能想到的所有問題。不過,由于內核使用了大量的 inline 匯編代碼,這使得 analyzer 要做的事情在分析完整內核的情況下就變得更加復雜。他已經(jīng)為這個功能添加了一些基本的處理代碼,但是目前并沒有去檢查實際的匯編操作碼(opcodes)。
他一直在針對 upstream 的內核來不斷運行自己的檢查功能,并且已經(jīng)發(fā)現(xiàn)了一個真正的漏洞,這個漏洞已經(jīng)被報告出來但是尚未被修復或披露出來。因此,Malcolm 的最新成果代碼仍然只能放在在公司內部的代碼倉庫中。因為它發(fā)現(xiàn)了真正的系統(tǒng)漏洞,那么在它所發(fā)現(xiàn)的問題被修復之前,他可不想把工具發(fā)布出來成為幫助攻擊者的 zero-day-finding 工具。其他大部分工作現(xiàn)在都提交到 GCC 12 的主分支中了。他希望能夠在 GCC 12 stage 1 周期結束前就完成這項工作并將其推到 upstream(https://gcc.gnu.org/develop.html?這里可以看到 GCC 開發(fā)周期的各個 stage 的含義)。
關于這個項目的更多信息可以在 GCC wiki (https://gcc.gnu.org/wiki/DavidMalcolm/StaticAnalyzer) 中找到。
全文完
LWN 文章遵循 CC BY-SA 4.0 許可協(xié)議。
長按下面二維碼關注,關注 LWN 深度文章以及開源社區(qū)的各種新近言論~
