7

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

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

出于某种原因,讨论微架构细节的网络安全论文往往不够精确。
幸运的是,MSBDS 论文引用了专利 US 2008/0082765 A1(从中拍摄图片)。

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

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

加载和存储的内存消歧算法

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

[0026]段补充说:

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

这意味着如果物理地址不可用,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漏洞吗?


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

4

1 回答 1

5

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

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

当加载uop 被分派到加载AGU 端口之一时,使用指定的段基址、基址寄存器操作数、索引寄存器操作数、比例和位移来计算加载的有效地址,即线性地址。同时,存储缓冲区中可以有存储。将加载的线性地址与执行了 STA 微指令的所有早期存储的线性地址进行比较(即,存储的线性地址可用)。可能还需要比较物理地址,但此时加载的物理地址仍然不可用(这种情况在专利中称为无效物理地址)。为了最小化负载的可观察延迟,MOB 仅使用加载和每个较早存储的线性地址的最低有效 12 位执行快速比较。有关此比较的更多信息,请参阅L1 内存带宽:使用相差 4096+64 字节的地址,效率下降 50%(但此处未讨论屏蔽的微指令)。这种逻辑称为松散网络,它构成了推测性记忆消歧机制的另一部分。自 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) 段基地址为零,否则至少有一个周期的惩罚)。有关更多信息,请参阅评论中的讨论。

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

如果松散的净结果表明负载与较早的存储重叠,则 MOB 并行执行两件事。其中之一是内存消歧过程继续使用精细网络(即全线性地址比较)。如果细网中的负载丢失,则在可用时比较物理地址。否则,如果负载击中细网,则负载和存储重叠。请注意,x86 ISA 要求在更改分页结构后使用完全序列化指令。所以在fine net hit的情况下不需要比较物理地址。除此之外,每当分派新的 STA 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 缓解措施。

我不知道第 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。但它们在这里不相关。

于 2019-05-20T02:14:55.873 回答