Meltdown又名为恶意数据缓存加载漏洞,其存在于大多数现代处理器中,该漏洞可以使得用户态进程读取内核空间中的内容。本文将对其大致原理进行解读,其中的细节并不会过度涉及,稍有计算机底层基础知识的同学即可放心食用。

Meltdown
Meltdown

1. 背景知识

首先先来回顾一下计算机的基础知识,可以帮助更好地理解Meltdown漏洞。

乱序执行(来自维基百科):

在计算机工程领域,乱序执行(错序执行,英语:out-of-order execution,简称OoOE或OOE)是一种应用在高性能微处理器中来利用指令周期以避免特定类型的延迟消耗的范式。在这种范式中,处理器在一个由输入数据可用性所决定的顺序中执行指令,而不是由程序的原始数据所决定。在这种方式下,可以避免因为获取下一条程序指令所引起的处理器等待,取而代之的处理下一条可以立即执行的指令。

说白了,就是CPU执行指令的时候并不会严格按照顺序执行,只要指令之间没有依赖性,CPU就会尝试去执行当前指令后面的指令。

CPU缓存(来自维基百科):

在计算机系统中,CPU高速缓存(英语:CPU Cache,在本文中简称缓存)是用于减少处理器访问内存所需平均时间的部件。在金字塔式存储体系中它位于自顶向下的第二层,仅次于CPU寄存器。其容量远小于内存,但速度却可以接近处理器的频率。

上面的话可能不是很好懂,在本文中,只需要知道从高速缓存中读取数据和从内存中读取数据的时间差距很大就行了,一般从高速缓存中读取数据仅需要若干个时钟周期,而从内存中读取数据需要上百个时钟周期。

旁路攻击(来自维基百科):

在密码学中,旁道攻击又称侧信道攻击、边信道攻击(英语:Side-channel attack)是一种攻击方式,它基于从密码系统的物理实现中获取的信息而非暴力破解法或是算法中的理论性弱点(较之密码分析)。例如:时间信息、功率消耗、电磁泄露或甚是声音可以提供额外的信息来源,这可被利用于对系统的进一步破解。

这个就有点神奇了,比如本文中的Meltdown漏洞将利用缓存读取的时间信息来破解内核空间的内容。

Meltdown漏洞原理剖析

首先先明确一下我们想干什么:给定一个内核空间的内存指针 ptr,我们希望从用户态进程中读取其内容 *ptr

为了实现这一目的,我们可以在用户态程序中构造这样的代码:

raise_exception();
access(probe_data[data*4096]); // data = *ptr

第一行,我们先制造一个异常(随便什么异常,比如被零除之类的)。

第二行我们会尝试访问 probe_data 这个数组的某个字节,probe_data 是一个很大的数组,其大小至少为 256*4096。访问字节的下标为 data*4096,而 data 是内核空间中指针 ptr 的值。正常来说这一行代码会产生内存非法访问的错误,因为用户态进程是没有权限访问内核空间的。

但是由于在第一行中引发的一个异常,这会导致程序需要暂时进入内核态处理这个异常。最蛋疼的是,因为CPU的乱序执行,导致第二行被执行了,因为这个时候在内核态,可以正常读取指针 ptr 的内容,并不会发生内存非法访问的问题。之后CPU发现第二行代码不应该被执行,将所有执行的结果都回滚,然后返回用户进程。

本来一切都没有问题,所有东西都回滚了。但是因为第二行是会访问数组 probe_data 的某个字节,根据现代CPU的缓存原理,这个字节(严格来说,是包括其前后的某些字节构成的缓存行)会被载入缓存,这个缓存CPU可没有令其回滚。这就可以进行旁路攻击,我们可以根据这个缓存做文章,得到 ptr 的内容。

具体做法如下,之前我们说过了 probe_data 大小至少为 256*4096,因为内存的分页大小一般为 4096 字节,所以这样就相当于有 256 个内存分页。然后我们使用下面的代码遍历一遍这 256 个内存分页:

for i in range(256){
    access(probe_data[i]); // record the access time
}

因为上述的第二行代码被乱序执行导致了这 256 个内存分页有某一页的开头被载入缓存,所以我们只需要记录遍历 256 个内存分页所需要的时间,其中肯定有一个访问时间远远短于其他的内存分页。那么这个分页对应的索引就是 ptr 的内容。最终我们通过这种旁路攻击的方式得到了 ptr 的值。

这里最重要的就是要保证 256 个内存分页一开始都不在缓存里面,所以我们将数组 probe_data 设置的很大。其次 ptr 是一个 char* 的指针,所以其只能有256个值,所以我们设置了 256 个内存分页。

下面我们使用某种方法将 probe_data 从缓存中清掉,改变 ptr 的值,使其指向下一个字节,再重复以上内容就可以从内核空间中取到下一个字节的数据了。

这个漏洞的构思很巧妙,CPU的乱序执行和缓存设计都没有什么问题,但是合在一起却出现了这么个漏洞。现有的CPU的操作系统大都不能抵御这种漏洞,操作系统进行更新后通过重新设计应该可以抵御,但是可能会损失一些性能。