• 1.摘要
  • 2.基本信息
  • 3.定义
  • 4.危害
  • 5.指针分析技术
  • 6.检测方法
  • 7.检测工具
  • 8.参考资料

空指针引用故障

C语言中的指针机制使得它灵活高效,但由于指针操作不当产生的动态内存错误也有很多,比如内存泄漏(Memory Leakage)、内存的重复释放、空指针解引用(NullPointer Dereference)。其中空指针引用是一类普遍存在的内存故障,当指针指向无效内存地址时对其引用,有可能产生不可预见的错误,导致软件系统崩溃。空指针引用缺陷可能导致系统崩溴、拒绝服务等诸多不良后果。因此,消除软件中的空指针引用缺陷将是一件具有价值的工作。

基本信息

  • 中文名

    空指针引用故障

  • 外文名

    Null Pointer Dereference

定义

空指针引用故障(Null Pointer Dereference),也叫空指针解引用,是程序设计语言中一类常见的动态内存错误。指针变量可以指向堆地址、静态变量和空地址单元 ,当引用指向空地址单元的指针变量时,就会产生空指针引用故障,有可能产生不可预见的错误,导致软件系统崩。

危害

中国国家信息安全漏洞库(CNNVD)统计,2013年共发现空指针引用引发的漏洞共35个,这些漏洞存在于操作系统、服务器应用程序等软件系统中,漏洞类型有拒绝服务、代码注入、信息泄露、—区溢出、数字错误等。这些漏洞一旦被恶意攻击者利用,可能导致系统崩溃,服务器程序可能会拒绝服务,或者机密信息泄露,这都将严重的影响软件的运行以及系统的安全。

根据我们对国内航天、航空、武器装备、金融、电信等数千万行国产软件应用DTSC的测试报告统计,在所有故障类缺陷中,空指针引用缺陷大约会占到30%左右,空指针引用缺陷的密度大致是0.3个/KLOC。

MicrosoftWindows针对传递给windows内核系统调用的注册键值没有进行充分的校验,攻击者通过运行特殊构建的应用程序,使内核触发空指针引用而造成系统崩馈。MicrosoftSMB协议软件在处理SMB报文中的share和servername字段时存在空指针引用错误,未经认证的攻击者可以通过向运行Server服务的计算机发送特制网络消息来利用该漏洞,导致受影响系统停止响应,直至手动重新启动。

Linux Kernel是Linux所使用的内核,Linux Kernel 的 kemel/posix-timers.c 文件中的 clock—nanosleep()函数存在安全漏洞,如果使用等于CLOCK_MONOTONIC—RAW的时钟ID调用该函数就会触发空指针引用,导致拒绝服务的情况。deep—rev—state_process()函数在关闭了套接字后仍然允许接受,关闭后重置没有阻止对己经废弃套接字的操作,可造成空指针引用。Linux Kernel没有正确地执行tty操作,本地用户可以在目录drivers/net/的以下文件中触发空指针引用,导致系统崩馈。1

指针分析技术

指针分析的目标是确定每个指针在运行时的指向。与其它静态分析一样,指针分析也是一个不可判定问题。即便是对程序做出某种限制(例如限制动态内存分配、忽略分支条件)以便易于处理,指针分析依然是一个NP难的问题;特别是指针间存在别名、复杂数据结构类型、指针计算、函数指针等更加剧了指针分析的难度;因此,为达到分析精度与效率的平衡,指针分析时需要作某种程度的近似。

进行指针分析首先要确定指针指向信息的表示形式,指针指向信息的表示形式对指针分析算法的精度与效率有很大的影响。指向信息的表示通常有两种:storeless方法和store based方法。Storeless方法是以别名对(Alias Pair)的方式表示指针分析结果;但是当分析的程序中有递归的数据结构时,别名对构成的集合可能是一个无穷集,为此需要采取一些损失精度的处理。Store based方法使用图的方式表示分析结果,如指向图(Points-to Graph) 别名图(Alias Graph) 、存储图(StoreGraph) 等。

指针分析的精度主要体现在:在过程内分析中是否考虑程序中的语句执行顺序,在过程间的分析中是否考虑过程调用的上下文信息,在对复杂数据结构的数据分析时是否把每个成员看作,在基于控制流分析时是否考虑在汇合节点不同的路径的合并,在对面向对象语言程序分析时是否考虑同一个类的不同实例化对象。

