【问题标题】:What are the microarchitectural details behind MSBDS (Fallout)?MSBDS(辐射)背后的微架构细节是什么?
【发布时间】:2019-10-03 00:23:57
【问题描述】:

CVE-2018-12126 已分配给 MSBDS(微架构 StoreBuffer 数据采样),这是英特尔处理器的漏洞,属于新创建的 MDS (Microarchitectural Data Sampling) 类。

我正在尝试了解这些漏洞背后的微架构细节。我从 MSBDS 开始,也称为 Fallout (cfr Meltdown),它允许攻击者泄露存储缓冲区的内容。

出于某种原因,讨论微架构细节的网络安全论文往往不够精确。
幸运的是,MSBDS 论文引用了patent US 2008/0082765 A1(图片来自该地址)。

据我所知,在 MSBDS 的情况下,漏洞似乎在于内存消歧算法如何处理带有无效物理地址的负载。

这是用于检查存储缓冲区中的负载是否匹配的算法:

302 检查加载引用的页面偏移量是否与存储缓冲区中任何先前存储引用的页面偏移量匹配。
如果此检查失败,则加载与任何存储都不匹配,并且可以在 304 处执行(已调度)。
如果 302 进行检查,则加载的 虚拟地址 的上半部分将根据商店的虚拟地址检查1。
如果找到匹配项,则加载匹配,并且在 308 转发所需的数据或加载本身被阻止(直到匹配的存储提交)如果转发是不可能的(例如,将存储缩小到更宽加载)。
注意相同的虚拟地址可以映射到两个不同的物理地址(在不同的时间,但在存储转发窗口内)。不通过此算法防止不正确的转发,而是通过耗尽存储缓冲区(例如,使用正在序列化的mov cr3, X)2。
如果加载的虚拟地址与存储的任何虚拟地址都不匹配,则在 310 处检查物理地址。
这是处理不同虚拟地址映射到相同物理地址的情况所必需的。

第 [0026] 段补充说:

在一个实施例中,如果在操作302有命中 以及加载或存储操作的物理地址 无效,则操作 310 的物理地址检查可能 被认为是命中并且方法300可以继续 操作 308。在一种情况下,如果 加载指令无效,加载指令可能是 由于 DTLB 118 未命中而被阻止。此外,如果物理 存储操作的地址无效,结果可能 在一个实施例中基于细网命中/未命中结果 或者加载操作可能会在此存储操作上被阻止 直到解析存储操作的物理地址 在一个实施例中。

这意味着如果物理地址不可用,CPU 将只考虑地址的低 (12) 位3。
考虑到 TLB 未命中的情况在下面几行中得到解决,这仅留下访问页面不存在的情况。

这确实是研究人员提出攻击的方式:

char * victim_page = mmap (... , PAGE_SIZE , ...) ;
char * attacker_page = mmap (... , PAGE_SIZE, ...) ;

mprotect ( attacker_page , PAGE_SIZE , PROT_NONE ) ;

offset = 7;
victim_page [ offset ] = 42;

//Why people hate specpolines??
if ( tsx_begin () == 0) {
  //Read the stale value and exfiltrate it with a spectre gadget
  memory_access ( lut + 4096 * attacker_page [ offset ]) ;
  tsx_end () ;
}

//Reload phase of FLUSH+RELOAD
for ( i = 0; i < 256; i ++) {
  if ( flush_reload ( lut + i * 4096) ) {
     report ( i ) ;
  }
}

我不确定还有什么会导致无效的物理地址(访问特权页面返回正确的物理地址)。

真的是对无效物理地址的处理触发了MSBDS漏洞吗?


1SBA(存储缓冲区地址)组件同时保存存储的虚拟地址和物理地址,可能只是物理地址的一部分(其余部分位于专用数组中,可能命名为物理地址缓冲区)。
2我不清楚是否真的可以通过将页表条目更改为指向其他位置然后发出 invlpg 来触发错误转发。
3我对此的理由是,由于我们没有处于可恢复的情况下,负载有问题,跳过另一次检查冒着不正确转发的风险是值得的性能方面 因为它会使负载提前退出(和故障)。

