Page 31 - 《软件学报》2025年第7期
P. 31
2952 软件学报 2025 年第 36 卷第 7 期
′
4. update( p,o, p )
5. return p ′
6. }
2.2.1 可靠性增强
由于基于语义信息的变异操作符实现起来相对字符级别操作复杂很多, 正确性很难得到完全保证, 实践中总
会有一定比例的崩溃出现. 尽管在图 4 中, 我们已经通过持续的测试运行和缺陷修复来尽可能地减少崩溃, 却始终
无法完全避免. 又由于通过自定义接口实现的变异操作符模块直接在 AFL++的进程空间中被调用. 因此一旦崩溃
发生, 便会导致 AFL++停止运行, 这对于一个需要长时间执行的模糊测试工具而言是不可接受的.
针对经常出现的崩溃中断模糊测试工具这一可靠性问题, 我们使用了进程隔离的方式. 在调用自定义接口时
克隆出一个子进程, 将所有操作放在子进程中完成, 并将结果通过进程间通信返回给父进程, 则基于语义信息的变
异操作符的崩溃都会被隔离在子进程内, 从而大幅增加了基于语义信息的变异操作符的可用性.
我们将进程隔离实现在算法 1 的第 3 行. 具体来说, 我们实现了一个变异服务器, 专门处理程序变异请求. 算
法 1 在第 3 行通过 request_mutation 向这个变异服务器发送请求. 如果服务器发生崩溃或卡死, 无法及时响应请
求, request_mutation 会重启这个服务器并返回 ⊥ 用以区分.
如算法 2 所示, 变异服务器循环处理来自客户端的程序变异请求. 服务器的工作流程开始于等待客户端通过
S req 信号量发起的变异请求. 一旦接收到请求, 服务器从共享内存中提取待变异程序 p 和变异操作符 o. 随后, 服务
′
器执行变异操作, 该操作可能修改程序 p 的结构或行为, 根据变异结果, 服务器将变异后的程序 p 写回共享内存.
完成这些操作后, 服务器通过 S res 信号量通知客户端, 变异处理已完成.
算法 2. 变异服务器处理变异请求.
全局: 共享内存 M s , 请求信号量 S req , 回复信号量 S res ;
p .
′
输出: 变异后的程序
1. function mutation_server() {
2. while ( ¬ terminated()) {
3. sem_wait( S req )
4. p,o ← fetch_request( M s )
5. p,o) // 这一步会出现崩溃
′
p ← mutate(
6. write_response( M s , p )
′
7. sem_signal( S res )
8. }
9. }
相对应地, 变异客户端的职责是构建变异请求, 并从服务器获取处理结果. 如算法 3 所示, 客户端首先将待变
S req 信号量通知服务器发起变异请求. 在通知服务器后,
异的程序 p 和变异操作符 o 写入共享内存 M s 中, 然后通过
客户端等待服务器的响应. 这里使用了带超时机制的信号量等待函数 timed_sem_wait, 如果在规定的超时时间
T tmout 内没有收到服务器的响应, 则认为变异处理失败. 此时, 客户端需要重置信号量 S req 和 S res 的状态, 并重新启
动变异服务器, 以确保系统能够继续正常工作. 如果客户端成功接收到服务器的响应, 则从共享内存中读取变异后
′
的程序 p 并将其返回.
值得一提的是, 上述的变异客户端/服务器框架可以兼容多进程进行并行处理. 具体而言, 可以创建多个变异
服务器实例, 每个实例独立处理一部分变异请求. 通过并行处理, 可以充分利用多核 CPU 的计算能力, 从而缩短变
异操作的总体耗时. 当然, 引入并行处理也会带来一些新的问题, 例如需要对共享资源 (如共享内存) 的访问进行