流不敏感的分析不考虑语句执行顺序,流敏感的分析区分语句的执行顺序与控制流信息;前者对整个方法只生成一个指针的指向关系表示,后者在每个语句处会生成一个分析图。流敏感的指针分析能够对指针的指向进行强更新,因此能够更精确的描述指针指向的信息。流不敏感的指针指向分析研究方向主要分为两个方面:一个是基于Andersen算法_的扩展;另一个是基于Steensgaard算法丨的扩展。Andersen利用包含约束集使得指向分析的结果更精确;Steensgaard通过建立一种统一的类型推导模型,精度不如前者,却拥有接近线性的时间复杂度。传统的流敏感指针分析是在控制流图上进行标准的迭代分析,在每个可达的程序点上对指针指向信息进行分析求得不动点,通过在每个节点上消除掉不需要的指向信息,从而得到比流不敏感分析更精确的结果。但传统的流敏感指针分析存在的问题。(1)分析时在控制流上传递了过多指针信息,因为只有部分指针信息被后续节点使用;(2)因为指针指向信息比数据流分析的数值信息复杂,简单的借鉴数据流分析技术来分析指针导致效率低下;(3)每个节点都保存指向信息,分析大型程序时将会耗用大量内存资源。为克服传统流敏感分析在效率方面的不足。近来,有些研究对指针进行稀疏的流敏感分析该类分析技术主要是应用静态单赋值(Static singleassignment)表示程序中变量的定义-使用关系,在控制流图节点上对只对需要进行指针分析的节点进行分析,并用二元决策图表示指针的指向信息以更高效的实现对指针进行强更新操作。稀疏分析能够大大提升分析的效率,但是如何跟数据流分析结合是该方法需要结合的问题。

过程间指针分析时的两个核心问题是:传入的指针指向信息如何映射成被调用函数的指针指向信息,被调用函数出口处的指针指向信息如何反映射到调用点。上下文不敏感的分析方法对同一函数的不同调用只产生一个唯一的近似结果,上下文敏感的分析方法依据同一函数不同调用点指针指向模式的不同而产生不同的分析结果。后者较前者的分析结果更加精确。最简单有效的过程间分析是函数内联,另一种常用的方法是记录函数调用串来区分调用上下文。这两种方法因为在每个调用点都进行重复展开而效率低下。基于调用关系生成调用图,在扩展的调用图上进行分析。该方法的思想是全路径扩展,分析结果较为精确;但会因为路径膨胀导致分析效率低下,难以胜任对大型程序的分析。为避免全路径扩展与被调用函数多次分析,最常用的一种方法是通过函数摘要建立输入与输出间的映射关系,函数摘要描述了输入与输出间的映射关系以及函数副作用等信息。函数摘要的准确度将决定分析的精度。为进一步提高分析的效率,有些基于特定的需求进行有选择的上下文敏感分析。

域敏感分析是将程序中所有复杂数据类型的每个成员变量都视作不同的存储对象。Steensgaard本人釆用了最大公因子前缀技术来建立结构体类型之间的对应关系,根据非标准类型约束准则,利用类型推导算法对其工作进行了改进形成了支持域敏感的指向分析。Yong将Steensgaard的思想进一步扩展,给出了三种不同精度的方式解析结构体类型之间的对应关系。Pearce利用Steensgaard的最大公因子前缀思想改进了 Andersen的算法其对结构体的域成员给出了不同于Yong的表示方法,利用域地址模型来代替整数偏移模型,效率有所提高。除了Steensgaard的算法提出的扩展外,比较流行的还有基于偏移表示的分析方法。Stocks认为所有代码都是一种基于偏移的形态对重叠域的计算虽然可以更精确的表示其别名关系,但只计算匹配域便可满足别名分析的精度要求。Wilson为每一个对象保留一个步长,用于记录它的偏移,这种方法对于结构体中包含数组指针的处理有较好的效果。另外,还有一些算法把结构体的域作为区分单元,利用三元模型对其进行建模,但并没有实际的对其进行数据流计算或者计算的不够精确。

路径敏感分析利用控制语句的表达式限制可能的值进入不同的分支语句。例如在数据流分析过程中加入路径标签实现了路径敏感的指针指向分析,但其分析效率相对较低,影响了该技术在实际应用中的可扩展性。SPAS在全稀疏分析的基础上进行了可扩展的路径敏感的指针分析。

上述的各种敏感技术的运用与否,决定着测试的精度与效率。其中最重要的是上下文敏感与流敏感,目前的检测工具大多应用了上下文敏感分析。1

检测方法

相关技术可粗略的分为空指针引用缺陷检测与指针引用验证两大类。

前者侧重于如何尽可能多的发现程序中空指针引用,后者侧重于如何验证程序中的指针是否为空。空指针引用缺陷检测一般是在数据流分析、指针进行分析的基础上,根据一些规则基于控制流前向的检测。指针引用验证技术是基于需求驱动的思想,一般是首先识别出指针,再沿着控制流后向的验证指针是否是空,指针引用验证也要进行数据流分析与指针分析才能实现精确的验证。