TensorFlow的杀虫利器LibFuzzer

  1. 云栖社区>
  2. 博客>
  3. 正文

TensorFlow的杀虫利器LibFuzzer

【方向】 2017-03-09 13:51:12 浏览4229
展开阅读全文

更多深度文章,请关注:https://yq.aliyun.com/cloud


作者介绍:David Andersen, 美国宾西法尼亚州卡内基梅隆大学的计算机科学教授,居住于美国加州帕罗奥多市。热衷于计算机科学,关注机器学习中的各个领域。平时他也是一名极客,转轮和登山者爱好者,狂热的素食主义者。

在过去的一年中,我一直在研究如何提高TensorFlow的稳定性。 正如我之前的文章提到过的,Google期间目标之一是挖掘编写优质代码的行业最佳实践 Google,编写良好的代码是从每个普通程序员开始的,需要在一个良好的内部测试环境下进行测试,并且能通过代码审查进行改进,同时还要使用一些出色的代码质量工具。

最近越来越多的人开始关注基于模糊测试的漏洞检查工具(在程序或程序库中输入随机值,以试图导致它们崩溃)在软件测试中的作用John Regehr,一位非常杰出漏洞检查工具编译员 (链接里是他对模糊测试类型的阐述。)GoogleZero项目在过去4年里对FreeType库进行了持续的漏洞检查测试,并在各种程序中发现了大量的安全漏洞。 (基于模糊测试的漏洞检查并不是一个新的概念 - 它已经被软件安全研究人员使用了多年。它是由Barton Miller1988年发明。而在1997年,时还不了解模糊测试的我使用了一个随机输入生成器,通过使用这种方法我一个ISP终端服务器中发现了一个错误。我记得当时为此非常开心)。

当下,模糊测试工具,如AFLlibFuzzer不是完全随机的,而是可以被引导:它们以一个输入样本(我们称为“语料库”)开始,然后对进行不停的替换。 这些工具使用编译器和二进制重写来进行替换从而追求代码覆盖的最大化。 有了良好的起始语料库和覆盖引导这两个要素,就能达到令人满意的测试结果。

我在Google期间,Chris OlahDario AmodeiDanMané发表了人工智能安全中的具体问题研究报告 在文中他们讨论的问题和解决方案大都在机器学习领域,而我则对这些案例中所涉及的系统方面的问题感兴趣,例如奖励函数的缓冲溢出。传统的隔离方法,如沙盒测试,并不能有效的防止这类奖励函数的问题 (它表现为一种逻辑错误,而不是系统运行错误)。 虽然基于模糊测试的漏洞检测并不能说是一个完整的解决方案,但它却启发了我去挖掘把它运用到TensorFlow中的价值 所以,Kostya Serebryany鼓励,我决定libFuzzer写一些针TensorFlow内核的适配器

有些人可能会指出这类错误并不会出现在那些强类型编程语言但高性能的机器学习软件对性能准确率 和灵活性都有非常高的期望值。同时实现这三个目标是极具挑战的。我希望新的XLA编译器框架可以让这些目标更容易实现但这个框架还在开发中, 最终的效果还有待观察

如何开始?

所有libFuzzer测试都是从编写一个测试库调用或函数调用开始的就像这样

// fuzz_target.cc
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
  DoSomethingInterestingWithMyAPI(Data, Size);
  return 0;  // Non-zero return values are reserved for future use.
}

当编译后的漏洞检测机器码被执行时,它用在Data变量中提供的“随机”(已替换的)数据重复地调用该函数。如何将随机数据的Blob映射到对应用程序的调用,就是漏洞检测适配器程序员要做的。

当进行漏洞检测时,二进制代码通常使用LLVM的内存检测器进行编译,它会检测几个常见的内存错误,例如数组越界访问,并将其转换为程序崩溃。 libFuzzer驱动程序检测到崩溃并保存导致它的示例。 输出可能如下所示:

