bpf 如何检查系统调用参数
bpf how to inspect syscall arguments
trace_output_kern.c
跟踪 sys_write
系统调用并在用户空间中打印 pid:
#include <linux/ptrace.h>
#include <linux/version.h>
#include <uapi/linux/bpf.h>
#include "bpf_helpers.h"
struct bpf_map_def SEC("maps") my_map = {
.type = BPF_MAP_TYPE_PERF_EVENT_ARRAY,
.key_size = sizeof(int),
.value_size = sizeof(u32),
.max_entries = 2,
};
SEC("kprobe/sys_write")
int bpf_prog1(struct pt_regs *ctx)
{
struct S {
u64 pid;
u64 cookie;
} data;
data.pid = bpf_get_current_pid_tgid();
data.cookie = 0x12345678;
bpf_perf_event_output(ctx, &my_map, 0, &data, sizeof(data));
return 0;
}
char _license[] SEC("license") = "GPL";
u32 _version SEC("version") = LINUX_VERSION_CODE;
sys_read
的签名为 sys_read(unsigned int fd, char __user *buf, size_t count);
,目前我们只能看到 PID。跟踪的前提是我们可以拦截并检查参数。我也试图查看传递的论点。
如果我将 struct S
更改为保存一个 char 数组以将 char *buf
保存为
struct S {
u64 pid;
u64 cookie;
char bleh[128]; //<-- added this
} data;
它正在发作:
/usr/src/linux-5.4/samples/bpf# ./trace_output
bpf_load_program() err=13
0: (bf) r6 = r1
1: (85) call bpf_get_current_pid_tgid#14
2: (b7) r1 = 305419896
3: (7b) *(u64 *)(r10 -136) = r1
4: (7b) *(u64 *)(r10 -144) = r0
5: (bf) r4 = r10
6: (07) r4 += -144
7: (bf) r1 = r6
8: (18) r2 = 0xffff8975bd44aa00
10: (b7) r3 = 0
11: (b7) r5 = 144
12: (85) call bpf_perf_event_output#25
invalid indirect read from stack off -144+16 size 144
processed 12 insns (limit 1000000) max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0
0: (bf) r6 = r1
1: (85) call bpf_get_current_pid_tgid#14
2: (b7) r1 = 305419896
3: (7b) *(u64 *)(r10 -136) = r1
4: (7b) *(u64 *)(r10 -144) = r0
5: (bf) r4 = r10
6: (07) r4 += -144
7: (bf) r1 = r6
8: (18) r2 = 0xffff8975bd44aa00
10: (b7) r3 = 0
11: (b7) r5 = 144
12: (85) call bpf_perf_event_output#25
invalid indirect read from stack off -144+16 size 144
processed 12 insns (limit 1000000) max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0
如果 sys_write
是一个错误的(问题)示例,我也一直在尝试跟踪 sys_execve
,它的参数列表为
asmlinkage long sys_execve(const char __user *filename,
const char __user *const __user *argv,
const char __user *const __user *envp);
请指点方向,谢谢!
编辑 1
如何拦截用于 __x64_sys_execve
的参数?
当我在下面尝试这个时,
#include <linux/ptrace.h>
#include <linux/version.h>
#include <uapi/linux/bpf.h>
#include "bpf_helpers.h"
struct bpf_map_def SEC("maps") my_map = {
.type = BPF_MAP_TYPE_PERF_EVENT_ARRAY,
.key_size = sizeof(int),
.value_size = sizeof(u32),
.max_entries = 2,
};
//SEC("kprobe/sys_write")
SEC("kprobe/__x64_sys_execve")
/* Signature of sys_execve:
asmlinkage long sys_execve(const char __user *filename,
const char __user *const __user *argv,
const char __user *const __user *envp);
*/
int bpf_prog1(struct pt_regs *ctx, const char *filename)
{
struct S {
u64 pid;
u64 cookie;
char bleh[128];
} data;
data.pid = bpf_get_current_pid_tgid();
data.cookie = 0x12345678;
//bpf_get_current_comm(&data.bleh, 128);
bpf_probe_read(&data.bleh, 128, (void *)filename);
bpf_perf_event_output(ctx, &my_map, 0, &data, sizeof(data));
return 0;
}
char _license[] SEC("license") = "GPL";
u32 _version SEC("version") = LINUX_VERSION_CODE;
就这么炸了:
/usr/src/linux-5.4/samples/bpf# ./borky
bpf_load_program() err=13
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
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
你问题的第一部分由 pchaigno 回答:如果你扩展你的 struct S
并尝试在没有初始化它的情况下读取它 (bpf_perf_event_output(ctx, &my_map, 0, &data, sizeof(data));
),验证者会抱怨,因为读取未初始化的内存从内核引入安全风险。你可以做的是,例如,在声明它时将整个结构归零:
struct S {
u64 pid;
u64 cookie;
char bleh[128];
} data = {0};
关于 sys_execve
问题的第二部分,事实证明您无法像您尝试的那样将系统调用参数传递给函数 bpf_prog1()
。你的函数应该只接受 struct pt_regs *ctx
.
混淆可能来自 bcc 中使用的语法,其中参数以这种方式传递,但重要的是要了解 bcc rewrites some parts 的内幕,特别是关于访问参数的事情。
您可以使用一组 PT_REGS_PARM*(ctx)
宏,这些宏专门定义用于从相关计算机寄存器 (example, definition) 访问被探测函数的参数。我认为 bcc 在重写工作时也会使用它们,但你不会看到它。
trace_output_kern.c
跟踪 sys_write
系统调用并在用户空间中打印 pid:
#include <linux/ptrace.h>
#include <linux/version.h>
#include <uapi/linux/bpf.h>
#include "bpf_helpers.h"
struct bpf_map_def SEC("maps") my_map = {
.type = BPF_MAP_TYPE_PERF_EVENT_ARRAY,
.key_size = sizeof(int),
.value_size = sizeof(u32),
.max_entries = 2,
};
SEC("kprobe/sys_write")
int bpf_prog1(struct pt_regs *ctx)
{
struct S {
u64 pid;
u64 cookie;
} data;
data.pid = bpf_get_current_pid_tgid();
data.cookie = 0x12345678;
bpf_perf_event_output(ctx, &my_map, 0, &data, sizeof(data));
return 0;
}
char _license[] SEC("license") = "GPL";
u32 _version SEC("version") = LINUX_VERSION_CODE;
sys_read
的签名为 sys_read(unsigned int fd, char __user *buf, size_t count);
,目前我们只能看到 PID。跟踪的前提是我们可以拦截并检查参数。我也试图查看传递的论点。
如果我将 struct S
更改为保存一个 char 数组以将 char *buf
保存为
struct S {
u64 pid;
u64 cookie;
char bleh[128]; //<-- added this
} data;
它正在发作:
/usr/src/linux-5.4/samples/bpf# ./trace_output
bpf_load_program() err=13
0: (bf) r6 = r1
1: (85) call bpf_get_current_pid_tgid#14
2: (b7) r1 = 305419896
3: (7b) *(u64 *)(r10 -136) = r1
4: (7b) *(u64 *)(r10 -144) = r0
5: (bf) r4 = r10
6: (07) r4 += -144
7: (bf) r1 = r6
8: (18) r2 = 0xffff8975bd44aa00
10: (b7) r3 = 0
11: (b7) r5 = 144
12: (85) call bpf_perf_event_output#25
invalid indirect read from stack off -144+16 size 144
processed 12 insns (limit 1000000) max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0
0: (bf) r6 = r1
1: (85) call bpf_get_current_pid_tgid#14
2: (b7) r1 = 305419896
3: (7b) *(u64 *)(r10 -136) = r1
4: (7b) *(u64 *)(r10 -144) = r0
5: (bf) r4 = r10
6: (07) r4 += -144
7: (bf) r1 = r6
8: (18) r2 = 0xffff8975bd44aa00
10: (b7) r3 = 0
11: (b7) r5 = 144
12: (85) call bpf_perf_event_output#25
invalid indirect read from stack off -144+16 size 144
processed 12 insns (limit 1000000) max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0
如果 sys_write
是一个错误的(问题)示例,我也一直在尝试跟踪 sys_execve
,它的参数列表为
asmlinkage long sys_execve(const char __user *filename,
const char __user *const __user *argv,
const char __user *const __user *envp);
请指点方向,谢谢!
编辑 1
如何拦截用于 __x64_sys_execve
的参数?
当我在下面尝试这个时,
#include <linux/ptrace.h>
#include <linux/version.h>
#include <uapi/linux/bpf.h>
#include "bpf_helpers.h"
struct bpf_map_def SEC("maps") my_map = {
.type = BPF_MAP_TYPE_PERF_EVENT_ARRAY,
.key_size = sizeof(int),
.value_size = sizeof(u32),
.max_entries = 2,
};
//SEC("kprobe/sys_write")
SEC("kprobe/__x64_sys_execve")
/* Signature of sys_execve:
asmlinkage long sys_execve(const char __user *filename,
const char __user *const __user *argv,
const char __user *const __user *envp);
*/
int bpf_prog1(struct pt_regs *ctx, const char *filename)
{
struct S {
u64 pid;
u64 cookie;
char bleh[128];
} data;
data.pid = bpf_get_current_pid_tgid();
data.cookie = 0x12345678;
//bpf_get_current_comm(&data.bleh, 128);
bpf_probe_read(&data.bleh, 128, (void *)filename);
bpf_perf_event_output(ctx, &my_map, 0, &data, sizeof(data));
return 0;
}
char _license[] SEC("license") = "GPL";
u32 _version SEC("version") = LINUX_VERSION_CODE;
就这么炸了:
/usr/src/linux-5.4/samples/bpf# ./borky
bpf_load_program() err=13
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
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
你问题的第一部分由 pchaigno 回答:如果你扩展你的 struct S
并尝试在没有初始化它的情况下读取它 (bpf_perf_event_output(ctx, &my_map, 0, &data, sizeof(data));
),验证者会抱怨,因为读取未初始化的内存从内核引入安全风险。你可以做的是,例如,在声明它时将整个结构归零:
struct S {
u64 pid;
u64 cookie;
char bleh[128];
} data = {0};
关于 sys_execve
问题的第二部分,事实证明您无法像您尝试的那样将系统调用参数传递给函数 bpf_prog1()
。你的函数应该只接受 struct pt_regs *ctx
.
混淆可能来自 bcc 中使用的语法,其中参数以这种方式传递,但重要的是要了解 bcc rewrites some parts 的内幕,特别是关于访问参数的事情。
您可以使用一组 PT_REGS_PARM*(ctx)
宏,这些宏专门定义用于从相关计算机寄存器 (example, definition) 访问被探测函数的参数。我认为 bcc 在重写工作时也会使用它们,但你不会看到它。