23. 微架构数据采样 (MDS) 缓解

23.1. 概述

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

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

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

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

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

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

MFBDS 泄漏填充缓冲区条目。 填充缓冲区在内部用于管理 L1 未命中情况,并保存响应内存或 I/O 操作而返回或发送的数据。 填充缓冲区可以将数据转发到加载操作,也可以将数据写入缓存。 当填充缓冲区被释放时,它可以保留先前操作的陈旧数据,然后这些数据可以转发到故障或辅助加载操作,这在某些条件下可以被利用。 填充缓冲区在超线程之间共享,因此可能发生跨线程泄漏。

MLPDS 泄漏加载端口数据。 加载端口用于执行来自内存或 I/O 的加载操作。 然后,接收到的数据被转发到寄存器文件或后续操作。 在某些实现中,加载端口可能包含来自先前操作的陈旧数据,这些数据可以在某些条件下转发到故障或辅助加载,这又可以最终被利用。 加载端口在超线程之间共享,因此可能发生跨线程泄漏。

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

23.2. 暴露假设

假设攻击代码驻留在用户空间或 guest 中,但有一个例外。 此假设背后的理由是,利用 MDS 所需的代码结构需要:

  • 控制加载以触发故障或辅助

  • 拥有一个披露小工具,通过侧信道暴露推测性访问的数据以供使用。

  • 控制披露小工具暴露数据的指针

不能 100% 排除内核中存在这种结构的可能性,但涉及的复杂性使其极不可能。

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

23.3. 缓解策略

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

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

对于虚拟化,有两种方法可以实现 CPU 缓冲区清除。 可以使用修改后的 VERW 指令,也可以通过 L1D Flush 命令。 后者在启用 L1TF 缓解时发出,因此可以避免额外的 VERW。 如果 CPU 不受 L1TF 影响,则需要发出 VERW。

如果在没有微码更新的 CPU 上执行带有提供的段选择器参数的 VERW 指令,除了浪费少量 CPU 周期之外,不会产生任何副作用。

这不能防止跨超线程攻击,除了 MSBDS 仅在其中一个超线程进入 C 状态时才可跨超线程利用。

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

mds_clear_cpu_buffers()

此外,宏 CLEAR_CPU_BUFFERS 可用于 exit-to-user 路径中的 ASM 末尾。 除了 CFLAGS.ZF 之外,此宏不会破坏任何寄存器。

缓解措施在内核/用户空间、虚拟机管理程序/guest 和 C 状态(空闲)转换时调用。

作为解决虚拟化场景的特殊怪癖,如果主机更新了微码,但虚拟机管理程序(尚未)向 guest 公开 MD_CLEAR CPUID 位,则内核会发出 VERW 指令,希望它可以实际清除缓冲区。 状态会相应地反映出来。

根据目前的了解,内核本身不需要额外的缓解措施,因为暴露泄漏数据的必要小工具无法以允许从恶意用户空间或 VM guest 进行利用的方式进行控制。

23.4. 内核内部缓解模式

off

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

full

缓解已启用。 CPU 受影响,并且在 CPUID 中声明了 MD_CLEAR。

vmwerv

缓解已启用。 CPU 受影响,并且未在 CPUID 中声明 MD_CLEAR。 这主要用于虚拟化场景,其中主机更新了微码,但虚拟机管理程序未在 CPUID 中暴露 MD_CLEAR。 这是一种尽力而为的方法,没有保证。

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

23.5. 缓解点

23.5.1. 1. 返回用户空间

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

在用户寄存器恢复后,缓解措施会在转换到用户空间之前调用。 这样做是为了最小化在 VERW 之后可以访问内核数据的窗口,例如通过 VERW 后的 NMI。

未处理的极端情况 返回内核的中断不会清除 CPU 缓冲区,因为 exit-to-user 路径无论如何都会这样做。 但是,可能会出现这样一种情况:在 exit-to-user 路径清除缓冲区后,在内核中生成了 NMI。 这种情况未得到处理,返回内核的 NMI 不会清除 CPU 缓冲区,因为

  1. 在 VERW 之后但在返回用户空间之前获得 NMI 的情况很少见。

  2. 对于非特权用户,没有已知的方法可以使 NMI 不那么罕见或针对它。

  3. 需要大量这些精确计时的 NMI 才能发起实际攻击。 据推测没有足够的带宽。

  4. 所讨论的 NMI 发生在 VERW 之后,即当用户状态恢复并且大多数有趣的数据已经被清除时。 剩下的只是 NMI 触摸的数据,这些数据可能有用也可能没有用。

23.5.2. 2. C 状态转换

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

当 SMT 不活动时,即 CPU 不支持 SMT 或所有兄弟线程都处于离线状态时,不需要清除 CPU 缓冲区。

空闲清除在仅受 MSBDS 影响而不受任何其他 MDS 变种影响的 CPU 上启用。 其他 MDS 变种无法防止跨超线程攻击,因为填充缓冲区和加载端口是共享的。 因此,在受其他变种影响的 CPU 上,空闲清除将是一种表面文章,因此不会激活。

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

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

当从空闲状态退出时,存储缓冲区会再次分区,因此每个兄弟线程都有一半可用。 然后,从空闲状态返回的 CPU 可能会推测性地暴露给兄弟线程的内容。 缓冲区会在退出到用户空间或 VMENTER 时刷新,因此用户空间或 guest 中的恶意代码无法推测性地访问它们。

缓解措施已连接到所有变体的 halt()/mwait(),但不包括传统的 ACPI IO 端口机制,因为 ACPI 空闲驱动程序已在 2010 年左右被 intel_idle 驱动程序取代,并且在所有受影响的 CPU 上都首选 intel_idle 驱动程序,预计这些 CPU 将在微码中获得 MD_CLEAR 功能。 除此之外,IO 端口机制是一个传统接口,仅用于较旧的系统,这些系统要么不受影响,要么不再接收微码更新。