其他分享
首页 > 其他分享> > 寻找驱逐集的理论和实践

寻找驱逐集的理论和实践

作者:互联网

摘要

许多微架构攻击依赖于攻击者有效地找到小型 驱逐集(映射到同一缓存集的虚拟地址集合)的能力。这种能力已经成为缓存侧信道、rowhammer 和推测性执行攻击的决定性基础。尽管这很重要,但寻找小型驱逐集的算法在文献中还没有得到系统的研究。

在本文中,我们进行了这样一个系统的研究。我们首先将问题形式化,并分析一组随机的虚拟地址是驱逐集的概率。然后,我们基于阈值组测试的思想,提出了新的算法,该算法能在线性时间内将随机驱逐集缩减到最小驱逐集,比目前最先进的算法(时间复杂度为O2的算法)有所改进。

我们用严格的经验评估来补充我们算法的理论分析,在评估中,我们确定并隔离了影响算法在实践中可靠性的因素,如自适应缓存替换策略和TLB颠簸。结果表明,我们的算法能够比以前更快地找到小的驱逐集,而且是在以前认为不切实际的条件下。

1. 导言

针对现代CPU微架构的攻击已经从学术上的噱头迅速发展为现实世界中攻击者的有力工具。著名的攻击例子包括针对共享CPU缓存的侧信道攻击,针对DRAM的故障注入攻击,以及从推测执行中泄露信息的隐蔽信道攻击。

许多记录在案攻击的一个前提是,攻击者能够使特定的缓存集成为受控状态。例如,flush+reload 攻击使用特殊指令使目标缓存内容无效(如x86上的clflush),为此它们需要特权执行和共享内存空间。另一类攻击称为 prime+probe,通过用新的内容替换来驱逐缓存内容,并且可以在没有特权的情况下从用户空间或沙箱中执行。

用于替换缓存内容的基本单元称为驱逐集。从技术上讲,一个驱逐集是一个(虚拟)地址的集合,它包含的元素至少与缓存集的 ways 一样多,映射到一个特定的缓存集。直觉来看,当访问时,一个驱逐集会清除该缓存集中所有以前的内容。驱逐集使攻击者能够:(1)使特定的缓存集成为受控状态; (2)通过测量访问驱逐集的延迟,探究该状态是否被受害者修改过。

缓存的ways,也称为缓存的相联程度,例如,我们说8路组相联,意思就是每个缓存集中有8个way,关于缓存集,缓存行和缓存way,下图描述比较清晰:

访问一个足够大的虚拟地址集是足以从缓存中驱逐任何内容的。然而,这样大的驱逐集会增加驱逐和探测所需的时间,并且由于不必要的内存访问而引入噪音。为了更有针对性和更隐蔽地清除缓存内容,攻击者需要寻求最小的驱逐集,这是基本的,例如,对于:

计算最小驱逐集被认为是一个有挑战的问题,相当于学习从虚拟地址到缓存集的映射。这个问题的难度是由攻击者对物理地址位的控制程度所决定的。例如,在裸机上,攻击者完全控制虚拟地址到高速缓存集的映射。 在大页面上,它控制到每个缓存片(slice)中的缓存集的映射,但不控制虚拟地址到片的映射; 在常规的4KB页面上,它仅控制每个片内部分集合的映射; 在沙盒或更苛刻的环境中,它可能根本无法控制这种映射。

文献中的几种方法讨论了用于寻找最小驱逐集的算法,见第七节的概述。这些算法依赖于两个步骤,即首先收集一个足够大的地址集,作为一个驱逐集,然后连续将这个驱逐集减少到最小驱逐集。不幸的是,这些算法通常只被认为是达到另一个目的的手段,比如设计一种新型的攻击。因此,它们在复杂性、实时性能、正确性和通用性等方面仍然缺乏深入的分析,这也阻碍了攻击和对策的研究进展。

在本文中,我们进行了第一个系统性的研究,即将寻找最小驱逐集作为算法问题。 我们进行了如下的研究。

贡献总结

我们的贡献包括理论和实践两方面。在理论方面,我们将寻找最小驱逐集的问题形式化,并设计了新颖的算法,将最先进的算法从二次方复杂度改进为线性复杂度。在实践方面,我们进行了严格的经验分析,展示了我们的算法成功或失败的条件。总的来说,我们的见解为找到驱逐集的算法提供了原则性的对策或进一步提高鲁棒性的途径。

我们还包括一个工具,用于在不同的平台上评估本文中提出的所有测试和算法。

https://github.com/cgvwzq/evsets

2. 缓存和虚拟内存入门指南

在这一节中,我们提供了必要的背景和论文中用到的符号。

2.1 缓存

缓存是速度快但体积小的存储器,可以弥补CPU和主存储器之间的延迟差距。为了从空间局部性中获益并减少管理开销,主存被逻辑地划分为一组块。每个块作为一个整体被缓存在一个相同大小的缓存行。当访问一个块时,缓存逻辑必须确定该块是否存储在缓存中(缓存命中)或不存储在缓存中(缓存未命中)。为此,缓存被分割成大小相等的缓存集。缓存集的大小或行数称为缓存的关联度,表示为 a(或路 ways)。

