在写rcore 选择内核栈的分配问题时 了解到有这种漏洞.
感觉内容挺多,就另外开个文章了
前置知识:指令流水线
先了解一下现代处理器的推测执行:
引用^wiki
推测执行(亦作预测执行、投机性执行,英语:Speculative execution)是优化技术的一类,采用这个技术的计算机系统会根据现有信息,利用空转时间提前执行一些将来可能用得上,也可能用不上的指令。如果指令执行完成后发现用不上,系统会抛弃计算结果,并回退执行期间造成的副作用(如缓存)。
推测执行的目标是在处理器系统资源过剩的情况下并行处理其他任务,实际上电脑处理器在工作中,闲置性能的这种情况还不少。因此为了充分运用效率,推测执行无处不在。流水处理器的分支预测、数值预测、预读取内存和文件、以及数据库系统的乐观并发控制等机能中都采用到了推测执行。最终可以达到提高整体性能的目的。
wiki已经说的很简洁了. 再加以解释, 则为 “在cpu空闲时(有并行能力时) 提前执行可能的分支”. 在这个过程中会用到分支预测器.
直接分支: text段中的跳转是一个常量地址
间接分支: 用于跳转的地址存储在寄存器里, 需要跳转的时候再取.
还有乱序执行:
这种范式通过以下步骤挑选可执行的指令先执行:
指令获取。
指令被发送到一个指令序列中(也称执行缓冲区或者保留站)。
指令将在序列中等待直到它的数据运算对象是可以获取的。然后指令被允许在先进入的(较旧的)指令之前离开序列缓冲区。
指令被分配给一个合适的功能单元并由之执行。
结果被放到一个序列中。
仅当所有在该指令之前的指令都将他们的结果写入寄存器后,这条指令的结果才会被写入寄存器中。这个过程被称为毕业或者退休周期。
乱序执行使用其他“可以执行”的指令来填补了时间的空隙,然后再在结束时重新排序运算结果来实现指令的顺序执行中的运行结果。
简单来说,就是尽量提前执行需要等待数据的某些指令(在数据就绪后),而不是等待多条指令需要的数据全部就绪后再一一执行.
这里使用的用户栈和内核栈是如何分配的
在rcore中,所有用户程序产生的内核栈(trap)统一存放到单独的内核态中的地址空间管理.
而这里的分配方式是: 让每个应用都有一个包含应用和内核的地址空间,并将其中的逻辑段分为内核和用户两部分,分别映射到内核/用户的数据和代码,且分别在 CPU 处于 S/U 特权级时访问。这种分配方式产生了熔断和幽灵漏洞.
熔断(meltdown)
基于推测执行(或推测性内存访问)和乱序执行, 实现低权限的进程访问到高权限限制的内存空间(内核空间).
指令在执行前,数据会先被加载到缓存中,然后是寄存器.
数据将要准备加载到寄存器时,cpu会对数据进行权限检查和地址合法性检查.
**但是!**在加载到缓存后,加载到寄存器之前,我们是有途径获取缓存中的数据的.
cpu在处理非法指令引发的异常的同时时, 也会推测执行一些其他指令, 这时 推测执行 所用到的数据会被加载到缓存中,没有用到的则还在内存里.
然后我们可以计算 取内存中某些地址需要的时间. (所用时间较短的地址就是 被刚才的推测执行 预先加载到缓存中的地址)
再进一步缩小范围,就可以取到重要信息了.
伪代码(by gpt)
1 | // 假设 sensitive_data 是内核空间中的敏感数据 |
主要影响intel和部分amd处理器.
熔断的解决方法
软件:
linux使用了内核页表隔离方法(kpti/kaier).
刚开始, linux将用户空间中的内核地址部分 位置随机化, 使内核地址对用户隐匿.
熔断漏洞发现后, linux改为完全分离用户空间和内核空间的页表.
在进入内核态时,切换到包含内核地址空间的页表。
在返回用户态时,切换回仅包含用户空间的页表。
硬件:
没找到具体资料,不过各个硬件厂商有不同程度的修复.
幽灵
有多种表现形式. wiki上举了两种.
CVE-2017-5753(边界绕过检查)依赖于运行中的即时编译(JIT)系统,用于Javascript的JIT引擎已被发现存在此漏洞。网站可以读取浏览器中存储的另一个网站的数据,或者浏览器本身的存储器。
CVE-2017-5715(分支目标注入).通过影响间接分支预测器的操作方式,攻击者可以让恶意代码被预测性地执行,从而推断数据内容.
通过诱导处理器进行错误的推测执行, 加上侧信道攻击, 从而获得敏感数据.
可以在不同进程间攻击.
幽灵漏洞更通用,作用于具有分支预测等特性的现代处理器.