原文 https://lwn.net/Articles/569635/
配套ppt http://selinuxproject.org/~jmorris/lss2013_slides/cook_kaslr.pdf

引言

地址空间布局随机化(ASLR,Address-space layout randomization)是一种常见的技术,它将各种对象随机的放置在内存中,而不是使用固定地址,它使得被漏洞利用更加困难。在Linux系统中广泛在用户空间程序中使用ASLR,Kees Cook想将这个技术应用于Kernel中。在4月他提交了这个技术的代码。这份代码基于2011年Dan Rosenberg的提案。

攻击

这里有一种经典的数据结构用于向内核攻击。一个攻击者需要通过阅读内核代码、留意内核补丁或查看CVE来找到内核bug。攻击者使用这个bug来插入恶意代码到内核地址空间,使得内核执行到恶意代码中。下面的两个函数是一种简单的获取内核root权限的方式:

1
commit_creds(prepare_creds())

这些函数的存在使得攻击“对攻击者来说绝对容易”,一旦运行了恶意代码,该漏洞利用便会自行清除。例如,Rosenberg的RDS协议本地提权漏洞

这些类型的漏洞依赖内核地址空间中那些有趣的符号地址。这些符号地址在内核版本和发行版本中改变,但是在特定内核中是已知的(或是可以找到的)。ASLR技术破坏了这个过程,并给攻击增加了一层难度。

ASLR技术在用户空间中将栈、mmap区域、堆、代码区放置在随机区域。攻击必须依靠信息泄露才能绕过ASLR。通过利用其他bug漏洞,攻击者可以找到关注的有趣代码被加载的地址。

内核地址随机化

Cook的内核ASLR(KASLR)目前只会对内核代码(text)在启动时进行随机化。KASLR从某些地方开始,未来也将对其他区域进行随机化。

KASLR有许多好处。副作用是将中断描述表(IDT)从内核的其他部分移动到只读内存区域。非特权的SIDT指令可用于获取IDT位置,该IDT之前可用于确定内核代码的位置。因为IDT在其他地方,所以不能以这种方式使用它,当是它是只读的所以可以防止覆盖。

KASLR是一种”统计式防守”,因为使用暴力方法可以打破它。假设感兴趣的目标可能出现在1000个地方,那么暴力法会失败999次,成功一次。在用户空间这种错误会导致程序崩溃,但不会引起999台电脑崩溃。后者是对KASLR的错误暴力猜测法的后果。

另一方面,KASLR与休眠(即挂起磁盘)不兼容。这是一个可解决的问题。另一个问题是内核代码可以移动的空间容量。由于页表限制,代码必须2M对齐,那么可用空间为2G。在“理想世界”中,该位置提供1024个槽。在现实世界中,事实证明它更少。

我们需要使用一些步骤来防止信息泄露,这些信息会被用来确定内核加载地址。我们应该启用kprt_restrictsysctl,这样内核指针将不会被泄露至用户空间。同样的dmesg_restrict应该被启用,dmesg常常用于定位信息。同样日志文件(比如/var/log/messages)应该设置root用户访问权限。

最后一个泄露资源理论上很容易修复,但是被网络子系统的维护人员抵制。INET_DIAG套接字API使用内核对象地址做为句柄。这个地址对与用户空间来说是不透明的,但是这是一个真正的内核指针,因此可以用来确定内核地址。将其改为某些混淆值可以解决这个问题。

在一个完全不受限制的系统中,尤其是具有本地不受信任用户系统中,KASLR不会很有用。但是在使用容器或大量进程的系统上,KASLR很有用。比如Chrome浏览器中的渲染进程使用seccomp-BPF沙箱,限制漏洞利用获取所需信息。同时防止远程服务进行攻击,因为远程服务很少有信息泄露。

实现

KASLR已经被添加至ChromeOS中。它在发行版内核的Git树中,并将很快在稳定版本中推出。 Cook笑着说,他以“为那些不一定想要的人带来破坏性的安全变更”而闻名,但是,KASLR实际上是他提出的变更最少的问题。 造成这种情况的部分原因是“其他一些非常聪明的人”提供了帮助,其中包括Rosenberg,其他Google开发人员以及内核邮件列表中的人员。

Cook的补丁修改了boot过程,从而确定放置内核的最小安全地址。它会使用e820工具提供的内存区域计算内核可以使用的插槽,然后用最佳随机数源选择一个插槽。根据系统不同,可能使用rdrand指令、rdtsc(时间戳计数器,timestamp counter)指令中的低位、计时器I/O端口位。之后这个补丁会解压缩内核、处理重定位,然后启动内核。

这些补丁目前只适用于64位x86,尽管Cook计划接下来研究ARM,但是他对ARM知之甚少,所以他希望有人帮他。

内核虚拟地址空间当前的布局只为内核代码保留512M空间,为模块保留1.5G的空间。由于不需要那么多模块空间,因此补丁将其减少到1G,为内核留出1G空间,因此有512个可能的插槽(因为插槽需要2M对齐)。将模块位置添加到KASLR时,插槽数可能会增加。

接下来时三个虚拟机的演示,其中一个运行“Stock”内核(固定内核加载地址)和两个KASLR内核。查看每个上的/proc/kallsyms/sys/kernel/debug/kernel_page_tables会发现不同的地址值。Cook表示,他没有发现启用KASLR对性能引起可衡量的影响。

地址上的差异使得Panic难以解码,因此用于定位内核插槽的偏移被添加到Panic时的输出。Cook强调,对于启用KASLR的系统,信息泄露将成为一个更大的问题,并指出它与Secure Boot相似,区别于root和kernel ring0。在大多数情况中,开发人员并不关心内核信息泄露,这需要改进。

Cook说,开发人员可以采取一些更简单的步骤用于避免泄露内核地址,使用"%pK"格式打印地址普通用户将显示地址为0,而root用户仍然可以看到真实地址(如果启用了kptr_restrict,否则每个人都可以看到真实地址)。dmesg使用dmesg_restrict保护,内核不应该使用地址作为句柄。所有的这些技术使得KASLR至少在有限的环境中成为阻止攻击的有效技术。