当前位置: 首页 > 工具软件 > Meltdown > 使用案例 >

Meltdown漏洞分析

彭鹭洋
2023-12-01

Meltdown的原理

CVE-2017-5754(Meltdown)虽然已经被修复,而距离它的发生也已经过了几年了。但是研究这个漏洞仍然能够增加对操作系统内存管理的理解,而下一个这样的漏洞在那里?仍然令人期待;-)

OS为本质上就是代替User操纵底层硬件的一个软件,它需要做到很重要的一点就是隔离。对很多个用户的隔离,对底层硬件的隔离,隔离是所有安全的基础。Metldown漏洞其实就是有人在猜测硬件CPU的执行细节,一般来说硬件的细节被隐藏,是不能够被上层用户知道的。

//core of Metltdown attack
char buf[8192];
r1 = <a kernel virtual address>;
r2 = *r1;
r2 = r2 & 1;
r2 = r2 * 4096;
r3 = buf[r2];
  • buffer就声明了一个正常的,普通用户可以使用的内存,当然是从0开始一个虚拟内存,要被重新映射才能被MMU找到。
  • r1是某个感兴趣的虚拟地址
  • 将r1里的内容取出放到r2内
  • 取下r2的低bit位,这里只是1位
  • 因为一个bit要么是0,要么是1因此这里的r2要么是4096,要么是0
  • 可以取到buffer的0位或1位

问题1:为什么要进行这样的操作?
因为用户能得到内存里的数据却不能访问内存里真实的地址。所有指令对应的都是虚拟地址,通过pagetable来查找对应,而如果是SV39模式下,每个PTE表项会有一个权限标志位Valid来表示用户的权限,如果这一位没有被设置,那用户其实是不能越权访问的。

问题2:为什么在现在的场景下这样的攻击失效?
这个攻击能成功的最大前提就是内核的地址被直接映射到用户进程的内存空间里了,所以CPU的执行模式才能被猜中啊,而现在已经不是这样了。也就是当用户代码在运行时,完整的内核PTE也出现在用户程序的Page Table中,但是这些PTE的pte_u比特位没有被设置,所以用户代码在尝试使用内核虚拟内存地址时,会得到Page Fault。但这一个表项仍然存在。

这样的攻击为什么会成功呢?

这其实依赖于CPU的一些特性,一个是Speculative execution(预测执行),另一个是CPU缓存。

Speculative execution(预测执行)

(插句嘴,预测执行其实是很经典的CS提升性能的方法啊,如果能够被预测,那就可以让大概率事件发生时的响应加快,但就是往往没法预测准啊……)

//example of speculation execution
r0 = <something>;
r1 = valid;
if(r1 == 1){
    r2 = *r0;
    r3 = r2 + 1;
}else{
    r3 = 0;
}
  • 这里r0是某个内存地址,r1是某个能够被访问的变量
  • 接下来需要要做的if其实是一个分支判断(branch prediction)选择某一个要执行的岔路口
  • 如果r1的值为1,就把r0寄存器里面的值取出来放到r2里,然后+1后再传给r3;否则把r3设为0

这个逻辑是很简单的,但是站在CPU的角度上,第3行对应的load指令,可能会使2Ghz的CPU消耗掉数百个CPU cycle,而这每一个cycle都可以执行一个指令的。因此branch prediction就是在得到r1并且在对r1做判断得出结果之前,提前执行5、6、8步,哪怕它目前没有得到足够多的信息来做判断,CPU其实也是在赌。

CPU的赌有一些很有趣的地方,它在提前执行的5、6步的时候保存在临时寄存器上,只有当它确实赌对了之后才会让这几步操作真正生效。而r0是一个有效的地址还好说,但在提前预测执行的情况下,即使r0无效或者权限pte_u没有被设置也会被取出执行,Metldown attack在ARM上并不能攻击成功,猜测可能就是在预测执行的时候也是判断了权限.他也不能产生Page Fault,万一if要执行的是第8行呢?hhhhhhh~~

CPU判断自己有没有赌对,也就是执行是不是正确的时刻被叫做retired,如果retired执行正确先前的每一步都会生效,但如果赌错,先前的执行会被抛弃。而且就在这个简单的例子里面,CPU也赌了两件事,if要选一条执行,如果执行5\6,r0一定要有效。

Intel的AMD没有披露太多的细节,同时CPU所做的这一切其实都是透明的。当第4行的retired返回的时候,如果预测失败,所有的执行都会被回滚,你不应该看到你不该看到的东西。Mirco-Architecture的细节保密,但是很多人都对它有兴趣,因为这关乎性能,当然也关乎安全。不过写编译器的人应该知道的多一点,因为很多编译器的优化不就基于CPU的特性么?

CPU缓存

CPU如果是多核,很可能有不止一级缓存,一半每个CPU都有自己的L1(Level 1第1级缓存)、L2缓存,然后公用L3。L1是最快最小的缓存L1命中可能只要几个CPU cycle,如果查不到L2缓存命中需要几十个CPU cycle,还找不到那只有去RAM了那就需要几百个CPU cycle了。
这有个问题:TLB和MMU是在哪一级上的?我认为它是与L1 cache并列的。如果你miss了L1 cache,你会查看TLB并获取物理内存地址。MMU并不是一个位于某个位置的单元,它是分布在整个CPU上的。
不过在L1、L2里面内核空间和用换空间切换的时候(先前)是没有更换Page Table的,L1Cache甚至没有被清空,kernel的PTE表项存在只是不能被用户访问。

如果不动手这篇文章一定完不成,目前未完待续啦……

 类似资料: