1989年的模糊测试技术如何在2018年仍发现Linux漏洞

本文通过复现1989年的原始模糊测试方法,在现代Ubuntu系统上发现多个漏洞,包括glibc缓冲区溢出和经典死锁问题,展示了基础模糊测试技术的持久有效性。

1989年的模糊测试:穿越30年的漏洞挖掘之旅

快速回顾

对于没有阅读原始论文的读者(你真的应该读一读),本节提供快速摘要和一些精选引用。

模糊测试程序通过生成随机字符流工作,可选择生成仅可打印、控制或不可打印字符。程序使用种子生成可重现结果,这是现代模糊测试工具经常缺乏的有用功能。一组脚本执行目标程序并检查核心转储。程序挂起需要手动检测。适配器为交互式程序(1990年论文)、网络服务(1995年论文)和图形X程序(1995年论文)提供随机输入。

1990年论文测试了四种不同的处理器架构(i386、CVAX、Sparc、68020)和五个操作系统(4.3BSD、SunOS、AIX、Xenix、Dynix)。1995年论文具有类似的平台多样性。在第一篇论文中,25-33%的实用程序失败,具体取决于平台。在1995年的后续研究中,这一数字范围为9%-33%,GNU(在SunOS上)和Linux的崩溃可能性最低。

1990年论文得出结论:(1)程序员不检查数组边界或错误代码,(2)宏使代码难以阅读和调试,(3)C语言非常不安全。特别提到了极其不安全的gets函数和C的类型系统。在测试期间,作者在格式化字符串漏洞被广泛利用之前数年就发现了它们(见第15页)。论文最后进行了一项用户调查,询问用户修复或报告错误的频率。事实证明报告错误很困难,而且修复错误的兴趣不大。

1995年论文提到了开源软件,并讨论了为什么它可能具有更少的错误。它还包含以下精选引用:

当我们检查导致失败的错误时,出现了一个令人沮丧的现象:许多在1990年发现并报告的错误(约40%)在1995年仍然以其确切形式存在…本研究中使用的技术简单且大部分自动化。很难理解为什么供应商不利用这种免费且简单的可靠性改进来源。

模糊测试成为大型软件开发商店的标准实践还需要15-20年。

我还发现1990年写的这句话对未来的事情很有预见性:

C编程风格的简洁性常常被发挥到极致;形式被强调而正确的功能被忽视。溢出输入缓冲区的能力也是一个潜在的安全漏洞,正如最近的互联网蠕虫所显示的那样。

测试方法

值得庆幸的是,30年后,Barton博士仍然提供完整的源代码、脚本和数据来重现他的结果,这是一个值得称赞的目标,更多研究人员应该效仿。脚本和模糊测试代码老化得惊人地好。脚本按原样工作,模糊测试工具只需要少量更改即可编译和运行。

30年新软件的更新

显然,过去30年Linux软件包发生了一些变化,尽管许多测试的实用程序仍然可以追溯到几十年前。尽可能测试了1995年论文中审计的相同软件的现代版本。一些软件不再可用,必须更换。每个替换的理由如下:

  • cfe ⇨ cc1:这是C预处理器,等同于1995年论文中使用的
  • dbx ⇨ gdb:这是调试器,等同于1995年论文中使用的
  • ditroff ⇨ groff:ditroff不再可用
  • dtbl ⇨ gtbl:GNU Troff等效于旧的dtbl实用程序
  • lisp ⇨ clisp:通用lisp实现
  • more ⇨ less:Less is more!
  • prolog ⇨ swipl:prolog有两个选择:SWI Prolog和GNU Prolog。SWI Prolog胜出,因为它更老且更全面的实现
  • awk ⇨ gawk:GNU版本的awk
  • cc ⇨ gcc:默认C编译器
  • compress ⇨ gzip:GZip是旧Unix compress的精神继承者
  • lint ⇨ splint:GPL许可的lint重写
  • /bin/mail ⇨ /usr/bin/mail:这应该是不同路径下的等效实用程序
  • f77 ⇨ fort77:Fortran77编译器有两个可能选择:GNU Fortran和Fort77。GNU Fortran推荐用于Fortran 90,而Fort77推荐用于Fortran77支持。f2c程序积极维护,变更日志记录可追溯到1989年

结果

1989年的模糊测试方法在2018年仍然发现错误。然而,已经取得了进展。

衡量进展需要基线,幸运的是,Linux实用程序有基线。虽然1990年的原始模糊测试论文早于Linux,但1995年的重新测试使用相同的代码在1995年Slackware 2.1.0发行版上模糊测试Linux实用程序。相关结果出现在1995年论文的表3中(第7-9页)。GNU/Linux在商业竞争对手面前表现非常好:

