gh_mirrors/li/linux内核系统调用过滤:seccomp配置与使用

gh_mirrors/li/linux内核系统调用过滤:seccomp配置与使用

【免费下载链接】linux Linux kernel source tree 【免费下载链接】linux 项目地址: 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_STRICTSECCOMP_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.cmode1_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)程序实现复杂的系统调用过滤逻辑,其内核处理流程如下:

mermaid

关键数据结构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程序仅允许readwriteexitopenat四个系统调用:

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_dataargs字段,可实现基于参数的细粒度控制。以下示例禁止打开某些敏感文件:

// 检查系统调用是否为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 通知工作流程

mermaid

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过滤会带来一定性能开销,可通过以下方法优化:

  1. 利用过滤器缓存:内核自动缓存允许的系统调用(struct action_cache),避免重复执行BPF程序
  2. 合并过滤器规则:减少过滤器链长度,避免过多跳转
  3. 使用TSYNC标志SECCOMP_FILTER_FLAG_TSYNC确保线程同步应用过滤器
  4. 禁用不必要的日志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有望支持更复杂的过滤逻辑和动态策略更新。

关键建议:

  1. 始终遵循最小权限原则,只允许必要的系统调用
  2. 优先使用FILTER模式而非STRICT模式,平衡安全性和功能性
  3. 对容器镜像实施seccomp配置审计,防止特权升级
  4. 监控seccomp拒绝事件,作为入侵检测的早期预警

seccomp虽然强大,但不应作为唯一的安全防线,应与capabilities、namespaces、AppArmor等机制配合使用,构建纵深防御体系。

附录:常用系统调用号参考

系统调用x86_64说明
read0读取文件
write1写入文件
openat257打开文件
execve59执行程序
clone56创建进程
unlink87删除文件
socket41创建套接字
connect42建立连接

【免费下载链接】linux Linux kernel source tree 【免费下载链接】linux 项目地址: https://gitcode.com/GitHub_Trending/li/linux

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值