24. 微架构数据采样(MDS)缓解措施

24.1. 概述

微架构数据采样(MDS)是针对 Intel CPU 内部缓冲区的系列侧信道攻击。其变种包括:

  • 微架构存储缓冲区数据采样(MSBDS)(CVE-2018-12126)

  • 微架构填充缓冲区数据采样(MFBDS)(CVE-2018-12130)

  • 微架构加载端口数据采样(MLPDS)(CVE-2018-12127)

  • 微架构不可缓存内存数据采样(MDSUM)(CVE-2019-11091)

MSBDS 会泄露存储缓冲区条目(Store Buffer Entries),作为优化,这些条目可能会被推测性地转发到相关的加载操作(存储到加载转发,store-to-load forwarding)。这种转发也可能发生在针对不同内存地址的错误或辅助加载操作上,在特定条件下可被利用。存储缓冲区在超线程(Hyper-Threads)之间进行分区,因此跨线程转发是不可能的。但是,如果某个线程进入或退出睡眠状态,存储缓冲区会被重新分区,这可能会将一个线程的数据暴露给另一个线程。

MFBDS 会泄露填充缓冲区条目(Fill Buffer Entries)。填充缓冲区在内部用于管理 L1 缓存未命中(miss)情况,并保存作为对内存或 I/O 操作的响应而返回或发送的数据。填充缓冲区可以将数据转发到加载操作,也可以将数据写入缓存。当填充缓冲区被取消分配时,它可能会保留先前操作的陈旧数据(stale data),然后这些数据可能会被转发到错误或辅助加载操作中,在特定条件下可被利用。填充缓冲区在超线程之间是共享的,因此跨线程泄露是可能的。

MLPDS 会泄露加载端口数据(Load Port Data)。加载端口用于执行来自内存或 I/O 的加载操作。接收到的数据随后被转发到寄存器文件或后续操作。在某些实现中,加载端口可能包含来自先前操作的陈旧数据,在特定条件下这些数据可能会被转发到错误或辅助加载操作中,从而最终可能被利用。加载端口在超线程之间是共享的,因此跨线程泄露是可能的。

MDSUM 是 MSBDS、MFBDS 和 MLPDS 的特例。来自内存的不可缓存加载如果发生错误或辅助,可能会在微架构结构中留下数据,随后可以使用 MSBDS、MFBDS 或 MLPDS 使用的相同方法之一来观察到这些数据。

24.2. 暴露假设

假定攻击代码驻留在用户空间或虚拟机(guest)中(有一个例外)。此假设背后的基本原理是,利用 MDS 所需的代码结构需要:

  • 控制加载以触发错误或辅助

  • 拥有一个泄露小工具(disclosure gadget),它通过侧信道公开推测访问的数据以供消费。

  • 控制泄露小工具用来公开数据的指针

无法百分之百排除内核中存在这种结构的可能性,但其涉及的复杂性使其极其不可能发生。

有一个例外,即不受信任的 BPF(untrusted BPF)。不受信任的 BPF 的功能是有限的,但需要彻底调查它是否可用于创建此类结构。

24.3. 缓解策略

至少对于单 CPU 线程情况(关闭 SMT),所有变种都具有相同的缓解策略:强制 CPU 清除受影响的缓冲区。

这是通过结合使用原本未被使用且过时的 VERW 指令和微代码更新来实现的。当执行 VERW 指令时,微代码会清除受影响的 CPU 缓冲区。

对于虚拟化,有两种方法来实现 CPU 缓冲区清除。要么通过修改后的 VERW 指令,要么通过 L1D 刷新(Flush)命令。启用 L1TF 缓解措施时会发出后者,从而可以避免额外的 VERW。如果 CPU 不受 L1TF 影响,则需要发出 VERW。

如果在没有微代码更新的 CPU 上执行带有提供的段选择器参数的 VERW 指令,除了白白浪费少量的 CPU 周期外,没有其他副作用。

这不能防范跨超线程攻击,MSBDS 除外,MSBDS 仅在其中一个超线程进入 C 状态(C-state)时才可进行跨超线程利用。

内核提供了一个用于调用缓冲区清除的函数

x86_clear_cpu_buffers()

此外,宏 CLEAR_CPU_BUFFERS 也可以在退出用户空间路径(exit-to-user path)后期的汇编代码中使用。除了 CFLAGS.ZF 之外,此宏不会破坏任何寄存器。

该缓解措施在内核/用户空间、虚拟机监控程序(hypervisor)/虚拟机(guest)以及 C 状态(空闲)转换时被调用。