【问题讨论】:

  • 关于第二个脚注,invlpg 是一个完全序列化的指令,因此不会发生不正确的转发,因为在不提交所有先前存储的情况下无法更改同一虚拟地址的映射。关于第 0026 段,最后一句话看起来很重要,因为它描述了 4K 混叠,作者似乎称之为 WTF。我没有读过这篇论文,但看起来 WTF 是对 4K 混叠的利用,这很有意义。我打算阅读这篇论文,然后可能会在此之后发布答案以确保确定。
  • @HadiBrais 谢谢,我实际上是想了解这是否只是 4K 混叠问题。在我读过的所有官方新闻或论文中,都强调了“故障负载”的重要性。正常的 4K 混叠只会导致延迟。
  • 是的,看起来这种推测仅在别名负载出现故障时才会发生。 RIDL 也是如此。但如果是 4K 混叠,那么作者在论文中的任何地方都没有提到 4K 混叠,这将是非常奇怪的,即使它是众所周知的。 (也许是故意混淆大家。)
  • @HadiBrais 我不确定,但我认为直到负载不断重播更年轻的依赖微指令才能调度。据我了解,正确检测到 4K 混叠只会延迟加载,而在出现错误加载的情况下,会跳过此检查并且加载完成执行(允许相关的 uops 调度和安装经典的隐蔽通道攻击)。

标签: security x86 cpu-architecture speculative-execution cpu-mds


【解决方案1】:

内存一致性要求加载 uop 获取最近存储到目标内存位置的值。因此,内存顺序缓冲区 (MOB) 必须确定加载是否与程序顺序中任何较早的存储 uop 重叠。加载缓冲区和存储缓冲区都是循环的,并且每个加载都标记有按程序顺序加载之前的最年轻存储的 ID(分配器知道它在必须分配负载时分配的最后一个存储的 ID )。这使 MOB 能够正确确定哪些存储先于哪些加载。

从 Intel Core 微架构和 Goldmont 微架构开始,调度程序包括一个推测性内存消歧 (SMD) 逻辑,该逻辑使用负载的 IP 来决定是否允许负载相对于所有早期商店的 STA uops。这类似于分支预测如何使用当前获取的 16 字节块的 IP 来预测控制流,除了在这种情况下 IP 用于内存消歧。如果 RS 中没有等待的 STA,或者如果所有 STA 都可以在与加载 uop 相同的周期内调度,则忽略 SMD 结果并调度负载。否则,如果 SMD 决定阻塞负载,则调度程序仅在所有较早的 STA 已被调度或将在与负载相同的周期内调度时才调度负载。对于某些负载微指令,SMD 始终会阻止 RS 中的负载。

当加载 uop 被分派到加载 AGU 端口之一时,加载的有效地址(即线性地址)使用指定的段基址、基址寄存器操作数、索引寄存器操作数、比例和位移来计算。同时,存储缓冲区中可以有存储。将加载的线性地址与执行了 STA 微指令的所有早期存储的线性地址进行比较(即,存储的线性地址可用)。可能还需要比较物理地址,但此时加载的物理地址仍然不可用(这种情况在专利中称为无效物理地址)。为了最小化加载的可观察延迟,MOB 仅使用加载和每个较早存储的线性地址的最低有效 12 位执行快速比较。有关此比较的更多信息,请参阅L1 memory bandwidth: 50% drop in efficiency using addresses which differ by 4096+64 bytes(但此处未讨论屏蔽的微指令)。这种逻辑称为松散网络,它构成了推测性记忆消歧机制的另一部分。自 Pentium Pro(包括有序 Bonnell)以来,所有 Intel 微架构都支持松散网络,但由于单个加载或存储 uop 可以操作的数据大小增加,并且由于引入了从 Pentium II 开始的屏蔽内存 uops。在松散网络操作的同时,加载的线性地址被发送到TLB以获得相应的物理地址并执行必要的页面属性检查以及段检查。