2.2 虚拟内存

虚拟内存是对进程的存储资源的一种抽象,它提供了一个与其他进程隔离的线性内存空间,并大于物理上可用的资源。操作系统在CPU的内存管理单元(MMU)的帮助下,负责将虚拟地址转换为物理地址。

3. 驱逐集

在本节中,我们首次给出了驱逐集的形式化,并提出了能够确定一个给定地址集是否是驱逐集的测试。然后,我们将随机地址集形成驱逐集的概率表示为其大小的函数。本节的内容构成了我们开发的算法及其评估的基础。

3.1 定义驱逐集

我们说,如果两个虚拟地址 xy 映射到同一个缓存集,那么这两个地址是全等的,用 x≃y 表示。当且仅当其各自物理地址 pt(x)pt(y) 的集索引位 set(·) 和片索引位 slice(·) 相等时,才会出现这种情况。也就是说,当且仅当以下条件满足时, x ≃ y x\simeq y x≃y 成立:

同余是一种等价关系。x 关于 ≃ \simeq ≃ 的等价类 [x] 是映射到与 x 相同的缓存集的虚拟地址集。如果只满足上式中的第一项,我们说地址 x 和 y 部分全等,即它们在集合索引位上重合,但不一定在切片索引位上重合。

现在我们给出了驱逐集的定义,在这里我们区分了两个目标。在第一个目标中,我们寻求从缓存中驱逐一个特定的地址。例如,这与在 rowhammer 攻击中执行精确的刷新有关。在第二种情况下,我们寻求的是驱逐任意缓存集的内容。例如,对于高带宽隐蔽信道,人们试图控制大量的缓存集,但并不关心是哪些缓存集。

定义1,我们说一组虚拟地址S是:

定义1背后的直觉是,顺序访问 x 的驱逐集中的所有元素将保证 x 在被访问之后不会再次缓存。类似地,顺序访问 a+1 个全等的元素将保证其中至少有1个元素被驱逐。

为了使这一直觉成立,缓存替换策略需要满足一个条件,即对缓存集顺序访问 a 次都未命中的话,会驱逐缓存集中之前的所有的内容。例如,所有基于置换的策略(包括LRU、FIFO和PLRU)都满足这个条件。然而,现代(即Sandy Bridge之后)的Intel CPU只能部分满足这个条件。更详细的讨论见第六节。

3.2 测试缓存集

根据定义1,识别驱逐集需要检查等式是否成立。这需要访问物理地址的位,不能由用户程序执行。在本节中,我们将介绍一些测试,这些测试依赖于基于时间的侧信道来确定一组虚拟地址是否为驱逐集。

3.3 驱逐集的分布

在这一节中,我们分析了驱逐集的分布。更具体地说,对于不同的攻击者控制级别,我们计算一个适当选择的随机虚拟地址集合形成一个驱逐集的概率。

3.3.1 选择候选集

为了解释"适当选择"的含义,我们需要区分可以从用户空间控制的物理地址缓存集索引 γ \gamma γ 位和不能控制的 c - γ \gamma γ 位。例如, γ \gamma γ 的值取决于我们考虑的是大页还是小页。

从用户空间控制缓存集索引位是可能的,这是因为虚拟到物理的转换 pt 在页偏移上起着标识的作用,见第二节-B,当试图找到一个最小的驱逐集时,只需要考虑在这些 γ \gamma γ 位上重合的虚拟地址。

挑战是在不能从用户空间控制的 c - γ \gamma γ 位缓存集索引位(图1 中的页面颜色位)以及未知的 s 片位上寻找碰撞。在本节中,我们假设虚拟到物理的转换 pt 在这些比特位上作为一个随机函数。从攻击者的角度来看,这个假设对应的是最坏的情况;实际上,关于地址翻译的更详细的知识可以减少寻找驱逐集的努力。

因此,当我们在本文中谈到"选择一个给定大小的随机虚拟地址集"时,我们指的是选择随机的虚拟地址,这些地址在用户控制的 γ \gamma γ 位缓存集索引位上完全重合。现在我们来确定这样的集合是驱逐集的概率。

3.3.1 虚拟地址冲突的概率

我们首先计算在用户控制的 γ \gamma γ 位缓存集索引位上重合的两个虚拟地址 y 和 x 实际上完全相等的概率。我们把这个事件称为碰撞,用 C 表示。由于 pt 作为随机函数作用于剩余的 c - γ \gamma γ 位缓存集索引位和 s 片位,我们有:
P ( C ) = 2 γ − c − s P(C)=2^{\gamma-c-s} P(C)=2γ−c−s
以下示例说明了如何删除攻击者控制,从而增加在常见缓存配置上查找冲突的难度。

例1,考虑图1中的缓存,有8个片(即 s=3 ),每个片有1024个缓存集(即 c=10 )。

3.3.2 一个地址集合是x的驱逐集的概率

