运行eBPF程序
LSM(Linux Security Modules)是一种Linux内核安全框架,它在内核处理系统调用的敏感路径(例如文件访问、网络连接、加载模块等)上提供静态的钩子函数,允许安全模块(如SELinux、AppArmor、eBPF LSM程序等)在关键决策点介入,从而实现访问控制、审计和策略等功能。Linux支持的LSM钩子可查询lsm_hook.h文件。例如与BPF系统调用相关的LSM钩子如下。

要使用LSM特性,需要首先检查系统内核版本高于5.7,且确认当前运行的内核是否编译了对BPF LSM的支持,图示结果即为支持。
1
|
grep CONFIG_BPF_LSM /boot/config-$(uname -r)
|

随后检查内核加载并启用了哪些LSM框架,图示结果表明为启动BPF LSM框架。
1
|
cat /sys/kernel/security/lsm
|

此时必须将LSM BPF添加到内核配置参数中来手动启用它。
1
2
3
4
5
6
|
sudo vim /etc/default/grub
# 在GRUB_CMDLINE_LINUX中添加
lsm=lockdown,capability,landlock,yama,apparmor,ima,evm,bpf
# 添加完成,退出VIM
sudo update-grub
sudo reboot
|

本示例演示的是bpf()系统调用的拦截,运行该eBPF程序。
例如bpftool涉及使用bpf()系统调用,可以观察到该工具已被禁止。

eBPF程序:lsm.bpf.c
eBPF程序将通过BPF_MAP_TYPE_RINGBUF向用户态进程传递事件数据,因此需要定义事件数据的格式,本示例使用so_event结构体封装了源IP地址、目的IP地址、端口号、IP协议等数据。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
char LICENSE[] SEC("license") = "GPL";
#define EPERM 1
// 指示eBPF程序lsm_bpf挂载到LSM钩子上,拦截对bpf()的调用
// 参数从cmd、attr、size为bpf()函数从参数
// ret表示前一个挂载在同一钩子上的LSM程序返回值,非0值表示已经有别的程序拒绝了该调用
SEC("lsm/bpf")
int BPF_PROG(lsm_bpf, int cmd, union bpf_attr *attr, unsigned int size, int ret)
{
// 如果之前的LSM程序已经阻止了该调用,则当前程序不做修改
if (ret != 0)
return ret;
// 打印信息
bpf_printk("LSM: block bpf() worked");
// 返回非0值,拒绝bpf()系统调用
// 返回值需要使用Linux内核错误码(errno)规范中的通用错误码
// -EPERM(1)表示操作不允许,拒绝访问
// -EACCES(13)表示权限被拒绝
// -EINVAL(22)表示参数无效
return -EPERM;
}
|
用户态程序:lsm.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
#include <stdio.h>
#include <unistd.h>
#include <sys/resource.h>
#include <bpf/libbpf.h>
#include "lsm.skel.h"
// libbpf的日志回调函数
static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
{
return vfprintf(stderr, format, args);
}
int main(int argc, char **argv)
{
struct lsm_bpf *skel;
int err;
// 设置libbpf的日志回调函数
libbpf_set_print(libbpf_print_fn);
// 加载eBPF程序
skel = lsm_bpf__open_and_load();
if (!skel) {
fprintf(stderr, "Failed to open and load BPF skeleton\n");
goto cleanup;
}
// 附加LSM eBPF程序到内核LSM钩子上
err = lsm_bpf__attach(skel);
if (err) {
fprintf(stderr, "Failed to attach BPF skeleton\n");
goto cleanup;
}
printf("Successfully started! Please run `sudo cat /sys/kernel/debug/tracing/trace_pipe` "
"to see output of the BPF programs.\n");
// 进入循环以保持程序运行状态
for (;;) {
fprintf(stderr, ".");
sleep(1);
}
cleanup:
// 资源清理
lsm_bpf__destroy(skel);
return -err;
}
|