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

关于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对多条件判断的跟踪能力不足:

  1. 合并条件判断的优化导致范围丢失:
    当你写if (option_length ==8 || option_length ==12)时,编译器会将其优化为位运算(错误日志第24-26行:r3 = r8 → r3 |=4 → 判断是否等于0xc)。但Verifier无法识别这个优化后的逻辑对应的原始条件,仍然认为option_length是0-255的完整无符号字节范围,而非仅8或12两个值。
  2. 后续内存访问的越界风险判断:
    当你用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,导致

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 23:27:11