我们分析一个虚拟地址集 S 是给定地址 x 的驱逐集的概率,这个概率可以用二项分布的随机变量 X ∼ B ( N , p ) X∼B(N,p) X∼B(N,p)来表示,参数 N = ∣ S ∣ N=|S| N=∣S∣, p = P ( C ) p=P(C) p=P(C)。有这样的一个 X,找到 k 次碰撞的概率,即 ∣ S ∩ [ x ] ∣ = k |S∩[x]|=k ∣S∩[x]∣=k,由以下公式给出:
P ( X = k ) = ( N k ) p k ( 1 − p ) N − k P(X = k) = {\dbinom{N}{k}}p^k(1 − p)^{N−k} P(X=k)=(kN​)pk(1−p)N−k
根据定义1,如果 S 至少包含 a 个与 x 相同的地址,则 S 是一个驱逐集,这种情况发生的概率为:
P ( ∣ S ∩ [ x ] ∣ ≥ a ) = 1 − P ( X < a ) = 1 − ∑ k = 0 a − 1 ( N k ) p k ( 1 − p ) N − k P(|S∩[x]|≥a)=1 − P(X <a) = 1 − {\sum_{k=0}^{a-1}} {\dbinom{N}{k}} p^k (1 − p)^{N − k} P(∣S∩[x]∣≥a)=1−P(X<a)=1−k=0∑a−1​(kN​)pk(1−p)N−k
图3 描述了根据图1中的缓存,缓存集是 x 的驱逐集的分布。

3.3.3 一个地址集合成为任意地址的驱逐集的概率

我们分析一个集合 S 至少包含 a+1 个地址映射到同一个缓存集的概率。为此,我们把这个问题看作是一个单元占用问题。

即,我们考虑 B = 2 s + c − γ B = 2^{s+c-γ} B=2s+c−γ 个可能的缓存集(或容器),其 N = |S| 个地址(或球)是均匀分布的,求至少一个缓存集(或容器)容纳有 a 个以上地址(或球)的概率。

我们用随机变量 N1,… NB 来表示这个概率,其中 Ni 代表映射到第 i 个缓存集的地址数,约束条件是 N = N1 + … + NB,有了这一点,至少有一个缓存集的地址数超过 a 的概率可以简化成所有 Ni 都小于或等于 a 的互补事件。
P ( ∃ i ∣ N i > a ) = 1 − P ( N 1 ≤ a , . . . , N B ≤ a ) P(\exist i|N_i >a)=1−P(N_1 ≤a,...,N_B ≤a) P(∃i∣Ni​>a)=1−P(N1​≤a,...,NB​≤a)
右侧是一个累积的多项式分布,其精确的组合分析对于大的 N 值来说是很费时间的,并且对于我们的目的来说是不实用的。取而代之的是,我们依靠一个著名的基于泊松分布的近似值来计算概率。

图3 描述了基于图1的缓存,成为任意地址的驱逐集的分布。我们观察到,随着缓存集集的大小逐渐变大,多项式的分布的概率比二项式分布的概率增长得更快。这表明,一个地址集合对于任意地址比对于特定地址更有可能是一个驱逐集。

3.3.4 寻找驱逐集的资源消耗

在本节的最后,我们计算了通过反复、独立地选择和测试备选地址集来寻找大小为 N 的驱逐集所消耗的资源(以预期所需的内存访问次数为标准)。

为此,我们将驱逐集的重复独立选择建模为大小为 N 的候选地址集是驱逐集的概率 p(N ) 的几何分布。这个分布的期望值 1 / p ( N ) 1/p(N) 1/p(N) 是我们找到一个驱逐集最少要测试的候选集数量。假设对一个大小为 N 的地址集合的测试需要 O(N ) 次内存访问,如第三节-B中,这就得到了寻找初始驱逐集需要 O(N/p(N )) 次内存访问的总内存消耗。

图4 描述了例1中的攻击者寻找特定地址的驱逐集的资源消耗函数 N/p(N ),并得到了寻找驱逐集的最佳地址集合大小。由于概率随着缓存集的大小而增长,所以寻找一个较小的驱逐集需要大量的试验。一旦概率稳定下来(即地址集足够大),我们开始看到资源消耗的测试结呈线性。

4. 计算最小驱逐集的算法

一组虚拟地址集形成驱逐集的概率取决于它的大小、缓存设置(如关联性和缓存集的数量)以及攻击者对物理地址的控制程度。特别是,一个小的随机虚拟地址集不太可能成为驱逐集。这就需要我们采用两步法来寻找最小的驱逐集,其中(1)首先确定一个大的驱逐集,(2)然后将这个缓存集的尺寸缩小。

文献中以前的方案依赖于这种两步法。在本节中,我们首先介绍文献中基本的缩小缓存集尺寸的方法,需要 O(N2) 次内存访问。然后,我们表明,可以只使用 O(N) 次内存访问就缩小缓存集尺寸,这样就可以处理比以前大得多的初始驱逐集。

这个结果的主要实际意义是,从用户(或沙箱)空间中寻找最小驱逐集的速度比以前认为的要快,因此,即使没有对片或缓存集索引位的任何控制,也是实用的。这使得基于减少攻击者对这些位控制的对策不起作用。

4.1 基本算法

我们重新审视文献中已经非正式描述过的减少驱逐集的基本算法,其伪代码在算法1 中给出。

