我试图通过在tracepoint/syscalls/sys_enter_read
和tracepoint/syscalls/sys_exit_read
上运行ebpf程序来转储read()系统调用中的用户空间缓冲区的内容。这个想法是在sys_enter_read
跟踪点期间保存用户缓冲区地址,并且,当read()
返回时,在sys_exit_read
内部,将用户缓冲区的内容复制到perf缓冲区中。所以我使用bpf_probe_read()
从用户空间缓冲区复制内容。然而,验证者在加载时抱怨。据我所知,验证者希望确保size
参数的绑定,从而防止加载。那么如何复制可变长度数据呢?
我在5.13内核上尝试这个
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <stddef.h>
struct read_exit_ctx {
unsigned long long unused;
int __syscall_nr;
long ret;
};
struct read_enter_ctx {
unsigned long long unused;
int __syscall_nr;
unsigned int padding;
unsigned long fd;
char* buf;
size_t count;
};
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(max_entries, 1);
__type(key, int);
__type(value, void*);
} saved_read_ctx SEC(".maps");
SEC("tracepoint/syscalls/sys_enter_read")
int trace_read_enter(struct read_enter_ctx *ctx)
{
int zero = 0;
void *p = ctx->buf;
bpf_map_update_elem(&saved_read_ctx, &zero, &p, BPF_ANY);
return 0;
}
SEC("tracepoint/syscalls/sys_exit_read")
int trace_read_exit(struct read_exit_ctx *ctx)
{
char tmp_buffer[128];
#define KEY_SIZE (sizeof(tmp_buffer) - 1)
int zero = 0;
void **ubuf = bpf_map_lookup_elem(&saved_read_ctx, &zero);
if (!ubuf) {
return 0;
}
if (ctx->ret <= 0) {
return 0;
}
unsigned int ret = ctx->ret;
// for the time being, copy to a stack buffer instead of perf buffer
bpf_probe_read(tmp_buffer, ret & KEY_SIZE, *ubuf);
return 0;
}
char _license[] SEC("license") = "GPL";
# ./a.out
libbpf: load bpf program failed: Permission denied
libbpf: -- BEGIN DUMP LOG ---
libbpf:
R1 type=ctx expected=fp
; int trace_read_exit(struct read_exit_ctx *ctx)
0: (bf) r6 = r1
1: (b7) r1 = 0
; int zero = 0;
2: (63) *(u32 *)(r10 -132) = r1
last_idx 2 first_idx 0
regs=2 stack=0 before 1: (b7) r1 = 0
3: (bf) r2 = r10
;
4: (07) r2 += -132
; void **ubuf = bpf_map_lookup_elem(&saved_read_ctx, &zero);
5: (18) r1 = 0xffff920183aa8e00
7: (85) call bpf_map_lookup_elem#1
; if (!ubuf) {
8: (15) if r0 == 0x0 goto pc+8
R0_w=map_value(id=0,off=0,ks=4,vs=8,imm=0) R6_w=ctx(id=0,off=0,imm=0) R10=fp0 fp-136=mmmm????
; if (ctx->ret <= 0) {
9: (79) r2 = *(u64 *)(r6 +16)
10: (b7) r1 = 1
; if (ctx->ret <= 0) {
11: (6d) if r1 s> r2 goto pc+5
R0=map_value(id=0,off=0,ks=4,vs=8,imm=0) R1=inv1 R2=inv(id=0,umin_value=1,umax_value=9223372036854775807,var_off=(0x0; 0x7fffffffffffffff)) R6=ctx(id=0,off=0,imm=0) R10=fp0 fp-136=mmmm????
; bpf_probe_read(tmp_buffer, ret & KEY_SIZE, *ubuf);
12: (79) r3 = *(u64 *)(r0 +0)
R0=map_value(id=0,off=0,ks=4,vs=8,imm=0) R1=inv1 R2=inv(id=0,umin_value=1,umax_value=9223372036854775807,var_off=(0x0; 0x7fffffffffffffff)) R6=ctx(id=0,off=0,imm=0) R10=fp0 fp-136=mmmm????
; bpf_probe_read(tmp_buffer, ret & KEY_SIZE, *ubuf);
13: (57) r2 &= 127
14: (bf) r1 = r10
;
15: (07) r1 += -128
; bpf_probe_read(tmp_buffer, ret & KEY_SIZE, *ubuf);
16: (85) call bpf_probe_read#4
invalid indirect read from stack R1 off -128+0 size 127
processed 16 insns (limit 1000000) max_states_per_insn 0 total_states 1 peak_states 1 mark_read 1
libbpf: -- END LOG --
libbpf: failed to load program 'trace_read_exit'
libbpf: failed to load object './EXE'
编辑:我初始化了注释中提到的tmp_buffer[128]
数组,bpf程序加载成功。
您需要在将tmp_buffer
数组传递给helper之前初始化它,例如:
char tmp_buffer[128] = {0};
这是因为内核验证器看到您将该数组传递给内核助手。验证器不知道helper对缓冲区做了什么:对于它所知道的一切,它可能是一个向用户空间发送数据的helper(例如,如果您调用bpf_trace_printk()
)。在这种情况下,将未初始化的数据发送到用户空间会带来安全风险,因为缓冲区中未初始化的数据可能是敏感的和可利用的。
由于这个原因,如果您之前没有初始化数据,验证器会阻止您将在eBPF堆栈上分配的缓冲区传递给helper。这就是invalid indirect read from stack R1
的含义:R1
是传递给助手的第一个参数(即tmp_buffer
),并且读取是不正确的,因为内存尚未初始化。