随着2019年的到来,最好记住过去并思考未来。 让我们回顾30年,回顾一下有关模糊测试的第一篇科学文章:
“对UNIX实用程序可靠性的实证研究” ,以及其后1995年
由同一作者
Barton Miller撰写的
“修订模糊测试” 。
在本文中,我们将尝试使用
与原始模糊测试
相同的工具来查找现代版本的Ubuntu Linux中
的错误。 您不仅必须阅读原始文档以了解上下文,而且还需要阅读以便理解。 事实证明,对于未来几十年的漏洞和利用,他们非常有说服力。 细心的读者可能会注意到原始文章的出版日期:1990年。 更细心的人会在原始评论中注意到版权:1989。
简短评论
对于那些尚未阅读文档的人(尽管确实应该这样做),本节包含一个简短的摘要和一些选定的引号。
模糊测试程序生成随机的字符流,并仅生成可打印或不可打印的字符。 它使用一定的初始值(种子),以确保可重复的结果,这是现代模糊测试人员通常缺乏的。 一组脚本在经过测试的程序上运行,并检查是否存在基本转储。 挂起是手动检测到的。 适配器为交互式程序(1990年文章),网络服务(1995年)和图形X应用程序(1995年)提供随机输入。
1990年的一篇文章测试了四种处理器体系结构(i386,CVAX,Sparc,68020)和五个操作系统(4.3 BSD,SunOS,AIX,Xenix,Dynix)。 在1995年的文章中,选择了类似的平台。 在第一篇文章中,根据平台的不同,有25-33%的实用程序会失败。 在随后的文章中,这些数字的范围从9%到33%,其中GNU(在SunOS上)和Linux的故障率最低。
1990年的一篇文章得出的结论是:1)程序员不检查数组边界或错误代码; 2)宏使其难以读取和调试代码; 3)C语言非常不安全。 特别提到了极其不安全的
gets
功能和类型系统C,在测试过程中,作者发现了格式字符串漏洞,在其被大规模利用之前已有数年。 本文的结尾是对用户的调查,调查涉及他们修正错误或报告错误的频率。 事实证明,报告错误很困难,并且几乎没有修复它们的兴趣。
1995年的一篇文章提到了开源软件,并讨论了为什么它的错误更少。 报价:
当我们调查失败的原因时,出现了一个令人不安的现象:1990年报告的许多错误(约40%)在1995年仍以其确切形式存在。 ...
此处使用的方法很简单,而且大多是自动化的。 很难理解为什么开发人员不使用这种简单而免费的资源来提高可靠性。
仅在15-20年内,模糊测试技术才成为大型供应商的标准做法。
在我看来,1990年的声明还预见了未来的事件:
通常,编程C的简洁风格被视为极端,形式胜过正确的功能。 如最近的Internet蠕虫所示,输入缓冲区中溢出的可能性是一个潜在的安全漏洞。
测试方法
幸运的是,三十年后,巴顿博士仍然提供了
完整的源代码,脚本和数据来重现他的发现 :这是其他研究人员应效仿的值得赞扬的例子。 脚本可以正常工作,而模糊测试工具只需要进行少量更改即可编译和运行。
对于这些测试,我们使用了
来自fuzz-1995-basic信息库的脚本和输入 ,因为这里有
经过测试的应用程序的最新列表。 根据
README的说法,这是与原始研究相同的随机输入。 以下现代Linux的结果是通过与原始文章
相同的模糊代码和输入数据获得的。 仅用于测试的实用程序列表已更改。
超过30年的公用事业变化
显然,在过去的30年中,Linux软件包发生了一些变化,尽管许多行之有效的实用程序已经延续了数十年的血统。 在可能的情况下,我们从1995年的文章中获取了相同程序的现代版本。 一些程序不再可用,我们将其替换。 所有替换的理由:
cc1
:等效于1995年文章中的C预处理器。gdb
:等效于1995年的调试器。ditroff
: ditroff
不再可用。dtbl
:等效于旧dtbl
实用程序的GNU Troff。clisp
: lisp
的标准实现。more
⇨ less
:更少就是更多!swipl
: prolog
有两个选项:SWI Prolog和GNU Prolog。 SWI Prolog是更可取的,因为它是一个较旧且更完整的实现。gawk
: awk
GNU版本。- cc⇨gcc:标准C编译器。
compress
:GZip是旧版Unix实用程序的概念后代。lint
⇨ splint
:根据GPL重写的lint
。/bin/mail
mail⇨ /usr/bin/mail
:等效的实用程序以不同的方式。fort77
:Fortan77编译器有两种变体:GNU Fortran和Fort77。 建议将第一个用于Fortran 90,第二个用于Fortran77支持。 f2c
程序f2c
积极的支持;其更改清单自1989年以来一直得到保留。
结果
1989年的模糊技术在2018年仍然发现错误。 但是有一些进展。
要衡量进度,您需要一些基础。 幸运的是,这种框架适用于Linux实用程序。 尽管在1990年撰写第一篇文章时还没有Linux,但1995年的第二次测试在1995年Slackware 2.1.0发行版的实用程序上启动了相同的模糊代码。 相应的结果
在1995年文章的表3中给出
(第7-9页) 。 与商业竞争对手相比,GNU / Linux看起来非常好:
在免费的Linux版本的UNIX中,实用程序崩溃的百分比第二高:9%。
因此,让我们将1995年和2018年的Linux实用工具与1989年的模糊测试工具进行比较:
| Ubuntu 18.10(2018) | Ubuntu 18.04(2018) | Ubuntu 16.04(2016年) | Ubuntu 14.04(2014年) | Slackware 2.1.0(1995年) |
---|
崩溃 | 1(f77) | 1(f77) | 2(f77,ul) | 2(swipl,f77) | 4(ul,flex,indent,gdb) |
冻结 | 1(法术) | 1(法术) | 1(法术) | 2(法术,单位) | 1(标签) |
测试总数 | 81 | 81 | 81 | 81 | 55 |
失败/冻结,% | 2% | 2% | 4% | 5% | 9% |
令人惊讶的是,即使在最新版本的Ubuntu上,Linux崩溃和死机的数量仍然大于零。 因此,
f77
使用分段错误调用
f2c
程序,而
spell
程序挂在两个版本的测试输入上。
什么错误?
我能够手动找出某些错误的根本原因。 有些结果(例如glibc中的错误)是意外的,而其他结果(例如具有固定缓冲区大小的sprintf)是可以预测的。
Ul失败
ul中的错误实际上是glibc中的错误。 特别是,2016年在
这里和
这里 (另一人在
ul
中发现了它)都被报道
了 。 根据错误跟踪器,该错误仍未修复。 由于该错误无法在Ubuntu 18.04及更高版本上重现,因此已在发行版级别进行了修复。 从错误跟踪器的评论来看,主要问题可能非常严重。
撞机f77
f77
程序位于fort77软件包中,该软件包本身是f2c的外壳脚本,它是从Fortran77到C的源代码转换程序。调试
f2c
表明,当
errstr
函数打印的错误消息过长时,会发生故障。
f2c源代码显示sprintf函数用于将可变长度的字符串写入固定大小的缓冲区:
errstr(const char *s, const char *t) #endif { char buff[100]; sprintf(buff, s, t); err(buff); }
自创建
f2c
以来,似乎已经保留了该代码。 该程序至少自1989年以来就有
变化的
历史 。 在1995年重新进行模糊测试时,尚未对Fortran77编译器进行测试,否则将更早发现问题。
冻结法术
经典死锁的一个很好的例子。
spell
ispell
代表通过管道进行
ispell
。
spell
逐行读取文本,并在
ispell
生成该行大小的阻止记录。 但是,
ispell
一次最多读取
BUFSIZ/2
个字节的
BUFSIZ/2
(在我的系统上为4096个字节),并发出阻止记录以确保客户端已收到到目前为止已处理的验证数据。 两个不同的测试输入强制
spell
为
ispell
写入了一个超过4096个字符的字符串,从而导致死锁:
spell
等待
ispell
读取整个字符串,而
ispell
等待
spell
确认它已经读取了原始拼写更正。
悬挂单位
乍看之下,似乎存在无限循环条件。 挂起似乎是在
libreadline
而不是在
units
,尽管较新版本的
units
不会遇到此错误。 更改日志表明已添加输入过滤,可能会意外解决此问题。 但是,对原因进行彻底调查超出了本博客的范围。 挂
libreadline
的方法也许仍然存在。
旋转崩溃
为了完整起见,我想提一下
swipl
崩溃,尽管我没有仔细研究它,因为该bug早已得到修复,而且看起来质量很高。 失败实际上是在转换字符时调用的一种语句(即永远不应该发生的语句):
[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年的Fuzzing”文章,我们将
在使用fuzzer进行测试时检查Windows 10应用程序与
Windows NT / 2000等效版本相比如何健壮。 我认为答案是可以预见的。