算法1输入一个虚拟地址 xx 的驱逐集 S,从 S 中选取一个地址 c,然后测试 S ∖ c S \setminus {c} S∖c 是否还能驱逐 x,见第4行。如果不是(注意是if分支),c 一定是 x 的等价地址,将其记录在 R 中,见第5行。然后,算法将cS 中移除,见第7行,再回到第2行,开始下一次循环。

需要注意的是,第4行的驱逐测试 TEST 应用在 R ∪ ( S ∖ c ) R∪(S \setminus {c}) R∪(S∖c),即包括了目前找到的所有等价地址。这使得即使在剩余元素个数少于 a 个的情况下,也可以扫描 S 来寻找等价元素。当 R 形成一个元素的最小驱逐集时,算法终止。因为 S 最初是一个驱逐集, 这一点是可以保证的。

命题1:算法1在 O(N2) 次内存访问中,将一个驱逐集 S 减少到其最小驱逐集,其中 N = |S|。

复杂度的边界是这样的,因为|S|是循环迭代次数的上界,也是对 Test 1 的参数的大小,因此也是每次调用 TEST 时执行的内存访问次数。

文献中包含了算法1的不同变体。例如,《The Spy in the Sandbox: Practical Cache Attacks in JavaScript and Their Implications》中提出的变体总是将 c 放回 S 中,并不断迭代直到 |S|=a,这仍然是接近二次的,但增加了一些冗余度,有助于减少误差。

如果二次的基本方法是最优的,我们可以考虑通过减少或取消对地址集索引位的控制来防止攻击者计算小的驱逐集,或者通过硬件添加掩码,或者通过软添加置换层(参见例1中的限制说明)。

4.2 计算特定地址的最小驱逐集

我们提出了一种新的算法,它可以在 O(N ) 次内存访问中,将驱逐集缩减到最小,其中 N = |S|,这使得处理比二次的基本方法大得多的驱逐集成为可能,并使基于隐藏物理地址的对策是无效的。我们的算法是基于阈值组测试的思想,先简单介绍一下。

4.2.1 阈值组测试

组测试是指通过对集合(即那些元素的组)进行测试来分解识别具有所需属性的元素的任务的过程。 已经提出了通过血液测试来识别疾病的组测试,其中,通过测试血液样本而不是单个样本,可以减少发现所有阳性个体(从线性到对数)所需的测试数量。

阈值组测试是指基于以下测试的组测试:如果被测试集合中的阳性个体数量最多为 l,则给出否定答案,如果数量至少为 u,则给出肯定答案,如果介于 lu之间,则给出任意答案。 这里,lu 是自然数,分别代表下阈值和上阈值。

4.2.2 一种计算最小驱逐集的线性时间算法

我们的算法背后的关键思想是,测试一组虚拟地址 S 是否驱逐 x(请参阅测试1)实际上可以看作是与 x 相等的阈值组测试,其中 l = a − 1 l = a−1 l=a−1 且 u = a u = a u=a, 这是因为如果 ∣ [ x ] ∩ S ∣ ≥ a | [x]\cap S | ≥a ∣[x]∩S∣≥a,得到肯定(positive)答案,否则为否定答案。 这种关系使我们可以利用组测试文献中的以下结果来计算最小驱逐集。

引理1:如果一个集合 S 包含 p 个或更多测试结果为肯定的元素,可以用 O( p   l o g ∣ S ∣ p\ log |S| p log∣S∣) 次阈值组测试来识别其中的 p 个元素,其中 $ l = p - 1 , , ,u = p$。

证明:引理1的思想是将 S 划分为 p + 1 个大小(大约)相同且不相交的子集T1,… …,Tp + 1。数学论证表明至少有一个 j ∈ { 1 , . . . . . . , p + 1 } j\in\{1,......, p+1\} j∈{1,......,p+1},使得 S ∖ T j S \setminus T_j S∖Tj​ 仍然是一个驱逐集。我们可以通过组测试来识别这样的 j,并在 S ∖ T j S \setminus T_j S∖Tj​ 上重复这个过程。对数复杂度是由于 ∣ S ∖ T j ∣ = ∣ S ∣ p p + 1 |S \setminus Tj| = |S| \frac{p}{p+1} ∣S∖Tj∣=∣S∣p+1p​ 这一事实,即每次迭代都会将驱逐集的大小减少一个系数,而不是像算法1那样减少一个常数。

算法2 基于这个思想计算最小驱逐集。需要注意的是,引理1给出了组测试次数的边界。然而,对于计算驱逐集,相关的复杂性度量是所进行的内存访问的总次数,也就是执行测试的地址集的大小之和。接下来我们将证明,在这种复杂性度量下,算法2 的复杂性和初始驱逐集的大小是线性相关的。

命题2:算法2 与测试1 使用 O(a2N ) 次内存访问将一个驱逐集 S 减少到其最小驱逐集,其中 N = |S|。

