CVE-2018-3639/CVE-2019-7308—Spectre攻击linux内核ebpf的分析

栏目: 服务器 · Linux · 发布时间: 5年前

内容简介:为了避免重复,这里只根据微软的文章简述Speculative Store Bypass的原理。考虑下面的代码。

前言

下图是bleepingcomputer上总结的一些CPU漏洞,已经有许多博客和论文分析了这些漏洞。值得注意的是,ebpf的Spectre变种1(CVE-2017-5753)的补丁并没有考虑全面,导致了CVE-2019-7308。对于Spectre变种4(CVE-2018-3639)Jann Horn也给出了一个需要patch内核的利用ebpf的POC。这篇文章主要分析这两个漏洞ebpf的利用方式。在一些漏洞分析的文章中对ebpf有所提及,所以这里也不再赘述,不清楚的读者在阅读接下来的部分之前最好自行查看参考资料。

CVE-2018-3639/CVE-2019-7308—Spectre攻击 <a href='https://www.codercto.com/topics/18170.html'>linux</a> 内核ebpf的分析

CVE-2018-3639利用ebpf的攻击方式

为了避免重复,这里只根据微软的文章简述Speculative Store Bypass的原理。考虑下面的代码。

01: 88040F            mov [rdi+rcx],al
02: 4C0FB6040E        movzx r8,byte [rsi+rcx]
03: 49C1E00C          shl r8,byte 0xc
04: 428B0402          mov eax,[rdx+r8]

如果CPU推测加载指令与之前的存储指令无关,那么可以在存储指令执行之前执行加载指令。第1行的MOV指令在特殊情况下可能需要额外的时间来执行(如果计算RDI+RCX的地址表达式正在等待先前的指令执行)。在这种情况下,CPU可能会推测MOVZX指令不依赖于MOV指令,并且可能在MOV指令执行之前执行MOVZX指令。这可能会导致位于RSI+RCX的内存中的旧数据被加载到R8中,从而导致第四行代码使用了错误的数据。

project zero给出的利用CVE-2018-3639攻击ebpf的EXP需要patch linux内核源码,先来看看改动的内容。在ebpf系统调用中增加了处理0x13370001的情况,处理这个新增的cmd用的是 map_time_flush_loc 函数。

CVE-2018-3639/CVE-2019-7308—Spectre攻击linux内核ebpf的分析

该函数读取 ptr + attr->flags 指向的内容,使用rdtscp计算读取的时间。

CVE-2018-3639/CVE-2019-7308—Spectre攻击linux内核ebpf的分析

EXP的 array_time_flush_loc 函数中用到这部分逻辑计算读取mapfd中偏移 idx+off 处内容需要的时间。

CVE-2018-3639/CVE-2019-7308—Spectre攻击linux内核ebpf的分析

还有就是在 bpf_base_func_proto 函数中加了一个helper函数 bpf_clflush_mfence_proto ,它的主要作用是清空缓存。

CVE-2018-3639/CVE-2019-7308—Spectre攻击linux内核ebpf的分析 CVE-2018-3639/CVE-2019-7308—Spectre攻击linux内核ebpf的分析

EXP中使用 BPF_CALL 可以调用到这部分逻辑。

CVE-2018-3639/CVE-2019-7308—Spectre攻击linux内核ebpf的分析

EXP中我们先看ebpf指令部分,旁边我已经加上了伪代码注释。首先是一些设置。

CVE-2018-3639/CVE-2019-7308—Spectre攻击linux内核ebpf的分析

然后拿到需要泄露的内存的地址。之前 leak_bit 函数调用了 array_set_dw(input_map, 0, addr) 对此进行了设置。

CVE-2018-3639/CVE-2019-7308—Spectre攻击linux内核ebpf的分析

设置r9为 leak_map 偏移2048处。

CVE-2018-3639/CVE-2019-7308—Spectre攻击linux内核ebpf的分析

设置r1=r7,然后清一下缓存。

CVE-2018-3639/CVE-2019-7308—Spectre攻击linux内核ebpf的分析

设置r3=fp-216。

CVE-2018-3639/CVE-2019-7308—Spectre攻击linux内核ebpf的分析

接下来就是最关键的部分了。因为 *(u64 *)(r8)=r3 执行起来比较慢,所以提前执行了 r1=*(u64 *)(r6) 取到了需要泄露的内存的地址,然后 r2=*(u8 *)(r1) 取到了其中的值。

CVE-2018-3639/CVE-2019-7308—Spectre攻击linux内核ebpf的分析

