Seccomp、BPF与容器安全 - 教学文档
1. Seccomp简介
Seccomp(全称secure computing mode)是Linux内核支持的一种安全机制,用于限制进程可用的系统调用(system call)。通过Seccomp,我们可以限制程序使用某些系统调用,从而减少系统的暴露面,提高安全性。
1.1 Seccomp发展历史
-
2005年(Linux 2.6.12):引入第一个版本的seccomp,通过
/proc/PID/seccomp接口启用,仅支持严格模式(strict mode),只允许4种系统调用:read()、write()、_exit()和sigreturn() -
2007年(Linux 2.6.23):使用
prctl()操作代替/proc/PID/seccomp接口 -
2012年(Linux 3.5):引入"seccomp mode 2"(过滤模式),使用BPF程序过滤系统调用及其参数
-
2013年(Linux 3.8):在
/proc/PID/status中添加Seccomp字段,可查看进程的seccomp模式状态 -
2014年(Linux 3.17):引入
seccomp()系统调用,提供prctl()的超集功能
2. Seccomp模式
2.1 严格模式(SECCOMP_MODE_STRICT)
只允许4种系统调用:
read()write()_exit()sigreturn()
示例代码:
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/prctl.h>
#include <linux/seccomp.h>
void configure_seccomp() {
printf("Configuring seccomp\n");
prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT);
}
int main(int argc, char *argv[]) {
int infd, outfd;
ssize_t read_bytes;
char buffer[1024];
if(argc < 3) {
printf("Usage:\n\t dup_file <input path> <output_path>\n");
return -1;
}
configure_seccomp(); /* 配置seccomp */
printf("Opening '%s' for reading\n", argv[1]);
if((infd = open(argv[1], O_RDONLY)) > 0) {
/* open()被禁用,进程会在此终止*/
printf("Opening '%s' for writing\n", argv[2]);
if((outfd = open(argv[2], O_WRONLY|O_CREAT, 0644)) > 0) {
while((read_bytes = read(infd, &buffer, 1024)) > 0)
write(outfd, &buffer, (ssize_t)read_bytes);
}
}
close(infd);
close(outfd);
return 0;
}
2.2 过滤模式(SECCOMP_MODE_FILTER)
使用BPF程序过滤系统调用,可以基于系统调用号和参数值进行过滤。
3. BPF(伯克利包过滤器)
BPF最初是为网络数据包过滤设计的,后来被应用到seccomp中。
3.1 BPF特性
- 简单指令集
- 小型指令集
- 所有指令大小相同
- 实现简单、快速
- 只有分支向前指令
- 程序是有向无环图(DAGs),没有循环
- 易于验证程序的有效性/安全性
- 程序限制为4096条指令
3.2 BPF数据结构
struct sock_fprog {
unsigned short len; /* BPF指令的数量 */
struct sock_filter __user *filter; /* 指向BPF数组的指针 */
};
struct sock_filter {
__u16 code; /* 实际过滤代码 */
__u8 jt; /* Jump true */
__u8 jf; /* Jump false */
__u32 k; /* 通用多用途字段 */
};
3.3 BPF指令集
主要指令类型:
- 加载指令
- 存储指令
- 跳转指令
- 算术逻辑指令(ADD、SUB、MUL、DIV、MOD、NEG、OR、AND、XOR、LSH、RSH)
- Return指令
- 条件跳转指令
3.4 BPF编写宏
#define BPF_STMT(code, k) { (unsigned short)(code), 0, 0, k }
#define BPF_JUMP(code, k, jt, jf) { (unsigned short)(code), jt, jf, k }
4. Seccomp + BPF实现
4.1 Seccomp返回值
Seccomp过滤器返回一个32位值:
- 高16位(SECCOMP_RET_ACTION掩码):指定内核应采取的操作
- 低16位(SECCOMP_RET_DATA掩码):与操作关联的数据
返回值定义:
#define SECCOMP_RET_KILL_PROCESS 0x80000000U /* kill the process */
#define SECCOMP_RET_KILL_THREAD 0x00000000U /* kill the thread */
#define SECCOMP_RET_KILL SECCOMP_RET_KILL_THREAD
#define SECCOMP_RET_TRAP 0x00030000U /* disallow and force a SIGSYS */
#define SECCOMP_RET_ERRNO 0x00050000U /* returns an errno */
#define SECCOMP_RET_USER_NOTIF 0x7fc00000U /* notifies userspace */
#define SECCOMP_RET_TRACE 0x7ff00000U /* pass to a tracer or disallow */
#define SECCOMP_RET_LOG 0x7ffc0000U /* allow after logging */
#define SECCOMP_RET_ALLOW 0x7fff0000U /* allow */
4.2 Seccomp输入参数
struct seccomp_data {
int nr; /* 系统调用号 */
__u32 arch; /* 架构 */
__u64 instruction_pointer; /* CPU指令指针 */
__u64 args[6]; /* 系统调用参数(最多6个) */
};
4.3 使用prctl()实现Seccomp-BPF
#include <stdio.h>
#include <sys/prctl.h>
#include <linux/seccomp.h>
#include <linux/filter.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
struct sock_filter filter[] = {
BPF_STMT(BPF_LD + BPF_W + BPF_ABS, 0), // 加载系统调用号
BPF_JUMP(BPF_JMP + BPF_JEQ, 59, 0, 1), // 判断是否为execve(59)
BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_KILL), // 返回KILL
BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW), // 返回ALLOW
};
struct sock_fprog prog = {
.len = (unsigned short)(sizeof(filter)/sizeof(filter[0])),
.filter = filter,
};
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); // 设置NO_NEW_PRIVS
prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog);
write(0, "test\n", 5);
system("/bin/sh");
return 0;
}
4.4 更复杂的过滤示例
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stddef.h>
#include <sys/prctl.h>
#include <linux/seccomp.h>
#include <linux/filter.h>
#include <linux/unistd.h>
void configure_seccomp() {
struct sock_filter filter[] = {
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (offsetof(struct seccomp_data, nr))),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_write, 0, 1),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_open, 0, 3),
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (offsetof(struct seccomp_data, args[1]))),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, O_RDONLY, 0, 1),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL)
};
struct sock_fprog prog = {
.len = (unsigned short)(sizeof(filter)/sizeof(filter[0])),
.filter = filter,
};
printf("Configuring seccomp\n");
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog);
}
5. libseccomp库
libseccomp提供了更高级的API来使用seccomp,无需直接编写BPF规则。
5.1 基本使用
#include <unistd.h>
#include <seccomp.h>
#include <linux/seccomp.h>
int main(void) {
scmp_filter_ctx ctx;
ctx = seccomp_init(SCMP_ACT_ALLOW);
seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(execve), 0);
seccomp_load(ctx);
char *filename = "/bin/sh";
char *argv[] = {"/bin/sh", NULL};
char *envp[] = {NULL};
write(1, "i will give you a shell\n", 24);
syscall(59, filename, argv, envp); //execve
return 0;
}
5.2 参数过滤
#include <unistd.h>
#include <seccomp.h>
#include <linux/seccomp.h>
int main(void) {
scmp_filter_ctx ctx;
ctx = seccomp_init(SCMP_ACT_ALLOW);
seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(write), 1, SCMP_A2(SCMP_CMP_GT, 0x10));
seccomp_load(ctx);
write(1, "1234567812345678", 0x10); // 不被拦截
write(1, "i will give you a shell\n", 24); // 会拦截
return 0;
}
6. Seccomp与容器安全
Docker支持使用seccomp来限制容器的系统调用。
6.1 Docker默认seccomp配置
Docker使用默认的seccomp配置文件(JSON格式),禁用了大约44个系统调用。
6.2 自定义seccomp配置文件
示例:禁止容器创建文件夹
{
"defaultAction": "SCMP_ACT_ALLOW",
"syscalls": [
{
"name": "mkdir",
"action": "SCMP_ACT_ERRNO",
"args": []
}
]
}
使用方式:
docker run --rm -it --security-opt seccomp=seccomp_mkdir.json busybox /bin/sh
6.3 白名单模式
{
"defaultAction": "SCMP_ACT_ERRNO",
"architectures": [
"SCMP_ARCH_X86_64",
"SCMP_ARCH_X86",
"SCMP_ARCH_X32"
],
"syscalls": [
{
"names": [
"arch_prctl",
"sched_yield",
"futex",
"write",
"mmap",
"exit_group",
"madvise",
"rt_sigprocmask",
"getpid",
"gettid",
"tgkill",
"rt_sigaction",
"read",
"getpgrp"
],
"action": "SCMP_ACT_ALLOW",
"args": [],
"comment": "",
"includes": {},
"excludes": {}
}
]
}
7. 工具
7.1 seccomp-tools
用于分析seccomp的开源工具,功能包括:
- Dump:从可执行文件中转储seccomp BPF
- Disasm:将seccomp BPF转换为可读格式
- Asm:编写seccomp规则
- Emu:模拟seccomp规则
安装:
sudo apt install gcc ruby-dev
gem install seccomp-tools
使用示例:
seccomp-tools dump ./simple_syscall_seccomp
7.2 zaz
自动生成JSON格式seccomp文件的工具。
使用示例:
zaz seccomp docker alpine "ping -c5 8.8.8.8" > seccomp_ping.json
7.3 sysdig
系统可见性工具,可用于监控容器运行时使用的系统调用。
安装:
curl -s https://download.sysdig.com/stable/install-sysdig | sudo bash
使用示例:
sysdig -w runc.scap container.name=ping && proc.name=runc
8. 总结
Seccomp是Linux内核提供的重要安全机制,通过限制进程可用的系统调用,可以有效减少系统的暴露面。结合BPF可以实现灵活的系统调用过滤策略。在容器环境中,合理配置seccomp可以显著提高容器的安全性。
关键点:
- Seccomp有两种模式:严格模式和过滤模式
- 过滤模式使用BPF程序进行系统调用过滤
- 可以通过prctl()或libseccomp库配置seccomp
- Docker支持自定义seccomp配置文件
- 使用seccomp-tools等工具可以分析和生成seccomp策略