作为一种特殊变通方法,用于解决宿主机(host)已更新微代码,但虚拟机监控程序(尚未)向虚拟机公开 MD_CLEAR CPUID 位的虚拟化场景,内核会发出 VERW 指令,希望它确实能清除缓冲区。状态会进行相应的反映。

根据目前的认知,内核本身不需要额外的缓解措施,因为无法以允许从恶意的用户空间或 VM 虚拟机进行利用的方式来控制公开泄露数据所需的小工具。

24.4. 内核内部缓解模式

关闭

缓解已禁用。CPU 不受影响,或者在内核命令行中提供了 mds=off

full

缓解已启用。CPU 受到影响,并且在 CPUID 中通告了 MD_CLEAR。

vmwerv

缓解已启用。CPU 受到影响,但未在 CPUID 中通告 MD_CLEAR。这主要用于宿主机拥有更新的微代码,但虚拟机监控程序未在 CPUID 中公开 MD_CLEAR 的虚拟化场景。这是一种尽力而为(best effort)的方法,不作保证。

如果 CPU 受到影响且内核命令行中未提供 mds=off,则内核会根据 MD_CLEAR CPUID 位的可用性选择适当的缓解模式。

24.5. 缓解点

24.5.1. 1. 返回用户空间

当从内核转换到用户空间时,如果内核命令行未禁用缓解措施,则会在受影响的 CPU 上刷新 CPU 缓冲区。该缓解措施通过功能标志 X86_FEATURE_CLEAR_CPU_BUF 启用。

缓解措施在恢复用户寄存器后、刚要转换到用户空间之前被调用。这样做是为了尽量缩短在 VERW 之后可以访问内核数据的窗口期(例如通过 VERW 之后的 NMI)。

未处理的极端情况(Corner case):返回内核的中断不会清除 CPU 缓冲区,因为退出用户空间路径无论如何都会做这件事。但是,可能存在这样一种情况:在退出用户空间路径清除缓冲区之后,内核中生成了 NMI。这种情况未得到处理,返回内核的 NMI 不会清除 CPU 缓冲区,因为

  1. 在 VERW 之后但在返回用户空间之前收到 NMI 是很少见的。

  2. 对于无特权用户,没有已知的方法可以让该 NMI 变得不那么罕见或对其进行定向攻击。

  3. 发起实际攻击需要大量这种定时精确的 NMI。据推测,带宽是不够的。

  4. 所述的 NMI 发生在 VERW 之后,即当用户状态已恢复且大多数感兴趣的数据已被擦除时。剩下的只有 NMI 触及的数据,这些数据可能具有也可能不具有任何价值。

24.5.2. 2. C 状态转换

当 CPU 空闲并进入 C 状态时,如果在 SMT 处于活动状态的受影响 CPU 上,则需要清除 CPU 缓冲区。这解决了当其中一个超线程进入 C 状态时存储缓冲区的重新分区问题。

当 SMT 处于非活动状态时(即 CPU 不支持 SMT 或所有同级线程都处于离线状态),则不需要清除 CPU 缓冲区。

空闲清除功能在仅受 MSBDS 影响而不受任何其他 MDS 变种影响的 CPU 上启用。其他 MDS 变种无法防范跨超线程攻击,因为填充缓冲区和加载端口是共享的。因此,在受其他变种影响的 CPU 上,空闲清除将只是一种装门面的做法(window dressing exercise),因此不会被激活。

调用由静态键 cpu_buf_idle_clear 控制,该键根据所选的缓解模式和系统的 SMT 状态进行切换。

缓冲区清除仅在进入 C 状态之前调用,以防止来自空闲 CPU 的陈旧数据在存储缓冲区重新分区后溢出到超线程同级,从而使所有条目都可供非空闲同级使用。

当从空闲状态退出时,存储缓冲区会再次被分区,以便每个同级线程可以使用它的一半。从空闲状态恢复的 CPU 随后可能会被推测性地暴露给同级线程的内容。缓冲区会在退出用户空间或执行 VMENTER 时被刷新,因此用户空间或虚拟机中的恶意代码无法推测性地访问它们。

该缓解措施挂钩到了 halt()/mwait() 的所有变种,但未涵盖传统的 ACPI IO 端口机制,因为 ACPI 空闲驱动程序在大约 2010 年已被 intel_idle 驱动程序取代,并在所有受影响的(预期在微代码中获得 MD_CLEAR 功能的)CPU 上被首选。除此之外,IO 端口机制是一个传统接口,仅用于不受影响或不再接收微代码更新的较旧系统。