什么是回跳线?它是如何工作的?

为了减轻内核或跨进程内存泄露(幽灵攻击),Linux内核1将被编译为一个新的选项-mindirect-branch=thunk-extern引入到gcc中,通过所谓的retpoline执行间接调用。

这似乎是一个新发明的术语,因为谷歌搜索最近才出现(一般都是在2018年)。

什么是retpoline ?它如何防止最近的内核信息泄露攻击?


1它不是Linux特定的,然而-类似或相同的构造似乎在其他操作系统上被用作缓解策略的一部分。

91320 次浏览

这篇文章提到的sgbj在谷歌的保罗特纳写的评论中更详细地解释了以下内容,但我会给它一个机会:

就我目前从有限的信息中所能拼凑出来的而言,retpoline是一个返回蹦床,它使用了一个永远不会执行的无限循环,以防止CPU推测间接跳转的目标。

Andi Kleen的内核分支中可以看到解决这个问题的基本方法:

它引入了新的__x86.indirect_thunk调用,该调用装入内存地址(我将称之为ADDR)存储在堆栈顶部的调用目标,并使用RET指令执行跳转。然后使用NOSPEC_JMP /电话宏调用thunk本身,该宏用于替换许多(如果不是全部)间接调用和跳转。宏只是将调用目标放在堆栈上,并在必要时正确设置返回地址(注意非线性控制流):

.macro NOSPEC_CALL target
jmp     1221f            /* jumps to the end of the macro */
1222:
push    \target          /* pushes ADDR to the stack */
jmp __x86.indirect_thunk /* executes the indirect jump */
1221:
call    1222b            /* pushes the return address to the stack */
.endm

call放置在最后是必要的,这样当间接调用结束时,控制流继续在使用NOSPEC_CALL宏之后,因此可以使用它来代替常规的call

坦克本身看起来如下:

    call retpoline_call_target
2:
lfence /* stop speculation */
jmp 2b
retpoline_call_target:
lea 8(%rsp), %rsp
ret

这里的控制流可能有点令人困惑,所以让我澄清一下:

  • call将当前指令指针(标签2)压入堆栈。
  • lea将8添加到堆栈指针中,有效地丢弃了最近推入的四字,这是最后一个返回地址(标签2)。在此之后,堆栈顶部再次指向实际返回地址ADDR。
  • ret跳转到*ADDR并将堆栈指针重置到调用堆栈的开头。

最后,整个行为实际上等同于直接跳转到*ADDR。我们得到的一个好处是,用于返回语句(返回堆栈缓冲区,RSB)的分支预测器在执行call指令时,假定相应的ret语句将跳转到标签2。

标签2之后的部分实际上永远不会被执行,它只是一个无限循环,理论上会用JMP指令填充指令管道。通过使用LFENCEPAUSE或更普遍的导致指令管道暂停的指令,可以防止CPU在这种推测执行上浪费任何能量和时间。这是因为如果对retpoline_call_target的调用正常返回,LFENCE将是下一个要执行的指令。这也是分支预测器根据原始返回地址(标签2)所预测的结果。

引用英特尔的架构手册:

LFENCE之后的指令可以在LFENCE之前从内存中提取,但它们在LFENCE完成之前不会执行。

但是请注意,规范从未提到LFENCE和PAUSE会导致管道暂停,所以我在这里读到了一些字里行间的意思。

现在回到你最初的问题: 内核内存信息公开是可能的,因为结合了两个思想:

  • 即使投机执行在投机错误时应该没有副作用,推测执行仍然会影响缓存层次结构。这意味着当推测性地执行内存加载时,它仍然可能导致缓存线被清除。缓存层次结构中的这种变化可以通过仔细测量映射到相同缓存集的内存访问时间来识别 当读取的内存源地址本身是从内核内存中读取时,您甚至可以泄漏任意内存的一些位

  • 英特尔cpu的间接分支预测器只使用源指令的最低12位,因此很容易用用户控制的内存地址破坏所有2^12可能的预测历史。然后,当在内核中预测到间接跳转时,可以推测地使用内核特权执行这些跳转。因此,使用缓存计时侧通道可以泄漏任意内核内存。

