From 4343fd8e174d3e560ce1de5358356d821f9521b0 Mon Sep 17 00:00:00 2001 From: CH3CHOHCH3 <72899584+CH3CHOHCH3@users.noreply.github.com> Date: Sun, 23 Apr 2023 19:04:49 +0800 Subject: [PATCH] add lsm tutorial (#27) --- src/19-lsm-connect/README.md | 144 +++++++++++++++++++++++++++++++++-- 1 file changed, 136 insertions(+), 8 deletions(-) diff --git a/src/19-lsm-connect/README.md b/src/19-lsm-connect/README.md index 106d9ab..e7b6243 100644 --- a/src/19-lsm-connect/README.md +++ b/src/19-lsm-connect/README.md @@ -2,23 +2,119 @@ ## 背景 -TODO +LSM 从 Linux 2.6 开始成为官方内核的一个安全框架,基于此的安全实现包括 SELinux 和 AppArmor 等。在 Linux 5.7 引入 BPF LSM 后,系统开发人员已经能够自由地实现函数粒度的安全检查能力,本文就提供了这样一个案例:限制通过 socket connect 函数对特定 IPv4 地址进行访问的 BPF LSM 程序。(可见其控制精度是很高的) ## LSM 概述 -TODO +LSM(Linux Security Modules)是 Linux 内核中用于支持各种计算机安全模型的框架。LSM 在 Linux 内核安全相关的关键路径上预置了一批 hook 点,从而实现了内核和安全模块的解耦,使不同的安全模块可以自由地在内核中加载/卸载,无需修改原有的内核代码就可以加入安全检查功能。 + +在过去,使用 LSM 主要通过配置已有的安全模块(如 SELinux 和 AppArmor)或编写自己的内核模块;而在 Linux 5.7 引入 BPF LSM 机制后,一切都变得不同了:现在,开发人员可以通过 eBPF 编写自定义的安全策略,并将其动态加载到内核中的 LSM 挂载点,而无需配置或编写内核模块。 + +现在 LSM 支持的 hook 点包括但不限于: + ++ 对文件的打开、创建、删除和移动等; ++ 文件系统的挂载; ++ 对 task 和 process 的操作; ++ 对 socket 的操作(创建、绑定 socket,发送和接收消息等); + +更多 hook 点可以参考 [lsm_hooks.h](https://github.com/torvalds/linux/blob/master/include/linux/lsm_hooks.h)。 + +## 确认 BPF LSM 是否可用 + +首先,请确认内核版本高于 5.7。接下来,可以通过 + +```console +$ cat /boot/config-$(uname -r) | grep BPF_LSM +CONFIG_BPF_LSM=y +``` + +判断是否内核是否支持 BPF LSM。上述条件都满足的情况下,可以通过 + +```console +$ cat /sys/kernel/security/lsm +ndlock,lockdown,yama,integrity,apparmor +``` + +查看输出是否包含 bpf 选项,如果输出不包含(像上面的例子),可以通过修改 `/etc/default/grub`: + +```conf +GRUB_CMDLINE_LINUX="lsm=ndlock,lockdown,yama,integrity,apparmor,bpf" +``` + +并通过 `update-grub2` 命令更新 grub 配置(不同系统的对应命令可能不同),然后重启系统。 ## 编写 eBPF 程序 -TODO +```C +// lsm-connect.bpf.c +#include "vmlinux.h" +#include +#include +#include + +char LICENSE[] SEC("license") = "GPL"; + +#define EPERM 1 +#define AF_INET 2 + +const __u32 blockme = 16843009; // 1.1.1.1 -> int + +SEC("lsm/socket_connect") +int BPF_PROG(restrict_connect, struct socket *sock, struct sockaddr *address, int addrlen, int ret) +{ + // Satisfying "cannot override a denial" rule + if (ret != 0) + { + return ret; + } + + // Only IPv4 in this example + if (address->sa_family != AF_INET) + { + return 0; + } + + // Cast the address to an IPv4 socket address + struct sockaddr_in *addr = (struct sockaddr_in *)address; + + // Where do you want to go? + __u32 dest = addr->sin_addr.s_addr; + bpf_printk("lsm: found connect to %d", dest); + + if (dest == blockme) + { + bpf_printk("lsm: blocking %d", dest); + return -EPERM; + } + return 0; +} + +``` + +这是一段 C 实现的 eBPF 内核侧代码,它会阻碍所有试图通过 socket 对 1.1.1.1 的连接操作,其中: + ++ `SEC("lsm/socket_connect")` 宏指出该程序期望的挂载点; ++ 程序通过 `BPF_PROG` 宏定义(详情可查看 [tools/lib/bpf/bpf_tracing.h](https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/tools/lib/bpf/bpf_tracing.h)); ++ `restrict_connect` 是 `BPF_PROG` 宏要求的程序名; ++ `ret` 是该挂载点上(潜在的)当前函数之前的 LSM 检查程序的返回值; + +整个程序的思路不难理解: + ++ 首先,若其他安全检查函数返回值不为 0(不通过),则无需检查,直接返回不通过; ++ 接下来,判断是否为 IPV4 的连接请求,并比较试图连接的地址是否为 1.1.1.1; ++ 若请求地址为 1.1.1.1 则拒绝连接,否则允许连接; + +在程序运行期间,所有通过 socket 的连接操作都会被输出到 `/sys/kernel/debug/tracing/trace_pipe`。 ## 编译运行 +通过容器编译: + ```console docker run -it -v `pwd`/:/src/ yunwei37/ebpm:latest ``` -or compile with `ecc`: +或是通过 `ecc` 编译: ```console $ ecc lsm-connect.bpf.c @@ -26,14 +122,46 @@ Compiling bpf object... Packing ebpf object and config into package.json... ``` -Run: +并通过 `ecli` 运行: ```console -sudo ecli run examples/bpftools/lsm-connect/package.json +$ sudo ecli run package.json +``` + +接下来,可以打开另一个 terminal,并尝试访问 1.1.1.1: + +```console +$ ping 1.1.1.1 +ping: connect: Operation not permitted +$ curl 1.1.1.1 +curl: (7) Couldn't connect to server +$ wget 1.1.1.1 +--2023-04-23 08:41:18-- (try: 2) http://1.1.1.1/ +Connecting to 1.1.1.1:80... failed: Operation not permitted. +Retrying. +``` + +同时,我们可以查看 `bpf_printk` 的输出: + +```console +$ sudo cat /sys/kernel/debug/tracing/trace_pipe + ping-7054 [000] d...1 6313.430872: bpf_trace_printk: lsm: found connect to 16843009 + ping-7054 [000] d...1 6313.430874: bpf_trace_printk: lsm: blocking 16843009 + curl-7058 [000] d...1 6316.346582: bpf_trace_printk: lsm: found connect to 16843009 + curl-7058 [000] d...1 6316.346584: bpf_trace_printk: lsm: blocking 16843009 + wget-7061 [000] d...1 6318.800698: bpf_trace_printk: lsm: found connect to 16843009 + wget-7061 [000] d...1 6318.800700: bpf_trace_printk: lsm: blocking 16843009 ``` ## 总结 -TODO +本文介绍了如何使用 BPF LSM 来限制通过 socket 对特定 IPv4 地址的访问。我们可以通过修改 GRUB 配置文件来开启 LSM 的 BPF 挂载点。在 eBPF 程序中,我们通过 `BPF_PROG` 宏定义函数,并通过 `SEC` 宏指定挂载点;在函数实现上,遵循 LSM 安全检查模块中 "cannot override a denial" 的原则,并根据 socket 连接请求的目的地址对该请求进行限制。 -参考: +更多的例子和详细的开发指南,请参考 eunomia-bpf 的官方文档: + +完整的教程和源代码已经全部开源,可以在 中查看。 + +## 参考 + + +