4

我正在尝试了解RIDL类漏洞。

这是一类能够从各种微架构缓冲区读取陈旧数据的漏洞。
今天已知的漏洞利用:LFB、加载端口、eMC 和存储缓冲区。

链接的论文主要集中在 LFB。

我不明白为什么 CPU 会用 LFB 中的陈旧数据来满足负载。
我可以想象,如果负载在 L1d 中命中,它会在内部“重放”,直到 L1d 将数据带入 LFB 信号通知 OoO 核心停止“重放”它(因为读取的数据现在有效)。

但是我不确定“重播”实际上是什么意思。
我认为负载被分派到具有负载能力的端口,然后记录在负载缓冲区(在 MOB 中),并最终根据需要保持,直到它们的数据可用(由 L1 发出信号)。
所以我不确定“重播”是如何发挥作用的,此外,为了让 RIDL 工作,每次“播放”负载的尝试也应该解除阻塞相关指令。
这对我来说似乎很奇怪,因为 CPU 需要跟踪加载正确完成后要重播的指令。

关于 RIDL 的论文使用此代码作为示例(不幸的是,我不得不将其粘贴为图像,因为 PDF 布局不允许我复制它):

RIDL 片段

它可以工作的唯一原因是,如果 CPU 将首先用过时的数据满足第 6 行的负载,然后重放它。
这似乎证实了以下几行:

具体来说,我们可能期望两个访问速度很快,而不仅仅是与泄露信息对应的一个。毕竟,当处理器发现错误并在第 6 行以正确的值重新启动时,程序也会使用该索引访问缓冲区。

但我希望 CPU 在转发 LFB(或任何其他内部缓冲区)中的数据之前检查负载的地址。
除非 CPU 实际重复执行加载,直到它检测到加载的数据现在有效(即重放)。
但是,为什么每次尝试都会解除对相关指令的阻塞?

重放机制究竟是如何工作的(如果它存在的话),以及它如何与 RIDL 漏洞交互?

4

2 回答 2

3

重播 = 再次从 RS(调度程序)调度。(这不是你整个问题的完整答案,只是关于回放是什么的部分。虽然我认为这涵盖了大部分内容,包括解除阻塞依赖的微指令。)

这个答案的一部分对负载重播有误解。

请参阅聊天中的讨论- 重播依赖于拆分或缓存未命中负载的微指令,但不会重播负载本身。(除非负载在循环中依赖于自身,就像我一直在做的测试 >.<)。TODO:修复此答案的其余部分和其他内容。


事实证明,缓存未命中负载不仅位于负载缓冲区中,并且在数据到达时唤醒相关的微指令。调度程序必须重新分派加载 uop 以实际读取数据并写回物理寄存器。(并将其放在转发网络上,依赖的微指令可以在下一个循环中读取它。)

因此,L1 未命中/L2 命中将导致分派 2 倍的负载微指令。(调度器是乐观的,并且 L2 在核心上,因此 L2 命中的预期延迟是固定的,这与非核心响应的时间不同。IDK 如果调度器继续对从 L3 到达某个时间的数据持乐观态度。 )


RIDL 论文提供了一些有趣的证据,表明加载微指令确实直接与 LFB 交互,而不是等待传入的数据被放入 L1d 并从那里读取。


对于缓存行拆分加载,我们可以在实践中最容易地观察重播,因为重复导致这种情况比缓存未命中更简单,占用的代码更少。对于仅拆分负载的循环,计数uops_dispatched_port.port_2port_3将是大约两倍。(我已经在 Skylake 的实践中验证了这一点,使用与如何在 x86_64 上准确地对未对齐访问速度进行基准测试相同的循环和测试程序)

检测到拆分的加载(仅在地址计算之后才可能)将加载数据的第一部分,而不是向 RS 发送成功完成的信号,将此结果放入拆分缓冲区1以与数据连接uop 第二次从第二个缓存行开始调度。(假设这两个时间都不是缓存未命中,否则它也需要重放。)


