为什么 AMD 处理器不易受到 Meltdown 和 Spectre 的影响?

信息安全 x86 英特尔
2021-08-10 01:26:48

我已经阅读了有关 Meltdown 和 Spectre 的文章,但对我而言,为什么 AMD 不会那么容易受到攻击并不明显。AMD 处理器根本就没有推测执行吗?或者他们是否有某种方法不爆炸相同的侧通道?

更新:我问是因为 AMD 的新闻稿声称他们的产品不那么容易受到攻击。

1个回答

只有 Meltdown 是专门的英特尔漏洞/设计缺陷。

更新:似乎AMD 对 Spectre 有很大的弹性目前尚不清楚为什么会这样。根据 AMD

(从 1 月初开始,现已更换,请参阅下面的更新 2)

AMD 架构的差异意味着利用此变体的风险几乎为零。迄今为止,尚未在 AMD 处理器上证明存在变体 2(分支目标注入)的漏洞。

请注意,它只是“接近零”,与 Meltdown 的“零”不同。(并且“边界检查绕过”Spectre v1 攻击(数组访问之前的条件直接分支)仍然需要在软件中修复。)

更新 2:AMD 可能低估了 Spectre,因为他们仍在发布微码更新,他们的网站现在说:

GPZ Variant 2(分支目标注入或 Spectre)适用于 AMD 处理器。

  • 虽然我们认为 AMD 的处理器架构难以利用变体 2,但我们将继续与业界密切合作以应对这一威胁。

目前尚不清楚是什么微架构设计特性/差异使 AMD CPU 比英特尔更具弹性;它们仍然有一个很大的无序窗口,并且 AFAIK 在一次可以飞行的内容方面并没有受到明显的限制。但是他们的分支预测器的设计不同,可能更难训练错误或以某种方式跨越特权边界?但后者也会使它们对 Spectre v1 免疫,并且 AMD CPU 同样容易受到 Spectre 的边界检查绕过版本的影响。


Spectre (v2) 通过分支预测 + 间接分支的推测执行影响任何“正常”CPU 设计。在 Spectre 中,攻击者启动/训练分支预测器以错误预测内核代码或另一个进程中的间接分支的目标。在发现错误预测之前,特权进程将推测性地执行一些指令,这些指令以依赖于秘密数据的方式改变机器的架构状态。

(然后攻击者使用缓存定时攻击来读取该微架构状态,与在 Meltdown 中相同。在攻击内核时,您最好选择一个“小工具”,它使用秘密数据来索引您的攻击用户空间的数组代码也有读取权限。如果你不能,你可能不得不使用缓存驱逐定时攻击来寻找哪一组缓存有一条线被驱逐,而不是寻找一条变得热的线。)

在硬件中击败 Spectre 可能需要像在每次跨越信任边界的转换时刷新分支预测器缓存一样昂贵。或者至少是间接分支目标预测缓冲区。(内核/用户是硬件知道的一个,但进入沙盒 JIT 编译代码(尤其是 Javascript)是一个问题。)


Meltdown 的关键在于 Intel CPU(但显然不是 ARM 或 AMD)不会压制低权限的 TLB 命中。负载执行,遵循指令也是如此,并且只有在故障负载尝试退出时才会真正发生故障这允许非特权代码根据其无权读取的数据直接导致微架构状态本身发生变化,而不是(Spectre)欺骗特权进程执行此操作。

(请注意,如果该行在 L1d 缓存中已经很热,您只会获得实际数据;似乎在 CPU 上,即使在同一行上重复 Meltdown 攻击也不会导致 CPU 将行放入缓存中,如果它已经不存在的话.)

;; run this in user-space
;; (and suppress or catch the fault somehow so you can do it quickly/repeatedly)

;; clflush all the cache lines in local_array (which you have permission to read)

;; create a long delay before following instructions can retire, but with few uops so OoO exec can see past it
times 30  sqrtpd  xmm0, xmm0     ; high latency per uop

movzx  eax, byte [kernel_byte]    ; eventually faults, but OoO exec continues first
shl    eax, 12
mov    eax, [local_array + rax]   ; the cache-line this touches depends on the secret data
;; after the movzx faults, the cache-line touched by the mov will still be hot

; then check which cache line of local_array is already hot.