dga@instance-1:~/fuzz$ ./fuzz 
INFO: Seed: 44456222
INFO: Loaded 0 modules (0 guards): 
INFO: -max_len is not provided, using 64
INFO: A corpus is not provided, starting from an empty corpus
#0 READ units: 1
#1 INITED cov: 8 units: 1 exec/s: 0
#2 NEW    cov: 9 units: 2 exec/s: 0 L: 64 MS: 0 
=================================================================
==1310==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7fff56ccc6e8 at pc 0x0000004f69e1 bp 0x7fff56ccc690 sp 0x7fff56ccc688

WRITE of size 4 at 0x7fff56ccc6e8 thread T0

然后,保存为一个可以用来重现程序崩溃的文件。 等错误修复以后,我们通常会将此文件添加到Fuzz种子集,以备之后的回归测试使用。

TensorFlow中的运用

TensorFlow处理的大部分是数字信息,因此在这些操作中发现错误就变得很重要 - 例如,在尝试调试某个模型为什么不工作时,它可以大大减轻工作量。 但是测试数值结果需要一个正确的参考或规范。 在对TensorFlowXLA编译器进行测试时一般会做这些,例如,通过使用CPU版本作为参考,并确保XLA版本产生相同的结果。 这是确保XLA正确性的一个重要测试,但是当我开始编写我的版本时,我没有这么做,也不想尝试为每个操作编写规范。

然而,我则专注于漏洞检测更擅长的事情,那就是发现系统/程序崩溃。 首先从最简单的地方开始,那些我认为bug最可能出现的地方:复杂类型输入解析器。 TensorFlow包括编码和解码多种图像格式的功能,如tf.image.decode_png

从图像解码器能让事情变得很容易 我们可以示例输入的现有语料库开始,同时这些函数只需要一个输入,将字符串转化为Tensor类型就可以了。

发现并修复更多的错误

这种方法也可以在PNG解码器JPEG解码器中发现错误。 更多的模糊测试则使用了自动模糊测试架构Brennan Saeta已经基本完成了。

在解决了图像解码器后,我把注意力转向了一些字符串解析函数,先是在strtonum函数中发现一个微妙的错误, 然后是TensorProto解析器中。

有趣的是,模糊测试不只是发现预期的缓冲区溢出或其他程序失败 - 它也能指出代码在错误处理中薄弱的地方。

TensorFlow的基本设计原则是,内核中的错误应以友好的方式返回给调用者,以便他们可以适当地处理它。 处理这种情况的一个常见模式是编写类似这样的代码,其中在进入内核之后,程序员为尽可能多的错误条件写入检查:

  explicit AsStringOp(OpKernelConstruction* ctx) : OpKernel(ctx) {
...
    OP_REQUIRES_OK(ctx, ctx->GetAttr("T", &dtype));
    OP_REQUIRES_OK(ctx, ctx->GetAttr("precision", &precision));
...
    ... do the work here after making sure the above worked ...

这是一种很好的类库设计,因为它不会将设计者对异常处理的想法强加于用户。 通过在TensorFlow里抛出Fuzzer,我能够找到那些导致程序失败错误的确切位置,而不仅仅只是一个友好的返回信息。

我是快乐的杀虫者

之前,我一直认为模糊测试就是为了在其他人的代码中找到安全漏洞的工具。 但是,现在我我的想法改变了,我相信使用现代的基于模糊测试的漏洞检测工具是软件开发和测试的重要组成部分。 它们可能不会找到程序中所有的错误,你还必须为他们编写合适的适配器来让他们测试你的代码。但通过这种方式,你能从代码中发现一些新的东西,并帮助你预先发现问题。如果你有兴趣尝试用它检测自己的代码,那下一步就是看看这个libFuzzer教程

祝杀“虫”快乐!

(有关我的更多帖子,请参阅存档。)

数十款阿里云产品限时折扣中,赶紧点击领劵开始云上实践吧!

以上为译文

本文由北邮@爱可可-爱生活 老师推荐,阿里云云栖社区组织翻译。

文章原标题《Finding Bugs in TensorFlow with LibFuzzer》

作者:David Andersen,译者:friday012,审校:海棠。

文章为简译,更为详细的内容,请查看原文


网友评论

登录后评论
0/500
评论
【方向】
+ 关注