You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

我的疑问:

  1. 为什么当前代码无法通过验证?
  2. 为什么底部的常规for循环可以正常运行,但bpf_for不行?我本来想用常规for循环,但它会导致指令数激增,无法通过验证。
  3. 如果在代码开头把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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 17:55:55