计算机操作系统有一个最基本的安全目标:保证用户程序不能任意访问内核/其他用户程序的内存。一旦某个恶意应用可以任意访问其他应用程序/操作系统的内存,那么后果则不堪设想。
为了实现这个目标,操作系统和计算机硬件(CPU)采取了以下措施:
操作系统:通过虚拟内存为每个应用程序和内核开辟独立的地址空间,规定相应的访问权限。
CPU:通过硬件实现支持虚拟内存(TLB)及其相应的访问权限。
举例来讲,如果有一个用户程序试图访问一个内核的内存地址,那么在CPU执行相应指令的过程中会探测到没有访问权限,进而触发中断/异常,导致程序终结。
所以一般情况下,除非恶意程序通过软件漏洞提权获得了内核权限,否则是无论如何也绕不过CPU 的权限检查从而能访问内核地址的。
具体来看,假设非法访问内存的指令为
mov rax byte[x] // 将内核地址x处的内容存到寄存器rax
操作系统会事先标注好内核的内存地址范围,如果 x 在内核的这个地址范围内,并且 CPU 不是以内核模式运行的话,那么该指令会被 CPU 标注为非法,引起异常,异常处理程序会将 rax 清空为0,并且终结此程序,这样后续指令再来读 rax 的时候就只能读到0了。
目前看来这一整套流程还是无懈可击的。
然而这里一个重要的前提假设是:CPU 在做权限检查并且将 rax 清零的过程中,不会泄露任何关于 [x] 的信息。
然而 Meltdown 和 Spectre 这两个漏洞厉害的地方就在于,利用现代CPU speculative execution (预测执行)的漏洞,在 rax 被清零之前把信息传递了出去。
以 Meltdown 的攻击代码(简化版)为例
mov rax byte[x]// 非法操作
shl rax 0xC // rax * 4096, page alignment
mov rbx qword [rbx + rax] // [rbx] 为用户空间的一个array,合法操作
理论上讲,在执行第二条指令之前,rax应该已经被清零了。然而在实际的 CPU 运行中,为了达到更好的性能,第二条和第三条指令在异常处理生效之前都会被部分执行,直到异常处理时 rax 和 rbx 被清零。
目前看起来也没什么问题,因为rbx 也会被清零,关于 [x] 的任何信息都没有留下。
但问题的关键就在第三行指令:如果地址 rbx + rax 不在cache中的话,CPU 会自动将这一地址调入cache中,以便之后访问时获得更好的性能,然而异常处理并不会将这个cache flush掉。而这条 cache 的地址是和 rax 直接相关的,这样就相当于在 CPU 硬件中留下了和rax 相关的信息。
那么如何还原 rbx + rax 这个被cache的地址呢?这时候需要用到的原理就是利用cache的访问延时,即已经被cache的数据访问时间短,没有被cache的数据访问时间长。由于[rbx]这个array是在用户地址空间内的,可以自由操作,首先我们要确保整个 [rbx]这个array 都是没有被cache的,然后执行上述攻击代码,这时候 rbx + rax 这个地址就已经被cache了,接下来遍历整个[rbx] array,来测量访问时间,访问时间最短的那个 page 就可以确定为 rbx + rax。(如下图,图来自原论文meltdown)
rbx + 84 * page_size 处访问时间最短,因此确定原 rax 为84
至此,我们就取到了内核地址空间 x 处的一个 byte (84),如此循环,即可获得全部内核空间的数据,也就是说,整个电脑没有任何秘密可言,而且此漏洞完全不需要特殊权限,因此个人台式/云主机几乎都不可幸免(可怕!)。
至于 spectre,和 meltdown 的原理相似,利用分支预测错误的 speculative execution,访问到不该访问的信息,并且同样是通过 cache 这个side channel 传递出去。而且由于利用的分支预测原理普及率非常高,Spectre 的影响面更大一些。
关于这两个漏洞的论文在这里(为PDF英文文档):
[1] Meltdown:https://meltdownattack.com/meltdown.pdf
[2] Spectre:https://spectreattack.com/spectre.pdf
[3] Flush-Reload Attack:http://t.cn/asLbQf(百度学术)
[4] KAISER: https://gruss.cc/files/kaiser.pdf
注:看不懂没关系,你只用知道这两个漏洞是CPU设计缺陷所致,是无法完全修补的就可以啦
领取专属 10元无门槛券
私享最新 技术干货