如果加载不与根据松散的净结果分派加载时地址已知的任何早期存储重叠,则将加载请求发送到 L1D。我们已经从 RIDL 漏洞中知道,即使没有来自 TLB 的有效物理地址,某些数据也可能被转发到 MOB,但前提是负载导致故障或辅助。在第一级 TLB 未命中时,加载在加载缓冲区中被阻止,因此它不会继续其 L1D 访问。稍后,当请求的页面条目到达第一级 TLB 时,MOB 被告知该虚拟页面的地址,该地址反过来检查该页面上阻塞的所有加载和存储,并通过重播 uops 来解除阻塞。 TLB 端口的可用性。

我认为松散网络只需要一个周期就可以将给定加载的地址与存储缓冲区中任意数量的存储进行比较,并确定比加载更老的最年轻的重叠存储(如果找到的话)。查找第一级TLB并在命中时向L1D提供物理地址的过程应该只需要一个周期。这就是如何获得 4 个周期的最佳加载使用延迟的方式(这还需要 (1) 正确推测物理页面地址,(2) 没有索引或零的 base+disp 寻址模式索引,以及 (3) 段基地址为零,否则至少有一个周期的惩罚)。有关更多信息,请参阅 cmets 中的讨论。

请注意,如果在松散网络中丢失了加载 uop,则可以得出结论,加载不与任何先前的存储重叠,但前提是所有早期 uop 的 STA 在调度加载 uop 时已经执行。两个最低有效 12 位不同的线性地址不可能重叠。

如果松散的净结果表明负载与较早的存储重叠,则 MOB 会并行执行两件事。其中之一是内存消歧过程继续使用精细网络(即全线性地址比较)。如果细网中的负载丢失,则在可用时比较物理地址。否则,如果负载击中细网,则负载和存储重叠。请注意,x86 ISA 要求在更改分页结构后使用完全序列化指令。所以在fine net hit的情况下不需要比较物理地址。除此之外,每当分派新的 STA uop 时,都会重复整个过程,但这次所有负载都在加载缓冲区中。将所有这些比较的结果结合起来,当已针对所有早期存储检查加载时,最终结果将确定如何正确执行加载 uop。

同时,MOB 推测与负载一起命中松散网络的存储具有应该转发给负载的值。如果加载和存储到同一个虚拟页面,那么推测是正确的。如果加载和存储到不同的虚拟页面,但虚拟页面映射到同一个物理页面,那么推测也是正确的。否则,如果加载和存储到不同的物理页面,则 MOB 搞砸了,导致出现称为 4K 混叠的情况。但是等等,让我们回滚一点。

可能无法将存储数据转发给负载。例如,如果加载没有完全包含在存储中,那么它必须等到存储被提交,然后才允许加载继续并从缓存中获取数据。此外,如果存储的 STD uop 尚未执行(例如,它依赖于长延迟的 uop)怎么办?通常,只有在满足存储转发的要求时,才会从存储缓冲区中转发数据。但是,MSBDS 漏洞表明情况并非总是如此。特别是,当负载导致故障或辅助时,存储缓冲区可以将数据转发给负载,而不进行任何存储转发检查。来自英特尔关于 MDS 的文章:

存储可能不会覆盖整个数据字段 由于存储空间较小,因此在存储缓冲区内 大于存储缓冲区宽度,或尚未执行数据 商店的一部分。这些情况可能导致数据被转发 包含来自旧商店的数据。

显然,即使 STD uop 尚未执行,数据也可能被转发。但是到时候数据从哪里来?好吧,存储缓冲区条目的数据字段在释放时不会被清除。数据字段的大小等于存储微指令的宽度,可以通过测量执行最宽的可用存储指令(例如,来自 XMM、YMM 或 ZMM 寄存器)所需的存储微指令数来确定。这似乎是 Haswell 上的 32 个字节和 Skyake-SP 上的 64 个字节。存储缓冲区条目的每个数据字段都那么大。由于它永远不会被清除,它可能会保存一些随机组合的数据,这些数据来自碰巧在该存储缓冲区条目中分配的存储。当负载命中松散网络并导致故障/辅助时,负载指定宽度的数据将从存储缓冲区转发到负载,甚至无需检查 STD 的执行或存储的宽度。这就是负载如何从一个或多个存储区获取数据的方式,这些存储区甚至可能在 10 亿条指令前就已提交。与 MLBDS 类似,转发的部分数据或全部数据可能是陈旧的(即不属于占用条目的存储)。