后面的代码需要结合 leak_bit 函数理解。对于sockfds[16]来说根据最开始的 BPF_ST_MEM(BPF_B, BPF_REG_FP, -216, dummy_ff ? 0x00 : 0xff) 指令可以知道sockfds[bit+8]的 *(u8 *)(fp-216)=0x00 ,sockfds[bit+0]的 *(u8 *)(fp-216)=0xff 。对于sockfds[bit+8]和sockfds[bit+0],它们的selected_bit=bit&7是相同的,如果取到的值是1,r2=0x1000,那么最后 r0=*(u8 *)(r9)= r0=*(u8 *)( leak_map偏移2048+0x1000) ;如果取到的值是0,r2=0,那么最后 r0=*(u8 *)(r9)= r0=*(u8 *)( leak_map偏移2048) 。而sockfds[bit+8]没有推测执行的话 r0=*(u8 *)( leak_map偏移2048) ;sockfds[bit+0]没有推测执行的话 r0=*(u8 *)( leak_map偏移2048+0x1000) 。所以如果sockfds[bit+8]读取 leak_map 偏移2048+0x1000需要的时间短,说明取到的值应该是1;如果sockfds[bit+0]读取 leak_map 偏移2048需要的时间短,说明取到的值应该是0。

CVE-2018-3639/CVE-2019-7308—Spectre攻击linux内核ebpf的分析

后面再在 leak_byte 函数中进一步调用 leak_bit 函数一个一个bit拼起来就得到了泄露的byte。

CVE-2018-3639/CVE-2019-7308—Spectre攻击linux内核ebpf的分析

用该EXP泄露指定地址的数据如下。

$ sudo grep core_pattern /proc/kallsyms
ffffffff9b2954e0 D core_pattern
$ gcc -o bpf_store_skipper_assisted bpf_store_skipper_assisted.c
$ time ./bpf_store_skipper_assisted ffffffff9b2954e0 5
BPF PROG LOADED SUCCESSFULLY
BPF PROG LOADED SUCCESSFULLY
BPF PROG LOADED SUCCESSFULLY
BPF PROG LOADED SUCCESSFULLY
BPF PROG LOADED SUCCESSFULLY
BPF PROG LOADED SUCCESSFULLY
BPF PROG LOADED SUCCESSFULLY
BPF PROG LOADED SUCCESSFULLY
BPF PROG LOADED SUCCESSFULLY
BPF PROG LOADED SUCCESSFULLY
BPF PROG LOADED SUCCESSFULLY
BPF PROG LOADED SUCCESSFULLY
BPF PROG LOADED SUCCESSFULLY
BPF PROG LOADED SUCCESSFULLY
BPF PROG LOADED SUCCESSFULLY
BPF PROG LOADED SUCCESSFULLY
4 vs 96
1 vs 99
100 vs 0
100 vs 0
100 vs 0
2 vs 98
0 vs 100
100 vs 0
ffffffff9b2954e0: 0x63 ('c')
2 vs 98
1 vs 99
1 vs 99
1 vs 99
100 vs 0
2 vs 98
0 vs 100
100 vs 0
ffffffff9b2954e1: 0x6f ('o')
100 vs 0
3 vs 97
100 vs 0
100 vs 0
1 vs 99
2 vs 98
0 vs 100
100 vs 0
ffffffff9b2954e2: 0x72 ('r')
2 vs 98
100 vs 0
0 vs 100
100 vs 0
100 vs 0
0 vs 100
0 vs 100
100 vs 0
ffffffff9b2954e3: 0x65 ('e')
100 vs 0
100 vs 0
100 vs 0
100 vs 0
100 vs 0
100 vs 0
100 vs 0
100 vs 0
ffffffff9b2954e4: 0x00 ('')

real    0m31.591s
user    0m2.547s
sys     0m27.429s

ebpf中CVE-2017-5753的补丁不完善导致的CVE-2019-7308

我们仍然只根据project zero的文章简述Bounds Check Bypass的原理。考虑下面的代码。

CVE-2018-3639/CVE-2019-7308—Spectre攻击linux内核ebpf的分析

CPU可能会不检查if语句中的条件就推测其为真,先将下一步指令所需要的内存加载到缓存之中,假如发现推测错误再回滚指令。但是这时CPU缓存并没有清空,通过侧信道攻击可以取到 arr1->data[untrusted_offset_from_caller] 中的值。

在ebpf系统中为了修补这个漏洞,在每个需要取数组元素的操作中都将数组的索引和掩码相与避免越界访问。

CVE-2018-3639/CVE-2019-7308—Spectre攻击linux内核ebpf的分析

如果map不是由具有 CAP_SYS_ADMIN 权限的用户创建的,还会为JIT代码添加同样的操作。

CVE-2018-3639/CVE-2019-7308—Spectre攻击linux内核ebpf的分析

