空指標解引用
程式碼審計是使用靜態分析發現原始碼中安全缺陷的方法,能夠輔助開發或測試人員在軟體上線前較為全面地瞭解其安全問題,防患於未然,因此一直以來都是學術界和產業界研究的熱點,並且已經成為安全開發生命週期 SDL 和 DevSecOps 等保障體系的重要技術手段。360程式碼衛士團隊基於自主研發的國內首款原始碼安全檢測商用工具,以及十餘年漏洞技術研究的積累,推出“缺陷周話”系列欄目。每週針對 CWE、OWASP 等標準中的一類缺陷,結合例項和工具使用進行詳細介紹,旨在為廣大開發和安全人員提供程式碼審計的基礎性標準化教程。
1、空指標解引用
C語言空指標的值為NULL。一般NULL指標指向程序的最小地址,通常這個值為0。試圖通過空指標對資料進行訪問,會導致執行時錯誤。當程式試圖解引用一個期望非空但是實際為空的指標時,會發生空指標解引用錯誤。對空指標的解引用會導致未定義的行為。在很多平臺上,解引用空指標可能會導致程式異常終止或拒絕服務。如:在 Linux 系統中訪問空指標會產生 Segmentation fault 的錯誤。詳細請參見CWEID 476: NULL Pointer Dereference ( ofollow,noindex" target="_blank">http://cwe.mitre.org/data/definitions/476.html) 。
2、空指標解引用的危害
空指標解引用是 C/C++ 程式中較為普遍的記憶體缺陷型別,當指標指向無效的記憶體地址並且對其引用時,有可能產生不可預見的錯誤,導致軟體系統崩潰。空指標引用缺陷可能導致系統崩潰、拒絕服務等諸多嚴重後果。自2018年1月至9月,CVE 中共有100多條漏洞資訊與其相關。其中包括18個Linux kernel 漏洞,部分漏洞如下:
CVE-2018-16517 | Netwide Assembler 的 asm/labels.c 檔案中存在空指標解引用,導致允許攻擊者進行拒絕服務攻擊。 |
CVE-2018-16428 | GNOME Glib 2.56.1,gmarkup. 中的 g_markup_parse_context_end_parse() 函式存在一個空指標解引用。 |
CVE-2018-16329 | ImageMagick 7.0.8-8 之前版本,MagickCore/property.c 檔案中的 GetMagickProperty() 函式存在空指標解引用。 |
CVE-2018-16328 | ImageMagick 7.0.8-8 之前版本,MagickCore/log.c 檔案中的 CheckEventLogging () 函式存在空指標解引用。 |
3、示例程式碼
3.1 缺陷程式碼
上述示例程式碼雖然在第16行對指標 p 進行了非空驗證,但由於驗證邏輯不完整,在第20行傳入函式 foo 中的指標 p 仍可能為空指標。從而導致空指標解引用的發生。
使用360程式碼衛士對上述示例程式碼進行檢測,可以檢出“空指標解引用”缺陷,顯示等級為高。如圖1所示:
圖1 空指標解引用檢測示例
3.2 修復程式碼
在上述修復程式碼中,在第19行對指標 p 為空的情況進行了處理,當指標 p 為空時,函式 npd_check_call_might() 返回,避免了第23行傳入函式 foo 中的指標 p 為空指標。
使用 360 程式碼衛士對修復後的程式碼進行檢測,可以看到已不存在“空指標解引用”缺陷。如圖2:
圖2:修復後檢測結果
4、開原始碼檢測計劃某專案空指標解引用示例
開原始碼檢測計劃是一項免費的公益計劃。通過使用360程式碼衛士對開源專案進行原始碼檢測和審計,找到原始碼中存在的安全缺陷並及時與專案開發人員進行溝通和確認,使得開源專案的安全性得到提高。
以下是開源專案檢測計劃中檢測出的一個“空指標解引用”示例,如圖3所示。
圖3 開源專案檢出“空指標解引用”
4.1 缺陷程式碼
在缺陷程式碼中, 指標 P 在第133行通過 ibwgetnode(iface) 函式返回值進行賦值,並在第136行 p->limit=speed; 中進行使用,通過 ibwgetnode(iface) 函式的實現分析,可以看到在162行中返回 NULL 值,因此當 p->limit=speed; 操作時,由於在之前並沒有對 P 是否為空進行判斷,導致空指標解引用。
4.2 修復程式碼
針對該空指標解引用問題的提出,開發人員在近期對相關程式碼進行了修復。在139行對 p 進行解引用前,進行了非空判斷,從而避免瞭解引用空指標。
5、如何避免空指標解引用
為了避免空指標解引用的發生,需要在程式碼編寫過程中養成良好的程式設計習慣,如:
(1)指標在使用前需要進行健壯性檢查,從而避免對空指標進行解引用操作;
(2)當呼叫函式的返回值可能為空時,需要對函式返回值進行非空驗證,從而避免空指標解引用;
(3)在釋放指標指向的空間後,需要將指標的值賦為空。
(4)確保異常被正確處理。