自由分发的Linux版本UNIX上实用程序的失败率第二低,为9%。

让我们使用1989年的模糊测试工具检查2018年Linux实用程序与1995年Linux实用程序的比较:

系统 崩溃 挂起 总测试数 崩溃/挂起%
Ubuntu 18.10 (2018) 1 (f77) 1 (spell) 81 2%
Ubuntu 18.04 (2018) 1 (f77) 1 (spell) 81 2%
Ubuntu 16.04 (2016) 2 (f77, ul) 1 (spell) 81 4%
Ubuntu 14.04 (2014) 2 (swipl, f77) 2 (spell, units) 81 5%
Slackware 2.1.0 (1995) 4 (ul, flex, indent, gdb) 1 (ctags) 55 9%

令人惊讶的是,即使是最新的Ubuntu版本,Linux崩溃和挂起计数仍然不为零。f77调用的f2c程序触发分段错误,spell程序在两个测试输入上挂起。

错误是什么?

错误足够少,我可以手动调查一些问题的根本原因。一些结果,如glibc中的错误,令人惊讶,而其他结果,如sprintf到固定大小缓冲区,是可预测的。

ul崩溃

ul中的错误实际上是glibc中的错误。具体来说,这是2016年在这里和这里报告的问题(另一个人也在ul中触发了它)。根据错误跟踪器,它仍未修复。由于该问题无法在Ubuntu 18.04及更新版本上触发,该错误已在发行版级别修复。从错误跟踪器评论来看,核心问题可能非常严重。

f77崩溃

f77程序由fort77包提供,它本身是f2c的包装脚本,f2c是Fortran77到C源代码转换器。调试f2c显示崩溃发生在errstr函数中,当打印过长的错误消息时。f2c源代码显示它使用sprintf将可变长度字符串写入固定大小的缓冲区:

1
2
3
4
5
6
7
errstr(const char *s, const char *t)
#endif
{
  char buff[100];
  sprintf(buff, s, t);
  err(buff);
}

这个问题看起来自f2c诞生以来就一直存在。根据变更日志,f2c程序至少从1989年就存在。1995年的模糊测试重新测试中没有在Linux上测试Fortran77编译器,但如果测试了,这个问题会更早被发现。

spell挂起

这是经典死锁的一个很好的例子。spell程序通过管道将拼写检查委托给ispell程序。spell程序逐行读取文本并向ispell发出行大小的阻塞写入。然而,ispell程序每次最多读取BUFSIZ/2字节(在我的系统上为4096字节),并发出阻塞写入以确保客户端收到迄今为止处理的拼写数据。两个不同的测试输入导致spell向ispell写入超过4096字符的行,导致死锁:spell等待ispell读取整行,而ispell等待spell确认它读取了初始更正。

units挂起

初步检查这似乎是一个无限循环条件。挂起看起来在libreadline中而不是units中,尽管新版本的units不受此错误影响。变更日志表明添加了一些输入过滤,可能无意中修复了这个问题。虽然彻底调查原因和修正超出了本博客文章的范围,但可能仍然有方法向libreadline提供挂起输入。

swipl崩溃

为了完整性,我想包括swipl崩溃。然而,我没有彻底调查它,因为崩溃早已修复且看起来相当良性。崩溃实际上是在字符转换期间触发的断言(即不应该发生的事情发生了):

1
2
3
4
5
6
[Thread 1] pl-fli.c:2495: codeToAtom: Assertion failed: chrcode >= 0
C-stack trace labeled "crash":
  [0] __assert_fail+0x41
  [1] PL_put_term+0x18e
  [2] PL_unify_text+0x1c4

应用程序崩溃从来不是好事,但至少在这种情况下,程序可以告诉有些事情不对,它早期且大声地失败。

结论

模糊测试在过去30年中一直是发现程序中错误的简单可靠方法。虽然模糊测试研究正在迅速发展,但即使是重复使用30年代代码的最简单尝试也能成功识别现代Linux实用程序中的错误。

原始模糊测试论文很好地预见了C语言的危险及其将在几十年内导致的安全问题。它们令人信服地论证了C使编写不安全代码变得太容易,应尽可能避免。更直接地,论文表明即使是朴素的模糊测试仍然暴露错误,这种测试应作为标准软件开发实践纳入。可悲的是,这个建议几十年来没有被遵循。

我希望你喜欢这个30年的回顾。请关注本系列的下一部分:2000年的模糊测试,它将调查Windows 10应用程序在面对Windows消息模糊测试器时与Windows NT/2000等效程序的比较。我想你已经可以猜到答案了。

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