eBPF程序调用bpf_probe_read()遇权限拒绝问题排查求助
问题分析与解决方案
错误根因
你遇到的R3 !read_ok错误,是eBPF验证器判定寄存器R3(对应sys_accept的addr参数)指向的内存不可读,核心问题有三个:
- 结构体设计错误:
accept_args_t里的addr是指针类型,栈上初始化的new_accept_args.addr是空指针,调用bpf_probe_read往空指针写入数据,属于非法内存访问。 - 用户空间内存访问错误:
sys_accept的addr是用户空间地址,eBPF内核程序不能直接用bpf_probe_read访问用户空间内存,需要用专门的用户空间读取函数。 - syscall阶段选择错误:在
sys_accept的entry探针中,内核还未将客户端地址写入用户空间的addr缓冲区,此时读取到的是无效数据。
修复代码示例
1. 修改结构体定义
把指针类型改为实际的struct sockaddr_in结构体,存储具体数据而非指针:
struct accept_args_t { struct sockaddr_in addr; };
2. 修正内存读取逻辑
使用bpf_probe_read_user读取用户空间内存,同时建议移到sys_accept的exit探针中读取(确保内核已填充数据):
// 先在entry探针中保存syscall参数到临时map SEC("kprobe/sys_accept") int syscall__probe_entry_accept(struct pt_regs *ctx, int sockfd, struct sockaddr *addr, socklen_t *addrlen) { u64 id = bpf_get_current_pid_tgid(); u64 pid = id >> 32; if (pid != PID) return 0; struct { struct sockaddr *addr; socklen_t *addrlen; } entry = {addr, addrlen}; bpf_map_update_elem(&active_accept_args_map, &id, &entry, BPF_ANY); return 0; } // 在exit探针中读取已填充的地址数据 SEC("kretprobe/sys_accept") int syscall__probe_exit_accept(struct pt_regs *ctx) { u64 id = bpf_get_current_pid_tgid(); u64 pid = id >> 32; if (pid != PID) return 0; int ret = PT_REGS_RC(ctx); if (ret < 0) { bpf_map_delete_elem(&active_accept_args_map, &id); return 0; } struct { struct sockaddr *addr; socklen_t *addrlen; } *entry = bpf_map_lookup_elem(&active_accept_args_map, &id); if (!entry) return 0; struct accept_args_t new_accept_args = {}; socklen_t len = 0; bpf_probe_read_user(&len, sizeof(len), entry->addrlen); if (len > sizeof(new_accept_args.addr)) len = sizeof(new_accept_args.addr); bpf_probe_read_user(&new_accept_args.addr, len, entry->addr); bpf_map_update_elem(&accept_result_map, &id, &new_accept_args, BPF_ANY); bpf_map_delete_elem(&active_accept_args_map, &id); return 0; }
错误排查方法
- 解析验证器错误:
R3:对应kprobe函数的第三个参数(struct sockaddr *addr),验证器会跟踪每个寄存器的内存有效性。!read_ok:表示该寄存器指向的内存无法安全读取,可能是指针未初始化、指向用户空间未用专用函数访问、或内存区域无访问权限。
- 检查指针有效性:所有指针在使用前必须指向已分配的有效内存(栈上结构体、map中的内存),禁止使用空指针。
- 区分内核/用户空间内存:
- 访问内核空间内存用
bpf_probe_read或bpf_probe_read_kernel。 - 访问用户空间内存必须用
bpf_probe_read_user或bpf_copy_from_user。
- 访问内核空间内存用
- 确认syscall阶段:对于需要获取返回结果或内核填充数据的场景,优先使用kretprobe(exit探针)而非kprobe(entry探针)。
可查询错误含义的资源
- Linux内核文档:内核源码目录下的
Documentation/bpf/verifier.rst,详细讲解eBPF验证器的检查规则和错误类型。 - man手册:
man bpf、man bpf-helpers,包含验证器错误的基础说明和辅助函数的使用规范。 - 内核源码:查看
kernel/bpf/verifier.c中的错误提示生成逻辑,能直接找到每个验证错误的触发条件。
内容的提问来源于stack exchange,提问作者Gourav Kumar
相关产品推荐
相关产品推荐

