目录
前言
在 限制程序能够使用的系统调用 这篇文章中我描述了使用 seccomp 来限制程序系统调用的技术,虽然能够上手却并不了解它提供的真正功能与实际的应用场景,在本篇文章中探讨下。
seccomp 的全称是什么?
seccomp 全称为 Secure Computing,它能够用来限定程序能够执行的系统调用,这一限定可以用于实现沙盒等安全容器,让程序仅能执行受限的内核功能,强化系统的安全性。
seccomp 的作用范围
seccomp 配置单位为进程(存疑),它在每一个进程的 task_struct 结构中保存配置信息,这个配置信息在进程 fork 的时候会被子进程继承。
在 限制程序能够使用的系统调用 文章中描述的 demo 有如下代码:
int main(int argc, char const *argv[]) {
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
perror("prctl(NO_NEW_PRIVS)");
return 1;
}
install_filter(__NR_write, AUDIT_ARCH_X86_64, EPERM);
return system(argv[1]);
}
此代码在当前进程中注册了一个 seccomp 的 filter 条件后,使用 system 函数创建子进程执行目标程序来限定子进程不能使用 write 系统调用,它能够工作的基础就是 seccomp 配置能够被【子进程继承】。
seccomp 的三种工作模式
SECCOMP_SET_MODE_STRICT
在此种模式下线程只被允许使用 read、 write, _exit (不包含 exit_group )、与 sigreturn 系统调用,使用其它系统调用时内核会发送 SIGKILL 信号杀死线程。
此功能需要开启 CONFIG_SECCOMP 内核配置。
SECCOMP_SET_MODE_FILTER
此模式允许用户基于 BPF 指令编写过滤规则下发到进程中,扩展了过滤规则的描述元素,同时支持过滤任意的系统调用及系统调用使用的参数。
要使用 SECCOMP_SET_MODE_FILTER,调用线程所在的命名空间需要有 CAP_SYS_ADMIN 权限、线程设定了 no_new_privs 标志位。当已经设定的 filter 允许线程使用 prctl、seccomp 系统调用时,可以添加更多的过滤规则。多个过滤规则会增加执行时间,但是能够允许进一步降低线程执行过程中可能遇到的攻击。
要使用 SECCOMP_SET_MODE_FILTER,内核需要开启 CONFIG_SECCOMP_FILTER 配置。
SECCOMP_GET_ACTION_AVAIL
此模式用于查询特定的 action 是否被内核支持。这里的 action 表示 seccomp filter 命中后内核执行的行为。
seccomp bpf filter 的结构与过滤的数据格式
fitler 信息由如下结构体描述:
struct sock_fprog {
unsigned short len; /* Number of BPF instructions */
struct sock_filter *filter; /* Pointer to array of
BPF instructions */
};
len 表示 bpf 指令的数量,filter 指向指令存储的数组。sock_filter 结构如下:
struct sock_filter { /* Filter block */
__u16 code; /* Actual filter code */
__u8 jt; /* Jump true */
__u8 jf; /* Jump false */
__u32 k; /* Generic multiuse field */
};
当执行 bpf 规则过滤时,bpf 指令码基于内核生成的系统调用信息工作,此信息的定义如下:
struct seccomp_data {
int nr; /* System call number */
__u32 arch; /* AUDIT_ARCH_* value
(see <linux/audit.h>) */
__u64 instruction_pointer; /* CPU instruction pointer */
__u64 args[6]; /* Up to 6 system call arguments */
};
这个结构表明 seccomp filter 功能不仅支持过滤指定的系统调用,也能过滤传递特定参数的系统调用。
seccomp filter 命中后不同的 action
seccomp 支持在 filter 规则命中后设定不同的 action,action 按照优先级降低的顺序排列如下:
- SECCOMP_RET_KILL_PROCESS
- SECCOMP_RET_KILL_THREAD
- SECCOMP_RET_TRAP
- SECCOMP_RET_ERRNO
- SECCOMP_RET_TRACE
- SECCOMP_RET_LOG
- SECCOMP_RET_ALLOW
当命中多个 action 时,优先级最高的 action 被触发。
seccomp 的使用场景
- 进程沙箱
- 监控特定进程执行的系统调用
- 过滤进程执行的系统调用
- 限定进程能够执行的系统调用
linux 发行版中有在使用 seccomp 吗?
在 debian11 发行版中使用 【ftrace】 跟踪 __seccomp_filter
函数的调用栈帧,得到了如下信息:
=> 0xffffffffc0c94083
=> __seccomp_filter
=> syscall_trace_enter.constprop.0
=> do_syscall_64
=> entry_SYSCALL_64_after_hwframe
Timer-11769 [000] ..... 25307.796774: __seccomp_filter <-syscall_trace_enter.constprop.0
Timer-11769 [000] ..... 25307.796775: <stack trace>
=> 0xffffffffc0c94083
=> __seccomp_filter
=> syscall_trace_enter.constprop.0
=> do_syscall_64
=> entry_SYSCALL_64_after_hwframe
Web Content-11763 [003] ..... 25307.796776: __seccomp_filter <-syscall_trace_enter.constprop.0
Web Content-11763 [003] ..... 25307.796777: <stack trace>
11763 在系统中对应的进程信息如下:
root@debian:/sys/kernel/debug/tracing# ps aux | grep 11763
longyu 11763 7.2 4.7 3105732 371780 ? Sl 07:56 3:26 /usr/lib/firefox-esr/firefox-esr -contentproc -childID 18 -isForBrowser -prefsLen 8791 -prefMapSize 246122 -jsInit 285636 -parentBuildID 20220623065118 -appdir /usr/lib/firefox-esr/browser 2093 true tab
可以看到该进程为火狐浏览器创建的某个进程,表明 seccomp 技术有一定的用户。
使用 seccomp 让程序无法加载内核模块
曾经在某次面试的时候被问到如何让程序无法加载内核模块,当时就想到了可以使用 seccomp 来搞,其实可以编写一条过滤 init_module、finit_module 的 seccomp filter,并将 action 设置为不允许访问来完成目标。
写到这里想到其实上面的描述遗漏了一个关键问题:seccomp filter 要加载到哪个进程中?
init?systemd?
其实可以编写一个 pam 模块来添加 filter 并在 /etc/pam.d 中用户的登录配置中增加一条配置来实现。
libseccomp 库
在 上手 seccomp 这篇文章中,示例程序使用如下代码设定一个 filter:
static int install_filter(int nr, int arch, int error) {
struct sock_filter filter[] = {
BPF_STMT(BPF_LD + BPF_W + BPF_ABS, (offsetof(struct seccomp_data, arch))),
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arch, 0, 3),
BPF_STMT(BPF_LD + BPF_W + BPF_ABS, (offsetof(struct seccomp_data, nr))),
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, nr, 0, 1),
BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ERRNO | (error & SECCOMP_RET_DATA)),
BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW),
};
struct sock_fprog prog = {
.len = (unsigned short)(sizeof(filter) / sizeof(filter[0])),
.filter = filter,
};
if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) {
perror("prctl(PR_SET_SECCOMP)");
return 1;
}
return 0;
}
上述代码直接使用宏封装的 bpf 指令码编写,代码逻辑难以理解。实际发行版中有提供一个 libseccomp 的三方库,它提供了一些对用户更友好的 api,可以使用这个库来实现 seccomp filter 规则。
seccomp bpf 支持的架构
- x86-64, i386, x32 (since Linux 3.5)
- ARM (since Linux 3.8)
- s390 (since Linux 3.8)
- MIPS (since Linux 3.16)
- ARM-64 (since Linux 3.19)
- PowerPC (since Linux 4.3)
- Tile (since Linux 4.3)
- PA-RISC (since Linux 4.6)
seccomp bpf filter 的两种运行模式
- jit
- vm
这两种方式隐含在 bpf 框架中,对 seccomp 不可见。