关于eBPF Verifier在自定义IP选项处理场景中行为的问询
eBPF Verifier多条件判断下的权限报错机制解释
问题背景
我通过Python BCC模块部署eBPF程序,意图查找并移除数据包中的自定义IP选项31。但当使用if (option_length == 8 || option_length == 12)语句时,eBPF Verifier报错;将条件拆分为if-else if结构则可正常运行,同时if (option_length <=32)的写法也会触发错误。
完整eBPF代码
#include <linux/bpf.h> #include <linux/if_ether.h> #include <linux/ip.h> #include <linux/udp.h> #define MY_OPTION_TYPE 31 #define MAX_CHECKING 4 static inline __u16 iph_csum(struct iphdr *iph, void *data_end) { __u32 sum = 0; __u16 *buf = (__u16 *)iph; __u16 ihl = iph->ihl << 2; // Convert IHL to bytes iph->check = 0; for (__u8 i = 0; i < ihl && i < 60; i += 2) { if ((void *)(buf + 1) > data_end) { break; } sum += *buf++; } for (__u8 i = 0; sum >> 16 && i < MAX_CHECKING; i += 1) { sum = (sum & 0xFFFF) + (sum >> 16); } return ~sum; } int inter_op_ebpf(struct xdp_md *ctx) { void *data = (void *)(long)ctx->data; void *data_end = (void *)(long)ctx->data_end; struct ethhdr *eth = data; // Check if the Ethernet header is within bounds if ((void *)eth + sizeof(*eth) > data_end) return XDP_PASS; struct iphdr *ip = data + sizeof(*eth); // Check if the IP header is within bounds if ((void *)ip + sizeof(*ip) > data_end) return XDP_PASS; if (ip->version != 4) { return XDP_PASS; } // Check if the packet has IP options int options_len = (ip->ihl * 4) - sizeof(struct iphdr); __u8 *options = (__u8 *)(ip + 1); __u8 is_register = 1; __u8 is_exist_custom_option = 0; if (options_len > 0 && (void *)(options + 4) < data_end) { __u8 option_type = options[0]; if (option_type == MY_OPTION_TYPE) { is_exist_custom_option = 1; __u8 option_length = options[1]; __u8 *data_bytes = (__u8 *)data; int shift_data_length = sizeof(*eth) + sizeof(struct iphdr); if (option_length == 8 || option_length == 12) { for (int i = shift_data_length - 1; i >= 0; i--) { if ((void *)(data_bytes + i + option_length + 1) > data_end) return XDP_PASS; data_bytes[i + option_length] = data_bytes[i]; } } int ret = bpf_xdp_adjust_head(ctx, option_length); // Cut option length bytes at the head if (ret < 0) { return XDP_PASS; // If adjustment fails, pass the packet } data = (void *)(long)ctx->data; data_end = (void *)(long)ctx->data_end; eth = data; // Check if the Ethernet header is within bounds if ((void *)eth + sizeof(*eth) > data_end) return XDP_DROP; ip = data + sizeof(*eth); // Check if the IP header is within bounds if ((void *)ip + sizeof(*ip) > data_end) return XDP_DROP; if (ip->version != 4) { return XDP_DROP; } int new_header_size = sizeof(struct iphdr); // 20 + 8 = 28 bytes ip->ihl = new_header_size / 4; ip->tot_len = htons(ntohs(ip->tot_len) - option_length); } ip->check = iph_csum(ip, data_end); } return XDP_PASS; }
错误日志
bpf: Failed to load program: Permission denied btf_vmlinux is malformed ; int inter_op_ebpf(struct xdp_md *ctx) 0: (bf) r7 = r1 1: (b7) r6 = 2 ; void *data_end = (void *)(long)ctx->data_end; 2: (61) r2 = *(u32 *)(r7 +4) ; void *data = (void *)(long)ctx->data; 3: (61) r1 = *(u32 *)(r7 +0) ; if ((void *)eth + sizeof(*eth) > data_end) 4: (bf) r3 = r1 5: (07) r3 += 14 ; if ((void *)eth + sizeof(*eth) > data_end) 6: (2d) if r3 > r2 goto pc+304 R1_w=pkt(id=0,off=0,r=14,imm=0) R2_w=pkt_end(id=0,off=0,imm=0) R3_w=pkt(id=0,off=14,r=14,imm=0) R6_w=inv2 R7_w=ctx(id=0,off=0,imm=0) R10=fp0 ; if ((void *)ip + sizeof(*ip) > data_end) 7: (bf) r4 = r1 8: (07) r4 += 34 ; if ((void *)ip + sizeof(*ip) > data_end) 9: (2d) if r4 > r2 goto pc+301 R1_w=pkt(id=0,off=0,r=34,imm=0) R2_w=pkt_end(id=0,off=0,imm=0) R3_w=pkt(id=0,off=14,r=34,imm=0) R4_w=pkt(id=0,off=34,r=34,imm=0) R6_w=inv2 R7_w=ctx(id=0,off=0,imm=0) R10=fp0 ; if (ip->version != 4) 10: (71) r5 = *(u8 *)(r3 +0) ; if (ip->version != 4) 11: (bf) r0 = r5 12: (57) r0 &= 240 ; if (ip->version != 4) 13: (55) if r0 != 0x40 goto pc+297 R0=inv64 R1=pkt(id=0,off=0,r=34,imm=0) R2=pkt_end(id=0,off=0,imm=0) R3=pkt(id=0,off=14,r=34,imm=0) R4=pkt(id=0,off=34,r=34,imm=0) R5=inv(id=1,umax_value=255,var_off=(0x0; 0xff)) R6=inv2 R7=ctx(id=0,off=0,imm=0) R10=fp0 14: (bf) r0 = r1 15: (07) r0 += 38 ; if (options_len > 0 && (void *)(options + 4) < data_end) 16: (3d) if r0 >= r2 goto pc+294 R0_w=pkt(id=0,off=38,r=39,imm=0) R1=pkt(id=0,off=0,r=39,imm=0) R2=pkt_end(id=0,off=0,imm=0) R3=pkt(id=0,off=14,r=39,imm=0) R4=pkt(id=0,off=34,r=39,imm=0) R5=inv(id=1,umax_value=255,var_off=(0x0; 0xff)) R6=inv2 R7=ctx(id=0,off=0,imm=0) R10=fp0 17: (bf) r0 = r5 18: (57) r0 &= 14 19: (b7) r8 = 6 20: (2d) if r8 > r0 goto pc+290 R0_w=inv(id=0,umin_value=6,umax_value=14,var_off=(0x0; 0xe)) R1=pkt(id=0,off=0,r=39,imm=0) R2=pkt_end(id=0,off=0,imm=0) R3=pkt(id=0,off=14,r=39,imm=0) R4=pkt(id=0,off=34,r=39,imm=0) R5=inv(id=1,umax_value=255,var_off=(0x0; 0xff)) R6=inv2 R7=ctx(id=0,off=0,imm=0) R8_w=inv6 R10=fp0 ; __u8 option_type = options[0]; 21: (71) r4 = *(u8 *)(r4 +0) ; if (option_type == MY_OPTION_TYPE) 22: (55) if r4 != 0x1f goto pc+258 R0=inv(id=0,umin_value=6,umax_value=14,var_off=(0x0; 0xe)) R1=pkt(id=0,off=0,r=39,imm=0) R2=pkt_end(id=0,off=0,imm=0) R3=pkt(id=0,off=14,r=39,imm=0) R4=inv31 R5=inv(id=1,umax_value=255,var_off=(0x0; 0xff)) R6=inv2 R7=ctx(id=0,off=0,imm=0) R8=inv6 R10=fp0 ; __u8 option_length = options[1]; 23: (71) r8 = *(u8 *)(r1 +35) ; if (option_length == 8 || option_length == 12) { 24: (bf) r3 = r8 25: (47) r3 |= 4 26: (15) if r3 == 0xc goto pc+1 from 26 to 28: R0=inv(id=0,umin_value=6,umax_value=14,var_off=(0x0; 0xe)) R1=pkt(id=0,off=0,r=39,imm=0) R2=pkt_end(id=0,off=0,imm=0) R3_w=inv12 R4=inv31 R5=inv(id=1,umax_value=255,var_off=(0x0; 0xff)) R6=inv2 R7=ctx(id=0,off=0,imm=0) R8_w=inv(id=2,umax_value=255,var_off=(0x0; 0xff)) R10=fp0 ; 28: (bf) r3 = r1 29: (0f) r3 += r8 last_idx 29 first_idx 22 regs=100 stack=0 before 28: (bf) r3 = r1 regs=100 stack=0 before 26: (15) if r3 == 0xc goto pc+1 regs=100 stack=0 before 25: (47) r3 |= 4 regs=100 stack=0 before 24: (bf) r3 = r8 regs=100 stack=0 before 23: (71) r8 = *(u8 *)(r1 +35) ; if ((void *)(data_bytes + i + option_length + 1) > data_end) 30: (bf) r4 = r3 31: (07) r4 += 34 ; if ((void *)(data_bytes + i + option_length + 1) > data_end) 32: (2d) if r4 > r2 goto pc+278 R0=inv(id=0,umin_value=6,umax_value=14,var_off=(0x0; 0xe)) R1=pkt(id=0,off=0,r=39,imm=0) R2=pkt_end(id=0,off=0,imm=0) R3_w=pkt(id=9,off=0,r=34,umax_value=255,var_off=(0x0; 0xff)) R4_w=pkt(id=9,off=34,r=34,umax_value=255,var_off=(0x0; 0xff)) R5=inv(id=1,umax_value=255,var_off=(0x0; 0xff)) R6=inv2 R7=ctx(id=0,off=0,imm=0) R8_w=invP(id=2,umax_value=255,var_off=(0x0; 0xff)) R10=fp0 ; data_bytes[i + option_length] = data_bytes[i]; 33: (bf) r4 = r8 34: (0f) r4 += r1 ; data_bytes[i + option_length] = data_bytes[i]; 35: (71) r5 = *(u8 *)(r1 +33) ; data_bytes[i + option_length] = data_bytes[i]; 36: (73) *(u8 *)(r4 +33) = r5 invalid access to packet, off=33 size=1, R4(id=10,off=33,r=0) R4 offset is outside of the packet processed 196 insns (limit 1000000) max_states_per_insn 1 total_states 12 peak_states 12 mark_read 6 Traceback (most recent call last): File "a.py", line 9, in <module> fn = b.load_func("inter_op_ebpf", BPF.XDP) File "/usr/lib/python3/dist-packages/bcc/__init__.py", line 523, in load_func raise Exception("Failed to load BPF program %s: %s" % Exception: Failed to load BPF program b'inter_op_ebpf': Permission denied
Verifier工作机制解释
核心限制:数值范围跟踪的精度问题
eBPF Verifier的核心职责是确保程序不会访问非法内存,它会跟踪每个寄存器中数值的可能范围,以此判断内存访问是否安全。这里的问题源于Verifier对多条件判断的跟踪能力不足:
- 合并条件判断的优化导致范围丢失:
当你写if (option_length ==8 || option_length ==12)时,编译器会将其优化为位运算(错误日志第24-26行:r3 = r8→r3 |=4→ 判断是否等于0xc)。但Verifier无法识别这个优化后的逻辑对应的原始条件,仍然认为option_length是0-255的完整无符号字节范围,而非仅8或12两个值。 - 后续内存访问的越界风险判断:
当你用option_length计算内存偏移data_bytes[i + option_length]时,Verifier认为option_length可能取到255这样的大值,导致偏移超出数据包边界,因此抛出越界错误。
为什么拆分if-else if能通过?
拆分成两个独立的条件分支后:
if (option_length ==8) { // ... } else if (option_length ==12) { // ... }
Verifier会在每个分支中单独跟踪option_length的数值——进入第一个分支时,它明确知道option_length=8;进入第二个分支时,明确知道option_length=12。这样就能精确计算出内存访问的偏移,确认不会越界,因此通过验证。
为什么if (option_length <=32)也会报错?
Verifier只能跟踪到option_length的最大值是32,但无法将这个范围和你写的动态边界检查(void *)(data_bytes + i + option_length +1) > data_end关联起来。它仍然担心当option_length取32时,计算出的偏移可能超出数据包实际长度,因此拒绝通过验证。
错误日志的关键细节
错误日志第36行明确指出:invalid access to packet, off=33 size=1, R4(id=10,off=33,r=0),这是因为Verifier认为option_length可能是255,导致
相关产品推荐
相关产品推荐