当负载微指令分派时,调度程序预计它将在 L1d 中命中并分派相关微指令,以便它们可以在负载将它们放在该总线上的周期中从转发网络读取结果。

如果这没有发生(因为加载数据还没有准备好),那么依赖的微指令也必须被重放。dispatch同样,IIRC 这可以通过端口的 perf 计数器观察到。


现有问答与英特尔 CPU 上的 uop 重放证据:


脚注1:

我们知道拆分缓冲区的数量是有限的;有一个ld_blocks.no_sr负载计数器,因为缺少一个而停止。我推断它们在装载端口中,因为这是有道理的。重新分派相同的加载 uop 会将其发送到相同的加载端口,因为 uop 在发布/重命名时分配给端口。虽然也许有一个共享的拆分缓冲区池。


RIDL:

乐观调度是产生问题的机制的一部分。更明显的问题是让后来的微指令的执行看到来自 LFB 的“垃圾”内部值,就像在 Meltdown 中一样。

http://blog.stuffedcow.net/2018/05/meltdown-microarchitecture/甚至显示 PPro 中的崩溃负载暴露了各种微架构状态,就像最新处理器中仍然存在的这个漏洞一样。

Pentium Pro 从字面上理解“负载值是无关紧要的”。对于所有禁止的加载,加载单元完成并产生一个值,该值似乎是从处理器的各个部分获取的各种值。该值会有所不同,并且可能是不确定的。返回的值似乎都不是内存数据,因此 Pentium Pro 似乎不易受到 Meltdown 的影响。

可识别的值包括负载的 PTE(至少在最近几年,它本身被认为是特权信息)、第 12 个最近存储的值(存储队列有 12 个条目),以及很少来自某处的段描述符.

(后来的 CPU,从 Core 2 开始,暴露了 L1d 缓存中的值;这就是 Meltdown 漏洞本身。但是 PPro / PII / PIII 不容易受到 Meltdown 的影响。在这种情况下,它显然容易受到 RIDL 攻击。)

因此,将一些微架构状态暴露给推测执行的英特尔设计理念是相同的。

在硬件中将其压缩为 0 应该很容易解决;加载端口已经知道它不成功,因此根据成功/失败屏蔽加载数据应该希望只增加几个额外的门延迟,并且在不限制时钟速度的情况下是可能的。(除非加载端口中的最后一个流水线阶段已经是 CPU 频率的关键路径。)

因此,对于未来的 CPU,这可能是一个简单而廉价的硬件修复,但很难通过现有 CPU 的微码和软件来缓解。

于 2019-05-17T14:36:22.303 回答
3

我不认为来自 RS 的负载回放与 RIDL 攻击有关。因此,我将根据我对 RIDL 论文中提供的信息的理解、英特尔对这些漏洞的分析以及相关专利。

行填充缓冲区是 L1D 缓存中的硬件结构,用于保存缓存中未命中的内存请求和 I/O 请求,直到它们得到服务。当所需的高速缓存行填充到 L1D 数据阵列中时,会为可高速缓存的请求提供服务。当驱逐写组合缓冲区的任何条件发生时(如手册中所述),将提供写组合写服务。UC 或 I/O 请求在被发送到 L2 缓存时得到服务(这会尽快发生)。

请参阅 RIDL论文的图 4 。用于产生这些结果的实验​​工作如下:

  • 受害线程将已知值写入单个内存位置。内存位置的内存类型为WB、WT、WC或UC。
  • 受害线程在循环中读取相同的内存位置。每个加载操作后面MFENCE都有一个可选的CLFLUSH. 从论文中我不清楚CLFLUSH其他两个指令的顺序,但这可能无关紧要。MFENCE序列化缓存行刷新操作以查看缓存中每次加载未命中时会发生什么。此外,MFENCE减少了 L1D 端口上两个逻辑核之间的争用,从而提高了攻击者的吞吐量。
  • 在同级逻辑核心上运行的攻击者线程循环执行清单 1 中所示的代码。第 6 行使用的地址可以是任何东西。唯一重要的是第 6 行的加载要么出错,要么导致需要微码辅助(设置页表条目中的访问位)的页面遍历。页面遍历也需要使用 LFB,并且大多数 LFB 在逻辑内核之间共享。

