C语言是一种灵活并且可移植的高级编程语言,广泛使用近40年,但是它所造成的一些安全问题时有发生,C语言的目标即成为一种内存耗用微小轻量级语言。C的这种特点会令当编程人员误以为某些行为会由C自动去进行处理(而实际上并没有)时,就会出现漏洞。假如程序员熟悉Java、Pascal。那么他们容易误以为C语言会为其提供保护机制。这些不正确假设会致使犯这样一些错误:对数组的越界不进行保护处理、用错误的实参数目去调用函数等等。C语言经常有下列漏洞出现:
(1)C标准库中的一些公共函数本身就存在较大危险,容易出现缓冲区溢出等问题。
(2)if、while、switch等是用途十分普遍的程序控制流语句类型,如果编码人员能使条件表达式正确,这时程序就能够按照编码人员的指令合理地运行。反之,程序中就会出现死循环等安全隐患。
-
-
- 项目背景
-
本文的项目背景基于华为公司的C代码静态分析漏洞检测项目。主要针对一些通用的特定的漏洞目标进行检测。例如当程序中使用strcpy(),strcat()和wctomb()三个函数时均没有保证给出的缓冲区大小能够容纳操作后输出的数据。这些都是需要检测的安全漏洞。本文的目的就是能够及时发现代码漏洞。
-
-
- 程序分析和静态检查
-
对C的程序安全检测研究已经有较长历史,从1979年UNIX V7公布的lint代码检测工起始,已经经过将近三十多年。如今相关领域提出了多种不同的代码安全检测法,例如模型检查(Model Checking)、代码验证(Code Validation)、以及进化测试(Evolutionary Testing),静态程序安全检查是在项目研发中一般应用最多的方法[1][2]。
如今静态安全检查工具已出现许多可用于C编码的工具,许多已经开始使用,其中最具影响力软件包括:
- Coverity Prevent
美国 Coverity公司最初由美国国土安全部支持,Coverity将基于布尔可满足性验证技术,Coverity的新型代码分析由新的Coverity整合中心执行,该中心旨在将各种代码分析的片段联系起来[3]。为开发人员提供全新的认识。Coverity是一个完整的Synopsys的软件平台的核心组件,一个全面的解决方案组合,提高质量和安全软件通过严格的验收过程,且精简化的软件。Coverity可以准确、快速分析大规模高复杂度代码的工具[4]。
- PC-Lint
PC-Lint是一个功能强大的静态分析工具它将检查的C / C++源代码,它可以发现错误,以及不一致的情况,不可移动的结构,代码冗余,以及更多。它可以跨多个模块[5]。
- Fortify Static Code Analyzer
Fortify SCA是一套软件安全分析器,可以搜索与安全编码规则在多语言的情况下,利用静态代码分析器语言技术提供了丰富的数据,使这些解析器识别违反规则和分类,使得我们可以快速、准确地进行修复[6]。这样能够在开发与维护的过程中降低修复代码的成本 [7]。FortifySCA目前世界上最大的源代码静态检测和支持大多数语言的制造商,但是它太过昂贵,且不方便使用[8]。
- Prefast
Prefast是静态代码的分析器由微软研究院提出的但安全缺陷类型的检测中最重要的部分。在微软PREfast发射已被广泛使用。
以上工具是比较常见的代码检测工具,还有一些不常见的代码检测产品,如CodeCheck和CQUAL等。
本文的工作是项目的一部分内容。该项目将设计一种针对C代码的安全规范,本文C程序静态安全检测就是建立在安全规范之上的。该项目旨在为C编码人员引入相对自动且定制化的代码检测方法,协助编码人员检测软件安全漏洞,以确保软件产品的安全度[10]。
图1.1为本文进行设计与研究的C安全检测工具框架图。该工具是由前端源代码处理工具以及后端检测工具两大模块所构成。前端任务是解析C源程序之后,然后为程序后端分析生成用来分析的抽象语法树信息。进而生成符号表以及控制流图等信息。前端是由四大部分所构成,如下所示:
-
-
-
-
-
-
-
-
- C程序检查工具整体框架图
-
-
-
-
-
-
-
(1) C预处理器:是在ANTLR的Lexer以及Parser基础上研发,可以对C代码进行相应预处理工作,并且产生相应“*.i”文件,用于之后解析。
(2) C语法分析器:同样是建立在ANTLR的Lexer以及Parser基础上,用于“*.i”格式文件的解析,从而可以生成符号表以及相应AST(Abstract Syntax Tree)抽象语法树,进而可以完成AST结点名称的解析等相关任务。
(3) 控制流图分析器:遍历抽象语法树从而产生带有AST参数的控制流图,它产生的数据可以为后端分析给与信息。
(4) 函数依赖分析器:函数之间的相互调用关系通过扫描控制流信息来获得,函数之间调用相关拓扑排序序列便是由此依赖而生成。
后端即是通过前端得到相应抽象语法树、符号表以及控制流图,然后进一步进行相应的代码检测以及数据流的相关分析。后端包括了三个部分:
(1) 数据流分析器:数据流分析器的功能实现同样是建立在函数调用的相关拓扑排序序列之上的。它扫描控制流图,对数据流方程的处理而得到信息会存储在在所对应的控制流图的结点上。
(2) 安全分析器:同样建立在函数调用的相关拓扑排序的序列之上,安全分析器将遍历控制流图。安全规则的检测就是通过利用操作对象可能的状态集合与结点上的别名信息进行检测。
-
-
- 状态机检查模块
-
本文设计了状态机安全检查模块,检查的主要过程是通过遍历源程序中各条路径的信息,源程序的安全漏洞检测是通过遍历路径上与源程序相关信息,以及安全规则文本中指定安全规则能否进行匹配来进行的。通过项目框架图1.1可以得出,路径列表、外部安全规则、指针别名信息、对象状态等为状态机分析器的输入。第一步为将外部安全规则输入到状态机安全分析器中来进行相应的分析。安全规则是存储于程序数据结构中。然后,对于任意一条路径,基于数据流分析,用存储在控制流结点上的源程序与存储的安全规则与来进行相应的对应,安全检测信息会在对应到安全规则中所对应不合法转移时发出提示。
-
- 本文工作及内容组织
- 本文的工作
- 本文工作及内容组织
为了满足项目实际需要,本文工作为完成图1.1安全检查工具总体框架的用户自行定义安全规则的分析模块以及之后的安全分析器。通过解析外部制定的安全规则静态安全检查工具建立在数据流分析的工作上进行相应解析,来寻找不合法的安全漏洞问题。本文对基于定制化规则的安全分析工具研究与实现。工作具体如下:
(1)首先设计了状态机的语言文法。本文对定制化规则模式的文法进行了相应的设计,令定制化规则源程序模式能够符合C中大部分结构。
(2)设计并且实现了模式与源程序的相关匹配算法。设计成ANTLR中的抽象语法树形式来对应安全规则中的模式。完成了树同构判定的相关匹配算法。
安全分析器读入外界用户制定的安全规则,与源代码相匹配用以进行安全分析检测,最后查找出安全漏洞。
-
-
- 本文的内容组织
-
论文分为六章,内容组织安排如下:
第一章:绪论。对C程序相关的安全问题以及项目背景进行介绍,同时介绍了常用的安全检测软件,最后对静态安全检查工具的整体框架进行介绍。
第二章:相关技术和整体设计。对安全规则制定进行了相应的需求分析,介绍了本文用到的相关技术以及工具。
第三章:定制化工具描述语言相关文法的设计以及实现。讨论了定制化工具的文法的设计以及实现。给出了安全规则的语法结构以及状态机模式文法的详细设计。之后是对状态机相关的数据结构设计的讨论。
第四章:状态控制的设计与实现。首先讨论表达式模式匹配算法,介绍了状态转移以及状态复制等机制的数据结构。然后给出了模式匹配的具体算法以及状态转移控制的流程。
第五章:对定制化安全检测工具进行了实例的分析。重点检测了两类漏洞分析了定制化规则的安全分析工具的分析过程。
第六章:结束语。总结已完成的工作,对不足进行了分析,对下一步计划进行了展望。
- 相关技术介绍和总体设计
本文任务是基于实际项目所需要首先设计一个易于掌握的安全漏洞的检查机制,使得编码人员可以依据需求来外部制定安全规则,C源代码中潜在的漏洞可以通过检测工具依据制定的安全规则实现检测。本章首先介绍了ANTLR以及其他项目用到的相关技术,接着介绍了整体机制的基本设计。
-
- 相关技术简介
- ANTLR的简介
- 相关技术简介
ANTLR,它可以使用包含C语言动作的语法描述来构造语言的识别器,编译器和解析器[11] [12]。
在学术界广泛应用,构建各种语言、工具和框架。数据仓库和分析系统的Hadoop,使用ANTLR。Lex Machina利用ANTLR从法律文本信息提取。Oracle使用ANTLR在SQL开发IDE和迁移工具。NetBeans IDE解析C++用ANTLR。在Hibernate对象关系映射的框架HQL语言建立了ANTLR[13]。
ANTLR是一种文法,可以根据需要生成任何语言的语言识别器、编译器。ANTLR能够自动的生成AST(抽象语法树)[15]。
项目的语法分析以及建立AST等过程都使用了ANTLR工具来辅助进行。
-
-
- ANTLR的功能说明
-
ANTLR提供了能够自动生成AST并且能够显示出来。ANTLR基本流程词法分析器以及语法分析器两个过程解释以及翻译解析过程[16]。以下是每一部分的简要介绍:
词法分析器(Lexer)也被称为scanner,程序语言一般是由词法的结构以及关键字来构成。词法分析器任务即用来解析以及量化一些本来看起来无章可循字符,然后把这些字符转化成为离散形式字符组(即单个Token,其中包括标识符、关键字以及操作符等等给语法解析器利用[17]。
语法编译器(Parser)又称为syntactical analyser。语法分析器将记录到的Tokens经过一定形式进行重新组织,转变成目标语言语法定义的序列。他们在本质上是类似的[18]。lexer分析出来的记号流给Parser分析和使用。
ANTLR可以依据编码人员提供的文件自动产生相应词法以及语法的分析代码。编码人员可以将输入的文本通过编译,转变成另一种形式,例如AST一Abstract Syntax Tree。
-
-
- 项目关键技术概述
-
- 控制流图
该模块主要负责遍历前端提取出AST以及符号表,设计生成控制流图。该模块基于分析器生成工具ANTLR遍历AST然后形成控制流图,以便给后端提供足够数据进行安全检查。并且该模块生成控制流图的过程中,可以对一定条件下的不可达语句进行漏洞检查并报错。本文中CFG类是控制流图对外的接口,它由顶点结点(VexNode)和边结点(ArcBox)组成。顶点结点中data域记录有关语句结点的相关数据,用CFGNode类来表示。
(2)数据流分析
数据流分析可以从源代码中收集代码的语义信息 [21] [22]。
数据流分析基于CFG,它在每个节点都拥有一个传递函数。传递函数就是对源代码语句语义另一种表示形式。输入值的数据是流在节点上的值,而输出值是数据流节点经过变化的值。数据流的值其实是一个集合,它存储了源代码程序对象以及相应的状态 [23]。
数据流分析的程序流信息通过使用迭代器来遍历AST进行数据的采集工作。可以根据AST得到相应的信息,通过分析源代码语义然后把CFG节点输入数据流值更新为出口流值。对于源程序不同数据流信息、数据流值表示形式和源代码传递函数问题是不相同。所以,一次遍历AST仅仅可以解决一类数据流问题。数据流分析其实是建立在前端分析的基础上的。
(3)抽象语法树
抽象语法树(AST)是对源程序抽象语法结构一种树表示方法,在抽象语法树中,每一个节点就能代表源代码的一种结构,抽象语法树不是展现例如真正语法的具体内容,嵌套的括号在树结构中,而不是以节点的形式。抽象语法树不依赖于语法的源语言,语法树的树形结构比源代码的线性结构具有更强的可操作性,并且易于分析以及使用。由于在编写语法时,往往对语法进行等价转换(排除左递归、回溯、意义等)操作,它对语法分析带来部分额外的成分.
(4)符号表
符号表(symbol table)为一种帮助编译器存储有关源代码的各种信息的一种数据结构形式。符号表存放着在编译器的解析过程中被逐渐收集的数据。符号表条目中涵盖了单个标识符所相关内容,例如字符串、类型、存储位置等信息[24][25]。
符号表在编译全过程中处在非常关键的地位,是作为上下文的检查及语义处理和代码生成的依据。对源代码解析行为中,在下文相关检查行为中要利用符号表来对上下文的相关数据进行记录,如变量能否做到在使用之前先定义,函数的相关调用过程能否与函数的定义相匹配,并且为C代码检测工具之后的安全检测给予符号信息的相关支持。符号表作用是合理组成并记录源代码中相应的符号条目,进而在每个分析过程可以进行高效的插入、查找、删除和修改等行为[26][27]。
安全分析要求符号表能够在各个阶段提供快速、准确搜索和操作。本符号表能够进行各种查找的方法,如用名称查找、名称以及组合查找方法等等。查找可以给定作用域或者默认当前的作用域。
源代码安全检测工具的后端使用了符号表中相关的内容。如函数符号以及变量内容就是保存于符号表的。对源程序进行对应的安全检测时,可以利用符号表提供的接口来访问符号的相关数据。
-
- 定制化规则的安全分析工具概要设计
对于定制化规则的安全分析工具,需要有一个统一的安全规则,并且对于外部输入的安全规则,需要有灵活精确的需求,该安全规则需要的要求有以下几个方面。
编码人员能够方便的来制定所需的安全规则。根据项目需要,由于许多安全规则是项目特有的,所以安全规则需要通过自定义的方式来实现。分析工具需要外部规则才可以进行检测程序,开发人员也可以提前定义一个安全规则集合。用户可以对安全规则进行修改,或者重新增加所需的规则。
该安全规则工具具有灵活的扩展性,在使用过程中可以随时修改安全规则,或者添加新的安全规则。针对不同的漏洞形式可以将安全规则划分成不同的集合,以便于管理。用户或开发人员定义的安全规则之间是互不干扰的,可以分别存储在不同的安全规则文件中,在进行安全检查时,每个安全规则是分开检测的,但是结果是工具汇总的结果,在用户体验上是同时检测的。最后统一提供提示信息。
本文所指定制化规则代码安全检测工具是通过参考国际的安全规则语言Metal其相关概念-状态机来进行研究实现,使用ANTLR(ANother Tool for Language Recognition)来进行语法以及词法的解析工作。并且同时ANTLR可以生成源代码的AST(抽象语法树),编码人员首先编写外部输入的安全规则,即相应的安全规则描述。接着安全规则解析器就会分析安全规则,并且将外部输入定制化安全规则文件里的信息转换成某种数据结构存在程序数据结构里。
-
-
-
-
-
-
-
-
- 安全检查机制总体设计
-
-
-
-
-
-
-
本文所指安全规则主要根据状态机的形式设计与实现的,并且可以匹配源程序中比较不好识别的结构,比如赋值,初始化操作、函数的调用等等。安全规则定义时,本文把对源代码进行了特征建模称为模型代码。把这种可以代表模型代码的结构形式叫做模式。源程序需要经过解析并且与安全规则模型代码通过比对得到结果。比对完成后如果匹配成功那么就会依据状态机的规则将程序变量绑定其相应状态,并且可以变量的状态能够随着状态机的状态进行相应的转移。安全分析器能够匹配源程序代码中与不符合规则的相应动作。如果源代码匹配了不安全状态,安全分析程序就会发出提示消息。定制化规则的安全分析工具会根据安全规则文件描述的漏洞,基于前端数据流分析对每条路径都进行检测。如图2.1,安全分析器基于控制流图,安全分析器依据定义的安全规则搜索到安全漏洞发出提示消息。
对于状态的转移处理器,它不仅包含了模式匹配检查器而且还包括状态转移控制器。模式匹配检查器任务就是检测安全规则中的模式模型程序是否与需要匹配的源程序相匹配。如果匹配完成,并且匹配成功就会对模型代码中相应变量执行状态绑定,如果匹配到了转移相关的规则那么就将进行变量状态的转移。如果转移的状态是不安全的,那么就会发出提示信息,这样就检测到了安全漏洞。在CFG的基础上。模式匹配检查器检测每一条路径,抽象语法树遍历器搜索控制流结点对应的AST,该工具是通过在抽象语法树遍历器中增加有关模式匹配的算法的语义用来完成模式匹配。
定制化规则的安全分析工具不仅能够检测安全漏洞,同样能够检测编码规范等约束。检测方法与检测漏洞类似,也是由安全规则定义的约束。检测工具可以通过这些约束检测需要的形式。
-
- 本章小结
本章主要进行了相关技术的介绍以及定制化安全工具的概要设计。本章首先就ANTLR编译前端工具进行了介绍,详细介绍了ANTLR的作用以及功能,其次介绍了ANTLR的语法结构,并对ANTLR的建树以及代码植入和操作进行了相应的介绍。最后针对制化规则的安全分析工具检测机制进行了概要设计,介绍了安全规则描述文件以及规则解析器的功能作用,概述了模式匹配以及状态转移控制以及最后分析结果等流程。
- 安全规则描述语言需求分析及设计
定制化状态机的语言是对程序员的配置文本实行的规范。安全规则的书写是程序员依据给定的格式进行的。安全规则的约束需要一套文法,本章具体讨论检测机制安全规则,状态机描述语言语法。具体包含了状态机描述语言的语法框架、状态机数据结构、状态机模式文法的具体设计。基于状态机的特性,用户就能够根据需求定制外部安全规则。
-
- 需求分析
本课题研究的方向主要着重于对C程序设计语言本身在实际软件设计过程中可能引起的设计缺陷进行研究和总结,同时设计并实现一种基于编译时静态程序分析方法检查此类缺陷的自动安全检查工具。
C程序缺乏运行时环境保证的特点使得动态安全检查方法难以对C程序中的某些错误进行认定。而静态安全检查方法则不受运行时限制。因此,本课题选用静态程序的分析方法作为主要安全检测法。
例如华为防火墙存在内存泄露的问题。攻击者能够访问Web然后通过Web访问设备上相关的网页,从而导致主控板上发生内存泄露。如果程序员使用了当程序中使用memcpy()函数时,程序员需要保证进行操作的两个操作数不能交叠。另外,无论程序中使用的是memcpy()还是memmove()函数,程序员均应当保证目的缓冲区指针指向空间应当能够容纳源缓冲区中保存的数据。
安全漏洞总体分类包括指针有关的访问引起的程序漏洞,由于函数配对引起的资源泄漏和程序异常,类型转换相关问题,标准库中禁用或者危险函数列表,控制流相关的漏洞,文件输入输出安全相关问题等等。下面对出现频率较高的安全漏洞进行讨论。
-
-
- 与指针操作相关的程序漏洞
-
-
-
-
- 与指针操作相关的程序漏洞
-
-
-
-
- 与指针操作相关的程序漏洞
-
与指针操作相关的程序漏洞 | 1 | 数组下标访问越界 |
2 | 字符串缓冲区没有添加相应的结束符'\0' | |
3 | 对未初始化的指针进行引用 | |
4 | 对空指针进行引用 | |
5 | 对指向空间已被释放的指针进行引用 | |
6 | 释放栈或静态存储区中分配的内存 | |
7 | 释放无效指针指向的空间 |
(1)数组下标访问越界。
在对数组进行访问时,如果用非法的下标访问数组之外的不可靠数据以及写入通过非法的下标访问数组得到的内存空间,将会破坏程序执行时的堆或栈空间,从而导致程序执行逻辑被破坏而引起运行时错误。
- 字符串缓冲区没有添加相应的结束符'\0'
当程序员在操作C字符串对象时,如果没有对给定的缓冲区末尾使用‘\0’正确结尾,则当程序中使用C标准库函数读写字符串时,将出现运行时错误。
(3)对未初始化的指针进行引用
在C程序中,每一个局部指针变量在定义时如果没有执行初始化操作,则该指针的指向地址不可预知。如果此时对该指针执行引用操作,则程序就可能由于访问地址非法而引发运行时错误。
(4)对空指针进行引用
当程序员将某个指针变量初始化为空值时,如果程序的后续代码对其进行引用,则程序必然会由于访问非法地址而导致运行时错误。
(5)引用指向空间已被释放的指针
指针初始是指向一个有效的空间,然后代码流程中会释放这段空间,在这之后,如果代码接着对该指针所指向空间进行引用,此时程序将由于访问非法地址而出现不可预知的问题。
(6)释放栈或静态存储区中分配的内存
释放无效指针,可能会导致程序在运行期间发生无法预知的问题。
(7)释放无效指针指向的空间
释放无效指针,可能使程序在运行过程中出现不可预知的问题。
-
-
- 与函数调用配对相关的安全漏洞
-
与函数调用配对相关的安全漏洞 | 1 | 申请与释放资源的类型不一致 |
2 | 未申请资源却释放 |
-
-
-
-
-
-
-
- 与函数调用配对相关的安全漏洞
-
-
-
-
-
-
配对关系
C语言中大量出现在对各种C中不可自动回收的资源的控制函数中。一般一类资源的操作集合中都有函数用于创建资源,以及另外的函数会销毁资源。当程序员需要使用这些资源时,他们必须严格按照“先创建资源,后释放资源”原则来开展程序编写,才能够确保代码的合理性。这一类资源控制函数当中,最为熟悉就是C语言内存分配函数,即malloc()以及free()。但除此之外,C语言中还存在许多类似的、非常常用的函数配对,文件操作接口fopen()和fclose()等。另外,在实际工程项目中,还存在许多本身不属于标准库的一部分但应用也非常广泛的函数,如网络套接字接口socket()和close()等。
- 申请与释放资源的类型不一致
C语言中资源在释放的过程中需要函数进行释放的行为。如果程序中释放与申请的函数不匹配,就可能发生错误。
(2)未申请资源却释放
在C中,当程序需要对不可自动释放的程序资源进行操作时,程序中通常需要使用一类句柄对象对其进行记录。例如当程序中可以使用指针记录动态分配的内存资源。如果程序中某个句柄没有正确指向合法的资源,而程序中又调用对其进行资源释放操作,则程序执行时程序就可能发生无法预测的错误。
-
-
- 与类型匹配相关的安全问题
-
与类型匹配相关的安全漏洞 | 1 | 带符号数以及无符号数相互之间执行类型转换 |
2 | 不同数据类型进行转换造成数据精度损失 | |
3 | 由于变量数据表示范围有限引起的运算上溢出或下溢出 | |
4 | 由于把小类型赋值给大类型造成的溢出 | |
5 | 对有符号整数进行右移操作 |
-
-
-
-
-
-
-
- 与类型匹配相关的安全漏洞
-
-
-
-
-
-
(1)带符号数以及无符号数相互之间执行类型转换
C中,无符号类型数据与带符号类型数据进行各种运算时都存在着类型转换问题,特别是当这些运算中存在取值为负的带符号类型数据时,这个小于0的数就会被机器解释为一个取值较大的无符号数,导致错误的结果。
(2)不同数据类型进行转换造成数据精度损失
C中不同类型数据之间进行相互转换(隐式或显式转换),特别是宽类型向窄类型进行转换就有可能引起精度损失。特别是如果程序中如果出现多种类型数据进行混合运算,则就可能在计算过程中得到不准确的计算结果。如果程序代码中错误地假设其精度保持不变,就可能造成程序逻辑错误而引发安全漏洞。
(3)由于变量数据表示范围有限引起的运算上溢出或下溢出
当一个变量的取值较接近其类型能够表达数的上限或下限时,程序中对该变量的有关运算结果可能会超出该变量类型所能表示的范围,造成数据上溢出或下溢出。这时该变量表示的数值将程序员的期望不符。当后续代码中需要对该变量进行操作时,就可能由于数据错误而引发安全漏洞。
(4)由于把小类型赋值给大类型造成的溢出
当在整形表达式比较或者赋值计算之前,如果一方是类型比较大的数而另一方是类型比较小的数,那么有可能数据类型比较小的一方在将运算结果赋值给类型较大的数据之前,已经发生溢出,致使最终的数据发生错误。
(5)对有符号整数进行右移操作
对有符号整数进行右移操作,由于涉及到最高位的符号位,因此移位的效果可能和程序员编写代码时的预想效果不同。
C 标准库中的危险函数
此类C函数属于C标准库中最危险的一类函数,它们均满足以下条件中的若干条:
(1)对一个外部传入的内存区块执行写入操作;
(2)没有提供任何额外的参数传递,保证写入的数据大小能够被限制在一定的范围之内。
(3)对内存中数据的挪动造成指针失效。
(4)在安全关键程序中生成伪随机数。
-
-
- C 标准库中的危险函数
-
C 标准库中的危险函数 | 1 | 数据量不确定的无限制缓冲区操作函数 |
2 | 容易造成内存越界的函数 | |
3 | 数据来源确定但未给出限制的缓冲区操作函数 | |
4 | 对内存的重新分配使指针失效 | |
5 | 在安全关键程序中生成伪随机数函数并使用 | |
6 | 不能超过缓冲区大小的函数 | |
7 | 需手工指定目的缓冲区大小的函数 | |
8 | 与fwrite()函数相关的安全漏洞 | |
9 | OS函数的参数漏洞 | |
10 | 与alloca()函数相关的漏洞 | |
11 | 与malloc()函数相关的漏洞 |
-
-
-
-
-
-
-
- C标准库中的危险函数
-
-
-
-
-
-
(1)数据量不确定的无限制缓冲区操作函数
此类函数的共同特征是:其运行时需要操作的源数据量不可知,并且也不要求程序员指定目的缓冲区大小。此类函数的典型例子是C标准库中的gets()或sprintf()等。由于程序员在使用此类函数时无法预知源数据的数据量,也无法指定缓冲区大小,因此当给定的缓冲区大小无法容纳实际读取的数据时,就可能引发缓冲区溢出错误。
(2)容易造成内存越界的函数
在C标准库中存在一组内存复制操作函数,如memcpy()、memmove()等,用于专门对内存缓冲区进行直接读写操作。此类函数通常要求三个参数:目的缓冲区首地址、源缓冲区首地址以及大小。当程序员传入参数时,如果两个缓冲区首地址指针指向同一块缓冲区的不同部分时,在实际操作时就可能由于地址重叠而造成缓冲区越界错误。
(3)数据来源确定但未给出限制的缓冲区操作函数
此类缓冲区操作函数的共同特征是:此类函数中可以正确获得的需要处理的源数据量,但无法确定目的缓冲区的大小。当编码人员给定的目的缓冲区大小无法容纳最终生成的数据时,程序就可能发生缓冲区溢出错误。
(4)对内存的重新分配使指针失效
当用realloc函数重新对之前分配的堆内存进行扩充时,可能会使之前分配的内存区域被挪动,造成之前指向该内存区域的指针失效,当对这些指针进行操作时可能引发程序崩溃或者运行结果错误。
(5)在安全关键程序中生成伪随机数函数并使用
使用一些伪随机函数生成随机数,如果该随机数被应用到安全方面的话,将会很容易被破解,对系统构成危害。
(6)不能超过缓冲区大小的函数
此类函数在函数原型中通常要求程序员指定目的缓冲区以及缓冲区大小。但其自身可能输出的数据大小无法预知。当程序中实际输出的数据量大于缓冲区能够容纳的数据量时,此类函数会将得到的输出结果截断,从而使得程序员得到的信息不完整。当程序员没有对输出不完整的情况进行判断时,后续代码就可能由于数据信息不完整而造成程序逻辑错误,从而引发安全漏洞。
(7)需手工指定目的缓冲区大小的函数
在C中存在大量的缓冲区操作函数,其允许程序员使用时制的规缓冲区首地址和一个用于记录缓冲区大小值的参数。但是此类函数原型中没有能力保证得到的缓冲区参数能够真正反映得到的缓冲区大小。如果程序员指定的缓冲区的实际大小与给定的大小不符,则程序中就可能发生内存越界访问错误。
- 与fwrite()函数相关的安全漏洞
当程序中使用fwrite()函数从源地址向目标文件写入数据时,如果给定的数据块实际大小与给定的表示缓冲区大小的变量取值不符,就可能将将源地址空间以外地址的东西输出到文件中(给定的缓冲区实际大小小于指定值)。
- OS函数的参数漏洞
当利用系统调用如system或者popen执行shell命令时,如果参数未经检查就直接运行的话,很可能会发生命令注入的情况。
- 与alloca()函数相关的漏洞
alloca()函数在栈上分配空间,可移植性低,当调用它的函数执行完毕,空间自动被释放,此时如果返回之前分配的栈空间地址,一旦该空间被使用,程序行为将发生错误。
(11)与malloc()函数相关的漏洞
未经初始化的内存中的内容是随机的,如果被直接使用的话,就可能造成程序运行结果不正确。所以malloc分配空间后需要用memset()进行初始化,以避免误用还未被赋有效值的空间。
-
-
- 与C程序控制流相关的安全漏洞
-
switch-case结构中缺少break、default语句;
-
-
- 文件输入输出安全
-
-
-
-
- 文件输入输出安全
-
-
-
-
- 文件输入输出安全
-
文件输入输出安全 | 1 | 必须使用int类型类接收字符输入/输出函数的返回值 |
2 | 创建文件时必须显示指定文件访问权限 | |
3 | 对文件路径标准化并检查 |
(1)必须使用int类型类接收字符输入/输出函数返回值
用fgetc(),get(),getchar(),fput(),putc(),putchar(),ungetc()等函数进行I/O操作时,函数返回值用char类型接收,造成识别EOF的不确定性。fgetc(),get(),getchar(),fput(),putc(),putchar(),ungetc()这些函数的返回值必须设定为int型。
- 创建文件时需要显示指示文件访问权限
当用open()函数打开或者创建文件时,没有指定第三个参数,可能使得被创建的文件被不应该有权限访问的用户访问。建议使用open()函数式必须有第三个参数。
- 对文件路径标准化并检查
对于来自不信任源的文件路径,如果直接使用,用户使用../操作可以访问到本来没有权限访问的文件,对系统的安全造成威胁。如果直接使用用户输入的路径,那么无法保证用户合法的访问文件,比如用户可以使用../无限制的返回上级目录,进而操作本不该被其访问的文件,对系统运行可能造成损害,违反了最小权限原则。
对于来自污染源的文件路径,需要将其用realpath()函数转换成绝对地址,并且需要验证该路径的合法性,保护文件安全。
-
-
- 敏感信息隐藏相关漏洞
-
敏感信息隐藏相关漏洞 | 1 | 禁止在日志中保存口令,秘钥 |
2 | 基于哈希算法的口令安全存储必须加入盐值 | |
3 | 尽量不要在共享目录中创建临时文件 | |
4 | 尽快清储存在可重复利用资源的敏感信息 | |
5 | 尽量不要硬编码敏感信息 |
-
-
-
-
-
-
-
- 敏感信息隐藏相关漏洞
-
-
-
-
-
-
- 禁止在日志中保存口令,秘钥
将敏感信息直接写入到日志中,造成敏感信息的泄漏。将造成信息泄漏,影响系统安全。一般不允许在日志中保存口令,秘钥。不打印在日志中,明文或密文,若必须打印用“*”代替。
- 基于哈希算法的口令安全存储必须加入盐值
在密码中混入一段盐值再进行哈希加密,由于随机化了哈希值,提高了数据的安全性。
- 尽量不要在共享目录中创建临时文件
在共享目录中创建临时文件,会被其他人员利用,造成隐患。
(4)尽快清储存在可重复利用资源的敏感信息
堆,栈,数据段,数据库的映射缓存,使用完成后必须覆盖清除。如果堆,栈,数据段,数据库的映射缓存,使用完成后不进行覆盖清除,会造成信息泄露。
(5)尽量不要硬编码敏感信息
硬编码敏感信息造成信息泄漏。
-
- 安全规则的设计
安全分析器分析的主要是程序中的变量、操作、函数等等。与目标变量相关的代码会被搜索,安全分析器可以绑定目标变量的状态,而且可以根据对源程序的分析对其绑定的变量状态进行迁移。分析器同样可以通过虚拟变量绑定相关状态来分析与源代码变量不相关的动作。
变量状态转移的规则的集合就构成了状态机安全概念描述。转移规则包含:
(1)变量的起始状态。
(2)模式匹配,模式中的代码与源代码向对应。
(3)变量的目的状态。
代码如果具备一定的条件,变量才能产生相应状态转移,这包括:
(1)安全分析器中转移规则中模式被源程序匹配到。
(2)模式所属规则中所声明的变量类型与相对应的源代码变量的类型相等;但是如果与源代码变量类型无关,就不用考虑类型问题。
(3)转移规则起始状态与源程序变量当前状态等同。
当源程序一同符合这三个条件时,变量就会依据转移规则转移为目的状态。转移规则是编码人员根据需要进行定义的,变量转移为不安全时就可以输出提示消息。
本节列举两个例子以更加形象的说明状态机的配置。图3.3为被释放指针的解引用以及二次释放的定制化规则的语法描述。
图3.3 free安全规则
图3.4为检查lock( )以及unlock( )配对与否的安全规则描述。
图3.4 lock配对安全规则
-
-
- 变量
-
这里变量是编码人员在描述安全规则时所使用的变量。又包括三种不同的类型依次为特定变量、虚拟变量以及辅助变量等类型。
(1)特定变量
特定变量指在模式中代表的安全规则所重点关注的源代码的变量,它可以根据情况发生相应的状态绑定以及状态转移行为。特定变量同时存在标识符以及变量类型。如图3.3中第一行所示的变量w,w在安全规则中其实不是单独指某个源代码的变量,而表示的是类型相同的同类变量。
(2)辅助变量
特定变量并不能与源程序中特定的行为操作匹配,于是设计了辅助变量来对这些操作进行匹配,这样就能够更加完整的表示模式。在安全规则中,辅助变量并不会源程序变量开始状态绑定和转移的相应操作。辅助变量是没有状态值的,因为辅助变量在模式匹配中才会使用,但是并没有给这些源代码变量绑定相应的状态的必要。辅助变量可以进行初始化操作。
(3)虚拟变量
虚拟变量是用来区分与源程序代码变量没有关系的安全规则。所以它并不会代表任何源代码的变量。多个虚拟的变量可以声明在单个安全规则中。
-
-
- 状态
-
状态根据所属范围不同被划为局部状态和全局状态两类。全局状态是属于整个安全规则的状态。如free_定制安全规则中第3行的start,它是整个安全规则,就是全局状态。可以将全局状态的状态值视为该状态域的属性。如图free安全规则中第3行的v.free为局部状态的表示形式,局部状态是状态变量以及状态值由点隔开形式表示。
-
-
- 模式
-
模拟源程序是用模型代码来进行的,它在转移规则中的表现形式在安全规则中就是模式,就是括号中的状态机描述。模式表现了源代码的相关操作,用于识别源程序的有规律的操作。可以识别如变量声明,表达式计算等等代码。模式概括了源代码中需要跟踪的行为,使用起来比较简洁。
模型代码就是用来配对源程序中相应格式,例如free检测中的{ *v }就是用来匹配*,并且模式变量v的类型pointer就是被解引用变量的相对应类型,这时,匹配即成功。
-
-
- 转移规则
-
转移规则即指在定制化安全规则中代表了变量的状态进行转移的条件。
在安全分析过程进行时,首先检测源程序的抽象语法树的子树的起始状态如果匹配全局状态中转移规则的模式。那么就对该变量状态绑定。接着,变量所有实例,判断安全规则中是否存在一种转移规则是起始的状态,然后确定运行那一规则。对于相同的源程序片段,如果通过模式匹配成功运行规则R1并进行了绑定,如果规则R2与R1有一样的模式。并且R2的起始状态与状态值都是此状态变量实例当前的情况,此时状态不会通过R2执行迁移。状态机的活动图如下:
-
-
-
-
-
-
-
-
- 状态机活动图
-
-
-
-
-
-
-
-
- 安全规则描述语言的设计
安全规则的描述语言目的是能够准确并且尽量能够完全的解释安全规则的形式。本节从语法规则以及词法规则的方面来对安全规则描述语言展开讨论。
词法规则
定制化安全规则描述语言中共有下列几种符号:常量符号、标识符号、分隔符以及运算符。还需要有标识符,它指的是由字母以及数字构成。标识符的首个字符必须是下划线或者字母。大写字母和小写字母是不同的。标识符可以为任意长度。有些标识符被用作关键字,这类符号不能用做其他用途。如下列标识符:declare、error、start、sm、stop、patternn、还有C语言中的一些类型double、int、long、char、float、 short、unsigned、signed。同样需要有常量类型,包括了整型类型、字符类型、枚举多种常量类型。
安全规则在定义时同时也借用了c语言中的运算符,这样安全规则就会比较容易让编码人员定义,并且同时对源代码特征也提供了准确的模拟。所以,安全规则描述语言同时支持乘除求余运算符(*、/、%),一元运算符(&、*、+、-、~、!),加法类运算符(+、-),关系运算符(<、>、<=、>=),赋值运算符(=、*=、/=、%=、+=、-=、<<=、>>=、&=、^=、|=),移位运算符(<<、>>),相等类运算符(==、!=),逻辑运算符(&&、||),逗号运算符(,)等。
-
- 状态机模式语法
本文的主要内容之一便是设计状态机模式的文法,主要用于描述C语言中的各种语法结构,这样就能够更精确检测源程序的安全问题,本文会对模式的结构作为重点进行介绍,但是由于模式设计结构较复杂,在此只能提炼出一些重要的部分进行介绍,下面对模式文法进行相应的介绍。
(1) 状态机定义 example.sm check_free { …. }
语义设计:首先识别关键sm,然后是状态机名称,状态机的成员变量的声明,状态机迁移的声明,同时在状态机名称填入数据结构_SM中。
(2) 状态机变量声明 decl any_expr a;
语义设计:识别变量的声明关键字decl,变量的类型,变量名称,赋初始值是可选的。
var_declaration SEMICOLON
(3) 状态机模式的声明eg. q = &v
语义设计:识别变量的声明关键字decl,变量的类型,变量名称,赋初始值是可选的。
pattern_specifiers pattern_def SEMICOLON
(4)状态机迁移的声明eg. start: { q = &v} =>p. tainted
语义设计:识别start(每个状态机的第一条迁移),状态机的边,状态机的末状态,然后将其关联起来填入_SM中
START COLON substruct SEMICOLON
状态机迁移的声明eg. start: { p = &r } =>p. tainted
语义设计:识别状态机的初始状态,状态机的边,状态机的末状态,然后将其关联起来填入_SM中。
b_state COLON substruct SEMICOLON);
var_declare
:
(5) 状态机迁移变量的声明 decl any_pointer v,m,n;
语义设计:识别变量的声明关键字decl,变量类型,赋值(=)是可选的。当然可以识别连续声明结构,需要将变量的类型,值填入_SM结构中。*/
(declare_ var_specifier
( variable_declaration
(ASSIGNEQUAL
(
DECIMALINT
| ID
)
)?
(COMMA)?
)*
);
substruct
:
(6)此产生式是用来识别状态机的边和末状态。
eg. {*p}=>p.Error2 {Msg("非法使用野指针");}
语义设计:识别状态机的模式,末状态,以及出错信息。将其填入_SM结构
*/
(
pattern_name TRANSFER destination
( err_Info = error_expression)? (BITWISEOR)?
)+
;
destination:
(
/*
(7)此产生式是用来识别状态机的分支状态
true = x.need_lb, false = x.need_ub
语义设计:识别状态机的分支状态
*/
condition_state
|
(8)此产生式是用来识别状态机的原子状态eg. p.Freed
语义设计:识别状态机的原子状态*/
basic_state
);
condition_state
:
(9)状态机分支状态的声明 eg. true = x.need_lb, false = x.need_ub
语义设计:识别true,false分支状态*/
(
(
"true"
|"false"
)
ASSIGNEQUAL basic_state (COMMA)?
)*
;
declaration_hole_var_specifier
:
(10)自动机变量类型声明
DECL sm_type_specifier
;
declaration_pattern_specifiers
:
(11)匹配关键字pattern
PATTERN
;
variable_declaration
:
(12)匹配变量声明
ID
;
pattern_definition
:
(13)自动机模式赋值产生式
pattern_name ASSIGNEQUAL pattern_content
;
pattern_content
:
(14)expression产生式
LCURLY (expression) RCURLY
;
basic_state
:
/*
condition_state
*/
variable_declaration DOT ID
;
pattern_name_content
:
(15)匹配自动机模式以及和自动机模式相关的语义
(
( pattern_name
| pattern_content
)
( AND semantics_context)?
)
;
semantics_context
:
(16)自动机语义header
DOLLAR LCURLY semantics_expression RCURLY
;
(17) 逗号表达式
产生式如下:
expression:
assign_expr (COMMA assign_expr)*
expression,expression的语法结构即为逗号表达式,逗号表达式能够连接有若干个表达式结构。
(18) 赋值表达式
产生式如下:
assign_expr: conditio_expr
(
(SHIFTRIGHTEQUAL |SHIFTLEFTEQUAL|
……
)
remainder_expr
)?
赋值表达式主要为=、*=、=、%=、+=、-=、<<=、>>=、&=、|=、^=这些操作,并且这些操作符拥有相同的优先级。
(19) 条件表达式
产生式如下:
conditional_expr: ogical_or_expr
(QUESTIONMARK expr COLON conditional_expr )?
条件表达式是形如expression?expression1:expression2的语法结构,当条件为真时,执行的expr1,条件为假时执行expr2。
(20) 逻辑或表达式
产生式如下:
logic_or_expr:
logic_and_expr (OR logic_and_expr)*
逻辑或表达式,如 cond1 || cond2
语义设计:构建以OR为跟节点的树,当然OR的左孩子可以是以OR为跟节点的树,可以递归。该产生式对应的树遍历框架则是:
LogicOrExprTree:
LogicAndExpreTree
|#(OR LogicOrExprTree LogicAndExprTree)
树的搜索则是左递归,相比建树行为是一个逆向的过程。
(21) 逻辑与表达式
产生式如下:
logic_and_expr:
inclusive_or_expr (AND inclusive_or_expr)*
逻辑与表达式是如conditU&& conditV的结构。当且仅当conditU以及conditV都为真时时候这是表达式才可以为真。
逻辑与表达式,如 cond1 && cond2
语义设计:构建以AND为跟节点的树,当然AND的左孩子可以是以AND为跟节点的树,可以递归。该产生式对应的树遍历框架则是:
LogicAndExprTree:
InclusiveOrExprTree
|#(AND LogicAndExprTree InclusiveOrExprTree)
树的搜索则是左递归,相比建树行为是一个逆向的过程。
(22) 位或表达式
产生式如下:
inclusive_or_expr:
exclusive_or_expr (BITWISEOR exclusive_or_expr)*
位或表达式就是如expression | expression 的结构。主要用于所有bit位的或操作。
(23) 异或表达式
产生式如下:
exclusive_or_expr:
and_expr (BITWISEXOR and_expr)*
异或表达式是如expr ^ expr的结构。主要用于所有bit位的异或操作。
(24) 位与表达式
产生式如下:
and_expr:
equality_expr (AMPERSAND equality_expr)*
位与表达式是如 exprression & exprression的结构。主要是用于所有bit位的与操作。
(25) 判等表达式
产生式如下:
equality_expr: relational_expr
( (NOTEQUAL | EQUAL) relational_expr)*
判等表达式主要包括 ! =、==两操作符,主要是用来判等。
*equality_expr: 判等表达式,如a==b, 结果为布尔值
注意:如果有多项关系表达式进行判等操作,从左至右进行计算,等到的结果(布尔值)再与下一表达式进行操作
例:
if(a==b==c){...} //将先计算a==b,结果为true或者false,再将该结果与c比较。构建以EQUAL(NOTEQUAL)为跟节点的树,当然EQUAL(NOTEQUAL)的左孩子可以是以EQUAL(NOTEQUAL)为跟节点的树,可以递归。该产生式对应的树遍历框架则是:
EqualityExpressionTree:
RelationalExpressionTree
|#(NOTEQUAL|EQUAL,
RelationalExpressionTree,
RelationalExpressionTree)
树的搜索则是左递归,相比建树行为是一个逆向的过程。
。
(26) 关系表达式
产生式如下:
relational_expr: shift_expr
((|GREATERTHAN| LESSTHAN |OREQUALTO
|GREATERTHANOREQUALTO)
shift_expr
)*
关系表达式中主要为<、>、<=、>=等操作符,关系表达式是用来判断值的大小的。
关系表达式: 判断各类不等关系(如大于,小于等于等),如 a<=b, 结果为布尔值,若多项表达式进行大小比较,从左至右计算,得到结果(布尔值)再与下一项表达式比较.
例:if(a<b<=c){...} //先计算a<b,结果为true或false,再将结果与c进行比较
语义设计:构建以LESSTHAN(……)为跟节点的树,当然LESSTHAN(……)的左孩子可以是以LESSTHAN(……)为跟节点的树,可以递归。该产生式对应的树遍历框架则是:
RelationalExpressionTree:
ShiftExpressionTree
|#( GREATERTHAN |LESSTHAN |LESSTHANOREQUAL
|GREATERTHANOREQUAL
ShiftExprTree
ShiftExprTree)
树的搜索则是左递归,相比建树行为是一个逆向的过程。
(27) 移位表达式
产生式如下:
shift_expr: additive_expr
((SHIFTLEFT|SHIFTRIGHT)additive_expr)*
移位表达式包括<<、>>等操作符,主要是针对位进行操作的。
(28) 加减表达式
产生式如下:
additive_expr: multiplicative_expr
((PLUS|MINUS)multiplicative_expr)*
加减表达式包括+、-等操作符,算术表达式主要是对数字进行加、减等操作。算术表达式拥有左结合性。
加减表达式语义:构建以PLUS(MINUS)为跟节点的树,当然PLUS(MINUS)的左孩子可以是以PLUS(MINUS)为跟节点的树,可以递归。该产生式对应的树遍历框架则是:
AdditiveExpressionTree:
MultiplicativeExpressionTree
|#(PLUS|MINUS
AdditiveExpressionTree
MultiplicativeExpressionTree)
树的搜索则是左递归,相比建树行为是一个逆向的行为。
(29) 乘,除,求余表达式
产生式如下:
multiplicative_expr:
pm_expr ((STAR|DIVIDE|MOD) pm_expr)*
乘,除,求余表达式主要指的是*、 /、|等操作符,乘、除、取模具有左结合性,主要是针对数字进行乘、除、取模操作。
乘,除,求余表达式语义:构建以STAR (……)跟节点的树,当然STAR (……)的左孩子可以是以STAR (……)为跟节点的树,可以递归。该产生式对应的树遍历框架则是:
MultiplicativeExpressionTree:
PmExpressionTree
|#(STAR|DIVIDE|MOD
MultiplicativeExpressionTree
PmExpressionTree)
树的搜索则是左递归,相比建树行为是一个逆向的行为。
(30) unary产生式
产生式如下:
unary_expr:
(postfix_expr
|(NOT|STAR|PLUS) unary_expression
……
)
unary产生式主要是用来识别前缀的++、--、~、*、+、-、!、&操作,以及sizeof、new、delete表达式。
(31) 后缀产生式
产生式如下:
postfix_expr:
(simple_type_specifier LPAREN RPAREN LPAREN
(expression_list)? RPAREN
|primary_expr
……
)
后缀产生式是用来识别函数调用结构、数组结构、后缀的++、--等。
(32) 主表达式
产生式如下:
primary_expr:
(id_expr|constant|"this"| LPAREN expr RPAREN)
主表达式是用来识别常量、ID、有左右括号的形式、this关键字等等。
以上模式文法,可匹配C基本的语法,其中,产生式如果靠近文法入口,它的优先等级就越低。产生式远离入口,操作符那么优先级就高。
-
- 安全规则数据结构的设计
安全规则解析器会解析规则的形式,然后安全规则会以一定的结构形式存储在相应的结构中,用来给之后的解析提供规则。其数据结构就可以代表状态、模式、转移规则等意义,本节将探讨有关安全规则的数据结构的设计。
-
-
- 状态
-
状态是安全规则中的主要内容之一。每个状态变量实例都具有状态,它们的
意义与所具有的功能也有相应的差别。
-
-
-
-
-
-
-
-
- 状态相关的类图
-
-
-
-
-
-
-
图3.6是与状态相关的类图。特定变量所代表的源程序的变量。它可以表示许多类型的变量,比如一些基础类型,或者指针类型,各个作用域的变量等等。
state
state用于表示状态的基础类。specificr_name为安全规则中其状态本身的值,而litera为状态值。例如,在状态v.freed中,specific_ name的值为v,litera的值为freed。
Global_State
Global_State为全局状态枚举类。全局状态目前只有start_状态,不过全局状态还可以增加更多。
vir_var_state
类vir_var_state所表示的是虚拟变量实例。其成员sta是个state对象指针,is_Checked是一个判断标志,是为后面进行状态转移前检测所用。
Point_
Point_是用来表示转移规则起始状态以及相应的状态。gtype是枚举类全局状态,现在只有start_状态,不过全局状态还能够添加。
模式
模式表现了源代码的相关操作,用于识别源程序的有规律的操作。可以识别如变量声明,表达式计算等等代码。
-
-
-
-
-
-
-
-
- 模式相关的类图
-
-
-
-
-
-
-
图3.7中的类OperatorKind可以分别运算符模式中更细情况的枚举类。
Function、Operator和Array是pattern的派生类。在Function里,funcName为函数名称,args类型是list<string>,表示函数的参数列表。在Operator_中,operator_e表示的是运算符的字面量。Oper_为运算符的类型。在Array中,array_表示数组名,index表示数组下标。
因为运算符模式还可以细分为三个子类型,所以由Operator又派生出UnaryOper、BinaryOper和ComplexOper三个子类。在一元运算符Unary_Oper_中,operand为操作数。在二元运算符模式Binary_Oper_Pattern中,operands表示操作数列表,类型为list<string>。
转移规则
转移规则的表示形式如下:“起始状态:模式=>目的状态”。由于开始状态不同,全局开始状态start_以及状态变量开始状态nonstart_,并由枚举类Transit_Kind来区分。
由于目的状态的不同类型,转移规则也能够分成普通类型以及条件类型两种类型。普通类型记为common_tran;条件类型,记为condition_tran。可以用枚举类Transition_Type来区别。即模式是条件判断时,那么这时转移规则就会有true_以及false_两个状态。
-
-
-
-
-
-
-
-
- 转移规则相关的类图
-
-
-
-
-
-
-
图3.8是转移规则的类图。在transition中,patern为模式,source_为起始状态,error_为提示信息,tran_为具体类型。tran_kindw为起始状态所划分的相应类型,carerAlia_别名判断的标志。
Common_和Condition_是从transition派生出两个子类。其中,Common_是普通类型,destination_表示目的状态;Condition_是条件类型,destination_state_表示true分支的目的状态,destination_state_表示false分支的目的状态,true_path_为可以控制流图路径上检测解析过与否的标识。假设true_path_值为false,那么就会跟着true分支进行解析,并且同时会更新变量为destination_state_;如果其值为true,则沿着false分支分析,并更新变量状态为destination_state_false。
-
- 本章小结
本章首先对项目进行了简要的需求分析,统计并总结了C语言中常见的漏洞以及项目需要检测的漏洞形式。接着根据漏洞的主要形式对安全规则进行了设计,
安全规则分析的主要是程序中的变量、操作、函数等等。这样就对需要检测的代码模式进行了对应。本章利用状态机的形式设计了安全检测模式语法。最后对安全规则的数据结构进行了讨论。
- 状态控制的设计与实现
安全规则定义好之后,安全分析器就可以利用安全规则作为依据,利用安全规则与源程序的匹配程度,就可以检测出代码中的漏洞。安全分析器作用就是在安全分析器运行安全检测时,由输入的外界安全规则针对变量实施相应状态绑定。在源程序分析过程里变量状态会根据安全规则进行相应转移。在分析过程中提示信息会在变量状态转移成为不正常的时候出现。本章主要内容就是讨论有关状态转移处理器详细设计方案以及实现。
-
- 状态转移处理器的总体设计
数据流分析时会进行控制流图搜索,即会被安全检测器检查每一条分支。控制流图的任意一条路径以及节点会被安全分析器根据遍历的整体框架进行模式的匹配,检测该节点源程序AST是否与安全规则中模式相互匹配。如果与模式相匹配,相关变量会通过状态转移处理器来执行相应状态绑定以及转移等相关操作。控制流图总框架算法由算法4.1来讨论,算法4.3讨论了状态处理器相关的总体的设计问题,算法4.3利用了算法4.2的有关匹配算法。
算法4.1控制流图的总框架算法
输入:数据流分析得到的路径信息
输出:基于状态机检查的漏洞错误报告
方法:
For(路径管理器中的每条路径f)
for(单条路径上的每个节点k)
for(状态机上的每个规则 r)
规则r和节点k进行匹配,做相应的状态的迁移处理
同时,报告漏洞错误。
end for
end for
end for
-
-
-
-
-
-
-
-
- 状态机检查框架
-
-
-
-
-
-
-
-
- 同构判断介绍
- 表达式结构的匹配
- 同构判断介绍
默认情况下每个产生式的第一个参数是一个模式的AST,第二个参数是源代码执行语句的AST。产生式调用的过程是不停展开并且匹配子树的过程。所有有效匹配时终结符和叶子节点相互匹配。仅当2个AST叶子节点和中间终结符都匹配完成时才算2个AST是同构。因为树遍历器基于antlr,产生式第一个参数在默认的情况下传入的是第一个AST结构的参数,因此该遍历框架其实都是跟着第一个参数在进行遍历,所以只需要判断第二个参数是否符合中间节点,如果符合就获取子树,继续往下匹配,如果不符合,则返回false,表明这2个AST结构不是同构的。
(1)入口开始调用时候是expressionTree(pAST,sAST);pAST是指模式的ast,sAST是源代码的ast,默认情况下传入的第一参数是隐式的。
expressionTree [RefMyAST sAST]
(2)语义:判断sAST节点是否是Expression类型的,如果是则获取当前sAST节点的孩子ast,作为参数传入commaExpressionTree,否则返回false。
#(Expression commaExpressionTree);
commaExpressionTree[RefMyAST sAST]:
(3)语义:如果当前模式的AST节点是COMMA类型的,则判断sAST 的根节点是否是COMMA类型的,如果是则获取当前sAST节点左 右孩子的ast,作为参数传入commaExpressionTree和assignmentExpressionTree,否则返回false。如果当前模式的AST根节点不是COMMA类型的,则直接将sAST作为参数传入assignmentExpressionTree。
( #( COMMA commaExpressionTree assignmentExpressionTree)
| assignmentExpressionTree);
assignmentExpressionTree[RefMyAST sAST:
(4)语义:如果当前模式的AST节点是ASSIGNEQUAL(……)类型的,则判断sAST的根节点是否是ASSIGNEQUAL(……)类型的,如果是则获取当前sAST节点左右孩子的ast,作为参数传入conditionalExpressionTree和remainderExpressionTree,否则返回false。如果当前模式的AST根节点不是ASSIGNEQUAL(……)类型的,则直接将sAST作为参数传入assignmentExpressionTree。
(conditionalExpressionTree
|#(ASSIGNEQUALconditionalExpressionTree remainderExpressionTree)
……
remainderExpressionTree[RefMyAST sAST]:
(5)语义:直接将sAST作为参数传入assignmentExpressionTree
assignmentExpressionTree;
conditionalExpressionTree[RefMyAST sAST]:
如果当前模式的AST节点是ConditionalExpression类型的,ConstExpression类型, OR类型, AND类型, BITWISEOR类型, AMPERSAND类型, EQUAL(NOTEQUAL)类型, LESSTHAN (……)类型, SHIFTLEFT (SHIFTRIGHT )类型,PLUS(MINUS)类型, STAR (DIVIDE,MOD)类型, DOTMBR (POINTERTOMBR)类,TypeCast类型, UnaryOperator类型等等,判断方法与上面相同。
算法4.2:模式匹配算法
输入:AST和安全规则Q
输出:集合N:包含了转移规则P以及源代码的变量列表L
方法:
begin
if AST不为空
then
for(安全规则Q)
AST与当前转移规则P的模式进行匹配。
if匹配成功then
集合N加入P和P的模式中代表的源代码变量列表L元素。
else break;
end for;
返回N
end;
算法4.2是用来进行模式匹配的算法框架。算法4.2输入AST指源程序的AST,它存储在控制流图节点结构。安全规则Q是通过外部输入的安全规则经过解析器处理得到。匹配成功之后,模式里特定变量代表的源代码L以及其所属的转移规则P的集合N会作为输出。N中元素数目不唯一,因为模式可以匹配大于等于一条转移规则。如果模式的模型程序中有若干特定变量,则返回的就是源代码的变量列表。
算法4.3:安全分析器检测源代码中的漏洞
输入:安全规则Q、控制流P以及虚拟变量的实际变量V
输出:警告信息
方法:
begin
for(P中的路径)
沿着p开始遍历路径上的每个结点
for(路径p中的结点)
分析源程序相应的AST
if该结点中的AST不为空
then
安全规则Q以及AST可以为算法4.2的输入,能够检测输出S。
if S中元素数目为1
if P以及源程序的列表L都不是空那么then
if P是start_tran类型then
可以依据P,为L中的源代码的变量绑定相应的状态;
设定源代码变量状态值是P目的状态其相应状态值;
输出P中的警告信息。
else if P是nonstart_tran类型then
检查源代码变量的相应类型是否与P的开始状态的特定变量相兼容。
if 兼容then
这时将L中源代码变量状态值变为P目的状态所指示的该变量域内的状态值。
输出P中的警告信息。
else
break;
else if 转移规则P中不空,可是源程序列表L是空then
if P是start_tran类型then
生成虚拟实例并添加到集合中。
输出P中的警告信息。
else if P是nonstart_tran类型then
从V中,检索和P初始状态相同虚拟变量;
将P的目的状态的状态值变为该虚拟变量的状态值;
输出P中的警告信息。
else if S中元素多于一个then
搜索目前关注所有源代码变量以及虚拟变量的实例。
if有一条转移规则匹配
then根据该转移规则进行状态转移。
else即该结点中的AST为空
break;
end for;
end for;
返回生成的所有警告信息。
end;
算法4.3检测源代码的类型与P状态的变量相关类型相同与否以及相容。即为两个变量的类型相同,而相容意味着其为一种类型为另一种的子集。在算法4.3中状态转移的控制与模式的匹配是重点。本章在随后将详细讨论状态转移控制以及模式匹配的详细设计实现。
-
-
- 类MingledSymbolItem的设计
-
该类表示变量的符号条目。
MingledSymbolItem派生自SymbolItem类,实现了SymbolItem的接口,可以许多有关符号的相关信息。
符号表用Symbol_Item来表示,项目前端将每个变量与一个Symbol_Item相对应。可以依据数据流分析的Mingled_Symbol_Item来表示对象引用其成员变量时数值。其实Symbol_Item*构成的队列就是Minglede_Symbol_Item,它是利用双端队列来进行存储,比如符号b.i是队列来保存b和i这些符号表的。
专为源代码变量设计的一个状态是类Attach_To_Symbol_Item,该类的成员变量有:
bool is_ItSelf;
bool is_Checked;
bool is_CurrentReturned;
Mingled_Symbol_Item mingled_si;
map<SM*,state*> sm_list。
其中,isItSelf表示是否是该符号条目本身的状态;是否已经经过状态的绑定或转移是通过is_Checked来标记的。是否为当前模式匹配时返回的源代码变量用is_CurrentReturned来标记。假如is_ItSelf值是false,也就意味着这不是该变量本身的状态,这时将利用mingled_si来表示所属变量的变量标记。否则,mingled_si就为空。上文设计的成员是为了使之后的状态转移时进行的的判断更加方便来设计的。map<SM*,state*>* sm_list是这个类的主要结构之一,主要用来标记变量的状态。在这个map中,SM就是定制化安全规则,而state则是在定制化安全规则中的变量所属的状态。设计成map原因是不同的安全规则中,同一变量可以包含若干个状态,并且状态也不会相同。
每个源代码变量可能拥有它自身所属状态,此成员变量同样可能拥有状态,这些状态都要加于该变量所属符号上。这就需要一个状态链表list<AttachToSymbolItem*> state_list来表示每个状态。
-
-
-
-
-
-
-
-
- 类MingledSymbolItem的详细设计
-
-
-
-
-
-
-
如图在类MingledSymbolItem中成员sblFullName的类型为deque<SymbolItem*>这是一个双端队列用来存储完整表示符号条目的符号条目序列。而成员函数MingledSymbolItem是构造函数,根据不同的输入参数执行不同的函数,输入参数可以为:sbl, 符号条目;s, 符号条目序列;s, 符号条目完整表示。又如成员函数operator=它的主要功能是重载运算符=,使得类对象之间可以相互赋值操作。它的输入参数是r, 符号条目完整表示。它的返回值为MingledSymbolItem&,符号条目完整表示。它的作用就是将r的sblFullName赋给当前对象的sblFullName。
对于成员函数getSblFullName,它的主要功能是获得符号条目完整表示的符号条目队列。它返回的是deque<SymbolItem*>&, 这是一个符号条目队列。成员函数addSubSblItem它的功能是在符号条目队列队尾添加一条符号条目。它的输入参数为s, 即符号条目。它的作用是将s添加到sblFullName队尾。函数addSubSblItem_head的主要功能是在符号条目队列队首添加一条符号条目。
成员函数addMingleItem的功能是在符号条目队列队尾添加另一条符号条目完整表示的符号条目序列。它的输入是符号条目完整表示s。该函数将s的sblFullName添加到当前对象sblFullName的队尾。
成员函数isGlobalSymbol的功能是判断完整表示的符号条目是否为全局符号。
它可以判断sblFullName队首符号条目是否为全局符号,是则返回true;否则返回false。函数getLastSymbol的功能是获得符号条目完整表示序列队尾符号条目。它能够返回sblFullName队尾元素。函数getFirstSymbol的功能是获得符号条目完整表示序列队首符号条目。它可以返回符号条目SymbolItem*。函数getParentSymbolTable的功能是获得所表示符号条目的作用域。它能够返回sblFullName队首符号条目的作用域。函数getType可以获得所表示符号条目的类型。返回sblFullName队尾符号条目的类型。
函数setHeader可以像符号条目完整表示序列队首添加另一完整表示的符号条目序列,它将s.. sblFullName添加到sblFullName队首。函数getHeader可以获得符号条目完整表示的头部。它返回MingledSymbolItem, 符号条目完整表示。该函数返回sblFullName出去尾部一个条目后所表示的符号条目完整表示。Empty函数可以判断符号条目队列是否为空。返回的是sblFullName.empty()。函数clear的作用是,清空符号条目队列。
函数size的主要功能是返回符号条目队列的长度。它可以返回sblFullName.size()。
-
-
- 状态机状态类设计
-
-
-
-
-
- 状态机状态类设计
-
-
-
-
-
- 状态机状态类设计
-
对于SM_State类成员变量type它的类型是StateType,为状态类型函数getStateType的功能是获取状态类型。它用来获取该成员函数是获取当前对象的状态类型。而函数setStateType则可以设置该对象的状态类型。
类NormalState的成员变量pstate存储的是类型State*,为初始状态。函数getState,可以获取当前对象的状态信息。调用形式:State*getState(),它返回一个指向状态信息的指针。函数setState可以用来设置当前对象的状态信息。
类ConditionState的成员变量true_branch是State*类型表明true分支状态。成员变量false_branch也是State*类型,表明false分支状态。成员函数ConditionState
的功能是构造函数。函数getTrueState可以用来获取true分支的状态信息。返回一个指向状态信息指针,该成员函数是获取一个指向true分支状态信息的指针。
函数setTrueState用来设置true分支的状态信息。函数getFalseState可以获取false分支的状态信息。它返回一个指向状态信息的指针。该函数是获取一个指向false分支状态信息的指针。函数setFalseState可以设置false分支的状态信息。输入参数为一个指向状态信息的指针。
-
- 模式匹配的设计与实现
在抽象语法树工具提供的遍历器里增加语义动作就可以进行模式匹配。ANTLR提供对树文法和语义添加功能的支持,本节将讨论模式匹配的语义动作设计。第三章讨论了模式类型划分。本节讨论的模式匹配的类型有函数的调用的匹配、数组以及运算符等相关匹配。
-
-
- 函数调用
-
检查源程序抽象语法树是否与定制的规则中模式匹配成功就是函数的模式匹配。算法4.3描述的为模式匹配的算法,算法的语义是增加在抽象语法树parser中的相关函数调用的待选处。
算法4.4:函数调用的模式匹配
输入:AST和安全规则R
输出:源程序变量列表L以及转移规则Q
方法:
begin
for(安全规则R中的转移规则)
检测当前转移规则里模型代码为函数与否。
if 函数类型then
检查Q中的函数名与抽象语法树中函数的相关函数名相同。
if 相同then
检查Q中模式的参数是否和抽象语法树中参数列表中类型相兼容。
if 模式是any arguments,而抽象语法树中函数兼容q。即参数列表至少有一个参数then
返回Q,就不会返回其源代码相关的变量。
else if 模式无参数,抽象语法树中函数列表为空then
返回Q,不返回源代码变量。
else if 抽象语法树中函数条目中参数和相关的模式的参数列表中参数如果相同then
逐一检查类型是否兼容。
if 均兼容then
返回Q以及模式中特定变量所指示源代码变量,生成源代码变量列表L。
end for;
end;
4.2节所有算法输出都为源程序变量列表L以及转移规则Q。Q代表与源程序的抽象语法树相匹配的模式所在的规则。若Q为空则说明无任何模式匹配源程序。列表L是模式匹配成功时的输出。
-
-
- 数组引用
-
模式匹配中数组引用即检测定制的安全规则相关的数组引用是否和源程序的抽象语法树是否相同。算法4.5描述的是数组引用的模式匹配算法。语义动作添加在了抽象语法书遍历器中关于数组引用的匹配的候选项为位置。
算法4.5:数组引用的模式匹配
输入:AST和安全规则R
输出:源程序变量列表L以及转移规则Q
方法:
begin
for(安全规则R的转移规则)
检测目前转移规则Q里,源程序所对应的模式模型程序为数组类型。
if 数组类型then
检测Q里模式的数组类型与抽象语法树中数组类型能否相互兼容。
if 类型兼容then
检测Q中模式数组下标类型Q是否与模式的抽象语法树里数组下标相同。
if 类型相同then
返回源程序变量的列表L以及Q。
end for;
end;
-
-
- 一元运算符
-
检测源程序抽象语法树是否和定制化安全规则中一元运算符相同,这就是关于一元运算符的模式相同。算法4.6就是模式匹配算法。在抽象语法树遍历器中一元运算符的待选处增加了语义动作。
算法4.6:一元运算符的模式匹配
输入:AST和安全规则R
输出:源代码变量列表L以及转移规则Q
方法:
begin
for(安全规则R中的转移规则)
将一元运算符和安全规则Q相互匹配,检测为一元运算符类型与否。
if一元运算符类型then
检测Q中模式的运算符和抽象语法树里的运算符相同与否。
if 运算符相同then
Q的模式中代表操作数的特定变量和抽象语法树里操作数类型能否相互兼容。
if 类型兼容then
返回源代码的变量的列表L以及Q。
end for;
end;
-
-
- 二元运算符
-
一元运算符模式匹配和二元运算符相类似,如算法4.7所示。
算法4.7:二元运算符的模式匹配
输入:AST和安全规则R
输出:源代码变量列表L以及转移规则Q
方法:
begin
for(安全规则R里的转移规则)
检测规则Q是否为二元运算符。
if二元运算符类型then
检测Q中、的模式的运算符与抽象语法树的运算符相同性。
if 运算符相同then
检测抽象语法树中其中左操作数的变量类型是否和左操作数相互兼容。
if 兼容then
检测抽象语法树中其中右操作数的变量类型是否和左操作数相互兼容。
if 兼容then
返回源代码变量其列表L以及Q。
end for;
end;
对于算法4.7,其模式中其二元运算符状态变量分别指示了左操以及右操作,可以为特定变量。但是只有特定变量所指示的源代码变量作为输出。
-
-
- 逻辑与、逻辑或运算符
-
逻辑与以及逻辑或属于逻辑性较复杂的,两个算符的左右两边也能够是一或二元的。逻辑或以及逻辑与运算符的模式匹配由算法4.8描述。
算法4.8:复合二元运算符模式匹配
输入:AST和安全规则R
输出:源代码变量列表L以及转移规则Q
方法:
begin
for(安全规则R的转移规则)
检测转移规则Q是否为复合形式的二元运算符。
if复合二元运算符类型then
检测如果Q中模式的运算符和抽象语法树运算符一样。
if 运算符相同then
检查抽象语法树中复合二元运算符的左边,接着根据类型通过算法进行下一步匹配。
if 函数调用类型then
执行算法4.4。
else if 数组引用的类型then
执行算法4.5。
else if一元运算符类型then
执行算法4.6。
else if二元运算符类型then
执行算法4.7。
假设抽象语法树二元运算符的左边搜索完成,再对右边进行相应的搜素。
end for;
end;
当抽象语法树不停地展开。最后就会匹配到叶子结点。这时判断源程序中叶子结点是否和模式中的叶子结点匹配成功。假设完成,那么会返true,它会一直返到PLUS_,树的同构就是指位于PLUS_的孩子们全匹配完成。算法4.9描述抽象语法树原子结构相关算法。
-
- 状态转移控制的设计与实现
算法4.3当模式匹配完成时,假如返回的模式中特定变量所指源代码的列表以及转移规则都不是空,那么将会进行转移规则起始状态的判别,接着会发生源代码变量实施相应的绑定以及迁移。假如返回的源代码变量的所在列表是空并且转移规则不是空。就会查找已存在的实例进行处理,或者在虚拟变量实例列表中增加相应的实例。
本文针对的源代码变量,需设计一个结构,标识该变量当前状态,并且添加到其在符号表中。而且可以对其状态实行相应的状态转移。如果源代码变量列表为空,就应该有虚拟变量实例集合,其中包含一或多个虚拟变量实例构成,这样就可以标记虚拟变量实例所处信息。
本节主要将讨论源代码变量以及虚拟变量实例相关的实现状态转移操作的相关算法介绍。
算法4.9:源代码的变量进行状态转移
输入:转移规则Q以及源代码变量符号表条目S
输出:源代码的变量符号表条目S
方法:
begin
从S里拿出状态链表state_list中链表头部第一个状态,并记为Sta。
if 如果Sta当前还未被检查并且需要进行状态转移,(即isChecked为false且isCurrentReturned为true)then
这时针对Sta中sm_list展开搜索,查找T为索引的映射项。
if 存在映射项then
检测其状态值是和Q中起始状态是否一样,并且将映射项记为R,。
if 相同then
判断Q的类型
if 普通类型then
将R状态值替换成Q中目的状态。
else
检查Q中的true_path_checked值。
if 为false then
用true的目的状态状态值来更换R状态值
else
用false的目的状态状态值来更换R状态值
end if;
end if;
设置isChecked为true,isCurrentReturned为false,并将Sta从链头的位置移动到链尾。
end if;
end if;
返回S
end;
算法4.9是算法4.3中实行状态转移的相关具体算法,算法4.9输入为模式匹配完成后返回的规则Q以及源代码变量符号表列表S,输出状态转移后的S。算法结束前将Sta_从链头的位置移到链尾,为使得能够任意一次从链头取出都为没有进行状态绑定的。所以看到算法中并将Sta从链头的位置移动到链尾。。
算法4.10是用来更新变量的状态的。当变量状态实施更新过程时,这时就要对符号上的条目信息进行相应的处理。当初始时,符号条目上的信息为空,程序检测时,挂在符号条目上的状态会不断地更新。
算法4.10状态的更新算法
输入:转移始末状态,模式,将要检查的源程序AST,条目信息
输出:状态更新成功与否
方法:
begin
通过源程序中的符号条目来获取条目状态列表;
查找当前状态列表中会不会存在需要查找条目的信息;
if(当前条目有状态)
if(目前状态与模式的初始状态一样)
更新条目状态;
若是指针变量,则进行别名处理;
else
if(挂接在该条目上的信息为空)
为条目列表开辟空间,同时将当前条目状态插入;
将开辟的条目列表挂接在条目上;
更新维持全局状态的集合;
else if(当前变量条目不存在)
为条目状态开辟一个空间;
把条目插入到状态列表当中;
更新维持全局状态的集合;
else if(不存在录属于当前状态机上状态)
插入条目到状态列表中;
更新维持全局状态的集合;
end if
end if
end if
end
-
- 本章小结
本章首先对状态转移处理器进行了设计,对表达式结构的匹配方法进行了介绍。接着介绍状态转移控制器模块的相关数据结构。并对模式匹配的算法进行相应的讨论,最后对状态转移控制器进行设计。至此完成定制化规则的安全分析工具相关功能。
- 基于定制化规则的安全分析工具的实例分析
- 数组下标检查
在访问数组时,非法的下标访问数组以外的不可靠数据以及写入通过非法的下标访问数组得到的内存空间,将会破坏程序执行时的堆或栈空间,从而导致程序执行逻辑被破坏而引起运行时错误。
在实际编码时通过静态分析检查不能做到精确的计算,比如利用外部输入的整型变量作为下标来使用。图5.1描述了下标越界的代码。
-
-
-
-
-
-
-
-
- 下标边界检查实例代码
-
-
-
-
-
-
-
数组访问越界为C中最常出现的非法操作。在C中,静态分配或者动态分配的数组仅仅是一块连续的缓冲区,而作为一个内存块,缓冲区本身无法提供任何形式的边界检查。如果程序员引用的地址超出了缓冲区的地址范围,就会由于访问非法地址而产生不可预料的后果。而又由于数组下标的可变性,使得其往往无法从代码中静态计算得到,为了避免程序中可能发生的越界操作,程序员在编写程序时必须自行保证保证每一次通过下标访问数组元素操作时,给定的下标值的取值范围不能小于0,同时不能超过下标的最大值。
如图5.1所示代码存在安全隐患。当分析的静态路径为true分支时(b>1成立,a==10也成立),对数组arr的访问,就会存在一定安全问题。只对arr进行下界的相应检测,而没有对上界做检测,然后对数组的元素进行访问,就会出现数组越界访问的安全漏洞。
对于图5.1中的安全漏洞,本文编写了数组上下界检查的安全规则如图5.2所示。
-
-
-
-
-
-
-
-
- 下标边界检查的安全规则
-
-
-
-
-
-
-
对图5.1的程序,由图5.2图1.2下标边界检查的定制安全规则中20行的规则a>c来配对b>1,在检测TRUE分支时,d的状态就可以迁移到need_ub,对arr进行访问时,此时会匹配v[a]安全模式,然后a的状态迁移到stop状态,然后显示“对数组索引缺少下界检查”警告信息。
-
- 资源泄漏检查
-
-
-
-
-
-
- 资源泄漏代码实例
-
-
-
-
-
-
- 资源泄漏检查
由于C中指针本身不携带其指向地址的有关属性信息,程序员无法依赖编译器确定某个指针指向的空间是否可以被手工释放。通常情况下,程序员必须保证某个确定指针所指向的空间特性的唯一性,避免出现在一段代码中同一个指针同时出现指向堆空间或者指向栈空间的情况。如图5.3所示当静态分析时,要对条件判断i<j的两个TURE、FALSE分支一起做分析。以上代码运行时会出现问题,因为释放的是栈内存以及静态变量。
安全规则图5.4就是对以上代码中出现的类似资源泄漏等安全隐患而设计的。运用图5.4所示的安全规则,需要对每个分支都分析,对于TURE分支,首先源代码中的“a = &static_var”会匹配到安全规则中的p=&r模式,p的状态就变成Allocated,而delete a会匹配安全规则中的第29行,这时会发出“不能释放栈内存”错误信息。而对于FALSE的分支,则应该配对到定制的安全规则中14行以及第30行,此时会提示“不允许释放栈内存”的警告。
-
-
-
-
-
-
-
-
- 资源泄漏安全规则
-
-
-
-
-
-
-
除了以上实例,本文基于状态机的检测方法还检测其他源代码检测,并检测出了其中的安全漏洞,检测验证了本文方法的可行性以及有效性。
-
- 本章小结
本章主要进行了对安全分析工具进行了实例分析,通过对数组下标越界以及内存资源泄漏等漏洞的检测,通过设计实验与编写安全规则,最后得出了比较满意的漏洞提示,实现了安全分析工具的功能。验证了基于定制化规则的安全分析工具的有效性。
- 总结与展望
本文的内容来自于实际项目,此项目的目标是设计实现一套软件安全的检测软件使其能够使用静态方法来检测C程序的相关安全漏洞。而本文重点则是该软件的安全规则的定义以及安全问题匹配与查找过程。本文设计并实现了一种基于状态机的可定制化安全漏洞的检测方法,此方法可以利用外部定义的安全规则来对源代码进行匹配并有效的检测出用户定义的漏洞。检测工具通过对源代码的分析,匹配到源代码的漏洞并报错,这样就可以防止漏洞在运行期间产生更大的问题。
本文完成的主要工作有:
(1)利用编译器前端工具ANTLR对模式文法进行了设计与实现。安全规则中的程序模式能够匹配C语法的程序结构。利用安全规则的描述语言可以编写用户定制化的安全规则。
(2)完成了对定制化工具符号存储,状态存储转移部分数据结构以及转移逻辑的实现使得一个变量在同一时间只有一种状态,而在不同时间可以有不同状态,而不同变量可以有相同的状态。使得变量状态能够高效的存储与利用。
(3)本文设计了有关模式的匹配算法。要使得模式匹配的算法能够更加准确。在ANTLR语法树遍历框架框架之上,将安全规则模式设计成为ANTLR结构AST。然后与源程序的AST相匹配,完成安全检查的工作。
设计与实现定制化安全规则分析工具的难点有:
(1)词法以及语法解析器的设计。
(2)怎样高效的的设计状态机的安全规则语法,并且能够准确的蛮熟安全规则。
(3)设计出安全规则解析器的数据结构,以及之间的联系与操作方法。
(4)基于ANLTR语法分析器的分析,添加语义完成解析工作中相应语义相关的动作。
(5)安全规则在模式匹配时对算法的设计,以及状态转移时对状态转移算法的设计。
本文还有很多不足的地方需要提高:
(1)在安全规则解析器运行中,该工具的异常处理能力还需要加强。
(2)在安全分析设计时,对一条语句每个安全规则都需要进行匹配。效率有待提高。
(3)根据以上问题,本文的下一步工作就是如何高效并且准确的匹配安全漏洞,使工具更加完善。