什么是retpoline,它如何工作?


244

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

这似乎是一个新发明的术语,因为Google搜索仅在最近才使用(通常在2018年全部使用)。

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


1它不是特定于Linux的-类似或相同的构造似乎被用作其他OS 的缓解策略的一部分。


6
Google的一篇有趣的支持文章
sgbj

2
哦,所以它的发音是/ ˌtræmpəˈlin /(美国)或/ ˈtræmpəˌliːn /(英国)
Walter Tross

2
您可能会提到,这就是Linux内核,尽管如此gcc!在Linux Kernel Mailing List网站上,我什至没有一次看到lkml.org/lkml/2018/1/3/780,甚至在我浏览那里时(因为它处于脱机状态也被提供了快照),我都没有意识到 。
PJTraill '18年

@PJTraill-添加了Linux内核标签
RichVel

@PJTraill-好的,我更新了问题文本。请注意,由于它是相对开放的开发过程,因此我首先在Linux内核中看到了它,但是毫无疑问,相同或相似的技术已被用作缓解开放源代码和封闭源代码OS范围的技术。因此,我不认为这是特定于Linux的,但是链接肯定是。
BeeOnRope

Answers:


158

sgbj在Google的Paul Turner撰写的评论中提到的文章更详细地解释了以下内容,但我会试一试:

就目前为止我可以从有限的信息中总结出来的是,retpoline是一个返回的蹦床,它使用了一个无限循环,该循环从未执行过,可以防止CPU在间接跳转的目标上进行推测。

基本方法可以在Andi Kleen的内核分支中解决此问题:

它引入了新的__x86.indirect_thunk调用,该调用将加载其内存地址(我称为ADDR)存储在堆栈顶部的调用目标,并使用一条RET指令执行跳转。然后使用NOSPEC_JMP / CALL宏调用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。我们得到的一个好处是,在执行call指令时,用于返回语句的分支预测变量(Return Stack Buffer,RSB)假定相应的ret语句将跳转到标签2。

标签2之后的部分实际上从未执行过,它只是一个无限循环,理论上将用JMP指令填充指令流水线。通过使用LFENCEPAUSE或更一般而言,使用导致指令流水线停止的指令,可以阻止CPU在这种推测性执行上浪费任何功率和时间。这是因为如果对retpoline_call_target的调用正常返回,LFENCE则将是下一条要执行的指令。这也是分支预测程序根据原始返回地址(标签2)预测的结果

引用英特尔架构手册:

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

但是请注意,该规范从未提及LFENCE和PAUSE会导致管道停顿,因此我在这里在两行之间读了一点。

现在回到您的原始问题:由于以下两种思想的结合,可以公开内核内存信息:

  • 即使在推测错误时推测执行应该没有副作用,但推测执行仍会影响缓存层次结构。这意味着当以推测方式执行内存加载时,它仍然可能导致缓存行被驱逐。可以通过仔细测量对映射到同一高速缓存集的内存的访问时间来确定高速缓存层次结构中的此更改。
    当读取的内存源地址本身是从内核内存中读取的时,您甚至可能泄漏任意内存的某些位。

  • 英特尔CPU的间接分支预测变量仅使用源指令的最低12位,因此很容易使用用户控制的存储器地址来破坏所有2 ^ 12个可能的预测历史。然后,当在内核中预测到间接跳转时,就可以使用内核特权进行推测性执行。因此,使用缓存定时辅助通道可以泄漏任意内核内存。

更新:在内核邮件列表上,正在进行的讨论使我相信,回传不能充分缓解分支预测问题,因为当返回堆栈缓冲区(RSB)空着时,较新的Intel体系结构(Skylake +)会退缩到易受攻击的分支目标缓冲区(BTB):

Retpoline作为缓解策略,将间接分支机构换成回报,以避免使用来自BTB的预测,因为这些预测可能会受到攻击者的毒害。Skylake +的问题在于RSB下溢回落到使用BTB预测,这使攻击者可以控制推测。


我认为LFENCE指令并不重要,Google的实现改为使用PAUSE指令。support.google.com/faqs/answer/7625886请注意,您引用的文档说“不会执行”不会“会被投机地执行”。
罗斯岭

1
在该Google常见问题解答页面上:“上面的推测性循环中的暂停指令不是正确性所必需的。但这确实意味着非生产性的推测性执行在处理器上占用的功能单元较少。” 因此,它不支持您得出结论:LFENCE是此处的关键。
罗斯岭

@RossRidge我部分同意,对我来说,这似乎是无限循环的两种可能的实现,它们提示CPU不要在PAUSE / LFENCE之后以推测方式执行代码。但是,如果LFENCE 推测性执行的,并且由于推测正确而没有回滚,则这与仅在内存加载完成后才执行的说法相矛盾。(否则,必须对已推测性执行的整个指令集进行回滚并再次执行以符合规范)
Tobias Ribizel

1
这样做的好处push/ ret没有不平衡的返回地址预测栈。有一个错误的预测(转到lfence使用实际返回地址之前的),但是使用call+修改rsp平衡了该结果ret
彼得·科德斯

1
糟糕,相对于 push /的优势ret(在我的最后评论中)。回复:您的编辑:RSB下溢应该是不可能的,因为repoline包含一个call。如果内核抢占在此处进行了上下文切换,那么我们将使用从引发的RSB恢复call到调度程序中继续执行。但是也许中断处理程序可以以足够的rets 结束以清空RSB。
彼得·科德斯

