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
相关产品推荐
相关产品推荐

