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. 漏洞类型分析

这类漏洞的共同特点是验证阶段取值范围计算错误,导致:

  1. 验证阶段计算x的取值范围为0 <= x <= 0x1000
  2. 实际运行时x的值可以是0x2000
  3. 导致内存越界读写

典型漏洞案例:

  • 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

漏洞利用构造

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
    // ...后续利用代码...
};

利用技巧

  1. 通过OR操作制造取值范围判断错误
  2. 利用右移操作进一步扩大取值范围差异
  3. 最终实现任意长度溢出读写
  4. 需要根据不同内核版本调整array_map_opsinit_pid_ns的偏移

4. GeekPwn 2020决赛kernel题分析

漏洞点

题目基于Linux kernel 5.8.6,关键修改:

  1. 删除了scalar_min_max_add函数的整数溢出检查
  2. 修改了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
    // ...后续利用代码...
};

实际挑战

  1. 内核编译时开启了结构体随机化
  2. 内核结构体变量偏移与正常编译不同
  3. 需要通过分析内核函数汇编手动计算各种偏移

5. 漏洞利用通用模式

  1. 取值范围欺骗:构造使验证器误判变量取值范围的代码
  2. 数学运算放大:通过加减、位运算等放大取值范围差异
  3. 内存操作突破:利用错误的取值范围进行越界内存访问
  4. 权限提升:修改关键内核数据结构实现提权

6. 防御建议

  1. 加强验证器对数学运算的边界检查
  2. 对取值范围转换添加更严格的验证
  3. 启用内核结构体随机化(虽然会增加利用难度,但不解决根本问题)
  4. 及时更新内核补丁

7. 学习资源

  1. BPF验证器fuzzing方法
  2. CVE-2020-8835分析
  3. Linux内核官方文档:BPF验证器实现细节

8. 总结

BPF模块漏洞的核心在于验证器对变量取值范围的错误计算。通过精心构造的BPF程序,可以欺骗验证器,实现内核内存的越界访问。这类漏洞的利用需要深入理解:

  1. BPF验证器的工作原理
  2. 取值范围的计算逻辑
  3. 数学运算对取值范围的影响
  4. 内核内存布局和关键数据结构

研究这些漏洞不仅能提高漏洞挖掘能力,也能加深对Linux内核安全机制的理解。

Linux Kernel BPF模块漏洞分析与利用教学 1. BPF基础概述 BPF (Berkeley Packet Filter) 是Linux内核中的一个重要模块,它允许用户态程序在内核中执行受限的字节码。主要特点包括: 实现了BPF字节码的JIT (Just-In-Time)编译器 用户可以在用户态编写BPF代码,经JIT编译后在内核执行 通过验证器(verifier)机制进行静态分析,防止不安全行为 关键安全机制: 对常数变量设置取值范围限制 结构体 bpf_reg_state 存储8个取值范围变量: 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运算的取值范围分析错误: 问题点 : 直接将64位无符号数的取值范围赋给32位有符号数 导致"差1错误":当x的64位无符号数范围是 1 <= x <= 0x100000001 时 验证阶段认为x的32位有符号数范围是 1 <= x <= 1 (x=1) 实际x可以是2 漏洞利用构造 BPF程序构造示例: 利用技巧 通过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 的检查 漏洞利用 构造加法整数溢出: 实际挑战 内核编译时开启了结构体随机化 内核结构体变量偏移与正常编译不同 需要通过分析内核函数汇编手动计算各种偏移 5. 漏洞利用通用模式 取值范围欺骗 :构造使验证器误判变量取值范围的代码 数学运算放大 :通过加减、位运算等放大取值范围差异 内存操作突破 :利用错误的取值范围进行越界内存访问 权限提升 :修改关键内核数据结构实现提权 6. 防御建议 加强验证器对数学运算的边界检查 对取值范围转换添加更严格的验证 启用内核结构体随机化(虽然会增加利用难度,但不解决根本问题) 及时更新内核补丁 7. 学习资源 BPF验证器fuzzing方法 CVE-2020-8835分析 Linux内核官方文档:BPF验证器实现细节 8. 总结 BPF模块漏洞的核心在于验证器对变量取值范围的错误计算。通过精心构造的BPF程序,可以欺骗验证器,实现内核内存的越界访问。这类漏洞的利用需要深入理解: BPF验证器的工作原理 取值范围的计算逻辑 数学运算对取值范围的影响 内核内存布局和关键数据结构 研究这些漏洞不仅能提高漏洞挖掘能力,也能加深对Linux内核安全机制的理解。