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

eBPF程序调用bpf_probe_read()遇权限拒绝问题排查求助

问题分析与解决方案

错误根因

你遇到的R3 !read_ok错误,是eBPF验证器判定寄存器R3(对应sys_accept的addr参数)指向的内存不可读,核心问题有三个:

  1. 结构体设计错误:accept_args_t里的addr是指针类型,栈上初始化的new_accept_args.addr是空指针,调用bpf_probe_read往空指针写入数据,属于非法内存访问。
  2. 用户空间内存访问错误:sys_accept的addr是用户空间地址,eBPF内核程序不能直接用bpf_probe_read访问用户空间内存,需要用专门的用户空间读取函数。
  3. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 22:42:38