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

eBPF写入数组触发“内存范围越界”错误的排查求助

问题:eBPF验证器报错“value is outside of the allowed memory range”

尝试从用户空间读取数据并存入char数组,由于读取可能触发多次,因此记录偏移量来存储数据。已对读取长度和偏移量做了边界检查,但eBPF验证器仍报错“value is outside of the allowed memory range”。

经分析,边界检查后验证器认为len和read均处于[0,4096)区间,但event->len + read < MAX_READ_CONTENT_LENGTH的条件未被验证器识别,导致失败。

相关代码

struct ReadArgs{
    int fd;
    uintptr_t buf;  // 结构体指针的兼容处理
};

struct ReadEvent{
    int eventType;
    int fd;
    int len;
    u8 content[MAX_READ_CONTENT_LENGTH];
};

static __always_inline int readData(struct ReadArgs* args, struct ReadEvent* event, int read){
    if((void *) args->buf == NULL){
        return -1;
    }
    event->fd = args->fd;
    if(event->len > MAX_READ_CONTENT_LENGTH){
        return -1;
    } else {
        event->len &= (MAX_READ_CONTENT_LENGTH-1);
    }
    if(read > MAX_READ_CONTENT_LENGTH){
        read = MAX_READ_CONTENT_LENGTH - 1;
    }else{
        read &= (MAX_READ_CONTENT_LENGTH-1);
    }
    if(event->len + read < MAX_READ_CONTENT_LENGTH) {
        long res = bpf_probe_read_user(&event->content[event->len], read, (const void *) args->buf);    // 此处报错
        if (res < 0) {
            DEBUG("readData: bpf_probe_read_user return %d", res);
            return -1;
        }
        event->len += read;
    }
    return 0;
}

验证器日志