我不清楚图 4 中的 Y 轴代表什么。我的理解是,它表示每秒从隐蔽通道获取到缓存层次结构(第 10 行)的行数,其中数组中行的索引等于受害者写入的值。

如果内存位置是 WB 类型,当受害线程将已知值写入内存位置时,该行将被填充到 L1D 缓存中。如果内存位置是 WT 类型,当受害线程将已知值写入内存位置时,该行将不会被填充到 L1D 缓存中。但是,在第一次读取该行时,它将被填充。因此,在这两种情况下CLFLUSH,来自受害线程的大多数负载都会在缓存中命中。

当加载请求的缓存行到达 L1D 缓存时,它首先被写入为请求分配的 LFB。缓存行的请求部分可以直接从 LFB 提供给加载缓冲区,而无需等待行被填充到缓存中。根据 MFBDS 漏洞的描述,在某些情况下,可能会将来自先前请求的陈旧数据转发到加载缓冲区以满足加载 uop。在 WB 和 WT 情况下(没有刷新),受害者的数据最多被写入 2 个不同的 LFB。来自攻击者线程的页面遍历可以轻松覆盖 LFB 中受害者的数据,之后攻击者线程将永远无法在其中找到数据。所有在 L1D 缓存中命中的加载请求都不会通过 LFB;他们有一条单独的道路,它与来自 LFB 的路径复用。尽管如此,在某些情况下,来自 LFB 的陈旧数据(噪声)被推测性地转发到攻击者的逻辑核心,这可能来自页面遍历(也可能来自中断处理程序和硬件预取器)。

有趣的是,在 WB 和 WT 情况下,陈旧数据转发的频率远低于所有其他情况。这可以通过以下事实来解释:在这些情况下,受害者的吞吐量要高得多,并且实验可能会提前终止。