(顺便说一句,我通过电子邮件向Meltdown 论文的作者发送了有关此事的电子邮件。他们急于在公开披露日期之前将其发布,显然出乎意料地提前了 1 周。他们计划澄清他们关于微架构细节的部分明确说明这是导致 Meltdown 的真正设计缺陷。)

(崩溃取决于内核将其页面映射到进程的虚拟地址空间,但在页表条目中设置了一个位,将它们标记为仅内核映射。这使得系统调用和中断更便宜,因为它们没有不必修改页表以允许访问内核内存:当 CPU 在 ring 0 中运行时,CPU 刚刚开始允许那些仅限内核的映射工作。有关 x86-64 页表条目格式的图表,请参阅此 Q&A。 U/S 位(用户/主管)是控制映射是否仅限内核的位。)

最终引发的故障要么被处理(捕获 SIGSEGV),要么被抑制(在 TSX(事务内存)事务中运行瞬态序列,或者由于错误推测)。因此,在 Meltdown 中,分支错误预测仅与通过抑制内核虚拟地址的负载故障来使攻击高效和可靠有关。但是,即使对于依赖指令,在错误加载之后的推测执行也是关键(不仅仅是在加载地址准备好之前执行的独立指令或任何简单的指令)。


据推测,AMD 的负载执行单元/TLB 设计不同,对较早或不同应用的负载进行特权检查来自具有仅内核映射的虚拟地址的加载被视为与来自未映射页面的加载相同,或者用于推测执行的物理页面位被设置为全零或全一等。或者它只是在不触发页面浏览的情况下压缩负载(就像来自未映射页面的负载一样)。

请注意,当将页表条目从无效更改为有效时,x86 不需要 TLB 无效,因此不允许地址转换硬件进行“负”缓存。或者如果是这样,它必须与使先前无效条目有效的页表写入保持一致。 英特尔 CPU 会执行一种 TLB 击落以保持仅推测加载的 TLB 条目的一致性,这超出了 x86 手册所要求的范围,以避免破坏在当前 TLB 无效规则发布之前存在的旧代码(例如 Win95 到 ME)。


关键是,不同的微架构设计选择可能会完全阻止 Meltdown 依赖的东西而且这样的设计选择本身是合理的,而不是专门为了避免 Meltdown。


一个相关的问题:为什么英特尔的延迟权限检查设计选择有意义?

在 Meltdown / Spectre 之前,CPU 设计人员只担心确保将内存保护应用于架构状态(架构寄存器中的非推测值,而不是乱序执行使用的物理寄存器)。即这个旁道不在任何人的雷达上。指令执行的结果在退休之前不会成为架构状态,因此这就是一切都必须正确的时候(在崩溃前的思考中)。

作为 CPU 设计人员,您希望一切都尽可能高效地执行,尽可能少的特殊情况。或者特别是在尽可能少的组件中的特殊情况下。如果执行单元永远不会停止,那么整体设计会更简单,这样逻辑的流水线部分就不需要任何流控制,只需要每个时钟始终接受一个新输入。

(更新:停止的替代方法是报告故障。英特尔 CPU 中的加载 uops 已经可以将故障报告回 OoO 调度程序并需要重播。这发生在 L1d 缓存未命中和缓存检测(在地址计算期间) -line split(从其他缓存行获取数据),或者如果加载端口尝试使用简单寻址模式的 4c 延迟特殊情况,但实际地址与基址寄存器位于不同的页面中。甚至重放其他uops依赖预期负载在某个周期内产生其结果而调度的负载。缓存未命中也会导致这种情况。因此,理论上,特权不足的负载可以使用这种机制来有效地不产生值。)

看起来英特尔当前的负载执行单元没有办法压缩 TLB 命中时的负载。TLB 本身是一个CAM(内容可寻址存储器)使用推测性(预取)页面遍历管理 TLB 条目涉及一些更活跃的硬件,但 TLB 本身必须快速以支持每个时钟 3 次查找(来自端口 2、3 和 7)。

