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

eBPF验证器报'unbounded variable-offset read'错误,读取已做安全边界检查

问题

我编写了一个eBPF程序用于比较字符串结尾,核心代码如下:

char buffer[MAX_FILE_NAME];
int length = bpf_probe_read_str(buffer, sizeof(buffer), file_name) - 1;

if (length < 0 || length >= (MAX_FILE_NAME - 1)) {
    bpf_trace_printk("ERROR: Failed to read file name. \n");
    return 1;
}

if (length == 0) {
    return 0;
}

if (ends_with(file->file_name, file->length, buffer, length)) {
    bpf_trace_printk("INFO: Accessed file: %s \n", file->file_name);
}

return 0;

为实现字符串结尾比较,我生成了展开至32字符的ends_with系列内联函数,示例如下:

inline bool ends_with09(char needle[], const char haystack[], int haystack_length) {

    int haystack_start = haystack_length -  9;

    if (haystack_start < 0) {
        return false; 
    }

    return
      needle [0] == haystack[(haystack_start +  0)] &&
      needle [1] == haystack[(haystack_start +  1)] &&
      needle [2] == haystack[(haystack_start +  2)] &&
      needle [3] == haystack[(haystack_start +  3)] &&
      needle [4] == haystack[(haystack_start +  4)] &&
      needle [5] == haystack[(haystack_start +  5)] &&
      needle [6] == haystack[(haystack_start +  6)] &&
      needle [7] == haystack[(haystack_start +  7)] &&
      needle [8] == haystack[(haystack_start +  8)];
};

inline bool ends_with(char needle[], u8 needle_length, char haystack[], int haystack_length) {

    switch (needle_length) {

      case  1: return ends_with01(needle, haystack, haystack_length);
      case  2: return ends_with02(needle, haystack, haystack_length);
      /* ... more generated code ... */
      case  9: return ends_with09(needle, haystack, haystack_length);
      /* ... more generated code ... */
      case 31: return ends_with31(needle, haystack, haystack_length);
      case 32: return ends_with32(needle, haystack, haystack_length);
      default: return false;
    }
};

但eBPF验证器持续抛出错误:

invalid unbounded variable-offset read from stack R2

或:

R2 invalid mem access 'scalar'

即使添加额外边界检查,程序仍无法通过验证:

inline bool ends_with09(char needle[], const char haystack[], int haystack_length) {

    int haystack_start = haystack_length -  9;

    if (haystack_start < 0 || haystack_start + 8 >= haystack_length) {
        return false; 
    }

    return
      needle [0] == haystack[(haystack_start +  0)] &&
      needle [1] == haystack[(haystack_start +  1)] &&
      needle [2] == haystack[(haystack_start +  2)] &&
      needle [3] == haystack[(haystack_start +  3)] &&
      needle [4] == haystack[(haystack_start +  4)] &&
      needle [5] == haystack[(haystack_start +  5)] &&
      needle [6] == haystack[(haystack_start +  6)] &&
      needle [7] == haystack[(haystack_start +  7)] &&
      needle [8] == haystack[(haystack_start +  8)];
};

解决方案

1. 用bpf_probe_read安全读取内核内存

eBPF程序不能直接访问内核空间指针(比如file->file_name指向的内存),必须通过bpf_probe_read系列函数读取。直接数组访问会让验证器认为偏移是未约束的标量,触发安全检查失败。修改ends_withN函数,逐个读取haystack的字符:

inline bool ends_with09(char needle[], const char haystack[], int haystack_length) {
    int haystack_start = haystack_length - 9;
    
    // 明确约束:haystack长度必须至少为9,且起始位置合法
    if (haystack_length < 9 || haystack_start < 0) {
        return false;
    }

    char c0, c1, c2, c3, c4, c5, c6, c7, c8;
    // 逐个读取目标位置的字符
    bpf_probe_read(&c0, sizeof(c0), &haystack[haystack_start + 0]);
    bpf_probe_read(&c1, sizeof(c1), &haystack[haystack_start + 1]);
    bpf_probe_read(&c2, sizeof(c2), &haystack[haystack_start + 2]);
    bpf_probe_read(&c3, sizeof(c3), &haystack[haystack_start + 3]);
    bpf_probe_read(&c4, sizeof(c4), &haystack[haystack_start + 4]);
    bpf_probe_read(&c5, sizeof(c5), &haystack[haystack_start + 5]);
    bpf_probe_read(&c6, sizeof(c6), &haystack[haystack_start + 6]);
    bpf_probe_read(&c7, sizeof(c7), &haystack[haystack_start + 7]);
    bpf_probe_read(&c8, sizeof(c8), &haystack[haystack_start + 8]);

    // 比较字符
    return needle[0] == c0 &&
           needle[1] == c1 &&
           needle[2] == c2 &&
           needle[3] == c3 &&
           needle[4] == c4 &&
           needle[5] == c5 &&
           needle[6] == c6 &&
           needle[7] == c7 &&
           needle[8] == c8;
}

2. 强化边界约束,让验证器能识别安全范围

eBPF验证器的静态分析能力有限,需要更直白的边界条件。比如在ends_with主函数中,先检查haystack_length >= needle_length,再调用对应子函数,这样子函数里的起始位置计算可以简化:

inline bool ends_with(char needle[], u8 needle_length, char haystack[], int haystack_length) {
    // 先做全局检查:haystack长度必须不小于needle长度
    if (haystack_length < needle_length) {
        return false;
    }

    switch (needle_length) {
      case  1: return ends_with01(needle, haystack, haystack_length);
      case  2: return ends_with02(needle, haystack, haystack_length);
      /* ... 其他case ... */
      case  9: return ends_with09(needle, haystack, haystack_length);
      /* ... 其他case ... */
      case 32: return ends_with32(needle, haystack, haystack_length);
      default: return false;
    }
}

这样子函数里只需要检查haystack_start >=0(而这已经被全局检查覆盖),验证器更容易确认访问的安全性。

3. 避免栈上的可变偏移访问

如果haystack是栈上的变量,验证器同样会对可变偏移的栈访问报错。此时可以将栈上的buffer复制到eBPF map中,或者用固定偏移的方式读取,但更简单的是直接用bpf_probe_read读取栈内容(虽然栈访问本身允许,但可变偏移会触发检查)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 13:55:52