eBPF验证器不允许返回被调用函数u16类型值的问题解析
问题描述
我编写了以下eBPF函数:
SEC(...) int BPF_PROG(...) { ... u16 mount_last_index = get_file_mount_root(file, event_info->mountroot); ... } static inline u16 get_file_mount_root(struct file *file, u8 buf[MAX_PATH_LENGTH]) { struct path f_path = BPF_CORE_READ(file, f_path); return get_path_mount_root(&f_path, buf); } static inline u16 get_path_mount_root(const struct path *path, u8 buf[MAX_PATH_LENGTH]) { struct mount *mnt = get_mount(path); struct dentry *mnt_mountpoint = BPF_CORE_READ(mnt, mnt_mountpoint); return read_dentry_strings(mnt_mountpoint, buf, 0); } static inline u16 read_dentry_strings(struct dentry *dentry, u8 buf[MAX_PATH_LENGTH], u16 cur_index) { if (!dentry) return 0; if (cur_index >= MAX_PATH_LENGTH) return 0; struct dentry *curr_dtry = dentry; struct dentry *lastdtryp = dentry; for (int capacity = MAX_PATH_LENGTH; capacity > 0; capacity--) { if (cur_index + MAX_DIR_NAME_LENGTH >= MAX_PATH_LENGTH) return cur_index <= MAX_PATH_LENGTH ? cur_index : MAX_PATH_LENGTH; const unsigned char *name = BPF_CORE_READ(curr_dtry, d_name.name); long read_len = bpf_core_read_str(&buf[cur_index], MAX_DIR_NAME_LENGTH, name); if (read_len > 1) { capacity -= (read_len - 1); if (cur_index + read_len >= MAX_PATH_LENGTH) break; cur_index += read_len; struct dentry *parent = BPF_CORE_READ(curr_dtry, d_parent); if (parent != lastdtryp) { lastdtryp = parent; curr_dtry = parent; buf[cur_index - 1] = '/'; buf[cur_index] = '\0'; } else { if (buf[cur_index - 2] == '/') { buf[cur_index - 2] = '\0'; cur_index = cur_index - 2; } break; } } else { break; } } return cur_index; }
运行时出现如下错误:
=5 341: (c7) r8 s>>= 32 ; frame1: R8_w=scalar(umin=2566,umax=4084,var_off=(0x800; 0x7ff),s32_min=2048,s32_max=4095,u32_min=2048,u32_max=4095) refs=5 ; for (int capacity = MAX_PATH_LENGTH; capacity > 0; capacity--) 342: (b7) r2 = 1 ; frame1: R2_w=1 refs=5 343: (6d) if r2 s> r8 goto pc+6 ; frame1: R2_w=1 R8_w=scalar(umin=2566,umax=4084,var_off=(0x800; 0x7ff)) refs=5 344: (bf) r2 = r7 ; frame1: R2_w=scalar(id=31,umin=2,umax=1530,var_off=(0x0; 0x7ff)) R7_w=scalar(id=31,umin=2,umax=1530,var_off=(0x0; 0x7ff)) refs=5 345: (57) r2 &= 65535 ; frame1: R2_w=scalar(umax=1530,var_off=(0x0; 0x7ff)) refs=5 346: (bf) r9 = r7 ; frame1: R7_w=scalar(id=31,umin=2,umax=1530,var_off=(0x0; 0x7ff)) R9_w=scalar(id=31,umin=2,umax=1530,var_off=(0x0; 0x7ff)) refs=5 347: (bf) r6 = r1 ; frame1: R1_w=scalar(id=32) R6_w=scalar(id=32) refs=5 348: (b7) r1 = 3841 ; frame1: R1_w=3841 refs=5 349: (2d) if r1 > r2 goto pc+1 ; frame1: R0_w=scalar() R1_w=3841 R2_w=scalar(umax=1530,var_off=(0x0; 0x7ff)) R3_w=47 R6_w=scalar(id=32) R7_w=scalar(id=31,umin=2,umax=1530,var_off=(0x0; 0x7ff)) R8_w=scalar(umin=2566,umax=4084,var_off=(0x800; 0x7ff)) R9_w=scalar(id=31,umin=2,umax=1530,var_off=(0x0; 0x7ff)) R10=fp0 fp-8=mmmmmmmm fp-16=mmmmmmmm fp-24=mmmmmmmm fp-32=alloc_mem refs=5 ; return get_path_mount_root(&f_path, buf); 351: (bf) r3 = r6 ; frame1: R3_w=scalar(id=32) R6_w=scalar(id=32) refs=5 352: (b7) r1 = 40 (truncated) [06:22:38.693] ERROR [EBPF] failed to load objects: field DoSysOpenat2: program do_sys_openat2: load program: permission denied: 351: (bf) r3 = r6 ; frame1: R3_w=scalar(id=32) R6_w=scalar(id=32) refs=5: 352: (b7) r1 = 40 (truncated, 770 line(s) omitted) exit status 1 make: *** [Makefile:5: runEbpf] Error 1
当我将任意一个函数的返回类型改为非u16类型(例如u8)时,即便后续函数期望u16类型,程序也能正常运行:
static inline u8 get_path_mount_root(const struct path *path, u8 buf[MAX_PATH_LENGTH]) { struct mount *mnt = get_mount(path); struct dentry *mnt_mountpoint = BPF_CORE_READ(mnt, mnt_mountpoint); return read_dentry_strings(mnt_mountpoint, buf, 0); }
原因分析
这是eBPF验证器对16位整数类型的边界检查逻辑缺陷导致的:
- 寄存器宽度不匹配:eBPF通用寄存器是64位,返回
u16时编译器会生成& 65535的截断操作,但验证器无法正确跟踪截断后值的实际范围,误判其可能达到65535的上限。 - 范围跟踪失效:你的函数返回的
cur_index实际最大值是MAX_PATH_LENGTH(远小于65535),但验证器没识别到这个约束。当用该值作为数组索引时,会被判定存在越界风险。 - 非16位类型的简化检查:改为
u8后,验证器对8位值的范围跟踪逻辑更简单,能正确识别其最大值不会超过数组边界,因此通过检查。截断后的值零扩展为64位也不会影响业务逻辑。
解决方法
方案1:显式约束返回值范围
在每个返回u16的函数中,给返回值加上明确的上限限制,帮助验证器识别实际边界:
static inline u16 read_dentry_strings(struct dentry *dentry, u8 buf[MAX_PATH_LENGTH], u16 cur_index) { // ... 原有逻辑 ... // 显式限制返回值不超过数组长度 return cur_index > MAX_PATH_LENGTH ? MAX_PATH_LENGTH : cur_index; }
调用链上的其他u16返回函数也需要添加同样的约束。
方案2:改用32位整数类型
eBPF验证器对32位整数的范围跟踪更成熟,将所有相关函数的返回类型改为u32:
static inline u32 get_file_mount_root(struct file *file, u8 buf[MAX_PATH_LENGTH]) { struct path f_path = BPF_CORE_READ(file, f_path); return get_path_mount_root(&f_path, buf); } static inline u32 get_path_mount_root(const struct path *path, u8 buf[MAX_PATH_LENGTH]) { struct mount *mnt = get_mount(path); struct dentry *mnt_mountpoint = BPF_CORE_READ(mnt, mnt_mountpoint); return read_dentry_strings(mnt_mountpoint, buf, 0); } static inline u32 read_dentry_strings(struct dentry *dentry, u8 buf[MAX_PATH_LENGTH], u32 cur_index) { // ... 原有逻辑,将cur_index相关检查调整为u32 ... return cur_index; }
方案3:用局部变量传递范围信息
在函数内部用局部变量保存返回值,并显式做范围限制,引导验证器跟踪正确的边界:
static inline u16 read_dentry_strings(struct dentry *dentry, u8 buf[MAX_PATH_LENGTH], u16 cur_index) { // ... 原有逻辑 ... u16 ret = cur_index; // 显式告知验证器返回值上限 if (ret > MAX_PATH_LENGTH) ret = MAX_PATH_LENGTH; return ret; }
内容的提问来源于stack exchange,提问作者ZOOM SMASH
相关产品推荐
相关产品推荐

