【论文分享】Validating Static Warnings via Testing Code Fragments

这篇论文来自ISSTA 2021
论文地址:https://dl.acm.org/doi/10.1145/3460319.3464832

简介

静态分析是一个很重要的找bug和漏洞的方法。然而检查和验证static warning是很有挑战性并且很耗时的。在这篇论文里,我们提出了一个新的方法来验证warning。文章提出了一种打补丁的算法能够语法合法,保留语义的可执行代码片段。同时,还使用fuzzer,klee,valgrind来测试代码片段。评估部分,采用12个真实的C程序和2个静态分析工具提取的1955个warning。helium成功构建了65%的代码片段,并且生成了1033个测试样例。通过自动化测试,发现了48个真漏洞和27个误报,以及205个疑似误报。还用4个CVE来测试,发现只有我们的工作能够触发漏洞,其他的baseline都不行。

方法

为什么要用LCA算法?

token-based : may change semantic

image-20210717152103138

基于token的方法,对于语句var=foo(a);bar(b);可能会出现foo(b)的情况,改变了程序原有的语义。

tree-based: a large program

基于tree的方法,就不会出现改变语义的情况,但有可能导致生成一个大型程序。比如下面的图里,只要s1和s3就足够了,但是使用这种树的方法会把s2,s4也加进来,形成一个大的程序。

image-20210717152455381

所以作者提出了一个LCA的方法,在这二者做权衡。这个方法的思想是只复制重要的非终止的节点,这些节点可以保存两个选择的token的语法关系。下图中,原始解析树p是a中的情况,在用了LCA的方法后,只提取了子节点,就变成了b中的情况。此外,作者在附录里用partial order的理论证明了LCA算法会保留任何两个token的partial order。

image-20210717153645872

生成代码片段

LCA-based算法包含两个部分,一个是保存LCA的关联,一个是生成最小的补丁。

简单介绍下面的算法,算法的输入是程序p,代码片段s,语法G。输出是修补的程序s’。

line3-5:初始化。N保存的是需要计算的LCA的节点。Δs存储的是过程内的补丁。

line5-10:对于选择的token,识别它的LCA节点。

  • 5-6行:worklist存储着待计算的节点。从解析树的最底下往上面遍历。
  • 8行:找到节点l的子节点和N的交集Clca
  • 9-10:如果发现重叠的节点数量大于等于2,就将节点l加入到N里,并且移除Clca

line11-15:基于LCA关系生成补丁

  • 12:当LCA节点的子节点c是终止节点,就直接将他们加入到补丁里。也就是Δs=Δs+C
  • 13-15:如果不是终止节点,就使用GENMINPATCH生成最小补丁。

line16:对于解析树的根节点和LCA的top节点,也生成最小补丁。

image-20210717155859428

上面算法提到的GenMINPATCH函数的算法如下图所示。他的主要目的是在一个非叶子节点到目标之间,找到最短的路径。还挺复杂的,暂时和我研究没有关系,先跳过,后面有时间再看。

image-20210717164844544

测试代码片段

求解代码片段的依赖

使用两种方式来识别构建代码片段所需要的依赖。

第一种方法是将代码里所有的定义存在数据库里,当处理代码片段时,按需取用定义。这种方法不需要build project,只要他的语法错误不影响代码片段,我们仍然可编译代码片段。

第二种方法是去找到包含代码片段需要的符号定义的头文件。然后将这些include加到代码片段里。

为了生成可执行文件,也采用了两种方法:

  • 使用头文件来告诉project要链接哪个对象文件或者库。
  • 记录了所有的编译器的链接器的flag来构建原始project。采用的工具是bear,Cmake的export 命令。
用KLEE、Fuzzer和Valgrind测试

生成main函数:

使用def-use分析去识别输入变量v:如果在代码片段里没有找到变量v前面的定义,就是输入变量了。

生成输入:

如果我们能够在代码片段前就能确定变量的值,我们可以用这个值去初始化变量。否则,我们使用自动生成的输入去初始化输入变量。现阶段支持随机生成整数、浮点、字符、数组、指针和这些类型的数据结构。我们选择Radamasa和KLEE去进一步生成测试输入。使用Radamsa是因为它能够使用一个随机输入作为seed然后生成有用的输入,并且已经在各种真实应用软件中发现了漏洞。

生成测试集:

发现了两种类型的有用的测试,一种是valid,一种是pass。valid测试样例会在test oracle中触发错误。test oracle包含错误定位和错误的特征。在测试过程中,会去对比实际发生错误的位置是否和设定的一样。对于错误的特征,我们通过加断言来映射warning type到动态分析工具的错误type。

我们分析静态warning的类型并且分为两类。理想情况下,静态分析工具可以报告包含与bug有关的所有路径。在这些情况下,阳性warning只要一个valid test case去描述这个bug,比如buffer overflow,divide by zero。对于那些测试不会触发定义在oracle里的错误的warning,认为是likely false positive。negative warning指的是不可达的条件或者dead code。

局限性

如果原始的warning中缺少数据依赖,helium的补丁算法在修补语法错误的时候,可能会去添加。但不能保证所有的数据依赖都能补上。

当warning路径中缺少引入错误的语句或者有从程序入口到不了的路径,比如dead code,helium的结果就会不精确。然而,大部分的静态分析工具都满足我们的假设。这些工具都比较保守,并且会提供更多的语句来帮助开发者去诊断warning。

有些bug不能处理,比如concurrency bugs。

实现

用C语言实现Helium,使用了Clang,pycparser和srcML工具。LCA补丁算法是在Racket实现的,测试框架是用C++写的。

使用的静态分析工具是PolySpace和一个不知名的商业工具。

实验用的数据开源于:https://zenodo.org/record/5034975#.YPI2M-gzYuU

三个测试工具:

  • Radamsa:https://gitlab.com/akihe/radamsa
  • klee:https://klee.github.io/
  • valgrind:https://valgrind.org/

实验评估

针对三个问题:

  • RQ1:LCA-based 补丁算法有多高效?
  • RQ2:生成测试函数、输入、测试集有多高效?能保证warning的语义吗?
  • RQ3:验证真实的static warning有多高效?

RQ1

使用三种metric:

  • 成功解析的代码片段的数量
  • 成功编译的代码片段的数量
  • 代码片段的平均长度

几个baseline(RLAssist、MACER两个自动修补的工作):

  • np:直接从static warning弄过来的
  • R:RLAssist
  • M:MACER

image-20210718103110192

RQ2

问题2采用的metric:

  • 可执行测试样例的数量
  • 通过随机测试、Radamsa和KLEE触发的测试样例的数目

除了RLAssist和MACER,还使用unit test作为baseline。

image-20210718103124695

RQ3

额外添加了两个baseline,一个是bovinspector,另外一个使用valgrind来测试existing test suite。同时,让三个作者去验证表格中50%的结果。

image-20210718103138553

总结

这篇文章思路清奇,选择的角度挺好的。文章写作上也值得学习,基本上想知道的信息都可以从文章里找到。此外,文章实现的工作用到了很多工具。或许作者们一开始也不是很顺利,也是一步一步实现完成的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

破落之实

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值