46

retpoline被设计成防止所述分支目标喷射(CVE-2017-5715)利用。这是一种攻击,其中内核中的间接分支指令用于强制推测性执行任意代码块。选择的代码是对攻击者有用的“小工具”。例如,可以选择代码,以便通过影响缓存的方式泄漏内核数据。retpoline通过简单地将所有间接分支指令替换为return指令来防止这种利用。

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

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

请注意,retpoline不会直接阻止内核信息的泄露,而只是防止间接的分支指令被用来推测性地执行将披露信息的小工具。如果攻击者可以找到其他手段来推测性地执行该小工具,则举报方式不会阻止攻击。

Paul Kocher,Daniel Genkin,Daniel Gruss,Werner Haas,Mike Hamburg,Moritz Lipp,Stefan Mangard,Thomas Prescher,Michael Schwarz和Yuval Yarom撰写的论文《Spectre Attacks:利用推测执行》

利用间接分支。从面向返回的程序设计(ROP)中提取,攻击者通过这种方法选择一个小工具从受害者的地址空间中获取信息,并影响受害者以推测方式执行小工具。与ROP不同,攻击者不依赖受害者代码中的漏洞。相反,攻击者训练分支目标缓冲区(BTB),以错误预测从间接分支指令到小工具地址的分支,从而导致小工具的推测执行。尽管放弃了推测执行的指令,但它们对缓存的影响不会恢复。小工具可以使用这些效果来泄漏敏感信息。我们展示了如何通过仔细选择一个小工具来使用此方法从受害者读取任意内存。

为了训练BTB,攻击者在受害者的地址空间中找到小工具的虚拟地址,然后执行间接分支到该地址的操作。该训练是从攻击者的地址空间完成的,与位于攻击者的地址空间中的小工具地址无关紧要;所需要的只是用于训练分支的分支使用相同的目标虚拟地址。(实际上,只要攻击者能够处理异常,即使在攻击者的地址空间中没有在小工具的虚拟地址处映射的代码,攻击也可以进行。)也无需完全匹配源地址用于训练的分支的地址以及目标分支的地址。因此,攻击者在设置训练上具有很大的灵活性。

Google的Project Zero小组题为“ 通过侧通道读取特权内存来读取特权内存 ”的博客条目提供了另一个示例,说明如何使用分支目标注入来创建有效的漏洞利用程序。


9

这个问题是在不久前提出的,应该得到一个新的答案。

执行摘要

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

ret poline一词是“ return”和“ trampoline”一词的组合,就像对“ rel poline ” 的改进来自“ relative call”和“ trampoline”。它是使用返回操作构造的蹦床构造,它还可以比喻地确保任何相关的投机执行将无休止地“反弹”。

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

[1]但是,它不是特定于Linux的-类似或相同的构造似乎被用作其他OS上的缓解策略的一部分。

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

这似乎是一个新发明的术语,因为Google搜索仅在最近才使用(通常在2018年全部使用)。

2018年1月4日起,LLVM编译器已进行了更改。该日期是该漏洞首次公开报告的时间。GCC 2018年1月7日发布了补丁程序-mretpoline

CVE日期表明该漏洞是在2017 年“ 发现 ”的,但它影响了过去二十年中制造的某些处理器(因此很可能是很久以前发现的)。

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

首先,一些定义:

  • 蹦床 -有时称为间接跳转向量,蹦床是存储地址的内存位置,这些地址指向中断服务例程,I / O例程等。执行跳入蹦床,然后立即跳出或反弹,因此称为蹦床。传统上,GCC通过在运行时创建嵌套函数的地址时创建可执行的蹦床来支持嵌套函数。这是一小段代码,通常驻留在包含功能的堆栈框架中的堆栈上。蹦床加载静态链寄存器,然后跳转到嵌套函数的实际地址。

  • Thunk - Thunk是一个子例程,用于将其他计算注入到另一个子例程中。Thunk主要用于将计算延迟到需要结果之前,或在其他子例程的开头或结尾插入操作

  • 备注 -备注功能“记住”与某些特定输入集相对应的结果。带有记忆输入的后续调用将返回记忆结果,而不是重新计算该结果,从而消除了除具有该参数的函数的首次调用之外的所有调用给定参数的主要成本。

很不客气,一个retpoline蹦床回报形实转换,以“ 糟蹋memoization的间接分支预测。

资料来源:retpoline包含Intel的PAUSE指令,但是AMD需要LFENCE指令,因为在该处理器上,PAUSE指令不是序列化指令,因此,推测它在等待返回时将使用多余的功率。错误地预测了正确的目标。

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

“每个处理器都有一个体系结构行为(记录的行为描述了指令的工作方式以及程序员编写程序所依赖的行为)和一个微体系结构行为(该体系结构的实际实现方式的行为)。这些行为可以细微地区别开来。例如,在架构上,从内存中的特定地址加载值的程序将等到知道该地址后再尝试执行加载,但是从微体系结构的角度来看,处理器可能会推测性地猜测该地址,以便它可以启动即使在绝对确定应该使用哪个地址之前,也要从内存中加载值(这很慢)。

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

摘自Intel的论文:“ Retpoline:缓解分支目标注入 ”(.PDF):

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

请注意,元素4是:“漏洞利用程序必须成功影响此间接分支,以推测方式错误地预测并执行一个小工具。由漏洞利用程序选择的该小工具通常会通过缓存定时,通过一条边通道泄漏机密数据。”

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.