更新:关于内核邮件列表,有一个正在进行的讨论,让我相信retpoline不能完全缓解分支预测问题,因为当返回堆栈缓冲区(RSB)运行空时,最新的英特尔架构(Skylake+)回落到脆弱的分支目标缓冲区(BTB):

Retpoline作为缓解策略,将间接分支替换为回报, 避免使用来自BTB的预测,因为它们可能是 被攻击者毒死了。 Skylake+的问题是RSB下流回落到使用 BTB预测,允许攻击者控制推测。

retpoline被设计用来防止分支目标注入(cve - 2017 - 5715)利用。这是一种攻击,使用内核中的间接分支指令强制任意代码块的推测执行。所选择的代码是一个“小工具”,在某种程度上对攻击者有用。例如,可以选择通过影响缓存的方式泄漏内核数据的代码。retpoline通过简单地将所有间接分支指令替换为返回指令来防止这种攻击。

我认为retpoline的关键是“ret”部分,它用返回指令代替间接分支,这样CPU就可以使用返回堆栈预测器而不是可利用的分支预测器。如果使用简单的推送和返回指令,那么将被推测执行的代码将是函数最终将返回的代码,而不是对攻击者有用的小工具。蹦床部分的主要好处似乎是维护返回堆栈,因此当函数实际返回给调用者时,这是正确的预测。

分支目标注入背后的基本思想很简单。它利用了CPU不会在分支目标缓冲区中记录分支源和目标的完整地址这一事实。因此,攻击者可以在自己的地址空间中使用跳转来填充缓冲区,当在内核地址空间中执行特定的间接跳转时,这将导致预测命中。

请注意,retpoline并不直接阻止内核信息的公开,它只是防止间接分支指令被用于推测性地执行将公开信息的小工具。如果攻击者可以找到一些其他的方法来投机地执行小工具,那么retpoline就不能阻止攻击。

论文幽灵攻击:利用推测执行作者:Paul Kocher, Daniel Genkin, Daniel Gruss, Werner Haas, Mike Hamburg, Moritz Lipp、Stefan Mangard、Thomas Prescher、Michael Schwarz和Yuval Yarom概述了如何利用间接分支

利用间接分支。从面向返回的编程绘制 (ROP),在这种方法中,攻击者从地址中选择小工具 空间,并影响受害者执行小工具 大胆的。与ROP不同,攻击者不依赖于 受害者代码中的漏洞。相反,攻击者训练 分支目标缓冲区(BTB)从间接错误地预测一个分支 分支指令到小工具的地址,导致 投机执行小工具。而投机执行 指令被丢弃,但它们对缓存的影响没有 恢复。这些效果可以被小工具用来泄漏敏感 信息。我们展示如何,与一个小工具的仔细选择,这 方法可以从受害者中读取任意内存 < p > 如果不能控制BTB,攻击者就会找到小工具的虚拟地址 在受害者的地址空间中,然后对此执行间接分支 地址。这个训练是从攻击者的地址空间完成的,并且 控件中的小部件地址中驻留的内容无关紧要 攻击者地址空间;所需要的只是使用的分支 用于训练分支机构使用相同的目标虚拟地址。(在 事实上,只要攻击者处理异常,攻击就可以工作 即使没有代码映射到小工具的虚拟地址 在攻击者的地址空间中。)也不需要一个完整的 用于训练的分支的源地址与 目标分支地址。因此,攻击者具有显著的

Project Zero团队在谷歌上的一篇题为使用侧通道读取特权内存的博客文章提供了另一个如何使用分支目标注入来创建工作漏洞的示例。

这个问题是很久以前提出的,应该有一个新的答案。

执行概要:

“Retpoline”序列是一种软件结构,它允许间接分支与推测执行隔离。这可以用于保护敏感的二进制文件(如操作系统或管理程序实现)免受针对其间接分支的分支目标注入攻击。

