本篇介绍
ebpf 是一种观测系统行为的方法, 全称是Extended Berkeley Packet Filter, 本来是观测网络数据包的, 后来由于功能太过强大与方便, 于是越来越多的系统也接入了. 因此目前我们通过ebpf 可以看到非常多的信息. 本系列主要是记录ebpf的一些使用, 等了解使用后, 在慢慢深入机制.
ebpf 的hello world
ebpf 核心部分是内核添加了一个虚拟机, 可以解释bpf指令,而用户态的代码编译成bpf指令后,就可以通过bpf系统调用加载到内核中,由内核的bpf 虚拟机来执行. 而内核的虚拟机可以保证安全,不用担心crash等问题.
接下来我们就先看一段bpf用户态的代码:
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#ifdef SEC
#undef SEC
#endif
#define SEC(NAME) __attribute__((section(NAME), used))
SEC("tracepoint/syscalls/sys_enter_execve")
int bpf_prog(void *ctx) {
char msg[] = "Hello, BPF World";
bpf_trace_printk(msg, sizeof(msg));
return 0;
}
char _license[] SEC("license") = "GPL";
这儿的SEC 就是告诉VM 执行该段bpf指令的时机.
使用clang指定target就可以编译bpf
clang -O2 -target bpf -c hello.c -o hello.o
接下来使用bpftool 就可以加载这个目标文件
bpftool prog load hello.o /sys/fs/bpf/hello type raw_tracepoint
load 成功以后,就可以在对应节点上看到:
root@shanks-ThinkPad-T460s:/sys/fs/bpf# ll
-rw------- 1 root root 0 8月 27 21:44 hello
drwx------ 2 root root 0 8月 26 10:38 snap/
利用bpftool 指令也可以看到
root@shanks-ThinkPad-T460s:/sys/fs/bpf# bpftool prog show id 127
127: raw_tracepoint name bpf_prog tag a843b44ef43e57e8 gpl
loaded_at 2024-08-27T21:44:06+0800 uid 0
xlated 112B jited 74B memlock 4096B map_ids 10
甚至可以dump对应的指令
root@shanks-ThinkPad-T460s:/sys/fs/bpf# bpftool prog dump xlated id 127
0: (18) r1 = 0x646c726f57204650
2: (7b) *(u64 *)(r10 -16) = r1
3: (18) r1 = 0x42202c6f6c6c6548
5: (7b) *(u64 *)(r10 -24) = r1
6: (b7) r1 = 0
7: (73) *(u8 *)(r10 -8) = r1
8: (bf) r1 = r10
9: (07) r1 += -24
10: (b7) r2 = 17
11: (85) call bpf_trace_printk#-111824
12: (b7) r0 = 0
13: (95) exit
可以看到是在打印log, r1 指向的是字符串地址,r2 是字符串长度.
接下来可以查看bpf 日志
root@shanks-ThinkPad-T460s:/sys/kernel/debug/tracing# bpftool prog trace log |more
docker-981042 [000] ..... 177140.763737: sys_execve(filename: 7fffdae4b580, argv: 3d6003715020, envp: 3d600086d880
)
docker-981042 [000] ..... 177140.763769: sys_execve(filename: 7fffdae4b580, argv: 3d6003715020, envp: 3d600086d880
)
docker-981042 [000] ..... 177140.763778: sys_execve(filename: 7fffdae4b580, argv: 3d6003715020, envp: 3d600086d880
)
docker-981042 [000] ..... 177140.763788: sys_execve(filename: 7fffdae4b580, argv: 3d6003715020, envp: 3d600086d880
)
docker-981042 [000] ..... 177140.763797: sys_execve(filename: 7fffdae4b580, argv: 3d6003715020, envp: 3d600086d880
)
docker-981042 [000] ..... 177140.763805: sys_execve(filename: 7fffdae4b580, argv: 3d6003715020, envp: 3d600086d880
可以看到会源源不断在打印log,因为系统在后台也会不停创建新的进程.
刚开始时候设备上没有任何log打印,经过排查,是syscalls对应的tracepoint没打开,需要手动打开,对应的命令如下:
echo 1 > /sys/kernel/tracing/events/syscalls/sys_enter_execve/enable
bpf verifier
运行了上面的例子后,可能会有疑问,是否可以注入一些恶意代码,比如死循环,crash,甚至读取任意内存的逻辑? 如果是这样,那就会给系统的安全性带来致命的危险. 接下来我们看下bpf 是如何避免该问题的.
bpf中有一个安全保护机制verifier,会做如下的校验:
- 指令执行一定会结束, 手段是把注入的指令翻译成DAG ,也就是类似与编译器在语法生成树阶段形成的结构, 然后遍历该图,用来保证指令执行一定会结束, 这也使得bpf 中不能包含循环,如果包含了循环,就会被误认为死循环,拒绝加载.
- 会分析每个指令,确保访问的内存都是安全的
网友评论