首页
社区
课程
招聘
[原创] CVE-2017-16995 的一些追加分析
发表于: 2018-5-24 22:39 4351

[原创] CVE-2017-16995 的一些追加分析

2018-5-24 22:39
4351
最近把在研究BPF,看到了关于CVE-2017-16995的一些分析报告。[2]和[3]上面对这个CVE的成因分析已经很详细了,主要就是32位无符号数被强制转化为64位无符号数的时候会产生符号拓展, 进而导致了在do_check和bpf_prog_run这两个程序中,ebpf程序的执行流程并不一致。具体细节可以参看[2]和[3], 我在我自己博客[4]里面也有分析, 欢迎围观。
这里我想讲讲在[2]和[3]中所漏掉的一些细节。首先上图证明我也重现出来了


在[1]和[2]中, 两文的作者都认为这个eBPF程序有41条指令, 但实际上在执行过程中,实际上只解析了40条指令。因为在bpf_prog_run[5]里对于opcode 0x18 (LD_IMM_DW) 是用如下代码进行的处理。可以发现是由两条insn合并成了一条指令
LD_IMM_DW:
		DST = (u64) (u32) insn[0].imm | ((u64) (u32) insn[1].imm) << 32;
		insn++;
		CONT;

LD_IMM_DW:
		DST = (u64) (u32) insn[0].imm | ((u64) (u32) insn[1].imm) << 32;
		insn++;
		CONT;

我也编写了一个小程序把ebpf程序转换成了可读代码如下。

程序源代码我已经放在了github[6]上,这个版本是用C写的,有一些corner case并没有包含在里面。之后可能回去写一个python版本的,感觉写起来更友好一些。

在[2]和[3]中似乎都没有讲明为何第五个指令(opcode 0x18)在执行过程中,就把一个指向bpf_map的pointer载入到了R[9]里。
这其实还是跟第五个指令的特殊性有关。
在调用do_check之前,bpf_check会调用一个特殊的函数 replace_map_fd_with_map_ptr
//   https://elixir.bootlin.com/linux/v4.4.33/source/kernel/bpf/verifier.c#L2241
	ret = replace_map_fd_with_map_ptr(env);
	if (ret < 0)
		goto skip_full_check;

	env->explored_states = kcalloc(env->prog->len,
				       sizeof(struct verifier_state_list *),
				       GFP_USER);
	ret = -ENOMEM;
	if (!env->explored_states)
		goto skip_full_check;

	ret = check_cfg(env);
	if (ret < 0)
		goto skip_full_check;

	env->allow_ptr_leaks = capable(CAP_SYS_ADMIN);

	ret = do_check(env);
这个函数会对opcode 0x18做一些特殊的处理
//   https://elixir.bootlin.com/linux/v4.4.33/source/kernel/bpf/verifier.c#L2241
	ret = replace_map_fd_with_map_ptr(env);
	if (ret < 0)
		goto skip_full_check;

	env->explored_states = kcalloc(env->prog->len,
				       sizeof(struct verifier_state_list *),
				       GFP_USER);
	ret = -ENOMEM;
	if (!env->explored_states)
		goto skip_full_check;

	ret = check_cfg(env);
	if (ret < 0)
		goto skip_full_check;

	env->allow_ptr_leaks = capable(CAP_SYS_ADMIN);

	ret = do_check(env);
这个函数会对opcode 0x18做一些特殊的处理
//https://elixir.bootlin.com/linux/v4.4.33/source/kernel/bpf/verifier.c#L2021			
/* store map pointer inside BPF_LD_IMM64 instruction */
insn[0].imm = (u32) (unsigned long) map;
insn[1].imm = ((u64) (unsigned long) map) >> 32;
之前由bpf_create_map所创建的bpf_map会被拆分成2个32bit数,并被load进内存中。

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 1
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//