eBPF程序中bpf_probe_read_user触发权限拒绝错误求助
问题
开发eBPF程序时,在kretprobe/sys_read探针中尝试将用户态请求缓冲区数据读取到BPF_MAP_TYPE_ARRAY类型映射中,eBPF验证器报错:
{"error": "field SyscallProbeRetRead: program syscall__probe_ret_read: load program: permission denied: invalid access to map value, value_size=70 off=0 size=16383: R1 min value is outside of the allowed memory range (truncated, 73 line(s) omitted)"}
逻辑说明:若buf_size大于MAX_MSG_SIZE,则将最多TRACE_PARENT_SIZE字节的数据读取到seventy_bytes_array->data中;否则不执行读取操作。
探针代码
#define MAX_MSG_SIZE 16383 #define TRACE_PARENT_SIZE 70 SEC("kretprobe/sys_read") int syscall__probe_ret_read(struct pt_regs *ctx) { size_t buf_size = PT_REGS_RC(ctx); u64 id = bpf_get_current_pid_tgid(); struct data_args_t *read_args = bpf_map_lookup_elem(&active_read_args_map, &id); if (read_args) { char *buf = read_args->buf; const u32 keyOne = 0; struct char_array_value *seventy_bytes_array = bpf_map_lookup_elem(&leftover_buf, &keyOne); unsigned int overrided_bytes = (buf_size - MAX_MSG_SIZE) > TRACE_PARENT_SIZE ? TRACE_PARENT_SIZE : ((buf_size - MAX_MSG_SIZE) > 0 ? (buf_size - MAX_MSG_SIZE) : 0); if (overrided_bytes > 0 && buf + MAX_MSG_SIZE && seventy_bytes_array) { asm volatile("%[overrided_bytes] &= 0x3fff;\n" ::[overrided_bytes] "+r"(overrided_bytes) :); bpf_probe_read_user(&seventy_bytes_array, overrided_bytes & 0x3fff, buf + MAX_MSG_SIZE); } } return 0; }
映射定义
struct { __uint(type, BPF_MAP_TYPE_ARRAY); __type(key, u32); __type(value, struct char_array_value); __uint(max_entries, 1); } leftover_buf SEC(".maps");
结构体定义
struct char_array_value { char data[TRACE_PARENT_SIZE]; // Replace SIZE with the desired size of your char array };
完整验证器日志
599: frame1: R0=map_value_or_null(id=13,off=0,ks=4,vs=70,imm=0) R2=scalar(id=4,umin=16,umax=4294967295,var_off=(0x0; 0xffffffff)) R6=scalar(umin=16384,umax=16453,var_off=(0x4000; 0x7f)) R8=map_value(off=0,ks=8,vs=40,imm=0) R9=map_value(off=0,ks=4,vs=16448,imm=0) R10=fp0 fp-80=mmmm???? fp-88= fp-96=mmmmmmmm fp-104=1 :; unsigned int overrided_bytes = (buf_size - MAX_MSG_SIZE) > TRACE_PARENT_SIZE ? TRACE_PARENT_SIZE : ((buf_size - MAX_MSG_SIZE) > 0 ? (buf_size - MAX_MSG_SIZE) : 0); :599: (07) r2 += -16383 ; frame1: R2_w=scalar(smin=-16367,smax=4294950912) :; if (overrided_bytes > 0 && buf + MAX_MSG_SIZE && seventy_bytes_array) :600: (bf) r1 = r2 ; frame1: R1_w=scalar(id=255,smin=-16367,smax=4294950912) R2_w=scalar(id=255,smin=-16367,smax=4294950912) :601: (67) r1 <<= 32 ; frame1: R1_w=scalar(smax=9223372032559808512,umax=18446744069414584320,var_off=(0x0; 0xffffffff00000000),s32_min=0,s32_max=0,u32_max=0) :602: (77) r1 >>= 32 ; frame1: R1_w=scalar(umax=4294967295,var_off=(0x0; 0xffffffff)) :; if (overrided_bytes > 0 && buf + MAX_MSG_SIZE && seventy_bytes_array) :603: (15) if r1 == 0x0 goto pc+6 ; frame1: R1_w=scalar(umax=4294967295,var_off=(0x0; 0xffffffff)) :604: (15) if r0 == 0x0 goto pc+5 ; frame1: R0=map_value(off=0,ks=4,vs=70,imm=0) :; asm volatile("%[overrided_bytes] &= 0x3fff;\n" ::[overrided_bytes] "+r"(overrided_bytes) :); :605: (57) r2 &= 16383 ; frame1: R2_w=scalar(umax=16383,var_off=(0x0; 0x3fff)) :606: (79) r3 = *(u64 *)(r10 -96) ; frame1: R3_w=scalar() R10=fp0 fp-96=mmmmmmmm :607: (07) r3 += 16383 ; frame1: R3_w=scalar() :; bpf_probe_read_user(&seventy_bytes_array->data, overrided_bytes & 0x3fff, buf + MAX_MSG_SIZE); :608: (bf) r1 = r0 ; frame1: R0=map_value(off=0,ks=4,vs=70,imm=0) R1_w=map_value(off=0,ks=4,vs=70,imm=0) :609: (85) call bpf_probe_read_user#112 :invalid access to map value, value_size=70 off=0 size=16383 :R1 min value is outside of the allowed memory range :processed 7704 insns (limit 1000000) max_states_per_insn 1 total_states 336 peak_states 336 mark_read 253
解决方案
问题根源
- 目标地址错误:
bpf_probe_read_user第一个参数传了&seventy_bytes_array,这是指向map元素指针的地址,而非map元素内部的data数组地址,导致写入越界。 - 读取长度约束不明确:验证器无法识别复杂三元表达式的约束,看到代码中存在读取16383字节的可能,但map的value仅70字节,判定存在越界风险。
- 冗余汇编操作:手动用汇编限制长度无必要,反而干扰验证器分析约束逻辑。
修复后的代码
#define MAX_MSG_SIZE 16383 #define TRACE_PARENT_SIZE 70 SEC("kretprobe/sys_read") int syscall__probe_ret_read(struct pt_regs *ctx) { size_t buf_size = PT_REGS_RC(ctx); u64 id = bpf_get_current_pid_tgid(); struct data_args_t *read_args = bpf_map_lookup_elem(&active_read_args_map, &id); if (read_args) { char *buf = read_args->buf; const u32 keyOne = 0; struct char_array_value *seventy_bytes_array = bpf_map_lookup_elem(&leftover_buf, &keyOne); // 拆分计算逻辑,明确长度约束 unsigned int extra_bytes = buf_size > MAX_MSG_SIZE ? (buf_size - MAX_MSG_SIZE) : 0; unsigned int overrided_bytes = extra_bytes > TRACE_PARENT_SIZE ? TRACE_PARENT_SIZE : extra_bytes; if (overrided_bytes > 0 && seventy_bytes_array) { // 直接写入data数组,目标地址和长度约束清晰 bpf_probe_read_user(seventy_bytes_array->data, overrided_bytes, buf + MAX_MSG_SIZE); } } return 0; }
额外说明
- 移除
buf + MAX_MSG_SIZE的检查:当overrided_bytes > 0时,buf_size > MAX_MSG_SIZE成立,此时sys_read返回值有效,说明用户态缓冲区合法,无需额外检查。 - 拆分长度计算逻辑:让约束关系更直观,帮助验证器识别长度不会超过map value的70字节限制。
内容的提问来源于stack exchange,提问作者Govind Tomar
相关产品推荐
相关产品推荐

