(论文解析)FPVI+SCSB漏洞论文解析
论文核心内容
这篇论文的核心贡献在于,它将研究视角从基于已知瞬态执行窗口(如分支预测错误)开发新攻击,转向了深入探索瞬态执行本身的根源 。论文系统性地研究了“机器清除”这一大类此前未被充分探索的“错误推测”(Bad Speculation)事件 。
通过逆向工程,论文详细分析了四种未曾被深入研究的机器清除原因:
- 浮点(Floating Point, FP)机器清除
- 自修改代码(Self-Modifying Code, SMC)机器清除
- 内存排序(Memory Ordering, MO)机器清除
- 内存消歧(Memory Disambiguation, MD)机器清除
研究发现,这些机器清除不仅为已知攻击提供了新的瞬态执行窗口,还催生了两种全新的攻击原语:
浮点值注入(Floating Point Value Injection, FPVI)和 推测性代码存储绕过(Speculative Code Store Bypass, SCSB)。论文最终成功在最新版的 Mozilla Firefox 浏览器上实现了一个端到端的 FPVI 攻击,并提出了相应的缓解措施和新的瞬态执行分类方法 。
以下是这四种机器清除的详细成因与利用原理:
1. 自修改代码 (Self-Modifying Code, SMC) 机器清除
成因 (Why it happens):
- 现代 CPU 拥有独立的指令缓存(L1i)和数据缓存(L1d),并通过缓存一致性协议来保证代码和数据视图的统一 。
- 当一个“存储”指令修改了一块代码区域,而这块代码恰好已经被 CPU 的指令预取单元(IFU)加载到了指令缓存(L1i)或执行流水线中时,就会产生冲突 。
- 此时,CPU 的数据视图(即通过数据缓存写入的新代码)和指令视图(即在指令缓存中的陈旧代码)出现了短暂的不同步。
- 为了纠正这种不同步并确保执行的是最新的代码,CPU 必须触发一次 SMC 机器清除,以冲刷整个流水线和指令缓存中的陈旧指令,然后重新获取新指令 。
利用原理 (How it’s exploited):
- 利用的关键在于从“写入新代码”到“CPU 检测到不同步并执行清除”之间存在一个时间延迟。
- 在这个短暂的延迟窗口中,CPU 会继续瞬态地执行已经在流水线中的陈旧代码(Stale Code)。
- 攻击者可以利用这一点,先让一段恶意代码(Gadget)存在于某块内存中,然后通过 JIT 引擎等方式让系统在这块内存上写入新的、无害的代码。在 SMC 机器清除发生前,那段陈旧的恶意代码会被瞬态执行,其产生的微架构副作用(如改变缓存状态)可被用于泄露信息 。这被称为**推测性代码存储绕过 (SCSB)**。
2. 浮点 (Floating Point, FP) 机器清除
成因 (Why it happens):
- 当浮点运算单元(FPU)遇到它无法直接在硬件中高效处理的操作时,例如对非规格化浮点数(Denormal Numbers) 的运算,它需要特殊的软件辅助来完成 。
- 这种特殊处理是通过微码辅助 (Microcode Assist) 实现的 。
- 在执行微码辅助程序之前,CPU 必须清空当前的执行流水线,而这个清空操作就是通过一次机器清除完成的 。因此,任何需要微码辅助的浮点操作都会引发一次 FP 机器清除 。
利用原理 (How it’s exploited):
- 利用的关键在于 FPU 在请求微码辅助之前的行为。
- 研究发现,FPU 会首先“盲目地”假设操作数都是正常的,并快速计算出一个瞬态的、错误的结果。
- CPU 会在稍后才检测到这个计算是错误的(因为它涉及非规格化数),并触发机器清除来纠正它。这个检测延迟创造了一个瞬态执行窗口 。
- 在这个窗口期内,后续指令会使用那个错误的瞬态结果进行运算 。攻击者可以通过精心构造浮点操作的输入,使得这个错误的瞬态结果变成一个可控的、有特定位模式的值(例如一个指针),从而在瞬态执行路径上实现任意值注入,这被称为**浮点值注入 (FPVI)**。
3. 内存排序 (Memory Ordering, MO) 机器清除
成因 (Why it happens):
- 现代多核 CPU 为了隐藏内存延迟,允许乱序执行(Out-of-Order Execution),但这必须遵守特定的内存排序模型(如 Intel 的 TSO 模型)以保证程序行为的正确性 。
- 当一个核心(核心 A)正在乱序执行内存操作时(例如,一个快的加载操作先于一个慢的加载操作执行),如果此时它收到了来自另一个核心(核心 B)的**窥探请求 (snoop request)**,表明某个内存地址的数据已被修改,这可能导致核心 A 的执行顺序违反内存排序模型 。
- 例如,如果核心 A 瞬态地看到了 Y 的新值,但看到的却是 X 的旧值,这在 TSO 模型中可能是一个非法状态 。
- 为了维持内存排序的正确性,核心 A 别无选择,只能触发一次 MO 机器清除,冲刷流水线并按正确的顺序重新执行加载操作 。
利用原理 (How it’s exploited):
- 利用点在于,在内存排序冲突被检测到并被机器清除纠正之前,CPU 已经基于一个陈旧的内存值进行了瞬态执行 。
- 在上述例子中,核心 A 上的指令会基于从 Y 读取到的陈旧值(在核心 B 写入之前的值)继续瞬态执行。
- 攻击者可以通过在两个线程间制造内存竞争,让一个线程(攻击线程)的写操作去“污染”另一个线程(受害线程)即将读取的缓存行,从而在受害线程中触发一个基于陈旧数据的瞬态执行路径 。
4. 内存消歧 (Memory Disambiguation, MD) 机器清除
成因 (Why it happens):
- 为了优化性能,CPU 包含一个内存消歧预测器,用于预测一个加载(Load)操作是否与它前面的、地址尚未计算出来的存储(Store)操作访问相同的内存地址(即别名 Aliasing)。
- 如果预测器预测“无别名”,CPU 就会乐观地将该加载操作提前执行(称为“提升” Hoisting)。
- 当这个预测错误时(即预测无别名,但实际上有别名),就会触发一次 MD 机器清除 。
- 清除是必要的,因为提前执行的加载操作从内存中读取了一个陈旧的值(因为真正应该提供数据的那个存储操作还没来得及写入),CPU 必须撤销这个错误操作并重新正确地执行加载 。
利用原理 (How it’s exploited):
- 利用原理非常直接:当预测器被诱导发生错误时,被提升的加载操作会瞬态地读取到一个陈旧的数据值。
- 这个陈旧的值会被传递给后续的指令,在瞬态执行路径上使用,从而可以被用来泄露信息 。
- 攻击者需要做的就是通过一系列特定的内存访问模式来“训练”或“误导”内存消歧预测器,使其在关键时刻做出错误的“无别名”预测 。这正是Spectre Variant 4 (Speculative Store Bypass, SSB) 攻击的核心机制 。
威胁模型 (Threat Model)
论文设定的威胁模型如下:
- 攻击者能力:攻击者是无特权的,可以在受害者机器上运行代码,例如通过 JavaScript、普通用户进程或虚拟机 。
- 攻击目标:目标是跨越安全边界(如从用户空间到内核,或从一个进程到另一个进程)泄露机密信息,如私钥、密码或随机化指针 。
- 受害者环境:受害者是一台基于 x86-64 架构的机器,运行着最新的操作系统和 CPU 微码,并且已经部署了所有针对瞬态执行攻击的最新缓解措施 。
漏洞成因 (Cause of Vulnerabilities)
漏洞的根本原因在于 CPU 为了提升性能而进行的“错误推测”,当这些推测被发现错误时,CPU 会执行“机器清除”来冲刷整个处理器流水线 。这个过程会产生一个时间窗口,其中错误推测路径上执行的指令虽然最终会被丢弃,但其产生的微架构层面的副作用(如缓存状态变化)却可能保留下来,从而被攻击者利用。
具体来说,两种新攻击原语的成因如下:
SCSB 的成因(基于 SMC 机器清除):
在现代 CPU 架构中,为了性能,指令缓存(L1i)和数据缓存(L1d)是分离的,但缓存一致性协议会确保二者同步 。
当一个“存储”指令修改了一块正在被执行或即将被取指的代码区域时(即自修改代码),就会发生 SMC 。
这会导致 CPU 的数据视图(新写入的代码)和指令视图(已在流水线或 L1i 缓存中的旧代码)之间出现短暂的不同步。
在 CPU 检测到这种不同步并触发 SMC 机器清除以刷新流水线之前,它会瞬态地执行陈旧的(stale)代码。攻击者可以利用这个窗口执行恶意的旧代码片段。
FPVI 的成因(基于 FP 机器清除):
当浮点运算单元(FPU)处理非规格化浮点数(denormal numbers)时,硬件无法直接处理,需要借助微码辅助(microcode assist)来完成 。
调用微码辅助前,CPU 必须触发一次机器清除来清空流水线 。
在触发机器清除之前,FPU 会“盲目地”按照处理正常数值的方式进行计算,从而产生一个瞬态的、错误的计算结果。
这个错误的瞬态结果会被后续指令使用,直到 CPU 检测到错误并纠正它 。攻击者可以通过精心构造非规格化浮点数,来“注入”一个任意的、可控的瞬态值。
攻击原语 (Attack Primitives)
基于上述成因,论文提出了两种新的攻击原语:
Speculative Code Store Bypass (SCSB) - 推测性代码存储绕过:
此原语利用 SMC 机器清除,允许攻击者在瞬态执行窗口中执行一段陈旧且受控的代码。
它类似于对 JIT(Just-In-Time)代码缓存的瞬态“释放后使用”(Use-After-Free)攻击 。
主要应用场景是 JIT 引擎(如 JavaScript 引擎),攻击者可以控制 JIT 编译过程,将恶意小工具(gadget)写入一块内存,然后让 JIT 引擎在这块内存上生成新代码,从而触发对旧有恶意小工具的瞬态执行 。
Floating Point Value Injection (FPVI) - 浮点值注入:
此原语利用 FP 机器清除,允许攻击者将一个任意值注入到受害者的瞬态执行路径中 。
攻击者通过向受害者应用中的浮点运算提供特制的非规格化操作数来实现 。
这与 LVI(Load Value Injection)攻击类似,但 FPVI 是通过控制浮点运算的操作数,而不是通过诱导错误的加载操作来实现价值注入 。
攻击链 (Attack Chain)
1. 推测性代码存储绕过 (SCSB) 的攻击链
SCSB 攻击的核心是利用自修改代码(SMC)机器清除前的延迟,在瞬态执行窗口中执行一段陈旧且受控的代码 。其攻击链主要针对 JIT (Just-In-Time) 引擎,如浏览器中的 JavaScript 引擎 。
攻击步骤如下:
控制代码缓存 (Code Cache Manipulation)
攻击者首先需要操纵 JIT 引擎的代码缓存分配器 。
通过特定的操作(类似堆喷射),攻击者让一段包含恶意泄密小工具(gadget)的陈旧代码
g
存在于 JIT 代码缓存的某个特定、可预测的内存区域,这块区域随后被“释放” 。
触发代码写入与执行 (Triggering Code Write & Execution)
攻击者接着强制 JIT 引擎编译一段新的、无害的代码
f
,并让引擎将这段新代码写入到刚才被控制的同一块内存区域 。紧接着,JIT 引擎会立即跳转到这块内存区域,准备执行新代码
f
。
瞬态执行陈旧代码 (Transient Execution of Stale Code)
这是攻击的关键。由于 CPU 的数据视图(刚写入的新代码
f
)和指令视图(已在预取队列或指令缓存中的陈旧代码g
)之间存在短暂的不同步,CPU 不会立即执行新代码f
。相反,CPU 会瞬态地执行那段陈旧的、由攻击者植入的恶意小工具代码
g
,直到 SMC 机器清除被触发来纠正这个不同步 。
泄露秘密与恢复 (Leakage and Recovery)
被瞬态执行的恶意小工具
g
会执行一个可以泄露秘密的操作,例如,根据某个秘密值去访问特定的缓存行,从而在缓存中留下痕迹 。之后,SMC 机器清除发生,流水线被冲刷,CPU 的代码和数据视图恢复同步 。
最终,CPU 开始从架构上正确地执行新的无害代码
f
。但此时,秘密信息已经通过微架构状态(缓存)的变化被泄露,攻击者可以通过侧信道(如 FLUSH+RELOAD)检测到这个变化来获取秘密。
2. 浮点值注入 (FPVI) 的攻击链
FPVI 攻击允许攻击者通过浮点运算,将一个任意值注入到瞬态执行路径中 。论文中展示了一个针对 Mozilla Firefox 浏览器 SpiderMonkey 引擎的完整攻击链,该攻击利用了其 NaN-boxing 实现 。
攻击步骤如下:
构造输入操作数 (Crafting Input Operands)
攻击者在 JavaScript 沙箱中,执行一个浮点运算(例如除法)。
攻击者为这个运算提供了两个经过精心构造的非规格化浮点数(denormal numbers) 作为操作数 。
注入瞬态值 (Injecting the Transient Value)
当 FPU 处理这些非规格化数时,它会首先“盲目地”按正常流程计算,从而瞬态地产生一个错误的、但可被攻击者预测和控制的结果
z
。攻击者构造的操作数,会使这个瞬态结果
z
的二进制位模式恰好符合 SpiderMonkey 的 NaN-boxing 编码:高位部分是一个“字符串”类型的标签,而低位部分(payload)则是攻击者想要读取的任意内存地址。
瞬态类型混淆 (Transient Type Confusion)
在瞬态执行路径上,JIT 编译后的代码会检查运算结果
z
的类型。它会读取到伪造的“字符串”标签,并错误地认为z
是一个合法的字符串对象指针 。然而,在架构上,这次运算的正确结果是一个浮点数(例如
-Infinity
),因此这个代码分支在正常情况下永远不会被执行 。这就造成了一次**推测性类型混淆 (speculative type confusion)**。
瞬态内存读取与泄露 (Transient Memory Read and Leakage)
接着,瞬态执行的代码会尝试访问这个伪造“字符串”对象的属性,例如
z.length
。这次访问在微架构层面变成了一次对攻击者注入的任意地址的内存读取操作 。
从该地址读取到的秘密数据,会被用来访问一个共享缓存区,通过
EVICT+RELOAD
等侧信道技术将秘密泄露出来 。
恢复与清理 (Recovery and Cleanup)
CPU 最终检测到最初的浮点运算存在错误(因涉及非规格化数),触发 FP 机器清除,冲刷流水线 。
然后,CPU 以正确的、架构上的结果重新执行代码 。但此时,秘密信息已经通过瞬态执行期间留下的缓存痕迹成功泄露 。
复现环境 (Reproduction Environment)
FPVI 端到端攻击的复现环境:
- CPU: Intel Core i9-9900K (微码版本 0xde) 。
- 操作系统: Linux 5.8.0 。
- 软件: Firefox 85.0 。
- 配置:为了简化侧信道攻击,实验中在 Firefox 中启用了高精度计时器 。
受影响的处理器范围:
- 论文在多个 Intel 和 AMD 处理器上测试并验证了漏洞的存在 。
- SCSB:所有测试的 Intel 和 AMD 处理器均受影响 。ARM 架构由于其规范要求在自修改代码后使用显式的软件屏障,因此不受影响 。
- FPVI:所有测试的 Intel 和 AMD 处理器均受影响 。尽管在 AMD 上没有找到可利用的 NaN-boxing 瞬态结果,但漏洞本身存在 。在 ARM 上未观察到瞬态结果的痕迹 。
- 测试过的处理器型号包括:
- Intel: Core i7-10700K, Xeon Silver 4214, Core i9-9900K, Core i7-7700K 。
- AMD: Ryzen 5 5600X, Ryzen Threadripper 2990WX, Ryzen 7 2700X 。
- ARM: Cortex-A72 (Broadcom BCM2711) 。
攻击效果 (Implementation Effect)
FPVI 攻击效果:在 Firefox 上的端到端攻击,成功实现了从浏览器进程中读取任意内存 。
- 泄露速率:约 13 KB/s 。
- 瞬态窗口大小:通过增加 FPU 的计算压力,瞬态窗口可以扩展到约 12 条加载指令 。
各机制性能对比:
- 泄露效率:FP 机器清除被证明是一种高效的瞬态执行触发机制,其泄露率可与已被广泛研究但常被禁用的 TSX 相媲美,并且在 Intel 和 AMD 平台上都可用 。
- 适用性:与需要复杂“训练”的分支预测和内存消歧不同,许多机器清除(如 FP、SMC)无需训练,这使得攻击更高效,且能绕过基于模式检测的防御措施 。