ChatGPT解决这个技术问题 Extra ChatGPT

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

为了缓解内核或跨进程内存泄露(Spectre 攻击),the Linux kernel1 will be compiled with a new option-mindirect-branch=thunk-externgcc 引入了通过所谓的 retpoline 执行间接调用。

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

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

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

来自 Google 的有趣support article
哦,所以发音为 /ˌtræmpəˈlin/(美式)或 /ˈtræmpəˌliːn/(英式)
您可能会提到这是 Linux 内核,尽管 gcc 指出了这一点!我没有在 Linux Kernel Mailing List 站点上识别出 lkml.org/lkml/2018/1/3/780,甚至在我查看该站点时都没有(并且因为它处于脱机状态而获得了快照)。
@PJTraill - 添加了一个 Linux 内核标签
@PJTraill - 好点,我更新了问题文本。请注意,我首先在 Linux 内核中看到了它,因为它是相对开放的开发过程,但毫无疑问,相同或相似的技术正被用作各种开源和闭源操作系统的缓解措施。所以我不认为这是特定于 Linux 的,但链接肯定是。

C
Community

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

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

解决此问题的基本方法见 Andi Kleen's kernel branch

它引入了新的 __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

thunk 本身如下所示:

    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 会导致管道停止,所以我在这里读了几句。

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

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

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

更新:在 kernel mailing list,有一个持续的讨论让我相信 retpolines 不能完全缓解分支预测问题,因为当返回堆栈缓冲区 (RSB) 运行为空时,更新的英特尔架构 (Skylake+ ) 回退到易受攻击的分支目标缓冲区 (BTB):

Retpoline 作为一种缓解策略,将间接分支换成回报,以避免使用来自 BTB 的预测,因为它们可能被攻击者毒害。 Skylake+ 的问题在于 RSB 下溢回退到使用 BTB 预测,这允许攻击者控制推测。


我不认为 LFENCE 指令很重要,谷歌的实现使用了 PAUSE 指令。 support.google.com/faqs/answer/7625886 请注意,您引用的文档说“不会执行”而不是“不会被推测执行”。
从那个谷歌常见问题页面:“我们上面的推测循环中的暂停指令不是正确性所必需的。但这确实意味着非生产性推测执行在处理器上占用的功能单元更少。”因此,它不支持您的结论,即 LFENCE 是这里的关键。
@RossRidge 我部分同意,对我来说,这看起来像是无限循环的两种可能实现,提示 CPU 在 PAUSE/LFENCE 之后不会推测性地执行代码。但是,如果 LFENCE 是推测性地执行的,并且因为推测是正确的而没有回滚,这将与仅在内存加载完成后才会执行的说法相矛盾。 (否则,已经被推测执行的整套指令将不得不回滚并再次执行以符合规范)
这具有 push / ret 的优点,即它不会使返回地址预测器堆栈失衡。有一个错误预测(在使用实际返回地址之前转到 lfence),但使用 call + 修改 rsp 平衡了 ret
哎呀,优势超过 push / ret(在我的最后评论中)。回复:您的编辑:RSB 下溢应该是不可能的,因为 retpoline 包含 call。如果内核抢占在那里进行了上下文切换,我们将使用从 call 启动的 RSB 恢复执行到调度程序中。但也许中断处理程序可以以足够的 ret 结束以清空 RSB。
D
Davide Maggioni

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 的论文 Spectre Attacks: Exploiting Speculative Execution 概述了如何利用间接分支:

利用间接分支。从面向返回的编程 (ROP) 中提取,在这种方法中,攻击者从受害者的地址空间中选择一个小工具,并影响受害者推测性地执行小工具。与 ROP 不同,攻击者不依赖受害者代码中的漏洞。相反,攻击者训练分支目标缓冲区 (BTB) 以错误预测从间接分支指令到小工具地址的分支,从而导致小工具的推测执行。虽然推测性执行的指令被放弃,但它们对高速缓存的影响不会恢复。小工具可以使用这些效果来泄露敏感信息。我们展示了如何通过仔细选择小工具,使用此方法从受害者那里读取任意内存。为了误导 BTB,攻击者在受害者的地址空间中找到小工具的虚拟地址,然后执行间接分支到该地址。这种训练是从攻击者的地址空间完成的,不管攻击者地址空间中的小工具地址是什么;所需要的只是用于训练分支的分支使用相同的目标虚拟地址。 (其实只要攻击者处理异常,即使攻击者的地址空间中gadget的虚拟地址没有映射到任何代码,攻击也可以进行。)同样不需要源地址完全匹配用于训练的分支的地址和目标分支的地址。因此,攻击者在设置训练方面具有很大的灵活性。

