CVE-2018-3639/CVE-2019-7308—Spectre攻击linux内核ebpf的分析
字数 2972 2025-08-05 19:10:02
eBPF与Spectre漏洞分析:CVE-2018-3639和CVE-2019-7308深入解析
前言
本文深入分析两个与eBPF相关的Spectre漏洞:CVE-2018-3639(Spectre变种4)和CVE-2019-7308(Spectre变种1补丁不完善导致的问题)。这些漏洞利用了现代CPU的推测执行特性,通过eBPF子系统实现侧信道攻击。
1. Spectre变种4 (CVE-2018-3639) 与eBPF
1.1 Speculative Store Bypass原理
Spectre变种4又称Speculative Store Bypass (SSB),其核心原理如下:
考虑以下代码序列:
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]
攻击场景:
- 第1行的MOV指令可能因地址计算等待而延迟执行
- CPU可能推测MOVZX指令(第2行)不依赖MOV指令(第1行)
- 在MOV指令执行前就执行MOVZX指令
- 导致加载了RSI+RCX处的旧数据而非更新后的数据
- 最终第4行使用了错误的数据
1.2 eBPF利用实现
Project Zero提供的利用CVE-2018-3639攻击eBPF的EXP需要对Linux内核进行修改:
- 在eBPF系统调用中添加处理0x13370001的特殊命令
- 添加
map_time_flush_loc函数,用于测量读取特定内存位置的时间 - 添加
bpf_clflush_mfence_protohelper函数,用于清空缓存
eBPF指令分析
关键eBPF指令及其伪代码注释:
-
初始化设置:
- 获取需要泄露的内存地址
- 设置
leak_map偏移2048处为基准点
-
关键操作序列:
r1 = r7并清空缓存r3 = fp-216(设置探测点)- 利用
*(u64 *)(r8) = r3执行慢的特性,提前执行r1 = *(u64 *)(r6)获取目标地址 r2 = *(u8 *)(r1)获取目标值
-
探测逻辑:
- 对于
sockfds[bit+8],*(u8 *)(fp-216) = 0x00 - 对于
sockfds[bit+0],*(u8 *)(fp-216) = 0xff - 根据读取
leak_map不同偏移的时间差判断bit值:- 读取
leak_map偏移2048+0x1000时间短 → bit=1 - 读取
leak_map偏移2048时间短 → bit=0
- 读取
- 对于
实际利用示例
泄露内核符号core_pattern的值:
$ 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
...
ffffffff9b2954e0: 0x63 ( 'c' )
ffffffff9b2954e1: 0x6f ( 'o' )
ffffffff9b2954e2: 0x72 ( 'r' )
ffffffff9b2954e3: 0x65 ( 'e' )
ffffffff9b2954e4: 0x00 ( '' )
2. Spectre变种1补丁不完善导致的CVE-2019-7308
2.1 Bounds Check Bypass原理
Spectre变种1又称Bounds Check Bypass (BCB),其核心原理:
考虑以下代码:
if (untrusted_offset_from_caller < arr1->length) {
unsigned char value = arr1->data[untrusted_offset_from_caller];
unsigned long index2 = ((value&1)*0x100)+0x200;
if (index2 < arr2_length) {
unsigned char value2 = arr2->data[index2];
}
}
攻击场景:
- CPU可能不检查if条件就推测其为真
- 提前将
arr1->data[untrusted_offset_from_caller]加载到缓存 - 即使发现推测错误回滚指令,缓存状态仍保留
- 通过侧信道攻击可获取越界访问的数据
2.2 eBPF补丁不完善问题
原始补丁措施:
- 在每个数组元素访问操作中将索引与掩码相与,避免越界访问
- 对于非CAP_SYS_ADMIN用户创建的map,在JIT代码中添加相同操作
补丁缺陷:
- 未正确处理数组指针计算的情况
- 例如
ptr += val或val += ptr等操作未充分保护
2.3 利用方式分析
利用程序包含两个关键结构体:
mem_leaker_prog:主攻击程序array_timed_reader_prog:用于时间测量
攻击步骤:
- 设置
mem_leaker_prog.data_map偏移0x1200、0x2000和0x3000处为1 - 通过
leak_byte函数调用leak_bit逐bit泄露内存
eBPF指令分析
关键指令序列:
- 获取
12-bit_index和byte_offset参数 - 读取
data_map偏移0x1200处的值(已知为1) - 通过
BPF_AND和BPF_OR延长执行时间,促使BPF_JGT推测执行失败 r4 = *(u8 *)(r4)读取目标内存- 根据bit值:
- bit=1:访问
data_map偏移0x3000 - bit=0:访问
data_map偏移0x2000
- bit=1:访问
- 通过测量两个偏移的访问时间差确定bit值
2.4 完整补丁方案
完整补丁需要考虑:
- 值可能存在于源寄存器(
ptr+=val)或目的寄存器(val+=ptr) - limit取决于ALU操作类型(加/减)和偏移量正负
retrieve_ptr_limit函数逻辑:
- 加法操作但偏移为负,或减法操作但偏移为正 → 视为减法,
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)
- 减法:
fixup_bpf_calls函数:
- 根据计算的limit对寄存器值进行掩码运算
- 彻底防止越界访问
3. 总结与防护建议
关键发现
- eBPF子系统成为Spectre漏洞的重要攻击向量
- 原始补丁未全面考虑所有指针运算场景
- 通过精心构造的eBPF程序可绕过边界检查
- 侧信道计时攻击是核心利用手段
防护措施
- 及时更新内核,应用完整补丁
- 限制非特权用户的eBPF使用
- 启用所有Spectre变种的缓解措施
- 监控异常的eBPF程序行为
进一步研究资源
CPU漏洞相关研究资料:
https://github.com/houjingyi233/CPU-vulnerability-collections
参考资料
- Reading privileged memory with a side-channel
- 深入分析Ubuntu本地提权漏洞—【CVE-2017-16995】
- http://man7.org/linux/man-pages/man2/bpf.2.html
- Linux CVE-2017-16995整数扩展问题导致提权漏洞分析
- Issue 1711: Linux: eBPF Spectre v1 mitigation is insufficient
- https://www.kernel.org/doc/Documentation/networking/filter.txt
- Analysis and mitigation of speculative store bypass (CVE-2018-3639)
- Issue 1528: speculative execution, variant 4: speculative store bypass