什么是retpoline?它是如何工作的?

时间:2018-01-04 05:52:05

标签: security assembly x86 cpu-architecture

为了减轻内核或跨进程内存泄露(Spectre攻击),the Linux kernel1 will be compiled with a new option-mindirect-branch=thunk-extern引入gcc以执行间接调用 - 叫做 retpoline

这似乎是一个新发明的术语,因为谷歌搜索仅在最近的使用中出现(通常都是在2018年)。

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

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

3 个答案:

答案 0 :(得分:145)

sgbj在Google撰写的评论中提到的{p {3}}更详细地解释了以下内容,但我会试一试:

至于我可以从目前有限的信息中将它拼凑在一起,一个retpoline是一个返回trampoline ,它使用一个永远不会执行的无限循环来防止CPU在目标上进行推测间接跳跃。

在解决此问题的The article中可以看到基本方法:

它引入了新的Andi Kleen's kernel branch调用,它加载了调用目标,其内存地址(我称之为ADDR)存储在堆栈顶部并使用{执行跳转{1}}指示。然后使用__x86.indirect_thunk宏调用thunk本身,该宏用于替换许多(如果不是全部)间接调用和跳转。宏只是将调用目标放在堆栈上并在必要时正确设置返回地址(注意非线性控制流程):

RET

最后放置.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

thunk本身如下:

call

控制流程在这里可能会有点混乱,所以让我澄清一下:

  • call retpoline_call_target 2: lfence /* stop speculation */ jmp 2b retpoline_call_target: lea 8(%rsp), %rsp ret 将当前指令指针(标签2)推送到堆栈。
  • call堆栈指针添加8,有效地丢弃最近推送的四字,这是最后一个返回地址(标记为2)。在此之后,堆栈顶部再次指向实际返回地址ADDR。
  • lea跳转到ret并将堆栈指针重置为调用堆栈的开头。

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

标签2之后的部分实际上从未被执行过,它只是一个无限循环,理论上会用ret指令填充指令管道。通过使用JMPLFENCE或更一般地,导致指令流水线停止的指令停止CPU在该推测执行上浪费任何功率和时间。这是因为如果对retpoline_call_target的调用将正常返回,PAUSE将是下一条要执行的指令。这也是分支预测器将根据原始返回地址(标签2)

预测的内容

引用英特尔架构手册:

  

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

但请注意,规范从未提及LFENCE和PAUSE导致管道停滞,所以我在这里读取了一些内容。

现在回到原来的问题: 由于两个想法的结合,内核内存信息泄露是可能的:

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

  • Intel CPU的间接分支预测器仅使用源指令的最低12位,因此很容易用用户控制的内存地址中毒所有2 ^ 12个可能的预测历史。然后,当在内核中预测间接跳转时,可以使用内核权限推测性地执行这些跳转。因此,使用缓存时序侧通道可以泄漏任意内核内存。

更新:在NOSPEC_JMP/CALL上,有一个持续的讨论让我相信retpolines不能完全缓解分支预测问题,就像返回堆栈缓冲区(RSB)运行时一样,最近的英特尔架构(Skylake +)回归到易受攻击的分支目标缓冲区(BTB):

  

Retpoline作为缓解策略交换间接分支以获得回报,   避免使用来自BTB的预测,因为它们可以   被攻击者毒死。   Skylake +的问题是RSB下溢回退到使用a   BTB预测,允许攻击者控制猜测。

答案 1 :(得分:42)

retpoline旨在防范branch target injection (CVE-2017-5715)攻击。这是一种攻击,其中内核中的间接分支指令用于强制推测执行任意一块代码。选择的代码是一个对攻击者有用的“小工具”。例如,可以选择代码,以便通过它如何影响缓存来泄漏内核数据。 retpoline通过简单地用返回指令替换所有间接分支指令来防止这种利用。

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

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

请注意,retpoline不会直接阻止内核信息泄露,它只会阻止间接分支指令用于推测性地执行会泄露信息的小工具。如果攻击者可以找到其他方法来推测性地执行小工具,那么retpoline不会阻止攻击。

Paul Kocher,Daniel Genkin,Daniel Gruss,Werner Haas,Mike Hamburg撰写的论文Spectre Attacks: Exploiting Speculative Execution, Moritz Lipp,Stefan Mangard,Thomas Prescher,Michael Schwarz和Yuval Yarom给出了如何利用间接分支的以下概述:

  

利用间接分支。从面向回归的编程中进行绘制   (ROP),在此方法中,攻击者从地址中选择小工具   受害者的空间并影响受害者执行小工具   推测。与ROP不同,攻击者不依赖于   受害者代码中的漏洞。相反,攻击者训练   分支目标缓冲区(BTB)从间接错误预测分支   分支指令到小工具的地址,产生一个   投机执行小工具。虽然在推测上执行了   指令被放弃,它们对缓存的影响不大   恢复。小工具可以使用这些效果来泄漏敏感信息   信息。我们通过仔细选择小工具来展示这一点   方法可用于从受害者那里读取任意内存。

     

要   在攻击BTB时,攻击者会找到小工具的虚拟地址   在受害者的地址空间中,然后执行间接分支   地址。此培训是从攻击者的地址空间完成的,并且   什么位于小工具地址并不重要   攻击者的地址空间;所需要的就是使用的分支   用于培训分支机构以使用相同的目标虚拟地址。 (在   事实上,只要攻击者处理异常,攻击就可以起作用   即使没有代码映射到小工具的虚拟地址   在攻击者的地址空间。)也没有必要完整   匹配用于训练的分支的源地址和   目标分支的地址。因此,攻击者具有重要意义   建立培训的灵活性。

Google的Project Zero团队标题为Reading privileged memory with a side-channel的博客条目提供了另一个分支目标注入如何用于创建工作漏洞的示例。

答案 2 :(得分:5)

这个问题是前一段时间提出的,应该得到一个新的答案。

Executive Summary

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

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

  

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

     

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

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

  

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

LLVM compiler起,before Jan 4 2018进行了-mretpoline切换。该漏洞发生的日期是first publically reported。 GCC made their patches available,2018年1月7日。

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

  

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

首先,一些定义:

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

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

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

非常粗略地讲,水上运动 蹦床 ,带有返回作为重击 strong>,在间接分支预测变量中为“ 破坏 记忆化

Source:retpoline包含Intel的PAUSE指令,但是AMD需要LFENCE指令,因为在该处理器上,PAUSE指令不是序列化指令,因此,pause / jmp循环将使用多余的功率作为推测它是在等待返回错误的预测到正确的目标。

Arstechnica对问题进行了简单的解释:

  

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

     

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

摘自Intel的论文:“ Retpoline: A Branch Target Injection Mitigation”(.PDF):

  

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

     

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