本文是Reading privileged memory with a side-channel的翻译文章。上篇见这里
我们的PoC通过几个步骤来定位主机内核。下一步攻击所确定和必要的信息包括:
回顾一下,这并不是必要的,但它很好地演示了攻击者可以使用的各种技术。 更简单的方法是首先确定vmlinux的地址,然后平分kvm.ko和kvm-intel.ko的地址。
第一步,kvm-intel.ko的地址被泄露。 为此,guest输入后的分支历史缓冲区状态被转出。然后,对于kvm-intel.ko的加载地址的位12..19的每个可能的值,历史缓冲区的预期最低16位是基于加载地址推测和guest入口之前的最后8个分支的已知偏移来计算的,并将结果与泄漏历史缓冲区状态的最低16位进行比较。
通过测量具有两个目标的间接调用的误预测速率,分支历史缓冲区状态以2位的步长泄漏。间接调用的一种方式是从vmcall指令开始,然后是一系列N个分支,其相关的源地址位和目标地址位均为零。间接调用的第二种方式来自用户空间中的一系列受控分支,可用于将任意值写入分支历史缓冲区。
错误预测率的测量方法与“逆向工程分支预测器内部”部分相同,使用一个调用目标加载缓存行,另一个检查是否加载了相同的缓存行。
在N = 29的情况下,如果受控分支历史缓冲器值为零,则由于来自超级调用的所有历史缓冲器状态已被擦除,错误预测将以高速率发生。
在N = 28的情况下,如果受控分支历史缓冲值为 0<<(282), 1<<(282), 2<<(282), 3<<(282) 之一,通过测试所有四种可能性,可以检测哪一种是正确的。然后,为了减少N的值,四种可能性是{0|1|2|3}<<(28*2) | (history_buffer_for(N+1) >> 2)。
通过重复此操作以减少N的值,可以确定N = 0的分支历史缓冲值。
此时,kvm-intel.ko的低20位是已知的; 下一步是大致找到kvm.ko.
为此,使用通用分支预测器,和从kvm.ko到kvm-intel.ko的间接调用插入到BTB中的数据,该数据发生在每个超级调用上;这意味着间接调用的源地址必须从BTB中泄漏出去。
kvm.ko可能位于从0xffffffffc0000000到0xffffffffc4000000的范围内,页面对齐(0x1000)。 这意味着“通用预测变量”一节中表中的前四项适用; 会有24-1 = 15个别名的地址。 但这也是一个优点:它将搜索空间从0x4000减少到0x4000 / 2^4 = 1024。
要找到源或其某个别名地址的正确地址,通过特定寄存器加载数据的代码被放置在所有可能的调用目标(kvm-intel.ko的泄漏低20位加上调用目标的模块内偏移加上220的倍数)并且间接呼叫被放置在所有可能的呼叫源。
然后,交替执行超级调用,并通过不同的可能的非别名调用源执行间接调用,并使用随机历史缓冲区状态,防止专用预测工作。 在此步骤之后,kvm.ko的加载地址仍有216个可能性。
接下来,可以使用从vmlinux到kvm.ko的间接调用,以类似的方式确定vmlinux的加载地址。幸运的是,在vmlinux的加载地址中随机化的位没有一个被折叠在一起,所以与定位kvm.ko时不同,结果将直接是唯一的。vmlinux具有2MiB的对齐和1GiB的随机化范围,所以仍然只有512个可能的地址。
因为(据我们所知),一个简单的超级调用实际上并不会导致从vmlinux到kvm.ko的间接调用,我们使用模拟串行端口的状态寄存器中的端口I / O,该端口出现在使用virt-manager创建的虚拟机的默认配置中。
剩下的唯一信息是kvm.ko的16个别名加载地址中的哪一个实际上是正确的。
因为对kvm.ko的间接调用的源地址是已知的,所以可以使用二分法来解决:将代码放置在各种可能的目标上,这取决于推测性地执行代码的哪个实例,加载两个缓存行中的一个,并检测哪一个缓存行被加载。
PoC假定虚拟机无法访问超大页面。要发现具有相对于4KiB页面边界的特定对齐的所有L3高速缓存集合的驱逐集合,PoC将首先分配25600页内存。然后,在一个循环中,它选择所有剩余的未排序页面的随机子集,使得子集中包含驱逐集合的集合的期望数目为1,通过反复访问它的缓存行并测试缓存行是否总是被缓存(在这种情况下,它们可能不是驱逐集的一部分),从而将每个子集减少到驱逐集,并尝试使用新的驱逐集来驱逐所有剩余的未分类的缓存行,以确定它们是否在相同的缓存集中[12]。
由于此攻击使用FLUSH + RELOAD方法泄漏数据,因此需要知道一个guest页面的主机内核虚拟地址。 诸如PRIME + PROBE等替代方法应该没有这个要求。
攻击这一步的基本思路是对管理程序使用分支目标注入攻击来加载攻击者控制的地址并测试是否导致加载客户拥有的页面。为此,可以使用从R8指定的内存位置进行简单加载的gadget - 在此内核版本上达到guest退出后的第一个间接调用时,R8-R11仍包含guest控制值。
我们预计攻击者需要知道在这一点上必须使用哪个驱逐集或者同时暴力驱逐,但是,通过实验,使用随机驱逐集也可以。 我们的理论是观察到的行为实际上是L1D和L2驱逐的结果,这可能足以允许一些指令值的推测性执行。
主机内核映射(近似于)physmap区域中的所有物理内存,包括分配给KVM guest虚拟机的内存。 但是,physmap的位置是随机的(1GiB对齐),在大小为128PiB的区域。 因此,直接强制guest页面的主机虚拟地址需要很长时间。 这是可能的; 作为估计值,假设每秒12000次成功注入和30个并行测试的guest页面,应该可能在一天左右或更短的时间内完成; 但不像几分钟内那么令人印象深刻。
为了优化这个问题,可以将问题分解:首先,使用可以从物理地址加载的小工具蛮力物理地址,然后蛮力施加physmap区域的基地址。
因为通常可以假定物理地址远远低于128PiB,所以它可以更有效地暴力破解,并且之后暴力破坏physmap区域的基地址也更容易,因为可以使用1GiB对齐的地址猜测。
要强制使用物理地址,可以使用以下gadget:
ffffffff810a9def: 4c 89 c0 mov rax,r8
ffffffff810a9df2: 4d 63 f9 movsxd r15,r9d
ffffffff810a9df5: 4e 8b 04 fd c0 b3 a6 mov r8,QWORD PTR [r15*8-0x7e594c40]
ffffffff810a9dfc: 81
ffffffff810a9dfd: 4a 8d 3c 00 lea rdi,[rax+r8*1]
ffffffff810a9e01: 4d 8b a4 00 f8 00 00 mov r12,QWORD PTR [r8+rax*1+0xf8]
ffffffff810a9e08: 00
这个gadget允许通过适当地设置R9来加载内核文本部分周围8字节对齐的值,尤其允许加载physmap的起始地址page_offset_base。 然后,原来在R8中的值 - 物理地址推测值减去0xf8 - 被添加到先前加载的结果中,将0xfa加入其中,并且结果被解除引用。
为了选择正确的L3驱逐集,来自下一节的攻击基本上以不同的驱逐集执行,直到它工作。
在这一点上,通常有必要在主机内核代码中定位可用于实际泄漏数据的小工具,方法是从攻击者控制的位置读取数据,对结果进行适当的移位和掩码,然后将结果作为抵消攻击者控制地址的负载。但是将gadget拼凑在一起,弄清楚哪些在推测环境中起作用看起来很烦人。
所以作为替代,我们决定使用eBPF解释器,它是内置于宿主内核的 - 虽然没有合法的方式从虚拟机内部调用它,但主机内核的文本部分中代码的存在足以使其可用于攻击,就像普通的ROP gadget一样。
eBPF解释器入口点具有以下函数签名:
static unsigned int __bpf_prog_run(void *ctx, const struct bpf_insn *insn)
第二个参数是指向要执行的静态预验证eBPF指令数组的指针 - 这意味着__bpf_prog_run()不会执行任何类型检查或边界检查。 第一个参数只是作为初始模拟寄存器状态的一部分存储,所以它的值并不重要。
eBPF解释器提供了以下内容:
要调用解释器入口点,需要一个给定R8-R11控制和受控数据在已知内存位置的RSI和RIP控制的gadget。 以下gadget提供了此功能:
ffffffff81514edd: 4c 89 ce mov rsi,r9
ffffffff81514ee0: 41 ff 90 b0 00 00 00 call QWORD PTR [r8+0xb0]
现在,通过将R8和R9指向physmap中客户拥有页面的映射,可以在主机内核中推测性地执行任意未验证的eBPF字节码。 然后,可以使用相对简单的字节码将数据泄漏到缓存中。
基本上看Anders Fogh的博文就够了: https://cyber.wtf/2017/07/28/negative-result-reading-kernel-memory-from-user-mode/
总之,使用此问题变体的攻击尝试从用户空间读取内核内存,而不会误导内核代码的控制流。
这通过使用用于以前变体的代码模式,但是是在用户空间中作用。
其基本思想是,访问地址的权限检查可能不是关于从内存向寄存器读取数据的关键路径,权限检查可能会对性能产生重大影响。相反,内存读取可以使读取的结果立即可用于以下指令,并且只是异步执行权限检查,如果权限检查失败,则在重新排序缓冲区中设置一个标志,导致引发异常。
我们对Anders Fogh的博文有一些补充:
“想象一下在usermode中执行的以下指令
mov rax,[somekernelmodeaddress]
它在退出时会导致中断,[...]“
还可以在高延迟预测错误分支后面执行该指令以避免发生页面错误。 也可以通过增加从内核地址读取和传送相关异常之间的延迟来扩大推测窗口。
“首先,我调用一个系统调用触及这个内存,其次,我使用prefetcht0指令来提高我在L1中加载地址的几率。”
当我们在系统调用之后使用预取指令时,攻击停止了对我们的工作,我们不知道为什么。 也许是CPU以某种方式存储访问是否在上次访问时被拒绝,并在这种情况下阻止了攻击?
“幸运的是,当不允许访问时,我没有得到一个缓慢的read suggesting的英特尔的null.”
(从内核地址读取返回全零)似乎发生的内存不足够缓存,但对于哪些可跳转表项存在,至少在重复读取尝试后。 对于未映射的内存,内核地址读取根本不返回结果。
我们相信,我们的研究提供了许多尚未研究的剩余研究课题,我们鼓励其他公共研究人员研究这些课题。
本节包含的博客数量比本博客的其余部分还要高 - 它包含未经测试的想法,这可能毫无用处。
除了测量数据高速缓存时序之外,研究是否存在微架构攻击会很有趣,这些数据高速缓存时序可用于从推测执行中泄露数据。
到目前为止,我们的研究相对以Haswell为中心。查看细节,例如其他现代处理器的分支预测如何工作,以及如何攻击可能会很有趣。
我们针对Linux内核中内置的JIT引擎开发了成功的变体1攻击。看看对系统控制较少的更先进的JIT引擎的攻击是否也是实用的 - 尤其是JavaScript引擎是很有意义的。
在变体2中,在扫描客户拥有的页面的主机虚拟地址的同时,尝试首先确定其L3缓存集合可能是有意义的。这可以通过使用通过physmap的逐出模式执行L3逐出,然后测试逐出是否影响客户拥有的页面来完成。
对于缓存集合也可能有效 - 使用L1D + L2驱逐集合来驱逐主机内核上下文中的函数指针,使用内核中的小配件使用物理地址驱逐L3集合,然后使用它来确定哪些缓存集合是来宾直到宾客拥有的驱逐集已经构建完成为止。
考虑到通用BTB似乎只能区分2个31-8个或更少的源地址,似乎可行的是在约几个小时的时间范围内转储由例如超级调用产生的完整BTB状态。(扫描跳转源,然后对于每个发现的跳转源,将跳转目标等分)。即使主机内核是定制的,也可能用于识别主机内核中函数的位置。
源地址别名会在某种程度上降低实用性,但由于目标地址不会受到这种影响,因此可能会将来自具有不同KASLR偏移量的机器的(源,目标)对关联起来,并且可能会基于KASLR而减少候选地址的数量加法,而混叠是按位。
然后,这可能允许攻击者根据跳转偏移或函数之间的距离来猜测主机内核版本或用于构建它的编译器。
如果对变体2使用足够高效的gadget,那么可能根本不需要从L3缓存驱逐主机内核函数指针,只用L1D和L2驱逐它们就足够了。
特别是变体2 PoC仍然有点慢。这可能是因为:
使用变体2可以实现哪些数据泄漏率会很有趣。
如果返回预测器在特权级别更改时也不会丢失状态,它可能对于从VM内部定位主机内核(在这种情况下,可以使用二分法来快速发现主机内核的完整地址)或注入返回目标(特别是如果返回地址存储在高速缓存行中,可以被攻击者清除并且在返回指令之前不重新加载)。
然而,我们还没有对迄今为止取得确凿结果的返回预测因子进行任何实验。
我们试图从间接调用预测器泄露目标信息,但尚未使其工作。
Project Zero向其透露此漏洞的供应商向我们提供了有关此问题的以下声明:
英特尔致力于提高计算机系统的整体安全性。这里描述的方法依赖于现代微处理器的共同特性。因此,对这些方法的敏感度不仅限于英特尔处理器,也不意味着处理器超出其预期的功能规格。英特尔正在与我们的生态系统合作伙伴以及其他处理器受到影响的芯片供应商密切合作,为这些方法设计和分发软件和硬件缓解措施。
有关更多信息和有用资源的链接,请访问:
https://security-center.intel.com/advisory.aspx?intelid=INTEL-SA-00088&languageid=en-fr
http://newsroom.intel.com/wp-content/uploads/sites/11/2018/01/Intel-Analysis-of-Speculative-Execution-Side-Channels.pdf
AMD提供了以下链接: http://www.amd.com/en/corporate/speculative-execution
Arm认识到,尽管按预期工作,许多现代高性能处理器的推测功能可以与缓存操作的时间结合使用,以泄漏本博客中描述的一些信息。相应地,Arm开发了我们推荐部署的软件缓解措施。
有关受影响的处理器和缓解的具体细节可以在此网站上找到:https://developer.arm.com/support/security-update
Arm包含详细的技术白皮书以及来自Arm架构合作伙伴关于其特定实施和缓解措施的信息的链接。
请注意,其中一些文档 - 特别是英特尔的文档 - 会随着时间而改变,所以引用和引用它们可能不会反映英特尔文档的最新版本。
[1] 这个最初的报告没有包含关于变体3的任何信息。我们曾经讨论过直接读取内核内存是否可行,但认为这不太可能。在https://cyber.wtf/2017/07/28/negative-result-reading-kernel-memory-from-user-mode/发布Anders Fogh的工作之前,我们后来测试并报告了变体3 。
[2] “测试处理器”部分列出了精确的型号名称。用于重现此操作的代码位于bugtracker的writeup_files.tar归档中,位于文件夹userland_test_x86和userland_test_aarch64中。
[3] 攻击者控制的偏移量用于通过此PoC对阵列执行超出边界的访问,是一个32位值,将可访问地址限制为内核堆区域中的4GiB窗口。
[4] 此PoC不支持SMAP支持的CPU; 然而,这不是一个基本的限制。
[5] linux-image-4.9.0-3-amd64,版本为4.9.30-2 + deb9u2(http://snapshot.debian.org/archive/debian/20170701T224614Z/pool/main/l/linux/ linux-image-4.9.0-3-amd64_4.9.30-2%2Bdeb9u2_amd64.deb,sha256 5f950b26aa7746d75ecb8508cc7dab19b3381c9451ee044cd2edfd6f5efff1f8,通过Release.gpg,Release,Packages.xz签名); 那是我安装机器时的当前发行版内核版本。PoC不太可能与其他内核版本无变化地协作; 它包含许多硬编码地址/偏移量。
[6] 手机从2017年5月开始运行Android版本。
[7] https://software.intel.com/en-us/articles/intel-sdm
[8] https://software.intel.com/en-us/articles/avoiding-and-identifying-false-sharing-among-threads,section“background ”
[9] 超过2 15种映射效率更高,但内核对流程可以拥有的VMA数量设置了2 16 的硬限制。
[10] 英特尔的优化手册指出:“在HT技术的第一个实现中,物理执行资源是共享的,并且每个逻辑处理器的体系结构状态都是重复的”,因此预测状态可以共享。虽然预测器状态可能由逻辑核心标记,但这可能会降低多线程进程的性能,因此似乎不太可能。
[11] 如果历史缓冲区比我们测量的大一点,我们增加了一些余量 - 特别是因为我们在不同的实验中看到了稍微不同的历史缓冲区长度,并且因为26不是一个非常整数。
[12] 基本思想来自http://palms.ee.princeton.edu/system/files/SP_vfinal.pdf,第四节,尽管该论文的作者仍然使用了大量的页面。