# 3.12 微体系结构侧信道攻击
<!-- source: p0270 -->

本章介绍的推测执行和多线程技术，使宽发射处理器能够在广泛的工作负载上取得良好性能。但这些技术也引入了一类新的<!-- source: p0271 -->安全漏洞，称为**微体系结构侧信道攻击**，或**瞬态执行攻击（transient execution attacks）**。从 Meltdown［Lipp 等，2018］和 Spectre［Kocher 等，2019］攻击问世以来，新的侧信道攻击类型及变体不断出现。它们利用超标量处理器的性能机制，访问并泄露秘密数据。

微体系结构侧信道攻击，与第 2 章介绍的 prime-probe 攻击相似。特别令人担忧的是，这类攻击允许窥探者控制 prime 与 probe 两个阶段之间执行的指令序列。下面将解释，推测如何让这些指令绕过软件边界检查、虚拟内存保护、虚拟机隔离以及可信执行的硬件隔离等安全机制，直接访问秘密数据。因此，它们能够快速、准确地在缓存中留下标记，供探测阶段观察。更糟的是，SMT 处理器允许窥探进程与受害进程共享处理器流水线。这带来两个问题。第一，窥探者可以利用一级缓存、BTB 等多种缓存结构，建立高带宽侧信道。第二，由于窥探者与受害者并行运行，窥探者能够在正常执行活动（例如缓存重新填充）覆盖泄露的信息之前，迅速探测侧信道。推测与 SMT 相结合，使侧信道攻击在准确性和有效带宽方面，有可能超过此前所有已知攻击。大量服务运行在公有云框架等共享基础设施上，而这些设施采用超标量与 SMT 处理器，因此，微体系结构侧信道攻击可能带来严重的安全影响。

下面用一段概括最初 Meltdown 攻击的伪代码，说明推测如何协助侧信道攻击［Lipp 等，2018］。在共享核心上运行这段代码的窥探者，有可能观察到操作系统内核所使用的任意内存地址的值。

~~~text
Allocate user_memory[];             // prime 阶段
Flush_caches();
Generate_exception();              // 推测并泄露
Value = kernel_array[Address];
Index = (Value & 1) * 256;          // probe 阶段
Value = user_array[Index];
~~~

> 译注：原书分配及后文说明使用 `user_memory`，最后一条伪代码却使用 `user_array`。注释的位置也沿用原页，不据此将代码视为完整可执行实现。

窥探者首先准备系统：（1）分配数组 `user_memory`；（2）确保其所有元素都不在处理器一级缓存中。接着，（3）发射一条会产生异常的指令，例如算术异常（除以零），或由加载指令触发的内存保护错误。如果处理器一次只执行一条指令，那么后续步骤（4～6）都不会执行。但支持推测的处理器会继续执行后面的代码，直到产生异常的指令到达 ROB 队首。因此，在异常被触发、步骤 4～6 的指令结果被丢弃之前，后续所有指令很可能已经完整或部分执行。

<!-- source: p0272 -->

下一条指令（4）访问一处本不应有权访问的内核内存位置，例如，给窥探者地址空间中的指针加上或减去一个较大的偏移量。指令（5）依据读出的内核内存值的最低有效位，生成索引值 0 或 256；指令（6）用这个索引读取 `user_memory` 数组中的一个元素。异常到达 ROB 队首时，指令（4～6）会被撤销，但指令（6）对处理器一级缓存产生的影响仍然保留：数组 `user_memory` 的一部分已经进入缓存。窥探者可以在异常恢复后，或者在同一 SMT 处理器的另一个并行线程中，通过测量访问 `user_memory` 各元素的耗时来探测缓存。如果元素 0 的加载很快，说明缓存命中，也就说明该内核内存位置的最低有效位为 0；如果元素 256 的加载很快，则最低有效位为 1。窥探者可以重复这些步骤，绕过虚拟内存保护，逐步读取大片内核内存及其中的全部秘密。Meltdown 论文详细解释了各个步骤，并展示了 x86 指令集上的实现［Lipp 等，2018］。

大多数微体系结构侧信道攻击的结构都与 Meltdown 相似。在准备与探测之间，窥探者利用推测执行一些访问、泄露秘密数据的指令，但不让它们提交。秘密数据用于改变某个硬件性能机制的状态，随后再探测该机制，以推断秘密数据的若干位。近期提出的攻击使用了超标量处理器中广泛的性能机制（缓存、BTB 等）或推测事件（延迟异常报告、分支预测、内存消歧）。在包括 Spectre 在内的许多攻击中，窥探者通过现有软件接口调用操作系统或受害进程，并精心选择输入，使受害者自身的代码以推测方式访问、泄露秘密数据。受害进程处理这个调用时，窥探进程利用同一核心上的 SMT 线程探测泄露的数据。关于微体系结构侧信道攻击的详细回顾与分类，可参阅 Guangyuan Hu 等的综述［Hu 等，2021］。

在不损失性能的前提下，很难防御微体系结构侧信道攻击。第一种防御方式，是重新编译并修改可能操作秘密数据的代码。编译器可以加入 fence 指令，在某个条件确定之前，禁止推测执行。另一种做法是，在利用推测的指令与可能泄露秘密数据的加载之间，引入依赖；这称为**推测加载加固（speculative load hardening）**。这类防御的缺点在于依赖重新编译，而重新编译并非总是可行；另外，决定对潜在易受攻击代码多广泛地应用这些防御时，必须在性能与安全之间取舍。第二种方式，是限制对可用于传递秘密的共享硬件结构的访问。有些情况下会关闭 SMT 多线程，防止窥探进程轻易观察同一 SMT 处理器上与受害进程共享的各种结构。

<!-- source: p0273 -->

通过缓存分区技术隔离不同进程使用的缓存空间，也能降低某些攻击的有效性。最后，还有若干方案提出修改处理器流水线，在推测期间限制访问或推迟使用潜在敏感数据，或者撤销错误推测指令对硬件性能机制的影响。Guangyuan Hu 等的综述也回顾了微体系结构侧信道攻击防御的主要类别［Hu 等，2021］。截至本书写作时，还没有一组防御措施能够保证应对所有可能的攻击。

微体系结构侧信道攻击使人们意识到：现有 ISA 对正确行为的规定，并不足以支持对安全攻击的推理。ISA 以独立于时序的方式，定义可编程处理器的功能行为。因此，完全可能构建出正确实现 ISA、却容易受到安全攻击的超标量处理器。当前的推测式超标量处理器正存在这一弱点。本领域还缺少相应的抽象与工具，用于建模并形式化分析硬件实现的时序行为，如何与安全和隐私意义上的正确执行相互作用。截至本书写作时，已有若干研究工作在应对这一挑战。
