使用bpf_loop读取数据包时出现非法访问的问题咨询
BPF数据包解析问题:
bpf_for循环验证失败,常规for循环指令超限 我尝试解析stream_parser程序接收的数据包前几个字节,遇到了几个难以理解的问题。以下是我的代码:
__u32 len = 256; // 取消注释后即使使用常规for循环也会失效 // if (len > skb->len) len = skb->len; if (bpf_skb_pull_data(skb, len) < 0) { return -1; } char *data_end = (char *)(long)skb->data_end; char *data = (char *)(long)skb->data; if (data + len > data_end) { return -1; } __u32 k = 0; bpf_for(k, 0, len) { __u32 i = k & 0xFF; if (i < len && data + i > data_end) break; bpf_printk("data[i] = %d", data[i]); } // 这段代码可以正常运行! // for (__u32 i = 0; i < len; i++) { // if (data + i > data_end) break; // bpf_printk("data[i] = %d", data[i]); // }
BPF验证器输出如下:
0: R1=ctx() R10=fp0 ; int bpf_prog_parser(struct __sk_buff *skb) { 0: (bf) r6 = r1 ; R1=ctx() R6_w=ctx() ; if (bpf_skb_pull_data(skb, len) < 0) { 1: (b7) r2 = 256 ; R2_w=256 2: (85) call bpf_skb_pull_data#39 ; R0_w=scalar() 3: (b7) r1 = 0 ; R1_w=0 ; if (bpf_skb_pull_data(skb, len) < 0) { 4: (6d) if r1 s> r0 goto pc+50 ; R0_w=scalar(smin=0,umax=0x7fffffffffffffff,var_off=(0x0; 0x7fffffffffffffff)) R1_w=0 ; char *data_end = (char *)(long)skb->data_end; 5: (61) r8 = *(u32 *)(r6 +80) ; R6_w=ctx() R8_w=pkt_end() ; char *data = (char *)(long)skb->data; 6: (61) r9 = *(u32 *)(r6 +76) ; R6_w=ctx() R9_w=pkt(r=0) ; if (data + len > data_end) { 7: (bf) r1 = r9 ; R1_w=pkt(r=0) R9_w=pkt(r=0) 8: (07) r1 += 256 ; R1=pkt(off=256,r=0) ; if (data + len > data_end) { 9: (2d) if r1 > r8 goto pc+45 ; R1=pkt(off=256,r=256) R8=pkt_end() 10: (bf) r7 = r10 ; R7_w=fp0 R10=fp0 ; 11: (07) r7 += -24 ; R7_w=fp-24 ; bpf_for(k, 0, len) { 12: (bf) r1 = r7 ; R1_w=fp-24 R7_w=fp-24 13: (b7) r2 = 0 ; R2_w=0 14: (b7) r3 = 256 ; R3_w=256 15: (85) call bpf_iter_num_new#76394 ; R0_w=scalar() fp-24_w=iter_num(ref_id=1,state=active,depth=0) refs=1 ; bpf_for(k, 0, len) { 16: (bf) r1 = r7 ; R1=fp-24 R7=fp-24 refs=1 17: (85) call bpf_iter_num_next#76396 18: R0_w=rdonly_mem(id=2,ref_obj_id=1,sz=4) R6=ctx() R7=fp-24 R8=pkt_end() R9=pkt(r=256) R10=fp0 fp-24=iter_num(ref_id=1,state=active,depth=1) refs=1 ; bpf_for(k, 0, len) { 18: (15) if r0 == 0x0 goto pc+17 ; R0_w=rdonly_mem(id=2,ref_obj_id=1,sz=4) refs=1 19: (61) r1 = *(u32 *)(r0 +0) ; R0_w=rdonly_mem(id=2,ref_obj_id=1,sz=4) R1_w=scalar(smin=0,smax=umax=0xffffffff,var_off=(0x0; 0xffffffff)) refs=1 ; bpf_for(k, 0, len) { 20: (25) if r1 > 0xff goto pc+15 ; R1_w=scalar(smin=smin32=0,smax=umax=smax32=umax32=255,var_off=(0x0; 0xff)) refs=1 ; if (i < len && data + i > data_end) break; 21: (bf) r2 = r9 ; R2_w=pkt(r=256) R9=pkt(r=256) refs=1 22: (0f) r2 += r1 ; R1=scalar(smin=smin32=0,smax=umax=smax32=umax32=255,var_off=(0x0; 0xff)) R2=pkt(id=5,r=0,smin=smin32=0,smax=umax=smax32=umax32=255,var_off=(0x0; 0xff)) refs=1 ; if (i < len && data + i > data_end) break; 23: (2d) if r2 > r8 goto pc+12 ; R2=pkt(id=5,r=0,smin=smin32=0,smax=umax=smax32=umax32=255,var_off=(0x0; 0xff)) R8=pkt_end() refs=1 ; bpf_printk("data[i] = %d", data[i]); 24: (71) r3 = *(u8 *)(r2 +0) invalid access to packet, off=0 size=1, R2(id=5,off=0,r=0) R2 offset is outside of the packet processed 55 insns (limit 1000000) max_states_per_insn 0 total_states 5 peak_states 5 mark_read 2
我的疑问:
- 为什么当前代码无法通过验证?
- 为什么底部的常规for循环可以正常运行,但
bpf_for不行?我本来想用常规for循环,但它会导致指令数激增,无法通过验证。 - 如果在代码开头把
len限制为skb->len(即取消注释那行),两种循环都会验证失败,这是为什么?毕竟len的上限还是256。
本质上我只是想读取数据包的前几个字节,这类基础解析应该可行,恳请帮忙解答。
问题解答
1. 当前代码无法运行的原因
验证器报错核心是R2 offset is outside of the packet,问题出在两处:
- 无意义的变量操作:
i = k & 0xFF完全多余,k本身就是0到255的迭代值,这步操作干扰了验证器的数据流跟踪。 - 边界检查逻辑反向:
if (i < len && data + i > data_end) break的逻辑是当偏移超出数据包范围时才终止循环,验证器无法理解这种反向逻辑,会判定存在非法内存访问的风险。
2. 常规for循环能运行但bpf_for不行的原因
常规for循环中,验证器可以清晰跟踪循环变量i的范围:从0开始递增,且有i < len的约束,再加上前置的data + len <= data_end检查,验证器能确定data+i永远不会超出数据包边界。
而bpf_for基于BPF迭代器实现,验证器对迭代器返回的变量跟踪能力有限,再加上你额外的变量操作和错误的边界检查,导致验证器无法确认内存访问的安全性。
3. 限制len为skb->len后两种循环都失败的原因
当len变为动态值(取决于数据包实际长度):
- 常规for循环需要验证器展开所有可能的迭代分支来确认安全,256次迭代会导致指令数激增,超出验证器的处理能力或指令限制。
bpf_for本身对动态循环边界的跟踪能力就弱,加上之前的逻辑问题,验证器直接无法确认data+i的合法性。
修复方案
读取数据包前N字节的正确写法,需简化逻辑并帮助验证器跟踪变量范围:
#include <linux/bpf.h> #include <bpf/bpf_helpers.h> #include <bpf/bpf_tracing.h> __u32 len = 256; // 用min宏直接限制len,减少分支逻辑 len = bpf_min(len, (__u32)skb->len); // 拉取足够长度的数据,失败直接返回 if (bpf_skb_pull_data(skb, len) < 0) { return -1; } char *data = (char *)(long)skb->data; __u32 i = 0; // 直接用i作为偏移,无需额外转换 bpf_for(i, 0, len) { // bpf_skb_pull_data已保证data+i在合法范围内,无需额外检查 bpf_printk("data[i] = %d", data[i]); }
如果需要保留边界检查,需调整为正向逻辑:
__u32 len = 256; len = bpf_min(len, (__u32)skb->len); if (bpf_skb_pull_data(skb, len) < 0) { return -1; } char *data_end = (char *)(long)skb->data_end; char *data = (char *)(long)skb->data; __u32 i = 0; bpf_for(i, 0, len) { // 正向判断:超出范围则终止 if (data + i >= data_end) { break; } bpf_printk("data[i] = %d", data[i]); }
关键要点:
- 用
bpf_min直接限制len,避免多余条件分支,帮助验证器跟踪变量范围。 - 去掉无意义的变量转换操作,让迭代变量直接对应数据包偏移。
- 确保边界检查逻辑正向明确,让验证器能清晰判断内存访问的安全性。
内容的提问来源于stack exchange,提问作者lbrndnr
相关产品推荐
相关产品推荐

