NULL指针引用和内核bug的利用

• 本文约 1197 字,阅读大致需要 3 分钟 | Security

总算是发公告了,可以说具体的事情了。

FreeBSD昨天发布了2项安全公告和1项Errata Notice:SA-09:13.pipeSA-09:14.devfsEN-09:05.null。两个安全公告修正的是同一类问题,也就是我们常说的多线程程序中的竞态条件(Race Condition);EN-09:05.null则是增加了一个使这类问题不再那么容易被利用达到特权提升目的的功能,但默认并不启用。

在 C 程序中,NULL 指针是一项很有用的特性。NULL有很多功能,例如:

等等。与用户态程序类似,内核也使用 NULL 完成相同的目的。当内核自己引用空指针内存(NULL deferencing)时,x86/amd64硬件会产生异常12。

不过,传统上 Unix 系统并不明确地禁止用户在虚拟地址0上映射自己的内存页。因为对于程序来说,它也完全可以把地址 0 看作一个再普通不过的内存地址。另一方面,有些应用程序,特别是模拟器一类的程序,很可能会需要利用这一特性来完成一些功能,因为如果靠截取 SIGSEGV 并进行特殊处理的话,效率会很差。

事实上,用户程序能够在地址 0 上映射内存页这件事本身并不是一个问题。真正的问题在于,为了改善性能,许多操作系统(如果不是全部支持 x86 的通用操作系统的话)在内存分配方面会采用一个小技巧,将内核内存和用户内存映射在同一个虚拟地址空间,从而避免在执行系统调用进入和退出内核时进行完整的地址空间切换,因为这个操作在x86硬件上的代价是比较大的,而另一方面,由于同样的原因,这个技巧也会改善内核访问用户地址空间中内存的性能。

然而,这一切能够相安无事的前提是,内核没有bug,或者用户态运行的程序是"友善"的。由于欠锁等原因,内核可能会引用 NULL 指针(例如,在没有获取对象引用的情况下调用对象中的函数指针,而此时另一线程已经将对象的内存释放并进行了抹零操作)。这种竞态条件通常会导致系统崩溃,但如果用户能够控制虚拟地址为0的那一小块内存,则这类竞态条件就有可能被利用来执行用户所指定的代码,进而导致特权提升问题。

从 FreeBSD 8.0-RELEASE 开始,FreeBSD将默认禁止用户程序映射虚拟地址为0的内存页(返回EINVAL,个人认为应返回EPERM)。修正日期之后的 FreeBSD 其他版本如 6.x 和 7.x,可以通过将 security.bsd.map_at_zero=“0” 增加到 /boot/loader.conf 来启用这一特性。