这些细节实际上仅由英特尔提供,而非 Fallout 论文。在这篇论文中,作者在禁用 KPTI 的系统上进行了一个实验(第 4 节)(我将解释原因),但他们没有利用 Meltdown 漏洞。以下是实验的工作原理:

  1. 攻击者执行一系列存储,所有这些都未命中缓存层次结构。存储的数量至少与存储缓冲区条目的数量一样大。
  2. 调用内核模块,该模块执行一系列存储,每个存储到不同内核页面中的不同偏移量。存储的值是已知的。存储的数量在 1-50 之间变化,如图 5 所示。之后,内核模块返回给攻击者。
  3. 攻击者对用户页面(不同于内核页面)执行一系列加载到相同的偏移量。每个用户页面仅分配在虚拟地址空间中,并已撤销访问权限(通过调用mprotect(...,PROT_NONE),将其标记为用户和不存在)。表 1 显示不存在的主管页面不起作用。加载次数与内核模块执行的存储次数相同。然后使用传统的 FLUSH+RELOAD 攻击泄露加载的值。

第一步尝试尽可能多地占用存储缓冲区,以延迟从内核模块提交存储。请记住,虚假存储转发仅适用于占用的存储缓冲区条目。第一步有效,因为商店必须按顺序提交。在第三步中,重要的是获得松散的净命中率。请注意,在这个实验中,作者并没有考虑泄漏任何陈旧数据,他们只是想从内核存储中获取希望仍在存储缓冲区中的数据。更改当前特权级别时,所有指令都会在执行新特权级别的任何指令之前退出。存储可以快速退出,甚至在 RFO 请求完成之前,但它们仍然必须在存储缓冲区中等待以按顺序提交。人们认为以这种方式在存储缓冲区中存储来自不同特权级别的存储不是问题。但是,当攻击者开始执行加载时,如果与当前正在分派的负载具有相同偏移量的存储仍在存储缓冲区中,则在推测转发(非陈旧)数据时会发生松散的净命中。其余的你都知道。

启用 KPTI 后,大多数内核页面与用户页面位于不同的虚拟地址空间中。因此,当从内核模块返回时,内核必须通过将值写入CR3 寄存器来切换地址空间。但这是一个序列化操作,这意味着它将停止管道,直到所有(内核)存储都被提交。这就是为什么作者需要禁用 KPTI 以使他们的实验工作(即,存储缓冲区将为空)。不幸的是,由于 Coffee Lake R 具有针对 Meltdown 的硬件缓解措施,Linux 内核默认禁用此处理器上的 KPTI。这就是为什么作者说硬件缓解使处理器更容易受到攻击。

英特尔文章(但不是论文)中描述的内容表明 MSBDS 比这更危险:故障/辅助负载也可能泄漏存储缓冲区中的陈旧数据。英特尔文章还表明 MSBDS 可以跨同级逻辑内核工作:当一个逻辑内核进入睡眠状态时,已为其静态分配的存储缓冲区条目可能可供其他逻辑内核使用。稍后,如果逻辑核心再次激活,存储缓冲区将被静态分区,这可能使该核心能够从其他核心写入的条目中泄漏陈旧数据。

所有这些都表明,启用 KPTI 不足以缓解 MSBDS。此外,第 6 节的论文中推荐的缓解措施(跨越安全边界时使用 MFENCE 刷新存储缓冲区)也不够。讨论了适当的 MDS 缓解措施here。

我不知道第 3.2 节中的作者如何从英特尔专利的以下引用中得出结论:

