王國峰 唐云善 徐立飛
(1.南瑞集團(tuán)有限公司(國網(wǎng)電力科學(xué)研究院有限公司) 南京 210003)(2.南京南瑞信息通信科技有限公司 南京 210003)
源代碼的安全是計算軟件安全的基石,而源代碼的缺陷檢測正是穩(wěn)固此塊基石的重要方式[1]。據(jù)中國國家信息安全漏洞庫統(tǒng)計,由空指針引用異常(Null Pointer Dereference,NPD)造成的安全漏洞引發(fā)了許多嚴(yán)重的安全問題,諸如系統(tǒng)崩潰、越權(quán)操作、拒絕服務(wù)和執(zhí)行惡意代碼等[2~3]。目前對于NPD 漏洞的檢測主要分為動態(tài)檢測和靜態(tài)檢測兩種方式[4~5]。其中靜態(tài)檢測無需運(yùn)行程序,直接通過語法和詞法等技術(shù)手段對編譯后的文件進(jìn)一步分析,即可找出程序中可能存在的缺陷。這種檢測方式速度快、效率高,廣受業(yè)界青睞[6~7]。
在現(xiàn)有的關(guān)于NPD 缺陷靜態(tài)檢測方法中,Zhang 等[8]通過對缺陷報告的分析,排除一些明顯的誤報,來提升對NPD 缺陷的檢測準(zhǔn)確度,但噪聲報告同時也會對優(yōu)化結(jié)果產(chǎn)生二次影響。畢學(xué)軍等[9]通過在控制流的基礎(chǔ)上利用變量區(qū)間來表示狀態(tài)的前提條件,對程序中不可達(dá)路徑進(jìn)行處理,從而達(dá)到減少誤報的目的。但該方法未能進(jìn)一步解決狀態(tài)出現(xiàn)ERROR 情況下,導(dǎo)致分支不可達(dá)下的缺陷漏報問題。楊睿等[10]基于NPD 缺陷狀態(tài)機(jī)模型,通過函數(shù)摘要的生成傳遞與使用支撐數(shù)組空指針故障的全局分析與檢測,從而減少漏報。但其考慮的函數(shù)摘要并不完善,對Java代碼中的諸多函數(shù)類型如返回值、I/O 操作類型等均未涉及,因此該方法僅適用于數(shù)組的NPD檢測上。
針對上述問題,本文提出了一種基于數(shù)據(jù)流分析的Java空指針引用異常缺陷檢測方法,該方法根據(jù)NPD 缺陷模式的缺陷特征,設(shè)計了對應(yīng)的數(shù)據(jù)流值、格值計算規(guī)則和傳遞函數(shù),以此來確保數(shù)據(jù)流值在程序控制流圖(Control Flow Graph,CFG)上按照設(shè)定的傳遞函數(shù)進(jìn)行前向分析,然后遍歷CFG上每個節(jié)點(diǎn)的數(shù)據(jù)流值,找出格值為ERROR 的變量,報出NPD 缺陷故障點(diǎn)。最后,文章通過對比試驗驗證了該方法的有效性及性能效果。
缺陷模式指一類缺陷產(chǎn)生時程序所呈現(xiàn)的共同的語法或語義特征,描述了程序的一種屬性,滿足該屬性則產(chǎn)生缺陷[11]。NPD 缺陷模式則是描述了被引用的指針指向了無效的內(nèi)存區(qū)域的屬性。當(dāng)缺陷模式確定后,被用來檢測某一程序是否違反了程序語法或語義規(guī)則的屬性,即被稱為該缺陷模式的缺陷特征[12]。NPD 缺陷模式的缺陷特征就是被引用的指針,因此,通過檢測該指針變量是否為空指針,即可判定是否產(chǎn)生NPD缺陷。
Java 語言中沒有指針這個概念,因而Java 中的空指針用空值句柄(Handle)來表征[13]??罩羔樢脛t表明對一個空值句柄的方法、字段或者域的調(diào)用,在此調(diào)用過程中,編譯器則會拋出Null Pointer Exception錯誤,即空指針引用異常。
結(jié)合圖1 的NPD 代碼實例,變量a 和變量c 的初始化均為空值null。若第8 行變量num1 的值大于b.hashCode(),則變量b 加上字符串“world”的值將賦給變量a,此時a 不再是空值null,從而第15 行系統(tǒng)打印函數(shù)對a 的調(diào)用不會報錯,但若第8 行的if 條件語句的判斷為false,則a 的值依然為null,從而導(dǎo)致第15行出現(xiàn)NPD錯誤。
圖1 NPD缺陷代碼實例
程序第11 行,a 加c 之后的值賦給變量d,由于a和c均為空值null,從而變量d的值也為空值null,這就導(dǎo)致第12行的d.length()調(diào)用語句引發(fā)對變量d的NPD,以及第14行也同時出現(xiàn)NPD錯誤。
NPD 缺陷檢測流程如下所示,共分為五個步驟。其中步驟3至步驟5為本文的主要工作。
步驟1:對待分析源碼進(jìn)行掃描,通過詞法、語法分析構(gòu)建源碼的抽象語法樹模型;
步驟2:通過抽象語法樹模型將源碼轉(zhuǎn)化成三地址碼這種代碼的中間表示;
步驟3:通過多次遍歷三地址碼,構(gòu)建程序CFG,并設(shè)計NPD 缺陷模式的數(shù)據(jù)流值、數(shù)據(jù)流值的交匯計算規(guī)則和數(shù)據(jù)流的傳遞函數(shù);
步驟4:在CFG 上,根據(jù)傳遞函數(shù)計算CFG 中每個節(jié)點(diǎn)的數(shù)據(jù)流值,完成數(shù)據(jù)流前向分析;
步驟5:遍歷CFG 上各節(jié)點(diǎn)分析后的數(shù)據(jù)流值,根據(jù)NPD 缺陷模式狀態(tài)劃分,找到格值為ERROR的變量,報出NPD缺陷代碼片段。
NPD缺陷模式數(shù)據(jù)流值是一個對,由被分析變量及其格值組成的pair<變量名,格值>。根據(jù)NPD的缺陷模式,本文設(shè)計的格值集合一共有三個定值,即S={NULL,NOTNULL,ERROR}。其中定值NULL表示Java句柄中被分析變量在前趨數(shù)據(jù)流節(jié)點(diǎn)的格值為NULL;定值NOTNULL 表示Java 句柄中被分析變量在前趨數(shù)據(jù)流節(jié)點(diǎn)的格值為NOTNULL;定值ERROR 表示當(dāng)前Java句柄中被分析變量產(chǎn)生了空指針引用異常缺陷。
圖2 所示格圖的頂元素為格值ERROR,表明所有檢測變量均出現(xiàn)了NPD 缺陷,這個分析結(jié)果是安全的但會出現(xiàn)大量誤報;格圖的底元素為格值NOTNULL,表明所有檢測變量均未出現(xiàn)NPD缺陷,這個分析結(jié)果是不安全的且會出現(xiàn)大量漏報;其他數(shù)據(jù)流值則是無序的空值NULL。NPD缺陷檢測的數(shù)據(jù)流分析屬于may分析,所以只需在格圖上找到最小不動點(diǎn),即可得到分析的最優(yōu)結(jié)果。
圖2 數(shù)據(jù)流值格圖
當(dāng)程序經(jīng)過諸如分支判斷語句時,數(shù)據(jù)流會分成多個分支,進(jìn)而需要在分支語句結(jié)束后對同一變量的格值完成交匯操作,記作Fmeet。數(shù)據(jù)流值交匯操作時的計算規(guī)則如下:
Fmeet(NULL,NULL)=NULL,表示空值與空值交匯后是空值;
Fmeet(NULL,NOTNULL)=NULL,表示空值與非空值交匯的最小上界是空值;
Fmeet(NOTNULL,NOTNULL)=NOTNULL,表示非空值與非空值交匯后是非空值;
Fmeet(ERROR,NULL)=ERROR,表示ERROR與空值交匯的最小上界是ERROR;
Fmeet(ERROR,NOTNULL)=ERROR,表示ERROR與非空值交匯的最小上界是ERROR;
Fmeet(ERROR,ERROR)=ERROR,表示ERROR與ERROR交匯后是ERROR。
NPD 缺陷檢測的數(shù)據(jù)流前向分析主要由三個部分組成:數(shù)據(jù)流的傳遞、數(shù)據(jù)流的交匯以及格值的計算。其中數(shù)據(jù)流動的規(guī)則又稱為傳遞函數(shù),它表征在一個分析語句之前和之后的數(shù)據(jù)流值受該分析語句的語義約束[14]。對于傳遞函數(shù),本文設(shè)定的基本塊(Basic Block)中只有一條程序語句,且一條程序語句對應(yīng)的一條三地址碼語句記作一條Unit,包含多個程序語句的傳遞函數(shù)就可以將各個語句對應(yīng)的傳遞函數(shù)組合起來得到。傳遞函數(shù)接受的輸入是一個從程序變量到格中抽象值中元素的映射,而函數(shù)的返回值也是這樣一個映射[15]。數(shù)據(jù)流傳遞函數(shù)的設(shè)計如下:
式(1)中,Unit 表示CFG 中Unit 圖上的一個節(jié)點(diǎn)。{(x,LatticeValue)}就是數(shù)據(jù)流值。對傳遞函數(shù)的規(guī)則描述如下:
1)如果Unit是一個賦值語句,則會計算賦值語句右側(cè)部分的格值,并與賦值語句的左側(cè)變量組成計算后的數(shù)據(jù)流值傳入下一條Unit;
2)如果Unit 是一個調(diào)用語句(包括虛擬調(diào)用,靜態(tài)調(diào)用,特殊調(diào)用),則會計算調(diào)用語句的傳入?yún)?shù)的格值,并與調(diào)用主體組成計算后的數(shù)據(jù)流值傳入下一條Unit。
根據(jù)傳遞函數(shù),具體的算法如圖3所示。
圖3 NPD缺陷檢測的數(shù)據(jù)流傳遞算法
算法1 首先將CFG 上的每個節(jié)點(diǎn)流入數(shù)據(jù)流值(FlowMapIn)和流出數(shù)據(jù)流值(FlowMapOut)初始為空的哈希集合,將CFG 上的每一條語句(Unit)存到工作表里(WorkList),然后對每一條語句執(zhí)行前向分析以確定語句中是否存在狀態(tài)約束轉(zhuǎn)換(第1行~23行)。
若當(dāng)前分析語句是賦值語句,且賦值語句的左側(cè)元素是局部變量,算法1 則會調(diào)用算法2 獲取當(dāng)前變量的數(shù)據(jù)流值,并將計算后的格值傳遞給下一條分析語句的輸入數(shù)據(jù)流值(第8行~13行)。
若當(dāng)前分析語句是調(diào)用語句,且調(diào)用語句的調(diào)用主體是局部變量,算法1 則會調(diào)用算法2 獲取此變量的數(shù)據(jù)流值,并將計算后的格值傳遞給下一條分析語句的輸入數(shù)據(jù)流值(第14行~19行)。
當(dāng)工作表里的語句全部遍歷完成后算法1 終止,進(jìn)而得到CFG上的每個節(jié)點(diǎn)的格值。
其中格值計算算法如圖4的算法2所示。算法2的輸入為FlowMapIn和StatementValue。
圖4 NPD缺陷檢測的格值計算算法
StatementValue 為待計算語句部分,它可能是賦值語句的右側(cè)部分或者調(diào)用語句的傳入?yún)?shù)。算法2會根據(jù)待計算部分進(jìn)行分類計算。
若待計算部分是一個整形常量或字符常量則會返回NOTNULL(第15行~20行);
若待計算部分為空或者構(gòu)建語句,則會返回NULL(第21行~26行);
若待計算部分是一個局部變量,算法2 則會遍歷流入的數(shù)據(jù)流值,判斷此處的局部變量在先前節(jié)點(diǎn)中的格值,若先前的格值是NULL 或者ERROR,那此時對該變量的調(diào)用則為NPD 缺陷,從而返回ERROR,若先前的格值是NOTNULL,則返回NOTNULL,其他情況返回NULL(第6行~14行);
若待計算部分為調(diào)用表達(dá)式,算法2 則會遞歸調(diào)用該算法,獲取計算后的格值(第27行~30行);
若待計算部分是一個二元操作表達(dá)式,則會先計算二元操作符兩側(cè)成分的格值,當(dāng)兩側(cè)成分的格值均為NOTNULL 時,則返回NOTNULL,當(dāng)兩側(cè)成分的格值中有一側(cè)是NULL,且不為ERROR 時,則返回NULL,當(dāng)兩側(cè)成分的格值中有一側(cè)是ERROR時,則返回ERROR(第31行~39行)。
最后,當(dāng)工作表里的待計算語句部分全部計算完成后算法2 終止。此外,數(shù)據(jù)流值交匯計算的算法如圖5的算法3所示。
圖5 NPD缺陷檢測的數(shù)據(jù)流值交匯計算算法
按照第4 節(jié)的數(shù)據(jù)流分析算法,對圖1 的NPD缺陷代碼實例進(jìn)行分析,得到圖6所示結(jié)果。
圖6 NPD實例數(shù)據(jù)流分析
其中,虛線指向的是各語句的輸出數(shù)據(jù)流值。從圖中可以得到OUT[6]的數(shù)據(jù)流值中變量num1的格值為ERROR,OUT[7]的數(shù)據(jù)流值中變量$stack1的格值為ERROR,OUT[8]的數(shù)據(jù)流值中變量$stack2 的格值為ERROR。這就表明圖1 所示的NPD 實例中,第12 行、14 行和15 行出現(xiàn)了NPD 錯誤,這與2.2節(jié)的分析結(jié)果是完全一致的。
以本文上述的NPD 缺陷檢測方法實現(xiàn)的檢測工具ASCA-Java進(jìn)行試驗,對比目前業(yè)界主流的開源和商業(yè)檢測工具SpotBugs、PMD 和Fortify,分析本文所提方法的有效性和實際性能表現(xiàn)。
試驗環(huán)境:JDK 版本為1.8,操作系統(tǒng)為Mac OS 10.14.6,CPU為因特爾-酷睿i7-8559u四核八線程處理器,主頻2.7GHz,內(nèi)存32G,硬盤容量1T。
測試數(shù)據(jù):本實驗以美國國家安全機(jī)構(gòu)(NSA)旗下的軟件安全中心發(fā)布的Juliet test suite java 項目開源測試集為檢測對象。
通過表1 可以看出,本文提出的基于數(shù)據(jù)流分析的NPD 缺陷檢測法能夠有效地檢測出代碼中的NPD缺陷,與以抽象語法樹為分析基礎(chǔ)的檢測工具SpotBugs和PMD相比,本方法具有更高的準(zhǔn)確率和更低的誤報率,由于本方法暫不支持過程間的數(shù)據(jù)流分析,所以漏報率稍高于商業(yè)工具Fortify,但檢測效率要高于Fortify和其他兩款開源檢測工具。
表1 NPD缺陷檢測試驗結(jié)果對比
本文提出了一種基于數(shù)據(jù)流分析的NPD 缺陷檢測方法,并通過所開發(fā)的測試工具進(jìn)行對比試驗證明了該方法的有效性。在準(zhǔn)確率和誤報率上,該方法要優(yōu)于Java開源檢測工具SpotBugs和PMD,且在對比的三個檢測工具中耗時最短。本文的下一步工作重點(diǎn)是研究基于過程間的數(shù)據(jù)流分析來進(jìn)一步降低缺陷檢測的漏報率。