eBPF - 无法从跟踪点 sys_enter_execve 读取 argv 和 envp

问题描述

我学习 BPF 是为了自己的乐趣,我很难弄清楚如何从传递给 sys_enter_execve 的 eBPF 程序的上下文中读取 argvenvp

我将在这里展示我的 BPF 程序,然后更详细地解释我想要实现的目标。

这是我的代码

#include <linux/bpf.h>
#include <bpf_helpers.h>

struct
{
    __uint(type,BPF_MAP_TYPE_ARRAY);
    __type(key,__u32);
    __type(value,char[300]);
    __uint(max_entries,1);
} mymap SEC(".maps");

// Based on /sys/kernel/debug/tracing/events/syscalls/sys_enter_execve/format
struct execve_args {
    short common_type;
    char common_flags;
    char common_preempt_count;
    int common_pid;
    int __syscall_nr;
    char *filename;
    const char *const *argv;
    const char *const *envp;
};

SEC("tracepoint/syscalls/sys_enter_execve")
int bpf_prog(struct execve_args *ctx) {
  
  __u32 index = 0;
  __u64 *value = bpf_map_lookup_elem(&mymap,&index);

  // An array of length 300 is purely arbitrary here
  char fn[300];

    // null check for the value fetched from the map
    if (value){
     
      // trying here to get the first env var passed to the process
      // started with execve
      const char *const first_env_value = ctx->envp[0];

      // null check
      if (!first_env_value){
        return 0;
      }
      
      // trying to safely read the value pointed by first_env_value
      bpf_probe_read_user_str(fn,sizeof(fn),first_env_value);
      bpf_map_update_elem(&mymap,&index,fn,BPF_ANY);
      return 0;
    
    }

    return 0;
}

char _license[] SEC("license") = "GPL";

在这里,我想要的是最终读取 ctx->envp 引用的第一个环境变量并将其保存在地图中。

构建程序成功,但当我尝试将其加载到内核时失败:

8: (15) if r0 == 0x0 goto pc+15
 R0_w=map_value(id=0,off=0,ks=4,vs=300,imm=0) R6_w=ctx(id=0,imm=0) R10=fp0 fp-8=mmmm????
; const char *const first_env_value = ctx->envp[0];
9: (79) r1 = *(u64 *)(r6 +32)
; const char *const first_env_value = ctx->envp[0];
10: (79) r3 = *(u64 *)(r1 +0)
R1 invalid mem access 'inv'
processed 10 insns (limit 1000000) max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0

我使用 Cilium 项目中的 bpf2go 将 BPF 程序加载到内核中。我使用 Go 程序读取 BPF 映射。

有人可以给我一些关于我做错了什么的提示吗?

也许是双指针让我困惑(const char *const *envp),也许我误解了sys_enter_execve系统调用和tracepoint输入等。

任何提示将不胜感激!

我不是内核开发人员。我主要用 Go 和 Python 编写代码,但我真的很想学习如何用纯 C 编写 BPF 程序,只是为了好玩。

提前致谢

解决方法

TL;DR。您正在尝试读取任意内核内存。为此,您需要使用 bpf_probe_read


让我们看看错误日志:

无效的内存访问是从 r1 加载的。 r1 中的值是使用 r6 中的地址作为基址从内存中加载的。根据第二行,验证器将类型 ctx 关联到 r6

所以 r6 指向您的变量 ctx。该变量是特殊的(因此验证器具有特殊的 ctx 类型)。只要有界(确切的界取决于程序类型),你的 BPF 程序就可以访问该变量指向的内存。

8: (15) if r0 == 0x0 goto pc+15
 R0_w=map_value(id=0,off=0,ks=4,vs=300,imm=0) R6_w=ctx(id=0,imm=0) R10=fp0 fp-8=mmmm????
; const char *const first_env_value = ctx->envp[0];
9: (79) r1 = *(u64 *)(r6 +32)
; const char *const first_env_value = ctx->envp[0];
10: (79) r3 = *(u64 *)(r1 +0)
R1 invalid mem access 'inv'

但是,您从 ctx->envp 中检索的值(存储在 r1 中的值)不是 ctx 的一部分,并且可能指向任意内核内存。因此,BPF 验证器无法提前确保访问的安全性并拒绝您的程序。

您需要使用 BPF 助手 bpf_probe_read 来访问该内存。该助手将执行运行时检查以确保内存访问是安全的。如果不安全,则会返回负错误。

,

非常感谢@pchaigno,你说得对。为了向其他人展示我是如何解决我的问题的,以下是我根据 pchaigno 的回答给出的解决方案。

#include <linux/bpf.h>
#include <bpf_helpers.h>

struct
{
    __uint(type,BPF_MAP_TYPE_ARRAY);
    __type(key,__u32);
    __type(value,char[300]);
    __uint(max_entries,1);
} mymap SEC(".maps");

// Based on /sys/kernel/debug/tracing/events/syscalls/sys_enter_execve/format
struct execve_args {
    short common_type;
    char common_flags;
    char common_preempt_count;
    int common_pid;
    int __syscall_nr;
    char *filename;
    const char *const *argv;
    const char *const *envp;
};

SEC("tracepoint/syscalls/sys_enter_execve")
int bpf_prog(struct execve_args *ctx) {
  
  __u32 index = 0;

  // Here we reserve a pointer to the first env var
  char *first_env_var;

  // Here we attempt to read the value pointed by ctx->envp[0] and store it in *first_env_var
  long res = bpf_probe_read(&first_env_var,sizeof(first_env_var),&ctx->envp[2]);

  // For demo purposes,simply return from the program 
  // if there is an error with bpf_probe_read
  if (res != 0){
    return 0;
  }
  
  // Read the value pointed by the (now) safe pointer *first_env_var
  // and store the value in 'value'
  char value[300];
  bpf_probe_read_str(value,sizeof(value),first_env_var);

  // Copy the value to the map
  bpf_map_update_elem(&mymap,&index,&value,BPF_ANY);
      
    return 0;
}

char _license[] SEC("license") = "GPL";