补丁对数组中的指针计算并没有作用,来看看EXP是如何利用这一点的。EXP中有两个重要的结构体, mem_leaker_progarray_timed_reader_prog

CVE-2018-3639/CVE-2019-7308—Spectre攻击linux内核ebpf的分析 CVE-2018-3639/CVE-2019-7308—Spectre攻击linux内核ebpf的分析

首先设置 mem_leaker_prog.data_map 的偏移0x1200,0x2000和0x3000处为1。

CVE-2018-3639/CVE-2019-7308—Spectre攻击linux内核ebpf的分析

leak_byte 函数中依次调用 leak_bit 函数得到泄露出指定地址的内存。

CVE-2018-3639/CVE-2019-7308—Spectre攻击linux内核ebpf的分析 CVE-2018-3639/CVE-2019-7308—Spectre攻击linux内核ebpf的分析

下面来看 trigger_proc(leakprog->sockfd) 调用的ebpf指令。首先,取到之前 array_set_2dw(leakprog->control_map, 0, 12-bit_index, byte_offset) 设置的 12-bit_indexbyte_offset

CVE-2018-3639/CVE-2019-7308—Spectre攻击linux内核ebpf的分析

mem_leaker_prog.data_map 的偏移0x1200处的值,前面说了这个值被设置为1, BPF_ANDBPF_OR 对值没有影响,只是为了延长时间,使得之后的 BPF_JGT 指令CPU推测执行为false。

CVE-2018-3639/CVE-2019-7308—Spectre攻击linux内核ebpf的分析

通过 r4=*(u8 *)(r4) 读到了泄露地址的内存,左移 12-bit_index 位之后和0x1000相与,如果 bit_index 位的值是1,结果是0x1000,加上0x2000导致 mem_leaker_prog.data_map 偏移0x3000处的值被加载到了缓存;如果 bit_index 位的值是0,结果是0x0,加上加上0x2000导致 mem_leaker_prog.data_map 偏移0x2000处的值被加载到了缓存。

CVE-2018-3639/CVE-2019-7308—Spectre攻击linux内核ebpf的分析

返回到 leak_bit 函数中,接下来分别去算读 mem_leaker_prog.data_map 偏移0x2000处的值需要的时间times[0]和 mem_leaker_prog.data_map 偏移0x3000处的值需要的时间times[1]。times[0] < times[1]说明 bit_index 位的值是0,times[0] > times[1]说明 bit_index 位的值是1。ebpf指令非常简单,结合注释可以很容易看懂。

CVE-2018-3639/CVE-2019-7308—Spectre攻击linux内核ebpf的分析 CVE-2018-3639/CVE-2019-7308—Spectre攻击linux内核ebpf的分析

对于这种情况的补丁,掩码需要考虑的情况有很多,要处理的值可能存在于源寄存器中(如 ptr+=val ),也可以存在于目的寄存器中(如 val+=ptr );limit取决于ALU操作是加或减以及偏移量的正负。在补丁增加的 retrieve_ptr_limit 函数中如果ALU操作是加但是偏移是负的,或者ALU操作是减但是偏移是正的说明是减法, mask_to_left 被设置为1;否则说明是加法, mask_to_left 被设置为0。对于map类型的指针减法的 limit=ptr_reg->umax_value+ptr_reg->off ,加法的 limit=ptr_reg->map_ptr->value_size–(ptr_reg->smin_value+ptr_reg->off) ;对于stack类型的指针减法的 limit=MAX_BPF_STACK+off ,加法的 limit=-off ,其中 off=ptr_reg->off+ptr_reg->var_off.value

CVE-2018-3639/CVE-2019-7308—Spectre攻击linux内核ebpf的分析

fixup_bpf_calls 函数中根据前面计算的limit对寄存器中的值进行掩码运算从而避免越界访问。

CVE-2018-3639/CVE-2019-7308—Spectre攻击linux内核ebpf的分析

参考


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

C# 6.0本质论

C# 6.0本质论

[美] Mark Michaelis(马克·米凯利斯)、[美] Eric Lippert(埃里克·利珀特) / 周靖、庞燕 / 人民邮电出版社 / 2017-2-1 / 108

这是C#领域中一部广受好评的名作,作者用一种易于理解的方式详细介绍了C#语言的各个方面。全书共有21章和4个附录(其中哟2个附录从网上下载),介绍了C#语言的数据类型、操作符、方法、类、接口、异常处理等基本概念,深入讨论了泛型、迭代器、反射、线程和互操作性等高级主题,还介绍了LINQ技术,以及与其相关的扩展方法、分部方法、Lambda表达式、标准查询操作符和查询表达式等内容。每章开头的“思维导图”......一起来看看 《C# 6.0本质论》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

随机密码生成器
随机密码生成器

多种字符组合密码

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码