Google 零项目团队的一篇名为 Reading privileged memory with a side-channel 的博客文章提供了另一个示例,说明如何使用分支目标注入来创建有效的漏洞利用。


C
Community

这个问题是不久前提出的,值得一个更新的答案。

Executive Summary

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

单词“retpoline”是单词“return”和“trampoline”的portmanteau,很像改进“relpoline”是从“relative call”和“trampoline”中创造出来的。它是一个使用返回操作构建的蹦床结构,它还象征性地确保任何相关的投机执行将无休止地“反弹”。

为了缓解内核或跨进程内存泄露(Spectre 攻击),Linux 内核 [1] 将使用新选项进行编译,将 -mindirect-branch=thunk-extern 引入 gcc 以通过 so 执行间接调用-称为retpoline。 [1] 但是,它不是特定于 Linux 的——类似或相同的构造似乎被用作其他操作系统缓解策略的一部分。

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

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

before Jan 4 2018 以来,LLVM compiler 有一个 -mretpoline 开关。该日期是漏洞出现的时间为 first publically reported。 GCC made their patches available 2018 年 1 月 7 日。

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

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

首先,几个定义:

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

Thunk - thunk 是用于将附加计算注入另一个子程序的子程序。 Thunks 主要用于延迟计算直到需要它的结果,或者在其他子程序的开头或结尾插入操作

记忆 - 记忆功能“记住”与某些特定输入集相对应的结果。具有记住输入的后续调用返回记住的结果而不是重新计算它,从而消除了使用给定参数调用的主要成本,除了使用这些参数对函数进行的第一次调用。

非常粗略地说,retpoline 是一个以 thunk 形式返回的蹦床,以“破坏”间接分支预测器中的记忆。

Source:retpoline 包含一条针对 Intel 的 PAUSE 指令,但对于 AMD 而言,一条 LFENCE 指令是必需的,因为在该处理器上,PAUSE 指令不是序列化指令,因此暂停/jmp 循环将使用过多的功率,因为它被推测为等待返回错误预测到正确的目标。

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

“每个处理器都有一个架构行为(描述指令如何工作以及程序员依赖于编写程序的记录行为)和一个微架构行为(架构的实际实现行为方式)。这些可能会以微妙的方式出现差异。例如,在架构上,从内存中的特定地址加载值的程序将等待直到知道地址后再尝试执行加载。然而,在微架构上,处理器可能会尝试推测性地猜测地址以便它可以启动即使在绝对确定应该使用哪个地址之前从内存中加载值(这很慢)。如果处理器猜错了,它将忽略猜测的值并再次执行加载,这次使用正确的地址。因此保留了架构定义的行为。但是这种错误的猜测会干扰处理器的其他部分——特别是缓存的内容。这些微控制器可以通过计时访问应该(或不应该)在缓存中的数据所需的时间来检测和测量架构干扰,从而允许恶意程序对存储在内存中的值进行推断。”。

来自英特尔的论文:“Retpoline: A Branch Target Injection Mitigation”(.PDF):

“retpoline 序列可防止处理器的推测执行使用“间接分支预测器”(一种预测程序流的方式)推测由漏洞控制的地址(满足分支目标注入的五个元素中的元素 4(Spectre 变体 2) )利用上面列出的组合)。”。请注意,元素 4 是:“漏洞利用必须成功地影响这个间接分支,以推测性地错误预测并执行小工具。漏洞利用选择的这个小工具通过侧通道泄漏秘密数据,通常是通过缓存定时。”。