导语:如果让机器自己完成我的工作,每天岂不是躺着收钱? 虽然我知道除非自由职业或创业,老板永远都会找活给你干。
我在GCC上实现的trace-cmp功能正式成为GCC官方标准。Wish Wu是我的英文名。
我写的主要代码文件:
https://gcc.gnu.org/svn/gcc/trunk/gcc/sancov.c
GCC 2017-09-06 修改日志:
https://gcc.gnu.org/svn/gcc/trunk/gcc/ChangeLog
我用公司邮箱与GCC维护者和开发者交流的邮件列表:
https://gcc.gnu.org/ml/gcc-patches/2017-09/msg00378.html
我为什么想到去改GCC
需要介绍一下我自己和我的工作,我是来自蚂蚁金服巴斯光年实验室的安全研究员,主要负责Android操作系统本身的漏洞挖掘和漏洞的利用。Google的安全公告里可以找到我的名字https://source.android.com/security/bulletin/2015-08-01。过去我都是在不知道有没有漏洞的情况下阅读源代码,推测漏洞的位置。这样工作精力消耗大,也有产出风险。日渐重复的工作内容也使工作变得无聊,于是我诞生了一些想法。
我有一个梦想
如果让机器自己完成我的工作,每天岂不是躺着收钱?虽然我知道除非自由职业或创业,老板永远都会找活给你干。不过我还是想找一个方法能使工作轻松一点。Google开发的模糊测试工具AFL、libfuzzer启发了我,他们利用机器学习中的遗传算法https://www.zhihu.com/question/23293449 ,使程序能有一定自动走代码分支的能力,进而达到测试代码的目的。
具体实现方法就是编译时在代码里插桩,运行时获取此次运行的效果,通过一个算法判断是否保留当前用例。
可以用机器学习里的遗传算法的术语解释:把软件的一个用例视作“个体”,“个体”的集合称作“种群”,“种群”中挑出一个或多个“个体”通过“变异”产生新“个体”,新“个体”通过“适应函数”进行计算会得出此“个体”是否应该存活,如果存活新“个体”加入“种群”。目标是能诞生一个理想的“个体”,此“个体”能触发软件的漏洞。
AFL、libfuzzer用一套复杂的“适应函数”来判断新用例是否有继续存在的价值。此工具的功效很大程度上取决于“适应函数”的好坏。如果能从代码里获得更多的运行时信息,会非常有利于优化“适应函数”。
llvm的clang提供了一个参数用于追踪所有比较语句http://clang.llvm.org/docs/SanitizerCoverage.html#tracing-data-flow。
然而不幸的是,我当前的研究目标是Linux内核,Linux内核不支持用clang编译,也就用不了这个功能,而现在gcc当时也就提供trace-pc这种简陋的功能。
既然决心要让机器自己做事情怎么能退缩?于是翻看gcc源代码svn://gcc.gnu.org/svn/gcc/trunk 阅读相关文档https://gcc.gnu.org/onlinedocs/gccint/ 发现可以给gcc写插件修改代码,并且看上去不难,何乐而不为?
原来改起来挺简单
GCC是通过GIMPLE这种中间语言去描述代码,开始想只要在GIMPLE_COND和GIMPLE_SWITCH语句之前添加一个callback就行了,代码可以模仿gcc的其它代码如gcc/asan.c文件里的代码。刚开始只实现了插GIMPLE_COND的callback的插件,因为GIMPLE_SWITCH的callback需要声明添加一个初始化好的静态数组,这些代码想写好还要点时间。最终花了一个星期时间编写调通了代码,能成功编译Android Linux内核,并在Pixel手机上运行起来。
跟GCC的维护者打交道
由于GCC插件是可以直接编译到GCC内部的,我之前就考虑过直接给GCC写代码。考虑到竞争对手也可以用这段代码做出成绩或者泄漏自己的工作动向,所以没做。不过后来想到工具最大功效并不完全取决于有无这个功能。于是开始着手开发GCC。GCC开发的具体要求:https://gcc.gnu.org/contribute.html
激发讨论
当我发出第一封邮件https://gcc.gnu.org/ml/gcc/2017-07/msg00046.html 后,激起了一波讨论。主要集中在实现这个功能的意义、实现方法和API是否稳定等的问题上。来自Google的 Dmitry Vyukov首先提出了兴趣,他不仅是GCC的trace-pc功能的作者也是Google支持的syzkaller项目https://github.com/google/syzkaller ,一个Linux内核漏洞挖掘工具的主要开发者之一。我和他对trace-cmp用法上意见有很大不同,具体争论可以查阅邮件列表。当时Google支持的asan https://github.com/google/sanitizers/wiki/AddressSanitizer 的开发者Kostya Serebryany也参与了进来提出了一些有用的意见。
可惜的是当时GCC的维护者可能认为依旧没有足够的需求去添加这个功能,近一个月没有回复我的新邮件。
苛刻的代码要求
需要感谢Google的Dmitry Vyukov,他向RedHat的 Jakub Jelinek推进了这件事。Kakub是熟练的GCC开发者,具体到代码对齐和命名规则都提了要求。最后不仅在GIMPLE_COND语句上,GIMPLE_ASSIGN语句的两种用于“比较”的表达式上也添加了callback。并和LLVM的clang实现的API保持了一致。他的大部分建议都是有用的,我的patch改了几轮后依旧能提意见。不仅是功能,运行效率上也改了不少。最终他直接在我的patch上做了一些改动后合并到了GCC主线。Dmitry也已经把功能集成到了他的syzkaller工具中https://groups.google.com/forum/#!topic/syzkaller/r0ARNVV-Bhg
我们需要硬实力
我发现开源软件的维护者有很多都是在职的员工,我自己写的代码只有被他们需要了才会被接受。中国在操作系统和应用开发工具上发展还是缓慢的。在此我提出一些想法。将来会不会诞生一种中间语言,任何其它语言都可以转换成它,它也可以转换成任何其它语言,当某位开发者需要阅读它时,它会用当前开发者容易理解的语言表达出来,甚至是自然语言的形式。当它要运行的时候,它就转换成系统能“阅读”的形式运行。将来会不会诞生一个操作系统,几乎是硬件实现,它能把刚才的中间语言转成硬件能加速运行的形式运行。
这样测试人员能用同一套工具测试所有不同语言写的代码,开发人员各自用自己喜欢的语言开发,硬件也能用自己最快的速度运行。希望有人能实现它。
希望文章能对大家有所帮助。