已做边界检查仍触发eBPF map值非法访问问题排查
eBPF数组Map越界访问问题排查与解决建议
尝试通过索引和偏移量从array类型的eBPF map读取字节,已执行边界检查,但仍触发invalid access to map value错误,怀疑与溢出风险有关。
相关代码
struct bpf_elf_map __section("maps") data_store = { .type = BPF_MAP_TYPE_ARRAY, .size_key = sizeof(__u32), .size_value = 1024, .max_elem = 4096, .pinning = PIN_GLOBAL_NS, }; static __always_inline void read_data(__u32 idx, __u32 offset, void *dst, __u32 size) { if (size > 512 || offset >= 1024) { // for the ebpf verifier return; } void *b = bpf_map_lookup_elem(&data_store, &idx); if (!b) { // shouldn't happen return; } if (offset + size <= 1024) { for (__u32 i = 0; i < size && i < 512; ++i) { if (offset + i >= 1024) { return; } // !! where the verifier complains memcpy(dst + i, b + offset + i, sizeof(__u8)); } } else { // ... } }
验证器日志
; for (__u32 i = 0; i < size && i < 512; ++i) { 197: (25) if r1 > 0x1fe goto pc+8 ; 198: (bf) r2 = r1 199: (07) r2 += 1 ; for (__u32 i = 0; i < size && i < 512; ++i) { 200: (3d) if r2 >= r8 goto pc+5 R0=map_value(id=0,off=0,ks=4,vs=1024,umax_value=1023,var_off=(0x0; 0x3ff)) R1=invP0 R2_w=invP1 R3_w=inv(id=0,umax_value=255,var_off=(0x0; 0xff)) R6=map_value(id=0,off=0,ks=4,vs=32,imm=0) R7=map_value(id=0,off=0,ks=4,vs=512,imm=0) R8=inv(id=261,umin_value=2,umax_value=512,var_off=(0x0; 0x3ff)) R9=invP(id=0,umin_value=1,umax_value=1024,var_off=(0x0; 0x7ff)) R10=fp0 fp-8=mmmmm??? fp-48=????mmmm 201: (bf) r3 = r9 202: (0f) r3 += r1 203: (bf) r1 = r2 204: (25) if r3 > 0x3ff goto pc+1 R0=map_value(id=0,off=0,ks=4,vs=1024,umax_value=1023,var_off=(0x0; 0x3ff)) R1=invP1 R2=invP1 R3=invP(id=0,umin_value=1,umax_value=1023,var_off=(0x0; 0x3ff)) R6=map_value(id=0,off=0,ks=4,vs=32,imm=0) R7=map_value(id=0,off=0,ks=4,vs=512,imm=0) R8=inv(id=261,umin_value=2,umax_value=512,var_off=(0x0; 0x3ff)) R9=invP(id=263,umin_value=1,umax_value=1024,var_off=(0x0; 0x7ff)) R10=fp0 fp-8=mmmmm??? fp-48=????mmmm 205: (05) goto pc-15 ; memcpy(dst + i, b + offset + i, sizeof(__u8)); 191: (bf) r2 = r7 192: (0f) r2 += r1 193: (bf) r3 = r0 194: (0f) r3 += r1 195: (71) r3 = *(u8 *)(r3 +0) R0=map_value(id=0,off=0,ks=4,vs=1024,umax_value=1023,var_off=(0x0; 0x3ff)) R1=invP1 R2_w=map_value(id=0,off=1,ks=4,vs=512,imm=0) R3_w=map_value(id=0,off=1,ks=4,vs=1024,umax_value=1023,var_off=(0x0; 0x3ff)) R6=map_value(id=0,off=0,ks=4,vs=32,imm=0) R7=map_value(id=0,off=0,ks=4,vs=512,imm=0) R8=inv(id=261,umin_value=2,umax_value=512,var_off=(0x0; 0x3ff)) R9=invP(id=263,umin_value=1,umax_value=1024,var_off=(0x0; 0x7ff)) R10=fp0 fp-8=mmmmm??? fp-48=????mmmm invalid access to map value, value_size=1024 off=1024 size=1 R3 max value is outside of the allowed memory range
解决建议
- 合并简化边界检查:分散的检查逻辑会让eBPF验证器无法准确追踪变量范围。提前一次性完成所有校验,将
offset >=1024改为offset >1023(无符号整数等于1024时直接越界),同时确认offset + size <=1024。 - 移除循环内冗余检查:进入循环前已确认
offset + size <=1024,循环内的if (offset + i >=1024)完全多余,反而干扰验证器的数据流分析。 - 替换单字节memcpy为直接指针访问:单字节
memcpy效率低且不利于验证器识别内存范围,改用数组下标或指针赋值更清晰。 - 优化循环条件:循环条件只需
i < size,前面已限制size <=512,无需重复判断i <512。
修改后的示例代码
static __always_inline void read_data(__u32 idx, __u32 offset, void *dst, __u32 size) { // 一次性完成所有边界检查,明确告知验证器变量范围 if (size > 512 || offset > 1023 || (offset + size) > 1024) { return; } void *b = bpf_map_lookup_elem(&data_store, &idx); if (!b) { return; } // 转换为字节指针直接拷贝 __u8 *src_ptr = (__u8 *)b + offset; __u8 *dst_ptr = (__u8 *)dst; for (__u32 i = 0; i < size; ++i) { dst_ptr[i] = src_ptr[i]; } }
说明
修改后的代码通过提前明确的边界检查,让eBPF验证器能清晰识别src_ptr + i的访问范围始终在0到1023之间(符合map的value_size=1024约束),简化的逻辑也降低了验证器的分析复杂度,避免误判越界。
内容的提问来源于stack exchange,提问作者qwd
相关产品推荐
相关产品推荐

