BruceFan's Blog

Stay hungry, stay foolish

0%

eBPF LSM入门

LSM介绍

LSM从Linux 2.6开始成为官方内核的一个安全框架,基于此的安全实现包括SELinux和AppArmor等。在Linux 5.7引入BPF LSM后,系统开发人员已经能够自由地实现函数粒度的安全检查能力。LSM(Linux Security Modules)是Linux内核中用于支持各种计算机安全模型的框架。LSM在Linux内核安全相关的关键路径上预置了一批hook点,从而实现了内核和安全模块的解耦,使不同的安全模块可以自由地在内核中加载/卸载,无需修改原有的内核代码就可以加入安全检查功能。
现在LSM支持的hook点包括但不限于:

  • 对文件的打开、创建、删除和移动等
  • 文件系统的挂载
  • 对task和process的操作
  • 对socket的操作(创建、绑定socket,发送和接收消息等)

更多hook点可以参考lsm_hook_defs.h
应用场景:

  • 安全监控和审计:实时监控系统调用和敏感操作,记录并分析潜在的安全事件。
  • 访问控制:定义细粒度的访问控制策略,限制不安全的操作。
  • 恶意行为检测:检测和响应恶意软件或异常行为。

确认BPF LSM可用

内核版本需要高于5.7,查看内核配置

1
2
$ cat /boot/config-$(uname -r) | grep BPF_LSM
CONFIG_BPF_LSM=y

查看是否包含bpf

1
2
cat /sys/kernel/security/lsm
lockdown,capability,landlock,yama,bpf

如果输出不包含bpf,可以修改/etc/default/grub

1
GRUB_CMDLINE_LINUX="lsm=lockdown,capability,landlock,yama,bpf"

并通过 update-grub2 命令更新 grub 配置(不同系统的对应命令可能不同),然后重启系统。

编写eBPF程序

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
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>

char LICENSE[] SEC("license") = "GPL";

#define EPERM 1
#define MAX_PATH 256

SEC("lsm/file_open")
int BPF_PROG(restricted_file_open, struct file *file)
{
u32 pid = bpf_get_current_pid_tgid() >> 32;
char filename[MAX_PATH];
if (bpf_d_path(&file->f_path, filename, MAX_PATH) < 0) {
bpf_printk("[LSM] Parse file name failed!");
return 0;
}
char *target = "/home/fanrong/orig";
if (bpf_strncmp(filename, 18, target) == 0) {
bpf_printk("[LSM] PID: %d, Block file: %s open", pid, filename);
return -EPERM;
}
return 0;
}

这里的eBPF LSM程序是用来监控和阻断文件打开操作的,hook点为file_open。
BPF_PROG是libbpf中定义函数的宏,具体含义可见BPF_PROG,简单来说就是一个辅助定义函数的宏,如果不用这个宏,自己定义函数需要定义为func_name(unsigned long long *ctx),然后解析ctx为hook点的参数。现在只需要BPF(func_name, hook点参数)即可。
类似的还有BPF_KPROBE,如果不用这个宏,需要定义func_name(struct pt_regs *ctx),然后解析ctx为hook点的参数。是否使用这些宏主要取决于要eBPF程序类型和hook点的位置,例如前面介绍的XDP就不需要使用。

编写用户态程序

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
#include <stdio.h>
#include <unistd.h>
#include <sys/resource.h>
#include <bpf/libbpf.h>
#include "lsm_file.skel.h"

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_file_bpf *skel;
int err;

/* Set up libbpf errors and debug info callback */
libbpf_set_print(libbpf_print_fn);
/* Open, load, and verify BPF application */
skel = lsm_file_bpf__open_and_load();
if (!skel) {
fprintf(stderr, "Failed to open and load BPF skeleton\n");
goto cleanup;
}
/* Attach lsm handler */
err = lsm_file_bpf__attach(skel);
if (err) {
fprintf(stderr, "Failed to attach BPF skeleton\n");
goto cleanup;
}
printf("Successfully started! Please run `sudo cat /sys/kernel/tracing/trace_pipe` "
"to see output of the BPF programs.\n");

for (;;) {
/* trigger our BPF program */
fprintf(stderr, ".");
sleep(1);
}

cleanup:
lsm_file_bpf__destroy(skel);
return -err;
}

编译运行

将程序放到libbpf-bootstrap中,在Makefile中添加lsm_file选项,执行:

1
2
3
4
5
6
7
8
9
10
$ make lsm_file
BPF .output/lsm_file.bpf.o
GEN-SKEL .output/lsm_file.skel.h
CC .output/lsm_file.o
BINARY lsm_file
$ sudo ./lsm_file
libbpf: map 'lsm_file.rodata': created successfully, fd=3
libbpf: map '.rodata.str1.1': created successfully, fd=4
Successfully started! Please run `sudo cat /sys/kernel/tracing/trace_pipe` to see output of the BPF programs.
.........

另起一个终端,尝试打开文件:

1
2
$ cat orig
cat: orig: Operation not permitted

查看eBPF输出:

1
2
$ sudo cat /sys/kernel/tracing/trace_pipe
cat-2214088 [010] ...11 5469210.568468: bpf_trace_printk: [LSM] PID: 2214088, Block file: /home/fanrong/orig open

可以看到打开文件的操作被阻断了。

reference
https://eunomia.dev/zh/tutorials/19-lsm-connect/
https://github.com/mrtc0/bouheki/blob/master/pkg/bpf/c/restricted-file.bpf.c
https://github.com/libbpf/libbpf-bootstrap/blob/master/examples/c/lsm.bpf.c