eBPF程序bpf_probe_read_user权限拒绝:如何让验证器认可安全写入?
eBPF验证器不认可边界检查的修复方案
问题背景
定义了如下结构体:
struct ip_event_t { __u32 src_ip; __u32 dst_ip; __u16 src_port; __u16 dst_port; __u32 payload_length; char payload[MAX_PAYLOAD_LENGTH]; };
编写的边界检查代码无法被eBPF验证器识别:
if (total_len + to_read > MAX_PAYLOAD_LENGTH) { to_read = 0; return -1; } if (bpf_probe_read_user(&event->payload[total_len], to_read, iov_entry.iov_base) < 0) { return -1; }
验证器报错信息:
; if (total_len + to_read > MAX_PAYLOAD_LENGTH) { 191: (bf) r8 = r2 ; frame1: R2_w=2000 R8_w=2000 192: (0f) r8 += r7 ; frame1: R7=scalar(id=10,smin=smin32=0,smax=umax=smax32=umax32=1999,var_off=(0x0; 0x7ff)) R8_w=scalar(smin=umin=smin32=umin32=2000,smax=umax=smax32=umax32=3999,var_off=(0x0; 0xfff)) ; if (total_len + to_read > MAX_PAYLOAD_LENGTH) { 193: (25) if r8 > 0x7d0 goto pc+88 ; frame1: R8_w=2000 ; if (bpf_probe_read_user(&event->payload[total_len], to_read, iov_entry.iov_base) < 0) { 194: (bf) r1 = r6 ; frame1: R1_w=map_value(map=ip_event_map,ks=4,vs=2016) R6=map_value(map=ip_event_map,ks=4,vs=2016) 195: (0f) r1 += r7 ; frame1: R1_w=map_value(map=ip_event_map,ks=4,vs=2016,smin=smin32=0,smax=umax=smax32=umax32=1999,var_off=(0x0; 0x7ff)) R7=scalar(id=10,smin=smin32=0,smax=umax=smax32=umax32=1999,var_off=(0x0; 0x7ff)) 196: (bf) r3 = r10 ; frame1: R3_w=fp0 R10=fp0 197: (07) r3 += -56 ; frame1: R3_w=fp-56 ; if (bpf_probe_read_user(&event->payload[total_len], to_read, iov_entry.iov_base) < 0) { 198: (79) r3 = *(u64 *)(r3 +0) ; frame1: R3_w=scalar() fp-56=mmmmmmmm ; if (bpf_probe_read_user(&event->payload[total_len], to_read, iov_entry.iov_base) < 0) { 199: (07) r1 += 16 ; frame1: R1_w=map_value(map=ip_event_map,ks=4,vs=2016,off=16,smin=smin32=0,smax=umax=smax32=umax32=1999,var_off=(0x0; 0x7ff)) ; if (bpf_probe_read_user(&event->payload[total_len], to_read, iov_entry.iov_base) < 0) { 200: (85) call bpf_probe_read_user#112 invalid access to map value, value_size=2016 off=2015 size=2000 R1 max value is outside of the allowed memory range processed 278 insns (limit 1000000) max_states_per_insn 2 total_states 21 peak_states 21 mark_read 9 -- END PROG LOAD LOG -- libbpf: prog 'bpf_prog_tcp_sendmsg': failed to load: -13
问题:如何调整代码让eBPF验证器认可该写入操作是安全的?
修复方案
eBPF验证器对内存边界的追踪依赖直接关联到目标指针的约束,原代码的检查逻辑没有和event->payload[total_len]的偏移量建立明确关联,导致验证器无法确认访问安全。可通过以下方式修复:
1. 拆分约束,明确限制total_len的上限
把原有检查拆分为对total_len和to_read的单独约束,让验证器清晰追踪到偏移量的合法性:
// 先确保total_len本身不超过payload数组的最大下标 if (total_len >= MAX_PAYLOAD_LENGTH) { return -1; } // 再检查total_len + to_read不越界,同时限制to_read的最大值 __u32 remaining = MAX_PAYLOAD_LENGTH - total_len; if (to_read > remaining) { to_read = remaining; // 若不需要截断,此处可直接return -1 } if (bpf_probe_read_user(&event->payload[total_len], to_read, iov_entry.iov_base) < 0) { return -1; }
2. 使用指针偏移替代数组下标(辅助验证器识别)
将数组下标写法改为指针偏移,验证器更容易识别指针的范围:
__u32 remaining = MAX_PAYLOAD_LENGTH - total_len; if (to_read > remaining) { to_read = remaining; if (to_read == 0) { return -1; } } char *payload_ptr = event->payload + total_len; if (bpf_probe_read_user(payload_ptr, to_read, iov_entry.iov_base) < 0) { return -1; }
3. 改用__builtin_memcpy(内核5.14+适用)
新版本内核支持__builtin_memcpy,验证器对该内置函数的内存边界追踪更友好:
if (total_len + to_read > MAX_PAYLOAD_LENGTH) { to_read = MAX_PAYLOAD_LENGTH - total_len; if (to_read == 0) { return -1; } } __builtin_memcpy(&event->payload[total_len], iov_entry.iov_base, to_read);
注意:使用该函数需确保源地址是合法的用户空间指针,避免触发内核错误。
核心原因
eBPF验证器不会跨指令推导复杂的算术约束,原代码中total_len + to_read > MAX_PAYLOAD_LENGTH的检查,没有直接告知验证器total_len的上限,导致验证器认为total_len可能接近MAX_PAYLOAD_LENGTH,加上to_read后会触发越界。修复的关键是让验证器能明确追踪到目标指针的偏移量和写入长度均在合法范围内。
内容的提问来源于stack exchange,提问作者triple fault
相关产品推荐
相关产品推荐

