APP下载

基于数据流分析的Java 空指针引用异常缺陷检测∗

2024-01-23王国峰唐云善徐立飞

计算机与数字工程 2023年10期
关键词:指针数据流调用

王国峰 唐云善 徐立飞

(1.南瑞集团有限公司(国网电力科学研究院有限公司) 南京 210003)(2.南京南瑞信息通信科技有限公司 南京 210003)

1 引言

源代码的安全是计算软件安全的基石,而源代码的缺陷检测正是稳固此块基石的重要方式[1]。据中国国家信息安全漏洞库统计,由空指针引用异常(Null Pointer Dereference,NPD)造成的安全漏洞引发了许多严重的安全问题,诸如系统崩溃、越权操作、拒绝服务和执行恶意代码等[2~3]。目前对于NPD 漏洞的检测主要分为动态检测和静态检测两种方式[4~5]。其中静态检测无需运行程序,直接通过语法和词法等技术手段对编译后的文件进一步分析,即可找出程序中可能存在的缺陷。这种检测方式速度快、效率高,广受业界青睐[6~7]。

在现有的关于NPD 缺陷静态检测方法中,Zhang 等[8]通过对缺陷报告的分析,排除一些明显的误报,来提升对NPD 缺陷的检测准确度,但噪声报告同时也会对优化结果产生二次影响。毕学军等[9]通过在控制流的基础上利用变量区间来表示状态的前提条件,对程序中不可达路径进行处理,从而达到减少误报的目的。但该方法未能进一步解决状态出现ERROR 情况下,导致分支不可达下的缺陷漏报问题。杨睿等[10]基于NPD 缺陷状态机模型,通过函数摘要的生成传递与使用支撑数组空指针故障的全局分析与检测,从而减少漏报。但其考虑的函数摘要并不完善,对Java代码中的诸多函数类型如返回值、I/O 操作类型等均未涉及,因此该方法仅适用于数组的NPD检测上。

针对上述问题,本文提出了一种基于数据流分析的Java空指针引用异常缺陷检测方法,该方法根据NPD 缺陷模式的缺陷特征,设计了对应的数据流值、格值计算规则和传递函数,以此来确保数据流值在程序控制流图(Control Flow Graph,CFG)上按照设定的传递函数进行前向分析,然后遍历CFG上每个节点的数据流值,找出格值为ERROR 的变量,报出NPD 缺陷故障点。最后,文章通过对比试验验证了该方法的有效性及性能效果。

2 空指针引用异常缺陷

2.1 缺陷模式

缺陷模式指一类缺陷产生时程序所呈现的共同的语法或语义特征,描述了程序的一种属性,满足该属性则产生缺陷[11]。NPD 缺陷模式则是描述了被引用的指针指向了无效的内存区域的属性。当缺陷模式确定后,被用来检测某一程序是否违反了程序语法或语义规则的属性,即被称为该缺陷模式的缺陷特征[12]。NPD 缺陷模式的缺陷特征就是被引用的指针,因此,通过检测该指针变量是否为空指针,即可判定是否产生NPD缺陷。

2.2 空指针引用异常缺陷实例

Java 语言中没有指针这个概念,因而Java 中的空指针用空值句柄(Handle)来表征[13]。空指针引用则表明对一个空值句柄的方法、字段或者域的调用,在此调用过程中,编译器则会抛出Null Pointer Exception错误,即空指针引用异常。

结合图1 的NPD 代码实例,变量a 和变量c 的初始化均为空值null。若第8 行变量num1 的值大于b.hashCode(),则变量b 加上字符串“world”的值将赋给变量a,此时a 不再是空值null,从而第15 行系统打印函数对a 的调用不会报错,但若第8 行的if 条件语句的判断为false,则a 的值依然为null,从而导致第15行出现NPD错误。

图1 NPD缺陷代码实例

程序第11 行,a 加c 之后的值赋给变量d,由于a和c均为空值null,从而变量d的值也为空值null,这就导致第12行的d.length()调用语句引发对变量d的NPD,以及第14行也同时出现NPD错误。

3 空指针引用异常缺陷检测流程

NPD 缺陷检测流程如下所示,共分为五个步骤。其中步骤3至步骤5为本文的主要工作。