大多数代码不会通过尝试从内核地址加载而出现页面错误,因此没有考虑针对这种情况进行优化。如果通过乱序执行看到这样的加载,它们通常是由于错误推测(在寄存器中使用错误数据运行加载指令)而发生的。(不一定是恶意诱导的错误推测(Spectre),只是来自不完美分支预测的常规类型。) 如果大多数时候它永远不会达到退休,那么在退休之前不对 TLB 命中的故障负载做任何事情是一个好的设计决策,因为在有序退出中较早地检测到分支错误预测或其他错误推测. 浪费负载带宽并导致缓存污染是值得怀疑的,但是(除了 Meltdown 攻击)这可能是非常罕见的情况,因此保持硬件简单是最有价值的事情。

通常页面错误的发生是因为访问了一个根本没有映射的地址:页表条目被标记为无效,而不仅仅是内核。甚至更高级别的 4 级嵌套页表也是无效的,例如页目录条目。如前所述,负缓存在架构上是不允许的(AFAIK 也没有通过窥探来确保正确性),因此此类 PTE 永远不会出现在 TLB 中。只有在页面遍历(发现该页面的映射不存在)之后才会引发未映射页面的页面错误。x86 具有专用的页面遍历硬件,因此页面表加载可以在后台发生,而执行单元运行其他微指令。(Skylake 甚至有两个 HW page-walk 单元)。但无论如何,

因此,尝试读取仅内核映射的页面错误是一种特殊情况,在微体系结构上与未映射页面的情况非常不同。(这实际上类似于尝试存储到只读映射中,这可能也延迟了故障。存储不会立即成为全局可见的;存储缓冲区通过将它们保持私有直到退休后使推测存储执行成为可能,此时点他们可以承诺 L1D)。


英特尔如何在未来的硬件中修复 Meltdown?

修复 Meltdown 相对容易(与 Spectre 相比),尽管它可能无法通过微码更新来完成。除了在 uop 上设置 fault-if/when-this-reaches-retirement 位外,TLB 查找还可以通过特权检查将页地址位(全为 1)选通。例如,来自任何内核页面的用户空间加载可以在微架构上作为来自最顶层物理页面的加载执行。(并且 RAM 少于最大数量的系统在该物理地址不会有任何物理 RAM。)

或者,失败的权限检查可能仍然允许加载在微架构上发生,但在加载端口中将结果屏蔽为全零。(请记住,Meltdown 问题并不是非特权加载可以将内核数据带入缓存,而是秘密数据加载结果可用于使用数据相关地址进行另一次加载。继续推测执行任何结果为零在 TLB 中命中的特权不足的负载不允许任何依赖于数据的微架构效果)。

TLB 查找与索引 VIPT L1D 缓存并行发生,但 L1D 缓存加载的标记检查部分需要 TLB 结果。因此,已经需要 TLB 查找结果来从地址位 6 到 11 索引的集合中选择正确的方式。(L1D 是 8 路集合关联的)。因此,要求 TLB 权限检查提前准备好也不应该引入任何额外的延迟。使用许可结果进行屏蔽会引入一个额外的门延迟,但一个时钟周期有很多门延迟的时间。(例如,64 位add延迟只有 1 个周期,64 位imul延迟在 Intel Sandybridge 系列上只有 3 个周期。http://agner.org/optimize/


您甚至可以将其设计为导致出错的加载(如果它达到退休)根本不会完成执行,并且不会将加载结果转发给相关指令。如果它不在 L1D 缓存中,甚至可能会压缩它,这样它就不会将内存请求沿层次结构向下发送到外部缓存。(即甚至不检查 L1D)。这可能是一个更棘手的设计。

(但是根据 Henry Wong 的测试,一些 CPU 确实以这种方式工作:例如 AMD CPU 根本不会产生 Meltdown 测试的结果,而一些不易受攻击的 CPU,如 Via Nano 产生零或 Pentium Pro 产生一些随机内部值.)

这种设计可能使用户空间进程甚至无法(?)检测内核地址是否被任何类型的定时攻击映射,因为加载到仅内核映射的微架构效应将是与未映射的页面相同。(但实际上,如果它还触发了页面遍历,则实际上是相同的。特权不足的 TLB 命中不会触发页面遍历,您可能会直接检测到这一点。)

如果内核使用任何小于 1G 的大页面来映射它自己的任何内存,这对于停止击败 KASLR 的进程可能很有价值。


关于 CPU 内部结构的进一步阅读:


熔断特定细节