怎样生成一个有效的测试包?对于测试输入,怎样能够有更多的这样的字节码,或者产生更多可运行的应用程序并在虚拟机上再跑一跑?
1.1 如何暴露出产品级虚拟机的缺陷
对于第一个问题,即怎么样去暴露出一个产品及虚拟机的缺陷,这里面在跑的时候就会发现有一个困难,这个困难就是在学术圈里叫做“缺少一个测试喻言”。如果要测一个 Java 虚拟机的话,我们拿一个类过来跑跑,在一个 Java 虚拟机上面,会得到一个真实的结果,这个时候我们把真实结果和一个预期结果来比较一下,如果能够发现它们里面的不一致,那么这个就说明 Java 虚拟机出现了一些问题。
我们的预期结果到底是怎么来的?实际上有一个 Java 虚拟机规范,假如 Java 虚拟机规范,它也是一个能够运行的机器,那么它跑一跑,能够得到一个运预期结果。但是实际上,我们说 Java 虚拟机规范它本身也不能跑,所以这个事情实际上没有很好的解决方案。后来,我们意识到差别测试技术,这个也是 Java 虚拟机开发中,大家都采用的一个方法,也就是说有多个虚拟机,把一个类或者是应用拿到不同的虚拟机上去跑,比较它们之间的结果是不是有差别。
如果这个大家结果都一致,那就很好,如果结果不一致,那么就可以去预测一下这里面是不是有 Java 虚拟机的实现出了问题。
1.2 如何获得一个有效的测试包
对于第二个问题,就是怎么样能够有更加复杂的或者更加花样繁多的字节码来做测试?一开始,我们尝试去使用现实中大量的类,从网上甚至从 openJDK 里面自己的包里解压出很多类文件,放在 JVM 不同版本上面去跑。这里面的确还是能够发现一些问题,一些不一致的现象,但是这个不一致更多是兼容性问题。
于是我们很快就转向了第二个技术,叫“领域感知的模糊测试技术”。模糊测试是应用在安全领域里面的一个测试技术,它可以帮助发现一些安全问题。比如说有一个文件,有个图像,把这个图像一位一位地变化,用以查看应用软件是否比较健壮。如果把技术应用到 Java 虚拟机上面,就要做一些调整,这种调整是领域感知的 ,也就是说我们知道 Java 字节码它本身的一些特性,根据它的特性来做一些变化,这个工作更加泛,我们有一个种子类,通过这个种子,我们会把它变来变去,变成一堆的测试类,放到 Java 虚拟机上跑。
这个工作我们曾发在 PLDI2016,还有一个明年的 ICSE 上的工作,第一个是 classfuzz,第二个 classming。让我们对于 Java的类执行过程进行一个深入了解,一开始做的工作比较偏向于上层,就是更多的去关注了 Java 类的是怎么样去导进来,怎么样链接,怎么样去初始化等等。这个是 classfuzz 的主要工作。后来做到一定的程度,我们就转向了下层,怎么样去做验证,执行,这个时候就会去想类的执行会不会引发一些差别,我能不能在不同虚拟机上真的跑出一些不一样的结果?
Classfuzz
下面,我就分别对这两个工作进行介绍。classfuzz 是一个很简单的一个想法,就有点像一开始最传统的模糊测试的技术,对合法的 Java 字节码文件,我们想进行一个语法变种,变种以后,比如说对于 Java 类我们得到它的一个语法树,去尝试修改,比如说把 public 改成 private,把文件名改一改,把这个函数名改一改,这样的话可以生成很多比较奇怪的类,把奇怪的类拿过来以后,就可以去测试一下 Java 虚拟机的一个健壮性。
但是这个时候我们很快就意识到,这个时候它有一个缺陷,我们只是去挑战了虚拟机的一个启动过程,就是看看它的格式检查对不对?去看看他的链接过程对不对,看看它的初始化对不对。Classfuzz 是 2016 年的一个论文,但一直到近期我们还是用它发现了一点问题。右边是一个字节码,当然这个字节码比较繁杂,把这样的一个类,放到 Open J9 和 HotSpot 上面跑, HotSpot 立刻就报了一个格式错误,那么 Open J9 是属于一个正常运行,这里面是因为没有 main 函数,但是它总体算是一个正常通过的类。后来我们就研究了差别原因,它的主要原因就是这里面它有一个
flag,表明这个是一个接口文件 interface。那么从规范上来说,如果接口 flag 被设定了以后,它就要同时去设一个 abstract 的 flag,所以 HotSpot 报了一个格式问题,这个是正确的。那么 Open J9 上我们就找到一个缺陷。