如果在操作 302 命中 [使用页面偏移的部分匹配] 并且加载或存储操作的物理地址不是 有效,在操作 310 [完整物理地址检查 地址匹配]可能被视为命中

以下内容:

也就是说,如果加载 μOP 的地址转换失败并且 12 个最少 加载地址的有效位与先前存储的有效位相匹配,则 处理器假定负载的物理地址和 存储匹配并将之前存储的值转发给加载μOP。

整个专利没有提到比较 12 位,也没有说负载必须出错才能发生错误的存储转发。此外,结论本身是不正确的,因为 12 个最低有效位不必完全匹配,负载也不必出错(但攻击只有在出错时才有效)。

MSBDS 与 Meltdown 的不同之处在于,攻击者会从位于单独虚拟地址空间中的内核页面泄漏数据。 MSBDS 与 SSB 的不同之处在于攻击者错误地训练了 SMD,以便它在调度负载之前的所有 STA 之前调度负载。这样,负载不会在松散网络中命中的可能性较小,这使得 MOB 将负载发布到 L1D 缓存并根据程序顺序获得一个可能不是最新值的值。可以通过将IA32_SPEC_CTRL[2] 设置为 1 来禁用 SMD。当禁用 SMD 时,调度程序会像在 Pentium Pro 中一样处理加载微指令。

值得注意的是,有些加载和存储微指令的工作方式与我上面描述的不同。示例包括来自MFENCE、SFENCE 和CLFLUSH 的内存微指令。但它们在这里不相关。

【讨论】:

  • L1d 不需要完整的物理地址,直到它加载了要检查的标签。 L1 缓存是 VIPT,因此 TLB 访问可以与从索引位选择的集合的所有方式中获取标签+数据并行发生。 (在英特尔设计中,它们都位于低 12 位中,即页面偏移的一部分,因此它们是免费翻译的。)我认为英特尔不太可能具有 1 周期 dTLB 访问权限。鉴于 4 周期快速路径的详细信息,关键路径可能是 TLB。 reg+offset 计算可以影响设置的索引位而不会错误推测,因此不会绕过它进行索引
  • 测量执行最广泛的可用存储指令所需的存储微指令数。这是否意味着 SnB/IvB 具有 256 位存储缓冲区条目,因为它们的 256b 加载/存储是单 uop,但它们占用加载数据或存储数据执行单元 2 个周期?或者即使对齐的 32 字节加载/存储,SnB/IvB 是否实际上需要 2 个条目? 256b 负载不会分派两次;存储地址 uop 可以在 256b 加载的第二个纯数据周期中使用加载端口。
  • 哦,您对 4 周期加载延迟的要求不太正确。 base+disp 可以有一个从 0..2047 开始的 disp,它不必是零。它错误地推测+disp 是否跨越了 4k 边界。 Is there a penalty when base+offset is in a different page than the base? 这就是为什么我得出的结论是索引 L1d 使用加法的结果,而不是完全绕过。低 12 位可能比完整的 64 位结果准备得更快,即使使用进位先行等。
  • @PeterCordes 我在想基地址(来自基址寄存器)首先发送到 TLB 进行查找。在同一个循环中,base+disp 是并行计算的。在第二个周期中,来自完整地址的缓存集索引被发送到缓存以打开缓存集(带有标签),并且并行地 TLB(在命中时)将物理地址发送到缓存。在第三个循环中,比较标签并确定匹配行。在第 4 个循环中,请求的数据被放置在转发网络上。在第 5 个周期中,可以分派一个依赖的 uop。因此,4 个周期。
  • 但你可能是对的。例如,将基地址发送到 TLB 可能需要“半个周期”,然后查找可能需要最多 2 个周期,而标签比较可能需要半个周期。请注意所有这些操作如何同时在关键路径上并限制最大 CPU 频率。在 4-cycle load 的要求中,我没有说 disp 必须为零,我说 index 必须为零或不存在,segment base 必须为零。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-18
  • 1970-01-01
  • 2016-08-06
  • 2013-02-16
  • 2017-07-30
  • 2017-02-16
相关资源
最近更新 更多