2018年1月3日,安全研究员Jann Horn在Google Project Zero的博客上发布了一篇技术报告。报告描述了一种攻击方式,可以读取任意进程的内存内容,包括密码、加密密钥和隐私数据。这台机器没有运行任何恶意软件,操作系统内核没有漏洞,应用程序也没有被入侵。
攻击的目标是CPU本身。
七年后,2025年5月,苏黎世联邦理工学院的研究团队发布了新的研究成果:所有2018年至2024年间生产的处理器,依然受到分支预测相关漏洞的影响。硬件层面的修复被再次绕过。
这不是一个简单的安全补丁问题。分支预测是现代CPU性能的基石,占处理器芯片面积的相当比例。理解为什么一个性能优化机制会变成安全噩梦,需要回到问题的起点:CPU为什么需要预测未来?
性能的代价:流水线与分支的矛盾
现代CPU的执行单元不会等待。当一条指令正在解码时,下一条指令正在从内存读取,再下一条指令正在计算地址。这就是流水线(pipeline)——让多个指令在不同阶段并行执行,将CPU的吞吐量提升数倍。
但流水线有一个致命的敌人:分支。
当CPU遇到条件分支(如if-else)或间接分支(如函数指针调用)时,它不知道下一条应该执行哪条指令。等待分支结果意味着流水线必须停顿——在深度流水线的现代处理器中,这可能浪费15到20个周期。对于每秒执行数十亿条指令的CPU来说,这种停顿是不可接受的。
解决方案是预测。CPU猜测分支的方向,继续执行预测路径上的指令。如果猜对了,流水线保持满载,性能提升显著。如果猜错了,CPU必须丢弃所有推测执行的指令,重新从正确的路径开始。这个代价被称为"分支预测错误惩罚"(branch misprediction penalty)。
关键问题是:如何让猜测尽可能准确?
从抛硬币到模式识别:预测器的演进
两位计数器:最简单的学习机制
1980年代,最简单的动态分支预测器诞生了:两位饱和计数器(2-bit saturating counter)。它的逻辑极其简单:
- 维护一个两位的状态计数器,取值0到3
- 分支执行时,计数器向"跳转"方向加一
- 分支不执行时,计数器向"不跳转"方向减一
- 计数器值≥2时预测跳转,否则预测不跳转
这个看似粗糙的机制有一个关键特性:滞后性。一个偶然的异常结果不会立即改变预测方向。对于大多数倾向于单向执行的分支(如循环),两位计数器的预测准确率可以达到90%以上。
但两位计数器有一个明显的弱点:它无法识别模式。考虑一个分支的执行序列是"跳转-不跳转-跳转-不跳转…",两位计数器会持续预测错误,因为它只记录"总体倾向",而不记录"具体序列"。
两级预测器:历史的力量
1990年代初,两级自适应预测器(two-level adaptive predictor)引入了一个革命性的概念:分支历史。
预测器不再只看当前分支的单一计数器,而是记录最近N次分支的执行方向(跳转或不跳转),形成一个"分支历史寄存器"(Branch History Register, BHR)。这个历史值被用作索引,查找一个"模式历史表"(Pattern History Table, PHT)中的计数器。
这意味着:如果分支A在过去10次都是"跳转-跳转-不跳转"的模式,预测器可以学到这个模式,而不是简单地猜测"大多数情况下跳转"。
Yeh和Patt在1991年的研究中提出了gshare预测器:将分支地址和全局历史进行异或操作,生成索引。这种设计同时考虑了"哪个分支"和"这个分支的历史模式",显著提升了预测准确率。
神经预测器:从计数到关联
2000年代初,学者们开始将机器学习引入分支预测。Jiménez提出的感知机预测器(perceptron predictor)使用一个简单的单层神经网络,学习分支历史与分支方向之间的相关性。
感知机预测器维护一组权重向量,每个权重对应历史中的一位。预测时,将历史向量的每一位乘以对应的权重,求和后根据正负判断方向。如果预测错误,根据感知机学习规则调整权重。
这种方法允许预测器捕捉长距离的依赖关系,而不需要指数级增长的存储空间。在2006年的CBP(Championship Branch Prediction)竞赛中,神经预测器展现了强大的竞争力。
TAGE:几何级历史的艺术
2006年,André Seznec提出了TAGE预测器(TAgged GEometric history length predictor),这代表了当今分支预测的最高水平。
TAGE的核心洞察是:不同的分支需要不同长度的历史来准确预测。
简单分支(如循环结束条件)只需要短历史,复杂分支(如多层嵌套条件或数据依赖的控制流)可能需要数百位的历史信息。传统的固定历史长度预测器无法兼顾这两种情况。
TAGE维护多个预测表,每个表使用不同长度的历史——从短到长呈几何级数增长(如4、8、16、32、64、128、256…)。每个表项除了存储预测信息,还存储一个标签(tag),用于识别是否是正确的分支。
预测时,TAGE同时查询所有表,选择具有最长历史且标签匹配的表项的预测结果。如果预测错误,TAGE会在更长历史的表中分配新条目,尝试学习更复杂的模式。
TAGE还有一个巧妙的设计:有用性计数器(usefulness counter)。每个表项记录它是否真的提供了比更短历史表项更好的预测。如果一个表项长期没有用,它会被替换。这防止了历史表被无用的条目污染。
根据CBP竞赛的数据,TAGE预测器在标准测试集上可以达到97%以上的预测准确率。现代高性能CPU(包括Intel和AMD的最新产品)普遍采用了TAGE或其变体。
graph TB
subgraph "TAGE预测器架构"
BHR["分支历史寄存器<br/>(BHR)"]
T0["基础预测表 T0<br/>历史长度: 无"]
T1["预测表 T1<br/>历史长度: 4"]
T2["预测表 T2<br/>历史长度: 16"]
T3["预测表 T3<br/>历史长度: 64"]
Tn["预测表 Tn<br/>历史长度: 256"]
SEL["选择器<br/>(选择最长历史匹配)"]
PRED["预测结果"]
BHR --> T1
BHR --> T2
BHR --> T3
BHR --> Tn
PC["分支地址 (PC)"] --> T0
PC --> T1
PC --> T2
PC --> T3
PC --> Tn
T0 --> SEL
T1 --> SEL
T2 --> SEL
T3 --> SEL
Tn --> SEL
SEL --> PRED
end
间接分支预测:更复杂的挑战
条件分支只需要预测"跳转"或"不跳转"两个方向。间接分支(如jmp *%rax或call *%rbx)的目标地址是一个运行时计算的值,可能有数百个可能的目标。
现代处理器使用两套机制预测间接分支:
分支目标缓冲区(Branch Target Buffer, BTB):存储从分支地址到目标地址的映射。这是一个基于地址的直接映射,不依赖历史。对于目标相对固定的间接分支(如虚函数调用的常见目标),BTB表现良好。
间接分支预测器(Indirect Branch Predictor, IBP):基于分支历史预测目标地址。ITTAGE是TAGE的间接分支版本,每个表项存储的是目标地址而不是方向。对于目标频繁变化的间接分支(如解释器字节码分发),IBP可以学习"历史模式→目标"的映射关系。
Intel处理器的预测逻辑是:先查询IBP,如果命中则使用IBP的目标;否则查询BTB。这个优先级关系后来成为Spectre攻击的关键点。
返回指令预测:调用栈的硬件镜像
函数返回是一种特殊的间接分支。ret指令的目标地址在栈上,由对应的call指令压入。由于调用栈的LIFO(后进先出)特性,返回地址的模式高度可预测。
现代CPU使用**返回栈缓冲区(Return Stack Buffer, RSB)**来预测返回指令。每次执行call指令时,CPU将返回地址压入RSB;执行ret指令时,从RSB弹出预测的目标地址。这是一个简单的硬件栈,通常深度为16到32项。
RSB极其高效:它不需要学习或训练,只需要维护调用栈的镜像。预测准确率接近100%,延迟极低。
但RSB有一个致命的限制:容量有限。当程序深度超过RSB大小时,最早的返回地址会被挤出RSB。此时如果执行返回指令,RSB发生下溢(underflow)。
在Intel处理器上,RSB下溢时会回退到BTB或IBP进行预测。这个后备机制被称为RSBA(Return Stack Buffer Alternate)。从安全角度看,RSBA将返回指令的预测与BTB/IBP共享——这打开了跨安全域攻击的大门。
Spectre:预测器变成攻击向量
2018年,Spectre论文揭示了分支预测的安全含义。论文区分了两种攻击变体:
Spectre v1(边界检查绕过):攻击条件分支预测。通过"训练"分支预测器使其预期某个边界检查为真,然后在恶意输入上触发预测错误。CPU在预测错误的路径上执行,访问了越界内存,并通过缓存侧信道泄露数据。
Spectre v2(分支目标注入):攻击间接分支预测。这是更隐蔽也更危险的攻击方式。
分支目标注入的工作原理
攻击者的目标是让受害者代码中的间接分支预测到攻击者选择的地址。该地址包含一个"gadget"——一小段代码,在推测执行中访问敏感数据并通过缓存侧信道泄露。
攻击分三步:
训练(Mistraining):攻击者在自己的地址空间执行一系列间接分支,目标地址与受害者代码中的gadget地址相同。由于BTB和IBP是基于地址索引的,而且不同安全域的代码可能映射到相同的虚拟地址,攻击者可以"污染"预测器状态。
触发(Triggering):攻击者触发受害者代码执行间接分支。由于预测器被污染,CPU预测的目标是攻击者训练的gadget地址。
泄露(Leaking):CPU推测执行gadget代码,访问敏感内存,将数据编码到缓存状态。攻击者通过时间测量(如Flush+Reload)恢复泄露的数据。
关键洞察是:预测器状态跨安全域共享。用户态代码的训练可以影响内核态代码的预测。这是性能优化的代价——为每个安全域维护独立的预测器状态会引入巨大的硬件复杂度和延迟。
为什么预测错误可以泄露数据?
推测执行的关键特性是:架构状态(architectural state)被回滚,但微架构状态(microarchitectural state)可能残留。
当分支预测错误被检测到时,CPU丢弃所有推测执行的指令结果,恢复到分支前的寄存器状态。从程序角度看,就像这些指令从未执行过。
但CPU不会撤销推测执行对缓存的影响。如果推测执行的指令访问了某个内存地址,该地址可能被加载到缓存中。攻击者可以通过测量访问时间来判断某个地址是否在缓存中——这就是缓存侧信道。
Spectre v2的组合是:
- 分支目标注入 → 控制推测执行路径
- 推测执行中的内存访问 → 产生缓存状态变化
- 缓存侧信道 → 恢复泄露的数据
七年的猫鼠游戏:从IBRS到BPRC
Spectre v2披露后,业界迅速部署了多种缓解措施。但这些措施的有效性在过去七年中不断受到挑战。
Retpoline:软件层面的隔离
2018年1月,Google提出了Retpoline(Return Trampoline)——一种纯软件的缓解方案。
Retpoline的核心思想是:利用RSB优先级高于BTB/IBP的特性,控制间接分支的推测执行。
传统的间接分支jmp *%rax被替换为以下序列:
call set_up_target
capture_spec:
pause
jmp capture_spec
set_up_target:
mov %rax, (%rsp) ; 修改栈上的返回地址
ret ; 使用RSB预测,而非BTB/IBP
这个序列的工作方式:
call set_up_target将capture_spec压入栈和RSB- 修改栈上的返回地址为实际目标
%rax ret使用RSB中的capture_spec进行预测- 推测执行进入无限循环
pause; jmp capture_spec,不会泄露任何数据 - 实际执行使用栈上的
%rax,正确返回
Retpoline的优点是完全软件实现,不需要硬件或微码更新。缺点是需要重新编译所有敏感代码,且对间接分支性能有一定影响。
IBRS/eIBRS:硬件层面的隔离
Intel引入了间接分支限制推测(Indirect Branch Restricted Speculation, IBRS)——一种硬件机制,限制低权限域的预测器状态影响高权限域。
原始IBRS需要操作系统在每次特权级切换时写入MSR(Model Specific Register),性能开销显著。增强版IBRS(eIBRS)只需要在启动时启用一次,硬件自动隔离不同特权级的预测器状态。
AMD的AutoIBRS和ARM的CSV2提供了类似的功能。
理论上,eIBRS应该阻止用户态训练影响内核态预测。但2025年5月发布的BPRC(Branch Predictor Race Condition)研究表明,这并非事实。
BPRC:竞态条件绕过硬件隔离
苏黎世联邦理工学院的研究团队发现了一个关键问题:分支预测器更新是异步的。
当一条分支指令执行完毕,预测器的更新不是立即完成的。存在数百个周期的延迟,即使使用lfence等序列化指令也无法保证同步。
这意味着,如果用户态代码训练分支预测器,然后立即执行系统调用进入内核,预测器更新可能在特权级切换之后才完成。此时,新的预测条目会被错误地标记为内核态权限。
研究者将这种现象称为分支预测器竞态条件(BPRC)。利用BPRC,攻击者可以:
- 在用户态训练预测器
- 立即执行系统调用
- 预测器更新在内核态完成,获得内核权限标记
- 后续内核代码的间接分支使用这个被污染的预测
这个攻击对2018年至2024年的所有Intel处理器有效,包括已启用eIBRS的系统。AMD和ARM处理器似乎不受影响,这可能与它们的预测器更新机制不同有关。
BHI:分支历史的跨域影响
另一种绕过eIBRS的方式是分支历史注入(Branch History Injection, BHI)。
eIBRS隔离了预测器的"目标"部分,但没有隔离"历史"部分。攻击者可以构造特定的控制流模式,将分支历史设置为期望的值。由于IBP使用历史进行索引,攻击者可以影响内核态间接分支的预测目标。
Intel在2022年引入了BHI_DIS_S控制位来禁用特权模式下的历史依赖预测。有趣的是,这个缓解措施实际上改善了BPRC攻击的成功率——因为它禁用了IBP,使得预测只能来自BTB,而BTB正是BPRC的目标。
性能与安全的永恒博弈
分支预测漏洞的根本矛盾在于:隔离预测器状态会损害性能,共享预测器状态会损害安全。
性能开销的真实数据
原始IBRS的性能开销是惊人的。根据Red Hat的测试,数据库工作负载的延迟增加了5%到10%,某些微基准测试的开销超过20%。这是因为每次系统调用和中断都需要MSR写入,而MSR访问是序列化操作,会清空流水线。
eIBRS显著改善了性能,但仍然有开销。一项2022年的研究表明,即使在有eIBRS的系统上,某些工作负载仍有约2%的性能损失——主要来自禁用某些预测优化和额外的安全检查。
Retpoline的性能影响取决于代码中间接分支的密度。对于系统调用密集的工作负载,开销可达5-10%。Linux内核在2018年初期默认启用Retpoline,后来在eIBRS可用时切换到混合策略。
无解的权衡?
从体系结构角度看,分支预测漏洞揭示了一个深层次的设计哲学问题。
现代处理器的性能很大程度上依赖于"推测"——推测数据值、推测分支方向、推测内存地址。这些推测的准确性来自于:过去的行为可以预测未来的行为。
这个假设是程序局部性原理的体现,也是分支预测器能够达到97%以上准确率的原因。但同样的机制也意味着:一个安全域的行为可以影响另一个安全域的预测。
彻底的解决方案需要:
-
完全隔离预测器状态:每个安全域维护独立的预测器,切换时进行隔离。代价是显著的硬件复杂度和潜在的预测准确率下降。
-
限制推测执行:禁止跨安全域边界的推测执行。代价是性能损失,因为流水线在边界处必须停顿。
-
消除侧信道:让推测执行不留任何微架构痕迹。这在技术上极其困难,因为缓存本身是性能的必需品。
目前的主流方案是折中的:保持性能,接受一定程度的残余风险,通过软件缓解和微码更新逐步堵住漏洞。但这不是根本性的解决方案——只要分支预测存在,攻击面就存在。
结语:性能与安全的博弈
从两位计数器到TAGE,从简单的历史表到神经预测器,分支预测技术的演进是计算机体系结构追求性能的缩影。每一代预测器都在解决前一代的局限,将预测准确率从90%推向97%,将分支预测错误惩罚的影响降到最低。
但性能优化的另一面是攻击面。预测器越智能,可被利用的状态就越多。历史越长,跨域影响就越难隔离。预测准确率越高,推测执行的深度就越大,泄露的潜在数据量就越多。
Spectre不是一次性的安全事件,而是一个深层次矛盾的体现。这个矛盾不会因为一个微码更新或一个编译器补丁而消失。它是现代计算机体系结构的固有属性——就像软件工程中没有万能解法一样,硬件设计中也没有完美的安全机制。
理解这一点,才能理解为什么七年后的今天,我们仍在讨论分支预测漏洞。这不是安全工程师的失败,而是复杂系统的必然。
参考文献
-
Kocher, P., Horn, J., Fogh, A., et al. (2019). Spectre Attacks: Exploiting Speculative Execution. IEEE S&P.
-
Seznec, A. (2006). The L-TAGE Branch Predictor. Journal of Instruction-Level Parallelism.
-
Seznec, A. (2011). A New Case for the TAGE Branch Predictor. ACM TACO.
-
Rüegge, S., Wikner, J., & Razavi, K. (2025). Branch Privilege Injection: Compromising Spectre v2 Hardware Mitigations by Exploiting Branch Predictor Race Conditions. USENIX Security.
-
Turner, P. (2018). Retpoline: A Software Construct for Preventing Branch Target Injection. Google Technical Report.
-
Intel Corporation. (2018). Speculative Execution Side Channel Mitigations.
-
Yeh, T., & Patt, Y. N. (1991). Two-Level Adaptive Training Branch Prediction. ISCA.
-
Jiménez, D. A., & Lin, C. (2001). Dynamic Branch Prediction with Perceptrons. HPCA.
-
Barberis, E., Frigo, P., Muench, M., et al. (2022). Branch History Injection: On the Effectiveness of Hardware Mitigations Against Cross-Privilege Spectre-v2 Attacks. USENIX Security.
-
Wikner, J., & Razavi, K. (2022). RETBLEED: Arbitrary Speculative Code Execution with Return Instructions. USENIX Security.
-
Li, L., Yavarzadeh, H., & Tullsen, D. (2024). Indirector: High-Precision Branch Target Injection Attacks Exploiting the Indirect Branch Predictor. USENIX Security.
-
Wikipedia. Branch Predictor. https://en.wikipedia.org/wiki/Branch_predictor