eBPF验证器报错‘R1 is not a scalar’,求问题排查思路
eBPF验证器报错原因分析及解决办法
问题描述
编写的eBPF代码
struct sock_info { struct sockaddr addr; }; SEC("tracepoint/syscalls/sys_enter_accept4") int sys_enter_accept4(int fd, struct sockaddr *upeer_sockaddr, int *upeer_addrlen, int flags) { struct sock_info *iad = bpf_ringbuf_reserve(&connections, sizeof(struct sock_info), 0); if (!iad) { bpf_printk("can't reserve ringbuf space"); return 0; } // 尝试读取用户态socket地址 bpf_probe_read(&iad->addr, sizeof(struct sockaddr), upeer_sockaddr); bpf_ringbuf_submit(iad, 0); return 0; }
加载时的验证错误
permission denied R1 is not a scalar ; int sys_enter_accept4(int fd, struct sockaddr *upeer_sockaddr, int *upeer_addrlen, int flags) { 0: (bf) r6 = r2 R2 !read_ok processed 1 insns (limit 1000000) max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0
llvm-objdump输出
llvm-objdump -S --no-show-raw-insn pkg/ebpf/bpf_bpfel.o pkg/ebpf/bpf_bpfel.o: file format elf64-bpf Disassembly of section tracepoint/syscalls/sys_enter_accept4: 0000000000000000 <sys_enter_accept4>: 0: r6 = r2 1: r1 = 0 ll 3: r2 = 16 4: r3 = 0 5: call 131 6: r7 = r0 7: if r7 != 0 goto +5 <LBB0_2> 8: r1 = 0 ll 10: r2 = 28 11: call 6 12: goto +7 <LBB0_3> 0000000000000068 <LBB0_2>: 13: r1 = r7 14: r2 = 16 15: r3 = r6 16: call 4 17: r1 = r7 18: r2 = 0 19: call 132 00000000000000a0 <LBB0_3>: 20: r0 = 0 21: exit
疑问
移除bpf_probe_read后代码可正常加载,但尝试多种读取*upeer_sockaddr内容的方法均失败,请问验证器报错的原因是什么?
报错原因及解决办法
核心原因
报错里的R2 !read_ok是关键——eBPF验证器判定寄存器R2(对应函数参数upeer_sockaddr)指向的内存不具备可读权限。
这是因为sys_enter_accept4 tracepoint触发时,内核还没执行完accept4系统调用:用户态传入的upeer_sockaddr此时可能是空指针,或者内核还未向该地址填充客户端数据。eBPF验证器出于安全防护,会直接拒绝访问这种未被内核标记为“安全可用”的用户态指针。
另外补充:bpf_probe_read是旧版辅助函数,现在推荐用更安全的bpf_probe_read_user,但这不是当前报错的直接原因——哪怕换用新函数,只要指针本身不被验证器认可,还是会报错。
解决思路
要拿到accept4返回的客户端地址,必须跟踪sys_exit_accept4而非sys_enter_accept4:
- 当
sys_exit_accept4触发时,内核已经完成了地址填充操作,此时upeer_sockaddr指向的内存是有效的,验证器会允许访问。 - 需要注意,
sys_exit_accept4的参数结构和sys_enter_accept4不同,要参考对应tracepoint的格式定义获取正确参数。
修正后的代码示例
struct sock_info { struct sockaddr addr; }; // 跟踪sys_exit_accept4而非sys_enter_accept4 SEC("tracepoint/syscalls/sys_exit_accept4") int sys_exit_accept4(long ret, int fd, struct sockaddr *upeer_sockaddr, int *upeer_addrlen, int flags) { // 先检查accept4是否调用成功(ret >=0表示成功) if (ret < 0) { return 0; } struct sock_info *iad = bpf_ringbuf_reserve(&connections, sizeof(struct sock_info), 0); if (!iad) { bpf_printk("can't reserve ringbuf space"); return 0; } // 使用新版的bpf_probe_read_user读取用户态内存 bpf_probe_read_user(&iad->addr, sizeof(struct sockaddr), upeer_sockaddr); bpf_ringbuf_submit(iad, 0); return 0; }
额外说明
- 不同内核版本的tracepoint参数可能有差异,可通过
/sys/kernel/debug/tracing/events/syscalls/sys_exit_accept4/format文件查看当前系统的参数定义。 - 确保用户态程序加载eBPF程序时,正确映射了
connections环形缓冲区。
内容的提问来源于stack exchange,提问作者Mario
相关产品推荐
相关产品推荐