; if((void *) args->buf == NULL){
109: (79) r3 = *(u64 *)(r7 +8)        ; R3_w=scalar() R7=map_value(off=0,ks=4,vs=16,imm=0)
; if((void *) args->buf == NULL){
110: (15) if r3 == 0x0 goto pc+64     ; R3_w=scalar()
; event->fd = args->fd;
111: (61) r1 = *(u32 *)(r7 +0)        ; R1_w=scalar(umax=4294967295,var_off=(0x0; 0xffffffff)) R7=map_value(off=0,ks=4,vs=16,imm=0)
; event->fd = args->fd;
112: (63) *(u32 *)(r6 +4) = r1        ; R1_w=scalar(umax=4294967295,var_off=(0x0; 0xffffffff)) R6=map_value(off=0,ks=8,vs=4108,imm=0)
; if(event->len > MAX_READ_CONTENT_LENGTH){
113: (61) r2 = *(u32 *)(r6 +8)        ; R2_w=scalar(umax=4294967295,var_off=(0x0; 0xffffffff)) R6=map_value(off=0,ks=8,vs=4108,imm=0)
114: (67) r2 <<= 32                   ; R2_w=scalar(smax=9223372032559808512,umax=18446744069414584320,var_off=(0x0; 0xffffffff00000000),s32_min=0,s32_max=0,u32_max=0)
115: (c7) r2 s>>= 32                  ; R2=scalar(smin=-2147483648,smax=2147483647)
; if(event->len > MAX_READ_CONTENT_LENGTH){
116: (65) if r2 s> 0x1000 goto pc+58          ; R2=scalar(smin=-2147483648,smax=4096)
; int retVal = ctx->ret;
117: (bf) r1 = r8                     ; R1_w=scalar(id=1) R8=scalar(id=1)
118: (67) r1 <<= 32                   ; R1_w=scalar(smax=9223372032559808512,umax=18446744069414584320,var_off=(0x0; 0xffffffff00000000),s32_min=0,s32_max=0,u32_max=0)
119: (77) r1 >>= 32                   ; R1_w=scalar(umax=4294967295,var_off=(0x0; 0xffffffff))
120: (b7) r7 = 4095                   ; R7_w=4095
; if(read > MAX_READ_CONTENT_LENGTH){
121: (25) if r1 > 0x1000 goto pc+2    ; R1_w=scalar(umax=4096,var_off=(0x0; 0x1fff))
122: (57) r8 &= 4095                  ; R8_w=scalar(umax=4095,var_off=(0x0; 0xfff))
123: (bf) r7 = r8                     ; R7=scalar(id=13,umax=4095,var_off=(0x0; 0xfff)) R8=scalar(id=13,umax=4095,var_off=(0x0; 0xfff))
; event->len &= (MAX_READ_CONTENT_LENGTH-1);
124: (57) r2 &= 4095                  ; R2_w=scalar(umax=4095,var_off=(0x0; 0xfff))
125: (63) *(u32 *)(r6 +8) = r2        ; R2_w=scalar(umax=4095,var_off=(0x0; 0xfff)) R6=map_value(off=0,ks=8,vs=4108,imm=0)
; if(event->len + read < MAX_READ_CONTENT_LENGTH) {
126: (bf) r1 = r2                     ; R1_w=scalar(id=14,umax=4095,var_off=(0x0; 0xfff)) R2_w=scalar(id=14,umax=4095,var_off=(0x0; 0xfff))
127: (0f) r1 += r7                    ; R1_w=scalar(umax=8190,var_off=(0x0; 0x1fff)) R7=scalar(id=13,umax=4095,var_off=(0x0; 0xfff))
; if(event->len + read < MAX_READ_CONTENT_LENGTH) {
128: (25) if r1 > 0xfff goto pc+10    ; R1_w=scalar(umax=4095,var_off=(0x0; 0xfff))
; long res = bpf_probe_read_user(&event->content[event->len], read, (const void *) args->buf);
129: (bf) r1 = r6                     ; R1_w=map_value(off=0,ks=8,vs=4108,imm=0) R6=map_value(off=0,ks=8,vs=4108,imm=0)
130: (0f) r1 += r2                    ; R1_w=map_value(off=0,ks=8,vs=4108,umax=4095,var_off=(0x0; 0xfff)) R2_w=scalar(id=14,umax=4095,var_off=(0x0; 0xfff))
131: (07) r1 += 12                    ; R1_w=map_value(off=12,ks=8,vs=4108,umax=4095,var_off=(0x0; 0xfff))
; long res = bpf_probe_read_user(&event->content[event->len], read, (const void *) args->buf);
132: (bf) r2 = r7                     ; R2_w=scalar(id=13,umax=4095,var_off=(0x0; 0xfff)) R7=scalar(id=13,umax=4095,var_off=(0x0; 0xfff))
133: (85) call bpf_probe_read_user#112
invalid access to map value, value_size=4108 off=4107 size=4095
R1 max value is outside of the allowed memory range
processed 154 insns (limit 1000000) max_states_per_insn 0 total_states 12 peak_states 12 mark_read 4 

解决方案

核心问题是eBPF验证器的数据流分析无法直接关联event->len + read < MAX_READ_CONTENT_LENGTH和后续数组索引的边界限制。可以通过以下方式修复:

1. 显式计算剩余空间,用它限制读取长度

先算出content数组剩余的可用空间,再将读取长度截断为不超过剩余空间,验证器能直接识别这种直白的边界限制逻辑。

2. 修改后的代码示例

static __always_inline int readData(struct ReadArgs* args, struct ReadEvent* event, int read){
    if((void *) args->buf == NULL){
        return -1;
    }
    event->fd = args->fd;

    // 确保event->len在合法范围内,避免负数或越界值
    if(event->len < 0 || event->len >= MAX_READ_CONTENT_LENGTH){
        event->len = 0;
    }

    // 计算剩余可用空间
    int remaining = MAX_READ_CONTENT_LENGTH - event->len;
    if(remaining <= 0){
        return 0; // 无剩余空间,直接返回
    }

    // 限制读取长度:不能为负,且不超过剩余空间和单次最大读取限制
    if(read <= 0){
        return 0;
    }
    if(read > remaining){
        read = remaining;
    }
    if(read > MAX_READ_CONTENT_LENGTH){
        read = MAX_READ_CONTENT_LENGTH;
    }

    // 此时验证器可确认event->len + read <= MAX_READ_CONTENT_LENGTH,内存访问安全
    long res = bpf_probe_read_user(&event->content[event->len], read, (const void *) args->buf);
    if (res < 0) {
        DEBUG("readData: bpf_probe_read_user return %d", res);
        return -1;
    }
    event->len += read;

    return 0;
}

3. 额外优化:使用无符号类型

将event->len和read改为uint32_t等无符号整数,避免验证器处理负数分支的复杂度,进一步简化数据流分析。

原理说明

eBPF验证器对remaining = MAX - current、read <= remaining这类直接的减法和比较逻辑推导更友好,能直接得出current + read <= MAX的结论,从而确认数组访问的安全性。原代码中的a + b < MAX复合条件虽然逻辑正确,但验证器无法将其关联到数组索引的边界限制,因此需要转换为更直白的剩余空间计算方式。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 00:18:11