gh_mirrors/li/linux内核系统调用过滤:seccomp配置与使用
【免费下载链接】linux Linux kernel source tree 项目地址: https://gitcode.com/GitHub_Trending/li/linux
引言:你还在为容器逃逸、恶意程序滥用系统调用而烦恼吗?
在云原生与容器化时代,系统调用(System Call,系统调用)作为用户态与内核态交互的唯一接口,其安全性直接关系到整个系统的安全防线。2024年CNCERT报告显示,68%的容器逃逸漏洞与未过滤的危险系统调用相关,而Linux内核提供的seccomp(Secure Computing Mode,安全计算模式)机制正是防御此类攻击的关键技术。本文将系统讲解seccomp的工作原理、配置方法及实战案例,帮助你构建坚不可摧的系统调用防火墙。
读完本文你将掌握:
- seccomp两种工作模式的技术原理与安全边界
- BPF过滤器编写规范及内核验证机制
- 从基础白名单到用户态通知的全场景配置方案
- 容器环境中的seccomp最佳实践与性能优化
- 常见配置错误及调试技巧
一、seccomp技术架构与内核实现
1.1 核心数据结构解析
seccomp在内核中的实现围绕struct seccomp_filter结构体展开,该结构定义于kernel/seccomp.c:
struct seccomp_filter {
refcount_t refs; // 引用计数
refcount_t users; // 用户计数
bool log; // 是否记录动作
bool wait_killable_recv; // 通知接收进程是否可被杀死
struct action_cache cache; // 系统调用缓存
struct seccomp_filter *prev; // 指向前一个过滤器(形成链表)
struct bpf_prog *prog; // BPF程序
struct notification *notif; // 用户态通知结构体
struct mutex notify_lock; // 通知锁
wait_queue_head_t wqh; // 等待队列头
};
过滤器采用链表组织,新添加的过滤器会成为链表头部,系统调用检查时会从最新过滤器开始依次执行,形成"策略叠加"效应。这种设计允许进程动态增加安全策略而不影响已有规则。
1.2 工作模式演进
seccomp提供两种工作模式,其能力对比见表1:
| 特性 | SECCOMP_MODE_STRICT | SECCOMP_MODE_FILTER |
|---|---|---|
| 引入版本 | Linux 2.6.12 (2005) | Linux 3.5 (2012) |
| 灵活性 | 固定白名单(仅4个系统调用) | 自定义BPF过滤器 |
| 系统调用参数检查 | 不支持 | 支持 |
| 返回动作类型 | 仅KILL | 支持ALLOW/ERRNO/TRAP等6种 |
| 用户态通知 | 不支持 | 支持 |
| 典型应用 | 沙箱基础限制 | 容器安全、应用加固 |
1.2.1 STRICT模式原理
STRICT模式仅允许4个系统调用:read()、write()、exit()和sigreturn(),其实现位于kernel/seccomp.c的mode1_syscalls数组:
static const int mode1_syscalls[] = {
__NR_seccomp_read,
__NR_seccomp_write,
__NR_seccomp_exit,
__NR_seccomp_sigreturn,
0 /* 终止标记 */
};
当进程进入STRICT模式后,任何不在此列表的系统调用都会触发SIGKILL信号终止进程。这种"最小权限"模型适合对安全性要求极高且功能简单的场景,如某些安全进程的签名进程。
1.2.2 FILTER模式工作流
FILTER模式通过BPF(Berkeley Packet Filter)程序实现复杂的系统调用过滤逻辑,其内核处理流程如下:
关键数据结构struct seccomp_data包含系统调用的完整信息,定义于include/uapi/linux/seccomp.h:
struct seccomp_data {
int nr; // 系统调用号
__u32 arch; // 体系架构(如AUDIT_ARCH_X86_64)
__u64 instruction_pointer; // 指令指针
__u64 args[6]; // 系统调用参数
};
二、seccomp配置实战指南
2.1 基础配置方法
2.1.1 使用prctl系统调用
进程可通过prctl(PR_SET_SECCOMP, ...)启用seccomp,示例代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <sys/prctl.h>
#include <linux/seccomp.h>
int main() {
// 启用STRICT模式
if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT) == -1) {
perror("prctl failed");
exit(EXIT_FAILURE);
}
// 以下调用允许
write(1, "Hello, seccomp!\n", 16);
// 以下调用会触发SIGKILL
printf("This line will never execute\n");
return 0;
}
2.1.2 使用seccomp系统调用
Linux 3.17引入了seccomp(2)系统调用,提供更灵活的配置接口:
#include <linux/seccomp.h>
int seccomp(unsigned int operation, unsigned int flags, void *args);
主要操作码包括:
SECCOMP_SET_MODE_STRICT:设置STRICT模式SECCOMP_SET_MODE_FILTER:设置FILTER模式SECCOMP_GET_ACTION_AVAIL:查询支持的返回动作
2.2 BPF过滤器开发指南
2.2.1 过滤器结构规范
BPF过滤器由一系列struct sock_filter指令组成,每条指令包含操作码、跳转偏移和常量:
struct sock_filter { // 32-bit filter instruction
__u16 code; // Instruction code
__u8 jt; // Jump true
__u8 jf; // Jump false
__u32 k; // Generic multiuse field
};
内核通过seccomp_check_filter()函数验证过滤器合法性(位于kernel/seccomp.c),禁止以下操作:
- 访问超出
struct seccomp_data范围的内存 - 使用未允许的BPF指令(如
BPF_STX等存储指令) - 指令长度超过
BPF_MAXINSNS(4096)
2.2.2 白名单过滤器示例
以下BPF程序仅允许read、write、exit和openat四个系统调用:
struct sock_filter filter[] = {
// 加载系统调用号到A寄存器
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, offsetof(struct seccomp_data, nr)),
// 检查是否为openat(syscall 257)
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 257, 0, 5),
// 检查是否为read(syscall 0)
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 0, 0, 4),
// 检查是否为write(syscall 1)
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 1, 0, 3),
// 检查是否为exit(syscall 60)
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 60, 0, 2),
// 允许调用
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),
// 拒绝其他所有调用
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL_PROCESS),
};
struct sock_fprog prog = {
.len = (unsigned short)(sizeof(filter)/sizeof(filter[0])),
.filter = filter,
};
加载此过滤器的代码:
if (seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_TSYNC, &prog) == -1) {
perror("seccomp");
exit(EXIT_FAILURE);
}
2.2.3 参数过滤高级技巧
通过访问struct seccomp_data的args字段,可实现基于参数的细粒度控制。以下示例禁止打开某些敏感文件:
// 检查系统调用是否为openat
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 257, 0, 9),
// 加载第一个参数(fd)到A寄存器
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, offsetof(struct seccomp_data, args[0])),
// 检查是否为AT_FDCWD(-100)
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, -100, 0, 7),
// 加载第二个参数(pathname)到A寄存器
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, offsetof(struct seccomp_data, args[1])),
// 存储到X寄存器
BPF_STMT(BPF_MISC+BPF_TAX, 0),
// 比较路径名前若干字节是否为敏感路径
BPF_STMT(BPF_LD+BPF_B+BPF_MEM, 0), // 加载第一个字节
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, '/', 0, 5),
BPF_STMT(BPF_LD+BPF_B+BPF_MEM, 1), // 加载第二个字节
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 'e', 0, 4),
// ... 省略中间字节检查 ...
BPF_STMT(BPF_LD+BPF_B+BPF_MEM, 10), // 加载第十一个字节
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 'd', 0, 1),
// 匹配成功,拒绝访问
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ERRNO|EACCES),
// 允许其他调用
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),
2.3 用户态通知机制
seccomp提供用户态通知功能(SECCOMP_RET_USER_NOTIF),允许用户态进程动态裁决系统调用。典型应用场景包括:
- 动态权限管理
- 可疑行为审计
- 复杂策略决策
2.3.1 通知工作流程
2.3.2 实现代码框架
监控进程实现:
int notify_fd = seccomp(SECCOMP_GET_NOTIF_FD, 0, NULL);
if (notify_fd == -1) { /* 错误处理 */ }
struct seccomp_notif req;
struct seccomp_notif_resp resp = {0};
while (1) {
// 等待通知
if (ioctl(notify_fd, SECCOMP_IOCTL_NOTIF_RECV, &req) == -1) {
if (errno == EINTR) continue;
/* 错误处理 */
}
// 决策逻辑
bool allow = false;
if (req.data.nr == __NR_openat) {
char path[256];
// 解析路径(需要配合/proc/pid/mem)
allow = is_safe_path(path);
}
// 发送响应
resp.id = req.id;
resp.error = allow ? 0 : -EPERM;
resp.val = 0;
resp.flags = 0;
if (ioctl(notify_fd, SECCOMP_IOCTL_NOTIF_SEND, &resp) == -1) {
/* 错误处理 */
}
}
三、容器环境中的seccomp最佳实践
3.1 Docker配置方案
Docker自1.10起支持seccomp,默认配置位于default.json,禁用了44个危险系统调用。自定义配置示例:
{
"defaultAction": "SCMP_ACT_ALLOW",
"architectures": ["SCMP_ARCH_X86_64", "SCMP_ARCH_X86", "SCMP_ARCH_X32"],
"syscalls": [
{
"name": "mkdir",
"action": "SCMP_ACT_ERRNO",
"args": []
},
{
"name": "open",
"action": "SCMP_ACT_ALLOW",
"args": [
{
"index": 1,
"value": 2,
"op": "SCMP_CMP_MASKED_EQ"
}
]
}
]
}
使用自定义配置启动容器:
docker run --rm --security-opt seccomp=my_profile.json nginx
3.2 Kubernetes集成
Kubernetes通过securityContext字段配置seccomp:
apiVersion: v1
kind: Pod
metadata:
name: seccomp-demo
spec:
securityContext:
seccompProfile:
type: Localhost
localhostProfile: profiles/restricted.json
containers:
- name: demo
image: nginx
3.3 性能优化策略
seccomp过滤会带来一定性能开销,可通过以下方法优化:
- 利用过滤器缓存:内核自动缓存允许的系统调用(
struct action_cache),避免重复执行BPF程序 - 合并过滤器规则:减少过滤器链长度,避免过多跳转
- 使用TSYNC标志:
SECCOMP_FILTER_FLAG_TSYNC确保线程同步应用过滤器 - 禁用不必要的日志:
SECCOMP_FILTER_FLAG_LOG会显著增加开销
性能测试表明,配置合理的seccomp过滤器对系统调用吞吐量影响小于5%,远低于其他安全机制(如ptrace)。
四、调试与故障排查
4.1 常见问题诊断
| 症状 | 可能原因 | 解决方案 |
|---|---|---|
| 进程立即被杀死 | 系统调用被拒绝 | 检查dmesg中的seccomp日志 |
| 过滤器加载失败 | BPF指令不合法 | 使用bpf-verify工具验证 |
| 部分线程未受限制 | 未使用TSYNC标志 | 添加SECCOMP_FILTER_FLAG_TSYNC |
| 通知机制不工作 | 文件描述符未轮询 | 使用epoll监控通知FD |
4.2 内核日志分析
启用seccomp日志:
echo 1 > /proc/sys/kernel/seccomp/actions_logged
被拒绝的系统调用会记录在dmesg中:
seccomp: process 12345 (nginx) called syscall mkdir(2) which is not allowed
4.3 调试工具推荐
scmp_bpf_disasm:反汇编BPF过滤器strace:跟踪seccomp相关系统调用seccomp-notify:用户态通知调试工具bpftool:查看已加载的BPF程序
五、总结与展望
seccomp作为Linux内核的轻量级系统调用过滤机制,已成为容器安全的基石技术。通过合理配置,可显著降低攻击面,防御各类系统调用滥用漏洞。随着eBPF技术的发展,未来seccomp有望支持更复杂的过滤逻辑和动态策略更新。
关键建议:
- 始终遵循最小权限原则,只允许必要的系统调用
- 优先使用FILTER模式而非STRICT模式,平衡安全性和功能性
- 对容器镜像实施seccomp配置审计,防止特权升级
- 监控seccomp拒绝事件,作为入侵检测的早期预警
seccomp虽然强大,但不应作为唯一的安全防线,应与capabilities、namespaces、AppArmor等机制配合使用,构建纵深防御体系。
附录:常用系统调用号参考
| 系统调用 | x86_64 | 说明 |
|---|---|---|
| read | 0 | 读取文件 |
| write | 1 | 写入文件 |
| openat | 257 | 打开文件 |
| execve | 59 | 执行程序 |
| clone | 56 | 创建进程 |
| unlink | 87 | 删除文件 |
| socket | 41 | 创建套接字 |
| connect | 42 | 建立连接 |
【免费下载链接】linux Linux kernel source tree 项目地址: https://gitcode.com/GitHub_Trending/li/linux
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