证明:算法2 的正确性来自于 S 是一个驱逐集的不变量,并且它在终止时满足 |S| = a,见引理1。对于复杂度边界的证明,观察到算法2 在大小为 N 的集合 S 上执行的内存访问次数遵循以下递归公式:
T ( N ) = T ( N a a + 1 ) + N ⋅ a T(N) = T(N \frac{a}{a+1}) +N · a T(N)=T(Na+1a​)+N⋅a
对于 N > a,且 T(a) = a,因为在输入 S 上递归成立,算法在 Sa + 1 个子集上应用阈值组测试,每个子集大小为 N − N a + 1 N - \frac{N}{a+1} N−a+1N​ 。分割和测试的总成本为 N · a,算法正好在 S 的这些子集上递归,它的大小为 N − N a + 1 N - \frac{N}{a+1} N−a+1N​ ,由主定理可知, T ( N ) ∈ Θ ( N ) T(N)∈Θ(N) T(N)∈Θ(N)。

4.2.3 计算任意地址的最小驱逐集

到目前为止,所介绍的算法计算的是一个特定虚拟地址 x 的最小驱逐集,现在我们考虑计算任意地址的最小驱逐集的情况。这种情况很有趣,因为如第三节-C 所示,一个虚拟地址集更有可能驱逐任意的地址而不是特定的地址。也就是说,在目标地址不相关的情况下,可以从更小的候选集开始减少。

关键是,算法1 和算法2 都可以很容易地调整为计算任意地址的驱逐集。这只需要将特定地址的驱逐测试(测试1)替换为任意地址的驱逐测试(测试3)。

命题3:算法1使用测试3 寻找任意地址的驱逐集,将初试驱逐集减少到其最小驱逐集,需要 O(N 2) 次内存访问,其中 N = |S|。

命题4 算法2 使用测试3 把一个初始驱逐集缩减到其最小驱逐集需要O(N )次内存访问,其中 N = |S|。

计算任意地址的驱逐集的复杂性界限与命题1 和命题2 中的复杂性界限相吻合,因为测试1 和测试3 在测试集的大小上都是线性的。

4.2.4 计算多个虚拟地址的最小驱逐集

我们现在讨论为大量的缓存集寻找驱逐集的情况。为此,我们假设一个给定的虚拟地址池 P,并解释如何计算 P 中包含的所有驱逐集的最小驱逐集。对于足够大的 P,其结果可以是所有虚拟地址的驱逐集。

核心思想是使用一个足够大的 P 子集,并将其减少为一个任意地址(比如 x)的最小驱逐集 S,使用 S 建立一个测试 T E S T ( ( S ∖ { x } ) ∪ { y } , x ) TEST((S \setminus\{x\}) \cup \{y\}, x) TEST((S∖{x})∪{y},x),用于测试各个地址 y 是否与 x 相等。使用这个测试来扫描 P,并删除所有与 x 一致的元素。重复这个过程,直到在 P 中找不到驱逐集。使用算法2 线性缩减,进行一次线性扫描,处理固定数量的缓存集,这个过程需要 O(|P|) 次内存访问来识别 P 中的所有驱逐集。

先前的工作基于二次缩减方法提出了一种类似的方法。 作者利用这样一个事实,即在较早的Intel CPU上,对于任何偏移 ∆ < 2 γ ∆ <2γ ∆<2γ,给定两个相等的物理地址 xy,然后 x + ∆ ≃ y + ∆ x + ∆ \simeq y + ∆ x+∆≃y+∆。 这意味着,如果给每个 2 c − γ 2c-γ 2c−γ 页面颜色设置一个驱逐集,则可以通过向每个地址添加适当的偏移量而立即获得其他 2 γ − 1 2γ-1 2γ−1 个。 不幸的是,使用未知的切片功能时,这种可能性只有 2-s,这增加了攻击者的工作量。 在这些条件下,我们的线性时间算法有助于减少大量的逐出集。

在《Last-Level Cache Side-Channel Attacks Are Practical》中提出了另一种解决寻找许多驱逐集问题的方法。这种解决方案与两步法的不同之处在于,算法首先构建一个所谓的冲突集,即 P 中包含的所有最小驱逐集的联合,然后再拆分为各个最小驱逐集。使用冲突集的主要优点是,一旦找到了最小驱逐集,就不需要再扫描冲突集以寻找进一步的相等地址。

5. 评估

在本节中,我们对第四节中开发的计算最小驱逐集的算法进行了评估。该评估补充了我们沿两个维度的理论分析。

5.1 我们的分析设计

实现:我们实现了第三节-B和第四节中描述的测试和算法,作为一个命令行工具,它可以被指定参数,以找到不同平台上的最小驱逐集。我们所有的实验都是使用该工具进行的。源代码在:https://github.com/cgvwzq/evsets

分析的平台:我们在两个不同的 CPU 上评估我们的算法,系统是 Linux 4.9 :

  1. Intel i5-6500 4 x 3.20 GHz(Skylake系列),16 GB内存,以及一个6 MB LLC,8192个12路缓存集。我们的实验表明,在这台机器上只用了10位作为缓存集索引,因此我们得出结论,每个核心有2个片。按照我们前面的记法,即:asky=12*,csky=10,ssky=3*,ℓsky=6。

  2. 英特尔i7-4790 8 x 3.60GHz主频(Haswell系列),8 GB的内存,以及一个8MB的LLC,8192个16路缓存集。这台机器有4个物理核和4个片。按照我们之前的符号,即:ahas=16,chas=11,shas=2,ℓhas=6。我们强调,所有的实验都是在用户操作系统(有默认的窗口管理器和后台服务)、默认的内核和默认的BIOS设置的机器上运行的。