< >强ret < / >强poline"”是单词“return”的多用途的;和“蹦床”,很像改进的“< >强rel < / >强poline"由“相对称呼”造出;和“;trampoline"。这是一个使用返回操作构建的蹦床结构,也形象地确保任何相关的投机执行将无休止地“反弹”。

为了减少内核或跨进程内存泄露(Spectre攻击),Linux内核[1]将被编译为一个新的选项,-mindirect-branch=thunk-extern被引入到gcc中,通过所谓的retpoline执行间接调用。

[1]它不是Linux特有的,但是-类似或相同的结构似乎被用作其他操作系统上的缓解策略的一部分。

使用此编译器选项只有可以在具有CVE-2017-5715所需微码更新的受影响处理器中防止幽灵V2。它会在任何代码(不仅仅是内核)上执行“工作”,但只会在包含“秘密”的代码上执行“工作”。值得攻击。

这似乎是一个新发明的术语,因为谷歌搜索最近才出现(一般都是在2018年)。

LLVM编译器2018年1月4日前开始有一个-mretpoline开关。该日期是漏洞首次公开报道的日期。GCC 让他们的补丁可用 2018年1月7日。

CVE日期表明,2017年的漏洞是“发现”,但它影响了过去二十年生产的一些处理器(因此它很可能很久以前就被发现了)。

什么是retpoline ?它如何防止最近的内核信息泄露攻击?

首先是几个定义:

  • 有时被称为间接跳向量蹦床是存储指向中断服务例程,I/O例程等地址的内存位置。执行跳入蹦床,然后立即跳出,或弹跳,因此蹦床这个术语。海湾合作委员会传统上支持嵌套函数,方法是在运行时获取嵌套函数的地址时创建可执行的蹦床。这是一小段代码,通常位于包含函数的堆栈框架中。蹦床加载静态链寄存器,然后跳转到嵌套函数的实际地址。

  • - thunk是一个子例程,用于将额外的计算注入另一个子例程。坦克主要用于延迟计算,直到需要其结果,或在另一个子例程的开始或结束插入操作

  • 记忆有关 -一个记忆函数"结果对应于一组特定的输入。使用记住的输入的后续调用将返回记住的结果,而不是重新计算它,从而消除了使用给定参数调用的主要代价,只有第一次调用使用这些参数的函数时除外。

非常粗略地,一个retpoline是一个< >强蹦床< / >强加上一个返回作为一个,在间接分支预测器中为'破坏' < >强记忆< / >强

: retpoline包括Intel的PAUSE指令,但AMD的LFENCE指令是必要的,因为在处理器上PAUSE指令不是序列化指令,所以PAUSE /jmp循环将使用多余的能量,因为它被推测为等待返回而错误地预测到正确的目标。

Arstechnica对这个问题有一个简单的解释:

每个处理器都有一个体系结构行为(描述指令如何工作以及程序员编写程序所依赖的有文档记录的行为)和一个微体系结构行为(体系结构行为的实际实现方式)。这些可能以微妙的方式产生分歧。例如,从体系结构上讲,从内存中的特定地址加载值的程序将等待,直到该地址已知后才尝试执行加载。然而,在微架构上,处理器可能会尝试推测地址,以便在完全确定应该使用哪个地址之前就开始从内存中加载值(这是很慢的)。

如果处理器猜测错误,它将忽略猜测值并再次执行加载,这一次使用正确的地址。这样就保留了体系结构上定义的行为。但是错误的猜测会干扰处理器的其他部分——特别是缓存的内容。这些微架构干扰可以通过定时访问应该(或不应该)在缓存中的数据所需的时间来检测和测量,允许恶意程序对存储在内存中的值进行推断”。

来自英特尔的论文:"Retpoline:分支目标注入缓解"(. pdf):

回滚序列防止处理器的推测执行使用“间接分支预测器”。(预测程序流的一种方式)推测到由漏洞控制的地址(满足上述分支目标注入(Spectre变体2)漏洞组成的五个元素中的第4个元素)。

注意,元素4是:“利用必须成功地影响这个间接分支来投机性地错误预测和执行一个小工具。”这个小工具,被利用,泄漏秘密数据通过一个侧通道,通常是通过缓存定时。