Spectre
Spectre*
Spectre Attacks: Exploiting Speculative Execution*
Abstract*
现代处理器使用分支预测和推测执行来最大化性能。例如,分支目的地址依赖于正在读取的内存值,CPU 将猜测目的地址并尝试提前执行。当内存值到达时,CPU 会根据预测是否正确来决定提交或丢弃推测计算结果。推测逻辑执行不可靠,可以访问受害者内存和寄存器,并执行有可测量的副作用的操作。
Spectre 攻击包括诱骗受害者推测地执行在正确程序执行中不会出现的操作,并通过侧信道将受害者机密信息泄露给对手。本文描述了结合侧信道攻击、错误攻击和 ROP 的使用攻击方法,实现受害者进程内存的任意读。更广泛地说,本文表明推测执行违反了很多安全机制的安全假设,包括操作系统进程隔离、容器化、JIT 编译以及针对缓存计时和侧信道攻击的对策。这些攻击是对现实系统的严重威胁,Intel、AMD 和 ARM 的处理器都存在易受攻击的推测执行能力。
在某些情况下,临时的针对处理器的对策可能会有效,但合理的解决方案需要修复处理器设计并更新指令集架构。
Introduction*
物理设备执行的计算会在表面上计算输出之外留下可观测的副作用。侧信道攻击致力于利用这些副作用来提取其他方式无法获取的秘密信息。
利用硬件漏洞泄露敏感信息的攻击,包括利用缓存计时、分支预测历史、分支目标缓冲区等的微架构攻击。
推测执行被广泛用于提升处理器性能,让 CPU 猜测未来可能的执行方向,并在这些路径上提前执行。更具体地说,如果程序控制流依赖于位于外部物理内存中的未缓存值,而通常需要几百个时钟周期才能读到值。CPU 尝试猜测控制流的方向,保存寄存器状态的检查点,然后在猜测的路径上推测地执行程序。当值最终到达 CPU 时,检查猜测的正确性。如果是错误的,CPU 会将寄存器状态恢复到存储的检查点以丢弃不正确的推测执行结果。但如果猜测是正确的,就提交计算结果,从而显著提升性能。
从安全的交读,推测执行涉及到可能以不正确的方式执行程序。然而,由于 CPU 设计了恢复不正确推测执行结果的操作来维护功能正确性,所以以前认为是安全的。
Our Results*
在本文中,我们分析了这种不正确的推测执行的安全性。提出一种微体系结构攻击,称为 Spectre。Spectre 攻击诱骗处理器推测性地执行在正确程序执行中不应该执行的指令序列。由于这些指令对正常 CPU 状态的影响最终会恢复,称之为瞬时指令 transient instruction。通过影响哪些瞬时指令被推测性执行,我们可以从受害者内存地址空间泄露信息。
使用本地代码和可移植的 js 代码实现利用顺势指令序列跨安全域泄露信息。
本地代码 POC。创建一个简单的受害者程序,在其内存地址空间中有 secret。然后搜索受害者二进制文件和操作系统共享库,查找可用于泄露信息的指令序列。最后编写一个攻击者程序,利用 CPU 的推测执行将之前找到的序列作为顺势指令执行。
还可以用于沙箱逃逸,通过可移植 js 代码状态。通过 js 可以从运行它的浏览器进程的地址空间读取数据。
Our Techniques*
Spectre 攻击将推测执行与微体系结构隐蔽信道的数据泄露结合来突破内存隔离边界。更具体地说,要发动 Spectre 攻击,攻击者首先要在进程地址空间定位或引入一系列指令,当执行指令时,它充当隐蔽信道的发送端,泄露受害者的内存或寄存器内容。然后攻击者诱骗 CPU 推测地执行该指令序列,从而通过隐蔽信道泄露受害者信息。最后攻击者通过隐蔽信道恢复受害者信息。虽然表面上的 CPU 状态会被恢复,但微体系结构层面的状态如缓存内容却保留下来。
以上是对 Spectre 攻击的概述,现在需要有诱骗推测执行的方法和构建隐蔽信道来具体实施攻击。本文使用基于缓存的隐蔽信道,即 FLUSH+RELOAD, EVICT+RELOAD。
接下来介绍用于诱骗和影响错误推测性执行的方法。
变体1:利用条件分支
对于此种 Spectre 变体,攻击者首先训练 mistrain CPU 的分支预测器使其错误地预测分支方相,导致 CPU 暂时违反程序语义执行本不会之心的代码。这种错误的推测执行使得攻击者能够读取存储在程序地址空间的 secret。具体说,考虑一下代码:
if(x < array1_size)
y = array2[array1[x] * 4096];
x 由攻击者控制,if 用于控制 x 在合法范围内。那么攻击者需要绕过 if 语句,进而读取 secret。
- mistrain 阶段,攻击者使用合法的输入调用以上代码,从而训练分支预测变量预测 if=true
- exploit 阶段,攻击者使用越界的 x 调用以上代码,CPU 预测 if=true,使用恶意的 x 来执行
array2[array1[x] * 4096]。而这条读取操作会将依赖于 array1[x] 的地址的数据加载到 cache。 - 当边界检查结果确定时,CPU 发现错误并恢复一些状态。但是对 cache 的修改不会被换原,这时攻击者就可以分析 cache 内容找出从受害者内存越界读取到的 secret 的值。
变体2:利用间接跳转
借鉴 ROP 的原理,这个变体从受害者地址空间中选择一个 gadget,影响受害者推测执行这个 gadget。不同的是,攻击者并不依赖于受害者代码的漏洞。攻击者训练分支目标缓存 Branch Target Buffer, BTB 使其误预测间接寻址指令跳转到 gadget 处执行。gadget 通过缓存侧信道泄露信息。
mistrain BTB,攻击者获取受害者地址空间中 gadget 的虚拟地址,然后执行到这个地址的简介跳转。训练是在攻击者地址空间进行的,不用管攻击者地址空间里这一地址的内容,只要这个虚拟地址跟受害者中 gadget 对应。
其他变体
通过改变实现推测执行的方法和泄露信息的方法进一步设计攻击手法。
目标硬件*
对 Intel、AMD、ARM 处理器都适用。
CVE-2017-5753
CVE-2017-5715
Meltdown*
Meltdown 是微体系结构攻击,利用无需执行来泄露内核内存。Spectre 与 Meltdown 主要有两点不同:
- 首先,Meltdown 未使用分支预测,而是依赖于指令导致异常时,其后的指令在被终止之前会乱序执行。
- 其次,Meltdown 利用 Intel 和一些 ARM 处理器特有的漏洞,允许乱序执行以绕过内存保护。而 Spectre 适用更广泛,且 KAISER 可以缓解 Meltdown,却无法防御 Spectre。
Background*
介绍现代告诉处理器中的一些微体系结构组件,描述它们如何提高性能,如何从运行的程序中泄露信息。
乱序执行*
乱序执行通过允许程序指令流下游的指令提前执行,从而提高处理器利用率。
现代处理器中,指令被解码为微操作。一旦一条指令对应的所有微操作以及所有前面的指令都完成,这些指令就 retired,提交体系结构层面的更改。因此指令按照程序执行顺序 retired,intel 的安全检查是在 retire 阶段。
推测执行*
通常,处理器不知道程序的未来指令流。例如,当无需执行到一个条件分支指令,其方向取决于前面的未执行完成的指令。在这种情况下,处理器可以保存当前寄存器状态,预测程序执行路径,推测性地执行指令。如果预测正确,则提交推测执行的结果,从而产生性能优势。否则,处理器走错路径,通过恢复寄存器状态,放弃推测执行结果。
将错误执行的指令称为瞬时指令,它们可能会留下微体系结构痕迹。现在推测执行可以提前运行几百条指令,这个限制通常由 CPU 中的重排缓冲区大小控制。
分支预测*
在推测执行过程中,处理器猜测分支指令的可能结果。预测越准确,越能提升性能。
现代处理器的分支预测器由多种直接和间接的分支预测机制。间接寻址即在条转到运行时计算出的任意地址,如寄存器、内存、栈上的值。
对于间接寻址的灵活性,间接跳转和调用使用至少两种预测机制来优化。
处理器需要预测一下内容:
- 直接调用和跳转,静态或单调的方式
- 间接调用和跳转,根据程序行为决定是单调方式还是多样方式
- 条件分支,分支目标和是否分支。
要用到多个处理器组件来进行分支预测。Branch Target Buffer, BTB 保存最近执行的分支指令的地址到目标地址的映射。处理器可以使用 BTB 来预测未来的代码地址。
对于条件分支,处理器保存最近的直接和间接分支的分支结果。
虽然返回指令是间接分支,但现代 CPU 使用单独机制预测,Return Stack Buffer, RSB 维护堆栈最近使用的部分。
分支预测逻辑,BTB 和 RSB,通常不在物理核之间共i昂。因此 CPU 只从同一个核心上执行的分支学习。
存储层次结构*
为了弥合处理器和较慢的内存之间的速度差距,处理器使用一个连续更小但更快的缓存的层次结构。cache 单位是 line,一般是 64 或 128 bytes。处理器需要从内存取数据,依次找 L1, L2, L3。
处理器通过缓存一致性协议确保每个核心的 L1 和 L2 缓存内容已知。
微体系结构侧信道攻击*
利用局部性原理提升性能。
当多个程序在同一硬件执行,一个程序引起的微体系结构状态变化可能会影响其他程序,反过来这个影响也可能被另一个程序窃取 secret。
最初的攻击利用时序可变性并利用 L1 数据 cache 提取密码原语的秘钥。
本文中,使用 FLUSH+RELOAD 及其变体 EVICT+RELOAD 来泄露敏感信息。
攻击者首先从与受害者共享的 cache 中逐出 line,然后等待受害者执行一段时间,攻击者测量逐出内容的读取时间。