步骤1:对待分析源码进行扫描,通过词法、语法分析构建源码的抽象语法树模型;

步骤2:通过抽象语法树模型将源码转化成三地址码这种代码的中间表示;

步骤3:通过多次遍历三地址码,构建程序CFG,并设计NPD 缺陷模式的数据流值、数据流值的交汇计算规则和数据流的传递函数;

步骤4:在CFG 上,根据传递函数计算CFG 中每个节点的数据流值,完成数据流前向分析;

步骤5:遍历CFG 上各节点分析后的数据流值,根据NPD 缺陷模式状态划分,找到格值为ERROR的变量,报出NPD缺陷代码片段。

4 空指针引用异常缺陷检测算法

4.1 NPD缺陷模式数据流值

NPD缺陷模式数据流值是一个对,由被分析变量及其格值组成的pair<变量名,格值>。根据NPD的缺陷模式,本文设计的格值集合一共有三个定值,即S={NULL,NOTNULL,ERROR}。其中定值NULL表示Java句柄中被分析变量在前趋数据流节点的格值为NULL;定值NOTNULL 表示Java 句柄中被分析变量在前趋数据流节点的格值为NOTNULL;定值ERROR 表示当前Java句柄中被分析变量产生了空指针引用异常缺陷。

图2 所示格图的顶元素为格值ERROR,表明所有检测变量均出现了NPD 缺陷,这个分析结果是安全的但会出现大量误报;格图的底元素为格值NOTNULL,表明所有检测变量均未出现NPD缺陷,这个分析结果是不安全的且会出现大量漏报;其他数据流值则是无序的空值NULL。NPD缺陷检测的数据流分析属于may分析,所以只需在格图上找到最小不动点,即可得到分析的最优结果。

图2 数据流值格图

4.2 数据流值交汇计算规则

当程序经过诸如分支判断语句时,数据流会分成多个分支,进而需要在分支语句结束后对同一变量的格值完成交汇操作,记作Fmeet。数据流值交汇操作时的计算规则如下:

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。

4.3 NPD缺陷检测的数据流前向分析

NPD 缺陷检测的数据流前向分析主要由三个部分组成:数据流的传递、数据流的交汇以及格值的计算。其中数据流动的规则又称为传递函数,它表征在一个分析语句之前和之后的数据流值受该分析语句的语义约束[14]。对于传递函数,本文设定的基本块(Basic Block)中只有一条程序语句,且一条程序语句对应的一条三地址码语句记作一条Unit,包含多个程序语句的传递函数就可以将各个语句对应的传递函数组合起来得到。传递函数接受的输入是一个从程序变量到格中抽象值中元素的映射,而函数的返回值也是这样一个映射[15]。数据流传递函数的设计如下:

式(1)中,Unit 表示CFG 中Unit 图上的一个节点。{(x,LatticeValue)}就是数据流值。对传递函数的规则描述如下:

1)如果Unit是一个赋值语句,则会计算赋值语句右侧部分的格值,并与赋值语句的左侧变量组成计算后的数据流值传入下一条Unit;

2)如果Unit 是一个调用语句(包括虚拟调用,静态调用,特殊调用),则会计算调用语句的传入参数的格值,并与调用主体组成计算后的数据流值传入下一条Unit。

根据传递函数,具体的算法如图3所示。

图3 NPD缺陷检测的数据流传递算法

算法1 首先将CFG 上的每个节点流入数据流值(FlowMapIn)和流出数据流值(FlowMapOut)初始为空的哈希集合,将CFG 上的每一条语句(Unit)存到工作表里(WorkList),然后对每一条语句执行前向分析以确定语句中是否存在状态约束转换(第1行~23行)。

若当前分析语句是赋值语句,且赋值语句的左侧元素是局部变量,算法1 则会调用算法2 获取当前变量的数据流值,并将计算后的格值传递给下一条分析语句的输入数据流值(第8行~13行)。

若当前分析语句是调用语句,且调用语句的调用主体是局部变量,算法1 则会调用算法2 获取此变量的数据流值,并将计算后的格值传递给下一条分析语句的输入数据流值(第14行~19行)。

当工作表里的语句全部遍历完成后算法1 终止,进而得到CFG上的每个节点的格值。

