Linux LTS 版本内核 CPU Spectre 侧信道漏洞补丁分析
2019-09-10 12:31:37 Author: www.4hou.com(查看原文) 阅读量:108 收藏

导语:通过这篇文章我将深入分析最近的一个Specter漏洞补丁程序,还会介绍我们是如何独立挖到此漏洞的以及我们的Respectre插件是如何自动修复这个底层漏洞的。

通过这篇文章我将深入分析最近的一个Specter漏洞补丁程序,其中一个补丁是手动打到Linux内核中的。我将介绍此修复程序所采取的方法,从其发出警告到向后移植到Long Term Support (LTS) kernels时被破坏。我们将研究后端漏洞的原理以及导致这种backporting失败的upstream过程中的bug。还会介绍我们是如何独立挖到此漏洞的以及我们的Respectre插件是如何自动修复这个底层漏洞的。

我能够找到的最早版本的补丁程序是来自于Dianzhang Chen的这个补丁,它是在2019年5月24日被发布的。它通过ptrace系统调用来解决具有用户控制索引的数组的推测访问。最初的解决方案还是挺好的,但Thomas Gleixner提出补丁可以被绕过,一个月后,他们又发布了补丁的第二个版本。

由于补丁的细节很重要,我做了一个比较:

 diff --git a/arch/x86/kernel/ptrace.c b/arch/x86/kernel/ptrace.c
 index a166c96..cbac646 100644
 --- a/arch/x86/kernel/ptrace.c
 +++ b/arch/x86/kernel/ptrace.c
 @@ -25,6 +25,7 @@
  #include <linux/rcupdate.h>
  #include <linux/export.h>
  #include <linux/context_tracking.h>
 +#include <linux/nospec.h>
  
  #include <linux/uaccess.h>
  #include <asm/pgtable.h>
 @@ -643,9 +644,11 @@ static unsigned long ptrace_get_debugreg(struct task_struct *tsk, int n)
  {
   struct thread_struct *thread = &tsk->thread;
   unsigned long val = 0;
 + int index = n;
  
   if (n < HBP_NUM) {
 -  struct perf_event *bp = thread->ptrace_bps[n];
 +  index = array_index_nospec(index, HBP_NUM);
 +  struct perf_event *bp = thread->ptrace_bps[index];
  
    if (bp)
     val = bp->hw.info.address;

可以看到,代码块的开头在声明bp指针之前初始化了索引变量。

这导致下面的编译器警告:

 arch/x86/kernel/ptrace.c: In function 'ptrace_get_debugreg':
 arch/x86/kernel/ptrace.c:705:3: warning: ISO C90 forbids mixed declarations and code [-Wdeclaration-after-statement]
    struct perf_event *bp = thread->ptrace_bps[index];
    ^~~~~~

尽管有这个警告,但这段代码被逐字地合并到了Thomas Gleixner的x86 / tip tree中,可以在这里看到。此前合并的5.3-RC1的补丁,Linus Torvalds发现警告的LKML邮件列表。但是,当对tree做实际合并时,没有提到对补丁的更新。

LTS内核中采用的修复方法是通过简单地交换顺序:

 +               struct perf_event *bp = thread->ptrace_bps[index];
 +               index = array_index_nospec(index, HBP_NUM);

为了解释为什么说这个补丁是失效的,先看一下array_index_nospec()API。此函数获取索引值以及索引的第一个较大的越界值。如果不使用条件控制流,它会将该索引转换为原始索引值(如果在边界内)或零(如果超出边界)。通过确保原始索引的所有后续使用改为使用从此API返回的值,可以防止具有越界索引值的推测路径的破坏。

但是,在索引array_index_nospec ()宏之前,“index”用作ptrace_bps数组的索引。此外,由于“索引”在函数中没有后续使用,整个array_index_nospec()操作通常被编译器视为dead store,并通过称为死存储消除(DSE)的优化传递消除。由于在执行array_index_nospec()的某些内联汇编中使用了“volatile”关键字,后一种效果最终不会发生。

我们可以通过查看函数的反汇编来看下一:

 .text:0000000000000648 ptrace_get_debugreg proc near           ; CODE XREF: getreg32+EF
 .text:0000000000000648                                         ; arch_ptrace+6E
 .text:0000000000000648                 push    rbp
 .text:0000000000000649                 cmp     esi, 3 ; esi = index (or n), this is the if (n < HBP_NUM) check
 .text:000000000000064C                 mov     rbp, rsp
 .text:000000000000064F                 jg      short loc_673
 .text:0000000000000651                 movsxd  rsi, esi ; rsi = sign-extended index
 .text:0000000000000654 ; rdx is 'bp' here, using rsi derived from user-provided one, with no speculation barrier
 .text:0000000000000654                 mov     rdx, [rdi+rsi*8+800h]
 .text:000000000000065C ; here begins the unused array_index_nospec()
 .text:000000000000065C                 cmp     rsi, 4 ; from arch/x86/include/asm/barrier.h asm volatile
 .text:0000000000000660                 sbb     rsi, rsi ; from arch/x86/include/asm/barrier.h asm volatile
 .text:0000000000000663                 xor     eax, eax ; fallout from arch/x86/include/asm/barrier.h asm volatile
 .text:0000000000000665                 test    rdx, rdx
 .text:0000000000000668                 jz      short loc_68F
 .text:000000000000066A                 mov     rax, [rdx+138h]
 .text:0000000000000671                 jmp     short loc_68F
 .text:0000000000000673 ; ---------------------------------------------------------------------------
 .text:0000000000000673
 .text:0000000000000673 loc_673:                                ; CODE XREF: ptrace_get_debugreg+7
 .text:0000000000000673                 cmp     esi, 6
 .text:0000000000000676                 jnz     short loc_681
 .text:0000000000000678                 mov     rax, [rdi+820h]
 .text:000000000000067F                 jmp     short loc_68F
 .text:0000000000000681 ; ---------------------------------------------------------------------------
 .text:0000000000000681
 .text:0000000000000681 loc_681:                                ; CODE XREF: ptrace_get_debugreg+2E
 .text:0000000000000681                 xor     eax, eax
 .text:0000000000000683                 cmp     esi, 7
 .text:0000000000000686                 jnz     short loc_68F
 .text:0000000000000688                 mov     rax, [rdi+828h]
 .text:000000000000068F
 .text:000000000000068F loc_68F:                                ; CODE XREF: ptrace_get_debugreg+20
 .text:000000000000068F                                         ; ptrace_get_debugreg+29 ...
 .text:000000000000068F                 pop     rbp
 .text:0000000000000690                 retn
 .text:0000000000000690 ptrace_get_debugreg endp

Respectre编译器插件是世界上最先进,最有效,最高效的防御 CPU Spectre 侧信道攻击的工具。该插件使用高级静态分析自动查找潜在的Specter实例,并通过高性能检测消除它们。值

通过获取当前代码并修复漏洞后,可以得到以下输出:

 arch/x86/kernel/ptrace.c: In function 'ptrace_get_debugreg':
 arch/x86/kernel/ptrace.c:717:22: note: Spectre v1 array index bound '3'
    struct perf_event *bp = thread->ptrace_bps[n];
                       ^
 arch/x86/kernel/ptrace.c:717:22: note: Spectre v1 array index mask adjust: inc constbound: yes

我们为客户提供像Respectre这样的防御工具可以填补个人的临时手动修补留下的空白,这确实是有好处的。

最后要说的是,上游社区的开发者并没有发现这个漏洞,这么多人都参与其中,因此应该存在很多机会来阻止这种糟糕的修复技术被引入。然而,这个补丁漏洞不仅被发布,而且传播到了所有支持的稳定内核中,这表明上游的开发社区根本不像公众所认为的那样厉害。


文章来源: https://www.4hou.com/technology/20172.html
如有侵权请联系:admin#unsafe.sh