初始搜索空间的选择:我们首先分配一个大的内存缓冲区,作为地址池,在那里我们可以适当地选择候选集(回顾第三节-C)。这种选择是根据攻击者的能力(即 γ γ γ)来进行的,例如,以 2 γ + ℓ 2^{γ+ℓ} 2γ+ℓ 的步长收集缓冲区中的所有地址,然后随机选择其中的 N 个地址。使用这种方法,我们能够模拟任何数量的攻击者对集合索引位的控制,即任何 γ γ γ 与 γ < p − ℓ γ<p -ℓ γ<p−ℓ。

隔离和减少干扰:我们确定了隔离两个重要干扰源的方法,这两个干扰源会影响我们测试的可靠性,从而影响我们算法的正确性。

我们进一步依靠文献中的常用技术来减轻其他干扰的影响:

5.1.1 评估稳健性

我们依靠两个指标来衡量我们测试和缩减算法的稳健性:

在这里,如果返回的元素是一致的,也就是说,它们在集合位和片位上是一致的,那么缩减就是成功的。对于这个检查,我们依靠对英特尔CPU逆向工程得到的分片函数。

在完美的测试下(因此算法也是正确的),驱逐率和缩减率都应该与第三节中给出的理论预测相吻合。因此,我们的分析重点是驱逐率和缩减率与这些预测的偏差。

实验结果图5图6a (Skylake)和图7a (Haswell)给出了特定地址 x 的驱逐集和缩减的实验结果。图6b图7b(分别为Skylake和Haswell)给出了任意地址的结果。我们关注以下发现:

5.1.2 评估性能

我们评估了我们的新型缩减算法的性能,并将其与文献中的基本方法进行比较。为此,我们测量了将不同大小的驱逐集减少到最小驱逐集所需的平均时间。我们首先关注的是理想化的条件,这些条件与第四节理论分析的假设非常匹配。

为了说明缩减的效果,我们还评估了寻找初始驱逐集缩减所需的努力。为此,我们考虑了具有不同能力的攻击者来控制缓存集索引位,基于大页面( γ γ γ = 10),4 KB页面( γ γ γ = 6),以及没有控制集索引位( γ γ γ = 0)。

总之,我们的评估说明了我们的新型缩减算法的性能提升如何影响计算最小驱逐集的整体工作量。

实验结果图8 给出了Skylake上特定地址的缩减评估结果。我们专注于缓存集零,以减轻替换策略的影响,我们减轻了TLB和预取的影响,如第五节-A所述。

每个数据点都是基于10次成功缩减的平均执行时间。选择初始集的大小(x轴)是为了描述找到初始驱逐集不需要挑选太多候选集的范围(由绿色条形图描述)集不需要选取太多候选集(用绿条描述)。关于初始集大小的更系统的选择,请看下面的讨论。

我们强调以下几点意见:

  1. 橙色曲线的斜率清楚地说明了基本缩减方法的执行时间的二次方增长,而蓝色曲线则显示了我们新提出算法的线性增长。绝对值考虑了恒定的因素,如每次测试50次的时间测量,以及收集数据导致的开销。
  2. 对于大尺寸的地址集,我们的新型缩减说方法明显优于基本的二次元方法。例如,对于大小为3000的集合,我们已经观察到了10倍的性能提升,优势很明显。对于小尺寸的集合,这种实际优势似乎不太相关。然而,对于这样的大小,找到一个真正的驱逐集所需的重复次数会增加,如绿色条形图所示。对于寻找驱逐集的总成本来说,需要综合考虑这两种效应。

初始集大小的最优选择:为了评估首次识别和减少驱逐集的成本,我们依靠一个表达式来计算寻找最小驱逐集所需的总体内存访问次数。这个表达式是寻找一个驱逐集的内存访问的期望值 N p ( N ) \frac{N}{p(N)} p(N)N​ 的总和,见第三节-C,分别减少 N2 和 N 次内存访问时的结果,参见命题1和命题2。根据这个表达式,我们计算出线性和二次缩减方法的最佳地址集大小(从攻击者的角度)。我们将这些大小作为每个缩减算法在整个流程中的最佳使用的近似值,并评估它们在这个大小的集合上的执行时间。

表1 显示了三种不同攻击者的最佳大小的集合上的线性和二次缩减的比较:有大页面,有4KB的页面,以及极限情况。

我们强调以下几点意见:

5.1.3 实践中的表现

在本节中,我们举例说明了我们的缩减算法在实际场景中的性能优势,即在TLB噪声和自适应替换策略的情况下。

我们实现了两种启发式方法来抵消最终的次优缩减率(见第五节-A):repeat-until-success,即在一次失败的缩减之后,我们选择一个新的集合并重新开始;backtracking,即在计算树的每一层,我们存储被丢弃的元素,如果出现错误,则回到测试成功的父节点,从那里继续计算。更多细节我们可以参考我们的开源实现。