在所有其他情况下(WC、UC 和所有带刷新的类型),缓存中的每个加载都未命中,并且必须通过 LFB 从主内存将数据提取到加载缓冲区。发生以下事件序列:

  1. 来自受害者的访问在 TLB 中命中,因为它们访问的是相同的有效虚拟页面。物理地址从 TLB 获得并提供给 L1D,L1D 为请求分配一个 LFB(由于未命中),并将物理地址与描述加载请求的其他信息一起写入 L​​FB。此时,来自受害者的请求在 LFB 中等待处理。由于受害者MFENCE在每次加载后都会执行一次,因此在任何给定周期内,LFB 中最多有一个未完成的加载来自受害者。
  2. 攻击者在兄弟逻辑核心上运行,向 L1D 和 TLB 发出加载请求。每次加载都是到一个未映射的用户页面,因此会导致错误。当它在 TLB 中未命中时,MMU 告诉加载缓冲区应该阻止加载,直到地址转换完成。根据专利和其他英特尔专利的第 26 段,这就是 TLB 未命中的处理方式。地址转换仍在进行中,加载被阻塞。
  3. 来自受害者的加载请求接收它的缓存线,该缓存线被写入 LFB 中,并为加载而涂上所有涂层。负载请求的部分行被转发到 MOB,同时,该行被写入 L1D 缓存。之后,可以解除 LFB 的涂层,但不会清除任何字段(除了指示其空闲的字段)。特别是,数据仍在LFB中。受害者然后发送另一个加载请求,该请求在缓存中也未命中,要么是因为它不可缓存,要么是因为缓存行已被刷新。
  4. 攻击者加载的地址转换过程完成。MMU 确定需要引发故障,因为物理页面不存在。但是,直到负载即将退休(当它到达 ROB 的顶部时)才会引发故障。无效的翻译不会缓存在 Intel 处理器的 MMU 中。MMU 仍然必须告诉 MOB 翻译已经完成,并且在这种情况下,在 ROB 的相应条目中设置错误代码。似乎当 ROB 看到其中一个微指令具有有效的故障/辅助代码时,它会禁用与该微指令的大小和地址相关的所有检查(可能还有 ROB 中的所有后续微指令)。这些检查不再重要。据推测,禁用这些检查可以节省动态能源消耗。退休逻辑知道当负载即将退休时,无论如何都会提出错误。同时,当 MOB 被告知翻译完成时,它像往常一样重放攻击者的负载。然而,这一次,一些无效的物理地址被提供给 L1D 缓存。通常,物理地址需要与来自同一逻辑核心的 LFB 中的所有未决请求进行比较,以确保逻辑核心看到最新的值。这是在查找 L1D 缓存之前或同时完成的。物理地址并不重要,因为比较逻辑被禁用。但是,所有比较的结果都表现得好像结果表明成功一样。如果至少有一个已分配的 LFB,则物理地址将匹配某个已分配的 LFB。由于受害者有一个未完成的请求,并且由于受害者' s secret 可能已经从先前的请求中写入到同一个 LFB 中,缓存行的同一部分,在技术上包含陈旧数据,在这种情况下(陈旧数据是机密),将被转发给攻击者。请注意,攻击者可以控制缓存行内的偏移量和要获取的字节数,但无法控制哪个 LFB。高速缓存行的大小为 64 字节,因此只有攻击者加载的虚拟地址的 6 个最低有效位以及加载的大小很重要。然后,攻击者使用数据索引到其数组中,以使用缓存侧通道攻击来揭示秘密。这种行为也可以解释 MSBDS,显然数据大小和 STD uop 检查被禁用(即,检查很容易通过)。缓存行的同一部分,在技术上包含陈旧数据,在这种情况下(陈旧数据是秘密),将被转发给攻击者。请注意,攻击者可以控制缓存行内的偏移量和要获取的字节数,但无法控制哪个 LFB。高速缓存行的大小为 64 字节,因此只有攻击者加载的虚拟地址的 6 个最低有效位以及加载的大小很重要。然后,攻击者使用数据索引到其数组中,以使用缓存侧通道攻击来揭示秘密。这种行为也可以解释 MSBDS,显然数据大小和 STD uop 检查被禁用(即,检查很容易通过)。缓存行的同一部分,在技术上包含陈旧数据,在这种情况下(陈旧数据是秘密),将被转发给攻击者。请注意,攻击者可以控制缓存行内的偏移量和要获取的字节数,但无法控制哪个 LFB。高速缓存行的大小为 64 字节,因此只有攻击者加载的虚拟地址的 6 个最低有效位以及加载的大小很重要。然后,攻击者使用数据索引到其数组中,以使用缓存侧通道攻击来揭示秘密。这种行为也可以解释 MSBDS,显然数据大小和 STD uop 检查被禁用(即,检查很容易通过)。请注意,攻击者可以控制缓存行内的偏移量和要获取的字节数,但无法控制哪个 LFB。高速缓存行的大小为 64 字节,因此只有攻击者加载的虚拟地址的 6 个最低有效位以及加载的大小很重要。然后,攻击者使用数据索引到其数组中,以使用缓存侧通道攻击来揭示秘密。这种行为也可以解释 MSBDS,显然数据大小和 STD uop 检查被禁用(即,检查很容易通过)。请注意,攻击者可以控制缓存行内的偏移量和要获取的字节数,但无法控制哪个 LFB。高速缓存行的大小为 64 字节,因此只有攻击者加载的虚拟地址的 6 个最低有效位以及加载的大小很重要。然后,攻击者使用数据索引到其数组中,以使用缓存侧通道攻击来揭示秘密。这种行为也可以解释 MSBDS,显然数据大小和 STD uop 检查被禁用(即,检查很容易通过)。然后,攻击者使用数据索引到其数组中,以使用缓存侧通道攻击来揭示秘密。这种行为也可以解释 MSBDS,显然数据大小和 STD uop 检查被禁用(即,检查很容易通过)。然后,攻击者使用数据索引到其数组中,以使用缓存侧通道攻击来揭示秘密。这种行为也可以解释 MSBDS,显然数据大小和 STD uop 检查被禁用(即,检查很容易通过)。
  5. 随后,故障/辅助负载到达 ROB 的顶部。负载没有退出,管道被刷新。如果负载发生故障,则会引发故障。在辅助加载的情况下,从相同的加载指令重新开始执行,但辅助在分页结构中设置所需的标志。
  6. 重复这些步骤。但是攻击者可能并不总是能够从受害者那里泄露秘密。如您所见,来自攻击者的加载请求必须命中包含机密的已分配 LFB 条目。为页面遍历和硬件预取器分配的 LFB 可能会使成功执行攻击变得更加困难。

