关于RIDL漏洞和负载的“重放”

时间:2019-05-17 13:19:07

标签: x86 cpu cpu-architecture micro-architecture cpu-mds

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

这是一类漏洞,能够从各种微体系结构缓冲区读取过时的数据。
今天,已知的漏洞得以利用:LFB,加载端口,eMC和存储缓冲区。

链接的论文主要针对LFB。

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

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

关于RIDL的论文以该代码为例(很遗憾,由于PDF布局不允许我复制它,因此我不得不将其粘贴为图像):

RIDL snippet

它可以工作的唯一原因是,CPU将首先用旧数据满足第6行的负载,然后重播它。
似乎已确认以下几行:

  

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

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

即使重播机制存在,重播机制是如何工作的又如何与RIDL漏洞相互作用?

2 个答案:

答案 0 :(得分:3)

我不认为RIDL攻击涉及RS的负载重播。因此,除了解释负载重播是什么(@Peter的回答是一个很好的起点),我将基于对RIDL文件(英特尔的analysis)中提供的信息的理解,讨论我认为正在发生的事情。这些漏洞以及相关专利。

行填充缓冲区是L1D高速缓存中的硬件结构,用于保存在高速缓存中丢失的内存请求和I / O请求,直到它们得到服务为止。当所需的缓存行填充到L1D数据阵列中时,可缓存的请求得到服务。当出现用于退出写合并缓冲区的任何条件时(如手册中所述),将执行写合并写操作。将UC或I / O请求发送到L2高速缓存时,该请求会得到服务(尽快发生)。

请参阅RIDL paper的图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,后者为请求分配LFB(由于未命中),并且物理地址与描述加载请求的其他信息一起被写入LFB。此时,来自受害者的请求正在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看到其中一个uops具有有效的故障/辅助代码时,它将禁用与该uops的大小和地址有关的所有检查(可能还有ROB中所有以后的uops)。这些检查不再重要。据推测,禁用这些检查可以节省动态能耗。退出逻辑知道,当负载即将退出时,无论如何都会出现故障。同时,当MOB获悉翻译已完成时,它将照常重放攻击者的负载。但是,这一次,某些无效的物理地址被提供给L1D高速缓存。通常,需要将物理地址与来自同一逻辑核心的LFB中所有未决请求进行比较,以确保逻辑核心看到最新的值。这是在查找L1D缓存之前或同时完成的。物理地址并不重要,因为禁用了比较逻辑。但是,所有比较的结果都表现为结果表明成功。如果至少有一个分配的LFB,则物理地址将与某个分配的LFB匹配。由于受害人有未完成的请求,并且受害人的机密可能已经与先前的请求写入了相同的LFB,因此缓存行的相同部分从技术上讲包含过时数据,在这种情况下,过时数据是机密),将转发给攻击者。请注意,攻击者可以控制高速缓存行中的偏移量和要获取的字节数,但无法控制哪个LFB。高速缓存行的大小为64字节,因此攻击者负载的虚拟地址中只有6个最低有效位与负载的大小有关。然后,攻击者使用数据将其索引到其数组中,以使用高速缓存侧通道攻击来揭示秘密。这种行为也可以解释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的信息,请参阅:What are the new MDS attacks, and how can they be mitigated?。我的回答仅讨论基于英特尔微码更新的缓解措施(不是非常有趣的“软件序列”)。


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

enter image description here

答案 1 :(得分:2)

重播=从RS(调度程序)再次调度。 (这不是对整个问题的完整答案,仅是有关重放的部分。尽管我认为这涵盖了大部分内容,包括解除阻塞相关联的uops。)

事实证明,高速缓存未命中加载不是只是坐在加载缓冲区中,并在数据到达时唤醒相关的微指令。调度程序必须重新分派加载uop,以实际读取数据并将其写回物理寄存器。 (并将其放在转发网络上,相关的uops可以在下一个周期中读取它。)

因此L1丢失/ L2命中将导致分配的负载uops的数量是原来的2倍。 (调度程序是乐观的,并且L2在内核上,因此L2命中的预期等待时间是固定的,这与脱核响应的时间不同。如果调度程序继续对在特定时间从L3到达的数据保持乐观,则IDK。 )


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


在实践中,对于高速缓存行拆分加载,我们可以最轻松地观察到重播,因为与高速缓存未命中相比,重复导致的重击更为琐碎,所需的代码更少。对于仅分割负载的循环,uops_dispatched_port.port_2port_3的计数大约是该计数的两倍。 (我已经在Skylake上使用与How can I accurately benchmark unaligned access speed on x86_64基本上相同的循环和测试过程在实践中对此进行了验证)

检测到拆分的负载(仅在地址计算之后才有可能)不会向RS发信号通知成功完成,而是将对数据的第一部分进行加载,并将此结果放入拆分缓冲区中。 1 与uop派发的第二次来自第二条缓存行的数据结合在一起。 (假设这两个时间都不是高速缓存未命中的时间,否则也将需要重播。)


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

如果未发生 (因为加载数据尚未准备好),则也必须重播相关的uops。同样,IIRC可以通过dispatch到端口的性能计数器来观察。


现有的问答,有英特尔CPU上uop重播的证据:


脚注1:

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


RIDL:

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

http://blog.stuffedcow.net/2018/05/meltdown-microarchitecture/甚至表明PPro中的崩溃负载暴露了微体系结构状态的各个方面,就像最新处理器中仍然存在的此漏洞一样。

  

Pentium Pro从字面上讲是“负载值不在乎”。对于所有禁止的负载,负载单元完成并产生一个值,该值似乎是从处理器各个部分获取的各种值。该值会有所不同,并且可能不确定。返回的值似乎都不是内存数据,因此Pentium Pro似乎不容易崩溃。

     

可识别的值包括负载的PTE(至少在最近几年本身被认为是特权信息),最近存储的第12个存储值(存储队列中有12个条目),很少有段某个地方的描述符。

(从Core 2开始,较新的CPU公开了L1d缓存中的值;这是Meltdown漏洞本身。但是PPro / PII / PIII并不容易受到Meltdown的攻击。而是RIDL攻击。)

因此,这与英特尔的设计哲学相同,是将微体系结构状态的一部分暴露给推测性执行。

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

因此可能是对将来的CPU的硬件进行了简单且便宜的修复,但很难通过现有CPU的微代码和软件来缓解。