为了比较这些启发式算法中的缩减算法的性能,我们遵循文献,关注初始地址集大小,保证初始集大概率是一个驱逐集。这是因为现实世界中的攻击者很可能会放弃反复采样的复杂性,直接选择一个足够大的初始集。

下面的例子提供了不同攻击者在随机选择的目标缓存集上的平均执行时间(超过100个样本)。Skylake (a = 12) 每次测试使用10个时间测量。

5.1.4 概要

综上所述,我们的实验表明,我们的算法在实际场景中可以将计算最小驱逐集所需的时间提高 5-20 倍。此外,它们还表明,即使没有对分片或集合索引位的任何控制,从虚拟(或沙盒)内存空间中寻找最小驱逐集的速度也是很快的,这使得基于掩蔽这些位的对策无效。

6. 近一步讨论缓存替换策略的影响

现代微架构有几个特征没有被我们的模型所捕获,这些特征会影响我们算法的功效,比如自适应和随机化替换策略、TLBs、预取等。第五节的评估表明,预取的影响可以被攻击者部分缓解,而TLBs的影响在实践中并不是一个限制因素。缓存替换策略的作用不太明确。

在这一节中,我们将仔细研究现代缓存替换策略在计算最小驱逐集中的作用。正如第二节所讨论的那样,Sandy Bridge 架构的替换策略具有自适应性或抗冲击性等特性。有了这样的特性,访问与 [ x ] [x] [x] 一致的地址集既不是驱逐 x 的必要条件,也不是充分条件,这就在我们的一致性测试中引入了错误(假阳性和假阴性)。我们首先解释导致这两种错误的关键机制,然后再通过实验分析它对 Skylake 和 Haswell 的影响。

6.1 自适应替换策略

自适应缓存替换策略根据哪种策略在特定负载上可能更有效,动态地选择替换策略。为此,他们依靠实现不同策略的所谓领导者集。计数器会跟踪在领导者上发生的缓存未命中,并根据当下哪个领导者更有效来调整跟随者集的替换策略。有不同的方式来选择领导者:一种是静态(static)策略,其中领导者集是固定的;另一种是运行时随机(rand-runtime)策略,每几百万条指令随机选择不同的领导者。

之前的一项研究表明,Ivy Bridge 中使用的替换机制确实是自适应的,具有静态的领导者集。据我们所知,目前还没有对 Haswell、Broadwell 或 Skylake 等最新一代英特尔处理器上的替换机制进行详细的研究,但有提到高频策略开关在 Haswell 和Broadwell CPUs 中时为了阻止 prime+probe 攻击。

我们进行了不同的实验,以进一步了解 Skylake 和 Haswell 中自适应策略的实现,以及它们对计算最小驱逐集的影响。为此,我们分别跟踪每一个缓存集索引的驱逐率和缩减率(见第五节)。

  1. 在任意驱逐集上;

  2. 在所有地址都是部分相等的驱逐集中。

在第二种情况下,缩减使用的地址只属于每个片的一个缓存集。假设缓存集在不同片是独立的,与第一种情况的比较可以让我们确定缓存集之间的影响。对于这两种实验,我们都依靠大页面来精确控制目标缓存集,减少TLB的影响,见第五节-A部分。

6.2 评价自适应的效果

图9 给出了在Skylake上减少任意驱逐集的结果,图10 给出了Haswell的结果。我们专注于大小为 N=4000 的初始驱逐集(但观察到其他大小的驱逐集也有类似的结果)。

我们强调以下发现:

图11 给出了我们在 Haswell 和 Skylake 上对部分相同驱逐集的缩减结果。结果显示:驱逐率和缩减率都接近预测的最优值。这比图9 和图10 中的缩减率有所提高,说明在访问相邻缓存集时,对驱逐测试有很强的干扰。特别是,我们观察到缩减的鲁棒性随着初始驱逐集中部分一致地址的比例增加而增加。

最后,图12 描述了为每个缓存集索引寻找最小驱逐集的平均执行时间,包括回溯启发式的开销。较低的缩减率意味着较高的错误数,因此需要更多的回溯步骤和更长的执行时间。这种效果在与图9比较时可见:缩减率最高的缓存集的执行时间最低。

6.3 今后的工作

对自适应缓存替换策略的算法影响进行更详细的分析,不在本文的讨论范围之内。但是,我们简要介绍了一些未来工作的思路。

7. 相关工作

计算最小的,或至少是小的驱逐集,为删除或放置任意数据到缓存中提供了一个基本的基元,这对于LLC缓存攻击(Prime+Probe,Evict+Reload等),对于DRAM故障攻击(如Rowhammer,它打破了安全域之间的间隔),对于内存重复数据删除攻击(如VUSION),以及最近的Meltdown和Spectre攻击(利用缓存跨边界泄露数据,增加推测执行指令的数量)都是必不可少的。

Gruss等人已经确定了寻找驱逐集的动态和静态方法。动态方法使用时序测量来识别碰撞地址,而不知道 LLC 分片哈希函数或物理地址;而静态方法使用反向工程哈希和(部分)物理地址信息来计算驱逐集。在实践中,大多数攻击都依赖于混合方法,用静态方法产生部分一致的地址集,再用动态方法修剪或缩减结果(大多是算法1 的变体)。我们回顾一些最相关的方法:

