TensorFlow的杀虫利器LibFuzzer
更多深度文章,请关注云计算频道:https://yq.aliyun.com/cloud
作者介绍:David Andersen, 美国宾西法尼亚州卡内基梅隆大学的计算机科学教授,居住于美国加州帕罗奥多市。热衷于计算机科学,关注机器学习中的各个领域。平时他也是一名极客,转轮和登山者爱好者,狂热的素食主义者。
在过去的一年中,我一直在研究如何提高TensorFlow的稳定性。正如我之前的文章提到过的,我在Google期间目标之一是挖掘编写优质代码的行业最佳实践。在Google,编写良好的代码是从每个普通程序员开始的,需要在一个良好的内部测试环境下进行测试,并且能通过代码审查进行改进,同时还要使用一些出色的代码质量工具。
最近越来越多的人开始关注基于模糊测试的漏洞检查工具(在程序或程序库中输入随机值,以试图导致它们崩溃)在软件测试中的作用。John Regehr,是一位非常杰出的漏洞检查工具编译员。(链接里是他对模糊测试类型的阐述。)Google的Zero项目在过去4年里对FreeType库进行了持续的漏洞检查测试,并在各种程序中发现了大量的安全漏洞。(基于模糊测试的漏洞检查并不是一个全新的概念 - 它已经被软件安全研究人员使用了多年。它是由Barton Miller在1988年发明的。而在1997年,那时还不了解模糊测试的我使用了一个随机输入生成器,通过使用这种方法我在一个ISP终端服务器中发现了一个错误。我记得当时我为此非常开心)。
当下,模糊测试工具,如AFL和libFuzzer并不是完全随机的,而是可以被引导的:它们以一个输入样本(我们称为“语料库”)开始,然后对其进行不停的替换。这些工具使用编译器和二进制重写来进行替换从而追求代码覆盖的最大化。有了良好的起始语料库和覆盖引导这两个要素,就能达到令人满意的测试结果。
我在Google期间,Chris Olah,Dario Amodei和DanMané发表了人工智能安全中的具体问题研究报告。在文中他们讨论的问题和解决方案大都在机器学习领域,而我则对这些案例中所涉及的系统方面的问题感兴趣,例如奖励函数的缓冲溢出。传统的隔离方法,如沙盒测试,并不能有效的防止这类奖励函数的问题 (它表现为一种逻辑错误,而不是系统运行错误)。虽然基于模糊测试的漏洞检测并不能说是一个完整的解决方案,但它却启发了我去挖掘把它运用到TensorFlow中的价值。所以,在Kostya Serebryany鼓励下,我决定为libFuzzer写一些针对TensorFlow内核的适配器。
(有些人可能会指出这类错误并不会出现在那些强类型编程语言,但高性能的机器学习软件对性能,准确率,和灵活性都有非常高的期望值。同时实现这三个目标是极具挑战的。我希望新的XLA编译器框架可以让这些目标更容易实现,但这个框架还在开发中, 最终的效果还有待观察)
如何开始?
所有libFuzzer测试都是从编写一个测试库调用或函数调用开始的,就像这样:
// fuzz_target.cc
当编译后的漏洞检测机器码被执行时,它用在Data变量中提供的“随机”(已替换的)数据重复地调用该函数。如何将随机数据的Blob映射到对应用程序的调用,就是漏洞检测适配器程序员要做的。
当进行漏洞检测时,二进制代码通常使用LLVM的内存检测器进行编译,它会检测几个常见的内存错误,例如数组越界访问,并将其转换为程序崩溃。 libFuzzer驱动程序检测到崩溃并保存导致它的示例。输出可能如下所示:
dga@instance-1:~/fuzz$ ./fuzz
然后,保存为一个可以用来重现程序崩溃的文件。等错误修复以后,我们通常会将此文件添加到Fuzz种子集,以备之后的回归测试使用。
TensorFlow中的运用
TensorFlow处理的大部分是数字信息,因此在这些操作中发现错误就变得很重要 - 例如,在尝试调试某个模型为什么不工作时,它可以大大减轻工作量。但是测试数值结果需要一个正确的参考或规范。在对TensorFlow的XLA编译器进行测试时一般会做这些,例如,通过使用CPU版本作为参考,并确保XLA版本产生相同的结果。这是确保XLA正确性的一个重要测试,但是当我开始编写我的版本时,我没有这么做,也不想尝试为每个操作编写规范。
然而,我则专注于漏洞检测更擅长的事情,那就是发现系统/程序崩溃。首先从最简单的地方开始,那些我认为bug最可能出现的地方:复杂类型输入解析器。 TensorFlow包括编码和解码多种图像格式的功能,如tf.image.decode_png。
从图像解码器能让事情变得很容易– 我们可以示例输入的现有语料库开始,同时这些函数只需要一个输入,将字符串转化为Tensor类型就可以了。
发现并修复更多的错误
这种方法也可以在PNG解码器和JPEG解码器中发现错误。更多的模糊测试则使用了自动模糊测试架构,Brennan Saeta已经基本完成了。
在解决了图像解码器后,我把注意力转向了一些字符串解析函数,先是在strtonum函数中发现一个微妙的错误,然后是TensorProto解析器中。
有趣的是,模糊测试不只是发现预期的缓冲区溢出或其他程序失败 - 它也能指出代码在错误处理中薄弱的地方。
TensorFlow的基本设计原则是,内核中的错误应以友好的方式返回给调用者,以便他们可以适当地处理它。处理这种情况的一个常见模式是编写类似这样的代码,其中在进入内核之后,程序员为尽可能多的错误条件写入检查:
explicit AsStringOp(OpKernelConstruction* ctx) : OpKernel(ctx) {
这是一种很好的类库设计,因为它不会将设计者对异常处理的想法强加于用户。通过在TensorFlow里抛出Fuzzer,我能够找到那些导致程序失败错误的确切位置,而不仅仅只是一个友好的返回信息。
我是快乐的杀虫者
之前,我一直认为模糊测试就是为了在其他人的代码中找到安全漏洞的工具。但是,现在我我的想法改变了,我相信使用现代的基于模糊测试的漏洞检测工具是软件开发和测试的重要组成部分。它们可能不会找到程序中所有的错误,你还必须为他们编写合适的适配器来让他们测试你的代码。但通过这种方式,你能从代码中发现一些新的东西,并帮助你预先发现问题。如果你有兴趣尝试用它检测自己的代码,那下一步就是看看这个libFuzzer教程。
祝杀“虫”快乐!
(有关我的更多帖子,请参阅存档。)
以上为译文
本文由北邮@爱可可-爱生活老师推荐,阿里云云栖社区组织翻译。
文章原标题《Finding Bugs in TensorFlow with LibFuzzer》
作者:David Andersen,译者:friday012,审校:海棠。
文章为简译,更为详细的内容,请查看原文