其中格值计算算法如图4的算法2所示。算法2的输入为FlowMapIn和StatementValue。

图4 NPD缺陷检测的格值计算算法

StatementValue 为待计算语句部分,它可能是赋值语句的右侧部分或者调用语句的传入参数。算法2会根据待计算部分进行分类计算。

若待计算部分是一个整形常量或字符常量则会返回NOTNULL(第15行~20行);

若待计算部分为空或者构建语句,则会返回NULL(第21行~26行);

若待计算部分是一个局部变量,算法2 则会遍历流入的数据流值,判断此处的局部变量在先前节点中的格值,若先前的格值是NULL 或者ERROR,那此时对该变量的调用则为NPD 缺陷,从而返回ERROR,若先前的格值是NOTNULL,则返回NOTNULL,其他情况返回NULL(第6行~14行);

若待计算部分为调用表达式,算法2 则会递归调用该算法,获取计算后的格值(第27行~30行);

若待计算部分是一个二元操作表达式,则会先计算二元操作符两侧成分的格值,当两侧成分的格值均为NOTNULL 时,则返回NOTNULL,当两侧成分的格值中有一侧是NULL,且不为ERROR 时,则返回NULL,当两侧成分的格值中有一侧是ERROR时,则返回ERROR(第31行~39行)。

最后,当工作表里的待计算语句部分全部计算完成后算法2 终止。此外,数据流值交汇计算的算法如图5的算法3所示。

图5 NPD缺陷检测的数据流值交汇计算算法

4.4 实例解析

按照第4 节的数据流分析算法,对图1 的NPD缺陷代码实例进行分析,得到图6所示结果。

图6 NPD实例数据流分析

其中,虚线指向的是各语句的输出数据流值。从图中可以得到OUT[6]的数据流值中变量num1的格值为ERROR,OUT[7]的数据流值中变量$stack1的格值为ERROR,OUT[8]的数据流值中变量$stack2 的格值为ERROR。这就表明图1 所示的NPD 实例中,第12 行、14 行和15 行出现了NPD 错误,这与2.2节的分析结果是完全一致的。

5 试验结果分析

以本文上述的NPD 缺陷检测方法实现的检测工具ASCA-Java进行试验,对比目前业界主流的开源和商业检测工具SpotBugs、PMD 和Fortify,分析本文所提方法的有效性和实际性能表现。

5.1 试验环境及测试数据

试验环境:JDK 版本为1.8,操作系统为Mac OS 10.14.6,CPU为因特尔-酷睿i7-8559u四核八线程处理器,主频2.7GHz,内存32G,硬盘容量1T。

测试数据:本实验以美国国家安全机构(NSA)旗下的软件安全中心发布的Juliet test suite java 项目开源测试集为检测对象。

5.2 结果分析

通过表1 可以看出,本文提出的基于数据流分析的NPD 缺陷检测法能够有效地检测出代码中的NPD缺陷,与以抽象语法树为分析基础的检测工具SpotBugs和PMD相比,本方法具有更高的准确率和更低的误报率,由于本方法暂不支持过程间的数据流分析,所以漏报率稍高于商业工具Fortify,但检测效率要高于Fortify和其他两款开源检测工具。

表1 NPD缺陷检测试验结果对比

6 结语

本文提出了一种基于数据流分析的NPD 缺陷检测方法,并通过所开发的测试工具进行对比试验证明了该方法的有效性。在准确率和误报率上,该方法要优于Java开源检测工具SpotBugs和PMD,且在对比的三个检测工具中耗时最短。本文的下一步工作重点是研究基于过程间的数据流分析来进一步降低缺陷检测的漏报率。

猜你喜欢

指针数据流调用
汽车维修数据流基础(下)
核电项目物项调用管理的应用研究
LabWindows/CVI下基于ActiveX技术的Excel调用
一种提高TCP与UDP数据流公平性的拥塞控制机制
为什么表的指针都按照顺时针方向转动
基于系统调用的恶意软件检测技术研究
基于数据流聚类的多目标跟踪算法
基于改进Hough变换和BP网络的指针仪表识别
北医三院 数据流疏通就诊量
ARM Cortex—MO/MO+单片机的指针变量替换方法