完全静态,没有分片。在没有分片的CPU中(如ARM),可以直接利用 pagemap 接口的信息找到驱逐集。Lipp等人探讨了如何在 ARM 上进行 Prime+Probe、Evict+Reload 等跨核缓存攻击。幸运的是,Google在2016年3月对 Android 进行了5次修补,现在需要 root 权限才能公开物理地址,给寻找驱逐集的任务带来困难。

静态/动态的巨大页面。Liu等人和Irazoqui等人在其针对 LLC 攻击的开创性工作中,依靠 2MB 的大页面来规避将虚拟地址映射到缓存集的问题。他们是第一个提出这种方法的人。

Gruss等人提出了第一个来自JavaScript的 rowhammer攻击。为了实现这个目标,他们建立了驱逐集,这要归功于2MB的大页面(由一些Linux发行版提供的透明大页面支持)。

另一方面,来自英特尔SGX的更复杂的缓存攻击依赖于SGX飞地内大型阵列的可预测物理分配,以及从DRAM行缓冲区的另一个侧信道中提取的信息。

没有巨大页面的沙盒环境。Oren等人提出了对Liu等人工作的扩展,进行了第一次来自JavaScript的缓存攻击,其中使用了常规的4KB页面,并且不能直接使用指针。它利用浏览器对大缓冲区的页面对齐分配的知识,构造了一个具有相同页面偏移位的初始集。然后他们利用第四节-D中描述的巧妙技术,进一步加速寻找其他驱逐集的过程。

Dedup Est Machina也实现了JavaScript rowhammer攻击,但这次是针对Windows 10上的Microsoft Edge。有趣的是,他们不能依赖大页面,因为Microsoft Edge并没有明确地请求它们。然而,他们发现,他们发现Windows内核从不同的物理内存池分配页面以分配大量页面,这些内存池经常属于相同的缓存集。从而,他们能够通过访问相隔128KB的几个地址(并且经常落在同一个缓存集中)来有效地找到驱逐集(不是最小的)。

Horn的使用启发式方法打破虚拟机隔离,通过多次迭代Test 3,并丢弃所有总是热的元素(即总是产生缓存命中)来寻找小的驱逐集。虽然这个启发式在实践中表现得非常好,但其渐进成本在地址集大小的二次方。

最后,最近的一项关于来自可移植代码的缓存攻击的工作PNaCl和WebAssembly讨论了在常规的4KB页面上寻找驱逐集的问题,以及如何部分处理 TLB 抖动。

与这些方法相比,我们的工作是第一个考虑对物理地址控制小于12位的攻击者,它将寻找驱逐集的问题形式化,并提供了可能实现纯动态方法的新技术。

分片函数的逆向工程。现代的 CPU 与 LLC 切片使用专有的哈希函数将块分配到切片中,这导致了对它们进行逆向工程的尝试。这些工作是基于1)分配和识别碰撞地址集; 2)利用这些地址之间的汉明距离, 或解方程组, 重新构造切片函数。尽管我们现在已经知道了几种微架构的分片哈希函数,而且Maurice等人也利用它加快了寻找大页面的驱逐集的速度,但我们认为它在真实攻击上的使用受到了物理地址信息稀缺的约束环境的阻碍。

抗震荡/抗扫描的替换策略。现代的替换策略,如插入策略或 DRRIP,在应对导致扫描或冲击的工作负载时,表现比PLRU更好。然而,它们也使得驱逐的可靠性降低,并且不属于我们当前的模型(见第三节)。Howg提出了一个双指针驱逐的以减轻这些影响;而Gruss等人用驱逐策略概括了这一方法,即在一些未知的现代策略下增加驱逐机会的驱逐集上的访问模式。这两种方法与我们的方法都是正交的,因为他们已经假定拥有驱逐集。

集索引随机化。同时进行的工作提出了一些新的随机化缓存设计,其中缓存集的索引是用一个键控函数,使攻击者对物理地址位的控制完全失效。这些建议的一个关键结果是,它们使基于缓存的攻击,特别是寻找小的驱逐集变得更加困难。然而,他们的安全分析考虑了二次的攻击者;看看它如何受到我们的线性时间的影响将是有趣的算法。

8. 结论

寻找最小的驱逐集是许多微架构攻击的基本步骤。在本文中,我们首次将寻找驱逐集作为一个算法问题进行研究。

我们的核心理论贡献是新颖的算法,可以在线性时间内计算驱逐集,比最先进的二次算法有所改进。我们的核心实践贡献是严格的实证评估,在评估中,我们确定并隔离了影响其可靠性的因素,如自适应替换策略和 TLB 抖动。

我们的结果表明,我们的算法能够比以前更快地找到最小驱逐集,在以前被认为不切实际的情况下实现攻击。它们还展示了算法失败的条件,为研究原则性对策提供了基础。

标签:驱逐,缓存,实践,寻找,算法,测试,地址,我们
来源: https://blog.csdn.net/t1506376703/article/details/111301340