现代CPU为了优化速度,会在等待某些慢速操作(内存访问,分支判断,权限检查等)返回之前,提前执行后面的代码,如果执行的不对,就回退掉之前执行的结果,但是回退的不是很干净,会存在某些残余(加载到CPU缓存中的内存数据不会被清除等),基于这些残余,就有可能绕过一些安全机制,获取到一些非法数据。
Meltdown
利用了CPU中的乱序执行(Out-of-order execution)。CPU在内存访问和权限检查之间有个竞争,CPU访问一个没有权限的地址,在乱序执行期间被暂时加载到CPU的缓存中。如果权限检查没有通过,加载到缓存中的内容不会被清除。通过测量内存访问的时间(加载到缓存里的数据用时少),就可以推断出没有权限的地址里的内容。
1 ; rcx = kernel address, rbx = probe array
2 xor rax, rax
3 retry:
4 mov al, byte [rcx]
5 shl rax, 0xc
6 jz retry
7 mov rbx, qword [rbx + rax]
Meltdown的核心:一个无法访问的内核地址被移到一个寄存器中(指令4),引发异常。后面的指令(指令5,6,7)应该不会被执行到,但是由于CPU存在乱序执行,所以后面的指令(指令5,6,7)在异常发生前被执行了。导致qword中的数据被加载到cpu缓存中,通过测量qword数组中数据的访问时间,找出用时最快的就可以推导出rax的值,从而得到rcx的值。
spectre
利用了CPU的分支预测(branch prediction),有很多变种,这里只介绍几个最简单的,存在CPU分支预测的点,几乎都可以通过训练,使CPU提前运行非法代码。
- Bounds Check Bypass
if (x < array1_size)
y = array2[array1[x] * 4096];
if语句中对于x的判断,可以保证x的合法,重复运行这段代码,引导CPU的分支预测提前运行后面的代码。这时把x的值设成大于array1_size的值,后面的代码也会在条件检查结果出来之前提前被CPU运行。通过测量array2的CPU缓存就能推导出array1越界内存中的值。
- Branch Target Injection
懒了,直接看这个吧Static calls in Linux 5.10
网友评论