http://www.cc.gatech.edu/~klu38。
操作系统内核作为可信计算基(TCB),其安全性是至关重要的。为保证内核的安全性,多种安全机制已经被普遍应用,其中主要包括kASLR和StackGuard。然而这两种安全机制的有效性的前提是内核中没有信息泄漏。换句话说,如果有信息泄漏,kASLR和StackGuard将会变得没有意义,因为它们所依赖的一些随机值会被泄漏。那么问题来了,内核中是否存在大量的信息泄露?我们通过调研分析发现答案是肯定的:内核中存在大量的信息泄漏漏洞。比如,仅2013年就有近60个有CVE的Linux内核信息漏洞。随着进一步研究发现大部分信息泄漏(大约60%)是因为读取未初始化内存(uninitialized data read)导致,如图1所示。
可以想象一下,如果建立一个内存对象(memory object)但不去初始化它,那么读取这个内存对象所得到的数据将会是以前遗留在内存中的一些数据。如果这些数据包含kASLR随机过的地址,StackGuard使用的随机canary,或者是之前用户遗漏的密码等重要信息,那么就会造成这些重要信息的泄漏。而导致读取未初始化内存的原因又分为两种:程序员的疏忽和编译器的优化。编译器的问题导致了一个严重且普遍的问题:即使程序员初始化一个对象中的所有成员,仍然有一些字节没有被初始化。代码1列出了一个例子。在这个例子中程序员初始化了内核堆栈对象“ci”的每一个成员,然而编译器在第二个成员“slow”之后插入的3个填充字节仍然没有初始化,当“copy_ to_user”把未初始化字节拷贝给用户空间时导致信息泄漏。目前已经有很多攻击利用这样的信息泄漏来突破kASLR和StackGuard然后实现iOS越狱,Android root等等攻击。虽然这个问题这么严重,然而,目前并没有有效地防止读取未初始化内存的方法或工具。
图1 导致Linux 内核信息泄漏原因统计(2013-2016年)为了解决这个问题读取未初始化导致信息安全泄漏,我们提出了一种基于LLVM编译器的方法。简单来说,需要通过细颗粒度的(字节级别)、跨函数的、精确的可到达性和初始化性程序分析来检查是否存在这样一些不安全内存对象:存在至少一条程序路径使得一个内存对象在离开内核空间的时候并没有完全被初始化。当检测出这样的内存对象以后,我们会在内核中插入一些代码对这些内存对象进行置零初始化。在实现UniSan过程中,通过设计很多方法来保证程序分析的完全性(也就是没有漏报),比如解决了找间接调用对象的问题。目前已经基于LLVM实现了UniSan,并且用它来保护最新的Linux内核和Android内核。实验结果显示UniSan保护过的内核运行非常稳定,毫无问题。作为验证,我们发现UniSan能完全防止已有的信息安全漏洞并且发现大量的新漏洞。
代码1 编译器优化插入未初始化的填充字节,导致信息泄漏从性能角度看,UniSan几乎没有引起任何性能的降低(大多数情况下,性能影响小于0.5%)。综合这些实验结果,UniSan以一种有效的,高性能的,无漏报的方法解决了操作系统内核中由读取未初始化内存导致的普遍的信息泄漏问题。
(注:原文发表在ACM CCS 2016,更多细节请看UniSan文章:http://www.cc.gatech.edu/~klu38/ publications/unisan-ccs16.pdf)