编译器模糊测试实战:一年挖出88个漏洞的技术揭秘

本文详细记录了对Solidity等编译器进行长期模糊测试的技术实践,涵盖定制化变异策略、跨语言测试方法,以及如何通过AFL工具在一年内发现88个已修复漏洞的实战经验。

编译器模糊测试一年实战记录

项目背景与成果

2020年夏季,我们启动了对Solidity编译器solc的模糊测试项目。截至2021年3月,我们共提交74份错误报告,其中67个被确认为真实漏洞,66个已完成修复,7个属于重复报告或非真实缺陷。值得注意的是,仅从去年12月以来就提交了21个漏洞,这充分证明了独立模糊测试在已有OSSFuzz测试基础上仍具有重要价值。

持续测试的价值所在

代码路径覆盖的深度探索

生产级编译器具有极高的复杂性,深度模糊测试至关重要。即使连续运行AFL超过30天,我们的测试仍能每隔1-2小时发现新的执行路径。某些漏洞甚至需要持续测试一个月才能被发现。

示例代码(触发SMT检查器崩溃的Solidity代码):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
pragma experimental SMTChecker;
contract A {
    function f() internal virtual {
        v();
    }
    function v() internal virtual {
    }
}

contract B is A {
    function f() internal virtual override {
        super.f();
    }
}

contract C is B {
    function v() internal override {
        if (0==1)
            f();
    }
}

差异化测试方法

与OSSFuzz的传统字典或语法方法不同,我们采用跨语言变异测试理念,为AFL添加了"代码特异性"变异算子。这些算子主要包括:

  • 删除括号间代码
  • 更改算术或逻辑运算符
  • 交换代码行顺序
  • 将if语句改为while循环
  • 移除函数参数

持续集成策略

我们定期获取最新代码版本进行测试,但并非每次发布都重新开始,以避免丢失长期测试积累的成果。测试语料库中包含的数万个测试案例可能包含大量无趣路径,这些路径反而有助于在新代码中发现漏洞。

多语言编译器测试扩展

Vyper编译器测试

使用基于语法的方法和TSTL Python库通过python-afl进行测试,但由于检测式Python测试的速度和吞吐量较差,测试深度有限。

Move语言编译器测试

合作者Rijnard van Tonder在Diem项目的Move语言编译器测试中取得显著成果:两个月内报告14个漏洞,全部被确认并分配,其中11个已修复。

Fe语言测试

在Fe语言的第一个alpha版本发布当天开始测试,9天后提交首个问题。Fe团队配合修改了Yul后端的失败处理机制,使其产生对afl可见的Rust恐慌。至今已提交31个问题,占Fe所有GitHub问题的18%以上,其中14个确认为漏洞,10个已修复。

Zig编译器测试

对旨在简化和透明的系统编程语言Zig进行测试,发现两个已确认但尚未修复的漏洞。

技术实现细节

我们的模糊测试器在正则表达式级别操作,甚至不使用上下文无关语言信息。主要基于快速的C字符串启发式方法进行"类代码"变更,这种方法即使对非C类语言(如Vyper和Fe)也同样有效。

未来发展方向

语义检查增强

正在与Solidity内部模糊测试负责人Bhargava Shastry合作,应用他们在Yul优化级别protobuf模糊测试中使用的语义检查方法。通过solc的strict-assembly选项直接对Yul进行模糊测试,已经发现一个有趣漏洞。

编译器检测能力提升

设想编译器包含测试选项,启用激进且昂贵的检查(如寄存器分配的健全性检查)。虽然这些检查在正常运行中成本过高,但可用于关键代码的最终生产编译。

开发者教育推广

希望让编译器开发者和其他处理源代码的工具开发者认识到,有效的模糊测试不一定需要大量开发时间。使用空闲CPU周期、一组良好的源代码示例和afl-compiler-fuzzer工具,通常就能轻松找到导致编译器崩溃的输入。

总结

通过对多个编译器的长期模糊测试,我们证明了这种方法在发现深层次漏洞方面的有效性。编译器项目的复杂性和快速变化特性使得持续模糊测试成为确保代码质量的重要手段。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计