如何在 EBPF 程序中使用 bpf_probe_read() 复制可变长度数据?
How do I copy variable length data using bpf_probe_read() in EBPF programs?
我试图通过 tracepoint/syscalls/sys_enter_read
和 tracepoint/syscalls/sys_exit_read
上的 运行 ebpf 程序在 read() 系统调用中转储用户 space 缓冲区的内容。这个想法是在 sys_enter_read
tracepoint 期间保存用户缓冲区地址,并且,当 read()
returns 时,在 sys_exit_read
内,将用户缓冲区的内容复制到 perf 缓冲区中.所以我正在使用 bpf_probe_read()
从 userspace 缓冲区复制内容。然而,验证者在加载时抱怨。据我了解,验证者希望确保对 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
数组,然后再将其传递给助手,例如:
char tmp_buffer[128] = {0};
这是因为内核验证程序发现您将此数组传递给了内核助手。验证者不知道助手对缓冲区做了什么:就其所知,它可能是一个向用户 space 发送数据的助手(例如,如果您调用 bpf_trace_printk()
)。在这种情况下,将未初始化的数据发送给用户 space 会带来安全风险,因为缓冲区中的未初始化数据可能是敏感的和可利用的。
因此,如果您之前没有初始化数据,验证程序会阻止您将分配在 eBPF 堆栈上的缓冲区传递给帮助程序。这就是invalid indirect read from stack R1
的意思:R1
是传递给helper的第一个参数(即tmp_buffer
),读取不正确,因为内存还没有初始化。
我试图通过 tracepoint/syscalls/sys_enter_read
和 tracepoint/syscalls/sys_exit_read
上的 运行 ebpf 程序在 read() 系统调用中转储用户 space 缓冲区的内容。这个想法是在 sys_enter_read
tracepoint 期间保存用户缓冲区地址,并且,当 read()
returns 时,在 sys_exit_read
内,将用户缓冲区的内容复制到 perf 缓冲区中.所以我正在使用 bpf_probe_read()
从 userspace 缓冲区复制内容。然而,验证者在加载时抱怨。据我了解,验证者希望确保对 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
数组,然后再将其传递给助手,例如:
char tmp_buffer[128] = {0};
这是因为内核验证程序发现您将此数组传递给了内核助手。验证者不知道助手对缓冲区做了什么:就其所知,它可能是一个向用户 space 发送数据的助手(例如,如果您调用 bpf_trace_printk()
)。在这种情况下,将未初始化的数据发送给用户 space 会带来安全风险,因为缓冲区中的未初始化数据可能是敏感的和可利用的。
因此,如果您之前没有初始化数据,验证程序会阻止您将分配在 eBPF 堆栈上的缓冲区传递给帮助程序。这就是invalid indirect read from stack R1
的意思:R1
是传递给helper的第一个参数(即tmp_buffer
),读取不正确,因为内存还没有初始化。