Page 30 - 《软件学报》2025年第7期
P. 30

欧先飞 等: 语义可感知的灰盒编译器模糊测试                                                          2951


                    表  1  展示了这  43  个初始变异操作符中的一小部分, 这些操作符涵盖了表达式、语句、函数、变量等多个方
                 面, 并且其中包含一些相当复杂的操作, 如表             1  所示的“扁平化高维数组”. 不过即便如此, 在我们所搜集的测试程
                 序集中, 依旧有不少的程序无法被所有这些操作符变异, 这也驱使我们去添加更多的变异操作符来覆盖这些无法
                 被变异的程序.


                                               表 1 本文实现的部分变异操作符

                          变异操作符                                       功能描述
                       修改表达式操作符                        修改表达式的操作符, 并保证语义合法, 如a+b改为a–b
                          简单内联                                  将被调用函数内联至掉用处
                        分支转多路分支                                将if分支语句转化为switch语句
                        翻转内联修饰符                    若选定函数为内联函数, 则移除内联修饰符, 反之添加内联修饰符
                          表达式拷贝                  对在同一个作用域内类型相同的两个表达式, 将其中一个替换为另一个
                        扁平化高维数组                     将多维数组a[M][N][...]降为一维数组, 并相应的修改所有引用
                         分支条件翻转                                将if分支语句的判断表达式取反

                 2.1.1    可用性增强
                    对于我们所设计和实现的操作符而言, 可用性问题主要包含两个方面, 一是有一部分测试程序无法被任何一
                 个我们的操作符所变异, 另一个是操作符由于实现当中的缺陷, 会导致输出程序非法亦或是操作符自身崩溃. 针对
                 这两个问题, 如图     4  所示, 我们会采用如下的解决方案来缓解.
                    (1) 无法被变异的程序. 在获得表        1  所示的初始的操作符集合        (包括名称, 功能描述和代码实现) 之后, 如图           4
                 所示, 我们会在搜集来的测试程序上进行大量的测试运行. 如果某个程序未能被所有操作符成功变异, 我们会人工
                 分析该程序, 设计出可以覆盖该程序的变异操作符. 该过程会持续迭代, 直至所有测试程序都能被至少一个操作符
                 变异. 这一过程迭代结束后, 共计获得了           72  个变异操作符.
                    (2) 变异操作符的缺陷. 为了降低操作符实现中的缺陷带来的影响, 我们在测试运行的时候也会搜集分析缺
                 陷. 如图  4  所示, 如果测试过程中, 在某个变异操作符出现了崩溃或者变异出了不可编译的程序, 我们会人工介入
                 并修复这个变异操作符.

                 2.2   集成适配与可靠性增强
                    为了将这些操作符和模糊测试相结合, 我们还需要将这些操作符集成进模糊测试工具中. 这里我们选择了目
                 前最先进的灰盒模糊测试工具           AFL++ [20] . AFL++为二次开发提供了一套插件机制, 我们可以将已实现的变异操作
                 符按  AFL++的接口描述打包成动态链接库, 然后将其作为基于语义信息的变异模块交由                         AFL++加载运行. 具体来
                 说, 我们需要实现如下接口.
                    1. // 自定义的变异操作模块
                    2. size_t afl_custom_havoc_mutation(my_mutator_t *data, …);
                    如算法 1   所示, 在具体实现过程中, 我们在该函数内部会先通过                select 函数选出合适的种子程序        p 和变异操
                     o, 随后我们通过    request_mutation                       p 获得  , 之后通过   update 函数更新一下
                                                                              p
                                                                               ′
                 作符                            函数将变异操作符       o 应用于程序
                                                    ′
                 select 函数的选择权重, 最后将变异后的程序          p  返回给  AFL++进行后续的模糊测试, 包括覆盖率更新、超时检查、
                 崩溃检查以及种子程序池更新等.
                 算法  1. 自定义变异操作.
                                 ′
                 输出: 变异后的程序      p .
                 1. function afl_custom_havoc_mutation() {

                 2.    p,o ← select()
                 3.                   p,o)
                     p ← request_mutation(
                     ′
   25   26   27   28   29   30   31   32   33   34   35