LLVM 项目博客

LLVM 项目新闻和来自实战的细节

使用 LLVM 新 libFuzzer 对库进行简单的引导式模糊测试


模糊测试(或 模糊测试)正变得越来越流行。模糊测试 Clang 和使用 with Clang 进行模糊测试并非新鲜事:基于 Clang 的 AddressSanitizer已用于模糊测试 Chrome 浏览器多年, 几年,而 Clang 本身也已经使用 csmith 以及最近的 AFL进行了广泛的模糊测试。 现在,我们已经闭环并开始使用 LLVM 本身对 LLVM 的部分内容(包括 Clang)进行模糊测试。

LibFuzzer,最近添加到 LLVM 树中,是一个用于进程内模糊测试的库,它使用 Sanitizer Coverage 检测来引导测试生成。 使用 LibFuzzer,可以通过编写一个简单的函数来实现某个库的引导式模糊测试器: 
extern "C" void TestOneInput(const uint8_t *Data, size_t Size);

我们在 LibFuzzer 之上实现了两个模糊测试器:clang-format-fuzzerclang-fuzzer. Clang-format 主要是一个词法分析器,因此,将随机字节提供给它进行格式化效果很好,发现了 超过 20 个错误. 然而,Clang 不仅仅是一个词法分析器,将随机字节提供给它仅仅触及了皮毛,因此,除了使用随机字节进行测试外,我们还以 token-aware 模式对 Clang 进行了模糊测试。 两种模式都发现了 错误;其中一些错误之前 被 AFL 检测到 ,而另外一些则没有:我们已经使用 AddressSanitizer 运行了这个模糊测试器,其中一些错误在没有它的情况下不容易发现。

仅供了解,以下是一些由 token-aware clang-fuzzer 从空测试语料库开始发现的输入样本:
 static void g(){}
 signed*Qwchar_t;
 overridedouble++!=~;inline-=}y=^bitand{;*=or;goto*&&k}==n
 int XS/=~char16_t&s<=const_cast<Xchar*>(thread_local3+=char32_t

模糊测试不是一次性的事情——它在持续使用时表现出色。我们已经设置了一个
公开构建机器人,它全天候运行 clang-fuzzer 和 clang-format-fuzzer。这样,模糊测试器就可以不断改进测试语料库,并定期发现旧错误或新回归(该机器人已经 捕获至少一次这样的回归))。

与进程外模糊测试相比,进程内模糊测试的优势在于你可以每秒测试更多输入。但这也有弊端:你无法有效地限制每个输入的执行时间。如果一些输入触发超线性行为,它可能会减慢或瘫痪模糊测试。我们的模糊测试机器人几乎在发现 clang-format 中的指数解析时间后就停止工作了。你可以通过为模糊测试器设置超时来解决这个问题,但最好是修复超线性行为。

对 LLVM 的其他部分进行模糊测试会很有趣,但进程内模糊测试的一个要求是库在无效输入上不会崩溃。这适用于 clang 和 clang-format,但不适用于 LLVM 位代码阅读器等。

非常欢迎你的帮助!你可以从修复 clang 或 clang-format 中的现有错误开始(参见 PR23057PR23052 以及来自 AFL 的 结果或为 LLVM 的其他部分编写你自己的模糊测试器,或者分析现有的模糊测试器并尝试通过修复性能错误来使其更快。

当然,LibFuzzer 可以用来测试 LLVM 项目之外的东西。例如,遵循 Hanno Böck 的博客文章关于 Heartbleed,我们已经将 LibFuzzer 应用于 openssl 并在 不到一分钟内发现了 Heartbleed。此外,在 PCRE2 (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)、Glibc 以及 MUSL libc (1, 2) 中发现了不少新错误。

模糊测试,尤其是覆盖率引导和 Sanitizer 辅助的模糊测试,应该直接补充单元测试、集成测试和系统功能测试。我们鼓励每个人开始积极地对他们的接口进行模糊测试,尤其是那些可能受到攻击者控制的输入影响的接口。我们希望 LLVM 模糊测试库能帮助你开始利用我们的工具更好地测试你的代码,并让我们知道他们帮助你发现的任何真正令人兴奋的错误!