eBPF - 无法从跟踪点读取 argv 和 envp sys_enter_execve
eBPF - Cannot read argv and envp from tracepoint sys_enter_execve
我学习 BPF 是为了自己的乐趣,我很难弄清楚如何从传递给我的 eBPF 程序的上下文中读取 argv
和 envp
sys_enter_execve
我会在这里展示我的 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,off=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
系统调用和跟踪点输入等
如有任何提示,我们将不胜感激!
我不是内核开发人员。我主要用 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,off=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
来访问该内存。该助手将执行运行时检查以确保内存访问是安全的。如果它不安全,它将 return 一个负错误。
非常感谢@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";
我学习 BPF 是为了自己的乐趣,我很难弄清楚如何从传递给我的 eBPF 程序的上下文中读取 argv
和 envp
sys_enter_execve
我会在这里展示我的 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,off=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
系统调用和跟踪点输入等
如有任何提示,我们将不胜感激!
我不是内核开发人员。我主要用 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,off=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
来访问该内存。该助手将执行运行时检查以确保内存访问是安全的。如果它不安全,它将 return 一个负错误。
非常感谢@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";