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

eBPF启用RST flag处理触发map内存访问权限错误排查

eBPF添加TCP RST标志位处理后触发内存访问权限错误

错误信息

permission denied: invalid access to map value, value_size=20 off=21 size=1: R2 max value is outside of the allowed memory range

问题背景

原有eBPF代码仅处理TCP SYN、FIN标志位时运行正常,取消RST标志位处理逻辑的注释后,触发上述内存访问权限错误。相关核心代码如下:

struct value {
    int index;
    char flags[MAX_FLAGS_LEN];
};

static __always_inline void store_flags(struct value *value, struct tcphdr *tcp){
    __u8 index = value->index;

    if (index >= sizeof(value->flags)){
        return;
    }
    
    if (index < sizeof(value->flags)){
        if (tcp->syn){
            value->flags[index] = 'S';
            index++;
        }
    }

    if (index < sizeof(value->flags)){
        if (tcp->fin){
            value->flags[index] = 'F';
            index++;
        }
    }

    // if (index < sizeof(value->flags)){
    //     if (tcp->rst){
    //         value->flags[index] = 'R';
    //         index++;
    //     }
    // }

    bpf_printk("Flags: %d", tcp->syn);

    value->index=index;
}

问题分析

反编译代码显示,RST标志位处理部分的index < sizeof(value->flags)检查被编译器重排为两次,且第二次无跳转分支,导致BPF验证器判定存在未校验的内存访问。进一步排查发现:处理RST时,变量index从寄存器r2转移到r1,BPF验证器丢失了此前的边界校验上下文;而SYN、FIN的处理逻辑未出现该寄存器转移,因此未触发错误。

解决办法

方案1:调整代码结构,减少编译器重排空间

将多次独立的边界检查改为顺序处理+即时退出,避免寄存器上下文丢失:

static __always_inline void store_flags(struct value *value, struct tcphdr *tcp){
    __u8 index = value->index;
    const __u8 max_len = sizeof(value->flags);

    if (index >= max_len){
        return;
    }
    
    if (tcp->syn) {
        value->flags[index++] = 'S';
        if (index >= max_len) goto out;
    }
    if (tcp->fin) {
        value->flags[index++] = 'F';
        if (index >= max_len) goto out;
    }
    if (tcp->rst) {
        value->flags[index++] = 'R';
        if (index >= max_len) goto out;
    }

out:
    bpf_printk("Flags: %d", tcp->syn);
    value->index = index;
}

方案2:使用循环统一处理标志位

通过数组映射表+循环处理所有标志位,让验证器更容易识别边界校验逻辑:

#include <linux/tcp.h>

// 定义TCP标志位与字符的映射表
struct flag_map {
    __u8 flag_bit;
    char flag_char;
};

static const struct flag_map tcp_flag_map[] = {
    { .flag_bit = TCPHDR_SYN, .flag_char = 'S' },
    { .flag_bit = TCPHDR_FIN, .flag_char = 'F' },
    { .flag_bit = TCPHDR_RST, .flag_char = 'R' },
};

static __always_inline void store_flags(struct value *value, struct tcphdr *tcp){
    __u8 index = value->index;
    const __u8 max_len = sizeof(value->flags);

    if (index >= max_len){
        return;
    }

    for (int i = 0; i < ARRAY_SIZE(tcp_flag_map); i++) {
        if (tcp->flags & tcp_flag_map[i].flag_bit) {
            value->flags[index++] = tcp_flag_map[i].flag_char;
            if (index >= max_len) break;
        }
    }

    bpf_printk("Flags: %d", tcp->syn);
    value->index = index;
}

方案3:显式固定寄存器使用(针对编译器重排)

通过显式赋值强制保留index的寄存器上下文,避免验证器丢失校验信息:

static __always_inline void store_flags(struct value *value, struct tcphdr *tcp){
    __u8 index = value->index;
    const __u8 max_len = sizeof(value->flags);

    if (index >= max_len){
        return;
    }
    
    // 显式复制index到临时变量,固定寄存器上下文
    __u8 curr_idx = index;
    if (curr_idx < max_len && tcp->syn) {
        value->flags[curr_idx++] = 'S';
    }
    if (curr_idx < max_len && tcp->fin) {
        value->flags[curr_idx++] = 'F';
    }
    if (curr_idx < max_len && tcp->rst) {
        value->flags[curr_idx++] = 'R';
    }

    bpf_printk("Flags: %d", tcp->syn);
    value->index = curr_idx;
}

内容的提问来源于stack exchange,提问作者hdmicable

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 03:52:45