投机执行与侧信道
Speculative Execution and Side Channels
2018 年 1 月,两个被提前披露的微架构问题同时进入公众视野。一组工作被命名为 Spectre(幽灵),另一组被命名为 Meltdown(熔断)。它们不是某一种病毒,也不是某条具体指令的「写错」。它们是两类被公开命名的泄露形态:处理器为了填满流水线而做的投机执行,会在架构状态(寄存器、程序员可见内存)回滚之后,仍在缓存、缓冲区等微架构结构里留下可分辨的痕迹;这些痕迹不在指令集手册的观察等价里,却能被做成隐蔽信道的一端。
本文只在概念层说明:投机是什么、缓存为何能成为信道、工业界用隔离与串行化做了哪些回应。它不提供测量代码、不描述如何构造触发序列、不讨论如何读取密钥或跨进程搬运机密。那些属于攻击教程,不属于这里。
破除误解:这不是「缓存有 bug」,也不是单家厂商的一次事故
流水线与乱序执行是过去三十年高性能 CPU 的默认形态。见 CPU 流水线。投机执行——在分支结果或权限检查尚未确定时先往下走——是为了不让长延迟把流水线排空。架构契约要求:若投机猜错,程序员可见状态必须像没发生过。微架构契约从未以同样严格的方式写入 ISA:缓存行是否被填充、缓冲区是否被占用,历来被当成性能细节。2018 年的冲击,是研究社区把这条缝提升成安全模型缺陷:安全论证若只谈架构状态,会漏掉旁路。
第二个误解是把 Spectre 与 Meltdown 当成同一个 CVE 的两个名字。公开文献把它们写成不同的类。Meltdown 类问题的核心叙事是:投机窗口里出现了架构模型禁止的特权数据触达,而微架构仍可能把相关效应留下来。Spectre 类问题的核心叙事是:投机沿着预测器(分支历史、间接跳转目标等)走进本不应在当前架构路径上执行的代码区域,使受害上下文的微架构状态变成信道的一端。两者都依赖「投机 + 微架构残留」,分类标签不同,缓解也不同。后续年份里出现的变体,多半是在这两个类名下增加新的预测器或缓冲区名称,而不是全新的物理学。
第三个误解是:关掉缓存、或「不要用共享计算机」,就算理解了问题。共享缓存、共享功能单元、同时多线程,是服务器与浏览器进程模型的日常。问题的尺度是多租户与站点共存,不是某台实验室机器的配置开关。浏览器一侧的长期回应是把站点放进不同进程,让地址空间成为墙,而不是在脚本层禁止一切计时。见 Chromium 的站点隔离。
投机执行:架构回滚与微架构残留
一条指令在流水线里可以处于「已执行但尚未提交」的状态。分支预测器若判断「将跳转」,后续指令会进入乱序窗口;权限检查、地址翻译、内存保护也可能在关键结果回来之前被越过一截。提交(retire)才把结果写进架构寄存器文件。发现预测失败或故障时,处理器扔掉窗口里的投机结果,从正确路径重填。
对程序员,这条故事应当到此结束。对微架构,执行过的加载可能已经向缓存层次发出填充请求,预取器可能已经改了内部表,预测器本身也可能用投机路径更新了历史。这些结构影响的是以后某次访问的延迟与资源争用,而不是手册里的读写语义。侧信道研究把这种「延迟依赖秘密」的现象称为微架构泄露。缓存是被讨论得最多的介质,因为它是共享的、分层的,并且延迟差以数量级计;它不是唯一介质。执行端口、翻译缓冲、预测器表,都可以在概念上成为争用点。本文不展开任何一种介质的操作步骤。
隐蔽信道在信息论里是一条不该存在于规范接口上的通信通路。一端(发送者)按某种规律扰动共享微架构;另一端(接收者)观察扰动造成的延迟或资源差异。在投机执行的叙事里,发送者往往不是一个自愿的恶意程序,而是受害上下文里被投机执行到的那一段逻辑,接收者则在架构允许的位置观察微架构。安全模型若只禁止架构上的读,就拦不住这条通路。这是类的定义,不是一份清单。
两个被命名的类:Spectre 与 Meltdown
Spectre 作为类名,强调预测器与投机路径。公开论文(Kocher 等人,IEEE S&P 2019,预印本 2018)把它表述为:投机可以走进与当前架构控制流不一致的路径,从而使依赖于秘密值的微架构效应出现在窗口内。类之下的变体按预测器命名——条件分支方向、间接分支目标、返回地址预测等——因为缓解必须对准被滥用的预测结构。把所有变体当成同一补丁,会漏掉「这一处预测器已经串行化、另一处还没有」的现实。正确的阅读是:Spectre 是家族名,变体是不同的预测入口。
Meltdown 作为类名(Lipp 等人,2018),强调投机窗口与特权边界的交错。架构上,用户态不应获得内核映射数据的值。若微架构在检查完成前已经把一次被禁止的加载推进到会留下残留的程度,泄露叙事就从「预测器走错路径」变成「特权检查与加载流水线的交错」。缓解因此更多落在隔离内核映射、限制用户态可见的地址翻译、以及改变提交与检查的相对顺序。同一年之后,类似的交错在其他边界(虚拟机、SGX 类飞地、管理程序)上被冠以新的名字;它们仍属于「投机越过架构边界」这个类,而不是教程序如何去越过。
两个类共享的概念骨架只有一句:架构等价不蕴含微架构观察等价。 安全证明若停在 ISA,就必须额外假设微架构观察不可用,或把观察者排除出共享硬件。云计算与浏览器让后一个假设失败。
工业回应:串行化、隔离、站点隔离
缓解分成几条,彼此不能互相替代。
串行化投机。 在敏感的控制流之后插入限制投机的栅栏,使后续加载必须等到预测确认。这直接砍掉窗口,也砍掉填满流水线的理由。性能账单落在系统调用密集、解释器与 JIT、以及间接调用很多的代码上。微码更新与编译器改写(例如用替代序列降低间接预测的可利用性)属于同一家族:让预测器更难把控制流送到会形成信道的窗口。具体指令选择随微架构而变,本文不罗列。
硬件与调度隔离。 关闭或限制同时多线程、按站点或按虚拟机划分缓存颜色、在超线程之间避免共享敏感核心——这些是把信道的两端拆开。代价是吞吐。云厂商用调度策略降低「两个互不信任的租户落到同一核心」的概率;这不是密码学,是拓扑。
软件故障域重划。 浏览器的站点隔离把不同站点放进不同进程,使渲染器即使在微架构上留下残留,也不与另一站点共享地址空间里的机密对象。操作系统的内核页表隔离(KPTI 一类)缩小用户态与内核映射的共存窗口。这些措施承认:微架构信道难以从 ISA 层彻底删除,于是缩小共享同一微架构、又同时持有不同机密的配置。站点隔离的机制、进程模型和代价,在 Chromium 专文里展开;这里只需一句:它是对 Spectre 类问题的产品级回答,不是对某一变体的一次性补丁。
没有一种回应恢复「微架构完全不可见」。研究与工业的共识更接近:缩小窗口、拆开共享、把高价值机密留在更少的共享域里,并把剩余风险当成与侧信道共存的系统假设。
流水线为什么必须投机
指令级并行的墙是延迟。一次缓存未命中可以相当于几百个时钟,一次分支的条件可能依赖刚刚才写完的寄存器。若不投机,流水线在等待时是空的。预测器把历史压缩成「下一次大概走哪边」,让解码与发射继续。这与操作系统的推测无关,也与程序员写的 if 是否「应该」执行无关:它是微架构为吞吐做的赌注。计算机体系结构 把赌注的收益写成 IPC;安全把赌注的失败模式写成残留。
乱序窗口越大,可投机的指令越多,窗口关闭时要丢掉的微架构效应也可能越多。这不是鼓励加大或减小窗口的操作建议,而是机制:窗口是残留的时间积分上限。序列化缓解正是在敏感点把上限打到接近零。工业界按微架构世代选择在哪些预测器上加栅栏、在哪些微码路径上推迟加载,是因为每一处栅栏都有不同的 IPC 账单。把「关掉投机」当成通用按钮,等于放弃过去三十年的流水线设计;把「投机无害」当成 ISA 的推论,则是 2018 年之前的默认错误。
缓存作为概念上的信道介质,只需要一条物理学:同一组硬件上,近期被触及的行与未被触及的行,后续访问延迟不同。任何能让延迟进入观察者统计的通路,都可以在原则上成为隐蔽信道的一端。共享末级缓存、共享包含关系、同时多线程下的竞争,都放大「两端是否共处」。拆开共处,是隔离策略;不解释如何采样延迟,是因为采样属于操作细节。概念上到「延迟可分辨」已经足够理解为什么站点隔离要把互不信任的文档拆进不同地址空间。
瞬态执行(transient execution)后来被用作伞名:凡是执行了但未成为架构历史的指令,都在伞下。Spectre 与 Meltdown 是最先被广泛命名的两支。伞名的用处是文献检索与缓解分类,不是把所有 CVE 压成同一补丁。Canella 等人 2019 年的系统化梳理,按预测入口和泄露介质把变体放进表格,目的是让防御者核对清单,而不是提供复现步骤。阅读这类表格时,应停留在「哪一个预测器、哪一种共享结构尚未覆盖」。
代价与争议
性能是公开账单。2018 年之后的若干内核与超调用路径显著变慢,随后又被选择性关闭、按 CPU 世代分级缓解。争议在于:对不可信代码(浏览器、云函数)必须默认强硬;对完全受控的高性能计算集群,同一套默认会浪费流水线。没有全局最优,只有威胁模型。
命名与披露是第二条争议。家族名帮助沟通,也会让「已经打了 Spectre 补丁」被误读成「侧信道结束」。每年新的缓冲区名称被写成新论文,产品安全团队必须判断它属于已有类的新入口,还是新的共享结构。过度细分与过度合并都有代价。
责任分配是第三条。CPU 厂商、操作系统、编译器、浏览器、云调度,每一层都能声称「真正的洞在下一层」。健康的做法是把假设写出来:ISA 不保证微架构观察等价;共享硬件上的互不信任代码必须另加隔离。把责任推给「用户不要跑不可信代码」,在浏览器经济里不是可执行的政策。
本文刻意不进入如何把残留翻译成比特。那一步一旦写成步骤,就从概念解释变成操作手册。读者若需要实现缓解,应阅读厂商白皮书与操作系统发行说明中的防御选项,而不是寻找复现序列。
跨域连接
- 信息不对称:侧信道把「规范接口上看不见的状态」变成一部分观察者可及、另一部分观察者不可及的信息。架构手册让程序员以为双方平等地只看见 ISA;微架构让共享同一芯片的租户处于不对称的观察位置。安全模型若假定对称可见性,会把这种不对称误当成不存在。云与浏览器的定价与隔离策略,正是在给不对称定价。
- 信息哲学:投机执行逼问「一次计算是否发生过」。架构说:未提交则未发生。微架构说:发生过的扰动仍在。这是两种关于痕迹的本体论。幽灵类问题之所以难,不是因为指令集写错了一条规则,而是因为「发生」被定义在两个不相等的层次上。知识论上的教训是:观察等价必须声明观察者能看哪一层。
- 概率论:预测器是统计装置,用历史频率猜测下一条路径。泄露叙事不依赖某一次猜测永远正确,而依赖窗口以不可忽略的频率被打开。缓解里的随机化(某些表索引的盐)把观察变成噪声更大的估计,而不提供零误差的删除。概率在这里描述的是预测与残留的统计可用性,不是攻击配方。
- 热力学定律:计算是物理过程,必然在共享介质上留下能量与时间上的差。缓存延迟差是这种差的工程形态之一。热力学提醒:完全消灭旁路等于要求观察者对物理过程无任何分辨力,这与有限温度、有限时间的装置冲突。可执行的目标因此是把分辨力压到威胁模型之下,并用隔离减少共享,而不是宣称物理痕迹为零。
参考文献
- Kocher, P. et al. "Spectre Attacks: Exploiting Speculative Execution." IEEE Symposium on Security and Privacy, 2019.
- Lipp, M. et al. "Meltdown: Reading Kernel Memory from User Space." USENIX Security, 2018.
- Canella, C. et al. "A Systematic Evaluation of Transient Execution Attacks and Defenses." USENIX Security, 2019.
- Reis, C., Moshchuk, A. & Oskov, N. "Site Isolation: Process Separation for Web Sites within the Browser." USENIX Security, 2019.
- Ge, Q., Yarom, Y., Cock, D. & Heiser, G. "A Survey of Microarchitectural Timing Attacks and Mitigation Techniques." ACM Computing Surveys 51(3), 2018. (概念综述,非操作手册)
延伸阅读
- Intel / AMD / ARM 各代微架构安全白皮书中关于投机限制与隔离选项的说明(按 CPU 世代阅读,不作为跨代通用步骤)。
- McIlroy, R. et al. "Spectre is Here to Stay: An Analysis of Side-Channels and Speculative Execution." arXiv:1902.05178, 2019.