如果攻击者的负载没有故障/协助,LFB 将从 MMU 接收到有效的物理地址,并执行正确性所需的所有检查。这就是负载必须故障/协助的原因。

论文中的以下引用讨论了如何在同一线程中执行 RIDL 攻击:

我们通过在我们自己的线程中写入值并观察我们从同一个线程泄漏的值来执行没有 SMT 的 RIDL 攻击。图 3 显示,如果我们不写入值(“没有受害者”),我们只会泄漏零,但是当受害者和攻击者运行在同一个硬件线程中(例如,在沙箱中)时,我们几乎在所有情况下都会泄漏秘密值.

我认为这个实验中没有特权级别的变化。受害者和攻击者在同一硬件线程上的同一操作系统线程中运行。当从受害者返回给攻击者时,LFB 中可能仍有一些未完成的请求来自(尤其是来自商店)。请注意,在 RIDL 论文中,在所有实验中都启用了 KPTI(与 Fallout 论文相比)。

除了从 LFB 泄漏数据外,MLPDS 还显示数据也可能从加载端口缓冲区泄漏。这些包括线分割缓冲区和用于大小大于 8 字节的加载的缓冲区(我认为当加载 uop 的大小大于加载端口的大小时需要它,例如 SnB/IvB 上的 AVX 256b占用端口 2 个周期)。

图 5 中的 WB 情况(无冲洗)也很有趣。在这个实验中,受害线程将 4 个不同的值写入 4 个不同的缓存行,而不是从同一个缓存行读取。该图显示,在 WB 情况下,只有写入最后一个缓存行的数据会泄露给攻击者。解释可能取决于循环的不同迭代中缓存行是否不同,遗憾的是在论文中并不清楚。论文说:

对于没有刷新的WB,只有最后一个缓存行有一个信号,这表明CPU在将数据存储到缓存之前在LFB的单个条目中执行写组合。

在将数据存储到缓存中之前,如何在同一个 LFB 中组合对不同缓存行的写入?那是零意义。LFB 可以保存单个高速缓存行和单个物理地址。像这样组合写入是不可能的。可能发生的情况是 WB 写入正在写入为其 RFO 请求分配的 LFB。当无效的物理地址被传输到 LFB 进行比较时,数据可能总是从最后分配的 LFB 中提供。这可以解释为什么只有第四个存储写入的值被泄露。

有关 MDS 缓解措施的信息,请参阅:什么是新的 MDS 攻击,如何缓解它们?. 我在那里的回答只讨论了基于英特尔微码更新的缓解措施(不是非常有趣的“软件序列”)。


下图显示了使用数据推测的易受攻击的结构。

在此处输入图像描述

于 2019-05-18T04:56:24.030 回答